gotosocial/vendor/codeberg.org/gruf/go-fastpath/path.go

320 lines
7.2 KiB
Go
Raw Normal View History

package fastpath
import (
"unsafe"
)
2021-11-13 12:29:08 +01:00
// allocate these just once
var (
dot = []byte(dotStr)
dotStr = "."
2021-11-13 12:29:08 +01:00
)
type Builder struct {
B []byte // B is the underlying byte buffer
dd int // pos of last '..' appended to builder
abs bool // abs stores whether path passed to first .Append() is absolute
set bool // set stores whether b.abs has been set i.e. not first call to .Append()
}
// NewBuilder returns a new Builder object using the
// supplied byte slice as the underlying buffer
func NewBuilder(b []byte) Builder {
if b != nil {
b = b[:0]
}
return Builder{
2021-11-13 12:29:08 +01:00
B: b,
dd: 0,
abs: false,
set: false,
}
}
// Reset resets the Builder object
func (b *Builder) Reset() {
2021-11-13 12:29:08 +01:00
b.B = b.B[:0]
b.dd = 0
b.abs = false
b.set = false
}
// Len returns the number of accumulated bytes in the Builder
func (b *Builder) Len() int {
2021-11-13 12:29:08 +01:00
return len(b.B)
}
// Cap returns the capacity of the underlying Builder buffer
func (b *Builder) Cap() int {
2021-11-13 12:29:08 +01:00
return cap(b.B)
}
// Bytes returns the accumulated path bytes.
func (b *Builder) Bytes() []byte {
if len(b.B) < 1 {
return dot
}
2021-11-13 12:29:08 +01:00
return b.B
}
// String returns the accumulated path string.
func (b *Builder) String() string {
if len(b.B) < 1 {
2021-11-13 12:29:08 +01:00
return dotStr
}
2021-11-13 12:29:08 +01:00
return string(b.B)
}
// StringPtr returns a ptr to the accumulated path string.
//
// Please note the underlying byte slice for this string is
// tied to the builder, so any changes will result in the
// returned string changing. Consider using .String() if
// this is undesired behaviour.
func (b *Builder) StringPtr() string {
if len(b.B) < 1 {
2021-11-13 12:29:08 +01:00
return dotStr
}
2021-11-13 12:29:08 +01:00
return *(*string)(unsafe.Pointer(&b.B))
}
2021-11-13 12:29:08 +01:00
// Absolute returns whether current path is absolute (not relative)
func (b *Builder) Absolute() bool {
return b.abs
}
2021-11-13 12:29:08 +01:00
// SetAbsolute converts the current path to / from absolute
func (b *Builder) SetAbsolute(val bool) {
if !b.set {
if val {
// .Append() has not been
// called, add a '/' and set abs
b.Guarantee(1)
b.appendByte('/')
b.abs = true
}
// Set as having been set
b.set = true
return
}
if !val && b.abs {
// Already set and absolute. Update
b.abs = false
// If not empty (i.e. not just '/'),
// then shift bytes 1 left
if len(b.B) > 1 {
2021-11-13 12:29:08 +01:00
copy(b.B, b.B[1:])
}
// Truncate 1 byte. In the case of empty,
// i.e. just '/' then it will drop this
b.truncate(1)
} else if val && !b.abs {
// Already set but NOT abs. Update
b.abs = true
// Guarantee 1 byte available
b.Guarantee(1)
// If empty, just append '/'
if len(b.B) < 1 {
b.appendByte('/')
return
}
// Increase length
l := len(b.B)
2021-11-13 12:29:08 +01:00
b.B = b.B[:l+1]
// Shift bytes 1 right
2021-11-13 12:29:08 +01:00
copy(b.B[1:], b.B[:l])
// Set first byte '/'
2021-11-13 12:29:08 +01:00
b.B[0] = '/'
}
}
// Append adds and cleans the supplied path bytes to the
// builder's internal buffer, growing the buffer if necessary
// to accomodate the extra path length
func (b *Builder) Append(p []byte) {
b.AppendString(*(*string)(unsafe.Pointer(&p)))
}
// AppendString adds and cleans the supplied path string to the
// builder's internal buffer, growing the buffer if necessary
// to accomodate the extra path length
func (b *Builder) AppendString(path string) {
defer func() {
// If buffer is empty, and an absolute
// path, ensure it starts with a '/'
if len(b.B) < 1 && b.abs {
b.appendByte('/')
}
}()
// Empty path, nothing to do
if len(path) == 0 {
return
}
// Guarantee at least the total length
// of supplied path available in the buffer
b.Guarantee(len(path))
// Try store if absolute
if !b.set {
b.abs = len(path) > 0 && path[0] == '/'
b.set = true
}
i := 0
for i < len(path) {
switch {
// Empty path segment
case path[i] == '/':
i++
// Singular '.' path segment, treat as empty
case path[i] == '.' && (i+1 == len(path) || path[i+1] == '/'):
i++
// Backtrack segment
case path[i] == '.' && path[i+1] == '.' && (i+2 == len(path) || path[i+2] == '/'):
i += 2
switch {
// Check if it's possible to backtrack with
// our current state of the buffer. i.e. is
// our buffer length longer than the last
// '..' we placed?
case len(b.B) > b.dd:
b.backtrack()
// b.cp = b.lp
// b.lp = 0
// If we reached here, need to check if
// we can append '..' to the path buffer,
// which is ONLY when path is NOT absolute
case !b.abs:
if len(b.B) > 0 {
b.appendByte('/')
}
b.appendByte('.')
b.appendByte('.')
b.dd = len(b.B)
// b.lp = lp - 2
// b.cp = b.dd
}
default:
if (b.abs && len(b.B) != 1) || (!b.abs && len(b.B) > 0) {
b.appendByte('/')
}
// b.lp = b.cp
// b.cp = len(b.B)
i += b.appendSlice(path[i:])
}
}
}
// Clean creates the shortest possible functional equivalent
// to the supplied path, resetting the builder before performing
// this operation. The builder object is NOT reset after return
func (b *Builder) Clean(path string) string {
b.Reset()
b.AppendString(path)
return b.String()
}
// Join connects and cleans multiple paths, resetting the builder before
// performing this operation and returning the shortest possible combination
// of all the supplied paths. The builder object is NOT reset after return
func (b *Builder) Join(base string, paths ...string) string {
b.Reset()
b.AppendString(base)
size := len(base)
for i := 0; i < len(paths); i++ {
b.AppendString(paths[i])
size += len(paths[i])
}
if size < 1 {
return ""
} else if len(b.B) < 1 {
return dotStr
}
return string(b.B)
}
// Guarantee ensures there is at least the requested size
// free bytes available in the buffer, reallocating if necessary
func (b *Builder) Guarantee(size int) {
if size > cap(b.B)-len(b.B) {
nb := make([]byte, 2*cap(b.B)+size)
copy(nb, b.B)
b.B = nb[:len(b.B)]
}
}
// Truncate reduces the length of the buffer by the requested
// number of bytes. If the builder is set to absolute, the first
// byte (i.e. '/') will never be truncated
func (b *Builder) Truncate(size int) {
// If absolute and just '/', do nothing
if b.abs && len(b.B) == 1 {
return
}
// Truncate requested bytes
b.truncate(size)
}
// truncate reduces the length of the buffer by the requested
// size, no sanity checks are performed
func (b *Builder) truncate(size int) {
b.B = b.B[:len(b.B)-size]
}
// appendByte appends the supplied byte to the end of
// the buffer. appending is achieved by continually reslicing the
// buffer and setting the next byte-at-index, this is safe as guarantee()
// will have been called beforehand
func (b *Builder) appendByte(c byte) {
b.B = b.B[:len(b.B)+1]
b.B[len(b.B)-1] = c
}
// appendSlice appends the supplied string slice to
// the end of the buffer and returns the number of indices
// we were able to iterate before hitting a path separator '/'.
// appending is achieved by continually reslicing the buffer
// and setting the next byte-at-index, this is safe as guarantee()
// will have been called beforehand
func (b *Builder) appendSlice(slice string) int {
i := 0
for i < len(slice) && slice[i] != '/' {
b.B = b.B[:len(b.B)+1]
b.B[len(b.B)-1] = slice[i]
i++
}
return i
}
// backtrack reduces the end of the buffer back to the last
// separating '/', or end of buffer
func (b *Builder) backtrack() {
b.B = b.B[:len(b.B)-1]
for len(b.B)-1 > b.dd && b.B[len(b.B)-1] != '/' {
b.B = b.B[:len(b.B)-1]
}
if len(b.B) > 0 {
b.B = b.B[:len(b.B)-1]
}
}