mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-31 09:42:26 -05: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] | ||
|  | 	} | ||
|  | } |