gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

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 }