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 }