index.js 14 KB

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