1 var isArray = Array.isArray || function (arr) {
2 return Object.prototype.toString.call(arr) == '[object Array]';
6 * Expose `pathToRegexp`.
8 // module.exports = pathToRegexp
11 * The main path matching regexp utility.
15 var PATH_REGEXP = new RegExp([
16 // Match escaped characters that would otherwise appear in future matches.
17 // This allows the user to escape special characters that won't transform.
19 // Match Express-style parameters and un-named parameters with a prefix
20 // and optional suffixes. Matches appear as:
22 // "/:test(\\d+)?" => ["/", "test", "\d+", undefined, "?"]
23 // "/route(\\d+)" => [undefined, undefined, undefined, "\d+", undefined]
24 '([\\/.])?(?:\\:(\\w+)(?:\\(((?:\\\\.|[^)])*)\\))?|\\(((?:\\\\.|[^)])*)\\))([+*?])?',
25 // Match regexp special characters that are always escaped.
26 '([.+*?=^!:${}()[\\]|\\/])'
30 * Escape the capturing group by escaping special characters and meaning.
32 * @param {String} group
35 function escapeGroup (group) {
36 return group.replace(/([=!:$\/()])/g, '\\$1');
40 * Attach the keys as a property of the regexp.
46 function attachKeys (re, keys) {
52 * Get the flags for a regexp from the options.
54 * @param {Object} options
57 function flags (options) {
58 return options.sensitive ? '' : 'i';
62 * Pull out keys from a regexp.
64 * @param {RegExp} path
68 function regexpToRegexp (path, keys) {
69 // Use a negative lookahead to match only capturing groups.
70 var groups = path.source.match(/\((?!\?)/g);
73 for (var i = 0; i < groups.length; i++) {
83 return attachKeys(path, keys);
87 * Transform an array into a regexp.
91 * @param {Object} options
94 function arrayToRegexp (path, keys, options) {
97 for (var i = 0; i < path.length; i++) {
98 parts.push(pathToRegexp(path[i], keys, options).source);
101 var regexp = new RegExp('(?:' + parts.join('|') + ')', flags(options));
102 return attachKeys(regexp, keys);
106 * Replace the specific tags with regexp strings.
108 * @param {String} path
109 * @param {Array} keys
112 function replacePath (path, keys) {
115 function replace (_, escaped, prefix, key, capture, group, suffix, escape) {
121 return '\\' + escape;
124 var repeat = suffix === '+' || suffix === '*';
125 var optional = suffix === '?' || suffix === '*';
128 name: key || index++,
129 delimiter: prefix || '/',
134 prefix = prefix ? ('\\' + prefix) : '';
135 capture = escapeGroup(capture || group || '[^' + (prefix || '\\/') + ']+?');
138 capture = capture + '(?:' + prefix + capture + ')*';
142 return '(?:' + prefix + '(' + capture + '))?';
145 // Basic parameter support.
146 return prefix + '(' + capture + ')';
149 return path.replace(PATH_REGEXP, replace);
153 * Normalize the given path string, returning a regular expression.
155 * An empty array can be passed in for the keys, which will hold the
156 * placeholder key descriptions. For example, using `/user/:id`, `keys` will
157 * contain `[{ name: 'id', delimiter: '/', optional: false, repeat: false }]`.
159 * @param {(String|RegExp|Array)} path
160 * @param {Array} [keys]
161 * @param {Object} [options]
164 function pathToRegexp (path, keys, options) {
167 if (!isArray(keys)) {
170 } else if (!options) {
174 if (path instanceof RegExp) {
175 return regexpToRegexp(path, keys, options);
179 return arrayToRegexp(path, keys, options);
182 var strict = options.strict;
183 var end = options.end !== false;
184 var route = replacePath(path, keys);
185 var endsWithSlash = path.charAt(path.length - 1) === '/';
187 // In non-strict mode we allow a slash at the end of match. If the path to
188 // match already ends with a slash, we remove it for consistency. The slash
189 // is valid at the end of a path match, not in the middle. This is important
190 // in non-ending mode, where "/test/" shouldn't match "/test//route".
192 route = (endsWithSlash ? route.slice(0, -2) : route) + '(?:\\/(?=$))?';
198 // In non-ending mode, we need the capturing groups to match as much as
199 // possible by using a positive lookahead to the end or next path segment.
200 route += strict && endsWithSlash ? '' : '(?=\\/|$)';
203 return attachKeys(new RegExp('^' + route, flags(options)), keys);