github.com/sdboyer/gps@v0.16.3/typed_radix.go (about) 1 package gps 2 3 import ( 4 "strings" 5 "sync" 6 7 "github.com/armon/go-radix" 8 ) 9 10 // Typed implementations of radix trees. These are just simple wrappers that let 11 // us avoid having to type assert anywhere else, cleaning up other code a bit. 12 // 13 // Some of the more annoying things to implement (like walks) aren't 14 // implemented. They can be added if/when we actually need them. 15 // 16 // Oh generics, where art thou... 17 18 type deducerTrie struct { 19 sync.RWMutex 20 t *radix.Tree 21 } 22 23 func newDeducerTrie() *deducerTrie { 24 return &deducerTrie{ 25 t: radix.New(), 26 } 27 } 28 29 // Delete is used to delete a key, returning the previous value and if it was deleted 30 func (t *deducerTrie) Delete(s string) (pathDeducer, bool) { 31 t.Lock() 32 defer t.Unlock() 33 if d, had := t.t.Delete(s); had { 34 return d.(pathDeducer), had 35 } 36 return nil, false 37 } 38 39 // Get is used to lookup a specific key, returning the value and if it was found 40 func (t *deducerTrie) Get(s string) (pathDeducer, bool) { 41 t.RLock() 42 defer t.RUnlock() 43 if d, has := t.t.Get(s); has { 44 return d.(pathDeducer), has 45 } 46 return nil, false 47 } 48 49 // Insert is used to add a newentry or update an existing entry. Returns if updated. 50 func (t *deducerTrie) Insert(s string, d pathDeducer) (pathDeducer, bool) { 51 t.Lock() 52 defer t.Unlock() 53 if d2, had := t.t.Insert(s, d); had { 54 return d2.(pathDeducer), had 55 } 56 return nil, false 57 } 58 59 // Len is used to return the number of elements in the tree 60 func (t *deducerTrie) Len() int { 61 t.RLock() 62 defer t.RUnlock() 63 return t.t.Len() 64 } 65 66 // LongestPrefix is like Get, but instead of an exact match, it will return the 67 // longest prefix match. 68 func (t *deducerTrie) LongestPrefix(s string) (string, pathDeducer, bool) { 69 t.RLock() 70 defer t.RUnlock() 71 if p, d, has := t.t.LongestPrefix(s); has { 72 return p, d.(pathDeducer), has 73 } 74 return "", nil, false 75 } 76 77 // ToMap is used to walk the tree and convert it to a map. 78 func (t *deducerTrie) ToMap() map[string]pathDeducer { 79 m := make(map[string]pathDeducer) 80 t.RLock() 81 t.t.Walk(func(s string, d interface{}) bool { 82 m[s] = d.(pathDeducer) 83 return false 84 }) 85 86 t.RUnlock() 87 return m 88 } 89 90 // isPathPrefixOrEqual is an additional helper check to ensure that the literal 91 // string prefix returned from a radix tree prefix match is also a path tree 92 // match. 93 // 94 // The radix tree gets it mostly right, but we have to guard against 95 // possibilities like this: 96 // 97 // github.com/sdboyer/foo 98 // github.com/sdboyer/foobar/baz 99 // 100 // The latter would incorrectly be conflated with the former. As we know we're 101 // operating on strings that describe import paths, guard against this case by 102 // verifying that either the input is the same length as the match (in which 103 // case we know they're equal), or that the next character is a "/". (Import 104 // paths are defined to always use "/", not the OS-specific path separator.) 105 func isPathPrefixOrEqual(pre, path string) bool { 106 prflen, pathlen := len(pre), len(path) 107 if pathlen == prflen+1 { 108 // this can never be the case 109 return false 110 } 111 112 // we assume something else (a trie) has done equality check up to the point 113 // of the prefix, so we just check len 114 return prflen == pathlen || strings.Index(path[prflen:], "/") == 0 115 }