core/router/utils/parseUrl.js

import pathToRegexp from 'path-to-regexp'
import { segmentize, beautifyPath, slugFromPath } from '@/core/utils/strings'

/**
 * extracts meta data from urls (queries, hash, host, et al)
 * @memberof RouterUtils
 * @function parseUri
 *
 * @property {String} url - the url to parse
 *
 * @return {Object}
 */
export function parseUri(str) {
	const o = parseUri.options
	const m = o.parser[o.strictMode ? 'strict' : 'loose'].exec(str)
	const uri = {}
	let i = 14

	while (i--) uri[o.key[i]] = m[i] || '' // eslint-disable-line

	uri[o.q.name] = {}
	uri[o.key[12]].replace(o.q.parser, ($0, $1, $2) => {
		if ($1) uri[o.q.name][$1] = $2
	})

	return uri
}

parseUri.options = {
	strictMode: false,
	key: [
		'source',
		'protocol',
		'authority',
		'userInfo',
		'user',
		'password',
		'host',
		'port',
		'relative',
		'path',
		'directory',
		'file',
		'query',
		'anchor'
	],
	q: {
		name: 'queryKey',
		parser: /(?:^|&)([^&=]*)=?([^&]*)/g
	},
	parser: {
		strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
		loose: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/
	}
}

function decodeParam(param) {
	try {
		return decodeURIComponent(param)
	} catch (_) {
		// eslint-disable-next-line no-console
		console.error('decodeParam error')
	}
}

/**
 *
 * @memberof RouterUtils
 * @function createPathObject
 * @property {Object} options - pathToRegexp options
 *
 * @return {Function}
 */
function createPathObject(options = {}) {
	return function(path) {
		const keys = []
		const re = pathToRegexp(path, keys, options)

		return function(pathname, params = {}) {
			const m = re.exec(pathname)
			if (!m) return false
			const output = params

			let key
			let param
			for (let i = 0; i < keys.length; i += 1) {
				key = keys[i]
				param = m[i + 1]
				// eslint-disable-next-line no-continue
				if (!param) continue
				output[key.name] = decodeParam(param)
				if (key.repeat) output[key.name] = params[key.name].split(key.delimiter)
			}

			return output
		}
	}
}

/**
 *
 * @memberof RouterUtils
 * @function matchRoute
 * @return {Function}
 *
 */
export const matchRoute = createPathObject({
	// path-to-regexp options
	sensitive: false,
	strict: false,
	end: false
})

/**
 *
 * @memberof RouterUtils
 * @function parseUrl
 * @property {String} href - the url to parse
 * @return {Object}
 *
 */
export const parseUrl = href => {
	const { anchor: hash, host, path, queryKey, source } = parseUri(href)
	return {
		isRoot: path === '/',
		path: beautifyPath(path),
		raw: path,
		segments: segmentize(path),
		slug: slugFromPath(path),
		hash,
		host,
		source,
		query: queryKey
	}
}