diff --git a/go.mod b/go.mod index 05991166..25c56b3e 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ require ( github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df github.com/go-playground/validator/v10 v10.28.0 github.com/hashicorp/nomad/api v0.0.0-20250812204832-62b195aaa535 // v1.10.4 - github.com/jedib0t/go-pretty/v6 v6.6.8 + github.com/jedib0t/go-pretty/v6 v6.7.2 github.com/matcornic/hermes/v2 v2.1.0 github.com/microcosm-cc/bluemonday v1.0.27 github.com/moby/buildkit v0.25.2 diff --git a/go.sum b/go.sum index 6e706984..2cd56dab 100644 --- a/go.sum +++ b/go.sum @@ -191,8 +191,8 @@ github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/jaytaylor/html2text v0.0.0-20180606194806-57d518f124b0 h1:xqgexXAGQgY3HAjNPSaCqn5Aahbo5TKsmhp8VRfr1iQ= github.com/jaytaylor/html2text v0.0.0-20180606194806-57d518f124b0/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk= -github.com/jedib0t/go-pretty/v6 v6.6.8 h1:JnnzQeRz2bACBobIaa/r+nqjvws4yEhcmaZ4n1QzsEc= -github.com/jedib0t/go-pretty/v6 v6.6.8/go.mod h1:YwC5CE4fJ1HFUDeivSV1r//AmANFHyqczZk+U6BDALU= +github.com/jedib0t/go-pretty/v6 v6.7.2 h1:EYWgQNIH/+JsyHki7ns9OHyBKuHPkzrBo02uYjran7w= +github.com/jedib0t/go-pretty/v6 v6.7.2/go.mod h1:YwC5CE4fJ1HFUDeivSV1r//AmANFHyqczZk+U6BDALU= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= diff --git a/vendor/github.com/jedib0t/go-pretty/v6/table/README.md b/vendor/github.com/jedib0t/go-pretty/v6/table/README.md index 3375bd3c..bf9df7fe 100644 --- a/vendor/github.com/jedib0t/go-pretty/v6/table/README.md +++ b/vendor/github.com/jedib0t/go-pretty/v6/table/README.md @@ -3,42 +3,7 @@ Pretty-print tables into ASCII/Unicode strings. - - Add Rows one-by-one or as a group (`AppendRow`/`AppendRows`) - - Add Header(s) and Footer(s) (`AppendHeader`/`AppendFooter`) - - Add a Separator manually after any Row (`AppendSeparator`) - - Auto Index Rows (1, 2, 3 ...) and Columns (A, B, C, ...) (`SetAutoIndex`) - - Auto Merge (_not supported in CSV/Markdown/TSV modes_) - - Cells in a Row (`RowConfig.AutoMerge`) - - Columns (`ColumnConfig.AutoMerge`) (_not supported in HTML mode_) - - Limit the length of - - Rows (`SetAllowedRowLength`) - - Columns (`ColumnConfig.Width*`) - - Auto-size Rows (`Style().Size.WidthMin` and `Style().Size.WidthMax`) - - Page results by a specified number of Lines (`SetPageSize`) - - Alignment - Horizontal & Vertical - - Auto (horizontal) Align (numeric columns aligned Right) - - Custom (horizontal) Align per column (`ColumnConfig.Align*`) - - Custom (vertical) VAlign per column with multi-line cell support (`ColumnConfig.VAlign*`) - - Mirror output to an `io.Writer` (ex. `os.StdOut`) (`SetOutputMirror`) - - Sort by one or more Columns (`SortBy`) - - Suppress/hide columns with no content (`SuppressEmptyColumns`) - - Suppress trailing spaces in the last column (`SupressTrailingSpaces`) - - Customizable Cell rendering per Column (`ColumnConfig.Transformer*`) - - Hide any columns that you don't want displayed (`ColumnConfig.Hidden`) - - Reset Headers/Rows/Footers at will to reuse the same Table Writer (`Reset*`) - - Completely customizable styles (`SetStyle`/`Style`) - - Many ready-to-use styles: [style.go](style.go) - - Colorize Headers/Body/Footers using [../text/color.go](../text/color.go) - - Custom text-case for Headers/Body/Footers - - Enable separators between each row - - Render table without a Border - - and a lot more... - - Render as: - - (ASCII/Unicode) Table - - CSV - - HTML Table (with custom CSS Class) - - Markdown Table - - TSV +## Sample Table Rendering ``` +---------------------------------------------------------------------+ @@ -57,14 +22,112 @@ Pretty-print tables into ASCII/Unicode strings. A demonstration of all the capabilities can be found here: [../cmd/demo-table](../cmd/demo-table) -If you want very specific examples, read ahead. +If you want very specific examples, look at the [Examples](#examples) section. -**Hint**: I've tried to ensure that almost all supported use-cases are covered -by unit-tests and that they print the table rendered. Run -`go test -v github.com/jedib0t/go-pretty/v6/table` to see the test outputs and -help you figure out how to do something. +## Features -# Examples +### Core Table Building + + - Add Rows one-by-one or as a group (`AppendRow`/`AppendRows`) + - Add Header(s) and Footer(s) (`AppendHeader`/`AppendFooter`) + - Add a Separator manually after any Row (`AppendSeparator`) + - Add Title above the table (`SetTitle`) + - Add Caption below the table (`SetCaption`) + - Import 1D or 2D arrays/grids as rows (`ImportGrid`) + - Reset Headers/Rows/Footers at will to reuse the same Table Writer (`Reset*`) + +### Indexing & Navigation + + - Auto Index Rows (1, 2, 3 ...) and Columns (A, B, C, ...) (`SetAutoIndex`) + - Set which column is the index column (`SetIndexColumn`) + - Pager interface for navigating through paged output (`Pager()`) + - `GoTo(pageNum)` - Jump to specific page + - `Next()` - Move to next page + - `Prev()` - Move to previous page + - `Location()` - Get current page number + - `Render()` - Render current page + - `SetOutputMirror()` - Mirror output to io.Writer + +### Auto Merge + + - Auto Merge cells (_not supported in CSV/Markdown/TSV modes_) + - Cells in a Row (`RowConfig.AutoMerge`) + - Columns (`ColumnConfig.AutoMerge`) (_not supported in HTML mode_) + - Custom alignment for merged cells (`RowConfig.AutoMergeAlign`) + +### Size & Width Control + + - Limit the length of Rows (`SetAllowedRowLength` or `Style().Size.WidthMax`) + - Auto-size Rows (`Style().Size.WidthMin` and `Style().Size.WidthMax`) + - Column width control (`ColumnConfig.WidthMin` and `ColumnConfig.WidthMax`) + - Custom width enforcement functions (`ColumnConfig.WidthMaxEnforcer`) + - Default: `text.WrapText` + - Options: `text.WrapSoft`, `text.WrapHard`, `text.Trim`, or custom function + +### Alignment + + - **Horizontal Alignment** + - Auto (numeric columns aligned Right, text aligned Left) + - Custom per column (`ColumnConfig.Align`, `AlignHeader`, `AlignFooter`) + - Options: Left, Center, Right, Justify, Auto + - **Vertical Alignment** + - Custom per column with multi-line cell support (`ColumnConfig.VAlign`, `VAlignHeader`, `VAlignFooter`) + - Options: Top, Middle, Bottom + +### Sorting & Filtering + + - Sort by one or more Columns (`SortBy`) + - Multiple column sorting support + - Various sort modes: alphabetical, numeric, alphanumeric, numeric-alpha + - Case-insensitive sorting option (`IgnoreCase`) + - Custom sorting functions (`CustomLess`) for advanced sorting logic + - Suppress/hide columns with no content (`SuppressEmptyColumns`) + - Hide specific columns (`ColumnConfig.Hidden`) + - Suppress trailing spaces in the last column (`SuppressTrailingSpaces`) + +### Customization & Styling + + - **Row Coloring** + - Custom row painter function (`SetRowPainter`) + - Row painter with attributes (`RowPainterWithAttributes`) + - Access to row number and sorted position + - **Cell Transformation** + - Customizable Cell rendering per Column (`ColumnConfig.Transformer`, `TransformerHeader`, `TransformerFooter`) + - Use built-in transformers from `text` package (Number, JSON, Time, URL, etc.) + - **Column Styling** + - Per-column colors (`ColumnConfig.Colors`, `ColorsHeader`, `ColorsFooter`) + - Per-column alignment (horizontal and vertical) + - Per-column width constraints + - **Completely customizable styles** (`SetStyle`/`Style`) + - Many ready-to-use styles: [style.go](style.go) + - `StyleDefault` - Classic ASCII borders + - `StyleLight` - Light box-drawing characters + - `StyleBold` - Bold box-drawing characters + - `StyleDouble` - Double box-drawing characters + - `StyleRounded` - Rounded box-drawing characters + - `StyleColoredBright` - Bright colors, no borders + - `StyleColoredDark` - Dark colors, no borders + - Many more colored variants (Blue, Cyan, Green, Magenta, Red, Yellow) + - Colorize Headers/Body/Footers using [../text/color.go](../text/color.go) + - Custom text-case for Headers/Body/Footers + - Enable/disable separators between rows + - Render table with or without borders + - Customize box-drawing characters + - Title and caption styling options + - HTML rendering options (CSS class, escaping, newlines) + - Bidirectional text support (`Style().Format.Direction`) + +### Output Formats + + - **Render as:** + - (ASCII/Unicode) Table - Human-readable pretty format + - CSV - Comma-separated values + - HTML Table - With custom CSS Class and options + - Markdown Table - Markdown-compatible format + - TSV - Tab-separated values + - Mirror output to an `io.Writer` (ex. `os.StdOut`) (`SetOutputMirror`) + +## Examples All the examples below are going to start with the following block, although nothing except a single Row is mandatory for the `Render()` function to render @@ -106,7 +169,7 @@ Running the above will result in: +-----+------------+-----------+--------+-----------------------------+ ``` -## Styles +### Styles You can customize almost every single thing about the table above. The previous example just defaulted to `StyleDefault` during `Render()`. You can use a @@ -143,7 +206,7 @@ to get: Colored Table -### Roll your own Style +#### Roll your own Style You can also roll your own style: ```golang @@ -198,7 +261,7 @@ Or you can use one of the ready-to-use Styles, and just make a few tweaks: t.Style().Options.DrawBorder = false ``` -## Auto-Merge +### Auto-Merge You can auto-merge cells horizontally and vertically, but you have request for it specifically for each row/column using `RowConfig` or `ColumnConfig`. @@ -255,10 +318,22 @@ to get: └───┴─────────┴────────┴───────────┴───────────┴─────┴─────┘ ``` -## Paging +### Paging -You can limit then number of lines rendered in a single "Page". This logic -can handle rows with multiple lines too. Here is a simple example: +You can limit the number of lines rendered in a single "Page". This logic +can handle rows with multiple lines too. The recommended way is to use the +`Pager()` interface: + +```golang + pager := t.Pager(PageSize(1)) + pager.Render() // Render first page + pager.Next() // Move to next page and render + pager.Prev() // Move to previous page and render + pager.GoTo(3) // Jump to page 3 + pager.Location() // Get current page number +``` + +Or use the deprecated `SetPageSize()` method for simple cases: ```golang t.SetPageSize(1) t.Render() @@ -290,7 +365,7 @@ to get: +-----+------------+-----------+--------+-----------------------------+ ``` -## Sorting +### Sorting Sorting can be done on one or more columns. The following code will make the rows be sorted first by "First Name" and then by "Last Name" (in case of similar @@ -302,7 +377,124 @@ rows be sorted first by "First Name" and then by "Last Name" (in case of similar }) ``` -## Wrapping (or) Row/Column Width restrictions +#### Sort Modes + +The `Mode` field in `SortBy` supports various sorting modes: +- `Asc` / `Dsc` - Alphabetical ascending/descending +- `AscNumeric` / `DscNumeric` - Numerical ascending/descending +- `AscAlphaNumeric` / `DscAlphaNumeric` - Alphabetical first, then numerical +- `AscNumericAlpha` / `DscNumericAlpha` - Numerical first, then alphabetical + +You can also make sorting case-insensitive by setting `IgnoreCase: true`: +```golang + t.SortBy([]table.SortBy{ + {Name: "First Name", Mode: table.Asc, IgnoreCase: true}, + }) +``` + +#### Custom Sorting + +For advanced sorting requirements, you can provide a custom comparison function +using `CustomLess`. This function overrides the `Mode` and `IgnoreCase` settings +and gives you full control over the sorting logic. + +The `CustomLess` function receives two string values (the cell contents converted +to strings) and must return: +- `-1` when the first value should come before the second +- `0` when the values are considered equal (sorting continues to the next column) +- `1` when the first value should come after the second + +
+Example: Custom numeric sorting that handles string numbers correctly + +```golang + t.SortBy([]table.SortBy{ + { + Number: 1, + CustomLess: func(iStr string, jStr string) int { + iNum, iErr := strconv.Atoi(iStr) + jNum, jErr := strconv.Atoi(jStr) + if iErr != nil || jErr != nil { + // Fallback to string comparison if not numeric + if iStr < jStr { + return -1 + } + if iStr > jStr { + return 1 + } + return 0 + } + if iNum < jNum { + return -1 + } + if iNum > jNum { + return 1 + } + return 0 + }, + }, + }) +``` + +
+ +
+Example: Custom case-insensitive sorting with fallback to case-sensitive + +```golang + t.SortBy([]table.SortBy{ + { + Name: "Name", + CustomLess: func(iStr string, jStr string) int { + iLower := strings.ToLower(iStr) + jLower := strings.ToLower(jStr) + if iLower < jLower { + return -1 + } + if iLower > jLower { + return 1 + } + // If case-insensitive equal, compare case-sensitive + if iStr < jStr { + return -1 + } + if iStr > jStr { + return 1 + } + return 0 + }, + }, + }) +``` + +
+ +
+Example: Combining custom sorting with default sorting modes + +```golang + t.SortBy([]table.SortBy{ + { + Number: 1, + CustomLess: func(iStr string, jStr string) int { + // Custom logic: "same" values come first + if iStr == "same" && jStr != "same" { + return -1 + } + if iStr != "same" && jStr == "same" { + return 1 + } + return 0 // Equal, continue to next column + }, + }, + {Number: 2, Mode: table.Asc}, // Default alphabetical sort + {Number: 3, Mode: table.AscNumeric}, // Default numeric sort + }) +``` + +
+ +### Wrapping (or) Row/Column Width restrictions You can restrict the maximum (text) width for a Row: ```golang @@ -323,7 +515,7 @@ to get: +-----+------------+-----------+--------+------- ~ ``` -## Column Control - Alignment, Colors, Width and more +### Column Control - Alignment, Colors, Width and more You can control a lot of things about individual cells/columns which overrides global properties/styles using the `SetColumnConfig()` interface: @@ -360,11 +552,11 @@ global properties/styles using the `SetColumnConfig()` interface: }) ``` -## Render As ... +### Render As ... Tables can be rendered in other common formats such as: -### ... CSV +#### ... CSV ```golang t.RenderCSV() @@ -378,7 +570,7 @@ to get: ,,Total,10000, ``` -### ... HTML Table +#### ... HTML Table ```golang t.Style().HTML = table.HTMLOptions{ @@ -436,7 +628,7 @@ to get: ``` -### ... Markdown Table +#### ... Markdown Table ```golang t.RenderMarkdown() diff --git a/vendor/github.com/jedib0t/go-pretty/v6/table/render.go b/vendor/github.com/jedib0t/go-pretty/v6/table/render.go index 4ef68f99..80d84dac 100644 --- a/vendor/github.com/jedib0t/go-pretty/v6/table/render.go +++ b/vendor/github.com/jedib0t/go-pretty/v6/table/render.go @@ -239,7 +239,7 @@ func (t *Table) renderLineMergeOutputs(out *strings.Builder, outLine *strings.Bu } func (t *Table) renderMarginLeft(out *strings.Builder, hint renderHint) { - out.WriteString(t.style.Format.Direction.Modifier()) + out.WriteString(t.directionModifier) if t.style.Options.DrawBorder { border := t.getBorderLeft(hint) colors := t.getBorderColors(hint) diff --git a/vendor/github.com/jedib0t/go-pretty/v6/table/render_init.go b/vendor/github.com/jedib0t/go-pretty/v6/table/render_init.go index 333c3988..f4c58170 100644 --- a/vendor/github.com/jedib0t/go-pretty/v6/table/render_init.go +++ b/vendor/github.com/jedib0t/go-pretty/v6/table/render_init.go @@ -43,11 +43,15 @@ func (t *Table) analyzeAndStringifyColumn(colIdx int, col interface{}, hint rend } else if colStrVal, ok := col.(string); ok { colStr = colStrVal } else { - colStr = fmt.Sprint(col) + colStr = convertValueToString(col) } colStr = strings.ReplaceAll(colStr, "\t", " ") colStr = text.ProcessCRLF(colStr) - return fmt.Sprintf("%s%s", t.style.Format.Direction.Modifier(), colStr) + // Avoid fmt.Sprintf when direction modifier is empty (most common case) + if t.directionModifier == "" { + return colStr + } + return t.directionModifier + colStr } func (t *Table) extractMaxColumnLengths(rows []rowStr, hint renderHint) { @@ -156,6 +160,9 @@ func (t *Table) initForRender() { // reset rendering state t.reset() + // cache the direction modifier to avoid repeated calls + t.directionModifier = t.style.Format.Direction.Modifier() + // initialize the column configs and normalize them t.initForRenderColumnConfigs() diff --git a/vendor/github.com/jedib0t/go-pretty/v6/table/sort.go b/vendor/github.com/jedib0t/go-pretty/v6/table/sort.go index 7a47765a..dd1d2175 100644 --- a/vendor/github.com/jedib0t/go-pretty/v6/table/sort.go +++ b/vendor/github.com/jedib0t/go-pretty/v6/table/sort.go @@ -21,6 +21,18 @@ type SortBy struct { // IgnoreCase makes sorting case-insensitive IgnoreCase bool + + // CustomLess is a function that can be used to sort the column in a custom + // manner. Note that: + // * This overrides and ignores the Mode and IgnoreCase settings + // * This is called after the column contents are converted to string form + // * This function is expected to return: + // * -1 => when iStr comes before jStr + // * 0 => when iStr and jStr are considered equal + // * 1 => when iStr comes after jStr + // + // Use this when the default sorting logic is not sufficient. + CustomLess func(iStr string, jStr string) int } // SortMode defines How to sort. @@ -49,12 +61,6 @@ const ( DscNumericAlpha ) -type rowsSorter struct { - rows []rowStr - sortBy []SortBy - sortedIndices []int -} - // getSortedRowIndices sorts and returns the row indices in Sorted order as // directed by Table.sortBy which can be set using Table.SortBy(...) func (t *Table) getSortedRowIndices() []int { @@ -63,11 +69,31 @@ func (t *Table) getSortedRowIndices() []int { sortedIndices[idx] = idx } - if t.sortBy != nil && len(t.sortBy) > 0 { - sort.Sort(rowsSorter{ - rows: t.rows, - sortBy: t.parseSortBy(t.sortBy), - sortedIndices: sortedIndices, + if len(t.sortBy) > 0 { + parsedSortBy := t.parseSortBy(t.sortBy) + sort.Slice(sortedIndices, func(i, j int) bool { + isEqual, isLess := false, false + realI, realJ := sortedIndices[i], sortedIndices[j] + for _, sortBy := range parsedSortBy { + // extract the values/cells from the rows for comparison + rowI, rowJ, colIdx := t.rows[realI], t.rows[realJ], sortBy.Number-1 + iVal, jVal := "", "" + if colIdx < len(rowI) { + iVal = rowI[colIdx] + } + if colIdx < len(rowJ) { + jVal = rowJ[colIdx] + } + + // compare and choose whether to continue + isEqual, isLess = less(iVal, jVal, sortBy) + // if the values are not equal, return the result immediately + if !isEqual { + return isLess + } + // if the values are equal, continue to the next column + } + return isLess }) } @@ -94,48 +120,32 @@ func (t *Table) parseSortBy(sortBy []SortBy) []SortBy { Number: colNum, Mode: col.Mode, IgnoreCase: col.IgnoreCase, + CustomLess: col.CustomLess, }) } } return resSortBy } -func (rs rowsSorter) Len() int { - return len(rs.rows) -} - -func (rs rowsSorter) Swap(i, j int) { - rs.sortedIndices[i], rs.sortedIndices[j] = rs.sortedIndices[j], rs.sortedIndices[i] -} - -func (rs rowsSorter) Less(i, j int) bool { - shouldContinue, returnValue := false, false - realI, realJ := rs.sortedIndices[i], rs.sortedIndices[j] - for _, sortBy := range rs.sortBy { - // extract the values/cells from the rows for comparison - rowI, rowJ, colIdx := rs.rows[realI], rs.rows[realJ], sortBy.Number-1 - iVal, jVal := "", "" - if colIdx < len(rowI) { - iVal = rowI[colIdx] - } - if colIdx < len(rowJ) { - jVal = rowJ[colIdx] - } - - // compare and choose whether to continue - shouldContinue, returnValue = less(iVal, jVal, sortBy) - if !shouldContinue { - break +func less(iVal string, jVal string, sb SortBy) (bool, bool) { + if sb.CustomLess != nil { + // use the custom less function to compare the values + rc := sb.CustomLess(iVal, jVal) + if rc < 0 { + return false, true + } else if rc > 0 { + return false, false + } else { // rc == 0 + return true, false } } - return returnValue -} -func less(iVal string, jVal string, sb SortBy) (bool, bool) { + // if the values are equal, return fast to continue to next column if iVal == jVal { return true, false } + // otherwise, use the default sorting logic defined by Mode and IgnoreCase switch sb.Mode { case Asc, Dsc: return lessAlphabetic(iVal, jVal, sb) @@ -168,37 +178,27 @@ func lessAlphabetic(iVal string, jVal string, sb SortBy) (bool, bool) { } } -func lessAlphaNumericI(sb SortBy) (bool, bool) { - // i == "abc"; j == 5 - switch sb.Mode { - case AscAlphaNumeric, DscAlphaNumeric: - return false, true - default: // AscNumericAlpha, DscNumericAlpha - return false, false - } -} - -func lessAlphaNumericJ(sb SortBy) (bool, bool) { - // i == 5; j == "abc" - switch sb.Mode { - case AscAlphaNumeric, DscAlphaNumeric: - return false, false - default: // AscNumericAlpha, DscNumericAlpha: - return false, true - } -} - func lessMixedMode(iVal string, jVal string, sb SortBy) (bool, bool) { iNumVal, iErr := strconv.ParseFloat(iVal, 64) jNumVal, jErr := strconv.ParseFloat(jVal, 64) if iErr != nil && jErr != nil { // both are alphanumeric return lessAlphabetic(iVal, jVal, sb) } - if iErr != nil { // iVal is alphabetic, jVal is numeric - return lessAlphaNumericI(sb) + if iErr != nil { // iVal == "abc"; jVal == 5 + switch sb.Mode { + case AscAlphaNumeric, DscAlphaNumeric: + return false, true + default: // AscNumericAlpha, DscNumericAlpha + return false, false + } } - if jErr != nil { // iVal is numeric, jVal is alphabetic - return lessAlphaNumericJ(sb) + if jErr != nil { // iVal == 5; jVal == "abc" + switch sb.Mode { + case AscAlphaNumeric, DscAlphaNumeric: + return false, false + default: // AscNumericAlpha, DscNumericAlpha: + return false, true + } } // both values numeric return lessNumericVal(iNumVal, jNumVal, sb) diff --git a/vendor/github.com/jedib0t/go-pretty/v6/table/table.go b/vendor/github.com/jedib0t/go-pretty/v6/table/table.go index 191dd771..c4127d44 100644 --- a/vendor/github.com/jedib0t/go-pretty/v6/table/table.go +++ b/vendor/github.com/jedib0t/go-pretty/v6/table/table.go @@ -28,6 +28,8 @@ type Table struct { // columnConfigMap stores the custom-configuration by column // number and is generated before rendering columnConfigMap map[int]ColumnConfig + // directionModifier caches the direction modifier string to avoid repeated calls + directionModifier string // firstRowOfPage tells if the renderer is on the first row of a page? firstRowOfPage bool // htmlCSSClass stores the HTML CSS Class to use on the node @@ -305,12 +307,12 @@ func (t *Table) SetRowPainter(painter interface{}) { t.rowPainterWithAttributes = nil // if called as SetRowPainter(RowPainter(func...)) - switch painter.(type) { + switch p := painter.(type) { case RowPainter: - t.rowPainter = painter.(RowPainter) + t.rowPainter = p return case RowPainterWithAttributes: - t.rowPainterWithAttributes = painter.(RowPainterWithAttributes) + t.rowPainterWithAttributes = p return } diff --git a/vendor/github.com/jedib0t/go-pretty/v6/table/util.go b/vendor/github.com/jedib0t/go-pretty/v6/table/util.go index 4636e881..b4179a20 100644 --- a/vendor/github.com/jedib0t/go-pretty/v6/table/util.go +++ b/vendor/github.com/jedib0t/go-pretty/v6/table/util.go @@ -1,8 +1,10 @@ package table import ( + "fmt" "reflect" "sort" + "strconv" ) // AutoIndexColumnID returns a unique Column ID/Name for the given Column Number. @@ -26,6 +28,48 @@ func widthEnforcerNone(col string, _ int) string { return col } +// convertValueToString converts a value to string using fast type assertions +// for common numeric types before falling back to fmt.Sprint. +// +//gocyclo:ignore +func convertValueToString(v interface{}) string { + switch val := v.(type) { + case int: + return strconv.FormatInt(int64(val), 10) + case int8: + return strconv.FormatInt(int64(val), 10) + case int16: + return strconv.FormatInt(int64(val), 10) + case int32: + return strconv.FormatInt(int64(val), 10) + case int64: + return strconv.FormatInt(val, 10) + case uint: + return strconv.FormatUint(uint64(val), 10) + case uint8: + return strconv.FormatUint(uint64(val), 10) + case uint16: + return strconv.FormatUint(uint64(val), 10) + case uint32: + return strconv.FormatUint(uint64(val), 10) + case uint64: + return strconv.FormatUint(val, 10) + case float32: + return strconv.FormatFloat(float64(val), 'g', -1, 32) + case float64: + return strconv.FormatFloat(val, 'g', -1, 64) + case bool: + if val { + return "true" + } + return "false" + case string: + return val + default: + return fmt.Sprint(v) + } +} + // isNumber returns true if the argument is a numeric type; false otherwise. func isNumber(x interface{}) bool { if x == nil { diff --git a/vendor/github.com/jedib0t/go-pretty/v6/text/README.md b/vendor/github.com/jedib0t/go-pretty/v6/text/README.md index afd163af..2b240dec 100644 --- a/vendor/github.com/jedib0t/go-pretty/v6/text/README.md +++ b/vendor/github.com/jedib0t/go-pretty/v6/text/README.md @@ -1,8 +1,132 @@ -# text +# Text +[![Go Reference](https://pkg.go.dev/badge/github.com/jedib0t/go-pretty/v6/text.svg)](https://pkg.go.dev/github.com/jedib0t/go-pretty/v6/text) -[![Go Reference](https://pkg.go.dev/badge/github.com/jedib0t/go-pretty/v6.svg)](https://pkg.go.dev/github.com/jedib0t/go-pretty/v6/text) - -Package with utility functions to manipulate strings/text. +Package with utility functions to manipulate strings/text with full support for +ANSI escape sequences (colors, formatting, etc.). Used heavily in the other packages in this repo ([list](../list), -[progress](../progress), and [table](../table)). \ No newline at end of file +[progress](../progress), and [table](../table)). + +## Features + +### Colors & Formatting + + - **ANSI Color Support** - Full support for terminal colors and formatting + - Foreground colors (Black, Red, Green, Yellow, Blue, Magenta, Cyan, White) + - Background colors (matching foreground set) + - Hi-intensity variants for both foreground and background + - Text attributes (Bold, Faint, Italic, Underline, Blink, Reverse, Concealed, CrossedOut) + - Automatic color detection based on environment variables (`NO_COLOR`, `FORCE_COLOR`, `TERM`) + - Global enable/disable functions for colors + - Cached escape sequences for performance + - **Text Formatting** - Transform text while preserving escape sequences + - `FormatDefault` - No transformation + - `FormatLower` - Convert to lowercase + - `FormatTitle` - Convert to title case + - `FormatUpper` - Convert to uppercase + - **HTML Support** - Generate HTML class attributes for colors + - **Color Combinations** - Combine multiple colors and attributes + +### Alignment + + - **Horizontal Alignment** + - `AlignDefault` / `AlignLeft` - Left-align text + - `AlignCenter` - Center-align text + - `AlignRight` - Right-align text + - `AlignJustify` - Justify text (distribute spaces between words) + - `AlignAuto` - Auto-detect: right-align numbers, left-align text + - HTML and Markdown property generation for alignment + - **Vertical Alignment** + - `VAlignTop` - Align to top + - `VAlignMiddle` - Align to middle + - `VAlignBottom` - Align to bottom + - Works with both string arrays and multi-line strings + - HTML property generation for vertical alignment + +### Text Wrapping + + - **WrapHard** - Hard wrap at specified length, breaks words if needed + - Handles ANSI escape sequences without breaking formatting + - Preserves paragraph breaks + - **WrapSoft** - Soft wrap at specified length, tries to keep words intact + - Handles ANSI escape sequences without breaking formatting + - Preserves paragraph breaks + - **WrapText** - Similar to WrapHard but also respects line breaks + - Handles ANSI escape sequences without breaking formatting + +### String Utilities + + - **Width Calculation** + - `StringWidth` - Calculate display width of string (including escape sequences) + - `StringWidthWithoutEscSequences` - Calculate display width ignoring escape sequences + - `RuneWidth` - Calculate display width of a single rune (handles East Asian characters) + - `LongestLineLen` - Find the longest line in a multi-line string + - **String Manipulation** + - `Trim` - Trim string to specified length while preserving escape sequences + - `Pad` - Pad string to specified length with a character + - `Snip` - Snip string to specified length with an indicator (e.g., "~") + - `RepeatAndTrim` - Repeat string until it reaches specified length + - `InsertEveryN` - Insert a character every N characters + - `ProcessCRLF` - Process carriage returns and line feeds correctly + - `Widen` - Convert half-width characters to full-width + - **Escape Sequence Handling** + - All functions properly handle ANSI escape sequences + - Escape sequences are preserved during transformations + - Width calculations ignore escape sequences + +### Cursor Control + + - Move cursor in all directions + - `CursorUp` - Move cursor up N lines + - `CursorDown` - Move cursor down N lines + - `CursorLeft` - Move cursor left N characters + - `CursorRight` - Move cursor right N characters + - `EraseLine` - Erase all characters to the right of cursor + - Generate ANSI escape sequences for terminal cursor manipulation + +### Hyperlinks + + - **Terminal Hyperlinks** - Create clickable hyperlinks in supported terminals + - Uses OSC 8 escape sequences + - Format: `Hyperlink(url, text)` + - Falls back to plain text in unsupported terminals + +### Transformers + + - **Number Transformer** - Format numbers with colors + - Positive numbers colored green + - Negative numbers colored red + - Custom format string support (e.g., `%.2f`) + - Supports all numeric types (int, uint, float) + - **JSON Transformer** - Pretty-print JSON strings or objects + - Customizable indentation (prefix and indent string) + - Validates JSON before formatting + - **Time Transformer** - Format time.Time objects + - Custom layout support (e.g., `time.RFC3339`) + - Timezone localization support + - Auto-detects common time formats from strings + - **Unix Time Transformer** - Format Unix timestamps + - Handles seconds, milliseconds, microseconds, and nanoseconds + - Auto-detects timestamp unit based on value + - Timezone localization support + - **URL Transformer** - Format URLs with styling + - Underlined and colored blue by default + - Custom color support + +### Text Direction + + - **Bidirectional Text Support** + - `LeftToRight` - Force left-to-right text direction + - `RightToLeft` - Force right-to-left text direction + - Uses Unicode directional markers + +### Filtering + + - **String Filtering** - Filter string slices with custom functions + - `Filter(slice, predicate)` - Returns filtered slice + +### East Asian Character Support + + - Proper width calculation for East Asian characters (full-width, half-width) + - Configurable East Asian width handling via `OverrideRuneWidthEastAsianWidth()` + - Handles mixed character sets correctly \ No newline at end of file diff --git a/vendor/github.com/jedib0t/go-pretty/v6/text/color.go b/vendor/github.com/jedib0t/go-pretty/v6/text/color.go index 4154111f..264e43ac 100644 --- a/vendor/github.com/jedib0t/go-pretty/v6/text/color.go +++ b/vendor/github.com/jedib0t/go-pretty/v6/text/color.go @@ -27,7 +27,11 @@ func areColorsOnInTheEnv() bool { if os.Getenv("FORCE_COLOR") == "1" { return true } - return os.Getenv("NO_COLOR") == "" || os.Getenv("NO_COLOR") == "0" + if os.Getenv("NO_COLOR") == "" || os.Getenv("NO_COLOR") == "0" { + return os.Getenv("TERM") != "dumb" + } + + return false } // The logic here is inspired from github.com/fatih/color; the following is diff --git a/vendor/github.com/jedib0t/go-pretty/v6/text/transformer.go b/vendor/github.com/jedib0t/go-pretty/v6/text/transformer.go index 193a721c..ef122272 100644 --- a/vendor/github.com/jedib0t/go-pretty/v6/text/transformer.go +++ b/vendor/github.com/jedib0t/go-pretty/v6/text/transformer.go @@ -11,9 +11,15 @@ import ( // Transformer related constants const ( - unixTimeMinMilliseconds = int64(10000000000) - unixTimeMinMicroseconds = unixTimeMinMilliseconds * 1000 - unixTimeMinNanoSeconds = unixTimeMinMicroseconds * 1000 + // Pre-computed time conversion constants to avoid repeated calculations + nanosPerSecond = int64(time.Second) + microsPerSecond = nanosPerSecond / 1000 + millisPerSecond = nanosPerSecond / 1000000 + + // Thresholds for detecting unix timestamp units (10 seconds worth in each unit) + unixTimeMinMilliseconds = 10 * nanosPerSecond + unixTimeMinMicroseconds = 10 * nanosPerSecond * 1000 + unixTimeMinNanoSeconds = 10 * nanosPerSecond * 1000000 ) // Transformer related variables @@ -40,103 +46,84 @@ type Transformer func(val interface{}) string // - transforms the number as directed by 'format' (ex.: %.2f) // - colors negative values Red // - colors positive values Green +// +//gocyclo:ignore func NewNumberTransformer(format string) Transformer { + // Pre-compute negative format string to avoid repeated allocations + negFormat := "-" + format + + transformInt64 := func(val int64) string { + if val < 0 { + return colorsNumberNegative.Sprintf(negFormat, -val) + } + if val > 0 { + return colorsNumberPositive.Sprintf(format, val) + } + return colorsNumberZero.Sprintf(format, val) + } + + transformUint64 := func(val uint64) string { + if val > 0 { + return colorsNumberPositive.Sprintf(format, val) + } + return colorsNumberZero.Sprintf(format, val) + } + + transformFloat64 := func(val float64) string { + if val < 0 { + return colorsNumberNegative.Sprintf(negFormat, -val) + } + if val > 0 { + return colorsNumberPositive.Sprintf(format, val) + } + return colorsNumberZero.Sprintf(format, val) + } + + // Use type switch for O(1) type checking instead of sequential type assertions return func(val interface{}) string { - if valStr := transformInt(format, val); valStr != "" { - return valStr + switch v := val.(type) { + case int: + return transformInt64(int64(v)) + case int8: + return transformInt64(int64(v)) + case int16: + return transformInt64(int64(v)) + case int32: + return transformInt64(int64(v)) + case int64: + return transformInt64(v) + case uint: + return transformUint64(uint64(v)) + case uint8: + return transformUint64(uint64(v)) + case uint16: + return transformUint64(uint64(v)) + case uint32: + return transformUint64(uint64(v)) + case uint64: + return transformUint64(v) + case float32: + return transformFloat64(float64(v)) + case float64: + return transformFloat64(v) + default: + return fmt.Sprint(val) } - if valStr := transformUint(format, val); valStr != "" { - return valStr - } - if valStr := transformFloat(format, val); valStr != "" { - return valStr - } - return fmt.Sprint(val) } } -func transformInt(format string, val interface{}) string { - transform := func(val int64) string { - if val < 0 { - return colorsNumberNegative.Sprintf("-"+format, -val) - } - if val > 0 { - return colorsNumberPositive.Sprintf(format, val) - } - return colorsNumberZero.Sprintf(format, val) - } - - if number, ok := val.(int); ok { - return transform(int64(number)) - } - if number, ok := val.(int8); ok { - return transform(int64(number)) - } - if number, ok := val.(int16); ok { - return transform(int64(number)) - } - if number, ok := val.(int32); ok { - return transform(int64(number)) - } - if number, ok := val.(int64); ok { - return transform(number) - } - return "" -} - -func transformUint(format string, val interface{}) string { - transform := func(val uint64) string { - if val > 0 { - return colorsNumberPositive.Sprintf(format, val) - } - return colorsNumberZero.Sprintf(format, val) - } - - if number, ok := val.(uint); ok { - return transform(uint64(number)) - } - if number, ok := val.(uint8); ok { - return transform(uint64(number)) - } - if number, ok := val.(uint16); ok { - return transform(uint64(number)) - } - if number, ok := val.(uint32); ok { - return transform(uint64(number)) - } - if number, ok := val.(uint64); ok { - return transform(number) - } - return "" -} - -func transformFloat(format string, val interface{}) string { - transform := func(val float64) string { - if val < 0 { - return colorsNumberNegative.Sprintf("-"+format, -val) - } - if val > 0 { - return colorsNumberPositive.Sprintf(format, val) - } - return colorsNumberZero.Sprintf(format, val) - } - - if number, ok := val.(float32); ok { - return transform(float64(number)) - } - if number, ok := val.(float64); ok { - return transform(number) - } - return "" -} - // NewJSONTransformer returns a Transformer that can format a JSON string or an // object into pretty-indented JSON-strings. func NewJSONTransformer(prefix string, indent string) Transformer { return func(val interface{}) string { if valStr, ok := val.(string); ok { + valStr = strings.TrimSpace(valStr) + // Validate JSON before attempting to indent to avoid unnecessary processing + if !json.Valid([]byte(valStr)) { + return fmt.Sprintf("%#v", valStr) + } var b bytes.Buffer - if err := json.Indent(&b, []byte(strings.TrimSpace(valStr)), prefix, indent); err == nil { + if err := json.Indent(&b, []byte(valStr), prefix, indent); err == nil { return b.String() } } else if b, err := json.MarshalIndent(val, prefix, indent); err == nil { @@ -154,17 +141,17 @@ func NewJSONTransformer(prefix string, indent string) Transformer { // location (use time.Local to get localized timestamps). func NewTimeTransformer(layout string, location *time.Location) Transformer { return func(val interface{}) string { - rsp := fmt.Sprint(val) + // Check for time.Time first to avoid unnecessary fmt.Sprint conversion if valTime, ok := val.(time.Time); ok { - rsp = formatTime(valTime, layout, location) - } else { - // cycle through some supported layouts to see if the string form - // of the object matches any of these layouts - for _, possibleTimeLayout := range possibleTimeLayouts { - if valTime, err := time.Parse(possibleTimeLayout, rsp); err == nil { - rsp = formatTime(valTime, layout, location) - break - } + return formatTime(valTime, layout, location) + } + // Only convert to string if not already time.Time + rsp := fmt.Sprint(val) + // Cycle through some supported layouts to see if the string form + // of the object matches any of these layouts + for _, possibleTimeLayout := range possibleTimeLayouts { + if valTime, err := time.Parse(possibleTimeLayout, rsp); err == nil { + return formatTime(valTime, layout, location) } } return rsp @@ -217,12 +204,13 @@ func formatTime(t time.Time, layout string, location *time.Location) string { } func formatTimeUnix(unixTime int64, timeTransformer Transformer) string { + // Use pre-computed constants instead of repeated time.Second.Nanoseconds() calls if unixTime >= unixTimeMinNanoSeconds { - unixTime = unixTime / time.Second.Nanoseconds() + unixTime = unixTime / nanosPerSecond } else if unixTime >= unixTimeMinMicroseconds { - unixTime = unixTime / (time.Second.Nanoseconds() / 1000) + unixTime = unixTime / microsPerSecond } else if unixTime >= unixTimeMinMilliseconds { - unixTime = unixTime / (time.Second.Nanoseconds() / 1000000) + unixTime = unixTime / millisPerSecond } return timeTransformer(time.Unix(unixTime, 0)) } diff --git a/vendor/modules.txt b/vendor/modules.txt index 82346929..ae823151 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -254,7 +254,7 @@ github.com/imdario/mergo # github.com/jaytaylor/html2text v0.0.0-20180606194806-57d518f124b0 ## explicit github.com/jaytaylor/html2text -# github.com/jedib0t/go-pretty/v6 v6.6.8 +# github.com/jedib0t/go-pretty/v6 v6.7.2 ## explicit; go 1.18 github.com/jedib0t/go-pretty/v6/table github.com/jedib0t/go-pretty/v6/text