index.js 5.0 KB

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