|
1 | 1 | import type { Node } from './ast.ts';
|
2 | 2 | import { parse } from './parse/parse.ts';
|
3 | 3 |
|
| 4 | +type Params = Record<string, string | undefined>; |
| 5 | +type Constraint = (url: URL) => Params | null; |
| 6 | + |
4 | 7 | export function createMatcher(sources: Array<string>) {
|
5 | 8 | const patterns = sources.map((source) => {
|
6 | 9 | const { protocol, hostname, pathname } = parse(source);
|
7 | 10 |
|
8 |
| - const regexs = { |
9 |
| - protocol: protocol === undefined ? /.*/ : toRegExp(protocol), |
10 |
| - hostname: hostname === undefined ? /.*/ : toRegExp(hostname, /[^.]*/), |
11 |
| - pathname: pathname === undefined ? /^\/$/ : toRegExp(pathname, /[^/]*/), |
12 |
| - }; |
13 |
| - return { pattern: source, regexs }; |
| 11 | + const protocolRE = protocol === undefined ? /^.*$/ : toRegExp(protocol); |
| 12 | + const hostnameRE = hostname === undefined ? /^.*$/ : toRegExp(hostname, { param: /[^.]*/ }); |
| 13 | + const pathnameRE = pathname === undefined ? /^\/$/ : toRegExp(pathname, { param: /[^/]*/ }); |
| 14 | + |
| 15 | + const constraints: Array<Constraint> = [ |
| 16 | + (url) => (protocolRE.test(url.protocol) ? {} : null), |
| 17 | + (url) => { |
| 18 | + const match = hostnameRE.exec(url.hostname); |
| 19 | + if (!match) return null; |
| 20 | + return match.groups ?? {}; |
| 21 | + }, |
| 22 | + (url) => { |
| 23 | + const match = pathnameRE.exec(url.pathname.slice(1)); |
| 24 | + if (!match) return null; |
| 25 | + return match.groups ?? {}; |
| 26 | + }, |
| 27 | + ]; |
| 28 | + |
| 29 | + return { pattern: source, constraints }; |
14 | 30 | });
|
15 | 31 |
|
16 | 32 | const match = function (url: string | URL) {
|
| 33 | + if (typeof url === 'string') url = new URL(url); |
| 34 | + |
17 | 35 | const matches: Array<{ pattern: string; params: Record<string, string | undefined> }> = [];
|
18 |
| - if (typeof url === 'string') { |
19 |
| - url = new URL(url); |
20 |
| - } |
21 |
| - for (const { pattern, regexs } of patterns) { |
22 |
| - const protocol = regexs.protocol.exec(url.protocol); |
23 |
| - const hostname = regexs.hostname.exec(url.hostname); |
24 |
| - const pathname = regexs.pathname.exec(url.pathname); |
25 |
| - if (protocol === null || hostname === null || pathname === null) { |
26 |
| - continue; |
| 36 | + for (const { pattern, constraints } of patterns) { |
| 37 | + let isMatch = true; |
| 38 | + const params: Params = {}; |
| 39 | + for (const constraint of constraints) { |
| 40 | + const result = constraint(url); |
| 41 | + if (result === null) { |
| 42 | + isMatch = false; |
| 43 | + break; |
| 44 | + } |
| 45 | + Object.assign(params, result); |
27 | 46 | }
|
28 |
| - matches.push({ pattern, params: { ...hostname.groups, ...pathname.groups } }); |
| 47 | + if (!isMatch) continue; |
| 48 | + matches.push({ pattern, params }); |
29 | 49 | }
|
30 | 50 | return matches;
|
31 | 51 | };
|
32 | 52 | return { match };
|
33 | 53 | }
|
34 | 54 |
|
35 |
| -export function toRegExp(part: Array<Node>, paramRegExp?: RegExp) { |
36 |
| - const source = toRegExpSource(part, paramRegExp); |
37 |
| - return new RegExp(source); |
| 55 | +export function toRegExp(part: Array<Node>, options?: { param: RegExp }) { |
| 56 | + const source = toRegExpSource(part, options?.param); |
| 57 | + return new RegExp('^' + source + '$'); |
38 | 58 | }
|
39 | 59 |
|
40 | 60 | function escape(text: string): string {
|
|
0 commit comments