ad70e10eb4a85b0b8e31ae581a1ff9b4a9d2d1869329b3e9fbed9e17cc131d402f050866526726e02a46118b0f2a210e2d233198c0ba02dd34a90d15552446 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  1. /**
  2. * Tokenize input string.
  3. */
  4. function lexer(str) {
  5. var tokens = [];
  6. var i = 0;
  7. while (i < str.length) {
  8. var char = str[i];
  9. if (char === "*" || char === "+" || char === "?") {
  10. tokens.push({ type: "MODIFIER", index: i, value: str[i++] });
  11. continue;
  12. }
  13. if (char === "\\") {
  14. tokens.push({ type: "ESCAPED_CHAR", index: i++, value: str[i++] });
  15. continue;
  16. }
  17. if (char === "{") {
  18. tokens.push({ type: "OPEN", index: i, value: str[i++] });
  19. continue;
  20. }
  21. if (char === "}") {
  22. tokens.push({ type: "CLOSE", index: i, value: str[i++] });
  23. continue;
  24. }
  25. if (char === ":") {
  26. var name = "";
  27. var j = i + 1;
  28. while (j < str.length) {
  29. var code = str.charCodeAt(j);
  30. if (
  31. // `0-9`
  32. (code >= 48 && code <= 57) ||
  33. // `A-Z`
  34. (code >= 65 && code <= 90) ||
  35. // `a-z`
  36. (code >= 97 && code <= 122) ||
  37. // `_`
  38. code === 95) {
  39. name += str[j++];
  40. continue;
  41. }
  42. break;
  43. }
  44. if (!name)
  45. throw new TypeError("Missing parameter name at ".concat(i));
  46. tokens.push({ type: "NAME", index: i, value: name });
  47. i = j;
  48. continue;
  49. }
  50. if (char === "(") {
  51. var count = 1;
  52. var pattern = "";
  53. var j = i + 1;
  54. if (str[j] === "?") {
  55. throw new TypeError("Pattern cannot start with \"?\" at ".concat(j));
  56. }
  57. while (j < str.length) {
  58. if (str[j] === "\\") {
  59. pattern += str[j++] + str[j++];
  60. continue;
  61. }
  62. if (str[j] === ")") {
  63. count--;
  64. if (count === 0) {
  65. j++;
  66. break;
  67. }
  68. }
  69. else if (str[j] === "(") {
  70. count++;
  71. if (str[j + 1] !== "?") {
  72. throw new TypeError("Capturing groups are not allowed at ".concat(j));
  73. }
  74. }
  75. pattern += str[j++];
  76. }
  77. if (count)
  78. throw new TypeError("Unbalanced pattern at ".concat(i));
  79. if (!pattern)
  80. throw new TypeError("Missing pattern at ".concat(i));
  81. tokens.push({ type: "PATTERN", index: i, value: pattern });
  82. i = j;
  83. continue;
  84. }
  85. tokens.push({ type: "CHAR", index: i, value: str[i++] });
  86. }
  87. tokens.push({ type: "END", index: i, value: "" });
  88. return tokens;
  89. }
  90. /**
  91. * Parse a string for the raw tokens.
  92. */
  93. export function parse(str, options) {
  94. if (options === void 0) { options = {}; }
  95. var tokens = lexer(str);
  96. var _a = options.prefixes, prefixes = _a === void 0 ? "./" : _a, _b = options.delimiter, delimiter = _b === void 0 ? "/#?" : _b;
  97. var result = [];
  98. var key = 0;
  99. var i = 0;
  100. var path = "";
  101. var tryConsume = function (type) {
  102. if (i < tokens.length && tokens[i].type === type)
  103. return tokens[i++].value;
  104. };
  105. var mustConsume = function (type) {
  106. var value = tryConsume(type);
  107. if (value !== undefined)
  108. return value;
  109. var _a = tokens[i], nextType = _a.type, index = _a.index;
  110. throw new TypeError("Unexpected ".concat(nextType, " at ").concat(index, ", expected ").concat(type));
  111. };
  112. var consumeText = function () {
  113. var result = "";
  114. var value;
  115. while ((value = tryConsume("CHAR") || tryConsume("ESCAPED_CHAR"))) {
  116. result += value;
  117. }
  118. return result;
  119. };
  120. var isSafe = function (value) {
  121. for (var _i = 0, delimiter_1 = delimiter; _i < delimiter_1.length; _i++) {
  122. var char = delimiter_1[_i];
  123. if (value.indexOf(char) > -1)
  124. return true;
  125. }
  126. return false;
  127. };
  128. var safePattern = function (prefix) {
  129. var prev = result[result.length - 1];
  130. var prevText = prefix || (prev && typeof prev === "string" ? prev : "");
  131. if (prev && !prevText) {
  132. throw new TypeError("Must have text between two parameters, missing text after \"".concat(prev.name, "\""));
  133. }
  134. if (!prevText || isSafe(prevText))
  135. return "[^".concat(escapeString(delimiter), "]+?");
  136. return "(?:(?!".concat(escapeString(prevText), ")[^").concat(escapeString(delimiter), "])+?");
  137. };
  138. while (i < tokens.length) {
  139. var char = tryConsume("CHAR");
  140. var name = tryConsume("NAME");
  141. var pattern = tryConsume("PATTERN");
  142. if (name || pattern) {
  143. var prefix = char || "";
  144. if (prefixes.indexOf(prefix) === -1) {
  145. path += prefix;
  146. prefix = "";
  147. }
  148. if (path) {
  149. result.push(path);
  150. path = "";
  151. }
  152. result.push({
  153. name: name || key++,
  154. prefix: prefix,
  155. suffix: "",
  156. pattern: pattern || safePattern(prefix),
  157. modifier: tryConsume("MODIFIER") || "",
  158. });
  159. continue;
  160. }
  161. var value = char || tryConsume("ESCAPED_CHAR");
  162. if (value) {
  163. path += value;
  164. continue;
  165. }
  166. if (path) {
  167. result.push(path);
  168. path = "";
  169. }
  170. var open = tryConsume("OPEN");
  171. if (open) {
  172. var prefix = consumeText();
  173. var name_1 = tryConsume("NAME") || "";
  174. var pattern_1 = tryConsume("PATTERN") || "";
  175. var suffix = consumeText();
  176. mustConsume("CLOSE");
  177. result.push({
  178. name: name_1 || (pattern_1 ? key++ : ""),
  179. pattern: name_1 && !pattern_1 ? safePattern(prefix) : pattern_1,
  180. prefix: prefix,
  181. suffix: suffix,
  182. modifier: tryConsume("MODIFIER") || "",
  183. });
  184. continue;
  185. }
  186. mustConsume("END");
  187. }
  188. return result;
  189. }
  190. /**
  191. * Compile a string to a template function for the path.
  192. */
  193. export function compile(str, options) {
  194. return tokensToFunction(parse(str, options), options);
  195. }
  196. /**
  197. * Expose a method for transforming tokens into the path function.
  198. */
  199. export function tokensToFunction(tokens, options) {
  200. if (options === void 0) { options = {}; }
  201. var reFlags = flags(options);
  202. var _a = options.encode, encode = _a === void 0 ? function (x) { return x; } : _a, _b = options.validate, validate = _b === void 0 ? true : _b;
  203. // Compile all the tokens into regexps.
  204. var matches = tokens.map(function (token) {
  205. if (typeof token === "object") {
  206. return new RegExp("^(?:".concat(token.pattern, ")$"), reFlags);
  207. }
  208. });
  209. return function (data) {
  210. var path = "";
  211. for (var i = 0; i < tokens.length; i++) {
  212. var token = tokens[i];
  213. if (typeof token === "string") {
  214. path += token;
  215. continue;
  216. }
  217. var value = data ? data[token.name] : undefined;
  218. var optional = token.modifier === "?" || token.modifier === "*";
  219. var repeat = token.modifier === "*" || token.modifier === "+";
  220. if (Array.isArray(value)) {
  221. if (!repeat) {
  222. throw new TypeError("Expected \"".concat(token.name, "\" to not repeat, but got an array"));
  223. }
  224. if (value.length === 0) {
  225. if (optional)
  226. continue;
  227. throw new TypeError("Expected \"".concat(token.name, "\" to not be empty"));
  228. }
  229. for (var j = 0; j < value.length; j++) {
  230. var segment = encode(value[j], token);
  231. if (validate && !matches[i].test(segment)) {
  232. throw new TypeError("Expected all \"".concat(token.name, "\" to match \"").concat(token.pattern, "\", but got \"").concat(segment, "\""));
  233. }
  234. path += token.prefix + segment + token.suffix;
  235. }
  236. continue;
  237. }
  238. if (typeof value === "string" || typeof value === "number") {
  239. var segment = encode(String(value), token);
  240. if (validate && !matches[i].test(segment)) {
  241. throw new TypeError("Expected \"".concat(token.name, "\" to match \"").concat(token.pattern, "\", but got \"").concat(segment, "\""));
  242. }
  243. path += token.prefix + segment + token.suffix;
  244. continue;
  245. }
  246. if (optional)
  247. continue;
  248. var typeOfMessage = repeat ? "an array" : "a string";
  249. throw new TypeError("Expected \"".concat(token.name, "\" to be ").concat(typeOfMessage));
  250. }
  251. return path;
  252. };
  253. }
  254. /**
  255. * Create path match function from `path-to-regexp` spec.
  256. */
  257. export function match(str, options) {
  258. var keys = [];
  259. var re = pathToRegexp(str, keys, options);
  260. return regexpToFunction(re, keys, options);
  261. }
  262. /**
  263. * Create a path match function from `path-to-regexp` output.
  264. */
  265. export function regexpToFunction(re, keys, options) {
  266. if (options === void 0) { options = {}; }
  267. var _a = options.decode, decode = _a === void 0 ? function (x) { return x; } : _a;
  268. return function (pathname) {
  269. var m = re.exec(pathname);
  270. if (!m)
  271. return false;
  272. var path = m[0], index = m.index;
  273. var params = Object.create(null);
  274. var _loop_1 = function (i) {
  275. if (m[i] === undefined)
  276. return "continue";
  277. var key = keys[i - 1];
  278. if (key.modifier === "*" || key.modifier === "+") {
  279. params[key.name] = m[i].split(key.prefix + key.suffix).map(function (value) {
  280. return decode(value, key);
  281. });
  282. }
  283. else {
  284. params[key.name] = decode(m[i], key);
  285. }
  286. };
  287. for (var i = 1; i < m.length; i++) {
  288. _loop_1(i);
  289. }
  290. return { path: path, index: index, params: params };
  291. };
  292. }
  293. /**
  294. * Escape a regular expression string.
  295. */
  296. function escapeString(str) {
  297. return str.replace(/([.+*?=^!:${}()[\]|/\\])/g, "\\$1");
  298. }
  299. /**
  300. * Get the flags for a regexp from the options.
  301. */
  302. function flags(options) {
  303. return options && options.sensitive ? "" : "i";
  304. }
  305. /**
  306. * Pull out keys from a regexp.
  307. */
  308. function regexpToRegexp(path, keys) {
  309. if (!keys)
  310. return path;
  311. var groupsRegex = /\((?:\?<(.*?)>)?(?!\?)/g;
  312. var index = 0;
  313. var execResult = groupsRegex.exec(path.source);
  314. while (execResult) {
  315. keys.push({
  316. // Use parenthesized substring match if available, index otherwise
  317. name: execResult[1] || index++,
  318. prefix: "",
  319. suffix: "",
  320. modifier: "",
  321. pattern: "",
  322. });
  323. execResult = groupsRegex.exec(path.source);
  324. }
  325. return path;
  326. }
  327. /**
  328. * Transform an array into a regexp.
  329. */
  330. function arrayToRegexp(paths, keys, options) {
  331. var parts = paths.map(function (path) { return pathToRegexp(path, keys, options).source; });
  332. return new RegExp("(?:".concat(parts.join("|"), ")"), flags(options));
  333. }
  334. /**
  335. * Create a path regexp from string input.
  336. */
  337. function stringToRegexp(path, keys, options) {
  338. return tokensToRegexp(parse(path, options), keys, options);
  339. }
  340. /**
  341. * Expose a function for taking tokens and returning a RegExp.
  342. */
  343. export function tokensToRegexp(tokens, keys, options) {
  344. if (options === void 0) { options = {}; }
  345. var _a = options.strict, strict = _a === void 0 ? false : _a, _b = options.start, start = _b === void 0 ? true : _b, _c = options.end, end = _c === void 0 ? true : _c, _d = options.encode, encode = _d === void 0 ? function (x) { return x; } : _d, _e = options.delimiter, delimiter = _e === void 0 ? "/#?" : _e, _f = options.endsWith, endsWith = _f === void 0 ? "" : _f;
  346. var endsWithRe = "[".concat(escapeString(endsWith), "]|$");
  347. var delimiterRe = "[".concat(escapeString(delimiter), "]");
  348. var route = start ? "^" : "";
  349. // Iterate over the tokens and create our regexp string.
  350. for (var _i = 0, tokens_1 = tokens; _i < tokens_1.length; _i++) {
  351. var token = tokens_1[_i];
  352. if (typeof token === "string") {
  353. route += escapeString(encode(token));
  354. }
  355. else {
  356. var prefix = escapeString(encode(token.prefix));
  357. var suffix = escapeString(encode(token.suffix));
  358. if (token.pattern) {
  359. if (keys)
  360. keys.push(token);
  361. if (prefix || suffix) {
  362. if (token.modifier === "+" || token.modifier === "*") {
  363. var mod = token.modifier === "*" ? "?" : "";
  364. route += "(?:".concat(prefix, "((?:").concat(token.pattern, ")(?:").concat(suffix).concat(prefix, "(?:").concat(token.pattern, "))*)").concat(suffix, ")").concat(mod);
  365. }
  366. else {
  367. route += "(?:".concat(prefix, "(").concat(token.pattern, ")").concat(suffix, ")").concat(token.modifier);
  368. }
  369. }
  370. else {
  371. if (token.modifier === "+" || token.modifier === "*") {
  372. throw new TypeError("Can not repeat \"".concat(token.name, "\" without a prefix and suffix"));
  373. }
  374. route += "(".concat(token.pattern, ")").concat(token.modifier);
  375. }
  376. }
  377. else {
  378. route += "(?:".concat(prefix).concat(suffix, ")").concat(token.modifier);
  379. }
  380. }
  381. }
  382. if (end) {
  383. if (!strict)
  384. route += "".concat(delimiterRe, "?");
  385. route += !options.endsWith ? "$" : "(?=".concat(endsWithRe, ")");
  386. }
  387. else {
  388. var endToken = tokens[tokens.length - 1];
  389. var isEndDelimited = typeof endToken === "string"
  390. ? delimiterRe.indexOf(endToken[endToken.length - 1]) > -1
  391. : endToken === undefined;
  392. if (!strict) {
  393. route += "(?:".concat(delimiterRe, "(?=").concat(endsWithRe, "))?");
  394. }
  395. if (!isEndDelimited) {
  396. route += "(?=".concat(delimiterRe, "|").concat(endsWithRe, ")");
  397. }
  398. }
  399. return new RegExp(route, flags(options));
  400. }
  401. /**
  402. * Normalize the given path string, returning a regular expression.
  403. *
  404. * An empty array can be passed in for the keys, which will hold the
  405. * placeholder key descriptions. For example, using `/user/:id`, `keys` will
  406. * contain `[{ name: 'id', delimiter: '/', optional: false, repeat: false }]`.
  407. */
  408. export function pathToRegexp(path, keys, options) {
  409. if (path instanceof RegExp)
  410. return regexpToRegexp(path, keys);
  411. if (Array.isArray(path))
  412. return arrayToRegexp(path, keys, options);
  413. return stringToRegexp(path, keys, options);
  414. }
  415. //# sourceMappingURL=index.js.map