UNPKG

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