204 lines
5 KiB
JavaScript
204 lines
5 KiB
JavaScript
var isArray = Array.isArray || function (arr) {
|
|
return Object.prototype.toString.call(arr) == '[object Array]';
|
|
};
|
|
|
|
/**
|
|
* Expose `pathToRegexp`.
|
|
*/
|
|
// module.exports = pathToRegexp
|
|
|
|
/**
|
|
* 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, "?"]
|
|
// "/route(\\d+)" => [undefined, undefined, undefined, "\d+", undefined]
|
|
'([\\/.])?(?:\\:(\\w+)(?:\\(((?:\\\\.|[^)])*)\\))?|\\(((?:\\\\.|[^)])*)\\))([+*?])?',
|
|
// Match regexp special characters that are always escaped.
|
|
'([.+*?=^!:${}()[\\]|\\/])'
|
|
].join('|'), 'g');
|
|
|
|
/**
|
|
* 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,
|
|
delimiter: null,
|
|
optional: false,
|
|
repeat: false
|
|
});
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
/**
|
|
* Replace the specific tags with regexp strings.
|
|
*
|
|
* @param {String} path
|
|
* @param {Array} keys
|
|
* @return {String}
|
|
*/
|
|
function replacePath (path, keys) {
|
|
var index = 0;
|
|
|
|
function replace (_, escaped, prefix, key, capture, group, suffix, escape) {
|
|
if (escaped) {
|
|
return escaped;
|
|
}
|
|
|
|
if (escape) {
|
|
return '\\' + escape;
|
|
}
|
|
|
|
var repeat = suffix === '+' || suffix === '*';
|
|
var optional = suffix === '?' || suffix === '*';
|
|
|
|
keys.push({
|
|
name: key || index++,
|
|
delimiter: prefix || '/',
|
|
optional: optional,
|
|
repeat: repeat
|
|
});
|
|
|
|
prefix = prefix ? ('\\' + prefix) : '';
|
|
capture = escapeGroup(capture || group || '[^' + (prefix || '\\/') + ']+?');
|
|
|
|
if (repeat) {
|
|
capture = capture + '(?:' + prefix + capture + ')*';
|
|
}
|
|
|
|
if (optional) {
|
|
return '(?:' + prefix + '(' + capture + '))?';
|
|
}
|
|
|
|
// Basic parameter support.
|
|
return prefix + '(' + capture + ')';
|
|
}
|
|
|
|
return path.replace(PATH_REGEXP, replace);
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
|
|
var strict = options.strict;
|
|
var end = options.end !== false;
|
|
var route = replacePath(path, keys);
|
|
var endsWithSlash = path.charAt(path.length - 1) === '/';
|
|
|
|
// 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 attachKeys(new RegExp('^' + route, flags(options)), keys);
|
|
}
|