pax_global_header00006660000000000000000000000064150725727310014523gustar00rootroot0000000000000052 comment=76ed661cdc046f5daa7cf1afd50d3ba2b86150d0 golang-github-olekukonko-cat-0.0~git20250911.50322a0/000077500000000000000000000000001507257273100214565ustar00rootroot00000000000000golang-github-olekukonko-cat-0.0~git20250911.50322a0/.gitignore000066400000000000000000000000211507257273100234370ustar00rootroot00000000000000.idea .github labgolang-github-olekukonko-cat-0.0~git20250911.50322a0/LICENSE000066400000000000000000000020541507257273100224640ustar00rootroot00000000000000MIT License Copyright (c) 2025 Oleku Konko Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. golang-github-olekukonko-cat-0.0~git20250911.50322a0/README.md000066400000000000000000000100001507257273100227240ustar00rootroot00000000000000# 🐱 `cat` - The Fast & Fluent String Concatenation Library for Go > **"Because building strings shouldn't feel like herding cats"** 😼 ## Why `cat`? Go's `strings.Builder` is great, but building complex strings often feels clunky. `cat` makes string concatenation: - **Faster** - Optimized paths for common types, zero-allocation conversions - **Fluent** - Chainable methods for beautiful, readable code - **Flexible** - Handles any type, nested structures, and custom formatting - **Smart** - Automatic pooling, size estimation, and separator handling ```go // Without cat var b strings.Builder b.WriteString("Hello, ") b.WriteString(user.Name) b.WriteString("! You have ") b.WriteString(strconv.Itoa(count)) b.WriteString(" new messages.") result := b.String() // With cat result := cat.Concat("Hello, ", user.Name, "! You have ", count, " new messages.") ``` ## πŸ”₯ Hot Features ### 1. Fluent Builder API Build strings like a boss with method chaining: ```go s := cat.New(", "). Add("apple"). If(user.IsVIP, "golden kiwi"). Add("orange"). Sep(" | "). // Change separator mid-way Add("banana"). String() // "apple, golden kiwi, orange | banana" ``` ### 2. Zero-Allocation Magic - **Pooled builders** (optional) reduce GC pressure - **Unsafe byte conversions** (opt-in) avoid `[]byte`β†’`string` copies - **Stack buffers** for numbers instead of heap allocations ```go // Enable performance features cat.Pool(true) // Builder pooling cat.SetUnsafeBytes(true) // Zero-copy []byte conversion ``` ### 3. Handles Any Type - Even Nested Ones! No more manual type conversions: ```go data := map[string]any{ "id": 12345, "tags": []string{"go", "fast", "efficient"}, } fmt.Println(cat.JSONPretty(data)) // { // "id": 12345, // "tags": ["go", "fast", "efficient"] // } ``` ### 4. Concatenation for Every Use Case ```go // Simple joins cat.With(", ", "apple", "banana", "cherry") // "apple, banana, cherry" // File paths cat.Path("dir", "sub", "file.txt") // "dir/sub/file.txt" // CSV cat.CSV(1, 2, 3) // "1,2,3" // Conditional elements cat.Start("Hello").If(user != nil, " ", user.Name) // "Hello" or "Hello Alice" // Repeated patterns cat.RepeatWith("-+", "X", 3) // "X-+X-+X" ``` ### 5. Smarter Than Your Average String Lib ```go // Automatic nesting handling nested := []any{"a", []any{"b", "c"}, "d"} cat.FlattenWith(",", nested) // "a,b,c,d" // Precise size estimation (minimizes allocations) b := cat.New(", ").Grow(estimatedSize) // Preallocate exactly what you need // Reflection support for any type cat.Reflect(anyComplexStruct) // "{Field1:value Field2:[1 2 3]}" ``` ## πŸš€ Getting Started ```bash go get github.com/your-repo/cat ``` ```go import "github.com/your-repo/cat" func main() { // Simple concatenation msg := cat.Concat("User ", userID, " has ", count, " items") // Pooled builder (for high-performance loops) builder := cat.New(", ") defer builder.Release() // Return to pool result := builder.Add(items...).String() } ``` ## πŸ€” Why Not Just Use...? - `fmt.Sprintf` - Slow, many allocations - `strings.Join` - Only works with strings - `bytes.Buffer` - No separator support, manual type handling - `string +` - Even worse performance, especially in loops ## πŸ’‘ Pro Tips 1. **Enable pooling** in high-throughput scenarios 2. **Preallocate** with `.Grow()` when you know the final size 3. Use **`If()`** for conditional elements in fluent chains 4. Try **`SetUnsafeBytes(true)`** if you can guarantee byte slices won't mutate 5. **Release builders** when pooling is enabled ## πŸ±β€πŸ‘€ Advanced Usage ```go // Custom value formatting type User struct { Name string Age int } func (u User) String() string { return cat.With(" ", u.Name, cat.Wrap("(", u.Age, ")")) } // JSON-like output func JSONPretty(v any) string { return cat.WrapWith(",\n ", "{\n ", "\n}", prettyFields(v)) } ``` ```text /\_/\ ( o.o ) > Concatenate with purr-fection! > ^ < ``` **`cat`** - Because life's too short for ugly string building code. 😻golang-github-olekukonko-cat-0.0~git20250911.50322a0/builder.go000066400000000000000000000112241507257273100234330ustar00rootroot00000000000000package cat import ( "strings" ) // Builder is a fluent concatenation helper. It is safe for concurrent use by // multiple goroutines only if each goroutine uses a distinct *Builder. // If pooling is enabled via Pool(true), call Release() when done. // The Builder uses an internal strings.Builder for efficient string concatenation // and manages a separator that is inserted between added values. // It supports chaining methods for a fluent API style. type Builder struct { buf strings.Builder sep string needsSep bool } // New begins a new Builder with a separator. If pooling is enabled, // the Builder is reused and MUST be released with b.Release() when done. // If sep is empty, uses DefaultSep(). // Optional initial arguments x are added immediately after creation. // Pooling is controlled globally via Pool(true/false); when enabled, Builders // are recycled to reduce allocations in high-throughput scenarios. func New(sep string, x ...any) *Builder { var b *Builder if poolEnabled.Load() { b = builderPool.Get().(*Builder) b.buf.Reset() b.sep = sep b.needsSep = false } else { b = &Builder{sep: sep} } // Process initial arguments *after* the builder is prepared. if len(x) > 0 { b.Add(x...) } return b } // Start begins a new Builder with no separator (using an empty string as sep). // It is a convenience function that wraps New(empty, x...), where empty is a constant empty string. // This allows starting a concatenation without any separator between initial or subsequent additions. // If pooling is enabled via Pool(true), the returned Builder MUST be released with b.Release() when done. // Optional variadic arguments x are passed directly to New and added immediately after creation. // Useful for fluent chains where no default separator is desired from the start. func Start(x ...any) *Builder { return New(empty, x...) } // Grow pre-sizes the internal buffer. // This can be used to preallocate capacity based on an estimated total size, // reducing reallocations during subsequent Add calls. // It chains, returning the Builder for fluent use. func (b *Builder) Grow(n int) *Builder { b.buf.Grow(n); return b } // Add appends values to the builder. // It inserts the current separator before each new value if needed (i.e., after the first addition). // Values are converted to strings using the optimized write function, which handles // common types efficiently without allocations where possible. // Supports any number of arguments of any type. // Chains, returning the Builder for fluent use. func (b *Builder) Add(args ...any) *Builder { for _, arg := range args { if b.needsSep && b.sep != empty { b.buf.WriteString(b.sep) } write(&b.buf, arg) b.needsSep = true } return b } // If appends values to the builder only if the condition is true. // Behaves like Add when condition is true; does nothing otherwise. // Useful for conditional concatenation in chains. // Chains, returning the Builder for fluent use. func (b *Builder) If(condition bool, args ...any) *Builder { if condition { b.Add(args...) } return b } // Sep changes the separator for subsequent additions. // Future Add calls will use this new separator. // Does not affect already added content. // If sep is empty, no separator will be added between future values. // Chains, returning the Builder for fluent use. func (b *Builder) Sep(sep string) *Builder { b.sep = sep; return b } // String returns the concatenated result. // This does not release the Builder; if pooling is enabled, call Release separately // if you are done with the Builder. // Can be called multiple times; the internal buffer remains unchanged. func (b *Builder) String() string { return b.buf.String() } // Output returns the concatenated result and releases the Builder if pooling is enabled. // This is a convenience method to get the string and clean up in one call. // After Output, the Builder should not be used further if pooled, as it may be recycled. // If pooling is disabled, it behaves like String without release. func (b *Builder) Output() string { out := b.buf.String() b.Release() // Release takes care of the poolEnabled check return out } // Release returns the Builder to the pool if pooling is enabled. // You should call this exactly once per New() when Pool(true) is active. // Resets the internal state (buffer, separator, needsSep) before pooling to avoid // retaining data or large allocations. // If pooling is disabled, this is a no-op. // Safe to call multiple times, but typically called once at the end of use. func (b *Builder) Release() { if poolEnabled.Load() { // Avoid retaining large buffers. b.buf.Reset() b.sep = empty b.needsSep = false builderPool.Put(b) } } golang-github-olekukonko-cat-0.0~git20250911.50322a0/cat.go000066400000000000000000000121621507257273100225560ustar00rootroot00000000000000// Package cat provides efficient and flexible string concatenation utilities. // It includes optimized functions for concatenating various types, builders for fluent chaining, // and configuration options for defaults, pooling, and unsafe optimizations. // The package aims to minimize allocations and improve performance in string building scenarios. package cat import ( "sync" "sync/atomic" ) // Constants used throughout the package for separators, defaults, and configuration. // These include common string literals for separators, empty strings, and special representations, // as well as limits like recursion depth. Defining them as constants allows for compile-time // optimizations, readability, and consistent usage in functions like Space, Path, CSV, and reflection handlers. // cat.go (updated constants section) const ( empty = "" // Empty string constant, used for checks and defaults. space = " " // Single space, default separator. slash = "/" // Forward slash, for paths. dot = "." // Period, for extensions or decimals. comma = "," // Comma, for CSV or lists. equal = "=" // Equals, for comparisons. newline = "\n" // Newline, for multi-line strings. // SQL-specific constants and = "AND" // AND operator, for SQL conditions. inOpen = " IN (" // Opening for SQL IN clause inClose = ")" // Closing for SQL IN clause asSQL = " AS " // SQL AS for aliasing count = "COUNT(" // SQL COUNT function prefix sum = "SUM(" // SQL SUM function prefix avg = "AVG(" // SQL AVG function prefix maxOpen = "MAX(" // SQL MAX function prefix minOpen = "MIN(" // SQL MIN function prefix caseSQL = "CASE " // SQL CASE keyword when = "WHEN " // SQL WHEN clause then = " THEN " // SQL THEN clause elseSQL = " ELSE " // SQL ELSE clause end = " END" // SQL END for CASE countAll = "COUNT(*)" // SQL COUNT(*) for all rows parenOpen = "(" // Opening parenthesis parenClose = ")" // Closing parenthesis maxRecursionDepth = 32 // Maximum recursion depth for nested structure handling. nilString = "" // String representation for nil values. unexportedString = "" // Placeholder for unexported fields. ) // Numeric is a generic constraint interface for numeric types. // It includes all signed/unsigned integers and floats. // Used in generic functions like Number and NumberWith to constrain to numbers. type Numeric interface { ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~float32 | ~float64 } // poolEnabled controls whether New() reuses Builder instances from a pool. // Atomic.Bool for thread-safe toggle. // When true, Builders from New must be Released to avoid leaks. var poolEnabled atomic.Bool // builderPool stores reusable *Builder to reduce GC pressure on hot paths. // Uses sync.Pool for efficient allocation/reuse. // New func creates a fresh &Builder when pool is empty. var builderPool = sync.Pool{ New: func() any { return &Builder{} }, } // Pool enables or disables Builder pooling for New()/Release(). // When enabled, you MUST call b.Release() after b.String() to return it. // Thread-safe via atomic.Store. // Enable for high-throughput scenarios to reduce allocations. func Pool(enable bool) { poolEnabled.Store(enable) } // unsafeBytesFlag controls zero-copy []byte -> string behavior via atomics. // Int32 used for atomic operations: 1 = enabled, 0 = disabled. // Affects bytesToString function for zero-copy conversions using unsafe. var unsafeBytesFlag atomic.Int32 // 1 = true, 0 = false // SetUnsafeBytes toggles zero-copy []byte -> string conversions globally. // When enabled, bytesToString uses unsafe.String for zero-allocation conversion. // Thread-safe via atomic.Store. // Use with caution: assumes the byte slice is not modified after conversion. // Compatible with Go 1.20+; fallback to string(bts) if disabled. func SetUnsafeBytes(enable bool) { if enable { unsafeBytesFlag.Store(1) } else { unsafeBytesFlag.Store(0) } } // IsUnsafeBytes reports whether zero-copy []byte -> string is enabled. // Thread-safe via atomic.Load. // Returns true if flag is 1, false otherwise. // Useful for checking current configuration. func IsUnsafeBytes() bool { return unsafeBytesFlag.Load() == 1 } // deterministicMaps controls whether map keys are sorted for deterministic output in string conversions. // It uses atomic.Bool for thread-safe access. var deterministicMaps atomic.Bool // SetDeterministicMaps controls whether map keys are sorted for deterministic output // in reflection-based handling (e.g., in writeReflect for maps). // When enabled, keys are sorted using a string-based comparison for consistent string representations. // Thread-safe via atomic.Store. // Useful for reproducible outputs in testing or logging. func SetDeterministicMaps(enable bool) { deterministicMaps.Store(enable) } // IsDeterministicMaps returns current map sorting setting. // Thread-safe via atomic.Load. // Returns true if deterministic sorting is enabled, false otherwise. func IsDeterministicMaps() bool { return deterministicMaps.Load() } golang-github-olekukonko-cat-0.0~git20250911.50322a0/cat_benchmark_test.go000066400000000000000000000064451507257273100256360ustar00rootroot00000000000000package cat import ( "fmt" "strconv" "strings" "testing" ) // sample args var ( strArgs = []string{"test", "123", "true", "4.56"} anyArgs = []any{"test", 123, true, 4.56} ) // ---------- Existing / package APIs ---------- func BenchmarkJoin_MixedAny(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { _ = Join(strArgs...) } } func BenchmarkConcat_MixedAny(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { _ = Concat(anyArgs...) } } func BenchmarkConcat_StringsAsAny(b *testing.B) { b.ReportAllocs() anyStrs := make([]any, len(strArgs)) for i, s := range strArgs { anyStrs[i] = s } for i := 0; i < b.N; i++ { _ = Concat(anyStrs...) } } func BenchmarkWith_FastPath(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { _ = With(" ", "GET", "/v1/resource", 200, 1234) } } // ---------- Baselines / stdlib ---------- func BenchmarkFmtSprint(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { _ = fmt.Sprint("GET", " ", "/v1/resource", " ", 200, " ", 1234) } } func BenchmarkStringsJoin(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { _ = strings.Join(strArgs, " ") } } // ---------- strings.Builder variants (manual) ---------- func BenchmarkBuilder_Manual_NoGrow(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { var sb strings.Builder sb.WriteString("GET") sb.WriteString(" ") sb.WriteString("/v1/resource") sb.WriteString(" ") sb.WriteString(strconv.Itoa(200)) sb.WriteString(" ") sb.WriteString(strconv.Itoa(1234)) _ = sb.String() } } func BenchmarkBuilder_Manual_Grow(b *testing.B) { b.ReportAllocs() // Rough size: len("GET /v1/resource 200 1234") = ~26 size := 26 for i := 0; i < b.N; i++ { var sb strings.Builder sb.Grow(size) sb.WriteString("GET") sb.WriteString(" ") sb.WriteString("/v1/resource") sb.WriteString(" ") sb.WriteString(strconv.Itoa(200)) sb.WriteString(" ") sb.WriteString(strconv.Itoa(1234)) _ = sb.String() } } func BenchmarkBuilder_FmtFprint(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { var sb strings.Builder fmt.Fprint(&sb, "GET", " ", "/v1/resource", " ", 200, " ", 1234) _ = sb.String() } } // ---------- Specialized fast paths in package ---------- func BenchmarkStrs_Specialized(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { _ = JoinWith(" ", strArgs...) } } func BenchmarkPair_2Args(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { _ = PairWith("-", "foo", "bar") } } func BenchmarkTrio_3Args(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { _ = TrioWith(":", "a", "b", "c") } } // ---------- Builder pooling & unsafe bytes ---------- func BenchmarkBuilder_NoPool(b *testing.B) { Pool(false) b.ReportAllocs() for i := 0; i < b.N; i++ { buf := New(" ") buf.Add("GET", "/v1/resource", 200, 1234) _ = buf.String() buf.Release() } } func BenchmarkBuilder_Pool(b *testing.B) { Pool(true) defer Pool(false) b.ReportAllocs() for i := 0; i < b.N; i++ { buf := New(" ") buf.Add("GET", "/v1/resource", 200, 1234) _ = buf.String() buf.Release() } } func BenchmarkWith_BytesUnsafe(b *testing.B) { SetUnsafeBytes(true) defer SetUnsafeBytes(false) m := []byte("GET") p := []byte("/v1/resource") b.ReportAllocs() for i := 0; i < b.N; i++ { _ = With(" ", m, p, 200, 1234) } } golang-github-olekukonko-cat-0.0~git20250911.50322a0/cat_test.go000066400000000000000000000245461507257273100236260ustar00rootroot00000000000000package cat import ( "errors" "fmt" "runtime" "strings" "sync" "testing" ) type sType string func (s sType) String() string { return "S(" + string(s) + ")" } func TestJoin(t *testing.T) { t.Parallel() tests := []struct { name string args []any want string }{ {"Empty", nil, ""}, {"Strings", []any{"a", "b"}, "ab"}, {"Mixed", []any{1, ":", true}, "1:true"}, {"Stringer", []any{sType("x")}, "S(x)"}, {"Error", []any{errors.New("boom")}, "boom"}, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() if got := Concat(tt.args...); got != tt.want { t.Errorf("Join() = %q, want %q", got, tt.want) } }) } } func TestWithAndSeparators(t *testing.T) { t.Parallel() if got := With("-", 1, 2, 3); got != "1-2-3" { t.Fatalf("With() = %q", got) } if got := Space("a", "b", "c"); got != "a b c" { t.Fatalf("Space() = %q", got) } if got := CSV("a", "b", "c"); got != "a,b,c" { t.Fatalf("CSV() = %q", got) } if got := Comma("a", "b"); got != "a, b" { t.Fatalf("Comma() = %q", got) } if got := Path("usr", "local", "bin"); got != "usr/local/bin" { t.Fatalf("Path() = %q", got) } if got := Lines("x", "y", "z"); got != "x\ny\nz" { t.Fatalf("Lines() = %q", got) } if got := Quote("a", 1, true); got != `"a" "1" "true"` { t.Fatalf("Quote() = %q", got) } } func TestWrap(t *testing.T) { t.Parallel() tests := []struct { name string before string after string args []any expected string }{ {"Empty", "<", ">", nil, "<>"}, {"Single", "[", "]", []any{"test"}, "[test]"}, {"MultiNoSep", "(", ")", []any{1, 2, 3}, "(123)"}, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() got := Wrap(tt.before, tt.after, tt.args...) if got != tt.expected { t.Errorf("Wrap() = %q, want %q", got, tt.expected) } }) } } func TestWrapEach(t *testing.T) { t.Parallel() tests := []struct { name string before string after string args []any expected string }{ {"Numbers", "<", ">", []any{1, 2, 3}, "<1><2><3>"}, {"Mixed", "'", "'", []any{"a", 1, true}, "'a''1''true'"}, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() got := WrapEach(tt.before, tt.after, tt.args...) if got != tt.expected { t.Errorf("WrapEach() = %q, want %q", got, tt.expected) } }) } } func TestWrapWithSep(t *testing.T) { t.Parallel() got := WrapWith(",", "[", "]", 1, 2, 3) if got != "[1,2,3]" { t.Fatalf("WrapWith: %q", got) } got = WrapWith(" | ", "<", ">", "a", "b") if got != "" { t.Fatalf("WrapWith 2: %q", got) } } func TestBetween(t *testing.T) { t.Parallel() got := BetweenWith(",", "START", "END", 1, 2, 3) if got != "START,1,2,3,END" { t.Fatalf("Between: %q", got) } } func TestPrefixSuffix(t *testing.T) { t.Parallel() if got := PrefixWith(" ", "P:", 1, 2); got != "P: 1 2" { t.Fatalf("Prefix: %q", got) } if got := SuffixWith(" ", ":S", 1, 2); got != "1 2 :S" { t.Fatalf("Suffix: %q", got) } if got := PrefixEach("pre-", ",", "a", "b", "c"); got != "pre-a,pre-b,pre-c" { t.Fatalf("PrefixEach: %q", got) } if got := SuffixEach("-s", " | ", "a", "b"); got != "a-s | b-s" { t.Fatalf("SuffixEach: %q", got) } } func TestIndent(t *testing.T) { t.Parallel() if got := Indent(0, "x", "y"); got != "xy" { t.Fatalf("Indent depth 0: %q", got) } if got := Indent(2, "x"); got != " x" { t.Fatalf("Indent depth 2: %q", got) } } func TestFlatten(t *testing.T) { t.Parallel() tests := []struct { name string sep string // The signature of FlattenWith is now `...any`, so the test data should be `[]any`. groups []any want string }{ {"Numbers", ",", []any{[]any{1, 2}, []any{3, 4}}, "1,2,3,4"}, {"WithEmptyGroups", "-", []any{[]any{"a"}, []any{}, []any{"b", "c"}, []any{}}, "a-b-c"}, {"AllEmpty", ",", []any{[]any{}, []any{}}, ""}, {"SingleGroupNoSep", ",", []any{[]any{"x"}}, "x"}, {"MultipleGroupsMultiElem", "|", []any{[]any{"x", "y"}, []any{"z"}}, "x|y|z"}, {"DeeplyNested", ".", []any{1, []any{2, []any{3, 4}}, 5}, "1.2.3.4.5"}, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() // We must use the `...` spread operator to pass the elements of the slice // as individual arguments to the variadic function. if got := FlattenWith(tt.sep, tt.groups...); got != tt.want { t.Errorf("FlattenWith() = %q, want %q", got, tt.want) } }) } } func TestBuilderBasic(t *testing.T) { t.Parallel() b := New(",") defer b.Release() // ok even if Pool(false) b.Add(1).Add(2) if got := b.String(); got != "1,2" { t.Fatalf("Builder String: %q", got) } // Sep and AddIf b2 := New(" ") defer b2.Release() b2.Add("hi").Sep("-").If(false, "no").If(true, "there") if got := b2.String(); got != "hi-there" { t.Fatalf("Builder AddIf/Sep: %q", got) } // Grow shouldn’t break anything b3 := New("|").Grow(64).Add("a", "b", "c") defer b3.Release() if got := b3.String(); got != "a|b|c" { t.Fatalf("Builder Grow/Add: %q", got) } // StartBetween b4 := New(":", "A").Add("B", "C") defer b4.Release() if got := b4.String(); got != "A:B:C" { t.Fatalf("StartBetween: %q", got) } } func TestBuilderPooling(t *testing.T) { t.Parallel() // Off by default: New returns a fresh builder. Pool(false) b1 := New(",") if b1 == nil { t.Fatal("New returned nil") } b1.Add(1, 2) out1 := b1.String() b1.Release() // no-op if out1 != "1,2" { t.Fatalf("Pool(false) output: %q", out1) } // Enable pool and ensure Release resets state. Pool(true) b2 := New(" ") b2.Add("x", "y") if got := b2.String(); got != "x y" { t.Fatalf("pooled builder out: %q", got) } b2.Release() // Re-acquire and ensure previous content is not retained. b3 := New(",") if got := b3.String(); got != "" { t.Fatalf("pooled builder should be reset, got %q", got) } b3.Add("a") if got := b3.String(); got != "a" { t.Fatalf("pooled builder after reuse: %q", got) } b3.Release() } func TestUnsafeBytesToggle(t *testing.T) { t.Parallel() // Ensure default is off (we don't assume, we force it). SetUnsafeBytes(false) if IsUnsafeBytes() { t.Fatal("expected IsUnsafeBytes=false") } b := []byte("xyz") got := Concat(b) if got != "xyz" { t.Fatalf("Join([]byte) copy mode: %q", got) } // Turn on and ensure content still matches. SetUnsafeBytes(true) if !IsUnsafeBytes() { t.Fatal("expected IsUnsafeBytes=true") } b2 := []byte("abc") got2 := Concat(b2) if got2 != "abc" { t.Fatalf("Join([]byte) unsafe mode: %q", got2) } } func TestTypesCoverage(t *testing.T) { t.Parallel() var ( i8 int8 = -8 i16 int16 = -16 i32 int32 = -32 i64 int64 = -64 u8 uint8 = 8 u16 uint16 = 16 u32 uint32 = 32 u64 uint64 = 64 f32 float32 = 3.5 f64 float64 = 9.25 ) err := errors.New("X") s := sType("Y") got := With(",", i8, i16, i32, i64, u8, u16, u32, u64, f32, f64, true, false, err, s) wantPrefix := "-8,-16,-32,-64,8,16,32,64,3.5,9.25,true,false,X,S(Y)" if got != wantPrefix { t.Fatalf("types coverage:\n got: %q\nwant: %q", got, wantPrefix) } } func TestConcurrencySmoke(t *testing.T) { t.Parallel() const N = 64 wg := sync.WaitGroup{} wg.Add(N) errs := make(chan error, N) for i := 0; i < N; i++ { i := i go func() { defer wg.Done() s := With("-", "g", i, "t", i*i) if !strings.HasPrefix(s, "g-") || !strings.Contains(s, "t-") { errs <- fmt.Errorf("bad string: %q", s) } }() } wg.Wait() close(errs) for err := range errs { t.Fatal(err) } } // Sanity: ensure no lingering goroutines or leaks on pool toggles (smoke). func TestPoolToggleDoesNotLeak(t *testing.T) { t.Parallel() // Force GC cycles around pool use; this is just a smoke test. Pool(true) for i := 0; i < 100; i++ { b := New("|") b.Add("a", i, "b").Release() } Pool(false) runtime.GC() } func TestAppendFunctions(t *testing.T) { t.Parallel() dst := []byte("start") got := Append(dst, "end", 1) if string(got) != "startend1" { t.Errorf("Append = %q, want startend1", string(got)) } dst2 := []byte("start") got2 := AppendWith(" ", dst2, "more", 2) // The function correctly produces "start" + "more 2". The test must expect this. expected := "startmore 2" if string(got2) != expected { t.Errorf("AppendWith = %q, want %q", string(got2), expected) } dst3 := []byte("start") got3 := AppendBytes(dst3, []byte("bytes")) if string(got3) != "startbytes" { t.Errorf("AppendBytes = %q, want startbytes", string(got3)) } } func TestAppendToFunctions(t *testing.T) { t.Parallel() var sb strings.Builder AppendTo(&sb, "a", 1) if sb.String() != "a1" { t.Errorf("AppendTo = %q, want a1", sb.String()) } AppendStrings(&sb, "b", "c") if sb.String() != "a1bc" { t.Errorf("AppendStrings = %q, want a1bc", sb.String()) } } func TestGroup(t *testing.T) { t.Parallel() tests := []struct { name string sep string groups [][]any want string }{ {"Empty", "", nil, ""}, {"Single", " ", [][]any{{1, "a"}}, "1a"}, {"Multiple", ",", [][]any{{"x"}, {"y", "z"}}, "x,yz"}, {"WithEmpty", "-", [][]any{{"a"}, {}, {"b"}}, "a-b"}, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() if got := GroupWith(tt.sep, tt.groups...); got != tt.want { t.Errorf("GroupWith() = %q, want %q", got, tt.want) } }) } } func TestNumber(t *testing.T) { t.Parallel() if got := Number(1, 2, 3); got != "123" { t.Errorf("Number() = %q, want 123", got) } if got := NumberWith(",", 4.5, 6.7); got != "4.5,6.7" { t.Errorf("NumberWith() = %q, want 4.5,6.7", got) } } func TestPairTrio(t *testing.T) { t.Parallel() if got := Pair("a", "b"); got != "ab" { t.Errorf("Pair() = %q, want ab", got) } if got := PairWith("-", "c", "d"); got != "c-d" { t.Errorf("PairWith() = %q, want c-d", got) } if got := Trio(1, 2, 3); got != "123" { t.Errorf("Trio() = %q, want 123", got) } if got := TrioWith(":", "x", "y", "z"); got != "x:y:z" { t.Errorf("TrioWith() = %q, want x:y:z", got) } } func TestRepeat(t *testing.T) { t.Parallel() if got := Repeat("a", 3); got != "aaa" { t.Errorf("Repeat() = %q, want aaa", got) } if got := RepeatWith("-", "b", 2); got != "b-b" { t.Errorf("RepeatWith() = %q, want b-b", got) } if got := Repeat("c", 0); got != "" { t.Errorf("Repeat(0) = %q, want empty", got) } if got := Repeat("d", -1); got != "" { t.Errorf("Repeat(-1) = %q, want empty", got) } if got := Repeat(1, 1); got != "1" { t.Errorf("Repeat(1) = %q, want 1", got) } } golang-github-olekukonko-cat-0.0~git20250911.50322a0/concat.go000066400000000000000000000435121507257273100232610ustar00rootroot00000000000000package cat import ( "reflect" "strings" ) // Append appends args to dst and returns the grown slice. // Callers can reuse dst across calls to amortize allocs. // It uses an internal Builder for efficient concatenation of the args (no separators), // then appends the result to the dst byte slice. // Preallocates based on a size estimate to minimize reallocations. // Benefits from Builder pooling if enabled. // Useful for building byte slices incrementally without separators. func Append(dst []byte, args ...any) []byte { return AppendWith(empty, dst, args...) } // AppendWith appends args to dst and returns the grown slice. // Callers can reuse dst across calls to amortize allocs. // Similar to Append, but inserts the specified sep between each arg. // Preallocates based on a size estimate including separators. // Benefits from Builder pooling if enabled. // Useful for building byte slices incrementally with custom separators. func AppendWith(sep string, dst []byte, args ...any) []byte { if len(args) == 0 { return dst } b := New(sep) b.Grow(estimateWith(sep, args)) b.Add(args...) out := b.Output() return append(dst, out...) } // AppendBytes joins byte slices without separators. // Only for compatibility with low-level byte processing. // Directly appends each []byte arg to dst without any conversion or separators. // Efficient for pure byte concatenation; no allocations if dst has capacity. // Returns the extended dst slice. // Does not use Builder, as it's simple append operations. func AppendBytes(dst []byte, args ...[]byte) []byte { if len(args) == 0 { return dst } for _, b := range args { dst = append(dst, b...) } return dst } // AppendTo writes arguments to an existing strings.Builder. // More efficient than creating new builders. // Appends each arg to the provided strings.Builder using the optimized write function. // No separators are added; for direct concatenation. // Useful when you already have a strings.Builder and want to add more values efficiently. // Does not use cat.Builder, as it appends to an existing strings.Builder. func AppendTo(b *strings.Builder, args ...any) { for _, arg := range args { write(b, arg) } } // AppendStrings writes strings to an existing strings.Builder. // Directly writes each string arg to the provided strings.Builder. // No type checks or conversions; assumes all args are strings. // Efficient for appending known strings without separators. // Does not use cat.Builder, as it appends to an existing strings.Builder. func AppendStrings(b *strings.Builder, ss ...string) { for _, s := range ss { b.WriteString(s) } } // Between concatenates values wrapped between x and y (no separator between args). // Equivalent to BetweenWith with an empty separator. func Between(x, y any, args ...any) string { return BetweenWith(empty, x, y, args...) } // BetweenWith concatenates values wrapped between x and y, using sep between x, args, and y. // Uses a pooled Builder if enabled; releases it after use. // Equivalent to With(sep, x, args..., y). func BetweenWith(sep string, x, y any, args ...any) string { b := New(sep) // Estimate size for all parts to avoid re-allocation. b.Grow(estimate([]any{x, y}) + estimateWith(sep, args)) b.Add(x) b.Add(args...) b.Add(y) return b.Output() } // CSV joins arguments with "," separators (no space). // Convenience wrapper for With using a comma as separator. // Useful for simple CSV string generation without spaces. func CSV(args ...any) string { return With(comma, args...) } // Comma joins arguments with ", " separators. // Convenience wrapper for With using ", " as separator. // Useful for human-readable lists with comma and space. func Comma(args ...any) string { return With(comma+space, args...) } // Concat concatenates any values (no separators). // Usage: cat.Concat("a", 1, true) β†’ "a1true" // Equivalent to With with an empty separator. func Concat(args ...any) string { return With(empty, args...) } // ConcatWith concatenates any values with separator. // Alias for With; joins args with the provided sep. func ConcatWith(sep string, args ...any) string { return With(sep, args...) } // Flatten joins nested values into a single concatenation using empty. // Convenience for FlattenWith using empty. func Flatten(args ...any) string { return FlattenWith(empty, args...) } // FlattenWith joins nested values into a single concatenation with sep, avoiding // intermediate slice allocations where possible. // It recursively flattens any nested []any arguments, concatenating all leaf items // with sep between them. Skips empty nested slices to avoid extra separators. // Leaf items (non-slices) are converted using the optimized write function. // Uses a pooled Builder if enabled; releases it after use. // Preallocates based on a recursive estimate for efficiency. // Example: FlattenWith(",", 1, []any{2, []any{3,4}}, 5) β†’ "1,2,3,4,5" func FlattenWith(sep string, args ...any) string { if len(args) == 0 { return empty } // Recursive estimate for preallocation. totalSize := recursiveEstimate(sep, args) b := New(sep) b.Grow(totalSize) recursiveAdd(b, args) return b.Output() } // Group joins multiple groups with empty between groups (no intra-group separators). // Convenience for GroupWith using empty. func Group(groups ...[]any) string { return GroupWith(empty, groups...) } // GroupWith joins multiple groups with a separator between groups (no intra-group separators). // Concatenates each group internally without separators, then joins non-empty groups with sep. // Preestimates total size for allocation; uses pooled Builder if enabled. // Optimized for single group: direct Concat. // Useful for grouping related items with inter-group separation. func GroupWith(sep string, groups ...[]any) string { if len(groups) == 0 { return empty } if len(groups) == 1 { return Concat(groups[0]...) } total := 0 nonEmpty := 0 for _, g := range groups { if len(g) == 0 { continue } if nonEmpty > 0 { total += len(sep) } total += estimate(g) nonEmpty++ } b := New(empty) b.Grow(total) first := true for _, g := range groups { if len(g) == 0 { continue } if !first && sep != empty { b.buf.WriteString(sep) } first = false for _, a := range g { write(&b.buf, a) } } return b.Output() } // Indent prefixes the concatenation of args with depth levels of two spaces per level. // Example: Indent(2, "hello") => " hello" // If depth <= 0, equivalent to Concat(args...). // Uses " " repeated depth times as prefix, followed by concatenated args (no separators). // Benefits from pooling via Concat. func Indent(depth int, args ...any) string { if depth <= 0 { return Concat(args...) } prefix := strings.Repeat(" ", depth) return Prefix(prefix, args...) } // Join joins strings (matches stdlib strings.Join behavior). // Usage: cat.Join("a", "b") β†’ "a b" (using empty) // Joins the variadic string args with the current empty. // Useful for compatibility with stdlib but using package default sep. func Join(elems ...string) string { return strings.Join(elems, empty) } // JoinWith joins strings with separator (variadic version). // Directly uses strings.Join on the variadic string args with sep. // Efficient for known strings; no conversions needed. func JoinWith(sep string, elems ...string) string { return strings.Join(elems, sep) } // Lines joins arguments with newline separators. // Convenience for With using "\n" as separator. // Useful for building multi-line strings. func Lines(args ...any) string { return With(newline, args...) } // Number concatenates numeric values without separators. // Generic over Numeric types. // Equivalent to NumberWith with empty sep. func Number[T Numeric](a ...T) string { return NumberWith(empty, a...) } // NumberWith concatenates numeric values with the provided separator. // Generic over Numeric types. // If no args, returns empty string. // Uses pooled Builder if enabled, with rough growth estimate (8 bytes per item). // Relies on valueToString for numeric conversion. func NumberWith[T Numeric](sep string, a ...T) string { if len(a) == 0 { return empty } b := New(sep) b.Grow(len(a) * 8) for _, v := range a { b.Add(v) } return b.Output() } // Path joins arguments with "/" separators. // Convenience for With using "/" as separator. // Useful for building file paths or URLs. func Path(args ...any) string { return With(slash, args...) } // Prefix concatenates with a prefix (no separator). // Equivalent to PrefixWith with empty sep. func Prefix(p any, args ...any) string { return PrefixWith(empty, p, args...) } // PrefixWith concatenates with a prefix and separator. // Adds p, then sep (if args present and sep not empty), then joins args with sep. // Uses pooled Builder if enabled. func PrefixWith(sep string, p any, args ...any) string { b := New(sep) b.Grow(estimateWith(sep, args) + estimate([]any{p})) b.Add(p) b.Add(args...) return b.Output() } // PrefixEach applies the same prefix to each argument and joins the pairs with sep. // Example: PrefixEach("pre-", ",", "a","b") => "pre-a,pre-b" // Preestimates size including prefixes and seps. // Uses pooled Builder if enabled; manually adds sep between pairs, no sep between p and a. // Returns empty if no args. func PrefixEach(p any, sep string, args ...any) string { if len(args) == 0 { return empty } pSize := estimate([]any{p}) total := len(sep)*(len(args)-1) + estimate(args) + pSize*len(args) b := New(empty) b.Grow(total) for i, a := range args { if i > 0 && sep != empty { b.buf.WriteString(sep) } write(&b.buf, p) write(&b.buf, a) } return b.Output() } // Pair joins exactly two values (no separator). // Equivalent to PairWith with empty sep. func Pair(a, b any) string { return PairWith(empty, a, b) } // PairWith joins exactly two values with a separator. // Optimized for two args: uses With(sep, a, b). func PairWith(sep string, a, b any) string { return With(sep, a, b) } // Quote wraps each argument in double quotes, separated by spaces. // Equivalent to QuoteWith with '"' as quote. func Quote(args ...any) string { return QuoteWith('"', args...) } // QuoteWith wraps each argument with the specified quote byte, separated by spaces. // Wraps each arg with quote, writes arg, closes with quote; joins with space. // Preestimates with quotes and spaces. // Uses pooled Builder if enabled. func QuoteWith(quote byte, args ...any) string { if len(args) == 0 { return empty } total := estimate(args) + 2*len(args) + len(space)*(len(args)-1) b := New(empty) b.Grow(total) need := false for _, a := range args { if need { b.buf.WriteString(space) } b.buf.WriteByte(quote) write(&b.buf, a) b.buf.WriteByte(quote) need = true } return b.Output() } // Repeat concatenates val n times (no sep between instances). // Equivalent to RepeatWith with empty sep. func Repeat(val any, n int) string { return RepeatWith(empty, val, n) } // RepeatWith concatenates val n times with sep between each instance. // If n <= 0, returns an empty string. // Optimized to make exactly one allocation; converts val once. // Uses pooled Builder if enabled. func RepeatWith(sep string, val any, n int) string { if n <= 0 { return empty } if n == 1 { return valueToString(val) } b := New(sep) b.Grow(n*estimate([]any{val}) + (n-1)*len(sep)) for i := 0; i < n; i++ { b.Add(val) } return b.Output() } // Reflect converts a reflect.Value to its string representation. // It handles all kinds of reflected values including primitives, structs, slices, maps, etc. // For nil values, it returns the nilString constant (""). // For unexported or inaccessible fields, it returns unexportedString (""). // The output follows Go's syntax conventions where applicable (e.g., slices as [a, b], maps as {k:v}). func Reflect(r reflect.Value) string { if !r.IsValid() { return nilString } var b strings.Builder writeReflect(&b, r.Interface(), 0) return b.String() } // Space concatenates arguments with space separators. // Convenience for With using " " as separator. func Space(args ...any) string { return With(space, args...) } // Dot concatenates arguments with dot separators. // Convenience for With using " " as separator. func Dot(args ...any) string { return With(dot, args...) } // Suffix concatenates with a suffix (no separator). // Equivalent to SuffixWith with empty sep. func Suffix(s any, args ...any) string { return SuffixWith(empty, s, args...) } // SuffixWith concatenates with a suffix and separator. // Joins args with sep, then adds sep (if args present and sep not empty), then s. // Uses pooled Builder if enabled. func SuffixWith(sep string, s any, args ...any) string { b := New(sep) b.Grow(estimateWith(sep, args) + estimate([]any{s})) b.Add(args...) b.Add(s) return b.Output() } // SuffixEach applies the same suffix to each argument and joins the pairs with sep. // Example: SuffixEach("-suf", " | ", "a","b") => "a-suf | b-suf" // Preestimates size including suffixes and seps. // Uses pooled Builder if enabled; manually adds sep between pairs, no sep between a and s. // Returns empty if no args. func SuffixEach(s any, sep string, args ...any) string { if len(args) == 0 { return empty } sSize := estimate([]any{s}) total := len(sep)*(len(args)-1) + estimate(args) + sSize*len(args) b := New(empty) b.Grow(total) for i, a := range args { if i > 0 && sep != empty { b.buf.WriteString(sep) } write(&b.buf, a) write(&b.buf, s) } return b.Output() } // Sprint concatenates any values (no separators). // Usage: Sprint("a", 1, true) β†’ "a1true" // Equivalent to Concat or With with an empty separator. func Sprint(args ...any) string { if len(args) == 0 { return empty } if len(args) == 1 { return valueToString(args[0]) } // For multiple args, use the existing Concat functionality return Concat(args...) } // Trio joins exactly three values (no separator). // Equivalent to TrioWith with empty sep func Trio(a, b, c any) string { return TrioWith(empty, a, b, c) } // TrioWith joins exactly three values with a separator. // Optimized for three args: uses With(sep, a, b, c). func TrioWith(sep string, a, b, c any) string { return With(sep, a, b, c) } // With concatenates arguments with the specified separator. // Core concatenation function with sep. // Optimized for zero or one arg: empty or direct valueToString. // Fast path for all strings: exact preallocation, direct writes via raw strings.Builder (minimal branches/allocs). // Fallback: pooled Builder with estimateWith, adds args with sep. // Benefits from pooling if enabled for mixed types. func With(sep string, args ...any) string { switch len(args) { case 0: return empty case 1: return valueToString(args[0]) } // Fast path for all strings: use raw strings.Builder for speed, no pooling needed. allStrings := true totalLen := len(sep) * (len(args) - 1) for _, a := range args { if s, ok := a.(string); ok { totalLen += len(s) } else { allStrings = false break } } if allStrings { var b strings.Builder b.Grow(totalLen) b.WriteString(args[0].(string)) for i := 1; i < len(args); i++ { if sep != empty { b.WriteString(sep) } b.WriteString(args[i].(string)) } return b.String() } // Fallback for mixed types: use pooled Builder. b := New(sep) b.Grow(estimateWith(sep, args)) b.Add(args...) return b.Output() } // Wrap encloses concatenated args between before and after strings (no inner separator). // Equivalent to Concat(before, args..., after). func Wrap(before, after string, args ...any) string { b := Start() b.Grow(len(before) + len(after) + estimate(args)) b.Add(before) b.Add(args...) b.Add(after) return b.Output() } // WrapEach wraps each argument individually with before/after, concatenated without separators. // Applies before + arg + after to each arg. // Preestimates size; uses pooled Builder if enabled. // Returns empty if no args. // Useful for wrapping multiple items identically without joins. func WrapEach(before, after string, args ...any) string { if len(args) == 0 { return empty } total := (len(before)+len(after))*len(args) + estimate(args) b := Start() // Use pooled builder, but we will write manually. b.Grow(total) for _, a := range args { write(&b.buf, before) write(&b.buf, a) write(&b.buf, after) } // No separators were ever added, so this is safe. b.needsSep = true // Correctly set state in case of reuse. return b.Output() } // WrapWith encloses concatenated args between before and after strings, // joining the arguments with the provided separator. // If no args, returns before + after. // Builds inner with With(sep, args...), then Concat(before, inner, after). // Benefits from pooling via With and Concat. func WrapWith(sep, before, after string, args ...any) string { if len(args) == 0 { return before + after } // First, efficiently build the inner part. inner := With(sep, args...) // Then, wrap it without allocating another slice. b := Start() b.Grow(len(before) + len(inner) + len(after)) b.Add(before) b.Add(inner) b.Add(after) return b.Output() } // Pad surrounds a string with spaces on both sides. // Ensures proper spacing for SQL operators like "=", "AND", etc. // Example: Pad("=") returns " = " for cleaner formatting. func Pad(s string) string { return Concat(space, s, space) } // PadWith adds a separator before the string and a space after it. // Useful for formatting SQL parts with custom leading separators. // Example: PadWith(",", "column") returns ",column ". func PadWith(sep, s string) string { return Concat(sep, s, space) } // Parens wraps content in parentheses // Useful for grouping SQL conditions or expressions // Example: Parens("a = b AND c = d") β†’ "(a = b AND c = d)" func Parens(content string) string { return Concat(parenOpen, content, parenClose) } // ParensWith wraps multiple arguments in parentheses with a separator // Example: ParensWith(" AND ", "a = b", "c = d") β†’ "(a = b AND c = d)" func ParensWith(sep string, args ...any) string { return Concat(parenOpen, With(sep, args...), parenClose) } golang-github-olekukonko-cat-0.0~git20250911.50322a0/fn.go000066400000000000000000000242111507257273100224100ustar00rootroot00000000000000package 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) } } } golang-github-olekukonko-cat-0.0~git20250911.50322a0/go.mod000066400000000000000000000000521507257273100225610ustar00rootroot00000000000000module github.com/olekukonko/cat go 1.21 golang-github-olekukonko-cat-0.0~git20250911.50322a0/sql.go000066400000000000000000000132121507257273100226030ustar00rootroot00000000000000package cat // On builds a SQL ON clause comparing two columns across tables. // Formats as: "table1.column1 = table2.column2" with proper spacing. // Useful in JOIN conditions to match keys between tables. func On(table1, column1, table2, column2 string) string { return With(space, With(dot, table1, column1), Pad(equal), With(dot, table2, column2), ) } // Using builds a SQL condition comparing two aliased columns. // Formats as: "alias1.column1 = alias2.column2" for JOINs or filters. // Helps when working with table aliases in complex queries. func Using(alias1, column1, alias2, column2 string) string { return With(space, With(dot, alias1, column1), Pad(equal), With(dot, alias2, column2), ) } // And joins multiple SQL conditions with the AND operator. // Adds spacing to ensure clean SQL output (e.g., "cond1 AND cond2"). // Accepts variadic arguments for flexible condition chaining. func And(conditions ...any) string { return With(Pad(and), conditions...) } // In creates a SQL IN clause with properly quoted values // Example: In("status", "active", "pending") β†’ "status IN ('active', 'pending')" // Handles value quoting and comma separation automatically func In(column string, values ...string) string { if len(values) == 0 { return Concat(column, inOpen, inClose) } quotedValues := make([]string, len(values)) for i, v := range values { quotedValues[i] = "'" + v + "'" } return Concat(column, inOpen, JoinWith(comma+space, quotedValues...), inClose) } // As creates an aliased SQL expression // Example: As("COUNT(*)", "total_count") β†’ "COUNT(*) AS total_count" func As(expression, alias string) string { return Concat(expression, asSQL, alias) } // Count creates a COUNT expression with optional alias // Example: Count("id") β†’ "COUNT(id)" // Example: Count("id", "total") β†’ "COUNT(id) AS total" // Example: Count("DISTINCT user_id", "unique_users") β†’ "COUNT(DISTINCT user_id) AS unique_users" func Count(column string, alias ...string) string { expression := Concat(count, column, parenClose) if len(alias) == 0 { return expression } return As(expression, alias[0]) } // CountAll creates COUNT(*) with optional alias // Example: CountAll() β†’ "COUNT(*)" // Example: CountAll("total") β†’ "COUNT(*) AS total" func CountAll(alias ...string) string { if len(alias) == 0 { return countAll } return As(countAll, alias[0]) } // Sum creates a SUM expression with optional alias // Example: Sum("amount") β†’ "SUM(amount)" // Example: Sum("amount", "total") β†’ "SUM(amount) AS total" func Sum(column string, alias ...string) string { expression := Concat(sum, column, parenClose) if len(alias) == 0 { return expression } return As(expression, alias[0]) } // Avg creates an AVG expression with optional alias // Example: Avg("score") β†’ "AVG(score)" // Example: Avg("score", "average") β†’ "AVG(score) AS average" func Avg(column string, alias ...string) string { expression := Concat(avg, column, parenClose) if len(alias) == 0 { return expression } return As(expression, alias[0]) } // Max creates a MAX expression with optional alias // Example: Max("price") β†’ "MAX(price)" // Example: Max("price", "max_price") β†’ "MAX(price) AS max_price" func Max(column string, alias ...string) string { expression := Concat(maxOpen, column, parenClose) if len(alias) == 0 { return expression } return As(expression, alias[0]) } // Min creates a MIN expression with optional alias // Example: Min("price") β†’ "MIN(price)" // Example: Min("price", "min_price") β†’ "MIN(price) AS min_price" func Min(column string, alias ...string) string { expression := Concat(minOpen, column, parenClose) if len(alias) == 0 { return expression } return As(expression, alias[0]) } // Case creates a SQL CASE expression with optional alias // Example: Case("WHEN status = 'active' THEN 1 ELSE 0 END", "is_active") β†’ "CASE WHEN status = 'active' THEN 1 ELSE 0 END AS is_active" func Case(expression string, alias ...string) string { caseExpr := Concat(caseSQL, expression) if len(alias) == 0 { return caseExpr } return As(caseExpr, alias[0]) } // CaseWhen creates a complete SQL CASE expression from individual parts with proper value handling // Example: CaseWhen("status =", "'active'", "1", "0", "is_active") β†’ "CASE WHEN status = 'active' THEN 1 ELSE 0 END AS is_active" // Example: CaseWhen("age >", "18", "'adult'", "'minor'", "age_group") β†’ "CASE WHEN age > 18 THEN 'adult' ELSE 'minor' END AS age_group" func CaseWhen(conditionPart string, conditionValue, thenValue, elseValue any, alias ...string) string { condition := Concat(conditionPart, valueToString(conditionValue)) expression := Concat( when, condition, then, valueToString(thenValue), elseSQL, valueToString(elseValue), end, ) return Case(expression, alias...) } // CaseWhenMulti creates a SQL CASE expression with multiple WHEN clauses // Example: CaseWhenMulti([]string{"status =", "age >"}, []any{"'active'", 18}, []any{1, "'adult'"}, 0, "result") β†’ "CASE WHEN status = 'active' THEN 1 WHEN age > 18 THEN 'adult' ELSE 0 END AS result" func CaseWhenMulti(conditionParts []string, conditionValues, thenValues []any, elseValue any, alias ...string) string { if len(conditionParts) != len(conditionValues) || len(conditionParts) != len(thenValues) { return "" // or handle error } var whenClauses []string for i := 0; i < len(conditionParts); i++ { condition := Concat(conditionParts[i], valueToString(conditionValues[i])) whenClause := Concat(when, condition, then, valueToString(thenValues[i])) whenClauses = append(whenClauses, whenClause) } expression := Concat( JoinWith(space, whenClauses...), elseSQL, valueToString(elseValue), end, ) return Case(expression, alias...) }