Skip to content

Instantly share code, notes, and snippets.

@abihf
Created October 7, 2020 20:32
Show Gist options
  • Save abihf/6d5b7e4245d0fe2810c2db2eb4f3f352 to your computer and use it in GitHub Desktop.
Save abihf/6d5b7e4245d0fe2810c2db2eb4f3f352 to your computer and use it in GitHub Desktop.
Fast path router supporting glob
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