1 |
|
2 |
|
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 |
|
32 | (code >= 48 && code <= 57) ||
|
33 |
|
34 | (code >= 65 && code <= 90) ||
|
35 |
|
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 |
|
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;
|
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 |
|
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 |
|
176 |
|
177 | export function compile(str, options) {
|
178 | return tokensToFunction(parse(str, options), options);
|
179 | }
|
180 |
|
181 |
|
182 |
|
183 | export 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 |
|
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 |
|
240 |
|
241 | export function match(str, options) {
|
242 | var keys = [];
|
243 | var re = pathToRegexp(str, keys, options);
|
244 | return regexpToFunction(re, keys, options);
|
245 | }
|
246 |
|
247 |
|
248 |
|
249 | export 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 |
|
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 |
|
280 |
|
281 | function escapeString(str) {
|
282 | return str.replace(/([.+*?=^!:${}()[\]|/\\])/g, "\\$1");
|
283 | }
|
284 |
|
285 |
|
286 |
|
287 | function flags(options) {
|
288 | return options && options.sensitive ? "" : "i";
|
289 | }
|
290 |
|
291 |
|
292 |
|
293 | function regexpToRegexp(path, keys) {
|
294 | if (!keys)
|
295 | return path;
|
296 |
|
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 |
|
313 |
|
314 | function 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 |
|
320 |
|
321 | function stringToRegexp(path, keys, options) {
|
322 | return tokensToRegexp(parse(path, options), keys, options);
|
323 | }
|
324 |
|
325 |
|
326 |
|
327 | export 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 |
|
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 | :
|
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 |
|
385 |
|
386 |
|
387 |
|
388 |
|
389 |
|
390 | export 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 |
|
\ | No newline at end of file |