UNPKG

3 kBJavaScriptView Raw
1const mutate = require('xtend/mutable')
2const assert = require('assert')
3const xtend = require('xtend')
4
5module.exports = Trie
6
7// create a new trie
8// null -> obj
9function Trie () {
10 if (!(this instanceof Trie)) return new Trie()
11 this.trie = { nodes: {} }
12}
13
14// create a node on the trie at route
15// and return a node
16// str -> null
17Trie.prototype.create = function (route) {
18 assert.equal(typeof route, 'string', 'route should be a string')
19 // strip leading '/' and split routes
20 const routes = route.replace(/^\//, '').split('/')
21 return (function createNode (index, trie, routes) {
22 const route = routes[index]
23
24 if (route === undefined) return trie
25
26 var node = null
27 if (/^:/.test(route)) {
28 // if node is a name match, set name and append to ':' node
29 if (!trie.nodes[':']) {
30 node = { nodes: {} }
31 trie.nodes[':'] = node
32 }
33 trie.name = route.replace(/^:/, '')
34 } else if (!trie.nodes[route]) {
35 node = { nodes: {} }
36 trie.nodes[route] = node
37 }
38
39 // we must recurse deeper
40 return createNode(index + 1, node, routes)
41 })(0, this.trie, routes)
42}
43
44// match a route on the trie
45// and return the node
46// str -> obj
47Trie.prototype.match = function (route) {
48 assert.equal(typeof route, 'string', 'route should be a string')
49
50 const routes = route.replace(/^\//, '').split('/')
51 const params = {}
52
53 var node = (function search (index, trie) {
54 // either there's no match, or we're done searching
55 if (trie === undefined) return undefined
56 const route = routes[index]
57 if (route === undefined) return trie
58
59 if (trie.nodes[route]) {
60 // match regular routes first
61 return search(index + 1, trie.nodes[route])
62 } else if (trie.name) {
63 // match named routes
64 params[trie.name] = route
65 return search(index + 1, trie.nodes[':'])
66 } else {
67 // no matches found
68 return search(index + 1)
69 }
70 })(0, this.trie)
71
72 if (!node) return undefined
73 node = xtend(node)
74 node.params = params
75 return node
76}
77
78// mount a trie onto a node at route
79// (str, obj) -> null
80Trie.prototype.mount = function (route, trie) {
81 assert.equal(typeof route, 'string', 'route should be a string')
82 assert.equal(typeof trie, 'object', 'trie should be a object')
83
84 const split = route.replace(/^\//, '').split('/')
85 var node = null
86 var key = null
87
88 if (split.length === 1) {
89 key = split[0]
90 node = this.create(key)
91 } else {
92 const headArr = split.splice(0, split.length - 1)
93 const head = headArr.join('/')
94 key = split[0]
95 node = this.create(head)
96 }
97
98 mutate(node.nodes, trie.nodes)
99 if (trie.name) node.name = trie.name
100
101 // delegate properties from '/' to the new node
102 // '/' cannot be reached once mounted
103 if (node.nodes['']) {
104 Object.keys(node.nodes['']).forEach(function (key) {
105 if (key === 'nodes') return
106 node[key] = node.nodes[''][key]
107 })
108 mutate(node.nodes, node.nodes[''].nodes)
109 delete node.nodes[''].nodes
110 }
111}