Created
October 7, 2020 20:32
-
-
Save abihf/6d5b7e4245d0fe2810c2db2eb4f3f352 to your computer and use it in GitHub Desktop.
Fast path router supporting glob
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
type MatcherNode<T> = { | |
children: Record<string, MatcherNode<T>>; | |
glob?: Array<MatcherNode<T>>; | |
glob2?: Array<MatcherNode<T>>; | |
value?: T; | |
}; | |
// sorry, I exercise here. | |
class GlobMatcher<T> { | |
root: MatcherNode<T>; | |
constructor() { | |
this.root = this._createNode(); | |
} | |
private _splitPath(path: string) { | |
return path.replace(/^\/*(.*)\/*$/, '$1').split('/'); | |
} | |
private _createNode(): MatcherNode<T> { | |
return { children: {} }; | |
} | |
add(path: string, value: T) { | |
return this._add(this.root, this._splitPath(path), value); | |
} | |
private _add(node: MatcherNode<T>, pathParts: string[], value: T): void { | |
if (pathParts.length === 0) { | |
node.value = value; | |
return; | |
} | |
// parth = path part | |
const [firstParth, ...restParth] = pathParts; | |
if (firstParth === '') return this._add(node, restParth, value); // ignore double slash | |
if (firstParth === '*') { | |
const globNode = this._createNode(); | |
if (!node.glob) node.glob = []; | |
node.glob.push(globNode); | |
return this._add(globNode, restParth, value); | |
} | |
if (firstParth === '**') { | |
const globNode = this._createNode(); | |
if (!node.glob2) node.glob2 = []; | |
node.glob2.push(globNode); | |
return this._add(globNode, restParth, value); | |
} | |
if (!node.children[firstParth]) { | |
node.children[firstParth] = this._createNode(); | |
} | |
return this._add(node.children[firstParth], restParth, value); | |
} | |
get(path: string): T | undefined { | |
return this._get(this.root, this._splitPath(path)); | |
} | |
private _get(node: MatcherNode<T>, pathParts: string[]) { | |
if (pathParts.length === 0) return node.value; | |
// parth = path part | |
const [firstParth, ...restParth] = pathParts; | |
if (firstParth === '') return this._get(node, restParth); | |
let value: T; | |
if (firstParth in node.children) { | |
value = this._get(node.children[firstParth], restParth); | |
} | |
if (!value && node.glob) { | |
for (const globNode of node.glob) { | |
value = this._get(globNode, restParth); | |
if (value) return value; | |
} | |
} | |
if (!value && node.glob2) { | |
for (const globNode of node.glob2) { | |
// support zero match ** | |
value = this._get(globNode, pathParts); | |
if (value) return value; | |
const recursiveNode = { ...globNode }; | |
recursiveNode.glob2 = [globNode]; // this is why it named recursiveNode | |
value = this._get(recursiveNode, restParth); | |
if (value) return value; | |
} | |
} | |
return value ?? node.value; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment