build(deps): bump github.com/olekukonko/tablewriter from 1.0.9 to 1.1.0
Bumps [github.com/olekukonko/tablewriter](https://github.com/olekukonko/tablewriter) from 1.0.9 to 1.1.0. - [Commits](https://github.com/olekukonko/tablewriter/compare/v1.0.9...v1.1.0) --- updated-dependencies: - dependency-name: github.com/olekukonko/tablewriter dependency-version: 1.1.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
committed by
Ralf Haferkamp
parent
d05d5bdc6f
commit
f4eaa8bd5b
@@ -59,7 +59,7 @@ require (
|
|||||||
github.com/nats-io/nats-server/v2 v2.12.0
|
github.com/nats-io/nats-server/v2 v2.12.0
|
||||||
github.com/nats-io/nats.go v1.46.0
|
github.com/nats-io/nats.go v1.46.0
|
||||||
github.com/oklog/run v1.2.0
|
github.com/oklog/run v1.2.0
|
||||||
github.com/olekukonko/tablewriter v1.0.9
|
github.com/olekukonko/tablewriter v1.1.0
|
||||||
github.com/onsi/ginkgo v1.16.5
|
github.com/onsi/ginkgo v1.16.5
|
||||||
github.com/onsi/ginkgo/v2 v2.25.3
|
github.com/onsi/ginkgo/v2 v2.25.3
|
||||||
github.com/onsi/gomega v1.38.2
|
github.com/onsi/gomega v1.38.2
|
||||||
|
|||||||
@@ -914,8 +914,8 @@ github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXI
|
|||||||
github.com/olekukonko/ll v0.0.9 h1:Y+1YqDfVkqMWuEQMclsF9HUR5+a82+dxJuL1HHSRpxI=
|
github.com/olekukonko/ll v0.0.9 h1:Y+1YqDfVkqMWuEQMclsF9HUR5+a82+dxJuL1HHSRpxI=
|
||||||
github.com/olekukonko/ll v0.0.9/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g=
|
github.com/olekukonko/ll v0.0.9/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g=
|
||||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||||
github.com/olekukonko/tablewriter v1.0.9 h1:XGwRsYLC2bY7bNd93Dk51bcPZksWZmLYuaTHR0FqfL8=
|
github.com/olekukonko/tablewriter v1.1.0 h1:N0LHrshF4T39KvI96fn6GT8HEjXRXYNDrDjKFDB7RIY=
|
||||||
github.com/olekukonko/tablewriter v1.0.9/go.mod h1:5c+EBPeSqvXnLLgkm9isDdzR3wjfBkHR9Nhfp3NWrzo=
|
github.com/olekukonko/tablewriter v1.1.0/go.mod h1:5c+EBPeSqvXnLLgkm9isDdzR3wjfBkHR9Nhfp3NWrzo=
|
||||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
github.com/onsi/ginkgo v1.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.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||||
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
|
||||||
|
|||||||
+55
@@ -0,0 +1,55 @@
|
|||||||
|
# See for configurations: https://golangci-lint.run/usage/configuration/
|
||||||
|
version: 2
|
||||||
|
# See: https://golangci-lint.run/usage/formatters/
|
||||||
|
formatters:
|
||||||
|
default: none
|
||||||
|
enable:
|
||||||
|
- gofmt # https://pkg.go.dev/cmd/gofmt
|
||||||
|
- gofumpt # https://github.com/mvdan/gofumpt
|
||||||
|
|
||||||
|
settings:
|
||||||
|
gofmt:
|
||||||
|
simplify: true # Simplify code: gofmt with `-s` option.
|
||||||
|
|
||||||
|
gofumpt:
|
||||||
|
# Module path which contains the source code being formatted.
|
||||||
|
# Default: ""
|
||||||
|
module-path: github.com/olekukonko/tablewriter # Should match with module in go.mod
|
||||||
|
# Choose whether to use the extra rules.
|
||||||
|
# Default: false
|
||||||
|
extra-rules: true
|
||||||
|
|
||||||
|
# See: https://golangci-lint.run/usage/linters/
|
||||||
|
linters:
|
||||||
|
default: none
|
||||||
|
enable:
|
||||||
|
- staticcheck
|
||||||
|
- govet
|
||||||
|
- gocritic
|
||||||
|
# - unused # TODO: There are many unused functions, should I directly remove those ?
|
||||||
|
- ineffassign
|
||||||
|
- unconvert
|
||||||
|
- mirror
|
||||||
|
- usestdlibvars
|
||||||
|
- loggercheck
|
||||||
|
- exptostd
|
||||||
|
- godot
|
||||||
|
- perfsprint
|
||||||
|
|
||||||
|
# See: https://golangci-lint.run/usage/false-positives/
|
||||||
|
exclusion:
|
||||||
|
# paths:
|
||||||
|
# rules:
|
||||||
|
|
||||||
|
settings:
|
||||||
|
staticcheck:
|
||||||
|
checks:
|
||||||
|
- all
|
||||||
|
- "-SA1019" # disabled because it warns about deprecated: kept for compatibility will be removed soon
|
||||||
|
- "-ST1019" # disabled because it warns about deprecated: kept for compatibility will be removed soon
|
||||||
|
- "-ST1021" # disabled because it warns to have comment on exported packages
|
||||||
|
- "-ST1000" # disabled because it warns to have comment on exported functions
|
||||||
|
- "-ST1020" # disabled because it warns to have at least one file in a package should have a package comment
|
||||||
|
|
||||||
|
godot:
|
||||||
|
period: false
|
||||||
+5
-3
@@ -28,7 +28,7 @@ go get github.com/olekukonko/tablewriter@v0.0.5
|
|||||||
#### Latest Version
|
#### Latest Version
|
||||||
The latest stable version
|
The latest stable version
|
||||||
```bash
|
```bash
|
||||||
go get github.com/olekukonko/tablewriter@v1.0.9
|
go get github.com/olekukonko/tablewriter@v1.1.0
|
||||||
```
|
```
|
||||||
|
|
||||||
**Warning:** Version `v1.0.0` contains missing functionality and should not be used.
|
**Warning:** Version `v1.0.0` contains missing functionality and should not be used.
|
||||||
@@ -62,7 +62,7 @@ func main() {
|
|||||||
data := [][]string{
|
data := [][]string{
|
||||||
{"Package", "Version", "Status"},
|
{"Package", "Version", "Status"},
|
||||||
{"tablewriter", "v0.0.5", "legacy"},
|
{"tablewriter", "v0.0.5", "legacy"},
|
||||||
{"tablewriter", "v1.0.9", "latest"},
|
{"tablewriter", "v1.1.0", "latest"},
|
||||||
}
|
}
|
||||||
|
|
||||||
table := tablewriter.NewWriter(os.Stdout)
|
table := tablewriter.NewWriter(os.Stdout)
|
||||||
@@ -77,7 +77,7 @@ func main() {
|
|||||||
│ PACKAGE │ VERSION │ STATUS │
|
│ PACKAGE │ VERSION │ STATUS │
|
||||||
├─────────────┼─────────┼────────┤
|
├─────────────┼─────────┼────────┤
|
||||||
│ tablewriter │ v0.0.5 │ legacy │
|
│ tablewriter │ v0.0.5 │ legacy │
|
||||||
│ tablewriter │ v1.0.9 │ latest │
|
│ tablewriter │ v1.1.0 │ latest │
|
||||||
└─────────────┴─────────┴────────┘
|
└─────────────┴─────────┴────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -1081,6 +1081,8 @@ func (t Time) Format() string {
|
|||||||
|
|
||||||
- `AutoFormat` changes See [#261](https://github.com/olekukonko/tablewriter/issues/261)
|
- `AutoFormat` changes See [#261](https://github.com/olekukonko/tablewriter/issues/261)
|
||||||
|
|
||||||
|
## What is new
|
||||||
|
- `Counting` changes See [#294](https://github.com/olekukonko/tablewriter/issues/294)
|
||||||
|
|
||||||
## Command-Line Tool
|
## Command-Line Tool
|
||||||
|
|
||||||
|
|||||||
+2
-5
@@ -14,6 +14,7 @@ type Config struct {
|
|||||||
Stream tw.StreamConfig
|
Stream tw.StreamConfig
|
||||||
Behavior tw.Behavior
|
Behavior tw.Behavior
|
||||||
Widths tw.CellWidth
|
Widths tw.CellWidth
|
||||||
|
Counter tw.Counter
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConfigBuilder provides a fluent interface for building Config
|
// ConfigBuilder provides a fluent interface for building Config
|
||||||
@@ -199,11 +200,7 @@ func (b *ConfigBuilder) WithHeaderMergeMode(mergeMode int) *ConfigBuilder {
|
|||||||
// WithMaxWidth sets the maximum width for the entire table (0 means unlimited).
|
// WithMaxWidth sets the maximum width for the entire table (0 means unlimited).
|
||||||
// Negative values are treated as 0.
|
// Negative values are treated as 0.
|
||||||
func (b *ConfigBuilder) WithMaxWidth(width int) *ConfigBuilder {
|
func (b *ConfigBuilder) WithMaxWidth(width int) *ConfigBuilder {
|
||||||
if width < 0 {
|
b.config.MaxWidth = max(width, 0)
|
||||||
b.config.MaxWidth = 0
|
|
||||||
} else {
|
|
||||||
b.config.MaxWidth = width
|
|
||||||
}
|
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+43
-9
@@ -1,11 +1,12 @@
|
|||||||
package tablewriter
|
package tablewriter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"reflect"
|
||||||
|
|
||||||
"github.com/mattn/go-runewidth"
|
"github.com/mattn/go-runewidth"
|
||||||
"github.com/olekukonko/ll"
|
"github.com/olekukonko/ll"
|
||||||
"github.com/olekukonko/tablewriter/pkg/twwidth"
|
"github.com/olekukonko/tablewriter/pkg/twwidth"
|
||||||
"github.com/olekukonko/tablewriter/tw"
|
"github.com/olekukonko/tablewriter/tw"
|
||||||
"reflect"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Option defines a function type for configuring a Table instance.
|
// Option defines a function type for configuring a Table instance.
|
||||||
@@ -498,6 +499,17 @@ func WithTrimSpace(state tw.State) Option {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithTrimLine sets whether empty visual lines within a cell are trimmed.
|
||||||
|
// Logs the change if debugging is enabled.
|
||||||
|
func WithTrimLine(state tw.State) Option {
|
||||||
|
return func(target *Table) {
|
||||||
|
target.config.Behavior.TrimLine = state
|
||||||
|
if target.logger != nil {
|
||||||
|
target.logger.Debugf("Option: WithTrimLine applied to Table: %v", state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// WithHeaderAutoFormat enables or disables automatic formatting for header cells.
|
// WithHeaderAutoFormat enables or disables automatic formatting for header cells.
|
||||||
// Logs the change if debugging is enabled.
|
// Logs the change if debugging is enabled.
|
||||||
func WithHeaderAutoFormat(state tw.State) Option {
|
func WithHeaderAutoFormat(state tw.State) Option {
|
||||||
@@ -607,10 +619,8 @@ func WithRendition(rendition tw.Rendition) Option {
|
|||||||
if target.logger != nil {
|
if target.logger != nil {
|
||||||
target.logger.Debugf("Option: WithRendition: Applied to renderer via Renditioning.SetRendition(): %+v", rendition)
|
target.logger.Debugf("Option: WithRendition: Applied to renderer via Renditioning.SetRendition(): %+v", rendition)
|
||||||
}
|
}
|
||||||
} else {
|
} else if target.logger != nil {
|
||||||
if target.logger != nil {
|
target.logger.Warnf("Option: WithRendition: Current renderer type %T does not implement tw.Renditioning. Rendition may not be applied as expected.", target.renderer)
|
||||||
target.logger.Warnf("Option: WithRendition: Current renderer type %T does not implement tw.Renditioning. Rendition may not be applied as expected.", target.renderer)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -654,15 +664,38 @@ func WithSymbols(symbols tw.Symbols) Option {
|
|||||||
if target.logger != nil {
|
if target.logger != nil {
|
||||||
target.logger.Debugf("Option: WithRendition: Applied to renderer via Renditioning.SetRendition(): %+v", cfg)
|
target.logger.Debugf("Option: WithRendition: Applied to renderer via Renditioning.SetRendition(): %+v", cfg)
|
||||||
}
|
}
|
||||||
} else {
|
} else if target.logger != nil {
|
||||||
if target.logger != nil {
|
target.logger.Warnf("Option: WithRendition: Current renderer type %T does not implement tw.Renditioning. Rendition may not be applied as expected.", target.renderer)
|
||||||
target.logger.Warnf("Option: WithRendition: Current renderer type %T does not implement tw.Renditioning. Rendition may not be applied as expected.", target.renderer)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithCounters enables line counting by wrapping the table's writer.
|
||||||
|
// If a custom counter (that implements tw.Counter) is provided, it will be used.
|
||||||
|
// If the provided counter is nil, a default tw.LineCounter will be used.
|
||||||
|
// The final count can be retrieved via the table.Lines() method after Render() is called.
|
||||||
|
func WithCounters(counters ...tw.Counter) Option {
|
||||||
|
return func(target *Table) {
|
||||||
|
// Iterate through the provided counters and add any non-nil ones.
|
||||||
|
for _, c := range counters {
|
||||||
|
if c != nil {
|
||||||
|
target.counters = append(target.counters, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithLineCounter enables the default line counter.
|
||||||
|
// A new instance of tw.LineCounter is added to the table's list of counters.
|
||||||
|
// The total count can be retrieved via the table.Lines() method after Render() is called.
|
||||||
|
func WithLineCounter() Option {
|
||||||
|
return func(target *Table) {
|
||||||
|
// Important: Create a new instance so tables don't share counters.
|
||||||
|
target.counters = append(target.counters, &tw.LineCounter{})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// defaultConfig returns a default Config with sensible settings for headers, rows, footers, and behavior.
|
// defaultConfig returns a default Config with sensible settings for headers, rows, footers, and behavior.
|
||||||
func defaultConfig() Config {
|
func defaultConfig() Config {
|
||||||
return Config{
|
return Config{
|
||||||
@@ -717,6 +750,7 @@ func defaultConfig() Config {
|
|||||||
Behavior: tw.Behavior{
|
Behavior: tw.Behavior{
|
||||||
AutoHide: tw.Off,
|
AutoHide: tw.Off,
|
||||||
TrimSpace: tw.On,
|
TrimSpace: tw.On,
|
||||||
|
TrimLine: tw.On,
|
||||||
Structs: tw.Struct{
|
Structs: tw.Struct{
|
||||||
AutoHeader: tw.Off,
|
AutoHeader: tw.Off,
|
||||||
Tags: []string{"json", "db"},
|
Tags: []string{"json", "db"},
|
||||||
|
|||||||
+10
-8
@@ -2,10 +2,11 @@ package twwidth
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"github.com/mattn/go-runewidth"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/mattn/go-runewidth"
|
||||||
)
|
)
|
||||||
|
|
||||||
// condition holds the global runewidth configuration, including East Asian width settings.
|
// condition holds the global runewidth configuration, including East Asian width settings.
|
||||||
@@ -35,15 +36,15 @@ var widthCache map[cacheKey]int
|
|||||||
// including CSI (Control Sequence Introducer) and OSC (Operating System Command) sequences.
|
// including CSI (Control Sequence Introducer) and OSC (Operating System Command) sequences.
|
||||||
// The returned regex can be used to strip ANSI codes from strings.
|
// The returned regex can be used to strip ANSI codes from strings.
|
||||||
func Filter() *regexp.Regexp {
|
func Filter() *regexp.Regexp {
|
||||||
var regESC = "\x1b" // ASCII escape character
|
regESC := "\x1b" // ASCII escape character
|
||||||
var regBEL = "\x07" // ASCII bell character
|
regBEL := "\x07" // ASCII bell character
|
||||||
|
|
||||||
// ANSI string terminator: either ESC+\ or BEL
|
// ANSI string terminator: either ESC+\ or BEL
|
||||||
var regST = "(" + regexp.QuoteMeta(regESC+"\\") + "|" + regexp.QuoteMeta(regBEL) + ")"
|
regST := "(" + regexp.QuoteMeta(regESC+"\\") + "|" + regexp.QuoteMeta(regBEL) + ")"
|
||||||
// Control Sequence Introducer (CSI): ESC[ followed by parameters and a final byte
|
// Control Sequence Introducer (CSI): ESC[ followed by parameters and a final byte
|
||||||
var regCSI = regexp.QuoteMeta(regESC+"[") + "[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e]"
|
regCSI := regexp.QuoteMeta(regESC+"[") + "[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e]"
|
||||||
// Operating System Command (OSC): ESC] followed by arbitrary content until a terminator
|
// Operating System Command (OSC): ESC] followed by arbitrary content until a terminator
|
||||||
var regOSC = regexp.QuoteMeta(regESC+"]") + ".*?" + regST
|
regOSC := regexp.QuoteMeta(regESC+"]") + ".*?" + regST
|
||||||
|
|
||||||
// Combine CSI and OSC patterns into a single regex
|
// Combine CSI and OSC patterns into a single regex
|
||||||
return regexp.MustCompile("(" + regCSI + "|" + regOSC + ")")
|
return regexp.MustCompile("(" + regCSI + "|" + regOSC + ")")
|
||||||
@@ -257,11 +258,12 @@ func Truncate(s string, maxWidth int, suffix ...string) string {
|
|||||||
terminated := false
|
terminated := false
|
||||||
if seqLen >= 2 {
|
if seqLen >= 2 {
|
||||||
introducer := seqBytes[1]
|
introducer := seqBytes[1]
|
||||||
if introducer == '[' {
|
switch introducer {
|
||||||
|
case '[':
|
||||||
if seqLen >= 3 && r >= 0x40 && r <= 0x7E {
|
if seqLen >= 3 && r >= 0x40 && r <= 0x7E {
|
||||||
terminated = true
|
terminated = true
|
||||||
}
|
}
|
||||||
} else if introducer == ']' {
|
case ']':
|
||||||
if r == '\x07' {
|
if r == '\x07' {
|
||||||
terminated = true
|
terminated = true
|
||||||
} else if seqLen > 1 && seqBytes[seqLen-2] == '\x1b' && r == '\\' { // Check for ST: \x1b\
|
} else if seqLen > 1 && seqBytes[seqLen-2] == '\x1b' && r == '\\' { // Check for ST: \x1b\
|
||||||
|
|||||||
+19
-25
@@ -1,11 +1,12 @@
|
|||||||
package renderer
|
package renderer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/olekukonko/ll"
|
|
||||||
"github.com/olekukonko/tablewriter/pkg/twwidth"
|
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/olekukonko/ll"
|
||||||
|
"github.com/olekukonko/tablewriter/pkg/twwidth"
|
||||||
|
|
||||||
"github.com/olekukonko/tablewriter/tw"
|
"github.com/olekukonko/tablewriter/tw"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -283,7 +284,7 @@ func (f *Blueprint) formatCell(content string, width int, padding tw.Padding, al
|
|||||||
leftPadChar := padding.Left
|
leftPadChar := padding.Left
|
||||||
rightPadChar := padding.Right
|
rightPadChar := padding.Right
|
||||||
|
|
||||||
//if f.config.Settings.Cushion.Enabled() || f.config.Settings.Cushion.Default() {
|
// if f.config.Settings.Cushion.Enabled() || f.config.Settings.Cushion.Default() {
|
||||||
// if leftPadChar == tw.Empty {
|
// if leftPadChar == tw.Empty {
|
||||||
// leftPadChar = tw.Space
|
// leftPadChar = tw.Space
|
||||||
// }
|
// }
|
||||||
@@ -297,10 +298,7 @@ func (f *Blueprint) formatCell(content string, width int, padding tw.Padding, al
|
|||||||
padRightWidth := twwidth.Width(rightPadChar)
|
padRightWidth := twwidth.Width(rightPadChar)
|
||||||
|
|
||||||
// Calculate available width for content
|
// Calculate available width for content
|
||||||
availableContentWidth := width - padLeftWidth - padRightWidth
|
availableContentWidth := max(width-padLeftWidth-padRightWidth, 0)
|
||||||
if availableContentWidth < 0 {
|
|
||||||
availableContentWidth = 0
|
|
||||||
}
|
|
||||||
f.logger.Debugf("Available content width: %d", availableContentWidth)
|
f.logger.Debugf("Available content width: %d", availableContentWidth)
|
||||||
|
|
||||||
// Truncate content if it exceeds available width
|
// Truncate content if it exceeds available width
|
||||||
@@ -311,10 +309,7 @@ func (f *Blueprint) formatCell(content string, width int, padding tw.Padding, al
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Calculate total padding needed
|
// Calculate total padding needed
|
||||||
totalPaddingWidth := width - runeWidth
|
totalPaddingWidth := max(width-runeWidth, 0)
|
||||||
if totalPaddingWidth < 0 {
|
|
||||||
totalPaddingWidth = 0
|
|
||||||
}
|
|
||||||
f.logger.Debugf("Total padding width: %d", totalPaddingWidth)
|
f.logger.Debugf("Total padding width: %d", totalPaddingWidth)
|
||||||
|
|
||||||
var result strings.Builder
|
var result strings.Builder
|
||||||
@@ -435,7 +430,7 @@ func (f *Blueprint) renderLine(ctx tw.Formatting) {
|
|||||||
prevWidth := ctx.Row.Widths.Get(colIndex - 1)
|
prevWidth := ctx.Row.Widths.Get(colIndex - 1)
|
||||||
prevCellCtx, prevOk := ctx.Row.Current[colIndex-1]
|
prevCellCtx, prevOk := ctx.Row.Current[colIndex-1]
|
||||||
prevIsHMergeEnd := prevOk && prevCellCtx.Merge.Horizontal.Present && prevCellCtx.Merge.Horizontal.End
|
prevIsHMergeEnd := prevOk && prevCellCtx.Merge.Horizontal.Present && prevCellCtx.Merge.Horizontal.End
|
||||||
if (prevWidth > 0 || prevIsHMergeEnd) && (!ok || !(cellCtx.Merge.Horizontal.Present && !cellCtx.Merge.Horizontal.Start)) {
|
if (prevWidth > 0 || prevIsHMergeEnd) && (!ok || (!cellCtx.Merge.Horizontal.Present || cellCtx.Merge.Horizontal.Start)) {
|
||||||
shouldAddSeparator = true
|
shouldAddSeparator = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -454,10 +449,7 @@ func (f *Blueprint) renderLine(ctx tw.Formatting) {
|
|||||||
if ctx.Row.Position == tw.Row {
|
if ctx.Row.Position == tw.Row {
|
||||||
dynamicTotalWidth := 0
|
dynamicTotalWidth := 0
|
||||||
for k := 0; k < span && colIndex+k < numCols; k++ {
|
for k := 0; k < span && colIndex+k < numCols; k++ {
|
||||||
normWidth := ctx.NormalizedWidths.Get(colIndex + k)
|
normWidth := max(ctx.NormalizedWidths.Get(colIndex+k), 0)
|
||||||
if normWidth < 0 {
|
|
||||||
normWidth = 0
|
|
||||||
}
|
|
||||||
dynamicTotalWidth += normWidth
|
dynamicTotalWidth += normWidth
|
||||||
if k > 0 && separatorDisplayWidth > 0 && ctx.NormalizedWidths.Get(colIndex+k) > 0 {
|
if k > 0 && separatorDisplayWidth > 0 && ctx.NormalizedWidths.Get(colIndex+k) > 0 {
|
||||||
dynamicTotalWidth += separatorDisplayWidth
|
dynamicTotalWidth += separatorDisplayWidth
|
||||||
@@ -501,21 +493,24 @@ func (f *Blueprint) renderLine(ctx tw.Formatting) {
|
|||||||
// Set cell padding and alignment
|
// Set cell padding and alignment
|
||||||
padding := cellCtx.Padding
|
padding := cellCtx.Padding
|
||||||
align := cellCtx.Align
|
align := cellCtx.Align
|
||||||
if align == tw.AlignNone {
|
switch align {
|
||||||
if ctx.Row.Position == tw.Header {
|
case tw.AlignNone:
|
||||||
|
switch ctx.Row.Position {
|
||||||
|
case tw.Header:
|
||||||
align = tw.AlignCenter
|
align = tw.AlignCenter
|
||||||
} else if ctx.Row.Position == tw.Footer {
|
case tw.Footer:
|
||||||
align = tw.AlignRight
|
align = tw.AlignRight
|
||||||
} else {
|
default:
|
||||||
align = tw.AlignLeft
|
align = tw.AlignLeft
|
||||||
}
|
}
|
||||||
f.logger.Debugf("renderLine: col %d (data: '%s') using renderer default align '%s' for position %s.", colIndex, cellCtx.Data, align, ctx.Row.Position)
|
f.logger.Debugf("renderLine: col %d (data: '%s') using renderer default align '%s' for position %s.", colIndex, cellCtx.Data, align, ctx.Row.Position)
|
||||||
} else if align == tw.Skip {
|
case tw.Skip:
|
||||||
if ctx.Row.Position == tw.Header {
|
switch ctx.Row.Position {
|
||||||
|
case tw.Header:
|
||||||
align = tw.AlignCenter
|
align = tw.AlignCenter
|
||||||
} else if ctx.Row.Position == tw.Footer {
|
case tw.Footer:
|
||||||
align = tw.AlignRight
|
align = tw.AlignRight
|
||||||
} else {
|
default:
|
||||||
align = tw.AlignLeft
|
align = tw.AlignLeft
|
||||||
}
|
}
|
||||||
f.logger.Debugf("renderLine: col %d (data: '%s') cellCtx.Align was Skip/empty, falling back to basic default '%s'.", colIndex, cellCtx.Data, align)
|
f.logger.Debugf("renderLine: col %d (data: '%s') cellCtx.Align was Skip/empty, falling back to basic default '%s'.", colIndex, cellCtx.Data, align)
|
||||||
@@ -587,7 +582,6 @@ func (f *Blueprint) renderLine(ctx tw.Formatting) {
|
|||||||
func (f *Blueprint) Rendition(config tw.Rendition) {
|
func (f *Blueprint) Rendition(config tw.Rendition) {
|
||||||
f.config = mergeRendition(f.config, config)
|
f.config = mergeRendition(f.config, config)
|
||||||
f.logger.Debugf("Blueprint.Rendition updated. New config: %+v", f.config)
|
f.logger.Debugf("Blueprint.Rendition updated. New config: %+v", f.config)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure Blueprint implements tw.Renditioning
|
// Ensure Blueprint implements tw.Renditioning
|
||||||
|
|||||||
+14
-22
@@ -1,12 +1,13 @@
|
|||||||
package renderer
|
package renderer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
"github.com/olekukonko/ll"
|
"github.com/olekukonko/ll"
|
||||||
"github.com/olekukonko/ll/lh"
|
"github.com/olekukonko/ll/lh"
|
||||||
"github.com/olekukonko/tablewriter/pkg/twwidth"
|
"github.com/olekukonko/tablewriter/pkg/twwidth"
|
||||||
"io"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/olekukonko/tablewriter/tw"
|
"github.com/olekukonko/tablewriter/tw"
|
||||||
)
|
)
|
||||||
@@ -378,22 +379,19 @@ func (c *Colorized) formatCell(content string, width int, padding tw.Padding, al
|
|||||||
|
|
||||||
// Set default padding characters
|
// Set default padding characters
|
||||||
padLeftCharStr := padding.Left
|
padLeftCharStr := padding.Left
|
||||||
if padLeftCharStr == tw.Empty {
|
// if padLeftCharStr == tw.Empty {
|
||||||
padLeftCharStr = tw.Space
|
// padLeftCharStr = tw.Space
|
||||||
}
|
//}
|
||||||
padRightCharStr := padding.Right
|
padRightCharStr := padding.Right
|
||||||
if padRightCharStr == tw.Empty {
|
// if padRightCharStr == tw.Empty {
|
||||||
padRightCharStr = tw.Space
|
// padRightCharStr = tw.Space
|
||||||
}
|
//}
|
||||||
|
|
||||||
// Calculate padding widths
|
// Calculate padding widths
|
||||||
definedPadLeftWidth := twwidth.Width(padLeftCharStr)
|
definedPadLeftWidth := twwidth.Width(padLeftCharStr)
|
||||||
definedPadRightWidth := twwidth.Width(padRightCharStr)
|
definedPadRightWidth := twwidth.Width(padRightCharStr)
|
||||||
// Calculate available width for content and alignment
|
// Calculate available width for content and alignment
|
||||||
availableForContentAndAlign := width - definedPadLeftWidth - definedPadRightWidth
|
availableForContentAndAlign := max(width-definedPadLeftWidth-definedPadRightWidth, 0)
|
||||||
if availableForContentAndAlign < 0 {
|
|
||||||
availableForContentAndAlign = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Truncate content if it exceeds available width
|
// Truncate content if it exceeds available width
|
||||||
if contentVisualWidth > availableForContentAndAlign {
|
if contentVisualWidth > availableForContentAndAlign {
|
||||||
@@ -403,10 +401,7 @@ func (c *Colorized) formatCell(content string, width int, padding tw.Padding, al
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Calculate remaining space for alignment
|
// Calculate remaining space for alignment
|
||||||
remainingSpaceForAlignment := availableForContentAndAlign - contentVisualWidth
|
remainingSpaceForAlignment := max(availableForContentAndAlign-contentVisualWidth, 0)
|
||||||
if remainingSpaceForAlignment < 0 {
|
|
||||||
remainingSpaceForAlignment = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply alignment padding
|
// Apply alignment padding
|
||||||
leftAlignmentPadSpaces := tw.Empty
|
leftAlignmentPadSpaces := tw.Empty
|
||||||
@@ -539,7 +534,7 @@ func (c *Colorized) renderLine(ctx tw.Formatting, line []string, tint Tint) {
|
|||||||
shouldAddSeparator := false
|
shouldAddSeparator := false
|
||||||
if i > 0 && c.config.Settings.Separators.BetweenColumns.Enabled() {
|
if i > 0 && c.config.Settings.Separators.BetweenColumns.Enabled() {
|
||||||
cellCtx, ok := ctx.Row.Current[i]
|
cellCtx, ok := ctx.Row.Current[i]
|
||||||
if !ok || !(cellCtx.Merge.Horizontal.Present && !cellCtx.Merge.Horizontal.Start) {
|
if !ok || (!cellCtx.Merge.Horizontal.Present || cellCtx.Merge.Horizontal.Start) {
|
||||||
shouldAddSeparator = true
|
shouldAddSeparator = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -574,10 +569,7 @@ func (c *Colorized) renderLine(ctx tw.Formatting, line []string, tint Tint) {
|
|||||||
dynamicTotalWidth := 0
|
dynamicTotalWidth := 0
|
||||||
for k := 0; k < span && i+k < numCols; k++ {
|
for k := 0; k < span && i+k < numCols; k++ {
|
||||||
colToSum := i + k
|
colToSum := i + k
|
||||||
normWidth := ctx.NormalizedWidths.Get(colToSum)
|
normWidth := max(ctx.NormalizedWidths.Get(colToSum), 0)
|
||||||
if normWidth < 0 {
|
|
||||||
normWidth = 0
|
|
||||||
}
|
|
||||||
dynamicTotalWidth += normWidth
|
dynamicTotalWidth += normWidth
|
||||||
if k > 0 && separatorDisplayWidth > 0 {
|
if k > 0 && separatorDisplayWidth > 0 {
|
||||||
dynamicTotalWidth += separatorDisplayWidth
|
dynamicTotalWidth += separatorDisplayWidth
|
||||||
@@ -633,7 +625,7 @@ func (c *Colorized) renderLine(ctx tw.Formatting, line []string, tint Tint) {
|
|||||||
}
|
}
|
||||||
// Override alignment for footer merges or TOTAL pattern
|
// Override alignment for footer merges or TOTAL pattern
|
||||||
if (ctx.Row.Position == tw.Footer && isHMergeStart) || isTotalPattern {
|
if (ctx.Row.Position == tw.Footer && isHMergeStart) || isTotalPattern {
|
||||||
if align != tw.AlignRight {
|
if align == tw.AlignNone {
|
||||||
c.logger.Debugf("renderLine: Applying AlignRight override for Footer HMerge/TOTAL pattern at col %d. Original/default align was: %s", i, align)
|
c.logger.Debugf("renderLine: Applying AlignRight override for Footer HMerge/TOTAL pattern at col %d. Original/default align was: %s", i, align)
|
||||||
align = tw.AlignRight
|
align = tw.AlignRight
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -2,6 +2,7 @@ package renderer
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
"github.com/olekukonko/tablewriter/tw"
|
"github.com/olekukonko/tablewriter/tw"
|
||||||
)
|
)
|
||||||
@@ -82,7 +83,6 @@ func defaultColorized() ColorizedConfig {
|
|||||||
|
|
||||||
// defaultOceanRendererConfig returns a base tw.Rendition for the Ocean renderer.
|
// defaultOceanRendererConfig returns a base tw.Rendition for the Ocean renderer.
|
||||||
func defaultOceanRendererConfig() tw.Rendition {
|
func defaultOceanRendererConfig() tw.Rendition {
|
||||||
|
|
||||||
return tw.Rendition{
|
return tw.Rendition{
|
||||||
Borders: tw.Border{
|
Borders: tw.Border{
|
||||||
Left: tw.On, Right: tw.On, Top: tw.On, Bottom: tw.On,
|
Left: tw.On, Right: tw.On, Top: tw.On, Bottom: tw.On,
|
||||||
@@ -190,7 +190,7 @@ func mergeSettings(defaults, overrides tw.Settings) tw.Settings {
|
|||||||
defaults.CompactMode = overrides.CompactMode
|
defaults.CompactMode = overrides.CompactMode
|
||||||
}
|
}
|
||||||
|
|
||||||
//if overrides.Cushion != tw.Unknown {
|
// if overrides.Cushion != tw.Unknown {
|
||||||
// defaults.Cushion = overrides.Cushion
|
// defaults.Cushion = overrides.Cushion
|
||||||
//}
|
//}
|
||||||
|
|
||||||
|
|||||||
+3
-2
@@ -3,11 +3,12 @@ package renderer
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/olekukonko/ll"
|
|
||||||
"html"
|
"html"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/olekukonko/ll"
|
||||||
|
|
||||||
"github.com/olekukonko/tablewriter/tw"
|
"github.com/olekukonko/tablewriter/tw"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -82,7 +83,7 @@ func (h *HTML) Config() tw.Rendition {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// debugLog appends a formatted message to the debug trace if debugging is enabled.
|
// debugLog appends a formatted message to the debug trace if debugging is enabled.
|
||||||
//func (h *HTML) debugLog(format string, a ...interface{}) {
|
// func (h *HTML) debugLog(format string, a ...interface{}) {
|
||||||
// if h.debug {
|
// if h.debug {
|
||||||
// msg := fmt.Sprintf(format, a...)
|
// msg := fmt.Sprintf(format, a...)
|
||||||
// h.trace = append(h.trace, fmt.Sprintf("[HTML] %s", msg))
|
// h.trace = append(h.trace, fmt.Sprintf("[HTML] %s", msg))
|
||||||
|
|||||||
+15
-19
@@ -1,11 +1,12 @@
|
|||||||
package renderer
|
package renderer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/olekukonko/ll"
|
|
||||||
"github.com/olekukonko/tablewriter/pkg/twwidth"
|
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/olekukonko/ll"
|
||||||
|
"github.com/olekukonko/tablewriter/pkg/twwidth"
|
||||||
|
|
||||||
"github.com/olekukonko/tablewriter/tw"
|
"github.com/olekukonko/tablewriter/tw"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -95,7 +96,6 @@ func (m *Markdown) Row(row []string, ctx tw.Formatting) {
|
|||||||
m.resolveAlignment(ctx)
|
m.resolveAlignment(ctx)
|
||||||
m.logger.Debugf("Rendering row with data=%v, widths=%v, previous=%v, current=%v, next=%v", row, ctx.Row.Widths, ctx.Row.Previous, ctx.Row.Current, ctx.Row.Next)
|
m.logger.Debugf("Rendering row with data=%v, widths=%v, previous=%v, current=%v, next=%v", row, ctx.Row.Widths, ctx.Row.Previous, ctx.Row.Current, ctx.Row.Next)
|
||||||
m.renderMarkdownLine(row, ctx, false)
|
m.renderMarkdownLine(row, ctx, false)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Footer renders the Markdown table footer.
|
// Footer renders the Markdown table footer.
|
||||||
@@ -155,7 +155,7 @@ func (m *Markdown) resolveAlignment(ctx tw.Formatting) tw.Alignment {
|
|||||||
|
|
||||||
// formatCell formats a Markdown cell's content with padding and alignment, ensuring at least 3 characters wide.
|
// formatCell formats a Markdown cell's content with padding and alignment, ensuring at least 3 characters wide.
|
||||||
func (m *Markdown) formatCell(content string, width int, align tw.Align, padding tw.Padding) string {
|
func (m *Markdown) formatCell(content string, width int, align tw.Align, padding tw.Padding) string {
|
||||||
//if m.config.Settings.TrimWhitespace.Enabled() {
|
// if m.config.Settings.TrimWhitespace.Enabled() {
|
||||||
// content = strings.TrimSpace(content)
|
// content = strings.TrimSpace(content)
|
||||||
//}
|
//}
|
||||||
contentVisualWidth := twwidth.Width(content)
|
contentVisualWidth := twwidth.Width(content)
|
||||||
@@ -177,10 +177,7 @@ func (m *Markdown) formatCell(content string, width int, align tw.Align, padding
|
|||||||
targetWidth := tw.Max(width, minWidth)
|
targetWidth := tw.Max(width, minWidth)
|
||||||
|
|
||||||
// Calculate padding
|
// Calculate padding
|
||||||
totalPaddingNeeded := targetWidth - contentVisualWidth
|
totalPaddingNeeded := max(targetWidth-contentVisualWidth, 0)
|
||||||
if totalPaddingNeeded < 0 {
|
|
||||||
totalPaddingNeeded = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
var leftPadStr, rightPadStr string
|
var leftPadStr, rightPadStr string
|
||||||
switch align {
|
switch align {
|
||||||
@@ -220,13 +217,14 @@ func (m *Markdown) formatCell(content string, width int, align tw.Align, padding
|
|||||||
adjNeeded := targetWidth - finalWidth
|
adjNeeded := targetWidth - finalWidth
|
||||||
if adjNeeded > 0 {
|
if adjNeeded > 0 {
|
||||||
adjStr := strings.Repeat(tw.Space, adjNeeded)
|
adjStr := strings.Repeat(tw.Space, adjNeeded)
|
||||||
if align == tw.AlignRight {
|
switch align {
|
||||||
|
case tw.AlignRight:
|
||||||
result = adjStr + result
|
result = adjStr + result
|
||||||
} else if align == tw.AlignCenter {
|
case tw.AlignCenter:
|
||||||
leftAdj := adjNeeded / 2
|
leftAdj := adjNeeded / 2
|
||||||
rightAdj := adjNeeded - leftAdj
|
rightAdj := adjNeeded - leftAdj
|
||||||
result = strings.Repeat(tw.Space, leftAdj) + result + strings.Repeat(tw.Space, rightAdj)
|
result = strings.Repeat(tw.Space, leftAdj) + result + strings.Repeat(tw.Space, rightAdj)
|
||||||
} else {
|
default:
|
||||||
result += adjStr
|
result += adjStr
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -346,10 +344,7 @@ func (m *Markdown) renderMarkdownLine(line []string, ctx tw.Formatting, isHeader
|
|||||||
span = cellCtx.Merge.Horizontal.Span
|
span = cellCtx.Merge.Horizontal.Span
|
||||||
totalWidth := 0
|
totalWidth := 0
|
||||||
for k := 0; k < span && colIndex+k < numCols; k++ {
|
for k := 0; k < span && colIndex+k < numCols; k++ {
|
||||||
colWidth := ctx.NormalizedWidths.Get(colIndex + k)
|
colWidth := max(ctx.NormalizedWidths.Get(colIndex+k), 0)
|
||||||
if colWidth < 0 {
|
|
||||||
colWidth = 0
|
|
||||||
}
|
|
||||||
totalWidth += colWidth
|
totalWidth += colWidth
|
||||||
if k > 0 && separatorWidth > 0 {
|
if k > 0 && separatorWidth > 0 {
|
||||||
totalWidth += separatorWidth
|
totalWidth += separatorWidth
|
||||||
@@ -388,17 +383,18 @@ func (m *Markdown) renderMarkdownLine(line []string, ctx tw.Formatting, isHeader
|
|||||||
}
|
}
|
||||||
// For rows, use the header's alignment if specified
|
// For rows, use the header's alignment if specified
|
||||||
rowAlign := align
|
rowAlign := align
|
||||||
if headerCellCtx, headerOK := ctx.Row.Previous[colIndex]; headerOK && isHeaderSep == false {
|
if headerCellCtx, headerOK := ctx.Row.Previous[colIndex]; headerOK && !isHeaderSep {
|
||||||
if headerCellCtx.Align != tw.AlignNone && headerCellCtx.Align != tw.Empty {
|
if headerCellCtx.Align != tw.AlignNone && headerCellCtx.Align != tw.Empty {
|
||||||
rowAlign = headerCellCtx.Align
|
rowAlign = headerCellCtx.Align
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if rowAlign == tw.AlignNone || rowAlign == tw.Empty {
|
if rowAlign == tw.AlignNone || rowAlign == tw.Empty {
|
||||||
if ctx.Row.Position == tw.Header {
|
switch ctx.Row.Position {
|
||||||
|
case tw.Header:
|
||||||
rowAlign = tw.AlignCenter
|
rowAlign = tw.AlignCenter
|
||||||
} else if ctx.Row.Position == tw.Footer {
|
case tw.Footer:
|
||||||
rowAlign = tw.AlignRight
|
rowAlign = tw.AlignRight
|
||||||
} else {
|
default:
|
||||||
rowAlign = tw.AlignLeft
|
rowAlign = tw.AlignLeft
|
||||||
}
|
}
|
||||||
m.logger.Debugf("renderMarkdownLine: Col %d using default align '%s'", colIndex, rowAlign)
|
m.logger.Debugf("renderMarkdownLine: Col %d using default align '%s'", colIndex, rowAlign)
|
||||||
|
|||||||
+9
-17
@@ -1,17 +1,18 @@
|
|||||||
package renderer
|
package renderer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/olekukonko/tablewriter/pkg/twwidth"
|
|
||||||
"io"
|
"io"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/olekukonko/tablewriter/pkg/twwidth"
|
||||||
|
|
||||||
"github.com/olekukonko/ll"
|
"github.com/olekukonko/ll"
|
||||||
"github.com/olekukonko/tablewriter/tw"
|
"github.com/olekukonko/tablewriter/tw"
|
||||||
)
|
)
|
||||||
|
|
||||||
// OceanConfig defines configuration specific to the Ocean renderer.
|
// OceanConfig defines configuration specific to the Ocean renderer.
|
||||||
type OceanConfig struct {
|
type OceanConfig struct{}
|
||||||
}
|
|
||||||
|
|
||||||
// Ocean is a streaming table renderer that writes ASCII tables.
|
// Ocean is a streaming table renderer that writes ASCII tables.
|
||||||
type Ocean struct {
|
type Ocean struct {
|
||||||
@@ -327,12 +328,9 @@ func (o *Ocean) renderContentLine(ctx tw.Formatting, lineData []string) {
|
|||||||
idxInMergeSpan := colIdx + k
|
idxInMergeSpan := colIdx + k
|
||||||
// Check if idxInMergeSpan is a defined column in fixedWidths
|
// Check if idxInMergeSpan is a defined column in fixedWidths
|
||||||
foundInFixedWidths := false
|
foundInFixedWidths := false
|
||||||
for _, sortedCIdx_inner := range sortedColIndices {
|
if slices.Contains(sortedColIndices, idxInMergeSpan) {
|
||||||
if sortedCIdx_inner == idxInMergeSpan {
|
currentMergeTotalRenderWidth += o.fixedWidths.Get(idxInMergeSpan)
|
||||||
currentMergeTotalRenderWidth += o.fixedWidths.Get(idxInMergeSpan)
|
foundInFixedWidths = true
|
||||||
foundInFixedWidths = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if !foundInFixedWidths && idxInMergeSpan <= sortedColIndices[len(sortedColIndices)-1] {
|
if !foundInFixedWidths && idxInMergeSpan <= sortedColIndices[len(sortedColIndices)-1] {
|
||||||
o.logger.Debugf("Col %d in HMerge span not found in fixedWidths, assuming 0-width contribution.", idxInMergeSpan)
|
o.logger.Debugf("Col %d in HMerge span not found in fixedWidths, assuming 0-width contribution.", idxInMergeSpan)
|
||||||
@@ -407,20 +405,14 @@ func (o *Ocean) formatCellContent(content string, cellVisualWidth int, padding t
|
|||||||
padLeftDisplayWidth := twwidth.Width(padLeftChar)
|
padLeftDisplayWidth := twwidth.Width(padLeftChar)
|
||||||
padRightDisplayWidth := twwidth.Width(padRightChar)
|
padRightDisplayWidth := twwidth.Width(padRightChar)
|
||||||
|
|
||||||
spaceForContentAndAlignment := cellVisualWidth - padLeftDisplayWidth - padRightDisplayWidth
|
spaceForContentAndAlignment := max(cellVisualWidth-padLeftDisplayWidth-padRightDisplayWidth, 0)
|
||||||
if spaceForContentAndAlignment < 0 {
|
|
||||||
spaceForContentAndAlignment = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
if contentDisplayWidth > spaceForContentAndAlignment {
|
if contentDisplayWidth > spaceForContentAndAlignment {
|
||||||
content = twwidth.Truncate(content, spaceForContentAndAlignment)
|
content = twwidth.Truncate(content, spaceForContentAndAlignment)
|
||||||
contentDisplayWidth = twwidth.Width(content)
|
contentDisplayWidth = twwidth.Width(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
remainingSpace := spaceForContentAndAlignment - contentDisplayWidth
|
remainingSpace := max(spaceForContentAndAlignment-contentDisplayWidth, 0)
|
||||||
if remainingSpace < 0 {
|
|
||||||
remainingSpace = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
var PL, PR string
|
var PL, PR string
|
||||||
switch align {
|
switch align {
|
||||||
|
|||||||
+7
-8
@@ -2,11 +2,12 @@ package renderer
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/olekukonko/ll"
|
|
||||||
"html"
|
"html"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/olekukonko/ll"
|
||||||
|
|
||||||
"github.com/olekukonko/tablewriter/tw"
|
"github.com/olekukonko/tablewriter/tw"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -512,10 +513,7 @@ func (s *SVG) renderVisualLine(visualLineData []string, ctx tw.Formatting, posit
|
|||||||
}
|
}
|
||||||
s.dataRowCounter++
|
s.dataRowCounter++
|
||||||
} else {
|
} else {
|
||||||
parentDataRowStripeIndex := s.dataRowCounter - 1
|
parentDataRowStripeIndex := max(s.dataRowCounter-1, 0)
|
||||||
if parentDataRowStripeIndex < 0 {
|
|
||||||
parentDataRowStripeIndex = 0
|
|
||||||
}
|
|
||||||
if s.config.RowAltBG != tw.Empty && parentDataRowStripeIndex%2 != 0 {
|
if s.config.RowAltBG != tw.Empty && parentDataRowStripeIndex%2 != 0 {
|
||||||
bgColor = s.config.RowAltBG
|
bgColor = s.config.RowAltBG
|
||||||
} else {
|
} else {
|
||||||
@@ -623,9 +621,10 @@ func (s *SVG) renderVisualLine(visualLineData []string, ctx tw.Formatting, posit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
textX := currentX + s.config.Padding
|
textX := currentX + s.config.Padding
|
||||||
if cellTextAnchor == "middle" {
|
switch cellTextAnchor {
|
||||||
|
case "middle":
|
||||||
textX = currentX + s.config.Padding + (rectWidth-2*s.config.Padding)/2.0
|
textX = currentX + s.config.Padding + (rectWidth-2*s.config.Padding)/2.0
|
||||||
} else if cellTextAnchor == "end" {
|
case "end":
|
||||||
textX = currentX + rectWidth - s.config.Padding
|
textX = currentX + rectWidth - s.config.Padding
|
||||||
}
|
}
|
||||||
textY := s.currentY + rectHeight/2.0
|
textY := s.currentY + rectHeight/2.0
|
||||||
@@ -686,7 +685,7 @@ func (s *SVG) Start(w io.Writer) error {
|
|||||||
func (s *SVG) debug(format string, a ...interface{}) {
|
func (s *SVG) debug(format string, a ...interface{}) {
|
||||||
if s.config.Debug {
|
if s.config.Debug {
|
||||||
msg := fmt.Sprintf(format, a...)
|
msg := fmt.Sprintf(format, a...)
|
||||||
s.trace = append(s.trace, fmt.Sprintf("[SVG] %s", msg))
|
s.trace = append(s.trace, "[SVG] "+msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+11
-10
@@ -1,10 +1,11 @@
|
|||||||
package tablewriter
|
package tablewriter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"math"
|
||||||
|
|
||||||
"github.com/olekukonko/errors"
|
"github.com/olekukonko/errors"
|
||||||
"github.com/olekukonko/tablewriter/pkg/twwidth"
|
"github.com/olekukonko/tablewriter/pkg/twwidth"
|
||||||
"github.com/olekukonko/tablewriter/tw"
|
"github.com/olekukonko/tablewriter/tw"
|
||||||
"math"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Close finalizes the table stream.
|
// Close finalizes the table stream.
|
||||||
@@ -92,8 +93,8 @@ func (t *Table) Start() error {
|
|||||||
return errors.Newf("renderer does not support streaming")
|
return errors.Newf("renderer does not support streaming")
|
||||||
}
|
}
|
||||||
|
|
||||||
//t.renderer.Start(t.writer)
|
// t.renderer.Start(t.writer)
|
||||||
//t.renderer.Logger(t.logger)
|
// t.renderer.Logger(t.logger)
|
||||||
|
|
||||||
if t.hasPrinted {
|
if t.hasPrinted {
|
||||||
// Prevent calling Start() multiple times on the same stream instance.
|
// Prevent calling Start() multiple times on the same stream instance.
|
||||||
@@ -119,7 +120,7 @@ func (t *Table) Start() error {
|
|||||||
t.streamWidths = t.config.Widths.PerColumn.Clone()
|
t.streamWidths = t.config.Widths.PerColumn.Clone()
|
||||||
// Determine numCols from the highest index in PerColumn map
|
// Determine numCols from the highest index in PerColumn map
|
||||||
maxColIdx := -1
|
maxColIdx := -1
|
||||||
t.streamWidths.Each(func(col int, width int) {
|
t.streamWidths.Each(func(col, width int) {
|
||||||
if col > maxColIdx {
|
if col > maxColIdx {
|
||||||
maxColIdx = col
|
maxColIdx = col
|
||||||
}
|
}
|
||||||
@@ -331,7 +332,7 @@ func (t *Table) streamAppendRow(row interface{}) error {
|
|||||||
t.logger.Debug("streamAppendRow: Separator line rendered. Updated lastRenderedPosition to 'separator'.")
|
t.logger.Debug("streamAppendRow: Separator line rendered. Updated lastRenderedPosition to 'separator'.")
|
||||||
} else {
|
} else {
|
||||||
details := ""
|
details := ""
|
||||||
if !(shouldDrawHeaderRowSeparator || shouldDrawRowRowSeparator) {
|
if !shouldDrawHeaderRowSeparator && !shouldDrawRowRowSeparator {
|
||||||
details = "neither header/row nor row/row separator was flagged true"
|
details = "neither header/row nor row/row separator was flagged true"
|
||||||
} else if t.lastRenderedPosition == tw.Position("separator") {
|
} else if t.lastRenderedPosition == tw.Position("separator") {
|
||||||
details = "lastRenderedPosition is already 'separator'"
|
details = "lastRenderedPosition is already 'separator'"
|
||||||
@@ -475,7 +476,7 @@ func (t *Table) streamCalculateWidths(sampling []string, config tw.CellConfig) i
|
|||||||
determinedNumCols := 0
|
determinedNumCols := 0
|
||||||
if t.config.Widths.PerColumn != nil && t.config.Widths.PerColumn.Len() > 0 {
|
if t.config.Widths.PerColumn != nil && t.config.Widths.PerColumn.Len() > 0 {
|
||||||
maxColIdx := -1
|
maxColIdx := -1
|
||||||
t.config.Widths.PerColumn.Each(func(col int, width int) {
|
t.config.Widths.PerColumn.Each(func(col, width int) {
|
||||||
if col > maxColIdx {
|
if col > maxColIdx {
|
||||||
maxColIdx = col
|
maxColIdx = col
|
||||||
}
|
}
|
||||||
@@ -600,7 +601,7 @@ func (t *Table) streamCalculateWidths(sampling []string, config tw.CellConfig) i
|
|||||||
if t.config.Widths.Global > 0 && t.streamNumCols > 0 {
|
if t.config.Widths.Global > 0 && t.streamNumCols > 0 {
|
||||||
t.logger.Debug("streamCalculateWidths: Applying global stream width constraint %d", t.config.Widths.Global)
|
t.logger.Debug("streamCalculateWidths: Applying global stream width constraint %d", t.config.Widths.Global)
|
||||||
currentTotalColumnWidthsSum := 0
|
currentTotalColumnWidthsSum := 0
|
||||||
t.streamWidths.Each(func(_ int, w int) {
|
t.streamWidths.Each(func(_, w int) {
|
||||||
currentTotalColumnWidthsSum += w
|
currentTotalColumnWidthsSum += w
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -665,7 +666,7 @@ func (t *Table) streamCalculateWidths(sampling []string, config tw.CellConfig) i
|
|||||||
// Distribute remainingSpace (positive or negative) among non-zero width columns
|
// Distribute remainingSpace (positive or negative) among non-zero width columns
|
||||||
if remainingSpace != 0 && t.streamNumCols > 0 {
|
if remainingSpace != 0 && t.streamNumCols > 0 {
|
||||||
colsToAdjust := []int{}
|
colsToAdjust := []int{}
|
||||||
t.streamWidths.Each(func(col int, w int) {
|
t.streamWidths.Each(func(col, w int) {
|
||||||
if w > 0 { // Only consider columns that currently have width
|
if w > 0 { // Only consider columns that currently have width
|
||||||
colsToAdjust = append(colsToAdjust, col)
|
colsToAdjust = append(colsToAdjust, col)
|
||||||
}
|
}
|
||||||
@@ -689,7 +690,7 @@ func (t *Table) streamCalculateWidths(sampling []string, config tw.CellConfig) i
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Final sanitization
|
// Final sanitization
|
||||||
t.streamWidths.Each(func(col int, width int) {
|
t.streamWidths.Each(func(col, width int) {
|
||||||
if width < 0 {
|
if width < 0 {
|
||||||
t.streamWidths.Set(col, 0)
|
t.streamWidths.Set(col, 0)
|
||||||
}
|
}
|
||||||
@@ -858,7 +859,7 @@ func (t *Table) streamRenderFooter(processedFooterLines [][]string) error {
|
|||||||
// If this is the last line of the last content block (footer), and no bottom border will be drawn,
|
// If this is the last line of the last content block (footer), and no bottom border will be drawn,
|
||||||
// its Location should be End.
|
// its Location should be End.
|
||||||
isLastLineOfTableContent := (i == totalFooterLines-1) &&
|
isLastLineOfTableContent := (i == totalFooterLines-1) &&
|
||||||
!(cfg.Borders.Bottom.Enabled() && cfg.Settings.Lines.ShowBottom.Enabled())
|
(!cfg.Borders.Bottom.Enabled() || !cfg.Settings.Lines.ShowBottom.Enabled())
|
||||||
if isLastLineOfTableContent {
|
if isLastLineOfTableContent {
|
||||||
resp.location = tw.LocationEnd
|
resp.location = tw.LocationEnd
|
||||||
t.logger.Debug("streamRenderFooter: Setting LocationEnd for last footer line as no bottom border will follow.")
|
t.logger.Debug("streamRenderFooter: Setting LocationEnd for last footer line as no bottom border will follow.")
|
||||||
|
|||||||
+87
-49
@@ -2,13 +2,6 @@ package tablewriter
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"github.com/olekukonko/errors"
|
|
||||||
"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"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"os"
|
||||||
@@ -16,11 +9,20 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/olekukonko/errors"
|
||||||
|
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Table represents a table instance with content and rendering capabilities.
|
// Table represents a table instance with content and rendering capabilities.
|
||||||
type Table struct {
|
type Table struct {
|
||||||
writer io.Writer // Destination for table output
|
writer io.Writer // Destination for table output
|
||||||
|
counters []tw.Counter // Counters for indices
|
||||||
rows [][][]string // Row data, supporting multi-line cells
|
rows [][][]string // Row data, supporting multi-line cells
|
||||||
headers [][]string // Header content
|
headers [][]string // Header content
|
||||||
footers [][]string // Footer content
|
footers [][]string // Footer content
|
||||||
@@ -410,7 +412,6 @@ func (t *Table) Footer(elements ...any) {
|
|||||||
// Parameter opts is a function that modifies the Table struct.
|
// Parameter opts is a function that modifies the Table struct.
|
||||||
// Returns the Table instance for method chaining.
|
// Returns the Table instance for method chaining.
|
||||||
func (t *Table) Options(opts ...Option) *Table {
|
func (t *Table) Options(opts ...Option) *Table {
|
||||||
|
|
||||||
// add logger
|
// add logger
|
||||||
if t.logger == nil {
|
if t.logger == nil {
|
||||||
t.logger = ll.New("table").Handler(lh.NewTextHandler(t.trace))
|
t.logger = ll.New("table").Handler(lh.NewTextHandler(t.trace))
|
||||||
@@ -423,7 +424,7 @@ func (t *Table) Options(opts ...Option) *Table {
|
|||||||
|
|
||||||
// force debugging mode if set
|
// force debugging mode if set
|
||||||
// This should be move away form WithDebug
|
// This should be move away form WithDebug
|
||||||
if t.config.Debug == true {
|
if t.config.Debug {
|
||||||
t.logger.Enable()
|
t.logger.Enable()
|
||||||
t.logger.Resume()
|
t.logger.Resume()
|
||||||
} else {
|
} else {
|
||||||
@@ -510,6 +511,28 @@ func (t *Table) Render() error {
|
|||||||
return t.render()
|
return t.render()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Lines returns the total number of lines rendered.
|
||||||
|
// This method is only effective if the WithLineCounter() option was used during
|
||||||
|
// table initialization and must be called *after* Render().
|
||||||
|
// It actively searches for the default tw.LineCounter among all active counters.
|
||||||
|
// It returns -1 if the line counter was not enabled.
|
||||||
|
func (t *Table) Lines() int {
|
||||||
|
for _, counter := range t.counters {
|
||||||
|
if lc, ok := counter.(*tw.LineCounter); ok {
|
||||||
|
return lc.Total()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// use -1 to indicate no line counter is attached
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Counters returns the slice of all active counter instances.
|
||||||
|
// This is useful when multiple counters are enabled.
|
||||||
|
// It must be called *after* Render().
|
||||||
|
func (t *Table) Counters() []tw.Counter {
|
||||||
|
return t.counters
|
||||||
|
}
|
||||||
|
|
||||||
// Trimmer trims whitespace from a string based on the Table’s configuration.
|
// Trimmer trims whitespace from a string based on the Table’s configuration.
|
||||||
// It conditionally applies strings.TrimSpace to the input string if the TrimSpace behavior
|
// It conditionally applies strings.TrimSpace to the input string if the TrimSpace behavior
|
||||||
// is enabled in t.config.Behavior, otherwise returning the string unchanged. This method
|
// is enabled in t.config.Behavior, otherwise returning the string unchanged. This method
|
||||||
@@ -945,10 +968,7 @@ func (t *Table) prepareContent(cells []string, config tw.CellConfig) [][]string
|
|||||||
currentLine := line
|
currentLine := line
|
||||||
breakCharWidth := twwidth.Width(tw.CharBreak)
|
breakCharWidth := twwidth.Width(tw.CharBreak)
|
||||||
for twwidth.Width(currentLine) > effectiveContentMaxWidth {
|
for twwidth.Width(currentLine) > effectiveContentMaxWidth {
|
||||||
targetWidth := effectiveContentMaxWidth - breakCharWidth
|
targetWidth := max(effectiveContentMaxWidth-breakCharWidth, 0)
|
||||||
if targetWidth < 0 {
|
|
||||||
targetWidth = 0
|
|
||||||
}
|
|
||||||
breakPoint := tw.BreakPoint(currentLine, targetWidth)
|
breakPoint := tw.BreakPoint(currentLine, targetWidth)
|
||||||
runes := []rune(currentLine)
|
runes := []rune(currentLine)
|
||||||
if breakPoint <= 0 || breakPoint > len(runes) {
|
if breakPoint <= 0 || breakPoint > len(runes) {
|
||||||
@@ -1362,23 +1382,40 @@ func (t *Table) prepareWithMerges(content [][]string, config tw.CellConfig, posi
|
|||||||
// No parameters are required.
|
// No parameters are required.
|
||||||
// Returns an error if rendering fails in any section.
|
// Returns an error if rendering fails in any section.
|
||||||
func (t *Table) render() error {
|
func (t *Table) render() error {
|
||||||
|
|
||||||
t.ensureInitialized()
|
t.ensureInitialized()
|
||||||
|
|
||||||
|
// Save the original writer and schedule its restoration upon function exit.
|
||||||
|
// This guarantees the table's writer is restored even if errors occur.
|
||||||
|
originalWriter := t.writer
|
||||||
|
defer func() {
|
||||||
|
t.writer = originalWriter
|
||||||
|
}()
|
||||||
|
|
||||||
|
// If a counter is active, wrap the writer in a MultiWriter.
|
||||||
|
if len(t.counters) > 0 {
|
||||||
|
// The slice must be of type io.Writer.
|
||||||
|
// Start it with the original destination writer.
|
||||||
|
allWriters := []io.Writer{originalWriter}
|
||||||
|
|
||||||
|
// Append each counter to the slice of writers.
|
||||||
|
for _, c := range t.counters {
|
||||||
|
allWriters = append(allWriters, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a MultiWriter that broadcasts to the original writer AND all counters.
|
||||||
|
t.writer = io.MultiWriter(allWriters...)
|
||||||
|
}
|
||||||
|
|
||||||
if t.config.Stream.Enable {
|
if t.config.Stream.Enable {
|
||||||
t.logger.Warn("Render() called in streaming mode. Use Start/Append/Close methods instead.")
|
t.logger.Warn("Render() called in streaming mode. Use Start/Append/Close methods instead.")
|
||||||
return errors.New("render called in streaming mode; use Start/Append/Close")
|
return errors.New("render called in streaming mode; use Start/Append/Close")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate and cache numCols for THIS batch render pass
|
// Calculate and cache the column count for this specific batch render pass.
|
||||||
t.batchRenderNumCols = t.maxColumns() // Calculate ONCE
|
t.batchRenderNumCols = t.maxColumns()
|
||||||
t.isBatchRenderNumColsSet = true // Mark the cache as active for this render pass
|
t.isBatchRenderNumColsSet = true
|
||||||
t.logger.Debugf("Render(): Set batchRenderNumCols to %d and isBatchRenderNumColsSet to true.", t.batchRenderNumCols)
|
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
t.isBatchRenderNumColsSet = false
|
t.isBatchRenderNumColsSet = false
|
||||||
// t.batchRenderNumCols = 0; // Optional: reset to 0, or leave as is.
|
|
||||||
// Since isBatchRenderNumColsSet is false, its value won't be used by getNumColsToUse.
|
|
||||||
t.logger.Debugf("Render(): Cleared isBatchRenderNumColsSet to false (batchRenderNumCols was %d).", t.batchRenderNumCols)
|
t.logger.Debugf("Render(): Cleared isBatchRenderNumColsSet to false (batchRenderNumCols was %d).", t.batchRenderNumCols)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -1387,9 +1424,10 @@ func (t *Table) render() error {
|
|||||||
(t.caption.Spot >= tw.SpotTopLeft && t.caption.Spot <= tw.SpotBottomRight)
|
(t.caption.Spot >= tw.SpotTopLeft && t.caption.Spot <= tw.SpotBottomRight)
|
||||||
|
|
||||||
var tableStringBuffer *strings.Builder
|
var tableStringBuffer *strings.Builder
|
||||||
targetWriter := t.writer
|
targetWriter := t.writer // Can be the original writer or the MultiWriter.
|
||||||
originalWriter := t.writer // Save original writer for restoration if needed
|
|
||||||
|
|
||||||
|
// If a caption is present, the main table content must be rendered to an
|
||||||
|
// in-memory buffer first to calculate its final width.
|
||||||
if isTopOrBottomCaption {
|
if isTopOrBottomCaption {
|
||||||
tableStringBuffer = &strings.Builder{}
|
tableStringBuffer = &strings.Builder{}
|
||||||
targetWriter = tableStringBuffer
|
targetWriter = tableStringBuffer
|
||||||
@@ -1398,17 +1436,15 @@ func (t *Table) render() error {
|
|||||||
t.logger.Debugf("No caption detected. Rendering table core directly to writer.")
|
t.logger.Debugf("No caption detected. Rendering table core directly to writer.")
|
||||||
}
|
}
|
||||||
|
|
||||||
//Render Table Core
|
// Point the table's writer to the target (either the final destination or the buffer).
|
||||||
t.writer = targetWriter
|
t.writer = targetWriter
|
||||||
ctx, mctx, err := t.prepareContexts()
|
ctx, mctx, err := t.prepareContexts()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.writer = originalWriter
|
|
||||||
t.logger.Errorf("prepareContexts failed: %v", err)
|
t.logger.Errorf("prepareContexts failed: %v", err)
|
||||||
return errors.Newf("failed to prepare table contexts").Wrap(err)
|
return errors.Newf("failed to prepare table contexts").Wrap(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ctx.renderer.Start(t.writer); err != nil {
|
if err := ctx.renderer.Start(t.writer); err != nil {
|
||||||
t.writer = originalWriter
|
|
||||||
t.logger.Errorf("Renderer Start() error: %v", err)
|
t.logger.Errorf("Renderer Start() error: %v", err)
|
||||||
return errors.Newf("renderer start failed").Wrap(err)
|
return errors.Newf("renderer start failed").Wrap(err)
|
||||||
}
|
}
|
||||||
@@ -1440,18 +1476,21 @@ func (t *Table) render() error {
|
|||||||
renderError = true
|
renderError = true
|
||||||
}
|
}
|
||||||
|
|
||||||
t.writer = originalWriter // Restore original writer
|
// Restore the writer to the original for the caption-handling logic.
|
||||||
|
// This is necessary because the caption must be written to the final
|
||||||
|
// destination, not the temporary buffer used for the table body.
|
||||||
|
t.writer = originalWriter
|
||||||
|
|
||||||
if renderError {
|
if renderError {
|
||||||
return firstRenderErr // Return error from core rendering if any
|
return firstRenderErr
|
||||||
}
|
}
|
||||||
|
|
||||||
//Caption Handling & Final Output ---
|
// Caption Handling & Final Output
|
||||||
if isTopOrBottomCaption {
|
if isTopOrBottomCaption {
|
||||||
renderedTableContent := tableStringBuffer.String()
|
renderedTableContent := tableStringBuffer.String()
|
||||||
t.logger.Debugf("[Render] Table core buffer length: %d", len(renderedTableContent))
|
t.logger.Debugf("[Render] Table core buffer length: %d", len(renderedTableContent))
|
||||||
|
|
||||||
// Check if the buffer is empty AND borders are enabled
|
// Handle edge case where table is empty but should have borders.
|
||||||
shouldHaveBorders := t.renderer != nil && (t.renderer.Config().Borders.Top.Enabled() || t.renderer.Config().Borders.Bottom.Enabled())
|
shouldHaveBorders := t.renderer != nil && (t.renderer.Config().Borders.Top.Enabled() || t.renderer.Config().Borders.Bottom.Enabled())
|
||||||
if len(renderedTableContent) == 0 && shouldHaveBorders {
|
if len(renderedTableContent) == 0 && shouldHaveBorders {
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
@@ -1503,7 +1542,7 @@ func (t *Table) render() error {
|
|||||||
|
|
||||||
t.hasPrinted = true
|
t.hasPrinted = true
|
||||||
t.logger.Info("Render() completed.")
|
t.logger.Info("Render() completed.")
|
||||||
return nil // Success
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// renderFooter renders the table's footer section with borders and padding.
|
// renderFooter renders the table's footer section with borders and padding.
|
||||||
@@ -1677,7 +1716,7 @@ func (t *Table) renderFooter(ctx *renderContext, mctx *mergeContext) error {
|
|||||||
if hasTopPadding {
|
if hasTopPadding {
|
||||||
hctx.rowIdx = 0
|
hctx.rowIdx = 0
|
||||||
hctx.lineIdx = -1
|
hctx.lineIdx = -1
|
||||||
if !(hasContentAbove && cfg.Settings.Lines.ShowFooterLine.Enabled()) {
|
if !hasContentAbove || !cfg.Settings.Lines.ShowFooterLine.Enabled() {
|
||||||
hctx.location = tw.LocationFirst
|
hctx.location = tw.LocationFirst
|
||||||
} else {
|
} else {
|
||||||
hctx.location = tw.LocationMiddle
|
hctx.location = tw.LocationMiddle
|
||||||
@@ -1699,7 +1738,7 @@ func (t *Table) renderFooter(ctx *renderContext, mctx *mergeContext) error {
|
|||||||
hctx.line = padLine(line, ctx.numCols)
|
hctx.line = padLine(line, ctx.numCols)
|
||||||
isFirstContentLine := i == 0
|
isFirstContentLine := i == 0
|
||||||
isLastContentLine := i == len(ctx.footerLines)-1
|
isLastContentLine := i == len(ctx.footerLines)-1
|
||||||
if isFirstContentLine && !hasTopPadding && !(hasContentAbove && cfg.Settings.Lines.ShowFooterLine.Enabled()) {
|
if isFirstContentLine && !hasTopPadding && (!hasContentAbove || !cfg.Settings.Lines.ShowFooterLine.Enabled()) {
|
||||||
hctx.location = tw.LocationFirst
|
hctx.location = tw.LocationFirst
|
||||||
} else if isLastContentLine && !hasBottomPaddingConfig {
|
} else if isLastContentLine && !hasBottomPaddingConfig {
|
||||||
hctx.location = tw.LocationEnd
|
hctx.location = tw.LocationEnd
|
||||||
@@ -1716,7 +1755,7 @@ func (t *Table) renderFooter(ctx *renderContext, mctx *mergeContext) error {
|
|||||||
if hasBottomPaddingConfig {
|
if hasBottomPaddingConfig {
|
||||||
paddingLineContentForContext = make([]string, ctx.numCols)
|
paddingLineContentForContext = make([]string, ctx.numCols)
|
||||||
formattedPaddingCells := make([]string, ctx.numCols)
|
formattedPaddingCells := make([]string, ctx.numCols)
|
||||||
var representativePadChar string = " "
|
representativePadChar := " "
|
||||||
ctx.logger.Debugf("Constructing Footer Bottom Padding line content strings")
|
ctx.logger.Debugf("Constructing Footer Bottom Padding line content strings")
|
||||||
for j := 0; j < ctx.numCols; j++ {
|
for j := 0; j < ctx.numCols; j++ {
|
||||||
colWd := ctx.widths[tw.Footer].Get(j)
|
colWd := ctx.widths[tw.Footer].Get(j)
|
||||||
@@ -1741,10 +1780,7 @@ func (t *Table) renderFooter(ctx *renderContext, mctx *mergeContext) error {
|
|||||||
if j == 0 || representativePadChar == " " {
|
if j == 0 || representativePadChar == " " {
|
||||||
representativePadChar = padChar
|
representativePadChar = padChar
|
||||||
}
|
}
|
||||||
padWidth := twwidth.Width(padChar)
|
padWidth := max(twwidth.Width(padChar), 1)
|
||||||
if padWidth < 1 {
|
|
||||||
padWidth = 1
|
|
||||||
}
|
|
||||||
repeatCount := 0
|
repeatCount := 0
|
||||||
if colWd > 0 && padWidth > 0 {
|
if colWd > 0 && padWidth > 0 {
|
||||||
repeatCount = colWd / padWidth
|
repeatCount = colWd / padWidth
|
||||||
@@ -2139,19 +2175,21 @@ func (t *Table) renderRow(ctx *renderContext, mctx *mergeContext) error {
|
|||||||
hctx.lineIdx = j
|
hctx.lineIdx = j
|
||||||
hctx.line = padLine(visualLineData, ctx.numCols)
|
hctx.line = padLine(visualLineData, ctx.numCols)
|
||||||
|
|
||||||
if j > 0 {
|
if t.config.Behavior.TrimLine.Enabled() {
|
||||||
visualLineHasActualContent := false
|
if j > 0 {
|
||||||
for kCellIdx, cellContentInVisualLine := range hctx.line {
|
visualLineHasActualContent := false
|
||||||
if t.Trimmer(cellContentInVisualLine) != "" {
|
for kCellIdx, cellContentInVisualLine := range hctx.line {
|
||||||
visualLineHasActualContent = true
|
if t.Trimmer(cellContentInVisualLine) != "" {
|
||||||
ctx.logger.Debug("Visual line [%d][%d] has content in cell %d: '%s'. Not skipping.", i, j, kCellIdx, cellContentInVisualLine)
|
visualLineHasActualContent = true
|
||||||
break
|
ctx.logger.Debug("Visual line [%d][%d] has content in cell %d: '%s'. Not skipping.", i, j, kCellIdx, cellContentInVisualLine)
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if !visualLineHasActualContent {
|
if !visualLineHasActualContent {
|
||||||
ctx.logger.Debug("Skipping visual line [%d][%d] as it's entirely blank after trimming. Line: %q", i, j, hctx.line)
|
ctx.logger.Debug("Skipping visual line [%d][%d] as it's entirely blank after trimming. Line: %q", i, j, hctx.line)
|
||||||
continue
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
-1
@@ -12,7 +12,6 @@ type CellFormatting struct {
|
|||||||
// Deprecated: kept for compatibility
|
// Deprecated: kept for compatibility
|
||||||
// will be removed soon
|
// will be removed soon
|
||||||
Alignment Align // Text alignment within the cell (e.g., Left, Right, Center)
|
Alignment Align // Text alignment within the cell (e.g., Left, Right, Center)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CellPadding defines padding settings for table cells.
|
// CellPadding defines padding settings for table cells.
|
||||||
|
|||||||
+12
-6
@@ -3,12 +3,18 @@
|
|||||||
package tw
|
package tw
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/olekukonko/tablewriter/pkg/twwidth"
|
"github.com/olekukonko/tablewriter/pkg/twwidth"
|
||||||
"math" // For mathematical operations like ceiling
|
// For mathematical operations like ceiling
|
||||||
"strconv" // For string-to-number conversions
|
// For string-to-number conversions
|
||||||
"strings" // For string manipulation utilities
|
// For string manipulation utilities
|
||||||
"unicode" // For Unicode character classification
|
// For Unicode character classification
|
||||||
"unicode/utf8" // For UTF-8 rune handling
|
// For UTF-8 rune handling
|
||||||
)
|
)
|
||||||
|
|
||||||
// Title normalizes and uppercases a label string for use in headers.
|
// Title normalizes and uppercases a label string for use in headers.
|
||||||
@@ -75,7 +81,7 @@ func PadLeft(s, pad string, width int) string {
|
|||||||
|
|
||||||
// Pad aligns a string within a specified width using a padding character.
|
// Pad aligns a string within a specified width using a padding character.
|
||||||
// It truncates if the string is wider than the target width.
|
// It truncates if the string is wider than the target width.
|
||||||
func Pad(s string, padChar string, totalWidth int, alignment Align) string {
|
func Pad(s, padChar string, totalWidth int, alignment Align) string {
|
||||||
sDisplayWidth := twwidth.Width(s)
|
sDisplayWidth := twwidth.Width(s)
|
||||||
if sDisplayWidth > totalWidth {
|
if sDisplayWidth > totalWidth {
|
||||||
return twwidth.Truncate(s, totalWidth) // Only truncate if necessary
|
return twwidth.Truncate(s, totalWidth) // Only truncate if necessary
|
||||||
|
|||||||
+9
-17
@@ -114,21 +114,17 @@ func (m Mapper[K, V]) Values() []V {
|
|||||||
// Each iterates over each key-value pair in the map and calls the provided function.
|
// Each iterates over each key-value pair in the map and calls the provided function.
|
||||||
// Does nothing if the map is nil.
|
// Does nothing if the map is nil.
|
||||||
func (m Mapper[K, V]) Each(fn func(K, V)) {
|
func (m Mapper[K, V]) Each(fn func(K, V)) {
|
||||||
if m != nil {
|
for k, v := range m {
|
||||||
for k, v := range m {
|
fn(k, v)
|
||||||
fn(k, v)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter returns a new Mapper containing only the key-value pairs that satisfy the predicate.
|
// Filter returns a new Mapper containing only the key-value pairs that satisfy the predicate.
|
||||||
func (m Mapper[K, V]) Filter(fn func(K, V) bool) Mapper[K, V] {
|
func (m Mapper[K, V]) Filter(fn func(K, V) bool) Mapper[K, V] {
|
||||||
result := NewMapper[K, V]()
|
result := NewMapper[K, V]()
|
||||||
if m != nil {
|
for k, v := range m {
|
||||||
for k, v := range m {
|
if fn(k, v) {
|
||||||
if fn(k, v) {
|
result[k] = v
|
||||||
result[k] = v
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
@@ -137,10 +133,8 @@ func (m Mapper[K, V]) Filter(fn func(K, V) bool) Mapper[K, V] {
|
|||||||
// MapValues returns a new Mapper with the same keys but values transformed by the provided function.
|
// MapValues returns a new Mapper with the same keys but values transformed by the provided function.
|
||||||
func (m Mapper[K, V]) MapValues(fn func(V) V) Mapper[K, V] {
|
func (m Mapper[K, V]) MapValues(fn func(V) V) Mapper[K, V] {
|
||||||
result := NewMapper[K, V]()
|
result := NewMapper[K, V]()
|
||||||
if m != nil {
|
for k, v := range m {
|
||||||
for k, v := range m {
|
result[k] = fn(v)
|
||||||
result[k] = fn(v)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@@ -148,10 +142,8 @@ func (m Mapper[K, V]) MapValues(fn func(V) V) Mapper[K, V] {
|
|||||||
// Clone returns a shallow copy of the Mapper.
|
// Clone returns a shallow copy of the Mapper.
|
||||||
func (m Mapper[K, V]) Clone() Mapper[K, V] {
|
func (m Mapper[K, V]) Clone() Mapper[K, V] {
|
||||||
result := NewMapper[K, V]()
|
result := NewMapper[K, V]()
|
||||||
if m != nil {
|
for k, v := range m {
|
||||||
for k, v := range m {
|
result[k] = v
|
||||||
result[k] = v
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-6
@@ -10,9 +10,6 @@ var (
|
|||||||
SeparatorsNone = Separators{ShowHeader: Off, ShowFooter: Off, BetweenRows: Off, BetweenColumns: Off}
|
SeparatorsNone = Separators{ShowHeader: Off, ShowFooter: Off, BetweenRows: Off, BetweenColumns: Off}
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
// PaddingDefault represents standard single-space padding on left/right
|
||||||
|
// Equivalent to Padding{Left: " ", Right: " ", Overwrite: true}
|
||||||
// PaddingDefault represents standard single-space padding on left/right
|
var PaddingDefault = Padding{Left: " ", Right: " ", Overwrite: true}
|
||||||
// Equivalent to Padding{Left: " ", Right: " ", Overwrite: true}
|
|
||||||
PaddingDefault = Padding{Left: " ", Right: " ", Overwrite: true}
|
|
||||||
)
|
|
||||||
|
|||||||
+2
-1
@@ -1,8 +1,9 @@
|
|||||||
package tw
|
package tw
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/olekukonko/ll"
|
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
|
"github.com/olekukonko/ll"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Renderer defines the interface for rendering tables to an io.Writer.
|
// Renderer defines the interface for rendering tables to an io.Writer.
|
||||||
|
|||||||
+13
-26
@@ -1,5 +1,7 @@
|
|||||||
package tw
|
package tw
|
||||||
|
|
||||||
|
import "slices"
|
||||||
|
|
||||||
// Slicer is a generic slice type that provides additional methods for slice manipulation.
|
// Slicer is a generic slice type that provides additional methods for slice manipulation.
|
||||||
type Slicer[T any] []T
|
type Slicer[T any] []T
|
||||||
|
|
||||||
@@ -69,21 +71,17 @@ func (s Slicer[T]) Last() T {
|
|||||||
// Each iterates over each element in the slice and calls the provided function.
|
// Each iterates over each element in the slice and calls the provided function.
|
||||||
// Does nothing if the slice is nil.
|
// Does nothing if the slice is nil.
|
||||||
func (s Slicer[T]) Each(fn func(T)) {
|
func (s Slicer[T]) Each(fn func(T)) {
|
||||||
if s != nil {
|
for _, v := range s {
|
||||||
for _, v := range s {
|
fn(v)
|
||||||
fn(v)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filter returns a new Slicer containing only elements that satisfy the predicate.
|
// Filter returns a new Slicer containing only elements that satisfy the predicate.
|
||||||
func (s Slicer[T]) Filter(fn func(T) bool) Slicer[T] {
|
func (s Slicer[T]) Filter(fn func(T) bool) Slicer[T] {
|
||||||
result := NewSlicer[T]()
|
result := NewSlicer[T]()
|
||||||
if s != nil {
|
for _, v := range s {
|
||||||
for _, v := range s {
|
if fn(v) {
|
||||||
if fn(v) {
|
result = result.Append(v)
|
||||||
result = result.Append(v)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
@@ -92,33 +90,22 @@ func (s Slicer[T]) Filter(fn func(T) bool) Slicer[T] {
|
|||||||
// Map returns a new Slicer with each element transformed by the provided function.
|
// Map returns a new Slicer with each element transformed by the provided function.
|
||||||
func (s Slicer[T]) Map(fn func(T) T) Slicer[T] {
|
func (s Slicer[T]) Map(fn func(T) T) Slicer[T] {
|
||||||
result := NewSlicer[T]()
|
result := NewSlicer[T]()
|
||||||
if s != nil {
|
for _, v := range s {
|
||||||
for _, v := range s {
|
result = result.Append(fn(v))
|
||||||
result = result.Append(fn(v))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// Contains returns true if the slice contains an element that satisfies the predicate.
|
// Contains returns true if the slice contains an element that satisfies the predicate.
|
||||||
func (s Slicer[T]) Contains(fn func(T) bool) bool {
|
func (s Slicer[T]) Contains(fn func(T) bool) bool {
|
||||||
if s != nil {
|
return slices.ContainsFunc(s, fn)
|
||||||
for _, v := range s {
|
|
||||||
if fn(v) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find returns the first element that satisfies the predicate, along with a boolean indicating if it was found.
|
// Find returns the first element that satisfies the predicate, along with a boolean indicating if it was found.
|
||||||
func (s Slicer[T]) Find(fn func(T) bool) (T, bool) {
|
func (s Slicer[T]) Find(fn func(T) bool) (T, bool) {
|
||||||
if s != nil {
|
for _, v := range s {
|
||||||
for _, v := range s {
|
if fn(v) {
|
||||||
if fn(v) {
|
return v, true
|
||||||
return v, true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var zero T
|
var zero T
|
||||||
|
|||||||
+32
-3
@@ -3,9 +3,12 @@
|
|||||||
package tw
|
package tw
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"bytes"
|
||||||
"github.com/olekukonko/errors"
|
"io"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/olekukonko/errors"
|
||||||
) // Custom error handling library
|
) // Custom error handling library
|
||||||
|
|
||||||
// Position defines where formatting applies in the table (e.g., header, footer, or rows).
|
// Position defines where formatting applies in the table (e.g., header, footer, or rows).
|
||||||
@@ -31,6 +34,13 @@ type Formatter interface {
|
|||||||
Format() string // Returns the formatted string representation
|
Format() string // Returns the formatted string representation
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Counter defines an interface that combines io.Writer with a method to retrieve a total.
|
||||||
|
// This is used by the WithCounter option to allow for counting lines, bytes, etc.
|
||||||
|
type Counter interface {
|
||||||
|
io.Writer // It must be a writer to be used in io.MultiWriter.
|
||||||
|
Total() int
|
||||||
|
}
|
||||||
|
|
||||||
// Align specifies the text alignment within a table cell.
|
// Align specifies the text alignment within a table cell.
|
||||||
type Align string
|
type Align string
|
||||||
|
|
||||||
@@ -52,7 +62,7 @@ func (a Alignment) String() string {
|
|||||||
if i > 0 {
|
if i > 0 {
|
||||||
str.WriteString("; ")
|
str.WriteString("; ")
|
||||||
}
|
}
|
||||||
str.WriteString(fmt.Sprint(i))
|
str.WriteString(strconv.Itoa(i))
|
||||||
str.WriteString("=")
|
str.WriteString("=")
|
||||||
str.WriteString(string(a))
|
str.WriteString(string(a))
|
||||||
}
|
}
|
||||||
@@ -157,6 +167,7 @@ type Struct struct {
|
|||||||
type Behavior struct {
|
type Behavior struct {
|
||||||
AutoHide State // AutoHide determines whether empty columns are hidden. Ignored in streaming mode.
|
AutoHide State // AutoHide determines whether empty columns are hidden. Ignored in streaming mode.
|
||||||
TrimSpace State // TrimSpace enables trimming of leading and trailing spaces from cell content.
|
TrimSpace State // TrimSpace enables trimming of leading and trailing spaces from cell content.
|
||||||
|
TrimLine State // TrimLine determines whether empty visual lines within a cell are collapsed.
|
||||||
|
|
||||||
Header Control // Header specifies control settings for the table header.
|
Header Control // Header specifies control settings for the table header.
|
||||||
Footer Control // Footer specifies control settings for the table footer.
|
Footer Control // Footer specifies control settings for the table footer.
|
||||||
@@ -212,3 +223,21 @@ func (p Padding) Empty() bool {
|
|||||||
func (p Padding) Paddable() bool {
|
func (p Padding) Paddable() bool {
|
||||||
return !p.Empty() || p.Overwrite
|
return !p.Empty() || p.Overwrite
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LineCounter is the default implementation of the Counter interface.
|
||||||
|
// It counts the number of newline characters written to it.
|
||||||
|
type LineCounter struct {
|
||||||
|
count int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write implements the io.Writer interface, counting newlines in the input.
|
||||||
|
// It uses a pointer receiver to modify the internal count.
|
||||||
|
func (lc *LineCounter) Write(p []byte) (n int, err error) {
|
||||||
|
lc.count += bytes.Count(p, []byte{'\n'})
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Total implements the Counter interface, returning the final count.
|
||||||
|
func (lc *LineCounter) Total() int {
|
||||||
|
return lc.count
|
||||||
|
}
|
||||||
|
|||||||
+30
-40
@@ -3,14 +3,15 @@ package tablewriter
|
|||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/olekukonko/errors"
|
|
||||||
"github.com/olekukonko/tablewriter/pkg/twwidth"
|
|
||||||
"github.com/olekukonko/tablewriter/tw"
|
|
||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/olekukonko/errors"
|
||||||
|
"github.com/olekukonko/tablewriter/pkg/twwidth"
|
||||||
|
"github.com/olekukonko/tablewriter/tw"
|
||||||
)
|
)
|
||||||
|
|
||||||
// applyHierarchicalMerges applies hierarchical merges to row content.
|
// applyHierarchicalMerges applies hierarchical merges to row content.
|
||||||
@@ -542,10 +543,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.
|
// 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 {
|
func (t *Table) buildPaddingLineContents(padChar string, widths tw.Mapper[int, int], numCols int, merges map[int]tw.MergeState) []string {
|
||||||
line := make([]string, numCols)
|
line := make([]string, numCols)
|
||||||
padWidth := twwidth.Width(padChar)
|
padWidth := max(twwidth.Width(padChar), 1)
|
||||||
if padWidth < 1 {
|
|
||||||
padWidth = 1
|
|
||||||
}
|
|
||||||
for j := 0; j < numCols; j++ {
|
for j := 0; j < numCols; j++ {
|
||||||
mergeState := tw.MergeState{}
|
mergeState := tw.MergeState{}
|
||||||
if merges != nil {
|
if merges != nil {
|
||||||
@@ -582,9 +580,9 @@ func (t *Table) calculateAndNormalizeWidths(ctx *renderContext) error {
|
|||||||
ctx.numCols, t.config.Behavior.Compact.Merge.Enabled())
|
ctx.numCols, t.config.Behavior.Compact.Merge.Enabled())
|
||||||
|
|
||||||
// Initialize width maps
|
// Initialize width maps
|
||||||
//t.headerWidths = tw.NewMapper[int, int]()
|
// t.headerWidths = tw.NewMapper[int, int]()
|
||||||
//t.rowWidths = tw.NewMapper[int, int]()
|
// t.rowWidths = tw.NewMapper[int, int]()
|
||||||
//t.footerWidths = tw.NewMapper[int, int]()
|
// t.footerWidths = tw.NewMapper[int, int]()
|
||||||
|
|
||||||
// Compute content-based widths for each section
|
// Compute content-based widths for each section
|
||||||
for _, lines := range ctx.headerLines {
|
for _, lines := range ctx.headerLines {
|
||||||
@@ -720,7 +718,7 @@ func (t *Table) calculateAndNormalizeWidths(ctx *renderContext) error {
|
|||||||
twwidth.Width(headerCellPadding.Left) +
|
twwidth.Width(headerCellPadding.Left) +
|
||||||
twwidth.Width(headerCellPadding.Right)
|
twwidth.Width(headerCellPadding.Right)
|
||||||
currentSumOfColumnWidths := 0
|
currentSumOfColumnWidths := 0
|
||||||
workingWidths.Each(func(_ int, w int) { currentSumOfColumnWidths += w })
|
workingWidths.Each(func(_, w int) { currentSumOfColumnWidths += w })
|
||||||
numSeparatorsInFullSpan := 0
|
numSeparatorsInFullSpan := 0
|
||||||
if ctx.numCols > 1 {
|
if ctx.numCols > 1 {
|
||||||
if t.renderer != nil && t.renderer.Config().Settings.Separators.BetweenColumns.Enabled() {
|
if t.renderer != nil && t.renderer.Config().Settings.Separators.BetweenColumns.Enabled() {
|
||||||
@@ -733,7 +731,7 @@ func (t *Table) calculateAndNormalizeWidths(ctx *renderContext) error {
|
|||||||
mergedContentString, actualMergedHeaderContentPhysicalWidth, totalCurrentSpanPhysicalWidth)
|
mergedContentString, actualMergedHeaderContentPhysicalWidth, totalCurrentSpanPhysicalWidth)
|
||||||
shortfall := actualMergedHeaderContentPhysicalWidth - totalCurrentSpanPhysicalWidth
|
shortfall := actualMergedHeaderContentPhysicalWidth - totalCurrentSpanPhysicalWidth
|
||||||
numNonZeroCols := 0
|
numNonZeroCols := 0
|
||||||
workingWidths.Each(func(_ int, w int) {
|
workingWidths.Each(func(_, w int) {
|
||||||
if w > 0 {
|
if w > 0 {
|
||||||
numNonZeroCols++
|
numNonZeroCols++
|
||||||
}
|
}
|
||||||
@@ -744,7 +742,7 @@ func (t *Table) calculateAndNormalizeWidths(ctx *renderContext) error {
|
|||||||
if numNonZeroCols > 0 && shortfall > 0 {
|
if numNonZeroCols > 0 && shortfall > 0 {
|
||||||
extraPerColumn := int(math.Ceil(float64(shortfall) / float64(numNonZeroCols)))
|
extraPerColumn := int(math.Ceil(float64(shortfall) / float64(numNonZeroCols)))
|
||||||
finalSumAfterExpansion := 0
|
finalSumAfterExpansion := 0
|
||||||
workingWidths.Each(func(colIdx int, currentW int) {
|
workingWidths.Each(func(colIdx, currentW int) {
|
||||||
if currentW > 0 || (numNonZeroCols == ctx.numCols && ctx.numCols > 0) {
|
if currentW > 0 || (numNonZeroCols == ctx.numCols && ctx.numCols > 0) {
|
||||||
newWidth := currentW + extraPerColumn
|
newWidth := currentW + extraPerColumn
|
||||||
workingWidths.Set(colIdx, newWidth)
|
workingWidths.Set(colIdx, newWidth)
|
||||||
@@ -782,7 +780,7 @@ func (t *Table) calculateAndNormalizeWidths(ctx *renderContext) error {
|
|||||||
if t.config.Widths.Global > 0 {
|
if t.config.Widths.Global > 0 {
|
||||||
ctx.logger.Debugf("Applying global width constraint: %d", t.config.Widths.Global)
|
ctx.logger.Debugf("Applying global width constraint: %d", t.config.Widths.Global)
|
||||||
currentSumOfFinalColWidths := 0
|
currentSumOfFinalColWidths := 0
|
||||||
finalWidths.Each(func(_ int, w int) { currentSumOfFinalColWidths += w })
|
finalWidths.Each(func(_, w int) { currentSumOfFinalColWidths += w })
|
||||||
numSeparators := 0
|
numSeparators := 0
|
||||||
if ctx.numCols > 1 && t.renderer != nil && t.renderer.Config().Settings.Separators.BetweenColumns.Enabled() {
|
if ctx.numCols > 1 && t.renderer != nil && t.renderer.Config().Settings.Separators.BetweenColumns.Enabled() {
|
||||||
numSeparators = (ctx.numCols - 1) * twwidth.Width(t.renderer.Config().Symbols.Column())
|
numSeparators = (ctx.numCols - 1) * twwidth.Width(t.renderer.Config().Symbols.Column())
|
||||||
@@ -790,16 +788,13 @@ func (t *Table) calculateAndNormalizeWidths(ctx *renderContext) error {
|
|||||||
totalCurrentTablePhysicalWidth := currentSumOfFinalColWidths + numSeparators
|
totalCurrentTablePhysicalWidth := currentSumOfFinalColWidths + numSeparators
|
||||||
if totalCurrentTablePhysicalWidth > t.config.Widths.Global {
|
if totalCurrentTablePhysicalWidth > t.config.Widths.Global {
|
||||||
ctx.logger.Debugf("Table width %d exceeds global limit %d. Shrinking.", totalCurrentTablePhysicalWidth, t.config.Widths.Global)
|
ctx.logger.Debugf("Table width %d exceeds global limit %d. Shrinking.", totalCurrentTablePhysicalWidth, t.config.Widths.Global)
|
||||||
targetTotalColumnContentWidth := t.config.Widths.Global - numSeparators
|
targetTotalColumnContentWidth := max(t.config.Widths.Global-numSeparators, 0)
|
||||||
if targetTotalColumnContentWidth < 0 {
|
|
||||||
targetTotalColumnContentWidth = 0
|
|
||||||
}
|
|
||||||
if ctx.numCols > 0 && targetTotalColumnContentWidth < ctx.numCols {
|
if ctx.numCols > 0 && targetTotalColumnContentWidth < ctx.numCols {
|
||||||
targetTotalColumnContentWidth = ctx.numCols
|
targetTotalColumnContentWidth = ctx.numCols
|
||||||
}
|
}
|
||||||
hardMinimums := tw.NewMapper[int, int]()
|
hardMinimums := tw.NewMapper[int, int]()
|
||||||
sumOfHardMinimums := 0
|
sumOfHardMinimums := 0
|
||||||
isHeaderContentHardToWrap := !(t.config.Header.Formatting.AutoWrap == tw.WrapNormal || t.config.Header.Formatting.AutoWrap == tw.WrapBreak)
|
isHeaderContentHardToWrap := t.config.Header.Formatting.AutoWrap != tw.WrapNormal && t.config.Header.Formatting.AutoWrap != tw.WrapBreak
|
||||||
for i := 0; i < ctx.numCols; i++ {
|
for i := 0; i < ctx.numCols; i++ {
|
||||||
minW := 1
|
minW := 1
|
||||||
if isHeaderContentHardToWrap && len(ctx.headerLines) > 0 {
|
if isHeaderContentHardToWrap && len(ctx.headerLines) > 0 {
|
||||||
@@ -820,7 +815,7 @@ func (t *Table) calculateAndNormalizeWidths(ctx *renderContext) error {
|
|||||||
}
|
}
|
||||||
tempSum := 0
|
tempSum := 0
|
||||||
scaledHardMinimums := tw.NewMapper[int, int]()
|
scaledHardMinimums := tw.NewMapper[int, int]()
|
||||||
hardMinimums.Each(func(colIdx int, currentMinW int) {
|
hardMinimums.Each(func(colIdx, currentMinW int) {
|
||||||
scaledMinW := int(math.Round(float64(currentMinW) * scaleFactorMin))
|
scaledMinW := int(math.Round(float64(currentMinW) * scaleFactorMin))
|
||||||
if scaledMinW < 1 && targetTotalColumnContentWidth > 0 {
|
if scaledMinW < 1 && targetTotalColumnContentWidth > 0 {
|
||||||
scaledMinW = 1
|
scaledMinW = 1
|
||||||
@@ -894,31 +889,29 @@ func (t *Table) calculateAndNormalizeWidths(ctx *renderContext) error {
|
|||||||
if errorInDist < 0 {
|
if errorInDist < 0 {
|
||||||
adj = -1
|
adj = -1
|
||||||
}
|
}
|
||||||
if !(adj < 0 && w+adj < hardMinimums.Get(colToAdjust)) {
|
if adj >= 0 || w+adj >= hardMinimums.Get(colToAdjust) {
|
||||||
finalWidths.Set(colToAdjust, w+adj)
|
finalWidths.Set(colToAdjust, w+adj)
|
||||||
} else if adj > 0 {
|
} else if adj > 0 {
|
||||||
finalWidths.Set(colToAdjust, w+adj)
|
finalWidths.Set(colToAdjust, w+adj)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else if ctx.numCols > 0 {
|
||||||
if ctx.numCols > 0 {
|
extraPerCol := remainingWidthToDistribute / ctx.numCols
|
||||||
extraPerCol := remainingWidthToDistribute / ctx.numCols
|
rem := remainingWidthToDistribute % ctx.numCols
|
||||||
rem := remainingWidthToDistribute % ctx.numCols
|
for i := 0; i < ctx.numCols; i++ {
|
||||||
for i := 0; i < ctx.numCols; i++ {
|
currentW := finalWidths.Get(i)
|
||||||
currentW := finalWidths.Get(i)
|
add := extraPerCol
|
||||||
add := extraPerCol
|
if i < rem {
|
||||||
if i < rem {
|
add++
|
||||||
add++
|
|
||||||
}
|
|
||||||
finalWidths.Set(i, currentW+add)
|
|
||||||
}
|
}
|
||||||
|
finalWidths.Set(i, currentW+add)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finalSumCheck := 0
|
finalSumCheck := 0
|
||||||
finalWidths.Each(func(idx int, w int) {
|
finalWidths.Each(func(idx, w int) {
|
||||||
if w < 1 && targetTotalColumnContentWidth > 0 {
|
if w < 1 && targetTotalColumnContentWidth > 0 {
|
||||||
finalWidths.Set(idx, 1)
|
finalWidths.Set(idx, 1)
|
||||||
} else if w < 0 {
|
} else if w < 0 {
|
||||||
@@ -945,10 +938,7 @@ func (t *Table) calculateContentMaxWidth(colIdx int, config tw.CellConfig, padLe
|
|||||||
|
|
||||||
if isStreaming {
|
if isStreaming {
|
||||||
// Existing streaming logic remains unchanged
|
// Existing streaming logic remains unchanged
|
||||||
totalColumnWidthFromStream := t.streamWidths.Get(colIdx)
|
totalColumnWidthFromStream := max(t.streamWidths.Get(colIdx), 0)
|
||||||
if totalColumnWidthFromStream < 0 {
|
|
||||||
totalColumnWidthFromStream = 0
|
|
||||||
}
|
|
||||||
effectiveContentMaxWidth = totalColumnWidthFromStream - padLeftWidth - padRightWidth
|
effectiveContentMaxWidth = totalColumnWidthFromStream - padLeftWidth - padRightWidth
|
||||||
if effectiveContentMaxWidth < 1 && totalColumnWidthFromStream > (padLeftWidth+padRightWidth) {
|
if effectiveContentMaxWidth < 1 && totalColumnWidthFromStream > (padLeftWidth+padRightWidth) {
|
||||||
effectiveContentMaxWidth = 1
|
effectiveContentMaxWidth = 1
|
||||||
@@ -1252,7 +1242,7 @@ func (t *Table) convertCellsToStrings(rowInput interface{}, cellCfg tw.CellConfi
|
|||||||
var err error
|
var err error
|
||||||
|
|
||||||
switch v := rowInput.(type) {
|
switch v := rowInput.(type) {
|
||||||
//Directly supported slice types
|
// Directly supported slice types
|
||||||
case []string:
|
case []string:
|
||||||
cells = v
|
cells = v
|
||||||
case []interface{}: // Catches variadic simple types grouped by Append
|
case []interface{}: // Catches variadic simple types grouped by Append
|
||||||
@@ -1338,7 +1328,7 @@ func (t *Table) convertCellsToStrings(rowInput interface{}, cellCfg tw.CellConfi
|
|||||||
cells[i] = val.String()
|
cells[i] = val.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
//Cases for single items that are NOT slices
|
// Cases for single items that are NOT slices
|
||||||
// These are now dispatched to convertItemToCells by the default case.
|
// These are now dispatched to convertItemToCells by the default case.
|
||||||
// Keeping direct tw.Formatter and fmt.Stringer here could be a micro-optimization
|
// Keeping direct tw.Formatter and fmt.Stringer here could be a micro-optimization
|
||||||
// if `rowInput` is *exactly* that type (not a struct implementing it),
|
// if `rowInput` is *exactly* that type (not a struct implementing it),
|
||||||
@@ -1578,7 +1568,7 @@ func (t *Table) processVariadic(elements []any) []any {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// toStringLines converts raw cells to formatted lines for table output
|
// toStringLines converts raw cells to formatted lines for table output
|
||||||
func (t *Table) toStringLines(row interface{}, config tw.CellConfig) ([][]string, error) {
|
func (t *Table) toStringLines(row any, config tw.CellConfig) ([][]string, error) {
|
||||||
cells, err := t.convertCellsToStrings(row, config)
|
cells, err := t.convertCellsToStrings(row, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
Vendored
+1
-1
@@ -1165,7 +1165,7 @@ github.com/olekukonko/errors
|
|||||||
github.com/olekukonko/ll
|
github.com/olekukonko/ll
|
||||||
github.com/olekukonko/ll/lh
|
github.com/olekukonko/ll/lh
|
||||||
github.com/olekukonko/ll/lx
|
github.com/olekukonko/ll/lx
|
||||||
# github.com/olekukonko/tablewriter v1.0.9
|
# github.com/olekukonko/tablewriter v1.1.0
|
||||||
## explicit; go 1.21
|
## explicit; go 1.21
|
||||||
github.com/olekukonko/tablewriter
|
github.com/olekukonko/tablewriter
|
||||||
github.com/olekukonko/tablewriter/pkg/twwarp
|
github.com/olekukonko/tablewriter/pkg/twwarp
|
||||||
|
|||||||
Reference in New Issue
Block a user