UNPKG

3.73 kBJavaScriptView Raw
1var mutate = require('xtend/mutable')
2var assert = require('assert')
3var 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 var routes = route.replace(/^\//, '').split('/')
21
22 function createNode (index, trie) {
23 var thisRoute = (routes.hasOwnProperty(index) && routes[index])
24 if (thisRoute === false) return trie
25
26 var node = null
27 if (/^:|^\*/.test(thisRoute)) {
28 // if node is a name match, set name and append to ':' node
29 if (!trie.nodes.hasOwnProperty('$$')) {
30 node = { nodes: {} }
31 trie.nodes['$$'] = node
32 } else {
33 node = trie.nodes['$$']
34 }
35
36 if (thisRoute[0] === '*') {
37 trie.wildcard = true
38 }
39
40 trie.name = thisRoute.replace(/^:|^\*/, '')
41 } else if (!trie.nodes.hasOwnProperty(thisRoute)) {
42 node = { nodes: {} }
43 trie.nodes[thisRoute] = node
44 } else {
45 node = trie.nodes[thisRoute]
46 }
47
48 // we must recurse deeper
49 return createNode(index + 1, node)
50 }
51
52 return createNode(0, this.trie)
53}
54
55// match a route on the trie
56// and return the node
57// str -> obj
58Trie.prototype.match = function (route) {
59 assert.equal(typeof route, 'string', 'route should be a string')
60
61 var routes = route.replace(/^\//, '').split('/')
62 var params = {}
63
64 function search (index, trie) {
65 // either there's no match, or we're done searching
66 if (trie === undefined) return undefined
67 var thisRoute = routes[index]
68 if (thisRoute === undefined) return trie
69
70 if (trie.nodes.hasOwnProperty(thisRoute)) {
71 // match regular routes first
72 return search(index + 1, trie.nodes[thisRoute])
73 } else if (trie.wildcard) {
74 // match wildcards
75 try {
76 params['wildcard'] = decodeURIComponent(routes.slice(index).join('/'))
77 } catch (e) {
78 return search(index, undefined)
79 }
80 // return early, or else search may keep recursing through the wildcard
81 return trie.nodes['$$']
82 } else if (trie.name) {
83 // match named routes
84 try {
85 params[trie.name] = decodeURIComponent(thisRoute)
86 } catch (e) {
87 console.log('must throw')
88 return search(index, undefined)
89 }
90 return search(index + 1, trie.nodes['$$'])
91 } else {
92 // no matches found
93 return search(index + 1)
94 }
95 }
96
97 var node = search(0, this.trie)
98
99 if (!node) return undefined
100 node = xtend(node)
101 node.params = params
102 return node
103}
104
105// mount a trie onto a node at route
106// (str, obj) -> null
107Trie.prototype.mount = function (route, trie) {
108 assert.equal(typeof route, 'string', 'route should be a string')
109 assert.equal(typeof trie, 'object', 'trie should be a object')
110
111 var split = route.replace(/^\//, '').split('/')
112 var node = null
113 var key = null
114
115 if (split.length === 1) {
116 key = split[0]
117 node = this.create(key)
118 } else {
119 var headArr = split.splice(0, split.length - 1)
120 var head = headArr.join('/')
121 key = split[0]
122 node = this.create(head)
123 }
124
125 mutate(node.nodes, trie.nodes)
126 if (trie.name) node.name = trie.name
127
128 // delegate properties from '/' to the new node
129 // '/' cannot be reached once mounted
130 if (node.nodes['']) {
131 Object.keys(node.nodes['']).forEach(function (key) {
132 if (key === 'nodes') return
133 node[key] = node.nodes[''][key]
134 })
135 mutate(node.nodes, node.nodes[''].nodes)
136 delete node.nodes[''].nodes
137 }
138}