mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-06-17 12:28:57 -04:00
Bumps [github.com/olekukonko/tablewriter](https://github.com/olekukonko/tablewriter) from 1.1.0 to 1.1.1. - [Commits](https://github.com/olekukonko/tablewriter/compare/v1.1.0...v1.1.1) --- updated-dependencies: - dependency-name: github.com/olekukonko/tablewriter dependency-version: 1.1.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
377 lines
10 KiB
Go
377 lines
10 KiB
Go
package cat
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"unsafe"
|
|
)
|
|
|
|
// write writes a value to the given strings.Builder using fast paths to avoid temporary allocations.
|
|
// It handles common types like strings, byte slices, integers, floats, and booleans directly for efficiency.
|
|
// For other types, it falls back to fmt.Fprint, which may involve allocations.
|
|
// This function is optimized for performance in string concatenation scenarios, prioritizing
|
|
// common cases like strings and numbers at the top of the type switch for compiler optimization.
|
|
// Note: For integers and floats, it uses stack-allocated buffers and strconv.Append* functions to
|
|
// convert numbers to strings without heap allocations.
|
|
func write(b *strings.Builder, arg any) {
|
|
writeValue(b, arg, 0)
|
|
}
|
|
|
|
// writeValue appends the string representation of arg to b, handling recursion with a depth limit.
|
|
// It serves as a recursive helper for write, directly handling primitives and delegating complex
|
|
// types to writeReflect. The depth parameter prevents excessive recursion in deeply nested structures.
|
|
func writeValue(b *strings.Builder, arg any, depth int) {
|
|
// Handle recursion depth limit
|
|
if depth > maxRecursionDepth {
|
|
b.WriteString("...")
|
|
return
|
|
}
|
|
|
|
// Handle nil values
|
|
if arg == nil {
|
|
b.WriteString(nilString)
|
|
return
|
|
}
|
|
|
|
// Fast path type switch for all primitive types
|
|
switch v := arg.(type) {
|
|
case string:
|
|
b.WriteString(v)
|
|
case []byte:
|
|
b.WriteString(bytesToString(v))
|
|
case int:
|
|
var buf [20]byte
|
|
b.Write(strconv.AppendInt(buf[:0], int64(v), 10))
|
|
case int64:
|
|
var buf [20]byte
|
|
b.Write(strconv.AppendInt(buf[:0], v, 10))
|
|
case int32:
|
|
var buf [11]byte
|
|
b.Write(strconv.AppendInt(buf[:0], int64(v), 10))
|
|
case int16:
|
|
var buf [6]byte
|
|
b.Write(strconv.AppendInt(buf[:0], int64(v), 10))
|
|
case int8:
|
|
var buf [4]byte
|
|
b.Write(strconv.AppendInt(buf[:0], int64(v), 10))
|
|
case uint:
|
|
var buf [20]byte
|
|
b.Write(strconv.AppendUint(buf[:0], uint64(v), 10))
|
|
case uint64:
|
|
var buf [20]byte
|
|
b.Write(strconv.AppendUint(buf[:0], v, 10))
|
|
case uint32:
|
|
var buf [10]byte
|
|
b.Write(strconv.AppendUint(buf[:0], uint64(v), 10))
|
|
case uint16:
|
|
var buf [5]byte
|
|
b.Write(strconv.AppendUint(buf[:0], uint64(v), 10))
|
|
case uint8:
|
|
var buf [3]byte
|
|
b.Write(strconv.AppendUint(buf[:0], uint64(v), 10))
|
|
case float64:
|
|
var buf [24]byte
|
|
b.Write(strconv.AppendFloat(buf[:0], v, 'f', -1, 64))
|
|
case float32:
|
|
var buf [24]byte
|
|
b.Write(strconv.AppendFloat(buf[:0], float64(v), 'f', -1, 32))
|
|
case bool:
|
|
if v {
|
|
b.WriteString("true")
|
|
} else {
|
|
b.WriteString("false")
|
|
}
|
|
case fmt.Stringer:
|
|
b.WriteString(v.String())
|
|
case error:
|
|
b.WriteString(v.Error())
|
|
default:
|
|
// Fallback to reflection-based handling
|
|
writeReflect(b, arg, depth)
|
|
}
|
|
}
|
|
|
|
// writeReflect handles all complex types safely.
|
|
func writeReflect(b *strings.Builder, arg any, depth int) {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
b.WriteString("[!reflect panic!]")
|
|
}
|
|
}()
|
|
|
|
val := reflect.ValueOf(arg)
|
|
if val.Kind() == reflect.Ptr {
|
|
if val.IsNil() {
|
|
b.WriteString(nilString)
|
|
return
|
|
}
|
|
val = val.Elem()
|
|
}
|
|
|
|
switch val.Kind() {
|
|
case reflect.Slice, reflect.Array:
|
|
b.WriteByte('[')
|
|
for i := 0; i < val.Len(); i++ {
|
|
if i > 0 {
|
|
b.WriteString(", ") // Use comma-space for readability
|
|
}
|
|
writeValue(b, val.Index(i).Interface(), depth+1)
|
|
}
|
|
b.WriteByte(']')
|
|
|
|
case reflect.Struct:
|
|
typ := val.Type()
|
|
b.WriteByte('{') // Use {} for structs to follow Go convention
|
|
first := true
|
|
for i := 0; i < val.NumField(); i++ {
|
|
fieldValue := val.Field(i)
|
|
if !fieldValue.CanInterface() {
|
|
continue // Skip unexported fields
|
|
}
|
|
if !first {
|
|
b.WriteByte(' ') // Use space as separator
|
|
}
|
|
first = false
|
|
b.WriteString(typ.Field(i).Name)
|
|
b.WriteByte(':')
|
|
|
|
writeValue(b, fieldValue.Interface(), depth+1)
|
|
}
|
|
b.WriteByte('}')
|
|
|
|
case reflect.Map:
|
|
b.WriteByte('{')
|
|
keys := val.MapKeys()
|
|
sort.Slice(keys, func(i, j int) bool {
|
|
// A simple string-based sort for keys
|
|
return fmt.Sprint(keys[i].Interface()) < fmt.Sprint(keys[j].Interface())
|
|
})
|
|
for i, key := range keys {
|
|
if i > 0 {
|
|
b.WriteByte(' ') // Use space as separator
|
|
}
|
|
writeValue(b, key.Interface(), depth+1)
|
|
b.WriteByte(':')
|
|
writeValue(b, val.MapIndex(key).Interface(), depth+1)
|
|
}
|
|
b.WriteByte('}')
|
|
|
|
case reflect.Interface:
|
|
if val.IsNil() {
|
|
b.WriteString(nilString)
|
|
return
|
|
}
|
|
writeValue(b, val.Elem().Interface(), depth+1)
|
|
|
|
default:
|
|
fmt.Fprint(b, arg)
|
|
}
|
|
}
|
|
|
|
// valueToString converts any value to a string representation.
|
|
// It uses optimized paths for common types to avoid unnecessary allocations.
|
|
// For types like integers and floats, it directly uses strconv functions.
|
|
// This function is useful for single-argument conversions or as a helper in other parts of the package.
|
|
// Unlike write, it returns a string instead of appending to a builder.
|
|
func valueToString(arg any) string {
|
|
switch v := arg.(type) {
|
|
case string:
|
|
return v
|
|
case []byte:
|
|
return bytesToString(v)
|
|
case int:
|
|
return strconv.Itoa(v)
|
|
case int64:
|
|
return strconv.FormatInt(v, 10)
|
|
case int32:
|
|
return strconv.FormatInt(int64(v), 10)
|
|
case uint:
|
|
return strconv.FormatUint(uint64(v), 10)
|
|
case uint64:
|
|
return strconv.FormatUint(v, 10)
|
|
case float64:
|
|
return strconv.FormatFloat(v, 'f', -1, 64)
|
|
case bool:
|
|
if v {
|
|
return "true"
|
|
}
|
|
return "false"
|
|
case fmt.Stringer:
|
|
return v.String()
|
|
case error:
|
|
return v.Error()
|
|
default:
|
|
return fmt.Sprint(v)
|
|
}
|
|
}
|
|
|
|
// estimateWith calculates a conservative estimate of the total string length when concatenating
|
|
// the given arguments with a separator. This is used for preallocating capacity in strings.Builder
|
|
// to minimize reallocations during building.
|
|
// It accounts for the length of separators and estimates the length of each argument based on its type.
|
|
// If no arguments are provided, it returns 0.
|
|
func estimateWith(sep string, args []any) int {
|
|
if len(args) == 0 {
|
|
return 0
|
|
}
|
|
size := len(sep) * (len(args) - 1)
|
|
size += estimate(args)
|
|
return size
|
|
}
|
|
|
|
// estimate calculates a conservative estimate of the combined string length of the given arguments.
|
|
// It iterates over each argument and adds an estimated length based on its type:
|
|
// - Strings and byte slices: exact length.
|
|
// - Numbers: calculated digit count using numLen or uNumLen.
|
|
// - Floats and others: fixed conservative estimates (e.g., 16 or 24 bytes).
|
|
// This helper is used internally by estimateWith and focuses solely on the arguments without separators.
|
|
func estimate(args []any) int {
|
|
var size int
|
|
for _, a := range args {
|
|
switch v := a.(type) {
|
|
case string:
|
|
size += len(v)
|
|
case []byte:
|
|
size += len(v)
|
|
case int:
|
|
size += numLen(int64(v))
|
|
case int8:
|
|
size += numLen(int64(v))
|
|
case int16:
|
|
size += numLen(int64(v))
|
|
case int32:
|
|
size += numLen(int64(v))
|
|
case int64:
|
|
size += numLen(v)
|
|
case uint:
|
|
size += uNumLen(uint64(v))
|
|
case uint8:
|
|
size += uNumLen(uint64(v))
|
|
case uint16:
|
|
size += uNumLen(uint64(v))
|
|
case uint32:
|
|
size += uNumLen(uint64(v))
|
|
case uint64:
|
|
size += uNumLen(v)
|
|
case float32:
|
|
size += 16
|
|
case float64:
|
|
size += 24
|
|
case bool:
|
|
size += 5 // "false"
|
|
case fmt.Stringer, error:
|
|
size += 16 // conservative
|
|
default:
|
|
size += 16 // conservative
|
|
}
|
|
}
|
|
return size
|
|
}
|
|
|
|
// numLen returns the number of characters required to represent the signed integer n as a string.
|
|
// It handles negative numbers by adding 1 for the '-' sign and uses a loop to count digits.
|
|
// Special handling for math.MinInt64 to avoid overflow when negating.
|
|
// Returns 1 for 0, and up to 20 for the largest values.
|
|
func numLen(n int64) int {
|
|
if n == 0 {
|
|
return 1
|
|
}
|
|
c := 0
|
|
if n < 0 {
|
|
c = 1 // for '-'
|
|
// NOTE: math.MinInt64 negated overflows; handle by adding one digit and returning 20.
|
|
if n == -1<<63 {
|
|
return 20
|
|
}
|
|
n = -n
|
|
}
|
|
for n > 0 {
|
|
n /= 10
|
|
c++
|
|
}
|
|
return c
|
|
}
|
|
|
|
// uNumLen returns the number of characters required to represent the unsigned integer n as a string.
|
|
// It uses a loop to count digits.
|
|
// Returns 1 for 0, and up to 20 for the largest uint64 values.
|
|
func uNumLen(n uint64) int {
|
|
if n == 0 {
|
|
return 1
|
|
}
|
|
c := 0
|
|
for n > 0 {
|
|
n /= 10
|
|
c++
|
|
}
|
|
return c
|
|
}
|
|
|
|
// bytesToString converts a byte slice to a string efficiently.
|
|
// If the package's UnsafeBytes flag is set (via IsUnsafeBytes()), it uses unsafe operations
|
|
// to create a string backed by the same memory as the byte slice, avoiding a copy.
|
|
// This is zero-allocation when unsafe is enabled.
|
|
// Falls back to standard string(bts) conversion otherwise.
|
|
// For empty slices, it returns a constant empty string.
|
|
// Compatible with Go 1.20+ unsafe functions like unsafe.String and unsafe.SliceData.
|
|
func bytesToString(bts []byte) string {
|
|
if len(bts) == 0 {
|
|
return empty
|
|
}
|
|
if IsUnsafeBytes() {
|
|
// Go 1.20+: unsafe.String with SliceData (1.20 introduced, 1.22 added SliceData).
|
|
return unsafe.String(unsafe.SliceData(bts), len(bts))
|
|
}
|
|
return string(bts)
|
|
}
|
|
|
|
// recursiveEstimate calculates the estimated string length for potentially nested arguments,
|
|
// including the lengths of separators between elements. It recurses on nested []any slices,
|
|
// flattening the structure while accounting for separators only between non-empty subparts.
|
|
// This function is useful for preallocating capacity in builders for nested concatenation operations.
|
|
func recursiveEstimate(sep string, args []any) int {
|
|
if len(args) == 0 {
|
|
return 0
|
|
}
|
|
size := 0
|
|
needsSep := false
|
|
for _, a := range args {
|
|
switch v := a.(type) {
|
|
case []any:
|
|
subSize := recursiveEstimate(sep, v)
|
|
if subSize > 0 {
|
|
if needsSep {
|
|
size += len(sep)
|
|
}
|
|
size += subSize
|
|
needsSep = true
|
|
}
|
|
default:
|
|
if needsSep {
|
|
size += len(sep)
|
|
}
|
|
size += estimate([]any{a})
|
|
needsSep = true
|
|
}
|
|
}
|
|
return size
|
|
}
|
|
|
|
// recursiveAdd appends the string representations of potentially nested arguments to the builder.
|
|
// It recurses on nested []any slices, effectively flattening the structure by adding leaf values
|
|
// directly via b.Add without inserting separators (separators are handled externally if needed).
|
|
// This function is designed for efficient concatenation of nested argument lists.
|
|
func recursiveAdd(b *Builder, args []any) {
|
|
for _, a := range args {
|
|
switch v := a.(type) {
|
|
case []any:
|
|
recursiveAdd(b, v)
|
|
default:
|
|
b.Add(a)
|
|
}
|
|
}
|
|
}
|