github.com/ipld/go-ipld-prime@v0.21.0/datamodel/path.go (about)

     1  package datamodel
     2  
     3  import (
     4  	"strings"
     5  )
     6  
     7  // Path describes a series of steps across a tree or DAG of Node,
     8  // where each segment in the path is a map key or list index
     9  // (literaly, Path is a slice of PathSegment values).
    10  // Path is used in describing progress in a traversal; and
    11  // can also be used as an instruction for traversing from one Node to another.
    12  // Path values will also often be encountered as part of error messages.
    13  //
    14  // (Note that Paths are useful as an instruction for traversing from
    15  // *one* Node to *one* other Node; to do a walk from one Node and visit
    16  // *several* Nodes based on some sort of pattern, look to IPLD Selectors,
    17  // and the 'traversal/selector' package in this project.)
    18  //
    19  // Path values are always relative.
    20  // Observe how 'traversal.Focus' requires both a Node and a Path argument --
    21  // where to start, and where to go, respectively.
    22  // Similarly, error values which include a Path will be speaking in reference
    23  // to the "starting Node" in whatever context they arose from.
    24  //
    25  // The canonical form of a Path is as a list of PathSegment.
    26  // Each PathSegment is a string; by convention, the string should be
    27  // in UTF-8 encoding and use NFC normalization, but all operations
    28  // will regard the string as its constituent eight-bit bytes.
    29  //
    30  // There are no illegal or magical characters in IPLD Paths
    31  // (in particular, do not mistake them for UNIX system paths).
    32  // IPLD Paths can only go down: that is, each segment must traverse one node.
    33  // There is no ".." which means "go up";
    34  // and there is no "." which means "stay here".
    35  // IPLD Paths have no magic behavior around characters such as "~".
    36  // IPLD Paths do not have a concept of "globs" nor behave specially
    37  // for a path segment string of "*" (but you may wish to see 'Selectors'
    38  // for globbing-like features that traverse over IPLD data).
    39  //
    40  // An empty string is a valid PathSegment.
    41  // (This leads to some unfortunate complications when wishing to represent
    42  // paths in a simple string format; however, consider that maps do exist
    43  // in serialized data in the wild where an empty string is used as the key:
    44  // it is important we be able to correctly describe and address this!)
    45  //
    46  // A string containing "/" (or even being simply "/"!) is a valid PathSegment.
    47  // (As with empty strings, this is unfortunate (in particular, because it
    48  // very much doesn't match up well with expectations popularized by UNIX-like
    49  // filesystems); but, as with empty strings, maps which contain such a key
    50  // certainly exist, and it is important that we be able to regard them!)
    51  //
    52  // A string starting, ending, or otherwise containing the NUL (\x00) byte
    53  // is also a valid PathSegment.  This follows from the rule of "a string is
    54  // regarded as its constituent eight-bit bytes": an all-zero byte is not exceptional.
    55  // In golang, this doesn't pose particular difficulty, but note this would be
    56  // of marked concern for languages which have "C-style nul-terminated strings".
    57  //
    58  // For an IPLD Path to be represented as a string, an encoding system
    59  // including escaping is necessary.  At present, there is not a single
    60  // canonical specification for such an escaping; we expect to decide one
    61  // in the future, but this is not yet settled and done.
    62  // (This implementation has a 'String' method, but it contains caveats
    63  // and may be ambiguous for some content.  This may be fixed in the future.)
    64  type Path struct {
    65  	segments []PathSegment
    66  }
    67  
    68  // NewPath returns a Path composed of the given segments.
    69  //
    70  // This constructor function does a defensive copy,
    71  // in case your segments slice should mutate in the future.
    72  // (Use NewPathNocopy if this is a performance concern,
    73  // and you're sure you know what you're doing.)
    74  func NewPath(segments []PathSegment) Path {
    75  	p := Path{make([]PathSegment, len(segments))}
    76  	copy(p.segments, segments)
    77  	return p
    78  }
    79  
    80  // NewPathNocopy is identical to NewPath but trusts that
    81  // the segments slice you provide will not be mutated.
    82  func NewPathNocopy(segments []PathSegment) Path {
    83  	return Path{segments}
    84  }
    85  
    86  // ParsePath converts a string to an IPLD Path, doing a basic parsing of the
    87  // string using "/" as a delimiter to produce a segmented Path.
    88  // This is a handy, but not a general-purpose nor spec-compliant (!),
    89  // way to create a Path: it cannot represent all valid paths.
    90  //
    91  // Multiple subsequent "/" characters will be silently collapsed.
    92  // E.g., `"foo///bar"` will be treated equivalently to `"foo/bar"`.
    93  // Prefixed and suffixed extraneous "/" characters are also discarded.
    94  // This makes this constructor incapable of handling some possible Path values
    95  // (specifically: paths with empty segements cannot be created with this constructor).
    96  //
    97  // There is no escaping mechanism used by this function.
    98  // This makes this constructor incapable of handling some possible Path values
    99  // (specifically, a path segment containing "/" cannot be created, because it
   100  // will always be intepreted as a segment separator).
   101  //
   102  // No other "cleaning" of the path occurs.  See the documentation of the Path struct;
   103  // in particular, note that ".." does not mean "go up", nor does "." mean "stay here" --
   104  // correspondingly, there isn't anything to "clean" in the same sense as
   105  // 'filepath.Clean' from the standard library filesystem path packages would.
   106  //
   107  // If the provided string contains unprintable characters, or non-UTF-8
   108  // or non-NFC-canonicalized bytes, no remark will be made about this,
   109  // and those bytes will remain part of the PathSegments in the resulting Path.
   110  func ParsePath(pth string) Path {
   111  	// FUTURE: we should probably have some escaping mechanism which makes
   112  	//  it possible to encode a slash in a segment.  Specification needed.
   113  	ss := strings.FieldsFunc(pth, func(r rune) bool { return r == '/' })
   114  	ssl := len(ss)
   115  	p := Path{make([]PathSegment, ssl)}
   116  	for i := 0; i < ssl; i++ {
   117  		p.segments[i] = PathSegmentOfString(ss[i])
   118  	}
   119  	return p
   120  }
   121  
   122  // String representation of a Path is simply the join of each segment with '/'.
   123  // It does not include a leading nor trailing slash.
   124  //
   125  // This is a handy, but not a general-purpose nor spec-compliant (!),
   126  // way to reduce a Path to a string.
   127  // There is no escaping mechanism used by this function,
   128  // and as a result, not all possible valid Path values (such as those with
   129  // empty segments or with segments containing "/") can be encoded unambiguously.
   130  // For Path values containing these problematic segments, ParsePath applied
   131  // to the string returned from this function may return a nonequal Path value.
   132  //
   133  // No escaping for unprintable characters is provided.
   134  // No guarantee that the resulting string is UTF-8 nor NFC canonicalized
   135  // is provided unless all the constituent PathSegment had those properties.
   136  func (p Path) String() string {
   137  	l := len(p.segments)
   138  	if l == 0 {
   139  		return ""
   140  	}
   141  	sb := strings.Builder{}
   142  	for i := 0; i < l-1; i++ {
   143  		sb.WriteString(p.segments[i].String())
   144  		sb.WriteByte('/')
   145  	}
   146  	sb.WriteString(p.segments[l-1].String())
   147  	return sb.String()
   148  }
   149  
   150  // Segments returns a slice of the path segment strings.
   151  //
   152  // It is not lawful to mutate nor append the returned slice.
   153  func (p Path) Segments() []PathSegment {
   154  	return p.segments
   155  }
   156  
   157  // Len returns the number of segments in this path.
   158  //
   159  // Zero segments means the path refers to "the current node".
   160  // One segment means it refers to a child of the current node; etc.
   161  func (p Path) Len() int {
   162  	return len(p.segments)
   163  }
   164  
   165  // Join creates a new path composed of the concatenation of this and the given path's segments.
   166  func (p Path) Join(p2 Path) Path {
   167  	combinedSegments := make([]PathSegment, len(p.segments)+len(p2.segments))
   168  	copy(combinedSegments, p.segments)
   169  	copy(combinedSegments[len(p.segments):], p2.segments)
   170  	p.segments = combinedSegments
   171  	return p
   172  }
   173  
   174  // AppendSegment is as per Join, but a shortcut when appending single segments.
   175  func (p Path) AppendSegment(ps PathSegment) Path {
   176  	l := len(p.segments)
   177  	combinedSegments := make([]PathSegment, l+1)
   178  	copy(combinedSegments, p.segments)
   179  	combinedSegments[l] = ps
   180  	p.segments = combinedSegments
   181  	return p
   182  }
   183  
   184  // AppendSegmentString is as per AppendSegment, but a shortcut when the segment is a string.
   185  func (p Path) AppendSegmentString(ps string) Path {
   186  	return p.AppendSegment(PathSegmentOfString(ps))
   187  }
   188  
   189  // AppendSegmentInt is as per AppendSegment, but a shortcut when the segment is an int.
   190  func (p Path) AppendSegmentInt(ps int64) Path {
   191  	return p.AppendSegment(PathSegmentOfInt(ps))
   192  }
   193  
   194  // Parent returns a path with the last of its segments popped off (or
   195  // the zero path if it's already empty).
   196  func (p Path) Parent() Path {
   197  	if len(p.segments) == 0 {
   198  		return Path{}
   199  	}
   200  	return Path{p.segments[0 : len(p.segments)-1]}
   201  }
   202  
   203  // Truncate returns a path with only as many segments remaining as requested.
   204  func (p Path) Truncate(i int) Path {
   205  	return Path{p.segments[0:i]}
   206  }
   207  
   208  // Last returns the trailing segment of the path.
   209  func (p Path) Last() PathSegment {
   210  	if len(p.segments) < 1 {
   211  		return PathSegment{}
   212  	}
   213  	return p.segments[len(p.segments)-1]
   214  }
   215  
   216  // Pop returns a path with all segments except the last.
   217  func (p Path) Pop() Path {
   218  	if len(p.segments) < 1 {
   219  		return Path{}
   220  	}
   221  	return Path{p.segments[0 : len(p.segments)-1]}
   222  }
   223  
   224  // Shift returns the first segment of the path together with the remaining path after that first segment.
   225  // If applied to a zero-length path, it returns an empty segment and the same zero-length path.
   226  func (p Path) Shift() (PathSegment, Path) {
   227  	if len(p.segments) < 1 {
   228  		return PathSegment{}, Path{}
   229  	}
   230  	return p.segments[0], Path{p.segments[1:]}
   231  }