mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2024-11-23 12:16:38 +00:00
327 lines
7.3 KiB
Go
327 lines
7.3 KiB
Go
|
package fastpath
|
||
|
|
||
|
import (
|
||
|
"unsafe"
|
||
|
)
|
||
|
|
||
|
// Clean: see Builder.Clean(). Analogous to path.Clean().
|
||
|
func Clean(path string) string {
|
||
|
return (&Builder{}).Clean(path)
|
||
|
}
|
||
|
|
||
|
// Join: see Builder.Join(). Analogous to path.Join().
|
||
|
func Join(elems ...string) string {
|
||
|
return (&Builder{}).Join(elems...)
|
||
|
}
|
||
|
|
||
|
// Builder provides a means of cleaning and joining system paths,
|
||
|
// while retaining a singular underlying byte buffer for performance.
|
||
|
type Builder struct {
|
||
|
// B is the underlying byte buffer
|
||
|
B []byte
|
||
|
|
||
|
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()
|
||
|
}
|
||
|
|
||
|
// Reset resets the Builder object
|
||
|
func (b *Builder) Reset() {
|
||
|
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 {
|
||
|
return len(b.B)
|
||
|
}
|
||
|
|
||
|
// Cap returns the capacity of the underlying Builder buffer
|
||
|
func (b Builder) Cap() int {
|
||
|
return cap(b.B)
|
||
|
}
|
||
|
|
||
|
// Bytes returns the accumulated path bytes.
|
||
|
func (b Builder) Bytes() []byte {
|
||
|
return b.B
|
||
|
}
|
||
|
|
||
|
// String returns the accumulated path string.
|
||
|
func (b Builder) String() string {
|
||
|
return *(*string)(unsafe.Pointer(&b.B))
|
||
|
}
|
||
|
|
||
|
// Absolute returns whether current path is absolute (not relative).
|
||
|
func (b Builder) Absolute() bool {
|
||
|
return b.abs
|
||
|
}
|
||
|
|
||
|
// SetAbsolute converts the current path to-or-from absolute.
|
||
|
func (b *Builder) SetAbsolute(enabled bool) {
|
||
|
if !b.set {
|
||
|
// Ensure 1B avail
|
||
|
b.Guarantee(1)
|
||
|
|
||
|
if enabled {
|
||
|
// Set empty 'abs'
|
||
|
b.appendByte('/')
|
||
|
b.abs = true
|
||
|
} else {
|
||
|
// Set empty 'rel'
|
||
|
b.appendByte('.')
|
||
|
b.abs = false
|
||
|
}
|
||
|
|
||
|
b.set = true
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if !enabled && b.abs {
|
||
|
// set && absolute
|
||
|
// -> update
|
||
|
b.abs = false
|
||
|
|
||
|
// If empty, set to '.' (empty rel path)
|
||
|
if len(b.B) == 0 || (len(b.B) == 1 && b.B[0] == '/') {
|
||
|
b.Guarantee(1)
|
||
|
b.B = b.B[:1]
|
||
|
b.B[0] = '.'
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if b.B[0] != '/' {
|
||
|
// No need to change
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if len(b.B) > 1 {
|
||
|
// Shift bytes 1 left
|
||
|
copy(b.B, b.B[1:])
|
||
|
}
|
||
|
|
||
|
// and drop the '/' prefix'
|
||
|
b.B = b.B[:len(b.B)-1]
|
||
|
} else if enabled && !b.abs {
|
||
|
// set && !absolute
|
||
|
// -> update
|
||
|
b.abs = true
|
||
|
|
||
|
// Ensure 1B avail
|
||
|
b.Guarantee(1)
|
||
|
|
||
|
// If empty, set to '/' (empty abs path)
|
||
|
if len(b.B) == 0 || (len(b.B) == 1 && b.B[0] == '.') {
|
||
|
b.Guarantee(1)
|
||
|
b.B = b.B[:1]
|
||
|
b.B[0] = '/'
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Increase length
|
||
|
l := len(b.B)
|
||
|
b.B = b.B[:l+1]
|
||
|
|
||
|
// Shift bytes 1 right
|
||
|
copy(b.B[1:], b.B[:l])
|
||
|
|
||
|
// Set first byte '/'
|
||
|
b.B[0] = '/'
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// AppendBytes 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) AppendBytes(path []byte) {
|
||
|
if len(path) == 0 {
|
||
|
return
|
||
|
}
|
||
|
b.Guarantee(len(path) + 1)
|
||
|
b.append(*(*string)(unsafe.Pointer(&b)))
|
||
|
}
|
||
|
|
||
|
// Append 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) Append(path string) {
|
||
|
if len(path) == 0 {
|
||
|
return
|
||
|
}
|
||
|
b.Guarantee(len(path) + 1)
|
||
|
b.append(path)
|
||
|
}
|
||
|
|
||
|
// 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 {
|
||
|
if path == "" {
|
||
|
return "."
|
||
|
}
|
||
|
b.Reset()
|
||
|
b.Guarantee(len(path) + 1)
|
||
|
b.append(path)
|
||
|
return string(b.B)
|
||
|
}
|
||
|
|
||
|
// 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(elems ...string) string {
|
||
|
var size int
|
||
|
for _, elem := range elems {
|
||
|
size += len(elem)
|
||
|
}
|
||
|
if size == 0 {
|
||
|
return ""
|
||
|
}
|
||
|
b.Reset()
|
||
|
b.Guarantee(size + 1)
|
||
|
for _, elem := range elems {
|
||
|
if elem == "" {
|
||
|
continue
|
||
|
}
|
||
|
b.append(elem)
|
||
|
}
|
||
|
return string(b.B)
|
||
|
}
|
||
|
|
||
|
// append performs the main logic of 'Append()' but without an empty path check or preallocation.
|
||
|
func (b *Builder) append(path string) {
|
||
|
if !b.set {
|
||
|
// Set if absolute or not
|
||
|
b.abs = path[0] == '/'
|
||
|
b.set = true
|
||
|
} else if !b.abs && len(b.B) == 1 && b.B[0] == '.' {
|
||
|
// Empty non-abs path segment, drop
|
||
|
// the period so not prefixed './'
|
||
|
b.B = b.B[:0]
|
||
|
}
|
||
|
|
||
|
for i := 0; 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()
|
||
|
|
||
|
// 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)
|
||
|
}
|
||
|
|
||
|
default:
|
||
|
if (b.abs && len(b.B) != 1) || (!b.abs && len(b.B) > 0) {
|
||
|
// Append path separator
|
||
|
b.appendByte('/')
|
||
|
}
|
||
|
|
||
|
// Append slice up to next '/'
|
||
|
i += b.appendSlice(path[i:])
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if len(b.B) > 0 {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if b.abs {
|
||
|
// Empty absolute path => /
|
||
|
b.appendByte('/')
|
||
|
} else {
|
||
|
// Empty relative path => .
|
||
|
b.appendByte('.')
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// 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 byte slice is *effectively* empty,
|
||
|
// i.e. absolute and "/" or relative and ".", it won't be truncated.
|
||
|
func (b *Builder) Truncate(size int) {
|
||
|
if len(b.B) == 0 {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if len(b.B) == 1 && ((b.abs && b.B[0] == '/') ||
|
||
|
(!b.abs && b.B[0] == '.')) {
|
||
|
// *effectively* empty
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// Truncate requested bytes
|
||
|
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]
|
||
|
}
|
||
|
}
|