go vendor
This commit is contained in:
228
vendor/github.com/revel/pathtree/tree.go
generated
vendored
Normal file
228
vendor/github.com/revel/pathtree/tree.go
generated
vendored
Normal file
@ -0,0 +1,228 @@
|
||||
// pathtree implements a tree for fast path lookup.
|
||||
//
|
||||
// Restrictions
|
||||
//
|
||||
// - Paths must be a '/'-separated list of strings, like a URL or Unix filesystem.
|
||||
// - All paths must begin with a '/'.
|
||||
// - Path elements may not contain a '/'.
|
||||
// - Path elements beginning with a ':' or '*' will be interpreted as wildcards.
|
||||
// - Trailing slashes are inconsequential.
|
||||
//
|
||||
// Wildcards
|
||||
//
|
||||
// Wildcards are named path elements that may match any strings in that
|
||||
// location. Two different kinds of wildcards are permitted:
|
||||
// - :var - names beginning with ':' will match any single path element.
|
||||
// - *var - names beginning with '*' will match one or more path elements.
|
||||
// (however, no path elements may come after a star wildcard)
|
||||
//
|
||||
// Extensions
|
||||
//
|
||||
// Single element wildcards in the last path element can optionally end with an
|
||||
// extension. This allows for routes like '/users/:id.json', which will not
|
||||
// conflict with '/users/:id'.
|
||||
//
|
||||
// Algorithm
|
||||
//
|
||||
// Paths are mapped to the tree in the following way:
|
||||
// - Each '/' is a Node in the tree. The root node is the leading '/'.
|
||||
// - Each Node has edges to other nodes. The edges are named according to the
|
||||
// possible path elements at that depth in the path.
|
||||
// - Any Node may have an associated Leaf. Leafs are terminals containing the
|
||||
// data associated with the path as traversed from the root to that Node.
|
||||
//
|
||||
// Edges are implemented as a map from the path element name to the next node in
|
||||
// the path.
|
||||
package pathtree
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Node struct {
|
||||
edges map[string]*Node // the various path elements leading out of this node.
|
||||
wildcard *Node // if set, this node had a wildcard as its path element.
|
||||
leaf *Leaf // if set, this is a terminal node for this leaf.
|
||||
extensions map[string]*Leaf // if set, this is a terminal node with a leaf that ends in a specific extension.
|
||||
star *Leaf // if set, this path ends in a star.
|
||||
leafs int // counter for # leafs in the tree
|
||||
}
|
||||
|
||||
type Leaf struct {
|
||||
Value interface{} // the value associated with this node
|
||||
Wildcards []string // the wildcard names, in order they appear in the path
|
||||
order int // the order this leaf was added
|
||||
}
|
||||
|
||||
// New returns a new path tree.
|
||||
func New() *Node {
|
||||
return &Node{edges: make(map[string]*Node)}
|
||||
}
|
||||
|
||||
// Add a path and its associated value to the tree.
|
||||
// - key must begin with "/"
|
||||
// - key must not duplicate any existing key.
|
||||
// Returns an error if those conditions do not hold.
|
||||
func (n *Node) Add(key string, val interface{}) error {
|
||||
if key == "" || key[0] != '/' {
|
||||
return errors.New("Path must begin with /")
|
||||
}
|
||||
n.leafs++
|
||||
return n.add(n.leafs, splitPath(key), nil, val)
|
||||
}
|
||||
|
||||
// Adds a leaf to a terminal node.
|
||||
// If the last wildcard contains an extension, add it to the 'extensions' map.
|
||||
func (n *Node) addLeaf(leaf *Leaf) error {
|
||||
extension := stripExtensionFromLastSegment(leaf.Wildcards)
|
||||
if extension != "" {
|
||||
if n.extensions == nil {
|
||||
n.extensions = make(map[string]*Leaf)
|
||||
}
|
||||
if n.extensions[extension] != nil {
|
||||
return errors.New("duplicate path")
|
||||
}
|
||||
n.extensions[extension] = leaf
|
||||
return nil
|
||||
}
|
||||
|
||||
if n.leaf != nil {
|
||||
return errors.New("duplicate path")
|
||||
}
|
||||
n.leaf = leaf
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *Node) add(order int, elements, wildcards []string, val interface{}) error {
|
||||
if len(elements) == 0 {
|
||||
leaf := &Leaf{
|
||||
order: order,
|
||||
Value: val,
|
||||
Wildcards: wildcards,
|
||||
}
|
||||
return n.addLeaf(leaf)
|
||||
}
|
||||
|
||||
var el string
|
||||
el, elements = elements[0], elements[1:]
|
||||
if el == "" {
|
||||
return errors.New("empty path elements are not allowed")
|
||||
}
|
||||
|
||||
// Handle wildcards.
|
||||
switch el[0] {
|
||||
case ':':
|
||||
if n.wildcard == nil {
|
||||
n.wildcard = New()
|
||||
}
|
||||
return n.wildcard.add(order, elements, append(wildcards, el[1:]), val)
|
||||
case '*':
|
||||
if n.star != nil {
|
||||
return errors.New("duplicate path")
|
||||
}
|
||||
n.star = &Leaf{
|
||||
order: order,
|
||||
Value: val,
|
||||
Wildcards: append(wildcards, el[1:]),
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// It's a normal path element.
|
||||
e, ok := n.edges[el]
|
||||
if !ok {
|
||||
e = New()
|
||||
n.edges[el] = e
|
||||
}
|
||||
|
||||
return e.add(order, elements, wildcards, val)
|
||||
}
|
||||
|
||||
// Find a given path. Any wildcards traversed along the way are expanded and
|
||||
// returned, along with the value.
|
||||
func (n *Node) Find(key string) (leaf *Leaf, expansions []string) {
|
||||
if len(key) == 0 || key[0] != '/' {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return n.find(splitPath(key), nil)
|
||||
}
|
||||
|
||||
func (n *Node) find(elements, exp []string) (leaf *Leaf, expansions []string) {
|
||||
if len(elements) == 0 {
|
||||
// If this node has explicit extensions, check if the path matches one.
|
||||
if len(exp) > 0 && n.extensions != nil {
|
||||
lastExp := exp[len(exp)-1]
|
||||
prefix, extension := extensionForPath(lastExp)
|
||||
if leaf := n.extensions[extension]; leaf != nil {
|
||||
exp[len(exp)-1] = prefix
|
||||
return leaf, exp
|
||||
}
|
||||
}
|
||||
return n.leaf, exp
|
||||
}
|
||||
|
||||
// If this node has a star, calculate the star expansions in advance.
|
||||
var starExpansion string
|
||||
if n.star != nil {
|
||||
starExpansion = strings.Join(elements, "/")
|
||||
}
|
||||
|
||||
// Peel off the next element and look up the associated edge.
|
||||
var el string
|
||||
el, elements = elements[0], elements[1:]
|
||||
if nextNode, ok := n.edges[el]; ok {
|
||||
leaf, expansions = nextNode.find(elements, exp)
|
||||
}
|
||||
|
||||
// Handle colon
|
||||
if n.wildcard != nil {
|
||||
wildcardLeaf, wildcardExpansions := n.wildcard.find(elements, append(exp, el))
|
||||
if wildcardLeaf != nil && (leaf == nil || leaf.order > wildcardLeaf.order) {
|
||||
leaf = wildcardLeaf
|
||||
expansions = wildcardExpansions
|
||||
}
|
||||
}
|
||||
|
||||
// Handle star
|
||||
if n.star != nil && (leaf == nil || leaf.order > n.star.order) {
|
||||
leaf = n.star
|
||||
expansions = append(exp, starExpansion)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func extensionForPath(path string) (string, string) {
|
||||
dotPosition := strings.LastIndex(path, ".")
|
||||
if dotPosition != -1 {
|
||||
return path[:dotPosition], path[dotPosition:]
|
||||
}
|
||||
return "", ""
|
||||
}
|
||||
|
||||
func splitPath(key string) []string {
|
||||
elements := strings.Split(key, "/")
|
||||
if elements[0] == "" {
|
||||
elements = elements[1:]
|
||||
}
|
||||
if elements[len(elements)-1] == "" {
|
||||
elements = elements[:len(elements)-1]
|
||||
}
|
||||
return elements
|
||||
}
|
||||
|
||||
// stripExtensionFromLastSegment determines if a string slice representing a path
|
||||
// ends with a file extension, removes the extension from the input, and returns it.
|
||||
func stripExtensionFromLastSegment(segments []string) string {
|
||||
if len(segments) == 0 {
|
||||
return ""
|
||||
}
|
||||
lastSegment := segments[len(segments)-1]
|
||||
prefix, extension := extensionForPath(lastSegment)
|
||||
if extension != "" {
|
||||
segments[len(segments)-1] = prefix
|
||||
}
|
||||
return extension
|
||||
}
|
Reference in New Issue
Block a user