gtsocial-umbx

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

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 }