go vendor
This commit is contained in:
21
vendor/github.com/revel/pathtree/LICENSE
generated
vendored
Normal file
21
vendor/github.com/revel/pathtree/LICENSE
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
Copyright (C) 2013 Rob Figueiredo
|
||||
All Rights Reserved.
|
||||
|
||||
MIT LICENSE
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
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
|
||||
}
|
179
vendor/github.com/revel/pathtree/tree_test.go
generated
vendored
Normal file
179
vendor/github.com/revel/pathtree/tree_test.go
generated
vendored
Normal file
@ -0,0 +1,179 @@
|
||||
package pathtree
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestColon(t *testing.T) {
|
||||
n := New()
|
||||
|
||||
n.Add("/:first/:second/", 1)
|
||||
n.Add("/:first", 2)
|
||||
n.Add("/", 3)
|
||||
|
||||
found(t, n, "/", nil, 3)
|
||||
found(t, n, "/a", []string{"a"}, 2)
|
||||
found(t, n, "/a/", []string{"a"}, 2)
|
||||
found(t, n, "/a/b", []string{"a", "b"}, 1)
|
||||
found(t, n, "/a/b/", []string{"a", "b"}, 1)
|
||||
|
||||
notfound(t, n, "/a/b/c")
|
||||
}
|
||||
|
||||
func TestStar(t *testing.T) {
|
||||
n := New()
|
||||
|
||||
n.Add("/first/second/*star", 1)
|
||||
n.Add("/:first/*star/", 2)
|
||||
n.Add("/*star", 3)
|
||||
n.Add("/", 4)
|
||||
|
||||
found(t, n, "/", nil, 4)
|
||||
found(t, n, "/a", []string{"a"}, 3)
|
||||
found(t, n, "/a/", []string{"a"}, 3)
|
||||
found(t, n, "/a/b", []string{"a", "b"}, 2)
|
||||
found(t, n, "/a/b/", []string{"a", "b"}, 2)
|
||||
found(t, n, "/a/b/c", []string{"a", "b/c"}, 2)
|
||||
found(t, n, "/a/b/c/", []string{"a", "b/c"}, 2)
|
||||
found(t, n, "/a/b/c/d", []string{"a", "b/c/d"}, 2)
|
||||
found(t, n, "/first/second", []string{"first", "second"}, 2)
|
||||
found(t, n, "/first/second/", []string{"first", "second"}, 2)
|
||||
found(t, n, "/first/second/third", []string{"third"}, 1)
|
||||
}
|
||||
|
||||
func TestMixedTree(t *testing.T) {
|
||||
n := New()
|
||||
|
||||
n.Add("/", 0)
|
||||
n.Add("/path/to/nowhere", 1)
|
||||
n.Add("/path/:i/nowhere", 2)
|
||||
n.Add("/:id/to/nowhere", 3)
|
||||
n.Add("/:a/:b", 4)
|
||||
n.Add("/not/found", 5)
|
||||
|
||||
found(t, n, "/", nil, 0)
|
||||
found(t, n, "/path/to/nowhere", nil, 1)
|
||||
found(t, n, "/path/to/nowhere/", nil, 1)
|
||||
found(t, n, "/path/from/nowhere", []string{"from"}, 2)
|
||||
found(t, n, "/walk/to/nowhere", []string{"walk"}, 3)
|
||||
found(t, n, "/path/to/", []string{"path", "to"}, 4)
|
||||
found(t, n, "/path/to", []string{"path", "to"}, 4)
|
||||
found(t, n, "/not/found", []string{"not", "found"}, 4)
|
||||
notfound(t, n, "/path/to/somewhere")
|
||||
notfound(t, n, "/path/to/nowhere/else")
|
||||
notfound(t, n, "/path")
|
||||
notfound(t, n, "/path/")
|
||||
|
||||
notfound(t, n, "")
|
||||
notfound(t, n, "xyz")
|
||||
notfound(t, n, "/path//to/nowhere")
|
||||
}
|
||||
|
||||
func TestExtensions(t *testing.T) {
|
||||
n := New()
|
||||
|
||||
n.Add("/:first/:second.json", 1)
|
||||
n.Add("/a/:second.xml", 2)
|
||||
n.Add("/:first/:second", 3)
|
||||
|
||||
found(t, n, "/a/b", []string{"a", "b"}, 3)
|
||||
found(t, n, "/a/b.json", []string{"a", "b"}, 1)
|
||||
found(t, n, "/a/b.xml", []string{"b"}, 2)
|
||||
found(t, n, "/a/b.c.xml", []string{"b.c"}, 2)
|
||||
found(t, n, "/other/b.xml", []string{"other", "b.xml"}, 3)
|
||||
}
|
||||
|
||||
func TestErrors(t *testing.T) {
|
||||
n := New()
|
||||
fails(t, n.Add("//", 1), "empty path elements not allowed")
|
||||
}
|
||||
|
||||
func BenchmarkTree100(b *testing.B) {
|
||||
n := New()
|
||||
n.Add("/", "root")
|
||||
|
||||
// Exact matches
|
||||
for i := 0; i < 100; i++ {
|
||||
depth := i%5 + 1
|
||||
key := ""
|
||||
for j := 0; j < depth-1; j++ {
|
||||
key += fmt.Sprintf("/dir%d", j)
|
||||
}
|
||||
key += fmt.Sprintf("/resource%d", i)
|
||||
n.Add(key, "literal")
|
||||
// b.Logf("Adding %s", key)
|
||||
}
|
||||
|
||||
// Wildcards at each level if no exact matches work.
|
||||
for i := 0; i < 5; i++ {
|
||||
var key string
|
||||
for j := 0; j < i; j++ {
|
||||
key += fmt.Sprintf("/dir%d", j)
|
||||
}
|
||||
key += "/:var"
|
||||
n.Add(key, "var")
|
||||
// b.Logf("Adding %s", key)
|
||||
}
|
||||
|
||||
n.Add("/public/*filepath", "static")
|
||||
// b.Logf("Adding /public/*filepath")
|
||||
|
||||
queries := map[string]string{
|
||||
"/": "root",
|
||||
"/dir0/dir1/dir2/dir3/resource4": "literal",
|
||||
"/dir0/dir1/resource97": "literal",
|
||||
"/dir0/variable": "var",
|
||||
"/dir0/dir1/dir2/dir3/variable": "var",
|
||||
"/public/stylesheets/main.css": "static",
|
||||
"/public/images/icons/an-image.png": "static",
|
||||
}
|
||||
|
||||
for query, answer := range queries {
|
||||
leaf, _ := n.Find(query)
|
||||
if leaf == nil {
|
||||
b.Errorf("Failed to find leaf for querY %s", query)
|
||||
return
|
||||
}
|
||||
if leaf.Value.(string) != answer {
|
||||
b.Errorf("Incorrect answer for querY %s: expected: %s, actual: %s",
|
||||
query, answer, leaf.Value.(string))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N/len(queries); i++ {
|
||||
for k, _ := range queries {
|
||||
n.Find(k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func notfound(t *testing.T, n *Node, p string) {
|
||||
if leaf, _ := n.Find(p); leaf != nil {
|
||||
t.Errorf("Should not have found: %s", p)
|
||||
}
|
||||
}
|
||||
|
||||
func found(t *testing.T, n *Node, p string, expectedExpansions []string, val interface{}) {
|
||||
leaf, expansions := n.Find(p)
|
||||
if leaf == nil {
|
||||
t.Errorf("Didn't find: %s", p)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(expansions, expectedExpansions) {
|
||||
t.Errorf("%s: Wildcard expansions (actual) %v != %v (expected)", p, expansions, expectedExpansions)
|
||||
}
|
||||
if leaf.Value != val {
|
||||
t.Errorf("%s: Value (actual) %v != %v (expected)", p, leaf.Value, val)
|
||||
}
|
||||
}
|
||||
|
||||
func fails(t *testing.T, err error, msg string) {
|
||||
if err == nil {
|
||||
t.Errorf("expected an error. %s", msg)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user