github.com/aristanetworks/goarista@v0.0.0-20240514173732-cca2755bbd44/path/path.go (about)

     1  // Copyright (c) 2017 Arista Networks, Inc.
     2  // Use of this source code is governed by the Apache License 2.0
     3  // that can be found in the COPYING file.
     4  
     5  // Package path contains methods for dealing with key.Paths.
     6  package path
     7  
     8  import (
     9  	"strings"
    10  
    11  	"github.com/aristanetworks/goarista/key"
    12  )
    13  
    14  // New constructs a path from a variable number of elements.
    15  // Each element may either be a key.Key or a value that can
    16  // be wrapped by a key.Key.
    17  func New(elements ...interface{}) key.Path {
    18  	return appendElements(nil, elements...)
    19  }
    20  
    21  // Append appends a variable number of elements to a path.
    22  // Each element may either be a key.Key or a value that can
    23  // be wrapped by a key.Key. Note that calling Append on a
    24  // single path returns that same path, whereas in all other
    25  // cases a new path is returned.
    26  func Append(path key.Path, elements ...interface{}) key.Path {
    27  	return appendElements(path, elements...)
    28  }
    29  
    30  // Join joins a variable number of paths together. Each path
    31  // in the joining is treated as a subpath of its predecessor.
    32  // Calling Join with no or only empty paths returns nil.
    33  func Join(paths ...key.Path) key.Path {
    34  	n := 0
    35  	for _, path := range paths {
    36  		n += len(path)
    37  	}
    38  	if n == 0 {
    39  		return nil
    40  	}
    41  	result, i := make(key.Path, n), 0
    42  	for _, path := range paths {
    43  		i += copy(result[i:], path)
    44  	}
    45  	return result
    46  }
    47  
    48  // Parent returns all but the last element of the path. If
    49  // the path is empty, Parent returns nil.
    50  func Parent(path key.Path) key.Path {
    51  	if len(path) > 0 {
    52  		return path[:len(path)-1]
    53  	}
    54  	return nil
    55  }
    56  
    57  // Base returns the last element of the path. If the path is
    58  // empty, Base returns nil.
    59  func Base(path key.Path) key.Key {
    60  	if len(path) > 0 {
    61  		return path[len(path)-1]
    62  	}
    63  	return nil
    64  }
    65  
    66  // Clone returns a new path with the same elements as in the
    67  // provided path.
    68  func Clone(path key.Path) key.Path {
    69  	result := make(key.Path, len(path))
    70  	copy(result, path)
    71  	return result
    72  }
    73  
    74  // Equal returns whether path a and path b are the same
    75  // length and whether each element in b corresponds to the
    76  // same element in a.
    77  func Equal(a, b key.Path) bool {
    78  	return len(a) == len(b) && hasPrefix(a, b)
    79  }
    80  
    81  // HasElement returns whether element b exists in path a.
    82  func HasElement(a key.Path, b key.Key) bool {
    83  	for _, element := range a {
    84  		if element.Equal(b) {
    85  			return true
    86  		}
    87  	}
    88  	return false
    89  }
    90  
    91  // HasPrefix returns whether path b is a prefix of path a.
    92  // It checks that b is at most the length of path a and
    93  // whether each element in b corresponds to the same element
    94  // in a from the first element.
    95  func HasPrefix(a, b key.Path) bool {
    96  	return len(a) >= len(b) && hasPrefix(a, b)
    97  }
    98  
    99  // Match returns whether path a and path b are the same
   100  // length and whether each element in b corresponds to the
   101  // same element or a wildcard in a.
   102  func Match(a, b key.Path) bool {
   103  	return len(a) == len(b) && matchPrefix(a, b)
   104  }
   105  
   106  // MatchPrefix returns whether path b is a prefix of path a
   107  // where path a may contain wildcards.
   108  // It checks that b is at most the length of path a and
   109  // whether each element in b corresponds to the same element
   110  // or a wildcard in a from the first element.
   111  func MatchPrefix(a, b key.Path) bool {
   112  	return len(a) >= len(b) && matchPrefix(a, b)
   113  }
   114  
   115  // FromString constructs a path from the elements resulting
   116  // from a split of the input string by "/". Strings that do
   117  // not lead with a '/' are accepted but not reconstructable
   118  // with key.Path.String. Both "" and "/" are treated as a
   119  // key.Path{}.
   120  func FromString(str string) key.Path {
   121  	if str == "" || str == "/" {
   122  		return key.Path{}
   123  	} else if str[0] == '/' {
   124  		str = str[1:]
   125  	}
   126  	elements := strings.Split(str, "/")
   127  	result := make(key.Path, len(elements))
   128  	for i, element := range elements {
   129  		result[i] = key.New(element)
   130  	}
   131  	return result
   132  }
   133  
   134  // appendElements makes a copy of dest when elements is non-empty and
   135  // then appends elements to the copy and returns it.
   136  func appendElements(dest key.Path, elements ...interface{}) key.Path {
   137  	if len(elements) == 0 {
   138  		return dest
   139  	}
   140  	clone := make(key.Path, len(dest), len(dest)+len(elements))
   141  	copy(clone, dest)
   142  	dest = clone
   143  	for _, element := range elements {
   144  		switch val := element.(type) {
   145  		case key.Key:
   146  			dest = append(dest, val)
   147  		case []key.Key:
   148  			dest = append(dest, val...)
   149  		case key.Path:
   150  			dest = append(dest, val...)
   151  		case []string:
   152  			for i := range val {
   153  				dest = append(dest, key.New(val[i]))
   154  			}
   155  		case []key.Path:
   156  			for i := range val {
   157  				dest = append(dest, val[i]...)
   158  			}
   159  		default:
   160  			dest = append(dest, key.New(val))
   161  		}
   162  	}
   163  	return dest
   164  }
   165  
   166  func hasPrefix(a, b key.Path) bool {
   167  	for i := range b {
   168  		if !b[i].Equal(a[i]) {
   169  			return false
   170  		}
   171  	}
   172  	return true
   173  }
   174  
   175  func matchPrefix(a, b key.Path) bool {
   176  	for i := range b {
   177  		if !a[i].Equal(Wildcard) && !b[i].Equal(a[i]) {
   178  			return false
   179  		}
   180  	}
   181  	return true
   182  }