UNPKG

choo-shortcache

Version:

choo nanocomponent cache shortcut

137 lines (116 loc) 3.65 kB
var mutate = require('xtend/mutable') var assert = require('assert') var xtend = require('xtend') module.exports = Trie // create a new trie // null -> obj function Trie () { if (!(this instanceof Trie)) return new Trie() this.trie = { nodes: {} } } // create a node on the trie at route // and return a node // str -> null Trie.prototype.create = function (route) { assert.equal(typeof route, 'string', 'route should be a string') // strip leading '/' and split routes var routes = route.replace(/^\//, '').split('/') function createNode (index, trie) { var thisRoute = (routes.hasOwnProperty(index) && routes[index]) if (thisRoute === false) return trie var node = null if (/^:|^\*/.test(thisRoute)) { // if node is a name match, set name and append to ':' node if (!trie.nodes.hasOwnProperty('$$')) { node = { nodes: {} } trie.nodes['$$'] = node } else { node = trie.nodes['$$'] } if (thisRoute[0] === '*') { trie.wildcard = true } trie.name = thisRoute.replace(/^:|^\*/, '') } else if (!trie.nodes.hasOwnProperty(thisRoute)) { node = { nodes: {} } trie.nodes[thisRoute] = node } else { node = trie.nodes[thisRoute] } // we must recurse deeper return createNode(index + 1, node) } return createNode(0, this.trie) } // match a route on the trie // and return the node // str -> obj Trie.prototype.match = function (route) { assert.equal(typeof route, 'string', 'route should be a string') var routes = route.replace(/^\//, '').split('/') var params = {} function search (index, trie) { // either there's no match, or we're done searching if (trie === undefined) return undefined var thisRoute = routes[index] if (thisRoute === undefined) return trie if (trie.nodes.hasOwnProperty(thisRoute)) { // match regular routes first return search(index + 1, trie.nodes[thisRoute]) } else if (trie.name) { // match named routes try { params[trie.name] = decodeURIComponent(thisRoute) } catch (e) { return search(index, undefined) } return search(index + 1, trie.nodes['$$']) } else if (trie.wildcard) { // match wildcards try { params['wildcard'] = decodeURIComponent(routes.slice(index).join('/')) } catch (e) { return search(index, undefined) } // return early, or else search may keep recursing through the wildcard return trie.nodes['$$'] } else { // no matches found return search(index + 1) } } var node = search(0, this.trie) if (!node) return undefined node = xtend(node) node.params = params return node } // mount a trie onto a node at route // (str, obj) -> null Trie.prototype.mount = function (route, trie) { assert.equal(typeof route, 'string', 'route should be a string') assert.equal(typeof trie, 'object', 'trie should be a object') var split = route.replace(/^\//, '').split('/') var node = null var key = null if (split.length === 1) { key = split[0] node = this.create(key) } else { var head = split.join('/') key = split[0] node = this.create(head) } mutate(node.nodes, trie.nodes) if (trie.name) node.name = trie.name // delegate properties from '/' to the new node // '/' cannot be reached once mounted if (node.nodes['']) { Object.keys(node.nodes['']).forEach(function (key) { if (key === 'nodes') return node[key] = node.nodes[''][key] }) mutate(node.nodes, node.nodes[''].nodes) delete node.nodes[''].nodes } }