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