UNPKG

14.4 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3/**
4 * Tokenize input string.
5 */
6function 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 */
95function 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}
176exports.parse = parse;
177/**
178 * Compile a string to a template function for the path.
179 */
180function compile(str, options) {
181 return tokensToFunction(parse(str, options), options);
182}
183exports.compile = compile;
184/**
185 * Expose a method for transforming tokens into the path function.
186 */
187function 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}
242exports.tokensToFunction = tokensToFunction;
243/**
244 * Create path match function from `path-to-regexp` spec.
245 */
246function match(str, options) {
247 var keys = [];
248 var re = pathToRegexp(str, keys, options);
249 return regexpToFunction(re, keys, options);
250}
251exports.match = match;
252/**
253 * Create a path match function from `path-to-regexp` output.
254 */
255function 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}
284exports.regexpToFunction = regexpToFunction;
285/**
286 * Escape a regular expression string.
287 */
288function escapeString(str) {
289 return str.replace(/([.+*?=^!:${}()[\]|/\\])/g, "\\$1");
290}
291/**
292 * Get the flags for a regexp from the options.
293 */
294function flags(options) {
295 return options && options.sensitive ? "" : "i";
296}
297/**
298 * Pull out keys from a regexp.
299 */
300function 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 */
321function 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 */
328function 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 */
334function 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}
390exports.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 */
398function 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}
405exports.pathToRegexp = pathToRegexp;
406//# sourceMappingURL=index.js.map
\No newline at end of file