205 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			205 lines
		
	
	
		
			5.0 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);
 | |
| }
 | 
