path.go (7449B)
1 package fastpath 2 3 import ( 4 "unsafe" 5 ) 6 7 // Clean: see Builder.Clean(). Analogous to path.Clean(). 8 func Clean(path string) string { 9 return (&Builder{}).Clean(path) 10 } 11 12 // Join: see Builder.Join(). Analogous to path.Join(). 13 func Join(elems ...string) string { 14 return (&Builder{}).Join(elems...) 15 } 16 17 // Builder provides a means of cleaning and joining system paths, 18 // while retaining a singular underlying byte buffer for performance. 19 type Builder struct { 20 // B is the underlying byte buffer 21 B []byte 22 23 dd int // pos of last '..' appended to builder 24 abs bool // abs stores whether path passed to first .Append() is absolute 25 set bool // set stores whether b.abs has been set i.e. not first call to .Append() 26 } 27 28 // Reset resets the Builder object 29 func (b *Builder) Reset() { 30 b.B = b.B[:0] 31 b.dd = 0 32 b.abs = false 33 b.set = false 34 } 35 36 // Len returns the number of accumulated bytes in the Builder 37 func (b Builder) Len() int { 38 return len(b.B) 39 } 40 41 // Cap returns the capacity of the underlying Builder buffer 42 func (b Builder) Cap() int { 43 return cap(b.B) 44 } 45 46 // Bytes returns the accumulated path bytes. 47 func (b Builder) Bytes() []byte { 48 return b.B 49 } 50 51 // String returns the accumulated path string. 52 func (b Builder) String() string { 53 return *(*string)(unsafe.Pointer(&b.B)) 54 } 55 56 // Absolute returns whether current path is absolute (not relative). 57 func (b Builder) Absolute() bool { 58 return b.abs 59 } 60 61 // SetAbsolute converts the current path to-or-from absolute. 62 func (b *Builder) SetAbsolute(enabled bool) { 63 if !b.set { 64 // Ensure 1B avail 65 b.Guarantee(1) 66 67 if enabled { 68 // Set empty 'abs' 69 b.appendByte('/') 70 b.abs = true 71 } else { 72 // Set empty 'rel' 73 b.appendByte('.') 74 b.abs = false 75 } 76 77 b.set = true 78 return 79 } 80 81 if !enabled && b.abs { 82 // set && absolute 83 // -> update 84 b.abs = false 85 86 // If empty, set to '.' (empty rel path) 87 if len(b.B) == 0 || (len(b.B) == 1 && b.B[0] == '/') { 88 b.Guarantee(1) 89 b.B = b.B[:1] 90 b.B[0] = '.' 91 return 92 } 93 94 if b.B[0] != '/' { 95 // No need to change 96 return 97 } 98 99 if len(b.B) > 1 { 100 // Shift bytes 1 left 101 copy(b.B, b.B[1:]) 102 } 103 104 // and drop the '/' prefix' 105 b.B = b.B[:len(b.B)-1] 106 } else if enabled && !b.abs { 107 // set && !absolute 108 // -> update 109 b.abs = true 110 111 // Ensure 1B avail 112 b.Guarantee(1) 113 114 // If empty, set to '/' (empty abs path) 115 if len(b.B) == 0 || (len(b.B) == 1 && b.B[0] == '.') { 116 b.Guarantee(1) 117 b.B = b.B[:1] 118 b.B[0] = '/' 119 return 120 } 121 122 // Increase length 123 l := len(b.B) 124 b.B = b.B[:l+1] 125 126 // Shift bytes 1 right 127 copy(b.B[1:], b.B[:l]) 128 129 // Set first byte '/' 130 b.B[0] = '/' 131 } 132 } 133 134 // AppendBytes adds and cleans the supplied path bytes to the 135 // builder's internal buffer, growing the buffer if necessary 136 // to accomodate the extra path length. 137 func (b *Builder) AppendBytes(path []byte) { 138 if len(path) == 0 { 139 return 140 } 141 b.Guarantee(len(path) + 1) 142 b.append(*(*string)(unsafe.Pointer(&b))) 143 } 144 145 // Append adds and cleans the supplied path string to the 146 // builder's internal buffer, growing the buffer if necessary 147 // to accomodate the extra path length. 148 func (b *Builder) Append(path string) { 149 if len(path) == 0 { 150 return 151 } 152 b.Guarantee(len(path) + 1) 153 b.append(path) 154 } 155 156 // Clean creates the shortest possible functional equivalent 157 // to the supplied path, resetting the builder before performing 158 // this operation. The builder object is NOT reset after return. 159 func (b *Builder) Clean(path string) string { 160 if path == "" { 161 return "." 162 } 163 b.Reset() 164 b.Guarantee(len(path) + 1) 165 b.append(path) 166 return string(b.B) 167 } 168 169 // Join connects and cleans multiple paths, resetting the builder before 170 // performing this operation and returning the shortest possible combination 171 // of all the supplied paths. The builder object is NOT reset after return. 172 func (b *Builder) Join(elems ...string) string { 173 var size int 174 for _, elem := range elems { 175 size += len(elem) 176 } 177 if size == 0 { 178 return "" 179 } 180 b.Reset() 181 b.Guarantee(size + 1) 182 for _, elem := range elems { 183 if elem == "" { 184 continue 185 } 186 b.append(elem) 187 } 188 return string(b.B) 189 } 190 191 // append performs the main logic of 'Append()' but without an empty path check or preallocation. 192 func (b *Builder) append(path string) { 193 if !b.set { 194 // Set if absolute or not 195 b.abs = path[0] == '/' 196 b.set = true 197 } else if !b.abs && len(b.B) == 1 && b.B[0] == '.' { 198 // Empty non-abs path segment, drop 199 // the period so not prefixed './' 200 b.B = b.B[:0] 201 } 202 203 for i := 0; i < len(path); { 204 switch { 205 // Empty path segment 206 case path[i] == '/': 207 i++ 208 209 // Singular '.' path segment, treat as empty 210 case path[i] == '.' && (i+1 == len(path) || path[i+1] == '/'): 211 i++ 212 213 // Backtrack segment 214 case path[i] == '.' && path[i+1] == '.' && (i+2 == len(path) || path[i+2] == '/'): 215 i += 2 216 217 switch { 218 // Check if it's possible to backtrack with 219 // our current state of the buffer. i.e. is 220 // our buffer length longer than the last 221 // '..' we placed? 222 case len(b.B) > b.dd: 223 b.backtrack() 224 225 // If we reached here, need to check if 226 // we can append '..' to the path buffer, 227 // which is ONLY when path is NOT absolute 228 case !b.abs: 229 if len(b.B) > 0 { 230 b.appendByte('/') 231 } 232 b.appendByte('.') 233 b.appendByte('.') 234 b.dd = len(b.B) 235 } 236 237 default: 238 if (b.abs && len(b.B) != 1) || (!b.abs && len(b.B) > 0) { 239 // Append path separator 240 b.appendByte('/') 241 } 242 243 // Append slice up to next '/' 244 i += b.appendSlice(path[i:]) 245 } 246 } 247 248 if len(b.B) > 0 { 249 return 250 } 251 252 if b.abs { 253 // Empty absolute path => / 254 b.appendByte('/') 255 } else { 256 // Empty relative path => . 257 b.appendByte('.') 258 } 259 } 260 261 // Guarantee ensures there is at least the requested size 262 // free bytes available in the buffer, reallocating if necessary 263 func (b *Builder) Guarantee(size int) { 264 if size > cap(b.B)-len(b.B) { 265 nb := make([]byte, 2*cap(b.B)+size) 266 copy(nb, b.B) 267 b.B = nb[:len(b.B)] 268 } 269 } 270 271 // Truncate reduces the length of the buffer by the requested 272 // number of bytes. If the byte slice is *effectively* empty, 273 // i.e. absolute and "/" or relative and ".", it won't be truncated. 274 func (b *Builder) Truncate(size int) { 275 if len(b.B) == 0 { 276 return 277 } 278 279 if len(b.B) == 1 && ((b.abs && b.B[0] == '/') || 280 (!b.abs && b.B[0] == '.')) { 281 // *effectively* empty 282 return 283 } 284 285 // Truncate requested bytes 286 b.B = b.B[:len(b.B)-size] 287 } 288 289 // appendByte appends the supplied byte to the end of 290 // the buffer. appending is achieved by continually reslicing the 291 // buffer and setting the next byte-at-index, this is safe as guarantee() 292 // will have been called beforehand 293 func (b *Builder) appendByte(c byte) { 294 b.B = b.B[:len(b.B)+1] 295 b.B[len(b.B)-1] = c 296 } 297 298 // appendSlice appends the supplied string slice to 299 // the end of the buffer and returns the number of indices 300 // we were able to iterate before hitting a path separator '/'. 301 // appending is achieved by continually reslicing the buffer 302 // and setting the next byte-at-index, this is safe as guarantee() 303 // will have been called beforehand 304 func (b *Builder) appendSlice(slice string) int { 305 i := 0 306 for i < len(slice) && slice[i] != '/' { 307 b.B = b.B[:len(b.B)+1] 308 b.B[len(b.B)-1] = slice[i] 309 i++ 310 } 311 return i 312 } 313 314 // backtrack reduces the end of the buffer back to the last 315 // separating '/', or end of buffer 316 func (b *Builder) backtrack() { 317 b.B = b.B[:len(b.B)-1] 318 319 for len(b.B)-1 > b.dd && b.B[len(b.B)-1] != '/' { 320 b.B = b.B[:len(b.B)-1] 321 } 322 323 if len(b.B) > 0 { 324 b.B = b.B[:len(b.B)-1] 325 } 326 }