mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-04-12 19:38:08 -04:00
build(deps): bump github.com/olekukonko/tablewriter from 1.0.7 to 1.0.8
Bumps [github.com/olekukonko/tablewriter](https://github.com/olekukonko/tablewriter) from 1.0.7 to 1.0.8. - [Commits](https://github.com/olekukonko/tablewriter/compare/v1.0.7...v1.0.8) --- updated-dependencies: - dependency-name: github.com/olekukonko/tablewriter dependency-version: 1.0.8 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
2
go.mod
2
go.mod
@@ -58,7 +58,7 @@ require (
|
||||
github.com/nats-io/nats-server/v2 v2.11.6
|
||||
github.com/nats-io/nats.go v1.43.0
|
||||
github.com/oklog/run v1.2.0
|
||||
github.com/olekukonko/tablewriter v1.0.7
|
||||
github.com/olekukonko/tablewriter v1.0.8
|
||||
github.com/onsi/ginkgo v1.16.5
|
||||
github.com/onsi/ginkgo/v2 v2.23.4
|
||||
github.com/onsi/gomega v1.37.0
|
||||
|
||||
4
go.sum
4
go.sum
@@ -848,8 +848,8 @@ github.com/olekukonko/errors v0.0.0-20250405072817-4e6d85265da6/go.mod h1:ppzxA5
|
||||
github.com/olekukonko/ll v0.0.8 h1:sbGZ1Fx4QxJXEqL/6IG8GEFnYojUSQ45dJVwN2FH2fc=
|
||||
github.com/olekukonko/ll v0.0.8/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g=
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
github.com/olekukonko/tablewriter v1.0.7 h1:HCC2e3MM+2g72M81ZcJU11uciw6z/p82aEnm4/ySDGw=
|
||||
github.com/olekukonko/tablewriter v1.0.7/go.mod h1:H428M+HzoUXC6JU2Abj9IT9ooRmdq9CxuDmKMtrOCMs=
|
||||
github.com/olekukonko/tablewriter v1.0.8 h1:f6wJzHg4QUtJdvrVPKco4QTrAylgaU0+b9br/lJxEiQ=
|
||||
github.com/olekukonko/tablewriter v1.0.8/go.mod h1:H428M+HzoUXC6JU2Abj9IT9ooRmdq9CxuDmKMtrOCMs=
|
||||
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.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||
|
||||
246
vendor/github.com/olekukonko/tablewriter/MIGRATION.md
generated
vendored
246
vendor/github.com/olekukonko/tablewriter/MIGRATION.md
generated
vendored
@@ -416,6 +416,244 @@ func main() {
|
||||
</table>
|
||||
```
|
||||
|
||||
|
||||
#### 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.
|
||||
@@ -1914,7 +2152,7 @@ func main() {
|
||||
- **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.<Section>.Filter.Global` or `PerColumn` to apply transformations like coloring dynamically (`tw/cell.go:CellFilter`).
|
||||
- **Width Handling**: `tw.DisplayWidth()` correctly calculates display width of ANSI-coded strings, ignoring escape sequences (`tw/fn.go:DisplayWidth`).
|
||||
- **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**:
|
||||
@@ -1928,7 +2166,7 @@ func main() {
|
||||
**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 `tw.DisplayWidth` (`tw/fn.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`).
|
||||
|
||||
@@ -2818,7 +3056,7 @@ func main() {
|
||||
**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; `tw.DisplayWidth` handles ANSI codes correctly (`tw/fn.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`).
|
||||
|
||||
@@ -3005,7 +3243,7 @@ This section addresses common migration issues with detailed solutions, covering
|
||||
| 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 `tw.DisplayWidth` (`tw/fn.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`). |
|
||||
|
||||
37
vendor/github.com/olekukonko/tablewriter/README.md
generated
vendored
37
vendor/github.com/olekukonko/tablewriter/README.md
generated
vendored
@@ -86,6 +86,43 @@ func main() {
|
||||
|
||||
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
|
||||
|
||||
4
vendor/github.com/olekukonko/tablewriter/deprecated.go
generated
vendored
4
vendor/github.com/olekukonko/tablewriter/deprecated.go
generated
vendored
@@ -1,6 +1,8 @@
|
||||
package tablewriter
|
||||
|
||||
import "github.com/olekukonko/tablewriter/tw"
|
||||
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.
|
||||
|
||||
36
vendor/github.com/olekukonko/tablewriter/option.go
generated
vendored
36
vendor/github.com/olekukonko/tablewriter/option.go
generated
vendored
@@ -1,7 +1,9 @@
|
||||
package tablewriter
|
||||
|
||||
import (
|
||||
"github.com/mattn/go-runewidth"
|
||||
"github.com/olekukonko/ll"
|
||||
"github.com/olekukonko/tablewriter/pkg/twwidth"
|
||||
"github.com/olekukonko/tablewriter/tw"
|
||||
"reflect"
|
||||
)
|
||||
@@ -613,6 +615,31 @@ func WithRendition(rendition tw.Rendition) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// 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. .
|
||||
@@ -682,8 +709,11 @@ func defaultConfig() Config {
|
||||
PerColumn: []tw.Align{},
|
||||
},
|
||||
},
|
||||
Stream: tw.StreamConfig{},
|
||||
Debug: false,
|
||||
Stream: tw.StreamConfig{
|
||||
Enable: false,
|
||||
StrictColumns: false,
|
||||
},
|
||||
Debug: false,
|
||||
Behavior: tw.Behavior{
|
||||
AutoHide: tw.Off,
|
||||
TrimSpace: tw.On,
|
||||
@@ -842,6 +872,8 @@ func mergeStreamConfig(dst, src tw.StreamConfig) tw.StreamConfig {
|
||||
if src.Enable {
|
||||
dst.Enable = true
|
||||
}
|
||||
|
||||
dst.StrictColumns = src.StrictColumns
|
||||
return dst
|
||||
}
|
||||
|
||||
|
||||
29
vendor/github.com/olekukonko/tablewriter/pkg/twwarp/wrap.go
generated
vendored
29
vendor/github.com/olekukonko/tablewriter/pkg/twwarp/wrap.go
generated
vendored
@@ -8,12 +8,13 @@
|
||||
package twwarp
|
||||
|
||||
import (
|
||||
"github.com/rivo/uniseg"
|
||||
"math"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/mattn/go-runewidth"
|
||||
"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 (
|
||||
@@ -59,7 +60,8 @@ func WrapString(s string, lim int) ([]string, int) {
|
||||
var lines []string
|
||||
max := 0
|
||||
for _, v := range words {
|
||||
max = runewidth.StringWidth(v)
|
||||
// max = runewidth.StringWidth(v) // OLD
|
||||
max = twwidth.Width(v) // NEW: Use twdw.Width
|
||||
if max > lim {
|
||||
lim = max
|
||||
}
|
||||
@@ -82,12 +84,13 @@ func WrapStringWithSpaces(s string, lim int) ([]string, int) {
|
||||
return []string{""}, lim
|
||||
}
|
||||
if strings.TrimSpace(s) == "" { // All spaces
|
||||
if runewidth.StringWidth(s) <= lim {
|
||||
return []string{s}, runewidth.StringWidth(s)
|
||||
// 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 {
|
||||
// Use our new helper function to get a substring of the correct display width
|
||||
substring, _ := stringToDisplayWidth(s, lim)
|
||||
return []string{substring}, lim
|
||||
}
|
||||
@@ -96,7 +99,6 @@ func WrapStringWithSpaces(s string, lim int) ([]string, int) {
|
||||
|
||||
var leadingSpaces, trailingSpaces, coreContent string
|
||||
firstNonSpace := strings.IndexFunc(s, func(r rune) bool { return !unicode.IsSpace(r) })
|
||||
// firstNonSpace will not be -1 due to TrimSpace check above.
|
||||
leadingSpaces = s[:firstNonSpace]
|
||||
lastNonSpace := strings.LastIndexFunc(s, func(r rune) bool { return !unicode.IsSpace(r) })
|
||||
trailingSpaces = s[lastNonSpace+1:]
|
||||
@@ -116,7 +118,8 @@ func WrapStringWithSpaces(s string, lim int) ([]string, int) {
|
||||
|
||||
maxCoreWordWidth := 0
|
||||
for _, v := range words {
|
||||
w := runewidth.StringWidth(v)
|
||||
// w := runewidth.StringWidth(v) // OLD
|
||||
w := twwidth.Width(v) // NEW: Use twdw.Width
|
||||
if w > maxCoreWordWidth {
|
||||
maxCoreWordWidth = w
|
||||
}
|
||||
@@ -153,15 +156,14 @@ func stringToDisplayWidth(s string, targetWidth int) (substring string, actualWi
|
||||
g := uniseg.NewGraphemes(s)
|
||||
for g.Next() {
|
||||
grapheme := g.Str()
|
||||
graphemeWidth := runewidth.StringWidth(grapheme) // Get width of the current grapheme cluster
|
||||
// graphemeWidth := runewidth.StringWidth(grapheme) // OLD
|
||||
graphemeWidth := twwidth.Width(grapheme) // NEW: Use twdw.Width
|
||||
|
||||
if currentWidth+graphemeWidth > targetWidth {
|
||||
// Adding this grapheme would exceed the target width
|
||||
break
|
||||
}
|
||||
|
||||
currentWidth += graphemeWidth
|
||||
// Get the end byte position of the current grapheme cluster
|
||||
_, e := g.Positions()
|
||||
endIndex = e
|
||||
}
|
||||
@@ -186,14 +188,15 @@ func WrapWords(words []string, spc, lim, pen int) [][]string {
|
||||
}
|
||||
lengths := make([]int, n)
|
||||
for i := 0; i < n; i++ {
|
||||
lengths[i] = runewidth.StringWidth(words[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]
|
||||
remainderLen := lengths[n-1] // Uses updated lengths
|
||||
for i := n - 1; i >= 0; i-- {
|
||||
if i < n-1 {
|
||||
remainderLen += spc + lengths[i]
|
||||
|
||||
321
vendor/github.com/olekukonko/tablewriter/pkg/twwidth/width.go
generated
vendored
Normal file
321
vendor/github.com/olekukonko/tablewriter/pkg/twwidth/width.go
generated
vendored
Normal file
@@ -0,0 +1,321 @@
|
||||
package twwidth
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/mattn/go-runewidth"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// 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 {
|
||||
var regESC = "\x1b" // ASCII escape character
|
||||
var regBEL = "\x07" // ASCII bell character
|
||||
|
||||
// ANSI string terminator: either ESC+\ or BEL
|
||||
var regST = "(" + regexp.QuoteMeta(regESC+"\\") + "|" + regexp.QuoteMeta(regBEL) + ")"
|
||||
// Control Sequence Introducer (CSI): ESC[ followed by parameters and a final byte
|
||||
var regCSI = regexp.QuoteMeta(regESC+"[") + "[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e]"
|
||||
// Operating System Command (OSC): ESC] followed by arbitrary content until a terminator
|
||||
var 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]
|
||||
if introducer == '[' {
|
||||
if seqLen >= 3 && r >= 0x40 && r <= 0x7E {
|
||||
terminated = true
|
||||
}
|
||||
} else if introducer == ']' {
|
||||
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
|
||||
}
|
||||
71
vendor/github.com/olekukonko/tablewriter/renderer/blueprint.go
generated
vendored
71
vendor/github.com/olekukonko/tablewriter/renderer/blueprint.go
generated
vendored
@@ -2,6 +2,7 @@ package renderer
|
||||
|
||||
import (
|
||||
"github.com/olekukonko/ll"
|
||||
"github.com/olekukonko/tablewriter/pkg/twwidth"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
@@ -42,7 +43,7 @@ func NewBlueprint(configs ...tw.Rendition) *Blueprint {
|
||||
// Merge user settings with default settings
|
||||
cfg.Settings = mergeSettings(cfg.Settings, userCfg.Settings)
|
||||
}
|
||||
return &Blueprint{config: cfg}
|
||||
return &Blueprint{config: cfg, logger: ll.New("blueprint")}
|
||||
}
|
||||
|
||||
// Close performs cleanup (no-op in this implementation).
|
||||
@@ -106,7 +107,7 @@ func (f *Blueprint) Line(ctx tw.Formatting) {
|
||||
}
|
||||
if prefix != tw.Empty || suffix != tw.Empty {
|
||||
line.WriteString(prefix + suffix + tw.NewLine)
|
||||
totalLineWidth = tw.DisplayWidth(prefix) + tw.DisplayWidth(suffix)
|
||||
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)
|
||||
@@ -119,13 +120,13 @@ func (f *Blueprint) Line(ctx tw.Formatting) {
|
||||
targetTotalWidth += ctx.Row.Widths.Get(colIdx)
|
||||
}
|
||||
if f.config.Borders.Left.Enabled() {
|
||||
targetTotalWidth += tw.DisplayWidth(f.config.Symbols.Column())
|
||||
targetTotalWidth += twwidth.Width(f.config.Symbols.Column())
|
||||
}
|
||||
if f.config.Borders.Right.Enabled() {
|
||||
targetTotalWidth += tw.DisplayWidth(f.config.Symbols.Column())
|
||||
targetTotalWidth += twwidth.Width(f.config.Symbols.Column())
|
||||
}
|
||||
if f.config.Settings.Separators.BetweenColumns.Enabled() && len(sortedKeys) > 1 {
|
||||
targetTotalWidth += tw.DisplayWidth(f.config.Symbols.Column()) * (len(sortedKeys) - 1)
|
||||
targetTotalWidth += twwidth.Width(f.config.Symbols.Column()) * (len(sortedKeys) - 1)
|
||||
}
|
||||
|
||||
// Add left border if enabled
|
||||
@@ -133,7 +134,7 @@ func (f *Blueprint) Line(ctx tw.Formatting) {
|
||||
if f.config.Borders.Left.Enabled() {
|
||||
leftBorder := jr.RenderLeft()
|
||||
line.WriteString(leftBorder)
|
||||
leftBorderWidth = tw.DisplayWidth(leftBorder)
|
||||
leftBorderWidth = twwidth.Width(leftBorder)
|
||||
totalLineWidth += leftBorderWidth
|
||||
f.logger.Debugf("Line: Left border='%s' (f.width %d)", leftBorder, leftBorderWidth)
|
||||
}
|
||||
@@ -156,11 +157,11 @@ func (f *Blueprint) Line(ctx tw.Formatting) {
|
||||
// Adjust colWidth to account for wider borders
|
||||
adjustedColWidth := colWidth
|
||||
if f.config.Borders.Left.Enabled() && keyIndex == 0 {
|
||||
adjustedColWidth -= leftBorderWidth - tw.DisplayWidth(f.config.Symbols.Column())
|
||||
adjustedColWidth -= leftBorderWidth - twwidth.Width(f.config.Symbols.Column())
|
||||
}
|
||||
if f.config.Borders.Right.Enabled() && keyIndex == len(visibleColIndices)-1 {
|
||||
rightBorderWidth := tw.DisplayWidth(jr.RenderRight(currentColIdx))
|
||||
adjustedColWidth -= rightBorderWidth - tw.DisplayWidth(f.config.Symbols.Column())
|
||||
rightBorderWidth := twwidth.Width(jr.RenderRight(currentColIdx))
|
||||
adjustedColWidth -= rightBorderWidth - twwidth.Width(f.config.Symbols.Column())
|
||||
}
|
||||
if adjustedColWidth < 0 {
|
||||
adjustedColWidth = 0
|
||||
@@ -172,7 +173,7 @@ func (f *Blueprint) Line(ctx tw.Formatting) {
|
||||
totalLineWidth += adjustedColWidth
|
||||
f.logger.Debugf("Line: Rendered spaces='%s' (f.width %d) for col %d", spaces, adjustedColWidth, currentColIdx)
|
||||
} else {
|
||||
segmentWidth := tw.DisplayWidth(segment)
|
||||
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)
|
||||
@@ -183,11 +184,11 @@ func (f *Blueprint) Line(ctx tw.Formatting) {
|
||||
repeat = 1
|
||||
}
|
||||
repeatedSegment := strings.Repeat(segment, repeat)
|
||||
actualWidth := tw.DisplayWidth(repeatedSegment)
|
||||
actualWidth := twwidth.Width(repeatedSegment)
|
||||
if actualWidth > adjustedColWidth {
|
||||
// Truncate if too long
|
||||
repeatedSegment = tw.TruncateString(repeatedSegment, adjustedColWidth)
|
||||
actualWidth = tw.DisplayWidth(repeatedSegment)
|
||||
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
|
||||
@@ -195,7 +196,7 @@ func (f *Blueprint) Line(ctx tw.Formatting) {
|
||||
for i := 0; i < remainingWidth/segmentWidth; i++ {
|
||||
repeatedSegment += segment
|
||||
}
|
||||
actualWidth = tw.DisplayWidth(repeatedSegment)
|
||||
actualWidth = twwidth.Width(repeatedSegment)
|
||||
if actualWidth < adjustedColWidth {
|
||||
repeatedSegment = tw.PadRight(repeatedSegment, tw.Space, adjustedColWidth)
|
||||
actualWidth = adjustedColWidth
|
||||
@@ -214,13 +215,13 @@ func (f *Blueprint) Line(ctx tw.Formatting) {
|
||||
nextColIdx := visibleColIndices[keyIndex+1]
|
||||
junction := jr.RenderJunction(currentColIdx, nextColIdx)
|
||||
// Use center symbol (❀) or column separator (|) to match data rows
|
||||
if tw.DisplayWidth(junction) != tw.DisplayWidth(f.config.Symbols.Column()) {
|
||||
if twwidth.Width(junction) != twwidth.Width(f.config.Symbols.Column()) {
|
||||
junction = f.config.Symbols.Center()
|
||||
if tw.DisplayWidth(junction) != tw.DisplayWidth(f.config.Symbols.Column()) {
|
||||
if twwidth.Width(junction) != twwidth.Width(f.config.Symbols.Column()) {
|
||||
junction = f.config.Symbols.Column()
|
||||
}
|
||||
}
|
||||
junctionWidth := tw.DisplayWidth(junction)
|
||||
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)
|
||||
@@ -232,7 +233,7 @@ func (f *Blueprint) Line(ctx tw.Formatting) {
|
||||
if f.config.Borders.Right.Enabled() && len(visibleColIndices) > 0 {
|
||||
lastIdx := visibleColIndices[len(visibleColIndices)-1]
|
||||
rightBorder := jr.RenderRight(lastIdx)
|
||||
rightBorderWidth = tw.DisplayWidth(rightBorder)
|
||||
rightBorderWidth = twwidth.Width(rightBorder)
|
||||
line.WriteString(rightBorder)
|
||||
totalLineWidth += rightBorderWidth
|
||||
f.logger.Debugf("Line: Right border='%s' (f.width %d)", rightBorder, rightBorderWidth)
|
||||
@@ -276,7 +277,7 @@ func (f *Blueprint) formatCell(content string, width int, padding tw.Padding, al
|
||||
content, width, align, padding.Left, padding.Right)
|
||||
|
||||
// Calculate display width of content
|
||||
runeWidth := tw.DisplayWidth(content)
|
||||
runeWidth := twwidth.Width(content)
|
||||
|
||||
// Set default padding characters
|
||||
leftPadChar := padding.Left
|
||||
@@ -292,8 +293,8 @@ func (f *Blueprint) formatCell(content string, width int, padding tw.Padding, al
|
||||
//}
|
||||
|
||||
// Calculate padding widths
|
||||
padLeftWidth := tw.DisplayWidth(leftPadChar)
|
||||
padRightWidth := tw.DisplayWidth(rightPadChar)
|
||||
padLeftWidth := twwidth.Width(leftPadChar)
|
||||
padRightWidth := twwidth.Width(rightPadChar)
|
||||
|
||||
// Calculate available width for content
|
||||
availableContentWidth := width - padLeftWidth - padRightWidth
|
||||
@@ -304,8 +305,8 @@ func (f *Blueprint) formatCell(content string, width int, padding tw.Padding, al
|
||||
|
||||
// Truncate content if it exceeds available width
|
||||
if runeWidth > availableContentWidth {
|
||||
content = tw.TruncateString(content, availableContentWidth)
|
||||
runeWidth = tw.DisplayWidth(content)
|
||||
content = twwidth.Truncate(content, availableContentWidth)
|
||||
runeWidth = twwidth.Width(content)
|
||||
f.logger.Debugf("Truncated content to fit %d: '%s' (new width %d)", availableContentWidth, content, runeWidth)
|
||||
}
|
||||
|
||||
@@ -363,10 +364,10 @@ func (f *Blueprint) formatCell(content string, width int, padding tw.Padding, al
|
||||
}
|
||||
|
||||
output := result.String()
|
||||
finalWidth := tw.DisplayWidth(output)
|
||||
finalWidth := twwidth.Width(output)
|
||||
// Adjust output to match target width
|
||||
if finalWidth > width {
|
||||
output = tw.TruncateString(output, 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)
|
||||
@@ -374,9 +375,9 @@ func (f *Blueprint) formatCell(content string, width int, padding tw.Padding, al
|
||||
}
|
||||
|
||||
// Log warning if final width doesn't match target
|
||||
if f.logger.Enabled() && tw.DisplayWidth(output) != width {
|
||||
if f.logger.Enabled() && twwidth.Width(output) != width {
|
||||
f.logger.Debugf("formatCell Warning: Final width %d does not match target %d for result '%s'",
|
||||
tw.DisplayWidth(output), width, output)
|
||||
twwidth.Width(output), width, output)
|
||||
}
|
||||
|
||||
f.logger.Debugf("Formatted cell final result: '%s' (target width %d)", output, width)
|
||||
@@ -407,14 +408,14 @@ func (f *Blueprint) renderLine(ctx tw.Formatting) {
|
||||
totalLineWidth := 0 // Track total display width
|
||||
if prefix != tw.Empty {
|
||||
output.WriteString(prefix)
|
||||
totalLineWidth += tw.DisplayWidth(prefix)
|
||||
f.logger.Debugf("renderLine: Prefix='%s' (f.width %d)", prefix, tw.DisplayWidth(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 = tw.DisplayWidth(columnSeparator)
|
||||
separatorDisplayWidth = twwidth.Width(columnSeparator)
|
||||
}
|
||||
|
||||
// Process each column
|
||||
@@ -542,7 +543,7 @@ func (f *Blueprint) renderLine(ctx tw.Formatting) {
|
||||
formattedCell := f.formatCell(cellData, visualWidth, padding, align)
|
||||
if len(formattedCell) > 0 {
|
||||
output.WriteString(formattedCell)
|
||||
cellWidth := tw.DisplayWidth(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)
|
||||
}
|
||||
@@ -561,17 +562,19 @@ func (f *Blueprint) renderLine(ctx tw.Formatting) {
|
||||
// Add suffix and adjust total width
|
||||
if output.Len() > len(prefix) || f.config.Borders.Right.Enabled() {
|
||||
output.WriteString(suffix)
|
||||
totalLineWidth += tw.DisplayWidth(suffix)
|
||||
f.logger.Debugf("renderLine: Suffix='%s' (f.width %d)", suffix, tw.DisplayWidth(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 internal config: %+v", f.config)
|
||||
f.logger.Debugf("Blueprint.Rendition updated. New config: %+v", f.config)
|
||||
|
||||
}
|
||||
|
||||
// Ensure Blueprint implements tw.Renditioning
|
||||
|
||||
28
vendor/github.com/olekukonko/tablewriter/renderer/colorized.go
generated
vendored
28
vendor/github.com/olekukonko/tablewriter/renderer/colorized.go
generated
vendored
@@ -4,6 +4,7 @@ import (
|
||||
"github.com/fatih/color"
|
||||
"github.com/olekukonko/ll"
|
||||
"github.com/olekukonko/ll/lh"
|
||||
"github.com/olekukonko/tablewriter/pkg/twwidth"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
@@ -254,7 +255,7 @@ func (c *Colorized) Line(ctx tw.Formatting) {
|
||||
line.WriteString(strings.Repeat(tw.Space, colWidth))
|
||||
} else {
|
||||
// Calculate how many times to repeat the segment
|
||||
segmentWidth := tw.DisplayWidth(segment)
|
||||
segmentWidth := twwidth.Width(segment)
|
||||
if segmentWidth <= 0 {
|
||||
segmentWidth = 1
|
||||
}
|
||||
@@ -266,7 +267,7 @@ func (c *Colorized) Line(ctx tw.Formatting) {
|
||||
line.WriteString(drawnSegment)
|
||||
|
||||
// Adjust for width discrepancies
|
||||
actualDrawnWidth := tw.DisplayWidth(drawnSegment)
|
||||
actualDrawnWidth := twwidth.Width(drawnSegment)
|
||||
if actualDrawnWidth < colWidth {
|
||||
missingWidth := colWidth - actualDrawnWidth
|
||||
spaces := strings.Repeat(tw.Space, missingWidth)
|
||||
@@ -373,7 +374,7 @@ func (c *Colorized) formatCell(content string, width int, padding tw.Padding, al
|
||||
}
|
||||
|
||||
// Calculate visual width of content
|
||||
contentVisualWidth := tw.DisplayWidth(content)
|
||||
contentVisualWidth := twwidth.Width(content)
|
||||
|
||||
// Set default padding characters
|
||||
padLeftCharStr := padding.Left
|
||||
@@ -386,8 +387,8 @@ func (c *Colorized) formatCell(content string, width int, padding tw.Padding, al
|
||||
}
|
||||
|
||||
// Calculate padding widths
|
||||
definedPadLeftWidth := tw.DisplayWidth(padLeftCharStr)
|
||||
definedPadRightWidth := tw.DisplayWidth(padRightCharStr)
|
||||
definedPadLeftWidth := twwidth.Width(padLeftCharStr)
|
||||
definedPadRightWidth := twwidth.Width(padRightCharStr)
|
||||
// Calculate available width for content and alignment
|
||||
availableForContentAndAlign := width - definedPadLeftWidth - definedPadRightWidth
|
||||
if availableForContentAndAlign < 0 {
|
||||
@@ -396,8 +397,8 @@ func (c *Colorized) formatCell(content string, width int, padding tw.Padding, al
|
||||
|
||||
// Truncate content if it exceeds available width
|
||||
if contentVisualWidth > availableForContentAndAlign {
|
||||
content = tw.TruncateString(content, availableForContentAndAlign)
|
||||
contentVisualWidth = tw.DisplayWidth(content)
|
||||
content = twwidth.Truncate(content, availableForContentAndAlign)
|
||||
contentVisualWidth = twwidth.Width(content)
|
||||
c.logger.Debugf("Truncated content to fit %d: '%s' (new width %d)", availableForContentAndAlign, content, contentVisualWidth)
|
||||
}
|
||||
|
||||
@@ -472,12 +473,12 @@ func (c *Colorized) formatCell(content string, width int, padding tw.Padding, al
|
||||
output := sb.String()
|
||||
|
||||
// Adjust output width if necessary
|
||||
currentVisualWidth := tw.DisplayWidth(output)
|
||||
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 = tw.TruncateString(output, width)
|
||||
output = twwidth.Truncate(output, width)
|
||||
} else {
|
||||
paddingSpacesStr := strings.Repeat(tw.Space, width-currentVisualWidth)
|
||||
if len(tint.BG) > 0 {
|
||||
@@ -486,10 +487,10 @@ func (c *Colorized) formatCell(content string, width int, padding tw.Padding, al
|
||||
output += paddingSpacesStr
|
||||
}
|
||||
}
|
||||
c.logger.Debugf("formatCell Post-Correction: Target %d, New Visual width %d. Output: '%s'", width, tw.DisplayWidth(output), output)
|
||||
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, tw.DisplayWidth(output))
|
||||
c.logger.Debugf("Formatted cell final result: '%s' (target width %d, display width %d)", output, width, twwidth.Width(output))
|
||||
return output
|
||||
}
|
||||
|
||||
@@ -529,7 +530,7 @@ func (c *Colorized) renderLine(ctx tw.Formatting, line []string, tint Tint) {
|
||||
separatorString := tw.Empty
|
||||
if c.config.Settings.Separators.BetweenColumns.Enabled() {
|
||||
separatorString = c.config.Separator.Apply(c.config.Symbols.Column())
|
||||
separatorDisplayWidth = tw.DisplayWidth(c.config.Symbols.Column())
|
||||
separatorDisplayWidth = twwidth.Width(c.config.Symbols.Column())
|
||||
}
|
||||
|
||||
// Process each column
|
||||
@@ -693,8 +694,7 @@ func (c *Colorized) renderLine(ctx tw.Formatting, line []string, tint Tint) {
|
||||
// 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)
|
||||
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,
|
||||
|
||||
1
vendor/github.com/olekukonko/tablewriter/renderer/html.go
generated
vendored
1
vendor/github.com/olekukonko/tablewriter/renderer/html.go
generated
vendored
@@ -63,6 +63,7 @@ func NewHTML(configs ...HTMLConfig) *HTML {
|
||||
tableStarted: false,
|
||||
tbodyStarted: false,
|
||||
tfootStarted: false,
|
||||
logger: ll.New("html"),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
21
vendor/github.com/olekukonko/tablewriter/renderer/markdown.go
generated
vendored
21
vendor/github.com/olekukonko/tablewriter/renderer/markdown.go
generated
vendored
@@ -2,6 +2,7 @@ package renderer
|
||||
|
||||
import (
|
||||
"github.com/olekukonko/ll"
|
||||
"github.com/olekukonko/tablewriter/pkg/twwidth"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
@@ -35,7 +36,7 @@ func NewMarkdown(configs ...tw.Rendition) *Markdown {
|
||||
if len(configs) > 0 {
|
||||
cfg = mergeMarkdownConfig(cfg, configs[0])
|
||||
}
|
||||
return &Markdown{config: cfg}
|
||||
return &Markdown{config: cfg, logger: ll.New("markdown")}
|
||||
}
|
||||
|
||||
// mergeMarkdownConfig combines user-provided config with Markdown defaults, enforcing Markdown-specific settings.
|
||||
@@ -157,7 +158,7 @@ func (m *Markdown) formatCell(content string, width int, align tw.Align, padding
|
||||
//if m.config.Settings.TrimWhitespace.Enabled() {
|
||||
// content = strings.TrimSpace(content)
|
||||
//}
|
||||
contentVisualWidth := tw.DisplayWidth(content)
|
||||
contentVisualWidth := twwidth.Width(content)
|
||||
|
||||
// Use specified padding characters or default to spaces
|
||||
padLeftChar := padding.Left
|
||||
@@ -170,8 +171,8 @@ func (m *Markdown) formatCell(content string, width int, align tw.Align, padding
|
||||
}
|
||||
|
||||
// Calculate padding widths
|
||||
padLeftCharWidth := tw.DisplayWidth(padLeftChar)
|
||||
padRightCharWidth := tw.DisplayWidth(padRightChar)
|
||||
padLeftCharWidth := twwidth.Width(padLeftChar)
|
||||
padRightCharWidth := twwidth.Width(padRightChar)
|
||||
minWidth := tw.Max(3, contentVisualWidth+padLeftCharWidth+padRightCharWidth)
|
||||
targetWidth := tw.Max(width, minWidth)
|
||||
|
||||
@@ -212,7 +213,7 @@ func (m *Markdown) formatCell(content string, width int, align tw.Align, padding
|
||||
result := leftPadStr + content + rightPadStr
|
||||
|
||||
// Adjust width if needed
|
||||
finalWidth := tw.DisplayWidth(result)
|
||||
finalWidth := twwidth.Width(result)
|
||||
if finalWidth != targetWidth {
|
||||
m.logger.Debugf("Markdown formatCell MISMATCH: content='%s', target_w=%d, paddingL='%s', paddingR='%s', align=%s -> result='%s', result_w=%d",
|
||||
content, targetWidth, padding.Left, padding.Right, align, result, finalWidth)
|
||||
@@ -229,9 +230,9 @@ func (m *Markdown) formatCell(content string, width int, align tw.Align, padding
|
||||
result += adjStr
|
||||
}
|
||||
} else {
|
||||
result = tw.TruncateString(result, targetWidth)
|
||||
result = twwidth.Truncate(result, targetWidth)
|
||||
}
|
||||
m.logger.Debugf("Markdown formatCell Corrected: target_w=%d, result='%s', new_w=%d", targetWidth, result, tw.DisplayWidth(result))
|
||||
m.logger.Debugf("Markdown formatCell Corrected: target_w=%d, result='%s', new_w=%d", targetWidth, result, twwidth.Width(result))
|
||||
}
|
||||
|
||||
m.logger.Debugf("Markdown formatCell: content='%s', width=%d, align=%s, paddingL='%s', paddingR='%s' -> '%s' (target %d)",
|
||||
@@ -262,11 +263,11 @@ func (m *Markdown) formatSeparator(width int, align tw.Align) string {
|
||||
}
|
||||
|
||||
result := sb.String()
|
||||
currentLen := tw.DisplayWidth(result)
|
||||
currentLen := twwidth.Width(result)
|
||||
if currentLen < targetWidth {
|
||||
result += strings.Repeat("-", targetWidth-currentLen)
|
||||
} else if currentLen > targetWidth {
|
||||
result = tw.TruncateString(result, targetWidth)
|
||||
result = twwidth.Truncate(result, targetWidth)
|
||||
}
|
||||
|
||||
m.logger.Debugf("Markdown formatSeparator: width=%d, align=%s -> '%s'", width, align, result)
|
||||
@@ -314,7 +315,7 @@ func (m *Markdown) renderMarkdownLine(line []string, ctx tw.Formatting, isHeader
|
||||
output.WriteString(prefix)
|
||||
|
||||
colIndex := 0
|
||||
separatorWidth := tw.DisplayWidth(separator)
|
||||
separatorWidth := twwidth.Width(separator)
|
||||
|
||||
for colIndex < numCols {
|
||||
cellCtx, ok := ctx.Row.Current[colIndex]
|
||||
|
||||
57
vendor/github.com/olekukonko/tablewriter/renderer/ocean.go
generated
vendored
57
vendor/github.com/olekukonko/tablewriter/renderer/ocean.go
generated
vendored
@@ -1,6 +1,7 @@
|
||||
package renderer
|
||||
|
||||
import (
|
||||
"github.com/olekukonko/tablewriter/pkg/twwidth"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
@@ -106,17 +107,9 @@ func (o *Ocean) Header(headers [][]string, ctx tw.Formatting) {
|
||||
if !o.widthsFinalized {
|
||||
o.tryFinalizeWidths(ctx)
|
||||
}
|
||||
// The batch renderer (table.go/renderHeader) will call Line() for the top border
|
||||
// and for the header separator if its main config t.config says so.
|
||||
// So, Ocean.Header should *not* draw these itself when in batch mode.
|
||||
// For true streaming, table.go's streamRenderHeader would make these Line() calls.
|
||||
|
||||
// Decision: Ocean.Header *only* renders header content.
|
||||
// Lines (top border, header separator) are managed by the caller (batch or stream logic in table.go).
|
||||
|
||||
if !o.widthsFinalized {
|
||||
o.logger.Error("Ocean.Header: Cannot render content, widths are not finalized.")
|
||||
// o.headerContentRendered = true; // No, content wasn't rendered.
|
||||
return
|
||||
}
|
||||
|
||||
@@ -133,10 +126,7 @@ func (o *Ocean) Header(headers [][]string, ctx tw.Formatting) {
|
||||
o.headerContentRendered = true
|
||||
} else {
|
||||
o.logger.Debug("Ocean.Header: No actual header content lines to render.")
|
||||
// If header is empty, table.go's renderHeader might still call Line() for the separator.
|
||||
// o.headerContentRendered remains false if no content.
|
||||
}
|
||||
// DO NOT draw the header separator line here. Let table.go's renderHeader or streamRenderHeader call o.Line().
|
||||
}
|
||||
|
||||
func (o *Ocean) Row(row []string, ctx tw.Formatting) {
|
||||
@@ -145,15 +135,6 @@ func (o *Ocean) Row(row []string, ctx tw.Formatting) {
|
||||
if !o.widthsFinalized {
|
||||
o.tryFinalizeWidths(ctx)
|
||||
}
|
||||
// Top border / header separator logic:
|
||||
// If this is the very first output, table.go's batch renderHeader (or streamRenderHeader)
|
||||
// should have already called Line() for top border and header separator.
|
||||
// If Header() was called but rendered no content, table.go's renderHeader would still call Line() for the separator.
|
||||
// If Header() was never called by table.go (e.g. streaming rows directly after Start()),
|
||||
// then table.go's streamAppendRow needs to handle initial lines.
|
||||
|
||||
// Decision: Ocean.Row *only* renders row content.
|
||||
|
||||
if !o.widthsFinalized {
|
||||
o.logger.Error("Ocean.Row: Cannot render content, widths are not finalized.")
|
||||
return
|
||||
@@ -171,11 +152,6 @@ func (o *Ocean) Footer(footers [][]string, ctx tw.Formatting) {
|
||||
o.tryFinalizeWidths(ctx)
|
||||
o.logger.Warn("Ocean.Footer: Widths finalized at Footer stage (unusual).")
|
||||
}
|
||||
// Separator line before footer:
|
||||
// This should be handled by table.go's renderFooter or streamRenderFooter calling o.Line().
|
||||
|
||||
// Decision: Ocean.Footer *only* renders footer content.
|
||||
|
||||
if !o.widthsFinalized {
|
||||
o.logger.Error("Ocean.Footer: Cannot render content, widths are not finalized.")
|
||||
return
|
||||
@@ -194,24 +170,19 @@ func (o *Ocean) Footer(footers [][]string, ctx tw.Formatting) {
|
||||
} else {
|
||||
o.logger.Debug("Ocean.Footer: No actual footer content lines to render.")
|
||||
}
|
||||
// DO NOT draw the bottom border here. Let table.go's main Close or batch renderFooter call o.Line().
|
||||
}
|
||||
|
||||
func (o *Ocean) Line(ctx tw.Formatting) {
|
||||
// This method is now called EXTERNALLY by table.go's batch or stream logic
|
||||
// to draw all horizontal lines (top border, header sep, footer sep, bottom border).
|
||||
if !o.widthsFinalized {
|
||||
// If Line is called before widths are known (e.g. table.go's batch renderHeader trying to draw top border)
|
||||
// we must try to finalize widths from this context.
|
||||
o.tryFinalizeWidths(ctx)
|
||||
if !o.widthsFinalized {
|
||||
o.logger.Error("Ocean.Line: Called but widths could not be finalized. Skipping line rendering.")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure Line uses the consistent fixedWidths for drawing
|
||||
ctx.Row.Widths = o.fixedWidths
|
||||
|
||||
o.logger.Debugf("Ocean.Line DRAWING: Level=%v, Loc=%s, Pos=%s, IsSubRow=%t, WidthsLen=%d", ctx.Level, ctx.Row.Location, ctx.Row.Position, ctx.IsSubRow, ctx.Row.Widths.Len())
|
||||
|
||||
jr := NewJunction(JunctionContext{
|
||||
@@ -262,7 +233,7 @@ func (o *Ocean) Line(ctx tw.Formatting) {
|
||||
if segmentChar == tw.Empty {
|
||||
segmentChar = o.config.Symbols.Row()
|
||||
}
|
||||
segmentDisplayWidth := tw.DisplayWidth(segmentChar)
|
||||
segmentDisplayWidth := twwidth.Width(segmentChar)
|
||||
if segmentDisplayWidth <= 0 {
|
||||
segmentDisplayWidth = 1
|
||||
}
|
||||
@@ -296,12 +267,6 @@ func (o *Ocean) Line(ctx tw.Formatting) {
|
||||
|
||||
func (o *Ocean) Close() error {
|
||||
o.logger.Debug("Ocean.Close() called.")
|
||||
// The actual bottom border drawing is expected to be handled by table.go's
|
||||
// batch render logic (renderFooter) or stream logic (streamRenderBottomBorder)
|
||||
// by making an explicit call to o.Line() with the correct context.
|
||||
// Ocean.Close() itself does not draw the bottom border to avoid duplication.
|
||||
|
||||
// Only reset state.
|
||||
o.resetState()
|
||||
return nil
|
||||
}
|
||||
@@ -374,7 +339,7 @@ func (o *Ocean) renderContentLine(ctx tw.Formatting, lineData []string) {
|
||||
}
|
||||
|
||||
if k < hSpan-1 && o.config.Settings.Separators.BetweenColumns.Enabled() {
|
||||
currentMergeTotalRenderWidth += tw.DisplayWidth(o.config.Symbols.Column())
|
||||
currentMergeTotalRenderWidth += twwidth.Width(o.config.Symbols.Column())
|
||||
}
|
||||
}
|
||||
actualCellWidthToRender = currentMergeTotalRenderWidth
|
||||
@@ -428,7 +393,7 @@ func (o *Ocean) formatCellContent(content string, cellVisualWidth int, padding t
|
||||
return tw.Empty
|
||||
}
|
||||
|
||||
contentDisplayWidth := tw.DisplayWidth(content)
|
||||
contentDisplayWidth := twwidth.Width(content)
|
||||
|
||||
padLeftChar := padding.Left
|
||||
if padLeftChar == tw.Empty {
|
||||
@@ -439,8 +404,8 @@ func (o *Ocean) formatCellContent(content string, cellVisualWidth int, padding t
|
||||
padRightChar = tw.Space
|
||||
}
|
||||
|
||||
padLeftDisplayWidth := tw.DisplayWidth(padLeftChar)
|
||||
padRightDisplayWidth := tw.DisplayWidth(padRightChar)
|
||||
padLeftDisplayWidth := twwidth.Width(padLeftChar)
|
||||
padRightDisplayWidth := twwidth.Width(padRightChar)
|
||||
|
||||
spaceForContentAndAlignment := cellVisualWidth - padLeftDisplayWidth - padRightDisplayWidth
|
||||
if spaceForContentAndAlignment < 0 {
|
||||
@@ -448,8 +413,8 @@ func (o *Ocean) formatCellContent(content string, cellVisualWidth int, padding t
|
||||
}
|
||||
|
||||
if contentDisplayWidth > spaceForContentAndAlignment {
|
||||
content = tw.TruncateString(content, spaceForContentAndAlignment)
|
||||
contentDisplayWidth = tw.DisplayWidth(content)
|
||||
content = twwidth.Truncate(content, spaceForContentAndAlignment)
|
||||
contentDisplayWidth = twwidth.Width(content)
|
||||
}
|
||||
|
||||
remainingSpace := spaceForContentAndAlignment - contentDisplayWidth
|
||||
@@ -477,7 +442,7 @@ func (o *Ocean) formatCellContent(content string, cellVisualWidth int, padding t
|
||||
sb.WriteString(PR)
|
||||
sb.WriteString(padRightChar)
|
||||
|
||||
currentFormattedWidth := tw.DisplayWidth(sb.String())
|
||||
currentFormattedWidth := twwidth.Width(sb.String())
|
||||
if currentFormattedWidth < cellVisualWidth {
|
||||
if align == tw.AlignRight {
|
||||
prefixSpaces := strings.Repeat(tw.Space, cellVisualWidth-currentFormattedWidth)
|
||||
@@ -490,7 +455,7 @@ func (o *Ocean) formatCellContent(content string, cellVisualWidth int, padding t
|
||||
} else if currentFormattedWidth > cellVisualWidth {
|
||||
tempStr := sb.String()
|
||||
sb.Reset()
|
||||
sb.WriteString(tw.TruncateString(tempStr, cellVisualWidth))
|
||||
sb.WriteString(twwidth.Truncate(tempStr, cellVisualWidth))
|
||||
o.logger.Warnf("formatCellContent: Final string '%s' (width %d) exceeded target %d. Force truncated.", tempStr, currentFormattedWidth, cellVisualWidth)
|
||||
}
|
||||
return sb.String()
|
||||
|
||||
1
vendor/github.com/olekukonko/tablewriter/renderer/svg.go
generated
vendored
1
vendor/github.com/olekukonko/tablewriter/renderer/svg.go
generated
vendored
@@ -138,6 +138,7 @@ func NewSVG(configs ...SVGConfig) *SVG {
|
||||
allVisualLineData: make([][][]string, 3),
|
||||
allVisualLineCtx: make([][]tw.Formatting, 3),
|
||||
vMergeTrack: make(map[int]int),
|
||||
logger: ll.New("svg"),
|
||||
}
|
||||
for i := 0; i < 3; i++ {
|
||||
r.allVisualLineData[i] = make([][]string, 0)
|
||||
|
||||
46
vendor/github.com/olekukonko/tablewriter/stream.go
generated
vendored
46
vendor/github.com/olekukonko/tablewriter/stream.go
generated
vendored
@@ -3,6 +3,7 @@ package tablewriter
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/olekukonko/errors"
|
||||
"github.com/olekukonko/tablewriter/pkg/twwidth"
|
||||
"github.com/olekukonko/tablewriter/tw"
|
||||
"math"
|
||||
)
|
||||
@@ -220,21 +221,34 @@ func (t *Table) streamAppendRow(row interface{}) error {
|
||||
}
|
||||
|
||||
if err := t.ensureStreamWidthsCalculated(rawCellsSlice, t.config.Row); err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to establish stream column count/widths: %w", err)
|
||||
}
|
||||
|
||||
if t.streamNumCols > 0 && len(rawCellsSlice) != t.streamNumCols {
|
||||
t.logger.Warnf("streamAppendRow: Input row column count (%d) != stream column count (%d). Padding/Truncating.", len(rawCellsSlice), t.streamNumCols)
|
||||
if len(rawCellsSlice) < t.streamNumCols {
|
||||
paddedCells := make([]string, t.streamNumCols)
|
||||
copy(paddedCells, rawCellsSlice)
|
||||
for i := len(rawCellsSlice); i < t.streamNumCols; i++ {
|
||||
paddedCells[i] = tw.Empty
|
||||
// Now, check for column mismatch if a column count has been established.
|
||||
if t.streamNumCols > 0 {
|
||||
if len(rawCellsSlice) != t.streamNumCols {
|
||||
if t.config.Stream.StrictColumns {
|
||||
err := errors.Newf("input row column count (%d) does not match established stream column count (%d) and StrictColumns is enabled", len(rawCellsSlice), t.streamNumCols)
|
||||
t.logger.Error(err.Error())
|
||||
return err
|
||||
}
|
||||
// If not strict, retain the old lenient behavior (warn and pad/truncate)
|
||||
t.logger.Warnf("streamAppendRow: Input row column count (%d) != stream column count (%d). Padding/Truncating (StrictColumns is false).", len(rawCellsSlice), t.streamNumCols)
|
||||
if len(rawCellsSlice) < t.streamNumCols {
|
||||
paddedCells := make([]string, t.streamNumCols)
|
||||
copy(paddedCells, rawCellsSlice)
|
||||
for i := len(rawCellsSlice); i < t.streamNumCols; i++ {
|
||||
paddedCells[i] = tw.Empty
|
||||
}
|
||||
rawCellsSlice = paddedCells
|
||||
} else {
|
||||
rawCellsSlice = rawCellsSlice[:t.streamNumCols]
|
||||
}
|
||||
rawCellsSlice = paddedCells
|
||||
} else {
|
||||
rawCellsSlice = rawCellsSlice[:t.streamNumCols]
|
||||
}
|
||||
} else if len(rawCellsSlice) > 0 && t.config.Stream.StrictColumns {
|
||||
err := errors.Newf("failed to establish stream column count from first data row (%d cells) and StrictColumns is enabled", len(rawCellsSlice))
|
||||
t.logger.Error(err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
if t.streamNumCols == 0 {
|
||||
@@ -511,7 +525,7 @@ func (t *Table) streamCalculateWidths(sampling []string, config tw.CellConfig) i
|
||||
|
||||
ellipsisWidthBuffer := 0
|
||||
if autoWrapForWidthCalc == tw.WrapTruncate {
|
||||
ellipsisWidthBuffer = tw.DisplayWidth(tw.CharEllipsis)
|
||||
ellipsisWidthBuffer = twwidth.Width(tw.CharEllipsis)
|
||||
}
|
||||
varianceBuffer := 2 // Your suggested variance
|
||||
minTotalColWidth := tw.MinimumColumnWidth
|
||||
@@ -525,14 +539,14 @@ func (t *Table) streamCalculateWidths(sampling []string, config tw.CellConfig) i
|
||||
if i < len(sampling) {
|
||||
sampleContent = t.Trimmer(sampling[i])
|
||||
}
|
||||
sampleContentDisplayWidth := tw.DisplayWidth(sampleContent)
|
||||
sampleContentDisplayWidth := twwidth.Width(sampleContent)
|
||||
|
||||
colPad := paddingForWidthCalc.Global
|
||||
if i < len(paddingForWidthCalc.PerColumn) && paddingForWidthCalc.PerColumn[i].Paddable() {
|
||||
colPad = paddingForWidthCalc.PerColumn[i]
|
||||
}
|
||||
currentPadLWidth := tw.DisplayWidth(colPad.Left)
|
||||
currentPadRWidth := tw.DisplayWidth(colPad.Right)
|
||||
currentPadLWidth := twwidth.Width(colPad.Left)
|
||||
currentPadRWidth := twwidth.Width(colPad.Right)
|
||||
currentTotalPaddingWidth := currentPadLWidth + currentPadRWidth
|
||||
|
||||
// Start with the target content width logic
|
||||
@@ -595,7 +609,7 @@ func (t *Table) streamCalculateWidths(sampling []string, config tw.CellConfig) i
|
||||
if t.renderer != nil {
|
||||
rendererConfig := t.renderer.Config()
|
||||
if rendererConfig.Settings.Separators.BetweenColumns.Enabled() {
|
||||
separatorWidth = tw.DisplayWidth(rendererConfig.Symbols.Column())
|
||||
separatorWidth = twwidth.Width(rendererConfig.Symbols.Column())
|
||||
}
|
||||
} else {
|
||||
separatorWidth = 1 // Default if renderer not available yet
|
||||
|
||||
49
vendor/github.com/olekukonko/tablewriter/tablewriter.go
generated
vendored
49
vendor/github.com/olekukonko/tablewriter/tablewriter.go
generated
vendored
@@ -7,11 +7,14 @@ import (
|
||||
"github.com/olekukonko/ll"
|
||||
"github.com/olekukonko/ll/lh"
|
||||
"github.com/olekukonko/tablewriter/pkg/twwarp"
|
||||
"github.com/olekukonko/tablewriter/pkg/twwidth"
|
||||
"github.com/olekukonko/tablewriter/renderer"
|
||||
"github.com/olekukonko/tablewriter/tw"
|
||||
"io"
|
||||
"math"
|
||||
"os"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
@@ -407,10 +410,14 @@ func (t *Table) Options(opts ...Option) *Table {
|
||||
t.logger.Suspend()
|
||||
}
|
||||
|
||||
// help resolve from deprecation
|
||||
//if t.config.Stream.Enable {
|
||||
// t.config.Widths = t.config.Stream.Widths
|
||||
//}
|
||||
// Get additional system information for debugging
|
||||
goVersion := runtime.Version()
|
||||
goOS := runtime.GOOS
|
||||
goArch := runtime.GOARCH
|
||||
numCPU := runtime.NumCPU()
|
||||
|
||||
t.logger.Infof("Environment: LC_CTYPE=%s, LANG=%s, TERM=%s", os.Getenv("LC_CTYPE"), os.Getenv("LANG"), os.Getenv("TERM"))
|
||||
t.logger.Infof("Go Runtime: Version=%s, OS=%s, Arch=%s, CPUs=%d", goVersion, goOS, goArch, numCPU)
|
||||
|
||||
// send logger to renderer
|
||||
// this will overwrite the default logger
|
||||
@@ -635,7 +642,7 @@ func (t *Table) finalizeHierarchicalMergeBlock(ctx *renderContext, mctx *mergeCo
|
||||
startState.Hierarchical.Present = true
|
||||
startState.Hierarchical.Start = true
|
||||
startState.Hierarchical.Span = finalSpan
|
||||
startState.Hierarchical.End = (finalSpan == 1)
|
||||
startState.Hierarchical.End = finalSpan == 1
|
||||
mctx.rowMerges[startRow][col] = startState
|
||||
}
|
||||
|
||||
@@ -756,7 +763,7 @@ func (t *Table) printTopBottomCaption(w io.Writer, actualTableWidth int) {
|
||||
captionWrapWidth = t.caption.Width
|
||||
t.logger.Debugf("[printCaption] Using user-defined caption.Width %d for wrapping.", captionWrapWidth)
|
||||
} else if actualTableWidth <= 4 {
|
||||
captionWrapWidth = tw.DisplayWidth(t.caption.Text)
|
||||
captionWrapWidth = twwidth.Width(t.caption.Text)
|
||||
t.logger.Debugf("[printCaption] Empty table, no user caption.Width: Using natural caption width %d.", captionWrapWidth)
|
||||
} else {
|
||||
captionWrapWidth = actualTableWidth
|
||||
@@ -879,8 +886,8 @@ func (t *Table) prepareContent(cells []string, config tw.CellConfig) [][]string
|
||||
colPad = config.Padding.PerColumn[i]
|
||||
}
|
||||
|
||||
padLeftWidth := tw.DisplayWidth(colPad.Left)
|
||||
padRightWidth := tw.DisplayWidth(colPad.Right)
|
||||
padLeftWidth := twwidth.Width(colPad.Left)
|
||||
padRightWidth := twwidth.Width(colPad.Right)
|
||||
|
||||
effectiveContentMaxWidth := t.calculateContentMaxWidth(i, config, padLeftWidth, padRightWidth, isStreaming)
|
||||
|
||||
@@ -902,12 +909,12 @@ func (t *Table) prepareContent(cells []string, config tw.CellConfig) [][]string
|
||||
}
|
||||
finalLinesForCell = append(finalLinesForCell, wrapped...)
|
||||
case tw.WrapTruncate:
|
||||
if tw.DisplayWidth(line) > effectiveContentMaxWidth {
|
||||
ellipsisWidth := tw.DisplayWidth(tw.CharEllipsis)
|
||||
if twwidth.Width(line) > effectiveContentMaxWidth {
|
||||
ellipsisWidth := twwidth.Width(tw.CharEllipsis)
|
||||
if effectiveContentMaxWidth >= ellipsisWidth {
|
||||
finalLinesForCell = append(finalLinesForCell, tw.TruncateString(line, effectiveContentMaxWidth-ellipsisWidth, tw.CharEllipsis))
|
||||
finalLinesForCell = append(finalLinesForCell, twwidth.Truncate(line, effectiveContentMaxWidth-ellipsisWidth, tw.CharEllipsis))
|
||||
} else {
|
||||
finalLinesForCell = append(finalLinesForCell, tw.TruncateString(line, effectiveContentMaxWidth, ""))
|
||||
finalLinesForCell = append(finalLinesForCell, twwidth.Truncate(line, effectiveContentMaxWidth, ""))
|
||||
}
|
||||
} else {
|
||||
finalLinesForCell = append(finalLinesForCell, line)
|
||||
@@ -915,8 +922,8 @@ func (t *Table) prepareContent(cells []string, config tw.CellConfig) [][]string
|
||||
case tw.WrapBreak:
|
||||
wrapped := make([]string, 0)
|
||||
currentLine := line
|
||||
breakCharWidth := tw.DisplayWidth(tw.CharBreak)
|
||||
for tw.DisplayWidth(currentLine) > effectiveContentMaxWidth {
|
||||
breakCharWidth := twwidth.Width(tw.CharBreak)
|
||||
for twwidth.Width(currentLine) > effectiveContentMaxWidth {
|
||||
targetWidth := effectiveContentMaxWidth - breakCharWidth
|
||||
if targetWidth < 0 {
|
||||
targetWidth = 0
|
||||
@@ -929,7 +936,7 @@ func (t *Table) prepareContent(cells []string, config tw.CellConfig) [][]string
|
||||
tempWidth := 0
|
||||
for charIdx, r := range runes {
|
||||
runeStr := string(r)
|
||||
rw := tw.DisplayWidth(runeStr)
|
||||
rw := twwidth.Width(runeStr)
|
||||
if tempWidth+rw > targetWidth && charIdx > 0 {
|
||||
break
|
||||
}
|
||||
@@ -956,10 +963,10 @@ func (t *Table) prepareContent(cells []string, config tw.CellConfig) [][]string
|
||||
currentLine = string(runes[breakPoint:])
|
||||
}
|
||||
}
|
||||
if tw.DisplayWidth(currentLine) > 0 {
|
||||
if twwidth.Width(currentLine) > 0 {
|
||||
wrapped = append(wrapped, currentLine)
|
||||
}
|
||||
if len(wrapped) == 0 && tw.DisplayWidth(line) > 0 && len(finalLinesForCell) == 0 {
|
||||
if len(wrapped) == 0 && twwidth.Width(line) > 0 && len(finalLinesForCell) == 0 {
|
||||
finalLinesForCell = append(finalLinesForCell, line)
|
||||
} else {
|
||||
finalLinesForCell = append(finalLinesForCell, wrapped...)
|
||||
@@ -1441,7 +1448,7 @@ func (t *Table) render() error {
|
||||
actualTableWidth := 0
|
||||
trimmedBuffer := strings.TrimRight(renderedTableContent, "\r\n \t")
|
||||
for _, line := range strings.Split(trimmedBuffer, "\n") {
|
||||
w := tw.DisplayWidth(line)
|
||||
w := twwidth.Width(line)
|
||||
if w > actualTableWidth {
|
||||
actualTableWidth = w
|
||||
}
|
||||
@@ -1713,7 +1720,7 @@ func (t *Table) renderFooter(ctx *renderContext, mctx *mergeContext) error {
|
||||
if j == 0 || representativePadChar == " " {
|
||||
representativePadChar = padChar
|
||||
}
|
||||
padWidth := tw.DisplayWidth(padChar)
|
||||
padWidth := twwidth.Width(padChar)
|
||||
if padWidth < 1 {
|
||||
padWidth = 1
|
||||
}
|
||||
@@ -1728,12 +1735,12 @@ func (t *Table) renderFooter(ctx *renderContext, mctx *mergeContext) error {
|
||||
repeatCount = 0
|
||||
}
|
||||
rawPaddingContent := strings.Repeat(padChar, repeatCount)
|
||||
currentWd := tw.DisplayWidth(rawPaddingContent)
|
||||
currentWd := twwidth.Width(rawPaddingContent)
|
||||
if currentWd < colWd {
|
||||
rawPaddingContent += strings.Repeat(" ", colWd-currentWd)
|
||||
}
|
||||
if currentWd > colWd && colWd > 0 {
|
||||
rawPaddingContent = tw.TruncateString(rawPaddingContent, colWd)
|
||||
rawPaddingContent = twwidth.Truncate(rawPaddingContent, colWd)
|
||||
}
|
||||
if colWd == 0 {
|
||||
rawPaddingContent = ""
|
||||
|
||||
156
vendor/github.com/olekukonko/tablewriter/tw/fn.go
generated
vendored
156
vendor/github.com/olekukonko/tablewriter/tw/fn.go
generated
vendored
@@ -3,146 +3,14 @@
|
||||
package tw
|
||||
|
||||
import (
|
||||
"bytes" // For buffering string output
|
||||
"github.com/mattn/go-runewidth" // For calculating display width of Unicode characters
|
||||
"math" // For mathematical operations like ceiling
|
||||
"regexp" // For regular expression handling of ANSI codes
|
||||
"strconv" // For string-to-number conversions
|
||||
"strings" // For string manipulation utilities
|
||||
"unicode" // For Unicode character classification
|
||||
"unicode/utf8" // For UTF-8 rune handling
|
||||
"github.com/olekukonko/tablewriter/pkg/twwidth"
|
||||
"math" // For mathematical operations like ceiling
|
||||
"strconv" // For string-to-number conversions
|
||||
"strings" // For string manipulation utilities
|
||||
"unicode" // For Unicode character classification
|
||||
"unicode/utf8" // For UTF-8 rune handling
|
||||
)
|
||||
|
||||
// ansi is a compiled regex pattern used to strip ANSI escape codes.
|
||||
// These codes are used in terminal output for styling and are invisible in rendered text.
|
||||
var ansi = CompileANSIFilter()
|
||||
|
||||
// CompileANSIFilter constructs and compiles a regex for matching ANSI sequences.
|
||||
// It supports both control sequences (CSI) and operating system commands (OSC) like hyperlinks.
|
||||
func CompileANSIFilter() *regexp.Regexp {
|
||||
var regESC = "\x1b" // ASCII escape character
|
||||
var regBEL = "\x07" // ASCII bell character
|
||||
|
||||
// ANSI string terminator: either ESC+\ or BEL
|
||||
var regST = "(" + regexp.QuoteMeta(regESC+"\\") + "|" + regexp.QuoteMeta(regBEL) + ")"
|
||||
// Control Sequence Introducer (CSI): ESC[ followed by parameters and a final byte
|
||||
var regCSI = regexp.QuoteMeta(regESC+"[") + "[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e]"
|
||||
// Operating System Command (OSC): ESC] followed by arbitrary content until a terminator
|
||||
var regOSC = regexp.QuoteMeta(regESC+"]") + ".*?" + regST
|
||||
|
||||
// Combine CSI and OSC patterns into a single regex
|
||||
return regexp.MustCompile("(" + regCSI + "|" + regOSC + ")")
|
||||
}
|
||||
|
||||
// DisplayWidth calculates the visual width of a string, excluding ANSI escape sequences.
|
||||
// It uses go-runewidth to handle Unicode characters correctly.
|
||||
func DisplayWidth(str string) int {
|
||||
// Strip ANSI codes before calculating width to avoid counting invisible characters
|
||||
return runewidth.StringWidth(ansi.ReplaceAllLiteralString(str, ""))
|
||||
}
|
||||
|
||||
// TruncateString shortens a string to a specified maximum display width while preserving ANSI color codes.
|
||||
// An optional suffix (e.g., "...") is appended if truncation occurs.
|
||||
func TruncateString(s string, maxWidth int, suffix ...string) string {
|
||||
// If maxWidth is 0 or negative, return an empty string
|
||||
if maxWidth <= 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Join suffix slices into a single string and calculate its display width
|
||||
suffixStr := strings.Join(suffix, " ")
|
||||
suffixDisplayWidth := 0
|
||||
if len(suffixStr) > 0 {
|
||||
// Strip ANSI from suffix for accurate width calculation
|
||||
suffixDisplayWidth = runewidth.StringWidth(ansi.ReplaceAllLiteralString(suffixStr, ""))
|
||||
}
|
||||
|
||||
// Check if the string (without ANSI) plus suffix fits within maxWidth
|
||||
strippedS := ansi.ReplaceAllLiteralString(s, "")
|
||||
if runewidth.StringWidth(strippedS)+suffixDisplayWidth <= maxWidth {
|
||||
// If it fits, return the original string (with ANSI) plus suffix
|
||||
return s + suffixStr
|
||||
}
|
||||
|
||||
// Handle edge case: maxWidth is too small for even the suffix
|
||||
if maxWidth < suffixDisplayWidth {
|
||||
// Try truncating the string without suffix
|
||||
return TruncateString(s, maxWidth) // Recursive call without suffix
|
||||
}
|
||||
// Handle edge case: maxWidth exactly equals suffix width
|
||||
if maxWidth == suffixDisplayWidth {
|
||||
if runewidth.StringWidth(strippedS) > 0 {
|
||||
// If there's content, it's fully truncated; return suffix
|
||||
return suffixStr
|
||||
}
|
||||
return "" // No content and no space for content; return empty string
|
||||
}
|
||||
|
||||
// Calculate the maximum width available for the content (excluding suffix)
|
||||
targetContentDisplayWidth := maxWidth - suffixDisplayWidth
|
||||
|
||||
var contentBuf bytes.Buffer // Buffer for building truncated content
|
||||
var currentContentDisplayWidth int // Tracks display width of content
|
||||
var ansiSeqBuf bytes.Buffer // Buffer for collecting ANSI sequences
|
||||
inAnsiSequence := false // Tracks if we're inside an ANSI sequence
|
||||
|
||||
// Iterate over runes to build content while respecting maxWidth
|
||||
for _, r := range s {
|
||||
if r == '\x1b' { // Start of ANSI escape sequence
|
||||
if inAnsiSequence {
|
||||
// Unexpected new ESC; flush existing sequence
|
||||
contentBuf.Write(ansiSeqBuf.Bytes())
|
||||
ansiSeqBuf.Reset()
|
||||
}
|
||||
inAnsiSequence = true
|
||||
ansiSeqBuf.WriteRune(r)
|
||||
} else if inAnsiSequence {
|
||||
ansiSeqBuf.WriteRune(r)
|
||||
// Detect end of common ANSI sequences (e.g., SGR 'm' or CSI terminators)
|
||||
if r == 'm' || (ansiSeqBuf.Len() > 2 && ansiSeqBuf.Bytes()[1] == '[' && r >= '@' && r <= '~') {
|
||||
inAnsiSequence = false
|
||||
contentBuf.Write(ansiSeqBuf.Bytes()) // Append completed sequence
|
||||
ansiSeqBuf.Reset()
|
||||
} else if ansiSeqBuf.Len() > 128 { // Prevent buffer overflow for malformed sequences
|
||||
inAnsiSequence = false
|
||||
contentBuf.Write(ansiSeqBuf.Bytes())
|
||||
ansiSeqBuf.Reset()
|
||||
}
|
||||
} else {
|
||||
// Handle displayable characters
|
||||
runeDisplayWidth := runewidth.RuneWidth(r)
|
||||
if currentContentDisplayWidth+runeDisplayWidth > targetContentDisplayWidth {
|
||||
// Adding this rune would exceed the content width; stop here
|
||||
break
|
||||
}
|
||||
contentBuf.WriteRune(r)
|
||||
currentContentDisplayWidth += runeDisplayWidth
|
||||
}
|
||||
}
|
||||
|
||||
// Append any unterminated ANSI sequence
|
||||
if ansiSeqBuf.Len() > 0 {
|
||||
contentBuf.Write(ansiSeqBuf.Bytes())
|
||||
}
|
||||
|
||||
finalContent := contentBuf.String()
|
||||
|
||||
// Append suffix if content was truncated or if suffix is provided and content exists
|
||||
if runewidth.StringWidth(ansi.ReplaceAllLiteralString(finalContent, "")) < runewidth.StringWidth(strippedS) {
|
||||
// Content was truncated; append suffix
|
||||
return finalContent + suffixStr
|
||||
} else if len(suffixStr) > 0 && len(finalContent) > 0 {
|
||||
// No truncation but suffix exists; append it
|
||||
return finalContent + suffixStr
|
||||
} else if len(suffixStr) > 0 && len(strippedS) == 0 {
|
||||
// Original string was empty; return suffix
|
||||
return suffixStr
|
||||
}
|
||||
|
||||
// Return content as is (with preserved ANSI codes)
|
||||
return finalContent
|
||||
}
|
||||
|
||||
// Title normalizes and uppercases a label string for use in headers.
|
||||
// It replaces underscores and certain dots with spaces and trims whitespace.
|
||||
func Title(name string) string {
|
||||
@@ -172,7 +40,7 @@ func Title(name string) string {
|
||||
// PadCenter centers a string within a specified width using a padding character.
|
||||
// Extra padding is split between left and right, with slight preference to left if uneven.
|
||||
func PadCenter(s, pad string, width int) string {
|
||||
gap := width - DisplayWidth(s)
|
||||
gap := width - twwidth.Width(s)
|
||||
if gap > 0 {
|
||||
// Calculate left and right padding; ceil ensures left gets extra if gap is odd
|
||||
gapLeft := int(math.Ceil(float64(gap) / 2))
|
||||
@@ -185,7 +53,7 @@ func PadCenter(s, pad string, width int) string {
|
||||
|
||||
// PadRight left-aligns a string within a specified width, filling remaining space on the right with padding.
|
||||
func PadRight(s, pad string, width int) string {
|
||||
gap := width - DisplayWidth(s)
|
||||
gap := width - twwidth.Width(s)
|
||||
if gap > 0 {
|
||||
// Append padding to the right
|
||||
return s + strings.Repeat(pad, gap)
|
||||
@@ -196,7 +64,7 @@ func PadRight(s, pad string, width int) string {
|
||||
|
||||
// PadLeft right-aligns a string within a specified width, filling remaining space on the left with padding.
|
||||
func PadLeft(s, pad string, width int) string {
|
||||
gap := width - DisplayWidth(s)
|
||||
gap := width - twwidth.Width(s)
|
||||
if gap > 0 {
|
||||
// Prepend padding to the left
|
||||
return strings.Repeat(pad, gap) + s
|
||||
@@ -208,9 +76,9 @@ func PadLeft(s, pad string, width int) string {
|
||||
// Pad aligns a string within a specified width using a padding character.
|
||||
// It truncates if the string is wider than the target width.
|
||||
func Pad(s string, padChar string, totalWidth int, alignment Align) string {
|
||||
sDisplayWidth := DisplayWidth(s)
|
||||
sDisplayWidth := twwidth.Width(s)
|
||||
if sDisplayWidth > totalWidth {
|
||||
return TruncateString(s, totalWidth) // Only truncate if necessary
|
||||
return twwidth.Truncate(s, totalWidth) // Only truncate if necessary
|
||||
}
|
||||
switch alignment {
|
||||
case AlignLeft:
|
||||
@@ -334,7 +202,7 @@ func BreakPoint(s string, limit int) int {
|
||||
runeCount := 0
|
||||
// Iterate over runes, accumulating display width
|
||||
for _, r := range s {
|
||||
runeWidth := DisplayWidth(string(r)) // Calculate width of individual rune
|
||||
runeWidth := twwidth.Width(string(r)) // Calculate width of individual rune
|
||||
if currentWidth+runeWidth > limit {
|
||||
// Adding this rune would exceed the limit; breakpoint is before this rune
|
||||
if currentWidth == 0 {
|
||||
|
||||
7
vendor/github.com/olekukonko/tablewriter/tw/renderer.go
generated
vendored
7
vendor/github.com/olekukonko/tablewriter/tw/renderer.go
generated
vendored
@@ -118,6 +118,13 @@ type Border struct {
|
||||
type StreamConfig struct {
|
||||
Enable bool
|
||||
|
||||
// StrictColumns, if true, causes Append() to return an error
|
||||
// in streaming mode if the number of cells in an appended row
|
||||
// does not match the established number of columns for the stream.
|
||||
// If false (default), rows with mismatched column counts will be
|
||||
// padded or truncated with a warning log.
|
||||
StrictColumns bool
|
||||
|
||||
// Deprecated: Use top-level Config.Widths for streaming width control.
|
||||
// This field will be removed in a future version. It will be respected if
|
||||
// Config.Widths is not set and this field is.
|
||||
|
||||
2
vendor/github.com/olekukonko/tablewriter/tw/tw.go
generated
vendored
2
vendor/github.com/olekukonko/tablewriter/tw/tw.go
generated
vendored
@@ -56,7 +56,7 @@ const (
|
||||
)
|
||||
|
||||
const (
|
||||
SectionHeader = "heder"
|
||||
SectionHeader = "header"
|
||||
SectionRow = "row"
|
||||
SectionFooter = "footer"
|
||||
)
|
||||
|
||||
23
vendor/github.com/olekukonko/tablewriter/zoo.go
generated
vendored
23
vendor/github.com/olekukonko/tablewriter/zoo.go
generated
vendored
@@ -4,6 +4,7 @@ import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"github.com/olekukonko/errors"
|
||||
"github.com/olekukonko/tablewriter/pkg/twwidth"
|
||||
"github.com/olekukonko/tablewriter/tw"
|
||||
"io"
|
||||
"math"
|
||||
@@ -165,7 +166,7 @@ func (t *Table) applyHorizontalMergeWidths(position tw.Position, ctx *renderCont
|
||||
if t.renderer != nil {
|
||||
rendererConfig := t.renderer.Config()
|
||||
if rendererConfig.Settings.Separators.BetweenColumns.Enabled() {
|
||||
separatorWidth = tw.DisplayWidth(rendererConfig.Symbols.Column())
|
||||
separatorWidth = twwidth.Width(rendererConfig.Symbols.Column())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -541,7 +542,7 @@ func (t *Table) buildCoreCellContexts(line []string, merges map[int]tw.MergeStat
|
||||
// It generates a []string where each element is the padding content for a column, using the specified padChar.
|
||||
func (t *Table) buildPaddingLineContents(padChar string, widths tw.Mapper[int, int], numCols int, merges map[int]tw.MergeState) []string {
|
||||
line := make([]string, numCols)
|
||||
padWidth := tw.DisplayWidth(padChar)
|
||||
padWidth := twwidth.Width(padChar)
|
||||
if padWidth < 1 {
|
||||
padWidth = 1
|
||||
}
|
||||
@@ -715,15 +716,15 @@ func (t *Table) calculateAndNormalizeWidths(ctx *renderContext) error {
|
||||
if 0 < len(t.config.Header.Padding.PerColumn) && t.config.Header.Padding.PerColumn[0].Paddable() {
|
||||
headerCellPadding = t.config.Header.Padding.PerColumn[0]
|
||||
}
|
||||
actualMergedHeaderContentPhysicalWidth := tw.DisplayWidth(mergedContentString) +
|
||||
tw.DisplayWidth(headerCellPadding.Left) +
|
||||
tw.DisplayWidth(headerCellPadding.Right)
|
||||
actualMergedHeaderContentPhysicalWidth := twwidth.Width(mergedContentString) +
|
||||
twwidth.Width(headerCellPadding.Left) +
|
||||
twwidth.Width(headerCellPadding.Right)
|
||||
currentSumOfColumnWidths := 0
|
||||
workingWidths.Each(func(_ int, w int) { currentSumOfColumnWidths += w })
|
||||
numSeparatorsInFullSpan := 0
|
||||
if ctx.numCols > 1 {
|
||||
if t.renderer != nil && t.renderer.Config().Settings.Separators.BetweenColumns.Enabled() {
|
||||
numSeparatorsInFullSpan = (ctx.numCols - 1) * tw.DisplayWidth(t.renderer.Config().Symbols.Column())
|
||||
numSeparatorsInFullSpan = (ctx.numCols - 1) * twwidth.Width(t.renderer.Config().Symbols.Column())
|
||||
}
|
||||
}
|
||||
totalCurrentSpanPhysicalWidth := currentSumOfColumnWidths + numSeparatorsInFullSpan
|
||||
@@ -784,7 +785,7 @@ func (t *Table) calculateAndNormalizeWidths(ctx *renderContext) error {
|
||||
finalWidths.Each(func(_ int, w int) { currentSumOfFinalColWidths += w })
|
||||
numSeparators := 0
|
||||
if ctx.numCols > 1 && t.renderer != nil && t.renderer.Config().Settings.Separators.BetweenColumns.Enabled() {
|
||||
numSeparators = (ctx.numCols - 1) * tw.DisplayWidth(t.renderer.Config().Symbols.Column())
|
||||
numSeparators = (ctx.numCols - 1) * twwidth.Width(t.renderer.Config().Symbols.Column())
|
||||
}
|
||||
totalCurrentTablePhysicalWidth := currentSumOfFinalColWidths + numSeparators
|
||||
if totalCurrentTablePhysicalWidth > t.config.Widths.Global {
|
||||
@@ -1655,16 +1656,16 @@ func (t *Table) updateWidths(row []string, widths tw.Mapper[int, int], padding t
|
||||
t.logger.Debugf(" Col %d: Using global padding: L:'%s' R:'%s'", i, padding.Global.Left, padding.Global.Right)
|
||||
}
|
||||
|
||||
padLeftWidth := tw.DisplayWidth(colPad.Left)
|
||||
padRightWidth := tw.DisplayWidth(colPad.Right)
|
||||
padLeftWidth := twwidth.Width(colPad.Left)
|
||||
padRightWidth := twwidth.Width(colPad.Right)
|
||||
|
||||
// Split cell into lines and find maximum content width
|
||||
lines := strings.Split(cell, tw.NewLine)
|
||||
contentWidth := 0
|
||||
for _, line := range lines {
|
||||
lineWidth := tw.DisplayWidth(line)
|
||||
lineWidth := twwidth.Width(line)
|
||||
if t.config.Behavior.TrimSpace.Enabled() {
|
||||
lineWidth = tw.DisplayWidth(t.Trimmer(line))
|
||||
lineWidth = twwidth.Width(t.Trimmer(line))
|
||||
}
|
||||
if lineWidth > contentWidth {
|
||||
contentWidth = lineWidth
|
||||
|
||||
3
vendor/modules.txt
vendored
3
vendor/modules.txt
vendored
@@ -1043,10 +1043,11 @@ github.com/olekukonko/errors
|
||||
github.com/olekukonko/ll
|
||||
github.com/olekukonko/ll/lh
|
||||
github.com/olekukonko/ll/lx
|
||||
# github.com/olekukonko/tablewriter v1.0.7
|
||||
# github.com/olekukonko/tablewriter v1.0.8
|
||||
## explicit; go 1.21
|
||||
github.com/olekukonko/tablewriter
|
||||
github.com/olekukonko/tablewriter/pkg/twwarp
|
||||
github.com/olekukonko/tablewriter/pkg/twwidth
|
||||
github.com/olekukonko/tablewriter/renderer
|
||||
github.com/olekukonko/tablewriter/tw
|
||||
# github.com/onsi/ginkgo v1.16.5
|
||||
|
||||
Reference in New Issue
Block a user