UNPKG

3.05 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 } else {
38 node = trie.nodes[route]
39 }
40
41 // we must recurse deeper
42 return createNode(index + 1, node, routes)
43 })(0, this.trie, routes)
44}
45
46// match a route on the trie
47// and return the node
48// str -> obj
49Trie.prototype.match = function (route) {
50 assert.equal(typeof route, 'string', 'route should be a string')
51
52 const routes = route.replace(/^\//, '').split('/')
53 const params = {}
54
55 var node = (function search (index, trie) {
56 // either there's no match, or we're done searching
57 if (trie === undefined) return undefined
58 const route = routes[index]
59 if (route === undefined) return trie
60
61 if (trie.nodes[route]) {
62 // match regular routes first
63 return search(index + 1, trie.nodes[route])
64 } else if (trie.name) {
65 // match named routes
66 params[trie.name] = route
67 return search(index + 1, trie.nodes[':'])
68 } else {
69 // no matches found
70 return search(index + 1)
71 }
72 })(0, this.trie)
73
74 if (!node) return undefined
75 node = xtend(node)
76 node.params = params
77 return node
78}
79
80// mount a trie onto a node at route
81// (str, obj) -> null
82Trie.prototype.mount = function (route, trie) {
83 assert.equal(typeof route, 'string', 'route should be a string')
84 assert.equal(typeof trie, 'object', 'trie should be a object')
85
86 const split = route.replace(/^\//, '').split('/')
87 var node = null
88 var key = null
89
90 if (split.length === 1) {
91 key = split[0]
92 node = this.create(key)
93 } else {
94 const headArr = split.splice(0, split.length - 1)
95 const head = headArr.join('/')
96 key = split[0]
97 node = this.create(head)
98 }
99
100 mutate(node.nodes, trie.nodes)
101 if (trie.name) node.name = trie.name
102
103 // delegate properties from '/' to the new node
104 // '/' cannot be reached once mounted
105 if (node.nodes['']) {
106 Object.keys(node.nodes['']).forEach(function (key) {
107 if (key === 'nodes') return
108 node[key] = node.nodes[''][key]
109 })
110 mutate(node.nodes, node.nodes[''].nodes)
111 delete node.nodes[''].nodes
112 }
113}