UNPKG

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