diff --git a/core/rawdb/database.go b/core/rawdb/database.go
index 5b2d84eac3..1e1197830c 100644
--- a/core/rawdb/database.go
+++ b/core/rawdb/database.go
@@ -610,10 +610,10 @@ func InspectDatabase(db ctxcdb.Database, keyPrefix, keyStart []byte) error {
total.Add(uint64(ancient.size()))
}
- table := newTableWriter(os.Stdout)
- table.Header([]string{"Database", "Category", "Size", "Items"})
- table.Footer([]string{"", "Total", common.StorageSize(total.Load()).String(), " "})
- table.Append(stats)
+ table := NewTableWriter(os.Stdout)
+ table.SetHeader([]string{"Database", "Category", "Size", "Items"})
+ table.SetFooter([]string{"", "Total", common.StorageSize(total.Load()).String(), fmt.Sprintf("%d", count.Load())})
+ table.AppendBulk(stats)
table.Render()
if !unaccounted.empty() {
diff --git a/core/rawdb/database_tablewriter_tinygo.go b/core/rawdb/database_tablewriter.go
similarity index 93%
rename from core/rawdb/database_tablewriter_tinygo.go
rename to core/rawdb/database_tablewriter.go
index 2f8e456fd5..e1cda5c93f 100644
--- a/core/rawdb/database_tablewriter_tinygo.go
+++ b/core/rawdb/database_tablewriter.go
@@ -14,10 +14,7 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see .
-// TODO: naive stub implementation for tablewriter
-
-//go:build tinygo
-// +build tinygo
+// Naive stub implementation for tablewriter
package rawdb
@@ -40,7 +37,7 @@ type Table struct {
rows [][]string
}
-func newTableWriter(w io.Writer) *Table {
+func NewTableWriter(w io.Writer) *Table {
return &Table{out: w}
}
@@ -89,6 +86,7 @@ func (t *Table) render() error {
rowSeparator := t.buildRowSeparator(widths)
if len(t.headers) > 0 {
+ fmt.Fprintln(t.out, rowSeparator)
t.printRow(t.headers, widths)
fmt.Fprintln(t.out, rowSeparator)
}
@@ -100,6 +98,7 @@ func (t *Table) render() error {
if len(t.footer) > 0 {
fmt.Fprintln(t.out, rowSeparator)
t.printRow(t.footer, widths)
+ fmt.Fprintln(t.out, rowSeparator)
}
return nil
@@ -172,21 +171,22 @@ func (t *Table) calculateColumnWidths() []int {
//
// It generates a string with dashes (-) for each column width, joined by plus signs (+).
//
-// Example output: "----------+--------+-----------"
+// Example output: "+----------+--------+-----------+"
func (t *Table) buildRowSeparator(widths []int) string {
parts := make([]string, len(widths))
for i, w := range widths {
parts[i] = strings.Repeat("-", w)
}
- return strings.Join(parts, "+")
+ return "+" + strings.Join(parts, "+") + "+"
}
// printRow outputs a single row to the table writer.
//
// Each cell is padded with spaces and separated by pipe characters (|).
//
-// Example output: " Database | Size | Items "
+// Example output: "| Database | Size | Items |"
func (t *Table) printRow(row []string, widths []int) {
+ fmt.Fprintf(t.out, "|")
for i, cell := range row {
if i > 0 {
fmt.Fprint(t.out, "|")
@@ -204,5 +204,6 @@ func (t *Table) printRow(row []string, widths []int) {
fmt.Fprintf(t.out, "%s%s%s", leftPadding, cell, rightPadding)
}
+ fmt.Fprintf(t.out, "|")
fmt.Fprintln(t.out)
}
diff --git a/core/rawdb/database_tablewriter_tinygo_test.go b/core/rawdb/database_tablewriter_test.go
similarity index 94%
rename from core/rawdb/database_tablewriter_tinygo_test.go
rename to core/rawdb/database_tablewriter_test.go
index 3bcf93832b..e9de5d8ce8 100644
--- a/core/rawdb/database_tablewriter_tinygo_test.go
+++ b/core/rawdb/database_tablewriter_test.go
@@ -14,9 +14,6 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see .
-//go:build tinygo
-// +build tinygo
-
package rawdb
import (
@@ -27,7 +24,7 @@ import (
func TestTableWriterTinyGo(t *testing.T) {
var buf bytes.Buffer
- table := newTableWriter(&buf)
+ table := NewTableWriter(&buf)
headers := []string{"Database", "Size", "Items", "Status"}
rows := [][]string{
@@ -51,7 +48,7 @@ func TestTableWriterValidationErrors(t *testing.T) {
// Test missing headers
t.Run("MissingHeaders", func(t *testing.T) {
var buf bytes.Buffer
- table := newTableWriter(&buf)
+ table := NewTableWriter(&buf)
rows := [][]string{{"x", "y", "z"}}
@@ -66,7 +63,7 @@ func TestTableWriterValidationErrors(t *testing.T) {
t.Run("NotEnoughRowColumns", func(t *testing.T) {
var buf bytes.Buffer
- table := newTableWriter(&buf)
+ table := NewTableWriter(&buf)
headers := []string{"A", "B", "C"}
badRows := [][]string{
@@ -85,7 +82,7 @@ func TestTableWriterValidationErrors(t *testing.T) {
t.Run("TooManyRowColumns", func(t *testing.T) {
var buf bytes.Buffer
- table := newTableWriter(&buf)
+ table := NewTableWriter(&buf)
headers := []string{"A", "B", "C"}
badRows := [][]string{
@@ -105,7 +102,7 @@ func TestTableWriterValidationErrors(t *testing.T) {
// Test mismatched footer columns
t.Run("MismatchedFooterColumns", func(t *testing.T) {
var buf bytes.Buffer
- table := newTableWriter(&buf)
+ table := NewTableWriter(&buf)
headers := []string{"A", "B", "C"}
rows := [][]string{{"x", "y", "z"}}
diff --git a/core/rawdb/database_tablewriter_unix.go b/core/rawdb/database_tablewriter_unix.go
deleted file mode 100644
index 8bec5396e8..0000000000
--- a/core/rawdb/database_tablewriter_unix.go
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2025 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-//go:build !tinygo
-// +build !tinygo
-
-package rawdb
-
-import (
- "io"
-
- "github.com/olekukonko/tablewriter"
-)
-
-// Re-export the real tablewriter types and functions
-type Table = tablewriter.Table
-
-func newTableWriter(w io.Writer) *Table {
- return tablewriter.NewWriter(w)
-}
diff --git a/go.mod b/go.mod
index f201fe43d9..d5b58870b1 100644
--- a/go.mod
+++ b/go.mod
@@ -53,7 +53,6 @@ require (
github.com/muesli/reflow v0.3.0
github.com/muesli/termenv v0.16.0
github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416
- github.com/olekukonko/tablewriter v1.1.0
github.com/peterh/liner v1.2.2
github.com/pion/stun/v2 v2.0.0
github.com/rs/cors v1.11.1
@@ -152,7 +151,6 @@ require (
github.com/elliotchance/orderedmap v1.8.0 // indirect
github.com/emicklei/dot v1.9.2 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
- github.com/fatih/color v1.18.0 // indirect
github.com/garslo/gogen v0.0.0-20170306192744-1d203ffc1f61 // indirect
github.com/getsentry/sentry-go v0.36.2 // indirect
github.com/go-llsqlite/adapter v0.2.0 // indirect
@@ -191,9 +189,6 @@ require (
github.com/ncruces/go-strftime v1.0.0 // indirect
github.com/nutsdb/nutsdb v1.0.4 // indirect
github.com/oapi-codegen/runtime v1.1.2 // indirect
- github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect
- github.com/olekukonko/errors v1.1.0 // indirect
- github.com/olekukonko/ll v0.1.2 // indirect
github.com/otiai10/copy v1.14.1 // indirect
github.com/otiai10/mint v1.6.3 // indirect
github.com/pion/datachannel v1.5.10 // indirect
diff --git a/go.sum b/go.sum
index 9461453fe7..d75bf6f228 100644
--- a/go.sum
+++ b/go.sum
@@ -475,8 +475,6 @@ github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab/go.mod h1
github.com/ethereum/go-verkle v0.2.2 h1:I2W0WjnrFUIzzVPwm8ykY+7pL2d4VhlsePn4j7cnFk8=
github.com/ethereum/go-verkle v0.2.2/go.mod h1:M3b90YRnzqKyyzBEWJGqj8Qff4IDeXnzFw0P9bFw3uk=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
-github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
-github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/ferranbt/fastssz v1.0.0 h1:9EXXYsracSqQRBQiHeaVsG/KQeYblPf40hsQPb9Dzk8=
github.com/ferranbt/fastssz v1.0.0/go.mod h1:Ea3+oeoRGGLGm5shYAeDgu6PGUlcvQhE2fILyD9+tGg=
github.com/fjl/gencodec v0.1.0 h1:B3K0xPfc52cw52BBgUbSPxYo+HlLfAgWMVKRWXUXBcs=
@@ -907,17 +905,9 @@ github.com/oapi-codegen/runtime v1.1.2/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
-github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 h1:zrbMGy9YXpIeTnGj4EljqMiZsIcE09mmF8XsD5AYOJc=
-github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6/go.mod h1:rEKTHC9roVVicUIfZK7DYrdIoM0EOr8mK1Hj5s3JjH0=
-github.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM=
-github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y=
-github.com/olekukonko/ll v0.1.2 h1:lkg/k/9mlsy0SxO5aC+WEpbdT5K83ddnNhAepz7TQc0=
-github.com/olekukonko/ll v0.1.2/go.mod h1:b52bVQRRPObe+yyBl0TxNfhesL0nedD4Cht0/zx55Ew=
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
github.com/olekukonko/tablewriter v0.0.5-0.20200416053754-163badb3bac6/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
-github.com/olekukonko/tablewriter v1.1.0 h1:N0LHrshF4T39KvI96fn6GT8HEjXRXYNDrDjKFDB7RIY=
-github.com/olekukonko/tablewriter v1.1.0/go.mod h1:5c+EBPeSqvXnLLgkm9isDdzR3wjfBkHR9Nhfp3NWrzo=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
diff --git a/vendor/github.com/fatih/color/LICENSE.md b/vendor/github.com/fatih/color/LICENSE.md
deleted file mode 100644
index 25fdaf639d..0000000000
--- a/vendor/github.com/fatih/color/LICENSE.md
+++ /dev/null
@@ -1,20 +0,0 @@
-The MIT License (MIT)
-
-Copyright (c) 2013 Fatih Arslan
-
-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.
diff --git a/vendor/github.com/fatih/color/README.md b/vendor/github.com/fatih/color/README.md
deleted file mode 100644
index d135bfe023..0000000000
--- a/vendor/github.com/fatih/color/README.md
+++ /dev/null
@@ -1,189 +0,0 @@
-# color [](https://github.com/fatih/color/actions) [](https://pkg.go.dev/github.com/fatih/color)
-
-Color lets you use colorized outputs in terms of [ANSI Escape
-Codes](http://en.wikipedia.org/wiki/ANSI_escape_code#Colors) in Go (Golang). It
-has support for Windows too! The API can be used in several ways, pick one that
-suits you.
-
-
-
-## Install
-
-```
-go get github.com/fatih/color
-```
-
-## Examples
-
-### Standard colors
-
-```go
-// Print with default helper functions
-color.Cyan("Prints text in cyan.")
-
-// A newline will be appended automatically
-color.Blue("Prints %s in blue.", "text")
-
-// These are using the default foreground colors
-color.Red("We have red")
-color.Magenta("And many others ..")
-
-```
-
-### RGB colors
-
-If your terminal supports 24-bit colors, you can use RGB color codes.
-
-```go
-color.RGB(255, 128, 0).Println("foreground orange")
-color.RGB(230, 42, 42).Println("foreground red")
-
-color.BgRGB(255, 128, 0).Println("background orange")
-color.BgRGB(230, 42, 42).Println("background red")
-```
-
-### Mix and reuse colors
-
-```go
-// Create a new color object
-c := color.New(color.FgCyan).Add(color.Underline)
-c.Println("Prints cyan text with an underline.")
-
-// Or just add them to New()
-d := color.New(color.FgCyan, color.Bold)
-d.Printf("This prints bold cyan %s\n", "too!.")
-
-// Mix up foreground and background colors, create new mixes!
-red := color.New(color.FgRed)
-
-boldRed := red.Add(color.Bold)
-boldRed.Println("This will print text in bold red.")
-
-whiteBackground := red.Add(color.BgWhite)
-whiteBackground.Println("Red text with white background.")
-
-// Mix with RGB color codes
-color.RGB(255, 128, 0).AddBgRGB(0, 0, 0).Println("orange with black background")
-
-color.BgRGB(255, 128, 0).AddRGB(255, 255, 255).Println("orange background with white foreground")
-```
-
-### Use your own output (io.Writer)
-
-```go
-// Use your own io.Writer output
-color.New(color.FgBlue).Fprintln(myWriter, "blue color!")
-
-blue := color.New(color.FgBlue)
-blue.Fprint(writer, "This will print text in blue.")
-```
-
-### Custom print functions (PrintFunc)
-
-```go
-// Create a custom print function for convenience
-red := color.New(color.FgRed).PrintfFunc()
-red("Warning")
-red("Error: %s", err)
-
-// Mix up multiple attributes
-notice := color.New(color.Bold, color.FgGreen).PrintlnFunc()
-notice("Don't forget this...")
-```
-
-### Custom fprint functions (FprintFunc)
-
-```go
-blue := color.New(color.FgBlue).FprintfFunc()
-blue(myWriter, "important notice: %s", stars)
-
-// Mix up with multiple attributes
-success := color.New(color.Bold, color.FgGreen).FprintlnFunc()
-success(myWriter, "Don't forget this...")
-```
-
-### Insert into noncolor strings (SprintFunc)
-
-```go
-// Create SprintXxx functions to mix strings with other non-colorized strings:
-yellow := color.New(color.FgYellow).SprintFunc()
-red := color.New(color.FgRed).SprintFunc()
-fmt.Printf("This is a %s and this is %s.\n", yellow("warning"), red("error"))
-
-info := color.New(color.FgWhite, color.BgGreen).SprintFunc()
-fmt.Printf("This %s rocks!\n", info("package"))
-
-// Use helper functions
-fmt.Println("This", color.RedString("warning"), "should be not neglected.")
-fmt.Printf("%v %v\n", color.GreenString("Info:"), "an important message.")
-
-// Windows supported too! Just don't forget to change the output to color.Output
-fmt.Fprintf(color.Output, "Windows support: %s", color.GreenString("PASS"))
-```
-
-### Plug into existing code
-
-```go
-// Use handy standard colors
-color.Set(color.FgYellow)
-
-fmt.Println("Existing text will now be in yellow")
-fmt.Printf("This one %s\n", "too")
-
-color.Unset() // Don't forget to unset
-
-// You can mix up parameters
-color.Set(color.FgMagenta, color.Bold)
-defer color.Unset() // Use it in your function
-
-fmt.Println("All text will now be bold magenta.")
-```
-
-### Disable/Enable color
-
-There might be a case where you want to explicitly disable/enable color output. the
-`go-isatty` package will automatically disable color output for non-tty output streams
-(for example if the output were piped directly to `less`).
-
-The `color` package also disables color output if the [`NO_COLOR`](https://no-color.org) environment
-variable is set to a non-empty string.
-
-`Color` has support to disable/enable colors programmatically both globally and
-for single color definitions. For example suppose you have a CLI app and a
-`-no-color` bool flag. You can easily disable the color output with:
-
-```go
-var flagNoColor = flag.Bool("no-color", false, "Disable color output")
-
-if *flagNoColor {
- color.NoColor = true // disables colorized output
-}
-```
-
-It also has support for single color definitions (local). You can
-disable/enable color output on the fly:
-
-```go
-c := color.New(color.FgCyan)
-c.Println("Prints cyan text")
-
-c.DisableColor()
-c.Println("This is printed without any color")
-
-c.EnableColor()
-c.Println("This prints again cyan...")
-```
-
-## GitHub Actions
-
-To output color in GitHub Actions (or other CI systems that support ANSI colors), make sure to set `color.NoColor = false` so that it bypasses the check for non-tty output streams.
-
-
-## Credits
-
-* [Fatih Arslan](https://github.com/fatih)
-* Windows support via @mattn: [colorable](https://github.com/mattn/go-colorable)
-
-## License
-
-The MIT License (MIT) - see [`LICENSE.md`](https://github.com/fatih/color/blob/master/LICENSE.md) for more details
diff --git a/vendor/github.com/fatih/color/color.go b/vendor/github.com/fatih/color/color.go
deleted file mode 100644
index ee39b408e9..0000000000
--- a/vendor/github.com/fatih/color/color.go
+++ /dev/null
@@ -1,685 +0,0 @@
-package color
-
-import (
- "fmt"
- "io"
- "os"
- "strconv"
- "strings"
- "sync"
-
- "github.com/mattn/go-colorable"
- "github.com/mattn/go-isatty"
-)
-
-var (
- // NoColor defines if the output is colorized or not. It's dynamically set to
- // false or true based on the stdout's file descriptor referring to a terminal
- // or not. It's also set to true if the NO_COLOR environment variable is
- // set (regardless of its value). This is a global option and affects all
- // colors. For more control over each color block use the methods
- // DisableColor() individually.
- NoColor = noColorIsSet() || os.Getenv("TERM") == "dumb" ||
- (!isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd()))
-
- // Output defines the standard output of the print functions. By default,
- // os.Stdout is used.
- Output = colorable.NewColorableStdout()
-
- // Error defines a color supporting writer for os.Stderr.
- Error = colorable.NewColorableStderr()
-
- // colorsCache is used to reduce the count of created Color objects and
- // allows to reuse already created objects with required Attribute.
- colorsCache = make(map[Attribute]*Color)
- colorsCacheMu sync.Mutex // protects colorsCache
-)
-
-// noColorIsSet returns true if the environment variable NO_COLOR is set to a non-empty string.
-func noColorIsSet() bool {
- return os.Getenv("NO_COLOR") != ""
-}
-
-// Color defines a custom color object which is defined by SGR parameters.
-type Color struct {
- params []Attribute
- noColor *bool
-}
-
-// Attribute defines a single SGR Code
-type Attribute int
-
-const escape = "\x1b"
-
-// Base attributes
-const (
- Reset Attribute = iota
- Bold
- Faint
- Italic
- Underline
- BlinkSlow
- BlinkRapid
- ReverseVideo
- Concealed
- CrossedOut
-)
-
-const (
- ResetBold Attribute = iota + 22
- ResetItalic
- ResetUnderline
- ResetBlinking
- _
- ResetReversed
- ResetConcealed
- ResetCrossedOut
-)
-
-var mapResetAttributes map[Attribute]Attribute = map[Attribute]Attribute{
- Bold: ResetBold,
- Faint: ResetBold,
- Italic: ResetItalic,
- Underline: ResetUnderline,
- BlinkSlow: ResetBlinking,
- BlinkRapid: ResetBlinking,
- ReverseVideo: ResetReversed,
- Concealed: ResetConcealed,
- CrossedOut: ResetCrossedOut,
-}
-
-// Foreground text colors
-const (
- FgBlack Attribute = iota + 30
- FgRed
- FgGreen
- FgYellow
- FgBlue
- FgMagenta
- FgCyan
- FgWhite
-
- // used internally for 256 and 24-bit coloring
- foreground
-)
-
-// Foreground Hi-Intensity text colors
-const (
- FgHiBlack Attribute = iota + 90
- FgHiRed
- FgHiGreen
- FgHiYellow
- FgHiBlue
- FgHiMagenta
- FgHiCyan
- FgHiWhite
-)
-
-// Background text colors
-const (
- BgBlack Attribute = iota + 40
- BgRed
- BgGreen
- BgYellow
- BgBlue
- BgMagenta
- BgCyan
- BgWhite
-
- // used internally for 256 and 24-bit coloring
- background
-)
-
-// Background Hi-Intensity text colors
-const (
- BgHiBlack Attribute = iota + 100
- BgHiRed
- BgHiGreen
- BgHiYellow
- BgHiBlue
- BgHiMagenta
- BgHiCyan
- BgHiWhite
-)
-
-// New returns a newly created color object.
-func New(value ...Attribute) *Color {
- c := &Color{
- params: make([]Attribute, 0),
- }
-
- if noColorIsSet() {
- c.noColor = boolPtr(true)
- }
-
- c.Add(value...)
- return c
-}
-
-// RGB returns a new foreground color in 24-bit RGB.
-func RGB(r, g, b int) *Color {
- return New(foreground, 2, Attribute(r), Attribute(g), Attribute(b))
-}
-
-// BgRGB returns a new background color in 24-bit RGB.
-func BgRGB(r, g, b int) *Color {
- return New(background, 2, Attribute(r), Attribute(g), Attribute(b))
-}
-
-// AddRGB is used to chain foreground RGB SGR parameters. Use as many as parameters to combine
-// and create custom color objects. Example: .Add(34, 0, 12).Add(255, 128, 0).
-func (c *Color) AddRGB(r, g, b int) *Color {
- c.params = append(c.params, foreground, 2, Attribute(r), Attribute(g), Attribute(b))
- return c
-}
-
-// AddRGB is used to chain background RGB SGR parameters. Use as many as parameters to combine
-// and create custom color objects. Example: .Add(34, 0, 12).Add(255, 128, 0).
-func (c *Color) AddBgRGB(r, g, b int) *Color {
- c.params = append(c.params, background, 2, Attribute(r), Attribute(g), Attribute(b))
- return c
-}
-
-// Set sets the given parameters immediately. It will change the color of
-// output with the given SGR parameters until color.Unset() is called.
-func Set(p ...Attribute) *Color {
- c := New(p...)
- c.Set()
- return c
-}
-
-// Unset resets all escape attributes and clears the output. Usually should
-// be called after Set().
-func Unset() {
- if NoColor {
- return
- }
-
- fmt.Fprintf(Output, "%s[%dm", escape, Reset)
-}
-
-// Set sets the SGR sequence.
-func (c *Color) Set() *Color {
- if c.isNoColorSet() {
- return c
- }
-
- fmt.Fprint(Output, c.format())
- return c
-}
-
-func (c *Color) unset() {
- if c.isNoColorSet() {
- return
- }
-
- Unset()
-}
-
-// SetWriter is used to set the SGR sequence with the given io.Writer. This is
-// a low-level function, and users should use the higher-level functions, such
-// as color.Fprint, color.Print, etc.
-func (c *Color) SetWriter(w io.Writer) *Color {
- if c.isNoColorSet() {
- return c
- }
-
- fmt.Fprint(w, c.format())
- return c
-}
-
-// UnsetWriter resets all escape attributes and clears the output with the give
-// io.Writer. Usually should be called after SetWriter().
-func (c *Color) UnsetWriter(w io.Writer) {
- if c.isNoColorSet() {
- return
- }
-
- if NoColor {
- return
- }
-
- fmt.Fprintf(w, "%s[%dm", escape, Reset)
-}
-
-// Add is used to chain SGR parameters. Use as many as parameters to combine
-// and create custom color objects. Example: Add(color.FgRed, color.Underline).
-func (c *Color) Add(value ...Attribute) *Color {
- c.params = append(c.params, value...)
- return c
-}
-
-// Fprint formats using the default formats for its operands and writes to w.
-// Spaces are added between operands when neither is a string.
-// It returns the number of bytes written and any write error encountered.
-// On Windows, users should wrap w with colorable.NewColorable() if w is of
-// type *os.File.
-func (c *Color) Fprint(w io.Writer, a ...interface{}) (n int, err error) {
- c.SetWriter(w)
- defer c.UnsetWriter(w)
-
- return fmt.Fprint(w, a...)
-}
-
-// Print formats using the default formats for its operands and writes to
-// standard output. Spaces are added between operands when neither is a
-// string. It returns the number of bytes written and any write error
-// encountered. This is the standard fmt.Print() method wrapped with the given
-// color.
-func (c *Color) Print(a ...interface{}) (n int, err error) {
- c.Set()
- defer c.unset()
-
- return fmt.Fprint(Output, a...)
-}
-
-// Fprintf formats according to a format specifier and writes to w.
-// It returns the number of bytes written and any write error encountered.
-// On Windows, users should wrap w with colorable.NewColorable() if w is of
-// type *os.File.
-func (c *Color) Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
- c.SetWriter(w)
- defer c.UnsetWriter(w)
-
- return fmt.Fprintf(w, format, a...)
-}
-
-// Printf formats according to a format specifier and writes to standard output.
-// It returns the number of bytes written and any write error encountered.
-// This is the standard fmt.Printf() method wrapped with the given color.
-func (c *Color) Printf(format string, a ...interface{}) (n int, err error) {
- c.Set()
- defer c.unset()
-
- return fmt.Fprintf(Output, format, a...)
-}
-
-// Fprintln formats using the default formats for its operands and writes to w.
-// Spaces are always added between operands and a newline is appended.
-// On Windows, users should wrap w with colorable.NewColorable() if w is of
-// type *os.File.
-func (c *Color) Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
- return fmt.Fprintln(w, c.wrap(sprintln(a...)))
-}
-
-// Println formats using the default formats for its operands and writes to
-// standard output. Spaces are always added between operands and a newline is
-// appended. It returns the number of bytes written and any write error
-// encountered. This is the standard fmt.Print() method wrapped with the given
-// color.
-func (c *Color) Println(a ...interface{}) (n int, err error) {
- return fmt.Fprintln(Output, c.wrap(sprintln(a...)))
-}
-
-// Sprint is just like Print, but returns a string instead of printing it.
-func (c *Color) Sprint(a ...interface{}) string {
- return c.wrap(fmt.Sprint(a...))
-}
-
-// Sprintln is just like Println, but returns a string instead of printing it.
-func (c *Color) Sprintln(a ...interface{}) string {
- return c.wrap(sprintln(a...)) + "\n"
-}
-
-// Sprintf is just like Printf, but returns a string instead of printing it.
-func (c *Color) Sprintf(format string, a ...interface{}) string {
- return c.wrap(fmt.Sprintf(format, a...))
-}
-
-// FprintFunc returns a new function that prints the passed arguments as
-// colorized with color.Fprint().
-func (c *Color) FprintFunc() func(w io.Writer, a ...interface{}) {
- return func(w io.Writer, a ...interface{}) {
- c.Fprint(w, a...)
- }
-}
-
-// PrintFunc returns a new function that prints the passed arguments as
-// colorized with color.Print().
-func (c *Color) PrintFunc() func(a ...interface{}) {
- return func(a ...interface{}) {
- c.Print(a...)
- }
-}
-
-// FprintfFunc returns a new function that prints the passed arguments as
-// colorized with color.Fprintf().
-func (c *Color) FprintfFunc() func(w io.Writer, format string, a ...interface{}) {
- return func(w io.Writer, format string, a ...interface{}) {
- c.Fprintf(w, format, a...)
- }
-}
-
-// PrintfFunc returns a new function that prints the passed arguments as
-// colorized with color.Printf().
-func (c *Color) PrintfFunc() func(format string, a ...interface{}) {
- return func(format string, a ...interface{}) {
- c.Printf(format, a...)
- }
-}
-
-// FprintlnFunc returns a new function that prints the passed arguments as
-// colorized with color.Fprintln().
-func (c *Color) FprintlnFunc() func(w io.Writer, a ...interface{}) {
- return func(w io.Writer, a ...interface{}) {
- c.Fprintln(w, a...)
- }
-}
-
-// PrintlnFunc returns a new function that prints the passed arguments as
-// colorized with color.Println().
-func (c *Color) PrintlnFunc() func(a ...interface{}) {
- return func(a ...interface{}) {
- c.Println(a...)
- }
-}
-
-// SprintFunc returns a new function that returns colorized strings for the
-// given arguments with fmt.Sprint(). Useful to put into or mix into other
-// string. Windows users should use this in conjunction with color.Output, example:
-//
-// put := New(FgYellow).SprintFunc()
-// fmt.Fprintf(color.Output, "This is a %s", put("warning"))
-func (c *Color) SprintFunc() func(a ...interface{}) string {
- return func(a ...interface{}) string {
- return c.wrap(fmt.Sprint(a...))
- }
-}
-
-// SprintfFunc returns a new function that returns colorized strings for the
-// given arguments with fmt.Sprintf(). Useful to put into or mix into other
-// string. Windows users should use this in conjunction with color.Output.
-func (c *Color) SprintfFunc() func(format string, a ...interface{}) string {
- return func(format string, a ...interface{}) string {
- return c.wrap(fmt.Sprintf(format, a...))
- }
-}
-
-// SprintlnFunc returns a new function that returns colorized strings for the
-// given arguments with fmt.Sprintln(). Useful to put into or mix into other
-// string. Windows users should use this in conjunction with color.Output.
-func (c *Color) SprintlnFunc() func(a ...interface{}) string {
- return func(a ...interface{}) string {
- return c.wrap(sprintln(a...)) + "\n"
- }
-}
-
-// sequence returns a formatted SGR sequence to be plugged into a "\x1b[...m"
-// an example output might be: "1;36" -> bold cyan
-func (c *Color) sequence() string {
- format := make([]string, len(c.params))
- for i, v := range c.params {
- format[i] = strconv.Itoa(int(v))
- }
-
- return strings.Join(format, ";")
-}
-
-// wrap wraps the s string with the colors attributes. The string is ready to
-// be printed.
-func (c *Color) wrap(s string) string {
- if c.isNoColorSet() {
- return s
- }
-
- return c.format() + s + c.unformat()
-}
-
-func (c *Color) format() string {
- return fmt.Sprintf("%s[%sm", escape, c.sequence())
-}
-
-func (c *Color) unformat() string {
- //return fmt.Sprintf("%s[%dm", escape, Reset)
- //for each element in sequence let's use the specific reset escape, or the generic one if not found
- format := make([]string, len(c.params))
- for i, v := range c.params {
- format[i] = strconv.Itoa(int(Reset))
- ra, ok := mapResetAttributes[v]
- if ok {
- format[i] = strconv.Itoa(int(ra))
- }
- }
-
- return fmt.Sprintf("%s[%sm", escape, strings.Join(format, ";"))
-}
-
-// DisableColor disables the color output. Useful to not change any existing
-// code and still being able to output. Can be used for flags like
-// "--no-color". To enable back use EnableColor() method.
-func (c *Color) DisableColor() {
- c.noColor = boolPtr(true)
-}
-
-// EnableColor enables the color output. Use it in conjunction with
-// DisableColor(). Otherwise, this method has no side effects.
-func (c *Color) EnableColor() {
- c.noColor = boolPtr(false)
-}
-
-func (c *Color) isNoColorSet() bool {
- // check first if we have user set action
- if c.noColor != nil {
- return *c.noColor
- }
-
- // if not return the global option, which is disabled by default
- return NoColor
-}
-
-// Equals returns a boolean value indicating whether two colors are equal.
-func (c *Color) Equals(c2 *Color) bool {
- if c == nil && c2 == nil {
- return true
- }
- if c == nil || c2 == nil {
- return false
- }
- if len(c.params) != len(c2.params) {
- return false
- }
-
- for _, attr := range c.params {
- if !c2.attrExists(attr) {
- return false
- }
- }
-
- return true
-}
-
-func (c *Color) attrExists(a Attribute) bool {
- for _, attr := range c.params {
- if attr == a {
- return true
- }
- }
-
- return false
-}
-
-func boolPtr(v bool) *bool {
- return &v
-}
-
-func getCachedColor(p Attribute) *Color {
- colorsCacheMu.Lock()
- defer colorsCacheMu.Unlock()
-
- c, ok := colorsCache[p]
- if !ok {
- c = New(p)
- colorsCache[p] = c
- }
-
- return c
-}
-
-func colorPrint(format string, p Attribute, a ...interface{}) {
- c := getCachedColor(p)
-
- if !strings.HasSuffix(format, "\n") {
- format += "\n"
- }
-
- if len(a) == 0 {
- c.Print(format)
- } else {
- c.Printf(format, a...)
- }
-}
-
-func colorString(format string, p Attribute, a ...interface{}) string {
- c := getCachedColor(p)
-
- if len(a) == 0 {
- return c.SprintFunc()(format)
- }
-
- return c.SprintfFunc()(format, a...)
-}
-
-// Black is a convenient helper function to print with black foreground. A
-// newline is appended to format by default.
-func Black(format string, a ...interface{}) { colorPrint(format, FgBlack, a...) }
-
-// Red is a convenient helper function to print with red foreground. A
-// newline is appended to format by default.
-func Red(format string, a ...interface{}) { colorPrint(format, FgRed, a...) }
-
-// Green is a convenient helper function to print with green foreground. A
-// newline is appended to format by default.
-func Green(format string, a ...interface{}) { colorPrint(format, FgGreen, a...) }
-
-// Yellow is a convenient helper function to print with yellow foreground.
-// A newline is appended to format by default.
-func Yellow(format string, a ...interface{}) { colorPrint(format, FgYellow, a...) }
-
-// Blue is a convenient helper function to print with blue foreground. A
-// newline is appended to format by default.
-func Blue(format string, a ...interface{}) { colorPrint(format, FgBlue, a...) }
-
-// Magenta is a convenient helper function to print with magenta foreground.
-// A newline is appended to format by default.
-func Magenta(format string, a ...interface{}) { colorPrint(format, FgMagenta, a...) }
-
-// Cyan is a convenient helper function to print with cyan foreground. A
-// newline is appended to format by default.
-func Cyan(format string, a ...interface{}) { colorPrint(format, FgCyan, a...) }
-
-// White is a convenient helper function to print with white foreground. A
-// newline is appended to format by default.
-func White(format string, a ...interface{}) { colorPrint(format, FgWhite, a...) }
-
-// BlackString is a convenient helper function to return a string with black
-// foreground.
-func BlackString(format string, a ...interface{}) string { return colorString(format, FgBlack, a...) }
-
-// RedString is a convenient helper function to return a string with red
-// foreground.
-func RedString(format string, a ...interface{}) string { return colorString(format, FgRed, a...) }
-
-// GreenString is a convenient helper function to return a string with green
-// foreground.
-func GreenString(format string, a ...interface{}) string { return colorString(format, FgGreen, a...) }
-
-// YellowString is a convenient helper function to return a string with yellow
-// foreground.
-func YellowString(format string, a ...interface{}) string { return colorString(format, FgYellow, a...) }
-
-// BlueString is a convenient helper function to return a string with blue
-// foreground.
-func BlueString(format string, a ...interface{}) string { return colorString(format, FgBlue, a...) }
-
-// MagentaString is a convenient helper function to return a string with magenta
-// foreground.
-func MagentaString(format string, a ...interface{}) string {
- return colorString(format, FgMagenta, a...)
-}
-
-// CyanString is a convenient helper function to return a string with cyan
-// foreground.
-func CyanString(format string, a ...interface{}) string { return colorString(format, FgCyan, a...) }
-
-// WhiteString is a convenient helper function to return a string with white
-// foreground.
-func WhiteString(format string, a ...interface{}) string { return colorString(format, FgWhite, a...) }
-
-// HiBlack is a convenient helper function to print with hi-intensity black foreground. A
-// newline is appended to format by default.
-func HiBlack(format string, a ...interface{}) { colorPrint(format, FgHiBlack, a...) }
-
-// HiRed is a convenient helper function to print with hi-intensity red foreground. A
-// newline is appended to format by default.
-func HiRed(format string, a ...interface{}) { colorPrint(format, FgHiRed, a...) }
-
-// HiGreen is a convenient helper function to print with hi-intensity green foreground. A
-// newline is appended to format by default.
-func HiGreen(format string, a ...interface{}) { colorPrint(format, FgHiGreen, a...) }
-
-// HiYellow is a convenient helper function to print with hi-intensity yellow foreground.
-// A newline is appended to format by default.
-func HiYellow(format string, a ...interface{}) { colorPrint(format, FgHiYellow, a...) }
-
-// HiBlue is a convenient helper function to print with hi-intensity blue foreground. A
-// newline is appended to format by default.
-func HiBlue(format string, a ...interface{}) { colorPrint(format, FgHiBlue, a...) }
-
-// HiMagenta is a convenient helper function to print with hi-intensity magenta foreground.
-// A newline is appended to format by default.
-func HiMagenta(format string, a ...interface{}) { colorPrint(format, FgHiMagenta, a...) }
-
-// HiCyan is a convenient helper function to print with hi-intensity cyan foreground. A
-// newline is appended to format by default.
-func HiCyan(format string, a ...interface{}) { colorPrint(format, FgHiCyan, a...) }
-
-// HiWhite is a convenient helper function to print with hi-intensity white foreground. A
-// newline is appended to format by default.
-func HiWhite(format string, a ...interface{}) { colorPrint(format, FgHiWhite, a...) }
-
-// HiBlackString is a convenient helper function to return a string with hi-intensity black
-// foreground.
-func HiBlackString(format string, a ...interface{}) string {
- return colorString(format, FgHiBlack, a...)
-}
-
-// HiRedString is a convenient helper function to return a string with hi-intensity red
-// foreground.
-func HiRedString(format string, a ...interface{}) string { return colorString(format, FgHiRed, a...) }
-
-// HiGreenString is a convenient helper function to return a string with hi-intensity green
-// foreground.
-func HiGreenString(format string, a ...interface{}) string {
- return colorString(format, FgHiGreen, a...)
-}
-
-// HiYellowString is a convenient helper function to return a string with hi-intensity yellow
-// foreground.
-func HiYellowString(format string, a ...interface{}) string {
- return colorString(format, FgHiYellow, a...)
-}
-
-// HiBlueString is a convenient helper function to return a string with hi-intensity blue
-// foreground.
-func HiBlueString(format string, a ...interface{}) string { return colorString(format, FgHiBlue, a...) }
-
-// HiMagentaString is a convenient helper function to return a string with hi-intensity magenta
-// foreground.
-func HiMagentaString(format string, a ...interface{}) string {
- return colorString(format, FgHiMagenta, a...)
-}
-
-// HiCyanString is a convenient helper function to return a string with hi-intensity cyan
-// foreground.
-func HiCyanString(format string, a ...interface{}) string { return colorString(format, FgHiCyan, a...) }
-
-// HiWhiteString is a convenient helper function to return a string with hi-intensity white
-// foreground.
-func HiWhiteString(format string, a ...interface{}) string {
- return colorString(format, FgHiWhite, a...)
-}
-
-// sprintln is a helper function to format a string with fmt.Sprintln and trim the trailing newline.
-func sprintln(a ...interface{}) string {
- return strings.TrimSuffix(fmt.Sprintln(a...), "\n")
-}
diff --git a/vendor/github.com/fatih/color/color_windows.go b/vendor/github.com/fatih/color/color_windows.go
deleted file mode 100644
index be01c558e5..0000000000
--- a/vendor/github.com/fatih/color/color_windows.go
+++ /dev/null
@@ -1,19 +0,0 @@
-package color
-
-import (
- "os"
-
- "golang.org/x/sys/windows"
-)
-
-func init() {
- // Opt-in for ansi color support for current process.
- // https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#output-sequences
- var outMode uint32
- out := windows.Handle(os.Stdout.Fd())
- if err := windows.GetConsoleMode(out, &outMode); err != nil {
- return
- }
- outMode |= windows.ENABLE_PROCESSED_OUTPUT | windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING
- _ = windows.SetConsoleMode(out, outMode)
-}
diff --git a/vendor/github.com/fatih/color/doc.go b/vendor/github.com/fatih/color/doc.go
deleted file mode 100644
index 9491ad5413..0000000000
--- a/vendor/github.com/fatih/color/doc.go
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
-Package color is an ANSI color package to output colorized or SGR defined
-output to the standard output. The API can be used in several way, pick one
-that suits you.
-
-Use simple and default helper functions with predefined foreground colors:
-
- color.Cyan("Prints text in cyan.")
-
- // a newline will be appended automatically
- color.Blue("Prints %s in blue.", "text")
-
- // More default foreground colors..
- color.Red("We have red")
- color.Yellow("Yellow color too!")
- color.Magenta("And many others ..")
-
- // Hi-intensity colors
- color.HiGreen("Bright green color.")
- color.HiBlack("Bright black means gray..")
- color.HiWhite("Shiny white color!")
-
-However, there are times when custom color mixes are required. Below are some
-examples to create custom color objects and use the print functions of each
-separate color object.
-
- // Create a new color object
- c := color.New(color.FgCyan).Add(color.Underline)
- c.Println("Prints cyan text with an underline.")
-
- // Or just add them to New()
- d := color.New(color.FgCyan, color.Bold)
- d.Printf("This prints bold cyan %s\n", "too!.")
-
-
- // Mix up foreground and background colors, create new mixes!
- red := color.New(color.FgRed)
-
- boldRed := red.Add(color.Bold)
- boldRed.Println("This will print text in bold red.")
-
- whiteBackground := red.Add(color.BgWhite)
- whiteBackground.Println("Red text with White background.")
-
- // Use your own io.Writer output
- color.New(color.FgBlue).Fprintln(myWriter, "blue color!")
-
- blue := color.New(color.FgBlue)
- blue.Fprint(myWriter, "This will print text in blue.")
-
-You can create PrintXxx functions to simplify even more:
-
- // Create a custom print function for convenient
- red := color.New(color.FgRed).PrintfFunc()
- red("warning")
- red("error: %s", err)
-
- // Mix up multiple attributes
- notice := color.New(color.Bold, color.FgGreen).PrintlnFunc()
- notice("don't forget this...")
-
-You can also FprintXxx functions to pass your own io.Writer:
-
- blue := color.New(FgBlue).FprintfFunc()
- blue(myWriter, "important notice: %s", stars)
-
- // Mix up with multiple attributes
- success := color.New(color.Bold, color.FgGreen).FprintlnFunc()
- success(myWriter, don't forget this...")
-
-Or create SprintXxx functions to mix strings with other non-colorized strings:
-
- yellow := New(FgYellow).SprintFunc()
- red := New(FgRed).SprintFunc()
-
- fmt.Printf("this is a %s and this is %s.\n", yellow("warning"), red("error"))
-
- info := New(FgWhite, BgGreen).SprintFunc()
- fmt.Printf("this %s rocks!\n", info("package"))
-
-Windows support is enabled by default. All Print functions work as intended.
-However, only for color.SprintXXX functions, user should use fmt.FprintXXX and
-set the output to color.Output:
-
- fmt.Fprintf(color.Output, "Windows support: %s", color.GreenString("PASS"))
-
- info := New(FgWhite, BgGreen).SprintFunc()
- fmt.Fprintf(color.Output, "this %s rocks!\n", info("package"))
-
-Using with existing code is possible. Just use the Set() method to set the
-standard output to the given parameters. That way a rewrite of an existing
-code is not required.
-
- // Use handy standard colors.
- color.Set(color.FgYellow)
-
- fmt.Println("Existing text will be now in Yellow")
- fmt.Printf("This one %s\n", "too")
-
- color.Unset() // don't forget to unset
-
- // You can mix up parameters
- color.Set(color.FgMagenta, color.Bold)
- defer color.Unset() // use it in your function
-
- fmt.Println("All text will be now bold magenta.")
-
-There might be a case where you want to disable color output (for example to
-pipe the standard output of your app to somewhere else). `Color` has support to
-disable colors both globally and for single color definition. For example
-suppose you have a CLI app and a `--no-color` bool flag. You can easily disable
-the color output with:
-
- var flagNoColor = flag.Bool("no-color", false, "Disable color output")
-
- if *flagNoColor {
- color.NoColor = true // disables colorized output
- }
-
-You can also disable the color by setting the NO_COLOR environment variable to any value.
-
-It also has support for single color definitions (local). You can
-disable/enable color output on the fly:
-
- c := color.New(color.FgCyan)
- c.Println("Prints cyan text")
-
- c.DisableColor()
- c.Println("This is printed without any color")
-
- c.EnableColor()
- c.Println("This prints again cyan...")
-*/
-package color
diff --git a/vendor/github.com/olekukonko/cat/.gitignore b/vendor/github.com/olekukonko/cat/.gitignore
deleted file mode 100644
index fbfee294e8..0000000000
--- a/vendor/github.com/olekukonko/cat/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-.idea
-.github
-lab
\ No newline at end of file
diff --git a/vendor/github.com/olekukonko/cat/LICENSE b/vendor/github.com/olekukonko/cat/LICENSE
deleted file mode 100644
index ca02db8c27..0000000000
--- a/vendor/github.com/olekukonko/cat/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-MIT 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.
diff --git a/vendor/github.com/olekukonko/cat/README.md b/vendor/github.com/olekukonko/cat/README.md
deleted file mode 100644
index c54e35f226..0000000000
--- a/vendor/github.com/olekukonko/cat/README.md
+++ /dev/null
@@ -1,168 +0,0 @@
-# 🐱 `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. 😻
\ No newline at end of file
diff --git a/vendor/github.com/olekukonko/cat/builder.go b/vendor/github.com/olekukonko/cat/builder.go
deleted file mode 100644
index 6f36b4f459..0000000000
--- a/vendor/github.com/olekukonko/cat/builder.go
+++ /dev/null
@@ -1,124 +0,0 @@
-package 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)
- }
-}
diff --git a/vendor/github.com/olekukonko/cat/cat.go b/vendor/github.com/olekukonko/cat/cat.go
deleted file mode 100644
index 03b20bea22..0000000000
--- a/vendor/github.com/olekukonko/cat/cat.go
+++ /dev/null
@@ -1,117 +0,0 @@
-// 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()
-}
diff --git a/vendor/github.com/olekukonko/cat/concat.go b/vendor/github.com/olekukonko/cat/concat.go
deleted file mode 100644
index 251c49c73f..0000000000
--- a/vendor/github.com/olekukonko/cat/concat.go
+++ /dev/null
@@ -1,590 +0,0 @@
-package 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)
-}
diff --git a/vendor/github.com/olekukonko/cat/fn.go b/vendor/github.com/olekukonko/cat/fn.go
deleted file mode 100644
index b4f5fc878a..0000000000
--- a/vendor/github.com/olekukonko/cat/fn.go
+++ /dev/null
@@ -1,376 +0,0 @@
-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)
- }
- }
-}
diff --git a/vendor/github.com/olekukonko/cat/sql.go b/vendor/github.com/olekukonko/cat/sql.go
deleted file mode 100644
index baa3e68c52..0000000000
--- a/vendor/github.com/olekukonko/cat/sql.go
+++ /dev/null
@@ -1,161 +0,0 @@
-package 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...)
-}
diff --git a/vendor/github.com/olekukonko/errors/.gitignore b/vendor/github.com/olekukonko/errors/.gitignore
deleted file mode 100644
index 8a1327cd96..0000000000
--- a/vendor/github.com/olekukonko/errors/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-# Created by .ignore support plugin (hsz.mobi)
-.idea/
-tmp/
\ No newline at end of file
diff --git a/vendor/github.com/olekukonko/errors/LICENSE b/vendor/github.com/olekukonko/errors/LICENSE
deleted file mode 100644
index ca02db8c27..0000000000
--- a/vendor/github.com/olekukonko/errors/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-MIT 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.
diff --git a/vendor/github.com/olekukonko/errors/README.md b/vendor/github.com/olekukonko/errors/README.md
deleted file mode 100644
index 8830486ea0..0000000000
--- a/vendor/github.com/olekukonko/errors/README.md
+++ /dev/null
@@ -1,1565 +0,0 @@
-# Enhanced Error Handling for Go with Context, Stack Traces, Monitoring, and More
-
-[](https://pkg.go.dev/github.com/olekukonko/errors)
-[](https://goreportcard.com/report/github.com/olekukonko/errors)
-[](LICENSE)
-[](README.md#benchmarks)
-
-A production-grade error handling library for Go, offering zero-cost abstractions, stack traces, multi-error support, retries, and advanced monitoring through two complementary packages: `errors` (core) and `errmgr` (management).
-
-## Features
-
-### `errors` Package (Core)
-- **Performance Optimized**
- - Optional memory pooling (12 ns/op with pooling)
- - Lazy stack trace collection (205 ns/op with stack)
- - Small context optimization (≤4 items, 40 ns/op)
- - Lock-free configuration reads
-
-- **Debugging Tools**
- - Full stack traces with internal frame filtering
- - Error wrapping and chaining
- - Structured context attachment
- - JSON serialization (662 ns/op)
-
-- **Advanced Utilities**
- - Configurable retry mechanism
- - Multi-error aggregation with sampling
- - HTTP status code support
- - Callback triggers for cleanup or side effects
-
-### `errmgr` Package (Management)
-- **Production Monitoring**
- - Error occurrence counting
- - Threshold-based alerting
- - Categorized metrics
- - Predefined error templates
-
-## Installation
-
-```bash
-go get github.com/olekukonko/errors@latest
-```
-
-## Package Overview
-
-- **`errors`**: Core error handling with creation, wrapping, context, stack traces, retries, and multi-error support.
-- **`errmgr`**: Error management with templates, monitoring, and predefined errors for consistent application use.
-
----
-
-> [!NOTE]
-> ✓ added support for `errors.Errorf("user %w not found", errors.New("bob"))`
-> ✓ added support for `sequential chain` execution
-``
-
-## Using the `errors` Package
-
-### Basic Error Creation
-
-#### Simple Error
-```go
-package main
-
-import (
- "fmt"
- "github.com/olekukonko/errors"
-)
-
-func main() {
- // Fast error with no stack trace
- err := errors.New("connection failed")
- fmt.Println(err) // "connection failed"
-
- // Standard error, no allocation, same speed
- stdErr := errors.Std("connection failed")
- fmt.Println(stdErr) // "connection failed"
-}
-```
-
-#### Formatted Error
-```go
-// main.go
-package main
-
-import (
- "fmt"
- "github.com/olekukonko/errors"
-)
-
-func main() {
- // Formatted error without stack trace
- errNoWrap := errors.Newf("user %s not found", "bob")
- fmt.Println(errNoWrap) // Output: "user bob not found"
-
- // Standard formatted error, no fmt.Errorf needed (using own pkg)
- stdErrNoWrap := errors.Stdf("user %s not found", "bob")
- fmt.Println(stdErrNoWrap) // Output: "user bob not found"
-
- // Added support for %w (compatible with fmt.Errorf output)
- // errors.Errorf is alias of errors.Newf
- errWrap := errors.Errorf("user %w not found", errors.New("bob"))
- fmt.Println(errWrap) // Output: "user bob not found"
-
- // Standard formatted error for comparison
- stdErrWrap := fmt.Errorf("user %w not found", fmt.Errorf("bob"))
- fmt.Println(stdErrWrap) // Output: "user bob not found"
-}
-```
-
-#### Error with Stack Trace
-```go
-package main
-
-import (
- "fmt"
- "github.com/olekukonko/errors"
-)
-
-func main() {
- // Create an error with stack trace using Trace
- err := errors.Trace("critical issue")
- fmt.Println(err) // Output: "critical issue"
- fmt.Println(err.Stack()) // Output: e.g., ["main.go:15", "caller.go:42"]
-
- // Convert basic error to traceable with WithStack
- errS := errors.New("critical issue")
- errS = errS.WithStack() // Add stack trace and update error
- fmt.Println(errS) // Output: "critical issue"
- fmt.Println(errS.Stack()) // Output: e.g., ["main.go:19", "caller.go:42"]
-}
-```
-
-#### Named Error
-```go
-package main
-
-import (
- "fmt"
- "github.com/olekukonko/errors"
-)
-
-func main() {
- // Create a named error with stack trace
- err := errors.Named("InputError")
- fmt.Println(err.Name()) // Output: "InputError"
- fmt.Println(err) // Output: "InputError"
-}
-```
-
-### Adding Context
-
-#### Basic Context
-```go
-package main
-
-import (
- "fmt"
- "github.com/olekukonko/errors"
-)
-
-func main() {
- // Create an error with context
- err := errors.New("processing failed").
- With("id", "123").
- With("attempt", 3).
- With("retryable", true)
- fmt.Println("Error:", err) // Output: "processing failed"
- fmt.Println("Full context:", errors.Context(err)) // Output: map[id:123 attempt:3 retryable:true]
-}
-```
-
-#### Context with Wrapped Standard Error
-```go
-package main
-
-import (
- "fmt"
- "github.com/olekukonko/errors"
-)
-
-func main() {
- // Wrap a standard error and add context
- err := errors.New("processing failed").
- With("id", "123")
- wrapped := fmt.Errorf("wrapped: %w", err)
- fmt.Println("Wrapped error:", wrapped) // Output: "wrapped: processing failed"
- fmt.Println("Direct context:", errors.Context(wrapped)) // Output: nil
-
- // Convert to access context
- e := errors.Convert(wrapped)
- fmt.Println("Converted context:", e.Context()) // Output: map[id:123]
-}
-```
-
-#### Adding Context to Standard Error
-```go
-package main
-
-import (
- "fmt"
- "github.com/olekukonko/errors"
-)
-
-func main() {
- // Convert a standard error and add context
- stdErr := fmt.Errorf("standard error")
- converted := errors.Convert(stdErr).
- With("source", "legacy").
- With("severity", "high")
- fmt.Println("Message:", converted.Error()) // Output: "standard error"
- fmt.Println("Context:", converted.Context()) // Output: map[source:legacy severity:high]
-}
-```
-
-#### Complex Context
-```go
-package main
-
-import (
- "fmt"
- "github.com/olekukonko/errors"
-)
-
-func main() {
- // Create an error with complex context
- err := errors.New("database operation failed").
- With("query", "SELECT * FROM users").
- With("params", map[string]interface{}{
- "limit": 100,
- "offset": 0,
- }).
- With("duration_ms", 45.2)
- fmt.Println("Complex error context:")
- for k, v := range errors.Context(err) {
- fmt.Printf("%s: %v (%T)\n", k, v, v)
- }
- // Output:
- // query: SELECT * FROM users (string)
- // params: map[limit:100 offset:0] (map[string]interface {})
- // duration_ms: 45.2 (float64)
-}
-```
-
-### Stack Traces
-
-#### Adding Stack to Any Error
-```go
-package main
-
-import (
- "fmt"
- "github.com/olekukonko/errors"
-)
-
-func main() {
- // Add stack trace to a standard error
- err := fmt.Errorf("basic error")
- enhanced := errors.WithStack(err)
- fmt.Println("Error with stack:")
- fmt.Println("Message:", enhanced.Error()) // Output: "basic error"
- fmt.Println("Stack:", enhanced.Stack()) // Output: e.g., "main.go:15"
-}
-```
-
-#### Chaining with Stack
-```go
-package main
-
-import (
- "fmt"
- "github.com/olekukonko/errors"
- "time"
-)
-
-func main() {
- // Create an enhanced error and add stack/context
- err := errors.New("validation error").
- With("field", "email")
- stackErr := errors.WithStack(err).
- With("timestamp", time.Now()).
- WithCode(500)
- fmt.Println("Message:", stackErr.Error()) // Output: "validation error"
- fmt.Println("Context:", stackErr.Context()) // Output: map[field:email timestamp:...]
- fmt.Println("Stack:")
- for _, frame := range stackErr.Stack() {
- fmt.Println(frame)
- }
-}
-```
-### Stack Traces with `WithStack()`
-```go
-package main
-
-import (
- "fmt"
- "github.com/olekukonko/errors"
- "math/rand"
- "time"
-)
-
-func basicFunc() error {
- return fmt.Errorf("basic error")
-}
-
-func enhancedFunc() *errors.Error {
- return errors.New("enhanced error")
-}
-
-func main() {
- // 1. Package-level WithStack - works with ANY error type
- err1 := basicFunc()
- enhanced1 := errors.WithStack(err1) // Handles basic errors
- fmt.Println("Package-level WithStack:")
- fmt.Println(enhanced1.Stack())
-
- // 2. Method-style WithStack - only for *errors.Error
- err2 := enhancedFunc()
- enhanced2 := err2.WithStack() // More natural chaining
- fmt.Println("\nMethod-style WithStack:")
- fmt.Println(enhanced2.Stack())
-
- // 3. Combined usage in real-world scenario
- result := processData()
- if result != nil {
- // Use package-level when type is unknown
- stackErr := errors.WithStack(result)
-
- // Then use method-style for chaining
- finalErr := stackErr.
- With("timestamp", time.Now()).
- WithCode(500)
-
- fmt.Println("\nCombined Usage:")
- fmt.Println("Message:", finalErr.Error())
- fmt.Println("Context:", finalErr.Context())
- fmt.Println("Stack:")
- for _, frame := range finalErr.Stack() {
- fmt.Println(frame)
- }
- }
-}
-
-func processData() error {
- // Could return either basic or enhanced error
- if rand.Intn(2) == 0 {
- return fmt.Errorf("database error")
- }
- return errors.New("validation error").With("field", "email")
-}
-```
-
-### Error Wrapping and Chaining
-
-#### Basic Wrapping
-```go
-package main
-
-import (
- "fmt"
- "github.com/olekukonko/errors"
-)
-
-func main() {
- // Wrap an error with additional context
- lowErr := errors.New("low-level failure")
- highErr := errors.Wrapf(lowErr, "high-level operation failed: %s", "details")
- fmt.Println(highErr) // Output: "high-level operation failed: details: low-level failure"
- fmt.Println(errors.Unwrap(highErr)) // Output: "low-level failure"
-}
-```
-
-#### Walking Error Chain
-```go
-package main
-
-import (
- "fmt"
- "github.com/olekukonko/errors"
-)
-
-func main() {
- // Create a chained error
- dbErr := errors.New("connection timeout").
- With("server", "db01.prod")
- bizErr := errors.New("failed to process user 12345").
- With("user_id", "12345").
- Wrap(dbErr)
- apiErr := errors.New("API request failed").
- WithCode(500).
- Wrap(bizErr)
-
- // Walk the error chain
- fmt.Println("Error Chain:")
- for i, e := range errors.UnwrapAll(apiErr) {
- fmt.Printf("%d. %s\n", i+1, e)
- }
- // Output:
- // 1. API request failed
- // 2. failed to process user 12345
- // 3. connection timeout
-}
-```
-
-### Type Assertions
-
-#### Using Is
-```go
-package main
-
-import (
- "fmt"
- "github.com/olekukonko/errors"
-)
-
-func main() {
- // Check error type with Is
- err := errors.Named("AuthError")
- wrapped := errors.Wrapf(err, "login failed")
- if errors.Is(wrapped, err) {
- fmt.Println("Is an AuthError") // Output: "Is an AuthError"
- }
-}
-```
-
-#### Using As
-```go
-package main
-
-import (
- "fmt"
- "github.com/olekukonko/errors"
-)
-
-func main() {
- // Extract error type with As
- err := errors.Named("AuthError")
- wrapped := errors.Wrapf(err, "login failed")
- var authErr *errors.Error
- if wrapped.As(&authErr) {
- fmt.Println("Extracted:", authErr.Name()) // Output: "Extracted: AuthError"
- }
-}
-```
-
-### Retry Mechanism
-
-#### Basic Retry
-```go
-package main
-
-import (
- "fmt"
- "github.com/olekukonko/errors"
- "math/rand"
- "time"
-)
-
-func main() {
- // Simulate a flaky operation
- attempts := 0
- retry := errors.NewRetry(
- errors.WithMaxAttempts(3),
- errors.WithDelay(100*time.Millisecond),
- )
- err := retry.Execute(func() error {
- attempts++
- if rand.Intn(2) == 0 {
- return errors.New("temporary failure").WithRetryable()
- }
- return nil
- })
- if err != nil {
- fmt.Printf("Failed after %d attempts: %v\n", attempts, err)
- } else {
- fmt.Printf("Succeeded after %d attempts\n", attempts)
- }
-}
-
-```
-
-#### Retry with Context Timeout
-```go
-package main
-
-import (
- "context"
- "fmt"
- "github.com/olekukonko/errors"
- "time"
-)
-
-func main() {
- // Retry with context timeout
- ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
- defer cancel()
- retry := errors.NewRetry(
- errors.WithContext(ctx),
- errors.WithMaxAttempts(5),
- errors.WithDelay(200*time.Millisecond),
- )
- err := retry.Execute(func() error {
- return errors.New("operation failed").WithRetryable()
- })
- if errors.Is(err, context.DeadlineExceeded) {
- fmt.Println("Operation timed out:", err)
- } else if err != nil {
- fmt.Println("Operation failed:", err)
- }
-}
-```
-
-
-### Retry Comprehensive
-
-```go
-package main
-
-import (
- "context"
- "fmt"
- "github.com/olekukonko/errors"
- "math/rand"
- "time"
-)
-
-// DatabaseClient simulates a flaky database connection
-type DatabaseClient struct {
- healthyAfterAttempt int
-}
-
-func (db *DatabaseClient) Query() error {
- if db.healthyAfterAttempt > 0 {
- db.healthyAfterAttempt--
- return errors.New("database connection failed").
- With("attempt_remaining", db.healthyAfterAttempt).
- WithRetryable() // Mark as retryable
- }
- return nil
-}
-
-// ExternalService simulates an unreliable external API
-func ExternalService() error {
- if rand.Intn(100) < 30 { // 30% failure rate
- return errors.New("service unavailable").
- WithCode(503).
- WithRetryable()
- }
- return nil
-}
-
-func main() {
- // Configure retry with exponential backoff and jitter
- retry := errors.NewRetry(
- errors.WithMaxAttempts(5),
- errors.WithDelay(200*time.Millisecond),
- errors.WithMaxDelay(2*time.Second),
- errors.WithJitter(true),
- errors.WithBackoff(errors.ExponentialBackoff{}),
- errors.WithOnRetry(func(attempt int, err error) {
- // Calculate delay using the same logic as in Execute
- baseDelay := 200 * time.Millisecond
- maxDelay := 2 * time.Second
- delay := errors.ExponentialBackoff{}.Backoff(attempt, baseDelay)
- if delay > maxDelay {
- delay = maxDelay
- }
- fmt.Printf("Attempt %d failed: %v (retrying in %v)\n",
- attempt,
- err.Error(),
- delay)
- }),
- )
-
- // Scenario 1: Database connection with known recovery point
- db := &DatabaseClient{healthyAfterAttempt: 3}
- fmt.Println("Starting database operation...")
- err := retry.Execute(func() error {
- return db.Query()
- })
- if err != nil {
- fmt.Printf("Database operation failed after %d attempts: %v\n", retry.Attempts(), err)
- } else {
- fmt.Println("Database operation succeeded!")
- }
-
- // Scenario 2: External service with random failures
- fmt.Println("\nStarting external service call...")
- var lastAttempts int
- start := time.Now()
-
- // Using ExecuteReply to demonstrate return values
- result, err := errors.ExecuteReply[string](retry, func() (string, error) {
- lastAttempts++
- if err := ExternalService(); err != nil {
- return "", err
- }
- return "service response data", nil
- })
-
- duration := time.Since(start)
-
- if err != nil {
- fmt.Printf("Service call failed after %d attempts (%.2f sec): %v\n",
- lastAttempts,
- duration.Seconds(),
- err)
- } else {
- fmt.Printf("Service call succeeded after %d attempts (%.2f sec): %s\n",
- lastAttempts,
- duration.Seconds(),
- result)
- }
-
- // Scenario 3: Context cancellation with more visibility
- fmt.Println("\nStarting operation with timeout...")
- ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
- defer cancel()
-
- timeoutRetry := retry.Transform(
- errors.WithContext(ctx),
- errors.WithMaxAttempts(10),
- errors.WithOnRetry(func(attempt int, err error) {
- fmt.Printf("Timeout scenario attempt %d: %v\n", attempt, err)
- }),
- )
-
- startTimeout := time.Now()
- err = timeoutRetry.Execute(func() error {
- time.Sleep(300 * time.Millisecond) // Simulate long operation
- return errors.New("operation timed out")
- })
-
- if errors.Is(err, context.DeadlineExceeded) {
- fmt.Printf("Operation cancelled by timeout after %.2f sec: %v\n",
- time.Since(startTimeout).Seconds(),
- err)
- } else if err != nil {
- fmt.Printf("Operation failed: %v\n", err)
- } else {
- fmt.Println("Operation succeeded (unexpected)")
- }
-}
-```
-
-### Multi-Error Aggregation
-
-#### Form Validation
-```go
-package main
-
-import (
- "fmt"
- "github.com/olekukonko/errors"
-)
-
-func main() {
- // Validate a form with multiple errors
- multi := errors.NewMultiError()
- multi.Add(errors.New("name is required"))
- multi.Add(errors.New("email is invalid"))
- multi.Add(errors.New("password too short"))
- if multi.Has() {
- fmt.Println(multi) // Output: "errors(3): name is required; email is invalid; password too short"
- fmt.Printf("Total errors: %d\n", multi.Count())
- }
-}
-```
-
-#### Sampling Multi-Errors
-```go
-package main
-
-import (
- "fmt"
- "github.com/olekukonko/errors"
-)
-
-func main() {
- // Simulate many errors with sampling
- multi := errors.NewMultiError(
- errors.WithSampling(10), // 10% sampling
- errors.WithLimit(5),
- )
- for i := 0; i < 100; i++ {
- multi.Add(errors.Newf("error %d", i))
- }
- fmt.Println(multi)
- fmt.Printf("Captured %d out of 100 errors\n", multi.Count())
-}
-```
-
-### Additional Examples
-
-#### Using Callbacks
-```go
-package main
-
-import (
- "fmt"
- "github.com/olekukonko/errors"
-)
-
-func main() {
- // Add a callback to an error
- err := errors.New("transaction failed").
- Callback(func() {
- fmt.Println("Reversing transaction...")
- })
- fmt.Println(err) // Output: "transaction failed" + "Reversing transaction..."
- err.Free()
-}
-```
-
-#### Copying Errors
-```go
-package main
-
-import (
- "fmt"
- "github.com/olekukonko/errors"
-)
-
-func main() {
- // Copy an error and modify the copy
- original := errors.New("base error").With("key", "value")
- copied := original.Copy().With("extra", "data")
- fmt.Println("Original:", original, original.Context()) // Output: "base error" map[key:value]
- fmt.Println("Copied:", copied, copied.Context()) // Output: "base error" map[key:value extra:data]
-}
-```
-
-#### Transforming Errors
-```go
-package main
-
-import (
- "fmt"
- "github.com/olekukonko/errors"
-)
-
-func main() {
- // Transform an error with additional context
- err := errors.New("base error")
- transformed := errors.Transform(err, func(e *errors.Error) {
- e.With("env", "prod").
- WithCode(500).
- WithStack()
- })
- fmt.Println(transformed.Error()) // Output: "base error"
- fmt.Println(transformed.Context()) // Output: map[env:prod]
- fmt.Println(transformed.Code()) // Output: 500
- fmt.Println(len(transformed.Stack()) > 0) // Output: true
- transformed.Free()
-}
-```
-
-### Transformation and Enrichment
-
-```go
-package main
-
-import (
- "fmt"
- "github.com/olekukonko/errors"
-)
-
-func process() error {
- return errors.New("base error")
-}
-
-func main() {
- err := process()
- transformedErr := errors.Transform(err, func(e *errors.Error) {
- e.With("env", "prod").
- WithCode(500).
- WithStack()
- })
-
- // No type assertion needed now
- fmt.Println(transformedErr.Error()) // "base error"
- fmt.Println(transformedErr.Context()) // map[env:prod]
- fmt.Println(transformedErr.Code()) // 500
- fmt.Println(len(transformedErr.Stack()) > 0) // true
- transformedErr.Free() // Clean up
-
- stdErr := process()
- convertedErr := errors.Convert(stdErr) // Convert standard error to *Error
- convertedErr.With("source", "external").
- WithCode(400).
- Callback(func() {
- fmt.Println("Converted error processed...")
- })
- fmt.Println("Converted Error:", convertedErr.Error())
- fmt.Println("Context:", convertedErr.Context())
- fmt.Println("Code:", convertedErr.Code())
- convertedErr.Free()
-
-}
-
-```
-
-#### Fast Stack Trace
-```go
-package main
-
-import (
- "fmt"
- "github.com/olekukonko/errors"
-)
-
-func main() {
- // Get a lightweight stack trace
- err := errors.Trace("lightweight error")
- fastStack := err.FastStack()
- fmt.Println("Fast Stack:")
- for _, frame := range fastStack {
- fmt.Println(frame) // Output: e.g., "main.go:15"
- }
-}
-```
-
-#### WarmStackPool
-```go
-package main
-
-import (
- "fmt"
- "github.com/olekukonko/errors"
-)
-
-func main() {
- // Pre-warm the stack pool
- errors.WarmStackPool(10)
- err := errors.Trace("pre-warmed error")
- fmt.Println("Stack after warming pool:")
- for _, frame := range err.Stack() {
- fmt.Println(frame)
- }
-}
-```
-
-### Multi-Error Aggregation
-
-```go
-package main
-
-import (
- "fmt"
- "net/mail"
- "strings"
- "time"
-
- "github.com/olekukonko/errors"
-)
-
-type UserForm struct {
- Name string
- Email string
- Password string
- Birthday string
-}
-
-func validateUser(form UserForm) *errors.MultiError {
- multi := errors.NewMultiError(
- errors.WithLimit(10),
- errors.WithFormatter(customFormat),
- )
-
- // Name validation
- if form.Name == "" {
- multi.Add(errors.New("name is required"))
- } else if len(form.Name) > 50 {
- multi.Add(errors.New("name cannot exceed 50 characters"))
- }
-
- // Email validation
- if form.Email == "" {
- multi.Add(errors.New("email is required"))
- } else {
- if _, err := mail.ParseAddress(form.Email); err != nil {
- multi.Add(errors.New("invalid email format"))
- }
- if !strings.Contains(form.Email, "@") {
- multi.Add(errors.New("email must contain @ symbol"))
- }
- }
-
- // Password validation
- if len(form.Password) < 8 {
- multi.Add(errors.New("password must be at least 8 characters"))
- }
- if !strings.ContainsAny(form.Password, "0123456789") {
- multi.Add(errors.New("password must contain at least one number"))
- }
- if !strings.ContainsAny(form.Password, "!@#$%^&*") {
- multi.Add(errors.New("password must contain at least one special character"))
- }
-
- // Birthday validation
- if form.Birthday != "" {
- if _, err := time.Parse("2006-01-02", form.Birthday); err != nil {
- multi.Add(errors.New("birthday must be in YYYY-MM-DD format"))
- } else if bday, _ := time.Parse("2006-01-02", form.Birthday); time.Since(bday).Hours()/24/365 < 13 {
- multi.Add(errors.New("must be at least 13 years old"))
- }
- }
-
- return multi
-}
-
-func customFormat(errs []error) string {
- var sb strings.Builder
- sb.WriteString("🚨 Validation Errors:\n")
- for i, err := range errs {
- sb.WriteString(fmt.Sprintf(" %d. %s\n", i+1, err))
- }
- sb.WriteString(fmt.Sprintf("\nTotal issues found: %d\n", len(errs)))
- return sb.String()
-}
-
-func main() {
- fmt.Println("=== User Registration Validation ===")
-
- user := UserForm{
- Name: "", // Empty name
- Email: "invalid-email",
- Password: "weak",
- Birthday: "2015-01-01", // Under 13
- }
-
- // Generate multiple validation errors
- validationErrors := validateUser(user)
-
- if validationErrors.Has() {
- fmt.Println(validationErrors)
-
- // Detailed error analysis
- fmt.Println("\n🔍 Error Analysis:")
- fmt.Printf("Total errors: %d\n", validationErrors.Count())
- fmt.Printf("First error: %v\n", validationErrors.First())
- fmt.Printf("Last error: %v\n", validationErrors.Last())
-
- // Categorized errors with consistent formatting
- fmt.Println("\n📋 Error Categories:")
- if emailErrors := validationErrors.Filter(contains("email")); emailErrors.Has() {
- fmt.Println("Email Issues:")
- if emailErrors.Count() == 1 {
- fmt.Println(customFormat([]error{emailErrors.First()}))
- } else {
- fmt.Println(emailErrors)
- }
- }
- if pwErrors := validationErrors.Filter(contains("password")); pwErrors.Has() {
- fmt.Println("Password Issues:")
- if pwErrors.Count() == 1 {
- fmt.Println(customFormat([]error{pwErrors.First()}))
- } else {
- fmt.Println(pwErrors)
- }
- }
- if ageErrors := validationErrors.Filter(contains("13 years")); ageErrors.Has() {
- fmt.Println("Age Restriction:")
- if ageErrors.Count() == 1 {
- fmt.Println(customFormat([]error{ageErrors.First()}))
- } else {
- fmt.Println(ageErrors)
- }
- }
- }
-
- // System Error Aggregation Example
- fmt.Println("\n=== System Error Aggregation ===")
- systemErrors := errors.NewMultiError(
- errors.WithLimit(5),
- errors.WithFormatter(systemErrorFormat),
- )
-
- // Simulate system errors
- systemErrors.Add(errors.New("database connection timeout").WithRetryable())
- systemErrors.Add(errors.New("API rate limit exceeded").WithRetryable())
- systemErrors.Add(errors.New("disk space low"))
- systemErrors.Add(errors.New("database connection timeout").WithRetryable()) // Duplicate
- systemErrors.Add(errors.New("cache miss"))
- systemErrors.Add(errors.New("database connection timeout").WithRetryable()) // Over limit
-
- fmt.Println(systemErrors)
- fmt.Printf("\nSystem Status: %d active issues\n", systemErrors.Count())
-
- // Filter retryable errors
- if retryable := systemErrors.Filter(errors.IsRetryable); retryable.Has() {
- fmt.Println("\n🔄 Retryable Errors:")
- fmt.Println(retryable)
- }
-}
-
-func systemErrorFormat(errs []error) string {
- var sb strings.Builder
- sb.WriteString("⚠️ System Alerts:\n")
- for i, err := range errs {
- sb.WriteString(fmt.Sprintf(" %d. %s", i+1, err))
- if errors.IsRetryable(err) {
- sb.WriteString(" (retryable)")
- }
- sb.WriteString("\n")
- }
- return sb.String()
-}
-
-func contains(substr string) func(error) bool {
- return func(err error) bool {
- return strings.Contains(err.Error(), substr)
- }
-}
-```
-
-### Chain Execution
-
-#### Sequential Task Processing
-```go
-package main
-
-import (
- "fmt"
- "github.com/olekukonko/errors"
- "time"
-)
-
-// validateOrder checks order input.
-func validateOrder() error {
- return nil // Simulate successful validation
-}
-
-// processKYC handles payment processing.
-func processKYC() error {
- return nil // Simulate successful validation
-}
-
-// processPayment handles payment processing.
-func processPayment() error {
- return errors.New("payment declined") // Simulate payment failure
-}
-
-// generateInvoice creates an invoice.
-func generateInvoice() error {
- return errors.New("invoicing unavailable") // Simulate invoicing issue
-}
-
-// sendNotification sends a confirmation.
-func sendNotification() error {
- return errors.New("notification failed") // Simulate notification failure
-}
-
-// processOrder simulates a multi-step order processing workflow.
-func processOrder() error {
- c := errors.NewChain()
-
- // Validate order input
- c.Step(validateOrder).Tag("validation")
-
- // KYC Process
- c.Step(validateOrder).Tag("validation")
-
- // Process payment with retries
- c.Step(processPayment).Tag("billing").Retry(3, 100*time.Millisecond)
-
- // Generate invoice
- c.Step(generateInvoice).Tag("invoicing")
-
- // Send notification (optional)
- c.Step(sendNotification).Tag("notification").Optional()
-
- return c.Run()
-}
-
-func main() {
- if err := processOrder(); err != nil {
- // Print error to stderr and exit
- errors.Inspect(err)
- }
- fmt.Println("Order processed successfully")
-}
-```
-
-#### Sequential Task Processing 2
-```go
-package main
-
-import (
- "fmt"
- "os"
-
- "github.com/olekukonko/errors"
-)
-
-// validate simulates a validation check that fails.
-func validate(name string) error {
- return errors.Newf("validation for %s failed", name)
-}
-
-// validateOrder checks order input.
-func validateOrder() error {
- return nil // Simulate successful validation
-}
-
-// verifyKYC handles Know Your Customer verification.
-func verifyKYC(name string) error {
- return validate(name) // Simulate KYC validation failure
-}
-
-// processPayment handles payment processing.
-func processPayment() error {
- return nil // Simulate successful payment
-}
-
-// processOrder coordinates the order processing workflow.
-func processOrder() error {
- chain := errors.NewChain().
- Step(validateOrder). // Step 1: Validate order
- Call(verifyKYC, "john"). // Step 2: Verify customer
- Step(processPayment) // Step 3: Process payment
-
- if err := chain.Run(); err != nil {
- return errors.Errorf("processing order: %w", err)
- }
- return nil
-}
-
-func main() {
- if err := processOrder(); err != nil {
- // Print the full error chain to stderr
- fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
- // Output
- // ERROR: processing order: validation for john failed
-
- // For debugging, you could print the stack trace:
- // errors.Inspect(err)
- os.Exit(1)
- }
-
- fmt.Println("order processed successfully")
-}
-
-```
-
-
-#### Retry with Timeout
-```go
-package main
-
-import (
- "context"
- "fmt"
- "github.com/olekukonko/errors"
- "time"
-)
-
-func main() {
- c := errors.NewChain(
- errors.ChainWithTimeout(1*time.Second),
- ).
- Step(func() error {
- time.Sleep(2 * time.Second)
- return errors.New("fetch failed")
- }).
- Tag("api").
- Retry(3, 200*time.Millisecond)
-
- err := c.Run()
- if err != nil {
- var deadlineErr error
- if errors.As(err, &deadlineErr) && deadlineErr == context.DeadlineExceeded {
- fmt.Println("Fetch timed out")
- } else {
- fmt.Printf("Fetch failed: %v\n", err)
- }
- return
- }
- fmt.Println("Fetch succeeded")
-}
-```
-
-#### Collecting All Errors
-```go
-package main
-
-import (
- "fmt"
- "github.com/olekukonko/errors"
-)
-
-func main() {
- c := errors.NewChain(
- errors.ChainWithMaxErrors(2),
- ).
- Step(func() error { return errors.New("task 1 failed") }).Tag("task1").
- Step(func() error { return nil }).Tag("task2").
- Step(func() error { return errors.New("task 3 failed") }).Tag("task3")
-
- err := c.RunAll()
- if err != nil {
- errors.Inspect(err)
- return
- }
- fmt.Println("All tasks completed successfully")
-}
-
-```
-
----
-
-## Using the `errmgr` Package
-
-### Predefined Errors
-
-#### Static Error
-```go
-package main
-
-import (
- "fmt"
- "github.com/olekukonko/errors/errmgr"
-)
-
-func main() {
- // Use a predefined static error
- err := errmgr.ErrNotFound
- fmt.Println(err) // Output: "not found"
- fmt.Println(err.Code()) // Output: 404
-}
-```
-
-#### Templated Error
-```go
-package main
-
-import (
- "fmt"
- "github.com/olekukonko/errors/errmgr"
-)
-
-func main() {
- // Use a templated error with category
- err := errmgr.ErrDBQuery("SELECT failed")
- fmt.Println(err) // Output: "database query failed: SELECT failed"
- fmt.Println(err.Category()) // Output: "database"
-}
-```
-
-### Error Monitoring
-
-#### Basic Monitoring
-```go
-package main
-
-import (
- "fmt"
- "github.com/olekukonko/errors/errmgr"
- "time"
-)
-
-func main() {
- // Define and monitor an error
- netErr := errmgr.Define("NetError", "network issue: %s")
- monitor := errmgr.NewMonitor("NetError")
- errmgr.SetThreshold("NetError", 2)
- defer monitor.Close()
-
- go func() {
- for alert := range monitor.Alerts() {
- fmt.Printf("Alert: %s, count: %d\n", alert.Error(), alert.Count())
- }
- }()
-
- for i := 0; i < 4; i++ {
- err := netErr(fmt.Sprintf("attempt %d", i))
- err.Free()
- }
- time.Sleep(100 * time.Millisecond)
-}
-```
-
-#### Realistic Monitoring
-```go
-package main
-
-import (
- "fmt"
- "github.com/olekukonko/errors"
- "github.com/olekukonko/errors/errmgr"
- "os"
- "os/signal"
- "syscall"
- "time"
-)
-
-func main() {
- // Define our error types
- netErr := errmgr.Define("NetError", "network connection failed: %s (attempt %d)")
- dbErr := errmgr.Define("DBError", "database operation failed: %s")
-
- // Create monitors with different buffer sizes
- netMonitor := errmgr.NewMonitorBuffered("NetError", 10) // Larger buffer for network errors
- dbMonitor := errmgr.NewMonitorBuffered("DBError", 5) // Smaller buffer for DB errors
- defer netMonitor.Close()
- defer dbMonitor.Close()
-
- // Set different thresholds
- errmgr.SetThreshold("NetError", 3) // Alert after 3 network errors
- errmgr.SetThreshold("DBError", 2) // Alert after 2 database errors
-
- // Set up signal handling for graceful shutdown
- sigChan := make(chan os.Signal, 1)
- signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
-
- // Alert handler goroutine
- done := make(chan struct{})
- go func() {
- defer close(done)
- for {
- select {
- case alert, ok := <-netMonitor.Alerts():
- if !ok {
- fmt.Println("Network alert channel closed")
- return
- }
- handleAlert("NETWORK", alert)
- case alert, ok := <-dbMonitor.Alerts():
- if !ok {
- fmt.Println("Database alert channel closed")
- return
- }
- handleAlert("DATABASE", alert)
- case <-time.After(2 * time.Second):
- // Periodic check for shutdown
- continue
- }
- }
- }()
-
- // Simulate operations with potential failures
- go func() {
- for i := 1; i <= 15; i++ {
- // Simulate different error scenarios
- if i%4 == 0 {
- // Database error
- err := dbErr("connection timeout")
- fmt.Printf("DB Operation %d: Failed\n", i)
- err.Free()
- } else {
- // Network error
- var errMsg string
- switch {
- case i%3 == 0:
- errMsg = "timeout"
- case i%5 == 0:
- errMsg = "connection reset"
- default:
- errMsg = "unknown error"
- }
-
- err := netErr(errMsg, i)
- fmt.Printf("Network Operation %d: Failed with %q\n", i, errMsg)
- err.Free()
- }
-
- // Random delay between operations
- time.Sleep(time.Duration(100+(i%200)) * time.Millisecond)
- }
- }()
-
- // Wait for shutdown signal or completion
- select {
- case <-sigChan:
- fmt.Println("\nReceived shutdown signal...")
- case <-time.After(5 * time.Second):
- fmt.Println("Completion timeout reached...")
- }
-
- // Cleanup
- fmt.Println("Initiating shutdown...")
- netMonitor.Close()
- dbMonitor.Close()
-
- // Wait for the alert handler to finish
- select {
- case <-done:
- fmt.Println("Alert handler shutdown complete")
- case <-time.After(1 * time.Second):
- fmt.Println("Alert handler shutdown timeout")
- }
-
- fmt.Println("Application shutdown complete")
-}
-
-func handleAlert(service string, alert *errors.Error) {
- if alert == nil {
- fmt.Printf("[%s] Received nil alert\n", service)
- return
- }
-
- fmt.Printf("[%s ALERT] %s (total occurrences: %d)\n",
- service, alert.Error(), alert.Count())
-
- if alert.Count() > 5 {
- fmt.Printf("[%s CRITICAL] High error rate detected!\n", service)
- }
-}
-```
-
----
-
-## Performance Optimization
-
-### Configuration Tuning
-```go
-package main
-
-import (
- "fmt"
- "github.com/olekukonko/errors"
-)
-
-func main() {
- // Tune error package configuration
- errors.Configure(errors.Config{
- StackDepth: 32, // Limit stack frames
- ContextSize: 4, // Optimize small contexts
- DisablePooling: false, // Enable pooling
- })
- err := errors.New("configured error")
- fmt.Println(err) // Output: "configured error"
-}
-```
-
-### Using `Free()` for Performance
-```go
-package main
-
-import (
- "fmt"
- "github.com/olekukonko/errors"
-)
-
-func main() {
- // Use Free() to return error to pool
- err := errors.New("temp error")
- fmt.Println(err) // Output: "temp error"
- err.Free() // Immediate pool return, reduces GC pressure
-}
-```
-
-### Benchmarks
-Real performance data (Apple M3 Pro, Go 1.21):
-```
-goos: darwin
-goarch: arm64
-pkg: github.com/olekukonko/errors
-cpu: Apple M3 Pro
-BenchmarkBasic_New-12 99810412 12.00 ns/op 0 B/op 0 allocs/op
-BenchmarkStack_WithStack-12 5879510 205.6 ns/op 24 B/op 1 allocs/op
-BenchmarkContext_Small-12 29600850 40.34 ns/op 16 B/op 1 allocs/op
-BenchmarkWrapping_Simple-12 100000000 11.73 ns/op 0 B/op 0 allocs/op
-```
-- **New with Pooling**: 12 ns/op, 0 allocations
-- **WithStack**: 205 ns/op, minimal allocation
-- **Context**: 40 ns/op for small contexts
-- Run: `go test -bench=. -benchmem`
-
-## Migration Guide
-
-### From Standard Library
-```go
-package main
-
-import (
- "fmt"
- "github.com/olekukonko/errors"
-)
-
-func main() {
- // Before: Standard library error
- err1 := fmt.Errorf("error: %v", "oops")
- fmt.Println(err1)
-
- // After: Enhanced error with context and stack
- err2 := errors.Newf("error: %v", "oops").
- With("source", "api").
- WithStack()
- fmt.Println(err2)
-}
-```
-
-### From `pkg/errors`
-```go
-package main
-
-import (
- "fmt"
- "github.com/olekukonko/errors"
-)
-
-func main() {
- // Before: pkg/errors (assuming similar API)
- // err := pkgerrors.Wrap(err, "context")
-
- // After: Enhanced wrapping
- err := errors.New("low-level").
- Msgf("context: %s", "details").
- WithStack()
- fmt.Println(err)
-}
-```
-
-### Compatibility with `errors.Is` and `errors.As`
-```go
-package main
-
-import (
- "fmt"
- "github.com/olekukonko/errors"
-)
-
-func main() {
- // Check compatibility with standard library
- err := errors.Named("MyError")
- wrapped := errors.Wrapf(err, "outer")
- if errors.Is(wrapped, err) { // Stdlib compatible
- fmt.Println("Matches MyError") // Output: "Matches MyError"
- }
-}
-```
-
-## FAQ
-
-- **When to use `Copy()`?**
- - Use ` SOCIALCopy()` to create a modifiable duplicate of an error without altering the original.
-
-- **When to use `Free()`?**
- - Use in performance-critical loops; otherwise, autofree handles it (Go 1.24+).
-
-- **How to handle cleanup?**
- - Use `Callback()` for automatic actions like rollbacks or logging.
-
-- **How to add stack traces later?**
- - Use `WithStack()` to upgrade a simple error:
- ```go
- package main
-
- import (
- "fmt"
- "github.com/olekukonko/errors"
- )
-
- func main() {
- err := errors.New("simple")
- err = err.WithStack()
- fmt.Println(err.Stack())
- }
- ```
-
-## Contributing
-- Fork, branch, commit, and PR—see [CONTRIBUTING.md](#).
-
-## License
-MIT License - See [LICENSE](LICENSE).
diff --git a/vendor/github.com/olekukonko/errors/chain.go b/vendor/github.com/olekukonko/errors/chain.go
deleted file mode 100644
index 5dc73a5852..0000000000
--- a/vendor/github.com/olekukonko/errors/chain.go
+++ /dev/null
@@ -1,610 +0,0 @@
-package errors
-
-import (
- "context"
- "fmt"
- "log/slog" // Standard structured logging package
- "reflect"
- "strings"
- "time"
-)
-
-// Chain executes functions sequentially with enhanced error handling.
-// Logging is optional and configured via a slog.Handler.
-type Chain struct {
- steps []chainStep // List of steps to execute
- errors []error // Accumulated errors during execution
- config chainConfig // Chain-wide configuration
- lastStep *chainStep // Pointer to the last added step for configuration
- logHandler slog.Handler // Optional logging handler (nil means no logging)
- cancel context.CancelFunc // Function to cancel the context
-}
-
-// chainStep represents a single step in the chain.
-type chainStep struct {
- execute func() error // Function to execute for this step
- optional bool // If true, errors don't stop the chain
- config stepConfig // Step-specific configuration
-}
-
-// chainConfig holds chain-wide settings.
-type chainConfig struct {
- timeout time.Duration // Maximum duration for the entire chain
- maxErrors int // Maximum number of errors before stopping (-1 for unlimited)
- autoWrap bool // Whether to automatically wrap errors with additional context
-}
-
-// stepConfig holds configuration for an individual step.
-type stepConfig struct {
- context map[string]interface{} // Arbitrary key-value pairs for context
- category ErrorCategory // Category for error classification
- code int // Numeric error code
- retry *Retry // Retry policy for the step
- logOnFail bool // Whether to log errors automatically
- metricsLabel string // Label for metrics (not used in this code)
- logAttrs []slog.Attr // Additional attributes for logging
-}
-
-// ChainOption defines a function that configures a Chain.
-type ChainOption func(*Chain)
-
-// NewChain creates a new Chain with the given options.
-// Logging is disabled by default (logHandler is nil).
-func NewChain(opts ...ChainOption) *Chain {
- c := &Chain{
- config: chainConfig{
- autoWrap: true, // Enable error wrapping by default
- maxErrors: -1, // No limit on errors by default
- },
- // logHandler is nil, meaning no logging unless explicitly configured
- }
- // Apply each configuration option
- for _, opt := range opts {
- opt(c)
- }
- return c
-}
-
-// ChainWithLogHandler sets a custom slog.Handler for logging.
-// If handler is nil, logging is effectively disabled.
-func ChainWithLogHandler(handler slog.Handler) ChainOption {
- return func(c *Chain) {
- c.logHandler = handler
- }
-}
-
-// ChainWithTimeout sets a timeout for the entire chain.
-func ChainWithTimeout(d time.Duration) ChainOption {
- return func(c *Chain) {
- c.config.timeout = d
- }
-}
-
-// ChainWithMaxErrors sets the maximum number of errors allowed.
-// A value <= 0 means no limit.
-func ChainWithMaxErrors(max int) ChainOption {
- return func(c *Chain) {
- if max <= 0 {
- c.config.maxErrors = -1 // No limit
- } else {
- c.config.maxErrors = max
- }
- }
-}
-
-// ChainWithAutoWrap enables or disables automatic error wrapping.
-func ChainWithAutoWrap(auto bool) ChainOption {
- return func(c *Chain) {
- c.config.autoWrap = auto
- }
-}
-
-// Step adds a new step to the chain with the provided function.
-// The function must return an error or nil.
-func (c *Chain) Step(fn func() error) *Chain {
- if fn == nil {
- // Panic to enforce valid input
- panic("Chain.Step: provided function cannot be nil")
- }
- // Create a new step with default configuration
- step := chainStep{execute: fn, config: stepConfig{}}
- c.steps = append(c.steps, step)
- // Update lastStep to point to the newly added step
- c.lastStep = &c.steps[len(c.steps)-1]
- return c
-}
-
-// Call adds a step by wrapping a function with arguments.
-// It uses reflection to validate and invoke the function.
-func (c *Chain) Call(fn interface{}, args ...interface{}) *Chain {
- // Wrap the function and arguments into an executable step
- wrappedFn, err := c.wrapCallable(fn, args...)
- if err != nil {
- // Panic on setup errors to catch them early
- panic(fmt.Sprintf("Chain.Call setup error: %v", err))
- }
- // Add the wrapped function as a step
- step := chainStep{execute: wrappedFn, config: stepConfig{}}
- c.steps = append(c.steps, step)
- c.lastStep = &c.steps[len(c.steps)-1]
- return c
-}
-
-// Optional marks the last step as optional.
-// Optional steps don't stop the chain on error.
-func (c *Chain) Optional() *Chain {
- if c.lastStep == nil {
- // Panic if no step exists to mark as optional
- panic("Chain.Optional: must call Step() or Call() before Optional()")
- }
- c.lastStep.optional = true
- return c
-}
-
-// WithLog adds logging attributes to the last step.
-func (c *Chain) WithLog(attrs ...slog.Attr) *Chain {
- if c.lastStep == nil {
- // Panic if no step exists to configure
- panic("Chain.WithLog: must call Step() or Call() before WithLog()")
- }
- // Append attributes to the step's logging configuration
- c.lastStep.config.logAttrs = append(c.lastStep.config.logAttrs, attrs...)
- return c
-}
-
-// Timeout sets a timeout for the entire chain.
-func (c *Chain) Timeout(d time.Duration) *Chain {
- c.config.timeout = d
- return c
-}
-
-// MaxErrors sets the maximum number of errors allowed.
-func (c *Chain) MaxErrors(max int) *Chain {
- if max <= 0 {
- c.config.maxErrors = -1 // No limit
- } else {
- c.config.maxErrors = max
- }
- return c
-}
-
-// With adds a key-value pair to the last step's context.
-func (c *Chain) With(key string, value interface{}) *Chain {
- if c.lastStep == nil {
- // Panic if no step exists to configure
- panic("Chain.With: must call Step() or Call() before With()")
- }
- // Initialize context map if nil
- if c.lastStep.config.context == nil {
- c.lastStep.config.context = make(map[string]interface{})
- }
- // Add the key-value pair
- c.lastStep.config.context[key] = value
- return c
-}
-
-// Tag sets an error category for the last step.
-func (c *Chain) Tag(category ErrorCategory) *Chain {
- if c.lastStep == nil {
- // Panic if no step exists to configure
- panic("Chain.Tag: must call Step() or Call() before Tag()")
- }
- c.lastStep.config.category = category
- return c
-}
-
-// Code sets a numeric error code for the last step.
-func (c *Chain) Code(code int) *Chain {
- if c.lastStep == nil {
- // Panic if no step exists to configure
- panic("Chain.Code: must call Step() or Call() before Code()")
- }
- c.lastStep.config.code = code
- return c
-}
-
-// Retry configures retry behavior for the last step.
-// Retry configures retry behavior for the last step.
-func (c *Chain) Retry(maxAttempts int, delay time.Duration, opts ...RetryOption) *Chain {
- if c.lastStep == nil {
- panic("Chain.Retry: must call Step() or Call() before Retry()")
- }
- if maxAttempts < 1 {
- maxAttempts = 1
- }
-
- // Define default retry options
- retryOpts := []RetryOption{
- WithMaxAttempts(maxAttempts),
- WithDelay(delay),
- WithRetryIf(func(err error) bool { return IsRetryable(err) }),
- }
-
- // Add logging for retry attempts if a handler is configured
- if c.logHandler != nil {
- step := c.lastStep
- retryOpts = append(retryOpts, WithOnRetry(func(attempt int, err error) {
- // Prepare logging attributes
- logAttrs := []slog.Attr{
- slog.Int("attempt", attempt),
- slog.Int("max_attempts", maxAttempts),
- }
- // Enhance the error with step context
- enhancedErr := c.enhanceError(err, step)
- // Log the retry attempt
- c.logError(enhancedErr, fmt.Sprintf("Retrying step (attempt %d/%d)", attempt, maxAttempts), step.config, logAttrs...)
- }))
- }
-
- // Append any additional retry options
- retryOpts = append(retryOpts, opts...)
- // Create and assign the retry configuration
- c.lastStep.config.retry = NewRetry(retryOpts...)
- return c
-}
-
-// LogOnFail enables automatic logging of errors for the last step.
-func (c *Chain) LogOnFail() *Chain {
- if c.lastStep == nil {
- // Panic if no step exists to configure
- panic("Chain.LogOnFail: must call Step() or Call() before LogOnFail()")
- }
- c.lastStep.config.logOnFail = true
- return c
-}
-
-// Run executes the chain, stopping on the first non-optional error.
-// It returns the first error encountered or nil if all steps succeed.
-func (c *Chain) Run() error {
- // Create a context with timeout or cancellation
- ctx, cancel := c.getContextAndCancel()
- defer cancel()
- c.cancel = cancel
- // Clear any previous errors
- c.errors = c.errors[:0]
-
- // Execute each step in sequence
- for i := range c.steps {
- step := &c.steps[i]
- // Check if the context has been canceled
- select {
- case <-ctx.Done():
- err := ctx.Err()
- // Enhance the error with step context
- enhancedErr := c.enhanceError(err, step)
- c.errors = append(c.errors, enhancedErr)
- // Log the context error
- c.logError(enhancedErr, "Chain stopped due to context error before step", step.config)
- return enhancedErr
- default:
- }
-
- // Execute the step
- err := c.executeStep(ctx, step)
- if err != nil {
- // Enhance the error with step context
- enhancedErr := c.enhanceError(err, step)
- c.errors = append(c.errors, enhancedErr)
- // Log the error if required
- if step.config.logOnFail || !step.optional {
- logMsg := "Chain stopped due to error in step"
- if step.optional {
- logMsg = "Optional step failed"
- }
- c.logError(enhancedErr, logMsg, step.config)
- }
- // Stop execution if the step is not optional
- if !step.optional {
- return enhancedErr
- }
- }
- }
- // Return nil if all steps completed successfully
- return nil
-}
-
-// RunAll executes all steps, collecting errors without stopping.
-// It returns a MultiError containing all errors or nil if none occurred.
-func (c *Chain) RunAll() error {
- ctx, cancel := c.getContextAndCancel()
- defer cancel()
- c.cancel = cancel
- c.errors = c.errors[:0]
- multi := NewMultiError()
-
- for i := range c.steps {
- step := &c.steps[i]
- select {
- case <-ctx.Done():
- err := ctx.Err()
- enhancedErr := c.enhanceError(err, step)
- c.errors = append(c.errors, enhancedErr)
- multi.Add(enhancedErr)
- c.logError(enhancedErr, "Chain stopped due to context error before step (RunAll)", step.config)
- goto endRunAll
- default:
- }
-
- err := c.executeStep(ctx, step)
- if err != nil {
- enhancedErr := c.enhanceError(err, step)
- c.errors = append(c.errors, enhancedErr)
- multi.Add(enhancedErr)
- if step.config.logOnFail && c.logHandler != nil {
- c.logError(enhancedErr, "Step failed during RunAll", step.config)
- }
- if c.config.maxErrors > 0 && multi.Count() >= c.config.maxErrors {
- if c.logHandler != nil {
- // Create a logger to log the max errors condition
- logger := slog.New(c.logHandler)
- logger.LogAttrs(
- context.Background(),
- slog.LevelError,
- fmt.Sprintf("Stopping RunAll after reaching max errors (%d)", c.config.maxErrors),
- slog.Int("max_errors", c.config.maxErrors),
- )
- }
- goto endRunAll
- }
- }
- }
-
-endRunAll:
- return multi.Single()
-}
-
-// Errors returns a copy of the collected errors.
-func (c *Chain) Errors() []error {
- if len(c.errors) == 0 {
- return nil
- }
- // Create a copy to prevent external modification
- errs := make([]error, len(c.errors))
- copy(errs, c.errors)
- return errs
-}
-
-// Len returns the number of steps in the chain.
-func (c *Chain) Len() int {
- return len(c.steps)
-}
-
-// HasErrors checks if any errors were collected.
-func (c *Chain) HasErrors() bool {
- return len(c.errors) > 0
-}
-
-// LastError returns the most recent error or nil if none exist.
-func (c *Chain) LastError() error {
- if len(c.errors) > 0 {
- return c.errors[len(c.errors)-1]
- }
- return nil
-}
-
-// Reset clears the chain's steps, errors, and context.
-func (c *Chain) Reset() {
- if c.cancel != nil {
- // Cancel any active context
- c.cancel()
- c.cancel = nil
- }
- // Clear steps and errors
- c.steps = c.steps[:0]
- c.errors = c.errors[:0]
- c.lastStep = nil
-}
-
-// Unwrap returns the collected errors (alias for Errors).
-func (c *Chain) Unwrap() []error {
- return c.errors
-}
-
-// getContextAndCancel creates a context based on the chain's timeout.
-// It returns a context and its cancellation function.
-func (c *Chain) getContextAndCancel() (context.Context, context.CancelFunc) {
- parentCtx := context.Background()
- if c.config.timeout > 0 {
- // Create a context with a timeout
- return context.WithTimeout(parentCtx, c.config.timeout)
- }
- // Create a cancellable context
- return context.WithCancel(parentCtx)
-}
-
-// logError logs an error with step-specific context and attributes.
-// It only logs if a handler is configured and the error is non-nil.
-func (c *Chain) logError(err error, msg string, config stepConfig, additionalAttrs ...slog.Attr) {
- // Skip logging if no handler is set or error is nil
- if c == nil || c.logHandler == nil || err == nil {
- return
- }
-
- // Create a logger on demand using the configured handler
- logger := slog.New(c.logHandler)
-
- // Initialize attributes with error and timestamp
- allAttrs := make([]slog.Attr, 0, 5+len(config.logAttrs)+len(additionalAttrs))
- allAttrs = append(allAttrs, slog.Any("error", err))
- allAttrs = append(allAttrs, slog.Time("timestamp", time.Now()))
-
- // Add step-specific metadata
- if config.category != "" {
- allAttrs = append(allAttrs, slog.String("category", string(config.category)))
- }
- if config.code != 0 {
- allAttrs = append(allAttrs, slog.Int("code", config.code))
- }
- for k, v := range config.context {
- allAttrs = append(allAttrs, slog.Any(k, v))
- }
- allAttrs = append(allAttrs, config.logAttrs...)
- allAttrs = append(allAttrs, additionalAttrs...)
-
- // Add stack trace and error name if the error is of type *Error
- if e, ok := err.(*Error); ok {
- if stack := e.Stack(); len(stack) > 0 {
- // Format stack trace, truncating if too long
- stackStr := "\n\t" + strings.Join(stack, "\n\t")
- if len(stackStr) > 1000 {
- stackStr = stackStr[:1000] + "..."
- }
- allAttrs = append(allAttrs, slog.String("stacktrace", stackStr))
- }
- if name := e.Name(); name != "" {
- allAttrs = append(allAttrs, slog.String("error_name", name))
- }
- }
-
- // Log the error at ERROR level with all attributes
- // Use a defer to catch any panics during logging
- defer func() {
- if r := recover(); r != nil {
- // Print to stdout to avoid infinite recursion
- fmt.Printf("ERROR: Recovered from panic during logging: %v\nAttributes: %v\n", r, allAttrs)
- }
- }()
- logger.LogAttrs(context.Background(), slog.LevelError, msg, allAttrs...)
-}
-
-// wrapCallable wraps a function and its arguments into an executable step.
-// It uses reflection to validate the function and arguments.
-func (c *Chain) wrapCallable(fn interface{}, args ...interface{}) (func() error, error) {
- val := reflect.ValueOf(fn)
- typ := val.Type()
-
- // Ensure the provided value is a function
- if typ.Kind() != reflect.Func {
- return nil, fmt.Errorf("provided 'fn' is not a function (got %T)", fn)
- }
- // Check if the number of arguments matches the function's signature
- if typ.NumIn() != len(args) {
- return nil, fmt.Errorf("function expects %d arguments, but %d were provided", typ.NumIn(), len(args))
- }
-
- // Prepare argument values
- argVals := make([]reflect.Value, len(args))
- errorType := reflect.TypeOf((*error)(nil)).Elem()
- for i, arg := range args {
- expectedType := typ.In(i)
- var providedVal reflect.Value
- if arg != nil {
- providedVal = reflect.ValueOf(arg)
- // Check if the argument type is assignable to the expected type
- if !providedVal.Type().AssignableTo(expectedType) {
- // Special case for error interfaces
- if expectedType.Kind() == reflect.Interface && expectedType.Implements(errorType) && providedVal.Type().Implements(errorType) {
- // Allow error interface
- } else {
- return nil, fmt.Errorf("argument %d type mismatch: expected %s, got %s", i, expectedType, providedVal.Type())
- }
- }
- } else {
- // Handle nil arguments for nullable types
- switch expectedType.Kind() {
- case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Pointer, reflect.Slice:
- providedVal = reflect.Zero(expectedType)
- default:
- return nil, fmt.Errorf("argument %d is nil, but expected non-nillable type %s", i, expectedType)
- }
- }
- argVals[i] = providedVal
- }
-
- // Validate the function's return type
- if typ.NumOut() > 1 || (typ.NumOut() == 1 && !typ.Out(0).Implements(errorType)) {
- return nil, fmt.Errorf("function must return either no values or a single error (got %d return values)", typ.NumOut())
- }
-
- // Return a wrapped function that calls the original with the provided arguments
- return func() error {
- results := val.Call(argVals)
- if len(results) == 1 && results[0].Interface() != nil {
- return results[0].Interface().(error)
- }
- return nil
- }, nil
-}
-
-// executeStep runs a single step, applying retries if configured.
-func (c *Chain) executeStep(ctx context.Context, step *chainStep) error {
- select {
- case <-ctx.Done():
- return ctx.Err()
- default:
- }
- if step.config.retry != nil {
- retry := step.config.retry.Transform(WithContext(ctx))
- // Wrap step execution to respect context
- wrappedFn := func() error {
- type result struct {
- err error
- }
- done := make(chan result, 1)
- go func() {
- done <- result{err: step.execute()}
- }()
- select {
- case res := <-done:
- return res.err
- case <-ctx.Done():
- return ctx.Err()
- }
- }
- return retry.Execute(wrappedFn)
- }
- // Non-retry case also respects context
- type result struct {
- err error
- }
- done := make(chan result, 1)
- go func() {
- done <- result{err: step.execute()}
- }()
- select {
- case res := <-done:
- return res.err
- case <-ctx.Done():
- return ctx.Err()
- }
-}
-
-// enhanceError wraps an error with additional context from the step.
-func (c *Chain) enhanceError(err error, step *chainStep) error {
- if err == nil || !c.config.autoWrap {
- // Return the error unchanged if nil or autoWrap is disabled
- return err
- }
-
- // Initialize the base error
- var baseError *Error
- if e, ok := err.(*Error); ok {
- // Copy existing *Error to preserve its properties
- baseError = e.Copy()
- } else {
- // Create a new *Error wrapping the original
- baseError = New(err.Error()).Wrap(err).WithStack()
- }
-
- if step != nil {
- // Add step-specific context to the error
- if step.config.category != "" && baseError.Category() == "" {
- baseError.WithCategory(step.config.category)
- }
- if step.config.code != 0 && baseError.Code() == 0 {
- baseError.WithCode(step.config.code)
- }
- for k, v := range step.config.context {
- baseError.With(k, v)
- }
- for _, attr := range step.config.logAttrs {
- baseError.With(attr.Key, attr.Value.Any())
- }
- if step.config.retry != nil && !baseError.HasContextKey(ctxRetry) {
- // Mark the error as retryable if retries are configured
- baseError.WithRetryable()
- }
- }
-
- return baseError
-}
diff --git a/vendor/github.com/olekukonko/errors/errors.go b/vendor/github.com/olekukonko/errors/errors.go
deleted file mode 100644
index 4f6509da98..0000000000
--- a/vendor/github.com/olekukonko/errors/errors.go
+++ /dev/null
@@ -1,1496 +0,0 @@
-// Package errors provides a robust error handling library with support for
-// error wrapping, stack traces, context storage, and retry mechanisms. It extends
-// the standard library's error interface with features like HTTP-like status codes,
-// error categorization, and JSON serialization, while maintaining compatibility
-// with `errors.Is`, `errors.As`, and `errors.Unwrap`. The package is thread-safe
-// and optimized with object pooling for performance.
-package errors
-
-import (
- "bytes"
- "encoding/json"
- "errors"
- "fmt"
- "regexp"
- "runtime"
- "strings"
- "sync"
- "sync/atomic"
-)
-
-// Constants defining default configuration and context keys.
-const (
- ctxTimeout = "[error] timeout" // Context key marking timeout errors.
- ctxRetry = "[error] retry" // Context key marking retryable errors.
-
- contextSize = 4 // Initial size of fixed-size context array for small contexts.
- bufferSize = 256 // Initial buffer size for JSON marshaling.
- warmUpSize = 100 // Number of errors to pre-warm the pool for efficiency.
- stackDepth = 32 // Maximum stack trace depth to prevent excessive memory use.
-
- DefaultCode = 500 // Default HTTP status code for errors if not specified.
-)
-
-// spaceRe is a precompiled regex for normalizing whitespace in error messages.
-var spaceRe = regexp.MustCompile(`\s+`)
-
-// ErrorCategory is a string type for categorizing errors (e.g., "network", "validation").
-type ErrorCategory string
-
-// ErrorOpts provides options for customizing error creation.
-type ErrorOpts struct {
- SkipStack int // Number of stack frames to skip when capturing the stack trace.
-}
-
-// Config defines the global configuration for the errors package, controlling
-// stack depth, context size, pooling, and frame filtering.
-type Config struct {
- StackDepth int // Maximum stack trace depth; 0 uses default (32).
- ContextSize int // Initial context map size; 0 uses default (4).
- DisablePooling bool // If true, disables object pooling for errors.
- FilterInternal bool // If true, filters internal package frames from stack traces.
- AutoFree bool // If true, automatically frees errors to pool after use.
-}
-
-// cachedConfig holds the current configuration, updated only by Configure().
-// Protected by configMu for thread-safety.
-type cachedConfig struct {
- stackDepth int
- contextSize int
- disablePooling bool
- filterInternal bool
- autoFree bool
-}
-
-var (
- // currentConfig stores the active configuration, read frequently and updated rarely.
- currentConfig cachedConfig
- // configMu protects updates to currentConfig for thread-safety.
- configMu sync.RWMutex
- // errorPool manages reusable Error instances to reduce allocations.
- errorPool = NewErrorPool()
- // stackPool manages reusable stack trace slices for efficiency.
- stackPool = sync.Pool{
- New: func() interface{} {
- return make([]uintptr, currentConfig.stackDepth)
- },
- }
- // emptyError is a pre-allocated empty error for lightweight reuse.
- emptyError = &Error{
- smallContext: [contextSize]contextItem{},
- msg: "",
- name: "",
- template: "",
- cause: nil,
- }
-)
-
-// contextItem holds a single key-value pair in the smallContext array.
-type contextItem struct {
- key string
- value interface{}
-}
-
-// Error is a custom error type with enhanced features: message, name, stack trace,
-// context, cause, and metadata like code and category. It is thread-safe and
-// supports pooling for performance.
-type Error struct {
- // Primary fields (frequently accessed).
- msg string // The error message displayed by Error().
- name string // The error name or type (e.g., "AuthError").
- stack []uintptr // Stack trace as program counters.
-
- // Secondary metadata.
- template string // Fallback message template if msg is empty.
- category string // Error category (e.g., "network").
- count uint64 // Occurrence count for tracking frequency.
- code int32 // HTTP-like status code (e.g., 400, 500).
- smallCount int32 // Number of items in smallContext.
-
- // Context and chaining.
- context map[string]interface{} // Key-value pairs for additional context.
- cause error // Wrapped underlying error for chaining.
- callback func() // Optional callback invoked by Error().
- smallContext [contextSize]contextItem // Fixed-size array for small contexts.
-
- // Synchronization.
- mu sync.RWMutex // Protects mutable fields (context, smallContext).
-
- // Internal flags.
- formatWrapped bool // True if created by Newf with %w verb.
-}
-
-// init sets up the package with default configuration and pre-warms the error pool.
-func init() {
- currentConfig = cachedConfig{
- stackDepth: stackDepth,
- contextSize: contextSize,
- disablePooling: false,
- filterInternal: true,
- autoFree: true,
- }
- WarmPool(warmUpSize) // Pre-allocate errors for performance.
-}
-
-// Configure updates the global configuration for the errors package.
-// It is thread-safe and should be called early to avoid race conditions.
-// Changes apply to all subsequent error operations.
-// Example:
-//
-// errors.Configure(errors.Config{StackDepth: 16, DisablePooling: true})
-func Configure(cfg Config) {
- configMu.Lock()
- defer configMu.Unlock()
-
- if cfg.StackDepth != 0 {
- currentConfig.stackDepth = cfg.StackDepth
- }
- if cfg.ContextSize != 0 {
- currentConfig.contextSize = cfg.ContextSize
- }
- currentConfig.disablePooling = cfg.DisablePooling
- currentConfig.filterInternal = cfg.FilterInternal
- currentConfig.autoFree = cfg.AutoFree
-}
-
-// newError creates a new Error instance, reusing from the pool if enabled.
-// Initializes smallContext and sets stack to nil.
-// Internal use; prefer New, Named, or Trace for public API.
-func newError() *Error {
- if currentConfig.disablePooling {
- return &Error{
- smallContext: [contextSize]contextItem{},
- stack: nil,
- }
- }
- return errorPool.Get()
-}
-
-// Empty returns a new empty error with no message, name, or stack trace.
-// Useful for incrementally building errors or as a neutral base.
-// Example:
-//
-// err := errors.Empty().With("key", "value").WithCode(400)
-func Empty() *Error {
- return emptyError
-}
-
-// Named creates an error with the specified name and captures a stack trace.
-// The name doubles as the error message if no message is set.
-// Use for errors where type identification and stack context are important.
-// Example:
-//
-// err := errors.Named("AuthError").WithCode(401)
-func Named(name string) *Error {
- e := newError()
- e.name = name
- return e.WithStack()
-}
-
-// New creates a lightweight error with the given message and no stack trace.
-// Optimized for performance; use Trace() for stack traces.
-// Returns a shared empty error for empty messages to reduce allocations.
-// Example:
-//
-// err := errors.New("invalid input")
-func New(text string) *Error {
- if text == "" {
- return emptyError.Copy() // Avoid modifying shared instance.
- }
- err := newError()
- err.msg = text
- return err
-}
-
-// Newf creates a formatted error, supporting the %w verb for wrapping errors.
-// If the format contains exactly one %w verb with a non-nil error argument,
-// the error is wrapped as the cause. The final error message string generated
-// by Error() will be compatible with the output of fmt.Errorf for the same inputs.
-// Does not capture a stack trace by default.
-// Example:
-//
-// cause := errors.New("db error")
-// err := errors.Newf("query failed: %w", cause)
-// // err.Error() will match fmt.Errorf("query failed: %w", cause).Error()
-// // errors.Unwrap(err) == cause
-func Newf(format string, args ...interface{}) *Error {
- err := newError()
-
- // --- Start: Parsing and Validation (mostly unchanged) ---
- var wCount int
- var wArgPos = -1
- var wArg error
- var validationErrorMsg string
- argPos := 0
- runes := []rune(format)
- i := 0
- parsingOk := true
- var fmtVerbs []struct {
- isW bool
- spec string // The full verb specifier or literal segment
- argIdx int // Index in the original 'args' slice, -1 for literals/%%
- }
-
- // Parse format string to identify verbs and literals.
- for i < len(runes) && parsingOk {
- segmentStart := i
- if runes[i] == '%' {
- if i+1 >= len(runes) {
- parsingOk = false
- validationErrorMsg = "ends with %"
- break
- }
- if runes[i+1] == '%' {
- fmtVerbs = append(fmtVerbs, struct {
- isW bool
- spec string
- argIdx int
- }{isW: false, spec: "%%", argIdx: -1})
- i += 2
- continue
- }
- i++ // Move past '%'
- // Parse flags, width, precision (simplified loop)
- for i < len(runes) && strings.ContainsRune("+- #0", runes[i]) {
- i++
- }
- for i < len(runes) && ((runes[i] >= '0' && runes[i] <= '9') || runes[i] == '.') {
- i++
- }
- if i >= len(runes) {
- parsingOk = false
- validationErrorMsg = "ends mid-specifier"
- break
- }
- verb := runes[i]
- specifierEndIndex := i + 1
- fullSpec := string(runes[segmentStart:specifierEndIndex])
- // Check if the verb consumes an argument
- currentVerbConsumesArg := strings.ContainsRune("vTtbcdoqxXUeEfFgGspw", verb)
- currentArgIdx := -1
- isWVerb := false
-
- if verb == 'w' {
- isWVerb = true
- wCount++
- if wCount == 1 {
- wArgPos = argPos // Record position of the error argument
- } else {
- parsingOk = false
- validationErrorMsg = "multiple %w"
- break
- }
- }
-
- if currentVerbConsumesArg {
- if argPos >= len(args) {
- parsingOk = false
- if isWVerb { // More specific message for missing %w arg
- validationErrorMsg = "missing %w argument"
- } else {
- validationErrorMsg = fmt.Sprintf("missing argument for %s", string(verb))
- }
- break
- }
- currentArgIdx = argPos
- if isWVerb {
- cause, ok := args[argPos].(error)
- if !ok || cause == nil {
- parsingOk = false
- validationErrorMsg = "bad %w argument type"
- break
- }
- wArg = cause // Store the actual error argument
- }
- argPos++ // Consume the argument position
- }
- fmtVerbs = append(fmtVerbs, struct {
- isW bool
- spec string
- argIdx int
- }{isW: isWVerb, spec: fullSpec, argIdx: currentArgIdx})
- i = specifierEndIndex // Move past the verb character
- } else {
- // Handle literal segment
- literalStart := i
- for i < len(runes) && runes[i] != '%' {
- i++
- }
- fmtVerbs = append(fmtVerbs, struct {
- isW bool
- spec string
- argIdx int
- }{isW: false, spec: string(runes[literalStart:i]), argIdx: -1})
- }
- }
-
- // Check for too many arguments after parsing
- if parsingOk && argPos < len(args) {
- parsingOk = false
- validationErrorMsg = fmt.Sprintf("too many arguments for format %q", format)
- }
-
- // Handle format validation errors.
- if !parsingOk {
- switch validationErrorMsg {
- case "multiple %w":
- err.msg = fmt.Sprintf("errors.Newf: format %q has multiple %%w verbs", format)
- case "missing %w argument":
- err.msg = fmt.Sprintf("errors.Newf: format %q has %%w but not enough arguments", format)
- case "bad %w argument type":
- argValStr := "()"
- if wArgPos >= 0 && wArgPos < len(args) && args[wArgPos] != nil {
- argValStr = fmt.Sprintf("(%T)", args[wArgPos])
- } else if wArgPos >= len(args) {
- argValStr = "(missing)" // Should be caught by "missing %w argument" case
- }
- err.msg = fmt.Sprintf("errors.Newf: argument %d for %%w is not a non-nil error %s", wArgPos, argValStr)
- case "ends with %":
- err.msg = fmt.Sprintf("errors.Newf: format %q ends with %%", format)
- case "ends mid-specifier":
- err.msg = fmt.Sprintf("errors.Newf: format %q ends during verb specifier", format)
- default: // Includes "too many arguments" and other potential fmt issues
- err.msg = fmt.Sprintf("errors.Newf: error in format %q: %s", format, validationErrorMsg)
- }
- err.cause = nil // Ensure no cause is set on format error
- err.formatWrapped = false
- return err
- }
- // --- End: Parsing and Validation ---
-
- // --- Start: Processing Valid Format String ---
- if wCount == 1 && wArg != nil {
- // --- Handle %w: Simulate for Sprintf and pre-format ---
- err.cause = wArg // Set the cause for unwrapping
- err.formatWrapped = true // Signal that msg is the final formatted string
-
- var finalFormat strings.Builder
- var finalArgs []interface{}
- causeStr := wArg.Error() // Get the string representation of the cause
-
- // Rebuild format string and argument list for Sprintf
- for _, verb := range fmtVerbs {
- if verb.isW {
- // Replace the %w verb specifier (e.g., "%w", "%+w") with "%s"
- finalFormat.WriteString("%s")
- // Add the cause's *string* to the arguments list for the new %s
- finalArgs = append(finalArgs, causeStr)
- } else {
- // Keep the original literal segment or non-%w verb specifier
- finalFormat.WriteString(verb.spec)
- if verb.argIdx != -1 {
- // Add the original argument for this non-%w verb/literal
- finalArgs = append(finalArgs, args[verb.argIdx])
- }
- }
- }
-
- // Format using the *modified* format string and arguments list
- result, fmtErr := FmtErrorCheck(finalFormat.String(), finalArgs...)
- if fmtErr != nil {
- // Handle potential errors during the final formatting step
- // This is unlikely if parsing passed, but possible with complex verbs/args
- err.msg = fmt.Sprintf("errors.Newf: formatting error during %%w simulation for format %q: %v", format, fmtErr)
- err.cause = nil // Don't keep the cause if final formatting failed
- err.formatWrapped = false
- } else {
- // Store the final, fully formatted string, matching fmt.Errorf output
- err.msg = result
- }
- // --- End %w Simulation ---
-
- } else {
- // --- No %w or wArg is nil: Format directly (original logic) ---
- result, fmtErr := FmtErrorCheck(format, args...)
- if fmtErr != nil {
- err.msg = fmt.Sprintf("errors.Newf: formatting error for format %q: %v", format, fmtErr)
- err.cause = nil
- err.formatWrapped = false
- } else {
- err.msg = result
- err.formatWrapped = false // Ensure false if no %w was involved
- }
- }
- // --- End: Processing Valid Format String ---
-
- return err
-}
-
-// Errorf is an alias for Newf, providing a familiar interface compatible with
-// fmt.Errorf. It creates a formatted error without capturing a stack trace.
-// See Newf for full details on formatting, including %w support for error wrapping.
-//
-// Example:
-//
-// err := errors.Errorf("failed: %w", errors.New("cause"))
-// // err.Error() == "failed: cause"
-func Errorf(format string, args ...interface{}) *Error {
- return Newf(format, args...)
-}
-
-// FmtErrorCheck safely formats a string using fmt.Sprintf, catching panics.
-// Returns the formatted string and any error encountered.
-// Internal use by Newf to validate format strings.
-// Example:
-//
-// result, err := FmtErrorCheck("value: %s", "test")
-func FmtErrorCheck(format string, args ...interface{}) (result string, err error) {
- defer func() {
- if r := recover(); r != nil {
- if e, ok := r.(error); ok {
- err = e
- } else {
- err = fmt.Errorf("panic during formatting: %v", r)
- }
- }
- }()
- result = fmt.Sprintf(format, args...)
- return result, nil
-}
-
-// countFmtArgs counts format specifiers that consume arguments in a format string.
-// Ignores %% and non-consuming verbs like %n.
-// Internal use by Newf for argument validation.
-func countFmtArgs(format string) int {
- count := 0
- runes := []rune(format)
- i := 0
- for i < len(runes) {
- if runes[i] == '%' {
- if i+1 < len(runes) && runes[i+1] == '%' {
- i += 2 // Skip %%
- continue
- }
- i++ // Move past %
- for i < len(runes) && (runes[i] == '+' || runes[i] == '-' || runes[i] == '#' ||
- runes[i] == ' ' || runes[i] == '0' ||
- (runes[i] >= '1' && runes[i] <= '9') || runes[i] == '.') {
- i++
- }
- if i < len(runes) {
- if strings.ContainsRune("vTtbcdoqxXUeEfFgGsp", runes[i]) {
- count++
- }
- i++ // Move past verb
- }
- } else {
- i++
- }
- }
- return count
-}
-
-// Std creates a standard error using errors.New for compatibility.
-// Does not capture stack traces or add context.
-// Example:
-//
-// err := errors.Std("simple error")
-func Std(text string) error {
- return errors.New(text)
-}
-
-// Stdf creates a formatted standard error using fmt.Errorf for compatibility.
-// Supports %w for wrapping; does not capture stack traces.
-// Example:
-//
-// err := errors.Stdf("failed: %w", cause)
-func Stdf(format string, a ...interface{}) error {
- return fmt.Errorf(format, a...)
-}
-
-// Trace creates an error with the given message and captures a stack trace.
-// Use when debugging context is needed; for performance, prefer New().
-// Example:
-//
-// err := errors.Trace("operation failed")
-func Trace(text string) *Error {
- e := New(text)
- return e.WithStack()
-}
-
-// Tracef creates a formatted error with a stack trace.
-// Supports %w for wrapping errors.
-// Example:
-//
-// err := errors.Tracef("query %s failed: %w", query, cause)
-func Tracef(format string, args ...interface{}) *Error {
- e := Newf(format, args...)
- return e.WithStack()
-}
-
-// As attempts to assign the error or one in its chain to the target interface.
-// Supports *Error and standard error types, traversing the cause chain.
-// Returns true if successful.
-// Example:
-//
-// var target *Error
-// if errors.As(err, &target) {
-// fmt.Println(target.Name())
-// }
-func (e *Error) As(target interface{}) bool {
- if e == nil {
- return false
- }
- // Handle *Error target.
- if targetPtr, ok := target.(*Error); ok {
- current := e
- for current != nil {
- if current.name != "" {
- *targetPtr = *current
- return true
- }
- if next, ok := current.cause.(*Error); ok {
- current = next
- } else if current.cause != nil {
- return errors.As(current.cause, target)
- } else {
- return false
- }
- }
- return false
- }
- // Handle *error target.
- if targetErr, ok := target.(*error); ok {
- innermost := error(e)
- current := error(e)
- for current != nil {
- if err, ok := current.(*Error); ok && err.cause != nil {
- current = err.cause
- innermost = current
- } else {
- break
- }
- }
- *targetErr = innermost
- return true
- }
- // Delegate to cause for other types.
- if e.cause != nil {
- return errors.As(e.cause, target)
- }
- return false
-}
-
-// Callback sets a function to be called when Error() is invoked.
-// Useful for logging or side effects on error access.
-// Example:
-//
-// err := errors.New("test").Callback(func() { log.Println("error accessed") })
-func (e *Error) Callback(fn func()) *Error {
- e.callback = fn
- return e
-}
-
-// Category returns the error’s category, if set.
-// Example:
-//
-// if err.Category() == "network" {
-// handleNetworkError(err)
-// }
-func (e *Error) Category() string {
- return e.category
-}
-
-// Code returns the error’s HTTP-like status code, if set.
-// Returns 0 if no code is set.
-// Example:
-//
-// if err.Code() == 404 {
-// renderNotFound()
-// }
-func (e *Error) Code() int {
- return int(e.code)
-}
-
-// Context returns the error’s context as a map, merging smallContext and map-based context.
-// Thread-safe; lazily initializes the map if needed.
-// Example:
-//
-// ctx := err.Context()
-// if userID, ok := ctx["user_id"]; ok {
-// fmt.Println(userID)
-// }
-func (e *Error) Context() map[string]interface{} {
- e.mu.RLock()
- defer e.mu.RUnlock()
-
- if e.smallCount > 0 && e.context == nil {
- e.context = make(map[string]interface{}, e.smallCount)
- for i := int32(0); i < e.smallCount; i++ {
- e.context[e.smallContext[i].key] = e.smallContext[i].value
- }
- }
- return e.context
-}
-
-// Copy creates a deep copy of the error, preserving all fields except stack freshness.
-// The new error can be modified independently.
-// Example:
-//
-// newErr := err.Copy().With("new_key", "value")
-func (e *Error) Copy() *Error {
- if e == emptyError {
- return &Error{
- smallContext: [contextSize]contextItem{},
- }
- }
-
- newErr := newError()
-
- newErr.msg = e.msg
- newErr.name = e.name
- newErr.template = e.template
- newErr.cause = e.cause
- newErr.code = e.code
- newErr.category = e.category
- newErr.count = e.count
-
- if e.smallCount > 0 {
- newErr.smallCount = e.smallCount
- for i := int32(0); i < e.smallCount; i++ {
- newErr.smallContext[i] = e.smallContext[i]
- }
- } else if e.context != nil {
- newErr.context = make(map[string]interface{}, len(e.context))
- for k, v := range e.context {
- newErr.context[k] = v
- }
- }
-
- if e.stack != nil && len(e.stack) > 0 {
- if newErr.stack == nil {
- newErr.stack = stackPool.Get().([]uintptr)
- }
- newErr.stack = append(newErr.stack[:0], e.stack...)
- }
-
- return newErr
-}
-
-// Count returns the number of times the error has been incremented.
-// Useful for tracking error frequency.
-// Example:
-//
-// fmt.Printf("Error occurred %d times", err.Count())
-func (e *Error) Count() uint64 {
- return e.count
-}
-
-// Err returns the error as an error interface.
-// Useful for type assertions or interface compatibility.
-// Example:
-//
-// var stdErr error = err.Err()
-func (e *Error) Err() error {
- return e
-}
-
-// Error returns the string representation of the error.
-// If the error was created using Newf/Errorf with the %w verb, it returns the
-// pre-formatted string compatible with fmt.Errorf.
-// Otherwise, it combines the message, template, or name with the cause's error
-// string, separated by ": ". Invokes any set callback.
-func (e *Error) Error() string {
- if e.callback != nil {
- e.callback()
- }
-
- // If created by Newf/Errorf with %w, msg already contains the final string.
- if e.formatWrapped {
- return e.msg // Return the pre-formatted fmt.Errorf-compatible string
- }
-
- // --- Original logic for errors not created via Newf("%w", ...) ---
- // --- or errors created via New/Named and then Wrap() called. ---
- var buf strings.Builder
-
- // Append primary message part (msg, template, or name)
- if e.msg != "" {
- buf.WriteString(e.msg)
- } else if e.template != "" {
- buf.WriteString(e.template)
- } else if e.name != "" {
- buf.WriteString(e.name)
- }
-
- // Append cause if it exists (only relevant if not formatWrapped)
- if e.cause != nil {
- if buf.Len() > 0 {
- // Add separator only if there was a prefix message/name/template
- buf.WriteString(": ")
- }
- buf.WriteString(e.cause.Error())
- } else if buf.Len() == 0 {
- // Handle case where msg/template/name are empty AND cause is nil
- // Could return a specific string like "[empty error]" or just ""
- return "" // Return empty string for a truly empty error
- }
-
- return buf.String()
-}
-
-// FastStack returns a lightweight stack trace with file and line numbers only.
-// Omits function names for performance; skips internal frames if configured.
-// Returns nil if no stack trace exists.
-// Example:
-//
-// for _, frame := range err.FastStack() {
-// fmt.Println(frame) // e.g., "main.go:42"
-// }
-func (e *Error) FastStack() []string {
- if e.stack == nil {
- return nil
- }
- configMu.RLock()
- filter := currentConfig.filterInternal
- configMu.RUnlock()
-
- pcs := e.stack
- frames := make([]string, 0, len(pcs))
- for _, pc := range pcs {
- fn := runtime.FuncForPC(pc)
- if fn == nil {
- frames = append(frames, "unknown")
- continue
- }
- file, line := fn.FileLine(pc)
- if filter && isInternalFrame(runtime.Frame{File: file, Function: fn.Name()}) {
- continue
- }
- frames = append(frames, fmt.Sprintf("%s:%d", file, line))
- }
- return frames
-}
-
-// Find searches the error chain for the first error where pred returns true.
-// Returns nil if no match is found or if pred is nil.
-// Example:
-//
-// err := err.Find(func(e error) bool { return strings.Contains(e.Error(), "timeout") })
-func (e *Error) Find(pred func(error) bool) error {
- if e == nil || pred == nil {
- return nil
- }
- return Find(e, pred)
-}
-
-// Format returns a detailed, human-readable string representation of the error,
-// including message, code, context, stack, and cause.
-// Recursive for causes that are also *Error.
-// Example:
-//
-// fmt.Println(err.Format())
-// // Output:
-// // Error: failed: cause
-// // Code: 500
-// // Context:
-// // key: value
-// // Stack:
-// // 1. main.main main.go:42
-func (e *Error) Format() string {
- var sb strings.Builder
-
- // Error message.
- sb.WriteString("Error: " + e.Error() + "\n")
-
- // Metadata.
- if e.code != 0 {
- sb.WriteString(fmt.Sprintf("Code: %d\n", e.code))
- }
-
- // Context.
- if ctx := e.contextAtThisLevel(); len(ctx) > 0 {
- sb.WriteString("Context:\n")
- for k, v := range ctx {
- sb.WriteString(fmt.Sprintf("\t%s: %v\n", k, v))
- }
- }
-
- // Stack trace.
- if e.stack != nil {
- sb.WriteString("Stack:\n")
- for i, frame := range e.Stack() {
- sb.WriteString(fmt.Sprintf("\t%d. %s\n", i+1, frame))
- }
- }
-
- // Cause.
- if e.cause != nil {
- sb.WriteString("Caused by: ")
- if causeErr, ok := e.cause.(*Error); ok {
- sb.WriteString(causeErr.Format())
- } else {
- sb.WriteString("Error: " + e.cause.Error() + "\n")
- }
- sb.WriteString("\n")
- }
-
- return sb.String()
-}
-
-// contextAtThisLevel returns context specific to this error, excluding inherited context.
-// Internal use by Format to isolate context per error level.
-func (e *Error) contextAtThisLevel() map[string]interface{} {
- if e.context == nil && e.smallCount == 0 {
- return nil
- }
-
- ctx := make(map[string]interface{})
- // Add smallContext items.
- for i := 0; i < int(e.smallCount); i++ {
- ctx[e.smallContext[i].key] = e.smallContext[i].value
- }
- // Add map context items.
- if e.context != nil {
- for k, v := range e.context {
- ctx[k] = v
- }
- }
- return ctx
-}
-
-// Free resets the error and returns it to the pool if pooling is enabled.
-// Safe to call multiple times; no-op if pooling is disabled.
-// Call after use to prevent memory leaks when autoFree is false.
-// Example:
-//
-// defer err.Free()
-func (e *Error) Free() {
- if currentConfig.disablePooling {
- return
- }
-
- e.Reset()
-
- if e.stack != nil {
- stackPool.Put(e.stack[:cap(e.stack)])
- e.stack = nil
- }
- errorPool.Put(e)
-}
-
-// Has checks if the error contains meaningful content (message, template, name, or cause).
-// Returns false for nil or empty errors.
-// Example:
-//
-// if !err.Has() {
-// return nil
-// }
-func (e *Error) Has() bool {
- return e != nil && (e.msg != "" || e.template != "" || e.name != "" || e.cause != nil)
-}
-
-// HasContextKey checks if the specified key exists in the error’s context.
-// Thread-safe; checks both smallContext and map-based context.
-// Example:
-//
-// if err.HasContextKey("user_id") {
-// fmt.Println(err.Context()["user_id"])
-// }
-func (e *Error) HasContextKey(key string) bool {
- e.mu.RLock()
- defer e.mu.RUnlock()
-
- if e.smallCount > 0 {
- for i := int32(0); i < e.smallCount; i++ {
- if e.smallContext[i].key == key {
- return true
- }
- }
- }
- if e.context != nil {
- _, exists := e.context[key]
- return exists
- }
- return false
-}
-
-// Increment atomically increases the error’s count by 1 and returns the error.
-// Useful for tracking repeated occurrences.
-// Example:
-//
-// err := err.Increment()
-func (e *Error) Increment() *Error {
- atomic.AddUint64(&e.count, 1)
- return e
-}
-
-// Is checks if the error matches the target by pointer, name, or cause chain.
-// Compatible with errors.Is; also matches by string for standard errors.
-// Returns true if the error or its cause matches the target.
-// Example:
-//
-// if errors.Is(err, errors.New("target")) {
-// handleTargetError()
-// }
-func (e *Error) Is(target error) bool {
- if e == nil || target == nil {
- return e == target
- }
- if e == target {
- return true
- }
- if e.name != "" {
- if te, ok := target.(*Error); ok && te.name != "" && e.name == te.name {
- return true
- }
- }
- // Match standard errors by string.
- if stdErr, ok := target.(error); ok && e.Error() == stdErr.Error() {
- return true
- }
- if e.cause != nil {
- return errors.Is(e.cause, target)
- }
- return false
-}
-
-// IsEmpty checks if the error lacks meaningful content (no message, name, template, or cause).
-// Returns true for nil or fully empty errors.
-// Example:
-//
-// if err.IsEmpty() {
-// return nil
-// }
-func (e *Error) IsEmpty() bool {
- if e == nil {
- return true
- }
- return e.msg == "" && e.template == "" && e.name == "" && e.cause == nil
-}
-
-// IsNull checks if the error is nil, empty, or contains only SQL NULL values in its context or cause.
-// Useful for handling database-related errors.
-// Example:
-//
-// if err.IsNull() {
-// return nil
-// }
-func (e *Error) IsNull() bool {
- if e == nil || e == emptyError {
- return true
- }
- // If no context or cause, and no content, it’s not null.
- if e.smallCount == 0 && e.context == nil && e.cause == nil {
- return false
- }
-
- // Check cause first.
- if e.cause != nil {
- var isNull bool
- if ce, ok := e.cause.(*Error); ok {
- isNull = ce.IsNull()
- } else {
- isNull = sqlNull(e.cause)
- }
- if isNull {
- return true
- }
- }
-
- // Check small context.
- if e.smallCount > 0 {
- allNull := true
- for i := 0; i < int(e.smallCount); i++ {
- isNull := sqlNull(e.smallContext[i].value)
- if !isNull {
- allNull = false
- break
- }
- }
- if !allNull {
- return false
- }
- }
-
- // Check regular context.
- if e.context != nil {
- allNull := true
- for _, v := range e.context {
- isNull := sqlNull(v)
- if !isNull {
- allNull = false
- break
- }
- }
- if !allNull {
- return false
- }
- }
-
- // Null if context exists and is all null.
- return e.smallCount > 0 || e.context != nil
-}
-
-// jsonBufferPool manages reusable buffers for JSON marshaling to reduce allocations.
-var (
- jsonBufferPool = sync.Pool{
- New: func() interface{} {
- return bytes.NewBuffer(make([]byte, 0, bufferSize))
- },
- }
-)
-
-// MarshalJSON serializes the error to JSON, including name, message, context, cause, stack, and code.
-// Causes are recursively serialized if they implement json.Marshaler or are *Error.
-// Example:
-//
-// data, _ := json.Marshal(err)
-// fmt.Println(string(data))
-func (e *Error) MarshalJSON() ([]byte, error) {
- // Get buffer from pool.
- buf := jsonBufferPool.Get().(*bytes.Buffer)
- defer jsonBufferPool.Put(buf)
- buf.Reset()
-
- // Create new encoder.
- enc := json.NewEncoder(buf)
- enc.SetEscapeHTML(false)
-
- // Prepare JSON structure.
- je := struct {
- Name string `json:"name,omitempty"`
- Message string `json:"message,omitempty"`
- Context map[string]interface{} `json:"context,omitempty"`
- Cause interface{} `json:"cause,omitempty"`
- Stack []string `json:"stack,omitempty"`
- Code int `json:"code,omitempty"`
- }{
- Name: e.name,
- Message: e.msg,
- Code: e.Code(),
- }
-
- // Add context.
- if ctx := e.Context(); len(ctx) > 0 {
- je.Context = ctx
- }
-
- // Add stack.
- if e.stack != nil {
- je.Stack = e.Stack()
- }
-
- // Add cause.
- if e.cause != nil {
- switch c := e.cause.(type) {
- case *Error:
- je.Cause = c
- case json.Marshaler:
- je.Cause = c
- default:
- je.Cause = c.Error()
- }
- }
-
- // Encode JSON.
- if err := enc.Encode(je); err != nil {
- return nil, err
- }
-
- // Remove trailing newline.
- result := buf.Bytes()
- if len(result) > 0 && result[len(result)-1] == '\n' {
- result = result[:len(result)-1]
- }
- return result, nil
-}
-
-// Msgf sets the error’s message using a formatted string and returns the error.
-// Overwrites any existing message.
-// Example:
-//
-// err := err.Msgf("user %s not found", username)
-func (e *Error) Msgf(format string, args ...interface{}) *Error {
- e.msg = fmt.Sprintf(format, args...)
- return e
-}
-
-// Name returns the error’s name, if set.
-// Example:
-//
-// if err.Name() == "AuthError" {
-// handleAuthError()
-// }
-func (e *Error) Name() string {
- return e.name
-}
-
-// Reset clears all fields of the error, preparing it for reuse in the pool.
-// Internal use by Free; does not release stack to stackPool.
-// Example:
-//
-// err.Reset() // Clear all fields.
-func (e *Error) Reset() {
- e.msg = ""
- e.name = ""
- e.template = ""
- e.category = ""
- e.code = 0
- e.count = 0
- e.cause = nil
- e.callback = nil
- e.formatWrapped = false
-
- if e.context != nil {
- for k := range e.context {
- delete(e.context, k)
- }
- }
- e.smallCount = 0
-
- if e.stack != nil {
- e.stack = e.stack[:0]
- }
-}
-
-// Stack returns a detailed stack trace with function names, files, and line numbers.
-// Filters internal frames if configured; returns nil if no stack exists.
-// Example:
-//
-// for _, frame := range err.Stack() {
-// fmt.Println(frame) // e.g., "main.main main.go:42"
-// }
-func (e *Error) Stack() []string {
- if e.stack == nil {
- return nil
- }
-
- frames := runtime.CallersFrames(e.stack)
- var trace []string
- for {
- frame, more := frames.Next()
- if frame == (runtime.Frame{}) {
- break
- }
-
- if currentConfig.filterInternal && isInternalFrame(frame) {
- continue
- }
-
- trace = append(trace, fmt.Sprintf("%s %s:%d",
- frame.Function,
- frame.File,
- frame.Line))
-
- if !more {
- break
- }
- }
- return trace
-}
-
-// Trace ensures the error has a stack trace, capturing it if absent.
-// Returns the error for chaining.
-// Example:
-//
-// err := errors.New("failed").Trace()
-func (e *Error) Trace() *Error {
- if e.stack == nil {
- e.stack = captureStack(2)
- }
- return e
-}
-
-// Transform applies transformations to a copy of the error and returns the new error.
-// The original error is unchanged; nil-safe.
-// Example:
-//
-// newErr := err.Transform(func(e *Error) { e.With("key", "value") })
-func (e *Error) Transform(fn func(*Error)) *Error {
- if e == nil || fn == nil {
- return e
- }
- newErr := e.Copy()
- fn(newErr)
- return newErr
-}
-
-// Unwrap returns the underlying cause of the error, if any.
-// Compatible with errors.Unwrap for chain traversal.
-// Example:
-//
-// cause := errors.Unwrap(err)
-func (e *Error) Unwrap() error {
- return e.cause
-}
-
-// UnwrapAll returns a slice of all errors in the chain, starting with this error.
-// Each error is isolated to prevent modifications affecting others.
-// Example:
-//
-// chain := err.UnwrapAll()
-// for _, e := range chain {
-// fmt.Println(e.Error())
-// }
-func (e *Error) UnwrapAll() []error {
- if e == nil {
- return nil
- }
- var chain []error
- current := error(e)
- for current != nil {
- if err, ok := current.(*Error); ok {
- isolated := newError()
- isolated.msg = err.msg
- isolated.name = err.name
- isolated.template = err.template
- isolated.code = err.code
- isolated.category = err.category
- if err.smallCount > 0 {
- isolated.smallCount = err.smallCount
- for i := int32(0); i < err.smallCount; i++ {
- isolated.smallContext[i] = err.smallContext[i]
- }
- }
- if err.context != nil {
- isolated.context = make(map[string]interface{}, len(err.context))
- for k, v := range err.context {
- isolated.context[k] = v
- }
- }
- if err.stack != nil {
- isolated.stack = append([]uintptr(nil), err.stack...)
- }
- chain = append(chain, isolated)
- } else {
- chain = append(chain, current)
- }
- if unwrapper, ok := current.(interface{ Unwrap() error }); ok {
- current = unwrapper.Unwrap()
- } else {
- break
- }
- }
- return chain
-}
-
-// Walk traverses the error chain, applying fn to each error.
-// Stops if fn is nil or the chain ends.
-// Example:
-//
-// err.Walk(func(e error) { fmt.Println(e.Error()) })
-func (e *Error) Walk(fn func(error)) {
- if e == nil || fn == nil {
- return
- }
- current := error(e)
- for current != nil {
- fn(current)
- if unwrappable, ok := current.(interface{ Unwrap() error }); ok {
- current = unwrappable.Unwrap()
- } else {
- break
- }
- }
-}
-
-// With adds key-value pairs to the error's context and returns the error.
-// Uses a fixed-size array (smallContext) for up to contextSize items, then switches
-// to a map. Thread-safe. Accepts variadic key-value pairs.
-// Example:
-//
-// err := err.With("key1", value1, "key2", value2)
-func (e *Error) With(keyValues ...interface{}) *Error {
- if len(keyValues) == 0 {
- return e
- }
-
- // Validate that we have an even number of arguments
- if len(keyValues)%2 != 0 {
- keyValues = append(keyValues, "(MISSING)")
- }
-
- // Fast path for small context when we can add all pairs to smallContext
- if e.smallCount < contextSize && e.context == nil {
- remainingSlots := contextSize - int(e.smallCount)
- if len(keyValues)/2 <= remainingSlots {
- e.mu.Lock()
- // Recheck conditions after acquiring lock
- if e.smallCount < contextSize && e.context == nil {
- for i := 0; i < len(keyValues); i += 2 {
- key, ok := keyValues[i].(string)
- if !ok {
- key = fmt.Sprintf("%v", keyValues[i])
- }
- e.smallContext[e.smallCount] = contextItem{key, keyValues[i+1]}
- e.smallCount++
- }
- e.mu.Unlock()
- return e
- }
- e.mu.Unlock()
- }
- }
-
- // Slow path - either we have too many pairs or already using map context
- e.mu.Lock()
- defer e.mu.Unlock()
-
- // Initialize map context if needed
- if e.context == nil {
- e.context = make(map[string]interface{}, max(currentConfig.contextSize, len(keyValues)/2+int(e.smallCount)))
- // Migrate existing smallContext items
- for i := int32(0); i < e.smallCount; i++ {
- e.context[e.smallContext[i].key] = e.smallContext[i].value
- }
- // Reset smallCount since we've moved to map context
- e.smallCount = 0
- }
-
- // Add all pairs to map context
- for i := 0; i < len(keyValues); i += 2 {
- key, ok := keyValues[i].(string)
- if !ok {
- key = fmt.Sprintf("%v", keyValues[i])
- }
- e.context[key] = keyValues[i+1]
- }
-
- return e
-}
-
-// Helper function to get maximum of two integers
-func max(a, b int) int {
- if a > b {
- return a
- }
- return b
-}
-
-// WithCategory sets the error’s category and returns the error.
-// Example:
-//
-// err := err.WithCategory("validation")
-func (e *Error) WithCategory(category ErrorCategory) *Error {
- e.category = string(category)
- return e
-}
-
-// WithCode sets an HTTP-like status code and returns the error.
-// Example:
-//
-// err := err.WithCode(400)
-func (e *Error) WithCode(code int) *Error {
- e.code = int32(code)
- return e
-}
-
-// WithName sets the error’s name and returns the error.
-// Example:
-//
-// err := err.WithName("AuthError")
-func (e *Error) WithName(name string) *Error {
- e.name = name
- return e
-}
-
-// WithRetryable marks the error as retryable in its context and returns the error.
-// Example:
-//
-// err := err.WithRetryable()
-func (e *Error) WithRetryable() *Error {
- return e.With(ctxRetry, true)
-}
-
-// WithStack captures a stack trace if none exists and returns the error.
-// Skips one frame (caller of WithStack).
-// Example:
-//
-// err := errors.New("failed").WithStack()
-func (e *Error) WithStack() *Error {
- if e.stack == nil {
- e.stack = captureStack(1)
- }
- return e
-}
-
-// WithTemplate sets a message template and returns the error.
-// Used as a fallback if the message is empty.
-// Example:
-//
-// err := err.WithTemplate("operation failed")
-func (e *Error) WithTemplate(template string) *Error {
- e.template = template
- return e
-}
-
-// WithTimeout marks the error as a timeout error in its context and returns the error.
-// Example:
-//
-// err := err.WithTimeout()
-func (e *Error) WithTimeout() *Error {
- return e.With(ctxTimeout, true)
-}
-
-// Wrap associates a cause error with this error, creating a chain.
-// Returns the error unchanged if cause is nil.
-// Example:
-//
-// err := errors.New("failed").Wrap(errors.New("cause"))
-func (e *Error) Wrap(cause error) *Error {
- if cause == nil {
- return e
- }
- e.cause = cause
- return e
-}
-
-// Wrapf wraps a cause error with formatted message and returns the error.
-// If cause is nil, returns the error unchanged.
-// Example:
-//
-// err := errors.New("base").Wrapf(io.EOF, "read failed: %s", "file.txt")
-func (e *Error) Wrapf(cause error, format string, args ...interface{}) *Error {
- e.msg = fmt.Sprintf(format, args...)
- if cause != nil {
- e.cause = cause
- }
- return e
-}
-
-// WrapNotNil wraps a cause error only if it is non-nil and returns the error.
-// Example:
-//
-// err := err.WrapNotNil(maybeError)
-func (e *Error) WrapNotNil(cause error) *Error {
- if cause != nil {
- e.cause = cause
- }
- return e
-}
-
-// WarmPool pre-populates the error pool with count instances.
-// Improves performance by reducing initial allocations.
-// No-op if pooling is disabled.
-// Example:
-//
-// errors.WarmPool(1000)
-func WarmPool(count int) {
- if currentConfig.disablePooling {
- return
- }
- for i := 0; i < count; i++ {
- e := &Error{
- smallContext: [contextSize]contextItem{},
- stack: nil,
- }
- errorPool.Put(e)
- stackPool.Put(make([]uintptr, 0, currentConfig.stackDepth))
- }
-}
-
-// WarmStackPool pre-populates the stack pool with count slices.
-// Improves performance for stack-intensive operations.
-// No-op if pooling is disabled.
-// Example:
-//
-// errors.WarmStackPool(500)
-func WarmStackPool(count int) {
- if currentConfig.disablePooling {
- return
- }
- for i := 0; i < count; i++ {
- stackPool.Put(make([]uintptr, 0, currentConfig.stackDepth))
- }
-}
diff --git a/vendor/github.com/olekukonko/errors/helper.go b/vendor/github.com/olekukonko/errors/helper.go
deleted file mode 100644
index 06c2adc55c..0000000000
--- a/vendor/github.com/olekukonko/errors/helper.go
+++ /dev/null
@@ -1,432 +0,0 @@
-package errors
-
-import (
- "context"
- "errors"
- "fmt"
- "strings"
- "time"
-)
-
-// As wraps errors.As, using custom type assertion for *Error types.
-// Falls back to standard errors.As for non-*Error types.
-// Returns false if either err or target is nil.
-func As(err error, target interface{}) bool {
- if err == nil || target == nil {
- return false
- }
-
- // First try our custom *Error handling
- if e, ok := err.(*Error); ok {
- return e.As(target)
- }
-
- // Fall back to standard errors.As
- return errors.As(err, target)
-}
-
-// Code returns the status code of an error, if it is an *Error.
-// Returns 500 as a default for non-*Error types to indicate an internal error.
-func Code(err error) int {
- if e, ok := err.(*Error); ok {
- return e.Code()
- }
- return DefaultCode
-}
-
-// Context extracts the context map from an error, if it is an *Error.
-// Returns nil for non-*Error types or if no context is present.
-func Context(err error) map[string]interface{} {
- if e, ok := err.(*Error); ok {
- return e.Context()
- }
- return nil
-}
-
-// Convert transforms any error into an *Error, preserving its message and wrapping it if needed.
-// Returns nil if the input is nil; returns the original if already an *Error.
-// Uses multiple strategies: direct assertion, errors.As, manual unwrapping, and fallback creation.
-func Convert(err error) *Error {
- if err == nil {
- return nil
- }
-
- // First try direct type assertion (fast path)
- if e, ok := err.(*Error); ok {
- return e
- }
-
- // Try using errors.As (more flexible)
- var e *Error
- if errors.As(err, &e) {
- return e
- }
-
- // Manual unwrapping as fallback
- visited := make(map[error]bool)
- for unwrapped := err; unwrapped != nil; {
- if visited[unwrapped] {
- break // Cycle detected
- }
- visited[unwrapped] = true
- if e, ok := unwrapped.(*Error); ok {
- return e
- }
- unwrapped = errors.Unwrap(unwrapped)
- }
-
- // Final fallback: create new error with original message and wrap it
- return New(err.Error()).Wrap(err)
-}
-
-// Count returns the occurrence count of an error, if it is an *Error.
-// Returns 0 for non-*Error types.
-func Count(err error) uint64 {
- if e, ok := err.(*Error); ok {
- return e.Count()
- }
- return 0
-}
-
-// Find searches the error chain for the first error matching pred.
-// Returns nil if no match is found or pred is nil; traverses both Unwrap() and Cause() chains.
-func Find(err error, pred func(error) bool) error {
- for current := err; current != nil; {
- if pred(current) {
- return current
- }
-
- // Attempt to unwrap using Unwrap() or Cause()
- switch v := current.(type) {
- case interface{ Unwrap() error }:
- current = v.Unwrap()
- case interface{ Cause() error }:
- current = v.Cause()
- default:
- return nil
- }
- }
- return nil
-}
-
-// From transforms any error into an *Error, preserving its message and wrapping it if needed.
-// Alias of Convert; returns nil if input is nil, original if already an *Error.
-func From(err error) *Error {
- return Convert(err)
-}
-
-// FromContext creates an *Error from a context and an existing error.
-// Enhances the error with context info: timeout status, deadline, or cancellation.
-// Returns nil if input error is nil; does not store context values directly.
-func FromContext(ctx context.Context, err error) *Error {
- if err == nil {
- return nil
- }
-
- e := New(err.Error())
-
- // Handle context errors
- switch ctx.Err() {
- case context.DeadlineExceeded:
- e.WithTimeout()
- if deadline, ok := ctx.Deadline(); ok {
- e.With("deadline", deadline.Format(time.RFC3339))
- }
- case context.Canceled:
- e.With("cancelled", true)
- }
-
- return e
-}
-
-// Category returns the category of an error, if it is an *Error.
-// Returns an empty string for non-*Error types or unset categories.
-func Category(err error) string {
- if e, ok := err.(*Error); ok {
- return e.category
- }
- return ""
-}
-
-// Has checks if an error contains meaningful content.
-// Returns true for non-nil standard errors or *Error with content (msg, name, template, or cause).
-func Has(err error) bool {
- if e, ok := err.(*Error); ok {
- return e.Has()
- }
- return err != nil
-}
-
-// HasContextKey checks if the error's context contains the specified key.
-// Returns false for non-*Error types or if the key is not present in the context.
-func HasContextKey(err error, key string) bool {
- if e, ok := err.(*Error); ok {
- ctx := e.Context()
- if ctx != nil {
- _, exists := ctx[key]
- return exists
- }
- }
- return false
-}
-
-// Is wraps errors.Is, using custom matching for *Error types.
-// Falls back to standard errors.Is for non-*Error types; returns true if err equals target.
-func Is(err, target error) bool {
- if err == nil || target == nil {
- return err == target
- }
-
- if e, ok := err.(*Error); ok {
- return e.Is(target)
- }
-
- // Use standard errors.Is for non-Error types
- return errors.Is(err, target)
-}
-
-// IsError checks if an error is an instance of *Error.
-// Returns true only for this package's custom error type; false for nil or other types.
-func IsError(err error) bool {
- _, ok := err.(*Error)
- return ok
-}
-
-// IsEmpty checks if an error has no meaningful content.
-// Returns true for nil errors, empty *Error instances, or standard errors with whitespace-only messages.
-func IsEmpty(err error) bool {
- if err == nil {
- return true
- }
- if e, ok := err.(*Error); ok {
- return e.IsEmpty()
- }
- return strings.TrimSpace(err.Error()) == ""
-}
-
-// IsNull checks if an error is nil or represents a NULL value.
-// Delegates to *Error’s IsNull for custom errors; uses sqlNull for others.
-func IsNull(err error) bool {
- if err == nil {
- return true
- }
- if e, ok := err.(*Error); ok {
- return e.IsNull()
- }
- return sqlNull(err)
-}
-
-// IsRetryable checks if an error is retryable.
-// For *Error, checks context for retry flag; for others, looks for "retry" or timeout in message.
-// Returns false for nil errors; thread-safe for *Error types.
-func IsRetryable(err error) bool {
- if err == nil {
- return false
- }
- if e, ok := err.(*Error); ok {
- e.mu.RLock()
- defer e.mu.RUnlock()
- // Check smallContext directly if context map isn’t populated
- for i := int32(0); i < e.smallCount; i++ {
- if e.smallContext[i].key == ctxRetry {
- if val, ok := e.smallContext[i].value.(bool); ok {
- return val
- }
- }
- }
- // Check regular context
- if e.context != nil {
- if val, ok := e.context[ctxRetry].(bool); ok {
- return val
- }
- }
- // Check cause recursively
- if e.cause != nil {
- return IsRetryable(e.cause)
- }
- }
- lowerMsg := strings.ToLower(err.Error())
- return IsTimeout(err) || strings.Contains(lowerMsg, "retry")
-}
-
-// IsTimeout checks if an error indicates a timeout.
-// For *Error, checks context for timeout flag; for others, looks for "timeout" in message.
-// Returns false for nil errors.
-func IsTimeout(err error) bool {
- if err == nil {
- return false
- }
- if e, ok := err.(*Error); ok {
- if val, ok := e.Context()[ctxTimeout].(bool); ok {
- return val
- }
- }
- return strings.Contains(strings.ToLower(err.Error()), "timeout")
-}
-
-// Merge combines multiple errors into a single *Error.
-// Aggregates messages with "; " separator, merges contexts and stacks; returns nil if no errors provided.
-func Merge(errs ...error) *Error {
- if len(errs) == 0 {
- return nil
- }
- var messages []string
- combined := New("")
- for _, err := range errs {
- if err == nil {
- continue
- }
- messages = append(messages, err.Error())
- if e, ok := err.(*Error); ok {
- if e.stack != nil && combined.stack == nil {
- combined.WithStack() // Capture stack from first *Error with stack
- }
- if ctx := e.Context(); ctx != nil {
- for k, v := range ctx {
- combined.With(k, v)
- }
- }
- if e.cause != nil {
- combined.Wrap(e.cause)
- }
- } else {
- combined.Wrap(err)
- }
- }
- if len(messages) > 0 {
- combined.msg = strings.Join(messages, "; ")
- }
- return combined
-}
-
-// Name returns the name of an error, if it is an *Error.
-// Returns an empty string for non-*Error types or unset names.
-func Name(err error) string {
- if e, ok := err.(*Error); ok {
- return e.name
- }
- return ""
-}
-
-// UnwrapAll returns a slice of all errors in the chain, including the root error.
-// Traverses both Unwrap() and Cause() chains; returns nil if err is nil.
-func UnwrapAll(err error) []error {
- if err == nil {
- return nil
- }
- if e, ok := err.(*Error); ok {
- return e.UnwrapAll()
- }
- var result []error
- Walk(err, func(e error) {
- result = append(result, e)
- })
- return result
-}
-
-// Stack extracts the stack trace from an error, if it is an *Error.
-// Returns nil for non-*Error types or if no stack is present.
-func Stack(err error) []string {
- if e, ok := err.(*Error); ok {
- return e.Stack()
- }
- return nil
-}
-
-// Transform applies transformations to an error, returning a new *Error.
-// Creates a new *Error from non-*Error types before applying fn; returns nil if err is nil.
-func Transform(err error, fn func(*Error)) *Error {
- if err == nil {
- return nil
- }
- if e, ok := err.(*Error); ok {
- newErr := e.Copy()
- fn(newErr)
- return newErr
- }
- // If not an *Error, create a new one and transform it
- newErr := New(err.Error())
- fn(newErr)
- return newErr
-}
-
-// Unwrap returns the underlying cause of an error, if it implements Unwrap.
-// For *Error, returns cause; for others, returns the error itself; nil if err is nil.
-func Unwrap(err error) error {
- for current := err; current != nil; {
- if e, ok := current.(*Error); ok {
- if e.cause == nil {
- return current
- }
- current = e.cause
- } else {
- return current
- }
- }
- return nil
-}
-
-// Walk traverses the error chain, applying fn to each error.
-// Supports both Unwrap() and Cause() interfaces; stops at nil or non-unwrappable errors.
-func Walk(err error, fn func(error)) {
- for current := err; current != nil; {
- fn(current)
-
- // Attempt to unwrap using Unwrap() or Cause()
- switch v := current.(type) {
- case interface{ Unwrap() error }:
- current = v.Unwrap()
- case interface{ Cause() error }:
- current = v.Cause()
- default:
- return
- }
- }
-}
-
-// With adds a key-value pair to an error's context, if it is an *Error.
-// Returns the original error unchanged if not an *Error; no-op for non-*Error types.
-func With(err error, key string, value interface{}) error {
- if e, ok := err.(*Error); ok {
- return e.With(key, value)
- }
- return err
-}
-
-// WithStack converts any error to an *Error and captures a stack trace.
-// Returns nil if input is nil; adds stack to existing *Error or wraps non-*Error types.
-func WithStack(err error) *Error {
- if err == nil {
- return nil
- }
- if e, ok := err.(*Error); ok {
- return e.WithStack()
- }
- return New(err.Error()).WithStack().Wrap(err)
-}
-
-// Wrap creates a new *Error that wraps another error with additional context.
-// Uses a copy of the provided wrapper *Error; returns nil if err is nil.
-func Wrap(err error, wrapper *Error) *Error {
- if err == nil {
- return nil
- }
- if wrapper == nil {
- wrapper = newError()
- }
- newErr := wrapper.Copy()
- newErr.cause = err
- return newErr
-}
-
-// Wrapf creates a new formatted *Error that wraps another error.
-// Formats the message and sets the cause; returns nil if err is nil.
-func Wrapf(err error, format string, args ...interface{}) *Error {
- if err == nil {
- return nil
- }
- e := newError()
- e.msg = fmt.Sprintf(format, args...)
- e.cause = err
- return e
-}
diff --git a/vendor/github.com/olekukonko/errors/inspect.go b/vendor/github.com/olekukonko/errors/inspect.go
deleted file mode 100644
index c87def9d00..0000000000
--- a/vendor/github.com/olekukonko/errors/inspect.go
+++ /dev/null
@@ -1,225 +0,0 @@
-// File: inspect.go
-// Updated to support both error and *Error with delegation for cleaner *Error handling
-
-package errors
-
-import (
- stderrs "errors"
- "fmt"
- "strings"
- "time"
-)
-
-// Inspect provides detailed examination of an error, handling both single errors and MultiError
-func Inspect(err error) {
- if err == nil {
- fmt.Println("No error occurred")
- return
- }
-
- fmt.Printf("\n=== Error Inspection ===\n")
- fmt.Printf("Top-level error: %v\n", err)
- fmt.Printf("Top-level error type: %T\n", err)
-
- // Handle *Error directly
- if e, ok := err.(*Error); ok {
- InspectError(e)
- return
- }
-
- // Handle MultiError
- if multi, ok := err.(*MultiError); ok {
- allErrors := multi.Errors()
- fmt.Printf("\nContains %d errors:\n", len(allErrors))
- for i, e := range allErrors {
- fmt.Printf("\n--- Error %d ---\n", i+1)
- inspectSingleError(e)
- }
- } else {
- // Inspect single error if not MultiError or *Error
- fmt.Println("\n--- Details ---")
- inspectSingleError(err)
- }
-
- // Additional diagnostics
- fmt.Println("\n--- Diagnostics ---")
- if IsRetryable(err) {
- fmt.Println("- Error chain contains retryable errors")
- }
- if IsTimeout(err) {
- fmt.Println("- Error chain contains timeout errors")
- }
- if code := getErrorCode(err); code != 0 {
- fmt.Printf("- Highest priority error code: %d\n", code)
- }
- fmt.Printf("========================\n\n")
-}
-
-// InspectError provides detailed inspection of a specific *Error instance
-func InspectError(err *Error) {
- if err == nil {
- fmt.Println("No error occurred")
- return
- }
-
- fmt.Printf("\n=== Error Inspection (*Error) ===\n")
- fmt.Printf("Top-level error: %v\n", err)
- fmt.Printf("Top-level error type: %T\n", err)
-
- fmt.Println("\n--- Details ---")
- inspectSingleError(err) // Delegate to handle unwrapping and details
-
- // Additional diagnostics specific to *Error
- fmt.Println("\n--- Diagnostics ---")
- if IsRetryable(err) {
- fmt.Println("- Error is retryable")
- }
- if IsTimeout(err) {
- fmt.Println("- Error chain contains timeout errors")
- }
- if code := err.Code(); code != 0 {
- fmt.Printf("- Error code: %d\n", code)
- }
- fmt.Printf("========================\n\n")
-}
-
-// inspectSingleError handles inspection of a single error (may be part of a chain)
-func inspectSingleError(err error) {
- if err == nil {
- fmt.Println(" (nil error)")
- return
- }
-
- fmt.Printf(" Error: %v\n", err)
- fmt.Printf(" Type: %T\n", err)
-
- // Handle wrapped errors, including *Error type
- var currentErr error = err
- depth := 0
- for currentErr != nil {
- prefix := strings.Repeat(" ", depth+1)
- if depth > 0 {
- fmt.Printf("%sWrapped Cause (%T): %v\n", prefix, currentErr, currentErr)
- }
-
- // Check if it's our specific *Error type
- if e, ok := currentErr.(*Error); ok {
- if name := e.Name(); name != "" {
- fmt.Printf("%sName: %s\n", prefix, name)
- }
- if cat := e.Category(); cat != "" {
- fmt.Printf("%sCategory: %s\n", prefix, cat)
- }
- if code := e.Code(); code != 0 {
- fmt.Printf("%sCode: %d\n", prefix, code)
- }
- if ctx := e.Context(); len(ctx) > 0 {
- fmt.Printf("%sContext:\n", prefix)
- for k, v := range ctx {
- fmt.Printf("%s %s: %v\n", prefix, k, v)
- }
- }
- if stack := e.Stack(); len(stack) > 0 {
- fmt.Printf("%sStack (Top 3):\n", prefix)
- limit := 3
- if len(stack) < limit {
- limit = len(stack)
- }
- for i := 0; i < limit; i++ {
- fmt.Printf("%s %s\n", prefix, stack[i])
- }
- if len(stack) > limit {
- fmt.Printf("%s ... (%d more frames)\n", prefix, len(stack)-limit)
- }
- }
- }
-
- // Unwrap using standard errors.Unwrap and handle *Error Unwrap
- var nextErr error
- // Prioritize *Error's Unwrap if available AND it returns non-nil
- if e, ok := currentErr.(*Error); ok {
- unwrapped := e.Unwrap()
- if unwrapped != nil {
- nextErr = unwrapped
- } else {
- // If *Error.Unwrap returns nil, fall back to standard unwrap
- // This handles cases where *Error might wrap a non-standard error
- // or where its internal cause is deliberately nil.
- nextErr = stderrs.Unwrap(currentErr)
- }
- } else {
- nextErr = stderrs.Unwrap(currentErr) // Fall back to standard unwrap for non-*Error types
- }
-
- // Prevent infinite loops if Unwrap returns the same error, or stop if no more unwrapping
- if nextErr == currentErr || nextErr == nil {
- break
- }
- currentErr = nextErr
- depth++
- if depth > 10 { // Safety break for very deep or potentially cyclic chains
- fmt.Printf("%s... (chain too deep or potential cycle)\n", strings.Repeat(" ", depth+1))
- break
- }
- }
-}
-
-// getErrorCode traverses the error chain to find the highest priority code.
-// It uses errors.As to find the first *Error in the chain.
-func getErrorCode(err error) int {
- var code int = 0 // Default code
- var target *Error
- if As(err, &target) { // Use the package's As helper
- if target != nil { // Add nil check for safety
- code = target.Code()
- }
- }
- // If the top-level error is *Error and has a code, it might take precedence.
- // This depends on desired logic. Let's keep it simple for now: first code found by As.
- if code == 0 { // Only check top-level if As didn't find one with a code
- if e, ok := err.(*Error); ok {
- code = e.Code()
- }
- }
- return code
-}
-
-// handleError demonstrates using Inspect with additional handling logic
-func handleError(err error) {
- fmt.Println("\n=== Processing Failure ===")
- Inspect(err) // Use the primary Inspect function
-
- // Additional handling based on inspection
- code := getErrorCode(err) // Use the helper
-
- switch {
- case IsTimeout(err):
- fmt.Println("\nAction: Check connectivity or increase timeout")
- case code == 402: // Check code obtained via helper
- fmt.Println("\nAction: Payment processing failed - notify billing")
- default:
- fmt.Println("\nAction: Generic failure handling")
- }
-}
-
-// processOrder demonstrates Chain usage with Inspect
-func processOrder() error {
- validateInput := func() error { return nil }
- processPayment := func() error { return stderrs.New("credit card declined") }
- sendNotification := func() error { fmt.Println("Notification sent."); return nil }
- logOrder := func() error { fmt.Println("Order logged."); return nil }
-
- chain := NewChain(ChainWithTimeout(2*time.Second)).
- Step(validateInput).Tag("validation").
- Step(processPayment).Tag("billing").Code(402).Retry(3, 100*time.Millisecond, WithRetryIf(IsRetryable)).
- Step(sendNotification).Optional().
- Step(logOrder)
-
- err := chain.Run()
- if err != nil {
- handleError(err) // Call the unified error handler
- return err // Propagate the error if needed
- }
- fmt.Println("Order processed successfully!")
- return nil
-}
diff --git a/vendor/github.com/olekukonko/errors/multi_error.go b/vendor/github.com/olekukonko/errors/multi_error.go
deleted file mode 100644
index 1d3dff5ab2..0000000000
--- a/vendor/github.com/olekukonko/errors/multi_error.go
+++ /dev/null
@@ -1,423 +0,0 @@
-package errors
-
-import (
- "bytes"
- "encoding/json"
- "fmt"
- "math/rand"
- "strings"
- "sync"
- "sync/atomic"
-)
-
-// MultiError represents a thread-safe collection of errors with enhanced features.
-// Supports limits, sampling, and custom formatting for error aggregation.
-type MultiError struct {
- errors []error
- mu sync.RWMutex
-
- // Configuration fields
- limit int // Maximum number of errors to store (0 = unlimited)
- formatter ErrorFormatter // Custom formatting function for error string
- sampling bool // Whether sampling is enabled to limit error collection
- sampleRate uint32 // Sampling percentage (1-100) when sampling is enabled
- rand *rand.Rand // Random source for sampling (nil defaults to fastRand)
-}
-
-// ErrorFormatter defines a function for custom error message formatting.
-// Takes a slice of errors and returns a single formatted string.
-type ErrorFormatter func([]error) string
-
-// MultiErrorOption configures MultiError behavior during creation.
-type MultiErrorOption func(*MultiError)
-
-// NewMultiError creates a new MultiError instance with optional configuration.
-// Initial capacity is set to 4; applies options in the order provided.
-func NewMultiError(opts ...MultiErrorOption) *MultiError {
- m := &MultiError{
- errors: make([]error, 0, 4),
- limit: 0, // Unlimited by default
- }
-
- for _, opt := range opts {
- opt(m)
- }
- return m
-}
-
-// Add appends an error to the collection with optional sampling, limit checks, and duplicate prevention.
-// Ignores nil errors and duplicates based on string equality; thread-safe.
-func (m *MultiError) Add(errs ...error) {
- if len(errs) == 0 {
- return
- }
-
- m.mu.Lock()
- defer m.mu.Unlock()
-
- for _, err := range errs {
- if err == nil {
- continue
- }
-
- // Check for duplicates by comparing error messages
- duplicate := false
- for _, e := range m.errors {
- if e.Error() == err.Error() {
- duplicate = true
- break
- }
- }
- if duplicate {
- continue
- }
-
- // Apply sampling if enabled and collection isn’t empty
- if m.sampling && len(m.errors) > 0 {
- var r uint32
- if m.rand != nil {
- r = uint32(m.rand.Int31n(100))
- } else {
- r = fastRand() % 100
- }
- if r > m.sampleRate { // Accept if random value is within sample rate
- continue
- }
- }
-
- // Respect limit if set
- if m.limit > 0 && len(m.errors) >= m.limit {
- continue
- }
-
- m.errors = append(m.errors, err)
- }
-}
-
-// Addf formats and adds a new error to the collection.
-func (m *MultiError) Addf(format string, args ...interface{}) {
- m.Add(Newf(format, args...))
-}
-
-// Clear removes all errors from the collection.
-// Thread-safe; resets the slice while preserving capacity.
-func (m *MultiError) Clear() {
- m.mu.Lock()
- defer m.mu.Unlock()
- m.errors = m.errors[:0]
-}
-
-// Count returns the number of errors in the collection.
-// Thread-safe.
-func (m *MultiError) Count() int {
- m.mu.RLock()
- defer m.mu.RUnlock()
- return len(m.errors)
-}
-
-// Error returns a formatted string representation of the errors.
-// Returns empty string if no errors, single error message if one exists,
-// or a formatted list using custom formatter or default if multiple; thread-safe.
-func (m *MultiError) Error() string {
- m.mu.RLock()
- defer m.mu.RUnlock()
-
- switch len(m.errors) {
- case 0:
- return ""
- case 1:
- return m.errors[0].Error()
- default:
- if m.formatter != nil {
- return m.formatter(m.errors)
- }
- return defaultFormat(m.errors)
- }
-}
-
-// Errors returns a copy of the contained errors.
-// Thread-safe; returns nil if no errors exist.
-func (m *MultiError) Errors() []error {
- m.mu.RLock()
- defer m.mu.RUnlock()
-
- if len(m.errors) == 0 {
- return nil
- }
- errs := make([]error, len(m.errors))
- copy(errs, m.errors)
- return errs
-}
-
-// Filter returns a new MultiError containing only errors that match the predicate.
-// Thread-safe; preserves original configuration including limit, formatter, and sampling.
-func (m *MultiError) Filter(fn func(error) bool) *MultiError {
- m.mu.RLock()
- defer m.mu.RUnlock()
-
- var opts []MultiErrorOption
- opts = append(opts, WithLimit(m.limit))
- if m.formatter != nil {
- opts = append(opts, WithFormatter(m.formatter))
- }
- if m.sampling {
- opts = append(opts, WithSampling(m.sampleRate))
- }
-
- filtered := NewMultiError(opts...)
- for _, err := range m.errors {
- if fn(err) {
- filtered.Add(err)
- }
- }
- return filtered
-}
-
-// First returns the first error in the collection, if any.
-// Thread-safe; returns nil if the collection is empty.
-func (m *MultiError) First() error {
- m.mu.RLock()
- defer m.mu.RUnlock()
- if len(m.errors) > 0 {
- return m.errors[0]
- }
- return nil
-}
-
-// Has reports whether the collection contains any errors.
-// Thread-safe.
-func (m *MultiError) Has() bool {
- m.mu.RLock()
- defer m.mu.RUnlock()
- return len(m.errors) > 0
-}
-
-// Last returns the most recently added error in the collection, if any.
-// Thread-safe; returns nil if the collection is empty.
-func (m *MultiError) Last() error {
- m.mu.RLock()
- defer m.mu.RUnlock()
- if len(m.errors) > 0 {
- return m.errors[len(m.errors)-1]
- }
- return nil
-}
-
-// Merge combines another MultiError's errors into this one.
-// Thread-safe; respects this instance’s limit and sampling settings; no-op if other is nil or empty.
-func (m *MultiError) Merge(other *MultiError) {
- if other == nil || !other.Has() {
- return
- }
-
- other.mu.RLock()
- defer other.mu.RUnlock()
-
- for _, err := range other.errors {
- m.Add(err)
- }
-}
-
-// IsNull checks if the MultiError is empty or contains only null errors.
-// Returns true if empty or all errors are null (via IsNull() or empty message); thread-safe.
-func (m *MultiError) IsNull() bool {
- m.mu.RLock()
- defer m.mu.RUnlock()
-
- // Fast path for empty MultiError
- if len(m.errors) == 0 {
- return true
- }
-
- // Check each error for null status
- allNull := true
- for _, err := range m.errors {
- switch e := err.(type) {
- case interface{ IsNull() bool }:
- if !e.IsNull() {
- allNull = false
- break
- }
- case nil:
- continue
- default:
- if e.Error() != "" {
- allNull = false
- break
- }
- }
- }
- return allNull
-}
-
-// Single returns nil if the collection is empty, the single error if only one exists,
-// or the MultiError itself if multiple errors are present.
-// Thread-safe; useful for unwrapping to a single error when possible.
-func (m *MultiError) Single() error {
- m.mu.RLock()
- defer m.mu.RUnlock()
-
- switch len(m.errors) {
- case 0:
- return nil
- case 1:
- return m.errors[0]
- default:
- return m
- }
-}
-
-// String implements the Stringer interface for a concise string representation.
-// Thread-safe; delegates to Error() for formatting.
-func (m *MultiError) String() string {
- return m.Error()
-}
-
-// Unwrap returns a copy of the contained errors for multi-error unwrapping.
-// Implements the errors.Unwrap interface; thread-safe; returns nil if empty.
-func (m *MultiError) Unwrap() []error {
- return m.Errors()
-}
-
-// WithFormatter sets a custom error formatting function.
-// Returns a MultiErrorOption for use with NewMultiError; overrides default formatting.
-func WithFormatter(f ErrorFormatter) MultiErrorOption {
- return func(m *MultiError) {
- m.formatter = f
- }
-}
-
-// WithLimit sets the maximum number of errors to store.
-// Returns a MultiErrorOption for use with NewMultiError; 0 means unlimited, negative values are ignored.
-func WithLimit(n int) MultiErrorOption {
- return func(m *MultiError) {
- if n < 0 {
- n = 0 // Ensure non-negative limit
- }
- m.limit = n
- }
-}
-
-// WithSampling enables error sampling with a specified rate (1-100).
-// Returns a MultiErrorOption for use with NewMultiError; caps rate at 100 for validity.
-func WithSampling(rate uint32) MultiErrorOption {
- return func(m *MultiError) {
- if rate > 100 {
- rate = 100
- }
- m.sampling = true
- m.sampleRate = rate
- }
-}
-
-// WithRand sets a custom random source for sampling, useful for testing.
-// Returns a MultiErrorOption for use with NewMultiError; defaults to fastRand if nil.
-func WithRand(r *rand.Rand) MultiErrorOption {
- return func(m *MultiError) {
- m.rand = r
- }
-}
-
-// MarshalJSON serializes the MultiError to JSON, including all contained errors and configuration metadata.
-// Thread-safe; errors are serialized using their MarshalJSON method if available, otherwise as strings.
-func (m *MultiError) MarshalJSON() ([]byte, error) {
- m.mu.RLock()
- defer m.mu.RUnlock()
-
- // Get buffer from pool for efficiency
- buf := jsonBufferPool.Get().(*bytes.Buffer)
- defer jsonBufferPool.Put(buf)
- buf.Reset()
-
- // Create encoder
- enc := json.NewEncoder(buf)
- enc.SetEscapeHTML(false)
-
- // Define JSON structure
- type jsonError struct {
- Error interface{} `json:"error"` // Holds either JSON-marshaled error or string
- }
-
- je := struct {
- Count int `json:"count"` // Number of errors
- Limit int `json:"limit,omitempty"` // Maximum error limit (omitted if 0)
- Sampling bool `json:"sampling,omitempty"` // Whether sampling is enabled
- SampleRate uint32 `json:"sample_rate,omitempty"` // Sampling rate (1-100, omitted if not sampling)
- Errors []jsonError `json:"errors"` // List of errors
- }{
- Count: len(m.errors),
- Limit: m.limit,
- Sampling: m.sampling,
- SampleRate: m.sampleRate,
- }
-
- // Serialize each error
- je.Errors = make([]jsonError, len(m.errors))
- for i, err := range m.errors {
- if err == nil {
- je.Errors[i] = jsonError{Error: nil}
- continue
- }
- // Check if the error implements json.Marshaler
- if marshaler, ok := err.(json.Marshaler); ok {
- marshaled, err := marshaler.MarshalJSON()
- if err != nil {
- // Fallback to string if marshaling fails
- je.Errors[i] = jsonError{Error: err.Error()}
- } else {
- var raw json.RawMessage = marshaled
- je.Errors[i] = jsonError{Error: raw}
- }
- } else {
- // Use error string for non-marshaler errors
- je.Errors[i] = jsonError{Error: err.Error()}
- }
- }
-
- // Encode JSON
- if err := enc.Encode(je); err != nil {
- return nil, fmt.Errorf("failed to marshal MultiError: %v", err)
- }
-
- // Remove trailing newline
- result := buf.Bytes()
- if len(result) > 0 && result[len(result)-1] == '\n' {
- result = result[:len(result)-1]
- }
- return result, nil
-}
-
-// defaultFormat provides the default formatting for multiple errors.
-// Returns a semicolon-separated list prefixed with the error count (e.g., "errors(3): err1; err2; err3").
-func defaultFormat(errs []error) string {
- var sb strings.Builder
- sb.WriteString(fmt.Sprintf("errors(%d): ", len(errs)))
- for i, err := range errs {
- if i > 0 {
- sb.WriteString("; ")
- }
- sb.WriteString(err.Error())
- }
- return sb.String()
-}
-
-// fastRand generates a quick pseudo-random number for sampling.
-// Uses a simple xorshift algorithm based on the current time; not cryptographically secure.
-var fastRandState uint32 = 1 // Must be non-zero
-
-func fastRand() uint32 {
- for {
- // Atomically load the current state
- old := atomic.LoadUint32(&fastRandState)
- // Xorshift computation
- x := old
- x ^= x << 13
- x ^= x >> 17
- x ^= x << 5
- // Attempt to store the new state atomically
- if atomic.CompareAndSwapUint32(&fastRandState, old, x) {
- return x
- }
- // Otherwise retry
- }
-}
diff --git a/vendor/github.com/olekukonko/errors/pool.go b/vendor/github.com/olekukonko/errors/pool.go
deleted file mode 100644
index 37515aac4c..0000000000
--- a/vendor/github.com/olekukonko/errors/pool.go
+++ /dev/null
@@ -1,75 +0,0 @@
-// pool.go
-package errors
-
-import (
- "sync"
- "sync/atomic"
-)
-
-// ErrorPool is a high-performance, thread-safe pool for reusing *Error instances.
-// Reduces allocation overhead by recycling errors; tracks hit/miss statistics.
-type ErrorPool struct {
- pool sync.Pool // Underlying pool for storing *Error instances
- poolStats struct { // Embedded struct for pool usage statistics
- hits atomic.Int64 // Number of times an error was reused from the pool
- misses atomic.Int64 // Number of times a new error was created due to pool miss
- }
-}
-
-// NewErrorPool creates a new ErrorPool instance.
-// Initializes the pool with a New function that returns a fresh *Error with default smallContext.
-func NewErrorPool() *ErrorPool {
- return &ErrorPool{
- pool: sync.Pool{
- New: func() interface{} {
- return &Error{
- smallContext: [contextSize]contextItem{},
- }
- },
- },
- }
-}
-
-// Get retrieves an *Error from the pool or creates a new one if pooling is disabled or pool is empty.
-// Resets are handled by Put; thread-safe; updates hit/miss stats when pooling is enabled.
-func (ep *ErrorPool) Get() *Error {
- if currentConfig.disablePooling {
- return &Error{
- smallContext: [contextSize]contextItem{},
- }
- }
-
- e := ep.pool.Get().(*Error)
- if e == nil { // Pool returned nil (unlikely due to New func, but handled for safety)
- ep.poolStats.misses.Add(1)
- return &Error{
- smallContext: [contextSize]contextItem{},
- }
- }
- ep.poolStats.hits.Add(1)
- return e
-}
-
-// Put returns an *Error to the pool after resetting it.
-// Ignores nil errors or if pooling is disabled; preserves stack capacity; thread-safe.
-func (ep *ErrorPool) Put(e *Error) {
- if e == nil || currentConfig.disablePooling {
- return
- }
-
- // Reset the error to a clean state, preserving capacity
- e.Reset()
-
- // Reset stack length while keeping capacity for reuse
- if e.stack != nil {
- e.stack = e.stack[:0]
- }
-
- ep.pool.Put(e)
-}
-
-// Stats returns the current pool statistics as hits and misses.
-// Thread-safe; uses atomic loads to ensure accurate counts.
-func (ep *ErrorPool) Stats() (hits, misses int64) {
- return ep.poolStats.hits.Load(), ep.poolStats.misses.Load()
-}
diff --git a/vendor/github.com/olekukonko/errors/pool_above_1_24.go b/vendor/github.com/olekukonko/errors/pool_above_1_24.go
deleted file mode 100644
index 706550fd16..0000000000
--- a/vendor/github.com/olekukonko/errors/pool_above_1_24.go
+++ /dev/null
@@ -1,24 +0,0 @@
-//go:build go1.24
-// +build go1.24
-
-package errors
-
-import "runtime"
-
-// setupCleanup configures a cleanup function for an *Error to auto-return it to the pool.
-// Only active for Go 1.24+; uses runtime.AddCleanup when autoFree is set and pooling is enabled.
-func (ep *ErrorPool) setupCleanup(e *Error) {
- if currentConfig.autoFree {
- runtime.AddCleanup(e, func(_ *struct{}) {
- if !currentConfig.disablePooling {
- ep.Put(e) // Return to pool when cleaned up
- }
- }, nil) // No additional context needed
- }
-}
-
-// clearCleanup is a no-op for Go 1.24 and above.
-// Cleanup is managed by runtime.AddCleanup; no explicit removal is required.
-func (ep *ErrorPool) clearCleanup(e *Error) {
- // No-op for Go 1.24+
-}
diff --git a/vendor/github.com/olekukonko/errors/pool_below_1_24.go b/vendor/github.com/olekukonko/errors/pool_below_1_24.go
deleted file mode 100644
index 3c94209f7f..0000000000
--- a/vendor/github.com/olekukonko/errors/pool_below_1_24.go
+++ /dev/null
@@ -1,24 +0,0 @@
-//go:build !go1.24
-// +build !go1.24
-
-package errors
-
-import "runtime"
-
-// setupCleanup configures a finalizer for an *Error to auto-return it to the pool.
-// Only active for Go versions < 1.24; enables automatic cleanup when autoFree is set and pooling is enabled.
-func (ep *ErrorPool) setupCleanup(e *Error) {
- if currentConfig.autoFree {
- runtime.SetFinalizer(e, func(e *Error) {
- if !currentConfig.disablePooling {
- ep.Put(e) // Return to pool when garbage collected
- }
- })
- }
-}
-
-// clearCleanup removes any finalizer set on an *Error.
-// Only active for Go versions < 1.24; ensures no cleanup action occurs on garbage collection.
-func (ep *ErrorPool) clearCleanup(e *Error) {
- runtime.SetFinalizer(e, nil) // Disable finalizer
-}
diff --git a/vendor/github.com/olekukonko/errors/retry.go b/vendor/github.com/olekukonko/errors/retry.go
deleted file mode 100644
index 6d6df3878a..0000000000
--- a/vendor/github.com/olekukonko/errors/retry.go
+++ /dev/null
@@ -1,368 +0,0 @@
-// Package errors provides utilities for error handling, including a flexible retry mechanism.
-package errors
-
-import (
- "context"
- "math/rand"
- "time"
-)
-
-// BackoffStrategy defines the interface for calculating retry delays.
-type BackoffStrategy interface {
- // Backoff returns the delay for a given attempt based on the base delay.
- Backoff(attempt int, baseDelay time.Duration) time.Duration
-}
-
-// ConstantBackoff provides a fixed delay for each retry attempt.
-type ConstantBackoff struct{}
-
-// Backoff returns the base delay regardless of the attempt number.
-// Implements BackoffStrategy with a constant delay.
-func (c ConstantBackoff) Backoff(_ int, baseDelay time.Duration) time.Duration {
- return baseDelay
-}
-
-// ExponentialBackoff provides an exponentially increasing delay for retry attempts.
-type ExponentialBackoff struct{}
-
-// Backoff returns a delay that doubles with each attempt, starting from the base delay.
-// Uses bit shifting for efficient exponential growth (e.g., baseDelay * 2^(attempt-1)).
-func (e ExponentialBackoff) Backoff(attempt int, baseDelay time.Duration) time.Duration {
- if attempt <= 1 {
- return baseDelay
- }
- return baseDelay * time.Duration(1< 0 && delay > r.maxDelay {
- delay = r.maxDelay
- }
- if r.jitter {
- delay = addJitter(delay)
- }
-
- // Wait with context
- select {
- case <-r.ctx.Done():
- return r.ctx.Err()
- case <-time.After(delay):
- }
- }
-
- return lastErr
-}
-
-// ExecuteContext runs the provided function with retry logic, respecting context cancellation.
-// Returns nil on success or the last error if all attempts fail or context is cancelled.
-func (r *Retry) ExecuteContext(ctx context.Context, fn func() error) error {
- var lastErr error
-
- // If the retry instance already has a context, use it. Otherwise, use the provided one.
- // If both are provided, maybe create a derived context? For now, prioritize the one from WithContext.
- execCtx := r.ctx
- if execCtx == context.Background() && ctx != nil { // Use provided ctx if retry ctx is default and provided one isn't nil
- execCtx = ctx
- } else if ctx == nil { // Ensure we always have a non-nil context
- execCtx = context.Background()
- }
- // Note: This logic might need refinement depending on how contexts should interact.
- // A safer approach might be: if r.ctx != background, use it. Else use provided ctx.
-
- for attempt := 1; attempt <= r.maxAttempts; attempt++ {
- // Check context before executing the function
- select {
- case <-execCtx.Done():
- return execCtx.Err() // Return context error immediately
- default:
- // Context is okay, proceed
- }
-
- err := fn()
- if err == nil {
- return nil // Success
- }
-
- // Check if retry is applicable based on the error
- if r.retryIf != nil && !r.retryIf(err) {
- return err // Not retryable, return the error
- }
-
- lastErr = err // Store the last encountered error
-
- // Execute the OnRetry callback if configured
- if r.onRetry != nil {
- r.onRetry(attempt, err)
- }
-
- // Exit loop if this was the last attempt
- if attempt == r.maxAttempts {
- break
- }
-
- // --- Calculate and apply delay ---
- currentDelay := r.backoff.Backoff(attempt, r.delay)
- if r.maxDelay > 0 && currentDelay > r.maxDelay { // Check maxDelay > 0 before capping
- currentDelay = r.maxDelay
- }
- if r.jitter {
- currentDelay = addJitter(currentDelay)
- }
- if currentDelay < 0 { // Ensure delay isn't negative after jitter
- currentDelay = 0
- }
- // --- Wait for the delay or context cancellation ---
- select {
- case <-execCtx.Done():
- // If context is cancelled during the wait, return the context error
- // Often more informative than returning the last application error.
- return execCtx.Err()
- case <-time.After(currentDelay):
- // Wait finished, continue to the next attempt
- }
- }
-
- // All attempts failed, return the last error encountered
- return lastErr
-}
-
-// Transform creates a new Retry instance with modified configuration.
-// Copies all settings from the original Retry and applies the given options.
-func (r *Retry) Transform(opts ...RetryOption) *Retry {
- newRetry := &Retry{
- maxAttempts: r.maxAttempts,
- delay: r.delay,
- maxDelay: r.maxDelay,
- retryIf: r.retryIf,
- onRetry: r.onRetry,
- backoff: r.backoff,
- jitter: r.jitter,
- ctx: r.ctx,
- }
- for _, opt := range opts {
- opt(newRetry)
- }
- return newRetry
-}
-
-// WithBackoff sets the backoff strategy using the BackoffStrategy interface.
-// Returns a RetryOption; no-op if strategy is nil, retaining the existing strategy.
-func WithBackoff(strategy BackoffStrategy) RetryOption {
- return func(r *Retry) {
- if strategy != nil {
- r.backoff = strategy
- }
- }
-}
-
-// WithContext sets the context for cancellation and deadlines.
-// Returns a RetryOption; retains context.Background if ctx is nil.
-func WithContext(ctx context.Context) RetryOption {
- return func(r *Retry) {
- if ctx != nil {
- r.ctx = ctx
- }
- }
-}
-
-// WithDelay sets the initial delay between retries.
-// Returns a RetryOption; ensures non-negative delay by setting negatives to 0.
-func WithDelay(delay time.Duration) RetryOption {
- return func(r *Retry) {
- if delay < 0 {
- delay = 0
- }
- r.delay = delay
- }
-}
-
-// WithJitter enables or disables jitter in the backoff delay.
-// Returns a RetryOption; toggles random delay variation.
-func WithJitter(jitter bool) RetryOption {
- return func(r *Retry) {
- r.jitter = jitter
- }
-}
-
-// WithMaxAttempts sets the maximum number of retry attempts.
-// Returns a RetryOption; ensures at least 1 attempt by adjusting lower values.
-func WithMaxAttempts(maxAttempts int) RetryOption {
- return func(r *Retry) {
- if maxAttempts < 1 {
- maxAttempts = 1
- }
- r.maxAttempts = maxAttempts
- }
-}
-
-// WithMaxDelay sets the maximum delay between retries.
-// Returns a RetryOption; ensures non-negative delay by setting negatives to 0.
-func WithMaxDelay(maxDelay time.Duration) RetryOption {
- return func(r *Retry) {
- if maxDelay < 0 {
- maxDelay = 0
- }
- r.maxDelay = maxDelay
- }
-}
-
-// WithOnRetry sets a callback to execute after each failed attempt.
-// Returns a RetryOption; callback receives attempt number and error.
-func WithOnRetry(onRetry func(attempt int, err error)) RetryOption {
- return func(r *Retry) {
- r.onRetry = onRetry
- }
-}
-
-// WithRetryIf sets the condition under which to retry.
-// Returns a RetryOption; retains IsRetryable default if retryIf is nil.
-func WithRetryIf(retryIf func(error) bool) RetryOption {
- return func(r *Retry) {
- if retryIf != nil {
- r.retryIf = retryIf
- }
- }
-}
-
-// ExecuteReply runs the provided function with retry logic and returns its result.
-// Returns the result and nil on success, or zero value and last error on failure; generic type T.
-func ExecuteReply[T any](r *Retry, fn func() (T, error)) (T, error) {
- var lastErr error
- var zero T
-
- for attempt := 1; attempt <= r.maxAttempts; attempt++ {
- result, err := fn()
- if err == nil {
- return result, nil
- }
-
- // Check if retry is applicable; return immediately if not retryable
- if r.retryIf != nil && !r.retryIf(err) {
- return zero, err
- }
-
- lastErr = err
- if r.onRetry != nil {
- r.onRetry(attempt, err)
- }
-
- if attempt == r.maxAttempts {
- break
- }
-
- // Calculate delay with backoff, cap at maxDelay, and apply jitter if enabled
- currentDelay := r.backoff.Backoff(attempt, r.delay)
- if currentDelay > r.maxDelay {
- currentDelay = r.maxDelay
- }
- if r.jitter {
- currentDelay = addJitter(currentDelay)
- }
-
- // Wait with respect to context cancellation or timeout
- select {
- case <-r.ctx.Done():
- return zero, r.ctx.Err()
- case <-time.After(currentDelay):
- }
- }
- return zero, lastErr
-}
diff --git a/vendor/github.com/olekukonko/errors/utils.go b/vendor/github.com/olekukonko/errors/utils.go
deleted file mode 100644
index bb6753dfe6..0000000000
--- a/vendor/github.com/olekukonko/errors/utils.go
+++ /dev/null
@@ -1,153 +0,0 @@
-// Package errors provides utility functions for error handling, including stack
-// trace capture and function name extraction.
-package errors
-
-import (
- "database/sql"
- "fmt"
- "reflect"
- "runtime"
- "strings"
-)
-
-// captureStack captures a stack trace with the configured depth.
-// Skip=0 captures the current call site; skips captureStack and its caller (+2 frames); thread-safe via stackPool.
-func captureStack(skip int) []uintptr {
- buf := stackPool.Get().([]uintptr)
- buf = buf[:cap(buf)]
-
- // +2 to skip captureStack and the immediate caller
- n := runtime.Callers(skip+2, buf)
- if n == 0 {
- stackPool.Put(buf)
- return nil
- }
-
- // Create a new slice to return, avoiding direct use of pooled memory
- stack := make([]uintptr, n)
- copy(stack, buf[:n])
- stackPool.Put(buf)
-
- return stack
-}
-
-// min returns the smaller of two integers.
-// Simple helper for limiting stack trace size or other comparisons.
-func min(a, b int) int {
- if a < b {
- return a
- }
- return b
-}
-
-// clearMap removes all entries from a map.
-// Helper function to reset map contents without reallocating.
-func clearMap(m map[string]interface{}) {
- for k := range m {
- delete(m, k)
- }
-}
-
-// sqlNull detects if a value represents a SQL NULL type.
-// Returns true for nil or invalid sql.Null* types (e.g., NullString, NullInt64); false otherwise.
-func sqlNull(v interface{}) bool {
- if v == nil {
- return true
- }
-
- switch val := v.(type) {
- case sql.NullString:
- return !val.Valid
- case sql.NullTime:
- return !val.Valid
- case sql.NullInt64:
- return !val.Valid
- case sql.NullBool:
- return !val.Valid
- case sql.NullFloat64:
- return !val.Valid
- default:
- return false
- }
-}
-
-// getFuncName extracts the function name from an interface, typically a function or method.
-// Returns "unknown" if the input is nil or invalid; trims leading dots from runtime name.
-func getFuncName(fn interface{}) string {
- if fn == nil {
- return "unknown"
- }
- fullName := runtime.FuncForPC(reflect.ValueOf(fn).Pointer()).Name()
- return strings.TrimPrefix(fullName, ".")
-}
-
-// isInternalFrame determines if a stack frame is considered "internal".
-// Returns true for frames from runtime, reflect, or this package’s subdirectories if FilterInternal is true.
-func isInternalFrame(frame runtime.Frame) bool {
- if strings.HasPrefix(frame.Function, "runtime.") || strings.HasPrefix(frame.Function, "reflect.") {
- return true
- }
-
- suffixes := []string{
- "errors",
- "utils",
- "helper",
- "retry",
- "multi",
- }
-
- file := frame.File
- for _, v := range suffixes {
- if strings.Contains(file, fmt.Sprintf("github.com/olekukonko/errors/%s", v)) {
- return true
- }
- }
- return false
-}
-
-// FormatError returns a formatted string representation of an error.
-// Includes message, name, context, stack trace, and cause for *Error types; just message for others; "" if nil.
-func FormatError(err error) string {
- if err == nil {
- return ""
- }
- var sb strings.Builder
- if e, ok := err.(*Error); ok {
- sb.WriteString(fmt.Sprintf("Error: %s\n", e.Error()))
- if e.name != "" {
- sb.WriteString(fmt.Sprintf("Name: %s\n", e.name))
- }
- if ctx := e.Context(); len(ctx) > 0 {
- sb.WriteString("Context:\n")
- for k, v := range ctx {
- sb.WriteString(fmt.Sprintf("\t%s: %v\n", k, v))
- }
- }
- if stack := e.Stack(); len(stack) > 0 {
- sb.WriteString("Stack Trace:\n")
- for _, frame := range stack {
- sb.WriteString(fmt.Sprintf("\t%s\n", frame))
- }
- }
- if e.cause != nil {
- sb.WriteString(fmt.Sprintf("Caused by: %s\n", FormatError(e.cause)))
- }
- } else {
- sb.WriteString(fmt.Sprintf("Error: %s\n", err.Error()))
- }
- return sb.String()
-}
-
-// Caller returns the file, line, and function name of the caller at the specified skip level.
-// Skip=0 returns the caller of this function, 1 returns its caller, etc.; returns "unknown" if no caller found.
-func Caller(skip int) (file string, line int, function string) {
- configMu.RLock()
- defer configMu.RUnlock()
- var pcs [1]uintptr
- n := runtime.Callers(skip+2, pcs[:]) // +2 skips Caller and its immediate caller
- if n == 0 {
- return "", 0, "unknown"
- }
- frame, _ := runtime.CallersFrames(pcs[:n]).Next()
- return frame.File, frame.Line, frame.Function
-}
diff --git a/vendor/github.com/olekukonko/ll/.gitignore b/vendor/github.com/olekukonko/ll/.gitignore
deleted file mode 100644
index 70d0877690..0000000000
--- a/vendor/github.com/olekukonko/ll/.gitignore
+++ /dev/null
@@ -1,5 +0,0 @@
-.idea
-lab
-tmp
-#_*
-_test/
diff --git a/vendor/github.com/olekukonko/ll/LICENSE b/vendor/github.com/olekukonko/ll/LICENSE
deleted file mode 100644
index ca02db8c27..0000000000
--- a/vendor/github.com/olekukonko/ll/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-MIT 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.
diff --git a/vendor/github.com/olekukonko/ll/README.md b/vendor/github.com/olekukonko/ll/README.md
deleted file mode 100644
index facb4736ca..0000000000
--- a/vendor/github.com/olekukonko/ll/README.md
+++ /dev/null
@@ -1,366 +0,0 @@
-# ll - A Modern Structured Logging Library for Go
-
-`ll` is a high-performance, production-ready logging library for Go, designed to provide **hierarchical namespaces**, **structured logging**, **middleware pipelines**, **conditional logging**, and support for multiple output formats, including text, JSON, colorized logs, and compatibility with Go’s `slog`. It’s ideal for applications requiring fine-grained log control, extensibility, and scalability.
-
-## Key Features
-
-- **Hierarchical Namespaces**: Organize logs with fine-grained control over subsystems (e.g., "app/db").
-- **Structured Logging**: Add key-value metadata for machine-readable logs.
-- **Middleware Pipeline**: Customize log processing with error-based rejection.
-- **Conditional Logging**: Optimize performance by skipping unnecessary log operations.
-- **Multiple Output Formats**: Support for text, JSON, colorized logs, and `slog` integration.
-- **Debugging Utilities**: Inspect variables (`Dbg`), binary data (`Dump`), and stack traces (`Stack`).
-- **Thread-Safe**: Built for concurrent use with mutex-protected state.
-- **Performance Optimized**: Minimal allocations and efficient namespace caching.
-
-## Installation
-
-Install `ll` using Go modules:
-
-```bash
-go get github.com/olekukonko/ll
-```
-
-Ensure you have Go 1.21 or later for optimal compatibility.
-
-## Getting Started
-
-Here’s a quick example to start logging with `ll`:
-
-
-```go
-package main
-
-import (
- "github.com/olekukonko/ll"
-)
-
-func main() {
- // Create a logger with namespace "app"
- logger := ll.New("")
-
- // enable output
- logger.Enable()
-
- // Basic log
- logger.Info("Welcome") // Output: [app] INFO: Application started
-
- logger = logger.Namespace("app")
-
- // Basic log
- logger.Info("start at :8080") // Output: [app] INFO: Application started
-
- //Output
- //INFO: Welcome
- //[app] INFO: start at :8080
-}
-
-```
-
-```go
-package main
-
-import (
- "github.com/olekukonko/ll"
- "github.com/olekukonko/ll/lh"
- "os"
-)
-
-func main() {
- // Chaining
- logger := ll.New("app").Enable().Handler(lh.NewTextHandler(os.Stdout))
-
- // Basic log
- logger.Info("Application started") // Output: [app] INFO: Application started
-
- // Structured log with fields
- logger.Fields("user", "alice", "status", 200).Info("User logged in")
- // Output: [app] INFO: User logged in [user=alice status=200]
-
- // Conditional log
- debugMode := false
- logger.If(debugMode).Debug("Debug info") // No output (debugMode is false)
-}
-```
-
-## Core Features
-
-### 1. Hierarchical Namespaces
-
-Namespaces allow you to organize logs hierarchically, enabling precise control over logging for different parts of your application. This is especially useful for large systems with multiple components.
-
-**Benefits**:
-- **Granular Control**: Enable/disable logs for specific subsystems (e.g., "app/db" vs. "app/api").
-- **Scalability**: Manage log volume in complex applications.
-- **Readability**: Clear namespace paths improve traceability.
-
-**Example**:
-```go
-logger := ll.New("app").Enable().Handler(lh.NewTextHandler(os.Stdout))
-
-// Child loggers
-dbLogger := logger.Namespace("db")
-apiLogger := logger.Namespace("api").Style(lx.NestedPath)
-
-// Namespace control
-logger.NamespaceEnable("app/db") // Enable DB logs
-logger.NamespaceDisable("app/api") // Disable API logs
-
-dbLogger.Info("Query executed") // Output: [app/db] INFO: Query executed
-apiLogger.Info("Request received") // No output
-```
-
-### 2. Structured Logging
-
-Add key-value metadata to logs for machine-readable output, making it easier to query and analyze logs in tools like ELK or Grafana.
-
-**Example**:
-```go
-logger := ll.New("app").Enable().Handler(lh.NewTextHandler(os.Stdout))
-
-// Variadic fields
-logger.Fields("user", "bob", "status", 200).Info("Request completed")
-// Output: [app] INFO: Request completed [user=bob status=200]
-
-// Map-based fields
-logger.Field(map[string]interface{}{"method": "GET"}).Info("Request")
-// Output: [app] INFO: Request [method=GET]
-```
-
-### 3. Middleware Pipeline
-
-Customize log processing with a middleware pipeline. Middleware functions can enrich, filter, or transform logs, using an error-based rejection mechanism (non-nil errors stop logging).
-
-**Example**:
-```go
-logger := ll.New("app").Enable().Handler(lh.NewTextHandler(os.Stdout))
-
-// Enrich logs with app metadata
-logger.Use(ll.FuncMiddleware(func(e *lx.Entry) error {
- if e.Fields == nil {
- e.Fields = make(map[string]interface{})
- }
- e.Fields["app"] = "myapp"
- return nil
-}))
-
-// Filter low-level logs
-logger.Use(ll.FuncMiddleware(func(e *lx.Entry) error {
- if e.Level < lx.LevelWarn {
- return fmt.Errorf("level too low")
- }
- return nil
-}))
-
-logger.Info("Ignored") // No output (filtered)
-logger.Warn("Warning") // Output: [app] WARN: Warning [app=myapp]
-```
-
-### 4. Conditional Logging
-
-Optimize performance by skipping expensive log operations when conditions are false, ideal for production environments.
-
-**Example**:
-```go
-logger := ll.New("app").Enable().Handler(lh.NewTextHandler(os.Stdout))
-
-featureEnabled := true
-logger.If(featureEnabled).Fields("action", "update").Info("Feature used")
-// Output: [app] INFO: Feature used [action=update]
-
-logger.If(false).Info("Ignored") // No output, no processing
-```
-
-### 5. Multiple Output Formats
-
-`ll` supports various output formats, including human-readable text, colorized logs, JSON, and integration with Go’s `slog` package.
-
-**Example**:
-```go
-logger := ll.New("app").Enable()
-
-// Text output
-logger.Handler(lh.NewTextHandler(os.Stdout))
-logger.Info("Text log") // Output: [app] INFO: Text log
-
-// JSON output
-logger.Handler(lh.NewJSONHandler(os.Stdout, time.RFC3339Nano))
-logger.Info("JSON log") // Output: {"timestamp":"...","level":"INFO","message":"JSON log","namespace":"app"}
-
-// Slog integration
-slogText := slog.NewTextHandler(os.Stdout, nil)
-logger.Handler(lh.NewSlogHandler(slogText))
-logger.Info("Slog log") // Output: level=INFO msg="Slog log" namespace=app class=Text
-```
-
-### 6. Debugging Utilities
-
-`ll` provides powerful tools for debugging, including variable inspection, binary data dumps, and stack traces.
-
-#### Core Debugging Methods
-
-1. **Dbg - Contextual Inspection**
- Inspects variables with file and line context, preserving variable names and handling all Go types.
- ```go
- x := 42
- user := struct{ Name string }{"Alice"}
- ll.Dbg(x) // Output: [file.go:123] x = 42
- ll.Dbg(user) // Output: [file.go:124] user = [Name:Alice]
- ```
-
-2. **Dump - Binary Inspection**
- Displays a hex/ASCII view of data, optimized for strings, bytes, and complex types (with JSON fallback).
- ```go
- ll.Handler(lh.NewColorizedHandler(os.Stdout))
- ll.Dump("hello\nworld") // Output: Hex/ASCII dump (see example/dump.png)
- ```
-
-3. **Stack - Stack Inspection**
- Logs a stack trace for debugging critical errors.
- ```go
- ll.Handler(lh.NewColorizedHandler(os.Stdout))
- ll.Stack("Critical error") // Output: [app] ERROR: Critical error [stack=...] (see example/stack.png)
- ```
-
-4**General Output**
- Logs a output in structured way for inspection of public & private values.
- ```go
- ll.Handler(lh.NewColorizedHandler(os.Stdout))
- ll.Output(&SomeStructWithPrivateValues{})
- ```
-
-#### Performance Tracking
-Measure execution time for performance analysis.
-```go
-// Automatic measurement
-defer ll.Measure(func() { time.Sleep(time.Millisecond) })()
-// Output: [app] INFO: function executed [duration=~1ms]
-
-// Explicit benchmarking
-start := time.Now()
-time.Sleep(time.Millisecond)
-ll.Benchmark(start) // Output: [app] INFO: benchmark [start=... end=... duration=...]
-```
-
-**Performance Notes**:
-- `Dbg` calls are disabled at compile-time when not enabled.
-- `Dump` optimizes for primitive types, strings, and bytes with zero-copy paths.
-- Stack traces are configurable via `StackSize`.
-
-## Real-World Example: Web Server
-
-A practical example of using `ll` in a web server with structured logging, middleware, and `slog` integration:
-
-```go
-package main
-
-import (
- "github.com/olekukonko/ll"
- "github.com/olekukonko/ll/lh"
- "log/slog"
- "net/http"
- "os"
- "time"
-)
-
-func main() {
- // Initialize logger with slog handler
- slogHandler := slog.NewJSONHandler(os.Stdout, nil)
- logger := ll.New("server").Enable().Handler(lh.NewSlogHandler(slogHandler))
-
- // HTTP child logger
- httpLogger := logger.Namespace("http").Style(lx.NestedPath)
-
- // Middleware for request ID
- httpLogger.Use(ll.FuncMiddleware(func(e *lx.Entry) error {
- if e.Fields == nil {
- e.Fields = make(map[string]interface{})
- }
- e.Fields["request_id"] = "req-" + time.Now().String()
- return nil
- }))
-
- http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
- start := time.Now()
- httpLogger.Fields("method", r.Method, "path", r.URL.Path).Info("Request received")
- w.Write([]byte("Hello, world!"))
- httpLogger.Fields("duration_ms", time.Since(start).Milliseconds()).Info("Request completed")
- })
-
- logger.Info("Starting server on :8080")
- http.ListenAndServe(":8080", nil)
-}
-```
-
-**Sample Output (JSON via slog)**:
-```json
-{"level":"INFO","msg":"Starting server on :8080","namespace":"server"}
-{"level":"INFO","msg":"Request received","namespace":"server/http","class":"Text","method":"GET","path":"/","request_id":"req-..."}
-{"level":"INFO","msg":"Request completed","namespace":"server/http","class":"Text","duration_ms":1,"request_id":"req-..."}
-```
-
-## Why Choose `ll`?
-
-- **Granular Control**: Hierarchical namespaces for precise log management.
-- **Performance**: Conditional logging and optimized concatenation reduce overhead.
-- **Extensibility**: Middleware pipeline for custom log processing.
-- **Structured Output**: Machine-readable logs with key-value metadata.
-- **Flexible Formats**: Text, JSON, colorized, and `slog` support.
-- **Debugging Power**: Advanced tools like `Dbg`, `Dump`, and `Stack` for deep inspection.
-- **Thread-Safe**: Safe for concurrent use in high-throughput applications.
-
-## Comparison with Other Libraries
-
-| Feature | `ll` | `log` (stdlib) | `slog` (stdlib) | `zap` |
-|--------------------------|--------------------------|----------------|-----------------|-------------------|
-| Hierarchical Namespaces | ✅ | ❌ | ❌ | ❌ |
-| Structured Logging | ✅ (Fields, Context) | ❌ | ✅ | ✅ |
-| Middleware Pipeline | ✅ | ❌ | ❌ | ✅ (limited) |
-| Conditional Logging | ✅ (If, IfOne, IfAny) | ❌ | ❌ | ❌ |
-| Slog Compatibility | ✅ | ❌ | ✅ (native) | ❌ |
-| Debugging (Dbg, Dump) | ✅ | ❌ | ❌ | ❌ |
-| Performance (disabled logs) | High (conditional) | Low | Medium | High |
-| Output Formats | Text, JSON, Color, Slog | Text | Text, JSON | JSON, Text |
-
-## Benchmarks
-
-`ll` is optimized for performance, particularly for disabled logs and structured logging:
-- **Disabled Logs**: 30% faster than `slog` due to efficient conditional checks.
-- **Structured Logging**: 2x faster than `log` with minimal allocations.
-- **Namespace Caching**: Reduces overhead for hierarchical lookups.
-
-See `ll_bench_test.go` for detailed benchmarks on namespace creation, cloning, and field building.
-
-## Testing and Stability
-
-The `ll` library includes a comprehensive test suite (`ll_test.go`) covering:
-- Logger configuration, namespaces, and conditional logging.
-- Middleware, rate limiting, and sampling.
-- Handler output formats (text, JSON, slog).
-- Debugging utilities (`Dbg`, `Dump`, `Stack`).
-
-Recent improvements:
-- Fixed sampling middleware for reliable behavior at edge cases (0.0 and 1.0 rates).
-- Enhanced documentation across `conditional.go`, `field.go`, `global.go`, `ll.go`, `lx.go`, and `ns.go`.
-- Added `slog` compatibility via `lh.SlogHandler`.
-
-## Contributing
-
-Contributions are welcome! To contribute:
-1. Fork the repository: `github.com/olekukonko/ll`.
-2. Create a feature branch: `git checkout -b feature/your-feature`.
-3. Commit changes: `git commit -m "Add your feature"`.
-4. Push to the branch: `git push origin feature/your-feature`.
-5. Open a pull request with a clear description.
-
-Please include tests in `ll_test.go` and update documentation as needed. Follow the Go coding style and run `go test ./...` before submitting.
-
-## License
-
-`ll` is licensed under the MIT License. See [LICENSE](LICENSE) for details.
-
-## Resources
-
-- **Source Code**: [github.com/olekukonko/ll](https://github.com/olekukonko/ll)
-- **Issue Tracker**: [github.com/olekukonko/ll/issues](https://github.com/olekukonko/ll/issues)
-- **GoDoc**: [pkg.go.dev/github.com/olekukonko/ll](https://pkg.go.dev/github.com/olekukonko/ll)
\ No newline at end of file
diff --git a/vendor/github.com/olekukonko/ll/conditional.go b/vendor/github.com/olekukonko/ll/conditional.go
deleted file mode 100644
index 0ec9e4b842..0000000000
--- a/vendor/github.com/olekukonko/ll/conditional.go
+++ /dev/null
@@ -1,340 +0,0 @@
-package ll
-
-// Conditional enables conditional logging based on a boolean condition.
-// It wraps a logger with a condition that determines whether logging operations are executed,
-// optimizing performance by skipping expensive operations (e.g., field computation, message formatting)
-// when the condition is false. The struct supports fluent chaining for adding fields and logging.
-type Conditional struct {
- logger *Logger // Associated logger instance for logging operations
- condition bool // Whether logging is allowed (true to log, false to skip)
-}
-
-// If creates a conditional logger that logs only if the condition is true.
-// It returns a Conditional struct that wraps the logger, enabling conditional logging methods.
-// This method is typically called on a Logger instance to start a conditional chain.
-// Thread-safe via the underlying logger’s mutex.
-// Example:
-//
-// logger := New("app").Enable()
-// logger.If(true).Info("Logged") // Output: [app] INFO: Logged
-// logger.If(false).Info("Ignored") // No output
-func (l *Logger) If(condition bool) *Conditional {
- return &Conditional{logger: l, condition: condition}
-}
-
-// IfOne creates a conditional logger that logs only if all conditions are true.
-// It evaluates a variadic list of boolean conditions, setting the condition to true only if
-// all are true (logical AND). Returns a new Conditional with the result. Thread-safe via the
-// underlying logger.
-// Example:
-//
-// logger := New("app").Enable()
-// logger.IfOne(true, true).Info("Logged") // Output: [app] INFO: Logged
-// logger.IfOne(true, false).Info("Ignored") // No output
-func (cl *Conditional) IfOne(conditions ...bool) *Conditional {
- result := true
- // Check each condition; set result to false if any is false
- for _, cond := range conditions {
- if !cond {
- result = false
- break
- }
- }
- return &Conditional{logger: cl.logger, condition: result}
-}
-
-// IfAny creates a conditional logger that logs only if at least one condition is true.
-// It evaluates a variadic list of boolean conditions, setting the condition to true if any
-// is true (logical OR). Returns a new Conditional with the result. Thread-safe via the
-// underlying logger.
-// Example:
-//
-// logger := New("app").Enable()
-// logger.IfAny(false, true).Info("Logged") // Output: [app] INFO: Logged
-// logger.IfAny(false, false).Info("Ignored") // No output
-func (cl *Conditional) IfAny(conditions ...bool) *Conditional {
- result := false
- // Check each condition; set result to true if any is true
- for _, cond := range conditions {
- if cond {
- result = true
- break
- }
- }
- return &Conditional{logger: cl.logger, condition: result}
-}
-
-// Fields starts a fluent chain for adding fields using variadic key-value pairs, if the condition is true.
-// It returns a FieldBuilder to attach fields, skipping field processing if the condition is false
-// to optimize performance. Thread-safe via the FieldBuilder’s logger.
-// Example:
-//
-// logger := New("app").Enable()
-// logger.If(true).Fields("user", "alice").Info("Logged") // Output: [app] INFO: Logged [user=alice]
-// logger.If(false).Fields("user", "alice").Info("Ignored") // No output, no field processing
-func (cl *Conditional) Fields(pairs ...any) *FieldBuilder {
- // Skip field processing if condition is false
- if !cl.condition {
- return &FieldBuilder{logger: cl.logger, fields: nil}
- }
- // Delegate to logger’s Fields method
- return cl.logger.Fields(pairs...)
-}
-
-// Field starts a fluent chain for adding fields from a map, if the condition is true.
-// It returns a FieldBuilder to attach fields from a map, skipping processing if the condition
-// is false. Thread-safe via the FieldBuilder’s logger.
-// Example:
-//
-// logger := New("app").Enable()
-// logger.If(true).Field(map[string]interface{}{"user": "alice"}).Info("Logged") // Output: [app] INFO: Logged [user=alice]
-// logger.If(false).Field(map[string]interface{}{"user": "alice"}).Info("Ignored") // No output
-func (cl *Conditional) Field(fields map[string]interface{}) *FieldBuilder {
- // Skip field processing if condition is false
- if !cl.condition {
- return &FieldBuilder{logger: cl.logger, fields: nil}
- }
- // Delegate to logger’s Field method
- return cl.logger.Field(fields)
-}
-
-// Info logs a message at Info level with variadic arguments if the condition is true.
-// It concatenates the arguments with spaces and delegates to the logger’s Info method if the
-// condition is true. Skips processing if false, optimizing performance. Thread-safe via the
-// logger’s log method.
-// Example:
-//
-// logger := New("app").Enable()
-// logger.If(true).Info("Action", "started") // Output: [app] INFO: Action started
-// logger.If(false).Info("Action", "ignored") // No output
-func (cl *Conditional) Info(args ...any) {
- // Skip logging if condition is false
- if !cl.condition {
- return
- }
- // Delegate to logger’s Info method
- cl.logger.Info(args...)
-}
-
-// Infof logs a message at Info level with a format string if the condition is true.
-// It formats the message using the provided format string and arguments, delegating to the
-// logger’s Infof method if the condition is true. Skips processing if false, optimizing performance.
-// Thread-safe via the logger’s log method.
-// Example:
-//
-// logger := New("app").Enable()
-// logger.If(true).Infof("Action %s", "started") // Output: [app] INFO: Action started
-// logger.If(false).Infof("Action %s", "ignored") // No output
-func (cl *Conditional) Infof(format string, args ...any) {
- // Skip logging if condition is false
- if !cl.condition {
- return
- }
- // Delegate to logger’s Infof method
- cl.logger.Infof(format, args...)
-}
-
-// Debug logs a message at Debug level with variadic arguments if the condition is true.
-// It concatenates the arguments with spaces and delegates to the logger’s Debug method if the
-// condition is true. Skips processing if false. Thread-safe via the logger’s log method.
-// Example:
-//
-// logger := New("app").Enable().Level(lx.LevelDebug)
-// logger.If(true).Debug("Debugging", "mode") // Output: [app] DEBUG: Debugging mode
-// logger.If(false).Debug("Debugging", "ignored") // No output
-func (cl *Conditional) Debug(args ...any) {
- // Skip logging if condition is false
- if !cl.condition {
- return
- }
- // Delegate to logger’s Debug method
- cl.logger.Debug(args...)
-}
-
-// Debugf logs a message at Debug level with a format string if the condition is true.
-// It formats the message and delegates to the logger’s Debugf method if the condition is true.
-// Skips processing if false. Thread-safe via the logger’s log method.
-// Example:
-//
-// logger := New("app").Enable().Level(lx.LevelDebug)
-// logger.If(true).Debugf("Debug %s", "mode") // Output: [app] DEBUG: Debug mode
-// logger.If(false).Debugf("Debug %s", "ignored") // No output
-func (cl *Conditional) Debugf(format string, args ...any) {
- // Skip logging if condition is false
- if !cl.condition {
- return
- }
- // Delegate to logger’s Debugf method
- cl.logger.Debugf(format, args...)
-}
-
-// Warn logs a message at Warn level with variadic arguments if the condition is true.
-// It concatenates the arguments with spaces and delegates to the logger’s Warn method if the
-// condition is true. Skips processing if false. Thread-safe via the logger’s log method.
-// Example:
-//
-// logger := New("app").Enable()
-// logger.If(true).Warn("Warning", "issued") // Output: [app] WARN: Warning issued
-// logger.If(false).Warn("Warning", "ignored") // No output
-func (cl *Conditional) Warn(args ...any) {
- // Skip logging if condition is false
- if !cl.condition {
- return
- }
- // Delegate to logger’s Warn method
- cl.logger.Warn(args...)
-}
-
-// Warnf logs a message at Warn level with a format string if the condition is true.
-// It formats the message and delegates to the logger’s Warnf method if the condition is true.
-// Skips processing if false. Thread-safe via the logger’s log method.
-// Example:
-//
-// logger := New("app").Enable()
-// logger.If(true).Warnf("Warning %s", "issued") // Output: [app] WARN: Warning issued
-// logger.If(false).Warnf("Warning %s", "ignored") // No output
-func (cl *Conditional) Warnf(format string, args ...any) {
- // Skip logging if condition is false
- if !cl.condition {
- return
- }
- // Delegate to logger’s Warnf method
- cl.logger.Warnf(format, args...)
-}
-
-// Error logs a message at Error level with variadic arguments if the condition is true.
-// It concatenates the arguments with spaces and delegates to the logger’s Error method if the
-// condition is true. Skips processing if false. Thread-safe via the logger’s log method.
-// Example:
-//
-// logger := New("app").Enable()
-// logger.If(true).Error("Error", "occurred") // Output: [app] ERROR: Error occurred
-// logger.If(false).Error("Error", "ignored") // No output
-func (cl *Conditional) Error(args ...any) {
- // Skip logging if condition is false
- if !cl.condition {
- return
- }
- // Delegate to logger’s Error method
- cl.logger.Error(args...)
-}
-
-// Errorf logs a message at Error level with a format string if the condition is true.
-// It formats the message and delegates to the logger’s Errorf method if the condition is true.
-// Skips processing if false. Thread-safe via the logger’s log method.
-// Example:
-//
-// logger := New("app").Enable()
-// logger.If(true).Errorf("Error %s", "occurred") // Output: [app] ERROR: Error occurred
-// logger.If(false).Errorf("Error %s", "ignored") // No output
-func (cl *Conditional) Errorf(format string, args ...any) {
- // Skip logging if condition is false
- if !cl.condition {
- return
- }
- // Delegate to logger’s Errorf method
- cl.logger.Errorf(format, args...)
-}
-
-// Stack logs a message at Error level with a stack trace and variadic arguments if the condition is true.
-// It concatenates the arguments with spaces and delegates to the logger’s Stack method if the
-// condition is true. Skips processing if false. Thread-safe via the logger’s log method.
-// Example:
-//
-// logger := New("app").Enable()
-// logger.If(true).Stack("Critical", "error") // Output: [app] ERROR: Critical error [stack=...]
-// logger.If(false).Stack("Critical", "ignored") // No output
-func (cl *Conditional) Stack(args ...any) {
- // Skip logging if condition is false
- if !cl.condition {
- return
- }
- // Delegate to logger’s Stack method
- cl.logger.Stack(args...)
-}
-
-// Stackf logs a message at Error level with a stack trace and a format string if the condition is true.
-// It formats the message and delegates to the logger’s Stackf method if the condition is true.
-// Skips processing if false. Thread-safe via the logger’s log method.
-// Example:
-//
-// logger := New("app").Enable()
-// logger.If(true).Stackf("Critical %s", "error") // Output: [app] ERROR: Critical error [stack=...]
-// logger.If(false).Stackf("Critical %s", "ignored") // No output
-func (cl *Conditional) Stackf(format string, args ...any) {
- // Skip logging if condition is false
- if !cl.condition {
- return
- }
- // Delegate to logger’s Stackf method
- cl.logger.Stackf(format, args...)
-}
-
-// Fatal logs a message at Error level with a stack trace and variadic arguments if the condition is true,
-// then exits. It concatenates the arguments with spaces and delegates to the logger’s Fatal method
-// if the condition is true, terminating the program with exit code 1. Skips processing if false.
-// Thread-safe via the logger’s log method.
-// Example:
-//
-// logger := New("app").Enable()
-// logger.If(true).Fatal("Fatal", "error") // Output: [app] ERROR: Fatal error [stack=...], then exits
-// logger.If(false).Fatal("Fatal", "ignored") // No output, no exit
-func (cl *Conditional) Fatal(args ...any) {
- // Skip logging if condition is false
- if !cl.condition {
- return
- }
- // Delegate to logger’s Fatal method
- cl.logger.Fatal(args...)
-}
-
-// Fatalf logs a formatted message at Error level with a stack trace if the condition is true, then exits.
-// It formats the message and delegates to the logger’s Fatalf method if the condition is true,
-// terminating the program with exit code 1. Skips processing if false. Thread-safe via the logger’s log method.
-// Example:
-//
-// logger := New("app").Enable()
-// logger.If(true).Fatalf("Fatal %s", "error") // Output: [app] ERROR: Fatal error [stack=...], then exits
-// logger.If(false).Fatalf("Fatal %s", "ignored") // No output, no exit
-func (cl *Conditional) Fatalf(format string, args ...any) {
- // Skip logging if condition is false
- if !cl.condition {
- return
- }
- // Delegate to logger’s Fatalf method
- cl.logger.Fatalf(format, args...)
-}
-
-// Panic logs a message at Error level with a stack trace and variadic arguments if the condition is true,
-// then panics. It concatenates the arguments with spaces and delegates to the logger’s Panic method
-// if the condition is true, triggering a panic. Skips processing if false. Thread-safe via the logger’s log method.
-// Example:
-//
-// logger := New("app").Enable()
-// logger.If(true).Panic("Panic", "error") // Output: [app] ERROR: Panic error [stack=...], then panics
-// logger.If(false).Panic("Panic", "ignored") // No output, no panic
-func (cl *Conditional) Panic(args ...any) {
- // Skip logging if condition is false
- if !cl.condition {
- return
- }
- // Delegate to logger’s Panic method
- cl.logger.Panic(args...)
-}
-
-// Panicf logs a formatted message at Error level with a stack trace if the condition is true, then panics.
-// It formats the message and delegates to the logger’s Panicf method if the condition is true,
-// triggering a panic. Skips processing if false. Thread-safe via the logger’s log method.
-// Example:
-//
-// logger := New("app").Enable()
-// logger.If(true).Panicf("Panic %s", "error") // Output: [app] ERROR: Panic error [stack=...], then panics
-// logger.If(false).Panicf("Panic %s", "ignored") // No output, no panic
-func (cl *Conditional) Panicf(format string, args ...any) {
- // Skip logging if condition is false
- if !cl.condition {
- return
- }
- // Delegate to logger’s Panicf method
- cl.logger.Panicf(format, args...)
-}
diff --git a/vendor/github.com/olekukonko/ll/field.go b/vendor/github.com/olekukonko/ll/field.go
deleted file mode 100644
index 4162162ff7..0000000000
--- a/vendor/github.com/olekukonko/ll/field.go
+++ /dev/null
@@ -1,375 +0,0 @@
-package ll
-
-import (
- "fmt"
- "github.com/olekukonko/cat"
- "github.com/olekukonko/ll/lx"
- "os"
- "strings"
-)
-
-// FieldBuilder enables fluent addition of fields before logging.
-// It acts as a builder pattern to attach key-value pairs (fields) to log entries,
-// supporting structured logging with metadata. The builder allows chaining to add fields
-// and log messages at various levels (Info, Debug, Warn, Error, etc.) in a single expression.
-type FieldBuilder struct {
- logger *Logger // Associated logger instance for logging operations
- fields map[string]interface{} // Fields to include in the log entry as key-value pairs
-}
-
-// Logger creates a new logger with the builder’s fields embedded in its context.
-// It clones the parent logger and copies the builder’s fields into the new logger’s context,
-// enabling persistent field inclusion in subsequent logs. This method supports fluent chaining
-// after Fields or Field calls.
-// Example:
-//
-// logger := New("app").Enable()
-// newLogger := logger.Fields("user", "alice").Logger()
-// newLogger.Info("Action") // Output: [app] INFO: Action [user=alice]
-func (fb *FieldBuilder) Logger() *Logger {
- // Clone the parent logger to preserve its configuration
- newLogger := fb.logger.Clone()
- // Initialize a new context map to avoid modifying the parent’s context
- newLogger.context = make(map[string]interface{})
- // Copy builder’s fields into the new logger’s context
- for k, v := range fb.fields {
- newLogger.context[k] = v
- }
- return newLogger
-}
-
-// Info logs a message at Info level with the builder’s fields.
-// It concatenates the arguments with spaces and delegates to the logger’s log method,
-// returning early if fields are nil. This method is used for informational messages.
-// Example:
-//
-// logger := New("app").Enable()
-// logger.Fields("user", "alice").Info("Action", "started") // Output: [app] INFO: Action started [user=alice]
-func (fb *FieldBuilder) Info(args ...any) {
- // Skip logging if fields are nil
- if fb.fields == nil {
- return
- }
- // Log at Info level with the builder’s fields, no stack trace
- fb.logger.log(lx.LevelInfo, lx.ClassText, cat.Space(args...), fb.fields, false)
-}
-
-// Infof logs a message at Info level with the builder’s fields.
-// It formats the message using the provided format string and arguments, then delegates
-// to the logger’s internal log method. If fields are nil, it returns early to avoid logging.
-// This method is part of the fluent API, typically called after adding fields.
-// Example:
-//
-// logger := New("app").Enable()
-// logger.Fields("user", "alice").Infof("Action %s", "started") // Output: [app] INFO: Action started [user=alice]
-func (fb *FieldBuilder) Infof(format string, args ...any) {
- // Skip logging if fields are nil to prevent invalid log entries
- if fb.fields == nil {
- return
- }
- // Format the message using the provided arguments
- msg := fmt.Sprintf(format, args...)
- // Log at Info level with the builder’s fields, no stack trace
- fb.logger.log(lx.LevelInfo, lx.ClassText, msg, fb.fields, false)
-}
-
-// Debug logs a message at Debug level with the builder’s fields.
-// It concatenates the arguments with spaces and delegates to the logger’s log method,
-// returning early if fields are nil. This method is used for debugging information.
-// Example:
-//
-// logger := New("app").Enable()
-// logger.Fields("user", "alice").Debug("Debugging", "mode") // Output: [app] DEBUG: Debugging mode [user=alice]
-func (fb *FieldBuilder) Debug(args ...any) {
- // Skip logging if fields are nil
- if fb.fields == nil {
- return
- }
- // Log at Debug level with the builder’s fields, no stack trace
- fb.logger.log(lx.LevelDebug, lx.ClassText, cat.Space(args...), fb.fields, false)
-}
-
-// Debugf logs a message at Debug level with the builder’s fields.
-// It formats the message and delegates to the logger’s log method, returning early if
-// fields are nil. This method is used for debugging information that may be disabled in
-// production environments.
-// Example:
-//
-// logger := New("app").Enable()
-// logger.Fields("user", "alice").Debugf("Debug %s", "mode") // Output: [app] DEBUG: Debug mode [user=alice]
-func (fb *FieldBuilder) Debugf(format string, args ...any) {
- // Skip logging if fields are nil
- if fb.fields == nil {
- return
- }
- // Format the message
- msg := fmt.Sprintf(format, args...)
- // Log at Debug level with the builder’s fields, no stack trace
- fb.logger.log(lx.LevelDebug, lx.ClassText, msg, fb.fields, false)
-}
-
-// Warn logs a message at Warn level with the builder’s fields.
-// It concatenates the arguments with spaces and delegates to the logger’s log method,
-// returning early if fields are nil. This method is used for warning conditions.
-// Example:
-//
-// logger := New("app").Enable()
-// logger.Fields("user", "alice").Warn("Warning", "issued") // Output: [app] WARN: Warning issued [user=alice]
-func (fb *FieldBuilder) Warn(args ...any) {
- // Skip logging if fields are nil
- if fb.fields == nil {
- return
- }
- // Log at Warn level with the builder’s fields, no stack trace
- fb.logger.log(lx.LevelWarn, lx.ClassText, cat.Space(args...), fb.fields, false)
-}
-
-// Warnf logs a message at Warn level with the builder’s fields.
-// It formats the message and delegates to the logger’s log method, returning early if
-// fields are nil. This method is used for warning conditions that do not halt execution.
-// Example:
-//
-// logger := New("app").Enable()
-// logger.Fields("user", "alice").Warnf("Warning %s", "issued") // Output: [app] WARN: Warning issued [user=alice]
-func (fb *FieldBuilder) Warnf(format string, args ...any) {
- // Skip logging if fields are nil
- if fb.fields == nil {
- return
- }
- // Format the message
- msg := fmt.Sprintf(format, args...)
- // Log at Warn level with the builder’s fields, no stack trace
- fb.logger.log(lx.LevelWarn, lx.ClassText, msg, fb.fields, false)
-}
-
-// Error logs a message at Error level with the builder’s fields.
-// It concatenates the arguments with spaces and delegates to the logger’s log method,
-// returning early if fields are nil. This method is used for error conditions.
-// Example:
-//
-// logger := New("app").Enable()
-// logger.Fields("user", "alice").Error("Error", "occurred") // Output: [app] ERROR: Error occurred [user=alice]
-func (fb *FieldBuilder) Error(args ...any) {
- // Skip logging if fields are nil
- if fb.fields == nil {
- return
- }
- // Log at Error level with the builder’s fields, no stack trace
- fb.logger.log(lx.LevelError, lx.ClassText, cat.Space(args...), fb.fields, false)
-}
-
-// Errorf logs a message at Error level with the builder’s fields.
-// It formats the message and delegates to the logger’s log method, returning early if
-// fields are nil. This method is used for error conditions that may require attention.
-// Example:
-//
-// logger := New("app").Enable()
-// logger.Fields("user", "alice").Errorf("Error %s", "occurred") // Output: [app] ERROR: Error occurred [user=alice]
-func (fb *FieldBuilder) Errorf(format string, args ...any) {
- // Skip logging if fields are nil
- if fb.fields == nil {
- return
- }
- // Format the message
- msg := fmt.Sprintf(format, args...)
- // Log at Error level with the builder’s fields, no stack trace
- fb.logger.log(lx.LevelError, lx.ClassText, msg, fb.fields, false)
-}
-
-// Stack logs a message at Error level with a stack trace and the builder’s fields.
-// It concatenates the arguments with spaces and delegates to the logger’s log method,
-// returning early if fields are nil. This method is useful for debugging critical errors.
-// Example:
-//
-// logger := New("app").Enable()
-// logger.Fields("user", "alice").Stack("Critical", "error") // Output: [app] ERROR: Critical error [user=alice stack=...]
-func (fb *FieldBuilder) Stack(args ...any) {
- // Skip logging if fields are nil
- if fb.fields == nil {
- return
- }
- // Log at Error level with the builder’s fields and a stack trace
- fb.logger.log(lx.LevelError, lx.ClassText, cat.Space(args...), fb.fields, true)
-}
-
-// Stackf logs a message at Error level with a stack trace and the builder’s fields.
-// It formats the message and delegates to the logger’s log method, returning early if
-// fields are nil. This method is useful for debugging critical errors.
-// Example:
-//
-// logger := New("app").Enable()
-// logger.Fields("user", "alice").Stackf("Critical %s", "error") // Output: [app] ERROR: Critical error [user=alice stack=...]
-func (fb *FieldBuilder) Stackf(format string, args ...any) {
- // Skip logging if fields are nil
- if fb.fields == nil {
- return
- }
- // Format the message
- msg := fmt.Sprintf(format, args...)
- // Log at Error level with the builder’s fields and a stack trace
- fb.logger.log(lx.LevelError, lx.ClassText, msg, fb.fields, true)
-}
-
-// Fatal logs a message at Error level with a stack trace and the builder’s fields, then exits.
-// It constructs the message from variadic arguments, logs it with a stack trace, and terminates
-// the program with exit code 1. Returns early if fields are nil. This method is used for
-// unrecoverable errors.
-// Example:
-//
-// logger := New("app").Enable()
-// logger.Fields("user", "alice").Fatal("Fatal", "error") // Output: [app] ERROR: Fatal error [user=alice stack=...], then exits
-func (fb *FieldBuilder) Fatal(args ...any) {
- // Skip logging if fields are nil
- if fb.fields == nil {
- return
- }
- // Build the message by concatenating arguments with spaces
- var builder strings.Builder
- for i, arg := range args {
- if i > 0 {
- builder.WriteString(lx.Space)
- }
- builder.WriteString(fmt.Sprint(arg))
- }
- // Log at Error level with the builder’s fields and a stack trace
- fb.logger.log(lx.LevelError, lx.ClassText, builder.String(), fb.fields, true)
- // Exit the program with status code 1
- os.Exit(1)
-}
-
-// Fatalf logs a formatted message at Error level with a stack trace and the builder’s fields,
-// then exits. It delegates to Fatal and returns early if fields are nil. This method is used
-// for unrecoverable errors.
-// Example:
-//
-// logger := New("app").Enable()
-// logger.Fields("user", "alice").Fatalf("Fatal %s", "error") // Output: [app] ERROR: Fatal error [user=alice stack=...], then exits
-func (fb *FieldBuilder) Fatalf(format string, args ...any) {
- // Skip logging if fields are nil
- if fb.fields == nil {
- return
- }
- // Format the message and pass to Fatal
- fb.Fatal(fmt.Sprintf(format, args...))
-}
-
-// Panic logs a message at Error level with a stack trace and the builder’s fields, then panics.
-// It constructs the message from variadic arguments, logs it with a stack trace, and triggers
-// a panic with the message. Returns early if fields are nil. This method is used for critical
-// errors that require immediate program termination with a panic.
-// Example:
-//
-// logger := New("app").Enable()
-// logger.Fields("user", "alice").Panic("Panic", "error") // Output: [app] ERROR: Panic error [user=alice stack=...], then panics
-func (fb *FieldBuilder) Panic(args ...any) {
- // Skip logging if fields are nil
- if fb.fields == nil {
- return
- }
- // Build the message by concatenating arguments with spaces
- var builder strings.Builder
- for i, arg := range args {
- if i > 0 {
- builder.WriteString(lx.Space)
- }
- builder.WriteString(fmt.Sprint(arg))
- }
- msg := builder.String()
- // Log at Error level with the builder’s fields and a stack trace
- fb.logger.log(lx.LevelError, lx.ClassText, msg, fb.fields, true)
- // Trigger a panic with the formatted message
- panic(msg)
-}
-
-// Panicf logs a formatted message at Error level with a stack trace and the builder’s fields,
-// then panics. It delegates to Panic and returns early if fields are nil. This method is used
-// for critical errors that require immediate program termination with a panic.
-// Example:
-//
-// logger := New("app").Enable()
-// logger.Fields("user", "alice").Panicf("Panic %s", "error") // Output: [app] ERROR: Panic error [user=alice stack=...], then panics
-func (fb *FieldBuilder) Panicf(format string, args ...any) {
- // Skip logging if fields are nil
- if fb.fields == nil {
- return
- }
- // Format the message and pass to Panic
- fb.Panic(fmt.Sprintf(format, args...))
-}
-
-// Err adds one or more errors to the FieldBuilder as a field and logs them.
-// It stores non-nil errors in the "error" field: a single error if only one is non-nil,
-// or a slice of errors if multiple are non-nil. It logs the concatenated string representations
-// of non-nil errors (e.g., "failed 1; failed 2") at the Error level. Returns the FieldBuilder
-// for chaining, allowing further field additions or logging. Thread-safe via the logger’s mutex.
-// Example:
-//
-// logger := New("app").Enable()
-// err1 := errors.New("failed 1")
-// err2 := errors.New("failed 2")
-// logger.Fields("k", "v").Err(err1, err2).Info("Error occurred")
-// // Output: [app] ERROR: failed 1; failed 2
-// // [app] INFO: Error occurred [error=[failed 1 failed 2] k=v]
-func (fb *FieldBuilder) Err(errs ...error) *FieldBuilder {
- // Initialize fields map if nil
- if fb.fields == nil {
- fb.fields = make(map[string]interface{})
- }
-
- // Collect non-nil errors and build log message
- var nonNilErrors []error
- var builder strings.Builder
- count := 0
- for i, err := range errs {
- if err != nil {
- if i > 0 && count > 0 {
- builder.WriteString("; ")
- }
- builder.WriteString(err.Error())
- nonNilErrors = append(nonNilErrors, err)
- count++
- }
- }
-
- // Set error field and log if there are non-nil errors
- if count > 0 {
- if count == 1 {
- // Store single error directly
- fb.fields["error"] = nonNilErrors[0]
- } else {
- // Store slice of errors
- fb.fields["error"] = nonNilErrors
- }
- // Log concatenated error messages at Error level
- fb.logger.log(lx.LevelError, lx.ClassText, builder.String(), nil, false)
- }
-
- // Return FieldBuilder for chaining
- return fb
-}
-
-// Merge adds additional key-value pairs to the FieldBuilder.
-// It processes variadic arguments as key-value pairs, expecting string keys. Non-string keys
-// or uneven pairs generate an "error" field with a descriptive message. Returns the FieldBuilder
-// for chaining to allow further field additions or logging.
-// Example:
-//
-// logger := New("app").Enable()
-// logger.Fields("k1", "v1").Merge("k2", "v2").Info("Action") // Output: [app] INFO: Action [k1=v1 k2=v2]
-func (fb *FieldBuilder) Merge(pairs ...any) *FieldBuilder {
- // Process pairs as key-value, advancing by 2
- for i := 0; i < len(pairs)-1; i += 2 {
- // Ensure the key is a string
- if key, ok := pairs[i].(string); ok {
- fb.fields[key] = pairs[i+1]
- } else {
- // Log an error field for non-string keys
- fb.fields["error"] = fmt.Errorf("non-string key in Merge: %v", pairs[i])
- }
- }
- // Check for uneven pairs (missing value)
- if len(pairs)%2 != 0 {
- fb.fields["error"] = fmt.Errorf("uneven key-value pairs in Merge: [%v]", pairs[len(pairs)-1])
- }
- return fb
-}
diff --git a/vendor/github.com/olekukonko/ll/global.go b/vendor/github.com/olekukonko/ll/global.go
deleted file mode 100644
index f4a8489d03..0000000000
--- a/vendor/github.com/olekukonko/ll/global.go
+++ /dev/null
@@ -1,662 +0,0 @@
-package ll
-
-import (
- "os"
- "sync/atomic"
- "time"
-
- "github.com/olekukonko/ll/lh"
- "github.com/olekukonko/ll/lx"
-)
-
-// defaultLogger is the global logger instance for package-level logging functions.
-// It provides a shared logger for convenience, allowing logging without explicitly creating
-// a logger instance. The logger is initialized with default settings: enabled, Debug level,
-// flat namespace style, and a text handler to os.Stdout. It is thread-safe due to the Logger
-// struct’s mutex.
-var defaultLogger = &Logger{
- enabled: true, // Initially enabled
- level: lx.LevelDebug, // Minimum log level set to Debug
- namespaces: defaultStore, // Shared namespace store for enable/disable states
- context: make(map[string]interface{}), // Empty context for global fields
- style: lx.FlatPath, // Flat namespace style (e.g., [parent/child])
- handler: lh.NewTextHandler(os.Stdout), // Default text handler to os.Stdout
- middleware: make([]Middleware, 0), // Empty middleware chain
- stackBufferSize: 4096, // Buffer size for stack traces
-}
-
-// Handler sets the handler for the default logger.
-// It configures the output destination and format (e.g., text, JSON) for logs emitted by
-// defaultLogger. Returns the default logger for method chaining, enabling fluent configuration.
-// Example:
-//
-// ll.Handler(lh.NewJSONHandler(os.Stdout)).Enable()
-// ll.Info("Started") // Output: {"level":"INFO","message":"Started"}
-func Handler(handler lx.Handler) *Logger {
- return defaultLogger.Handler(handler)
-}
-
-// Level sets the minimum log level for the default logger.
-// It determines which log messages (Debug, Info, Warn, Error) are emitted. Messages below
-// the specified level are ignored. Returns the default logger for method chaining.
-// Example:
-//
-// ll.Level(lx.LevelWarn)
-// ll.Info("Ignored") // No output
-// ll.Warn("Logged") // Output: [] WARN: Logged
-func Level(level lx.LevelType) *Logger {
- return defaultLogger.Level(level)
-}
-
-// Style sets the namespace style for the default logger.
-// It controls how namespace paths are formatted in logs (FlatPath: [parent/child],
-// NestedPath: [parent]→[child]). Returns the default logger for method chaining.
-// Example:
-//
-// ll.Style(lx.NestedPath)
-// ll.Info("Test") // Output: []: INFO: Test
-func Style(style lx.StyleType) *Logger {
- return defaultLogger.Style(style)
-}
-
-// NamespaceEnable enables logging for a namespace and its children using the default logger.
-// It activates logging for the specified namespace path (e.g., "app/db") and all its
-// descendants. Returns the default logger for method chaining. Thread-safe via the Logger’s mutex.
-// Example:
-//
-// ll.NamespaceEnable("app/db")
-// ll.Clone().Namespace("db").Info("Query") // Output: [app/db] INFO: Query
-func NamespaceEnable(path string) *Logger {
- return defaultLogger.NamespaceEnable(path)
-}
-
-// NamespaceDisable disables logging for a namespace and its children using the default logger.
-// It suppresses logging for the specified namespace path and all its descendants. Returns
-// the default logger for method chaining. Thread-safe via the Logger’s mutex.
-// Example:
-//
-// ll.NamespaceDisable("app/db")
-// ll.Clone().Namespace("db").Info("Query") // No output
-func NamespaceDisable(path string) *Logger {
- return defaultLogger.NamespaceDisable(path)
-}
-
-// Namespace creates a child logger with a sub-namespace appended to the current path.
-// The child inherits the default logger’s configuration but has an independent context.
-// Thread-safe with read lock. Returns the new logger for further configuration or logging.
-// Example:
-//
-// logger := ll.Namespace("app")
-// logger.Info("Started") // Output: [app] INFO: Started
-func Namespace(name string) *Logger {
- return defaultLogger.Namespace(name)
-}
-
-// Info logs a message at Info level with variadic arguments using the default logger.
-// It concatenates the arguments with spaces and delegates to defaultLogger’s Info method.
-// Thread-safe via the Logger’s log method.
-// Example:
-//
-// ll.Info("Service", "started") // Output: [] INFO: Service started
-func Info(args ...any) {
- defaultLogger.Info(args...)
-}
-
-// Infof logs a message at Info level with a format string using the default logger.
-// It formats the message using the provided format string and arguments, then delegates to
-// defaultLogger’s Infof method. Thread-safe via the Logger’s log method.
-// Example:
-//
-// ll.Infof("Service %s", "started") // Output: [] INFO: Service started
-func Infof(format string, args ...any) {
- defaultLogger.Infof(format, args...)
-}
-
-// Debug logs a message at Debug level with variadic arguments using the default logger.
-// It concatenates the arguments with spaces and delegates to defaultLogger’s Debug method.
-// Used for debugging information, typically disabled in production. Thread-safe.
-// Example:
-//
-// ll.Level(lx.LevelDebug)
-// ll.Debug("Debugging", "mode") // Output: [] DEBUG: Debugging mode
-func Debug(args ...any) {
- defaultLogger.Debug(args...)
-}
-
-// Debugf logs a message at Debug level with a format string using the default logger.
-// It formats the message and delegates to defaultLogger’s Debugf method. Used for debugging
-// information, typically disabled in production. Thread-safe.
-// Example:
-//
-// ll.Level(lx.LevelDebug)
-// ll.Debugf("Debug %s", "mode") // Output: [] DEBUG: Debug mode
-func Debugf(format string, args ...any) {
- defaultLogger.Debugf(format, args...)
-}
-
-// Warn logs a message at Warn level with variadic arguments using the default logger.
-// It concatenates the arguments with spaces and delegates to defaultLogger’s Warn method.
-// Used for warning conditions that do not halt execution. Thread-safe.
-// Example:
-//
-// ll.Warn("Low", "memory") // Output: [] WARN: Low memory
-func Warn(args ...any) {
- defaultLogger.Warn(args...)
-}
-
-// Warnf logs a message at Warn level with a format string using the default logger.
-// It formats the message and delegates to defaultLogger’s Warnf method. Used for warning
-// conditions that do not halt execution. Thread-safe.
-// Example:
-//
-// ll.Warnf("Low %s", "memory") // Output: [] WARN: Low memory
-func Warnf(format string, args ...any) {
- defaultLogger.Warnf(format, args...)
-}
-
-// Error logs a message at Error level with variadic arguments using the default logger.
-// It concatenates the arguments with spaces and delegates to defaultLogger’s Error method.
-// Used for error conditions requiring attention. Thread-safe.
-// Example:
-//
-// ll.Error("Database", "failure") // Output: [] ERROR: Database failure
-func Error(args ...any) {
- defaultLogger.Error(args...)
-}
-
-// Errorf logs a message at Error level with a format string using the default logger.
-// It formats the message and delegates to defaultLogger’s Errorf method. Used for error
-// conditions requiring attention. Thread-safe.
-// Example:
-//
-// ll.Errorf("Database %s", "failure") // Output: [] ERROR: Database failure
-func Errorf(format string, args ...any) {
- defaultLogger.Errorf(format, args...)
-}
-
-// Stack logs a message at Error level with a stack trace and variadic arguments using the default logger.
-// It concatenates the arguments with spaces and delegates to defaultLogger’s Stack method.
-// Thread-safe.
-// Example:
-//
-// ll.Stack("Critical", "error") // Output: [] ERROR: Critical error [stack=...]
-func Stack(args ...any) {
- defaultLogger.Stack(args...)
-}
-
-// Stackf logs a message at Error level with a stack trace and a format string using the default logger.
-// It formats the message and delegates to defaultLogger’s Stackf method. Thread-safe.
-// Example:
-//
-// ll.Stackf("Critical %s", "error") // Output: [] ERROR: Critical error [stack=...]
-func Stackf(format string, args ...any) {
- defaultLogger.Stackf(format, args...)
-}
-
-// Fatal logs a message at Error level with a stack trace and variadic arguments using the default logger,
-// then exits. It concatenates the arguments with spaces, logs with a stack trace, and terminates
-// with exit code 1. Thread-safe.
-// Example:
-//
-// ll.Fatal("Fatal", "error") // Output: [] ERROR: Fatal error [stack=...], then exits
-func Fatal(args ...any) {
- defaultLogger.Fatal(args...)
-}
-
-// Fatalf logs a formatted message at Error level with a stack trace using the default logger,
-// then exits. It formats the message, logs with a stack trace, and terminates with exit code 1.
-// Thread-safe.
-// Example:
-//
-// ll.Fatalf("Fatal %s", "error") // Output: [] ERROR: Fatal error [stack=...], then exits
-func Fatalf(format string, args ...any) {
- defaultLogger.Fatalf(format, args...)
-}
-
-// Panic logs a message at Error level with a stack trace and variadic arguments using the default logger,
-// then panics. It concatenates the arguments with spaces, logs with a stack trace, and triggers a panic.
-// Thread-safe.
-// Example:
-//
-// ll.Panic("Panic", "error") // Output: [] ERROR: Panic error [stack=...], then panics
-func Panic(args ...any) {
- defaultLogger.Panic(args...)
-}
-
-// Panicf logs a formatted message at Error level with a stack trace using the default logger,
-// then panics. It formats the message, logs with a stack trace, and triggers a panic. Thread-safe.
-// Example:
-//
-// ll.Panicf("Panic %s", "error") // Output: [] ERROR: Panic error [stack=...], then panics
-func Panicf(format string, args ...any) {
- defaultLogger.Panicf(format, args...)
-}
-
-// If creates a conditional logger that logs only if the condition is true using the default logger.
-// It returns a Conditional struct that wraps the default logger, enabling conditional logging methods.
-// Thread-safe via the Logger’s mutex.
-// Example:
-//
-// ll.If(true).Info("Logged") // Output: [] INFO: Logged
-// ll.If(false).Info("Ignored") // No output
-func If(condition bool) *Conditional {
- return defaultLogger.If(condition)
-}
-
-// Context creates a new logger with additional contextual fields using the default logger.
-// It preserves existing context fields and adds new ones, returning a new logger instance
-// to avoid mutating the default logger. Thread-safe with write lock.
-// Example:
-//
-// logger := ll.Context(map[string]interface{}{"user": "alice"})
-// logger.Info("Action") // Output: [] INFO: Action [user=alice]
-func Context(fields map[string]interface{}) *Logger {
- return defaultLogger.Context(fields)
-}
-
-// AddContext adds a key-value pair to the default logger’s context, modifying it directly.
-// It mutates the default logger’s context and is thread-safe using a write lock.
-// Example:
-//
-// ll.AddContext("user", "alice")
-// ll.Info("Action") // Output: [] INFO: Action [user=alice]
-func AddContext(key string, value interface{}) *Logger {
- return defaultLogger.AddContext(key, value)
-}
-
-// GetContext returns the default logger’s context map of persistent key-value fields.
-// It provides thread-safe read access to the context using a read lock.
-// Example:
-//
-// ll.AddContext("user", "alice")
-// ctx := ll.GetContext() // Returns map[string]interface{}{"user": "alice"}
-func GetContext() map[string]interface{} {
- return defaultLogger.GetContext()
-}
-
-// GetLevel returns the minimum log level for the default logger.
-// It provides thread-safe read access to the level field using a read lock.
-// Example:
-//
-// ll.Level(lx.LevelWarn)
-// if ll.GetLevel() == lx.LevelWarn {
-// ll.Warn("Warning level set") // Output: [] WARN: Warning level set
-// }
-func GetLevel() lx.LevelType {
- return defaultLogger.GetLevel()
-}
-
-// GetPath returns the default logger’s current namespace path.
-// It provides thread-safe read access to the currentPath field using a read lock.
-// Example:
-//
-// logger := ll.Namespace("app")
-// path := logger.GetPath() // Returns "app"
-func GetPath() string {
- return defaultLogger.GetPath()
-}
-
-// GetSeparator returns the default logger’s namespace separator (e.g., "/").
-// It provides thread-safe read access to the separator field using a read lock.
-// Example:
-//
-// ll.Separator(".")
-// sep := ll.GetSeparator() // Returns "."
-func GetSeparator() string {
- return defaultLogger.GetSeparator()
-}
-
-// GetStyle returns the default logger’s namespace formatting style (FlatPath or NestedPath).
-// It provides thread-safe read access to the style field using a read lock.
-// Example:
-//
-// ll.Style(lx.NestedPath)
-// if ll.GetStyle() == lx.NestedPath {
-// ll.Info("Nested style") // Output: []: INFO: Nested style
-// }
-func GetStyle() lx.StyleType {
- return defaultLogger.GetStyle()
-}
-
-// GetHandler returns the default logger’s current handler for customization or inspection.
-// The returned handler should not be modified concurrently with logger operations.
-// Example:
-//
-// handler := ll.GetHandler() // Returns the current handler (e.g., TextHandler)
-func GetHandler() lx.Handler {
- return defaultLogger.GetHandler()
-}
-
-// Separator sets the namespace separator for the default logger (e.g., "/" or ".").
-// It updates the separator used in namespace paths. Thread-safe with write lock.
-// Returns the default logger for method chaining.
-// Example:
-//
-// ll.Separator(".")
-// ll.Namespace("app").Info("Log") // Output: [app] INFO: Log
-func Separator(separator string) *Logger {
- return defaultLogger.Separator(separator)
-}
-
-// Prefix sets a prefix to be prepended to all log messages of the default logger.
-// The prefix is applied before the message in the log output. Thread-safe with write lock.
-// Returns the default logger for method chaining.
-// Example:
-//
-// ll.Prefix("APP: ")
-// ll.Info("Started") // Output: [] INFO: APP: Started
-func Prefix(prefix string) *Logger {
- return defaultLogger.Prefix(prefix)
-}
-
-// StackSize sets the buffer size for stack trace capture in the default logger.
-// It configures the maximum size for stack traces in Stack, Fatal, and Panic methods.
-// Thread-safe with write lock. Returns the default logger for chaining.
-// Example:
-//
-// ll.StackSize(65536)
-// ll.Stack("Error") // Captures up to 64KB stack trace
-func StackSize(size int) *Logger {
- return defaultLogger.StackSize(size)
-}
-
-// Use adds a middleware function to process log entries before they are handled by the default logger.
-// It registers the middleware and returns a Middleware handle for removal. Middleware returning
-// a non-nil error stops the log. Thread-safe with write lock.
-// Example:
-//
-// mw := ll.Use(ll.FuncMiddleware(func(e *lx.Entry) error {
-// if e.Level < lx.LevelWarn {
-// return fmt.Errorf("level too low")
-// }
-// return nil
-// }))
-// ll.Info("Ignored") // No output
-// mw.Remove()
-// ll.Info("Logged") // Output: [] INFO: Logged
-func Use(fn lx.Handler) *Middleware {
- return defaultLogger.Use(fn)
-}
-
-// Remove removes middleware by the reference returned from Use for the default logger.
-// It delegates to the Middleware’s Remove method for thread-safe removal.
-// Example:
-//
-// mw := ll.Use(someMiddleware)
-// ll.Remove(mw) // Removes middleware
-func Remove(m *Middleware) {
- defaultLogger.Remove(m)
-}
-
-// Clear removes all middleware functions from the default logger.
-// It resets the middleware chain to empty, ensuring no middleware is applied.
-// Thread-safe with write lock. Returns the default logger for chaining.
-// Example:
-//
-// ll.Use(someMiddleware)
-// ll.Clear()
-// ll.Info("No middleware") // Output: [] INFO: No middleware
-func Clear() *Logger {
- return defaultLogger.Clear()
-}
-
-// CanLog checks if a log at the given level would be emitted by the default logger.
-// It considers enablement, log level, namespaces, sampling, and rate limits.
-// Thread-safe via the Logger’s shouldLog method.
-// Example:
-//
-// ll.Level(lx.LevelWarn)
-// canLog := ll.CanLog(lx.LevelInfo) // false
-func CanLog(level lx.LevelType) bool {
- return defaultLogger.CanLog(level)
-}
-
-// NamespaceEnabled checks if a namespace is enabled in the default logger.
-// It evaluates the namespace hierarchy, considering parent namespaces, and caches the result
-// for performance. Thread-safe with read lock.
-// Example:
-//
-// ll.NamespaceDisable("app/db")
-// enabled := ll.NamespaceEnabled("app/db") // false
-func NamespaceEnabled(path string) bool {
- return defaultLogger.NamespaceEnabled(path)
-}
-
-// Print logs a message at Info level without format specifiers using the default logger.
-// It concatenates variadic arguments with spaces, minimizing allocations, and delegates
-// to defaultLogger’s Print method. Thread-safe via the Logger’s log method.
-// Example:
-//
-// ll.Print("message", "value") // Output: [] INFO: message value
-func Print(args ...any) {
- defaultLogger.Print(args...)
-}
-
-// Println logs a message at Info level without format specifiers, minimizing allocations
-// by concatenating arguments with spaces. It is thread-safe via the log method.
-// Example:
-//
-// ll.Println("message", "value") // Output: [] INFO: message value [New Line]
-func Println(args ...any) {
- defaultLogger.Println(args...)
-}
-
-// Printf logs a message at Info level with a format string using the default logger.
-// It formats the message and delegates to defaultLogger’s Printf method. Thread-safe via
-// the Logger’s log method.
-// Example:
-//
-// ll.Printf("Message %s", "value") // Output: [] INFO: Message value
-func Printf(format string, args ...any) {
- defaultLogger.Printf(format, args...)
-}
-
-// Len returns the total number of log entries sent to the handler by the default logger.
-// It provides thread-safe access to the entries counter using atomic operations.
-// Example:
-//
-// ll.Info("Test")
-// count := ll.Len() // Returns 1
-func Len() int64 {
- return defaultLogger.Len()
-}
-
-// Measure is a benchmarking helper that measures and returns the duration of a function’s execution.
-// It logs the duration at Info level with a "duration" field using defaultLogger. The function
-// is executed once, and the elapsed time is returned. Thread-safe via the Logger’s mutex.
-// Example:
-//
-// duration := ll.Measure(func() { time.Sleep(time.Millisecond) })
-// // Output: [] INFO: function executed [duration=~1ms]
-func Measure(fns ...func()) time.Duration {
- return defaultLogger.Measure(fns...)
-}
-
-// Benchmark logs the duration since a start time at Info level using the default logger.
-// It calculates the time elapsed since the provided start time and logs it with "start",
-// "end", and "duration" fields. Thread-safe via the Logger’s mutex.
-// Example:
-//
-// start := time.Now()
-// time.Sleep(time.Millisecond)
-// ll.Benchmark(start) // Output: [] INFO: benchmark [start=... end=... duration=...]
-func Benchmark(start time.Time) {
- defaultLogger.Benchmark(start)
-}
-
-// Clone returns a new logger with the same configuration as the default logger.
-// It creates a copy of defaultLogger’s settings (level, style, namespaces, etc.) but with
-// an independent context, allowing customization without affecting the global logger.
-// Thread-safe via the Logger’s Clone method.
-// Example:
-//
-// logger := ll.Clone().Namespace("sub")
-// logger.Info("Sub-logger") // Output: [sub] INFO: Sub-logger
-func Clone() *Logger {
- return defaultLogger.Clone()
-}
-
-// Err adds one or more errors to the default logger’s context and logs them.
-// It stores non-nil errors in the "error" context field and logs their concatenated string
-// representations (e.g., "failed 1; failed 2") at the Error level. Thread-safe via the Logger’s mutex.
-// Example:
-//
-// err1 := errors.New("failed 1")
-// ll.Err(err1)
-// ll.Info("Error occurred") // Output: [] ERROR: failed 1
-// // [] INFO: Error occurred [error=failed 1]
-func Err(errs ...error) {
- defaultLogger.Err(errs...)
-}
-
-// Start activates the global logging system.
-// If the system was shut down, this re-enables all logging operations,
-// subject to individual logger and namespace configurations.
-// Thread-safe via atomic operations.
-// Example:
-//
-// ll.Shutdown()
-// ll.Info("Ignored") // No output
-// ll.Start()
-// ll.Info("Logged") // Output: [] INFO: Logged
-func Start() {
- atomic.StoreInt32(&systemActive, 1)
-}
-
-// Shutdown deactivates the global logging system.
-// All logging operations are skipped, regardless of individual logger or namespace configurations,
-// until Start() is called again. Thread-safe via atomic operations.
-// Example:
-//
-// ll.Shutdown()
-// ll.Info("Ignored") // No output
-func Shutdown() {
- atomic.StoreInt32(&systemActive, 0)
-}
-
-// Active returns true if the global logging system is currently active.
-// Thread-safe via atomic operations.
-// Example:
-//
-// if ll.Active() {
-// ll.Info("System active") // Output: [] INFO: System active
-// }
-func Active() bool {
- return atomic.LoadInt32(&systemActive) == 1
-}
-
-// Enable activates logging for the default logger.
-// It allows logs to be emitted if other conditions (level, namespace) are met.
-// Thread-safe with write lock. Returns the default logger for method chaining.
-// Example:
-//
-// ll.Disable()
-// ll.Info("Ignored") // No output
-// ll.Enable()
-// ll.Info("Logged") // Output: [] INFO: Logged
-func Enable() *Logger {
- return defaultLogger.Enable()
-}
-
-// Disable deactivates logging for the default logger.
-// It suppresses all logs, regardless of level or namespace. Thread-safe with write lock.
-// Returns the default logger for method chaining.
-// Example:
-//
-// ll.Disable()
-// ll.Info("Ignored") // No output
-func Disable() *Logger {
- return defaultLogger.Disable()
-}
-
-// Dbg logs debug information including the source file, line number, and expression value
-// using the default logger. It captures the calling line of code and displays both the
-// expression and its value. Useful for debugging without temporary print statements.
-// Example:
-//
-// x := 42
-// ll.Dbg(x) // Output: [file.go:123] x = 42
-func Dbg(any ...interface{}) {
- defaultLogger.dbg(2, any...)
-}
-
-// Dump displays a hex and ASCII representation of a value’s binary form using the default logger.
-// It serializes the value using gob encoding or direct conversion and shows a hex/ASCII dump.
-// Useful for inspecting binary data structures.
-// Example:
-//
-// ll.Dump([]byte{0x41, 0x42}) // Outputs hex/ASCII dump
-func Dump(any interface{}) {
- defaultLogger.Dump(any)
-}
-
-// Enabled returns whether the default logger is enabled for logging.
-// It provides thread-safe read access to the enabled field using a read lock.
-// Example:
-//
-// ll.Enable()
-// if ll.Enabled() {
-// ll.Info("Logging enabled") // Output: [] INFO: Logging enabled
-// }
-func Enabled() bool {
- return defaultLogger.Enabled()
-}
-
-// Fields starts a fluent chain for adding fields using variadic key-value pairs with the default logger.
-// It creates a FieldBuilder to attach fields, handling non-string keys or uneven pairs by
-// adding an error field. Thread-safe via the FieldBuilder’s logger.
-// Example:
-//
-// ll.Fields("user", "alice").Info("Action") // Output: [] INFO: Action [user=alice]
-func Fields(pairs ...any) *FieldBuilder {
- return defaultLogger.Fields(pairs...)
-}
-
-// Field starts a fluent chain for adding fields from a map with the default logger.
-// It creates a FieldBuilder to attach fields from a map, supporting type-safe field addition.
-// Thread-safe via the FieldBuilder’s logger.
-// Example:
-//
-// ll.Field(map[string]interface{}{"user": "alice"}).Info("Action") // Output: [] INFO: Action [user=alice]
-func Field(fields map[string]interface{}) *FieldBuilder {
- return defaultLogger.Field(fields)
-}
-
-// Line adds vertical spacing (newlines) to the log output using the default logger.
-// If no arguments are provided, it defaults to 1 newline. Multiple values are summed to
-// determine the total lines. Useful for visually separating log sections. Thread-safe.
-// Example:
-//
-// ll.Line(2).Info("After two newlines") // Adds 2 blank lines before: [] INFO: After two newlines
-func Line(lines ...int) *Logger {
- return defaultLogger.Line(lines...)
-}
-
-// Indent sets the indentation level for all log messages of the default logger.
-// Each level adds two spaces to the log message, useful for hierarchical output.
-// Thread-safe with write lock. Returns the default logger for method chaining.
-// Example:
-//
-// ll.Indent(2)
-// ll.Info("Indented") // Output: [] INFO: Indented
-func Indent(depth int) *Logger {
- return defaultLogger.Indent(depth)
-}
-
-// Mark logs the current file and line number where it's called, without any additional debug information.
-// It's useful for tracing execution flow without the verbosity of Dbg.
-// Example:
-//
-// logger.Mark() // *MARK*: [file.go:123]
-func Mark(names ...string) {
- defaultLogger.mark(2, names...)
-
-}
-
-// Output logs data in a human-readable JSON format at Info level, including caller file and line information.
-// It is similar to Dbg but formats the output as JSON for better readability. It is thread-safe and respects
-// the logger’s configuration (e.g., enabled, level, suspend, handler, middleware).
-func Output(values ...interface{}) {
- o := NewInspector(defaultLogger)
- o.Log(2, values...)
-}
diff --git a/vendor/github.com/olekukonko/ll/inspector.go b/vendor/github.com/olekukonko/ll/inspector.go
deleted file mode 100644
index f4e0fa5919..0000000000
--- a/vendor/github.com/olekukonko/ll/inspector.go
+++ /dev/null
@@ -1,239 +0,0 @@
-package ll
-
-import (
- "encoding/json"
- "fmt"
- "reflect"
- "runtime"
- "strings"
- "unsafe"
-
- "github.com/olekukonko/ll/lx"
-)
-
-// Inspector is a utility for Logger that provides advanced inspection and logging of data
-// in human-readable JSON format. It uses reflection to access and represent unexported fields,
-// nested structs, embedded structs, and pointers, making it useful for debugging complex data structures.
-type Inspector struct {
- logger *Logger
-}
-
-// NewInspector returns a new Inspector instance associated with the provided logger.
-func NewInspector(logger *Logger) *Inspector {
- return &Inspector{logger: logger}
-}
-
-// Log outputs the given values as indented JSON at the Info level, prefixed with the caller's
-// file name and line number. It handles structs (including unexported fields, nested, and embedded),
-// pointers, errors, and other types. The skip parameter determines how many stack frames to skip
-// when identifying the caller; typically set to 2 to account for the call to Log and its wrapper.
-//
-// Example usage within a Logger method:
-//
-// o := NewInspector(l)
-// o.Log(2, someStruct) // Logs JSON representation with caller info
-func (o *Inspector) Log(skip int, values ...interface{}) {
- // Skip if logger is suspended or Info level is disabled
- if o.logger.suspend.Load() || !o.logger.shouldLog(lx.LevelInfo) {
- return
- }
-
- // Retrieve caller information for logging context
- _, file, line, ok := runtime.Caller(skip)
- if !ok {
- o.logger.log(lx.LevelError, lx.ClassText, "Inspector: Unable to parse runtime caller", nil, false)
- return
- }
-
- // Extract short filename for concise output
- shortFile := file
- if idx := strings.LastIndex(file, "/"); idx >= 0 {
- shortFile = file[idx+1:]
- }
-
- // Process each value individually
- for _, value := range values {
- var jsonData []byte
- var err error
-
- // Use reflection for struct types to handle unexported and nested fields
- val := reflect.ValueOf(value)
- if val.Kind() == reflect.Ptr {
- val = val.Elem()
- }
- if val.Kind() == reflect.Struct {
- valueMap := o.structToMap(val)
- jsonData, err = json.MarshalIndent(valueMap, "", " ")
- } else if errVal, ok := value.(error); ok {
- // Special handling for errors to represent them as a simple map
- value = map[string]string{"error": errVal.Error()}
- jsonData, err = json.MarshalIndent(value, "", " ")
- } else {
- // Fall back to standard JSON marshaling for non-struct types
- jsonData, err = json.MarshalIndent(value, "", " ")
- }
-
- if err != nil {
- o.logger.log(lx.LevelError, lx.ClassText, fmt.Sprintf("Inspector: JSON encoding error: %v", err), nil, false)
- continue
- }
-
- // Construct log message with file, line, and JSON data
- msg := fmt.Sprintf("[%s:%d] DUMP: %s", shortFile, line, string(jsonData))
- o.logger.log(lx.LevelInfo, lx.ClassText, msg, nil, false)
- }
-}
-
-// structToMap recursively converts a struct's reflect.Value to a map[string]interface{}.
-// It includes unexported fields (named with parentheses), prefixes pointers with '*',
-// flattens anonymous embedded structs without json tags, and uses unsafe pointers to access
-// unexported primitive fields when reflect.CanInterface() returns false.
-func (o *Inspector) structToMap(val reflect.Value) map[string]interface{} {
- result := make(map[string]interface{})
- if !val.IsValid() {
- return result
- }
-
- typ := val.Type()
- for i := 0; i < val.NumField(); i++ {
- field := val.Field(i)
- fieldType := typ.Field(i)
-
- // Determine field name: prefer json tag if present and not "-", else use struct field name
- baseName := fieldType.Name
- jsonTag := fieldType.Tag.Get("json")
- hasJsonTag := false
- if jsonTag != "" {
- if idx := strings.Index(jsonTag, ","); idx != -1 {
- jsonTag = jsonTag[:idx]
- }
- if jsonTag != "-" {
- baseName = jsonTag
- hasJsonTag = true
- }
- }
-
- // Enclose unexported field names in parentheses
- fieldName := baseName
- if !fieldType.IsExported() {
- fieldName = "(" + baseName + ")"
- }
-
- // Handle pointer fields
- isPtr := fieldType.Type.Kind() == reflect.Ptr
- if isPtr {
- fieldName = "*" + fieldName
- if field.IsNil() {
- result[fieldName] = nil
- continue
- }
- field = field.Elem()
- }
-
- // Recurse for struct fields
- if field.Kind() == reflect.Struct {
- subMap := o.structToMap(field)
- isNested := !fieldType.Anonymous || hasJsonTag
- if isNested {
- result[fieldName] = subMap
- } else {
- // Flatten embedded struct fields into the parent map, avoiding overwrites
- for k, v := range subMap {
- if _, exists := result[k]; !exists {
- result[k] = v
- }
- }
- }
- } else {
- // Handle primitive fields
- if field.CanInterface() {
- result[fieldName] = field.Interface()
- } else {
- // Use unsafe access for unexported primitives
- ptr := getDataPtr(field)
- switch field.Kind() {
- case reflect.String:
- result[fieldName] = *(*string)(ptr)
- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
- result[fieldName] = o.getIntFromUnexportedField(field)
- case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
- result[fieldName] = o.getUintFromUnexportedField(field)
- case reflect.Float32, reflect.Float64:
- result[fieldName] = o.getFloatFromUnexportedField(field)
- case reflect.Bool:
- result[fieldName] = *(*bool)(ptr)
- default:
- result[fieldName] = fmt.Sprintf("*unexported %s*", field.Type().String())
- }
- }
- }
- }
- return result
-}
-
-// emptyInterface represents the internal structure of an empty interface{}.
-// This is used for unsafe pointer manipulation to access unexported field data.
-type emptyInterface struct {
- typ unsafe.Pointer
- word unsafe.Pointer
-}
-
-// getDataPtr returns an unsafe.Pointer to the underlying data of a reflect.Value.
-// This enables direct access to unexported fields via unsafe operations.
-func getDataPtr(v reflect.Value) unsafe.Pointer {
- return (*emptyInterface)(unsafe.Pointer(&v)).word
-}
-
-// getIntFromUnexportedField extracts a signed integer value from an unexported field
-// using unsafe pointer access. It supports int, int8, int16, int32, and int64 kinds,
-// returning the value as int64. Returns 0 for unsupported kinds.
-func (o *Inspector) getIntFromUnexportedField(field reflect.Value) int64 {
- ptr := getDataPtr(field)
- switch field.Kind() {
- case reflect.Int:
- return int64(*(*int)(ptr))
- case reflect.Int8:
- return int64(*(*int8)(ptr))
- case reflect.Int16:
- return int64(*(*int16)(ptr))
- case reflect.Int32:
- return int64(*(*int32)(ptr))
- case reflect.Int64:
- return *(*int64)(ptr)
- }
- return 0
-}
-
-// getUintFromUnexportedField extracts an unsigned integer value from an unexported field
-// using unsafe pointer access. It supports uint, uint8, uint16, uint32, and uint64 kinds,
-// returning the value as uint64. Returns 0 for unsupported kinds.
-func (o *Inspector) getUintFromUnexportedField(field reflect.Value) uint64 {
- ptr := getDataPtr(field)
- switch field.Kind() {
- case reflect.Uint:
- return uint64(*(*uint)(ptr))
- case reflect.Uint8:
- return uint64(*(*uint8)(ptr))
- case reflect.Uint16:
- return uint64(*(*uint16)(ptr))
- case reflect.Uint32:
- return uint64(*(*uint32)(ptr))
- case reflect.Uint64:
- return *(*uint64)(ptr)
- }
- return 0
-}
-
-// getFloatFromUnexportedField extracts a floating-point value from an unexported field
-// using unsafe pointer access. It supports float32 and float64 kinds, returning the value
-// as float64. Returns 0 for unsupported kinds.
-func (o *Inspector) getFloatFromUnexportedField(field reflect.Value) float64 {
- ptr := getDataPtr(field)
- switch field.Kind() {
- case reflect.Float32:
- return float64(*(*float32)(ptr))
- case reflect.Float64:
- return *(*float64)(ptr)
- }
- return 0
-}
diff --git a/vendor/github.com/olekukonko/ll/lc.go b/vendor/github.com/olekukonko/ll/lc.go
deleted file mode 100644
index 1187135067..0000000000
--- a/vendor/github.com/olekukonko/ll/lc.go
+++ /dev/null
@@ -1,48 +0,0 @@
-package ll
-
-import "github.com/olekukonko/ll/lx"
-
-// defaultStore is the global namespace store for enable/disable states.
-// It is shared across all Logger instances to manage namespace hierarchy consistently.
-// Thread-safe via the lx.Namespace struct’s sync.Map.
-var defaultStore = &lx.Namespace{}
-
-// systemActive indicates if the global logging system is active.
-// Defaults to true, meaning logging is active unless explicitly shut down.
-// Or, default to false and require an explicit ll.Start(). Let's default to true for less surprise.
-var systemActive int32 = 1 // 1 for true, 0 for false (for atomic operations)
-
-// Option defines a functional option for configuring a Logger.
-type Option func(*Logger)
-
-// reverseString reverses the input string by swapping characters from both ends.
-// It converts the string to a rune slice to handle Unicode characters correctly,
-// ensuring proper reversal for multi-byte characters.
-// Used internally for string manipulation, such as in debugging or log formatting.
-func reverseString(s string) string {
- // Convert string to rune slice to handle Unicode characters
- r := []rune(s)
- // Iterate over half the slice, swapping characters from start and end
- for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
- r[i], r[j] = r[j], r[i]
- }
- // Convert rune slice back to string and return
- return string(r)
-}
-
-// viewString converts a byte slice to a printable string, replacing non-printable
-// characters (ASCII < 32 or > 126) with a dot ('.').
-// It ensures safe display of binary data in logs, such as in the Dump method.
-// Used for formatting binary data in a human-readable hex/ASCII dump.
-func viewString(b []byte) string {
- // Convert byte slice to rune slice via string for processing
- r := []rune(string(b))
- // Replace non-printable characters with '.'
- for i := range r {
- if r[i] < 32 || r[i] > 126 {
- r[i] = '.'
- }
- }
- // Return the resulting printable string
- return string(r)
-}
diff --git a/vendor/github.com/olekukonko/ll/lh/buffered.go b/vendor/github.com/olekukonko/ll/lh/buffered.go
deleted file mode 100644
index 0fc8c14d7f..0000000000
--- a/vendor/github.com/olekukonko/ll/lh/buffered.go
+++ /dev/null
@@ -1,279 +0,0 @@
-package lh
-
-import (
- "fmt"
- "io"
- "os"
- "runtime"
- "sync"
- "time"
-
- "github.com/olekukonko/ll/lx"
-)
-
-// Buffering holds configuration for the Buffered handler.
-type Buffering struct {
- BatchSize int // Flush when this many entries are buffered (default: 100)
- FlushInterval time.Duration // Maximum time between flushes (default: 10s)
- MaxBuffer int // Maximum buffer size before applying backpressure (default: 1000)
- OnOverflow func(int) // Called when buffer reaches MaxBuffer (default: logs warning)
-}
-
-// BufferingOpt configures Buffered handler.
-type BufferingOpt func(*Buffering)
-
-// WithBatchSize sets the batch size for flushing.
-// It specifies the number of log entries to buffer before flushing to the underlying handler.
-// Example:
-//
-// handler := NewBuffered(textHandler, WithBatchSize(50)) // Flush every 50 entries
-func WithBatchSize(size int) BufferingOpt {
- return func(c *Buffering) {
- c.BatchSize = size
- }
-}
-
-// WithFlushInterval sets the maximum time between flushes.
-// It defines the interval at which buffered entries are flushed, even if the batch size is not reached.
-// Example:
-//
-// handler := NewBuffered(textHandler, WithFlushInterval(5*time.Second)) // Flush every 5 seconds
-func WithFlushInterval(d time.Duration) BufferingOpt {
- return func(c *Buffering) {
- c.FlushInterval = d
- }
-}
-
-// WithMaxBuffer sets the maximum buffer size before backpressure.
-// It limits the number of entries that can be queued in the channel, triggering overflow handling if exceeded.
-// Example:
-//
-// handler := NewBuffered(textHandler, WithMaxBuffer(500)) // Allow up to 500 buffered entries
-func WithMaxBuffer(size int) BufferingOpt {
- return func(c *Buffering) {
- c.MaxBuffer = size
- }
-}
-
-// WithOverflowHandler sets the overflow callback.
-// It specifies a function to call when the buffer reaches MaxBuffer, typically for logging or metrics.
-// Example:
-//
-// handler := NewBuffered(textHandler, WithOverflowHandler(func(n int) { fmt.Printf("Overflow: %d entries\n", n) }))
-func WithOverflowHandler(fn func(int)) BufferingOpt {
- return func(c *Buffering) {
- c.OnOverflow = fn
- }
-}
-
-// Buffered wraps any Handler to provide buffering capabilities.
-// It buffers log entries in a channel and flushes them based on batch size, time interval, or explicit flush.
-// The generic type H ensures compatibility with any lx.Handler implementation.
-// Thread-safe via channels and sync primitives.
-type Buffered[H lx.Handler] struct {
- handler H // Underlying handler to process log entries
- config *Buffering // Configuration for batching and flushing
- entries chan *lx.Entry // Channel for buffering log entries
- flushSignal chan struct{} // Channel to trigger explicit flushes
- shutdown chan struct{} // Channel to signal worker shutdown
- shutdownOnce sync.Once // Ensures Close is called only once
- wg sync.WaitGroup // Waits for worker goroutine to finish
-}
-
-// NewBuffered creates a new buffered handler that wraps another handler.
-// It initializes the handler with default or provided configuration options and starts a worker goroutine.
-// Thread-safe via channel operations and finalizer for cleanup.
-// Example:
-//
-// textHandler := lh.NewTextHandler(os.Stdout)
-// buffered := NewBuffered(textHandler, WithBatchSize(50))
-func NewBuffered[H lx.Handler](handler H, opts ...BufferingOpt) *Buffered[H] {
- // Initialize default configuration
- config := &Buffering{
- BatchSize: 100, // Default: flush every 100 entries
- FlushInterval: 10 * time.Second, // Default: flush every 10 seconds
- MaxBuffer: 1000, // Default: max 1000 entries in buffer
- OnOverflow: func(count int) { // Default: log overflow to io.Discard
- fmt.Fprintf(io.Discard, "log buffer overflow: %d entries\n", count)
- },
- }
-
- // Apply provided options
- for _, opt := range opts {
- opt(config)
- }
-
- // Ensure sane configuration values
- if config.BatchSize < 1 {
- config.BatchSize = 1 // Minimum batch size is 1
- }
- if config.MaxBuffer < config.BatchSize {
- config.MaxBuffer = config.BatchSize * 10 // Ensure buffer is at least 10x batch size
- }
- if config.FlushInterval <= 0 {
- config.FlushInterval = 10 * time.Second // Minimum flush interval is 10s
- }
-
- // Initialize Buffered handler
- b := &Buffered[H]{
- handler: handler, // Set underlying handler
- config: config, // Set configuration
- entries: make(chan *lx.Entry, config.MaxBuffer), // Create buffered channel
- flushSignal: make(chan struct{}, 1), // Create single-slot flush signal channel
- shutdown: make(chan struct{}), // Create shutdown signal channel
- }
-
- // Start worker goroutine
- b.wg.Add(1)
- go b.worker()
-
- // Set finalizer for cleanup during garbage collection
- runtime.SetFinalizer(b, (*Buffered[H]).Final)
- return b
-}
-
-// Handle implements the lx.Handler interface.
-// It buffers log entries in the entries channel or triggers a flush on overflow.
-// Returns an error if the buffer is full and flush cannot be triggered.
-// Thread-safe via non-blocking channel operations.
-// Example:
-//
-// buffered.Handle(&lx.Entry{Message: "test"}) // Buffers entry or triggers flush
-func (b *Buffered[H]) Handle(e *lx.Entry) error {
- select {
- case b.entries <- e: // Buffer entry if channel has space
- return nil
- default: // Handle buffer overflow
- if b.config.OnOverflow != nil {
- b.config.OnOverflow(len(b.entries)) // Call overflow handler
- }
- select {
- case b.flushSignal <- struct{}{}: // Trigger flush if possible
- return fmt.Errorf("log buffer overflow, triggering flush")
- default: // Flush already in progress
- return fmt.Errorf("log buffer overflow and flush already in progress")
- }
- }
-}
-
-// Flush triggers an immediate flush of buffered entries.
-// It sends a signal to the worker to process all buffered entries.
-// If a flush is already pending, it waits briefly and may exit without flushing.
-// Thread-safe via non-blocking channel operations.
-// Example:
-//
-// buffered.Flush() // Flushes all buffered entries
-func (b *Buffered[H]) Flush() {
- select {
- case b.flushSignal <- struct{}{}: // Signal worker to flush
- case <-time.After(100 * time.Millisecond): // Timeout if flush is pending
- // Flush already pending
- }
-}
-
-// Close flushes any remaining entries and stops the worker.
-// It ensures shutdown is performed only once and waits for the worker to finish.
-// Thread-safe via sync.Once and WaitGroup.
-// Returns nil as it does not produce errors.
-// Example:
-//
-// buffered.Close() // Flushes entries and stops worker
-func (b *Buffered[H]) Close() error {
- b.shutdownOnce.Do(func() {
- close(b.shutdown) // Signal worker to shut down
- b.wg.Wait() // Wait for worker to finish
- runtime.SetFinalizer(b, nil) // Remove finalizer
- })
- return nil
-}
-
-// Final ensures remaining entries are flushed during garbage collection.
-// It calls Close to flush entries and stop the worker.
-// Used as a runtime finalizer to prevent log loss.
-// Example (internal usage):
-//
-// runtime.SetFinalizer(buffered, (*Buffered[H]).Final)
-func (b *Buffered[H]) Final() {
- b.Close()
-}
-
-// Config returns the current configuration of the Buffered handler.
-// It provides access to BatchSize, FlushInterval, MaxBuffer, and OnOverflow settings.
-// Example:
-//
-// config := buffered.Config() // Access configuration
-func (b *Buffered[H]) Config() *Buffering {
- return b.config
-}
-
-// worker processes entries and handles flushing.
-// It runs in a goroutine, buffering entries, flushing on batch size, timer, or explicit signal,
-// and shutting down cleanly when signaled.
-// Thread-safe via channel operations and WaitGroup.
-func (b *Buffered[H]) worker() {
- defer b.wg.Done() // Signal completion when worker exits
- batch := make([]*lx.Entry, 0, b.config.BatchSize) // Buffer for batching entries
- ticker := time.NewTicker(b.config.FlushInterval) // Timer for periodic flushes
- defer ticker.Stop() // Clean up ticker
- for {
- select {
- case entry := <-b.entries: // Receive new entry
- batch = append(batch, entry)
- // Flush if batch size is reached
- if len(batch) >= b.config.BatchSize {
- b.flushBatch(batch)
- batch = batch[:0]
- }
- case <-ticker.C: // Periodic flush
- if len(batch) > 0 {
- b.flushBatch(batch)
- batch = batch[:0]
- }
- case <-b.flushSignal: // Explicit flush
- if len(batch) > 0 {
- b.flushBatch(batch)
- batch = batch[:0]
- }
- b.drainRemaining() // Drain all entries from the channel
- case <-b.shutdown: // Shutdown signal
- if len(batch) > 0 {
- b.flushBatch(batch)
- }
- b.drainRemaining() // Flush remaining entries
- return
- }
- }
-}
-
-// flushBatch processes a batch of entries through the wrapped handler.
-// It writes each entry to the underlying handler, logging any errors to stderr.
-// Example (internal usage):
-//
-// b.flushBatch([]*lx.Entry{entry1, entry2})
-func (b *Buffered[H]) flushBatch(batch []*lx.Entry) {
- for _, entry := range batch {
- // Process each entry through the handler
- if err := b.handler.Handle(entry); err != nil {
- fmt.Fprintf(os.Stderr, "log flush error: %v\n", err) // Log errors to stderr
- }
- }
-}
-
-// drainRemaining processes any remaining entries in the channel.
-// It flushes all entries from the entries channel to the underlying handler,
-// logging any errors to stderr. Used during flush or shutdown.
-// Example (internal usage):
-//
-// b.drainRemaining() // Flushes all pending entries
-func (b *Buffered[H]) drainRemaining() {
- for {
- select {
- case entry := <-b.entries: // Process next entry
- if err := b.handler.Handle(entry); err != nil {
- fmt.Fprintf(os.Stderr, "log drain error: %v\n", err) // Log errors to stderr
- }
- default: // Exit when channel is empty
- return
- }
- }
-}
diff --git a/vendor/github.com/olekukonko/ll/lh/colorized.go b/vendor/github.com/olekukonko/ll/lh/colorized.go
deleted file mode 100644
index e343ff3816..0000000000
--- a/vendor/github.com/olekukonko/ll/lh/colorized.go
+++ /dev/null
@@ -1,480 +0,0 @@
-package lh
-
-import (
- "fmt"
- "io"
- "os"
- "sort"
- "strings"
- "sync"
- "time"
-
- "github.com/olekukonko/ll/lx"
-)
-
-// Palette defines ANSI color codes for various log components.
-// It specifies colors for headers, goroutines, functions, paths, stack traces, and log levels,
-// used by ColorizedHandler to format log output with color.
-type Palette struct {
- Header string // Color for stack trace header and dump separators
- Goroutine string // Color for goroutine lines in stack traces
- Func string // Color for function names in stack traces
- Path string // Color for file paths in stack traces
- FileLine string // Color for file line numbers (not used in provided code)
- Reset string // Reset code to clear color formatting
- Pos string // Color for position in hex dumps
- Hex string // Color for hex values in dumps
- Ascii string // Color for ASCII values in dumps
- Debug string // Color for Debug level messages
- Info string // Color for Info level messages
- Warn string // Color for Warn level messages
- Error string // Color for Error level messages
- Title string // Color for dump titles (BEGIN/END separators)
-}
-
-// darkPalette defines colors optimized for dark terminal backgrounds.
-// It uses bright, contrasting colors for readability on dark backgrounds.
-var darkPalette = Palette{
- Header: "\033[1;31m", // Bold red for headers
- Goroutine: "\033[1;36m", // Bold cyan for goroutines
- Func: "\033[97m", // Bright white for functions
- Path: "\033[38;5;245m", // Light gray for paths
- FileLine: "\033[38;5;111m", // Muted light blue (unused)
- Reset: "\033[0m", // Reset color formatting
-
- Title: "\033[38;5;245m", // Light gray for dump titles
- Pos: "\033[38;5;117m", // Light blue for dump positions
- Hex: "\033[38;5;156m", // Light green for hex values
- Ascii: "\033[38;5;224m", // Light pink for ASCII values
-
- Debug: "\033[36m", // Cyan for Debug level
- Info: "\033[32m", // Green for Info level
- Warn: "\033[33m", // Yellow for Warn level
- Error: "\033[31m", // Red for Error level
-}
-
-// lightPalette defines colors optimized for light terminal backgrounds.
-// It uses darker colors for better contrast on light backgrounds.
-var lightPalette = Palette{
- Header: "\033[1;31m", // Same red for headers
- Goroutine: "\033[34m", // Blue (darker for light bg)
- Func: "\033[30m", // Black text for functions
- Path: "\033[90m", // Dark gray for paths
- FileLine: "\033[94m", // Blue for file lines (unused)
- Reset: "\033[0m", // Reset color formatting
-
- Title: "\033[38;5;245m", // Light gray for dump titles
- Pos: "\033[38;5;117m", // Light blue for dump positions
- Hex: "\033[38;5;156m", // Light green for hex values
- Ascii: "\033[38;5;224m", // Light pink for ASCII values
-
- Debug: "\033[36m", // Cyan for Debug level
- Info: "\033[32m", // Green for Info level
- Warn: "\033[33m", // Yellow for Warn level
- Error: "\033[31m", // Red for Error level
-}
-
-// ColorizedHandler is a handler that outputs log entries with ANSI color codes.
-// It formats log entries with colored namespace, level, message, fields, and stack traces,
-// writing the result to the provided writer.
-// Thread-safe if the underlying writer is thread-safe.
-type ColorizedHandler struct {
- w io.Writer // Destination for colored log output
- palette Palette // Color scheme for formatting
- showTime bool // Whether to display timestamps
- timeFormat string // Format for timestamps (defaults to time.RFC3339)
- mu sync.Mutex
-}
-
-// ColorOption defines a configuration function for ColorizedHandler.
-// It allows customization of the handler, such as setting the color palette.
-type ColorOption func(*ColorizedHandler)
-
-// WithColorPallet sets the color palette for the ColorizedHandler.
-// It allows specifying a custom Palette for dark or light terminal backgrounds.
-// Example:
-//
-// handler := NewColorizedHandler(os.Stdout, WithColorPallet(lightPalette))
-func WithColorPallet(pallet Palette) ColorOption {
- return func(c *ColorizedHandler) {
- c.palette = pallet
- }
-}
-
-// NewColorizedHandler creates a new ColorizedHandler writing to the specified writer.
-// It initializes the handler with a detected or specified color palette and applies
-// optional configuration functions.
-// Example:
-//
-// handler := NewColorizedHandler(os.Stdout)
-// logger := ll.New("app").Enable().Handler(handler)
-// logger.Info("Test") // Output: [app] : Test
-func NewColorizedHandler(w io.Writer, opts ...ColorOption) *ColorizedHandler {
- // Initialize with writer
- c := &ColorizedHandler{w: w,
- showTime: false,
- timeFormat: time.RFC3339,
- }
-
- // Apply configuration options
- for _, opt := range opts {
- opt(c)
- }
- // Detect palette if not set
- c.palette = c.detectPalette()
- return c
-}
-
-// Handle processes a log entry and writes it with ANSI color codes.
-// It delegates to specialized methods based on the entry's class (Dump, Raw, or regular).
-// Returns an error if writing to the underlying writer fails.
-// Thread-safe if the writer is thread-safe.
-// Example:
-//
-// handler.Handle(&lx.Entry{Message: "test", Level: lx.LevelInfo}) // Writes colored output
-func (h *ColorizedHandler) Handle(e *lx.Entry) error {
-
- h.mu.Lock()
- defer h.mu.Unlock()
-
- switch e.Class {
- case lx.ClassDump:
- // Handle hex dump entries
- return h.handleDumpOutput(e)
- case lx.ClassRaw:
- // Write raw entries directly
- _, err := h.w.Write([]byte(e.Message))
- return err
- default:
- // Handle standard log entries
- return h.handleRegularOutput(e)
- }
-}
-
-// Timestamped enables or disables timestamp display and optionally sets a custom time format.
-// If format is empty, defaults to RFC3339.
-// Example:
-//
-// handler := NewColorizedHandler(os.Stdout).Timestamped(true, time.StampMilli)
-// // Output: Jan 02 15:04:05.000 [app] INFO: Test
-func (h *ColorizedHandler) Timestamped(enable bool, format ...string) {
- h.showTime = enable
- if len(format) > 0 && format[0] != "" {
- h.timeFormat = format[0]
- }
-}
-
-// handleRegularOutput handles normal log entries.
-// It formats the entry with colored namespace, level, message, fields, and stack trace (if present),
-// writing the result to the handler's writer.
-// Returns an error if writing fails.
-// Example (internal usage):
-//
-// h.handleRegularOutput(&lx.Entry{Message: "test", Level: lx.LevelInfo}) // Writes colored output
-func (h *ColorizedHandler) handleRegularOutput(e *lx.Entry) error {
- var builder strings.Builder // Buffer for building formatted output
-
- // Add timestamp if enabled
- if h.showTime {
- builder.WriteString(e.Timestamp.Format(h.timeFormat))
- builder.WriteString(lx.Space)
- }
-
- // Format namespace with colors
- h.formatNamespace(&builder, e)
-
- // Format level with color based on severity
- h.formatLevel(&builder, e)
-
- // Add message and fields
- builder.WriteString(e.Message)
- h.formatFields(&builder, e)
-
- // fmt.Println("------------>", len(e.Stack))
- // Format stack trace if present
- if len(e.Stack) > 0 {
- h.formatStack(&builder, e.Stack)
- }
-
- // Append newline for non-None levels
- if e.Level != lx.LevelNone {
- builder.WriteString(lx.Newline)
- }
-
- // Write formatted output to writer
- _, err := h.w.Write([]byte(builder.String()))
- return err
-}
-
-// formatNamespace formats the namespace with ANSI color codes.
-// It supports FlatPath ([parent/child]) and NestedPath ([parent]→[child]) styles.
-// Example (internal usage):
-//
-// h.formatNamespace(&builder, &lx.Entry{Namespace: "parent/child", Style: lx.FlatPath}) // Writes "[parent/child]: "
-func (h *ColorizedHandler) formatNamespace(b *strings.Builder, e *lx.Entry) {
- if e.Namespace == "" {
- return
- }
-
- b.WriteString(lx.LeftBracket)
- switch e.Style {
- case lx.NestedPath:
- // Split namespace and format as [parent]→[child]
- parts := strings.Split(e.Namespace, lx.Slash)
- for i, part := range parts {
- b.WriteString(part)
- b.WriteString(lx.RightBracket)
- if i < len(parts)-1 {
- b.WriteString(lx.Arrow)
- b.WriteString(lx.LeftBracket)
- }
- }
- default: // FlatPath
- // Format as [parent/child]
- b.WriteString(e.Namespace)
- b.WriteString(lx.RightBracket)
- }
- b.WriteString(lx.Colon)
- b.WriteString(lx.Space)
-}
-
-// formatLevel formats the log level with ANSI color codes.
-// It applies a color based on the level (Debug, Info, Warn, Error) and resets afterward.
-// Example (internal usage):
-//
-// h.formatLevel(&builder, &lx.Entry{Level: lx.LevelInfo}) // Writes "INFO: "
-func (h *ColorizedHandler) formatLevel(b *strings.Builder, e *lx.Entry) {
- // Map levels to colors
- color := map[lx.LevelType]string{
- lx.LevelDebug: h.palette.Debug, // Cyan
- lx.LevelInfo: h.palette.Info, // Green
- lx.LevelWarn: h.palette.Warn, // Yellow
- lx.LevelError: h.palette.Error, // Red
- }[e.Level]
-
- b.WriteString(color)
- b.WriteString(e.Level.String())
- b.WriteString(h.palette.Reset)
- b.WriteString(lx.Colon)
- b.WriteString(lx.Space)
-}
-
-// formatFields formats the log entry's fields in sorted order.
-// It writes fields as [key=value key=value], with no additional coloring.
-// Example (internal usage):
-//
-// h.formatFields(&builder, &lx.Entry{Fields: map[string]interface{}{"key": "value"}}) // Writes " [key=value]"
-func (h *ColorizedHandler) formatFields(b *strings.Builder, e *lx.Entry) {
- if len(e.Fields) == 0 {
- return
- }
-
- // Collect and sort field keys
- var keys []string
- for k := range e.Fields {
- keys = append(keys, k)
- }
- sort.Strings(keys)
-
- b.WriteString(lx.Space)
- b.WriteString(lx.LeftBracket)
- // Format fields as key=value
- for i, k := range keys {
- if i > 0 {
- b.WriteString(lx.Space)
- }
- b.WriteString(k)
- b.WriteString("=")
- b.WriteString(fmt.Sprint(e.Fields[k]))
- }
- b.WriteString(lx.RightBracket)
-}
-
-// formatStack formats a stack trace with ANSI color codes.
-// It structures the stack trace with colored goroutine, function, and path segments,
-// using indentation and separators for readability.
-// Example (internal usage):
-//
-// h.formatStack(&builder, []byte("goroutine 1 [running]:\nmain.main()\n\tmain.go:10")) // Appends colored stack trace
-func (h *ColorizedHandler) formatStack(b *strings.Builder, stack []byte) {
- b.WriteString("\n")
- b.WriteString(h.palette.Header)
- b.WriteString("[stack]")
- b.WriteString(h.palette.Reset)
- b.WriteString("\n")
-
- lines := strings.Split(string(stack), "\n")
- if len(lines) == 0 {
- return
- }
-
- // Format goroutine line
- b.WriteString(" ┌─ ")
- b.WriteString(h.palette.Goroutine)
- b.WriteString(lines[0])
- b.WriteString(h.palette.Reset)
- b.WriteString("\n")
-
- // Pair function name and file path lines
- for i := 1; i < len(lines)-1; i += 2 {
- funcLine := strings.TrimSpace(lines[i])
- pathLine := strings.TrimSpace(lines[i+1])
-
- if funcLine != "" {
- b.WriteString(" │ ")
- b.WriteString(h.palette.Func)
- b.WriteString(funcLine)
- b.WriteString(h.palette.Reset)
- b.WriteString("\n")
- }
- if pathLine != "" {
- b.WriteString(" │ ")
-
- // Look for last "/" before ".go:"
- lastSlash := strings.LastIndex(pathLine, "/")
- goIndex := strings.Index(pathLine, ".go:")
-
- if lastSlash >= 0 && goIndex > lastSlash {
- // Prefix path
- prefix := pathLine[:lastSlash+1]
- // File and line (e.g., ll.go:698 +0x5c)
- suffix := pathLine[lastSlash+1:]
-
- b.WriteString(h.palette.Path)
- b.WriteString(prefix)
- b.WriteString(h.palette.Reset)
-
- b.WriteString(h.palette.Path) // Use mainPath color for suffix
- b.WriteString(suffix)
- b.WriteString(h.palette.Reset)
- } else {
- // Fallback: whole line is gray
- b.WriteString(h.palette.Path)
- b.WriteString(pathLine)
- b.WriteString(h.palette.Reset)
- }
-
- b.WriteString("\n")
- }
- }
-
- // Handle any remaining unpaired line
- if len(lines)%2 == 0 && strings.TrimSpace(lines[len(lines)-1]) != "" {
- b.WriteString(" │ ")
- b.WriteString(h.palette.Func)
- b.WriteString(strings.TrimSpace(lines[len(lines)-1]))
- b.WriteString(h.palette.Reset)
- b.WriteString("\n")
- }
-
- b.WriteString(" └\n")
-}
-
-// handleDumpOutput formats hex dump output with ANSI color codes.
-// It applies colors to position, hex, ASCII, and title components of the dump,
-// wrapping the output with colored BEGIN/END separators.
-// Returns an error if writing fails.
-// Example (internal usage):
-//
-// h.handleDumpOutput(&lx.Entry{Class: lx.ClassDump, Message: "pos 00 hex: 61 62 'ab'"}) // Writes colored dump
-func (h *ColorizedHandler) handleDumpOutput(e *lx.Entry) error {
- var builder strings.Builder
-
- // Add timestamp if enabled
- if h.showTime {
- builder.WriteString(e.Timestamp.Format(h.timeFormat))
- builder.WriteString(lx.Newline)
- }
-
- // Write colored BEGIN separator
- builder.WriteString(h.palette.Title)
- builder.WriteString("---- BEGIN DUMP ----")
- builder.WriteString(h.palette.Reset)
- builder.WriteString("\n")
-
- // Process each line of the dump
- lines := strings.Split(e.Message, "\n")
- length := len(lines)
- for i, line := range lines {
- if strings.HasPrefix(line, "pos ") {
- // Parse and color position and hex/ASCII parts
- parts := strings.SplitN(line, "hex:", 2)
- if len(parts) == 2 {
- builder.WriteString(h.palette.Pos)
- builder.WriteString(parts[0])
- builder.WriteString(h.palette.Reset)
-
- hexAscii := strings.SplitN(parts[1], "'", 2)
- builder.WriteString(h.palette.Hex)
- builder.WriteString("hex:")
- builder.WriteString(hexAscii[0])
- builder.WriteString(h.palette.Reset)
-
- if len(hexAscii) > 1 {
- builder.WriteString(h.palette.Ascii)
- builder.WriteString("'")
- builder.WriteString(hexAscii[1])
- builder.WriteString(h.palette.Reset)
- }
- }
- } else if strings.HasPrefix(line, "Dumping value of type:") {
- // Color type dump lines
- builder.WriteString(h.palette.Header)
- builder.WriteString(line)
- builder.WriteString(h.palette.Reset)
- } else {
- // Write non-dump lines as-is
- builder.WriteString(line)
- }
-
- // Don't add newline for the last line
- if i < length-1 {
- builder.WriteString("\n")
- }
- }
-
- // Write colored END separator
- builder.WriteString(h.palette.Title)
- builder.WriteString("---- END DUMP ----")
- builder.WriteString(h.palette.Reset)
- builder.WriteString("\n")
-
- // Write formatted output to writer
- _, err := h.w.Write([]byte(builder.String()))
- return err
-}
-
-// detectPalette selects a color palette based on terminal environment variables.
-// It checks TERM_BACKGROUND, COLORFGBG, and AppleInterfaceStyle to determine
-// whether a light or dark palette is appropriate, defaulting to darkPalette.
-// Example (internal usage):
-//
-// palette := h.detectPalette() // Returns darkPalette or lightPalette
-func (h *ColorizedHandler) detectPalette() Palette {
- // Check TERM_BACKGROUND (e.g., iTerm2)
- if bg, ok := os.LookupEnv("TERM_BACKGROUND"); ok {
- if bg == "light" {
- return lightPalette // Use light palette for light background
- }
- return darkPalette // Use dark palette otherwise
- }
-
- // Check COLORFGBG (traditional xterm)
- if fgBg, ok := os.LookupEnv("COLORFGBG"); ok {
- parts := strings.Split(fgBg, ";")
- if len(parts) >= 2 {
- bg := parts[len(parts)-1] // Last part (some terminals add more fields)
- if bg == "7" || bg == "15" || bg == "0;15" { // Handle variations
- return lightPalette // Use light palette for light background
- }
- }
- }
-
- // Check macOS dark mode
- if style, ok := os.LookupEnv("AppleInterfaceStyle"); ok && strings.EqualFold(style, "dark") {
- return darkPalette // Use dark palette for macOS dark mode
- }
-
- // Default: dark (conservative choice for terminals)
- return darkPalette
-}
diff --git a/vendor/github.com/olekukonko/ll/lh/json.go b/vendor/github.com/olekukonko/ll/lh/json.go
deleted file mode 100644
index c40576d674..0000000000
--- a/vendor/github.com/olekukonko/ll/lh/json.go
+++ /dev/null
@@ -1,170 +0,0 @@
-package lh
-
-import (
- "encoding/json"
- "fmt"
- "github.com/olekukonko/ll/lx"
- "io"
- "os"
- "strings"
- "sync"
- "time"
-)
-
-// JSONHandler is a handler that outputs log entries as JSON objects.
-// It formats log entries with timestamp, level, message, namespace, fields, and optional
-// stack traces or dump segments, writing the result to the provided writer.
-// Thread-safe with a mutex to protect concurrent writes.
-type JSONHandler struct {
- writer io.Writer // Destination for JSON output
- timeFmt string // Format for timestamp (default: RFC3339Nano)
- pretty bool // Enable pretty printing with indentation if true
- fieldMap map[string]string // Optional mapping for field names (not used in provided code)
- mu sync.Mutex // Protects concurrent access to writer
-}
-
-// JsonOutput represents the JSON structure for a log entry.
-// It includes all relevant log data, such as timestamp, level, message, and optional
-// stack trace or dump segments, serialized as a JSON object.
-type JsonOutput struct {
- Time string `json:"ts"` // Timestamp in specified format
- Level string `json:"lvl"` // Log level (e.g., "INFO")
- Class string `json:"class"` // Entry class (e.g., "Text", "Dump")
- Msg string `json:"msg"` // Log message
- Namespace string `json:"ns"` // Namespace path
- Stack []byte `json:"stack"` // Stack trace (if present)
- Dump []dumpSegment `json:"dump"` // Hex/ASCII dump segments (for ClassDump)
- Fields map[string]interface{} `json:"fields"` // Custom fields
-}
-
-// dumpSegment represents a single segment of a hex/ASCII dump.
-// Used for ClassDump entries to structure position, hex values, and ASCII representation.
-type dumpSegment struct {
- Offset int `json:"offset"` // Starting byte offset of the segment
- Hex []string `json:"hex"` // Hexadecimal values of bytes
- ASCII string `json:"ascii"` // ASCII representation of bytes
-}
-
-// NewJSONHandler creates a new JSONHandler writing to the specified writer.
-// It initializes the handler with a default timestamp format (RFC3339Nano) and optional
-// configuration functions to customize settings like pretty printing.
-// Example:
-//
-// handler := NewJSONHandler(os.Stdout)
-// logger := ll.New("app").Enable().Handler(handler)
-// logger.Info("Test") // Output: {"ts":"...","lvl":"INFO","class":"Text","msg":"Test","ns":"app","stack":null,"dump":null,"fields":null}
-func NewJSONHandler(w io.Writer, opts ...func(*JSONHandler)) *JSONHandler {
- h := &JSONHandler{
- writer: w, // Set output writer
- timeFmt: time.RFC3339Nano, // Default timestamp format
- }
- // Apply configuration options
- for _, opt := range opts {
- opt(h)
- }
- return h
-}
-
-// Handle processes a log entry and writes it as JSON.
-// It delegates to specialized methods based on the entry's class (Dump or regular),
-// ensuring thread-safety with a mutex.
-// Returns an error if JSON encoding or writing fails.
-// Example:
-//
-// handler.Handle(&lx.Entry{Message: "test", Level: lx.LevelInfo}) // Writes JSON object
-func (h *JSONHandler) Handle(e *lx.Entry) error {
- h.mu.Lock()
- defer h.mu.Unlock()
-
- // Handle dump entries separately
- if e.Class == lx.ClassDump {
- return h.handleDump(e)
- }
- // Handle standard log entries
- return h.handleRegular(e)
-}
-
-// handleRegular handles standard log entries (non-dump).
-// It converts the entry to a JsonOutput struct and encodes it as JSON,
-// applying pretty printing if enabled. Logs encoding errors to stderr for debugging.
-// Returns an error if encoding or writing fails.
-// Example (internal usage):
-//
-// h.handleRegular(&lx.Entry{Message: "test", Level: lx.LevelInfo}) // Writes JSON object
-func (h *JSONHandler) handleRegular(e *lx.Entry) error {
- // Create JSON output structure
- entry := JsonOutput{
- Time: e.Timestamp.Format(h.timeFmt), // Format timestamp
- Level: e.Level.String(), // Convert level to string
- Class: e.Class.String(), // Convert class to string
- Msg: e.Message, // Set message
- Namespace: e.Namespace, // Set namespace
- Dump: nil, // No dump for regular entries
- Fields: e.Fields, // Copy fields
- Stack: e.Stack, // Include stack trace if present
- }
- // Create JSON encoder
- enc := json.NewEncoder(h.writer)
- if h.pretty {
- // Enable indentation for pretty printing
- enc.SetIndent("", " ")
- }
- // Log encoding attempt for debugging
- fmt.Fprintf(os.Stderr, "Encoding JSON entry: %v\n", e.Message)
- // Encode and write JSON
- err := enc.Encode(entry)
- if err != nil {
- // Log encoding error for debugging
- fmt.Fprintf(os.Stderr, "JSON encode error: %v\n", err)
- }
- return err
-}
-
-// handleDump processes ClassDump entries, converting hex dump output to JSON segments.
-// It parses the dump message into structured segments with offset, hex, and ASCII data,
-// encoding them as a JsonOutput struct.
-// Returns an error if parsing or encoding fails.
-// Example (internal usage):
-//
-// h.handleDump(&lx.Entry{Class: lx.ClassDump, Message: "pos 00 hex: 61 62 'ab'"}) // Writes JSON with dump segments
-func (h *JSONHandler) handleDump(e *lx.Entry) error {
- var segments []dumpSegment
- lines := strings.Split(e.Message, "\n")
-
- // Parse each line of the dump message
- for _, line := range lines {
- if !strings.HasPrefix(line, "pos") {
- continue // Skip non-dump lines
- }
- parts := strings.SplitN(line, "hex:", 2)
- if len(parts) != 2 {
- continue // Skip invalid lines
- }
- // Parse position
- var offset int
- fmt.Sscanf(parts[0], "pos %d", &offset)
-
- // Parse hex and ASCII
- hexAscii := strings.SplitN(parts[1], "'", 2)
- hexStr := strings.Fields(strings.TrimSpace(hexAscii[0]))
-
- // Create dump segment
- segments = append(segments, dumpSegment{
- Offset: offset, // Set byte offset
- Hex: hexStr, // Set hex values
- ASCII: strings.Trim(hexAscii[1], "'"), // Set ASCII representation
- })
- }
-
- // Encode JSON output with dump segments
- return json.NewEncoder(h.writer).Encode(JsonOutput{
- Time: e.Timestamp.Format(h.timeFmt), // Format timestamp
- Level: e.Level.String(), // Convert level to string
- Class: e.Class.String(), // Convert class to string
- Msg: "dumping segments", // Fixed message for dumps
- Namespace: e.Namespace, // Set namespace
- Dump: segments, // Include parsed segments
- Fields: e.Fields, // Copy fields
- Stack: e.Stack, // Include stack trace if present
- })
-}
diff --git a/vendor/github.com/olekukonko/ll/lh/memory.go b/vendor/github.com/olekukonko/ll/lh/memory.go
deleted file mode 100644
index e3bc939873..0000000000
--- a/vendor/github.com/olekukonko/ll/lh/memory.go
+++ /dev/null
@@ -1,113 +0,0 @@
-package lh
-
-import (
- "fmt"
- "github.com/olekukonko/ll/lx"
- "io"
- "sync"
-)
-
-// MemoryHandler is an lx.Handler that stores log entries in memory.
-// Useful for testing or buffering logs for later inspection.
-// It maintains a thread-safe slice of log entries, protected by a read-write mutex.
-type MemoryHandler struct {
- mu sync.RWMutex // Protects concurrent access to entries
- entries []*lx.Entry // Slice of stored log entries
- showTime bool // Whether to show timestamps when dumping
- timeFormat string // Time format for dumping
-}
-
-// NewMemoryHandler creates a new MemoryHandler.
-// It initializes an empty slice for storing log entries, ready for use in logging or testing.
-// Example:
-//
-// handler := NewMemoryHandler()
-// logger := ll.New("app").Enable().Handler(handler)
-// logger.Info("Test") // Stores entry in memory
-func NewMemoryHandler() *MemoryHandler {
- return &MemoryHandler{
- entries: make([]*lx.Entry, 0), // Initialize empty slice for entries
- }
-}
-
-// Timestamped enables/disables timestamp display when dumping and optionally sets a time format.
-// Consistent with TextHandler and ColorizedHandler signature.
-// Example:
-//
-// handler.Timestamped(true) // Enable with default format
-// handler.Timestamped(true, time.StampMilli) // Enable with custom format
-// handler.Timestamped(false) // Disable
-func (h *MemoryHandler) Timestamped(enable bool, format ...string) {
- h.mu.Lock()
- defer h.mu.Unlock()
-
- h.showTime = enable
- if len(format) > 0 && format[0] != "" {
- h.timeFormat = format[0]
- }
-}
-
-// Handle stores the log entry in memory.
-// It appends the provided entry to the entries slice, ensuring thread-safety with a write lock.
-// Always returns nil, as it does not perform I/O operations.
-// Example:
-//
-// handler.Handle(&lx.Entry{Message: "test", Level: lx.LevelInfo}) // Stores entry
-func (h *MemoryHandler) Handle(entry *lx.Entry) error {
- h.mu.Lock()
- defer h.mu.Unlock()
- h.entries = append(h.entries, entry) // Append entry to slice
- return nil
-}
-
-// Entries returns a copy of the stored log entries.
-// It creates a new slice with copies of all entries, ensuring thread-safety with a read lock.
-// The returned slice is safe for external use without affecting the handler's internal state.
-// Example:
-//
-// entries := handler.Entries() // Returns copy of stored entries
-func (h *MemoryHandler) Entries() []*lx.Entry {
- h.mu.RLock()
- defer h.mu.RUnlock()
- entries := make([]*lx.Entry, len(h.entries)) // Create new slice for copy
- copy(entries, h.entries) // Copy entries to new slice
- return entries
-}
-
-// Reset clears all stored entries.
-// It truncates the entries slice to zero length, preserving capacity, using a write lock for thread-safety.
-// Example:
-//
-// handler.Reset() // Clears all stored entries
-func (h *MemoryHandler) Reset() {
- h.mu.Lock()
- defer h.mu.Unlock()
- h.entries = h.entries[:0] // Truncate slice to zero length
-}
-
-// Dump writes all stored log entries to the provided io.Writer in text format.
-// Entries are formatted as they would be by a TextHandler, including namespace, level,
-// message, and fields. Thread-safe with read lock.
-// Returns an error if writing fails.
-// Example:
-//
-// logger := ll.New("test", ll.WithHandler(NewMemoryHandler())).Enable()
-// logger.Info("Test message")
-// handler := logger.handler.(*MemoryHandler)
-// handler.Dump(os.Stdout) // Output: [test] INFO: Test message
-func (h *MemoryHandler) Dump(w io.Writer) error {
- h.mu.RLock()
- defer h.mu.RUnlock()
-
- // Create a temporary TextHandler to format entries
- tempHandler := NewTextHandler(w)
- tempHandler.Timestamped(h.showTime, h.timeFormat)
-
- // Process each entry through the TextHandler
- for _, entry := range h.entries {
- if err := tempHandler.Handle(entry); err != nil {
- return fmt.Errorf("failed to dump entry: %w", err) // Wrap and return write errors
- }
- }
- return nil
-}
diff --git a/vendor/github.com/olekukonko/ll/lh/multi.go b/vendor/github.com/olekukonko/ll/lh/multi.go
deleted file mode 100644
index 8a9d8846d3..0000000000
--- a/vendor/github.com/olekukonko/ll/lh/multi.go
+++ /dev/null
@@ -1,51 +0,0 @@
-package lh
-
-import (
- "errors"
- "fmt"
- "github.com/olekukonko/ll/lx"
-)
-
-// MultiHandler combines multiple handlers to process log entries concurrently.
-// It holds a list of lx.Handler instances and delegates each log entry to all handlers,
-// collecting any errors into a single combined error.
-// Thread-safe if the underlying handlers are thread-safe.
-type MultiHandler struct {
- Handlers []lx.Handler // List of handlers to process each log entry
-}
-
-// NewMultiHandler creates a new MultiHandler with the specified handlers.
-// It accepts a variadic list of handlers to be executed in order.
-// The returned handler processes log entries by passing them to each handler in sequence.
-// Example:
-//
-// textHandler := NewTextHandler(os.Stdout)
-// jsonHandler := NewJSONHandler(os.Stdout)
-// multi := NewMultiHandler(textHandler, jsonHandler)
-// logger := ll.New("app").Enable().Handler(multi)
-// logger.Info("Test") // Processed by both text and JSON handlers
-func NewMultiHandler(h ...lx.Handler) *MultiHandler {
- return &MultiHandler{
- Handlers: h, // Initialize with provided handlers
- }
-}
-
-// Handle implements the Handler interface, calling Handle on each handler in sequence.
-// It collects any errors from handlers and combines them into a single error using errors.Join.
-// If no errors occur, it returns nil. Thread-safe if the underlying handlers are thread-safe.
-// Example:
-//
-// multi.Handle(&lx.Entry{Message: "test", Level: lx.LevelInfo}) // Calls Handle on all handlers
-func (h *MultiHandler) Handle(e *lx.Entry) error {
- var errs []error // Collect errors from handlers
- for i, handler := range h.Handlers {
- // Process entry with each handler
- if err := handler.Handle(e); err != nil {
- // fmt.Fprintf(os.Stderr, "MultiHandler error for handler %d: %v\n", i, err)
- // Wrap error with handler index for context
- errs = append(errs, fmt.Errorf("handler %d: %w", i, err))
- }
- }
- // Combine errors into a single error, or return nil if no errors
- return errors.Join(errs...)
-}
diff --git a/vendor/github.com/olekukonko/ll/lh/slog.go b/vendor/github.com/olekukonko/ll/lh/slog.go
deleted file mode 100644
index 77584202ea..0000000000
--- a/vendor/github.com/olekukonko/ll/lh/slog.go
+++ /dev/null
@@ -1,89 +0,0 @@
-package lh
-
-import (
- "context"
- "github.com/olekukonko/ll/lx"
- "log/slog"
-)
-
-// SlogHandler adapts a slog.Handler to implement lx.Handler.
-// It converts lx.Entry objects to slog.Record objects and delegates to an underlying
-// slog.Handler for processing, enabling compatibility with Go's standard slog package.
-// Thread-safe if the underlying slog.Handler is thread-safe.
-type SlogHandler struct {
- slogHandler slog.Handler // Underlying slog.Handler for processing log records
-}
-
-// NewSlogHandler creates a new SlogHandler wrapping the provided slog.Handler.
-// It initializes the handler with the given slog.Handler, allowing lx.Entry logs to be
-// processed by slog's logging infrastructure.
-// Example:
-//
-// slogText := slog.NewTextHandler(os.Stdout, nil)
-// handler := NewSlogHandler(slogText)
-// logger := ll.New("app").Enable().Handler(handler)
-// logger.Info("Test") // Output: level=INFO msg=Test namespace=app class=Text
-func NewSlogHandler(h slog.Handler) *SlogHandler {
- return &SlogHandler{slogHandler: h}
-}
-
-// Handle converts an lx.Entry to slog.Record and delegates to the slog.Handler.
-// It maps the entry's fields, level, namespace, class, and stack trace to slog attributes,
-// passing the resulting record to the underlying slog.Handler.
-// Returns an error if the slog.Handler fails to process the record.
-// Thread-safe if the underlying slog.Handler is thread-safe.
-// Example:
-//
-// handler.Handle(&lx.Entry{Message: "test", Level: lx.LevelInfo}) // Processes as slog record
-func (h *SlogHandler) Handle(e *lx.Entry) error {
- // Convert lx.LevelType to slog.Level
- level := toSlogLevel(e.Level)
-
- // Create a slog.Record with the entry's data
- record := slog.NewRecord(
- e.Timestamp, // time.Time for log timestamp
- level, // slog.Level for log severity
- e.Message, // string for log message
- 0, // pc (program counter, optional, not used)
- )
-
- // Add standard fields as attributes
- record.AddAttrs(
- slog.String("namespace", e.Namespace), // Add namespace as string attribute
- slog.String("class", e.Class.String()), // Add class as string attribute
- )
-
- // Add stack trace if present
- if len(e.Stack) > 0 {
- record.AddAttrs(slog.String("stack", string(e.Stack))) // Add stack trace as string
- }
-
- // Add custom fields
- for k, v := range e.Fields {
- record.AddAttrs(slog.Any(k, v)) // Add each field as a key-value attribute
- }
-
- // Handle the record with the underlying slog.Handler
- return h.slogHandler.Handle(context.Background(), record)
-}
-
-// toSlogLevel converts lx.LevelType to slog.Level.
-// It maps the logging levels used by the lx package to those used by slog,
-// defaulting to slog.LevelInfo for unknown levels.
-// Example (internal usage):
-//
-// level := toSlogLevel(lx.LevelDebug) // Returns slog.LevelDebug
-func toSlogLevel(level lx.LevelType) slog.Level {
- switch level {
- case lx.LevelDebug:
- return slog.LevelDebug
- case lx.LevelInfo:
- return slog.LevelInfo
- case lx.LevelWarn:
- return slog.LevelWarn
- case lx.LevelError:
- return slog.LevelError
- default:
- return slog.LevelInfo // Default for unknown levels
- }
-}
diff --git a/vendor/github.com/olekukonko/ll/lh/text.go b/vendor/github.com/olekukonko/ll/lh/text.go
deleted file mode 100644
index 0b88cf4b87..0000000000
--- a/vendor/github.com/olekukonko/ll/lh/text.go
+++ /dev/null
@@ -1,231 +0,0 @@
-package lh
-
-import (
- "fmt"
- "io"
- "sort"
- "strings"
- "sync"
- "time"
-
- "github.com/olekukonko/ll/lx"
-)
-
-// TextHandler is a handler that outputs log entries as plain text.
-// It formats log entries with namespace, level, message, fields, and optional stack traces,
-// writing the result to the provided writer.
-// Thread-safe if the underlying writer is thread-safe.
-type TextHandler struct {
- w io.Writer // Destination for formatted log output
- showTime bool // Whether to display timestamps
- timeFormat string // Format for timestamps (defaults to time.RFC3339)
- mu sync.Mutex
-}
-
-// NewTextHandler creates a new TextHandler writing to the specified writer.
-// It initializes the handler with the given writer, suitable for outputs like stdout or files.
-// Example:
-//
-// handler := NewTextHandler(os.Stdout)
-// logger := ll.New("app").Enable().Handler(handler)
-// logger.Info("Test") // Output: [app] INFO: Test
-func NewTextHandler(w io.Writer) *TextHandler {
- return &TextHandler{
- w: w,
- showTime: false,
- timeFormat: time.RFC3339,
- }
-}
-
-// Timestamped enables or disables timestamp display and optionally sets a custom time format.
-// If format is empty, defaults to RFC3339.
-// Example:
-//
-// handler := NewTextHandler(os.Stdout).TextWithTime(true, time.StampMilli)
-// // Output: Jan 02 15:04:05.000 [app] INFO: Test
-func (h *TextHandler) Timestamped(enable bool, format ...string) {
- h.showTime = enable
- if len(format) > 0 && format[0] != "" {
- h.timeFormat = format[0]
- }
-}
-
-// Handle processes a log entry and writes it as plain text.
-// It delegates to specialized methods based on the entry's class (Dump, Raw, or regular).
-// Returns an error if writing to the underlying writer fails.
-// Thread-safe if the writer is thread-safe.
-// Example:
-//
-// handler.Handle(&lx.Entry{Message: "test", Level: lx.LevelInfo}) // Writes "INFO: test"
-func (h *TextHandler) Handle(e *lx.Entry) error {
- h.mu.Lock()
- defer h.mu.Unlock()
-
- // Special handling for dump output
- if e.Class == lx.ClassDump {
- return h.handleDumpOutput(e)
- }
-
- // Raw entries are written directly without formatting
- if e.Class == lx.ClassRaw {
- _, err := h.w.Write([]byte(e.Message))
- return err
- }
-
- // Handle standard log entries
- return h.handleRegularOutput(e)
-}
-
-// handleRegularOutput handles normal log entries.
-// It formats the entry with namespace, level, message, fields, and stack trace (if present),
-// writing the result to the handler's writer.
-// Returns an error if writing fails.
-// Example (internal usage):
-//
-// h.handleRegularOutput(&lx.Entry{Message: "test", Level: lx.LevelInfo}) // Writes "INFO: test"
-func (h *TextHandler) handleRegularOutput(e *lx.Entry) error {
- var builder strings.Builder // Buffer for building formatted output
-
- // Add timestamp if enabled
- if h.showTime {
- builder.WriteString(e.Timestamp.Format(h.timeFormat))
- builder.WriteString(lx.Space)
- }
-
- // Format namespace based on style
- switch e.Style {
- case lx.NestedPath:
- if e.Namespace != "" {
- // Split namespace into parts and format as [parent]→[child]
- parts := strings.Split(e.Namespace, lx.Slash)
- for i, part := range parts {
- builder.WriteString(lx.LeftBracket)
- builder.WriteString(part)
- builder.WriteString(lx.RightBracket)
- if i < len(parts)-1 {
- builder.WriteString(lx.Arrow)
- }
- }
- builder.WriteString(lx.Colon)
- builder.WriteString(lx.Space)
- }
- default: // FlatPath
- if e.Namespace != "" {
- // Format namespace as [parent/child]
- builder.WriteString(lx.LeftBracket)
- builder.WriteString(e.Namespace)
- builder.WriteString(lx.RightBracket)
- builder.WriteString(lx.Space)
- }
- }
-
- // Add level and message
- builder.WriteString(e.Level.String())
- builder.WriteString(lx.Colon)
- builder.WriteString(lx.Space)
- builder.WriteString(e.Message)
-
- // Add fields in sorted order
- if len(e.Fields) > 0 {
- var keys []string
- for k := range e.Fields {
- keys = append(keys, k)
- }
- // Sort keys for consistent output
- sort.Strings(keys)
- builder.WriteString(lx.Space)
- builder.WriteString(lx.LeftBracket)
- for i, k := range keys {
- if i > 0 {
- builder.WriteString(lx.Space)
- }
- // Format field as key=value
- builder.WriteString(k)
- builder.WriteString("=")
- builder.WriteString(fmt.Sprint(e.Fields[k]))
- }
- builder.WriteString(lx.RightBracket)
- }
-
- // Add stack trace if present
- if len(e.Stack) > 0 {
- h.formatStack(&builder, e.Stack)
- }
-
- // Append newline for non-None levels
- if e.Level != lx.LevelNone {
- builder.WriteString(lx.Newline)
- }
-
- // Write formatted output to writer
- _, err := h.w.Write([]byte(builder.String()))
- return err
-}
-
-// handleDumpOutput specially formats hex dump output (plain text version).
-// It wraps the dump message with BEGIN/END separators for clarity.
-// Returns an error if writing fails.
-// Example (internal usage):
-//
-// h.handleDumpOutput(&lx.Entry{Class: lx.ClassDump, Message: "pos 00 hex: 61"}) // Writes "---- BEGIN DUMP ----\npos 00 hex: 61\n---- END DUMP ----\n"
-func (h *TextHandler) handleDumpOutput(e *lx.Entry) error {
- // For text handler, we just add a newline before dump output
- var builder strings.Builder // Buffer for building formatted output
-
- // Add timestamp if enabled
- if h.showTime {
- builder.WriteString(e.Timestamp.Format(h.timeFormat))
- builder.WriteString(lx.Newline)
- }
-
- // Add separator lines and dump content
- builder.WriteString("---- BEGIN DUMP ----\n")
- builder.WriteString(e.Message)
- builder.WriteString("---- END DUMP ----\n")
-
- // Write formatted output to writer
- _, err := h.w.Write([]byte(builder.String()))
- return err
-}
-
-// formatStack formats a stack trace for plain text output.
-// It structures the stack trace with indentation and separators for readability,
-// including goroutine and function/file details.
-// Example (internal usage):
-//
-// h.formatStack(&builder, []byte("goroutine 1 [running]:\nmain.main()\n\tmain.go:10")) // Appends formatted stack trace
-func (h *TextHandler) formatStack(b *strings.Builder, stack []byte) {
- lines := strings.Split(string(stack), "\n")
- if len(lines) == 0 {
- return
- }
-
- // Start stack trace section
- b.WriteString("\n[stack]\n")
-
- // First line: goroutine
- b.WriteString(" ┌─ ")
- b.WriteString(lines[0])
- b.WriteString("\n")
-
- // Iterate through remaining lines
- for i := 1; i < len(lines); i++ {
- line := strings.TrimSpace(lines[i])
- if line == "" {
- continue
- }
-
- if strings.Contains(line, ".go") {
- // File path lines get extra indent
- b.WriteString(" ├ ")
- } else {
- // Function names
- b.WriteString(" │ ")
- }
- b.WriteString(line)
- b.WriteString("\n")
- }
-
- // End stack trace section
- b.WriteString(" └\n")
-}
diff --git a/vendor/github.com/olekukonko/ll/ll.go b/vendor/github.com/olekukonko/ll/ll.go
deleted file mode 100644
index 04d2008103..0000000000
--- a/vendor/github.com/olekukonko/ll/ll.go
+++ /dev/null
@@ -1,1471 +0,0 @@
-package ll
-
-import (
- "bufio"
- "encoding/binary"
- "encoding/json"
- "fmt"
- "io"
- "math"
- "os"
- "reflect"
- "runtime"
- "strings"
- "sync"
- "sync/atomic"
- "time"
-
- "github.com/olekukonko/cat"
- "github.com/olekukonko/ll/lh"
- "github.com/olekukonko/ll/lx"
-)
-
-// Logger manages logging configuration and behavior, encapsulating state such as enablement,
-// log level, namespaces, context fields, output style, handler, middleware, and formatting.
-// It is thread-safe, using a read-write mutex to protect concurrent access to its fields.
-type Logger struct {
- mu sync.RWMutex // Guards concurrent access to fields
- enabled bool // Determines if logging is enabled
- suspend atomic.Bool // uses suspend path for most actions eg. skipping namespace checks
- level lx.LevelType // Minimum log level (e.g., Debug, Info, Warn, Error)
- namespaces *lx.Namespace // Manages namespace enable/disable states
- currentPath string // Current namespace path (e.g., "parent/child")
- context map[string]interface{} // Contextual fields included in all logs
- style lx.StyleType // Namespace formatting style (FlatPath or NestedPath)
- handler lx.Handler // Output handler for logs (e.g., text, JSON)
- middleware []Middleware // Middleware functions to process log entries
- prefix string // Prefix prepended to log messages
- indent int // Number of double spaces for message indentation
- stackBufferSize int // Buffer size for capturing stack traces
- separator string // Separator for namespace paths (e.g., "/")
- entries atomic.Int64 // Tracks total log entries sent to handler
-}
-
-// New creates a new Logger with the given namespace and optional configurations.
-// It initializes with defaults: disabled, Debug level, flat namespace style, text handler
-// to os.Stdout, and an empty middleware chain. Options (e.g., WithHandler, WithLevel) can
-// override defaults. The logger is thread-safe via mutex-protected methods.
-// Example:
-//
-// logger := New("app", WithHandler(lh.NewTextHandler(os.Stdout))).Enable()
-// logger.Info("Starting application") // Output: [app] INFO: Starting application
-func New(namespace string, opts ...Option) *Logger {
- logger := &Logger{
- enabled: lx.DefaultEnabled, // Defaults to disabled (false)
- level: lx.LevelDebug, // Default minimum log level
- namespaces: defaultStore, // Shared namespace store
- currentPath: namespace, // Initial namespace path
- context: make(map[string]interface{}), // Empty context for fields
- style: lx.FlatPath, // Default namespace style ([parent/child])
- handler: lh.NewTextHandler(os.Stdout), // Default text output to stdout
- middleware: make([]Middleware, 0), // Empty middleware chain
- stackBufferSize: 4096, // Default stack trace buffer size
- separator: lx.Slash, // Default namespace separator ("/")
- }
-
- // Apply provided configuration options
- for _, opt := range opts {
- opt(logger)
- }
-
- return logger
-}
-
-// AddContext adds a key-value pair to the logger's context, modifying it directly.
-// Unlike Context, it mutates the existing context. It is thread-safe using a write lock.
-// Example:
-//
-// logger := New("app").Enable()
-// logger.AddContext("user", "alice")
-// logger.Info("Action") // Output: [app] INFO: Action [user=alice]
-func (l *Logger) AddContext(key string, value interface{}) *Logger {
- l.mu.Lock()
- defer l.mu.Unlock()
-
- // Initialize context map if nil
- if l.context == nil {
- l.context = make(map[string]interface{})
- }
- l.context[key] = value
- return l
-}
-
-// Benchmark logs the duration since a start time at Info level, including "start",
-// "end", and "duration" fields. It is thread-safe via Fields and log methods.
-// Example:
-//
-// logger := New("app").Enable()
-// start := time.Now()
-// logger.Benchmark(start) // Output: [app] INFO: benchmark [start=... end=... duration=...]
-func (l *Logger) Benchmark(start time.Time) time.Duration {
- duration := time.Since(start)
- l.Fields(
- "duration_ms", duration.Milliseconds(),
- "duration", duration.String(),
- ).Infof("benchmark completed")
-
- return duration
-}
-
-// CanLog checks if a log at the given level would be emitted, considering enablement,
-// log level, namespaces, sampling, and rate limits. It is thread-safe via shouldLog.
-// Example:
-//
-// logger := New("app").Enable().Level(lx.LevelWarn)
-// canLog := logger.CanLog(lx.LevelInfo) // false
-func (l *Logger) CanLog(level lx.LevelType) bool {
- return l.shouldLog(level)
-}
-
-// Clear removes all middleware functions, resetting the middleware chain to empty.
-// It is thread-safe using a write lock and returns the logger for chaining.
-// Example:
-//
-// logger := New("app").Enable().Use(someMiddleware)
-// logger.Clear()
-// logger.Info("No middleware") // Output: [app] INFO: No middleware
-func (l *Logger) Clear() *Logger {
- l.mu.Lock()
- defer l.mu.Unlock()
- l.middleware = nil
- return l
-}
-
-// Clone creates a new logger with the same configuration and namespace as the parent,
-// but with a fresh context map to allow independent field additions. It is thread-safe
-// using a read lock.
-// Example:
-//
-// logger := New("app").Enable().Context(map[string]interface{}{"k": "v"})
-// clone := logger.Clone()
-// clone.Info("Cloned") // Output: [app] INFO: Cloned [k=v]
-func (l *Logger) Clone() *Logger {
- l.mu.RLock()
- defer l.mu.RUnlock()
-
- return &Logger{
- enabled: l.enabled, // Copy enablement state
- level: l.level, // Copy log level
- namespaces: l.namespaces, // Share namespace store
- currentPath: l.currentPath, // Copy namespace path
- context: make(map[string]interface{}), // Fresh context map
- style: l.style, // Copy namespace style
- handler: l.handler, // Copy output handler
- middleware: l.middleware, // Copy middleware chain
- prefix: l.prefix, // Copy message prefix
- indent: l.indent, // Copy indentation level
- stackBufferSize: l.stackBufferSize, // Copy stack trace buffer size
- separator: l.separator, // Default separator ("/")
- suspend: l.suspend,
- }
-}
-
-// Context creates a new logger with additional contextual fields, preserving existing
-// fields and adding new ones. It returns a new logger to avoid mutating the parent and
-// is thread-safe using a write lock.
-// Example:
-//
-// logger := New("app").Enable()
-// logger = logger.Context(map[string]interface{}{"user": "alice"})
-// logger.Info("Action") // Output: [app] INFO: Action [user=alice]
-func (l *Logger) Context(fields map[string]interface{}) *Logger {
- l.mu.Lock()
- defer l.mu.Unlock()
-
- // Create a new logger with inherited configuration
- newLogger := &Logger{
- enabled: l.enabled,
- level: l.level,
- namespaces: l.namespaces,
- currentPath: l.currentPath,
- context: make(map[string]interface{}),
- style: l.style,
- handler: l.handler,
- middleware: l.middleware,
- prefix: l.prefix,
- indent: l.indent,
- stackBufferSize: l.stackBufferSize,
- separator: l.separator,
- suspend: l.suspend,
- }
-
- // Copy parent's context fields
- for k, v := range l.context {
- newLogger.context[k] = v
- }
-
- // Add new fields
- for k, v := range fields {
- newLogger.context[k] = v
- }
-
- return newLogger
-}
-
-// Dbg logs debug information, including the source file, line number, and expression
-// value, capturing the calling line of code. It is useful for debugging without temporary
-// print statements.
-// Example:
-//
-// x := 42
-// logger.Dbg(x) // Output: [file.go:123] x = 42
-func (l *Logger) Dbg(values ...interface{}) {
- // Skip logging if Info level is not enabled
- if !l.shouldLog(lx.LevelInfo) {
- return
- }
-
- l.dbg(2, values...)
-}
-
-// Debug logs a message at Debug level, formatting it and delegating to the internal
-// log method. It is thread-safe.
-// Example:
-//
-// logger := New("app").Enable().Level(lx.LevelDebug)
-// logger.Debug("Debugging") // Output: [app] DEBUG: Debugging
-func (l *Logger) Debug(args ...any) {
- // check if suspended
- if l.suspend.Load() {
- return
- }
-
- // Skip logging if Debug level is not enabled
- if !l.shouldLog(lx.LevelDebug) {
- return
- }
-
- l.log(lx.LevelDebug, lx.ClassText, cat.Space(args...), nil, false)
-}
-
-// Debugf logs a formatted message at Debug level, delegating to Debug. It is thread-safe.
-// Example:
-//
-// logger := New("app").Enable().Level(lx.LevelDebug)
-// logger.Debugf("Debug %s", "message") // Output: [app] DEBUG: Debug message
-func (l *Logger) Debugf(format string, args ...any) {
- // check if suspended
- if l.suspend.Load() {
- return
- }
-
- l.Debug(fmt.Sprintf(format, args...))
-}
-
-// Disable deactivates logging, suppressing all logs regardless of level or namespace.
-// It is thread-safe using a write lock and returns the logger for chaining.
-// Example:
-//
-// logger := New("app").Enable().Disable()
-// logger.Info("Ignored") // No output
-func (l *Logger) Disable() *Logger {
- l.mu.Lock()
- defer l.mu.Unlock()
- l.enabled = false
- return l
-}
-
-// Dump displays a hex and ASCII representation of a value's binary form, using gob
-// encoding or direct conversion. It is useful for inspecting binary data structures.
-// Example:
-//
-// type Data struct { X int; Y string }
-// logger.Dump(Data{42, "test"}) // Outputs hex/ASCII dump
-func (l *Logger) Dump(values ...interface{}) {
- // Iterate over each value to dump
- for _, value := range values {
- // Log value description and type
- l.Infof("Dumping %v (%T)", value, value)
- var by []byte
- var err error
-
- // Convert value to byte slice based on type
- switch v := value.(type) {
- case []byte:
- by = v
- case string:
- by = []byte(v)
- case float32:
- // Convert float32 to 4-byte big-endian
- buf := make([]byte, 4)
- binary.BigEndian.PutUint32(buf, math.Float32bits(v))
- by = buf
- case float64:
- // Convert float64 to 8-byte big-endian
- buf := make([]byte, 8)
- binary.BigEndian.PutUint64(buf, math.Float64bits(v))
- by = buf
- case int, int8, int16, int32, int64:
- // Convert signed integer to 8-byte big-endian
- by = make([]byte, 8)
- binary.BigEndian.PutUint64(by, uint64(reflect.ValueOf(v).Int()))
- case uint, uint8, uint16, uint32, uint64:
- // Convert unsigned integer to 8-byte big-endian
- by = make([]byte, 8)
- binary.BigEndian.PutUint64(by, reflect.ValueOf(v).Uint())
- case io.Reader:
- // Read all bytes from io.Reader
- by, err = io.ReadAll(v)
- default:
- // Fallback to JSON marshaling for complex types
- by, err = json.Marshal(v)
- }
-
- // Log error if conversion fails
- if err != nil {
- l.Errorf("Dump error: %v", err)
- continue
- }
-
- // Generate hex/ASCII dump
- n := len(by)
- rowcount := 0
- stop := (n / 8) * 8
- k := 0
- s := strings.Builder{}
- // Process 8-byte rows
- for i := 0; i <= stop; i += 8 {
- k++
- if i+8 < n {
- rowcount = 8
- } else {
- rowcount = min(k*8, n) % 8
- }
- // Write position and hex prefix
- s.WriteString(fmt.Sprintf("pos %02d hex: ", i))
-
- // Write hex values
- for j := 0; j < rowcount; j++ {
- s.WriteString(fmt.Sprintf("%02x ", by[i+j]))
- }
- // Pad with spaces for alignment
- for j := rowcount; j < 8; j++ {
- s.WriteString(fmt.Sprintf(" "))
- }
- // Write ASCII representation
- s.WriteString(fmt.Sprintf(" '%s'\n", viewString(by[i:(i+rowcount)])))
- }
- // Log the hex/ASCII dump
- l.log(lx.LevelNone, lx.ClassDump, s.String(), nil, false)
- }
-}
-
-// Output logs data in a human-readable JSON format at Info level, including caller file and line information.
-// It is similar to Dbg but formats the output as JSON for better readability. It is thread-safe and respects
-// the logger's configuration (e.g., enabled, level, suspend, handler, middleware).
-// Example:
-//
-// logger := New("app").Enable()
-// x := map[string]int{"key": 42}
-// logger.Output(x) // Output: [app] INFO: [file.go:123] JSON: {"key": 42}
-//
-// Logger method to provide access to Output functionality
-func (l *Logger) Output(values ...interface{}) {
- o := NewInspector(l)
- o.Log(2, values...)
-}
-
-// Enable activates logging, allowing logs to be emitted if other conditions (e.g., level,
-// namespace) are met. It is thread-safe using a write lock and returns the logger for chaining.
-// Example:
-//
-// logger := New("app").Enable()
-// logger.Info("Started") // Output: [app] INFO: Started
-func (l *Logger) Enable() *Logger {
- l.mu.Lock()
- defer l.mu.Unlock()
- l.enabled = true
- return l
-}
-
-// Enabled checks if the logger is enabled for logging. It is thread-safe using a read lock.
-// Example:
-//
-// logger := New("app").Enable()
-// if logger.Enabled() {
-// logger.Info("Logging is enabled") // Output: [app] INFO: Logging is enabled
-// }
-func (l *Logger) Enabled() bool {
- l.mu.RLock()
- defer l.mu.RUnlock()
- return l.enabled
-}
-
-// Err adds one or more errors to the logger’s context and logs them at Error level.
-// Non-nil errors are stored in the "error" context field (single error or slice) and
-// logged as a concatenated string (e.g., "failed 1; failed 2"). It is thread-safe and
-// returns the logger for chaining.
-// Example:
-//
-// logger := New("app").Enable()
-// err1 := errors.New("failed 1")
-// err2 := errors.New("failed 2")
-// logger.Err(err1, err2).Info("Error occurred")
-// // Output: [app] ERROR: failed 1; failed 2
-// // [app] INFO: Error occurred [error=[failed 1 failed 2]]
-func (l *Logger) Err(errs ...error) {
- // Skip logging if Error level is not enabled
- if !l.shouldLog(lx.LevelError) {
- return
- }
-
- l.mu.Lock()
-
- // Initialize context map if nil
- if l.context == nil {
- l.context = make(map[string]interface{})
- }
-
- // Collect non-nil errors and build log message
- var nonNilErrors []error
- var builder strings.Builder
- count := 0
- for i, err := range errs {
- if err != nil {
- if i > 0 && count > 0 {
- builder.WriteString("; ")
- }
- builder.WriteString(err.Error())
- nonNilErrors = append(nonNilErrors, err)
- count++
- }
- }
-
- if count > 0 {
- if count == 1 {
- // Store single error directly
- l.context["error"] = nonNilErrors[0]
- } else {
- // Store slice of errors
- l.context["error"] = nonNilErrors
- }
- // Log concatenated error messages
- l.log(lx.LevelError, lx.ClassText, builder.String(), nil, false)
- }
- l.mu.Unlock()
-}
-
-// Error logs a message at Error level, formatting it and delegating to the internal
-// log method. It is thread-safe.
-// Example:
-//
-// logger := New("app").Enable()
-// logger.Error("Error occurred") // Output: [app] ERROR: Error occurred
-func (l *Logger) Error(args ...any) {
- // check if suspended
- if l.suspend.Load() {
- return
- }
-
- // Skip logging if Error level is not enabled
- if !l.shouldLog(lx.LevelError) {
- return
- }
- l.log(lx.LevelError, lx.ClassText, cat.Space(args...), nil, false)
-}
-
-// Errorf logs a formatted message at Error level, delegating to Error. It is thread-safe.
-// Example:
-//
-// logger := New("app").Enable()
-// logger.Errorf("Error %s", "occurred") // Output: [app] ERROR: Error occurred
-func (l *Logger) Errorf(format string, args ...any) {
- // check if suspended
- if l.suspend.Load() {
- return
- }
-
- l.Error(fmt.Errorf(format, args...))
-}
-
-// Fatal logs a message at Error level with a stack trace and exits the program with
-// exit code 1. It is thread-safe.
-// Example:
-//
-// logger := New("app").Enable()
-// logger.Fatal("Fatal error") // Output: [app] ERROR: Fatal error [stack=...], then exits
-func (l *Logger) Fatal(args ...any) {
- // check if suspended
- if l.suspend.Load() {
- return
- }
-
- // Exit immediately if Error level is not enabled
- if !l.shouldLog(lx.LevelError) {
- os.Exit(1)
- }
-
- l.log(lx.LevelError, lx.ClassText, cat.Space(args...), nil, false)
- os.Exit(1)
-}
-
-// Fatalf logs a formatted message at Error level with a stack trace and exits the program.
-// It delegates to Fatal and is thread-safe.
-// Example:
-//
-// logger := New("app").Enable()
-// logger.Fatalf("Fatal %s", "error") // Output: [app] ERROR: Fatal error [stack=...], then exits
-func (l *Logger) Fatalf(format string, args ...any) {
- // check if suspended
- if l.suspend.Load() {
- return
- }
-
- l.Fatal(fmt.Sprintf(format, args...))
-}
-
-// Field starts a fluent chain for adding fields from a map, creating a FieldBuilder
-// for type-safe field addition. It is thread-safe via the FieldBuilder’s logger.
-// Example:
-//
-// logger := New("app").Enable()
-// logger.Field(map[string]interface{}{"user": "alice"}).Info("Action") // Output: [app] INFO: Action [user=alice]
-func (l *Logger) Field(fields map[string]interface{}) *FieldBuilder {
- fb := &FieldBuilder{logger: l, fields: make(map[string]interface{})}
-
- // check if suspended
- if l.suspend.Load() {
- return fb
- }
-
- // Copy fields from input map to FieldBuilder
- for k, v := range fields {
- fb.fields[k] = v
- }
- return fb
-}
-
-// Fields starts a fluent chain for adding fields using variadic key-value pairs,
-// creating a FieldBuilder. Non-string keys or uneven pairs add an error field. It is
-// thread-safe via the FieldBuilder’s logger.
-// Example:
-//
-// logger := New("app").Enable()
-// logger.Fields("user", "alice").Info("Action") // Output: [app] INFO: Action [user=alice]
-func (l *Logger) Fields(pairs ...any) *FieldBuilder {
- fb := &FieldBuilder{logger: l, fields: make(map[string]interface{})}
-
- if l.suspend.Load() {
- return fb
- }
-
- // Process key-value pairs
- for i := 0; i < len(pairs)-1; i += 2 {
- if key, ok := pairs[i].(string); ok {
- fb.fields[key] = pairs[i+1]
- } else {
- // Log error for non-string keys
- fb.fields["error"] = fmt.Errorf("non-string key in Fields: %v", pairs[i])
- }
- }
- // Log error for uneven pairs
- if len(pairs)%2 != 0 {
- fb.fields["error"] = fmt.Errorf("uneven key-value pairs in Fields: [%v]", pairs[len(pairs)-1])
- }
- return fb
-}
-
-// GetContext returns the logger's context map of persistent key-value fields. It is
-// thread-safe using a read lock.
-// Example:
-//
-// logger := New("app").AddContext("user", "alice")
-// ctx := logger.GetContext() // Returns map[string]interface{}{"user": "alice"}
-func (l *Logger) GetContext() map[string]interface{} {
- l.mu.RLock()
- defer l.mu.RUnlock()
- return l.context
-}
-
-// GetHandler returns the logger's current handler for customization or inspection.
-// The returned handler should not be modified concurrently with logger operations.
-// Example:
-//
-// logger := New("app")
-// handler := logger.GetHandler() // Returns the current handler (e.g., TextHandler)
-func (l *Logger) GetHandler() lx.Handler {
- return l.handler
-}
-
-// GetLevel returns the minimum log level for the logger. It is thread-safe using a read lock.
-// Example:
-//
-// logger := New("app").Level(lx.LevelWarn)
-// if logger.GetLevel() == lx.LevelWarn {
-// logger.Warn("Warning level set") // Output: [app] WARN: Warning level set
-// }
-func (l *Logger) GetLevel() lx.LevelType {
- l.mu.RLock()
- defer l.mu.RUnlock()
- return l.level
-}
-
-// GetPath returns the logger's current namespace path. It is thread-safe using a read lock.
-// Example:
-//
-// logger := New("app").Namespace("sub")
-// path := logger.GetPath() // Returns "app/sub"
-func (l *Logger) GetPath() string {
- l.mu.RLock()
- defer l.mu.RUnlock()
- return l.currentPath
-}
-
-// GetSeparator returns the logger's namespace separator (e.g., "/"). It is thread-safe
-// using a read lock.
-// Example:
-//
-// logger := New("app").Separator(".")
-// sep := logger.GetSeparator() // Returns "."
-func (l *Logger) GetSeparator() string {
- l.mu.RLock()
- defer l.mu.RUnlock()
- return l.separator
-}
-
-// GetStyle returns the logger's namespace formatting style (FlatPath or NestedPath).
-// It is thread-safe using a read lock.
-// Example:
-//
-// logger := New("app").Style(lx.NestedPath)
-// if logger.GetStyle() == lx.NestedPath {
-// logger.Info("Nested style") // Output: [app]: INFO: Nested style
-// }
-func (l *Logger) GetStyle() lx.StyleType {
- l.mu.RLock()
- defer l.mu.RUnlock()
- return l.style
-}
-
-// Handler sets the handler for processing log entries, configuring the output destination
-// and format (e.g., text, JSON). It is thread-safe using a write lock and returns the
-// logger for chaining.
-// Example:
-//
-// logger := New("app").Enable().Handler(lh.NewTextHandler(os.Stdout))
-// logger.Info("Log") // Output: [app] INFO: Log
-func (l *Logger) Handler(handler lx.Handler) *Logger {
- l.mu.Lock()
- defer l.mu.Unlock()
- l.handler = handler
- return l
-}
-
-// Indent sets the indentation level for log messages, adding two spaces per level. It is
-// thread-safe using a write lock and returns the logger for chaining.
-// Example:
-//
-// logger := New("app").Enable().Indent(2)
-// logger.Info("Indented") // Output: [app] INFO: Indented
-func (l *Logger) Indent(depth int) *Logger {
- l.mu.Lock()
- defer l.mu.Unlock()
- l.indent = depth
- return l
-}
-
-// Info logs a message at Info level, formatting it and delegating to the internal log
-// method. It is thread-safe.
-// Example:
-//
-// logger := New("app").Enable().Style(lx.NestedPath)
-// logger.Info("Started") // Output: [app]: INFO: Started
-func (l *Logger) Info(args ...any) {
- if l.suspend.Load() {
- return
- }
-
- if !l.shouldLog(lx.LevelInfo) {
- return
- }
-
- l.log(lx.LevelInfo, lx.ClassText, cat.Space(args...), nil, false)
-}
-
-// Infof logs a formatted message at Info level, delegating to Info. It is thread-safe.
-// Example:
-//
-// logger := New("app").Enable().Style(lx.NestedPath)
-// logger.Infof("Started %s", "now") // Output: [app]: INFO: Started now
-func (l *Logger) Infof(format string, args ...any) {
- if l.suspend.Load() {
- return
- }
-
- l.Info(fmt.Sprintf(format, args...))
-}
-
-// Len returns the total number of log entries sent to the handler, using atomic operations
-// for thread safety.
-// Example:
-//
-// logger := New("app").Enable()
-// logger.Info("Test")
-// count := logger.Len() // Returns 1
-func (l *Logger) Len() int64 {
- return l.entries.Load()
-}
-
-// Level sets the minimum log level, ignoring messages below it. It is thread-safe using
-// a write lock and returns the logger for chaining.
-// Example:
-//
-// logger := New("app").Enable().Level(lx.LevelWarn)
-// logger.Info("Ignored") // No output
-// logger.Warn("Logged") // Output: [app] WARN: Logged
-func (l *Logger) Level(level lx.LevelType) *Logger {
- l.mu.Lock()
- defer l.mu.Unlock()
- l.level = level
- return l
-}
-
-// Line adds vertical spacing (newlines) to the log output, defaulting to 1 if no arguments
-// are provided. Multiple values are summed for total lines. It is thread-safe and returns
-// the logger for chaining.
-// Example:
-//
-// logger := New("app").Enable()
-// logger.Line(2).Info("After 2 newlines") // Adds 2 blank lines before logging
-// logger.Line().Error("After 1 newline") // Defaults to 1
-func (l *Logger) Line(lines ...int) *Logger {
- line := 1 // Default to 1 newline
- if len(lines) > 0 {
- line = 0
- // Sum all provided line counts
- for _, n := range lines {
- line += n
- }
- // Ensure at least 1 line
- if line < 1 {
- line = 1
- }
- }
- l.log(lx.LevelNone, lx.ClassRaw, strings.Repeat(lx.Newline, line), nil, false)
- return l
-}
-
-// Mark logs the current file and line number where it's called, without any additional debug information.
-// It's useful for tracing execution flow without the verbosity of Dbg.
-// Example:
-//
-// logger.Mark() // *MARK*: [file.go:123]
-func (l *Logger) Mark(name ...string) {
- l.mark(2, name...)
-}
-
-func (l *Logger) mark(skip int, names ...string) {
- // Skip logging if Info level is not enabled
- if !l.shouldLog(lx.LevelInfo) {
- return
- }
-
- // Get caller information (file, line)
- _, file, line, ok := runtime.Caller(skip)
- if !ok {
- l.log(lx.LevelError, lx.ClassText, "Mark: Unable to parse runtime caller", nil, false)
- return
- }
-
- // Extract just the filename (without full path)
- shortFile := file
- if idx := strings.LastIndex(file, "/"); idx >= 0 {
- shortFile = file[idx+1:]
- }
-
- name := strings.Join(names, l.separator)
- if name == "" {
- name = "MARK"
- }
-
- // Format as [filename:line]
- out := fmt.Sprintf("[*%s*]: [%s:%d]\n", name, shortFile, line)
- l.log(lx.LevelInfo, lx.ClassRaw, out, nil, false)
-}
-
-// Measure benchmarks function execution, logging the duration at Info level with a
-// "duration" field. It is thread-safe via Fields and log methods.
-// Example:
-//
-// logger := New("app").Enable()
-// duration := logger.Measure(func() { time.Sleep(time.Millisecond) })
-// // Output: [app] INFO: function executed [duration=~1ms]
-func (l *Logger) Measure(fns ...func()) time.Duration {
- start := time.Now()
-
- for _, fn := range fns {
- if fn != nil {
- fn()
- }
- }
-
- duration := time.Since(start)
- l.Fields(
- "duration_ns", duration.Nanoseconds(),
- "duration", duration.String(),
- "duration_ms", fmt.Sprintf("%.3fms", float64(duration.Nanoseconds())/1e6),
- ).Infof("execution completed")
-
- return duration
-}
-
-// Namespace creates a child logger with a sub-namespace appended to the current path,
-// inheriting the parent’s configuration but with an independent context. It is thread-safe
-// using a read lock.
-// Example:
-//
-// parent := New("parent").Enable()
-// child := parent.Namespace("child")
-// child.Info("Child log") // Output: [parent/child] INFO: Child log
-func (l *Logger) Namespace(name string) *Logger {
- if l.suspend.Load() {
- return l
- }
-
- l.mu.RLock()
- defer l.mu.RUnlock()
-
- // Construct full namespace path
- fullPath := name
- if l.currentPath != "" {
- fullPath = l.currentPath + l.separator + name
- }
-
- // Create child logger with inherited configuration
- return &Logger{
- enabled: l.enabled,
- level: l.level,
- namespaces: l.namespaces,
- currentPath: fullPath,
- context: make(map[string]interface{}),
- style: l.style,
- handler: l.handler,
- middleware: l.middleware,
- prefix: l.prefix,
- indent: l.indent,
- stackBufferSize: l.stackBufferSize,
- separator: l.separator,
- suspend: l.suspend,
- }
-}
-
-// NamespaceDisable disables logging for a namespace and its children, invalidating the
-// namespace cache. It is thread-safe via lx.Namespace’s sync.Map and returns the logger
-// for chaining.
-// Example:
-//
-// logger := New("parent").Enable().NamespaceDisable("parent/child")
-// logger.Namespace("child").Info("Ignored") // No output
-func (l *Logger) NamespaceDisable(relativePath string) *Logger {
- l.mu.RLock()
- fullPath := l.joinPath(l.currentPath, relativePath)
- l.mu.RUnlock()
-
- // Disable namespace in shared store
- l.namespaces.Set(fullPath, false)
- return l
-}
-
-// NamespaceEnable enables logging for a namespace and its children, invalidating the
-// namespace cache. It is thread-safe via lx.Namespace’s sync.Map and returns the logger
-// for chaining.
-// Example:
-//
-// logger := New("parent").Enable().NamespaceEnable("parent/child")
-// logger.Namespace("child").Info("Log") // Output: [parent/child] INFO: Log
-func (l *Logger) NamespaceEnable(relativePath string) *Logger {
- l.mu.RLock()
- fullPath := l.joinPath(l.currentPath, relativePath)
- l.mu.RUnlock()
-
- // Enable namespace in shared store
- l.namespaces.Set(fullPath, true)
- return l
-}
-
-// NamespaceEnabled checks if a namespace is enabled, considering parent namespaces and
-// caching results for performance. It is thread-safe using a read lock.
-// Example:
-//
-// logger := New("parent").Enable().NamespaceDisable("parent/child")
-// enabled := logger.NamespaceEnabled("parent/child") // false
-func (l *Logger) NamespaceEnabled(relativePath string) bool {
- l.mu.RLock()
- fullPath := l.joinPath(l.currentPath, relativePath)
- separator := l.separator
- if separator == "" {
- separator = lx.Slash
- }
- instanceEnabled := l.enabled
- l.mu.RUnlock()
-
- // Handle root path case
- if fullPath == "" && relativePath == "" {
- return instanceEnabled
- }
-
- if fullPath != "" {
- // Check namespace rules
- isEnabledByNSRule, isDisabledByNSRule := l.namespaces.Enabled(fullPath, separator)
- if isDisabledByNSRule {
- return false
- }
- if isEnabledByNSRule {
- return true
- }
- }
- // Fall back to logger's enabled state
- return instanceEnabled
-}
-
-// Panic logs a message at Error level with a stack trace and triggers a panic. It is
-// thread-safe.
-// Example:
-//
-// logger := New("app").Enable()
-// logger.Panic("Panic error") // Output: [app] ERROR: Panic error [stack=...], then panics
-func (l *Logger) Panic(args ...any) {
- // Build message by concatenating arguments with spaces
- msg := cat.Space(args...)
-
- if l.suspend.Load() {
- panic(msg)
- }
-
- // Panic immediately if Error level is not enabled
- if !l.shouldLog(lx.LevelError) {
- panic(msg)
- }
-
- l.log(lx.LevelError, lx.ClassText, msg, nil, true)
- panic(msg)
-}
-
-// Panicf logs a formatted message at Error level with a stack trace and triggers a panic.
-// It delegates to Panic and is thread-safe.
-// Example:
-//
-// logger := New("app").Enable()
-// logger.Panicf("Panic %s", "error") // Output: [app] ERROR: Panic error [stack=...], then panics
-func (l *Logger) Panicf(format string, args ...any) {
- l.Panic(fmt.Sprintf(format, args...))
-}
-
-// Prefix sets a prefix prepended to all log messages. It is thread-safe using a write
-// lock and returns the logger for chaining.
-// Example:
-//
-// logger := New("app").Enable().Prefix("APP: ")
-// logger.Info("Started") // Output: [app] INFO: APP: Started
-func (l *Logger) Prefix(prefix string) *Logger {
- l.mu.Lock()
- defer l.mu.Unlock()
- l.prefix = prefix
- return l
-}
-
-// Print logs a message at Info level without format specifiers, minimizing allocations
-// by concatenating arguments with spaces. It is thread-safe via the log method.
-// Example:
-//
-// logger := New("app").Enable()
-// logger.Print("message", "value") // Output: [app] INFO: message value
-func (l *Logger) Print(args ...any) {
- if l.suspend.Load() {
- return
- }
-
- // Skip logging if Info level is not enabled
- if !l.shouldLog(lx.LevelInfo) {
- return
- }
- l.log(lx.LevelNone, lx.ClassRaw, cat.Space(args...), nil, false)
-}
-
-// Println logs a message at Info level without format specifiers, minimizing allocations
-// by concatenating arguments with spaces. It is thread-safe via the log method.
-// Example:
-//
-// logger := New("app").Enable()
-// logger.Println("message", "value") // Output: [app] INFO: message value
-func (l *Logger) Println(args ...any) {
- if l.suspend.Load() {
- return
- }
-
- // Skip logging if Info level is not enabled
- if !l.shouldLog(lx.LevelInfo) {
- return
- }
- l.log(lx.LevelNone, lx.ClassRaw, cat.SuffixWith(lx.Space, lx.Newline, args...), nil, false)
-}
-
-// Printf logs a formatted message at Info level, delegating to Print. It is thread-safe.
-// Example:
-//
-// logger := New("app").Enable()
-// logger.Printf("Message %s", "value") // Output: [app] INFO: Message value
-func (l *Logger) Printf(format string, args ...any) {
- if l.suspend.Load() {
- return
- }
-
- l.Print(fmt.Sprintf(format, args...))
-}
-
-// Remove removes middleware by the reference returned from Use, delegating to the
-// Middleware’s Remove method for thread-safe removal.
-// Example:
-//
-// logger := New("app").Enable()
-// mw := logger.Use(someMiddleware)
-// logger.Remove(mw) // Removes middleware
-func (l *Logger) Remove(m *Middleware) {
- m.Remove()
-}
-
-// Resume reactivates logging for the current logger after it has been suspended.
-// It clears the suspend flag, allowing logs to be emitted if other conditions (e.g., level, namespace)
-// are met. Thread-safe with a write lock. Returns the logger for method chaining.
-// Example:
-//
-// logger := New("app").Enable().Suspend()
-// logger.Resume()
-// logger.Info("Resumed") // Output: [app] INFO: Resumed
-func (l *Logger) Resume() *Logger {
- l.suspend.Store(false)
- return l
-}
-
-// Separator sets the namespace separator for grouping namespaces and log entries (e.g., "/" or ".").
-// It is thread-safe using a write lock and returns the logger for chaining.
-// Example:
-//
-// logger := New("app").Separator(".")
-// logger.Namespace("sub").Info("Log") // Output: [app.sub] INFO: Log
-func (l *Logger) Separator(separator string) *Logger {
- l.mu.Lock()
- defer l.mu.Unlock()
- l.separator = separator
- return l
-}
-
-// Suspend temporarily deactivates logging for the current logger.
-// It sets the suspend flag, suppressing all logs regardless of level or namespace until resumed.
-// Thread-safe with a write lock. Returns the logger for method chaining.
-// Example:
-//
-// logger := New("app").Enable()
-// logger.Suspend()
-// logger.Info("Ignored") // No output
-func (l *Logger) Suspend() *Logger {
- l.suspend.Store(true)
- return l
-}
-
-// Suspended returns whether the logger is currently suspended.
-// It provides thread-safe read access to the suspend flag using a write lock.
-// Example:
-//
-// logger := New("app").Enable().Suspend()
-// if logger.Suspended() {
-// fmt.Println("Logging is suspended") // Prints message
-// }
-func (l *Logger) Suspended() bool {
- return l.suspend.Load()
-}
-
-// Stack logs messages at Error level with a stack trace for each provided argument.
-// It is thread-safe and skips logging if Debug level is not enabled.
-// Example:
-//
-// logger := New("app").Enable()
-// logger.Stack("Critical error") // Output: [app] ERROR: Critical error [stack=...]
-func (l *Logger) Stack(args ...any) {
- if l.suspend.Load() {
- return
- }
-
- // Skip logging if Debug level is not enabled
- if !l.shouldLog(lx.LevelDebug) {
- return
- }
-
- for _, arg := range args {
- l.log(lx.LevelError, lx.ClassText, cat.Concat(arg), nil, true)
- }
-}
-
-// Stackf logs a formatted message at Error level with a stack trace, delegating to Stack.
-// It is thread-safe.
-// Example:
-//
-// logger := New("app").Enable()
-// logger.Stackf("Critical %s", "error") // Output: [app] ERROR: Critical error [stack=...]
-func (l *Logger) Stackf(format string, args ...any) {
- if l.suspend.Load() {
- return
- }
-
- l.Stack(fmt.Sprintf(format, args...))
-}
-
-// StackSize sets the buffer size for stack trace capture in Stack, Fatal, and Panic methods.
-// It is thread-safe using a write lock and returns the logger for chaining.
-// Example:
-//
-// logger := New("app").Enable().StackSize(65536)
-// logger.Stack("Error") // Captures up to 64KB stack trace
-func (l *Logger) StackSize(size int) *Logger {
- l.mu.Lock()
- defer l.mu.Unlock()
- if size > 0 {
- l.stackBufferSize = size
- }
- return l
-}
-
-// Style sets the namespace formatting style (FlatPath or NestedPath). FlatPath uses
-// [parent/child], while NestedPath uses [parent]→[child]. It is thread-safe using a write
-// lock and returns the logger for chaining.
-// Example:
-//
-// logger := New("parent/child").Enable().Style(lx.NestedPath)
-// logger.Info("Log") // Output: [parent]→[child]: INFO: Log
-func (l *Logger) Style(style lx.StyleType) *Logger {
- l.mu.Lock()
- defer l.mu.Unlock()
- l.style = style
- return l
-}
-
-// Timestamped enables or disables timestamp logging for the logger and optionally sets the timestamp format.
-// It is thread-safe, using a write lock to ensure safe concurrent access.
-// If the logger's handler supports the lx.Timestamper interface, the timestamp settings are applied.
-// The method returns the logger instance to support method chaining.
-// Parameters:
-//
-// enable: Boolean to enable or disable timestamp logging
-// format: Optional string(s) to specify the timestamp format
-func (l *Logger) Timestamped(enable bool, format ...string) *Logger {
- l.mu.Lock()
- defer l.mu.Unlock()
-
- if h, ok := l.handler.(lx.Timestamper); ok {
- h.Timestamped(enable, format...)
- }
- return l
-}
-
-// Use adds a middleware function to process log entries before they are handled, returning
-// a Middleware handle for removal. Middleware returning a non-nil error stops the log.
-// It is thread-safe using a write lock.
-// Example:
-//
-// logger := New("app").Enable()
-// mw := logger.Use(ll.FuncMiddleware(func(e *lx.Entry) error {
-// if e.Level < lx.LevelWarn {
-// return fmt.Errorf("level too low")
-// }
-// return nil
-// }))
-// logger.Info("Ignored") // No output
-// mw.Remove()
-// logger.Info("Now logged") // Output: [app] INFO: Now logged
-func (l *Logger) Use(fn lx.Handler) *Middleware {
- l.mu.Lock()
- defer l.mu.Unlock()
-
- // Assign a unique ID to the middleware
- id := len(l.middleware) + 1
- // Append middleware to the chain
- l.middleware = append(l.middleware, Middleware{id: id, fn: fn})
-
- return &Middleware{
- logger: l,
- id: id,
- }
-}
-
-// Warn logs a message at Warn level, formatting it and delegating to the internal log
-// method. It is thread-safe.
-// Example:
-//
-// logger := New("app").Enable()
-// logger.Warn("Warning") // Output: [app] WARN: Warning
-func (l *Logger) Warn(args ...any) {
- if l.suspend.Load() {
- return
- }
-
- // Skip logging if Warn level is not enabled
- if !l.shouldLog(lx.LevelWarn) {
- return
- }
-
- l.log(lx.LevelWarn, lx.ClassText, cat.Space(args...), nil, false)
-}
-
-// Warnf logs a formatted message at Warn level, delegating to Warn. It is thread-safe.
-// Example:
-//
-// logger := New("app").Enable()
-// logger.Warnf("Warning %s", "issued") // Output: [app] WARN: Warning issued
-func (l *Logger) Warnf(format string, args ...any) {
- if l.suspend.Load() {
- return
- }
-
- l.Warn(fmt.Sprintf(format, args...))
-}
-
-// dbg is an internal helper for Dbg, logging debug information with source file and line
-// number, extracting the calling line of code. It is thread-safe via the log method.
-// Example (internal usage):
-//
-// logger.Dbg(x) // Calls dbg(2, x)
-func (l *Logger) dbg(skip int, values ...interface{}) {
- for _, exp := range values {
- // Get caller information (file, line)
- _, file, line, ok := runtime.Caller(skip)
- if !ok {
- l.log(lx.LevelError, lx.ClassText, "Dbg: Unable to parse runtime caller", nil, false)
- return
- }
-
- // Open source file
- f, err := os.Open(file)
- if err != nil {
- l.log(lx.LevelError, lx.ClassText, "Dbg: Unable to open expected file", nil, false)
- return
- }
-
- // Scan file to find the line
- scanner := bufio.NewScanner(f)
- scanner.Split(bufio.ScanLines)
- var out string
- i := 1
- for scanner.Scan() {
- if i == line {
- // Extract expression between parentheses
- v := scanner.Text()[strings.Index(scanner.Text(), "(")+1 : len(scanner.Text())-strings.Index(reverseString(scanner.Text()), ")")-1]
- // Format output with file, line, expression, and value
- out = fmt.Sprintf("[%s:%d] %s = %+v", file[len(file)-strings.Index(reverseString(file), "/"):], line, v, exp)
- break
- }
- i++
- }
- if err := scanner.Err(); err != nil {
- l.log(lx.LevelError, lx.ClassText, err.Error(), nil, false)
- return
- }
- // Log based on value type
- switch exp.(type) {
- case error:
- l.log(lx.LevelError, lx.ClassText, out, nil, false)
- default:
- l.log(lx.LevelInfo, lx.ClassText, out, nil, false)
- }
-
- f.Close()
- }
-}
-
-// joinPath joins a base path and a relative path using the logger's separator, handling
-// empty base or relative paths. It is used internally for namespace path construction.
-// Example (internal usage):
-//
-// logger.joinPath("parent", "child") // Returns "parent/child"
-func (l *Logger) joinPath(base, relative string) string {
- if base == "" {
- return relative
- }
- if relative == "" {
- return base
- }
- separator := l.separator
- if separator == "" {
- separator = lx.Slash // Default separator
- }
- return base + separator + relative
-}
-
-// log is the internal method for processing a log entry, applying rate limiting, sampling,
-// middleware, and context before passing to the handler. Middleware returning a non-nil
-// error stops the log. It is thread-safe with read/write locks for configuration and stack
-// trace buffer.
-// Example (internal usage):
-//
-// logger := New("app").Enable()
-// logger.Info("Test") // Calls log(lx.LevelInfo, "Test", nil, false)
-func (l *Logger) log(level lx.LevelType, class lx.ClassType, msg string, fields map[string]interface{}, withStack bool) {
- // Skip logging if level is not enabled
- if !l.shouldLog(level) {
- return
- }
-
- var stack []byte
-
- // Capture stack trace if requested
- if withStack {
- l.mu.RLock()
- buf := make([]byte, l.stackBufferSize)
- l.mu.RUnlock()
- n := runtime.Stack(buf, false)
- if fields == nil {
- fields = make(map[string]interface{})
- }
- stack = buf[:n]
- }
-
- l.mu.RLock()
- defer l.mu.RUnlock()
-
- // Apply prefix and indentation to the message
- var builder strings.Builder
- if l.indent > 0 {
- builder.WriteString(strings.Repeat(lx.DoubleSpace, l.indent))
- }
- if l.prefix != "" {
- builder.WriteString(l.prefix)
- }
- builder.WriteString(msg)
- finalMsg := builder.String()
-
- // Create log entry
- entry := &lx.Entry{
- Timestamp: time.Now(),
- Level: level,
- Message: finalMsg,
- Namespace: l.currentPath,
- Fields: fields,
- Style: l.style,
- Class: class,
- Stack: stack,
- }
-
- // Merge context fields, avoiding overwrites
- if len(l.context) > 0 {
- if entry.Fields == nil {
- entry.Fields = make(map[string]interface{})
- }
- for k, v := range l.context {
- if _, exists := entry.Fields[k]; !exists {
- entry.Fields[k] = v
- }
- }
- }
-
- // Apply middleware, stopping if any returns an error
- for _, mw := range l.middleware {
- if err := mw.fn.Handle(entry); err != nil {
- return
- }
- }
-
- // Pass to handler if set
- if l.handler != nil {
- _ = l.handler.Handle(entry)
- l.entries.Add(1)
- }
-}
-
-// shouldLog determines if a log should be emitted based on enabled state, level, namespaces,
-// sampling, and rate limits, caching namespace results for performance. It is thread-safe
-// with a read lock.
-// Example (internal usage):
-//
-// logger := New("app").Enable().Level(lx.LevelWarn)
-// if logger.shouldLog(lx.LevelInfo) { // false
-// // Log would be skipped
-// }
-func (l *Logger) shouldLog(level lx.LevelType) bool {
- // Skip if global logging system is inactive
- if !Active() {
- return false
- }
-
- // check for suspend mode
- if l.suspend.Load() {
- return false
- }
-
- // Skip if log level is below minimum
- if level > l.level {
- return false
- }
-
- separator := l.separator
- if separator == "" {
- separator = lx.Slash
- }
-
- // Check namespace rules if path is set
- if l.currentPath != "" {
- isEnabledByNSRule, isDisabledByNSRule := l.namespaces.Enabled(l.currentPath, separator)
- if isDisabledByNSRule {
- return false
- }
- if isEnabledByNSRule {
- return true
- }
- }
-
- // Fall back to logger's enabled state
- if !l.enabled {
- return false
- }
-
- return true
-}
-
-// WithHandler sets the handler for the logger as a functional option for configuring
-// a new logger instance.
-// Example:
-//
-// logger := New("app", WithHandler(lh.NewJSONHandler(os.Stdout)))
-func WithHandler(handler lx.Handler) Option {
- return func(l *Logger) {
- l.handler = handler
- }
-}
-
-// WithTimestamped returns an Option that configures timestamp settings for the logger's existing handler.
-// It enables or disables timestamp logging and optionally sets the timestamp format if the handler
-// supports the lx.Timestamper interface. If no handler is set, the function has no effect.
-// Parameters:
-//
-// enable: Boolean to enable or disable timestamp logging
-// format: Optional string(s) to specify the timestamp format
-func WithTimestamped(enable bool, format ...string) Option {
- return func(l *Logger) {
- if l.handler != nil { // Check if a handler is set
- // Verify if the handler supports the lx.Timestamper interface
- if h, ok := l.handler.(lx.Timestamper); ok {
- h.Timestamped(enable, format...) // Apply timestamp settings to the handler
- }
- }
- }
-}
-
-// WithLevel sets the minimum log level for the logger as a functional option for
-// configuring a new logger instance.
-// Example:
-//
-// logger := New("app", WithLevel(lx.LevelWarn))
-func WithLevel(level lx.LevelType) Option {
- return func(l *Logger) {
- l.level = level
- }
-}
-
-// WithStyle sets the namespace formatting style for the logger as a functional option
-// for configuring a new logger instance.
-// Example:
-//
-// logger := New("app", WithStyle(lx.NestedPath))
-func WithStyle(style lx.StyleType) Option {
- return func(l *Logger) {
- l.style = style
- }
-}
diff --git a/vendor/github.com/olekukonko/ll/lx/lx.go b/vendor/github.com/olekukonko/ll/lx/lx.go
deleted file mode 100644
index d370cb2d01..0000000000
--- a/vendor/github.com/olekukonko/ll/lx/lx.go
+++ /dev/null
@@ -1,223 +0,0 @@
-package lx
-
-import (
- "strings"
- "time"
-)
-
-// Formatting constants for log output.
-// These constants define the characters used to format log messages, ensuring consistency
-// across handlers (e.g., text, JSON, colorized). They are used to construct namespace paths,
-// level indicators, and field separators in log entries.
-const (
- Space = " " // Single space for separating elements (e.g., between level and message)
- DoubleSpace = " " // Double space for indentation (e.g., for hierarchical output)
- Slash = "/" // Separator for namespace paths (e.g., "parent/child")
- Arrow = "→" // Arrow for NestedPath style namespaces (e.g., [parent]→[child])
- LeftBracket = "[" // Opening bracket for namespaces and fields (e.g., [app])
- RightBracket = "]" // Closing bracket for namespaces and fields (e.g., [app])
- Colon = ":" // Separator after namespace or level (e.g., [app]: INFO:)
- Dot = "." // Separator for namespace paths (e.g., "parent.child")
- Newline = "\n" // Newline for separating log entries or stack trace lines
-)
-
-// DefaultEnabled defines the default logging state (disabled).
-// It specifies whether logging is enabled by default for new Logger instances in the ll package.
-// Set to false to prevent logging until explicitly enabled.
-const (
- DefaultEnabled = false // Default state for new loggers (disabled)
-)
-
-// Log level constants, ordered by increasing severity.
-// These constants define the severity levels for log messages, used to filter logs based
-// on the logger’s minimum level. They are ordered to allow comparison (e.g., LevelDebug < LevelWarn).
-const (
- LevelNone LevelType = iota // Debug level for detailed diagnostic information
- LevelInfo // Info level for general operational messages
- LevelWarn // Warn level for warning conditions
- LevelError // Error level for error conditions requiring attention
- LevelDebug // None level for logs without a specific severity (e.g., raw output)
- LevelUnknown // None level for logs without a specific severity (e.g., raw output)
-)
-
-// String constants for each level
-const (
- DebugString = "DEBUG"
- InfoString = "INFO"
- WarnString = "WARN"
- ErrorString = "ERROR"
- NoneString = "NONE"
- UnknownString = "UNKNOWN"
-
- TextString = "TEXT"
- JSONString = "JSON"
- DumpString = "DUMP"
- SpecialString = "SPECIAL"
- RawString = "RAW"
-)
-
-// Log class constants, defining the type of log entry.
-// These constants categorize log entries by their content or purpose, influencing how
-// handlers process them (e.g., text, JSON, hex dump).
-const (
- ClassText ClassType = iota // Text entries for standard log messages
- ClassJSON // JSON entries for structured output
- ClassDump // Dump entries for hex/ASCII dumps
- ClassSpecial // Special entries for custom or non-standard logs
- ClassRaw // Raw entries for unformatted output
- ClassUnknown // Raw entries for unformatted output
-)
-
-// Namespace style constants.
-// These constants define how namespace paths are formatted in log output, affecting the
-// visual representation of hierarchical namespaces.
-const (
- FlatPath StyleType = iota // Formats namespaces as [parent/child]
- NestedPath // Formats namespaces as [parent]→[child]
-)
-
-// LevelType represents the severity of a log message.
-// It is an integer type used to define log levels (Debug, Info, Warn, Error, None), with associated
-// string representations for display in log output.
-type LevelType int
-
-// String converts a LevelType to its string representation.
-// It maps each level constant to a human-readable string, returning "UNKNOWN" for invalid levels.
-// Used by handlers to display the log level in output.
-// Example:
-//
-// var level lx.LevelType = lx.LevelInfo
-// fmt.Println(level.String()) // Output: INFO
-func (l LevelType) String() string {
- switch l {
- case LevelDebug:
- return DebugString
- case LevelInfo:
- return InfoString
- case LevelWarn:
- return WarnString
- case LevelError:
- return ErrorString
- case LevelNone:
- return NoneString
- default:
- return UnknownString
- }
-}
-
-// LevelParse converts a string to its corresponding LevelType.
-// It parses a string (case-insensitive) and returns the corresponding LevelType, defaulting to
-// LevelUnknown for unrecognized strings. Supports "WARNING" as an alias for "WARN".
-func LevelParse(s string) LevelType {
- switch strings.ToUpper(s) {
- case DebugString:
- return LevelDebug
- case InfoString:
- return LevelInfo
- case WarnString, "WARNING": // Allow both "WARN" and "WARNING"
- return LevelWarn
- case ErrorString:
- return LevelError
- case NoneString:
- return LevelNone
- default:
- return LevelUnknown
- }
-}
-
-// StyleType defines how namespace paths are formatted in log output.
-// It is an integer type used to select between FlatPath ([parent/child]) and NestedPath
-// ([parent]→[child]) styles, affecting how handlers render namespace hierarchies.
-type StyleType int
-
-// Entry represents a single log entry passed to handlers.
-// It encapsulates all information about a log message, including its timestamp, severity,
-// content, namespace, metadata, and formatting style. Handlers process Entry instances
-// to produce formatted output (e.g., text, JSON). The struct is immutable once created,
-// ensuring thread-safety in handler processing.
-type Entry struct {
- Timestamp time.Time // Time the log was created
- Level LevelType // Severity level of the log (Debug, Info, Warn, Error, None)
- Message string // Log message content
- Namespace string // Namespace path (e.g., "parent/child")
- Fields map[string]interface{} // Additional key-value metadata (e.g., {"user": "alice"})
- Style StyleType // Namespace formatting style (FlatPath or NestedPath)
- Error error // Associated error, if any (e.g., for error logs)
- Class ClassType // Type of log entry (Text, JSON, Dump, Special, Raw)
- Stack []byte // Stack trace data (if present)
- Id int `json:"-"` // Unique ID for the entry, ignored in JSON output
-}
-
-// Handler defines the interface for processing log entries.
-// Implementations (e.g., TextHandler, JSONHandler) format and output log entries to various
-// destinations (e.g., stdout, files). The Handle method returns an error if processing fails,
-// allowing the logger to handle output failures gracefully.
-// Example (simplified handler implementation):
-//
-// type MyHandler struct{}
-// func (h *MyHandler) Handle(e *Entry) error {
-// fmt.Printf("[%s] %s: %s\n", e.Namespace, e.Level.String(), e.Message)
-// return nil
-// }
-type Handler interface {
- Handle(e *Entry) error // Processes a log entry, returning any error
-}
-
-// Timestamper defines an interface for handlers that support timestamp configuration.
-// It includes a method to enable or disable timestamp logging and optionally set the timestamp format.
-type Timestamper interface {
- // Timestamped enables or disables timestamp logging and allows specifying an optional format.
- // Parameters:
- // enable: Boolean to enable or disable timestamp logging
- // format: Optional string(s) to specify the timestamp format
- Timestamped(enable bool, format ...string)
-}
-
-// ClassType represents the type of a log entry.
-// It is an integer type used to categorize log entries (Text, JSON, Dump, Special, Raw),
-// influencing how handlers process and format them.
-type ClassType int
-
-// String converts a ClassType to its string representation.
-// It maps each class constant to a human-readable string, returning "UNKNOWN" for invalid classes.
-// Used by handlers to indicate the entry type in output (e.g., JSON fields).
-// Example:
-//
-// var class lx.ClassType = lx.ClassText
-// fmt.Println(class.String()) // Output: TEST
-func (t ClassType) String() string {
- switch t {
- case ClassText:
- return TextString
- case ClassJSON:
- return JSONString
- case ClassDump:
- return DumpString
- case ClassSpecial:
- return SpecialString
- case ClassRaw:
- return RawString
- default:
- return UnknownString
- }
-}
-
-// ParseClass converts a string to its corresponding ClassType.
-// It parses a string (case-insensitive) and returns the corresponding ClassType, defaulting to
-// ClassUnknown for unrecognized strings.
-func ParseClass(s string) ClassType {
- switch strings.ToUpper(s) {
- case TextString:
- return ClassText
- case JSONString:
- return ClassJSON
- case DumpString:
- return ClassDump
- case SpecialString:
- return ClassSpecial
- case RawString:
- return ClassRaw
- default:
- return ClassUnknown
- }
-}
diff --git a/vendor/github.com/olekukonko/ll/lx/ns.go b/vendor/github.com/olekukonko/ll/lx/ns.go
deleted file mode 100644
index 5d30c93940..0000000000
--- a/vendor/github.com/olekukonko/ll/lx/ns.go
+++ /dev/null
@@ -1,102 +0,0 @@
-package lx
-
-import (
- "strings"
- "sync"
-)
-
-// namespaceRule stores the cached result of Enabled.
-type namespaceRule struct {
- isEnabledByRule bool
- isDisabledByRule bool
-}
-
-// Namespace manages thread-safe namespace enable/disable states with caching.
-// The store holds explicit user-defined rules (path -> bool).
-// The cache holds computed effective states for paths (path -> namespaceRule)
-// based on hierarchical rules to optimize lookups.
-type Namespace struct {
- store sync.Map // path (string) -> rule (bool: true=enable, false=disable)
- cache sync.Map // path (string) -> namespaceRule
-}
-
-// Set defines an explicit enable/disable rule for a namespace path.
-// It clears the cache to ensure subsequent lookups reflect the change.
-func (ns *Namespace) Set(path string, enabled bool) {
- ns.store.Store(path, enabled)
- ns.clearCache()
-}
-
-// Load retrieves an explicit rule from the store for a path.
-// Returns the rule (true=enable, false=disable) and whether it exists.
-// Does not consider hierarchy or caching.
-func (ns *Namespace) Load(path string) (rule interface{}, found bool) {
- return ns.store.Load(path)
-}
-
-// Store directly sets a rule in the store, bypassing cache invalidation.
-// Intended for internal use or sync.Map parity; prefer Set for standard use.
-func (ns *Namespace) Store(path string, rule bool) {
- ns.store.Store(path, rule)
-}
-
-// clearCache clears the cache of Enabled results.
-// Called by Set to ensure consistency after rule changes.
-func (ns *Namespace) clearCache() {
- ns.cache.Range(func(key, _ interface{}) bool {
- ns.cache.Delete(key)
- return true
- })
-}
-
-// Enabled checks if a path is enabled by namespace rules, considering the most
-// specific rule (path or closest prefix) in the store. Results are cached.
-// Args:
-// - path: Absolute namespace path to check.
-// - separator: Character delimiting path segments (e.g., "/", ".").
-//
-// Returns:
-// - isEnabledByRule: True if an explicit rule enables the path.
-// - isDisabledByRule: True if an explicit rule disables the path.
-//
-// If both are false, no explicit rule applies to the path or its prefixes.
-func (ns *Namespace) Enabled(path string, separator string) (isEnabledByRule bool, isDisabledByRule bool) {
- if path == "" { // Root path has no explicit rule
- return false, false
- }
-
- // Check cache
- if cachedValue, found := ns.cache.Load(path); found {
- if state, ok := cachedValue.(namespaceRule); ok {
- return state.isEnabledByRule, state.isDisabledByRule
- }
- ns.cache.Delete(path) // Remove invalid cache entry
- }
-
- // Compute: Most specific rule wins
- parts := strings.Split(path, separator)
- computedIsEnabled := false
- computedIsDisabled := false
-
- for i := len(parts); i >= 1; i-- {
- currentPrefix := strings.Join(parts[:i], separator)
- if val, ok := ns.store.Load(currentPrefix); ok {
- if rule := val.(bool); rule {
- computedIsEnabled = true
- computedIsDisabled = false
- } else {
- computedIsEnabled = false
- computedIsDisabled = true
- }
- break
- }
- }
-
- // Cache result, including (false, false) for no rule
- ns.cache.Store(path, namespaceRule{
- isEnabledByRule: computedIsEnabled,
- isDisabledByRule: computedIsDisabled,
- })
-
- return computedIsEnabled, computedIsDisabled
-}
diff --git a/vendor/github.com/olekukonko/ll/middleware.go b/vendor/github.com/olekukonko/ll/middleware.go
deleted file mode 100644
index 694f75fac2..0000000000
--- a/vendor/github.com/olekukonko/ll/middleware.go
+++ /dev/null
@@ -1,124 +0,0 @@
-package ll
-
-import (
- "github.com/olekukonko/ll/lx"
-)
-
-// Middleware represents a registered middleware and its operations in the logging pipeline.
-// It holds an ID for identification, a reference to the parent logger, and the handler function
-// that processes log entries. Middleware is used to transform or filter log entries before they
-// are passed to the logger's output handler.
-type Middleware struct {
- id int // Unique identifier for the middleware
- logger *Logger // Parent logger instance for context and logging operations
- fn lx.Handler // Handler function that processes log entries
-}
-
-// Remove unregisters the middleware from the logger’s middleware chain.
-// It safely removes the middleware by its ID, ensuring thread-safety with a mutex lock.
-// If the middleware or logger is nil, it returns early to prevent panics.
-// Example usage:
-//
-// // Using a named middleware function
-// mw := logger.Use(authMiddleware)
-// defer mw.Remove()
-//
-// // Using an inline middleware
-// mw = logger.Use(ll.Middle(func(e *lx.Entry) error {
-// if e.Level < lx.LevelWarn {
-// return fmt.Errorf("level too low")
-// }
-// return nil
-// }))
-// defer mw.Remove()
-func (m *Middleware) Remove() {
- // Check for nil middleware or logger to avoid panics
- if m == nil || m.logger == nil {
- return
- }
- // Acquire write lock to modify middleware slice
- m.logger.mu.Lock()
- defer m.logger.mu.Unlock()
- // Iterate through middleware slice to find and remove matching ID
- for i, entry := range m.logger.middleware {
- if entry.id == m.id {
- // Remove middleware by slicing out the matching entry
- m.logger.middleware = append(m.logger.middleware[:i], m.logger.middleware[i+1:]...)
- return
- }
- }
-}
-
-// Logger returns the parent logger for optional chaining.
-// This allows middleware to access the logger for additional operations, such as logging errors
-// or creating derived loggers. It is useful for fluent API patterns.
-// Example:
-//
-// mw := logger.Use(authMiddleware)
-// mw.Logger().Info("Middleware registered")
-func (m *Middleware) Logger() *Logger {
- return m.logger
-}
-
-// Error logs an error message at the Error level if the middleware blocks a log entry.
-// It uses the parent logger to emit the error and returns the middleware for chaining.
-// This is useful for debugging or auditing when middleware rejects a log.
-// Example:
-//
-// mw := logger.Use(ll.Middle(func(e *lx.Entry) error {
-// if e.Level < lx.LevelWarn {
-// return fmt.Errorf("level too low")
-// }
-// return nil
-// }))
-// mw.Error("Rejected low-level log")
-func (m *Middleware) Error(args ...any) *Middleware {
- m.logger.Error(args...)
- return m
-}
-
-// Errorf logs an error message at the Error level if the middleware blocks a log entry.
-// It uses the parent logger to emit the error and returns the middleware for chaining.
-// This is useful for debugging or auditing when middleware rejects a log.
-// Example:
-//
-// mw := logger.Use(ll.Middle(func(e *lx.Entry) error {
-// if e.Level < lx.LevelWarn {
-// return fmt.Errorf("level too low")
-// }
-// return nil
-// }))
-// mw.Errorf("Rejected low-level log")
-func (m *Middleware) Errorf(format string, args ...any) *Middleware {
- m.logger.Errorf(format, args...)
- return m
-}
-
-// middlewareFunc is a function adapter that implements the lx.Handler interface.
-// It allows plain functions with the signature `func(*lx.Entry) error` to be used as middleware.
-// The function should return nil to allow the log to proceed or a non-nil error to reject it,
-// stopping the log from being emitted by the logger.
-type middlewareFunc func(*lx.Entry) error
-
-// Handle implements the lx.Handler interface for middlewareFunc.
-// It calls the underlying function with the log entry and returns its result.
-// This enables seamless integration of function-based middleware into the logging pipeline.
-func (mf middlewareFunc) Handle(e *lx.Entry) error {
- return mf(e)
-}
-
-// Middle creates a middleware handler from a function.
-// It wraps a function with the signature `func(*lx.Entry) error` into a middlewareFunc,
-// allowing it to be used in the logger’s middleware pipeline. A non-nil error returned by
-// the function will stop the log from being emitted, ensuring precise control over logging.
-// Example:
-//
-// logger.Use(ll.Middle(func(e *lx.Entry) error {
-// if e.Level == lx.LevelDebug {
-// return fmt.Errorf("debug logs disabled")
-// }
-// return nil
-// }))
-func Middle(fn func(*lx.Entry) error) lx.Handler {
- return middlewareFunc(fn)
-}
diff --git a/vendor/github.com/olekukonko/tablewriter/.gitignore b/vendor/github.com/olekukonko/tablewriter/.gitignore
deleted file mode 100644
index f79a287565..0000000000
--- a/vendor/github.com/olekukonko/tablewriter/.gitignore
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-# folders
-.idea
-.vscode
-/tmp
-/lab
-dev.sh
-*csv2table
-_test/
diff --git a/vendor/github.com/olekukonko/tablewriter/.golangci.yml b/vendor/github.com/olekukonko/tablewriter/.golangci.yml
deleted file mode 100644
index 5e3cc48c27..0000000000
--- a/vendor/github.com/olekukonko/tablewriter/.golangci.yml
+++ /dev/null
@@ -1,55 +0,0 @@
-# See for configurations: https://golangci-lint.run/usage/configuration/
-version: 2
-# See: https://golangci-lint.run/usage/formatters/
-formatters:
- default: none
- enable:
- - gofmt # https://pkg.go.dev/cmd/gofmt
- - gofumpt # https://github.com/mvdan/gofumpt
-
- settings:
- gofmt:
- simplify: true # Simplify code: gofmt with `-s` option.
-
- gofumpt:
- # Module path which contains the source code being formatted.
- # Default: ""
- module-path: github.com/olekukonko/tablewriter # Should match with module in go.mod
- # Choose whether to use the extra rules.
- # Default: false
- extra-rules: true
-
-# See: https://golangci-lint.run/usage/linters/
-linters:
- default: none
- enable:
- - staticcheck
- - govet
- - gocritic
- # - unused # TODO: There are many unused functions, should I directly remove those ?
- - ineffassign
- - unconvert
- - mirror
- - usestdlibvars
- - loggercheck
- - exptostd
- - godot
- - perfsprint
-
- # See: https://golangci-lint.run/usage/false-positives/
- exclusion:
- # paths:
- # rules:
-
- settings:
- staticcheck:
- checks:
- - all
- - "-SA1019" # disabled because it warns about deprecated: kept for compatibility will be removed soon
- - "-ST1019" # disabled because it warns about deprecated: kept for compatibility will be removed soon
- - "-ST1021" # disabled because it warns to have comment on exported packages
- - "-ST1000" # disabled because it warns to have comment on exported functions
- - "-ST1020" # disabled because it warns to have at least one file in a package should have a package comment
-
- godot:
- period: false
diff --git a/vendor/github.com/olekukonko/tablewriter/LICENSE.md b/vendor/github.com/olekukonko/tablewriter/LICENSE.md
deleted file mode 100644
index a0769b5c15..0000000000
--- a/vendor/github.com/olekukonko/tablewriter/LICENSE.md
+++ /dev/null
@@ -1,19 +0,0 @@
-Copyright (C) 2014 by 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.
diff --git a/vendor/github.com/olekukonko/tablewriter/MIGRATION.md b/vendor/github.com/olekukonko/tablewriter/MIGRATION.md
deleted file mode 100644
index 650a195b64..0000000000
--- a/vendor/github.com/olekukonko/tablewriter/MIGRATION.md
+++ /dev/null
@@ -1,3280 +0,0 @@
-# Migration Guide: tablewriter v0.0.5 to v1.0.x
-> **NOTE:** This document is work in progress, use with `caution`. This document is a comprehensive guide. For specific issues or advanced scenarios, please refer to the source code or open an issue.
-
-The `tablewriter` library has undergone a significant redesign between versions **v0.0.5** and **v1.0.x**, transitioning from a primarily method-driven API to a more robust, modular, and configuration-driven framework. This guide provides a detailed roadmap for migrating your v0.0.5 codebase to v1.0.x. It includes mappings of old methods to new approaches, practical examples, and explanations of new features.
-
-We believe these changes significantly improve the library's flexibility, maintainability, and power, enabling new features and making complex table configurations more manageable.
-
-## Why Migrate to v1.0.x?
-
-The v1.0.x redesign enhances `tablewriter`’s flexibility, maintainability, and feature set:
-- **Extensibility**: Decoupled rendering supports diverse output formats (e.g., HTML, Markdown, CSV).
-- **Robust Configuration**: Centralized `Config` struct and fluent builders ensure atomic, predictable setups.
-- **Streaming Capability**: Dedicated API for row-by-row rendering, ideal for large or real-time datasets.
-- **Type Safety**: Specific types (e.g., `tw.State`, `tw.Align`) reduce errors and improve clarity.
-- **Consistent API**: Unified interface for intuitive usage across simple and complex use cases.
-- **New Features**: Hierarchical merging, granular padding, table captions, and fixed column widths.
-
-These improvements make v1.0.x more powerful, but they require updating code to leverage the new configuration-driven framework and take advantage of advanced functionalities.
-
-## Key New Features in v1.0.x
-
-- **Fluent Configuration Builders**: `NewConfigBuilder()` enables chained, readable setups (`config.go:NewConfigBuilder`).
-- **Centralized Configuration**: `Config` struct governs table behavior and data processing (`config.go:Config`).
-- **Decoupled Renderer**: `tw.Renderer` interface with `tw.Rendition` for visual styling, allowing custom renderers (`tw/renderer.go`).
-- **True Streaming Support**: `Start()`, `Append()`, `Close()` for incremental rendering (`stream.go`).
-- **Hierarchical Cell Merging**: `tw.MergeHierarchical` for complex data structures (`tw/tw.go:MergeMode` constant, logic in `zoo.go`).
-- **Granular Padding Control**: Per-side (`Top`, `Bottom`, `Left`, `Right`) and per-column padding (`tw/cell.go:CellPadding`, `tw/types.go:Padding`).
-- **Enhanced Type System**: `tw.State`, `tw.Align`, `tw.Spot`, and others for clarity and safety (`tw/state.go`, `tw/types.go`).
-- **Comprehensive Error Handling**: Methods like `Render()` and `Append()` return errors (`tablewriter.go`, `stream.go`).
-- **Fixed Column Width System**: `Config.Widths` for precise column sizing, especially in streaming (`config.go:Config`, `tw/cell.go:CellWidth`).
-- **Table Captioning**: Flexible placement and styling with `tw.Caption` (`tw/types.go:Caption`).
-- **Advanced Data Processing**: Support for `tw.Formatter`, per-column filters, and stringer caching (`tw/cell.go:CellFilter`, `tablewriter.go:WithStringer`).
-
-## Core Philosophy Changes in v1.0.x
-
-Understanding these shifts is essential for a successful migration:
-
-1. **Configuration-Driven Approach**:
- - **Old**: Relied on `table.SetXxx()` methods for incremental, stateful modifications to table properties.
- - **New**: Table behavior is defined by a `tablewriter.Config` struct (`config.go:Config`), while visual styling is managed by a `tw.Rendition` struct (`tw/renderer.go:Rendition`). These are typically set at table creation using `NewTable()` with `Option` functions or via a fluent `ConfigBuilder`, ensuring atomic and predictable configuration changes.
-
-2. **Decoupled Rendering Engine**:
- - **Old**: Rendering logic was tightly integrated into the `Table` struct, limiting output flexibility.
- - **New**: The `tw.Renderer` interface (`tw/renderer.go:Renderer`) defines rendering logic, with `renderer.NewBlueprint()` as the default text-based renderer. The renderer’s appearance (e.g., borders, symbols) is controlled by `tw.Rendition`, enabling support for alternative formats like HTML or Markdown.
-
-3. **Unified Section Configuration**:
- - **Old**: Headers, rows, and footers had separate, inconsistent configuration methods.
- - **New**: `tw.CellConfig` (`tw/cell.go:CellConfig`) standardizes configuration across headers, rows, and footers, encompassing formatting (`tw.CellFormatting`), padding (`tw.CellPadding`), column widths (`tw.CellWidth`), alignments (`tw.CellAlignment`), and filters (`tw.CellFilter`).
-
-4. **Fluent Configuration Builders**:
- - **Old**: Configuration was done via individual setters, often requiring multiple method calls.
- - **New**: `tablewriter.NewConfigBuilder()` (`config.go:NewConfigBuilder`) provides a chained, fluent API for constructing `Config` objects, with nested builders for `Header()`, `Row()`, `Footer()`, `Alignment()`, `Behavior()`, and `ForColumn()` to simplify complex setups.
-
-5. **Explicit Streaming Mode**:
- - **Old**: No dedicated streaming support; tables were rendered in batch mode.
- - **New**: Streaming for row-by-row rendering is enabled via `Config.Stream.Enable` or `WithStreaming(tw.StreamConfig{Enable: true})` and managed with `Table.Start()`, `Table.Append()` (or `Table.Header()`, `Table.Footer()`), and `Table.Close()` (`stream.go`). This is ideal for large datasets or continuous output.
-
-6. **Enhanced Error Handling**:
- - **Old**: Methods like `Render()` did not return errors, making error detection difficult.
- - **New**: Key methods (`Render()`, `Start()`, `Close()`, `Append()`, `Bulk()`) return errors to promote robust error handling and improve application reliability (`tablewriter.go`, `stream.go`).
-
-7. **Richer Type System & `tw` Package**:
- - **Old**: Used integer constants (e.g., `ALIGN_CENTER`) and booleans, leading to potential errors.
- - **New**: The `tw` sub-package introduces type-safe constructs like `tw.State` (`tw/state.go`), `tw.Align` (`tw/types.go`), `tw.Position` (`tw/types.go`), and `tw.CellConfig` (`tw/cell.go`), replacing magic constants and enhancing code clarity.
-
-## Configuration Methods in v1.0.x
-
-v1.0.x offers four flexible methods to configure tables, catering to different use cases and complexity levels. Each method can be used independently or combined, providing versatility for both simple and advanced setups.
-
-1. **Using `WithConfig` Option**:
- - **Description**: Pass a fully populated `tablewriter.Config` struct during `NewTable` initialization using the `WithConfig` option.
- - **Use Case**: Ideal for predefined, reusable configurations that can be serialized or shared across multiple tables.
- - **Pros**: Explicit, portable, and suitable for complex setups; allows complete control over all configuration aspects.
- - **Cons**: Verbose for simple changes, requiring manual struct population.
- - **Example**:
-
-```go
-package main
-
-import (
- "github.com/olekukonko/tablewriter"
- "github.com/olekukonko/tablewriter/tw"
- "os"
-)
-
-func main() {
- cfg := tablewriter.Config{
- Header: tw.CellConfig{
- Alignment: tw.CellAlignment{Global: tw.AlignCenter},
- Formatting: tw.CellFormatting{AutoFormat: tw.On},
- },
- Row: tw.CellConfig{
- Alignment: tw.CellAlignment{Global: tw.AlignLeft},
- },
- MaxWidth: 80,
- Behavior: tw.Behavior{TrimSpace: tw.On},
- }
- table := tablewriter.NewTable(os.Stdout, tablewriter.WithConfig(cfg))
- table.Header("Name", "Status")
- table.Append("Node1", "Ready")
- table.Render()
-}
-```
-**Output**:
- ```
-┌───────┬────────┐
-│ NAME │ STATUS │
-├───────┼────────┤
-│ Node1 │ Ready │
-└───────┴────────┘
- ```
-
-2. **Using `Table.Configure` method**:
- - **Description**: After creating a `Table` instance, use the `Configure` method with a function that modifies the table's `Config` struct.
- - **Use Case**: Suitable for quick, ad-hoc tweaks post-initialization, especially for simple or dynamic adjustments.
- - **Pros**: Straightforward for minor changes; no need for additional structs or builders if you already have a `Table` instance.
- - **Cons**: Less readable for complex configurations compared to a builder; modifications are applied to an existing instance.
- - **Example**:
-```go
-package main
-
-import (
- "github.com/olekukonko/tablewriter"
- "github.com/olekukonko/tablewriter/tw"
- "os"
-)
-
-func main() {
- table := tablewriter.NewTable(os.Stdout)
- table.Configure(func(cfg *tablewriter.Config) {
- cfg.Header.Alignment.Global = tw.AlignCenter
- cfg.Row.Alignment.Global = tw.AlignLeft
- })
-
- table.Header("Name", "Status")
- table.Append("Node1", "Ready")
- table.Render()
-}
-```
-**Output**: Same as above.
-
-3. **Standalone `Option` Functions**:
- - **Description**: Use `WithXxx` functions (e.g., `WithHeaderAlignment`, `WithDebug`) during `NewTable` initialization or via `table.Options()` to apply targeted settings.
- - **Use Case**: Best for simple, specific configuration changes without needing a full `Config` struct.
- - **Pros**: Concise, readable, and intuitive for common settings; ideal for minimal setups.
- - **Cons**: Limited for complex, multi-faceted configurations; requires multiple options for extensive changes.
- - **Example**:
-```go
-package main
-
-import (
- "github.com/olekukonko/tablewriter"
- "github.com/olekukonko/tablewriter/tw"
- "os"
-)
-
-func main() {
- table := tablewriter.NewTable(os.Stdout,
- tablewriter.WithHeaderAlignment(tw.AlignCenter),
- tablewriter.WithRowAlignment(tw.AlignLeft),
- tablewriter.WithDebug(true),
- )
- table.Header("Name", "Status")
- table.Append("Node1", "Ready")
- table.Render()
-}
-```
- **Output**: Same as above.
-
-4. **Fluent `ConfigBuilder`**:
- - **Description**: Use `tablewriter.NewConfigBuilder()` to construct a `Config` struct through a chained, fluent API, then apply it with `WithConfig(builder.Build())`.
- - **Use Case**: Optimal for complex, dynamic, or programmatically generated configurations requiring fine-grained control.
- - **Pros**: Highly readable, maintainable, and expressive; supports nested builders for sections and columns.
- - **Cons**: Slightly verbose; requires understanding builder methods and `Build()` call.
- - **Example**:
-```go
-package main
-
-import (
- "github.com/olekukonko/tablewriter"
- "github.com/olekukonko/tablewriter/tw"
- "os"
-)
-
-func main() {
- cnfBuilder := tablewriter.NewConfigBuilder()
- cnfBuilder.Header().Alignment().WithGlobal(tw.AlignCenter)
- // Example of configuring a specific column (less emphasis on this for now)
- // cnfBuilder.ForColumn(0).WithAlignment(tw.AlignLeft).Build() // Call Build() to return to ConfigBuilder
-
- table := tablewriter.NewTable(os.Stdout, tablewriter.WithConfig(cnfBuilder.Build()))
- table.Header("Name", "Status")
- table.Append("Node1", "Ready")
- table.Render()
-}
-```
-**Output**: Same as above.
-
-**Best Practices**:
-- Use `WithConfig` or `ConfigBuilder` for complex setups or reusable configurations.
-- Opt for `Option` functions for simple, targeted changes.
-- Use `Table.Configure` for direct modifications after table creation, but avoid changes during rendering.
-- Combine methods as needed (e.g., `ConfigBuilder` for initial setup, `Option` functions for overrides).
-
-## Default Parameters in v1.0.x
-
-The `defaultConfig()` function (`config.go:defaultConfig`) establishes baseline settings for new tables, ensuring predictable behavior unless overridden. Below is a detailed table of default parameters, organized by configuration section, to help you understand the starting point for table behavior and appearance.
-
-| Section | Parameter | Default Value | Description |
-|---------------|-------------------------------|-----------------------------------|-----------------------------------------------------------------------------|
-| **Header** | `Alignment.Global` | `tw.AlignCenter` | Centers header text globally unless overridden by `PerColumn`. |
-| Header | `Alignment.PerColumn` | `[]tw.Align{}` | Empty; falls back to `Global` unless specified. |
-| Header | `Formatting.AutoFormat` | `tw.On` | Applies title case (e.g., "col_one" → "COL ONE") to header content. |
-| Header | `Formatting.AutoWrap` | `tw.WrapTruncate` | Truncates long header text with "…" based on width constraints. |
-| Header | `Formatting.MergeMode` | `tw.MergeNone` | Disables cell merging in headers by default. |
-| Header | `Padding.Global` | `tw.PaddingDefault` (`" "`) | Adds one space on left and right of header cells. |
-| Header | `Padding.PerColumn` | `[]tw.Padding{}` | Empty; falls back to `Global` unless specified. |
-| Header | `ColMaxWidths.Global` | `0` (unlimited) | No maximum content width for header cells unless set. |
-| Header | `ColMaxWidths.PerColumn` | `tw.NewMapper[int, int]()` | Empty map; no per-column content width limits unless specified. |
-| Header | `Filter.Global` | `nil` | No global content transformation for header cells. |
-| Header | `Filter.PerColumn` | `[]func(string) string{}` | No per-column content transformations unless specified. |
-| **Row** | `Alignment.Global` | `tw.AlignLeft` | Left-aligns row text globally unless overridden by `PerColumn`. |
-| Row | `Alignment.PerColumn` | `[]tw.Align{}` | Empty; falls back to `Global`. |
-| Row | `Formatting.AutoFormat` | `tw.Off` | Disables auto-formatting (e.g., title case) for row content. |
-| Row | `Formatting.AutoWrap` | `tw.WrapNormal` | Wraps long row text naturally at word boundaries based on width constraints.|
-| Row | `Formatting.MergeMode` | `tw.MergeNone` | Disables cell merging in rows by default. |
-| Row | `Padding.Global` | `tw.PaddingDefault` (`" "`) | Adds one space on left and right of row cells. |
-| Row | `Padding.PerColumn` | `[]tw.Padding{}` | Empty; falls back to `Global`. |
-| Row | `ColMaxWidths.Global` | `0` (unlimited) | No maximum content width for row cells. |
-| Row | `ColMaxWidths.PerColumn` | `tw.NewMapper[int, int]()` | Empty map; no per-column content width limits. |
-| Row | `Filter.Global` | `nil` | No global content transformation for row cells. |
-| Row | `Filter.PerColumn` | `[]func(string) string{}` | No per-column content transformations. |
-| **Footer** | `Alignment.Global` | `tw.AlignRight` | Right-aligns footer text globally unless overridden by `PerColumn`. |
-| Footer | `Alignment.PerColumn` | `[]tw.Align{}` | Empty; falls back to `Global`. |
-| Footer | `Formatting.AutoFormat` | `tw.Off` | Disables auto-formatting for footer content. |
-| Footer | `Formatting.AutoWrap` | `tw.WrapNormal` | Wraps long footer text naturally. |
-| Footer | `Formatting.MergeMode` | `tw.MergeNone` | Disables cell merging in footers. |
-| Footer | `Padding.Global` | `tw.PaddingDefault` (`" "`) | Adds one space on left and right of footer cells. |
-| Footer | `Padding.PerColumn` | `[]tw.Padding{}` | Empty; falls back to `Global`. |
-| Footer | `ColMaxWidths.Global` | `0` (unlimited) | No maximum content width for footer cells. |
-| Footer | `ColMaxWidths.PerColumn` | `tw.NewMapper[int, int]()` | Empty map; no per-column content width limits. |
-| Footer | `Filter.Global` | `nil` | No global content transformation for footer cells. |
-| Footer | `Filter.PerColumn` | `[]func(string) string{}` | No per-column content transformations. |
-| **Global** | `MaxWidth` | `0` (unlimited) | No overall table width limit. |
-| Global | `Behavior.AutoHide` | `tw.Off` | Displays empty columns (ignored in streaming). |
-| Global | `Behavior.TrimSpace` | `tw.On` | Trims leading/trailing spaces from cell content. |
-| Global | `Behavior.Header` | `tw.Control{Hide: tw.Off}` | Shows header if content is provided. |
-| Global | `Behavior.Footer` | `tw.Control{Hide: tw.Off}` | Shows footer if content is provided. |
-| Global | `Behavior.Compact` | `tw.Compact{Merge: tw.Off}` | No compact width optimization for merged cells. |
-| Global | `Debug` | `false` | Disables debug logging. |
-| Global | `Stream.Enable` | `false` | Disables streaming mode by default. |
-| Global | `Widths.Global` | `0` (unlimited) | No fixed column width unless specified. |
-| Global | `Widths.PerColumn` | `tw.NewMapper[int, int]()` | Empty map; no per-column fixed widths unless specified. |
-
-**Notes**:
-- Defaults can be overridden using any configuration method.
-- `tw.PaddingDefault` is `{Left: " ", Right: " "}` (`tw/preset.go`).
-- Alignment within `tw.CellFormatting` is deprecated; `tw.CellAlignment` is preferred. `tw.AlignDefault` falls back to `Global` or `tw.AlignLeft` (`tw/types.go`).
-- Streaming mode uses `Widths` for fixed column sizing (`stream.go`).
-
-## Renderer Types and Customization
-
-v1.0.x introduces a flexible rendering system via the `tw.Renderer` interface (`tw/renderer.go:Renderer`), allowing for both default text-based rendering and custom output formats. This decouples rendering logic from table data processing, enabling support for diverse formats like HTML, CSV, or JSON.
-
-### Default Renderer: `renderer.NewBlueprint`
-- **Description**: `renderer.NewBlueprint()` creates a text-based renderer. Its visual styles are configured using `tw.Rendition`.
-- **Use Case**: Standard terminal or text output with configurable borders, symbols, and separators.
-- **Configuration**: Styled via `tw.Rendition`, which controls:
- - `Borders`: Outer table borders (`tw.Border`) with `tw.State` for each side (`tw/renderer.go`).
- - `Settings`: Internal lines (`tw.Lines`) and separators (`tw.Separators`) (`tw/renderer.go`).
- - `Symbols`: Characters for drawing table lines and junctions (`tw.Symbols`) (`tw/symbols.go`).
- - **Example**:
-```go
-package main
-
-import (
- "github.com/olekukonko/tablewriter"
- "github.com/olekukonko/tablewriter/renderer" // Import the renderer package
- "github.com/olekukonko/tablewriter/tw"
- "os"
-)
-
-func main() {
- table := tablewriter.NewTable(os.Stdout,
- tablewriter.WithRenderer(renderer.NewBlueprint()), // Default Blueprint renderer
- tablewriter.WithRendition(tw.Rendition{ // Apply custom rendition
- Symbols: tw.NewSymbols(tw.StyleRounded),
- Borders: tw.Border{Top: tw.On, Bottom: tw.On, Left: tw.On, Right: tw.On},
- Settings: tw.Settings{
- Separators: tw.Separators{BetweenRows: tw.On},
- Lines: tw.Lines{ShowHeaderLine: tw.On},
- },
- }),
- )
- table.Header("Name", "Status")
- table.Append("Node1", "Ready")
- table.Render()
-}
-```
-**Output**:
- ```
- ╭───────┬────────╮
- │ Name │ Status │
- ├───────┼────────┤
- │ Node1 │ Ready │
- ╰───────┴────────╯
- ```
-
-### Custom Renderer Implementation
-- **Description**: Implement the `tw.Renderer` interface to create custom output formats (e.g., HTML, CSV).
-- **Use Case**: Non-text outputs, specialized formatting, or integration with other systems.
-- **Interface Methods**:
- - `Start(w io.Writer) error`: Initializes rendering.
- - `Header(headers [][]string, ctx tw.Formatting)`: Renders header rows.
- - `Row(row []string, ctx tw.Formatting)`: Renders a data row.
- - `Footer(footers [][]string, ctx tw.Formatting)`: Renders footer rows.
- - `Line(ctx tw.Formatting)`: Renders separator lines.
- - `Close() error`: Finalizes rendering.
- - `Config() tw.Rendition`: Returns renderer's current rendition configuration.
- - `Logger(logger *ll.Logger)`: Sets logger for debugging.
- - **Example (HTML Renderer)**:
-
-```go
-package main
-
-import (
- "fmt"
- "github.com/olekukonko/ll" // For logger type
- "github.com/olekukonko/tablewriter"
- "github.com/olekukonko/tablewriter/tw"
- "io"
- "os"
-)
-
-// BasicHTMLRenderer implements tw.Renderer
-type BasicHTMLRenderer struct {
- writer io.Writer
- config tw.Rendition // Store the rendition
- logger *ll.Logger
- err error
-}
-
-func (r *BasicHTMLRenderer) Start(w io.Writer) error {
- r.writer = w
- _, r.err = r.writer.Write([]byte("
\n"))
- if r.err != nil { return }
- // Iterate over cells from the context for the current line
- for _, cellCtx := range ctx.Row.Current {
- content := fmt.Sprintf("
\n"))
-}
-
-// Row expects []string for a single line row, but uses ctx for actual data
-func (r *BasicHTMLRenderer) Row(row []string, ctx tw.Formatting) { // row param is less relevant here, ctx.Row.Current is key
- if r.err != nil { return }
- _, r.err = r.writer.Write([]byte("
\n"))
- if r.err != nil { return }
- for _, cellCtx := range ctx.Row.Current {
- content := fmt.Sprintf("
\n"))
-}
-
-func (r *BasicHTMLRenderer) Footer(footers [][]string, ctx tw.Formatting) {
- if r.err != nil { return }
- // Similar to Header/Row, using ctx.Row.Current for the footer line data
- // The footers [][]string param might be used if the renderer needs multi-line footer logic
- r.Row(nil, ctx) // Reusing Row logic, passing nil for the direct row []string as ctx contains the data
-}
-
-func (r *BasicHTMLRenderer) Line(ctx tw.Formatting) { /* No lines in basic HTML */ }
-
-func (r *BasicHTMLRenderer) Close() error {
- if r.err != nil {
- return r.err
- }
- _, r.err = r.writer.Write([]byte("
-```
-
-
-#### Custom Invoice Renderer
-
-```go
-
-package main
-
-import (
- "fmt"
- "io"
- "os"
- "strings"
-
- "github.com/olekukonko/ll"
- "github.com/olekukonko/tablewriter"
- "github.com/olekukonko/tablewriter/tw"
-)
-
-// InvoiceRenderer implements tw.Renderer for a basic invoice style.
-type InvoiceRenderer struct {
- writer io.Writer
- logger *ll.Logger
- rendition tw.Rendition
-}
-
-func NewInvoiceRenderer() *InvoiceRenderer {
- rendition := tw.Rendition{
- Borders: tw.BorderNone,
- Symbols: tw.NewSymbols(tw.StyleNone),
- Settings: tw.Settings{Separators: tw.SeparatorsNone, Lines: tw.LinesNone},
- Streaming: false,
- }
- defaultLogger := ll.New("simple-invoice-renderer")
- return &InvoiceRenderer{logger: defaultLogger, rendition: rendition}
-}
-
-func (r *InvoiceRenderer) Logger(logger *ll.Logger) {
- if logger != nil {
- r.logger = logger
- }
-}
-
-func (r *InvoiceRenderer) Config() tw.Rendition {
- return r.rendition
-}
-
-func (r *InvoiceRenderer) Start(w io.Writer) error {
- r.writer = w
- r.logger.Debug("InvoiceRenderer: Start")
- return nil
-}
-
-func (r *InvoiceRenderer) formatLine(cells []string, widths tw.Mapper[int, int], cellContexts map[int]tw.CellContext) string {
- var sb strings.Builder
- numCols := 0
- if widths != nil { // Ensure widths is not nil before calling Len
- numCols = widths.Len()
- }
-
- for i := 0; i < numCols; i++ {
- data := ""
- if i < len(cells) {
- data = cells[i]
- }
-
- width := 0
- if widths != nil { // Check again before Get
- width = widths.Get(i)
- }
-
- align := tw.AlignDefault
- if cellContexts != nil { // Check cellContexts
- if ctx, ok := cellContexts[i]; ok {
- align = ctx.Align
- }
- }
-
- paddedCell := tw.Pad(data, " ", width, align)
- sb.WriteString(paddedCell)
-
- if i < numCols-1 {
- sb.WriteString(" ") // Column separator
- }
- }
- return sb.String()
-}
-
-func (r *InvoiceRenderer) Header(headers [][]string, ctx tw.Formatting) {
- if r.writer == nil {
- return
- }
- r.logger.Debugf("InvoiceRenderer: Header (lines: %d)", len(headers))
-
- for _, headerLineCells := range headers {
- lineStr := r.formatLine(headerLineCells, ctx.Row.Widths, ctx.Row.Current)
- fmt.Fprintln(r.writer, lineStr)
- }
-
- if len(headers) > 0 {
- totalWidth := 0
- if ctx.Row.Widths != nil {
- ctx.Row.Widths.Each(func(_ int, w int) { totalWidth += w })
- if ctx.Row.Widths.Len() > 1 {
- totalWidth += (ctx.Row.Widths.Len() - 1) * 3 // Separator spaces
- }
- }
- if totalWidth > 0 {
- fmt.Fprintln(r.writer, strings.Repeat("-", totalWidth))
- }
- }
-}
-
-func (r *InvoiceRenderer) Row(row []string, ctx tw.Formatting) {
- if r.writer == nil {
- return
- }
- r.logger.Debug("InvoiceRenderer: Row")
- lineStr := r.formatLine(row, ctx.Row.Widths, ctx.Row.Current)
- fmt.Fprintln(r.writer, lineStr)
-}
-
-func (r *InvoiceRenderer) Footer(footers [][]string, ctx tw.Formatting) {
- if r.writer == nil {
- return
- }
- r.logger.Debugf("InvoiceRenderer: Footer (lines: %d)", len(footers))
-
- if len(footers) > 0 {
- totalWidth := 0
- if ctx.Row.Widths != nil {
- ctx.Row.Widths.Each(func(_ int, w int) { totalWidth += w })
- if ctx.Row.Widths.Len() > 1 {
- totalWidth += (ctx.Row.Widths.Len() - 1) * 3
- }
- }
- if totalWidth > 0 {
- fmt.Fprintln(r.writer, strings.Repeat("-", totalWidth))
- }
- }
-
- for _, footerLineCells := range footers {
- lineStr := r.formatLine(footerLineCells, ctx.Row.Widths, ctx.Row.Current)
- fmt.Fprintln(r.writer, lineStr)
- }
-}
-
-func (r *InvoiceRenderer) Line(ctx tw.Formatting) {
- r.logger.Debug("InvoiceRenderer: Line (no-op)")
- // This simple renderer draws its own lines in Header/Footer.
-}
-
-func (r *InvoiceRenderer) Close() error {
- r.logger.Debug("InvoiceRenderer: Close")
- r.writer = nil
- return nil
-}
-
-func main() {
- data := [][]string{
- {"Product A", "2", "10.00", "20.00"},
- {"Super Long Product Name B", "1", "125.50", "125.50"},
- {"Item C", "10", "1.99", "19.90"},
- }
-
- header := []string{"Description", "Qty", "Unit Price", "Total Price"}
- footer := []string{"", "", "Subtotal:\nTax (10%):\nGRAND TOTAL:", "165.40\n16.54\n181.94"}
- invoiceRenderer := NewInvoiceRenderer()
-
- // Create table and set custom renderer using Options
- table := tablewriter.NewTable(os.Stdout,
- tablewriter.WithRenderer(invoiceRenderer),
- tablewriter.WithAlignment([]tw.Align{
- tw.AlignLeft, tw.AlignCenter, tw.AlignRight, tw.AlignRight,
- }),
- )
-
- table.Header(header)
- for _, v := range data {
- table.Append(v)
- }
-
- // Use the Footer method with strings containing newlines for multi-line cells
- table.Footer(footer)
-
- fmt.Println("Rendering with InvoiceRenderer:")
- table.Render()
-
- // For comparison, render with default Blueprint renderer
- // Re-create the table or reset it to use a different renderer
- table2 := tablewriter.NewTable(os.Stdout,
- tablewriter.WithAlignment([]tw.Align{
- tw.AlignLeft, tw.AlignCenter, tw.AlignRight, tw.AlignRight,
- }),
- )
-
- table2.Header(header)
- for _, v := range data {
- table2.Append(v)
- }
- table2.Footer(footer)
- fmt.Println("\nRendering with Default Blueprint Renderer (for comparison):")
- table2.Render()
-}
-
-```
-
-
-```
-Rendering with InvoiceRenderer:
-DESCRIPTION QTY UNIT PRICE TOTAL PRICE
---------------------------------------------------------------------
-Product A 2 10.00 20.00
-Super Long Product Name B 1 125.50 125.50
-Item C 10 1.99 19.90
---------------------------------------------------------------------
- Subtotal: 165.40
---------------------------------------------------------------------
- Tax (10%): 16.54
---------------------------------------------------------------------
- GRAND TOTAL: 181.94
-```
-
-
-```
-Rendering with Default Blueprint Renderer (for comparison):
-┌───────────────────────────┬─────┬──────────────┬─────────────┐
-│ DESCRIPTION │ QTY │ UNIT PRICE │ TOTAL PRICE │
-├───────────────────────────┼─────┼──────────────┼─────────────┤
-│ Product A │ 2 │ 10.00 │ 20.00 │
-│ Super Long Product Name B │ 1 │ 125.50 │ 125.50 │
-│ Item C │ 10 │ 1.99 │ 19.90 │
-├───────────────────────────┼─────┼──────────────┼─────────────┤
-│ │ │ Subtotal: │ 165.40 │
-│ │ │ Tax (10%): │ 16.54 │
-│ │ │ GRAND TOTAL: │ 181.94 │
-└───────────────────────────┴─────┴──────────────┴─────────────┘
-
-```
-**Notes**:
-- The `renderer.NewBlueprint()` is sufficient for most text-based use cases.
-- Custom renderers require implementing all interface methods to handle table structure correctly. `tw.Formatting` (which includes `tw.RowContext`) provides cell content and metadata.
-
-## Function Mapping Table (v0.0.5 → v1.0.x)
-
-The following table maps v0.0.5 methods to their v1.0.x equivalents, ensuring a quick reference for migration. All deprecated methods are retained for compatibility but should be replaced with new APIs.
-
-| v0.0.5 Method | v1.0.x Equivalent(s) | Notes |
-|-----------------------------------|--------------------------------------------------------------------------------------|----------------------------------------------------------------------|
-| `NewWriter(w)` | `NewTable(w, opts...)` | `NewWriter` deprecated; wraps `NewTable` (`tablewriter.go`). |
-| `SetHeader([]string)` | `Header(...any)` | Variadic or slice; supports any type (`tablewriter.go`). |
-| `Append([]string)` | `Append(...any)` | Variadic, slice, or struct (`tablewriter.go`). |
-| `AppendBulk([][]string)` | `Bulk([]any)` | Slice of rows; supports any type (`tablewriter.go`). |
-| `SetFooter([]string)` | `Footer(...any)` | Variadic or slice; supports any type (`tablewriter.go`). |
-| `Render()` | `Render()` (returns `error`) | Batch mode; streaming uses `Start/Close` (`tablewriter.go`). |
-| `SetBorder(bool)` | `WithRendition(tw.Rendition{Borders: ...})` or `WithBorders(tw.Border)` (deprecated) | Use `tw.Border` (`deprecated.go`, `tw/renderer.go`). |
-| `SetRowLine(bool)` | `WithRendition(tw.Rendition{Settings: {Separators: {BetweenRows: tw.On}}})` | `tw.Separators` (`tw/renderer.go`). |
-| `SetHeaderLine(bool)` | `WithRendition(tw.Rendition{Settings: {Lines: {ShowHeaderLine: tw.On}}})` | `tw.Lines` (`tw/renderer.go`). |
-| `SetColumnSeparator(string)` | `WithRendition(tw.Rendition{Symbols: ...})` or `WithSymbols(tw.Symbols)` (deprecated)| `tw.NewSymbols` or custom `tw.Symbols` (`tw/symbols.go`). |
-| `SetCenterSeparator(string)` | `WithRendition(tw.Rendition{Symbols: ...})` or `WithSymbols(tw.Symbols)` (deprecated)| `tw.Symbols` (`tw/symbols.go`). |
-| `SetRowSeparator(string)` | `WithRendition(tw.Rendition{Symbols: ...})` or `WithSymbols(tw.Symbols)` (deprecated)| `tw.Symbols` (`tw/symbols.go`). |
-| `SetAlignment(int)` | `WithRowAlignment(tw.Align)` or `Config.Row.Alignment.Global` | `tw.Align` type (`config.go`). |
-| `SetHeaderAlignment(int)` | `WithHeaderAlignment(tw.Align)` or `Config.Header.Alignment.Global` | `tw.Align` type (`config.go`). |
-| `SetAutoFormatHeaders(bool)` | `WithHeaderAutoFormat(tw.State)` or `Config.Header.Formatting.AutoFormat` | `tw.State` (`config.go`). |
-| `SetAutoWrapText(bool)` | `WithRowAutoWrap(int)` or `Config.Row.Formatting.AutoWrap` (uses `tw.Wrap...` const) | `tw.WrapNormal`, `tw.WrapTruncate`, etc. (`config.go`). |
-| `SetAutoMergeCells(bool)` | `WithRowMergeMode(int)` or `Config.Row.Formatting.MergeMode` (uses `tw.Merge...` const) | Supports `Vertical`, `Hierarchical` (`config.go`). |
-| `SetColMinWidth(col, w)` | `WithColumnWidths(tw.NewMapper[int,int]().Set(col, w))` or `Config.Widths.PerColumn` | `Config.Widths` for fixed widths (`config.go`). |
-| `SetTablePadding(string)` | Use `Config..Padding.Global` with `tw.Padding` | No direct equivalent; manage via cell padding (`tw/cell.go`). |
-| `SetDebug(bool)` | `WithDebug(bool)` or `Config.Debug` | Logs via `table.Debug()` (`config.go`). |
-| `Clear()` | `Reset()` | Clears data and state (`tablewriter.go`). |
-| `SetNoWhiteSpace(bool)` | `WithTrimSpace(tw.Off)` (if `true`) or `WithPadding(tw.PaddingNone)` | Manage via `Config.Behavior.TrimSpace` or padding (`config.go`). |
-| `SetColumnColor(Colors)` | Embed ANSI codes in cell data, use `tw.Formatter`, or `Config..Filter` | Colors via data or filters (`tw/cell.go`). |
-| `SetHeaderColor(Colors)` | Embed ANSI codes in cell data, use `tw.Formatter`, or `Config.Header.Filter` | Colors via data or filters (`tw/cell.go`). |
-| `SetFooterColor(Colors)` | Embed ANSI codes in cell data, use `tw.Formatter`, or `Config.Footer.Filter` | Colors via data or filters (`tw/cell.go`). |
-
-**Notes**:
-- Deprecated methods are in `deprecated.go` but should be replaced.
-- `tw` package types (e.g., `tw.Align`, `tw.State`) are required for new APIs.
-- Examples for each mapping are provided in the migration steps below.
-
-## Detailed Migration Steps: Initialization, Data Input, Rendering
-
-This section provides step-by-step guidance for migrating core table operations, with code examples and explanations to ensure clarity. Each step maps v0.0.5 methods to v1.0.x equivalents, highlighting changes, best practices, and potential pitfalls.
-
-### 1. Table Initialization
-Initialization has shifted from `NewWriter` to `NewTable`, which supports flexible configuration via `Option` functions, `Config` structs, or `ConfigBuilder`.
-
-**Old (v0.0.5):**
-```go
-package main
-
-import (
- "github.com/olekukonko/tablewriter"
- "os"
-)
-
-func main() {
- table := tablewriter.NewWriter(os.Stdout)
- // ... use table
- _ = table // Avoid "declared but not used"
-}
-```
-
-**New (v1.0.x):**
-```go
-package main
-
-import (
- "fmt" // Added for FormattableEntry example
- "github.com/olekukonko/tablewriter"
- "github.com/olekukonko/tablewriter/renderer" // Import renderer
- "github.com/olekukonko/tablewriter/tw"
- "os"
- "strings" // Added for Formatter example
-)
-
-func main() {
- // Minimal Setup (Default Configuration)
- tableMinimal := tablewriter.NewTable(os.Stdout)
- _ = tableMinimal // Avoid "declared but not used"
-
- // Using Option Functions for Targeted Configuration
- tableWithOptions := tablewriter.NewTable(os.Stdout,
- tablewriter.WithHeaderAlignment(tw.AlignCenter), // Center header text
- tablewriter.WithRowAlignment(tw.AlignLeft), // Left-align row text
- tablewriter.WithDebug(true), // Enable debug logging
- )
- _ = tableWithOptions // Avoid "declared but not used"
-
- // Using a Full Config Struct for Comprehensive Control
- cfg := tablewriter.Config{
- Header: tw.CellConfig{
- Alignment: tw.CellAlignment{
- Global: tw.AlignCenter,
- PerColumn: []tw.Align{tw.AlignLeft, tw.AlignRight}, // Column-specific alignment
- },
- Formatting: tw.CellFormatting{AutoFormat: tw.On},
- },
- Row: tw.CellConfig{
- Alignment: tw.CellAlignment{Global: tw.AlignLeft},
- Formatting: tw.CellFormatting{AutoWrap: tw.WrapNormal},
- },
- Footer: tw.CellConfig{
- Alignment: tw.CellAlignment{Global: tw.AlignRight},
- },
- MaxWidth: 80, // Constrain total table width
- Behavior: tw.Behavior{
- AutoHide: tw.Off, // Show empty columns
- TrimSpace: tw.On, // Trim cell spaces
- },
- Widths: tw.CellWidth{
- Global: 20, // Default fixed column width
- PerColumn: tw.NewMapper[int, int]().Set(0, 15), // Column 0 fixed at 15
- },
- }
- tableWithConfig := tablewriter.NewTable(os.Stdout, tablewriter.WithConfig(cfg))
- _ = tableWithConfig // Avoid "declared but not used"
-
- // Using ConfigBuilder for Fluent, Complex Configuration
- builder := tablewriter.NewConfigBuilder().
- WithMaxWidth(80).
- WithAutoHide(tw.Off).
- WithTrimSpace(tw.On).
- WithDebug(true). // Enable debug logging
- Header().
- Alignment().
- WithGlobal(tw.AlignCenter).
- Build(). // Returns *ConfigBuilder
- Header().
- Formatting().
- WithAutoFormat(tw.On).
- WithAutoWrap(tw.WrapTruncate). // Test truncation
- WithMergeMode(tw.MergeNone). // Explicit merge mode
- Build(). // Returns *HeaderConfigBuilder
- Padding().
- WithGlobal(tw.Padding{Left: "[", Right: "]", Overwrite: true}).
- Build(). // Returns *HeaderConfigBuilder
- Build(). // Returns *ConfigBuilder
- Row().
- Formatting().
- WithAutoFormat(tw.On). // Uppercase rows
- Build(). // Returns *RowConfigBuilder
- Build(). // Returns *ConfigBuilder
- Row().
- Alignment().
- WithGlobal(tw.AlignLeft).
- Build(). // Returns *ConfigBuilder
- Build() // Finalize Config
-
- tableWithFluent := tablewriter.NewTable(os.Stdout, tablewriter.WithConfig(builder))
- _ = tableWithFluent // Avoid "declared but not used"
-}
-```
-
-**Key Changes**:
-- **Deprecation**: `NewWriter` is deprecated but retained for compatibility, internally calling `NewTable` (`tablewriter.go:NewWriter`). Transition to `NewTable` for new code.
-- **Flexibility**: `NewTable` accepts an `io.Writer` and variadic `Option` functions (`tablewriter.go:NewTable`), enabling configuration via:
- - `WithConfig(Config)`: Applies a complete `Config` struct (`config.go:WithConfig`).
- - `WithRenderer(tw.Renderer)`: Sets a custom renderer, defaulting to `renderer.NewBlueprint()` (`tablewriter.go:WithRenderer`).
- - `WithRendition(tw.Rendition)`: Configures visual styles for the renderer (`tablewriter.go:WithRendition`).
- - `WithStreaming(tw.StreamConfig)`: Enables streaming mode (`tablewriter.go:WithStreaming`).
- - Other `WithXxx` functions for specific settings (e.g., `WithHeaderAlignment`, `WithDebug`).
-- **ConfigBuilder**: Provides a fluent API for complex setups; `Build()` finalizes the `Config` (`config.go:ConfigBuilder`).
-- **Method Chaining in Builder**: Remember to call `Build()` on nested builders to return to the parent builder (e.g., `builder.Header().Alignment().WithGlobal(...).Build().Formatting()...`).
-
-**Migration Tips**:
-- Replace `NewWriter` with `NewTable` and specify configurations explicitly.
-- Use `ConfigBuilder` for complex setups or when readability is paramount.
-- Apply `Option` functions for quick, targeted changes.
-- Ensure `tw` package is imported for types like `tw.Align` and `tw.CellConfig`.
-- Verify renderer settings if custom styling is needed, as `renderer.NewBlueprint()` is the default.
-
-**Potential Pitfalls**:
-- **Unintended Defaults**: Without explicit configuration, `defaultConfig()` applies (see Default Parameters), which may differ from v0.0.5 behavior (e.g., `Header.Formatting.AutoFormat = tw.On`).
-- **Renderer Absence**: If no renderer is set, `NewTable` defaults to `renderer.NewBlueprint()`; explicitly set for custom formats.
-- **ConfigBuilder Errors**: Always call `Build()` at the end of a builder chain and on nested builders; omitting it can lead to incomplete configurations or runtime errors.
-- **Concurrent Modification**: Avoid modifying `Table.config` (if using the `Configure` method or direct access) in concurrent scenarios or during rendering to prevent race conditions.
-
-### 2. Data Input
-Data input methods in v1.0.x are more flexible, accepting `any` type for headers, rows, and footers, with robust conversion logic to handle strings, structs, and custom types.
-
-#### 2.1. Setting Headers
-Headers define the table’s column labels and are typically the first data added.
-
-**Old (v0.0.5):**
-```go
-package main
-
-import (
- "github.com/olekukonko/tablewriter"
- "os"
-)
-
-func main() {
- table := tablewriter.NewWriter(os.Stdout)
- table.SetHeader([]string{"Name", "Sign", "Rating"})
- // ...
-}
-```
-
-**New (v1.0.x):**
-```go
-package main
-
-import (
- "fmt"
- "github.com/olekukonko/tablewriter"
- "github.com/olekukonko/tablewriter/tw" // For tw.Formatter
- "os"
- "strings"
-)
-
-// Struct with Formatter
-type HeaderData struct { // Renamed to avoid conflict
- Label string
-}
-
-func (h HeaderData) Format() string { return strings.ToUpper(h.Label) } // Implements tw.Formatter
-
-func main() {
- table := tablewriter.NewTable(os.Stdout)
-
- // Variadic Arguments (Preferred for Simplicity)
- table.Header("Name", "Sign", "Rating")
-
- // Slice of Strings
- // table.Header([]string{"Name", "Sign", "Rating"}) // Example, comment out if using variadic
-
- // Slice of Any Type (Flexible)
- // table.Header([]any{"Name", "Sign", 123}) // Numbers converted to strings
-
- // Using Formatter
- // table.Header(HeaderData{"name"}, HeaderData{"sign"}, HeaderData{"rating"}) // Outputs "NAME", "SIGN", "RATING"
-
- table.Append("Example", "Row", "Data") // Add some data to render
- table.Render()
-}
-```
-
-**Key Changes**:
-- **Method**: `SetHeader([]string)` replaced by `Header(...any)` (`tablewriter.go:Header`).
-- **Flexibility**: Accepts variadic arguments or a single slice; supports any type, not just strings.
-- **Conversion**: Elements are processed by `processVariadic` (`zoo.go:processVariadic`) and converted to strings via `convertCellsToStrings` (`zoo.go:convertCellsToStrings`), supporting:
- - Basic types (e.g., `string`, `int`, `float64`).
- - `fmt.Stringer` implementations.
- - `tw.Formatter` implementations (custom string conversion).
- - Structs (exported fields as cells or single cell if `Stringer`/`Formatter`).
-- **Streaming**: In streaming mode, `Header()` renders immediately via `streamRenderHeader` (`stream.go:streamRenderHeader`).
-- **Formatting**: Headers are formatted per `Config.Header` settings (e.g., `AutoFormat`, `Alignment`) during rendering (`zoo.go:prepareContent`).
-
-**Migration Tips**:
-- Replace `SetHeader` with `Header`, using variadic arguments for simplicity.
-- Use slices for dynamic header lists or when passing from a variable.
-- Implement `tw.Formatter` for custom header formatting (e.g., uppercase).
-- Ensure header count matches expected columns to avoid width mismatches.
-- In streaming mode, call `Header()` before rows, as it fixes column widths (`stream.go`).
-
-**Potential Pitfalls**:
-- **Type Mismatch**: Non-string types are converted to strings; ensure desired formatting (e.g., use `tw.Formatter` for custom types).
-- **Streaming Widths**: Headers influence column widths in streaming; set `Config.Widths` explicitly if specific widths are needed (`stream.go:streamCalculateWidths`).
-- **Empty Headers**: Missing headers may cause rendering issues; provide placeholders (e.g., `""`) if needed.
-- **AutoFormat**: Default `Header.Formatting.AutoFormat = tw.On` applies title case; disable with `WithHeaderAutoFormat(tw.Off)` if unwanted (`config.go`).
-
-#### 2.2. Appending Rows
-Rows represent the table’s data entries, and v1.0.x enhances flexibility with support for varied input types.
-
-**Old (v0.0.5):**
-```go
-package main
-
-import (
- "github.com/olekukonko/tablewriter"
- "os"
-)
-
-func main() {
- table := tablewriter.NewWriter(os.Stdout)
- table.SetHeader([]string{"ColA", "ColB", "ColC"}) // Add header for context
- table.Append([]string{"A", "The Good", "500"})
- table.Append([]string{"B", "The Very Bad", "288"})
- data := [][]string{
- {"C", "The Ugly", "120"},
- {"D", "The Gopher", "800"},
- }
- table.AppendBulk(data)
- table.Render()
-}
-```
-
-**New (v1.0.x):**
-```go
-package main
-
-import (
- "fmt"
- "github.com/olekukonko/tablewriter"
- "github.com/olekukonko/tablewriter/tw" // For tw.Formatter
- "log"
- "os"
-)
-
-// Struct for examples
-type Entry struct {
- ID string
- Label string
- Score int
-}
-
-// Struct with Formatter
-type FormattableEntry struct {
- ID string
- Label string
- Score int
-}
-
-func (e FormattableEntry) Format() string { return fmt.Sprintf("%s (%d)", e.Label, e.Score) } // Implements tw.Formatter
-
-func main() {
- table := tablewriter.NewTable(os.Stdout)
- table.Header("ID", "Description", "Value") // Header for context
-
- // Variadic Arguments (Single Row)
- table.Append("A", "The Good", 500) // Numbers converted to strings
-
- // Slice of Any Type (Single Row)
- table.Append([]any{"B", "The Very Bad", "288"})
-
- // Struct with Fields
- table.Append(Entry{ID: "C", Label: "The Ugly", Score: 120}) // Fields as cells
-
- // Struct with Formatter (will produce a single cell for this row based on Format())
- // To make it fit 3 columns, the formatter would need to produce a string that looks like 3 cells, or the table config would need to adapt.
- // For this example, let's assume it's meant to be one wide cell, or adjust the header.
- // For now, let's simplify and append it to a table with one header.
- tableOneCol := tablewriter.NewTable(os.Stdout)
- tableOneCol.Header("Formatted Entry")
- tableOneCol.Append(FormattableEntry{ID: "D", Label: "The Gopher", Score: 800}) // Single cell "The Gopher (800)"
- tableOneCol.Render()
- fmt.Println("---")
-
-
- // Re-initialize main table for Bulk example
- table = tablewriter.NewTable(os.Stdout)
- table.Header("ID", "Description", "Value")
-
- // Multiple Rows with Bulk
- data := []any{
- []any{"E", "The Fast", 300},
- Entry{ID: "F", Label: "The Swift", Score: 400}, // Struct instance
- // FormattableEntry{ID: "G", Label: "The Bold", Score: 500}, // Would also be one cell
- }
- if err := table.Bulk(data); err != nil {
- log.Fatalf("Bulk append failed: %v", err)
- }
- table.Render()
-}
-```
-
-**Key Changes**:
-- **Method**: `Append([]string)` replaced by `Append(...any)`; `AppendBulk([][]string)` replaced by `Bulk([]any)` (`tablewriter.go`).
-- **Input Flexibility**: `Append` accepts:
- - Multiple arguments as cells of a single row.
- - A single slice (e.g., `[]string`, `[]any`) as cells of a single row.
- - A single struct, processed by `convertItemToCells` (`zoo.go`):
- - Uses `tw.Formatter` or `fmt.Stringer` for single-cell output.
- - Extracts exported fields as multiple cells otherwise.
-- **Bulk Input**: `Bulk` accepts a slice where each element is a row (e.g., `[][]any`, `[]Entry`), processed by `appendSingle` (`zoo.go:appendSingle`).
-- **Streaming**: Rows render immediately in streaming mode via `streamAppendRow` (`stream.go:streamAppendRow`).
-- **Error Handling**: `Append` and `Bulk` return errors for invalid conversions or streaming issues (`tablewriter.go`).
-
-**Migration Tips**:
-- Replace `Append([]string)` with `Append(...any)` for variadic input.
-- Use `Bulk` for multiple rows, ensuring each element is a valid row representation.
-- Implement `tw.Formatter` for custom struct formatting to control cell output.
-- Match row cell count to headers to avoid alignment issues.
-- In streaming mode, append rows after `Start()` and before `Close()` (`stream.go`).
-
-**Potential Pitfalls**:
-- **Cell Count Mismatch**: Uneven row lengths may cause rendering errors; pad with empty strings (e.g., `""`) if needed.
-- **Struct Conversion**: Unexported fields are ignored; ensure fields are public or use `Formatter`/`Stringer` (`zoo.go`).
-- **Streaming Order**: Rows must be appended after headers in streaming to maintain width consistency (`stream.go`).
-- **Error Ignored**: Always check `Bulk` errors, as invalid data can cause failures.
-
-#### 2.3. Setting Footers
-Footers provide summary or closing data for the table, often aligned differently from rows.
-
-**Old (v0.0.5):**
-```go
-package main
-
-import (
- "github.com/olekukonko/tablewriter"
- "os"
-)
-
-func main() {
- table := tablewriter.NewWriter(os.Stdout)
- table.SetHeader([]string{"ColA", "ColB", "ColC", "ColD"}) // Add header for context
- table.SetFooter([]string{"", "", "Total", "1408"})
- table.Render()
-}
-```
-
-**New (v1.0.x):**
-```go
-package main
-
-import (
- "fmt"
- "github.com/olekukonko/tablewriter"
- "github.com/olekukonko/tablewriter/tw" // For tw.Formatter
- "os"
-)
-
-// Using Formatter
-type FooterSummary struct { // Renamed to avoid conflict
- Label string
- Value float64
-}
-
-func (s FooterSummary) Format() string { return fmt.Sprintf("%s: %.2f", s.Label, s.Value) } // Implements tw.Formatter
-
-func main() {
- table := tablewriter.NewTable(os.Stdout)
- table.Header("ColA", "ColB", "ColC", "ColD") // Header for context
-
- // Variadic Arguments
- table.Footer("", "", "Total", 1408)
-
- // Slice of Any Type
- // table.Footer([]any{"", "", "Total", 1408.50})
-
- table.Render() // Render this table
-
- fmt.Println("--- Another Table with Formatter Footer ---")
- table2 := tablewriter.NewTable(os.Stdout)
- table2.Header("Summary Info") // Single column header
- // Using Formatter for a single cell footer
- table2.Footer(FooterSummary{Label: "Grand Total", Value: 1408.50}) // Single cell: "Grand Total: 1408.50"
- table2.Render()
-}
-```
-
-**Key Changes**:
-- **Method**: `SetFooter([]string)` replaced by `Footer(...any)` (`tablewriter.go:Footer`).
-- **Input Flexibility**: Like `Header`, accepts variadic arguments, slices, or structs with `Formatter`/`Stringer` support (`zoo.go:processVariadic`).
-- **Streaming**: Footers are buffered via `streamStoreFooter` and rendered during `Close()` (`stream.go:streamStoreFooter`, `stream.go:streamRenderFooter`).
-- **Formatting**: Controlled by `Config.Footer` settings (e.g., `Alignment`, `AutoWrap`) (`zoo.go:prepareTableSection`).
-
-**Migration Tips**:
-- Replace `SetFooter` with `Footer`, preferring variadic input for simplicity.
-- Use `tw.Formatter` for custom footer formatting (e.g., formatted numbers).
-- Ensure footer cell count matches headers/rows to maintain alignment.
-- In streaming mode, call `Footer()` before `Close()` to include it in rendering.
-
-**Potential Pitfalls**:
-- **Alignment Differences**: Default `Footer.Alignment.Global = tw.AlignRight` differs from rows (`tw.AlignLeft`); adjust if needed (`config.go`).
-- **Streaming Timing**: Footers not rendered until `Close()`; ensure `Close()` is called (`stream.go`).
-- **Empty Footers**: Missing footers may affect table appearance; use placeholders if needed.
-
-### 3. Rendering the Table
-Rendering has been overhauled to support both batch and streaming modes, with mandatory error handling for robustness.
-
-**Old (v0.0.5):**
-```go
-package main
-
-import (
- "github.com/olekukonko/tablewriter"
- "os"
-)
-
-func main() {
- table := tablewriter.NewWriter(os.Stdout)
- table.SetHeader([]string{"Data"})
- table.Append([]string{"Example"})
- table.Render()
-}
-```
-
-**New (v1.0.x) - Batch Mode:**
-```go
-package main
-
-import (
- "github.com/olekukonko/tablewriter"
- "log"
- "os"
-)
-
-func main() {
- table := tablewriter.NewTable(os.Stdout)
- table.Header("Data")
- table.Append("Example")
- if err := table.Render(); err != nil {
- log.Fatalf("Table rendering failed: %v", err)
- }
-}
-```
-
-**New (v1.0.x) - Streaming Mode:**
-```go
-package main
-
-import (
- "fmt"
- "github.com/olekukonko/tablewriter"
- "github.com/olekukonko/tablewriter/tw"
- "log"
- "os"
-)
-
-func main() {
- tableStream := tablewriter.NewTable(os.Stdout,
- tablewriter.WithConfig(tablewriter.Config{
- Stream: tw.StreamConfig{Enable: true},
- Widths: tw.CellWidth{
- Global: 12, // Fixed column width for streaming
- PerColumn: tw.NewMapper[int, int]().Set(0, 15), // Column 0 at 15
- },
- }),
- )
- // Alternative: Using WithStreaming Option
- // tableStream := tablewriter.NewTable(os.Stdout,
- // tablewriter.WithStreaming(tw.StreamConfig{Enable: true}),
- // tablewriter.WithColumnMax(12), // Sets Config.Widths.Global
- // )
-
- if err := tableStream.Start(); err != nil {
- log.Fatalf("Failed to start table stream: %v", err)
- }
- tableStream.Header("Column 1", "Column 2")
- for i := 0; i < 3; i++ {
- if err := tableStream.Append(fmt.Sprintf("Data %d-1", i), fmt.Sprintf("Data %d-2", i)); err != nil {
- log.Printf("Failed to append row %d: %v", i, err) // Log and continue or handle differently
- }
- }
- tableStream.Footer("End", "Summary")
- if err := tableStream.Close(); err != nil {
- log.Fatalf("Failed to close table stream: %v", err)
- }
-}
-```
-
-**Key Changes**:
-- **Batch Mode**:
- - `Render()` processes all data (headers, rows, footers) in one go (`tablewriter.go:render`).
- - Calculates column widths dynamically based on content, `Config.Widths`, `ColMaxWidths`, and `MaxWidth` (`zoo.go:calculateAndNormalizeWidths`).
- - Invokes renderer methods: `Start()`, `Header()`, `Row()`, `Footer()`, `Line()`, `Close()` (`tw/renderer.go`).
- - Returns errors for invalid configurations or I/O issues.
-- **Streaming Mode**:
- - Enabled via `Config.Stream.Enable` or `WithStreaming(tw.StreamConfig{Enable: true})` (`tablewriter.go:WithStreaming`).
- - `Start()` initializes the stream, fixing column widths based on `Config.Widths` or first data (header/row) (`stream.go:streamCalculateWidths`).
- - `Header()`, `Append()` render immediately (`stream.go:streamRenderHeader`, `stream.go:streamAppendRow`).
- - `Footer()` buffers data, rendered by `Close()` (`stream.go:streamStoreFooter`, `stream.go:streamRenderFooter`).
- - `Close()` finalizes rendering with footer and borders (`stream.go:Close`).
- - All methods return errors for robust handling.
-- **Error Handling**: Mandatory error checks replace silent failures, improving reliability.
-
-**Migration Tips**:
-- Replace `Render()` calls with error-checked versions in batch mode.
-- For streaming, adopt `Start()`, `Append()`, `Close()` workflow, ensuring `Start()` precedes data input.
-- Set `Config.Widths` for consistent column widths in streaming mode (`config.go`).
-- Use `WithRendition` to customize visual output, as renderer settings are decoupled (`tablewriter.go`).
-- Test rendering with small datasets to verify configuration before scaling.
-
-**Potential Pitfalls**:
-- **Missing Error Checks**: Failing to check errors can miss rendering failures; always use `if err != nil`.
-- **Streaming Widths**: Widths are fixed after `Start()`; inconsistent data may cause truncation (`stream.go`).
-- **Renderer Misconfiguration**: Ensure `tw.Rendition` matches desired output style (`tw/renderer.go`).
-- **Incomplete Streaming**: Forgetting `Close()` in streaming mode omits footer and final borders (`stream.go`).
-- **Batch vs. Streaming**: Using `Render()` in streaming mode causes errors; use `Start()`/`Close()` instead (`tablewriter.go`).
-
-
-## Styling and Appearance Configuration
-
-Styling in v1.0.x is split between `tablewriter.Config` for data processing (e.g., alignment, padding, wrapping) and `tw.Rendition` for visual rendering (e.g., borders, symbols, lines). This section details how to migrate v0.0.5 styling methods to v1.0.x, providing examples, best practices, and migration tips to maintain or enhance table appearance.
-
-### 4.1. Table Styles
-Table styles define the visual structure through border and separator characters. In v0.0.5, styles were set via individual separator methods, whereas v1.0.x uses `tw.Rendition.Symbols` for a cohesive approach, offering predefined styles and custom symbol sets.
-
-**Available Table Styles**:
-The `tw.Symbols` interface (`tw/symbols.go`) supports a variety of predefined styles, each tailored to specific use cases, as well as custom configurations for unique requirements.
-
-| Style Name | Use Case | Symbols Example | Recommended Context |
-|----------------|-----------------------------------|-------------------------------------|-----------------------------------------|
-| `StyleASCII` | Simple, terminal-friendly | `+`, `-`, `|` | Basic CLI output, minimal dependencies; ensures compatibility across all terminals, including older or non-Unicode systems. |
-| `StyleLight` | Clean, lightweight borders | `┌`, `└`, `─`, `│` | Modern terminals, clean and professional aesthetics; suitable for most CLI applications requiring a modern look. |
-| `StyleHeavy` | Thick, prominent borders | `┏`, `┗`, `━`, `┃` | High-visibility reports, dashboards, or logs; emphasizes table structure for critical data presentation. |
-| `StyleDouble` | Double-line borders | `╔`, `╚`, `═`, `║` | Formal documents, structured data exports; visually distinct for presentations or printed outputs. |
-| `StyleRounded` | Rounded corners, aesthetic | `╭`, `╰`, `─`, `│` | User-friendly CLI applications, reports; enhances visual appeal with smooth, rounded edges. |
-| `StyleMarkdown`| Markdown-compatible | `|`, `-`, `:` | Documentation, GitHub READMEs, or Markdown-based platforms; ensures proper rendering in Markdown viewers. |
-*Note: `StyleBold` was mentioned but not defined in the symbols code, `StyleHeavy` is similar.*
-
-**Old (v0.0.5):**
-```go
-package main
-
-import (
- "github.com/olekukonko/tablewriter"
- "os"
-)
-
-func main() {
- table := tablewriter.NewWriter(os.Stdout)
- // table.SetCenterSeparator("*") // Example, actual v0.0.5 method might vary
- // table.SetColumnSeparator("!")
- // table.SetRowSeparator("=")
- // table.SetBorder(true)
- table.SetHeader([]string{"Header1", "Header2"})
- table.Append([]string{"Cell1", "Cell2"})
- table.Render()
-}
-```
-
-**New (v1.0.x):**
-```go
-package main
-
-import (
- "github.com/olekukonko/tablewriter"
- "github.com/olekukonko/tablewriter/renderer"
- "github.com/olekukonko/tablewriter/tw"
- "os"
-)
-
-func main() {
- // Using a Predefined Style (Rounded Corners for Aesthetic Output)
- tableStyled := tablewriter.NewTable(os.Stdout,
- tablewriter.WithRenderer(renderer.NewBlueprint()), // Ensure renderer is set
- tablewriter.WithRendition(tw.Rendition{
- Symbols: tw.NewSymbols(tw.StyleRounded), // Predefined rounded style
- Borders: tw.Border{Top: tw.On, Bottom: tw.On, Left: tw.On, Right: tw.On},
- Settings: tw.Settings{
- Separators: tw.Separators{BetweenRows: tw.On}, // Lines between rows
- },
- }),
- )
- tableStyled.Header("Name", "Status")
- tableStyled.Append("Node1", "Ready")
- tableStyled.Render()
-
- // Using Fully Custom Symbols (Mimicking v0.0.5 Custom Separators)
- mySymbols := tw.NewSymbolCustom("my-style"). // Fluent builder for custom symbols
- WithCenter("*"). // Junction of lines
- WithColumn("!"). // Vertical separator
- WithRow("="). // Horizontal separator
- WithTopLeft("/").
- WithTopMid("-").
- WithTopRight("\\").
- WithMidLeft("[").
- WithMidRight("]"). // Corrected from duplicate WithMidRight("+")
- // WithMidMid was not a method in SymbolCustom
- WithBottomLeft("\\").
- WithBottomMid("_").
- WithBottomRight("/").
- WithHeaderLeft("(").
- WithHeaderMid("-").
- WithHeaderRight(")") // Header-specific
- tableCustomSymbols := tablewriter.NewTable(os.Stdout,
- tablewriter.WithRenderer(renderer.NewBlueprint()),
- tablewriter.WithRendition(tw.Rendition{
- Symbols: mySymbols,
- Borders: tw.Border{Top: tw.On, Bottom: tw.On, Left: tw.On, Right: tw.On},
- }),
- )
- tableCustomSymbols.Header("Name", "Status")
- tableCustomSymbols.Append("Node1", "Ready")
- tableCustomSymbols.Render()
-}
-```
-
-**Output (Styled, Rounded):**
-```
-╭───────┬────────╮
-│ NAME │ STATUS │
-├───────┼────────┤
-│ Node1 │ Ready │
-╰───────┴────────╯
-```
-
-**Output (Custom Symbols):**
-```
-/=======-========\
-! NAME ! STATUS !
-[=======*========]
-! Node1 ! Ready !
-\=======_========/
-```
-
-**Key Changes**:
-- **Deprecated Methods**: `SetCenterSeparator`, `SetColumnSeparator`, `SetRowSeparator`, and `SetBorder` are deprecated and moved to `deprecated.go`. These are replaced by `tw.Rendition.Symbols` and `tw.Rendition.Borders` for a unified styling approach (`tablewriter.go`).
-- **Symbol System**: The `tw.Symbols` interface defines all drawing characters used for table lines, junctions, and corners (`tw/symbols.go:Symbols`).
- - `tw.NewSymbols(tw.BorderStyle)` provides predefined styles (e.g., `tw.StyleASCII`, `tw.StyleMarkdown`, `tw.StyleRounded`) for common use cases. (Note: `tw.Style` should be `tw.BorderStyle`)
- - `tw.NewSymbolCustom(name string)` enables fully custom symbol sets via a fluent builder, allowing precise control over each character (e.g., `WithCenter`, `WithTopLeft`).
-- **Renderer Dependency**: Styling requires a renderer, set via `WithRenderer`; the default is `renderer.NewBlueprint()` if not specified (`tablewriter.go:WithRenderer`).
-- **Border Control**: `tw.Rendition.Borders` uses `tw.State` (`tw.On`, `tw.Off`) to enable or disable borders on each side (Top, Bottom, Left, Right) (`tw/renderer.go:Border`).
-- **Extensibility**: Custom styles can be combined with custom renderers for non-text outputs, enhancing flexibility (`tw/renderer.go`).
-
-**Migration Tips**:
-- Replace individual separator setters (`SetCenterSeparator`, etc.) with `tw.NewSymbols` for predefined styles or `tw.NewSymbolCustom` for custom designs to maintain or enhance v0.0.5 styling.
-- Use `WithRendition` to apply `tw.Rendition` settings, ensuring a renderer is explicitly set to avoid default behavior (`tablewriter.go`).
-- Test table styles in your target environment (e.g., terminal, Markdown viewer, or log file) to ensure compatibility with fonts and display capabilities.
-- For Markdown-based outputs (e.g., GitHub READMEs), use `tw.StyleMarkdown` to ensure proper rendering in Markdown parsers (`tw/symbols.go`).
-- Combine `tw.Rendition.Symbols` with `tw.Rendition.Borders` and `tw.Rendition.Settings` to replicate or improve v0.0.5’s border and line configurations (`tw/renderer.go`).
-- Document custom symbol sets in code comments to aid maintenance, as they can be complex (`tw/symbols.go`).
-
-**Potential Pitfalls**:
-- **Missing Renderer**: If `WithRenderer` is omitted, `NewTable` defaults to `renderer.NewBlueprint` with minimal styling, which may not match v0.0.5’s `SetBorder(true)` behavior; always specify for custom styles (`tablewriter.go`).
-- **Incomplete Custom Symbols**: When using `tw.NewSymbolCustom`, failing to set all required symbols (e.g., `TopLeft`, `Center`, `HeaderLeft`) can cause rendering errors or inconsistent visuals; ensure all necessary characters are defined (`tw/symbols.go`).
-- **Terminal Compatibility Issues**: Advanced styles like `StyleDouble` or `StyleHeavy` may not render correctly in older terminals or non-Unicode environments; use `StyleASCII` for maximum compatibility across platforms (`tw/symbols.go`).
-- **Border and Symbol Mismatch**: Inconsistent `tw.Rendition.Borders` and `tw.Symbols` settings (e.g., enabling borders but using minimal symbols) can lead to visual artifacts; test with small tables to verify alignment (`tw/renderer.go`).
-- **Markdown Rendering**: Non-Markdown styles (e.g., `StyleRounded`) may not render correctly in Markdown viewers; use `StyleMarkdown` for documentation or web-based outputs (`tw/symbols.go`).
-
-### 4.2. Borders and Separator Lines
-Borders and internal lines define the table’s structural appearance, controlling the visibility of outer edges and internal divisions. In v0.0.5, these were set via specific methods, while v1.0.x uses `tw.Rendition` fields for a more integrated approach.
-
-**Old (v0.0.5):**
-```go
-package main
-
-import (
- "github.com/olekukonko/tablewriter"
- "os"
-)
-
-func main() {
- table := tablewriter.NewWriter(os.Stdout)
- // table.SetBorder(false) // Disable all outer borders
- // table.SetRowLine(true) // Enable lines between data rows
- // table.SetHeaderLine(true) // Enable line below header
- table.SetHeader([]string{"Header1", "Header2"})
- table.Append([]string{"Cell1", "Cell2"})
- table.Render()
-}
-```
-
-**New (v1.0.x):**
-```go
-package main
-
-import (
- "github.com/olekukonko/tablewriter"
- "github.com/olekukonko/tablewriter/renderer"
- "github.com/olekukonko/tablewriter/tw"
- "os"
-)
-
-func main() {
- // Standard Bordered Table with Internal Lines
- table := tablewriter.NewTable(os.Stdout,
- tablewriter.WithRenderer(renderer.NewBlueprint()),
- tablewriter.WithRendition(tw.Rendition{
- Borders: tw.Border{ // Outer table borders
- Left: tw.On,
- Right: tw.On,
- Top: tw.On,
- Bottom: tw.On,
- },
- Settings: tw.Settings{
- Lines: tw.Lines{ // Major internal separator lines
- ShowHeaderLine: tw.On, // Line after header
- ShowFooterLine: tw.On, // Line before footer (if footer exists)
- },
- Separators: tw.Separators{ // General row and column separators
- BetweenRows: tw.On, // Horizontal lines between data rows
- BetweenColumns: tw.On, // Vertical lines between columns
- },
- },
- }),
- )
- table.Header("Name", "Status")
- table.Append("Node1", "Ready")
- table.Render()
-
- // Borderless Table (kubectl-style, No Lines or Separators)
- // Configure the table with a borderless, kubectl-style layout
- config := tablewriter.NewConfigBuilder().
- Header().
- Padding().
- WithGlobal(tw.PaddingNone). // No header padding
- Build().
- Alignment().
- WithGlobal(tw.AlignLeft). // Left-align header
- Build().
- Row().
- Padding().
- WithGlobal(tw.PaddingNone). // No row padding
- Build().
- Alignment().
- WithGlobal(tw.AlignLeft). // Left-align rows
- Build().
- Footer().
- Padding().
- WithGlobal(tw.PaddingNone). // No footer padding
- Build().
- Alignment().
- WithGlobal(tw.AlignLeft). // Left-align footer (if used)
- Build().
- WithDebug(true). // Enable debug logging
- Build()
-
- // Create borderless table
- tableBorderless := tablewriter.NewTable(os.Stdout,
- tablewriter.WithRenderer(renderer.NewBlueprint()), // Assumes valid renderer
- tablewriter.WithRendition(tw.Rendition{
- Borders: tw.BorderNone, // No borders
- Symbols: tw.NewSymbols(tw.StyleNone), // No border symbols
- Settings: tw.Settings{
- Lines: tw.LinesNone, // No header/footer lines
- Separators: tw.SeparatorsNone, // No row/column separators
- },
- }),
- tablewriter.WithConfig(config),
- )
-
- // Set headers and data
- tableBorderless.Header("Name", "Status")
- tableBorderless.Append("Node1", "Ready")
- tableBorderless.Render()
-}
-```
-
-**Output (Standard Bordered):**
-```
-┌───────┬────────┐
-│ Name │ Status │
-├───────┼────────┤
-│ Node1 │ Ready │
-└───────┴────────┘
-```
-
-**Output (Borderless):**
-```
-NAME STATUS
-Node1 Ready
-```
-
-**Key Changes**:
-- **Deprecated Methods**: `SetBorder`, `SetRowLine`, and `SetHeaderLine` are deprecated and moved to `deprecated.go`. These are replaced by fields in `tw.Rendition` (`tw/renderer.go`):
- - `Borders`: Controls outer table borders (`tw.Border`) with `tw.State` (`tw.On`, `tw.Off`) for each side (Top, Bottom, Left, Right).
- - `Settings.Lines`: Manages major internal lines (`ShowHeaderLine` for header, `ShowFooterLine` for footer) (`tw.Lines`).
- - `Settings.Separators`: Controls general separators between rows (`BetweenRows`) and columns (`BetweenColumns`) (`tw.Separators`).
-- **Presets for Simplicity**: `tw.BorderNone`, `tw.LinesNone`, and `tw.SeparatorsNone` provide quick configurations for minimal or borderless tables (`tw/preset.go`).
-- **Renderer Integration**: Border and line settings are applied via `WithRendition`, requiring a renderer to be set (`tablewriter.go:WithRendition`).
-- **Granular Control**: Each border side and line type can be independently configured, offering greater flexibility than v0.0.5’s boolean toggles.
-- **Dependency on Symbols**: The appearance of borders and lines depends on `tw.Rendition.Symbols`; ensure compatible symbol sets (`tw/symbols.go`).
-
-**Migration Tips**:
-- Replace `SetBorder(false)` with `tw.Rendition.Borders = tw.BorderNone` to disable all outer borders (`tw/preset.go`).
-- Use `tw.Rendition.Settings.Separators.BetweenRows = tw.On` to replicate `SetRowLine(true)`, ensuring row separators are visible (`tw/renderer.go`).
-- Set `tw.Rendition.Settings.Lines.ShowHeaderLine = tw.On` to mimic `SetHeaderLine(true)` for a line below the header (`tw/renderer.go`).
-- For kubectl-style borderless tables, combine `tw.BorderNone`, `tw.LinesNone`, `tw.SeparatorsNone`, and `WithPadding(tw.PaddingNone)` (applied via `ConfigBuilder` or `WithConfig`) to eliminate all lines and spacing (`tw/preset.go`, `config.go`).
-- Test border and line configurations with small tables to verify visual consistency, especially when combining with custom `tw.Symbols`.
-- Use `WithDebug(true)` to log rendering details if borders or lines appear incorrectly (`config.go`).
-
-**Potential Pitfalls**:
-- **Separator Absence**: If `tw.Rendition.Settings.Separators.BetweenColumns` is `tw.Off` and borders are disabled, columns may lack visual separation; use `tw.CellPadding` or ensure content spacing (`tw/cell.go`).
-- **Line and Border Conflicts**: Mismatched settings (e.g., enabling `ShowHeaderLine` but disabling `Borders.Top`) can cause uneven rendering; align `Borders`, `Lines`, and `Separators` settings (`tw/renderer.go`).
-- **Renderer Dependency**: Border settings require a renderer; omitting `WithRenderer` defaults to `renderer.NewBlueprint` with minimal styling, which may not match v0.0.5 expectations (`tablewriter.go`).
-- **Streaming Limitations**: In streaming mode, separator rendering is fixed after `Start()`; ensure `tw.Rendition` is set correctly before rendering begins (`stream.go`).
-- **Symbol Mismatch**: Using minimal `tw.Symbols` (e.g., `StyleASCII`) with complex `Borders` settings may lead to visual artifacts; test with matching symbol sets (`tw/symbols.go`).
-
-### 4.3. Alignment
-Alignment controls the positioning of text within table cells, now configurable per section (Header, Row, Footer) with both global and per-column options for greater precision.
-
-**Old (v0.0.5):**
-```go
-package main
-
-import (
- "github.com/olekukonko/tablewriter" // Assuming ALIGN_CENTER etc. were constants here
- "os"
-)
-
-const ( // Mocking v0.0.5 constants for example completeness
- ALIGN_CENTER = 1 // Example values
- ALIGN_LEFT = 2
-)
-
-
-func main() {
- table := tablewriter.NewWriter(os.Stdout)
- // table.SetAlignment(ALIGN_CENTER) // Applied to data rows
- // table.SetHeaderAlignment(ALIGN_LEFT) // Applied to header
- // No specific footer alignment setter
- table.SetHeader([]string{"Header1", "Header2"})
- table.Append([]string{"Cell1", "Cell2"})
- table.Render()
-}
-```
-
-**New (v1.0.x):**
-```go
-package main
-
-import (
- "github.com/olekukonko/tablewriter"
- "github.com/olekukonko/tablewriter/tw"
- "os"
-)
-
-func main() {
- cfg := tablewriter.Config{
- Header: tw.CellConfig{
- Alignment: tw.CellAlignment{Global: tw.AlignLeft},
- },
- Row: tw.CellConfig{
- Alignment: tw.CellAlignment{
- Global: tw.AlignCenter,
- PerColumn: []tw.Align{tw.AlignLeft, tw.AlignRight}, // Col 0 left, Col 1 right
- },
- },
- Footer: tw.CellConfig{
- Alignment: tw.CellAlignment{Global: tw.AlignRight},
- },
- }
- table := tablewriter.NewTable(os.Stdout, tablewriter.WithConfig(cfg))
-
- table.Header("Name", "Status")
- table.Append("Node1", "Ready")
- table.Footer("", "Active") // Ensure footer has content to show alignment
- table.Render()
-}
-```
-
-**Output:**
-```
-┌───────┬────────┐
-│ NAME │ STATUS │
-├───────┼────────┤
-│ Node1 │ Ready │
-├───────┼────────┤
-│ │ Active │
-└───────┴────────┘
-```
-
-**Key Changes**:
-- **Deprecated Methods**: `SetAlignment` and `SetHeaderAlignment` are replaced by `WithRowAlignment`, `WithHeaderAlignment`, `WithFooterAlignment`, or direct `Config` settings (`config.go`). These old methods are retained in `deprecated.go` for compatibility but should be migrated.
-- **Alignment Structure**: Alignment is managed within `tw.CellConfig.Alignment` (`tw/cell.go:CellAlignment`), which includes:
- - `Global`: A single `tw.Align` value applied to all cells in the section (`tw.AlignLeft`, `tw.AlignCenter`, `tw.AlignRight`, `tw.AlignNone`).
- - `PerColumn`: A slice of `tw.Align` values for column-specific alignment, overriding `Global` for specified columns.
-- **Footer Alignment**: v1.0.x introduces explicit footer alignment via `WithFooterAlignment` or `Config.Footer.Alignment`, addressing v0.0.5’s lack of footer-specific control (`config.go`).
-- **Type Safety**: `tw.Align` string constants replace v0.0.5’s integer constants (e.g., `ALIGN_CENTER`), improving clarity and reducing errors (`tw/types.go`).
-- **Builder Support**: `ConfigBuilder` provides `Alignment()` methods for each section. `ForColumn(idx).WithAlignment()` applies alignment to a specific column across all sections (`config.go:ConfigBuilder`, `config.go:ColumnConfigBuilder`).
-- **Deprecated Fields**: `tw.CellConfig.ColumnAligns` (slice) and `tw.CellFormatting.Alignment` (single value) are supported for backward compatibility but should be migrated to `tw.CellAlignment.Global` and `tw.CellAlignment.PerColumn` (`tw/cell.go`).
-
-**Migration Tips**:
-- Replace `SetAlignment(ALIGN_X)` with `WithRowAlignment(tw.AlignX)` or `Config.Row.Alignment.Global = tw.AlignX` to set row alignment (`config.go`).
-- Use `WithHeaderAlignment(tw.AlignX)` for headers and `WithFooterAlignment(tw.AlignX)` for footers to maintain or adjust v0.0.5 behavior (`config.go`).
-- Specify per-column alignments with `ConfigBuilder.Row().Alignment().WithPerColumn([]tw.Align{...})` or by setting `Config.Row.Alignment.PerColumn` for fine-grained control (`config.go`).
-- Use `ConfigBuilder.ForColumn(idx).WithAlignment(tw.AlignX)` to apply consistent alignment to a specific column across all sections (Header, Row, Footer) (`config.go`).
-- Verify alignment settings against defaults (`Header: tw.AlignCenter`, `Row: tw.AlignLeft`, `Footer: tw.AlignRight`) to ensure expected output (`config.go:defaultConfig`).
-- Test alignment with varied cell content lengths to confirm readability, especially when combined with wrapping or padding settings (`zoo.go:prepareContent`).
-
-**Potential Pitfalls**:
-- **Alignment Precedence**: `PerColumn` settings override `Global` within a section; ensure column-specific alignments are intentional (`tw/cell.go:CellAlignment`).
-- **Deprecated Fields**: Relying on `ColumnAligns` or `tw.CellFormatting.Alignment` is temporary; migrate to `tw.CellAlignment` to avoid future issues (`tw/cell.go`).
-- **Cell Count Mismatch**: Rows or footers with fewer cells than headers can cause alignment errors; pad with empty strings (`""`) to match column count (`zoo.go`).
-- **Streaming Width Impact**: In streaming mode, alignment depends on fixed column widths set by `Config.Widths`; narrow widths may truncate content, misaligning text (`stream.go:streamCalculateWidths`).
-- **Default Misalignment**: The default `Footer.Alignment.Global = tw.AlignRight` differs from rows (`tw.AlignLeft`); explicitly set `WithFooterAlignment` if consistency is needed (`config.go`).
-
-### 4.4. Auto-Formatting (Header Title Case)
-Auto-formatting applies transformations like title case to cell content, primarily for headers to enhance readability, but it can be enabled for rows or footers in v1.0.x.
-
-**Old (v0.0.5):**
-```go
-package main
-
-import (
- "github.com/olekukonko/tablewriter"
- "os"
-)
-
-func main() {
- table := tablewriter.NewWriter(os.Stdout)
- // table.SetAutoFormatHeaders(true) // Default: true; applies title case to headers
- table.SetHeader([]string{"col_one", "status_report"})
- table.Append([]string{"Node1", "Ready"})
- table.Render()
-}
-```
-
-**New (v1.0.x):**
-```go
-package main
-
-import (
- "github.com/olekukonko/tablewriter"
- "github.com/olekukonko/tablewriter/tw"
- "os"
-)
-
-func main() {
- // Using Direct Config Struct to turn OFF default AutoFormat for Header
- cfg := tablewriter.Config{
- Header: tw.CellConfig{Formatting: tw.CellFormatting{AutoFormat: tw.Off}}, // Turn OFF title case for headers
- Row: tw.CellConfig{Formatting: tw.CellFormatting{AutoFormat: tw.Off}}, // No formatting for rows (default)
- Footer: tw.CellConfig{Formatting: tw.CellFormatting{AutoFormat: tw.Off}}, // No formatting for footers (default)
- }
- tableNoAutoFormat := tablewriter.NewTable(os.Stdout, tablewriter.WithConfig(cfg))
-
- tableNoAutoFormat.Header("col_one", "status_report") // Stays as "col_one", "status_report"
- tableNoAutoFormat.Append("Node1", "Ready")
- tableNoAutoFormat.Render()
-
- // Using Option Function for Headers (default is ON, this makes it explicit)
- tableWithAutoFormat := tablewriter.NewTable(os.Stdout,
- tablewriter.WithHeaderAutoFormat(tw.On), // Explicitly enable title case (default behavior)
- )
-
- tableWithAutoFormat.Header("col_one", "status_report") // Becomes "COL ONE", "STATUS REPORT"
- tableWithAutoFormat.Append("Node1", "Ready")
- tableWithAutoFormat.Render()
-}
-```
-
-**Output:**
-```
-┌─────────┬───────────────┐
-│ col_one │ status_report │
-├─────────┼───────────────┤
-│ Node1 │ Ready │
-└─────────┴───────────────┘
-┌─────────┬───────────────┐
-│ COL ONE │ STATUS REPORT │
-├─────────┼───────────────┤
-│ Node1 │ Ready │
-└─────────┴───────────────┘
-```
-
-**Key Changes**:
-- **Method**: `SetAutoFormatHeaders` is replaced by `Config.Header.Formatting.AutoFormat`, or equivalent builder methods (`config.go`).
-- **Extended Scope**: Auto-formatting can now be applied to rows and footers via `Config.Row.Formatting.AutoFormat` and `Config.Footer.Formatting.AutoFormat`, unlike v0.0.5’s header-only support (`tw/cell.go`).
-- **Type Safety**: `tw.State` (`tw.On`, `tw.Off`, `tw.Unknown`) controls formatting state, replacing boolean flags (`tw/state.go`).
-- **Behavior**: When `tw.On`, the `tw.Title` function converts text to uppercase and replaces underscores and some periods with spaces (e.g., "col_one" → "COL ONE") (`tw/fn.go:Title`, `zoo.go:prepareContent`).
-- **Defaults**: `Header.Formatting.AutoFormat = tw.On`, `Row.Formatting.AutoFormat = tw.Off`, `Footer.Formatting.AutoFormat = tw.Off` (`config.go:defaultConfig`).
-
-**Migration Tips**:
-- To maintain v0.0.5’s default header title case behavior, no explicit action is needed as `WithHeaderAutoFormat(tw.On)` is the default. If you were using `SetAutoFormatHeaders(false)`, you'd use `WithHeaderAutoFormat(tw.Off)`.
-- Explicitly set `WithRowAutoFormat(tw.Off)` or `WithFooterAutoFormat(tw.Off)` if you want to ensure rows and footers remain unformatted, as v1.0.x allows enabling formatting for these sections (`config.go`).
-- Use `ConfigBuilder.Header().Formatting().WithAutoFormat(tw.State)` for precise control over formatting per section (`config.go`).
-- Test header output with underscores or periods (e.g., "my_column") to verify title case transformation meets expectations.
-- For custom formatting beyond title case (e.g., custom capitalization), use `tw.CellFilter` instead of `AutoFormat` (`tw/cell.go`).
-
-**Potential Pitfalls**:
-- **Default Header Formatting**: `Header.Formatting.AutoFormat = tw.On` by default may unexpectedly alter header text (e.g., "col_one" → "COL ONE"); disable with `WithHeaderAutoFormat(tw.Off)` if raw headers are preferred (`config.go`).
-- **Row/Footer Formatting**: Enabling `AutoFormat` for rows or footers (not default) applies title case, which may not suit data content; verify with `tw.Off` unless intended (`tw/cell.go`).
-- **Filter Conflicts**: Combining `AutoFormat` with `tw.CellFilter` can lead to overlapping transformations; prioritize filters for complex formatting (`zoo.go:prepareContent`).
-- **Performance Overhead**: Auto-formatting large datasets may add minor processing time; disable for performance-critical applications (`zoo.go`).
-
-### 4.5. Text Wrapping
-Text wrapping determines how long cell content is handled within width constraints, offering more options in v1.0.x compared to v0.0.5’s binary toggle.
-
-**Old (v0.0.5):**
-```go
-package main
-
-import (
- "github.com/olekukonko/tablewriter"
- "os"
-)
-
-func main() {
- table := tablewriter.NewWriter(os.Stdout)
- // table.SetAutoWrapText(true) // Enable normal word wrapping
- // table.SetAutoWrapText(false) // Disable wrapping
- table.SetHeader([]string{"Long Header Text Example", "Status"})
- table.Append([]string{"This is some very long cell content that might wrap", "Ready"})
- table.Render()
-}
-```
-
-**New (v1.0.x):**
-```go
-package main
-
-import (
- "github.com/olekukonko/tablewriter"
- "github.com/olekukonko/tablewriter/tw" // For tw.WrapNormal etc.
- "os"
-)
-
-func main() {
- // Using Option Functions for Quick Wrapping Settings on a specific section (e.g., Row)
- // To actually see wrapping, a MaxWidth for the table or columns is needed.
- table := tablewriter.NewTable(os.Stdout,
- tablewriter.WithRowAutoWrap(tw.WrapNormal), // Set row wrapping
- tablewriter.WithHeaderAutoWrap(tw.WrapTruncate), // Header truncates (default)
- tablewriter.WithMaxWidth(30), // Force wrapping by setting table max width
- )
- table.Header("Long Header Text", "Status")
- table.Append("This is a very long cell content", "Ready")
- table.Footer("Summary", "Active")
- table.Render()
-
- // For more fine-grained control (e.g., different wrapping for header, row, footer):
- cfgBuilder := tablewriter.NewConfigBuilder()
- cfgBuilder.Header().Formatting().WithAutoWrap(tw.WrapTruncate) // Header: Truncate
- cfgBuilder.Row().Formatting().WithAutoWrap(tw.WrapNormal) // Row: Normal wrap
- cfgBuilder.Footer().Formatting().WithAutoWrap(tw.WrapBreak) // Footer: Break words
- cfgBuilder.WithMaxWidth(40) // Overall table width constraint
-
- tableFullCfg := tablewriter.NewTable(os.Stdout, tablewriter.WithConfig(cfgBuilder.Build()))
- tableFullCfg.Header("Another Very Long Header Example That Will Be Truncated", "Info")
- tableFullCfg.Append("This is an example of row content that should wrap normally based on available space.", "Detail")
- tableFullCfg.Footer("FinalSummaryInformationThatMightBreakAcrossLines", "End")
- tableFullCfg.Render()
-}
-```
-
-**Output (tableOpt):**
-```
-┌──────────────┬────────┐
-│ LONG HEADER… │ STATUS │
-├──────────────┼────────┤
-│ This is a │ Ready │
-│ very long │ │
-│ cell content │ │
-├──────────────┼────────┤
-│ Summary │ Active │
-└──────────────┴────────┘
-```
-*(Second table output will vary based on content and width)*
-
-**Key Changes**:
-- **Method**: `SetAutoWrapText` is replaced by `Config..Formatting.AutoWrap` or specific `WithAutoWrap` options (`config.go`).
-- **Wrapping Modes**: `int` constants for `AutoWrap` (e.g., `tw.WrapNone`, `tw.WrapNormal`, `tw.WrapTruncate`, `tw.WrapBreak`) replace v0.0.5’s binary toggle (`tw/tw.go`).
-- **Section-Specific Control**: Wrapping is configurable per section (Header, Row, Footer), unlike v0.0.5’s global setting (`tw/cell.go`).
-- **Defaults**: `Header: tw.WrapTruncate`, `Row: tw.WrapNormal`, `Footer: tw.WrapNormal` (`config.go:defaultConfig`).
-- **Width Dependency**: Wrapping behavior relies on width constraints set by `Config.Widths` (fixed column widths), `Config..ColMaxWidths` (max content width), or `Config.MaxWidth` (total table width) (`zoo.go:calculateContentMaxWidth`, `zoo.go:prepareContent`).
-
-**Migration Tips**:
-- Replace `SetAutoWrapText(true)` with `WithRowAutoWrap(tw.WrapNormal)` to maintain v0.0.5’s default wrapping for rows (`config.go`).
-- Use `WithHeaderAutoWrap(tw.WrapTruncate)` to align with v1.0.x’s default header behavior, ensuring long headers are truncated (`config.go`).
-- Set `Config.Widths` or `Config.MaxWidth` explicitly to enforce wrapping, as unconstrained widths may prevent wrapping (`config.go`).
-- Use `ConfigBuilder.().Formatting().WithAutoWrap(int_wrap_mode)` for precise control over wrapping per section (`config.go`).
-- Test wrapping with varied content lengths (e.g., short and long text) to ensure readability and proper width allocation.
-- Consider `tw.WrapNormal` for data rows to preserve content integrity, reserving `tw.WrapTruncate` for headers or footers (`tw/tw.go`).
-
-**Potential Pitfalls**:
-- **Missing Width Constraints**: Without `Config.Widths`, `ColMaxWidths`, or `MaxWidth`, wrapping may not occur, leading to overflow; always define width limits for wrapping (`zoo.go`).
-- **Streaming Width Impact**: In streaming mode, wrapping depends on fixed widths set at `Start()`; narrow widths may truncate content excessively (`stream.go:streamCalculateWidths`).
-- **Truncation Data Loss**: `tw.WrapTruncate` may obscure critical data in rows; use `tw.WrapNormal` or wider columns to retain content (`tw/tw.go`).
-- **Performance Overhead**: Wrapping large datasets with `tw.WrapNormal` or `tw.WrapBreak` can add processing time; optimize widths for performance-critical applications (`zoo.go:prepareContent`).
-- **Inconsistent Section Wrapping**: Default wrapping differs (`Header: tw.WrapTruncate`, `Row/Footer: tw.WrapNormal`); align settings if uniform behavior is needed (`config.go`).
-
-### 4.6. Padding
-Padding adds spacing within cells, enhancing readability and affecting cell width calculations. v1.0.x introduces granular, per-side padding, replacing v0.0.5’s single inter-column padding control.
-
-**Old (v0.0.5):**
-```go
-package main
-
-import (
- "github.com/olekukonko/tablewriter"
- "os"
-)
-
-func main() {
- table := tablewriter.NewWriter(os.Stdout)
- // table.SetTablePadding("\t") // Set inter-column space character when borders are off
- // Default: single space within cells
- table.SetHeader([]string{"Header1"})
- table.Append([]string{"Cell1"})
- table.Render()
-}
-```
-
-**New (v1.0.x):**
-```go
-package main
-
-import (
- "github.com/olekukonko/tablewriter"
- "github.com/olekukonko/tablewriter/tw"
- "os"
-)
-
-func main() {
- // Using Direct Config Struct
- cfg := tablewriter.Config{
- Header: tw.CellConfig{
- Padding: tw.CellPadding{
- Global: tw.Padding{Left: "[", Right: "]", Top: "-", Bottom: "-"}, // Padding for all header cells
- PerColumn: []tw.Padding{ // Specific padding for header column 0
- {Left: ">>", Right: "<<", Top: "=", Bottom: "="}, // Overrides Global for column 0
- },
- },
- },
- Row: tw.CellConfig{
- Padding: tw.CellPadding{
- Global: tw.PaddingDefault, // One space left/right for all row cells
- },
- },
- Footer: tw.CellConfig{
- Padding: tw.CellPadding{
- Global: tw.Padding{Top: "~", Bottom: "~"}, // Top/bottom padding for all footer cells
- },
- },
- }
- table := tablewriter.NewTable(os.Stdout, tablewriter.WithConfig(cfg))
-
- table.Header("Name", "Status") // Two columns
- table.Append("Node1", "Ready")
- table.Footer("End", "Active")
- table.Render()
-}
-```
-
-**Output:**
-```
-┌────────┬────────┐
-│========│[------]│
-│>>NAME<<│[STATUS]│
-│========│[------]│
-├────────┼────────┤
-│ Node1 │ Ready │
-│~~~~~~~~│~~~~~~~~│
-│End │Active │
-│~~~~~~~~│~~~~~~~~│
-└────────┴────────┘
-```
-
-**Key Changes**:
-- **No Direct Equivalent for `SetTablePadding`**: `SetTablePadding` controlled inter-column spacing when borders were off in v0.0.5; v1.0.x has no direct equivalent for *inter-column* spacing separate from cell padding. Inter-column visual separation is now primarily handled by `tw.Rendition.Settings.Separators.BetweenColumns` and the chosen `tw.Symbols`.
-- **Granular Cell Padding**: `tw.CellPadding` (`tw/cell.go:CellPadding`) supports:
- - `Global`: A `tw.Padding` struct with `Left`, `Right`, `Top`, `Bottom` strings and an `Overwrite` flag (`tw/types.go:Padding`). This padding is *inside* the cell.
- - `PerColumn`: A slice of `tw.Padding` for column-specific padding, overriding `Global` for specified columns.
-- **Per-Side Control**: `Top` and `Bottom` padding add extra lines *within* cells, unlike v0.0.5’s left/right-only spacing (`zoo.go:prepareContent`).
-- **Defaults**: `tw.PaddingDefault` is `{Left: " ", Right: " "}` for all sections (applied inside cells); `Top` and `Bottom` are empty by default (`tw/preset.go`).
-- **Width Impact**: Cell padding contributes to column widths, calculated in `Config.Widths` (`zoo.go:calculateAndNormalizeWidths`).
-- **Presets**: `tw.PaddingNone` (`{Left: "", Right: "", Top: "", Bottom: "", Overwrite: true}`) removes padding for tight layouts (`tw/preset.go`).
-
-**Migration Tips**:
-- To achieve spacing similar to `SetTablePadding("\t")` when borders are off, you would set cell padding: `WithPadding(tw.Padding{Left: "\t", Right: "\t"})`. If you truly mean space *between* columns and not *inside* cells, ensure `tw.Rendition.Settings.Separators.BetweenColumns` is `tw.On` and customize `tw.Symbols.Column()` if needed.
-- Use `tw.PaddingNone` (e.g., via `ConfigBuilder.().Padding().WithGlobal(tw.PaddingNone)`) for no cell padding.
-- Set `Top` and `Bottom` padding for vertical spacing *within* cells, enhancing readability for multi-line content (`tw/types.go`).
-- Use `ConfigBuilder.().Padding().WithPerColumn` for column-specific padding to differentiate sections or columns (`config.go`).
-- Test padding with varied content and widths to ensure proper alignment and spacing, especially with wrapping enabled (`zoo.go`).
-- Combine padding with `Config.Widths` or `ColMaxWidths` to control total cell size (`config.go`).
-
-**Potential Pitfalls**:
-- **Inter-Column Spacing vs. Cell Padding**: Be clear whether you want space *between* columns (separators) or *inside* cells (padding). `SetTablePadding` was ambiguous; v1.0.x distinguishes these.
-- **Width Inflation**: Cell padding increases column widths, potentially exceeding `Config.MaxWidth` or causing truncation in streaming; adjust `Config.Widths` accordingly (`zoo.go`).
-- **Top/Bottom Padding**: Non-empty `Top` or `Bottom` padding adds vertical lines *within* cells, increasing cell height; use sparingly for dense tables (`zoo.go:prepareContent`).
-- **Streaming Constraints**: Padding is fixed in streaming mode after `Start()`; ensure `Config.Widths` accommodates padding (`stream.go`).
-- **Default Padding**: `tw.PaddingDefault` adds spaces *inside* cells; set `tw.PaddingNone` for no internal cell padding (`tw/preset.go`).
-
-### 4.7. Column Widths (Fixed Widths and Max Content Widths)
-Column width control in v1.0.x is more sophisticated, offering fixed widths, maximum content widths, and overall table width constraints, replacing v0.0.5’s limited `SetColMinWidth`.
-
-**Old (v0.0.5):**
-```go
-package main
-
-import (
- "github.com/olekukonko/tablewriter"
- "os"
-)
-
-func main() {
- table := tablewriter.NewWriter(os.Stdout)
- // table.SetColMinWidth(0, 10) // Set minimum width for column 0
- table.SetHeader([]string{"Header1", "Header2"})
- table.Append([]string{"Cell1-Content", "Cell2-Content"})
- table.Render()
-}
-```
-
-**New (v1.0.x):**
-```go
-package main
-
-import (
- "github.com/olekukonko/tablewriter"
- "github.com/olekukonko/tablewriter/tw"
- "os"
-)
-
-func main() {
- // Direct Config for Width Control
- cfg := tablewriter.Config{
- Widths: tw.CellWidth{ // Fixed total column widths (content + padding)
- Global: 20, // Default fixed width for all columns
- PerColumn: tw.NewMapper[int, int]().Set(0, 15), // Column 0 fixed at 15
- },
- Header: tw.CellConfig{
- ColMaxWidths: tw.CellWidth{ // Max content width (excluding padding) for header cells
- Global: 15, // Max content width for all header cells
- PerColumn: tw.NewMapper[int, int]().Set(0, 10), // Header Col 0 max content at 10
- },
- // Default header padding is " " on left/right, so content 10 + padding 2 = 12.
- // If Widths.PerColumn[0] is 15, there's space.
- },
- Row: tw.CellConfig{
- ColMaxWidths: tw.CellWidth{Global: 18}, // Max content width for row cells (18 + default padding 2 = 20)
- },
- MaxWidth: 80, // Constrain total table width (optional, columns might shrink)
- }
- tableWithCfg := tablewriter.NewTable(os.Stdout, tablewriter.WithConfig(cfg))
- tableWithCfg.Header("Very Long Header Name", "Status Information")
- tableWithCfg.Append("Some long content for the first column", "Ready")
- tableWithCfg.Render()
-
- // Option Functions for Width Settings
- tableWithOpts := tablewriter.NewTable(os.Stdout,
- tablewriter.WithColumnMax(20), // Sets Config.Widths.Global (fixed total col width)
- tablewriter.WithColumnWidths(tw.NewMapper[int, int]().Set(0, 15)), // Sets Config.Widths.PerColumn for col 0
- tablewriter.WithMaxWidth(80), // Sets Config.MaxWidth
- // For max content width per section, you'd use WithHeaderConfig, WithRowConfig, etc.
- // e.g., tablewriter.WithRowMaxWidth(18) // Sets Config.Row.ColMaxWidths.Global
- )
- tableWithOpts.Header("Long Header", "Status")
- tableWithOpts.Append("Long Content", "Ready")
- tableWithOpts.Render()
-}
-```
-
-**Output (tableWithCfg - illustrative, exact wrapping depends on content and full config):**
-```
-┌───────────┬──────────────────┐
-│ VERY LONG │ STATUS INFORMAT… │
-│ HEADER NA…│ │
-├───────────┼──────────────────┤
-│ Some long │ Ready │
-│ content f…│ │
-└───────────┴──────────────────┘
-```
-
-**Key Changes**:
-- **Enhanced Width System**: v1.0.x introduces three levels of width control, replacing `SetColMinWidth` (`config.go`):
- - **Config.Widths**: Sets fixed total widths (content + padding) for columns, applied globally or per-column (`tw.CellWidth`).
- - `Global`: Default fixed width for all columns.
- - `PerColumn`: `tw.Mapper[int, int]` for specific column widths.
- - **Config..ColMaxWidths**: Sets maximum content widths (excluding padding) for a section (Header, Row, Footer) (`tw.CellWidth`).
- - `Global`: Max content width for all cells in the section.
- - `PerColumn`: `tw.Mapper[int, int]` for specific columns in the section.
- - **Config.MaxWidth**: Constrains the total table width, shrinking columns proportionally if needed (`config.go`).
-- **Streaming Support**: In streaming mode, `Config.Widths` fixes column widths at `Start()`; `ColMaxWidths` is used only for wrapping/truncation (`stream.go:streamCalculateWidths`).
-- **Calculation Logic**: Widths are computed by `calculateAndNormalizeWidths` in batch mode and `streamCalculateWidths` in streaming mode, considering content, padding, and constraints (`zoo.go`, `stream.go`).
-- **Deprecated Approach**: `SetColMinWidth` is replaced by `Config.Widths.PerColumn` or `Config..ColMaxWidths.PerColumn` for more precise control (`deprecated.go`).
-
-**Migration Tips**:
-- Replace `SetColMinWidth(col, w)` with `WithColumnWidths(tw.NewMapper[int, int]().Set(col, w))` for fixed column widths or `Config..ColMaxWidths.PerColumn` for content width limits (`config.go`).
-- Use `Config.Widths.Global` or `WithColumnMax(w)` to set a default fixed width for all columns, ensuring consistency (`tablewriter.go`).
-- Apply `Config.MaxWidth` to constrain total table width, especially for wide datasets (`config.go`).
-- Use `ConfigBuilder.ForColumn(idx).WithMaxWidth(w)` to set per-column content width limits across sections (`config.go`). *(Note: This sets it for Header, Row, and Footer)*
-- In streaming mode, set `Config.Widths` before `Start()` to fix widths, avoiding content-based sizing (`stream.go`).
-- Test width settings with varied content to ensure wrapping and truncation behave as expected (`zoo.go`).
-
-**Potential Pitfalls**:
-- **Width Precedence**: `Config.Widths.PerColumn` overrides `Widths.Global`; `ColMaxWidths` applies *within* those fixed total widths for content wrapping/truncation (`zoo.go`).
-- **Streaming Width Fixing**: Widths are locked after `Start()` in streaming; inconsistent data may cause truncation (`stream.go`).
-- **Padding Impact**: Padding adds to total width when considering `Config.Widths`; account for `tw.CellPadding` when setting fixed column widths (`zoo.go`).
-- **MaxWidth Shrinkage**: `Config.MaxWidth` may shrink columns unevenly; test with `MaxWidth` to avoid cramped layouts (`zoo.go`).
-- **No Width Constraints**: Without `Widths` or `MaxWidth`, columns size to content, potentially causing overflow; define limits (`zoo.go`).
-
-### 4.8. Colors
-Colors in v0.0.5 were applied via specific color-setting methods, while v1.0.x embeds ANSI escape codes in cell content or uses data-driven formatting for greater flexibility.
-
-**Old (v0.0.5):**
-```go
-package main
-// Assuming tablewriter.Colors and color constants existed in v0.0.5
-// This is a mock representation as the actual v0.0.5 definitions are not provided.
-// import "github.com/olekukonko/tablewriter"
-// import "os"
-
-// type Colors []interface{} // Mock
-// const (
-// Bold = 1; FgGreenColor = 2; FgRedColor = 3 // Mock constants
-// )
-
-func main() {
- // table := tablewriter.NewWriter(os.Stdout)
- // table.SetColumnColor(
- // tablewriter.Colors{tablewriter.Bold, tablewriter.FgGreenColor}, // Column 0
- // tablewriter.Colors{tablewriter.FgRedColor}, // Column 1
- // )
- // table.SetHeader([]string{"Name", "Status"})
- // table.Append([]string{"Node1", "Ready"})
- // table.Render()
-}
-```
-
-**New (v1.0.x):**
-```go
-package main
-
-import (
- "fmt"
- "github.com/olekukonko/tablewriter"
- "github.com/olekukonko/tablewriter/tw" // For tw.Formatter
- "os"
-)
-
-// Direct ANSI Code Embedding
-const (
- Reset = "\033[0m"
- Bold = "\033[1m"
- FgGreen = "\033[32m"
- FgRed = "\033[31m"
-)
-
-// Using tw.Formatter for Custom Types
-type Status string
-
-func (s Status) Format() string { // Implements tw.Formatter
- color := FgGreen
- if s == "Error" || s == "Inactive" {
- color = FgRed
- }
- return color + string(s) + Reset
-}
-
-func main() {
- // Example 1: Direct ANSI embedding
- tableDirectANSI := tablewriter.NewTable(os.Stdout,
- tablewriter.WithHeaderAutoFormat(tw.Off), // Keep header text as is for coloring
- )
- tableDirectANSI.Header(Bold+FgGreen+"Name"+Reset, Bold+FgRed+"Status"+Reset)
- tableDirectANSI.Append([]any{"Node1", FgGreen + "Ready" + Reset})
- tableDirectANSI.Append([]any{"Node2", FgRed + "Error" + Reset})
- tableDirectANSI.Render()
-
- fmt.Println("\n--- Table with Formatter for Colors ---")
-
- // Example 2: Using tw.Formatter
- tableFormatter := tablewriter.NewTable(os.Stdout)
- tableFormatter.Header("Name", "Status") // AutoFormat will apply to "NAME", "STATUS"
- tableFormatter.Append([]any{"Alice", Status("Active")})
- tableFormatter.Append([]any{"Bob", Status("Inactive")})
- tableFormatter.Render()
-
- fmt.Println("\n--- Table with CellFilter for Colors ---")
- // Example 3: Using CellFilter
- tableWithFilters := tablewriter.NewTable(os.Stdout,
- tablewriter.WithConfig(
- tablewriter.NewConfigBuilder().
- Row().
- Filter().
- WithPerColumn([]func(string) string{
- nil, // No filter for Item column
- func(s string) string { // Status column: apply color
- if s == "Ready" || s == "Active" {
- return FgGreen + s + Reset
- }
- return FgRed + s + Reset
- },
- }).
- Build(). // Return to RowConfigBuilder
- Build(). // Return to ConfigBuilder
- Build(), // Finalize Config
- ),
- )
- tableWithFilters.Header("Item", "Availability")
- tableWithFilters.Append("ItemA", "Ready")
- tableWithFilters.Append("ItemB", "Unavailable")
- tableWithFilters.Render()
-}
-```
-
-**Output (Text Approximation, Colors Not Shown):**
-```
-┌──────┬────────┐
-│ Name │ Status │
-├──────┼────────┤
-│Node1 │ Ready │
-│Node2 │ Error │
-└──────┴────────┘
-
---- Table with Formatter for Colors ---
-┌───────┬──────────┐
-│ NAME │ STATUS │
-├───────┼──────────┤
-│ Alice │ Active │
-│ Bob │ Inactive │
-└───────┴──────────┘
-
---- Table with CellFilter for Colors ---
-┌───────┬────────────┐
-│ ITEM │ AVAILABILI │
-│ │ TY │
-├───────┼────────────┤
-│ ItemA │ Ready │
-│ ItemB │ Unavailabl │
-│ │ e │
-└───────┴────────────┘
-```
-
-**Key Changes**:
-- **Removed Color Methods**: `SetColumnColor`, `SetHeaderColor`, and `SetFooterColor` are removed; colors are now applied by embedding ANSI escape codes in cell content or via data-driven mechanisms (`tablewriter.go`).
-- **Flexible Coloring Options**:
- - **Direct ANSI Codes**: Embed codes (e.g., `\033[32m` for green) in strings for manual control (`zoo.go:convertCellsToStrings`).
- - **tw.Formatter**: Implement `Format() string` on custom types to control cell output, including colors (`tw/types.go:Formatter`).
- - **tw.CellFilter**: Use `Config..Filter.Global` or `PerColumn` to apply transformations like coloring dynamically (`tw/cell.go:CellFilter`).
-- **Width Handling**: `twdw.Width()` correctly calculates display width of ANSI-coded strings, ignoring escape sequences (`tw/fn.go:DisplayWidth`).
-- **No Built-In Color Presets**: Unlike v0.0.5’s potential `tablewriter.Colors`, v1.0.x requires manual ANSI code management or external libraries for color constants.
-
-**Migration Tips**:
-- Replace `SetColumnColor` with direct ANSI code embedding for simple cases (e.g., `FgGreen+"text"+Reset`) (`zoo.go`).
-- Implement `tw.Formatter` on custom types for reusable, semantic color logic (e.g., `Status` type above) (`tw/types.go`).
-- Use `ConfigBuilder.().Filter().WithPerColumn` to apply color filters to specific columns, mimicking v0.0.5’s per-column coloring (`config.go`).
-- Define ANSI constants in your codebase or use a library (e.g., `github.com/fatih/color`) to simplify color management.
-- Test colored output in your target terminal to ensure ANSI codes render correctly.
-- Combine filters with `AutoFormat` or wrapping for consistent styling (`zoo.go:prepareContent`).
-
-**Potential Pitfalls**:
-- **Terminal Support**: Some terminals may not support ANSI codes, causing artifacts; test in your environment or provide a non-colored fallback.
-- **Filter Overlap**: Combining `tw.CellFilter` with `AutoFormat` or other transformations can lead to unexpected results; prioritize filters for coloring (`zoo.go`).
-- **Width Miscalculation**: Incorrect ANSI code handling (e.g., missing `Reset`) can skew width calculations; use `twdw.Width` (`tw/fn.go`).
-- **Streaming Consistency**: In streaming mode, ensure color codes are applied consistently across rows to avoid visual discrepancies (`stream.go`).
-- **Performance**: Applying filters to large datasets may add overhead; optimize filter logic for efficiency (`zoo.go`).
-
-## Advanced Features in v1.0.x
-
-v1.0.x introduces several advanced features that enhance table functionality beyond v0.0.5’s capabilities. This section covers cell merging, captions, filters, stringers, and performance optimizations, providing migration guidance and examples for leveraging these features.
-
-### 5. Cell Merging
-Cell merging combines adjacent cells with identical content, improving readability for grouped data. v1.0.x expands merging options beyond v0.0.5’s horizontal merging.
-
-**Old (v0.0.5):**
-```go
-package main
-
-import (
- "github.com/olekukonko/tablewriter"
- "os"
-)
-
-func main() {
- table := tablewriter.NewWriter(os.Stdout)
- // table.SetAutoMergeCells(true) // Enable horizontal merging
- table.SetHeader([]string{"Category", "Item", "Notes"})
- table.Append([]string{"Fruit", "Apple", "Red"})
- table.Append([]string{"Fruit", "Apple", "Green"}) // "Apple" might merge if SetAutoMergeCells was on
- table.Render()
-}
-```
-
-**New (v1.0.x):**
-```go
-package main
-
-import (
- "github.com/olekukonko/tablewriter"
- "github.com/olekukonko/tablewriter/renderer"
- "github.com/olekukonko/tablewriter/tw"
- "os"
-)
-
-func main() {
- // Horizontal Merging (Similar to v0.0.5)
- tableH := tablewriter.NewTable(os.Stdout,
- tablewriter.WithConfig(tablewriter.Config{Row: tw.CellConfig{Formatting: tw.CellFormatting{MergeMode: tw.MergeHorizontal}}}),
- tablewriter.WithRenderer(renderer.NewBlueprint(tw.Rendition{Symbols: tw.NewSymbols(tw.StyleASCII)})), // Specify renderer for symbols
- )
- tableH.Header("Category", "Item", "Item", "Notes") // Note: Two "Item" headers for demo
- tableH.Append("Fruit", "Apple", "Apple", "Red") // "Apple" cells merge
- tableH.Render()
-
- // Vertical Merging
- tableV := tablewriter.NewTable(os.Stdout,
- tablewriter.WithConfig(tablewriter.Config{Row: tw.CellConfig{Formatting: tw.CellFormatting{MergeMode: tw.MergeVertical}}}),
- tablewriter.WithRenderer(renderer.NewBlueprint(tw.Rendition{Symbols: tw.NewSymbols(tw.StyleASCII)})),
- )
- tableV.Header("User", "Permission")
- tableV.Append("Alice", "Read")
- tableV.Append("Alice", "Write") // "Alice" cells merge vertically
- tableV.Append("Bob", "Read")
- tableV.Render()
-
- // Hierarchical Merging
- tableHier := tablewriter.NewTable(os.Stdout,
- tablewriter.WithConfig(tablewriter.Config{Row: tw.CellConfig{Formatting: tw.CellFormatting{MergeMode: tw.MergeHierarchical}}}),
- tablewriter.WithRenderer(renderer.NewBlueprint(tw.Rendition{Symbols: tw.NewSymbols(tw.StyleASCII)})),
- )
- tableHier.Header("Group", "SubGroup", "Item")
- tableHier.Append("Tech", "CPU", "i7")
- tableHier.Append("Tech", "CPU", "i9") // "Tech" and "CPU" merge
- tableHier.Append("Tech", "RAM", "16GB") // "Tech" merges, "RAM" is new
- tableHier.Append("Office", "CPU", "i5") // "Office" is new
- tableHier.Render()
-}
-```
-
-**Output (Horizontal):**
-```
-+----------+-------+-------+-------+
-| CATEGORY | ITEM | ITEM | NOTES |
-+----------+-------+-------+-------+
-| Fruit | Apple | Red |
-+----------+---------------+-------+
-```
-
-**Output (Vertical):**
-```
-+-------+------------+
-| USER | PERMISSION |
-+-------+------------+
-| Alice | Read |
-| | Write |
-+-------+------------+
-| Bob | Read |
-+-------+------------+
-```
-
-**Output (Hierarchical):**
-```
-+---------+----------+------+
-| GROUP | SUBGROUP | ITEM |
-+---------+----------+------+
-| Tech | CPU | i7 |
-| | | i9 |
-| +----------+------+
-| | RAM | 16GB |
-+---------+----------+------+
-| Office | CPU | i5 |
-+---------+----------+------+
-```
-
-**Key Changes**:
-- **Method**: `SetAutoMergeCells` is replaced by `WithRowMergeMode(int_merge_mode)` or `Config.Row.Formatting.MergeMode` (`config.go`). Uses `tw.Merge...` constants.
-- **Merge Modes**: `tw.MergeMode` constants (`tw.MergeNone`, `tw.MergeHorizontal`, `tw.MergeVertical`, `tw.MergeHierarchical`) define behavior (`tw/tw.go`).
-- **Section-Specific**: Merging can be applied to `Header`, `Row`, or `Footer` via `Config..Formatting.MergeMode` (`tw/cell.go`).
-- **Processing**: Merging is handled during content preparation (`zoo.go:prepareWithMerges`, `zoo.go:applyVerticalMerges`, `zoo.go:applyHierarchicalMerges`).
-- **Width Adjustment**: Horizontal merging adjusts column widths (`zoo.go:applyHorizontalMergeWidths`).
-- **Renderer Support**: `tw.MergeState` in `tw.CellContext` ensures correct border drawing for merged cells (`tw/cell.go:CellContext`).
-- **Streaming Limitation**: Streaming mode supports only simple horizontal merging due to fixed widths (`stream.go:streamAppendRow`).
-
-**Migration Tips**:
-- Replace `SetAutoMergeCells(true)` with `WithRowMergeMode(tw.MergeHorizontal)` to maintain v0.0.5’s horizontal merging behavior (`config.go`).
-- Use `tw.MergeVertical` for vertical grouping (e.g., repeated user names) or `tw.MergeHierarchical` for nested data structures (`tw/tw.go`).
-- Apply merging to specific sections via `ConfigBuilder.().Formatting().WithMergeMode(int_merge_mode)` (`config.go`).
-- Test merging with sample data to verify visual output, especially for hierarchical merging with complex datasets.
-- In streaming mode, ensure `Config.Widths` supports merged cell widths to avoid truncation (`stream.go`).
-- Use `WithDebug(true)` to log merge processing for troubleshooting (`config.go`).
-
-**Potential Pitfalls**:
-- **Streaming Restrictions**: Vertical and hierarchical merging are unsupported in streaming mode; use batch mode for these features (`stream.go`).
-- **Width Misalignment**: Merged cells may require wider columns; adjust `Config.Widths` or `ColMaxWidths` (`zoo.go`).
-- **Data Dependency**: Merging requires identical content; case or whitespace differences prevent merging (`zoo.go`).
-- **Renderer Errors**: Incorrect `tw.MergeState` handling in custom renderers can break merged cell borders; test thoroughly (`tw/cell.go`).
-- **Performance**: Hierarchical merging with large datasets may slow rendering; optimize data or limit merging (`zoo.go`).
-
-### 6. Table Captions
-Captions add descriptive text above or below the table, a new feature in v1.0.x not present in v0.0.5.
-
-**Old (v0.0.5):**
-```go
-package main
-// No direct caption support in v0.0.5. Users might have printed text manually.
-// import "github.com/olekukonko/tablewriter"
-// import "os"
-// import "fmt"
-
-func main() {
- // fmt.Println("Movie ratings.") // Manual caption
- // table := tablewriter.NewWriter(os.Stdout)
- // table.SetHeader([]string{"Name", "Sign", "Rating"})
- // table.Render()
-}
-```
-
-**New (v1.0.x):**
-```go
-package main
-
-import (
- "github.com/olekukonko/tablewriter"
- "github.com/olekukonko/tablewriter/tw"
- "os"
-)
-
-func main() {
- table := tablewriter.NewTable(os.Stdout)
- table.Caption(tw.Caption{ // tw/types.go:Caption
- Text: "System Status Overview - A Very Long Caption Example To Demonstrate Wrapping Behavior",
- Spot: tw.SpotTopCenter, // Placement: TopLeft, TopCenter, TopRight, BottomLeft, BottomCenter, BottomRight
- Align: tw.AlignCenter, // Text alignment within caption width
- Width: 30, // Fixed width for caption text wrapping; if 0, wraps to table width
- })
- table.Header("Name", "Status")
- table.Append("Node1", "Ready")
- table.Append("SuperLongNodeNameHere", "ActiveNow")
- table.Render()
-}
-```
-
-**Output:**
-```
- System Status Overview - A Very
-Long Caption Example To Demonst…
-┌─────────────────────┬──────────┐
-│ NAME │ STATUS │
-├─────────────────────┼──────────┤
-│ Node1 │ Ready │
-│ SuperLongNodeNameHe │ ActiveNo │
-│ re │ w │
-└─────────────────────┴──────────┘
-```
-
-**Key Changes**:
-- **New Feature**: `Table.Caption(tw.Caption)` introduces captions, absent in v0.0.5 (`tablewriter.go:Caption`).
-- **Configuration**: `tw.Caption` (`tw/types.go:Caption`) includes:
- - `Text`: Caption content.
- - `Spot`: Placement (`tw.SpotTopLeft`, `tw.SpotBottomCenter`, etc.); defaults to `tw.SpotBottomCenter` if `tw.SpotNone`.
- - `Align`: Text alignment (`tw.AlignLeft`, `tw.AlignCenter`, `tw.AlignRight`).
- - `Width`: Optional fixed width for wrapping; defaults to table width.
-- **Rendering**: Captions are rendered by `printTopBottomCaption` before or after the table based on `Spot` (`tablewriter.go:printTopBottomCaption`).
-- **Streaming**: Captions are rendered during `Close()` in streaming mode if placed at the bottom (`stream.go`).
-
-**Migration Tips**:
-- Add captions to enhance table context, especially for reports or documentation (`tw/types.go`).
-- Use `tw.SpotTopCenter` for prominent placement above the table, aligning with common report formats.
-- Set `Align` to match table aesthetics (e.g., `tw.AlignCenter` for balanced appearance).
-- Specify `Width` for consistent wrapping, especially with long captions or narrow tables (`tablewriter.go`).
-- Test caption placement and alignment with different table sizes to ensure readability.
-
-**Potential Pitfalls**:
-- **Spot Default**: If `Spot` is `tw.SpotNone`, caption defaults to `tw.SpotBottomCenter`, which may surprise users expecting no caption (`tablewriter.go`).
-- **Width Overflow**: Without `Width`, captions wrap to table width, potentially causing misalignment; set explicitly for control (`tw/types.go`).
-- **Streaming Delay**: Bottom-placed captions in streaming mode appear only at `Close()`; ensure `Close()` is called (`stream.go`).
-- **Alignment Confusion**: Caption `Align` is independent of table cell alignment; verify separately (`tw/cell.go`).
-
-### 7. Filters
-Filters allow dynamic transformation of cell content during rendering, a new feature in v1.0.x for tasks like formatting, coloring, or sanitizing data.
-
-**Old (v0.0.5):**
-```go
-package main
-// No direct support for cell content transformation in v0.0.5.
-// Users would typically preprocess data before appending.
-// import "github.com/olekukonko/tablewriter"
-// import "os"
-// import "strings"
-
-func main() {
- // table := tablewriter.NewWriter(os.Stdout)
- // table.SetHeader([]string{"Name", "Status"})
- // status := " Ready "
- // preprocessedStatus := "Status: " + strings.TrimSpace(status)
- // table.Append([]string{"Node1", preprocessedStatus})
- // table.Render()
-}
-```
-
-**New (v1.0.x):**
-```go
-package main
-
-import (
- "github.com/olekukonko/tablewriter"
- "github.com/olekukonko/tablewriter/tw" // For tw.CellConfig etc.
- "os"
- "strings"
-)
-
-func main() {
- // Per-Column Filter for Specific Transformations
- cfgBuilder := tablewriter.NewConfigBuilder()
- cfgBuilder.Row().Filter().WithPerColumn([]func(string) string{
- nil, // No filter for Name column
- func(s string) string { // Status column: prefix and trim
- return "Status: " + strings.TrimSpace(s)
- },
- })
-
- tableWithFilter := tablewriter.NewTable(os.Stdout, tablewriter.WithConfig(cfgBuilder.Build()))
- tableWithFilter.Header("Name", "Status")
- tableWithFilter.Append("Node1", " Ready ") // Note the extra spaces
- tableWithFilter.Append("Node2", "Pending")
- tableWithFilter.Render()
-
- // Global filter example (applied to all cells in the Row section)
- cfgGlobalFilter := tablewriter.NewConfigBuilder()
- cfgGlobalFilter.Row().Filter().WithGlobal(func(s string) string {
- return "[" + s + "]" // Wrap all row cells in brackets
- })
- tableGlobalFilter := tablewriter.NewTable(os.Stdout, tablewriter.WithConfig(cfgGlobalFilter.Build()))
- tableGlobalFilter.Header("Item", "Count")
- tableGlobalFilter.Append("Apple", "5")
- tableGlobalFilter.Render()
-}
-```
-
-**Output (Per-Column Filter):**
-```
-┌───────┬─────────────────┐
-│ NAME │ STATUS │
-├───────┼─────────────────┤
-│ Node1 │ Status: Ready │
-│ Node2 │ Status: Pending │
-└───────┴─────────────────┘
-```
-
-**Output (Global Filter):**
-```
-┌───────┬─────────┐
-│ ITEM │ COUNT │
-├───────┼─────────┤
-│[Apple]│ [5] │
-└───────┴─────────┘
-```
-
-**Key Changes**:
-- **New Feature**: `tw.CellFilter` (`tw/cell.go:CellFilter`) introduces:
- - `Global`: A `func(s []string) []string` applied to entire rows (all cells in that row) of a section.
- - `PerColumn`: A slice of `func(string) string` for column-specific transformations on individual cells.
-- **Configuration**: Set via `Config..Filter` (`Header`, `Row`, `Footer`) using `ConfigBuilder` or direct `Config` (`config.go`).
-- **Processing**: Filters are applied during content preparation, after `AutoFormat` but before rendering (`zoo.go:convertCellsToStrings` calls `prepareContent` which applies some transformations, filters are applied in `convertCellsToStrings` itself).
-- **Use Cases**: Formatting (e.g., uppercase, prefixes), coloring (via ANSI codes), sanitization (e.g., removing sensitive data), or data normalization.
-
-**Migration Tips**:
-- Use filters to replace manual content preprocessing in v0.0.5 (e.g., string manipulation before `Append`).
-- Apply `Global` filters for uniform transformations across all cells of rows in a section (e.g., uppercasing all row data) (`tw/cell.go`).
-- Use `PerColumn` filters for column-specific formatting (e.g., adding prefixes to status columns) (`config.go`).
-- Combine filters with `tw.Formatter` for complex types or ANSI coloring for visual enhancements (`tw/cell.go`).
-- Test filters with diverse inputs to ensure transformations preserve data integrity (`zoo.go`).
-
-**Potential Pitfalls**:
-- **Filter Order**: Filters apply before some other transformations like padding and alignment; combining can lead to interactions.
-- **Performance**: Complex filters on large datasets may slow rendering; optimize logic (`zoo.go`).
-- **Nil Filters**: Unset filters (`nil`) are ignored, but incorrect indexing in `PerColumn` can skip columns (`tw/cell.go`).
-- **Streaming Consistency**: Filters must be consistent in streaming mode, as widths are fixed at `Start()` (`stream.go`).
-
-### 8. Stringers and Caching
-Stringers allow custom string conversion for data types, with v1.0.x adding caching for performance.
-
-**Old (v0.0.5):**
-```go
-package main
-// v0.0.5 primarily relied on fmt.Stringer for custom types.
-// import "fmt"
-// import "github.com/olekukonko/tablewriter"
-// import "os"
-
-// type MyCustomType struct {
-// Value string
-// }
-// func (m MyCustomType) String() string { return "Formatted: " + m.Value }
-
-func main() {
- // table := tablewriter.NewWriter(os.Stdout)
- // table.SetHeader([]string{"Custom Data"})
- // table.Append([]string{MyCustomType{"test"}.String()}) // Manual call to String()
- // table.Render()
-}
-```
-
-**New (v1.0.x):**
-```go
-package main
-
-import (
- "fmt"
- "github.com/olekukonko/tablewriter"
- "github.com/olekukonko/tablewriter/tw" // For tw.Formatter
- "os"
- "strings" // For Person example
-)
-
-// Example 1: Using WithStringer for general conversion
-type CustomInt int
-
-func main() {
- // Table with a general stringer (func(any) []string)
- tableWithStringer := tablewriter.NewTable(os.Stdout,
- tablewriter.WithStringer(func(v any) []string { // Must return []string
- if ci, ok := v.(CustomInt); ok {
- return []string{fmt.Sprintf("CustomInt Value: %d!", ci)}
- }
- return []string{fmt.Sprintf("Value: %v", v)} // Fallback
- }),
- tablewriter.WithDebug(true), // Enable caching if WithStringerCache() is also used
- // tablewriter.WithStringerCache(), // Optional: enable caching
- )
- tableWithStringer.Header("Data")
- tableWithStringer.Append(123)
- tableWithStringer.Append(CustomInt(456))
- tableWithStringer.Render()
-
- fmt.Println("\n--- Table with Type-Specific Stringer for Structs ---")
-
- // Example 2: Stringer for a specific struct type
- type Person struct {
- ID int
- Name string
- City string
- }
-
- // Stringer for Person to produce 3 cells
- personToStrings := func(p Person) []string {
- return []string{
- fmt.Sprintf("ID: %d", p.ID),
- p.Name,
- strings.ToUpper(p.City),
- }
- }
-
- tablePersonStringer := tablewriter.NewTable(os.Stdout,
- tablewriter.WithStringer(personToStrings), // Pass the type-specific function
- )
- tablePersonStringer.Header("User ID", "Full Name", "Location")
- tablePersonStringer.Append(Person{1, "Alice", "New York"})
- tablePersonStringer.Append(Person{2, "Bob", "London"})
- tablePersonStringer.Render()
-
- fmt.Println("\n--- Table with tw.Formatter ---")
- // Example 3: Using tw.Formatter for types
- type Product struct {
- Name string
- Price float64
- }
- func (p Product) Format() string { // Implements tw.Formatter
- return fmt.Sprintf("%s - $%.2f", p.Name, p.Price)
- }
- tableFormatter := tablewriter.NewTable(os.Stdout)
- tableFormatter.Header("Product Details")
- tableFormatter.Append(Product{"Laptop", 1200.99}) // Will use Format()
- tableFormatter.Render()
-}
-```
-
-**Output (Stringer Examples):**
-```
-┌─────────────────────┐
-│ DATA │
-├─────────────────────┤
-│ Value: 123 │
-│ CustomInt Value: 456! │
-└─────────────────────┘
-
---- Table with Type-Specific Stringer for Structs ---
-┌─────────┬───────────┬──────────┐
-│ USER ID │ FULL NAME │ LOCATION │
-├─────────┼───────────┼──────────┤
-│ ID: 1 │ Alice │ NEW YORK │
-│ ID: 2 │ Bob │ LONDON │
-└─────────┴───────────┴──────────┘
-
---- Table with tw.Formatter ---
-┌─────────────────┐
-│ PRODUCT DETAILS │
-├─────────────────┤
-│ Laptop - $1200… │
-└─────────────────┘
-```
-
-**Key Changes**:
-- **Stringer Support**: `WithStringer(fn any)` sets a table-wide string conversion function. This function must have a signature like `func(SomeType) []string` or `func(any) []string`. It's used to convert an input item (e.g., a struct) into a slice of strings, where each string is a cell for the row (`tablewriter.go:WithStringer`).
-- **Caching**: `WithStringerCache()` enables caching for the function provided via `WithStringer`, improving performance for repeated conversions of the same input type (`tablewriter.go:WithStringerCache`).
-- **Formatter**: `tw.Formatter` interface (`Format() string`) allows types to define their own single-string representation for a cell. This is checked before `fmt.Stringer` (`tw/types.go:Formatter`).
-- **Priority**: When converting an item to cell(s):
- 1. `WithStringer` (if provided and compatible with the item's type).
- 2. If not handled by `WithStringer`, or if `WithStringer` is not set:
- * If the item is a struct that does *not* implement `tw.Formatter` or `fmt.Stringer`, its exported fields are reflected into multiple cells.
- * If the item (or struct) implements `tw.Formatter` (`Format() string`), that's used for a single cell.
- * Else, if it implements `fmt.Stringer` (`String() string`), that's used for a single cell.
- * Else, default `fmt.Sprintf("%v", ...)` for a single cell.
- (`zoo.go:convertCellsToStrings`, `zoo.go:convertItemToCells`)
-
-**Migration Tips**:
-- For types that should produce a single cell with custom formatting, implement `tw.Formatter`.
-- For types (especially structs) that should be expanded into multiple cells with custom logic, use `WithStringer` with a function like `func(MyType) []string`.
-- If you have a general way to convert *any* type into a set of cells, use `WithStringer(func(any) []string)`.
-- Enable `WithStringerCache` for large datasets with repetitive data types if using `WithStringer`.
-- Test stringer/formatter output to ensure formatting meets expectations (`zoo.go`).
-
-**Potential Pitfalls**:
-- **Cache Overhead**: `WithStringerCache` may increase memory usage for diverse data; disable for small tables or if not using `WithStringer` (`tablewriter.go`).
-- **Stringer Signature**: The function passed to `WithStringer` *must* return `[]string`. A function returning `string` will lead to a warning and fallback behavior.
-- **Formatter vs. Stringer Priority**: Be aware of the conversion priority if your types implement multiple interfaces or if you also use `WithStringer`.
-- **Streaming**: Stringers/Formatters must produce consistent cell counts in streaming mode to maintain width alignment (`stream.go`).
-
-## Examples
-
-This section provides practical examples to demonstrate v1.0.x features, covering common and advanced use cases to aid migration. Each example includes code, output, and notes to illustrate functionality.
-
-### Example: Minimal Setup
-A basic table with default settings, ideal for quick setups.
-
-```go
-package main
-
-import (
- "github.com/olekukonko/tablewriter"
- "os"
-)
-
-func main() {
- table := tablewriter.NewTable(os.Stdout)
- table.Header("Name", "Status")
- table.Append("Node1", "Ready")
- table.Render()
-}
-```
-
-**Output**:
-```
-┌───────┬────────┐
-│ NAME │ STATUS │
-├───────┼────────┤
-│ Node1 │ Ready │
-└───────┴────────┘
-```
-
-**Notes**:
-- Uses default `renderer.NewBlueprint()` and `defaultConfig()` settings (`tablewriter.go`, `config.go`).
-- `Header: tw.AlignCenter`, `Row: tw.AlignLeft` (`config.go:defaultConfig`).
-- Simple replacement for v0.0.5’s `NewWriter` and `SetHeader`/`Append`.
-
-### Example: Streaming with Fixed Widths
-Demonstrates streaming mode for real-time data output.
-
-```go
-package main
-
-import (
- "fmt"
- "github.com/olekukonko/tablewriter"
- "github.com/olekukonko/tablewriter/tw"
- "log"
- "os"
-)
-
-func main() {
- table := tablewriter.NewTable(os.Stdout,
- tablewriter.WithStreaming(tw.StreamConfig{Enable: true}),
- tablewriter.WithColumnMax(10), // Sets Config.Widths.Global
- )
- if err := table.Start(); err != nil {
- log.Fatalf("Start failed: %v", err)
- }
- table.Header("Name", "Status")
- for i := 0; i < 2; i++ {
- err := table.Append(fmt.Sprintf("Node%d", i+1), "Ready")
- if err != nil {
- log.Printf("Append failed: %v", err)
- }
- }
- if err := table.Close(); err != nil {
- log.Fatalf("Close failed: %v", err)
- }
-}
-```
-
-**Output**:
-```
-┌──────────┬──────────┐
-│ NAME │ STATUS │
-├──────────┼──────────┤
-│ Node1 │ Ready │
-│ Node2 │ Ready │
-└──────────┴──────────┘
-```
-
-**Notes**:
-- Streaming requires `Start()` and `Close()`; `Config.Widths` (here set via `WithColumnMax`) fixes widths (`stream.go`).
-- Replaces v0.0.5’s batch rendering for real-time use cases.
-- Ensure error handling for `Start()`, `Append()`, and `Close()`.
-
-### Example: Markdown-Style Table
-Creates a Markdown-compatible table for documentation.
-
-```go
-package main
-
-import (
- "fmt"
- "github.com/olekukonko/tablewriter"
- "github.com/olekukonko/tablewriter/renderer" // Import renderer
- "github.com/olekukonko/tablewriter/tw"
- "os"
-)
-
-func main() {
- // Example 1: Using Blueprint renderer with Markdown symbols
- tableBlueprintMarkdown := tablewriter.NewTable(os.Stdout,
- tablewriter.WithRenderer(renderer.NewBlueprint()), // Use Blueprint
- tablewriter.WithRendition(tw.Rendition{
- Symbols: tw.NewSymbols(tw.StyleMarkdown),
- Borders: tw.Border{Left: tw.On, Right: tw.On}, // Markdown needs left/right borders
- }),
- tablewriter.WithRowAlignment(tw.AlignLeft), // Common for Markdown
- tablewriter.WithHeaderAlignment(tw.AlignCenter), // Center align headers
- )
- tableBlueprintMarkdown.Header("Name", "Status")
- tableBlueprintMarkdown.Append("Node1", "Ready")
- tableBlueprintMarkdown.Append("Node2", "NotReady")
- tableBlueprintMarkdown.Render()
-
- fmt.Println("\n--- Using dedicated Markdown Renderer (if one exists or is built) ---")
- // Example 2: Assuming a dedicated Markdown renderer (hypothetical example)
- // If a `renderer.NewMarkdown()` existed that directly outputs GitHub Flavored Markdown table syntax:
- /*
- tableDedicatedMarkdown := tablewriter.NewTable(os.Stdout,
- tablewriter.WithRenderer(renderer.NewMarkdown()), // Hypothetical Markdown renderer
- )
- tableDedicatedMarkdown.Header("Name", "Status")
- tableDedicatedMarkdown.Append("Node1", "Ready")
- tableDedicatedMarkdown.Append("Node2", "NotReady")
- tableDedicatedMarkdown.Render()
- */
- // Since `renderer.NewMarkdown()` isn't shown in the provided code,
- // the first example (Blueprint with StyleMarkdown) is the current viable way.
-}
-```
-
-**Output (Blueprint with StyleMarkdown):**
-```
-| NAME | STATUS |
-|--------|----------|
-| Node1 | Ready |
-| Node2 | NotReady |
-```
-
-**Notes**:
-- `StyleMarkdown` ensures compatibility with Markdown parsers (`tw/symbols.go`).
-- Left alignment for rows and center for headers is common for Markdown readability (`config.go`).
-- Ideal for GitHub READMEs or documentation.
-- A dedicated Markdown renderer (like the commented-out example) would typically handle alignment syntax (e.g., `|:---:|:---|`). With `Blueprint` and `StyleMarkdown`, alignment is visual within the text rather than Markdown syntax.
-
-### Example: ASCII-Style Table
-Uses `StyleASCII` for maximum terminal compatibility.
-
-```go
-package main
-
-import (
- "github.com/olekukonko/tablewriter"
- "github.com/olekukonko/tablewriter/renderer"
- "github.com/olekukonko/tablewriter/tw"
- "os"
-)
-
-func main() {
- table := tablewriter.NewTable(os.Stdout,
- tablewriter.WithRenderer(renderer.NewBlueprint()),
- tablewriter.WithRendition(tw.Rendition{
- Symbols: tw.NewSymbols(tw.StyleASCII),
- }),
- tablewriter.WithRowAlignment(tw.AlignLeft),
- )
- table.Header("ID", "Value")
- table.Append("1", "Test")
- table.Render()
-}
-```
-
-**Output**:
-```
-+----+-------+
-│ ID │ VALUE │
-+----+-------+
-│ 1 │ Test │
-+----+-------+
-```
-
-**Notes**:
-- `StyleASCII` is robust for all terminals (`tw/symbols.go`).
-- Replaces v0.0.5’s default style with explicit configuration.
-
-
-### Example: Kubectl-Style Output
-Creates a borderless, minimal table similar to `kubectl` command output, emphasizing simplicity and readability.
-
-```go
-package main
-
-import (
- "os"
- "sync"
- "github.com/olekukonko/tablewriter"
- "github.com/olekukonko/tablewriter/renderer"
- "github.com/olekukonko/tablewriter/tw"
-)
-
-var wg sync.WaitGroup
-
-func main() {
- data := [][]any{
- {"node1.example.com", "Ready", "compute", "1.11"},
- {"node2.example.com", "Ready", "compute", "1.11"},
- {"node3.example.com", "Ready", "compute", "1.11"},
- {"node4.example.com", "NotReady", "compute", "1.11"},
- }
-
- table := tablewriter.NewTable(os.Stdout,
- tablewriter.WithRenderer(renderer.NewBlueprint(tw.Rendition{
- Borders: tw.BorderNone,
- Settings: tw.Settings{
- Separators: tw.SeparatorsNone,
- Lines: tw.LinesNone,
- },
- })),
- tablewriter.WithConfig(tablewriter.Config{
- Header: tw.CellConfig{
- Formatting: tw.CellFormatting{Alignment: tw.AlignLeft},
- },
- Row: tw.CellConfig{
- Formatting: tw.CellFormatting{Alignment: tw.AlignLeft},
- Padding: tw.CellPadding{Global: tw.PaddingNone},
- },
- }),
- )
- table.Header("Name", "Status", "Role", "Version")
- table.Bulk(data)
- table.Render()
-}
-```
-
-**Output:**
-```
-NAME STATUS ROLE VERSION
-node1.example.com Ready compute 1.21.3
-node2.example.com Ready infra 1.21.3
-node3.example.com NotReady compute 1.20.7
-```
-
-**Notes**:
-- **Configuration**: Uses `tw.BorderNone`, `tw.LinesNone`, `tw.SeparatorsNone`, `tw.NewSymbols(tw.StyleNone)` and specific padding (`Padding{Right:" "}`) for a minimal, borderless layout.
-- **Migration from v0.0.5**: Replaces `SetBorder(false)` and manual spacing with `tw.Rendition` and `Config` settings, achieving a cleaner kubectl-like output.
-- **Key Features**:
- - Left-aligned text for readability (`config.go`).
- - `WithTrimSpace(tw.Off)` preserves spacing (`config.go`).
- - `Bulk` efficiently adds multiple rows (`tablewriter.go`).
- - Padding `Right: " "` is used to create space between columns as separators are off.
-- **Best Practices**: Test in terminals to ensure spacing aligns with command-line aesthetics; use `WithDebug(true)` for layout issues (`config.go`).
-- **Potential Issues**: Column widths are content-based. For very long content in one column and short in others, it might not look perfectly aligned like fixed-width CLI tools. `Config.Widths` could be used for more control if needed.
-
-### Example: Hierarchical Merging
-Demonstrates hierarchical cell merging for nested data structures, a new feature in v1.0.x.
-
-```go
-package main
-
-import (
- "github.com/olekukonko/tablewriter"
- "github.com/olekukonko/tablewriter/renderer"
- "github.com/olekukonko/tablewriter/tw"
- "os"
-)
-
-func main() {
- data := [][]string{
- // Header row is separate
- {"table\nwriter", "v0.0.1", "legacy"},
- {"table\nwriter", "v0.0.2", "legacy"},
- {"table\nwriter", "v0.0.2", "legacy"}, // Duplicate for testing merge
- {"table\nwriter", "v0.0.2", "legacy"}, // Duplicate for testing merge
- {"table\nwriter", "v0.0.5", "legacy"},
- {"table\nwriter", "v1.0.6", "latest"},
- }
-
- rendition := tw.Rendition{
- Symbols: tw.NewSymbols(tw.StyleLight), // Use light for clearer merge lines
- Settings: tw.Settings{
- Separators: tw.Separators{BetweenRows: tw.On},
- Lines: tw.Lines{ShowHeaderLine: tw.On, ShowFooterLine: tw.On}, // Show header line
- },
- Borders: tw.Border{Left:tw.On, Right:tw.On, Top:tw.On, Bottom:tw.On},
- }
-
- tableHier := tablewriter.NewTable(os.Stdout,
- tablewriter.WithRenderer(renderer.NewBlueprint()),
- tablewriter.WithRendition(rendition),
- tablewriter.WithConfig(tablewriter.Config{
- Row: tw.CellConfig{
- Formatting: tw.CellFormatting{
- MergeMode: tw.MergeHierarchical,
- // Alignment: tw.AlignCenter, // Default is Left, often better for hierarchical
- AutoWrap: tw.WrapNormal, // Allow wrapping for "table\nwriter"
- },
- },
- }),
- )
-
- tableHier.Header("Package", "Version", "Status") // Header
- tableHier.Bulk(data) // Bulk data
- tableHier.Render()
-
- // --- Vertical Merging Example for Contrast ---
- tableVert := tablewriter.NewTable(os.Stdout,
- tablewriter.WithRenderer(renderer.NewBlueprint()),
- tablewriter.WithRendition(rendition), // Reuse same rendition
- tablewriter.WithConfig(tablewriter.Config{
- Row: tw.CellConfig{
- Formatting: tw.CellFormatting{
- MergeMode: tw.MergeVertical,
- AutoWrap: tw.WrapNormal,
- },
- },
- }),
- )
- tableVert.Header("Package", "Version", "Status")
- tableVert.Bulk(data)
- tableVert.Render()
-}
-```
-
-**Output (Hierarchical):**
-```
-┌─────────┬─────────┬────────┐
-│ PACKAGE │ VERSION │ STATUS │
-├─────────┼─────────┼────────┤
-│ table │ v0.0.1 │ legacy │
-│ writer │ │ │
-│ ├─────────┼────────┤
-│ │ v0.0.2 │ legacy │
-│ │ │ │
-│ │ │ │
-│ │ │ │
-│ │ │ │
-│ ├─────────┼────────┤
-│ │ v0.0.5 │ legacy │
-│ ├─────────┼────────┤
-│ │ v1.0.6 │ latest │
-└─────────┴─────────┴────────┘
-```
-**Output (Vertical):**
-```
-┌─────────┬─────────┬────────┐
-│ PACKAGE │ VERSION │ STATUS │
-├─────────┼─────────┼────────┤
-│ table │ v0.0.1 │ legacy │
-│ writer │ │ │
-│ ├─────────┤ │
-│ │ v0.0.2 │ │
-│ │ │ │
-│ │ │ │
-│ │ │ │
-│ │ │ │
-│ ├─────────┤ │
-│ │ v0.0.5 │ │
-│ ├─────────┼────────┤
-│ │ v1.0.6 │ latest │
-└─────────┴─────────┴────────┘
-```
-
-**Notes**:
-- **Configuration**: Uses `tw.MergeHierarchical` to merge cells based on matching values in preceding columns (`tw/tw.go`).
-- **Migration from v0.0.5**: Extends `SetAutoMergeCells(true)` (horizontal only) with hierarchical merging for complex data (`tablewriter.go`).
-- **Key Features**:
- - `StyleLight` for clear visuals (`tw/symbols.go`).
- - `Bulk` for efficient data loading (`tablewriter.go`).
-- **Best Practices**: Test merging with nested data; use batch mode, as streaming doesn’t support hierarchical merging (`stream.go`).
-- **Potential Issues**: Ensure data is sorted appropriately for hierarchical merging to work as expected; mismatches prevent merging (`zoo.go`). `AutoWrap: tw.WrapNormal` helps with multi-line cell content like "table\nwriter".
-
-### Example: Colorized Table
-Applies ANSI colors to highlight status values, replacing v0.0.5’s `SetColumnColor`.
-
-```go
-package main
-
-import (
- "github.com/olekukonko/tablewriter"
- "github.com/olekukonko/tablewriter/tw" // For tw.State, tw.CellConfig etc.
- "os"
-)
-
-func main() {
- const (
- FgGreen = "\033[32m"
- FgRed = "\033[31m"
- Reset = "\033[0m"
- )
-
- cfgBuilder := tablewriter.NewConfigBuilder()
- cfgBuilder.Row().Filter().WithPerColumn([]func(string) string{
- nil, // No filter for Name
- func(s string) string { // Color Status
- if s == "Ready" {
- return FgGreen + s + Reset
- }
- return FgRed + s + Reset
- },
- })
-
- table := tablewriter.NewTable(os.Stdout, tablewriter.WithConfig(cfgBuilder.Build()))
- table.Header("Name", "Status")
- table.Append("Node1", "Ready")
- table.Append("Node2", "Error")
- table.Render()
-}
-```
-
-**Output (Text Approximation, Colors Not Shown):**
-```
-┌───────┬────────┐
-│ NAME │ STATUS │
-├───────┼────────┤
-│ Node1 │ Ready │
-│ Node2 │ Error │
-└───────┴────────┘
-```
-
-**Notes**:
-- **Configuration**: Uses `tw.CellFilter` for per-column coloring, embedding ANSI codes (`tw/cell.go`).
-- **Migration from v0.0.5**: Replaces `SetColumnColor` with dynamic filters (`tablewriter.go`).
-- **Key Features**: Flexible color application; `twdw.Width` handles ANSI codes correctly (`tw/fn.go`).
-- **Best Practices**: Test in ANSI-compatible terminals; use constants for code clarity.
-- **Potential Issues**: Non-ANSI terminals may show artifacts; provide fallbacks (`tw/fn.go`).
-
-### Example: Vertical Merging
-Shows vertical cell merging for repeated values in a column. (This example was very similar to the hierarchical one, so I'll ensure it's distinct by using simpler data).
-
-```go
-package main
-
-import (
- "github.com/olekukonko/tablewriter"
- "github.com/olekukonko/tablewriter/renderer"
- "github.com/olekukonko/tablewriter/tw"
- "os"
-)
-
-func main() {
- table := tablewriter.NewTable(os.Stdout,
- tablewriter.WithRenderer(renderer.NewBlueprint()), // Default renderer
- tablewriter.WithRendition(tw.Rendition{
- Symbols: tw.NewSymbols(tw.StyleLight),
- Settings: tw.Settings{Separators: tw.Separators{BetweenRows: tw.On}},
- Borders: tw.Border{Left:tw.On, Right:tw.On, Top:tw.On, Bottom:tw.On},
- }),
- tablewriter.WithConfig(
- tablewriter.NewConfigBuilder().
- Row().Formatting().WithMergeMode(tw.MergeVertical).Build(). // Enable Vertical Merge
- Build(),
- ),
- )
- table.Header("User", "Permission", "Target")
- table.Append("Alice", "Read", "FileA")
- table.Append("Alice", "Write", "FileA") // Alice and FileA will merge vertically
- table.Append("Alice", "Read", "FileB")
- table.Append("Bob", "Read", "FileA")
- table.Append("Bob", "Read", "FileC") // Bob and Read will merge
- table.Render()
-}
-```
-
-**Output:**
-```
-┌───────┬────────────┬────────┐
-│ USER │ PERMISSION │ TARGET │
-├───────┼────────────┼────────┤
-│ Alice │ Read │ FileA │
-│ │ │ │
-│ ├────────────┤ │
-│ │ Write │ │
-│ ├────────────┼────────┤
-│ │ Read │ FileB │
-├───────┼────────────┼────────┤
-│ Bob │ Read │ FileA │
-│ │ ├────────┤
-│ │ │ FileC │
-└───────┴────────────┴────────┘
-```
-
-**Notes**:
-- **Configuration**: `tw.MergeVertical` merges identical cells vertically (`tw/tw.go`).
-- **Migration from v0.0.5**: Extends `SetAutoMergeCells` with vertical merging (`tablewriter.go`).
-- **Key Features**: Enhances grouped data display; requires batch mode (`stream.go`).
-- **Best Practices**: Sort data by the columns you intend to merge for best results; test with `StyleLight` for clarity (`tw/symbols.go`).
-- **Potential Issues**: Streaming doesn’t support vertical merging; use batch mode (`stream.go`).
-
-### Example: Custom Renderer (CSV Output)
-Implements a custom renderer for CSV output.
-
-```go
-package main
-
-import (
- "fmt"
- "github.com/olekukonko/ll" // For logger type
- "github.com/olekukonko/tablewriter"
- "github.com/olekukonko/tablewriter/tw"
- "io"
- "os"
- "strings" // For CSV escaping
-)
-
-// CSVRenderer implements tw.Renderer
-type CSVRenderer struct {
- writer io.Writer
- config tw.Rendition // Store the rendition
- logger *ll.Logger
- err error
-}
-
-func (r *CSVRenderer) Start(w io.Writer) error {
- r.writer = w
- return nil // No initial output for CSV typically
-}
-
-func (r *CSVRenderer) escapeCSVCell(data string) string {
- // Basic CSV escaping: double quotes if it contains comma, newline, or quote
- if strings.ContainsAny(data, ",\"\n") {
- return `"` + strings.ReplaceAll(data, `"`, `""`) + `"`
- }
- return data
-}
-
-func (r *CSVRenderer) writeLine(cells map[int]tw.CellContext, numCols int) {
- if r.err != nil { return }
- var lineParts []string
- // Need to iterate in column order for CSV
- keys := make([]int, 0, len(cells))
- for k := range cells {
- keys = append(keys, k)
- }
- // This simple sort works for int keys 0,1,2...
- // For more complex scenarios, a proper sort might be needed if keys aren't sequential.
- for i := 0; i < numCols; i++ { // Assume numCols reflects the intended max columns
- if cellCtx, ok := cells[i]; ok {
- lineParts = append(lineParts, r.escapeCSVCell(cellCtx.Data))
- } else {
- lineParts = append(lineParts, "") // Empty cell if not present
- }
- }
- _, r.err = r.writer.Write([]byte(strings.Join(lineParts, ",") + "\n"))
-}
-
-
-func (r *CSVRenderer) Header(headers [][]string, ctx tw.Formatting) {
- // For CSV, usually only the first line of headers is relevant
- // The ctx.Row.Current will contain the cells for the first line of the header being processed
- r.writeLine(ctx.Row.Current, len(ctx.Row.Current))
-}
-
-func (r *CSVRenderer) Row(row []string, ctx tw.Formatting) {
- // ctx.Row.Current contains the cells for the current row line
- r.writeLine(ctx.Row.Current, len(ctx.Row.Current))
-}
-
-func (r *CSVRenderer) Footer(footers [][]string, ctx tw.Formatting) {
- // Similar to Header/Row, using ctx.Row.Current for the footer line data
- r.writeLine(ctx.Row.Current, len(ctx.Row.Current))
-}
-
-func (r *CSVRenderer) Line(ctx tw.Formatting) { /* No separator lines in CSV */ }
-
-func (r *CSVRenderer) Close() error { return r.err }
-
-func (r *CSVRenderer) Config() tw.Rendition { return r.config }
-func (r *CSVRenderer) Logger(logger *ll.Logger) { r.logger = logger }
-
-func main() {
- table := tablewriter.NewTable(os.Stdout, tablewriter.WithRenderer(&CSVRenderer{
- // config can be minimal for CSV as symbols/borders aren't used
- config: tw.Rendition{},
- }))
- table.Header("Name", "Status", "Notes, with comma")
- table.Append("Node1", "Ready", "All systems \"go\"!")
- table.Append("Node2", "Error", "Needs\nattention")
- table.Footer("Summary", "2 Nodes", "Check logs")
- table.Render()
-}
-```
-
-**Output:**
-```csv
-Name,Status,"Notes, with comma"
-Node1,Ready,"All systems ""go""!"
-Node2,Error,"Needs
-attention"
-Summary,2 Nodes,Check logs
-```
-
-**Notes**:
-- **Configuration**: Custom `CSVRenderer` implements `tw.Renderer` for CSV output (`tw/renderer.go`).
-- **Migration from v0.0.5**: Extends v0.0.5’s text-only output with custom formats (`tablewriter.go`).
-- **Key Features**: Handles basic CSV cell escaping; supports streaming if `Append` is called multiple times. `ctx.Row.Current` (map[int]tw.CellContext) is used to access cell data.
-- **Best Practices**: Test with complex data (e.g., commas, quotes, newlines); implement all renderer methods. A more robust CSV renderer would use the `encoding/csv` package.
-- **Potential Issues**: Custom renderers require careful error handling and correct interpretation of `tw.Formatting` and `tw.RowContext`. This example's `writeLine` assumes columns are 0-indexed and contiguous for simplicity.
-
-## Troubleshooting and Common Pitfalls
-
-This section addresses common migration issues with detailed solutions, covering 30+ scenarios to reduce support tickets.
-
-| Issue | Cause/Solution |
-|-------------------------------------------|-------------------------------------------------------------------------------|
-| No output from `Render()` | **Cause**: Missing `Start()`/`Close()` in streaming mode or invalid `io.Writer`. **Solution**: Ensure `Start()` and `Close()` are called in streaming; verify `io.Writer` (`stream.go`). |
-| Incorrect column widths | **Cause**: Missing `Config.Widths` in streaming or content-based sizing. **Solution**: Set `Config.Widths` before `Start()`; use `WithColumnMax` (`stream.go`). |
-| Merging not working | **Cause**: Streaming mode or mismatched data. **Solution**: Use batch mode for vertical/hierarchical merging; ensure identical content (`zoo.go`). |
-| Alignment ignored | **Cause**: `PerColumn` overrides `Global`. **Solution**: Check `Config.Section.Alignment.PerColumn` settings or `ConfigBuilder` calls (`tw/cell.go`). |
-| Padding affects widths | **Cause**: Padding included in `Config.Widths`. **Solution**: Adjust `Config.Widths` to account for `tw.CellPadding` (`zoo.go`). |
-| Colors not rendering | **Cause**: Non-ANSI terminal or incorrect codes. **Solution**: Test in ANSI-compatible terminal; use `twdw.Width` (`tw/fn.go`). |
-| Caption missing | **Cause**: `Close()` not called in streaming or incorrect `Spot`. **Solution**: Ensure `Close()`; verify `tw.Caption.Spot` (`tablewriter.go`). |
-| Filters not applied | **Cause**: Incorrect `PerColumn` indexing or nil filters. **Solution**: Set filters correctly; test with sample data (`tw/cell.go`). |
-| Stringer cache overhead | **Cause**: Large datasets with diverse types. **Solution**: Disable `WithStringerCache` for small tables if not using `WithStringer` or if types vary greatly (`tablewriter.go`). |
-| Deprecated methods used | **Cause**: Using `WithBorders`, old `tablewriter.Behavior` constants. **Solution**: Migrate to `WithRendition`, `tw.Behavior` struct (`tablewriter.go`, `deprecated.go`). |
-| Streaming footer missing | **Cause**: `Close()` not called. **Solution**: Always call `Close()` (`stream.go`). |
-| Hierarchical merging fails | **Cause**: Unsorted data or streaming mode. **Solution**: Sort data; use batch mode (`zoo.go`). |
-| Custom renderer errors | **Cause**: Incomplete method implementation or misinterpreting `tw.Formatting`. **Solution**: Implement all `tw.Renderer` methods; test thoroughly (`tw/renderer.go`). |
-| Width overflow | **Cause**: No `MaxWidth` or wide content. **Solution**: Set `Config.MaxWidth` (`config.go`). |
-| Truncated content | **Cause**: Narrow `Config.Widths` or `tw.WrapTruncate`. **Solution**: Widen columns or use `tw.WrapNormal` (`zoo.go`). |
-| Debug logs absent | **Cause**: `Debug = false`. **Solution**: Enable `WithDebug(true)` (`config.go`). |
-| Alignment mismatch across sections | **Cause**: Different defaults. **Solution**: Set uniform alignment options (e.g., via `ConfigBuilder..Alignment()`) (`config.go`). |
-| ANSI code artifacts | **Cause**: Non-ANSI terminal. **Solution**: Provide non-colored fallback (`tw/fn.go`). |
-| Slow rendering | **Cause**: Complex filters or merging. **Solution**: Optimize logic; limit merging (`zoo.go`). |
-| Uneven cell counts | **Cause**: Mismatched rows/headers. **Solution**: Pad with `""` (`zoo.go`). |
-| Border inconsistencies | **Cause**: Mismatched `Borders`/`Symbols`. **Solution**: Align settings (`tw/renderer.go`). |
-| Streaming width issues | **Cause**: No `Config.Widths`. **Solution**: Set before `Start()` (`stream.go`). |
-| Formatter ignored | **Cause**: `WithStringer` might take precedence if compatible. **Solution**: Review conversion priority; `tw.Formatter` is high-priority for single-item-to-single-cell conversion (`zoo.go`). |
-| Caption misalignment | **Cause**: Incorrect `Width` or `Align`. **Solution**: Set `tw.Caption.Width`/`Align` (`tablewriter.go`). |
-| Per-column padding errors | **Cause**: Incorrect indexing in `Padding.PerColumn`. **Solution**: Verify indices (`tw/cell.go`). |
-| Vertical merging in streaming | **Cause**: Unsupported. **Solution**: Use batch mode (`stream.go`). |
-| Filter performance | **Cause**: Complex logic. **Solution**: Simplify filters (`zoo.go`). |
-| Custom symbols incomplete | **Cause**: Missing characters. **Solution**: Define all symbols (`tw/symbols.go`). |
-| Table too wide | **Cause**: No `MaxWidth`. **Solution**: Set `Config.MaxWidth` (`config.go`). |
-| Streaming errors | **Cause**: Missing `Start()`. **Solution**: Call `Start()` before data input (`stream.go`). |
-
-## Additional Notes
-
-- **Performance Optimization**: Enable `WithStringerCache` for repetitive data types when using `WithStringer`; optimize filters and merging for large datasets (`tablewriter.go`, `zoo.go`).
-- **Debugging**: Use `WithDebug(true)` and `table.Debug()` to log configuration and rendering details; invaluable for troubleshooting (`config.go`).
-- **Testing Resources**: The `tests/` directory contains examples of various configurations.
-- **Community Support**: For advanced use cases or issues, consult the source code or open an issue on the `tablewriter` repository.
-- **Future Considerations**: Deprecated methods in `deprecated.go` (e.g., `WithBorders`) are slated for removal in future releases; migrate promptly to ensure compatibility.
-
-This guide aims to cover all migration scenarios comprehensively. For highly specific or advanced use cases, refer to the source files (`config.go`, `tablewriter.go`, `stream.go`, `tw/*`) or engage with the `tablewriter` community for support.
\ No newline at end of file
diff --git a/vendor/github.com/olekukonko/tablewriter/README.md b/vendor/github.com/olekukonko/tablewriter/README.md
deleted file mode 100644
index 16d288233d..0000000000
--- a/vendor/github.com/olekukonko/tablewriter/README.md
+++ /dev/null
@@ -1,1103 +0,0 @@
-# TableWriter for Go
-
-[](https://github.com/olekukonko/tablewriter/actions/workflows/go.yml)
-[](https://pkg.go.dev/github.com/olekukonko/tablewriter)
-[](https://goreportcard.com/report/github.com/olekukonko/tablewriter)
-[](LICENSE)
-[](README.md#benchmarks)
-
-`tablewriter` is a Go library for generating **rich text-based tables** with support for multiple output formats, including ASCII, Unicode, Markdown, HTML, and colorized terminals. Perfect for CLI tools, logs, and web applications.
-
-### Key Features
-- **Multi-format rendering**: ASCII, Unicode, Markdown, HTML, ANSI-colored
-- **Advanced styling**: Cell merging, alignment, padding, borders
-- **Flexible input**: CSV, structs, slices, or streaming data
-- **High performance**: Minimal allocations, buffer reuse
-- **Modern features**: Generics support, hierarchical merging, real-time streaming
-
----
-
-### Installation
-
-#### Legacy Version (v0.0.5)
-For use with legacy applications:
-```bash
-go get github.com/olekukonko/tablewriter@v0.0.5
-```
-
-#### Latest Version
-The latest stable version
-```bash
-go get github.com/olekukonko/tablewriter@v1.1.0
-```
-
-**Warning:** Version `v1.0.0` contains missing functionality and should not be used.
-
-
-> **Version Guidance**
-> - Legacy: Use `v0.0.5` (stable)
-> - New Features: Use `@latest` (includes generics, super fast streaming APIs)
-> - Legacy Docs: See [README_LEGACY.md](README_LEGACY.md)
-
----
-
-### Why TableWriter?
-- **CLI Ready**: Instant compatibility with terminal outputs
-- **Database Friendly**: Native support for `sql.Null*` types
-- **Secure**: Auto-escaping for HTML/Markdown
-- **Extensible**: Custom renderers and formatters
-
----
-
-### Quick Example
-```go
-package main
-
-import (
- "github.com/olekukonko/tablewriter"
- "os"
-)
-
-func main() {
- data := [][]string{
- {"Package", "Version", "Status"},
- {"tablewriter", "v0.0.5", "legacy"},
- {"tablewriter", "v1.1.0", "latest"},
- }
-
- table := tablewriter.NewWriter(os.Stdout)
- table.Header(data[0])
- table.Bulk(data[1:])
- table.Render()
-}
-```
-**Output**:
-```
-┌─────────────┬─────────┬────────┐
-│ PACKAGE │ VERSION │ STATUS │
-├─────────────┼─────────┼────────┤
-│ tablewriter │ v0.0.5 │ legacy │
-│ tablewriter │ v1.1.0 │ latest │
-└─────────────┴─────────┴────────┘
-```
-
-
-## Detailed Usage
-
-Create a table with `NewTable` or `NewWriter`, configure it using options or a `Config` struct, add data with `Append` or `Bulk`, and render to an `io.Writer`. Use renderers like `Blueprint` (ASCII), `HTML`, `Markdown`, `Colorized`, or `Ocean` (streaming).
-
-Here's how the API primitives map to the generated ASCII table:
-
-```
-API Call ASCII Table Component
--------- ---------------------
-
-table.Header([]string{"NAME", "AGE"}) ┌──────┬─────┐ ← Borders.Top
- │ NAME │ AGE │ ← Header row
- ├──────┼─────┤ ← Lines.ShowTop (header separator)
-
-table.Append([]string{"Alice", "25"}) │ Alice│ 25 │ ← Data row
- ├──────┼─────┤ ← Separators.BetweenRows
-
-table.Append([]string{"Bob", "30"}) │ Bob │ 30 │ ← Data row
- ├──────┼─────┤ ← Lines.ShowBottom (footer separator)
-
-table.Footer([]string{"Total", "2"}) │ Total│ 2 │ ← Footer row
- └──────┴─────┘ ← Borders.Bottom
-```
-
-The core components include:
-
-- **Renderer** - Implements the core interface for converting table data into output formats. Available renderers include Blueprint (ASCII), HTML, Markdown, Colorized (ASCII with color), Ocean (streaming ASCII), and SVG.
-
-- **Config** - The root configuration struct that controls all table behavior and appearance
- - **Behavior** - Controls high-level rendering behaviors including auto-hiding empty columns, trimming row whitespace, header/footer visibility, and compact mode for optimized merged cell calculations
- - **CellConfig** - The comprehensive configuration template used for table sections (header, row, footer). Combines formatting, padding, alignment, filtering, callbacks, and width constraints with global and per-column control
- - **StreamConfig** - Configuration for streaming mode including enable/disable state and strict column validation
-
-- **Rendition** - Defines how a renderer formats tables and contains the complete visual styling configuration
- - **Borders** - Control the outer frame visibility (top, bottom, left, right edges) of the table
- - **Lines** - Control horizontal boundary lines (above/below headers, above footers) that separate different table sections
- - **Separators** - Control the visibility of separators between rows and between columns within the table content
- - **Symbols** - Define the characters used for drawing table borders, corners, and junctions
-
-These components can be configured with various `tablewriter.With*()` functional options when creating a new table.
-
-## Examples
-
-### Basic Examples
-
-#### 1. Simple Tables
-
-Create a basic table with headers and rows.
-
-
-##### default
-
-```go
-package main
-
-import (
- "fmt"
- "github.com/olekukonko/tablewriter"
- "os"
-)
-
-type Age int
-
-func (a Age) String() string {
- return fmt.Sprintf("%d yrs", a)
-}
-
-func main() {
- data := [][]any{
- {"Alice", Age(25), "New York"},
- {"Bob", Age(30), "Boston"},
- }
-
- table := tablewriter.NewTable(os.Stdout)
- table.Header("Name", "Age", "City")
- table.Bulk(data)
- table.Render()
-}
-```
-
-**Output**:
-
-```
-┌───────┬────────┬──────────┐
-│ NAME │ AGE │ CITY │
-├───────┼────────┼──────────┤
-│ Alice │ 25 yrs │ New York │
-│ Bob │ 30 yrs │ Boston │
-└───────┴────────┴──────────┘
-
-```
-
-
-##### with customization
-
-
-```go
-package main
-
-import (
- "fmt"
- "github.com/olekukonko/tablewriter"
- "github.com/olekukonko/tablewriter/renderer"
- "github.com/olekukonko/tablewriter/tw"
- "os"
-)
-
-type Age int
-
-func (a Age) String() string {
- return fmt.Sprintf("%d yrs", a)
-}
-
-func main() {
- data := [][]any{
- {"Alice", Age(25), "New York"},
- {"Bob", Age(30), "Boston"},
- }
-
- symbols := tw.NewSymbolCustom("Nature").
- WithRow("~").
- WithColumn("|").
- WithTopLeft("🌱").
- WithTopMid("🌿").
- WithTopRight("🌱").
- WithMidLeft("🍃").
- WithCenter("❀").
- WithMidRight("🍃").
- WithBottomLeft("🌻").
- WithBottomMid("🌾").
- WithBottomRight("🌻")
-
- table := tablewriter.NewTable(os.Stdout, tablewriter.WithRenderer(renderer.NewBlueprint(tw.Rendition{Symbols: symbols})))
- table.Header("Name", "Age", "City")
- table.Bulk(data)
- table.Render()
-}
-```
-
-```
-🌱~~~~~~❀~~~~~~~~❀~~~~~~~~~🌱
-| NAME | AGE | CITY |
-🍃~~~~~~❀~~~~~~~~❀~~~~~~~~~🍃
-| Alice | 25 yrs | New York |
-| Bob | 30 yrs | Boston |
-🌻~~~~~~❀~~~~~~~~❀~~~~~~~~~🌻
-```
-
-See [symbols example](https://github.com/olekukonko/tablewriter/blob/master/_example/symbols/main.go) for more
-
-#### 2. Markdown Table
-
-Generate a Markdown table for documentation.
-
-```go
-package main
-
-import (
- "fmt"
- "github.com/olekukonko/tablewriter"
- "github.com/olekukonko/tablewriter/renderer"
- "os"
- "strings"
- "unicode"
-)
-
-type Name struct {
- First string
- Last string
-}
-
-// this will be ignored since Format() is present
-func (n Name) String() string {
- return fmt.Sprintf("%s %s", n.First, n.Last)
-}
-
-// Note: Format() overrides String() if both exist.
-func (n Name) Format() string {
- return fmt.Sprintf("%s %s", n.clean(n.First), n.clean(n.Last))
-}
-
-// clean ensures the first letter is capitalized and the rest are lowercase
-func (n Name) clean(s string) string {
- s = strings.TrimSpace(strings.ToLower(s))
- words := strings.Fields(s)
- s = strings.Join(words, "")
-
- if s == "" {
- return s
- }
- // Capitalize the first letter
- runes := []rune(s)
- runes[0] = unicode.ToUpper(runes[0])
- return string(runes)
-}
-
-type Age int
-
-// Age int will be ignore and string will be used
-func (a Age) String() string {
- return fmt.Sprintf("%d yrs", a)
-}
-
-func main() {
- data := [][]any{
- {Name{"Al i CE", " Ma SK"}, Age(25), "New York"},
- {Name{"bOb", "mar le y"}, Age(30), "Boston"},
- }
-
- table := tablewriter.NewTable(os.Stdout,
- tablewriter.WithRenderer(renderer.NewMarkdown()),
- )
-
- table.Header([]string{"Name", "Age", "City"})
- table.Bulk(data)
- table.Render()
-}
-```
-
-**Output**:
-
-```
-| NAME | AGE | CITY |
-|:----------:|:------:|:--------:|
-| Alice Mask | 25 yrs | New York |
-| Bob Marley | 30 yrs | Boston |
-
-
-```
-
-#### 3. CSV Input
-
-Create a table from a CSV file with custom row alignment.
-
-```go
-package main
-
-import (
- "github.com/olekukonko/tablewriter"
- "github.com/olekukonko/tablewriter/tw"
- "log"
- "os"
-)
-
-func main() {
- // Assuming "test.csv" contains: "First Name,Last Name,SSN\nJohn,Barry,123456\nKathy,Smith,687987"
- table, err := tablewriter.NewCSV(os.Stdout, "test.csv", true)
- if err != nil {
- log.Fatalf("Error: %v", err)
- }
-
- table.Configure(func(config *tablewriter.Config) {
- config.Row.Alignment.Global = tw.AlignLeft
- })
- table.Render()
-}
-```
-
-**Output**:
-
-```
-┌────────────┬───────────┬─────────┐
-│ FIRST NAME │ LAST NAME │ SSN │
-├────────────┼───────────┼─────────┤
-│ John │ Barry │ 123456 │
-│ Kathy │ Smith │ 687987 │
-└────────────┴───────────┴─────────┘
-```
-
-### Advanced Examples
-
-#### 4. Colorized Table with Long Values
-
-Create a colorized table with wrapped long values, per-column colors, and a styled footer (inspired by `TestColorizedLongValues` and `TestColorizedCustomColors`).
-
-```go
-package main
-
-import (
- "github.com/fatih/color"
- "github.com/olekukonko/tablewriter"
- "github.com/olekukonko/tablewriter/renderer"
- "github.com/olekukonko/tablewriter/tw"
- "os"
-)
-
-func main() {
- data := [][]string{
- {"1", "This is a very long description that needs wrapping for readability", "OK"},
- {"2", "Short description", "DONE"},
- {"3", "Another lengthy description requiring truncation or wrapping", "ERROR"},
- }
-
- // Configure colors: green headers, cyan/magenta rows, yellow footer
- colorCfg := renderer.ColorizedConfig{
- Header: renderer.Tint{
- FG: renderer.Colors{color.FgGreen, color.Bold}, // Green bold headers
- BG: renderer.Colors{color.BgHiWhite},
- },
- Column: renderer.Tint{
- FG: renderer.Colors{color.FgCyan}, // Default cyan for rows
- Columns: []renderer.Tint{
- {FG: renderer.Colors{color.FgMagenta}}, // Magenta for column 0
- {}, // Inherit default (cyan)
- {FG: renderer.Colors{color.FgHiRed}}, // High-intensity red for column 2
- },
- },
- Footer: renderer.Tint{
- FG: renderer.Colors{color.FgYellow, color.Bold}, // Yellow bold footer
- Columns: []renderer.Tint{
- {}, // Inherit default
- {FG: renderer.Colors{color.FgHiYellow}}, // High-intensity yellow for column 1
- {}, // Inherit default
- },
- },
- Border: renderer.Tint{FG: renderer.Colors{color.FgWhite}}, // White borders
- Separator: renderer.Tint{FG: renderer.Colors{color.FgWhite}}, // White separators
- }
-
- table := tablewriter.NewTable(os.Stdout,
- tablewriter.WithRenderer(renderer.NewColorized(colorCfg)),
- tablewriter.WithConfig(tablewriter.Config{
- Row: tw.CellConfig{
- Formatting: tw.CellFormatting{AutoWrap: tw.WrapNormal}, // Wrap long content
- Alignment: tw.CellAlignment{Global: tw.AlignLeft}, // Left-align rows
- ColMaxWidths: tw.CellWidth{Global: 25},
- },
- Footer: tw.CellConfig{
- Alignment: tw.CellAlignment{Global: tw.AlignRight},
- },
- }),
- )
-
- table.Header([]string{"ID", "Description", "Status"})
- table.Bulk(data)
- table.Footer([]string{"", "Total", "3"})
- table.Render()
-}
-```
-
-**Output** (colors visible in ANSI-compatible terminals):
-
-
-
-#### 5. Streaming Table with Truncation
-
-Stream a table incrementally with truncation and a footer, simulating a real-time data feed (inspired by `TestOceanStreamTruncation` and `TestOceanStreamSlowOutput`).
-
-```go
-package main
-
-import (
- "github.com/olekukonko/tablewriter"
- "github.com/olekukonko/tablewriter/tw"
- "log"
- "os"
- "time"
-)
-
-func main() {
- table := tablewriter.NewTable(os.Stdout, tablewriter.WithStreaming(tw.StreamConfig{Enable: true}))
-
- // Start streaming
- if err := table.Start(); err != nil {
- log.Fatalf("Start failed: %v", err)
- }
-
- defer table.Close()
-
- // Stream header
- table.Header([]string{"ID", "Description", "Status"})
-
- // Stream rows with simulated delay
- data := [][]string{
- {"1", "This description is too long", "OK"},
- {"2", "Short desc", "DONE"},
- {"3", "Another long description here", "ERROR"},
- }
- for _, row := range data {
- table.Append(row)
- time.Sleep(500 * time.Millisecond) // Simulate real-time data feed
- }
-
- // Stream footer
- table.Footer([]string{"", "Total", "3"})
-}
-```
-
-**Output** (appears incrementally):
-
-```
-┌────────┬───────────────┬──────────┐
-│ ID │ DESCRIPTION │ STATUS │
-├────────┼───────────────┼──────────┤
-│ 1 │ This │ OK │
-│ │ description │ │
-│ │ is too long │ │
-│ 2 │ Short desc │ DONE │
-│ 3 │ Another long │ ERROR │
-│ │ description │ │
-│ │ here │ │
-├────────┼───────────────┼──────────┤
-│ │ Total │ 3 │
-└────────┴───────────────┴──────────┘
-```
-
-**Note**: Long descriptions are truncated with `…` due to fixed column widths. The output appears row-by-row, simulating a real-time feed.
-
-#### 6. Hierarchical Merging for Organizational Data
-
-Show hierarchical merging for a tree-like structure, such as an organizational hierarchy (inspired by `TestMergeHierarchicalUnicode`).
-
-```go
-package main
-
-import (
- "github.com/olekukonko/tablewriter"
- "github.com/olekukonko/tablewriter/renderer"
- "github.com/olekukonko/tablewriter/tw"
- "os"
-)
-
-func main() {
- data := [][]string{
- {"Engineering", "Backend", "API Team", "Alice"},
- {"Engineering", "Backend", "Database Team", "Bob"},
- {"Engineering", "Frontend", "UI Team", "Charlie"},
- {"Marketing", "Digital", "SEO Team", "Dave"},
- {"Marketing", "Digital", "Content Team", "Eve"},
- }
-
- table := tablewriter.NewTable(os.Stdout,
- tablewriter.WithRenderer(renderer.NewBlueprint(tw.Rendition{
- Settings: tw.Settings{Separators: tw.Separators{BetweenRows: tw.On}},
- })),
- tablewriter.WithConfig(tablewriter.Config{
- Header: tw.CellConfig{Alignment: tw.CellAlignment{Global: tw.AlignCenter}},
- Row: tw.CellConfig{
- Formatting: tw.CellFormatting{MergeMode: tw.MergeHierarchical},
- Alignment: tw.CellAlignment{Global: tw.AlignLeft},
- },
- }),
- )
- table.Header([]string{"Department", "Division", "Team", "Lead"})
- table.Bulk(data)
- table.Render()
-}
-```
-
-**Output**:
-
-```
-┌────────────┬──────────┬──────────────┬────────┐
-│ DEPARTMENT │ DIVISION │ TEAM │ LEAD │
-├────────────┼──────────┼──────────────┼────────┤
-│ Engineering│ Backend │ API Team │ Alice │
-│ │ ├──────────────┼────────┤
-│ │ │ Database Team│ Bob │
-│ │ Frontend ├──────────────┼────────┤
-│ │ │ UI Team │ Charlie│
-├────────────┼──────────┼──────────────┼────────┤
-│ Marketing │ Digital │ SEO Team │ Dave │
-│ │ ├──────────────┼────────┤
-│ │ │ Content Team │ Eve │
-└────────────┴──────────┴──────────────┴────────┘
-```
-
-**Note**: Hierarchical merging groups repeated values (e.g., "Engineering" spans multiple rows, "Backend" spans two teams), creating a tree-like structure.
-
-#### 7. Custom Padding with Merging
-
-Showcase custom padding and combined horizontal/vertical merging (inspired by `TestMergeWithPadding` in `merge_test.go`).
-
-```go
-package main
-
-import (
- "github.com/olekukonko/tablewriter"
- "github.com/olekukonko/tablewriter/renderer"
- "github.com/olekukonko/tablewriter/tw"
- "os"
-)
-
-func main() {
- data := [][]string{
- {"1/1/2014", "Domain name", "Successful", "Successful"},
- {"1/1/2014", "Domain name", "Pending", "Waiting"},
- {"1/1/2014", "Domain name", "Successful", "Rejected"},
- {"", "", "TOTAL", "$145.93"},
- }
-
- table := tablewriter.NewTable(os.Stdout,
- tablewriter.WithRenderer(renderer.NewBlueprint(tw.Rendition{
- Settings: tw.Settings{Separators: tw.Separators{BetweenRows: tw.On}},
- })),
- tablewriter.WithConfig(tablewriter.Config{
- Row: tw.CellConfig{
- Formatting: tw.CellFormatting{MergeMode: tw.MergeBoth},
- Alignment: tw.CellAlignment{PerColumn: []tw.Align{tw.Skip, tw.Skip, tw.AlignRight, tw.AlignLeft}},
- },
-
- Footer: tw.CellConfig{
- Padding: tw.CellPadding{
- Global: tw.Padding{Left: "*", Right: "*"},
- PerColumn: []tw.Padding{{}, {}, {Bottom: "^"}, {Bottom: "^"}},
- },
- Alignment: tw.CellAlignment{PerColumn: []tw.Align{tw.Skip, tw.Skip, tw.AlignRight, tw.AlignLeft}},
- },
- }),
- )
- table.Header([]string{"Date", "Description", "Status", "Conclusion"})
- table.Bulk(data)
- table.Render()
-}
-```
-
-**Output**:
-
-```
-┌──────────┬─────────────┬────────────┬────────────┐
-│ DATE │ DESCRIPTION │ STATUS │ CONCLUSION │
-├──────────┼─────────────┼────────────┴────────────┤
-│ 1/1/2014 │ Domain name │ Successful │
-│ │ ├────────────┬────────────┤
-│ │ │ Pending │ Waiting │
-│ │ ├────────────┼────────────┤
-│ │ │ Successful │ Rejected │
-├──────────┼─────────────┼────────────┼────────────┤
-│ │ │ TOTAL │ $145.93 │
-│ │ │^^^^^^^^^^^^│^^^^^^^^^^^^│
-└──────────┴─────────────┴────────────┴────────────┘
-```
-
-#### 8. Nested Tables
-
-Create a table with nested sub-tables for complex layouts (inspired by `TestMasterClass` in `extra_test.go`).
-
-```go
-package main
-
-import (
- "bytes"
- "github.com/olekukonko/tablewriter"
- "github.com/olekukonko/tablewriter/renderer"
- "github.com/olekukonko/tablewriter/tw"
- "os"
-)
-
-func main() {
- // Helper to create a sub-table
- createSubTable := func(s string) string {
- var buf bytes.Buffer
- table := tablewriter.NewTable(&buf,
- tablewriter.WithRenderer(renderer.NewBlueprint(tw.Rendition{
- Borders: tw.BorderNone,
- Symbols: tw.NewSymbols(tw.StyleASCII),
- Settings: tw.Settings{
- Separators: tw.Separators{BetweenRows: tw.On},
- Lines: tw.Lines{ShowFooterLine: tw.On},
- },
- })),
- tablewriter.WithConfig(tablewriter.Config{
- MaxWidth: 10,
- Row: tw.CellConfig{Alignment: tw.CellAlignment{Global: tw.AlignCenter}},
- }),
- )
- table.Append([]string{s, s})
- table.Append([]string{s, s})
- table.Render()
- return buf.String()
- }
-
- // Main table
- table := tablewriter.NewTable(os.Stdout,
- tablewriter.WithRenderer(renderer.NewBlueprint(tw.Rendition{
- Borders: tw.BorderNone,
- Settings: tw.Settings{Separators: tw.Separators{BetweenColumns: tw.On}},
- })),
- tablewriter.WithConfig(tablewriter.Config{
- MaxWidth: 30,
- Row: tw.CellConfig{Alignment: tw.CellAlignment{Global: tw.AlignCenter}},
- }),
- )
- table.Append([]string{createSubTable("A"), createSubTable("B")})
- table.Append([]string{createSubTable("C"), createSubTable("D")})
- table.Render()
-}
-```
-
-**Output**:
-
-```
- A | A │ B | B
- ---+--- │ ---+---
- A | A │ B | B
- C | C │ D | D
- ---+--- │ ---+---
- C | C │ D | D
-```
-
-#### 9. Structs with Database
-
-Render a table from a slice of structs, simulating a database query (inspired by `TestStructTableWithDB` in `struct_test.go`).
-
-```go
-package main
-
-import (
- "fmt"
- "github.com/olekukonko/tablewriter"
- "github.com/olekukonko/tablewriter/renderer"
- "github.com/olekukonko/tablewriter/tw"
- "os"
-)
-
-type Employee struct {
- ID int
- Name string
- Age int
- Department string
- Salary float64
-}
-
-func employeeStringer(e interface{}) []string {
- emp, ok := e.(Employee)
- if !ok {
- return []string{"Error: Invalid type"}
- }
- return []string{
- fmt.Sprintf("%d", emp.ID),
- emp.Name,
- fmt.Sprintf("%d", emp.Age),
- emp.Department,
- fmt.Sprintf("%.2f", emp.Salary),
- }
-}
-
-func main() {
- employees := []Employee{
- {ID: 1, Name: "Alice Smith", Age: 28, Department: "Engineering", Salary: 75000.50},
- {ID: 2, Name: "Bob Johnson", Age: 34, Department: "Marketing", Salary: 62000.00},
- {ID: 3, Name: "Charlie Brown", Age: 45, Department: "HR", Salary: 80000.75},
- }
-
- table := tablewriter.NewTable(os.Stdout,
- tablewriter.WithRenderer(renderer.NewBlueprint(tw.Rendition{
- Symbols: tw.NewSymbols(tw.StyleRounded),
- })),
- tablewriter.WithStringer(employeeStringer),
- tablewriter.WithConfig(tablewriter.Config{
- Header: tw.CellConfig{
- Formatting: tw.CellFormatting{AutoFormat: tw.On},
- Alignment: tw.CellAlignment{Global: tw.AlignCenter},
- },
- Row: tw.CellConfig{Alignment: tw.CellAlignment{Global: tw.AlignLeft}},
- Footer: tw.CellConfig{Alignment: tw.CellAlignment{Global: tw.AlignRight}},
- }),
- )
- table.Header([]string{"ID", "Name", "Age", "Department", "Salary"})
-
- for _, emp := range employees {
- table.Append(emp)
- }
-
- totalSalary := 0.0
- for _, emp := range employees {
- totalSalary += emp.Salary
- }
- table.Footer([]string{"", "", "", "Total", fmt.Sprintf("%.2f", totalSalary)})
- table.Render()
-}
-```
-
-**Output**:
-
-```
-╭────┬───────────────┬─────┬─────────────┬───────────╮
-│ ID │ NAME │ AGE │ DEPARTMENT │ SALARY │
-├────┼───────────────┼─────┼─────────────┼───────────┤
-│ 1 │ Alice Smith │ 28 │ Engineering │ 75000.50 │
-│ 2 │ Bob Johnson │ 34 │ Marketing │ 62000.00 │
-│ 3 │ Charlie Brown │ 45 │ HR │ 80000.75 │
-├────┼───────────────┼─────┼─────────────┼───────────┤
-│ │ │ │ Total │ 217001.25 │
-╰────┴───────────────┴─────┴─────────────┴───────────╯
-```
-
-
-#### 10. Simple Html Table
-
-
-```go
-package main
-
-import (
- "github.com/olekukonko/tablewriter"
- "github.com/olekukonko/tablewriter/renderer"
- "github.com/olekukonko/tablewriter/tw"
- "os"
-)
-
-func main() {
- data := [][]string{
- {"North", "Q1 & Q2", "Q1 & Q2", "$2200.00"},
- {"South", "Q1", "Q1", "$1000.00"},
- {"South", "Q2", "Q2", "$1200.00"},
- }
-
- // Configure HTML with custom CSS classes and content escaping
- htmlCfg := renderer.HTMLConfig{
- TableClass: "sales-table",
- HeaderClass: "table-header",
- BodyClass: "table-body",
- FooterClass: "table-footer",
- RowClass: "table-row",
- HeaderRowClass: "header-row",
- FooterRowClass: "footer-row",
- EscapeContent: true, // Escape HTML characters (e.g., "&" to "&")
- }
-
- table := tablewriter.NewTable(os.Stdout,
- tablewriter.WithRenderer(renderer.NewHTML(htmlCfg)),
- tablewriter.WithConfig(tablewriter.Config{
- Header: tw.CellConfig{
- Formatting: tw.CellFormatting{MergeMode: tw.MergeHorizontal}, // Merge identical header cells
- Alignment: tw.CellAlignment{Global: tw.AlignCenter},
- },
- Row: tw.CellConfig{
- Formatting: tw.CellFormatting{MergeMode: tw.MergeHorizontal}, // Merge identical row cells
- Alignment: tw.CellAlignment{Global: tw.AlignLeft},
- },
- Footer: tw.CellConfig{Alignment: tw.CellAlignment{Global: tw.AlignRight}},
- }),
- )
-
- table.Header([]string{"Region", "Quarter", "Quarter", "Sales"})
- table.Bulk(data)
- table.Footer([]string{"", "", "Total", "$4400.00"})
- table.Render()
-}
-```
-
-**Output**:
-
-```
-
-
-
-
REGION
-
QUARTER
-
SALES
-
-
-
-
-
North
-
Q1 & Q2
-
$2200.00
-
-
-
South
-
Q1
-
$1000.00
-
-
-
South
-
Q2
-
$1200.00
-
-
-
-
-
-
-
Total
-
$4400.00
-
-
-
-
-```
-
-#### 11. SVG Support
-```go
-package main
-
-import (
- "fmt"
- "github.com/olekukonko/ll"
- "github.com/olekukonko/tablewriter"
- "github.com/olekukonko/tablewriter/renderer"
- "os"
-)
-
-type Age int
-
-func (a Age) String() string {
- return fmt.Sprintf("%d yrs", a)
-}
-
-func main() {
- data := [][]any{
- {"Alice", Age(25), "New York"},
- {"Bob", Age(30), "Boston"},
- }
-
- file, err := os.OpenFile("out.svg", os.O_CREATE|os.O_WRONLY, 0644)
- if err != nil {
- ll.Fatal(err)
- }
- defer file.Close()
-
- table := tablewriter.NewTable(file, tablewriter.WithRenderer(renderer.NewSVG()))
- table.Header("Name", "Age", "City")
- table.Bulk(data)
- table.Render()
-}
-```
-
-```go
-
-
-```
-
-#### 12 Simple Application
-
-
-```go
-package main
-
-import (
- "fmt"
- "github.com/olekukonko/tablewriter"
- "github.com/olekukonko/tablewriter/tw"
- "io/fs"
- "os"
- "path/filepath"
- "strings"
- "time"
-)
-
-const (
- folder = "📁"
- file = "📄"
- baseDir = "../"
- indentStr = " "
-)
-
-func main() {
- table := tablewriter.NewTable(os.Stdout, tablewriter.WithTrimSpace(tw.Off))
- table.Header([]string{"Tree", "Size", "Permissions", "Modified"})
- err := filepath.WalkDir(baseDir, func(path string, d fs.DirEntry, err error) error {
- if err != nil {
- return err
- }
-
- if d.Name() == "." || d.Name() == ".." {
- return nil
- }
-
- // Calculate relative path depth
- relPath, err := filepath.Rel(baseDir, path)
- if err != nil {
- return err
- }
-
- depth := 0
- if relPath != "." {
- depth = len(strings.Split(relPath, string(filepath.Separator))) - 1
- }
-
- indent := strings.Repeat(indentStr, depth)
-
- var name string
- if d.IsDir() {
- name = fmt.Sprintf("%s%s %s", indent, folder, d.Name())
- } else {
- name = fmt.Sprintf("%s%s %s", indent, file, d.Name())
- }
-
- info, err := d.Info()
- if err != nil {
- return err
- }
-
- table.Append([]string{
- name,
- Size(info.Size()).String(),
- info.Mode().String(),
- Time(info.ModTime()).Format(),
- })
-
- return nil
- })
-
- if err != nil {
- fmt.Fprintf(os.Stdout, "Error: %v\n", err)
- return
- }
-
- table.Render()
-}
-
-const (
- KB = 1024
- MB = KB * 1024
- GB = MB * 1024
- TB = GB * 1024
-)
-
-type Size int64
-
-func (s Size) String() string {
- switch {
- case s < KB:
- return fmt.Sprintf("%d B", s)
- case s < MB:
- return fmt.Sprintf("%.2f KB", float64(s)/KB)
- case s < GB:
- return fmt.Sprintf("%.2f MB", float64(s)/MB)
- case s < TB:
- return fmt.Sprintf("%.2f GB", float64(s)/GB)
- default:
- return fmt.Sprintf("%.2f TB", float64(s)/TB)
- }
-}
-
-type Time time.Time
-
-func (t Time) Format() string {
- now := time.Now()
- diff := now.Sub(time.Time(t))
-
- if diff.Seconds() < 60 {
- return "just now"
- } else if diff.Minutes() < 60 {
- return fmt.Sprintf("%d minutes ago", int(diff.Minutes()))
- } else if diff.Hours() < 24 {
- return fmt.Sprintf("%d hours ago", int(diff.Hours()))
- } else if diff.Hours() < 24*7 {
- return fmt.Sprintf("%d days ago", int(diff.Hours()/24))
- } else {
- return time.Time(t).Format("Jan 2, 2006")
- }
-}
-
-```
-
-```
-┌──────────────────┬─────────┬─────────────┬──────────────┐
-│ TREE │ SIZE │ PERMISSIONS │ MODIFIED │
-├──────────────────┼─────────┼─────────────┼──────────────┤
-│ 📁 filetable │ 160 B │ drwxr-xr-x │ just now │
-│ 📄 main.go │ 2.19 KB │ -rw-r--r-- │ 22 hours ago │
-│ 📄 out.txt │ 0 B │ -rw-r--r-- │ just now │
-│ 📁 testdata │ 128 B │ drwxr-xr-x │ 1 days ago │
-│ 📄 a.txt │ 11 B │ -rw-r--r-- │ 1 days ago │
-│ 📄 b.txt │ 17 B │ -rw-r--r-- │ 1 days ago │
-│ 📁 symbols │ 128 B │ drwxr-xr-x │ just now │
-│ 📄 main.go │ 4.58 KB │ -rw-r--r-- │ 1 hours ago │
-│ 📄 out.txt │ 8.72 KB │ -rw-r--r-- │ just now │
-└──────────────────┴─────────┴─────────────┴──────────────┘
-```
-
-
-## Changes
-
-- `AutoFormat` changes See [#261](https://github.com/olekukonko/tablewriter/issues/261)
-
-## What is new
-- `Counting` changes See [#294](https://github.com/olekukonko/tablewriter/issues/294)
-
-## Command-Line Tool
-
-The `csv2table` tool converts CSV files to ASCII tables. See `cmd/csv2table/csv2table.go` for details.
-
-Example usage:
-
-```bash
-csv2table -f test.csv -h true -a left
-```
-
-## Contributing
-
-Contributions are welcome! Submit issues or pull requests to the [GitHub repository](https://github.com/olekukonko/tablewriter).
-
-## License
-
-MIT License. See the [LICENSE](LICENSE) file for details.
\ No newline at end of file
diff --git a/vendor/github.com/olekukonko/tablewriter/README_LEGACY.md b/vendor/github.com/olekukonko/tablewriter/README_LEGACY.md
deleted file mode 100644
index ed9b9c0a38..0000000000
--- a/vendor/github.com/olekukonko/tablewriter/README_LEGACY.md
+++ /dev/null
@@ -1,466 +0,0 @@
-ASCII Table Writer
-=========
-
-[](https://github.com/olekukonko/tablewriter/actions?query=workflow%3Aci)
-[](https://sourcegraph.com/github.com/olekukonko/tablewriter)
-[](https://godoc.org/github.com/olekukonko/tablewriter)
-
-
-## Important Notice: Modernization in Progress
-
-The `tablewriter` package is being modernized on the `prototype` branch with generics, streaming support, and a modular design, targeting `v0.2.0`. Until this is released:
-
-**For Production Use**: Use the stable version `v0.0.5`:
-```bash
- go get github.com/olekukonko/tablewriter@v0.0.5
-```
-
-####
-
-For Development Preview: Try the in-progress version (unstable)
-
-```bash
-go get github.com/olekukonko/tablewriter@master
-```
-
-#### Features
-
-- Automatic Padding
-- Support Multiple Lines
-- Supports Alignment
-- Support Custom Separators
-- Automatic Alignment of numbers & percentage
-- Write directly to http , file etc via `io.Writer`
-- Read directly from CSV file
-- Optional row line via `SetRowLine`
-- Normalise table header
-- Make CSV Headers optional
-- Enable or disable table border
-- Set custom footer support
-- Optional identical cells merging
-- Set custom caption
-- Optional reflowing of paragraphs in multi-line cells.
-
-#### Example 1 - Basic
-
-```go
-data := [][]string{
-[]string{"A", "The Good", "500"},
-[]string{"B", "The Very very Bad Man", "288"},
-[]string{"C", "The Ugly", "120"},
-[]string{"D", "The Gopher", "800"},
-}
-
-table := tablewriter.NewWriter(os.Stdout)
-table.SetHeader([]string{"Name", "Sign", "Rating"})
-
-for _, v := range data {
-table.Append(v)
-}
-table.Render() // Send output
-```
-
-##### Output 1
-
-```
-+------+-----------------------+--------+
-| NAME | SIGN | RATING |
-+------+-----------------------+--------+
-| A | The Good | 500 |
-| B | The Very very Bad Man | 288 |
-| C | The Ugly | 120 |
-| D | The Gopher | 800 |
-+------+-----------------------+--------+
-```
-
-#### Example 2 - Without Border / Footer / Bulk Append
-
-```go
-data := [][]string{
-[]string{"1/1/2014", "Domain name", "2233", "$10.98"},
-[]string{"1/1/2014", "January Hosting", "2233", "$54.95"},
-[]string{"1/4/2014", "February Hosting", "2233", "$51.00"},
-[]string{"1/4/2014", "February Extra Bandwidth", "2233", "$30.00"},
-}
-
-table := tablewriter.NewWriter(os.Stdout)
-table.SetHeader([]string{"Date", "Description", "CV2", "Amount"})
-table.SetFooter([]string{"", "", "Total", "$146.93"}) // Add Footer
-table.EnableBorder(false) // Set Border to false
-table.AppendBulk(data) // Add Bulk Data
-table.Render()
-```
-
-##### Output 2
-
-```
-
- DATE | DESCRIPTION | CV2 | AMOUNT
------------+--------------------------+-------+----------
- 1/1/2014 | Domain name | 2233 | $10.98
- 1/1/2014 | January Hosting | 2233 | $54.95
- 1/4/2014 | February Hosting | 2233 | $51.00
- 1/4/2014 | February Extra Bandwidth | 2233 | $30.00
------------+--------------------------+-------+----------
- TOTAL | $146 93
- --------+----------
-
-```
-
-#### Example 3 - CSV
-
-```go
-table, _ := tablewriter.NewCSV(os.Stdout, "testdata/test_info.csv", true)
-table.SetAlignment(tablewriter.ALIGN_LEFT) // Set Alignment
-table.Render()
-```
-
-##### Output 3
-
-```
-+----------+--------------+------+-----+---------+----------------+
-| FIELD | TYPE | NULL | KEY | DEFAULT | EXTRA |
-+----------+--------------+------+-----+---------+----------------+
-| user_id | smallint(5) | NO | PRI | NULL | auto_increment |
-| username | varchar(10) | NO | | NULL | |
-| password | varchar(100) | NO | | NULL | |
-+----------+--------------+------+-----+---------+----------------+
-```
-
-#### Example 4 - Custom Separator
-
-```go
-table, _ := tablewriter.NewCSV(os.Stdout, "testdata/test.csv", true)
-table.SetRowLine(true) // Enable row line
-
-// Change table lines
-table.SetCenterSeparator("*")
-table.SetColumnSeparator("╪")
-table.SetRowSeparator("-")
-
-table.SetAlignment(tablewriter.ALIGN_LEFT)
-table.Render()
-```
-
-##### Output 4
-
-```
-*------------*-----------*---------*
-╪ FIRST NAME ╪ LAST NAME ╪ SSN ╪
-*------------*-----------*---------*
-╪ John ╪ Barry ╪ 123456 ╪
-*------------*-----------*---------*
-╪ Kathy ╪ Smith ╪ 687987 ╪
-*------------*-----------*---------*
-╪ Bob ╪ McCornick ╪ 3979870 ╪
-*------------*-----------*---------*
-```
-
-#### Example 5 - Markdown Format
-
-```go
-data := [][]string{
-[]string{"1/1/2014", "Domain name", "2233", "$10.98"},
-[]string{"1/1/2014", "January Hosting", "2233", "$54.95"},
-[]string{"1/4/2014", "February Hosting", "2233", "$51.00"},
-[]string{"1/4/2014", "February Extra Bandwidth", "2233", "$30.00"},
-}
-
-table := tablewriter.NewWriter(os.Stdout)
-table.SetHeader([]string{"Date", "Description", "CV2", "Amount"})
-table.SetBorders(tablewriter.Border{Left: true, Top: false, Right: true, Bottom: false})
-table.SetCenterSeparator("|")
-table.AppendBulk(data) // Add Bulk Data
-table.Render()
-```
-
-##### Output 5
-
-```
-| DATE | DESCRIPTION | CV2 | AMOUNT |
-|----------|--------------------------|------|--------|
-| 1/1/2014 | Domain name | 2233 | $10.98 |
-| 1/1/2014 | January Hosting | 2233 | $54.95 |
-| 1/4/2014 | February Hosting | 2233 | $51.00 |
-| 1/4/2014 | February Extra Bandwidth | 2233 | $30.00 |
-```
-
-#### Example 6 - Identical cells merging
-
-```go
-data := [][]string{
-[]string{"1/1/2014", "Domain name", "1234", "$10.98"},
-[]string{"1/1/2014", "January Hosting", "2345", "$54.95"},
-[]string{"1/4/2014", "February Hosting", "3456", "$51.00"},
-[]string{"1/4/2014", "February Extra Bandwidth", "4567", "$30.00"},
-}
-
-table := tablewriter.NewWriter(os.Stdout)
-table.SetHeader([]string{"Date", "Description", "CV2", "Amount"})
-table.SetFooter([]string{"", "", "Total", "$146.93"})
-table.SetAutoMergeCells(true)
-table.SetRowLine(true)
-table.AppendBulk(data)
-table.Render()
-```
-
-##### Output 6
-
-```
-+----------+--------------------------+-------+---------+
-| DATE | DESCRIPTION | CV2 | AMOUNT |
-+----------+--------------------------+-------+---------+
-| 1/1/2014 | Domain name | 1234 | $10.98 |
-+ +--------------------------+-------+---------+
-| | January Hosting | 2345 | $54.95 |
-+----------+--------------------------+-------+---------+
-| 1/4/2014 | February Hosting | 3456 | $51.00 |
-+ +--------------------------+-------+---------+
-| | February Extra Bandwidth | 4567 | $30.00 |
-+----------+--------------------------+-------+---------+
-| TOTAL | $146 93 |
-+----------+--------------------------+-------+---------+
-```
-
-#### Example 7 - Identical cells merging (specify the column index to merge)
-
-```go
-data := [][]string{
-[]string{"1/1/2014", "Domain name", "1234", "$10.98"},
-[]string{"1/1/2014", "January Hosting", "1234", "$10.98"},
-[]string{"1/4/2014", "February Hosting", "3456", "$51.00"},
-[]string{"1/4/2014", "February Extra Bandwidth", "4567", "$30.00"},
-}
-
-table := tablewriter.NewWriter(os.Stdout)
-table.SetHeader([]string{"Date", "Description", "CV2", "Amount"})
-table.SetFooter([]string{"", "", "Total", "$146.93"})
-table.SetAutoMergeCellsByColumnIndex([]int{2, 3})
-table.SetRowLine(true)
-table.AppendBulk(data)
-table.Render()
-```
-
-##### Output 7
-
-```
-+----------+--------------------------+-------+---------+
-| DATE | DESCRIPTION | CV2 | AMOUNT |
-+----------+--------------------------+-------+---------+
-| 1/1/2014 | Domain name | 1234 | $10.98 |
-+----------+--------------------------+ + +
-| 1/1/2014 | January Hosting | | |
-+----------+--------------------------+-------+---------+
-| 1/4/2014 | February Hosting | 3456 | $51.00 |
-+----------+--------------------------+-------+---------+
-| 1/4/2014 | February Extra Bandwidth | 4567 | $30.00 |
-+----------+--------------------------+-------+---------+
-| TOTAL | $146.93 |
-+----------+--------------------------+-------+---------+
-```
-
-#### Table with color
-
-```go
-data := [][]string{
-[]string{"1/1/2014", "Domain name", "2233", "$10.98"},
-[]string{"1/1/2014", "January Hosting", "2233", "$54.95"},
-[]string{"1/4/2014", "February Hosting", "2233", "$51.00"},
-[]string{"1/4/2014", "February Extra Bandwidth", "2233", "$30.00"},
-}
-
-table := tablewriter.NewWriter(os.Stdout)
-table.SetHeader([]string{"Date", "Description", "CV2", "Amount"})
-table.SetFooter([]string{"", "", "Total", "$146.93"}) // Add Footer
-table.EnableBorder(false) // Set Border to false
-
-table.SetHeaderColor(tablewriter.Colors{tablewriter.Bold, tablewriter.BgGreenColor},
-tablewriter.Colors{tablewriter.FgHiRedColor, tablewriter.Bold, tablewriter.BgBlackColor},
-tablewriter.Colors{tablewriter.BgRedColor, tablewriter.FgWhiteColor},
-tablewriter.Colors{tablewriter.BgCyanColor, tablewriter.FgWhiteColor})
-
-table.SetColumnColor(tablewriter.Colors{tablewriter.Bold, tablewriter.FgHiBlackColor},
-tablewriter.Colors{tablewriter.Bold, tablewriter.FgHiRedColor},
-tablewriter.Colors{tablewriter.Bold, tablewriter.FgHiBlackColor},
-tablewriter.Colors{tablewriter.Bold, tablewriter.FgBlackColor})
-
-table.SetFooterColor(tablewriter.Colors{}, tablewriter.Colors{},
-tablewriter.Colors{tablewriter.Bold},
-tablewriter.Colors{tablewriter.FgHiRedColor})
-
-table.AppendBulk(data)
-table.Render()
-```
-
-#### Table with color Output
-
-
-
-#### Example - 8 Table Cells with Color
-
-Individual Cell Colors from `func Rich` take precedence over Column Colors
-
-```go
-data := [][]string{
-[]string{"Test1Merge", "HelloCol2 - 1", "HelloCol3 - 1", "HelloCol4 - 1"},
-[]string{"Test1Merge", "HelloCol2 - 2", "HelloCol3 - 2", "HelloCol4 - 2"},
-[]string{"Test1Merge", "HelloCol2 - 3", "HelloCol3 - 3", "HelloCol4 - 3"},
-[]string{"Test2Merge", "HelloCol2 - 4", "HelloCol3 - 4", "HelloCol4 - 4"},
-[]string{"Test2Merge", "HelloCol2 - 5", "HelloCol3 - 5", "HelloCol4 - 5"},
-[]string{"Test2Merge", "HelloCol2 - 6", "HelloCol3 - 6", "HelloCol4 - 6"},
-[]string{"Test2Merge", "HelloCol2 - 7", "HelloCol3 - 7", "HelloCol4 - 7"},
-[]string{"Test3Merge", "HelloCol2 - 8", "HelloCol3 - 8", "HelloCol4 - 8"},
-[]string{"Test3Merge", "HelloCol2 - 9", "HelloCol3 - 9", "HelloCol4 - 9"},
-[]string{"Test3Merge", "HelloCol2 - 10", "HelloCol3 -10", "HelloCol4 - 10"},
-}
-
-table := tablewriter.NewWriter(os.Stdout)
-table.SetHeader([]string{"Col1", "Col2", "Col3", "Col4"})
-table.SetFooter([]string{"", "", "Footer3", "Footer4"})
-table.EnableBorder(false)
-
-table.SetHeaderColor(tablewriter.Colors{tablewriter.Bold, tablewriter.BgGreenColor},
-tablewriter.Colors{tablewriter.FgHiRedColor, tablewriter.Bold, tablewriter.BgBlackColor},
-tablewriter.Colors{tablewriter.BgRedColor, tablewriter.FgWhiteColor},
-tablewriter.Colors{tablewriter.BgCyanColor, tablewriter.FgWhiteColor})
-
-table.SetColumnColor(tablewriter.Colors{tablewriter.Bold, tablewriter.FgHiBlackColor},
-tablewriter.Colors{tablewriter.Bold, tablewriter.FgHiRedColor},
-tablewriter.Colors{tablewriter.Bold, tablewriter.FgHiBlackColor},
-tablewriter.Colors{tablewriter.Bold, tablewriter.FgBlackColor})
-
-table.SetFooterColor(tablewriter.Colors{}, tablewriter.Colors{},
-tablewriter.Colors{tablewriter.Bold},
-tablewriter.Colors{tablewriter.FgHiRedColor})
-
-colorData1 := []string{"TestCOLOR1Merge", "HelloCol2 - COLOR1", "HelloCol3 - COLOR1", "HelloCol4 - COLOR1"}
-colorData2 := []string{"TestCOLOR2Merge", "HelloCol2 - COLOR2", "HelloCol3 - COLOR2", "HelloCol4 - COLOR2"}
-
-for i, row := range data {
-if i == 4 {
-table.Rich(colorData1, []tablewriter.Colors{tablewriter.Colors{}, tablewriter.Colors{tablewriter.Normal, tablewriter.FgCyanColor}, tablewriter.Colors{tablewriter.Bold, tablewriter.FgWhiteColor}, tablewriter.Colors{}})
-table.Rich(colorData2, []tablewriter.Colors{tablewriter.Colors{tablewriter.Normal, tablewriter.FgMagentaColor}, tablewriter.Colors{}, tablewriter.Colors{tablewriter.Bold, tablewriter.BgRedColor}, tablewriter.Colors{tablewriter.FgHiGreenColor, tablewriter.Italic, tablewriter.BgHiCyanColor}})
-}
-table.Append(row)
-}
-
-table.SetAutoMergeCells(true)
-table.Render()
-
-```
-
-##### Table cells with color Output
-
-
-
-#### Example 9 - Set table caption
-
-```go
-data := [][]string{
-[]string{"A", "The Good", "500"},
-[]string{"B", "The Very very Bad Man", "288"},
-[]string{"C", "The Ugly", "120"},
-[]string{"D", "The Gopher", "800"},
-}
-
-table := tablewriter.NewWriter(os.Stdout)
-table.SetHeader([]string{"Name", "Sign", "Rating"})
-table.SetCaption(true, "Movie ratings.")
-
-for _, v := range data {
-table.Append(v)
-}
-table.Render() // Send output
-```
-
-Note: Caption text will wrap with total width of rendered table.
-
-##### Output 9
-
-```
-+------+-----------------------+--------+
-| NAME | SIGN | RATING |
-+------+-----------------------+--------+
-| A | The Good | 500 |
-| B | The Very very Bad Man | 288 |
-| C | The Ugly | 120 |
-| D | The Gopher | 800 |
-+------+-----------------------+--------+
-Movie ratings.
-```
-
-#### Example 10 - Set NoWhiteSpace and TablePadding option
-
-```go
-data := [][]string{
-{"node1.example.com", "Ready", "compute", "1.11"},
-{"node2.example.com", "Ready", "compute", "1.11"},
-{"node3.example.com", "Ready", "compute", "1.11"},
-{"node4.example.com", "NotReady", "compute", "1.11"},
-}
-
-table := tablewriter.NewWriter(os.Stdout)
-table.SetHeader([]string{"Name", "Status", "Role", "Version"})
-table.SetAutoWrapText(false)
-table.SetAutoFormatHeaders(true)
-table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
-table.SetAlignment(tablewriter.ALIGN_LEFT)
-table.SetCenterSeparator("")
-table.SetColumnSeparator("")
-table.SetRowSeparator("")
-table.SetHeaderLine(false)
-table.EnableBorder(false)
-table.SetTablePadding("\t") // pad with tabs
-table.SetNoWhiteSpace(true)
-table.AppendBulk(data) // Add Bulk Data
-table.Render()
-```
-
-##### Output 10
-
-```
-NAME STATUS ROLE VERSION
-node1.example.com Ready compute 1.11
-node2.example.com Ready compute 1.11
-node3.example.com Ready compute 1.11
-node4.example.com NotReady compute 1.11
-```
-
-#### Render table into a string
-
-Instead of rendering the table to `io.Stdout` you can also render it into a string. Go 1.10 introduced the
-`strings.Builder` type which implements the `io.Writer` interface and can therefore be used for this task. Example:
-
-```go
-package main
-
-import (
- "strings"
- "fmt"
-
- "github.com/olekukonko/tablewriter"
-)
-
-func main() {
- tableString := &strings.Builder{}
- table := tablewriter.NewWriter(tableString)
-
- /*
- * Code to fill the table
- */
-
- table.Render()
-
- fmt.Println(tableString.String())
-}
-```
-
-#### TODO
-
-- ~~Import Directly from CSV~~ - `done`
-- ~~Support for `SetFooter`~~ - `done`
-- ~~Support for `SetBorder`~~ - `done`
-- ~~Support table with uneven rows~~ - `done`
-- ~~Support custom alignment~~
-- General Improvement & Optimisation
-- `NewHTML` Parse table from HTML
diff --git a/vendor/github.com/olekukonko/tablewriter/config.go b/vendor/github.com/olekukonko/tablewriter/config.go
deleted file mode 100644
index ddad9bd188..0000000000
--- a/vendor/github.com/olekukonko/tablewriter/config.go
+++ /dev/null
@@ -1,963 +0,0 @@
-package tablewriter
-
-import (
- "github.com/olekukonko/tablewriter/tw"
-)
-
-// Config represents the table configuration
-type Config struct {
- MaxWidth int
- Header tw.CellConfig
- Row tw.CellConfig
- Footer tw.CellConfig
- Debug bool
- Stream tw.StreamConfig
- Behavior tw.Behavior
- Widths tw.CellWidth
- Counter tw.Counter
-}
-
-// ConfigBuilder provides a fluent interface for building Config
-type ConfigBuilder struct {
- config Config
-}
-
-// NewConfigBuilder creates a new ConfigBuilder with defaults
-func NewConfigBuilder() *ConfigBuilder {
- return &ConfigBuilder{
- config: defaultConfig(),
- }
-}
-
-// Build returns the built Config
-func (b *ConfigBuilder) Build() Config {
- return b.config
-}
-
-// Header returns a HeaderConfigBuilder for header configuration
-func (b *ConfigBuilder) Header() *HeaderConfigBuilder {
- return &HeaderConfigBuilder{
- parent: b,
- config: &b.config.Header,
- }
-}
-
-// Row returns a RowConfigBuilder for row configuration
-func (b *ConfigBuilder) Row() *RowConfigBuilder {
- return &RowConfigBuilder{
- parent: b,
- config: &b.config.Row,
- }
-}
-
-// Footer returns a FooterConfigBuilder for footer configuration
-func (b *ConfigBuilder) Footer() *FooterConfigBuilder {
- return &FooterConfigBuilder{
- parent: b,
- config: &b.config.Footer,
- }
-}
-
-// Behavior returns a BehaviorConfigBuilder for behavior configuration
-func (b *ConfigBuilder) Behavior() *BehaviorConfigBuilder {
- return &BehaviorConfigBuilder{
- parent: b,
- config: &b.config.Behavior,
- }
-}
-
-// ForColumn returns a ColumnConfigBuilder for column-specific configuration
-func (b *ConfigBuilder) ForColumn(col int) *ColumnConfigBuilder {
- return &ColumnConfigBuilder{
- parent: b,
- col: col,
- }
-}
-
-// WithTrimSpace enables or disables automatic trimming of leading/trailing spaces.
-// Ignored in streaming mode.
-func (b *ConfigBuilder) WithTrimSpace(state tw.State) *ConfigBuilder {
- b.config.Behavior.TrimSpace = state
- return b
-}
-
-// WithDebug enables/disables debug logging
-func (b *ConfigBuilder) WithDebug(debug bool) *ConfigBuilder {
- b.config.Debug = debug
- return b
-}
-
-// WithAutoHide enables or disables automatic hiding of empty columns (ignored in streaming mode).
-func (b *ConfigBuilder) WithAutoHide(state tw.State) *ConfigBuilder {
- b.config.Behavior.AutoHide = state
- return b
-}
-
-// WithFooterAlignment sets the text alignment for all footer cells.
-// Invalid alignments are ignored.
-func (b *ConfigBuilder) WithFooterAlignment(align tw.Align) *ConfigBuilder {
- if align != tw.AlignLeft && align != tw.AlignRight && align != tw.AlignCenter && align != tw.AlignNone {
- return b
- }
- b.config.Footer.Alignment.Global = align
- return b
-}
-
-// WithFooterAutoFormat enables or disables automatic formatting (e.g., title case) for footer cells.
-func (b *ConfigBuilder) WithFooterAutoFormat(autoFormat tw.State) *ConfigBuilder {
- b.config.Footer.Formatting.AutoFormat = autoFormat
- return b
-}
-
-// WithFooterAutoWrap sets the wrapping behavior for footer cells (e.g., truncate, normal, break).
-// Invalid wrap modes are ignored.
-func (b *ConfigBuilder) WithFooterAutoWrap(autoWrap int) *ConfigBuilder {
- if autoWrap < tw.WrapNone || autoWrap > tw.WrapBreak {
- return b
- }
- b.config.Footer.Formatting.AutoWrap = autoWrap
- return b
-}
-
-// WithFooterGlobalPadding sets the global padding for all footer cells.
-func (b *ConfigBuilder) WithFooterGlobalPadding(padding tw.Padding) *ConfigBuilder {
- b.config.Footer.Padding.Global = padding
- return b
-}
-
-// WithFooterMaxWidth sets the maximum content width for footer cells.
-// Negative values are ignored.
-func (b *ConfigBuilder) WithFooterMaxWidth(maxWidth int) *ConfigBuilder {
- if maxWidth < 0 {
- return b
- }
- b.config.Footer.ColMaxWidths.Global = maxWidth
- return b
-}
-
-// WithFooterMergeMode sets the merge behavior for footer cells (e.g., horizontal, hierarchical).
-// Invalid merge modes are ignored.
-func (b *ConfigBuilder) WithFooterMergeMode(mergeMode int) *ConfigBuilder {
- if mergeMode < tw.MergeNone || mergeMode > tw.MergeHierarchical {
- return b
- }
- b.config.Footer.Formatting.MergeMode = mergeMode
- return b
-}
-
-// WithHeaderAlignment sets the text alignment for all header cells.
-// Invalid alignments are ignored.
-func (b *ConfigBuilder) WithHeaderAlignment(align tw.Align) *ConfigBuilder {
- if align != tw.AlignLeft && align != tw.AlignRight && align != tw.AlignCenter && align != tw.AlignNone {
- return b
- }
- b.config.Header.Alignment.Global = align
- return b
-}
-
-// WithHeaderAutoFormat enables or disables automatic formatting (e.g., title case) for header cells.
-func (b *ConfigBuilder) WithHeaderAutoFormat(autoFormat tw.State) *ConfigBuilder {
- b.config.Header.Formatting.AutoFormat = autoFormat
- return b
-}
-
-// WithHeaderAutoWrap sets the wrapping behavior for header cells (e.g., truncate, normal).
-// Invalid wrap modes are ignored.
-func (b *ConfigBuilder) WithHeaderAutoWrap(autoWrap int) *ConfigBuilder {
- if autoWrap < tw.WrapNone || autoWrap > tw.WrapBreak {
- return b
- }
- b.config.Header.Formatting.AutoWrap = autoWrap
- return b
-}
-
-// WithHeaderGlobalPadding sets the global padding for all header cells.
-func (b *ConfigBuilder) WithHeaderGlobalPadding(padding tw.Padding) *ConfigBuilder {
- b.config.Header.Padding.Global = padding
- return b
-}
-
-// WithHeaderMaxWidth sets the maximum content width for header cells.
-// Negative values are ignored.
-func (b *ConfigBuilder) WithHeaderMaxWidth(maxWidth int) *ConfigBuilder {
- if maxWidth < 0 {
- return b
- }
- b.config.Header.ColMaxWidths.Global = maxWidth
- return b
-}
-
-// WithHeaderMergeMode sets the merge behavior for header cells (e.g., horizontal, vertical).
-// Invalid merge modes are ignored.
-func (b *ConfigBuilder) WithHeaderMergeMode(mergeMode int) *ConfigBuilder {
- if mergeMode < tw.MergeNone || mergeMode > tw.MergeHierarchical {
- return b
- }
- b.config.Header.Formatting.MergeMode = mergeMode
- return b
-}
-
-// WithMaxWidth sets the maximum width for the entire table (0 means unlimited).
-// Negative values are treated as 0.
-func (b *ConfigBuilder) WithMaxWidth(width int) *ConfigBuilder {
- b.config.MaxWidth = max(width, 0)
- return b
-}
-
-// WithRowAlignment sets the text alignment for all row cells.
-// Invalid alignments are ignored.
-func (b *ConfigBuilder) WithRowAlignment(align tw.Align) *ConfigBuilder {
- if align != tw.AlignLeft && align != tw.AlignRight && align != tw.AlignCenter && align != tw.AlignNone {
- return b
- }
- b.config.Row.Alignment.Global = align
- return b
-}
-
-// WithRowAutoFormat enables or disables automatic formatting for row cells.
-func (b *ConfigBuilder) WithRowAutoFormat(autoFormat tw.State) *ConfigBuilder {
- b.config.Row.Formatting.AutoFormat = autoFormat
- return b
-}
-
-// WithRowAutoWrap sets the wrapping behavior for row cells (e.g., truncate, normal).
-// Invalid wrap modes are ignored.
-func (b *ConfigBuilder) WithRowAutoWrap(autoWrap int) *ConfigBuilder {
- if autoWrap < tw.WrapNone || autoWrap > tw.WrapBreak {
- return b
- }
- b.config.Row.Formatting.AutoWrap = autoWrap
- return b
-}
-
-// WithRowGlobalPadding sets the global padding for all row cells.
-func (b *ConfigBuilder) WithRowGlobalPadding(padding tw.Padding) *ConfigBuilder {
- b.config.Row.Padding.Global = padding
- return b
-}
-
-// WithRowMaxWidth sets the maximum content width for row cells.
-// Negative values are ignored.
-func (b *ConfigBuilder) WithRowMaxWidth(maxWidth int) *ConfigBuilder {
- if maxWidth < 0 {
- return b
- }
- b.config.Row.ColMaxWidths.Global = maxWidth
- return b
-}
-
-// WithRowMergeMode sets the merge behavior for row cells (e.g., horizontal, hierarchical).
-// Invalid merge modes are ignored.
-func (b *ConfigBuilder) WithRowMergeMode(mergeMode int) *ConfigBuilder {
- if mergeMode < tw.MergeNone || mergeMode > tw.MergeHierarchical {
- return b
- }
- b.config.Row.Formatting.MergeMode = mergeMode
- return b
-}
-
-// HeaderConfigBuilder configures header settings
-type HeaderConfigBuilder struct {
- parent *ConfigBuilder
- config *tw.CellConfig
-}
-
-// Build returns the parent ConfigBuilder
-func (h *HeaderConfigBuilder) Build() *ConfigBuilder {
- return h.parent
-}
-
-// Alignment returns an AlignmentConfigBuilder for header alignment
-func (h *HeaderConfigBuilder) Alignment() *AlignmentConfigBuilder {
- return &AlignmentConfigBuilder{
- parent: h.parent,
- config: &h.config.Alignment,
- section: "header",
- }
-}
-
-// Formatting returns a HeaderFormattingBuilder for header formatting
-func (h *HeaderConfigBuilder) Formatting() *HeaderFormattingBuilder {
- return &HeaderFormattingBuilder{
- parent: h,
- config: &h.config.Formatting,
- section: "header",
- }
-}
-
-// Padding returns a HeaderPaddingBuilder for header padding
-func (h *HeaderConfigBuilder) Padding() *HeaderPaddingBuilder {
- return &HeaderPaddingBuilder{
- parent: h,
- config: &h.config.Padding,
- section: "header",
- }
-}
-
-// Filter returns a HeaderFilterBuilder for header filtering
-func (h *HeaderConfigBuilder) Filter() *HeaderFilterBuilder {
- return &HeaderFilterBuilder{
- parent: h,
- config: &h.config.Filter,
- section: "header",
- }
-}
-
-// Callbacks returns a HeaderCallbacksBuilder for header callbacks
-func (h *HeaderConfigBuilder) Callbacks() *HeaderCallbacksBuilder {
- return &HeaderCallbacksBuilder{
- parent: h,
- config: &h.config.Callbacks,
- section: "header",
- }
-}
-
-// RowConfigBuilder configures row settings
-type RowConfigBuilder struct {
- parent *ConfigBuilder
- config *tw.CellConfig
-}
-
-// Build returns the parent ConfigBuilder
-func (r *RowConfigBuilder) Build() *ConfigBuilder {
- return r.parent
-}
-
-// Alignment returns an AlignmentConfigBuilder for row alignment
-func (r *RowConfigBuilder) Alignment() *AlignmentConfigBuilder {
- return &AlignmentConfigBuilder{
- parent: r.parent,
- config: &r.config.Alignment,
- section: "row",
- }
-}
-
-// Formatting returns a RowFormattingBuilder for row formatting
-func (r *RowConfigBuilder) Formatting() *RowFormattingBuilder {
- return &RowFormattingBuilder{
- parent: r,
- config: &r.config.Formatting,
- section: "row",
- }
-}
-
-// Padding returns a RowPaddingBuilder for row padding
-func (r *RowConfigBuilder) Padding() *RowPaddingBuilder {
- return &RowPaddingBuilder{
- parent: r,
- config: &r.config.Padding,
- section: "row",
- }
-}
-
-// Filter returns a RowFilterBuilder for row filtering
-func (r *RowConfigBuilder) Filter() *RowFilterBuilder {
- return &RowFilterBuilder{
- parent: r,
- config: &r.config.Filter,
- section: "row",
- }
-}
-
-// Callbacks returns a RowCallbacksBuilder for row callbacks
-func (r *RowConfigBuilder) Callbacks() *RowCallbacksBuilder {
- return &RowCallbacksBuilder{
- parent: r,
- config: &r.config.Callbacks,
- section: "row",
- }
-}
-
-// FooterConfigBuilder configures footer settings
-type FooterConfigBuilder struct {
- parent *ConfigBuilder
- config *tw.CellConfig
-}
-
-// Build returns the parent ConfigBuilder
-func (f *FooterConfigBuilder) Build() *ConfigBuilder {
- return f.parent
-}
-
-// Alignment returns an AlignmentConfigBuilder for footer alignment
-func (f *FooterConfigBuilder) Alignment() *AlignmentConfigBuilder {
- return &AlignmentConfigBuilder{
- parent: f.parent,
- config: &f.config.Alignment,
- section: "footer",
- }
-}
-
-// Formatting returns a FooterFormattingBuilder for footer formatting
-func (f *FooterConfigBuilder) Formatting() *FooterFormattingBuilder {
- return &FooterFormattingBuilder{
- parent: f,
- config: &f.config.Formatting,
- section: "footer",
- }
-}
-
-// Padding returns a FooterPaddingBuilder for footer padding
-func (f *FooterConfigBuilder) Padding() *FooterPaddingBuilder {
- return &FooterPaddingBuilder{
- parent: f,
- config: &f.config.Padding,
- section: "footer",
- }
-}
-
-// Filter returns a FooterFilterBuilder for footer filtering
-func (f *FooterConfigBuilder) Filter() *FooterFilterBuilder {
- return &FooterFilterBuilder{
- parent: f,
- config: &f.config.Filter,
- section: "footer",
- }
-}
-
-// Callbacks returns a FooterCallbacksBuilder for footer callbacks
-func (f *FooterConfigBuilder) Callbacks() *FooterCallbacksBuilder {
- return &FooterCallbacksBuilder{
- parent: f,
- config: &f.config.Callbacks,
- section: "footer",
- }
-}
-
-// AlignmentConfigBuilder configures alignment settings
-type AlignmentConfigBuilder struct {
- parent *ConfigBuilder
- config *tw.CellAlignment
- section string
-}
-
-// Build returns the parent ConfigBuilder
-func (a *AlignmentConfigBuilder) Build() *ConfigBuilder {
- return a.parent
-}
-
-// WithGlobal sets global alignment
-func (a *AlignmentConfigBuilder) WithGlobal(align tw.Align) *AlignmentConfigBuilder {
- if err := align.Validate(); err == nil {
- a.config.Global = align
- }
- return a
-}
-
-// WithPerColumn sets per-column alignments
-func (a *AlignmentConfigBuilder) WithPerColumn(alignments []tw.Align) *AlignmentConfigBuilder {
- if len(alignments) > 0 {
- a.config.PerColumn = alignments
- }
- return a
-}
-
-// HeaderFormattingBuilder configures header formatting
-type HeaderFormattingBuilder struct {
- parent *HeaderConfigBuilder
- config *tw.CellFormatting
- section string
-}
-
-// Build returns the parent HeaderConfigBuilder
-func (hf *HeaderFormattingBuilder) Build() *HeaderConfigBuilder {
- return hf.parent
-}
-
-// WithAutoFormat enables/disables auto formatting
-func (hf *HeaderFormattingBuilder) WithAutoFormat(autoFormat tw.State) *HeaderFormattingBuilder {
- hf.config.AutoFormat = autoFormat
- return hf
-}
-
-// WithAutoWrap sets auto wrap mode
-func (hf *HeaderFormattingBuilder) WithAutoWrap(autoWrap int) *HeaderFormattingBuilder {
- if autoWrap >= tw.WrapNone && autoWrap <= tw.WrapBreak {
- hf.config.AutoWrap = autoWrap
- }
- return hf
-}
-
-// WithMergeMode sets merge mode
-func (hf *HeaderFormattingBuilder) WithMergeMode(mergeMode int) *HeaderFormattingBuilder {
- if mergeMode >= tw.MergeNone && mergeMode <= tw.MergeHierarchical {
- hf.config.MergeMode = mergeMode
- }
- return hf
-}
-
-// RowFormattingBuilder configures row formatting
-type RowFormattingBuilder struct {
- parent *RowConfigBuilder
- config *tw.CellFormatting
- section string
-}
-
-// Build returns the parent RowConfigBuilder
-func (rf *RowFormattingBuilder) Build() *RowConfigBuilder {
- return rf.parent
-}
-
-// WithAutoFormat enables/disables auto formatting
-func (rf *RowFormattingBuilder) WithAutoFormat(autoFormat tw.State) *RowFormattingBuilder {
- rf.config.AutoFormat = autoFormat
- return rf
-}
-
-// WithAutoWrap sets auto wrap mode
-func (rf *RowFormattingBuilder) WithAutoWrap(autoWrap int) *RowFormattingBuilder {
- if autoWrap >= tw.WrapNone && autoWrap <= tw.WrapBreak {
- rf.config.AutoWrap = autoWrap
- }
- return rf
-}
-
-// WithMergeMode sets merge mode
-func (rf *RowFormattingBuilder) WithMergeMode(mergeMode int) *RowFormattingBuilder {
- if mergeMode >= tw.MergeNone && mergeMode <= tw.MergeHierarchical {
- rf.config.MergeMode = mergeMode
- }
- return rf
-}
-
-// FooterFormattingBuilder configures footer formatting
-type FooterFormattingBuilder struct {
- parent *FooterConfigBuilder
- config *tw.CellFormatting
- section string
-}
-
-// Build returns the parent FooterConfigBuilder
-func (ff *FooterFormattingBuilder) Build() *FooterConfigBuilder {
- return ff.parent
-}
-
-// WithAutoFormat enables/disables auto formatting
-func (ff *FooterFormattingBuilder) WithAutoFormat(autoFormat tw.State) *FooterFormattingBuilder {
- ff.config.AutoFormat = autoFormat
- return ff
-}
-
-// WithAutoWrap sets auto wrap mode
-func (ff *FooterFormattingBuilder) WithAutoWrap(autoWrap int) *FooterFormattingBuilder {
- if autoWrap >= tw.WrapNone && autoWrap <= tw.WrapBreak {
- ff.config.AutoWrap = autoWrap
- }
- return ff
-}
-
-// WithMergeMode sets merge mode
-func (ff *FooterFormattingBuilder) WithMergeMode(mergeMode int) *FooterFormattingBuilder {
- if mergeMode >= tw.MergeNone && mergeMode <= tw.MergeHierarchical {
- ff.config.MergeMode = mergeMode
- }
- return ff
-}
-
-// HeaderPaddingBuilder configures header padding
-type HeaderPaddingBuilder struct {
- parent *HeaderConfigBuilder
- config *tw.CellPadding
- section string
-}
-
-// Build returns the parent HeaderConfigBuilder
-func (hp *HeaderPaddingBuilder) Build() *HeaderConfigBuilder {
- return hp.parent
-}
-
-// WithGlobal sets global padding
-func (hp *HeaderPaddingBuilder) WithGlobal(padding tw.Padding) *HeaderPaddingBuilder {
- hp.config.Global = padding
- return hp
-}
-
-// WithPerColumn sets per-column padding
-func (hp *HeaderPaddingBuilder) WithPerColumn(padding []tw.Padding) *HeaderPaddingBuilder {
- hp.config.PerColumn = padding
- return hp
-}
-
-// AddColumnPadding adds padding for a specific column in the header
-func (hp *HeaderPaddingBuilder) AddColumnPadding(padding tw.Padding) *HeaderPaddingBuilder {
- hp.config.PerColumn = append(hp.config.PerColumn, padding)
- return hp
-}
-
-// RowPaddingBuilder configures row padding
-type RowPaddingBuilder struct {
- parent *RowConfigBuilder
- config *tw.CellPadding
- section string
-}
-
-// Build returns the parent RowConfigBuilder
-func (rp *RowPaddingBuilder) Build() *RowConfigBuilder {
- return rp.parent
-}
-
-// WithGlobal sets global padding
-func (rp *RowPaddingBuilder) WithGlobal(padding tw.Padding) *RowPaddingBuilder {
- rp.config.Global = padding
- return rp
-}
-
-// WithPerColumn sets per-column padding
-func (rp *RowPaddingBuilder) WithPerColumn(padding []tw.Padding) *RowPaddingBuilder {
- rp.config.PerColumn = padding
- return rp
-}
-
-// AddColumnPadding adds padding for a specific column in the rows
-func (rp *RowPaddingBuilder) AddColumnPadding(padding tw.Padding) *RowPaddingBuilder {
- rp.config.PerColumn = append(rp.config.PerColumn, padding)
- return rp
-}
-
-// FooterPaddingBuilder configures footer padding
-type FooterPaddingBuilder struct {
- parent *FooterConfigBuilder
- config *tw.CellPadding
- section string
-}
-
-// Build returns the parent FooterConfigBuilder
-func (fp *FooterPaddingBuilder) Build() *FooterConfigBuilder {
- return fp.parent
-}
-
-// WithGlobal sets global padding
-func (fp *FooterPaddingBuilder) WithGlobal(padding tw.Padding) *FooterPaddingBuilder {
- fp.config.Global = padding
- return fp
-}
-
-// WithPerColumn sets per-column padding
-func (fp *FooterPaddingBuilder) WithPerColumn(padding []tw.Padding) *FooterPaddingBuilder {
- fp.config.PerColumn = padding
- return fp
-}
-
-// AddColumnPadding adds padding for a specific column in the footer
-func (fp *FooterPaddingBuilder) AddColumnPadding(padding tw.Padding) *FooterPaddingBuilder {
- fp.config.PerColumn = append(fp.config.PerColumn, padding)
- return fp
-}
-
-// BehaviorConfigBuilder configures behavior settings
-type BehaviorConfigBuilder struct {
- parent *ConfigBuilder
- config *tw.Behavior
-}
-
-// Build returns the parent ConfigBuilder
-func (bb *BehaviorConfigBuilder) Build() *ConfigBuilder {
- return bb.parent
-}
-
-// WithAutoHide enables/disables auto-hide
-func (bb *BehaviorConfigBuilder) WithAutoHide(state tw.State) *BehaviorConfigBuilder {
- bb.config.AutoHide = state
- return bb
-}
-
-// WithTrimSpace enables/disables trim space
-func (bb *BehaviorConfigBuilder) WithTrimSpace(state tw.State) *BehaviorConfigBuilder {
- bb.config.TrimSpace = state
- return bb
-}
-
-// WithHeaderHide enables/disables header visibility
-func (bb *BehaviorConfigBuilder) WithHeaderHide(state tw.State) *BehaviorConfigBuilder {
- bb.config.Header.Hide = state
- return bb
-}
-
-// WithFooterHide enables/disables footer visibility
-func (bb *BehaviorConfigBuilder) WithFooterHide(state tw.State) *BehaviorConfigBuilder {
- bb.config.Footer.Hide = state
- return bb
-}
-
-// WithCompactMerge enables/disables compact width optimization for merged cells
-func (bb *BehaviorConfigBuilder) WithCompactMerge(state tw.State) *BehaviorConfigBuilder {
- bb.config.Compact.Merge = state
- return bb
-}
-
-// WithAutoHeader enables/disables automatic header extraction for structs in Bulk.
-func (bb *BehaviorConfigBuilder) WithAutoHeader(state tw.State) *BehaviorConfigBuilder {
- bb.config.Structs.AutoHeader = state
- return bb
-}
-
-// ColumnConfigBuilder configures column-specific settings
-type ColumnConfigBuilder struct {
- parent *ConfigBuilder
- col int
-}
-
-// Build returns the parent ConfigBuilder
-func (c *ColumnConfigBuilder) Build() *ConfigBuilder {
- return c.parent
-}
-
-// WithAlignment sets alignment for the column
-func (c *ColumnConfigBuilder) WithAlignment(align tw.Align) *ColumnConfigBuilder {
- if err := align.Validate(); err == nil {
- // Ensure slices are large enough
- if len(c.parent.config.Header.Alignment.PerColumn) <= c.col {
- newAligns := make([]tw.Align, c.col+1)
- copy(newAligns, c.parent.config.Header.Alignment.PerColumn)
- c.parent.config.Header.Alignment.PerColumn = newAligns
- }
- c.parent.config.Header.Alignment.PerColumn[c.col] = align
-
- if len(c.parent.config.Row.Alignment.PerColumn) <= c.col {
- newAligns := make([]tw.Align, c.col+1)
- copy(newAligns, c.parent.config.Row.Alignment.PerColumn)
- c.parent.config.Row.Alignment.PerColumn = newAligns
- }
- c.parent.config.Row.Alignment.PerColumn[c.col] = align
-
- if len(c.parent.config.Footer.Alignment.PerColumn) <= c.col {
- newAligns := make([]tw.Align, c.col+1)
- copy(newAligns, c.parent.config.Footer.Alignment.PerColumn)
- c.parent.config.Footer.Alignment.PerColumn = newAligns
- }
- c.parent.config.Footer.Alignment.PerColumn[c.col] = align
- }
- return c
-}
-
-// WithMaxWidth sets max width for the column
-func (c *ColumnConfigBuilder) WithMaxWidth(width int) *ColumnConfigBuilder {
- if width >= 0 {
- // Initialize maps if needed
- if c.parent.config.Header.ColMaxWidths.PerColumn == nil {
- c.parent.config.Header.ColMaxWidths.PerColumn = make(tw.Mapper[int, int])
- c.parent.config.Row.ColMaxWidths.PerColumn = make(tw.Mapper[int, int])
- c.parent.config.Footer.ColMaxWidths.PerColumn = make(tw.Mapper[int, int])
- }
- c.parent.config.Header.ColMaxWidths.PerColumn[c.col] = width
- c.parent.config.Row.ColMaxWidths.PerColumn[c.col] = width
- c.parent.config.Footer.ColMaxWidths.PerColumn[c.col] = width
- }
- return c
-}
-
-// HeaderFilterBuilder configures header filtering
-type HeaderFilterBuilder struct {
- parent *HeaderConfigBuilder
- config *tw.CellFilter
- section string
-}
-
-// Build returns the parent HeaderConfigBuilder
-func (hf *HeaderFilterBuilder) Build() *HeaderConfigBuilder {
- return hf.parent
-}
-
-// WithGlobal sets the global filter function for the header
-func (hf *HeaderFilterBuilder) WithGlobal(filter func([]string) []string) *HeaderFilterBuilder {
- if filter != nil {
- hf.config.Global = filter
- }
- return hf
-}
-
-// WithPerColumn sets per-column filter functions for the header
-func (hf *HeaderFilterBuilder) WithPerColumn(filters []func(string) string) *HeaderFilterBuilder {
- if len(filters) > 0 {
- hf.config.PerColumn = filters
- }
- return hf
-}
-
-// AddColumnFilter adds a filter function for a specific column in the header
-func (hf *HeaderFilterBuilder) AddColumnFilter(filter func(string) string) *HeaderFilterBuilder {
- if filter != nil {
- hf.config.PerColumn = append(hf.config.PerColumn, filter)
- }
- return hf
-}
-
-// RowFilterBuilder configures row filtering
-type RowFilterBuilder struct {
- parent *RowConfigBuilder
- config *tw.CellFilter
- section string
-}
-
-// Build returns the parent RowConfigBuilder
-func (rf *RowFilterBuilder) Build() *RowConfigBuilder {
- return rf.parent
-}
-
-// WithGlobal sets the global filter function for the rows
-func (rf *RowFilterBuilder) WithGlobal(filter func([]string) []string) *RowFilterBuilder {
- if filter != nil {
- rf.config.Global = filter
- }
- return rf
-}
-
-// WithPerColumn sets per-column filter functions for the rows
-func (rf *RowFilterBuilder) WithPerColumn(filters []func(string) string) *RowFilterBuilder {
- if len(filters) > 0 {
- rf.config.PerColumn = filters
- }
- return rf
-}
-
-// AddColumnFilter adds a filter function for a specific column in the rows
-func (rf *RowFilterBuilder) AddColumnFilter(filter func(string) string) *RowFilterBuilder {
- if filter != nil {
- rf.config.PerColumn = append(rf.config.PerColumn, filter)
- }
- return rf
-}
-
-// FooterFilterBuilder configures footer filtering
-type FooterFilterBuilder struct {
- parent *FooterConfigBuilder
- config *tw.CellFilter
- section string
-}
-
-// Build returns the parent FooterConfigBuilder
-func (ff *FooterFilterBuilder) Build() *FooterConfigBuilder {
- return ff.parent
-}
-
-// WithGlobal sets the global filter function for the footer
-func (ff *FooterFilterBuilder) WithGlobal(filter func([]string) []string) *FooterFilterBuilder {
- if filter != nil {
- ff.config.Global = filter
- }
- return ff
-}
-
-// WithPerColumn sets per-column filter functions for the footer
-func (ff *FooterFilterBuilder) WithPerColumn(filters []func(string) string) *FooterFilterBuilder {
- if len(filters) > 0 {
- ff.config.PerColumn = filters
- }
- return ff
-}
-
-// AddColumnFilter adds a filter function for a specific column in the footer
-func (ff *FooterFilterBuilder) AddColumnFilter(filter func(string) string) *FooterFilterBuilder {
- if filter != nil {
- ff.config.PerColumn = append(ff.config.PerColumn, filter)
- }
- return ff
-}
-
-// HeaderCallbacksBuilder configures header callbacks
-type HeaderCallbacksBuilder struct {
- parent *HeaderConfigBuilder
- config *tw.CellCallbacks
- section string
-}
-
-// Build returns the parent HeaderConfigBuilder
-func (hc *HeaderCallbacksBuilder) Build() *HeaderConfigBuilder {
- return hc.parent
-}
-
-// WithGlobal sets the global callback function for the header
-func (hc *HeaderCallbacksBuilder) WithGlobal(callback func()) *HeaderCallbacksBuilder {
- if callback != nil {
- hc.config.Global = callback
- }
- return hc
-}
-
-// WithPerColumn sets per-column callback functions for the header
-func (hc *HeaderCallbacksBuilder) WithPerColumn(callbacks []func()) *HeaderCallbacksBuilder {
- if len(callbacks) > 0 {
- hc.config.PerColumn = callbacks
- }
- return hc
-}
-
-// AddColumnCallback adds a callback function for a specific column in the header
-func (hc *HeaderCallbacksBuilder) AddColumnCallback(callback func()) *HeaderCallbacksBuilder {
- if callback != nil {
- hc.config.PerColumn = append(hc.config.PerColumn, callback)
- }
- return hc
-}
-
-// RowCallbacksBuilder configures row callbacks
-type RowCallbacksBuilder struct {
- parent *RowConfigBuilder
- config *tw.CellCallbacks
- section string
-}
-
-// Build returns the parent RowConfigBuilder
-func (rc *RowCallbacksBuilder) Build() *RowConfigBuilder {
- return rc.parent
-}
-
-// WithGlobal sets the global callback function for the rows
-func (rc *RowCallbacksBuilder) WithGlobal(callback func()) *RowCallbacksBuilder {
- if callback != nil {
- rc.config.Global = callback
- }
- return rc
-}
-
-// WithPerColumn sets per-column callback functions for the rows
-func (rc *RowCallbacksBuilder) WithPerColumn(callbacks []func()) *RowCallbacksBuilder {
- if len(callbacks) > 0 {
- rc.config.PerColumn = callbacks
- }
- return rc
-}
-
-// AddColumnCallback adds a callback function for a specific column in the rows
-func (rc *RowCallbacksBuilder) AddColumnCallback(callback func()) *RowCallbacksBuilder {
- if callback != nil {
- rc.config.PerColumn = append(rc.config.PerColumn, callback)
- }
- return rc
-}
-
-// FooterCallbacksBuilder configures footer callbacks
-type FooterCallbacksBuilder struct {
- parent *FooterConfigBuilder
- config *tw.CellCallbacks
- section string
-}
-
-// Build returns the parent FooterConfigBuilder
-func (fc *FooterCallbacksBuilder) Build() *FooterConfigBuilder {
- return fc.parent
-}
-
-// WithGlobal sets the global callback function for the footer
-func (fc *FooterCallbacksBuilder) WithGlobal(callback func()) *FooterCallbacksBuilder {
- if callback != nil {
- fc.config.Global = callback
- }
- return fc
-}
-
-// WithPerColumn sets per-column callback functions for the footer
-func (fc *FooterCallbacksBuilder) WithPerColumn(callbacks []func()) *FooterCallbacksBuilder {
- if len(callbacks) > 0 {
- fc.config.PerColumn = callbacks
- }
- return fc
-}
-
-// AddColumnCallback adds a callback function for a specific column in the footer
-func (fc *FooterCallbacksBuilder) AddColumnCallback(callback func()) *FooterCallbacksBuilder {
- if callback != nil {
- fc.config.PerColumn = append(fc.config.PerColumn, callback)
- }
- return fc
-}
diff --git a/vendor/github.com/olekukonko/tablewriter/csv.go b/vendor/github.com/olekukonko/tablewriter/csv.go
deleted file mode 100644
index 0426320800..0000000000
--- a/vendor/github.com/olekukonko/tablewriter/csv.go
+++ /dev/null
@@ -1,94 +0,0 @@
-package tablewriter
-
-import (
- "encoding/csv"
- "io"
- "os"
-)
-
-// NewCSV Start A new table by importing from a CSV file
-// Takes io.Writer and csv File name
-func NewCSV(writer io.Writer, fileName string, hasHeader bool, opts ...Option) (*Table, error) {
- // Open the CSV file
- file, err := os.Open(fileName)
- if err != nil {
- // Log implicitly handled by NewTable if logger is configured via opts
- return nil, err // Return nil *Table on error
- }
- defer file.Close() // Ensure file is closed
-
- // Create a CSV reader
- csvReader := csv.NewReader(file)
-
- // Delegate to NewCSVReader, passing through the options
- return NewCSVReader(writer, csvReader, hasHeader, opts...)
-}
-
-// NewCSVReader Start a New Table Writer with csv.Reader
-// This enables customisation such as reader.Comma = ';'
-// See http://golang.org/src/pkg/encoding/csv/reader.go?s=3213:3671#L94
-func NewCSVReader(writer io.Writer, csvReader *csv.Reader, hasHeader bool, opts ...Option) (*Table, error) {
- // Create a new table instance using the modern API and provided options.
- // Options configure the table's appearance and behavior (renderer, borders, etc.).
- t := NewTable(writer, opts...) // Logger setup happens here if WithLogger/WithDebug is passed
-
- // Process header row if specified
- if hasHeader {
- headers, err := csvReader.Read()
- if err != nil {
- // Handle EOF specifically: means the CSV was empty or contained only an empty header line.
- if err == io.EOF {
- t.logger.Debug("NewCSVReader: CSV empty or only header found (EOF after header read attempt).")
- // Return the table configured by opts, but without data/header.
- // It's ready for Render() which will likely output nothing or just borders if configured.
- return t, nil
- }
- // Log other read errors
- t.logger.Errorf("NewCSVReader: Error reading CSV header: %v", err)
- return nil, err // Return nil *Table on critical read error
- }
-
- // Check if the read header is genuinely empty (e.g., a blank line in the CSV)
- isEmptyHeader := true
- for _, h := range headers {
- if h != "" {
- isEmptyHeader = false
- break
- }
- }
-
- if !isEmptyHeader {
- t.Header(headers) // Use the Table method to set the header data
- t.logger.Debugf("NewCSVReader: Header set from CSV: %v", headers)
- } else {
- t.logger.Debug("NewCSVReader: Read an empty header line, skipping setting table header.")
- }
- }
-
- // Process data rows
- rowCount := 0
- for {
- record, err := csvReader.Read()
- if err == io.EOF {
- break // Reached the end of the CSV data
- }
- if err != nil {
- // Log other read errors during data processing
- t.logger.Errorf("NewCSVReader: Error reading CSV record: %v", err)
- return nil, err // Return nil *Table on critical read error
- }
-
- // Append the record to the table's internal buffer (for batch rendering).
- // The Table.Append method handles conversion and storage.
- if appendErr := t.Append(record); appendErr != nil {
- t.logger.Errorf("NewCSVReader: Error appending record #%d: %v", rowCount+1, appendErr)
- // Decide if append error is fatal. For now, let's treat it as fatal.
- return nil, appendErr
- }
- rowCount++
- }
- t.logger.Debugf("NewCSVReader: Finished reading CSV. Appended %d data rows.", rowCount)
-
- // Return the configured and populated table instance, ready for Render() call.
- return t, nil
-}
diff --git a/vendor/github.com/olekukonko/tablewriter/deprecated.go b/vendor/github.com/olekukonko/tablewriter/deprecated.go
deleted file mode 100644
index aa119e48d6..0000000000
--- a/vendor/github.com/olekukonko/tablewriter/deprecated.go
+++ /dev/null
@@ -1,220 +0,0 @@
-package tablewriter
-
-import (
- "github.com/olekukonko/tablewriter/tw"
-)
-
-// WithBorders configures the table's border settings by updating the renderer's border configuration.
-// This function is deprecated and will be removed in a future version.
-//
-// Deprecated: Use [WithRendition] to configure border settings for renderers that support
-// [tw.Renditioning], or update the renderer's [tw.RenderConfig] directly via its Config() method.
-// This function has no effect if no renderer is set on the table.
-//
-// Example migration:
-//
-// // Old (deprecated)
-// table.Options(WithBorders(tw.Border{Top: true, Bottom: true}))
-// // New (recommended)
-// table.Options(WithRendition(tw.Rendition{Borders: tw.Border{Top: true, Bottom: true}}))
-//
-// Parameters:
-// - borders: The [tw.Border] configuration to apply to the renderer's borders.
-//
-// Returns:
-//
-// An [Option] that updates the renderer's border settings if a renderer is set.
-// Logs a debug message if debugging is enabled and a renderer is present.
-func WithBorders(borders tw.Border) Option {
- return func(target *Table) {
- if target.renderer != nil {
- cfg := target.renderer.Config()
- cfg.Borders = borders
- if target.logger != nil {
- target.logger.Debugf("Option: WithBorders applied to Table: %+v", borders)
- }
- }
- }
-}
-
-// Behavior is an alias for [tw.Behavior] to configure table behavior settings.
-// This type is deprecated and will be removed in a future version.
-//
-// Deprecated: Use [tw.Behavior] directly to configure settings such as auto-hiding empty
-// columns, trimming spaces, or controlling header/footer visibility.
-//
-// Example migration:
-//
-// // Old (deprecated)
-// var b tablewriter.Behavior = tablewriter.Behavior{AutoHide: tw.On}
-// // New (recommended)
-// var b tw.Behavior = tw.Behavior{AutoHide: tw.On}
-type Behavior tw.Behavior
-
-// Settings is an alias for [tw.Settings] to configure renderer settings.
-// This type is deprecated and will be removed in a future version.
-//
-// Deprecated: Use [tw.Settings] directly to configure renderer settings, such as
-// separators and line styles.
-//
-// Example migration:
-//
-// // Old (deprecated)
-// var s tablewriter.Settings = tablewriter.Settings{Separator: "|"}
-// // New (recommended)
-// var s tw.Settings = tw.Settings{Separator: "|"}
-type Settings tw.Settings
-
-// WithRendererSettings updates the renderer's settings, such as separators and line styles.
-// This function is deprecated and will be removed in a future version.
-//
-// Deprecated: Use [WithRendition] to update renderer settings for renderers that implement
-// [tw.Renditioning], or configure the renderer's [tw.Settings] directly via its
-// [tw.Renderer.Config] method. This function has no effect if no renderer is set.
-//
-// Example migration:
-//
-// // Old (deprecated)
-// table.Options(WithRendererSettings(tw.Settings{Separator: "|"}))
-// // New (recommended)
-// table.Options(WithRendition(tw.Rendition{Settings: tw.Settings{Separator: "|"}}))
-//
-// Parameters:
-// - settings: The [tw.Settings] configuration to apply to the renderer.
-//
-// Returns:
-//
-// An [Option] that updates the renderer's settings if a renderer is set.
-// Logs a debug message if debugging is enabled and a renderer is present.
-func WithRendererSettings(settings tw.Settings) Option {
- return func(target *Table) {
- if target.renderer != nil {
- cfg := target.renderer.Config()
- cfg.Settings = settings
- if target.logger != nil {
- target.logger.Debugf("Option: WithRendererSettings applied to Table: %+v", settings)
- }
- }
- }
-}
-
-// WithAlignment sets the text alignment for footer cells within the formatting configuration.
-// This method is deprecated and will be removed in the next version.
-//
-// Deprecated: Use [FooterConfigBuilder.Alignment] with [AlignmentConfigBuilder.WithGlobal]
-// or [AlignmentConfigBuilder.WithPerColumn] to configure footer alignments.
-// Alternatively, apply a complete [tw.CellAlignment] configuration using
-// [WithFooterAlignmentConfig].
-//
-// Example migration:
-//
-// // Old (deprecated)
-// builder.Footer().Formatting().WithAlignment(tw.AlignRight)
-// // New (recommended)
-// builder.Footer().Alignment().WithGlobal(tw.AlignRight)
-// // Or
-// table.Options(WithFooterAlignmentConfig(tw.CellAlignment{Global: tw.AlignRight}))
-//
-// Parameters:
-// - align: The [tw.Align] value to set for footer cells. Valid values are
-// [tw.AlignLeft], [tw.AlignRight], [tw.AlignCenter], and [tw.AlignNone].
-// Invalid alignments are ignored.
-//
-// Returns:
-//
-// The [FooterFormattingBuilder] instance for method chaining.
-func (ff *FooterFormattingBuilder) WithAlignment(align tw.Align) *FooterFormattingBuilder {
- if align != tw.AlignLeft && align != tw.AlignRight && align != tw.AlignCenter && align != tw.AlignNone {
- return ff
- }
- ff.config.Alignment = align
- return ff
-}
-
-// WithAlignment sets the text alignment for header cells within the formatting configuration.
-// This method is deprecated and will be removed in the next version.
-//
-// Deprecated: Use [HeaderConfigBuilder.Alignment] with [AlignmentConfigBuilder.WithGlobal]
-// or [AlignmentConfigBuilder.WithPerColumn] to configure header alignments.
-// Alternatively, apply a complete [tw.CellAlignment] configuration using
-// [WithHeaderAlignmentConfig].
-//
-// Example migration:
-//
-// // Old (deprecated)
-// builder.Header().Formatting().WithAlignment(tw.AlignCenter)
-// // New (recommended)
-// builder.Header().Alignment().WithGlobal(tw.AlignCenter)
-// // Or
-// table.Options(WithHeaderAlignmentConfig(tw.CellAlignment{Global: tw.AlignCenter}))
-//
-// Parameters:
-// - align: The [tw.Align] value to set for header cells. Valid values are
-// [tw.AlignLeft], [tw.AlignRight], [tw.AlignCenter], and [tw.AlignNone].
-// Invalid alignments are ignored.
-//
-// Returns:
-//
-// The [HeaderFormattingBuilder] instance for method chaining.
-func (hf *HeaderFormattingBuilder) WithAlignment(align tw.Align) *HeaderFormattingBuilder {
- if align != tw.AlignLeft && align != tw.AlignRight && align != tw.AlignCenter && align != tw.AlignNone {
- return hf
- }
- hf.config.Alignment = align
- return hf
-}
-
-// WithAlignment sets the text alignment for row cells within the formatting configuration.
-// This method is deprecated and will be removed in the next version.
-//
-// Deprecated: Use [RowConfigBuilder.Alignment] with [AlignmentConfigBuilder.WithGlobal]
-// or [AlignmentConfigBuilder.WithPerColumn] to configure row alignments.
-// Alternatively, apply a complete [tw.CellAlignment] configuration using
-// [WithRowAlignmentConfig].
-//
-// Example migration:
-//
-// // Old (deprecated)
-// builder.Row().Formatting().WithAlignment(tw.AlignLeft)
-// // New (recommended)
-// builder.Row().Alignment().WithGlobal(tw.AlignLeft)
-// // Or
-// table.Options(WithRowAlignmentConfig(tw.CellAlignment{Global: tw.AlignLeft}))
-//
-// Parameters:
-// - align: The [tw.Align] value to set for row cells. Valid values are
-// [tw.AlignLeft], [tw.AlignRight], [tw.AlignCenter], and [tw.AlignNone].
-// Invalid alignments are ignored.
-//
-// Returns:
-//
-// The [RowFormattingBuilder] instance for method chaining.
-func (rf *RowFormattingBuilder) WithAlignment(align tw.Align) *RowFormattingBuilder {
- if align != tw.AlignLeft && align != tw.AlignRight && align != tw.AlignCenter && align != tw.AlignNone {
- return rf
- }
- rf.config.Alignment = align
- return rf
-}
-
-// WithTableMax sets the maximum width of the entire table in characters.
-// Negative values are ignored, and the change is logged if debugging is enabled.
-// The width constrains the table's rendering, potentially causing text wrapping or truncation
-// based on the configuration's wrapping settings (e.g., tw.WrapTruncate).
-// If debug logging is enabled via WithDebug(true), the applied width is logged.
-//
-// Deprecated: Use WithMaxWidth instead, which provides the same functionality with a clearer name
-// and consistent naming across the package. For example:
-//
-// tablewriter.NewTable(os.Stdout, tablewriter.WithMaxWidth(80))
-func WithTableMax(width int) Option {
- return func(target *Table) {
- if width < 0 {
- return
- }
- target.config.MaxWidth = width
- if target.logger != nil {
- target.logger.Debugf("Option: WithTableMax applied to Table: %v", width)
- }
- }
-}
diff --git a/vendor/github.com/olekukonko/tablewriter/option.go b/vendor/github.com/olekukonko/tablewriter/option.go
deleted file mode 100644
index 2c1f3a2c15..0000000000
--- a/vendor/github.com/olekukonko/tablewriter/option.go
+++ /dev/null
@@ -1,937 +0,0 @@
-package tablewriter
-
-import (
- "reflect"
-
- "github.com/mattn/go-runewidth"
- "github.com/olekukonko/ll"
- "github.com/olekukonko/tablewriter/pkg/twwidth"
- "github.com/olekukonko/tablewriter/tw"
-)
-
-// Option defines a function type for configuring a Table instance.
-type Option func(target *Table)
-
-// WithAutoHide enables or disables automatic hiding of columns with empty data rows.
-// Logs the change if debugging is enabled.
-func WithAutoHide(state tw.State) Option {
- return func(target *Table) {
- target.config.Behavior.AutoHide = state
- if target.logger != nil {
- target.logger.Debugf("Option: WithAutoHide applied to Table: %v", state)
- }
- }
-}
-
-// WithColumnMax sets a global maximum column width for the table in streaming mode.
-// Negative values are ignored, and the change is logged if debugging is enabled.
-func WithColumnMax(width int) Option {
- return func(target *Table) {
- if width < 0 {
- return
- }
- target.config.Widths.Global = width
- if target.logger != nil {
- target.logger.Debugf("Option: WithColumnMax applied to Table: %v", width)
- }
- }
-}
-
-// WithMaxWidth sets a global maximum table width for the table.
-// Negative values are ignored, and the change is logged if debugging is enabled.
-func WithMaxWidth(width int) Option {
- return func(target *Table) {
- if width < 0 {
- return
- }
- target.config.MaxWidth = width
- if target.logger != nil {
- target.logger.Debugf("Option: WithTableMax applied to Table: %v", width)
- }
- }
-}
-
-// WithWidths sets per-column widths for the table.
-// Negative widths are removed, and the change is logged if debugging is enabled.
-func WithWidths(width tw.CellWidth) Option {
- return func(target *Table) {
- target.config.Widths = width
- if target.logger != nil {
- target.logger.Debugf("Option: WithColumnWidths applied to Table: %v", width)
- }
- }
-}
-
-// WithColumnWidths sets per-column widths for the table.
-// Negative widths are removed, and the change is logged if debugging is enabled.
-func WithColumnWidths(widths tw.Mapper[int, int]) Option {
- return func(target *Table) {
- for k, v := range widths {
- if v < 0 {
- delete(widths, k)
- }
- }
- target.config.Widths.PerColumn = widths
- if target.logger != nil {
- target.logger.Debugf("Option: WithColumnWidths applied to Table: %v", widths)
- }
- }
-}
-
-// WithConfig applies a custom configuration to the table by merging it with the default configuration.
-func WithConfig(cfg Config) Option {
- return func(target *Table) {
- target.config = mergeConfig(defaultConfig(), cfg)
- }
-}
-
-// WithDebug enables or disables debug logging and adjusts the logger level accordingly.
-// Logs the change if debugging is enabled.
-func WithDebug(debug bool) Option {
- return func(target *Table) {
- target.config.Debug = debug
- }
-}
-
-// WithFooter sets the table footers by calling the Footer method.
-func WithFooter(footers []string) Option {
- return func(target *Table) {
- target.Footer(footers)
- }
-}
-
-// WithFooterConfig applies a full footer configuration to the table.
-// Logs the change if debugging is enabled.
-func WithFooterConfig(config tw.CellConfig) Option {
- return func(target *Table) {
- target.config.Footer = config
- if target.logger != nil {
- target.logger.Debug("Option: WithFooterConfig applied to Table.")
- }
- }
-}
-
-// WithFooterAlignmentConfig applies a footer alignment configuration to the table.
-// Logs the change if debugging is enabled.
-func WithFooterAlignmentConfig(alignment tw.CellAlignment) Option {
- return func(target *Table) {
- target.config.Footer.Alignment = alignment
- if target.logger != nil {
- target.logger.Debugf("Option: WithFooterAlignmentConfig applied to Table: %+v", alignment)
- }
- }
-}
-
-// WithFooterMergeMode sets the merge mode for footer cells.
-// Invalid merge modes are ignored, and the change is logged if debugging is enabled.
-func WithFooterMergeMode(mergeMode int) Option {
- return func(target *Table) {
- if mergeMode < tw.MergeNone || mergeMode > tw.MergeHierarchical {
- return
- }
- target.config.Footer.Formatting.MergeMode = mergeMode
- if target.logger != nil {
- target.logger.Debugf("Option: WithFooterMergeMode applied to Table: %v", mergeMode)
- }
- }
-}
-
-// WithFooterAutoWrap sets the wrapping behavior for footer cells.
-// Invalid wrap modes are ignored, and the change is logged if debugging is enabled.
-func WithFooterAutoWrap(wrap int) Option {
- return func(target *Table) {
- if wrap < tw.WrapNone || wrap > tw.WrapBreak {
- return
- }
- target.config.Footer.Formatting.AutoWrap = wrap
- if target.logger != nil {
- target.logger.Debugf("Option: WithFooterAutoWrap applied to Table: %v", wrap)
- }
- }
-}
-
-// WithFooterFilter sets the filter configuration for footer cells.
-// Logs the change if debugging is enabled.
-func WithFooterFilter(filter tw.CellFilter) Option {
- return func(target *Table) {
- target.config.Footer.Filter = filter
- if target.logger != nil {
- target.logger.Debug("Option: WithFooterFilter applied to Table.")
- }
- }
-}
-
-// WithFooterCallbacks sets the callback configuration for footer cells.
-// Logs the change if debugging is enabled.
-func WithFooterCallbacks(callbacks tw.CellCallbacks) Option {
- return func(target *Table) {
- target.config.Footer.Callbacks = callbacks
- if target.logger != nil {
- target.logger.Debug("Option: WithFooterCallbacks applied to Table.")
- }
- }
-}
-
-// WithFooterPaddingPerColumn sets per-column padding for footer cells.
-// Logs the change if debugging is enabled.
-func WithFooterPaddingPerColumn(padding []tw.Padding) Option {
- return func(target *Table) {
- target.config.Footer.Padding.PerColumn = padding
- if target.logger != nil {
- target.logger.Debugf("Option: WithFooterPaddingPerColumn applied to Table: %+v", padding)
- }
- }
-}
-
-// WithFooterMaxWidth sets the maximum content width for footer cells.
-// Negative values are ignored, and the change is logged if debugging is enabled.
-func WithFooterMaxWidth(maxWidth int) Option {
- return func(target *Table) {
- if maxWidth < 0 {
- return
- }
- target.config.Footer.ColMaxWidths.Global = maxWidth
- if target.logger != nil {
- target.logger.Debugf("Option: WithFooterMaxWidth applied to Table: %v", maxWidth)
- }
- }
-}
-
-// WithHeader sets the table headers by calling the Header method.
-func WithHeader(headers []string) Option {
- return func(target *Table) {
- target.Header(headers)
- }
-}
-
-// WithHeaderAlignment sets the text alignment for header cells.
-// Invalid alignments are ignored, and the change is logged if debugging is enabled.
-func WithHeaderAlignment(align tw.Align) Option {
- return func(target *Table) {
- if align != tw.AlignLeft && align != tw.AlignRight && align != tw.AlignCenter && align != tw.AlignNone {
- return
- }
- target.config.Header.Alignment.Global = align
- if target.logger != nil {
- target.logger.Debugf("Option: WithHeaderAlignment applied to Table: %v", align)
- }
- }
-}
-
-// WithHeaderAutoWrap sets the wrapping behavior for header cells.
-// Invalid wrap modes are ignored, and the change is logged if debugging is enabled.
-func WithHeaderAutoWrap(wrap int) Option {
- return func(target *Table) {
- if wrap < tw.WrapNone || wrap > tw.WrapBreak {
- return
- }
- target.config.Header.Formatting.AutoWrap = wrap
- if target.logger != nil {
- target.logger.Debugf("Option: WithHeaderAutoWrap applied to Table: %v", wrap)
- }
- }
-}
-
-// WithHeaderMergeMode sets the merge mode for header cells.
-// Invalid merge modes are ignored, and the change is logged if debugging is enabled.
-func WithHeaderMergeMode(mergeMode int) Option {
- return func(target *Table) {
- if mergeMode < tw.MergeNone || mergeMode > tw.MergeHierarchical {
- return
- }
- target.config.Header.Formatting.MergeMode = mergeMode
- if target.logger != nil {
- target.logger.Debugf("Option: WithHeaderMergeMode applied to Table: %v", mergeMode)
- }
- }
-}
-
-// WithHeaderFilter sets the filter configuration for header cells.
-// Logs the change if debugging is enabled.
-func WithHeaderFilter(filter tw.CellFilter) Option {
- return func(target *Table) {
- target.config.Header.Filter = filter
- if target.logger != nil {
- target.logger.Debug("Option: WithHeaderFilter applied to Table.")
- }
- }
-}
-
-// WithHeaderCallbacks sets the callback configuration for header cells.
-// Logs the change if debugging is enabled.
-func WithHeaderCallbacks(callbacks tw.CellCallbacks) Option {
- return func(target *Table) {
- target.config.Header.Callbacks = callbacks
- if target.logger != nil {
- target.logger.Debug("Option: WithHeaderCallbacks applied to Table.")
- }
- }
-}
-
-// WithHeaderPaddingPerColumn sets per-column padding for header cells.
-// Logs the change if debugging is enabled.
-func WithHeaderPaddingPerColumn(padding []tw.Padding) Option {
- return func(target *Table) {
- target.config.Header.Padding.PerColumn = padding
- if target.logger != nil {
- target.logger.Debugf("Option: WithHeaderPaddingPerColumn applied to Table: %+v", padding)
- }
- }
-}
-
-// WithHeaderMaxWidth sets the maximum content width for header cells.
-// Negative values are ignored, and the change is logged if debugging is enabled.
-func WithHeaderMaxWidth(maxWidth int) Option {
- return func(target *Table) {
- if maxWidth < 0 {
- return
- }
- target.config.Header.ColMaxWidths.Global = maxWidth
- if target.logger != nil {
- target.logger.Debugf("Option: WithHeaderMaxWidth applied to Table: %v", maxWidth)
- }
- }
-}
-
-// WithRowAlignment sets the text alignment for row cells.
-// Invalid alignments are ignored, and the change is logged if debugging is enabled.
-func WithRowAlignment(align tw.Align) Option {
- return func(target *Table) {
- if err := align.Validate(); err != nil {
- return
- }
- target.config.Row.Alignment.Global = align
- if target.logger != nil {
- target.logger.Debugf("Option: WithRowAlignment applied to Table: %v", align)
- }
- }
-}
-
-// WithRowAutoWrap sets the wrapping behavior for row cells.
-// Invalid wrap modes are ignored, and the change is logged if debugging is enabled.
-func WithRowAutoWrap(wrap int) Option {
- return func(target *Table) {
- if wrap < tw.WrapNone || wrap > tw.WrapBreak {
- return
- }
- target.config.Row.Formatting.AutoWrap = wrap
- if target.logger != nil {
- target.logger.Debugf("Option: WithRowAutoWrap applied to Table: %v", wrap)
- }
- }
-}
-
-// WithRowMergeMode sets the merge mode for row cells.
-// Invalid merge modes are ignored, and the change is logged if debugging is enabled.
-func WithRowMergeMode(mergeMode int) Option {
- return func(target *Table) {
- if mergeMode < tw.MergeNone || mergeMode > tw.MergeHierarchical {
- return
- }
- target.config.Row.Formatting.MergeMode = mergeMode
- if target.logger != nil {
- target.logger.Debugf("Option: WithRowMergeMode applied to Table: %v", mergeMode)
- }
- }
-}
-
-// WithRowFilter sets the filter configuration for row cells.
-// Logs the change if debugging is enabled.
-func WithRowFilter(filter tw.CellFilter) Option {
- return func(target *Table) {
- target.config.Row.Filter = filter
- if target.logger != nil {
- target.logger.Debug("Option: WithRowFilter applied to Table.")
- }
- }
-}
-
-// WithRowCallbacks sets the callback configuration for row cells.
-// Logs the change if debugging is enabled.
-func WithRowCallbacks(callbacks tw.CellCallbacks) Option {
- return func(target *Table) {
- target.config.Row.Callbacks = callbacks
- if target.logger != nil {
- target.logger.Debug("Option: WithRowCallbacks applied to Table.")
- }
- }
-}
-
-// WithRowPaddingPerColumn sets per-column padding for row cells.
-// Logs the change if debugging is enabled.
-func WithRowPaddingPerColumn(padding []tw.Padding) Option {
- return func(target *Table) {
- target.config.Row.Padding.PerColumn = padding
- if target.logger != nil {
- target.logger.Debugf("Option: WithRowPaddingPerColumn applied to Table: %+v", padding)
- }
- }
-}
-
-// WithHeaderAlignmentConfig applies a header alignment configuration to the table.
-// Logs the change if debugging is enabled.
-func WithHeaderAlignmentConfig(alignment tw.CellAlignment) Option {
- return func(target *Table) {
- target.config.Header.Alignment = alignment
- if target.logger != nil {
- target.logger.Debugf("Option: WithHeaderAlignmentConfig applied to Table: %+v", alignment)
- }
- }
-}
-
-// WithHeaderConfig applies a full header configuration to the table.
-// Logs the change if debugging is enabled.
-func WithHeaderConfig(config tw.CellConfig) Option {
- return func(target *Table) {
- target.config.Header = config
- if target.logger != nil {
- target.logger.Debug("Option: WithHeaderConfig applied to Table.")
- }
- }
-}
-
-// WithLogger sets a custom logger for the table and updates the renderer if present.
-// Logs the change if debugging is enabled.
-func WithLogger(logger *ll.Logger) Option {
- return func(target *Table) {
- target.logger = logger
- if target.logger != nil {
- target.logger.Debug("Option: WithLogger applied to Table.")
- if target.renderer != nil {
- target.renderer.Logger(target.logger)
- }
- }
- }
-}
-
-// WithRenderer sets a custom renderer for the table and attaches the logger if present.
-// Logs the change if debugging is enabled.
-func WithRenderer(f tw.Renderer) Option {
- return func(target *Table) {
- target.renderer = f
- if target.logger != nil {
- target.logger.Debugf("Option: WithRenderer applied to Table: %T", f)
- f.Logger(target.logger)
- }
- }
-}
-
-// WithRowConfig applies a full row configuration to the table.
-// Logs the change if debugging is enabled.
-func WithRowConfig(config tw.CellConfig) Option {
- return func(target *Table) {
- target.config.Row = config
- if target.logger != nil {
- target.logger.Debug("Option: WithRowConfig applied to Table.")
- }
- }
-}
-
-// WithRowAlignmentConfig applies a row alignment configuration to the table.
-// Logs the change if debugging is enabled.
-func WithRowAlignmentConfig(alignment tw.CellAlignment) Option {
- return func(target *Table) {
- target.config.Row.Alignment = alignment
- if target.logger != nil {
- target.logger.Debugf("Option: WithRowAlignmentConfig applied to Table: %+v", alignment)
- }
- }
-}
-
-// WithRowMaxWidth sets the maximum content width for row cells.
-// Negative values are ignored, and the change is logged if debugging is enabled.
-func WithRowMaxWidth(maxWidth int) Option {
- return func(target *Table) {
- if maxWidth < 0 {
- return
- }
- target.config.Row.ColMaxWidths.Global = maxWidth
- if target.logger != nil {
- target.logger.Debugf("Option: WithRowMaxWidth applied to Table: %v", maxWidth)
- }
- }
-}
-
-// WithStreaming applies a streaming configuration to the table by merging it with the existing configuration.
-// Logs the change if debugging is enabled.
-func WithStreaming(c tw.StreamConfig) Option {
- return func(target *Table) {
- target.config.Stream = mergeStreamConfig(target.config.Stream, c)
- if target.logger != nil {
- target.logger.Debug("Option: WithStreaming applied to Table.")
- }
- }
-}
-
-// WithStringer sets a custom stringer function for converting row data and clears the stringer cache.
-// Logs the change if debugging is enabled.
-func WithStringer(stringer interface{}) Option {
- return func(t *Table) {
- t.stringer = stringer
- t.stringerCacheMu.Lock()
- t.stringerCache = make(map[reflect.Type]reflect.Value)
- t.stringerCacheMu.Unlock()
- if t.logger != nil {
- t.logger.Debug("Stringer updated, cache cleared")
- }
- }
-}
-
-// WithStringerCache enables caching for the stringer function.
-// Logs the change if debugging is enabled.
-func WithStringerCache() Option {
- return func(t *Table) {
- t.stringerCacheEnabled = true
- if t.logger != nil {
- t.logger.Debug("Option: WithStringerCache enabled")
- }
- }
-}
-
-// WithTrimSpace sets whether leading and trailing spaces are automatically trimmed.
-// Logs the change if debugging is enabled.
-func WithTrimSpace(state tw.State) Option {
- return func(target *Table) {
- target.config.Behavior.TrimSpace = state
- if target.logger != nil {
- target.logger.Debugf("Option: WithTrimSpace applied to Table: %v", state)
- }
- }
-}
-
-// WithTrimLine sets whether empty visual lines within a cell are trimmed.
-// Logs the change if debugging is enabled.
-func WithTrimLine(state tw.State) Option {
- return func(target *Table) {
- target.config.Behavior.TrimLine = state
- if target.logger != nil {
- target.logger.Debugf("Option: WithTrimLine applied to Table: %v", state)
- }
- }
-}
-
-// WithHeaderAutoFormat enables or disables automatic formatting for header cells.
-// Logs the change if debugging is enabled.
-func WithHeaderAutoFormat(state tw.State) Option {
- return func(target *Table) {
- target.config.Header.Formatting.AutoFormat = state
- if target.logger != nil {
- target.logger.Debugf("Option: WithHeaderAutoFormat applied to Table: %v", state)
- }
- }
-}
-
-// WithFooterAutoFormat enables or disables automatic formatting for footer cells.
-// Logs the change if debugging is enabled.
-func WithFooterAutoFormat(state tw.State) Option {
- return func(target *Table) {
- target.config.Footer.Formatting.AutoFormat = state
- if target.logger != nil {
- target.logger.Debugf("Option: WithFooterAutoFormat applied to Table: %v", state)
- }
- }
-}
-
-// WithRowAutoFormat enables or disables automatic formatting for row cells.
-// Logs the change if debugging is enabled.
-func WithRowAutoFormat(state tw.State) Option {
- return func(target *Table) {
- target.config.Row.Formatting.AutoFormat = state
- if target.logger != nil {
- target.logger.Debugf("Option: WithRowAutoFormat applied to Table: %v", state)
- }
- }
-}
-
-// WithHeaderControl sets the control behavior for the table header.
-// Logs the change if debugging is enabled.
-func WithHeaderControl(control tw.Control) Option {
- return func(target *Table) {
- target.config.Behavior.Header = control
- if target.logger != nil {
- target.logger.Debugf("Option: WithHeaderControl applied to Table: %v", control)
- }
- }
-}
-
-// WithFooterControl sets the control behavior for the table footer.
-// Logs the change if debugging is enabled.
-func WithFooterControl(control tw.Control) Option {
- return func(target *Table) {
- target.config.Behavior.Footer = control
- if target.logger != nil {
- target.logger.Debugf("Option: WithFooterControl applied to Table: %v", control)
- }
- }
-}
-
-// WithAlignment sets the default column alignment for the header, rows, and footer.
-// Logs the change if debugging is enabled.
-func WithAlignment(alignment tw.Alignment) Option {
- return func(target *Table) {
- target.config.Header.Alignment.PerColumn = alignment
- target.config.Row.Alignment.PerColumn = alignment
- target.config.Footer.Alignment.PerColumn = alignment
- if target.logger != nil {
- target.logger.Debugf("Option: WithAlignment applied to Table: %+v", alignment)
- }
- }
-}
-
-// WithBehavior applies a behavior configuration to the table.
-// Logs the change if debugging is enabled.
-func WithBehavior(behavior tw.Behavior) Option {
- return func(target *Table) {
- target.config.Behavior = behavior
- if target.logger != nil {
- target.logger.Debugf("Option: WithBehavior applied to Table: %+v", behavior)
- }
- }
-}
-
-// WithPadding sets the global padding for the header, rows, and footer.
-// Logs the change if debugging is enabled.
-func WithPadding(padding tw.Padding) Option {
- return func(target *Table) {
- target.config.Header.Padding.Global = padding
- target.config.Row.Padding.Global = padding
- target.config.Footer.Padding.Global = padding
- if target.logger != nil {
- target.logger.Debugf("Option: WithPadding applied to Table: %+v", padding)
- }
- }
-}
-
-// WithRendition allows updating the active renderer's rendition configuration
-// by merging the provided rendition.
-// If the renderer does not implement tw.Renditioning, a warning is logged.
-// Logs the change if debugging is enabled.
-func WithRendition(rendition tw.Rendition) Option {
- return func(target *Table) {
- if target.renderer == nil {
- if target.logger != nil {
- target.logger.Warn("Option: WithRendition: No renderer set on table.")
- }
- return
- }
- if ru, ok := target.renderer.(tw.Renditioning); ok {
- ru.Rendition(rendition)
- if target.logger != nil {
- target.logger.Debugf("Option: WithRendition: Applied to renderer via Renditioning.SetRendition(): %+v", rendition)
- }
- } else if target.logger != nil {
- target.logger.Warnf("Option: WithRendition: Current renderer type %T does not implement tw.Renditioning. Rendition may not be applied as expected.", target.renderer)
- }
- }
-}
-
-// WithEastAsian configures the global East Asian width calculation setting.
-// - enable=true: Enables East Asian width calculations. CJK and ambiguous characters
-// are typically measured as double width.
-// - enable=false: Disables East Asian width calculations. Characters are generally
-// measured as single width, subject to Unicode standards.
-//
-// This setting affects all subsequent display width calculations using the twdw package.
-func WithEastAsian(enable bool) Option {
- return func(target *Table) {
- twwidth.SetEastAsian(enable)
- }
-}
-
-// WithCondition provides a way to set a custom global runewidth.Condition
-// that will be used for all subsequent display width calculations by the twwidth (twdw) package.
-//
-// The runewidth.Condition object allows for more fine-grained control over how rune widths
-// are determined, beyond just toggling EastAsianWidth. This could include settings for
-// ambiguous width characters or other future properties of runewidth.Condition.
-func WithCondition(condition *runewidth.Condition) Option {
- return func(target *Table) {
- twwidth.SetCondition(condition)
- }
-}
-
-// WithSymbols sets the symbols used for drawing table borders and separators.
-// The symbols are applied to the table's renderer configuration, if a renderer is set.
-// If no renderer is set (target.renderer is nil), this option has no effect. .
-func WithSymbols(symbols tw.Symbols) Option {
- return func(target *Table) {
- if target.renderer != nil {
- cfg := target.renderer.Config()
- cfg.Symbols = symbols
-
- if ru, ok := target.renderer.(tw.Renditioning); ok {
- ru.Rendition(cfg)
- if target.logger != nil {
- target.logger.Debugf("Option: WithRendition: Applied to renderer via Renditioning.SetRendition(): %+v", cfg)
- }
- } else if target.logger != nil {
- target.logger.Warnf("Option: WithRendition: Current renderer type %T does not implement tw.Renditioning. Rendition may not be applied as expected.", target.renderer)
- }
- }
- }
-}
-
-// WithCounters enables line counting by wrapping the table's writer.
-// If a custom counter (that implements tw.Counter) is provided, it will be used.
-// If the provided counter is nil, a default tw.LineCounter will be used.
-// The final count can be retrieved via the table.Lines() method after Render() is called.
-func WithCounters(counters ...tw.Counter) Option {
- return func(target *Table) {
- // Iterate through the provided counters and add any non-nil ones.
- for _, c := range counters {
- if c != nil {
- target.counters = append(target.counters, c)
- }
- }
- }
-}
-
-// WithLineCounter enables the default line counter.
-// A new instance of tw.LineCounter is added to the table's list of counters.
-// The total count can be retrieved via the table.Lines() method after Render() is called.
-func WithLineCounter() Option {
- return func(target *Table) {
- // Important: Create a new instance so tables don't share counters.
- target.counters = append(target.counters, &tw.LineCounter{})
- }
-}
-
-// defaultConfig returns a default Config with sensible settings for headers, rows, footers, and behavior.
-func defaultConfig() Config {
- return Config{
- MaxWidth: 0,
- Header: tw.CellConfig{
- Formatting: tw.CellFormatting{
- AutoWrap: tw.WrapTruncate,
- AutoFormat: tw.On,
- MergeMode: tw.MergeNone,
- },
- Padding: tw.CellPadding{
- Global: tw.PaddingDefault,
- },
- Alignment: tw.CellAlignment{
- Global: tw.AlignCenter,
- PerColumn: []tw.Align{},
- },
- },
- Row: tw.CellConfig{
- Formatting: tw.CellFormatting{
- AutoWrap: tw.WrapNormal,
- AutoFormat: tw.Off,
- MergeMode: tw.MergeNone,
- },
- Padding: tw.CellPadding{
- Global: tw.PaddingDefault,
- },
- Alignment: tw.CellAlignment{
- Global: tw.AlignLeft,
- PerColumn: []tw.Align{},
- },
- },
- Footer: tw.CellConfig{
- Formatting: tw.CellFormatting{
- AutoWrap: tw.WrapNormal,
- AutoFormat: tw.Off,
- MergeMode: tw.MergeNone,
- },
- Padding: tw.CellPadding{
- Global: tw.PaddingDefault,
- },
- Alignment: tw.CellAlignment{
- Global: tw.AlignRight,
- PerColumn: []tw.Align{},
- },
- },
- Stream: tw.StreamConfig{
- Enable: false,
- StrictColumns: false,
- },
- Debug: false,
- Behavior: tw.Behavior{
- AutoHide: tw.Off,
- TrimSpace: tw.On,
- TrimLine: tw.On,
- Structs: tw.Struct{
- AutoHeader: tw.Off,
- Tags: []string{"json", "db"},
- },
- },
- }
-}
-
-// mergeCellConfig merges a source CellConfig into a destination CellConfig, prioritizing non-default source values.
-// It handles deep merging for complex fields like padding and callbacks.
-func mergeCellConfig(dst, src tw.CellConfig) tw.CellConfig {
- if src.Formatting.Alignment != tw.Empty {
- dst.Formatting.Alignment = src.Formatting.Alignment
- }
-
- if src.Formatting.AutoWrap != 0 {
- dst.Formatting.AutoWrap = src.Formatting.AutoWrap
- }
- if src.ColMaxWidths.Global != 0 {
- dst.ColMaxWidths.Global = src.ColMaxWidths.Global
- }
- if src.Formatting.MergeMode != 0 {
- dst.Formatting.MergeMode = src.Formatting.MergeMode
- }
-
- dst.Formatting.AutoFormat = src.Formatting.AutoFormat
-
- if src.Padding.Global.Paddable() {
- dst.Padding.Global = src.Padding.Global
- }
-
- if len(src.Padding.PerColumn) > 0 {
- if dst.Padding.PerColumn == nil {
- dst.Padding.PerColumn = make([]tw.Padding, len(src.Padding.PerColumn))
- } else if len(src.Padding.PerColumn) > len(dst.Padding.PerColumn) {
- dst.Padding.PerColumn = append(dst.Padding.PerColumn, make([]tw.Padding, len(src.Padding.PerColumn)-len(dst.Padding.PerColumn))...)
- }
- for i, pad := range src.Padding.PerColumn {
- if pad.Paddable() {
- dst.Padding.PerColumn[i] = pad
- }
- }
- }
- if src.Callbacks.Global != nil {
- dst.Callbacks.Global = src.Callbacks.Global
- }
- if len(src.Callbacks.PerColumn) > 0 {
- if dst.Callbacks.PerColumn == nil {
- dst.Callbacks.PerColumn = make([]func(), len(src.Callbacks.PerColumn))
- } else if len(src.Callbacks.PerColumn) > len(dst.Callbacks.PerColumn) {
- dst.Callbacks.PerColumn = append(dst.Callbacks.PerColumn, make([]func(), len(src.Callbacks.PerColumn)-len(dst.Callbacks.PerColumn))...)
- }
- for i, cb := range src.Callbacks.PerColumn {
- if cb != nil {
- dst.Callbacks.PerColumn[i] = cb
- }
- }
- }
- if src.Filter.Global != nil {
- dst.Filter.Global = src.Filter.Global
- }
- if len(src.Filter.PerColumn) > 0 {
- if dst.Filter.PerColumn == nil {
- dst.Filter.PerColumn = make([]func(string) string, len(src.Filter.PerColumn))
- } else if len(src.Filter.PerColumn) > len(dst.Filter.PerColumn) {
- dst.Filter.PerColumn = append(dst.Filter.PerColumn, make([]func(string) string, len(src.Filter.PerColumn)-len(dst.Filter.PerColumn))...)
- }
- for i, filter := range src.Filter.PerColumn {
- if filter != nil {
- dst.Filter.PerColumn[i] = filter
- }
- }
- }
-
- // Merge Alignment
- if src.Alignment.Global != tw.Empty {
- dst.Alignment.Global = src.Alignment.Global
- }
-
- if len(src.Alignment.PerColumn) > 0 {
- if dst.Alignment.PerColumn == nil {
- dst.Alignment.PerColumn = make([]tw.Align, len(src.Alignment.PerColumn))
- } else if len(src.Alignment.PerColumn) > len(dst.Alignment.PerColumn) {
- dst.Alignment.PerColumn = append(dst.Alignment.PerColumn, make([]tw.Align, len(src.Alignment.PerColumn)-len(dst.Alignment.PerColumn))...)
- }
- for i, align := range src.Alignment.PerColumn {
- if align != tw.Skip {
- dst.Alignment.PerColumn[i] = align
- }
- }
- }
-
- if len(src.ColumnAligns) > 0 {
- if dst.ColumnAligns == nil {
- dst.ColumnAligns = make([]tw.Align, len(src.ColumnAligns))
- } else if len(src.ColumnAligns) > len(dst.ColumnAligns) {
- dst.ColumnAligns = append(dst.ColumnAligns, make([]tw.Align, len(src.ColumnAligns)-len(dst.ColumnAligns))...)
- }
- for i, align := range src.ColumnAligns {
- if align != tw.Skip {
- dst.ColumnAligns[i] = align
- }
- }
- }
-
- if len(src.ColMaxWidths.PerColumn) > 0 {
- if dst.ColMaxWidths.PerColumn == nil {
- dst.ColMaxWidths.PerColumn = make(map[int]int)
- }
- for k, v := range src.ColMaxWidths.PerColumn {
- if v != 0 {
- dst.ColMaxWidths.PerColumn[k] = v
- }
- }
- }
- return dst
-}
-
-// mergeConfig merges a source Config into a destination Config, prioritizing non-default source values.
-// It performs deep merging for complex types like Header, Row, Footer, and Stream.
-func mergeConfig(dst, src Config) Config {
- if src.MaxWidth != 0 {
- dst.MaxWidth = src.MaxWidth
- }
-
- dst.Debug = src.Debug || dst.Debug
- dst.Behavior.AutoHide = src.Behavior.AutoHide
- dst.Behavior.TrimSpace = src.Behavior.TrimSpace
- dst.Behavior.Compact = src.Behavior.Compact
- dst.Behavior.Header = src.Behavior.Header
- dst.Behavior.Footer = src.Behavior.Footer
- dst.Behavior.Footer = src.Behavior.Footer
-
- dst.Behavior.Structs.AutoHeader = src.Behavior.Structs.AutoHeader
-
- // check lent of tags
- if len(src.Behavior.Structs.Tags) > 0 {
- dst.Behavior.Structs.Tags = src.Behavior.Structs.Tags
- }
-
- if src.Widths.Global != 0 {
- dst.Widths.Global = src.Widths.Global
- }
- if len(src.Widths.PerColumn) > 0 {
- if dst.Widths.PerColumn == nil {
- dst.Widths.PerColumn = make(map[int]int)
- }
- for k, v := range src.Widths.PerColumn {
- if v != 0 {
- dst.Widths.PerColumn[k] = v
- }
- }
- }
-
- dst.Header = mergeCellConfig(dst.Header, src.Header)
- dst.Row = mergeCellConfig(dst.Row, src.Row)
- dst.Footer = mergeCellConfig(dst.Footer, src.Footer)
- dst.Stream = mergeStreamConfig(dst.Stream, src.Stream)
-
- return dst
-}
-
-// mergeStreamConfig merges a source StreamConfig into a destination StreamConfig, prioritizing non-default source values.
-func mergeStreamConfig(dst, src tw.StreamConfig) tw.StreamConfig {
- if src.Enable {
- dst.Enable = true
- }
-
- dst.StrictColumns = src.StrictColumns
- return dst
-}
-
-// padLine pads a line to the specified column count by appending empty strings as needed.
-func padLine(line []string, numCols int) []string {
- if len(line) >= numCols {
- return line
- }
- padded := make([]string, numCols)
- copy(padded, line)
- for i := len(line); i < numCols; i++ {
- padded[i] = tw.Empty
- }
- return padded
-}
diff --git a/vendor/github.com/olekukonko/tablewriter/pkg/twwarp/wrap.go b/vendor/github.com/olekukonko/tablewriter/pkg/twwarp/wrap.go
deleted file mode 100644
index a577c1eb13..0000000000
--- a/vendor/github.com/olekukonko/tablewriter/pkg/twwarp/wrap.go
+++ /dev/null
@@ -1,237 +0,0 @@
-// Copyright 2014 Oleku Konko All rights reserved.
-// Use of this source code is governed by a MIT
-// license that can be found in the LICENSE file.
-
-// This module is a Table Writer API for the Go Programming Language.
-// The protocols were written in pure Go and works on windows and unix systems
-
-package twwarp
-
-import (
- "math"
- "strings"
- "unicode"
-
- "github.com/olekukonko/tablewriter/pkg/twwidth" // IMPORT YOUR NEW PACKAGE
- "github.com/rivo/uniseg"
- // "github.com/mattn/go-runewidth" // This can be removed if all direct uses are gone
-)
-
-const (
- nl = "\n"
- sp = " "
-)
-
-const defaultPenalty = 1e5
-
-func SplitWords(s string) []string {
- words := make([]string, 0, len(s)/5)
- var wordBegin int
- wordPending := false
- for i, c := range s {
- if unicode.IsSpace(c) {
- if wordPending {
- words = append(words, s[wordBegin:i])
- wordPending = false
- }
- continue
- }
- if !wordPending {
- wordBegin = i
- wordPending = true
- }
- }
- if wordPending {
- words = append(words, s[wordBegin:])
- }
- return words
-}
-
-// WrapString wraps s into a paragraph of lines of length lim, with minimal
-// raggedness.
-func WrapString(s string, lim int) ([]string, int) {
- if s == sp {
- return []string{sp}, lim
- }
- words := SplitWords(s)
- if len(words) == 0 {
- return []string{""}, lim
- }
- var lines []string
- max := 0
- for _, v := range words {
- // max = runewidth.StringWidth(v) // OLD
- max = twwidth.Width(v) // NEW: Use twdw.Width
- if max > lim {
- lim = max
- }
- }
- for _, line := range WrapWords(words, 1, lim, defaultPenalty) {
- lines = append(lines, strings.Join(line, sp))
- }
- return lines, lim
-}
-
-// WrapStringWithSpaces wraps a string into lines of a specified display width while preserving
-// leading and trailing spaces. It splits the input string into words, condenses internal multiple
-// spaces to a single space, and wraps the content to fit within the given width limit, measured
-// using Unicode-aware display width. The function is used in the logging library to format log
-// messages for consistent output. It returns the wrapped lines as a slice of strings and the
-// adjusted width limit, which may increase if a single word exceeds the input limit. Thread-safe
-// as it does not modify shared state.
-func WrapStringWithSpaces(s string, lim int) ([]string, int) {
- if len(s) == 0 {
- return []string{""}, lim
- }
- if strings.TrimSpace(s) == "" { // All spaces
- // if runewidth.StringWidth(s) <= lim { // OLD
- if twwidth.Width(s) <= lim { // NEW: Use twdw.Width
- // return []string{s}, runewidth.StringWidth(s) // OLD
- return []string{s}, twwidth.Width(s) // NEW: Use twdw.Width
- }
- // For very long all-space strings, "wrap" by truncating to the limit.
- if lim > 0 {
- substring, _ := stringToDisplayWidth(s, lim)
- return []string{substring}, lim
- }
- return []string{""}, lim
- }
-
- var leadingSpaces, trailingSpaces, coreContent string
- firstNonSpace := strings.IndexFunc(s, func(r rune) bool { return !unicode.IsSpace(r) })
- leadingSpaces = s[:firstNonSpace]
- lastNonSpace := strings.LastIndexFunc(s, func(r rune) bool { return !unicode.IsSpace(r) })
- trailingSpaces = s[lastNonSpace+1:]
- coreContent = s[firstNonSpace : lastNonSpace+1]
-
- if coreContent == "" {
- return []string{leadingSpaces + trailingSpaces}, lim
- }
-
- words := SplitWords(coreContent)
- if len(words) == 0 {
- return []string{leadingSpaces + trailingSpaces}, lim
- }
-
- var lines []string
- currentLim := lim
-
- maxCoreWordWidth := 0
- for _, v := range words {
- // w := runewidth.StringWidth(v) // OLD
- w := twwidth.Width(v) // NEW: Use twdw.Width
- if w > maxCoreWordWidth {
- maxCoreWordWidth = w
- }
- }
-
- if maxCoreWordWidth > currentLim {
- currentLim = maxCoreWordWidth
- }
-
- wrappedWordLines := WrapWords(words, 1, currentLim, defaultPenalty)
-
- for i, lineWords := range wrappedWordLines {
- joinedLine := strings.Join(lineWords, sp)
- finalLine := leadingSpaces + joinedLine
- if i == len(wrappedWordLines)-1 { // Last line
- finalLine += trailingSpaces
- }
- lines = append(lines, finalLine)
- }
- return lines, currentLim
-}
-
-// stringToDisplayWidth returns a substring of s that has a display width
-// as close as possible to, but not exceeding, targetWidth.
-// It returns the substring and its actual display width.
-func stringToDisplayWidth(s string, targetWidth int) (substring string, actualWidth int) {
- if targetWidth <= 0 {
- return "", 0
- }
-
- var currentWidth int
- var endIndex int // Tracks the byte index in the original string
-
- g := uniseg.NewGraphemes(s)
- for g.Next() {
- grapheme := g.Str()
- // graphemeWidth := runewidth.StringWidth(grapheme) // OLD
- graphemeWidth := twwidth.Width(grapheme) // NEW: Use twdw.Width
-
- if currentWidth+graphemeWidth > targetWidth {
- break
- }
-
- currentWidth += graphemeWidth
- _, e := g.Positions()
- endIndex = e
- }
- return s[:endIndex], currentWidth
-}
-
-// WrapWords is the low-level line-breaking algorithm, useful if you need more
-// control over the details of the text wrapping process. For most uses,
-// WrapString will be sufficient and more convenient.
-//
-// WrapWords splits a list of words into lines with minimal "raggedness",
-// treating each rune as one unit, accounting for spc units between adjacent
-// words on each line, and attempting to limit lines to lim units. Raggedness
-// is the total error over all lines, where error is the square of the
-// difference of the length of the line and lim. Too-long lines (which only
-// happen when a single word is longer than lim units) have pen penalty units
-// added to the error.
-func WrapWords(words []string, spc, lim, pen int) [][]string {
- n := len(words)
- if n == 0 {
- return nil
- }
- lengths := make([]int, n)
- for i := 0; i < n; i++ {
- // lengths[i] = runewidth.StringWidth(words[i]) // OLD
- lengths[i] = twwidth.Width(words[i]) // NEW: Use twdw.Width
- }
- nbrk := make([]int, n)
- cost := make([]int, n)
- for i := range cost {
- cost[i] = math.MaxInt32
- }
- remainderLen := lengths[n-1] // Uses updated lengths
- for i := n - 1; i >= 0; i-- {
- if i < n-1 {
- remainderLen += spc + lengths[i]
- }
- if remainderLen <= lim {
- cost[i] = 0
- nbrk[i] = n
- continue
- }
- phraseLen := lengths[i]
- for j := i + 1; j < n; j++ {
- if j > i+1 {
- phraseLen += spc + lengths[j-1]
- }
- d := lim - phraseLen
- c := d*d + cost[j]
- if phraseLen > lim {
- c += pen // too-long lines get a worse penalty
- }
- if c < cost[i] {
- cost[i] = c
- nbrk[i] = j
- }
- }
- }
- var lines [][]string
- i := 0
- for i < n {
- lines = append(lines, words[i:nbrk[i]])
- i = nbrk[i]
- }
- return lines
-}
-
-// getLines decomposes a multiline string into a slice of strings.
-func getLines(s string) []string {
- return strings.Split(s, nl)
-}
diff --git a/vendor/github.com/olekukonko/tablewriter/pkg/twwidth/width.go b/vendor/github.com/olekukonko/tablewriter/pkg/twwidth/width.go
deleted file mode 100644
index 3b9634b026..0000000000
--- a/vendor/github.com/olekukonko/tablewriter/pkg/twwidth/width.go
+++ /dev/null
@@ -1,323 +0,0 @@
-package twwidth
-
-import (
- "bytes"
- "regexp"
- "strings"
- "sync"
-
- "github.com/mattn/go-runewidth"
-)
-
-// condition holds the global runewidth configuration, including East Asian width settings.
-var condition *runewidth.Condition
-
-// mu protects access to condition and widthCache for thread safety.
-var mu sync.Mutex
-
-// ansi is a compiled regular expression for stripping ANSI escape codes from strings.
-var ansi = Filter()
-
-func init() {
- condition = runewidth.NewCondition()
- widthCache = make(map[cacheKey]int)
-}
-
-// cacheKey is used as a key for memoizing string width results in widthCache.
-type cacheKey struct {
- str string // Input string
- eastAsianWidth bool // East Asian width setting
-}
-
-// widthCache stores memoized results of Width calculations to improve performance.
-var widthCache map[cacheKey]int
-
-// Filter compiles and returns a regular expression for matching ANSI escape sequences,
-// including CSI (Control Sequence Introducer) and OSC (Operating System Command) sequences.
-// The returned regex can be used to strip ANSI codes from strings.
-func Filter() *regexp.Regexp {
- regESC := "\x1b" // ASCII escape character
- regBEL := "\x07" // ASCII bell character
-
- // ANSI string terminator: either ESC+\ or BEL
- regST := "(" + regexp.QuoteMeta(regESC+"\\") + "|" + regexp.QuoteMeta(regBEL) + ")"
- // Control Sequence Introducer (CSI): ESC[ followed by parameters and a final byte
- regCSI := regexp.QuoteMeta(regESC+"[") + "[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e]"
- // Operating System Command (OSC): ESC] followed by arbitrary content until a terminator
- regOSC := regexp.QuoteMeta(regESC+"]") + ".*?" + regST
-
- // Combine CSI and OSC patterns into a single regex
- return regexp.MustCompile("(" + regCSI + "|" + regOSC + ")")
-}
-
-// SetEastAsian enables or disables East Asian width handling for width calculations.
-// When the setting changes, the width cache is cleared to ensure accuracy.
-// This function is thread-safe.
-//
-// Example:
-//
-// twdw.SetEastAsian(true) // Enable East Asian width handling
-func SetEastAsian(enable bool) {
- mu.Lock()
- defer mu.Unlock()
- if condition.EastAsianWidth != enable {
- condition.EastAsianWidth = enable
- widthCache = make(map[cacheKey]int) // Clear cache on setting change
- }
-}
-
-// SetCondition updates the global runewidth.Condition used for width calculations.
-// When the condition is changed, the width cache is cleared.
-// This function is thread-safe.
-//
-// Example:
-//
-// newCond := runewidth.NewCondition()
-// newCond.EastAsianWidth = true
-// twdw.SetCondition(newCond)
-func SetCondition(newCond *runewidth.Condition) {
- mu.Lock()
- defer mu.Unlock()
- condition = newCond
- widthCache = make(map[cacheKey]int) // Clear cache on setting change
-}
-
-// Width calculates the visual width of a string, excluding ANSI escape sequences,
-// using the go-runewidth package for accurate Unicode handling. It accounts for the
-// current East Asian width setting and caches results for performance.
-// This function is thread-safe.
-//
-// Example:
-//
-// width := twdw.Width("Hello\x1b[31mWorld") // Returns 10
-func Width(str string) int {
- mu.Lock()
- key := cacheKey{str: str, eastAsianWidth: condition.EastAsianWidth}
- if w, found := widthCache[key]; found {
- mu.Unlock()
- return w
- }
- mu.Unlock()
-
- // Use a temporary condition to avoid holding the lock during calculation
- tempCond := runewidth.NewCondition()
- tempCond.EastAsianWidth = key.eastAsianWidth
-
- stripped := ansi.ReplaceAllLiteralString(str, "")
- calculatedWidth := tempCond.StringWidth(stripped)
-
- mu.Lock()
- widthCache[key] = calculatedWidth
- mu.Unlock()
-
- return calculatedWidth
-}
-
-// WidthNoCache calculates the visual width of a string without using or
-// updating the global cache. It uses the current global East Asian width setting.
-// This function is intended for internal use (e.g., benchmarking) and is thread-safe.
-//
-// Example:
-//
-// width := twdw.WidthNoCache("Hello\x1b[31mWorld") // Returns 10
-func WidthNoCache(str string) int {
- mu.Lock()
- currentEA := condition.EastAsianWidth
- mu.Unlock()
-
- tempCond := runewidth.NewCondition()
- tempCond.EastAsianWidth = currentEA
-
- stripped := ansi.ReplaceAllLiteralString(str, "")
- return tempCond.StringWidth(stripped)
-}
-
-// Display calculates the visual width of a string, excluding ANSI escape sequences,
-// using the provided runewidth condition. Unlike Width, it does not use caching
-// and is intended for cases where a specific condition is required.
-// This function is thread-safe with respect to the provided condition.
-//
-// Example:
-//
-// cond := runewidth.NewCondition()
-// width := twdw.Display(cond, "Hello\x1b[31mWorld") // Returns 10
-func Display(cond *runewidth.Condition, str string) int {
- return cond.StringWidth(ansi.ReplaceAllLiteralString(str, ""))
-}
-
-// Truncate shortens a string to fit within a specified visual width, optionally
-// appending a suffix (e.g., "..."). It preserves ANSI escape sequences and adds
-// a reset sequence (\x1b[0m) if needed to prevent formatting bleed. The function
-// respects the global East Asian width setting and is thread-safe.
-//
-// If maxWidth is negative, an empty string is returned. If maxWidth is zero and
-// a suffix is provided, the suffix is returned. If the string's visual width is
-// less than or equal to maxWidth, the string (and suffix, if provided and fits)
-// is returned unchanged.
-//
-// Example:
-//
-// s := twdw.Truncate("Hello\x1b[31mWorld", 5, "...") // Returns "Hello..."
-// s = twdw.Truncate("Hello", 10) // Returns "Hello"
-func Truncate(s string, maxWidth int, suffix ...string) string {
- if maxWidth < 0 {
- return ""
- }
-
- suffixStr := strings.Join(suffix, "")
- sDisplayWidth := Width(s) // Uses global cached Width
- suffixDisplayWidth := Width(suffixStr) // Uses global cached Width
-
- // Case 1: Original string is visually empty.
- if sDisplayWidth == 0 {
- // If suffix is provided and fits within maxWidth (or if maxWidth is generous)
- if len(suffixStr) > 0 && suffixDisplayWidth <= maxWidth {
- return suffixStr
- }
- // If s has ANSI codes (len(s)>0) but maxWidth is 0, can't display them.
- if maxWidth == 0 && len(s) > 0 {
- return ""
- }
- return s // Returns "" or original ANSI codes
- }
-
- // Case 2: maxWidth is 0, but string has content. Cannot display anything.
- if maxWidth == 0 {
- return ""
- }
-
- // Case 3: String fits completely or fits with suffix.
- // Here, maxWidth is the total budget for the line.
- if sDisplayWidth <= maxWidth {
- if len(suffixStr) == 0 { // No suffix.
- return s
- }
- // Suffix is provided. Check if s + suffix fits.
- if sDisplayWidth+suffixDisplayWidth <= maxWidth {
- return s + suffixStr
- }
- // s fits, but s + suffix is too long. Return s.
- return s
- }
-
- // Case 4: String needs truncation (sDisplayWidth > maxWidth).
- // maxWidth is the total budget for the final string (content + suffix).
-
- // Capture the global EastAsianWidth setting once for consistent use
- mu.Lock()
- currentGlobalEastAsianWidth := condition.EastAsianWidth
- mu.Unlock()
-
- // Special case for EastAsian true: if only suffix fits, return suffix.
- // This was derived from previous test behavior.
- if len(suffixStr) > 0 && currentGlobalEastAsianWidth {
- provisionalContentWidth := maxWidth - suffixDisplayWidth
- if provisionalContentWidth == 0 { // Exactly enough space for suffix only
- return suffixStr // <<<< MODIFIED: No ANSI reset here
- }
- }
-
- // Calculate the budget for the content part, reserving space for the suffix.
- targetContentForIteration := maxWidth
- if len(suffixStr) > 0 {
- targetContentForIteration -= suffixDisplayWidth
- }
-
- // If content budget is negative, means not even suffix fits (or no suffix and no space).
- // However, if only suffix fits, it should be handled.
- if targetContentForIteration < 0 {
- // Can we still fit just the suffix?
- if len(suffixStr) > 0 && suffixDisplayWidth <= maxWidth {
- if strings.Contains(s, "\x1b[") {
- return "\x1b[0m" + suffixStr
- }
- return suffixStr
- }
- return "" // Cannot fit anything.
- }
- // If targetContentForIteration is 0, loop won't run, result will be empty string, then suffix is added.
-
- var contentBuf bytes.Buffer
- var currentContentDisplayWidth int
- var ansiSeqBuf bytes.Buffer
- inAnsiSequence := false
- ansiWrittenToContent := false
-
- localRunewidthCond := runewidth.NewCondition()
- localRunewidthCond.EastAsianWidth = currentGlobalEastAsianWidth
-
- for _, r := range s {
- if r == '\x1b' {
- inAnsiSequence = true
- ansiSeqBuf.Reset()
- ansiSeqBuf.WriteRune(r)
- } else if inAnsiSequence {
- ansiSeqBuf.WriteRune(r)
- seqBytes := ansiSeqBuf.Bytes()
- seqLen := len(seqBytes)
- terminated := false
- if seqLen >= 2 {
- introducer := seqBytes[1]
- switch introducer {
- case '[':
- if seqLen >= 3 && r >= 0x40 && r <= 0x7E {
- terminated = true
- }
- case ']':
- if r == '\x07' {
- terminated = true
- } else if seqLen > 1 && seqBytes[seqLen-2] == '\x1b' && r == '\\' { // Check for ST: \x1b\
- terminated = true
- }
- }
- }
- if terminated {
- inAnsiSequence = false
- contentBuf.Write(ansiSeqBuf.Bytes())
- ansiWrittenToContent = true
- ansiSeqBuf.Reset()
- }
- } else { // Normal character
- runeDisplayWidth := localRunewidthCond.RuneWidth(r)
- if targetContentForIteration == 0 { // No budget for content at all
- break
- }
- if currentContentDisplayWidth+runeDisplayWidth > targetContentForIteration {
- break
- }
- contentBuf.WriteRune(r)
- currentContentDisplayWidth += runeDisplayWidth
- }
- }
-
- result := contentBuf.String()
-
- // Suffix is added if:
- // 1. A suffix string is provided.
- // 2. Truncation actually happened (sDisplayWidth > maxWidth originally)
- // OR if the content part is empty but a suffix is meant to be shown
- // (e.g. targetContentForIteration was 0).
- if len(suffixStr) > 0 {
- // Add suffix if we are in the truncation path (sDisplayWidth > maxWidth)
- // OR if targetContentForIteration was 0 (meaning only suffix should be shown)
- // but we must ensure we don't exceed original maxWidth.
- // The logic above for targetContentForIteration already ensures space.
-
- needsReset := false
- // Condition for reset: if styling was active in 's' and might affect suffix
- if (ansiWrittenToContent || (inAnsiSequence && strings.Contains(s, "\x1b["))) && (currentContentDisplayWidth > 0 || ansiWrittenToContent) {
- if !strings.HasSuffix(result, "\x1b[0m") {
- needsReset = true
- }
- } else if currentContentDisplayWidth > 0 && strings.Contains(result, "\x1b[") && !strings.HasSuffix(result, "\x1b[0m") && strings.Contains(s, "\x1b[") {
- // If result has content and ANSI, and original had ANSI, and result not already reset
- needsReset = true
- }
-
- if needsReset {
- result += "\x1b[0m"
- }
- result += suffixStr
- }
- return result
-}
diff --git a/vendor/github.com/olekukonko/tablewriter/renderer/blueprint.go b/vendor/github.com/olekukonko/tablewriter/renderer/blueprint.go
deleted file mode 100644
index 48638fb23e..0000000000
--- a/vendor/github.com/olekukonko/tablewriter/renderer/blueprint.go
+++ /dev/null
@@ -1,588 +0,0 @@
-package renderer
-
-import (
- "io"
- "strings"
-
- "github.com/olekukonko/ll"
- "github.com/olekukonko/tablewriter/pkg/twwidth"
-
- "github.com/olekukonko/tablewriter/tw"
-)
-
-// Blueprint implements a primary table rendering engine with customizable borders and alignments.
-type Blueprint struct {
- config tw.Rendition // Rendering configuration for table borders and symbols
- logger *ll.Logger // Logger for debug trace messages
- w io.Writer
-}
-
-// NewBlueprint creates a new Blueprint instance with optional custom configurations.
-func NewBlueprint(configs ...tw.Rendition) *Blueprint {
- // Initialize with default configuration
- cfg := defaultBlueprint()
- if len(configs) > 0 {
- userCfg := configs[0]
- // Override default borders if provided
- if userCfg.Borders.Left != 0 {
- cfg.Borders.Left = userCfg.Borders.Left
- }
- if userCfg.Borders.Right != 0 {
- cfg.Borders.Right = userCfg.Borders.Right
- }
- if userCfg.Borders.Top != 0 {
- cfg.Borders.Top = userCfg.Borders.Top
- }
- if userCfg.Borders.Bottom != 0 {
- cfg.Borders.Bottom = userCfg.Borders.Bottom
- }
- // Override symbols if provided
- if userCfg.Symbols != nil {
- cfg.Symbols = userCfg.Symbols
- }
-
- // Merge user settings with default settings
- cfg.Settings = mergeSettings(cfg.Settings, userCfg.Settings)
- }
- return &Blueprint{config: cfg, logger: ll.New("blueprint")}
-}
-
-// Close performs cleanup (no-op in this implementation).
-func (f *Blueprint) Close() error {
- f.logger.Debug("Blueprint.Close() called (no-op).")
- return nil
-}
-
-// Config returns the renderer's current configuration.
-func (f *Blueprint) Config() tw.Rendition {
- return f.config
-}
-
-// Footer renders the table footer section with configured formatting.
-func (f *Blueprint) Footer(footers [][]string, ctx tw.Formatting) {
- f.logger.Debugf("Starting Footer render: IsSubRow=%v, Location=%v, Pos=%s", ctx.IsSubRow, ctx.Row.Location, ctx.Row.Position)
- // Render the footer line
- f.renderLine(ctx)
- f.logger.Debug("Completed Footer render")
-}
-
-// Header renders the table header section with configured formatting.
-func (f *Blueprint) Header(headers [][]string, ctx tw.Formatting) {
- f.logger.Debugf("Starting Header render: IsSubRow=%v, Location=%v, Pos=%s, lines=%d, widths=%v",
- ctx.IsSubRow, ctx.Row.Location, ctx.Row.Position, len(ctx.Row.Current), ctx.Row.Widths)
- // Render the header line
- f.renderLine(ctx)
- f.logger.Debug("Completed Header render")
-}
-
-// Line renders a full horizontal row line with junctions and segments.
-func (f *Blueprint) Line(ctx tw.Formatting) {
- // Initialize junction renderer
- jr := NewJunction(JunctionContext{
- Symbols: f.config.Symbols,
- Ctx: ctx,
- ColIdx: 0,
- Logger: f.logger,
- BorderTint: Tint{},
- SeparatorTint: Tint{},
- })
-
- var line strings.Builder
- totalLineWidth := 0 // Track total display width
- // Get sorted column indices
- sortedKeys := ctx.Row.Widths.SortedKeys()
- numCols := 0
- if len(sortedKeys) > 0 {
- numCols = sortedKeys[len(sortedKeys)-1] + 1
- }
-
- // Handle empty row case
- if numCols == 0 {
- prefix := tw.Empty
- suffix := tw.Empty
- if f.config.Borders.Left.Enabled() {
- prefix = jr.RenderLeft()
- }
- if f.config.Borders.Right.Enabled() {
- suffix = jr.RenderRight(-1)
- }
- if prefix != tw.Empty || suffix != tw.Empty {
- line.WriteString(prefix + suffix + tw.NewLine)
- totalLineWidth = twwidth.Width(prefix) + twwidth.Width(suffix)
- f.w.Write([]byte(line.String()))
- }
- f.logger.Debugf("Line: Handled empty row/widths case (total width %d)", totalLineWidth)
- return
- }
-
- // Calculate target total width based on data rows
- targetTotalWidth := 0
- for _, colIdx := range sortedKeys {
- targetTotalWidth += ctx.Row.Widths.Get(colIdx)
- }
- if f.config.Borders.Left.Enabled() {
- targetTotalWidth += twwidth.Width(f.config.Symbols.Column())
- }
- if f.config.Borders.Right.Enabled() {
- targetTotalWidth += twwidth.Width(f.config.Symbols.Column())
- }
- if f.config.Settings.Separators.BetweenColumns.Enabled() && len(sortedKeys) > 1 {
- targetTotalWidth += twwidth.Width(f.config.Symbols.Column()) * (len(sortedKeys) - 1)
- }
-
- // Add left border if enabled
- leftBorderWidth := 0
- if f.config.Borders.Left.Enabled() {
- leftBorder := jr.RenderLeft()
- line.WriteString(leftBorder)
- leftBorderWidth = twwidth.Width(leftBorder)
- totalLineWidth += leftBorderWidth
- f.logger.Debugf("Line: Left border='%s' (f.width %d)", leftBorder, leftBorderWidth)
- }
-
- visibleColIndices := make([]int, 0)
- // Calculate visible columns
- for _, colIdx := range sortedKeys {
- colWidth := ctx.Row.Widths.Get(colIdx)
- if colWidth > 0 {
- visibleColIndices = append(visibleColIndices, colIdx)
- }
- }
-
- f.logger.Debugf("Line: sortedKeys=%v, Widths=%v, visibleColIndices=%v, targetTotalWidth=%d", sortedKeys, ctx.Row.Widths, visibleColIndices, targetTotalWidth)
- // Render each column segment
- for keyIndex, currentColIdx := range visibleColIndices {
- jr.colIdx = currentColIdx
- segment := jr.GetSegment()
- colWidth := ctx.Row.Widths.Get(currentColIdx)
- // Adjust colWidth to account for wider borders
- adjustedColWidth := colWidth
- if f.config.Borders.Left.Enabled() && keyIndex == 0 {
- adjustedColWidth -= leftBorderWidth - twwidth.Width(f.config.Symbols.Column())
- }
- if f.config.Borders.Right.Enabled() && keyIndex == len(visibleColIndices)-1 {
- rightBorderWidth := twwidth.Width(jr.RenderRight(currentColIdx))
- adjustedColWidth -= rightBorderWidth - twwidth.Width(f.config.Symbols.Column())
- }
- if adjustedColWidth < 0 {
- adjustedColWidth = 0
- }
- f.logger.Debugf("Line: colIdx=%d, segment='%s', adjusted colWidth=%d", currentColIdx, segment, adjustedColWidth)
- if segment == tw.Empty {
- spaces := strings.Repeat(tw.Space, adjustedColWidth)
- line.WriteString(spaces)
- totalLineWidth += adjustedColWidth
- f.logger.Debugf("Line: Rendered spaces='%s' (f.width %d) for col %d", spaces, adjustedColWidth, currentColIdx)
- } else {
- segmentWidth := twwidth.Width(segment)
- if segmentWidth == 0 {
- segmentWidth = 1 // Avoid division by zero
- f.logger.Warnf("Line: Segment='%s' has zero width, using 1", segment)
- }
- // Calculate how many full segments fit
- repeat := adjustedColWidth / segmentWidth
- if repeat < 1 && adjustedColWidth > 0 {
- repeat = 1
- }
- repeatedSegment := strings.Repeat(segment, repeat)
- actualWidth := twwidth.Width(repeatedSegment)
- if actualWidth > adjustedColWidth {
- // Truncate if too long
- repeatedSegment = twwidth.Truncate(repeatedSegment, adjustedColWidth)
- actualWidth = twwidth.Width(repeatedSegment)
- f.logger.Debugf("Line: Truncated segment='%s' to width %d", repeatedSegment, actualWidth)
- } else if actualWidth < adjustedColWidth {
- // Pad with segment character to match adjustedColWidth
- remainingWidth := adjustedColWidth - actualWidth
- for i := 0; i < remainingWidth/segmentWidth; i++ {
- repeatedSegment += segment
- }
- actualWidth = twwidth.Width(repeatedSegment)
- if actualWidth < adjustedColWidth {
- repeatedSegment = tw.PadRight(repeatedSegment, tw.Space, adjustedColWidth)
- actualWidth = adjustedColWidth
- f.logger.Debugf("Line: Padded segment with spaces='%s' to width %d", repeatedSegment, actualWidth)
- }
- f.logger.Debugf("Line: Padded segment='%s' to width %d", repeatedSegment, actualWidth)
- }
- line.WriteString(repeatedSegment)
- totalLineWidth += actualWidth
- f.logger.Debugf("Line: Rendered segment='%s' (f.width %d) for col %d", repeatedSegment, actualWidth, currentColIdx)
- }
-
- // Add junction between columns if not the last column
- isLast := keyIndex == len(visibleColIndices)-1
- if !isLast && f.config.Settings.Separators.BetweenColumns.Enabled() {
- nextColIdx := visibleColIndices[keyIndex+1]
- junction := jr.RenderJunction(currentColIdx, nextColIdx)
- // Use center symbol (❀) or column separator (|) to match data rows
- if twwidth.Width(junction) != twwidth.Width(f.config.Symbols.Column()) {
- junction = f.config.Symbols.Center()
- if twwidth.Width(junction) != twwidth.Width(f.config.Symbols.Column()) {
- junction = f.config.Symbols.Column()
- }
- }
- junctionWidth := twwidth.Width(junction)
- line.WriteString(junction)
- totalLineWidth += junctionWidth
- f.logger.Debugf("Line: Junction between %d and %d: '%s' (f.width %d)", currentColIdx, nextColIdx, junction, junctionWidth)
- }
- }
-
- // Add right border
- rightBorderWidth := 0
- if f.config.Borders.Right.Enabled() && len(visibleColIndices) > 0 {
- lastIdx := visibleColIndices[len(visibleColIndices)-1]
- rightBorder := jr.RenderRight(lastIdx)
- rightBorderWidth = twwidth.Width(rightBorder)
- line.WriteString(rightBorder)
- totalLineWidth += rightBorderWidth
- f.logger.Debugf("Line: Right border='%s' (f.width %d)", rightBorder, rightBorderWidth)
- }
-
- // Write the final line
- line.WriteString(tw.NewLine)
- f.w.Write([]byte(line.String()))
- f.logger.Debugf("Line rendered: '%s' (total width %d, target %d)", strings.TrimSuffix(line.String(), tw.NewLine), totalLineWidth, targetTotalWidth)
-}
-
-// Logger sets the logger for the Blueprint instance.
-func (f *Blueprint) Logger(logger *ll.Logger) {
- f.logger = logger.Namespace("blueprint")
-}
-
-// Row renders a table data row with configured formatting.
-func (f *Blueprint) Row(row []string, ctx tw.Formatting) {
- f.logger.Debugf("Starting Row render: IsSubRow=%v, Location=%v, Pos=%s, hasFooter=%v",
- ctx.IsSubRow, ctx.Row.Location, ctx.Row.Position, ctx.HasFooter)
-
- // Render the row line
- f.renderLine(ctx)
- f.logger.Debug("Completed Row render")
-}
-
-// Start initializes the rendering process (no-op in this implementation).
-func (f *Blueprint) Start(w io.Writer) error {
- f.w = w
- f.logger.Debug("Blueprint.Start() called (no-op).")
- return nil
-}
-
-// formatCell formats a cell's content with specified width, padding, and alignment, returning an empty string if width is non-positive.
-func (f *Blueprint) formatCell(content string, width int, padding tw.Padding, align tw.Align) string {
- if width <= 0 {
- return tw.Empty
- }
-
- f.logger.Debugf("Formatting cell: content='%s', width=%d, align=%s, padding={L:'%s' R:'%s'}",
- content, width, align, padding.Left, padding.Right)
-
- // Calculate display width of content
- runeWidth := twwidth.Width(content)
-
- // Set default padding characters
- leftPadChar := padding.Left
- rightPadChar := padding.Right
-
- // if f.config.Settings.Cushion.Enabled() || f.config.Settings.Cushion.Default() {
- // if leftPadChar == tw.Empty {
- // leftPadChar = tw.Space
- // }
- // if rightPadChar == tw.Empty {
- // rightPadChar = tw.Space
- // }
- //}
-
- // Calculate padding widths
- padLeftWidth := twwidth.Width(leftPadChar)
- padRightWidth := twwidth.Width(rightPadChar)
-
- // Calculate available width for content
- availableContentWidth := max(width-padLeftWidth-padRightWidth, 0)
- f.logger.Debugf("Available content width: %d", availableContentWidth)
-
- // Truncate content if it exceeds available width
- if runeWidth > availableContentWidth {
- content = twwidth.Truncate(content, availableContentWidth)
- runeWidth = twwidth.Width(content)
- f.logger.Debugf("Truncated content to fit %d: '%s' (new width %d)", availableContentWidth, content, runeWidth)
- }
-
- // Calculate total padding needed
- totalPaddingWidth := max(width-runeWidth, 0)
- f.logger.Debugf("Total padding width: %d", totalPaddingWidth)
-
- var result strings.Builder
- var leftPaddingWidth, rightPaddingWidth int
-
- // Apply alignment and padding
- switch align {
- case tw.AlignLeft:
- result.WriteString(leftPadChar)
- result.WriteString(content)
- rightPaddingWidth = totalPaddingWidth - padLeftWidth
- if rightPaddingWidth > 0 {
- result.WriteString(tw.PadRight(tw.Empty, rightPadChar, rightPaddingWidth))
- f.logger.Debugf("Applied right padding: '%s' for %d width", rightPadChar, rightPaddingWidth)
- }
- case tw.AlignRight:
- leftPaddingWidth = totalPaddingWidth - padRightWidth
- if leftPaddingWidth > 0 {
- result.WriteString(tw.PadLeft(tw.Empty, leftPadChar, leftPaddingWidth))
- f.logger.Debugf("Applied left padding: '%s' for %d width", leftPadChar, leftPaddingWidth)
- }
- result.WriteString(content)
- result.WriteString(rightPadChar)
- case tw.AlignCenter:
- leftPaddingWidth = (totalPaddingWidth-padLeftWidth-padRightWidth)/2 + padLeftWidth
- rightPaddingWidth = totalPaddingWidth - leftPaddingWidth
- if leftPaddingWidth > padLeftWidth {
- result.WriteString(tw.PadLeft(tw.Empty, leftPadChar, leftPaddingWidth-padLeftWidth))
- f.logger.Debugf("Applied left centering padding: '%s' for %d width", leftPadChar, leftPaddingWidth-padLeftWidth)
- }
- result.WriteString(leftPadChar)
- result.WriteString(content)
- result.WriteString(rightPadChar)
- if rightPaddingWidth > padRightWidth {
- result.WriteString(tw.PadRight(tw.Empty, rightPadChar, rightPaddingWidth-padRightWidth))
- f.logger.Debugf("Applied right centering padding: '%s' for %d width", rightPadChar, rightPaddingWidth-padRightWidth)
- }
- default:
- // Default to left alignment
- result.WriteString(leftPadChar)
- result.WriteString(content)
- rightPaddingWidth = totalPaddingWidth - padLeftWidth
- if rightPaddingWidth > 0 {
- result.WriteString(tw.PadRight(tw.Empty, rightPadChar, rightPaddingWidth))
- f.logger.Debugf("Applied right padding: '%s' for %d width", rightPadChar, rightPaddingWidth)
- }
- }
-
- output := result.String()
- finalWidth := twwidth.Width(output)
- // Adjust output to match target width
- if finalWidth > width {
- output = twwidth.Truncate(output, width)
- f.logger.Debugf("formatCell: Truncated output to width %d", width)
- } else if finalWidth < width {
- output = tw.PadRight(output, tw.Space, width)
- f.logger.Debugf("formatCell: Padded output to meet width %d", width)
- }
-
- // Log warning if final width doesn't match target
- if f.logger.Enabled() && twwidth.Width(output) != width {
- f.logger.Debugf("formatCell Warning: Final width %d does not match target %d for result '%s'",
- twwidth.Width(output), width, output)
- }
-
- f.logger.Debugf("Formatted cell final result: '%s' (target width %d)", output, width)
- return output
-}
-
-// renderLine renders a single line (header, row, or footer) with borders, separators, and merge handling.
-func (f *Blueprint) renderLine(ctx tw.Formatting) {
- // Get sorted column indices
- sortedKeys := ctx.Row.Widths.SortedKeys()
- numCols := 0
- if len(sortedKeys) > 0 {
- numCols = sortedKeys[len(sortedKeys)-1] + 1
- }
-
- // Set column separator and borders
- columnSeparator := f.config.Symbols.Column()
- prefix := tw.Empty
- if f.config.Borders.Left.Enabled() {
- prefix = columnSeparator
- }
- suffix := tw.Empty
- if f.config.Borders.Right.Enabled() {
- suffix = columnSeparator
- }
-
- var output strings.Builder
- totalLineWidth := 0 // Track total display width
- if prefix != tw.Empty {
- output.WriteString(prefix)
- totalLineWidth += twwidth.Width(prefix)
- f.logger.Debugf("renderLine: Prefix='%s' (f.width %d)", prefix, twwidth.Width(prefix))
- }
-
- colIndex := 0
- separatorDisplayWidth := 0
- if f.config.Settings.Separators.BetweenColumns.Enabled() {
- separatorDisplayWidth = twwidth.Width(columnSeparator)
- }
-
- // Process each column
- for colIndex < numCols {
- visualWidth := ctx.Row.Widths.Get(colIndex)
- cellCtx, ok := ctx.Row.Current[colIndex]
- isHMergeStart := ok && cellCtx.Merge.Horizontal.Present && cellCtx.Merge.Horizontal.Start
- if visualWidth == 0 && !isHMergeStart {
- f.logger.Debugf("renderLine: Skipping col %d (zero width, not HMerge start)", colIndex)
- colIndex++
- continue
- }
-
- // Determine if a separator is needed
- shouldAddSeparator := false
- if colIndex > 0 && f.config.Settings.Separators.BetweenColumns.Enabled() {
- prevWidth := ctx.Row.Widths.Get(colIndex - 1)
- prevCellCtx, prevOk := ctx.Row.Current[colIndex-1]
- prevIsHMergeEnd := prevOk && prevCellCtx.Merge.Horizontal.Present && prevCellCtx.Merge.Horizontal.End
- if (prevWidth > 0 || prevIsHMergeEnd) && (!ok || (!cellCtx.Merge.Horizontal.Present || cellCtx.Merge.Horizontal.Start)) {
- shouldAddSeparator = true
- }
- }
- if shouldAddSeparator {
- output.WriteString(columnSeparator)
- totalLineWidth += separatorDisplayWidth
- f.logger.Debugf("renderLine: Added separator '%s' before col %d (f.width %d)", columnSeparator, colIndex, separatorDisplayWidth)
- } else if colIndex > 0 {
- f.logger.Debugf("renderLine: Skipped separator before col %d due to zero-width prev col or HMerge continuation", colIndex)
- }
-
- // Handle merged cells
- span := 1
- if isHMergeStart {
- span = cellCtx.Merge.Horizontal.Span
- if ctx.Row.Position == tw.Row {
- dynamicTotalWidth := 0
- for k := 0; k < span && colIndex+k < numCols; k++ {
- normWidth := max(ctx.NormalizedWidths.Get(colIndex+k), 0)
- dynamicTotalWidth += normWidth
- if k > 0 && separatorDisplayWidth > 0 && ctx.NormalizedWidths.Get(colIndex+k) > 0 {
- dynamicTotalWidth += separatorDisplayWidth
- }
- }
- visualWidth = dynamicTotalWidth
- f.logger.Debugf("renderLine: Row HMerge col %d, span %d, dynamic visualWidth %d", colIndex, span, visualWidth)
- } else {
- visualWidth = ctx.Row.Widths.Get(colIndex)
- f.logger.Debugf("renderLine: H/F HMerge col %d, span %d, pre-adjusted visualWidth %d", colIndex, span, visualWidth)
- }
- } else {
- visualWidth = ctx.Row.Widths.Get(colIndex)
- f.logger.Debugf("renderLine: Regular col %d, visualWidth %d", colIndex, visualWidth)
- }
- if visualWidth < 0 {
- visualWidth = 0
- }
-
- // Skip processing for non-start merged cells
- if ok && cellCtx.Merge.Horizontal.Present && !cellCtx.Merge.Horizontal.Start {
- f.logger.Debugf("renderLine: Skipping col %d processing (part of HMerge)", colIndex)
- colIndex++
- continue
- }
-
- // Handle empty cell context
- if !ok {
- if visualWidth > 0 {
- spaces := strings.Repeat(tw.Space, visualWidth)
- output.WriteString(spaces)
- totalLineWidth += visualWidth
- f.logger.Debugf("renderLine: No cell context for col %d, writing %d spaces (f.width %d)", colIndex, visualWidth, visualWidth)
- } else {
- f.logger.Debugf("renderLine: No cell context for col %d, visualWidth is 0, writing nothing", colIndex)
- }
- colIndex += span
- continue
- }
-
- // Set cell padding and alignment
- padding := cellCtx.Padding
- align := cellCtx.Align
- switch align {
- case tw.AlignNone:
- switch ctx.Row.Position {
- case tw.Header:
- align = tw.AlignCenter
- case tw.Footer:
- align = tw.AlignRight
- default:
- align = tw.AlignLeft
- }
- f.logger.Debugf("renderLine: col %d (data: '%s') using renderer default align '%s' for position %s.", colIndex, cellCtx.Data, align, ctx.Row.Position)
- case tw.Skip:
- switch ctx.Row.Position {
- case tw.Header:
- align = tw.AlignCenter
- case tw.Footer:
- align = tw.AlignRight
- default:
- align = tw.AlignLeft
- }
- f.logger.Debugf("renderLine: col %d (data: '%s') cellCtx.Align was Skip/empty, falling back to basic default '%s'.", colIndex, cellCtx.Data, align)
- }
-
- isTotalPattern := false
-
- // Case-insensitive check for "total"
- if isHMergeStart && colIndex > 0 {
- if prevCellCtx, ok := ctx.Row.Current[colIndex-1]; ok {
- if strings.Contains(strings.ToLower(prevCellCtx.Data), "total") {
- isTotalPattern = true
- f.logger.Debugf("renderLine: total pattern in row in %d", colIndex)
- }
- }
- }
-
- // Get the alignment from the configuration
- align = cellCtx.Align
-
- // Override alignment for footer merged cells
- if (ctx.Row.Position == tw.Footer && isHMergeStart) || isTotalPattern {
- if align == tw.AlignNone {
- f.logger.Debugf("renderLine: Applying AlignRight HMerge/TOTAL override for Footer col %d. Original/default align was: %s", colIndex, align)
- align = tw.AlignRight
- }
- }
-
- // Handle vertical/hierarchical merges
- cellData := cellCtx.Data
- if (cellCtx.Merge.Vertical.Present && !cellCtx.Merge.Vertical.Start) ||
- (cellCtx.Merge.Hierarchical.Present && !cellCtx.Merge.Hierarchical.Start) {
- cellData = tw.Empty
- f.logger.Debugf("renderLine: Blanked data for col %d (non-start V/Hierarchical)", colIndex)
- }
-
- // Format and render the cell
- formattedCell := f.formatCell(cellData, visualWidth, padding, align)
- if len(formattedCell) > 0 {
- output.WriteString(formattedCell)
- cellWidth := twwidth.Width(formattedCell)
- totalLineWidth += cellWidth
- f.logger.Debugf("renderLine: Rendered col %d, formattedCell='%s' (f.width %d), totalLineWidth=%d", colIndex, formattedCell, cellWidth, totalLineWidth)
- }
-
- // Log rendering details
- if isHMergeStart {
- f.logger.Debugf("renderLine: Rendered HMerge START col %d (span %d, visualWidth %d, align %v): '%s'",
- colIndex, span, visualWidth, align, formattedCell)
- } else {
- f.logger.Debugf("renderLine: Rendered regular col %d (visualWidth %d, align %v): '%s'",
- colIndex, visualWidth, align, formattedCell)
- }
- colIndex += span
- }
-
- // Add suffix and adjust total width
- if output.Len() > len(prefix) || f.config.Borders.Right.Enabled() {
- output.WriteString(suffix)
- totalLineWidth += twwidth.Width(suffix)
- f.logger.Debugf("renderLine: Suffix='%s' (f.width %d)", suffix, twwidth.Width(suffix))
- }
- output.WriteString(tw.NewLine)
- f.w.Write([]byte(output.String()))
- f.logger.Debugf("renderLine: Final rendered line: '%s' (total width %d)", strings.TrimSuffix(output.String(), tw.NewLine), totalLineWidth)
-}
-
-// Rendition updates the Blueprint's configuration.
-func (f *Blueprint) Rendition(config tw.Rendition) {
- f.config = mergeRendition(f.config, config)
- f.logger.Debugf("Blueprint.Rendition updated. New config: %+v", f.config)
-}
-
-// Ensure Blueprint implements tw.Renditioning
-var _ tw.Renditioning = (*Blueprint)(nil)
diff --git a/vendor/github.com/olekukonko/tablewriter/renderer/colorized.go b/vendor/github.com/olekukonko/tablewriter/renderer/colorized.go
deleted file mode 100644
index 9bee749312..0000000000
--- a/vendor/github.com/olekukonko/tablewriter/renderer/colorized.go
+++ /dev/null
@@ -1,711 +0,0 @@
-package renderer
-
-import (
- "io"
- "strings"
-
- "github.com/fatih/color"
- "github.com/olekukonko/ll"
- "github.com/olekukonko/ll/lh"
- "github.com/olekukonko/tablewriter/pkg/twwidth"
-
- "github.com/olekukonko/tablewriter/tw"
-)
-
-// ColorizedConfig holds configuration for the Colorized table renderer.
-type ColorizedConfig struct {
- Borders tw.Border // Border visibility settings
- Settings tw.Settings // Rendering behavior settings (e.g., separators, whitespace)
- Header Tint // Colors for header cells
- Column Tint // Colors for row cells
- Footer Tint // Colors for footer cells
- Border Tint // Colors for borders and lines
- Separator Tint // Colors for column separators
- Symbols tw.Symbols // Symbols for table drawing (e.g., corners, lines)
-}
-
-// Colors is a slice of color attributes for use with fatih/color, such as color.FgWhite or color.Bold.
-type Colors []color.Attribute
-
-// Tint defines foreground and background color settings for table elements, with optional per-column overrides.
-type Tint struct {
- FG Colors // Foreground color attributes
- BG Colors // Background color attributes
- Columns []Tint // Per-column color settings
-}
-
-// Apply applies the Tint's foreground and background colors to the given text, returning the text unchanged if no colors are set.
-func (t Tint) Apply(text string) string {
- if len(t.FG) == 0 && len(t.BG) == 0 {
- return text
- }
- // Combine foreground and background colors
- combinedColors := append(t.FG, t.BG...)
- // Create a color function and apply it to the text
- c := color.New(combinedColors...).SprintFunc()
- return c(text)
-}
-
-// Colorized renders colored ASCII tables with customizable borders, colors, and alignments.
-type Colorized struct {
- config ColorizedConfig // Renderer configuration
- trace []string // Debug trace messages
- newLine string // Newline character
- defaultAlign map[tw.Position]tw.Align // Default alignments for header, row, and footer
- logger *ll.Logger // Logger for debug messages
- w io.Writer
-}
-
-// NewColorized creates a Colorized renderer with the specified configuration, falling back to defaults if none provided.
-// Only the first config is used if multiple are passed.
-func NewColorized(configs ...ColorizedConfig) *Colorized {
- // Initialize with default configuration
- baseCfg := defaultColorized()
-
- if len(configs) > 0 {
- userCfg := configs[0]
-
- // Override border settings if provided
- if userCfg.Borders.Left != 0 {
- baseCfg.Borders.Left = userCfg.Borders.Left
- }
- if userCfg.Borders.Right != 0 {
- baseCfg.Borders.Right = userCfg.Borders.Right
- }
- if userCfg.Borders.Top != 0 {
- baseCfg.Borders.Top = userCfg.Borders.Top
- }
- if userCfg.Borders.Bottom != 0 {
- baseCfg.Borders.Bottom = userCfg.Borders.Bottom
- }
-
- // Merge separator and line settings
- baseCfg.Settings.Separators = mergeSeparators(baseCfg.Settings.Separators, userCfg.Settings.Separators)
- baseCfg.Settings.Lines = mergeLines(baseCfg.Settings.Lines, userCfg.Settings.Lines)
-
- // Override compact mode if specified
- if userCfg.Settings.CompactMode != 0 {
- baseCfg.Settings.CompactMode = userCfg.Settings.CompactMode
- }
-
- // Override color settings for various table elements
- if len(userCfg.Header.FG) > 0 || len(userCfg.Header.BG) > 0 || userCfg.Header.Columns != nil {
- baseCfg.Header = userCfg.Header
- }
- if len(userCfg.Column.FG) > 0 || len(userCfg.Column.BG) > 0 || userCfg.Column.Columns != nil {
- baseCfg.Column = userCfg.Column
- }
- if len(userCfg.Footer.FG) > 0 || len(userCfg.Footer.BG) > 0 || userCfg.Footer.Columns != nil {
- baseCfg.Footer = userCfg.Footer
- }
- if len(userCfg.Border.FG) > 0 || len(userCfg.Border.BG) > 0 || userCfg.Border.Columns != nil {
- baseCfg.Border = userCfg.Border
- }
- if len(userCfg.Separator.FG) > 0 || len(userCfg.Separator.BG) > 0 || userCfg.Separator.Columns != nil {
- baseCfg.Separator = userCfg.Separator
- }
-
- // Override symbols if provided
- if userCfg.Symbols != nil {
- baseCfg.Symbols = userCfg.Symbols
- }
- }
-
- cfg := baseCfg
- // Ensure symbols are initialized
- if cfg.Symbols == nil {
- cfg.Symbols = tw.NewSymbols(tw.StyleLight)
- }
-
- // Initialize the Colorized renderer
- f := &Colorized{
- config: cfg,
- newLine: tw.NewLine,
- defaultAlign: map[tw.Position]tw.Align{
- tw.Header: tw.AlignCenter,
- tw.Row: tw.AlignLeft,
- tw.Footer: tw.AlignRight,
- },
- logger: ll.New("colorized", ll.WithHandler(lh.NewMemoryHandler())),
- }
- // Log initialization details
- f.logger.Debugf("Initialized Colorized renderer with symbols: Center=%q, Row=%q, Column=%q", f.config.Symbols.Center(), f.config.Symbols.Row(), f.config.Symbols.Column())
- f.logger.Debugf("Final ColorizedConfig.Settings.Lines: %+v", f.config.Settings.Lines)
- f.logger.Debugf("Final ColorizedConfig.Borders: %+v", f.config.Borders)
- return f
-}
-
-// Close performs cleanup (no-op in this implementation).
-func (c *Colorized) Close() error {
- c.logger.Debug("Colorized.Close() called (no-op).")
- return nil
-}
-
-// Config returns the renderer's configuration as a Rendition.
-func (c *Colorized) Config() tw.Rendition {
- return tw.Rendition{
- Borders: c.config.Borders,
- Settings: c.config.Settings,
- Symbols: c.config.Symbols,
- Streaming: true,
- }
-}
-
-// Debug returns the accumulated debug trace messages.
-func (c *Colorized) Debug() []string {
- return c.trace
-}
-
-// Footer renders the table footer with configured colors and formatting.
-func (c *Colorized) Footer(footers [][]string, ctx tw.Formatting) {
- c.logger.Debugf("Starting Footer render: IsSubRow=%v, Location=%v, Pos=%s",
- ctx.IsSubRow, ctx.Row.Location, ctx.Row.Position)
-
- // Check if there are footers to render
- if len(footers) == 0 || len(footers[0]) == 0 {
- c.logger.Debug("Footer: No footers to render")
- return
- }
-
- // Render the footer line
- c.renderLine(ctx, footers[0], c.config.Footer)
- c.logger.Debug("Completed Footer render")
-}
-
-// Header renders the table header with configured colors and formatting.
-func (c *Colorized) Header(headers [][]string, ctx tw.Formatting) {
- c.logger.Debugf("Starting Header render: IsSubRow=%v, Location=%v, Pos=%s, lines=%d, widths=%v",
- ctx.IsSubRow, ctx.Row.Location, ctx.Row.Position, len(headers), ctx.Row.Widths)
-
- // Check if there are headers to render
- if len(headers) == 0 || len(headers[0]) == 0 {
- c.logger.Debug("Header: No headers to render")
- return
- }
-
- // Render the header line
- c.renderLine(ctx, headers[0], c.config.Header)
- c.logger.Debug("Completed Header render")
-}
-
-// Line renders a horizontal row line with colored junctions and segments, skipping zero-width columns.
-func (c *Colorized) Line(ctx tw.Formatting) {
- c.logger.Debugf("Line: Starting with Level=%v, Location=%v, IsSubRow=%v, Widths=%v", ctx.Level, ctx.Row.Location, ctx.IsSubRow, ctx.Row.Widths)
-
- // Initialize junction renderer
- jr := NewJunction(JunctionContext{
- Symbols: c.config.Symbols,
- Ctx: ctx,
- ColIdx: 0,
- BorderTint: c.config.Border,
- SeparatorTint: c.config.Separator,
- Logger: c.logger,
- })
-
- var line strings.Builder
-
- // Get sorted column indices and filter out zero-width columns
- allSortedKeys := ctx.Row.Widths.SortedKeys()
- effectiveKeys := []int{}
- keyWidthMap := make(map[int]int)
-
- for _, k := range allSortedKeys {
- width := ctx.Row.Widths.Get(k)
- keyWidthMap[k] = width
- if width > 0 {
- effectiveKeys = append(effectiveKeys, k)
- }
- }
- c.logger.Debugf("Line: All keys=%v, Effective keys (width>0)=%v", allSortedKeys, effectiveKeys)
-
- // Handle case with no effective columns
- if len(effectiveKeys) == 0 {
- prefix := tw.Empty
- suffix := tw.Empty
- if c.config.Borders.Left.Enabled() {
- prefix = jr.RenderLeft()
- }
- if c.config.Borders.Right.Enabled() {
- originalLastColIdx := -1
- if len(allSortedKeys) > 0 {
- originalLastColIdx = allSortedKeys[len(allSortedKeys)-1]
- }
- suffix = jr.RenderRight(originalLastColIdx)
- }
- if prefix != tw.Empty || suffix != tw.Empty {
- line.WriteString(prefix + suffix + tw.NewLine)
- c.w.Write([]byte(line.String()))
- }
- c.logger.Debug("Line: Handled empty row/widths case (no effective keys)")
- return
- }
-
- // Add left border if enabled
- if c.config.Borders.Left.Enabled() {
- line.WriteString(jr.RenderLeft())
- }
-
- // Render segments for each effective column
- for keyIndex, currentColIdx := range effectiveKeys {
- jr.colIdx = currentColIdx
- segment := jr.GetSegment()
- colWidth := keyWidthMap[currentColIdx]
- c.logger.Debugf("Line: Drawing segment for Effective colIdx=%d, segment='%s', width=%d", currentColIdx, segment, colWidth)
-
- if segment == tw.Empty {
- line.WriteString(strings.Repeat(tw.Space, colWidth))
- } else {
- // Calculate how many times to repeat the segment
- segmentWidth := twwidth.Width(segment)
- if segmentWidth <= 0 {
- segmentWidth = 1
- }
- repeat := 0
- if colWidth > 0 && segmentWidth > 0 {
- repeat = colWidth / segmentWidth
- }
- drawnSegment := strings.Repeat(segment, repeat)
- line.WriteString(drawnSegment)
-
- // Adjust for width discrepancies
- actualDrawnWidth := twwidth.Width(drawnSegment)
- if actualDrawnWidth < colWidth {
- missingWidth := colWidth - actualDrawnWidth
- spaces := strings.Repeat(tw.Space, missingWidth)
- if len(c.config.Border.BG) > 0 {
- line.WriteString(Tint{BG: c.config.Border.BG}.Apply(spaces))
- } else {
- line.WriteString(spaces)
- }
- c.logger.Debugf("Line: colIdx=%d corrected segment width, added %d spaces", currentColIdx, missingWidth)
- } else if actualDrawnWidth > colWidth {
- c.logger.Debugf("Line: WARNING colIdx=%d segment draw width %d > target %d", currentColIdx, actualDrawnWidth, colWidth)
- }
- }
-
- // Add junction between columns if not the last visible column
- isLastVisible := keyIndex == len(effectiveKeys)-1
- if !isLastVisible && c.config.Settings.Separators.BetweenColumns.Enabled() {
- nextVisibleColIdx := effectiveKeys[keyIndex+1]
- originalPrecedingCol := -1
- foundCurrent := false
- for _, k := range allSortedKeys {
- if k == currentColIdx {
- foundCurrent = true
- }
- if foundCurrent && k < nextVisibleColIdx {
- originalPrecedingCol = k
- }
- if k >= nextVisibleColIdx {
- break
- }
- }
-
- if originalPrecedingCol != -1 {
- jr.colIdx = originalPrecedingCol
- junction := jr.RenderJunction(originalPrecedingCol, nextVisibleColIdx)
- c.logger.Debugf("Line: Junction between visible %d (orig preceding %d) and next visible %d: '%s'", currentColIdx, originalPrecedingCol, nextVisibleColIdx, junction)
- line.WriteString(junction)
- } else {
- c.logger.Debugf("Line: Could not determine original preceding column for junction before visible %d", nextVisibleColIdx)
- line.WriteString(c.config.Separator.Apply(jr.sym.Center()))
- }
- }
- }
-
- // Add right border if enabled
- if c.config.Borders.Right.Enabled() {
- originalLastColIdx := -1
- if len(allSortedKeys) > 0 {
- originalLastColIdx = allSortedKeys[len(allSortedKeys)-1]
- }
- jr.colIdx = originalLastColIdx
- line.WriteString(jr.RenderRight(originalLastColIdx))
- }
-
- // Write the final line
- line.WriteString(c.newLine)
- c.w.Write([]byte(line.String()))
- c.logger.Debugf("Line rendered: %s", strings.TrimSuffix(line.String(), c.newLine))
-}
-
-// Logger sets the logger for the Colorized instance.
-func (c *Colorized) Logger(logger *ll.Logger) {
- c.logger = logger.Namespace("colorized")
-}
-
-// Reset clears the renderer's internal state, including debug traces.
-func (c *Colorized) Reset() {
- c.trace = nil
- c.logger.Debugf("Reset: Cleared debug trace")
-}
-
-// Row renders a table data row with configured colors and formatting.
-func (c *Colorized) Row(row []string, ctx tw.Formatting) {
- c.logger.Debugf("Starting Row render: IsSubRow=%v, Location=%v, Pos=%s, hasFooter=%v",
- ctx.IsSubRow, ctx.Row.Location, ctx.Row.Position, ctx.HasFooter)
-
- // Check if there is data to render
- if len(row) == 0 {
- c.logger.Debugf("Row: No data to render")
- return
- }
-
- // Render the row line
- c.renderLine(ctx, row, c.config.Column)
- c.logger.Debugf("Completed Row render")
-}
-
-// Start initializes the rendering process (no-op in this implementation).
-func (c *Colorized) Start(w io.Writer) error {
- c.w = w
- c.logger.Debugf("Colorized.Start() called (no-op).")
- return nil
-}
-
-// formatCell formats a cell's content with color, width, padding, and alignment, handling whitespace trimming and truncation.
-func (c *Colorized) formatCell(content string, width int, padding tw.Padding, align tw.Align, tint Tint) string {
- c.logger.Debugf("Formatting cell: content='%s', width=%d, align=%s, paddingL='%s', paddingR='%s', tintFG=%v, tintBG=%v",
- content, width, align, padding.Left, padding.Right, tint.FG, tint.BG)
-
- // Return empty string if width is non-positive
- if width <= 0 {
- c.logger.Debugf("formatCell: width %d <= 0, returning empty string", width)
- return tw.Empty
- }
-
- // Calculate visual width of content
- contentVisualWidth := twwidth.Width(content)
-
- // Set default padding characters
- padLeftCharStr := padding.Left
- // if padLeftCharStr == tw.Empty {
- // padLeftCharStr = tw.Space
- //}
- padRightCharStr := padding.Right
- // if padRightCharStr == tw.Empty {
- // padRightCharStr = tw.Space
- //}
-
- // Calculate padding widths
- definedPadLeftWidth := twwidth.Width(padLeftCharStr)
- definedPadRightWidth := twwidth.Width(padRightCharStr)
- // Calculate available width for content and alignment
- availableForContentAndAlign := max(width-definedPadLeftWidth-definedPadRightWidth, 0)
-
- // Truncate content if it exceeds available width
- if contentVisualWidth > availableForContentAndAlign {
- content = twwidth.Truncate(content, availableForContentAndAlign)
- contentVisualWidth = twwidth.Width(content)
- c.logger.Debugf("Truncated content to fit %d: '%s' (new width %d)", availableForContentAndAlign, content, contentVisualWidth)
- }
-
- // Calculate remaining space for alignment
- remainingSpaceForAlignment := max(availableForContentAndAlign-contentVisualWidth, 0)
-
- // Apply alignment padding
- leftAlignmentPadSpaces := tw.Empty
- rightAlignmentPadSpaces := tw.Empty
- switch align {
- case tw.AlignLeft:
- rightAlignmentPadSpaces = strings.Repeat(tw.Space, remainingSpaceForAlignment)
- case tw.AlignRight:
- leftAlignmentPadSpaces = strings.Repeat(tw.Space, remainingSpaceForAlignment)
- case tw.AlignCenter:
- leftSpacesCount := remainingSpaceForAlignment / 2
- rightSpacesCount := remainingSpaceForAlignment - leftSpacesCount
- leftAlignmentPadSpaces = strings.Repeat(tw.Space, leftSpacesCount)
- rightAlignmentPadSpaces = strings.Repeat(tw.Space, rightSpacesCount)
- default:
- // Default to left alignment
- rightAlignmentPadSpaces = strings.Repeat(tw.Space, remainingSpaceForAlignment)
- }
-
- // Apply colors to content and padding
- coloredContent := tint.Apply(content)
- coloredPadLeft := padLeftCharStr
- coloredPadRight := padRightCharStr
- coloredAlignPadLeft := leftAlignmentPadSpaces
- coloredAlignPadRight := rightAlignmentPadSpaces
-
- if len(tint.BG) > 0 {
- bgTint := Tint{BG: tint.BG}
- // Apply foreground color to non-space padding if foreground is defined
- if len(tint.FG) > 0 && padLeftCharStr != tw.Space {
- coloredPadLeft = tint.Apply(padLeftCharStr)
- } else {
- coloredPadLeft = bgTint.Apply(padLeftCharStr)
- }
- if len(tint.FG) > 0 && padRightCharStr != tw.Space {
- coloredPadRight = tint.Apply(padRightCharStr)
- } else {
- coloredPadRight = bgTint.Apply(padRightCharStr)
- }
- // Apply background color to alignment padding
- if leftAlignmentPadSpaces != tw.Empty {
- coloredAlignPadLeft = bgTint.Apply(leftAlignmentPadSpaces)
- }
- if rightAlignmentPadSpaces != tw.Empty {
- coloredAlignPadRight = bgTint.Apply(rightAlignmentPadSpaces)
- }
- } else if len(tint.FG) > 0 {
- // Apply foreground color to non-space padding
- if padLeftCharStr != tw.Space {
- coloredPadLeft = tint.Apply(padLeftCharStr)
- }
- if padRightCharStr != tw.Space {
- coloredPadRight = tint.Apply(padRightCharStr)
- }
- }
-
- // Build final cell string
- var sb strings.Builder
- sb.WriteString(coloredPadLeft)
- sb.WriteString(coloredAlignPadLeft)
- sb.WriteString(coloredContent)
- sb.WriteString(coloredAlignPadRight)
- sb.WriteString(coloredPadRight)
- output := sb.String()
-
- // Adjust output width if necessary
- currentVisualWidth := twwidth.Width(output)
- if currentVisualWidth != width {
- c.logger.Debugf("formatCell MISMATCH: content='%s', target_w=%d. Calculated parts width = %d. String: '%s'",
- content, width, currentVisualWidth, output)
- if currentVisualWidth > width {
- output = twwidth.Truncate(output, width)
- } else {
- paddingSpacesStr := strings.Repeat(tw.Space, width-currentVisualWidth)
- if len(tint.BG) > 0 {
- output += Tint{BG: tint.BG}.Apply(paddingSpacesStr)
- } else {
- output += paddingSpacesStr
- }
- }
- c.logger.Debugf("formatCell Post-Correction: Target %d, New Visual width %d. Output: '%s'", width, twwidth.Width(output), output)
- }
-
- c.logger.Debugf("Formatted cell final result: '%s' (target width %d, display width %d)", output, width, twwidth.Width(output))
- return output
-}
-
-// renderLine renders a single line (header, row, or footer) with colors, handling merges and separators.
-func (c *Colorized) renderLine(ctx tw.Formatting, line []string, tint Tint) {
- // Determine number of columns
- numCols := 0
- if len(ctx.Row.Current) > 0 {
- maxKey := -1
- for k := range ctx.Row.Current {
- if k > maxKey {
- maxKey = k
- }
- }
- numCols = maxKey + 1
- } else {
- maxKey := -1
- for k := range ctx.Row.Widths {
- if k > maxKey {
- maxKey = k
- }
- }
- numCols = maxKey + 1
- }
-
- var output strings.Builder
-
- // Add left border if enabled
- prefix := tw.Empty
- if c.config.Borders.Left.Enabled() {
- prefix = c.config.Border.Apply(c.config.Symbols.Column())
- }
- output.WriteString(prefix)
-
- // Set up separator
- separatorDisplayWidth := 0
- separatorString := tw.Empty
- if c.config.Settings.Separators.BetweenColumns.Enabled() {
- separatorString = c.config.Separator.Apply(c.config.Symbols.Column())
- separatorDisplayWidth = twwidth.Width(c.config.Symbols.Column())
- }
-
- // Process each column
- for i := 0; i < numCols; {
- // Determine if a separator is needed
- shouldAddSeparator := false
- if i > 0 && c.config.Settings.Separators.BetweenColumns.Enabled() {
- cellCtx, ok := ctx.Row.Current[i]
- if !ok || (!cellCtx.Merge.Horizontal.Present || cellCtx.Merge.Horizontal.Start) {
- shouldAddSeparator = true
- }
- }
- if shouldAddSeparator {
- output.WriteString(separatorString)
- c.logger.Debugf("renderLine: Added separator '%s' before col %d", separatorString, i)
- } else if i > 0 {
- c.logger.Debugf("renderLine: Skipped separator before col %d due to HMerge continuation", i)
- }
-
- // Get cell context, use default if not present
- cellCtx, ok := ctx.Row.Current[i]
- if !ok {
- cellCtx = tw.CellContext{
- Data: tw.Empty,
- Align: c.defaultAlign[ctx.Row.Position],
- Padding: tw.Padding{Left: tw.Space, Right: tw.Space},
- Width: ctx.Row.Widths.Get(i),
- Merge: tw.MergeState{},
- }
- }
-
- // Handle merged cells
- visualWidth := 0
- span := 1
- isHMergeStart := ok && cellCtx.Merge.Horizontal.Present && cellCtx.Merge.Horizontal.Start
-
- if isHMergeStart {
- span = cellCtx.Merge.Horizontal.Span
- if ctx.Row.Position == tw.Row {
- // Calculate dynamic width for row merges
- dynamicTotalWidth := 0
- for k := 0; k < span && i+k < numCols; k++ {
- colToSum := i + k
- normWidth := max(ctx.NormalizedWidths.Get(colToSum), 0)
- dynamicTotalWidth += normWidth
- if k > 0 && separatorDisplayWidth > 0 {
- dynamicTotalWidth += separatorDisplayWidth
- }
- }
- visualWidth = dynamicTotalWidth
- c.logger.Debugf("renderLine: Row HMerge col %d, span %d, dynamic visualWidth %d", i, span, visualWidth)
- } else {
- visualWidth = ctx.Row.Widths.Get(i)
- c.logger.Debugf("renderLine: H/F HMerge col %d, span %d, pre-adjusted visualWidth %d", i, span, visualWidth)
- }
- } else {
- visualWidth = ctx.Row.Widths.Get(i)
- c.logger.Debugf("renderLine: Regular col %d, visualWidth %d", i, visualWidth)
- }
- if visualWidth < 0 {
- visualWidth = 0
- }
-
- // Skip processing for non-start merged cells
- if ok && cellCtx.Merge.Horizontal.Present && !cellCtx.Merge.Horizontal.Start {
- c.logger.Debugf("renderLine: Skipping col %d processing (part of HMerge)", i)
- i++
- continue
- }
-
- // Handle empty cell context with non-zero width
- if !ok && visualWidth > 0 {
- spaces := strings.Repeat(tw.Space, visualWidth)
- if len(tint.BG) > 0 {
- output.WriteString(Tint{BG: tint.BG}.Apply(spaces))
- } else {
- output.WriteString(spaces)
- }
- c.logger.Debugf("renderLine: No cell context for col %d, writing %d spaces", i, visualWidth)
- i += span
- continue
- }
-
- // Set cell alignment
- padding := cellCtx.Padding
- align := cellCtx.Align
- if align == tw.AlignNone {
- align = c.defaultAlign[ctx.Row.Position]
- c.logger.Debugf("renderLine: col %d using default renderer align '%s' for position %s because cellCtx.Align was AlignNone", i, align, ctx.Row.Position)
- }
-
- // Detect and handle TOTAL pattern
- isTotalPattern := false
- if i == 0 && isHMergeStart && cellCtx.Merge.Horizontal.Span >= 3 && strings.TrimSpace(cellCtx.Data) == "TOTAL" {
- isTotalPattern = true
- c.logger.Debugf("renderLine: Detected 'TOTAL' HMerge pattern at col 0")
- }
- // Override alignment for footer merges or TOTAL pattern
- if (ctx.Row.Position == tw.Footer && isHMergeStart) || isTotalPattern {
- if align == tw.AlignNone {
- c.logger.Debugf("renderLine: Applying AlignRight override for Footer HMerge/TOTAL pattern at col %d. Original/default align was: %s", i, align)
- align = tw.AlignRight
- }
- }
-
- // Handle vertical/hierarchical merges
- content := cellCtx.Data
- if (cellCtx.Merge.Vertical.Present && !cellCtx.Merge.Vertical.Start) ||
- (cellCtx.Merge.Hierarchical.Present && !cellCtx.Merge.Hierarchical.Start) {
- content = tw.Empty
- c.logger.Debugf("renderLine: Blanked data for col %d (non-start V/Hierarchical)", i)
- }
-
- // Apply per-column tint if available
- cellTint := tint
- if i < len(tint.Columns) {
- columnTint := tint.Columns[i]
- if len(columnTint.FG) > 0 || len(columnTint.BG) > 0 {
- cellTint = columnTint
- }
- }
-
- // Format and render the cell
- formattedCell := c.formatCell(content, visualWidth, padding, align, cellTint)
- if len(formattedCell) > 0 {
- output.WriteString(formattedCell)
- } else if visualWidth == 0 && isHMergeStart {
- c.logger.Debugf("renderLine: Rendered HMerge START col %d resulted in 0 visual width, wrote nothing.", i)
- } else if visualWidth == 0 {
- c.logger.Debugf("renderLine: Rendered regular col %d resulted in 0 visual width, wrote nothing.", i)
- }
-
- // Log rendering details
- if isHMergeStart {
- c.logger.Debugf("renderLine: Rendered HMerge START col %d (span %d, visualWidth %d, align %s): '%s'",
- i, span, visualWidth, align, formattedCell)
- } else {
- c.logger.Debugf("renderLine: Rendered regular col %d (visualWidth %d, align %s): '%s'",
- i, visualWidth, align, formattedCell)
- }
-
- i += span
- }
-
- // Add right border if enabled
- suffix := tw.Empty
- if c.config.Borders.Right.Enabled() {
- suffix = c.config.Border.Apply(c.config.Symbols.Column())
- }
- output.WriteString(suffix)
-
- // Write the final line
- output.WriteString(c.newLine)
- c.w.Write([]byte(output.String()))
- c.logger.Debugf("renderLine: Final rendered line: %s", strings.TrimSuffix(output.String(), c.newLine))
-}
-
-// Rendition updates the parts of ColorizedConfig that correspond to tw.Rendition
-// by merging the provided newRendition. Color-specific Tints are not modified.
-func (c *Colorized) Rendition(newRendition tw.Rendition) { // Method name matches interface
- c.logger.Debug("Colorized.Rendition called. Current B/Sym/Set: B:%+v, Sym:%T, S:%+v. Override: %+v", c.config.Borders, c.config.Symbols, c.config.Settings, newRendition)
-
- currentRenditionPart := tw.Rendition{
- Borders: c.config.Borders,
- Symbols: c.config.Symbols,
- Settings: c.config.Settings,
- }
-
- mergedRenditionPart := mergeRendition(currentRenditionPart, newRendition)
-
- c.config.Borders = mergedRenditionPart.Borders
- c.config.Symbols = mergedRenditionPart.Symbols
- if c.config.Symbols == nil {
- c.config.Symbols = tw.NewSymbols(tw.StyleLight)
- }
- c.config.Settings = mergedRenditionPart.Settings
-
- c.logger.Debugf("Colorized.Rendition updated. New B/Sym/Set: B:%+v, Sym:%T, S:%+v",
- c.config.Borders, c.config.Symbols, c.config.Settings)
-}
-
-// Ensure Colorized implements tw.Renditioning
-var _ tw.Renditioning = (*Colorized)(nil)
diff --git a/vendor/github.com/olekukonko/tablewriter/renderer/fn.go b/vendor/github.com/olekukonko/tablewriter/renderer/fn.go
deleted file mode 100644
index cb6a768bc6..0000000000
--- a/vendor/github.com/olekukonko/tablewriter/renderer/fn.go
+++ /dev/null
@@ -1,236 +0,0 @@
-package renderer
-
-import (
- "fmt"
-
- "github.com/fatih/color"
- "github.com/olekukonko/tablewriter/tw"
-)
-
-// defaultBlueprint returns a default Rendition for ASCII table rendering with borders and light symbols.
-func defaultBlueprint() tw.Rendition {
- return tw.Rendition{
- Borders: tw.Border{
- Left: tw.On,
- Right: tw.On,
- Top: tw.On,
- Bottom: tw.On,
- },
- Settings: tw.Settings{
- Separators: tw.Separators{
- ShowHeader: tw.On,
- ShowFooter: tw.On,
- BetweenRows: tw.Off,
- BetweenColumns: tw.On,
- },
- Lines: tw.Lines{
- ShowTop: tw.On,
- ShowBottom: tw.On,
- ShowHeaderLine: tw.On,
- ShowFooterLine: tw.On,
- },
- CompactMode: tw.Off,
- // Cushion: tw.On,
- },
- Symbols: tw.NewSymbols(tw.StyleLight),
- Streaming: true,
- }
-}
-
-// defaultColorized returns a default ColorizedConfig optimized for dark terminal backgrounds with colored headers, rows, and borders.
-func defaultColorized() ColorizedConfig {
- return ColorizedConfig{
- Borders: tw.Border{Left: tw.On, Right: tw.On, Top: tw.On, Bottom: tw.On},
- Settings: tw.Settings{
- Separators: tw.Separators{
- ShowHeader: tw.On,
- ShowFooter: tw.On,
- BetweenRows: tw.Off,
- BetweenColumns: tw.On,
- },
- Lines: tw.Lines{
- ShowTop: tw.On,
- ShowBottom: tw.On,
- ShowHeaderLine: tw.On,
- ShowFooterLine: tw.On,
- },
-
- CompactMode: tw.Off,
- },
- Header: Tint{
- FG: Colors{color.FgWhite, color.Bold},
- BG: Colors{color.BgBlack},
- },
- Column: Tint{
- FG: Colors{color.FgCyan},
- BG: Colors{color.BgBlack},
- },
- Footer: Tint{
- FG: Colors{color.FgYellow},
- BG: Colors{color.BgBlack},
- },
- Border: Tint{
- FG: Colors{color.FgWhite},
- BG: Colors{color.BgBlack},
- },
- Separator: Tint{
- FG: Colors{color.FgWhite},
- BG: Colors{color.BgBlack},
- },
- Symbols: tw.NewSymbols(tw.StyleLight),
- }
-}
-
-// defaultOceanRendererConfig returns a base tw.Rendition for the Ocean renderer.
-func defaultOceanRendererConfig() tw.Rendition {
- return tw.Rendition{
- Borders: tw.Border{
- Left: tw.On, Right: tw.On, Top: tw.On, Bottom: tw.On,
- },
- Settings: tw.Settings{
- Separators: tw.Separators{
- ShowHeader: tw.On,
- ShowFooter: tw.Off,
- BetweenRows: tw.Off,
- BetweenColumns: tw.On,
- },
- Lines: tw.Lines{
- ShowTop: tw.On,
- ShowBottom: tw.On,
- ShowHeaderLine: tw.On,
- ShowFooterLine: tw.Off,
- },
-
- CompactMode: tw.Off,
- },
- Symbols: tw.NewSymbols(tw.StyleDefault),
- Streaming: true,
- }
-}
-
-// getHTMLStyle remains the same
-func getHTMLStyle(align tw.Align) string {
- styleContent := tw.Empty
- switch align {
- case tw.AlignRight:
- styleContent = "text-align: right;"
- case tw.AlignCenter:
- styleContent = "text-align: center;"
- case tw.AlignLeft:
- styleContent = "text-align: left;"
- }
- if styleContent != tw.Empty {
- return fmt.Sprintf(` style="%s"`, styleContent)
- }
- return tw.Empty
-}
-
-// mergeLines combines default and override line settings, preserving defaults for unset (zero) overrides.
-func mergeLines(defaults, overrides tw.Lines) tw.Lines {
- if overrides.ShowTop != 0 {
- defaults.ShowTop = overrides.ShowTop
- }
- if overrides.ShowBottom != 0 {
- defaults.ShowBottom = overrides.ShowBottom
- }
- if overrides.ShowHeaderLine != 0 {
- defaults.ShowHeaderLine = overrides.ShowHeaderLine
- }
- if overrides.ShowFooterLine != 0 {
- defaults.ShowFooterLine = overrides.ShowFooterLine
- }
- return defaults
-}
-
-// mergeSeparators combines default and override separator settings, preserving defaults for unset (zero) overrides.
-func mergeSeparators(defaults, overrides tw.Separators) tw.Separators {
- if overrides.ShowHeader != 0 {
- defaults.ShowHeader = overrides.ShowHeader
- }
- if overrides.ShowFooter != 0 {
- defaults.ShowFooter = overrides.ShowFooter
- }
- if overrides.BetweenRows != 0 {
- defaults.BetweenRows = overrides.BetweenRows
- }
- if overrides.BetweenColumns != 0 {
- defaults.BetweenColumns = overrides.BetweenColumns
- }
- return defaults
-}
-
-// mergeSettings combines default and override settings, preserving defaults for unset (zero) overrides.
-func mergeSettings(defaults, overrides tw.Settings) tw.Settings {
- if overrides.Separators.ShowHeader != tw.Unknown {
- defaults.Separators.ShowHeader = overrides.Separators.ShowHeader
- }
- if overrides.Separators.ShowFooter != tw.Unknown {
- defaults.Separators.ShowFooter = overrides.Separators.ShowFooter
- }
- if overrides.Separators.BetweenRows != tw.Unknown {
- defaults.Separators.BetweenRows = overrides.Separators.BetweenRows
- }
- if overrides.Separators.BetweenColumns != tw.Unknown {
- defaults.Separators.BetweenColumns = overrides.Separators.BetweenColumns
- }
- if overrides.Lines.ShowTop != tw.Unknown {
- defaults.Lines.ShowTop = overrides.Lines.ShowTop
- }
- if overrides.Lines.ShowBottom != tw.Unknown {
- defaults.Lines.ShowBottom = overrides.Lines.ShowBottom
- }
- if overrides.Lines.ShowHeaderLine != tw.Unknown {
- defaults.Lines.ShowHeaderLine = overrides.Lines.ShowHeaderLine
- }
- if overrides.Lines.ShowFooterLine != tw.Unknown {
- defaults.Lines.ShowFooterLine = overrides.Lines.ShowFooterLine
- }
-
- if overrides.CompactMode != tw.Unknown {
- defaults.CompactMode = overrides.CompactMode
- }
-
- // if overrides.Cushion != tw.Unknown {
- // defaults.Cushion = overrides.Cushion
- //}
-
- return defaults
-}
-
-// MergeRendition merges the 'override' rendition into the 'current' rendition.
-// It only updates fields in 'current' if they are explicitly set (non-zero/non-nil) in 'override'.
-// This allows for partial updates to a renderer's configuration.
-func mergeRendition(current, override tw.Rendition) tw.Rendition {
- // Merge Borders: Only update if override border states are explicitly set (not 0).
- // A tw.State's zero value is 0, which is distinct from tw.On (1) or tw.Off (-1).
- // So, if override.Borders.Left is 0, it means "not specified", so we keep current.
- if override.Borders.Left != 0 {
- current.Borders.Left = override.Borders.Left
- }
- if override.Borders.Right != 0 {
- current.Borders.Right = override.Borders.Right
- }
- if override.Borders.Top != 0 {
- current.Borders.Top = override.Borders.Top
- }
- if override.Borders.Bottom != 0 {
- current.Borders.Bottom = override.Borders.Bottom
- }
-
- // Merge Symbols: Only update if override.Symbols is not nil.
- if override.Symbols != nil {
- current.Symbols = override.Symbols
- }
-
- // Merge Settings: Use the existing mergeSettings for granular control.
- // mergeSettings already handles preserving defaults for unset (zero) overrides.
- current.Settings = mergeSettings(current.Settings, override.Settings)
-
- // Streaming flag: typically set at renderer creation, but can be overridden if needed.
- // For now, let's assume it's not commonly changed post-creation by a generic rendition merge.
- // If override provides a different streaming capability, it might indicate a fundamental
- // change that a simple merge shouldn't handle without more context.
- // current.Streaming = override.Streaming // Or keep current.Streaming
-
- return current
-}
diff --git a/vendor/github.com/olekukonko/tablewriter/renderer/html.go b/vendor/github.com/olekukonko/tablewriter/renderer/html.go
deleted file mode 100644
index d02594b9ce..0000000000
--- a/vendor/github.com/olekukonko/tablewriter/renderer/html.go
+++ /dev/null
@@ -1,442 +0,0 @@
-package renderer
-
-import (
- "errors"
- "fmt"
- "html"
- "io"
- "strings"
-
- "github.com/olekukonko/ll"
-
- "github.com/olekukonko/tablewriter/tw"
-)
-
-// HTMLConfig defines settings for the HTML table renderer.
-type HTMLConfig struct {
- EscapeContent bool // Whether to escape cell content
- AddLinesTag bool // Whether to wrap multiline content in tags
- TableClass string // CSS class for
- HeaderClass string // CSS class for
- BodyClass string // CSS class for
- FooterClass string // CSS class for
- RowClass string // CSS class for
in body
- HeaderRowClass string // CSS class for
in header
- FooterRowClass string // CSS class for
in footer
-}
-
-// HTML renders tables in HTML format with customizable classes and content handling.
-type HTML struct {
- config HTMLConfig // Renderer configuration
- w io.Writer // Output w
- trace []string // Debug trace messages
- debug bool // Enables debug logging
- tableStarted bool // Tracks if
tag is open
- tbodyStarted bool // Tracks if tag is open
- tfootStarted bool // Tracks if tag is open
- vMergeTrack map[int]int // Tracks vertical merge spans by column index
- logger *ll.Logger
-}
-
-// NewHTML initializes an HTML renderer with the given w, debug setting, and optional configuration.
-// It panics if the w is nil and applies defaults for unset config fields.
-// Update: see https://github.com/olekukonko/tablewriter/issues/258
-func NewHTML(configs ...HTMLConfig) *HTML {
- cfg := HTMLConfig{
- EscapeContent: true,
- AddLinesTag: false,
- }
- if len(configs) > 0 {
- userCfg := configs[0]
- cfg.EscapeContent = userCfg.EscapeContent
- cfg.AddLinesTag = userCfg.AddLinesTag
- cfg.TableClass = userCfg.TableClass
- cfg.HeaderClass = userCfg.HeaderClass
- cfg.BodyClass = userCfg.BodyClass
- cfg.FooterClass = userCfg.FooterClass
- cfg.RowClass = userCfg.RowClass
- cfg.HeaderRowClass = userCfg.HeaderRowClass
- cfg.FooterRowClass = userCfg.FooterRowClass
- }
- return &HTML{
- config: cfg,
- vMergeTrack: make(map[int]int),
- tableStarted: false,
- tbodyStarted: false,
- tfootStarted: false,
- logger: ll.New("html"),
- }
-}
-
-func (h *HTML) Logger(logger *ll.Logger) {
- h.logger = logger
-}
-
-// Config returns a Rendition representation of the current configuration.
-func (h *HTML) Config() tw.Rendition {
- return tw.Rendition{
- Borders: tw.BorderNone,
- Symbols: tw.NewSymbols(tw.StyleNone),
- Settings: tw.Settings{},
- Streaming: false,
- }
-}
-
-// debugLog appends a formatted message to the debug trace if debugging is enabled.
-// func (h *HTML) debugLog(format string, a ...interface{}) {
-// if h.debug {
-// msg := fmt.Sprintf(format, a...)
-// h.trace = append(h.trace, fmt.Sprintf("[HTML] %s", msg))
-// }
-//}
-
-// Debug returns the accumulated debug trace messages.
-func (h *HTML) Debug() []string {
- return h.trace
-}
-
-// Start begins the HTML table rendering by opening the