UNPKG

5.75 kBJavaScriptView Raw
1
2/**
3 * Expose `pathToRegexp`.
4 */
5module.exports = pathToRegexp;
6
7// module.exports = function( path ) {
8// path = path.replace( /\:\w+\/?/g, function( val ) {
9// var suffix = '';
10// if( val.length - 1 === val.lastIndexOf( '\/' ) ) {
11// suffix = '\\/'
12// }
13// return '(\\w*)' + suffix;
14// } );
15// path = path.replace( /\//, '\\/' );
16// console.log( new RegExp( "^"+ path +".*$", "i" ) );
17// return new RegExp( "^"+ path +"(?:$|\/\\w*)$", "i" );
18// };
19
20/**
21 * The main path matching regexp utility.
22 *
23 * @type {RegExp}
24 */
25var PATH_REGEXP = new RegExp([
26 // Match escaped characters that would otherwise appear in future matches.
27 // This allows the user to escape special characters that won't transform.
28 '(\\\\.)',
29 // Match Express-style parameters and un-named parameters with a prefix
30 // and optional suffixes. Matches appear as:
31 //
32 // "/:test(\\d+)?" => ["/", "test", "\d+", undefined, "?"]
33 // "/route(\\d+)" => [undefined, undefined, undefined, "\d+", undefined]
34 '([\\/.])?(?:\\:(\\w+)(?:\\(((?:\\\\.|[^)])*)\\))?|\\(((?:\\\\.|[^)])*)\\))([+*?])?',
35 // Match regexp special characters that are always escaped.
36 '([.+*?=^!:${}()[\\]|\\/])'
37].join('|'), 'g');
38
39/**
40 * Escape the capturing group by escaping special characters and meaning.
41 *
42 * @param {String} group
43 * @return {String}
44 */
45function escapeGroup (group) {
46 return group.replace(/([=!:$\/()])/g, '\\$1');
47}
48
49/**
50 * Attach the keys as a property of the regexp.
51 *
52 * @param {RegExp} re
53 * @param {Array} keys
54 * @return {RegExp}
55 */
56function attachKeys (re, keys) {
57 re.keys = keys;
58 return re;
59}
60
61/**
62 * Get the flags for a regexp from the options.
63 *
64 * @param {Object} options
65 * @return {String}
66 */
67function flags (options) {
68 return options.sensitive ? '' : 'i';
69}
70
71/**
72 * Pull out keys from a regexp.
73 *
74 * @param {RegExp} path
75 * @param {Array} keys
76 * @return {RegExp}
77 */
78function regexpToRegexp (path, keys) {
79 // Use a negative lookahead to match only capturing groups.
80 var groups = path.source.match(/\((?!\?)/g);
81
82 if (groups) {
83 for (var i = 0; i < groups.length; i++) {
84 keys.push({
85 name: i,
86 delimiter: null,
87 optional: false,
88 repeat: false
89 });
90 }
91 }
92
93 return attachKeys(path, keys);
94}
95
96/**
97 * Transform an array into a regexp.
98 *
99 * @param {Array} path
100 * @param {Array} keys
101 * @param {Object} options
102 * @return {RegExp}
103 */
104function arrayToRegexp (path, keys, options) {
105 var parts = [];
106
107 for (var i = 0; i < path.length; i++) {
108 parts.push(pathToRegexp(path[i], keys, options).source);
109 }
110
111 var regexp = new RegExp('(?:' + parts.join('|') + ')', flags(options));
112 return attachKeys(regexp, keys);
113}
114
115/**
116 * Replace the specific tags with regexp strings.
117 *
118 * @param {String} path
119 * @param {Array} keys
120 * @return {String}
121 */
122function replacePath (path, keys) {
123 var index = 0;
124
125 if( '/' === path ) {
126 path = '';
127 }
128
129 path += '__regexp__';
130
131 function replace (_, escaped, prefix, key, capture, group, suffix, escape) {
132 if (escaped) {
133 return escaped;
134 }
135
136 if (escape) {
137 var pf = '\/' === escape ? '\\' : '';
138 return pf + escape;
139 }
140
141 var repeat = suffix === '+' || suffix === '*';
142 var optional = suffix === '?' || suffix === '*';
143
144 keys.push({
145 name: key || index++,
146 delimiter: prefix || '/',
147 optional: optional,
148 repeat: repeat
149 });
150
151 prefix = prefix ? ('\\' + prefix) : '';
152 capture = escapeGroup(capture || group || '[^' + (prefix || '\\/') + ']*');
153
154 if (repeat) {
155 capture = capture + '(?:' + prefix + capture + ')*';
156 }
157
158 if (optional) {
159 return '(?:' + prefix + '(' + capture + '))?';
160 }
161
162 // Basic parameter support.
163 return prefix + '(' + capture + ')';
164 }
165
166 // path = path.replace( /\//g, '\\/' );
167
168 path = path.replace( PATH_REGEXP, replace );
169 return path.replace( '__regexp__', '(?:$|\\/\\S*)' );
170
171 // return path.replace(PATH_REGEXP, replace);
172}
173
174/**
175 * Normalize the given path string, returning a regular expression.
176 *
177 * An empty array can be passed in for the keys, which will hold the
178 * placeholder key descriptions. For example, using `/user/:id`, `keys` will
179 * contain `[{ name: 'id', delimiter: '/', optional: false, repeat: false }]`.
180 *
181 * @param {(String|RegExp|Array)} path
182 * @param {Array} [keys]
183 * @param {Object} [options]
184 * @return {RegExp}
185 */
186function pathToRegexp (path, keys, options) {
187 keys = keys || [];
188
189 if (Array.isArray(keys)) {
190 options = keys;
191 keys = [];
192 } else if (!options) {
193 options = {};
194 }
195
196 if (path instanceof RegExp) {
197 return regexpToRegexp(path, keys, options);
198 }
199
200 if (Array.isArray(path)) {
201 return arrayToRegexp(path, keys, options);
202 }
203
204 var strict = options.strict;
205 var end = options.end !== false;
206 var route = replacePath(path, keys);
207 var endsWithSlash = false;
208 // var endsWithSlash = path.charAt(path.length - 1) === '/';
209
210 // In non-strict mode we allow a slash at the end of match. If the path to
211 // match already ends with a slash, we remove it for consistency. The slash
212 // is valid at the end of a path match, not in the middle. This is important
213 // in non-ending mode, where "/test/" shouldn't match "/test//route".
214 if (!strict) {
215 route = (endsWithSlash ? route.slice(0, -2) : route) + '(?:\\/(?=$))?';
216 }
217
218
219 if (end) {
220 route += '$';
221 } else {
222 // In non-ending mode, we need the capturing groups to match as much as
223 // possible by using a positive lookahead to the end or next path segment.
224 route += strict && endsWithSlash ? '' : '(?=\\/|$)';
225 }
226 return attachKeys(new RegExp('^' + route, flags(options)), keys);
227}