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  }