Files
vanillajs-seed/node_modules/path-to-regexp/index.js
2019-04-12 20:01:07 +02:00

391 lines
9.1 KiB
JavaScript

var isarray = require('isarray')
/**
* Expose `pathToRegexp`.
*/
module.exports = pathToRegexp
module.exports.parse = parse
module.exports.compile = compile
module.exports.tokensToFunction = tokensToFunction
module.exports.tokensToRegExp = tokensToRegExp
/**
* The main path matching regexp utility.
*
* @type {RegExp}
*/
var PATH_REGEXP = new RegExp([
// Match escaped characters that would otherwise appear in future matches.
// This allows the user to escape special characters that won't transform.
'(\\\\.)',
// Match Express-style parameters and un-named parameters with a prefix
// and optional suffixes. Matches appear as:
//
// "/:test(\\d+)?" => ["/", "test", "\d+", undefined, "?", undefined]
// "/route(\\d+)" => [undefined, undefined, undefined, "\d+", undefined, undefined]
// "/*" => ["/", undefined, undefined, undefined, undefined, "*"]
'([\\/.])?(?:(?:\\:(\\w+)(?:\\(((?:\\\\.|[^()])+)\\))?|\\(((?:\\\\.|[^()])+)\\))([+*?])?|(\\*))'
].join('|'), 'g')
/**
* Parse a string for the raw tokens.
*
* @param {String} str
* @return {Array}
*/
function parse (str) {
var tokens = []
var key = 0
var index = 0
var path = ''
var res
while ((res = PATH_REGEXP.exec(str)) != null) {
var m = res[0]
var escaped = res[1]
var offset = res.index
path += str.slice(index, offset)
index = offset + m.length
// Ignore already escaped sequences.
if (escaped) {
path += escaped[1]
continue
}
// Push the current path onto the tokens.
if (path) {
tokens.push(path)
path = ''
}
var prefix = res[2]
var name = res[3]
var capture = res[4]
var group = res[5]
var suffix = res[6]
var asterisk = res[7]
var repeat = suffix === '+' || suffix === '*'
var optional = suffix === '?' || suffix === '*'
var delimiter = prefix || '/'
var pattern = capture || group || (asterisk ? '.*' : '[^' + delimiter + ']+?')
tokens.push({
name: name || key++,
prefix: prefix || '',
delimiter: delimiter,
optional: optional,
repeat: repeat,
pattern: escapeGroup(pattern)
})
}
// Match any characters still remaining.
if (index < str.length) {
path += str.substr(index)
}
// If the path exists, push it onto the end.
if (path) {
tokens.push(path)
}
return tokens
}
/**
* Compile a string to a template function for the path.
*
* @param {String} str
* @return {Function}
*/
function compile (str) {
return tokensToFunction(parse(str))
}
/**
* Expose a method for transforming tokens into the path function.
*/
function tokensToFunction (tokens) {
// Compile all the tokens into regexps.
var matches = new Array(tokens.length)
// Compile all the patterns before compilation.
for (var i = 0; i < tokens.length; i++) {
if (typeof tokens[i] === 'object') {
matches[i] = new RegExp('^' + tokens[i].pattern + '$')
}
}
return function (obj) {
var path = ''
var data = obj || {}
for (var i = 0; i < tokens.length; i++) {
var token = tokens[i]
if (typeof token === 'string') {
path += token
continue
}
var value = data[token.name]
var segment
if (value == null) {
if (token.optional) {
continue
} else {
throw new TypeError('Expected "' + token.name + '" to be defined')
}
}
if (isarray(value)) {
if (!token.repeat) {
throw new TypeError('Expected "' + token.name + '" to not repeat, but received "' + value + '"')
}
if (value.length === 0) {
if (token.optional) {
continue
} else {
throw new TypeError('Expected "' + token.name + '" to not be empty')
}
}
for (var j = 0; j < value.length; j++) {
segment = encodeURIComponent(value[j])
if (!matches[i].test(segment)) {
throw new TypeError('Expected all "' + token.name + '" to match "' + token.pattern + '", but received "' + segment + '"')
}
path += (j === 0 ? token.prefix : token.delimiter) + segment
}
continue
}
segment = encodeURIComponent(value)
if (!matches[i].test(segment)) {
throw new TypeError('Expected "' + token.name + '" to match "' + token.pattern + '", but received "' + segment + '"')
}
path += token.prefix + segment
}
return path
}
}
/**
* Escape a regular expression string.
*
* @param {String} str
* @return {String}
*/
function escapeString (str) {
return str.replace(/([.+*?=^!:${}()[\]|\/])/g, '\\$1')
}
/**
* Escape the capturing group by escaping special characters and meaning.
*
* @param {String} group
* @return {String}
*/
function escapeGroup (group) {
return group.replace(/([=!:$\/()])/g, '\\$1')
}
/**
* Attach the keys as a property of the regexp.
*
* @param {RegExp} re
* @param {Array} keys
* @return {RegExp}
*/
function attachKeys (re, keys) {
re.keys = keys
return re
}
/**
* Get the flags for a regexp from the options.
*
* @param {Object} options
* @return {String}
*/
function flags (options) {
return options.sensitive ? '' : 'i'
}
/**
* Pull out keys from a regexp.
*
* @param {RegExp} path
* @param {Array} keys
* @return {RegExp}
*/
function regexpToRegexp (path, keys) {
// Use a negative lookahead to match only capturing groups.
var groups = path.source.match(/\((?!\?)/g)
if (groups) {
for (var i = 0; i < groups.length; i++) {
keys.push({
name: i,
prefix: null,
delimiter: null,
optional: false,
repeat: false,
pattern: null
})
}
}
return attachKeys(path, keys)
}
/**
* Transform an array into a regexp.
*
* @param {Array} path
* @param {Array} keys
* @param {Object} options
* @return {RegExp}
*/
function arrayToRegexp (path, keys, options) {
var parts = []
for (var i = 0; i < path.length; i++) {
parts.push(pathToRegexp(path[i], keys, options).source)
}
var regexp = new RegExp('(?:' + parts.join('|') + ')', flags(options))
return attachKeys(regexp, keys)
}
/**
* Create a path regexp from string input.
*
* @param {String} path
* @param {Array} keys
* @param {Object} options
* @return {RegExp}
*/
function stringToRegexp (path, keys, options) {
var tokens = parse(path)
var re = tokensToRegExp(tokens, options)
// Attach keys back to the regexp.
for (var i = 0; i < tokens.length; i++) {
if (typeof tokens[i] !== 'string') {
keys.push(tokens[i])
}
}
return attachKeys(re, keys)
}
/**
* Expose a function for taking tokens and returning a RegExp.
*
* @param {Array} tokens
* @param {Array} keys
* @param {Object} options
* @return {RegExp}
*/
function tokensToRegExp (tokens, options) {
options = options || {}
var strict = options.strict
var end = options.end !== false
var route = ''
var lastToken = tokens[tokens.length - 1]
var endsWithSlash = typeof lastToken === 'string' && /\/$/.test(lastToken)
// Iterate over the tokens and create our regexp string.
for (var i = 0; i < tokens.length; i++) {
var token = tokens[i]
if (typeof token === 'string') {
route += escapeString(token)
} else {
var prefix = escapeString(token.prefix)
var capture = token.pattern
if (token.repeat) {
capture += '(?:' + prefix + capture + ')*'
}
if (token.optional) {
if (prefix) {
capture = '(?:' + prefix + '(' + capture + '))?'
} else {
capture = '(' + capture + ')?'
}
} else {
capture = prefix + '(' + capture + ')'
}
route += capture
}
}
// In non-strict mode we allow a slash at the end of match. If the path to
// match already ends with a slash, we remove it for consistency. The slash
// is valid at the end of a path match, not in the middle. This is important
// in non-ending mode, where "/test/" shouldn't match "/test//route".
if (!strict) {
route = (endsWithSlash ? route.slice(0, -2) : route) + '(?:\\/(?=$))?'
}
if (end) {
route += '$'
} else {
// In non-ending mode, we need the capturing groups to match as much as
// possible by using a positive lookahead to the end or next path segment.
route += strict && endsWithSlash ? '' : '(?=\\/|$)'
}
return new RegExp('^' + route, flags(options))
}
/**
* Normalize the given path string, returning a regular expression.
*
* An empty array can be passed in for the keys, which will hold the
* placeholder key descriptions. For example, using `/user/:id`, `keys` will
* contain `[{ name: 'id', delimiter: '/', optional: false, repeat: false }]`.
*
* @param {(String|RegExp|Array)} path
* @param {Array} [keys]
* @param {Object} [options]
* @return {RegExp}
*/
function pathToRegexp (path, keys, options) {
keys = keys || []
if (!isarray(keys)) {
options = keys
keys = []
} else if (!options) {
options = {}
}
if (path instanceof RegExp) {
return regexpToRegexp(path, keys, options)
}
if (isarray(path)) {
return arrayToRegexp(path, keys, options)
}
return stringToRegexp(path, keys, options)
}