MIGRATION.md
NOTE: This document is work in progress, use with
caution. This document is a comprehensive guide. For specific issues or advanced scenarios, please refer to the source code or open an issue.
The tablewriter library has undergone a significant redesign between versions v0.0.5 and v1.0.x, transitioning from a primarily method-driven API to a more robust, modular, and configuration-driven framework. This guide provides a detailed roadmap for migrating your v0.0.5 codebase to v1.0.x. It includes mappings of old methods to new approaches, practical examples, and explanations of new features.
We believe these changes significantly improve the library's flexibility, maintainability, and power, enabling new features and making complex table configurations more manageable.
The v1.0.x redesign enhances tablewriter’s flexibility, maintainability, and feature set:
Config struct and fluent builders ensure atomic, predictable setups.tw.State, tw.Align) reduce errors and improve clarity.These improvements make v1.0.x more powerful, but they require updating code to leverage the new configuration-driven framework and take advantage of advanced functionalities.
NewConfigBuilder() enables chained, readable setups (config.go:NewConfigBuilder).Config struct governs table behavior and data processing (config.go:Config).tw.Renderer interface with tw.Rendition for visual styling, allowing custom renderers (tw/renderer.go).Start(), Append(), Close() for incremental rendering (stream.go).tw.MergeHierarchical for complex data structures (tw/tw.go:MergeMode constant, logic in zoo.go).Top, Bottom, Left, Right) and per-column padding (tw/cell.go:CellPadding, tw/types.go:Padding).tw.State, tw.Align, tw.Spot, and others for clarity and safety (tw/state.go, tw/types.go).Render() and Append() return errors (tablewriter.go, stream.go).Config.Widths for precise column sizing, especially in streaming (config.go:Config, tw/cell.go:CellWidth).tw.Caption (tw/types.go:Caption).tw.Formatter, per-column filters, and stringer caching (tw/cell.go:CellFilter, tablewriter.go:WithStringer).Understanding these shifts is essential for a successful migration:
Configuration-Driven Approach:
table.SetXxx() methods for incremental, stateful modifications to table properties.tablewriter.Config struct (config.go:Config), while visual styling is managed by a tw.Rendition struct (tw/renderer.go:Rendition). These are typically set at table creation using NewTable() with Option functions or via a fluent ConfigBuilder, ensuring atomic and predictable configuration changes.Decoupled Rendering Engine:
Table struct, limiting output flexibility.tw.Renderer interface (tw/renderer.go:Renderer) defines rendering logic, with renderer.NewBlueprint() as the default text-based renderer. The renderer’s appearance (e.g., borders, symbols) is controlled by tw.Rendition, enabling support for alternative formats like HTML or Markdown.Unified Section Configuration:
tw.CellConfig (tw/cell.go:CellConfig) standardizes configuration across headers, rows, and footers, encompassing formatting (tw.CellFormatting), padding (tw.CellPadding), column widths (tw.CellWidth), alignments (tw.CellAlignment), and filters (tw.CellFilter).Fluent Configuration Builders:
tablewriter.NewConfigBuilder() (config.go:NewConfigBuilder) provides a chained, fluent API for constructing Config objects, with nested builders for Header(), Row(), Footer(), Alignment(), Behavior(), and ForColumn() to simplify complex setups.Explicit Streaming Mode:
Config.Stream.Enable or WithStreaming(tw.StreamConfig{Enable: true}) and managed with Table.Start(), Table.Append() (or Table.Header(), Table.Footer()), and Table.Close() (stream.go). This is ideal for large datasets or continuous output.Enhanced Error Handling:
Render() did not return errors, making error detection difficult.Render(), Start(), Close(), Append(), Bulk()) return errors to promote robust error handling and improve application reliability (tablewriter.go, stream.go).Richer Type System & tw Package:
ALIGN_CENTER) and booleans, leading to potential errors.tw sub-package introduces type-safe constructs like tw.State (tw/state.go), tw.Align (tw/types.go), tw.Position (tw/types.go), and tw.CellConfig (tw/cell.go), replacing magic constants and enhancing code clarity.v1.0.x offers four flexible methods to configure tables, catering to different use cases and complexity levels. Each method can be used independently or combined, providing versatility for both simple and advanced setups.
WithConfig Option:
tablewriter.Config struct during NewTable initialization using the WithConfig option.package main
import (
"github.com/olekukonko/tablewriter"
"github.com/olekukonko/tablewriter/tw"
"os"
)
func main() {
cfg := tablewriter.Config{
Header: tw.CellConfig{
Alignment: tw.CellAlignment{Global: tw.AlignCenter},
Formatting: tw.CellFormatting{AutoFormat: tw.On},
},
Row: tw.CellConfig{
Alignment: tw.CellAlignment{Global: tw.AlignLeft},
},
MaxWidth: 80,
Behavior: tw.Behavior{TrimSpace: tw.On},
}
table := tablewriter.NewTable(os.Stdout, tablewriter.WithConfig(cfg))
table.Header("Name", "Status")
table.Append("Node1", "Ready")
table.Render()
}
Output:
┌───────┬────────┐
│ NAME │ STATUS │
├───────┼────────┤
│ Node1 │ Ready │
└───────┴────────┘
Table.Configure method:
Table instance, use the Configure method with a function that modifies the table's Config struct.Table instance.package main
import (
"github.com/olekukonko/tablewriter"
"github.com/olekukonko/tablewriter/tw"
"os"
)
func main() {
table := tablewriter.NewTable(os.Stdout)
table.Configure(func(cfg *tablewriter.Config) {
cfg.Header.Alignment.Global = tw.AlignCenter
cfg.Row.Alignment.Global = tw.AlignLeft
})
table.Header("Name", "Status")
table.Append("Node1", "Ready")
table.Render()
}
Output: Same as above.
Option Functions:
WithXxx functions (e.g., WithHeaderAlignment, WithDebug) during NewTable initialization or via table.Options() to apply targeted settings.Config struct.package main
import (
"github.com/olekukonko/tablewriter"
"github.com/olekukonko/tablewriter/tw"
"os"
)
func main() {
table := tablewriter.NewTable(os.Stdout,
tablewriter.WithHeaderAlignment(tw.AlignCenter),
tablewriter.WithRowAlignment(tw.AlignLeft),
tablewriter.WithDebug(true),
)
table.Header("Name", "Status")
table.Append("Node1", "Ready")
table.Render()
}
**Output**: Same as above.
4. Fluent ConfigBuilder:
- Description: Use tablewriter.NewConfigBuilder() to construct a Config struct through a chained, fluent API, then apply it with WithConfig(builder.Build()).
- Use Case: Optimal for complex, dynamic, or programmatically generated configurations requiring fine-grained control.
- Pros: Highly readable, maintainable, and expressive; supports nested builders for sections and columns.
- Cons: Slightly verbose; requires understanding builder methods and Build() call.
- Example:
package main
import (
"github.com/olekukonko/tablewriter"
"github.com/olekukonko/tablewriter/tw"
"os"
)
func main() {
cnfBuilder := tablewriter.NewConfigBuilder()
cnfBuilder.Header().Alignment().WithGlobal(tw.AlignCenter)
// Example of configuring a specific column (less emphasis on this for now)
// cnfBuilder.ForColumn(0).WithAlignment(tw.AlignLeft).Build() // Call Build() to return to ConfigBuilder
table := tablewriter.NewTable(os.Stdout, tablewriter.WithConfig(cnfBuilder.Build()))
table.Header("Name", "Status")
table.Append("Node1", "Ready")
table.Render()
}
Output: Same as above.
Best Practices:
WithConfig or ConfigBuilder for complex setups or reusable configurations.Option functions for simple, targeted changes.Table.Configure for direct modifications after table creation, but avoid changes during rendering.ConfigBuilder for initial setup, Option functions for overrides).The defaultConfig() function (config.go:defaultConfig) establishes baseline settings for new tables, ensuring predictable behavior unless overridden. Below is a detailed table of default parameters, organized by configuration section, to help you understand the starting point for table behavior and appearance.
| Section | Parameter | Default Value | Description |
|---|---|---|---|
| Header | Alignment.Global | tw.AlignCenter | Centers header text globally unless overridden by PerColumn. |
| Header | Alignment.PerColumn | []tw.Align{} | Empty; falls back to Global unless specified. |
| Header | Formatting.AutoFormat | tw.On | Applies title case (e.g., "col_one" → "COL ONE") to header content. |
| Header | Formatting.AutoWrap | tw.WrapTruncate | Truncates long header text with "…" based on width constraints. |
| Header | Merging.Mode | tw.MergeNone | Disables cell merging in headers by default. |
| Header | Padding.Global | tw.PaddingDefault (" ") | Adds one space on left and right of header cells. |
| Header | Padding.PerColumn | []tw.Padding{} | Empty; falls back to Global unless specified. |
| Header | ColMaxWidths.Global | 0 (unlimited) | No maximum content width for header cells unless set. |
| Header | ColMaxWidths.PerColumn | tw.NewMapper[int, int]() | Empty map; no per-column content width limits unless specified. |
| Header | Filter.Global | nil | No global content transformation for header cells. |
| Header | Filter.PerColumn | []func(string) string{} | No per-column content transformations unless specified. |
| Row | Alignment.Global | tw.AlignLeft | Left-aligns row text globally unless overridden by PerColumn. |
| Row | Alignment.PerColumn | []tw.Align{} | Empty; falls back to Global. |
| Row | Formatting.AutoFormat | tw.Off | Disables auto-formatting (e.g., title case) for row content. |
| Row | Formatting.AutoWrap | tw.WrapNormal | Wraps long row text naturally at word boundaries based on width constraints. |
| Row | Merging.Mode | tw.MergeNone | Disables cell merging in rows by default. |
| Row | Padding.Global | tw.PaddingDefault (" ") | Adds one space on left and right of row cells. |
| Row | Padding.PerColumn | []tw.Padding{} | Empty; falls back to Global. |
| Row | ColMaxWidths.Global | 0 (unlimited) | No maximum content width for row cells. |
| Row | ColMaxWidths.PerColumn | tw.NewMapper[int, int]() | Empty map; no per-column content width limits. |
| Row | Filter.Global | nil | No global content transformation for row cells. |
| Row | Filter.PerColumn | []func(string) string{} | No per-column content transformations. |
| Footer | Alignment.Global | tw.AlignRight | Right-aligns footer text globally unless overridden by PerColumn. |
| Footer | Alignment.PerColumn | []tw.Align{} | Empty; falls back to Global. |
| Footer | Formatting.AutoFormat | tw.Off | Disables auto-formatting for footer content. |
| Footer | Formatting.AutoWrap | tw.WrapNormal | Wraps long footer text naturally. |
| Footer | Formatting.MergeMode | tw.MergeNone | Disables cell merging in footers. |
| Footer | Padding.Global | tw.PaddingDefault (" ") | Adds one space on left and right of footer cells. |
| Footer | Padding.PerColumn | []tw.Padding{} | Empty; falls back to Global. |
| Footer | ColMaxWidths.Global | 0 (unlimited) | No maximum content width for footer cells. |
| Footer | ColMaxWidths.PerColumn | tw.NewMapper[int, int]() | Empty map; no per-column content width limits. |
| Footer | Filter.Global | nil | No global content transformation for footer cells. |
| Footer | Filter.PerColumn | []func(string) string{} | No per-column content transformations. |
| Global | MaxWidth | 0 (unlimited) | No overall table width limit. |
| Global | Behavior.AutoHide | tw.Off | Displays empty columns (ignored in streaming). |
| Global | Behavior.TrimSpace | tw.On | Trims leading/trailing spaces from cell content. |
| Global | Behavior.Header | tw.Control{Hide: tw.Off} | Shows header if content is provided. |
| Global | Behavior.Footer | tw.Control{Hide: tw.Off} | Shows footer if content is provided. |
| Global | Behavior.Compact | tw.Compact{Merge: tw.Off} | No compact width optimization for merged cells. |
| Global | Debug | false | Disables debug logging. |
| Global | Stream.Enable | false | Disables streaming mode by default. |
| Global | Widths.Global | 0 (unlimited) | No fixed column width unless specified. |
| Global | Widths.PerColumn | tw.NewMapper[int, int]() | Empty map; no per-column fixed widths unless specified. |
Notes:
tw.PaddingDefault is {Left: " ", Right: " "} (tw/preset.go).tw.CellFormatting is deprecated; tw.CellAlignment is preferred. tw.AlignDefault falls back to Global or tw.AlignLeft (tw/types.go).Widths for fixed column sizing (stream.go).v1.0.x introduces a flexible rendering system via the tw.Renderer interface (tw/renderer.go:Renderer), allowing for both default text-based rendering and custom output formats. This decouples rendering logic from table data processing, enabling support for diverse formats like HTML, CSV, or JSON.
renderer.NewBlueprintrenderer.NewBlueprint() creates a text-based renderer. Its visual styles are configured using tw.Rendition.tw.Rendition, which controls:
Borders: Outer table borders (tw.Border) with tw.State for each side (tw/renderer.go).Settings: Internal lines (tw.Lines) and separators (tw.Separators) (tw/renderer.go).Symbols: Characters for drawing table lines and junctions (tw.Symbols) (tw/symbols.go).package main
import (
"github.com/olekukonko/tablewriter"
"github.com/olekukonko/tablewriter/renderer" // Import the renderer package
"github.com/olekukonko/tablewriter/tw"
"os"
)
func main() {
table := tablewriter.NewTable(os.Stdout,
tablewriter.WithRenderer(renderer.NewBlueprint()), // Default Blueprint renderer
tablewriter.WithRendition(tw.Rendition{ // Apply custom rendition
Symbols: tw.NewSymbols(tw.StyleRounded),
Borders: tw.Border{Top: tw.On, Bottom: tw.On, Left: tw.On, Right: tw.On},
Settings: tw.Settings{
Separators: tw.Separators{BetweenRows: tw.On},
Lines: tw.Lines{ShowHeaderLine: tw.On},
},
}),
)
table.Header("Name", "Status")
table.Append("Node1", "Ready")
table.Render()
}
Output:
╭───────┬────────╮
│ Name │ Status │
├───────┼────────┤
│ Node1 │ Ready │
╰───────┴────────╯
tw.Renderer interface to create custom output formats (e.g., HTML, CSV).Start(w io.Writer) error: Initializes rendering.Header(headers [][]string, ctx tw.Formatting): Renders header rows.Row(row []string, ctx tw.Formatting): Renders a data row.Footer(footers [][]string, ctx tw.Formatting): Renders footer rows.Line(ctx tw.Formatting): Renders separator lines.Close() error: Finalizes rendering.Config() tw.Rendition: Returns renderer's current rendition configuration.Logger(logger *ll.Logger): Sets logger for debugging.package main
import (
"fmt"
"github.com/olekukonko/ll" // For logger type
"github.com/olekukonko/tablewriter"
"github.com/olekukonko/tablewriter/tw"
"io"
"os"
)
// BasicHTMLRenderer implements tw.Renderer
type BasicHTMLRenderer struct {
writer io.Writer
config tw.Rendition // Store the rendition
logger *ll.Logger
err error
}
func (r *BasicHTMLRenderer) Start(w io.Writer) error {
r.writer = w
_, r.err = r.writer.Write([]byte("<table border='1'>\n"))
return r.err
}
// Header expects [][]string for potentially multi-line headers
func (r *BasicHTMLRenderer) Header(headers [][]string, ctx tw.Formatting) {
if r.err != nil { return }
_, r.err = r.writer.Write([]byte(" <tr>\n"))
if r.err != nil { return }
// Iterate over cells from the context for the current line
for _, cellCtx := range ctx.Row.Current {
content := fmt.Sprintf(" <th>%s</th>\n", cellCtx.Data)
_, r.err = r.writer.Write([]byte(content))
if r.err != nil { return }
}
_, r.err = r.writer.Write([]byte(" </tr>\n"))
}
// Row expects []string for a single line row, but uses ctx for actual data
func (r *BasicHTMLRenderer) Row(row []string, ctx tw.Formatting) { // row param is less relevant here, ctx.Row.Current is key
if r.err != nil { return }
_, r.err = r.writer.Write([]byte(" <tr>\n"))
if r.err != nil { return }
for _, cellCtx := range ctx.Row.Current {
content := fmt.Sprintf(" <td>%s</td>\n", cellCtx.Data)
_, r.err = r.writer.Write([]byte(content))
if r.err != nil { return }
}
_, r.err = r.writer.Write([]byte(" </tr>\n"))
}
func (r *BasicHTMLRenderer) Footer(footers [][]string, ctx tw.Formatting) {
if r.err != nil { return }
// Similar to Header/Row, using ctx.Row.Current for the footer line data
// The footers [][]string param might be used if the renderer needs multi-line footer logic
r.Row(nil, ctx) // Reusing Row logic, passing nil for the direct row []string as ctx contains the data
}
func (r *BasicHTMLRenderer) Line(ctx tw.Formatting) { /* No lines in basic HTML */ }
func (r *BasicHTMLRenderer) Close() error {
if r.err != nil {
return r.err
}
_, r.err = r.writer.Write([]byte("</table>\n"))
return r.err
}
func (r *BasicHTMLRenderer) Config() tw.Rendition { return r.config }
func (r *BasicHTMLRenderer) Logger(logger *ll.Logger) { r.logger = logger }
func main() {
table := tablewriter.NewTable(os.Stdout, tablewriter.WithRenderer(&BasicHTMLRenderer{
config: tw.Rendition{Symbols: tw.NewSymbols(tw.StyleASCII)}, // Provide a default Rendition
}))
table.Header("Name", "Status")
table.Append("Node1", "Ready")
table.Render()
}
Output:
<table border='1'>
<tr>
<th>NAME</th>
<th>STATUS</th>
</tr>
<tr>
<td>Node1</td>
<td>Ready</td>
</tr>
</table>
package main
import (
"fmt"
"io"
"os"
"strings"
"github.com/olekukonko/ll"
"github.com/olekukonko/tablewriter"
"github.com/olekukonko/tablewriter/tw"
)
// InvoiceRenderer implements tw.Renderer for a basic invoice style.
type InvoiceRenderer struct {
writer io.Writer
logger *ll.Logger
rendition tw.Rendition
}
func NewInvoiceRenderer() *InvoiceRenderer {
rendition := tw.Rendition{
Borders: tw.BorderNone,
Symbols: tw.NewSymbols(tw.StyleNone),
Settings: tw.Settings{Separators: tw.SeparatorsNone, Lines: tw.LinesNone},
Streaming: false,
}
defaultLogger := ll.New("simple-invoice-renderer").Disable()
return &InvoiceRenderer{logger: defaultLogger, rendition: rendition}
}
func (r *InvoiceRenderer) Logger(logger *ll.Logger) {
if logger != nil {
r.logger = logger
}
}
func (r *InvoiceRenderer) Config() tw.Rendition {
return r.rendition
}
func (r *InvoiceRenderer) Start(w io.Writer) error {
r.writer = w
r.logger.Debug("InvoiceRenderer: Start")
return nil
}
func (r *InvoiceRenderer) formatLine(cells []string, widths tw.Mapper[int, int], cellContexts map[int]tw.CellContext) string {
var sb strings.Builder
numCols := 0
if widths != nil { // Ensure widths is not nil before calling Len
numCols = widths.Len()
}
for i := 0; i < numCols; i++ {
data := ""
if i < len(cells) {
data = cells[i]
}
width := 0
if widths != nil { // Check again before Get
width = widths.Get(i)
}
align := tw.AlignDefault
if cellContexts != nil { // Check cellContexts
if ctx, ok := cellContexts[i]; ok {
align = ctx.Align
}
}
paddedCell := tw.Pad(data, " ", width, align)
sb.WriteString(paddedCell)
if i < numCols-1 {
sb.WriteString(" ") // Column separator
}
}
return sb.String()
}
func (r *InvoiceRenderer) Header(headers [][]string, ctx tw.Formatting) {
if r.writer == nil {
return
}
r.logger.Debugf("InvoiceRenderer: Header (lines: %d)", len(headers))
for _, headerLineCells := range headers {
lineStr := r.formatLine(headerLineCells, ctx.Row.Widths, ctx.Row.Current)
fmt.Fprintln(r.writer, lineStr)
}
if len(headers) > 0 {
totalWidth := 0
if ctx.Row.Widths != nil {
ctx.Row.Widths.Each(func(_ int, w int) { totalWidth += w })
if ctx.Row.Widths.Len() > 1 {
totalWidth += (ctx.Row.Widths.Len() - 1) * 3 // Separator spaces
}
}
if totalWidth > 0 {
fmt.Fprintln(r.writer, strings.Repeat("-", totalWidth))
}
}
}
func (r *InvoiceRenderer) Row(row []string, ctx tw.Formatting) {
if r.writer == nil {
return
}
r.logger.Debug("InvoiceRenderer: Row")
lineStr := r.formatLine(row, ctx.Row.Widths, ctx.Row.Current)
fmt.Fprintln(r.writer, lineStr)
}
func (r *InvoiceRenderer) Footer(footers [][]string, ctx tw.Formatting) {
if r.writer == nil {
return
}
r.logger.Debugf("InvoiceRenderer: Footer (lines: %d)", len(footers))
if len(footers) > 0 {
totalWidth := 0
if ctx.Row.Widths != nil {
ctx.Row.Widths.Each(func(_ int, w int) { totalWidth += w })
if ctx.Row.Widths.Len() > 1 {
totalWidth += (ctx.Row.Widths.Len() - 1) * 3
}
}
if totalWidth > 0 {
fmt.Fprintln(r.writer, strings.Repeat("-", totalWidth))
}
}
for _, footerLineCells := range footers {
lineStr := r.formatLine(footerLineCells, ctx.Row.Widths, ctx.Row.Current)
fmt.Fprintln(r.writer, lineStr)
}
}
func (r *InvoiceRenderer) Line(ctx tw.Formatting) {
r.logger.Debug("InvoiceRenderer: Line (no-op)")
// This simple renderer draws its own lines in Header/Footer.
}
func (r *InvoiceRenderer) Close() error {
r.logger.Debug("InvoiceRenderer: Close")
r.writer = nil
return nil
}
func main() {
data := [][]string{
{"Product A", "2", "10.00", "20.00"},
{"Super Long Product Name B", "1", "125.50", "125.50"},
{"Item C", "10", "1.99", "19.90"},
}
header := []string{"Description", "Qty", "Unit Price", "Total Price"}
footer := []string{"", "", "Subtotal:\nTax (10%):\nGRAND TOTAL:", "165.40\n16.54\n181.94"}
invoiceRenderer := NewInvoiceRenderer()
// Create table and set custom renderer using Options
table := tablewriter.NewTable(os.Stdout,
tablewriter.WithRenderer(invoiceRenderer),
tablewriter.WithAlignment([]tw.Align{
tw.AlignLeft, tw.AlignCenter, tw.AlignRight, tw.AlignRight,
}),
)
table.Header(header)
for _, v := range data {
table.Append(v)
}
// Use the Footer method with strings containing newlines for multi-line cells
table.Footer(footer)
fmt.Println("Rendering with InvoiceRenderer:")
table.Render()
// For comparison, render with default Blueprint renderer
// Re-create the table or reset it to use a different renderer
table2 := tablewriter.NewTable(os.Stdout,
tablewriter.WithAlignment([]tw.Align{
tw.AlignLeft, tw.AlignCenter, tw.AlignRight, tw.AlignRight,
}),
)
table2.Header(header)
for _, v := range data {
table2.Append(v)
}
table2.Footer(footer)
fmt.Println("\nRendering with Default Blueprint Renderer (for comparison):")
table2.Render()
}
Rendering with InvoiceRenderer:
DESCRIPTION QTY UNIT PRICE TOTAL PRICE
--------------------------------------------------------------------
Product A 2 10.00 20.00
Super Long Product Name B 1 125.50 125.50
Item C 10 1.99 19.90
--------------------------------------------------------------------
Subtotal: 165.40
--------------------------------------------------------------------
Tax (10%): 16.54
--------------------------------------------------------------------
GRAND TOTAL: 181.94
Rendering with Default Blueprint Renderer (for comparison):
┌───────────────────────────┬─────┬──────────────┬─────────────┐
│ DESCRIPTION │ QTY │ UNIT PRICE │ TOTAL PRICE │
├───────────────────────────┼─────┼──────────────┼─────────────┤
│ Product A │ 2 │ 10.00 │ 20.00 │
│ Super Long Product Name B │ 1 │ 125.50 │ 125.50 │
│ Item C │ 10 │ 1.99 │ 19.90 │
├───────────────────────────┼─────┼──────────────┼─────────────┤
│ │ │ Subtotal: │ 165.40 │
│ │ │ Tax (10%): │ 16.54 │
│ │ │ GRAND TOTAL: │ 181.94 │
└───────────────────────────┴─────┴──────────────┴─────────────┘
Notes:
renderer.NewBlueprint() is sufficient for most text-based use cases.tw.Formatting (which includes tw.RowContext) provides cell content and metadata.The following table maps v0.0.5 methods to their v1.0.x equivalents, ensuring a quick reference for migration. All deprecated methods are retained for compatibility but should be replaced with new APIs.
| v0.0.5 Method | v1.0.x Equivalent(s) | Notes |
|---|---|---|
NewWriter(w) | NewTable(w, opts...) | NewWriter deprecated; wraps NewTable (tablewriter.go). |
SetHeader([]string) | Header(...any) | Variadic or slice; supports any type (tablewriter.go). |
Append([]string) | Append(...any) | Variadic, slice, or struct (tablewriter.go). |
AppendBulk([][]string) | Bulk([]any) | Slice of rows; supports any type (tablewriter.go). |
SetFooter([]string) | Footer(...any) | Variadic or slice; supports any type (tablewriter.go). |
Render() | Render() (returns error) | Batch mode; streaming uses Start/Close (tablewriter.go). |
SetBorder(bool) | WithRendition(tw.Rendition{Borders: ...}) or WithBorders(tw.Border) (deprecated) | Use tw.Border (deprecated.go, tw/renderer.go). |
SetRowLine(bool) | WithRendition(tw.Rendition{Settings: {Separators: {BetweenRows: tw.On}}}) | tw.Separators (tw/renderer.go). |
SetHeaderLine(bool) | WithRendition(tw.Rendition{Settings: {Lines: {ShowHeaderLine: tw.On}}}) | tw.Lines (tw/renderer.go). |
SetColumnSeparator(string) | WithRendition(tw.Rendition{Symbols: ...}) or WithSymbols(tw.Symbols) (deprecated) | tw.NewSymbols or custom tw.Symbols (tw/symbols.go). |
SetCenterSeparator(string) | WithRendition(tw.Rendition{Symbols: ...}) or WithSymbols(tw.Symbols) (deprecated) | tw.Symbols (tw/symbols.go). |
SetRowSeparator(string) | WithRendition(tw.Rendition{Symbols: ...}) or WithSymbols(tw.Symbols) (deprecated) | tw.Symbols (tw/symbols.go). |
SetAlignment(int) | WithRowAlignment(tw.Align) or Config.Row.Alignment.Global | tw.Align type (config.go). |
SetHeaderAlignment(int) | WithHeaderAlignment(tw.Align) or Config.Header.Alignment.Global | tw.Align type (config.go). |
SetAutoFormatHeaders(bool) | WithHeaderAutoFormat(tw.State) or Config.Header.Formatting.AutoFormat | tw.State (config.go). |
SetAutoWrapText(bool) | WithRowAutoWrap(int) or Config.Row.Formatting.AutoWrap (uses tw.Wrap... const) | tw.WrapNormal, tw.WrapTruncate, etc. (config.go). |
SetAutoMergeCells(bool) | WithRowMergeMode(int) or Config.Row.Formatting.MergeMode (uses tw.Merge... const) | Supports Vertical, Hierarchical (config.go). |
SetColMinWidth(col, w) | WithColumnWidths(tw.NewMapper[int,int]().Set(col, w)) or Config.Widths.PerColumn | Config.Widths for fixed widths (config.go). |
SetTablePadding(string) | Use Config.<Section>.Padding.Global with tw.Padding | No direct equivalent; manage via cell padding (tw/cell.go). |
SetDebug(bool) | WithDebug(bool) or Config.Debug | Logs via table.Debug() (config.go). |
Clear() | Reset() | Clears data and state (tablewriter.go). |
SetNoWhiteSpace(bool) | WithTrimSpace(tw.Off) (if true) or WithPadding(tw.PaddingNone) | Manage via Config.Behavior.TrimSpace or padding (config.go). |
SetColumnColor(Colors) | Embed ANSI codes in cell data, use tw.Formatter, or Config.<Section>.Filter | Colors via data or filters (tw/cell.go). |
SetHeaderColor(Colors) | Embed ANSI codes in cell data, use tw.Formatter, or Config.Header.Filter | Colors via data or filters (tw/cell.go). |
SetFooterColor(Colors) | Embed ANSI codes in cell data, use tw.Formatter, or Config.Footer.Filter | Colors via data or filters (tw/cell.go). |
Notes:
deprecated.go but should be replaced.tw package types (e.g., tw.Align, tw.State) are required for new APIs.This section provides step-by-step guidance for migrating core table operations, with code examples and explanations to ensure clarity. Each step maps v0.0.5 methods to v1.0.x equivalents, highlighting changes, best practices, and potential pitfalls.
Initialization has shifted from NewWriter to NewTable, which supports flexible configuration via Option functions, Config structs, or ConfigBuilder.
Old (v0.0.5):
package main
import (
"github.com/olekukonko/tablewriter"
"os"
)
func main() {
table := tablewriter.NewWriter(os.Stdout)
// ... use table
_ = table // Avoid "declared but not used"
}
New (v1.0.x):
package main
import (
"fmt" // Added for FormattableEntry example
"github.com/olekukonko/tablewriter"
"github.com/olekukonko/tablewriter/renderer" // Import renderer
"github.com/olekukonko/tablewriter/tw"
"os"
"strings" // Added for Formatter example
)
func main() {
// Minimal Setup (Default Configuration)
tableMinimal := tablewriter.NewTable(os.Stdout)
_ = tableMinimal // Avoid "declared but not used"
// Using Option Functions for Targeted Configuration
tableWithOptions := tablewriter.NewTable(os.Stdout,
tablewriter.WithHeaderAlignment(tw.AlignCenter), // Center header text
tablewriter.WithRowAlignment(tw.AlignLeft), // Left-align row text
tablewriter.WithDebug(true), // Enable debug logging
)
_ = tableWithOptions // Avoid "declared but not used"
// Using a Full Config Struct for Comprehensive Control
cfg := tablewriter.Config{
Header: tw.CellConfig{
Alignment: tw.CellAlignment{
Global: tw.AlignCenter,
PerColumn: []tw.Align{tw.AlignLeft, tw.AlignRight}, // Column-specific alignment
},
Formatting: tw.CellFormatting{AutoFormat: tw.On},
},
Row: tw.CellConfig{
Alignment: tw.CellAlignment{Global: tw.AlignLeft},
Formatting: tw.CellFormatting{AutoWrap: tw.WrapNormal},
},
Footer: tw.CellConfig{
Alignment: tw.CellAlignment{Global: tw.AlignRight},
},
MaxWidth: 80, // Constrain total table width
Behavior: tw.Behavior{
AutoHide: tw.Off, // Show empty columns
TrimSpace: tw.On, // Trim cell spaces
},
Widths: tw.CellWidth{
Global: 20, // Default fixed column width
PerColumn: tw.NewMapper[int, int]().Set(0, 15), // Column 0 fixed at 15
},
}
tableWithConfig := tablewriter.NewTable(os.Stdout, tablewriter.WithConfig(cfg))
_ = tableWithConfig // Avoid "declared but not used"
// Using ConfigBuilder for Fluent, Complex Configuration
builder := tablewriter.NewConfigBuilder().
WithMaxWidth(80).
WithAutoHide(tw.Off).
WithTrimSpace(tw.On).
WithDebug(true). // Enable debug logging
Header().
Alignment().
WithGlobal(tw.AlignCenter).
Build(). // Returns *ConfigBuilder
Header().
Formatting().
WithAutoFormat(tw.On).
WithAutoWrap(tw.WrapTruncate). // Test truncation
WithMergeMode(tw.MergeNone). // Explicit merge mode
Build(). // Returns *HeaderConfigBuilder
Padding().
WithGlobal(tw.Padding{Left: "[", Right: "]", Overwrite: true}).
Build(). // Returns *HeaderConfigBuilder
Build(). // Returns *ConfigBuilder
Row().
Formatting().
WithAutoFormat(tw.On). // Uppercase rows
Build(). // Returns *RowConfigBuilder
Build(). // Returns *ConfigBuilder
Row().
Alignment().
WithGlobal(tw.AlignLeft).
Build(). // Returns *ConfigBuilder
Build() // Finalize Config
tableWithFluent := tablewriter.NewTable(os.Stdout, tablewriter.WithConfig(builder))
_ = tableWithFluent // Avoid "declared but not used"
}
Key Changes:
NewWriter is deprecated but retained for compatibility, internally calling NewTable (tablewriter.go:NewWriter). Transition to NewTable for new code.NewTable accepts an io.Writer and variadic Option functions (tablewriter.go:NewTable), enabling configuration via:
WithConfig(Config): Applies a complete Config struct (config.go:WithConfig).WithRenderer(tw.Renderer): Sets a custom renderer, defaulting to renderer.NewBlueprint() (tablewriter.go:WithRenderer).WithRendition(tw.Rendition): Configures visual styles for the renderer (tablewriter.go:WithRendition).WithStreaming(tw.StreamConfig): Enables streaming mode (tablewriter.go:WithStreaming).WithXxx functions for specific settings (e.g., WithHeaderAlignment, WithDebug).Build() finalizes the Config (config.go:ConfigBuilder).Build() on nested builders to return to the parent builder (e.g., builder.Header().Alignment().WithGlobal(...).Build().Formatting()...).Migration Tips:
NewWriter with NewTable and specify configurations explicitly.ConfigBuilder for complex setups or when readability is paramount.Option functions for quick, targeted changes.tw package is imported for types like tw.Align and tw.CellConfig.renderer.NewBlueprint() is the default.Potential Pitfalls:
defaultConfig() applies (see Default Parameters), which may differ from v0.0.5 behavior (e.g., Header.Formatting.AutoFormat = tw.On).NewTable defaults to renderer.NewBlueprint(); explicitly set for custom formats.Build() at the end of a builder chain and on nested builders; omitting it can lead to incomplete configurations or runtime errors.Table.config (if using the Configure method or direct access) in concurrent scenarios or during rendering to prevent race conditions.Data input methods in v1.0.x are more flexible, accepting any type for headers, rows, and footers, with robust conversion logic to handle strings, structs, and custom types.
Headers define the table’s column labels and are typically the first data added.
Old (v0.0.5):
package main
import (
"github.com/olekukonko/tablewriter"
"os"
)
func main() {
table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"Name", "Sign", "Rating"})
// ...
}
New (v1.0.x):
package main
import (
"fmt"
"github.com/olekukonko/tablewriter"
"github.com/olekukonko/tablewriter/tw" // For tw.Formatter
"os"
"strings"
)
// Struct with Formatter
type HeaderData struct { // Renamed to avoid conflict
Label string
}
func (h HeaderData) Format() string { return strings.ToUpper(h.Label) } // Implements tw.Formatter
func main() {
table := tablewriter.NewTable(os.Stdout)
// Variadic Arguments (Preferred for Simplicity)
table.Header("Name", "Sign", "Rating")
// Slice of Strings
// table.Header([]string{"Name", "Sign", "Rating"}) // Example, comment out if using variadic
// Slice of Any Type (Flexible)
// table.Header([]any{"Name", "Sign", 123}) // Numbers converted to strings
// Using Formatter
// table.Header(HeaderData{"name"}, HeaderData{"sign"}, HeaderData{"rating"}) // Outputs "NAME", "SIGN", "RATING"
table.Append("Example", "Row", "Data") // Add some data to render
table.Render()
}
Key Changes:
SetHeader([]string) replaced by Header(...any) (tablewriter.go:Header).processVariadic (zoo.go:processVariadic) and converted to strings via convertCellsToStrings (zoo.go:convertCellsToStrings), supporting:
string, int, float64).fmt.Stringer implementations.tw.Formatter implementations (custom string conversion).Stringer/Formatter).Header() renders immediately via streamRenderHeader (stream.go:streamRenderHeader).Config.Header settings (e.g., AutoFormat, Alignment) during rendering (zoo.go:prepareContent).Migration Tips:
SetHeader with Header, using variadic arguments for simplicity.tw.Formatter for custom header formatting (e.g., uppercase).Header() before rows, as it fixes column widths (stream.go).Potential Pitfalls:
tw.Formatter for custom types).Config.Widths explicitly if specific widths are needed (stream.go:streamCalculateWidths)."") if needed.Header.Formatting.AutoFormat = tw.On applies title case; disable with WithHeaderAutoFormat(tw.Off) if unwanted (config.go).Rows represent the table’s data entries, and v1.0.x enhances flexibility with support for varied input types.
Old (v0.0.5):
package main
import (
"github.com/olekukonko/tablewriter"
"os"
)
func main() {
table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"ColA", "ColB", "ColC"}) // Add header for context
table.Append([]string{"A", "The Good", "500"})
table.Append([]string{"B", "The Very Bad", "288"})
data := [][]string{
{"C", "The Ugly", "120"},
{"D", "The Gopher", "800"},
}
table.AppendBulk(data)
table.Render()
}
New (v1.0.x):
package main
import (
"fmt"
"github.com/olekukonko/tablewriter"
"github.com/olekukonko/tablewriter/tw" // For tw.Formatter
"log"
"os"
)
// Struct for examples
type Entry struct {
ID string
Label string
Score int
}
// Struct with Formatter
type FormattableEntry struct {
ID string
Label string
Score int
}
func (e FormattableEntry) Format() string { return fmt.Sprintf("%s (%d)", e.Label, e.Score) } // Implements tw.Formatter
func main() {
table := tablewriter.NewTable(os.Stdout)
table.Header("ID", "Description", "Value") // Header for context
// Variadic Arguments (Single Row)
table.Append("A", "The Good", 500) // Numbers converted to strings
// Slice of Any Type (Single Row)
table.Append([]any{"B", "The Very Bad", "288"})
// Struct with Fields
table.Append(Entry{ID: "C", Label: "The Ugly", Score: 120}) // Fields as cells
// Struct with Formatter (will produce a single cell for this row based on Format())
// To make it fit 3 columns, the formatter would need to produce a string that looks like 3 cells, or the table config would need to adapt.
// For this example, let's assume it's meant to be one wide cell, or adjust the header.
// For now, let's simplify and append it to a table with one header.
tableOneCol := tablewriter.NewTable(os.Stdout)
tableOneCol.Header("Formatted Entry")
tableOneCol.Append(FormattableEntry{ID: "D", Label: "The Gopher", Score: 800}) // Single cell "The Gopher (800)"
tableOneCol.Render()
fmt.Println("---")
// Re-initialize main table for Bulk example
table = tablewriter.NewTable(os.Stdout)
table.Header("ID", "Description", "Value")
// Multiple Rows with Bulk
data := []any{
[]any{"E", "The Fast", 300},
Entry{ID: "F", Label: "The Swift", Score: 400}, // Struct instance
// FormattableEntry{ID: "G", Label: "The Bold", Score: 500}, // Would also be one cell
}
if err := table.Bulk(data); err != nil {
log.Fatalf("Bulk append failed: %v", err)
}
table.Render()
}
Key Changes:
Append([]string) replaced by Append(...any); AppendBulk([][]string) replaced by Bulk([]any) (tablewriter.go).Append accepts:
[]string, []any) as cells of a single row.convertItemToCells (zoo.go):
tw.Formatter or fmt.Stringer for single-cell output.Bulk accepts a slice where each element is a row (e.g., [][]any, []Entry), processed by appendSingle (zoo.go:appendSingle).streamAppendRow (stream.go:streamAppendRow).Append and Bulk return errors for invalid conversions or streaming issues (tablewriter.go).Migration Tips:
Append([]string) with Append(...any) for variadic input.Bulk for multiple rows, ensuring each element is a valid row representation.tw.Formatter for custom struct formatting to control cell output.Start() and before Close() (stream.go).Potential Pitfalls:
"") if needed.Formatter/Stringer (zoo.go).stream.go).Bulk errors, as invalid data can cause failures.Footers provide summary or closing data for the table, often aligned differently from rows.
Old (v0.0.5):
package main
import (
"github.com/olekukonko/tablewriter"
"os"
)
func main() {
table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"ColA", "ColB", "ColC", "ColD"}) // Add header for context
table.SetFooter([]string{"", "", "Total", "1408"})
table.Render()
}
New (v1.0.x):
package main
import (
"fmt"
"github.com/olekukonko/tablewriter"
"github.com/olekukonko/tablewriter/tw" // For tw.Formatter
"os"
)
// Using Formatter
type FooterSummary struct { // Renamed to avoid conflict
Label string
Value float64
}
func (s FooterSummary) Format() string { return fmt.Sprintf("%s: %.2f", s.Label, s.Value) } // Implements tw.Formatter
func main() {
table := tablewriter.NewTable(os.Stdout)
table.Header("ColA", "ColB", "ColC", "ColD") // Header for context
// Variadic Arguments
table.Footer("", "", "Total", 1408)
// Slice of Any Type
// table.Footer([]any{"", "", "Total", 1408.50})
table.Render() // Render this table
fmt.Println("--- Another Table with Formatter Footer ---")
table2 := tablewriter.NewTable(os.Stdout)
table2.Header("Summary Info") // Single column header
// Using Formatter for a single cell footer
table2.Footer(FooterSummary{Label: "Grand Total", Value: 1408.50}) // Single cell: "Grand Total: 1408.50"
table2.Render()
}
Key Changes:
SetFooter([]string) replaced by Footer(...any) (tablewriter.go:Footer).Header, accepts variadic arguments, slices, or structs with Formatter/Stringer support (zoo.go:processVariadic).streamStoreFooter and rendered during Close() (stream.go:streamStoreFooter, stream.go:streamRenderFooter).Config.Footer settings (e.g., Alignment, AutoWrap) (zoo.go:prepareTableSection).Migration Tips:
SetFooter with Footer, preferring variadic input for simplicity.tw.Formatter for custom footer formatting (e.g., formatted numbers).Footer() before Close() to include it in rendering.Potential Pitfalls:
Footer.Alignment.Global = tw.AlignRight differs from rows (tw.AlignLeft); adjust if needed (config.go).Close(); ensure Close() is called (stream.go).Rendering has been overhauled to support both batch and streaming modes, with mandatory error handling for robustness.
Old (v0.0.5):
package main
import (
"github.com/olekukonko/tablewriter"
"os"
)
func main() {
table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"Data"})
table.Append([]string{"Example"})
table.Render()
}
New (v1.0.x) - Batch Mode:
package main
import (
"github.com/olekukonko/tablewriter"
"log"
"os"
)
func main() {
table := tablewriter.NewTable(os.Stdout)
table.Header("Data")
table.Append("Example")
if err := table.Render(); err != nil {
log.Fatalf("Table rendering failed: %v", err)
}
}
New (v1.0.x) - Streaming Mode:
package main
import (
"fmt"
"github.com/olekukonko/tablewriter"
"github.com/olekukonko/tablewriter/tw"
"log"
"os"
)
func main() {
tableStream := tablewriter.NewTable(os.Stdout,
tablewriter.WithConfig(tablewriter.Config{
Stream: tw.StreamConfig{Enable: true},
Widths: tw.CellWidth{
Global: 12, // Fixed column width for streaming
PerColumn: tw.NewMapper[int, int]().Set(0, 15), // Column 0 at 15
},
}),
)
// Alternative: Using WithStreaming Option
// tableStream := tablewriter.NewTable(os.Stdout,
// tablewriter.WithStreaming(tw.StreamConfig{Enable: true}),
// tablewriter.WithColumnMax(12), // Sets Config.Widths.Global
// )
if err := tableStream.Start(); err != nil {
log.Fatalf("Failed to start table stream: %v", err)
}
tableStream.Header("Column 1", "Column 2")
for i := 0; i < 3; i++ {
if err := tableStream.Append(fmt.Sprintf("Data %d-1", i), fmt.Sprintf("Data %d-2", i)); err != nil {
log.Printf("Failed to append row %d: %v", i, err) // Log and continue or handle differently
}
}
tableStream.Footer("End", "Summary")
if err := tableStream.Close(); err != nil {
log.Fatalf("Failed to close table stream: %v", err)
}
}
Key Changes:
Render() processes all data (headers, rows, footers) in one go (tablewriter.go:render).Config.Widths, ColMaxWidths, and MaxWidth (zoo.go:calculateAndNormalizeWidths).Start(), Header(), Row(), Footer(), Line(), Close() (tw/renderer.go).Config.Stream.Enable or WithStreaming(tw.StreamConfig{Enable: true}) (tablewriter.go:WithStreaming).Start() initializes the stream, fixing column widths based on Config.Widths or first data (header/row) (stream.go:streamCalculateWidths).Header(), Append() render immediately (stream.go:streamRenderHeader, stream.go:streamAppendRow).Footer() buffers data, rendered by Close() (stream.go:streamStoreFooter, stream.go:streamRenderFooter).Close() finalizes rendering with footer and borders (stream.go:Close).Migration Tips:
Render() calls with error-checked versions in batch mode.Start(), Append(), Close() workflow, ensuring Start() precedes data input.Config.Widths for consistent column widths in streaming mode (config.go).WithRendition to customize visual output, as renderer settings are decoupled (tablewriter.go).Potential Pitfalls:
if err != nil.Start(); inconsistent data may cause truncation (stream.go).tw.Rendition matches desired output style (tw/renderer.go).Close() in streaming mode omits footer and final borders (stream.go).Render() in streaming mode causes errors; use Start()/Close() instead (tablewriter.go).Styling in v1.0.x is split between tablewriter.Config for data processing (e.g., alignment, padding, wrapping) and tw.Rendition for visual rendering (e.g., borders, symbols, lines). This section details how to migrate v0.0.5 styling methods to v1.0.x, providing examples, best practices, and migration tips to maintain or enhance table appearance.
Table styles define the visual structure through border and separator characters. In v0.0.5, styles were set via individual separator methods, whereas v1.0.x uses tw.Rendition.Symbols for a cohesive approach, offering predefined styles and custom symbol sets.
Available Table Styles:
The tw.Symbols interface (tw/symbols.go) supports a variety of predefined styles, each tailored to specific use cases, as well as custom configurations for unique requirements.
| Style Name | Use Case | Symbols Example | Recommended Context |
|---|---|---|---|
StyleASCII | Simple, terminal-friendly | +, -, ` | ` |
StyleLight | Clean, lightweight borders | ┌, └, ─, │ | Modern terminals, clean and professional aesthetics; suitable for most CLI applications requiring a modern look. |
StyleHeavy | Thick, prominent borders | ┏, ┗, ━, ┃ | High-visibility reports, dashboards, or logs; emphasizes table structure for critical data presentation. |
StyleDouble | Double-line borders | ╔, ╚, ═, ║ | Formal documents, structured data exports; visually distinct for presentations or printed outputs. |
StyleRounded | Rounded corners, aesthetic | ╭, ╰, ─, │ | User-friendly CLI applications, reports; enhances visual appeal with smooth, rounded edges. |
StyleMarkdown | Markdown-compatible | ` | , -, :` |
Note: StyleBold was mentioned but not defined in the symbols code, StyleHeavy is similar. |
Old (v0.0.5):
package main
import (
"github.com/olekukonko/tablewriter"
"os"
)
func main() {
table := tablewriter.NewWriter(os.Stdout)
// table.SetCenterSeparator("*") // Example, actual v0.0.5 method might vary
// table.SetColumnSeparator("!")
// table.SetRowSeparator("=")
// table.SetBorder(true)
table.SetHeader([]string{"Header1", "Header2"})
table.Append([]string{"Cell1", "Cell2"})
table.Render()
}
New (v1.0.x):
package main
import (
"github.com/olekukonko/tablewriter"
"github.com/olekukonko/tablewriter/renderer"
"github.com/olekukonko/tablewriter/tw"
"os"
)
func main() {
// Using a Predefined Style (Rounded Corners for Aesthetic Output)
tableStyled := tablewriter.NewTable(os.Stdout,
tablewriter.WithRenderer(renderer.NewBlueprint()), // Ensure renderer is set
tablewriter.WithRendition(tw.Rendition{
Symbols: tw.NewSymbols(tw.StyleRounded), // Predefined rounded style
Borders: tw.Border{Top: tw.On, Bottom: tw.On, Left: tw.On, Right: tw.On},
Settings: tw.Settings{
Separators: tw.Separators{BetweenRows: tw.On}, // Lines between rows
},
}),
)
tableStyled.Header("Name", "Status")
tableStyled.Append("Node1", "Ready")
tableStyled.Render()
// Using Fully Custom Symbols (Mimicking v0.0.5 Custom Separators)
mySymbols := tw.NewSymbolCustom("my-style"). // Fluent builder for custom symbols
WithCenter("*"). // Junction of lines
WithColumn("!"). // Vertical separator
WithRow("="). // Horizontal separator
WithTopLeft("/").
WithTopMid("-").
WithTopRight("\\").
WithMidLeft("[").
WithMidRight("]"). // Corrected from duplicate WithMidRight("+")
// WithMidMid was not a method in SymbolCustom
WithBottomLeft("\\").
WithBottomMid("_").
WithBottomRight("/").
WithHeaderLeft("(").
WithHeaderMid("-").
WithHeaderRight(")") // Header-specific
tableCustomSymbols := tablewriter.NewTable(os.Stdout,
tablewriter.WithRenderer(renderer.NewBlueprint()),
tablewriter.WithRendition(tw.Rendition{
Symbols: mySymbols,
Borders: tw.Border{Top: tw.On, Bottom: tw.On, Left: tw.On, Right: tw.On},
}),
)
tableCustomSymbols.Header("Name", "Status")
tableCustomSymbols.Append("Node1", "Ready")
tableCustomSymbols.Render()
}
Output (Styled, Rounded):
╭───────┬────────╮
│ NAME │ STATUS │
├───────┼────────┤
│ Node1 │ Ready │
╰───────┴────────╯
Output (Custom Symbols):
/=======-========\
! NAME ! STATUS !
[=======*========]
! Node1 ! Ready !
\=======_========/
Key Changes:
SetCenterSeparator, SetColumnSeparator, SetRowSeparator, and SetBorder are deprecated and moved to deprecated.go. These are replaced by tw.Rendition.Symbols and tw.Rendition.Borders for a unified styling approach (tablewriter.go).tw.Symbols interface defines all drawing characters used for table lines, junctions, and corners (tw/symbols.go:Symbols).
tw.NewSymbols(tw.BorderStyle) provides predefined styles (e.g., tw.StyleASCII, tw.StyleMarkdown, tw.StyleRounded) for common use cases. (Note: tw.Style should be tw.BorderStyle)tw.NewSymbolCustom(name string) enables fully custom symbol sets via a fluent builder, allowing precise control over each character (e.g., WithCenter, WithTopLeft).WithRenderer; the default is renderer.NewBlueprint() if not specified (tablewriter.go:WithRenderer).tw.Rendition.Borders uses tw.State (tw.On, tw.Off) to enable or disable borders on each side (Top, Bottom, Left, Right) (tw/renderer.go:Border).tw/renderer.go).Migration Tips:
SetCenterSeparator, etc.) with tw.NewSymbols for predefined styles or tw.NewSymbolCustom for custom designs to maintain or enhance v0.0.5 styling.WithRendition to apply tw.Rendition settings, ensuring a renderer is explicitly set to avoid default behavior (tablewriter.go).tw.StyleMarkdown to ensure proper rendering in Markdown parsers (tw/symbols.go).tw.Rendition.Symbols with tw.Rendition.Borders and tw.Rendition.Settings to replicate or improve v0.0.5’s border and line configurations (tw/renderer.go).tw/symbols.go).Potential Pitfalls:
WithRenderer is omitted, NewTable defaults to renderer.NewBlueprint with minimal styling, which may not match v0.0.5’s SetBorder(true) behavior; always specify for custom styles (tablewriter.go).tw.NewSymbolCustom, failing to set all required symbols (e.g., TopLeft, Center, HeaderLeft) can cause rendering errors or inconsistent visuals; ensure all necessary characters are defined (tw/symbols.go).StyleDouble or StyleHeavy may not render correctly in older terminals or non-Unicode environments; use StyleASCII for maximum compatibility across platforms (tw/symbols.go).tw.Rendition.Borders and tw.Symbols settings (e.g., enabling borders but using minimal symbols) can lead to visual artifacts; test with small tables to verify alignment (tw/renderer.go).StyleRounded) may not render correctly in Markdown viewers; use StyleMarkdown for documentation or web-based outputs (tw/symbols.go).Borders and internal lines define the table’s structural appearance, controlling the visibility of outer edges and internal divisions. In v0.0.5, these were set via specific methods, while v1.0.x uses tw.Rendition fields for a more integrated approach.
Old (v0.0.5):
package main
import (
"github.com/olekukonko/tablewriter"
"os"
)
func main() {
table := tablewriter.NewWriter(os.Stdout)
// table.SetBorder(false) // Disable all outer borders
// table.SetRowLine(true) // Enable lines between data rows
// table.SetHeaderLine(true) // Enable line below header
table.SetHeader([]string{"Header1", "Header2"})
table.Append([]string{"Cell1", "Cell2"})
table.Render()
}
New (v1.0.x):
package main
import (
"github.com/olekukonko/tablewriter"
"github.com/olekukonko/tablewriter/renderer"
"github.com/olekukonko/tablewriter/tw"
"os"
)
func main() {
// Standard Bordered Table with Internal Lines
table := tablewriter.NewTable(os.Stdout,
tablewriter.WithRenderer(renderer.NewBlueprint()),
tablewriter.WithRendition(tw.Rendition{
Borders: tw.Border{ // Outer table borders
Left: tw.On,
Right: tw.On,
Top: tw.On,
Bottom: tw.On,
},
Settings: tw.Settings{
Lines: tw.Lines{ // Major internal separator lines
ShowHeaderLine: tw.On, // Line after header
ShowFooterLine: tw.On, // Line before footer (if footer exists)
},
Separators: tw.Separators{ // General row and column separators
BetweenRows: tw.On, // Horizontal lines between data rows
BetweenColumns: tw.On, // Vertical lines between columns
},
},
}),
)
table.Header("Name", "Status")
table.Append("Node1", "Ready")
table.Render()
// Borderless Table (kubectl-style, No Lines or Separators)
// Configure the table with a borderless, kubectl-style layout
config := tablewriter.NewConfigBuilder().
Header().
Padding().
WithGlobal(tw.PaddingNone). // No header padding
Build().
Alignment().
WithGlobal(tw.AlignLeft). // Left-align header
Build().
Row().
Padding().
WithGlobal(tw.PaddingNone). // No row padding
Build().
Alignment().
WithGlobal(tw.AlignLeft). // Left-align rows
Build().
Footer().
Padding().
WithGlobal(tw.PaddingNone). // No footer padding
Build().
Alignment().
WithGlobal(tw.AlignLeft). // Left-align footer (if used)
Build().
WithDebug(true). // Enable debug logging
Build()
// Create borderless table
tableBorderless := tablewriter.NewTable(os.Stdout,
tablewriter.WithRenderer(renderer.NewBlueprint()), // Assumes valid renderer
tablewriter.WithRendition(tw.Rendition{
Borders: tw.BorderNone, // No borders
Symbols: tw.NewSymbols(tw.StyleNone), // No border symbols
Settings: tw.Settings{
Lines: tw.LinesNone, // No header/footer lines
Separators: tw.SeparatorsNone, // No row/column separators
},
}),
tablewriter.WithConfig(config),
)
// Set headers and data
tableBorderless.Header("Name", "Status")
tableBorderless.Append("Node1", "Ready")
tableBorderless.Render()
}
Output (Standard Bordered):
┌───────┬────────┐
│ Name │ Status │
├───────┼────────┤
│ Node1 │ Ready │
└───────┴────────┘
Output (Borderless):
NAME STATUS
Node1 Ready
Key Changes:
SetBorder, SetRowLine, and SetHeaderLine are deprecated and moved to deprecated.go. These are replaced by fields in tw.Rendition (tw/renderer.go):
Borders: Controls outer table borders (tw.Border) with tw.State (tw.On, tw.Off) for each side (Top, Bottom, Left, Right).Settings.Lines: Manages major internal lines (ShowHeaderLine for header, ShowFooterLine for footer) (tw.Lines).Settings.Separators: Controls general separators between rows (BetweenRows) and columns (BetweenColumns) (tw.Separators).tw.BorderNone, tw.LinesNone, and tw.SeparatorsNone provide quick configurations for minimal or borderless tables (tw/preset.go).WithRendition, requiring a renderer to be set (tablewriter.go:WithRendition).tw.Rendition.Symbols; ensure compatible symbol sets (tw/symbols.go).Migration Tips:
SetBorder(false) with tw.Rendition.Borders = tw.BorderNone to disable all outer borders (tw/preset.go).tw.Rendition.Settings.Separators.BetweenRows = tw.On to replicate SetRowLine(true), ensuring row separators are visible (tw/renderer.go).tw.Rendition.Settings.Lines.ShowHeaderLine = tw.On to mimic SetHeaderLine(true) for a line below the header (tw/renderer.go).tw.BorderNone, tw.LinesNone, tw.SeparatorsNone, and WithPadding(tw.PaddingNone) (applied via ConfigBuilder or WithConfig) to eliminate all lines and spacing (tw/preset.go, config.go).tw.Symbols.WithDebug(true) to log rendering details if borders or lines appear incorrectly (config.go).Potential Pitfalls:
tw.Rendition.Settings.Separators.BetweenColumns is tw.Off and borders are disabled, columns may lack visual separation; use tw.CellPadding or ensure content spacing (tw/cell.go).ShowHeaderLine but disabling Borders.Top) can cause uneven rendering; align Borders, Lines, and Separators settings (tw/renderer.go).WithRenderer defaults to renderer.NewBlueprint with minimal styling, which may not match v0.0.5 expectations (tablewriter.go).Start(); ensure tw.Rendition is set correctly before rendering begins (stream.go).tw.Symbols (e.g., StyleASCII) with complex Borders settings may lead to visual artifacts; test with matching symbol sets (tw/symbols.go).Alignment controls the positioning of text within table cells, now configurable per section (Header, Row, Footer) with both global and per-column options for greater precision.
Old (v0.0.5):
package main
import (
"github.com/olekukonko/tablewriter" // Assuming ALIGN_CENTER etc. were constants here
"os"
)
const ( // Mocking v0.0.5 constants for example completeness
ALIGN_CENTER = 1 // Example values
ALIGN_LEFT = 2
)
func main() {
table := tablewriter.NewWriter(os.Stdout)
// table.SetAlignment(ALIGN_CENTER) // Applied to data rows
// table.SetHeaderAlignment(ALIGN_LEFT) // Applied to header
// No specific footer alignment setter
table.SetHeader([]string{"Header1", "Header2"})
table.Append([]string{"Cell1", "Cell2"})
table.Render()
}
New (v1.0.x):
package main
import (
"github.com/olekukonko/tablewriter"
"github.com/olekukonko/tablewriter/tw"
"os"
)
func main() {
cfg := tablewriter.Config{
Header: tw.CellConfig{
Alignment: tw.CellAlignment{Global: tw.AlignLeft},
},
Row: tw.CellConfig{
Alignment: tw.CellAlignment{
Global: tw.AlignCenter,
PerColumn: []tw.Align{tw.AlignLeft, tw.AlignRight}, // Col 0 left, Col 1 right
},
},
Footer: tw.CellConfig{
Alignment: tw.CellAlignment{Global: tw.AlignRight},
},
}
table := tablewriter.NewTable(os.Stdout, tablewriter.WithConfig(cfg))
table.Header("Name", "Status")
table.Append("Node1", "Ready")
table.Footer("", "Active") // Ensure footer has content to show alignment
table.Render()
}
Output:
┌───────┬────────┐
│ NAME │ STATUS │
├───────┼────────┤
│ Node1 │ Ready │
├───────┼────────┤
│ │ Active │
└───────┴────────┘
Key Changes:
SetAlignment and SetHeaderAlignment are replaced by WithRowAlignment, WithHeaderAlignment, WithFooterAlignment, or direct Config settings (config.go). These old methods are retained in deprecated.go for compatibility but should be migrated.tw.CellConfig.Alignment (tw/cell.go:CellAlignment), which includes:
Global: A single tw.Align value applied to all cells in the section (tw.AlignLeft, tw.AlignCenter, tw.AlignRight, tw.AlignNone).PerColumn: A slice of tw.Align values for column-specific alignment, overriding Global for specified columns.WithFooterAlignment or Config.Footer.Alignment, addressing v0.0.5’s lack of footer-specific control (config.go).tw.Align string constants replace v0.0.5’s integer constants (e.g., ALIGN_CENTER), improving clarity and reducing errors (tw/types.go).ConfigBuilder provides Alignment() methods for each section. ForColumn(idx).WithAlignment() applies alignment to a specific column across all sections (config.go:ConfigBuilder, config.go:ColumnConfigBuilder).tw.CellConfig.ColumnAligns (slice) and tw.CellFormatting.Alignment (single value) are supported for backward compatibility but should be migrated to tw.CellAlignment.Global and tw.CellAlignment.PerColumn (tw/cell.go).Migration Tips:
SetAlignment(ALIGN_X) with WithRowAlignment(tw.AlignX) or Config.Row.Alignment.Global = tw.AlignX to set row alignment (config.go).WithHeaderAlignment(tw.AlignX) for headers and WithFooterAlignment(tw.AlignX) for footers to maintain or adjust v0.0.5 behavior (config.go).ConfigBuilder.Row().Alignment().WithPerColumn([]tw.Align{...}) or by setting Config.Row.Alignment.PerColumn for fine-grained control (config.go).ConfigBuilder.ForColumn(idx).WithAlignment(tw.AlignX) to apply consistent alignment to a specific column across all sections (Header, Row, Footer) (config.go).Header: tw.AlignCenter, Row: tw.AlignLeft, Footer: tw.AlignRight) to ensure expected output (config.go:defaultConfig).zoo.go:prepareContent).Potential Pitfalls:
PerColumn settings override Global within a section; ensure column-specific alignments are intentional (tw/cell.go:CellAlignment).ColumnAligns or tw.CellFormatting.Alignment is temporary; migrate to tw.CellAlignment to avoid future issues (tw/cell.go)."") to match column count (zoo.go).Config.Widths; narrow widths may truncate content, misaligning text (stream.go:streamCalculateWidths).Footer.Alignment.Global = tw.AlignRight differs from rows (tw.AlignLeft); explicitly set WithFooterAlignment if consistency is needed (config.go).Auto-formatting applies transformations like title case to cell content, primarily for headers to enhance readability, but it can be enabled for rows or footers in v1.0.x.
Old (v0.0.5):
package main
import (
"github.com/olekukonko/tablewriter"
"os"
)
func main() {
table := tablewriter.NewWriter(os.Stdout)
// table.SetAutoFormatHeaders(true) // Default: true; applies title case to headers
table.SetHeader([]string{"col_one", "status_report"})
table.Append([]string{"Node1", "Ready"})
table.Render()
}
New (v1.0.x):
package main
import (
"github.com/olekukonko/tablewriter"
"github.com/olekukonko/tablewriter/tw"
"os"
)
func main() {
// Using Direct Config Struct to turn OFF default AutoFormat for Header
cfg := tablewriter.Config{
Header: tw.CellConfig{Formatting: tw.CellFormatting{AutoFormat: tw.Off}}, // Turn OFF title case for headers
Row: tw.CellConfig{Formatting: tw.CellFormatting{AutoFormat: tw.Off}}, // No formatting for rows (default)
Footer: tw.CellConfig{Formatting: tw.CellFormatting{AutoFormat: tw.Off}}, // No formatting for footers (default)
}
tableNoAutoFormat := tablewriter.NewTable(os.Stdout, tablewriter.WithConfig(cfg))
tableNoAutoFormat.Header("col_one", "status_report") // Stays as "col_one", "status_report"
tableNoAutoFormat.Append("Node1", "Ready")
tableNoAutoFormat.Render()
// Using Option Function for Headers (default is ON, this makes it explicit)
tableWithAutoFormat := tablewriter.NewTable(os.Stdout,
tablewriter.WithHeaderAutoFormat(tw.On), // Explicitly enable title case (default behavior)
)
tableWithAutoFormat.Header("col_one", "status_report") // Becomes "COL ONE", "STATUS REPORT"
tableWithAutoFormat.Append("Node1", "Ready")
tableWithAutoFormat.Render()
}
Output:
┌─────────┬───────────────┐
│ col_one │ status_report │
├─────────┼───────────────┤
│ Node1 │ Ready │
└─────────┴───────────────┘
┌─────────┬───────────────┐
│ COL ONE │ STATUS REPORT │
├─────────┼───────────────┤
│ Node1 │ Ready │
└─────────┴───────────────┘
Key Changes:
SetAutoFormatHeaders is replaced by Config.Header.Formatting.AutoFormat, or equivalent builder methods (config.go).Config.Row.Formatting.AutoFormat and Config.Footer.Formatting.AutoFormat, unlike v0.0.5’s header-only support (tw/cell.go).tw.State (tw.On, tw.Off, tw.Unknown) controls formatting state, replacing boolean flags (tw/state.go).tw.On, the tw.Title function converts text to uppercase and replaces underscores and some periods with spaces (e.g., "col_one" → "COL ONE") (tw/fn.go:Title, zoo.go:prepareContent).Header.Formatting.AutoFormat = tw.On, Row.Formatting.AutoFormat = tw.Off, Footer.Formatting.AutoFormat = tw.Off (config.go:defaultConfig).Migration Tips:
WithHeaderAutoFormat(tw.On) is the default. If you were using SetAutoFormatHeaders(false), you'd use WithHeaderAutoFormat(tw.Off).WithRowAutoFormat(tw.Off) or WithFooterAutoFormat(tw.Off) if you want to ensure rows and footers remain unformatted, as v1.0.x allows enabling formatting for these sections (config.go).ConfigBuilder.Header().Formatting().WithAutoFormat(tw.State) for precise control over formatting per section (config.go).tw.CellFilter instead of AutoFormat (tw/cell.go).Potential Pitfalls:
Header.Formatting.AutoFormat = tw.On by default may unexpectedly alter header text (e.g., "col_one" → "COL ONE"); disable with WithHeaderAutoFormat(tw.Off) if raw headers are preferred (config.go).AutoFormat for rows or footers (not default) applies title case, which may not suit data content; verify with tw.Off unless intended (tw/cell.go).AutoFormat with tw.CellFilter can lead to overlapping transformations; prioritize filters for complex formatting (zoo.go:prepareContent).zoo.go).Text wrapping determines how long cell content is handled within width constraints, offering more options in v1.0.x compared to v0.0.5’s binary toggle.
Old (v0.0.5):
package main
import (
"github.com/olekukonko/tablewriter"
"os"
)
func main() {
table := tablewriter.NewWriter(os.Stdout)
// table.SetAutoWrapText(true) // Enable normal word wrapping
// table.SetAutoWrapText(false) // Disable wrapping
table.SetHeader([]string{"Long Header Text Example", "Status"})
table.Append([]string{"This is some very long cell content that might wrap", "Ready"})
table.Render()
}
New (v1.0.x):
package main
import (
"github.com/olekukonko/tablewriter"
"github.com/olekukonko/tablewriter/tw" // For tw.WrapNormal etc.
"os"
)
func main() {
// Using Option Functions for Quick Wrapping Settings on a specific section (e.g., Row)
// To actually see wrapping, a MaxWidth for the table or columns is needed.
table := tablewriter.NewTable(os.Stdout,
tablewriter.WithRowAutoWrap(tw.WrapNormal), // Set row wrapping
tablewriter.WithHeaderAutoWrap(tw.WrapTruncate), // Header truncates (default)
tablewriter.WithMaxWidth(30), // Force wrapping by setting table max width
)
table.Header("Long Header Text", "Status")
table.Append("This is a very long cell content", "Ready")
table.Footer("Summary", "Active")
table.Render()
// For more fine-grained control (e.g., different wrapping for header, row, footer):
cfgBuilder := tablewriter.NewConfigBuilder()
cfgBuilder.Header().Formatting().WithAutoWrap(tw.WrapTruncate) // Header: Truncate
cfgBuilder.Row().Formatting().WithAutoWrap(tw.WrapNormal) // Row: Normal wrap
cfgBuilder.Footer().Formatting().WithAutoWrap(tw.WrapBreak) // Footer: Break words
cfgBuilder.WithMaxWidth(40) // Overall table width constraint
tableFullCfg := tablewriter.NewTable(os.Stdout, tablewriter.WithConfig(cfgBuilder.Build()))
tableFullCfg.Header("Another Very Long Header Example That Will Be Truncated", "Info")
tableFullCfg.Append("This is an example of row content that should wrap normally based on available space.", "Detail")
tableFullCfg.Footer("FinalSummaryInformationThatMightBreakAcrossLines", "End")
tableFullCfg.Render()
}
Output (tableOpt):
┌──────────────┬────────┐
│ LONG HEADER… │ STATUS │
├──────────────┼────────┤
│ This is a │ Ready │
│ very long │ │
│ cell content │ │
├──────────────┼────────┤
│ Summary │ Active │
└──────────────┴────────┘
(Second table output will vary based on content and width)
Key Changes:
SetAutoWrapText is replaced by Config.<Section>.Formatting.AutoWrap or specific With<Section>AutoWrap options (config.go).int constants for AutoWrap (e.g., tw.WrapNone, tw.WrapNormal, tw.WrapTruncate, tw.WrapBreak) replace v0.0.5’s binary toggle (tw/tw.go).tw/cell.go).Header: tw.WrapTruncate, Row: tw.WrapNormal, Footer: tw.WrapNormal (config.go:defaultConfig).Config.Widths (fixed column widths), Config.<Section>.ColMaxWidths (max content width), or Config.MaxWidth (total table width) (zoo.go:calculateContentMaxWidth, zoo.go:prepareContent).Migration Tips:
SetAutoWrapText(true) with WithRowAutoWrap(tw.WrapNormal) to maintain v0.0.5’s default wrapping for rows (config.go).WithHeaderAutoWrap(tw.WrapTruncate) to align with v1.0.x’s default header behavior, ensuring long headers are truncated (config.go).Config.Widths or Config.MaxWidth explicitly to enforce wrapping, as unconstrained widths may prevent wrapping (config.go).ConfigBuilder.<Section>().Formatting().WithAutoWrap(int_wrap_mode) for precise control over wrapping per section (config.go).tw.WrapNormal for data rows to preserve content integrity, reserving tw.WrapTruncate for headers or footers (tw/tw.go).Potential Pitfalls:
Config.Widths, ColMaxWidths, or MaxWidth, wrapping may not occur, leading to overflow; always define width limits for wrapping (zoo.go).Start(); narrow widths may truncate content excessively (stream.go:streamCalculateWidths).tw.WrapTruncate may obscure critical data in rows; use tw.WrapNormal or wider columns to retain content (tw/tw.go).tw.WrapNormal or tw.WrapBreak can add processing time; optimize widths for performance-critical applications (zoo.go:prepareContent).Header: tw.WrapTruncate, Row/Footer: tw.WrapNormal); align settings if uniform behavior is needed (config.go).Padding adds spacing within cells, enhancing readability and affecting cell width calculations. v1.0.x introduces granular, per-side padding, replacing v0.0.5’s single inter-column padding control.
Old (v0.0.5):
package main
import (
"github.com/olekukonko/tablewriter"
"os"
)
func main() {
table := tablewriter.NewWriter(os.Stdout)
// table.SetTablePadding("\t") // Set inter-column space character when borders are off
// Default: single space within cells
table.SetHeader([]string{"Header1"})
table.Append([]string{"Cell1"})
table.Render()
}
New (v1.0.x):
package main
import (
"github.com/olekukonko/tablewriter"
"github.com/olekukonko/tablewriter/tw"
"os"
)
func main() {
// Using Direct Config Struct
cfg := tablewriter.Config{
Header: tw.CellConfig{
Padding: tw.CellPadding{
Global: tw.Padding{Left: "[", Right: "]", Top: "-", Bottom: "-"}, // Padding for all header cells
PerColumn: []tw.Padding{ // Specific padding for header column 0
{Left: ">>", Right: "<<", Top: "=", Bottom: "="}, // Overrides Global for column 0
},
},
},
Row: tw.CellConfig{
Padding: tw.CellPadding{
Global: tw.PaddingDefault, // One space left/right for all row cells
},
},
Footer: tw.CellConfig{
Padding: tw.CellPadding{
Global: tw.Padding{Top: "~", Bottom: "~"}, // Top/bottom padding for all footer cells
},
},
}
table := tablewriter.NewTable(os.Stdout, tablewriter.WithConfig(cfg))
table.Header("Name", "Status") // Two columns
table.Append("Node1", "Ready")
table.Footer("End", "Active")
table.Render()
}
Output:
┌────────┬────────┐
│========│[------]│
│>>NAME<<│[STATUS]│
│========│[------]│
├────────┼────────┤
│ Node1 │ Ready │
│~~~~~~~~│~~~~~~~~│
│End │Active │
│~~~~~~~~│~~~~~~~~│
└────────┴────────┘
Key Changes:
SetTablePadding: SetTablePadding controlled inter-column spacing when borders were off in v0.0.5; v1.0.x has no direct equivalent for inter-column spacing separate from cell padding. Inter-column visual separation is now primarily handled by tw.Rendition.Settings.Separators.BetweenColumns and the chosen tw.Symbols.tw.CellPadding (tw/cell.go:CellPadding) supports:
Global: A tw.Padding struct with Left, Right, Top, Bottom strings and an Overwrite flag (tw/types.go:Padding). This padding is inside the cell.PerColumn: A slice of tw.Padding for column-specific padding, overriding Global for specified columns.Top and Bottom padding add extra lines within cells, unlike v0.0.5’s left/right-only spacing (zoo.go:prepareContent).tw.PaddingDefault is {Left: " ", Right: " "} for all sections (applied inside cells); Top and Bottom are empty by default (tw/preset.go).Config.Widths (zoo.go:calculateAndNormalizeWidths).tw.PaddingNone ({Left: "", Right: "", Top: "", Bottom: "", Overwrite: true}) removes padding for tight layouts (tw/preset.go).Migration Tips:
SetTablePadding("\t") when borders are off, you would set cell padding: WithPadding(tw.Padding{Left: "\t", Right: "\t"}). If you truly mean space between columns and not inside cells, ensure tw.Rendition.Settings.Separators.BetweenColumns is tw.On and customize tw.Symbols.Column() if needed.tw.PaddingNone (e.g., via ConfigBuilder.<Section>().Padding().WithGlobal(tw.PaddingNone)) for no cell padding.Top and Bottom padding for vertical spacing within cells, enhancing readability for multi-line content (tw/types.go).ConfigBuilder.<Section>().Padding().WithPerColumn for column-specific padding to differentiate sections or columns (config.go).zoo.go).Config.Widths or ColMaxWidths to control total cell size (config.go).Potential Pitfalls:
SetTablePadding was ambiguous; v1.0.x distinguishes these.Config.MaxWidth or causing truncation in streaming; adjust Config.Widths accordingly (zoo.go).Top or Bottom padding adds vertical lines within cells, increasing cell height; use sparingly for dense tables (zoo.go:prepareContent).Start(); ensure Config.Widths accommodates padding (stream.go).tw.PaddingDefault adds spaces inside cells; set tw.PaddingNone for no internal cell padding (tw/preset.go).Column width control in v1.0.x is more sophisticated, offering fixed widths, maximum content widths, and overall table width constraints, replacing v0.0.5’s limited SetColMinWidth.
Old (v0.0.5):
package main
import (
"github.com/olekukonko/tablewriter"
"os"
)
func main() {
table := tablewriter.NewWriter(os.Stdout)
// table.SetColMinWidth(0, 10) // Set minimum width for column 0
table.SetHeader([]string{"Header1", "Header2"})
table.Append([]string{"Cell1-Content", "Cell2-Content"})
table.Render()
}
New (v1.0.x):
package main
import (
"github.com/olekukonko/tablewriter"
"github.com/olekukonko/tablewriter/tw"
"os"
)
func main() {
// Direct Config for Width Control
cfg := tablewriter.Config{
Widths: tw.CellWidth{ // Fixed total column widths (content + padding)
Global: 20, // Default fixed width for all columns
PerColumn: tw.NewMapper[int, int]().Set(0, 15), // Column 0 fixed at 15
},
Header: tw.CellConfig{
ColMaxWidths: tw.CellWidth{ // Max content width (excluding padding) for header cells
Global: 15, // Max content width for all header cells
PerColumn: tw.NewMapper[int, int]().Set(0, 10), // Header Col 0 max content at 10
},
// Default header padding is " " on left/right, so content 10 + padding 2 = 12.
// If Widths.PerColumn[0] is 15, there's space.
},
Row: tw.CellConfig{
ColMaxWidths: tw.CellWidth{Global: 18}, // Max content width for row cells (18 + default padding 2 = 20)
},
MaxWidth: 80, // Constrain total table width (optional, columns might shrink)
}
tableWithCfg := tablewriter.NewTable(os.Stdout, tablewriter.WithConfig(cfg))
tableWithCfg.Header("Very Long Header Name", "Status Information")
tableWithCfg.Append("Some long content for the first column", "Ready")
tableWithCfg.Render()
// Option Functions for Width Settings
tableWithOpts := tablewriter.NewTable(os.Stdout,
tablewriter.WithColumnMax(20), // Sets Config.Widths.Global (fixed total col width)
tablewriter.WithColumnWidths(tw.NewMapper[int, int]().Set(0, 15)), // Sets Config.Widths.PerColumn for col 0
tablewriter.WithMaxWidth(80), // Sets Config.MaxWidth
// For max content width per section, you'd use WithHeaderConfig, WithRowConfig, etc.
// e.g., tablewriter.WithRowMaxWidth(18) // Sets Config.Row.ColMaxWidths.Global
)
tableWithOpts.Header("Long Header", "Status")
tableWithOpts.Append("Long Content", "Ready")
tableWithOpts.Render()
}
Output (tableWithCfg - illustrative, exact wrapping depends on content and full config):
┌───────────┬──────────────────┐
│ VERY LONG │ STATUS INFORMAT… │
│ HEADER NA…│ │
├───────────┼──────────────────┤
│ Some long │ Ready │
│ content f…│ │
└───────────┴──────────────────┘
Key Changes:
SetColMinWidth (config.go):
tw.CellWidth).
Global: Default fixed width for all columns.PerColumn: tw.Mapper[int, int] for specific column widths.tw.CellWidth).
Global: Max content width for all cells in the section.PerColumn: tw.Mapper[int, int] for specific columns in the section.config.go).Config.Widths fixes column widths at Start(); ColMaxWidths is used only for wrapping/truncation (stream.go:streamCalculateWidths).calculateAndNormalizeWidths in batch mode and streamCalculateWidths in streaming mode, considering content, padding, and constraints (zoo.go, stream.go).SetColMinWidth is replaced by Config.Widths.PerColumn or Config.<Section>.ColMaxWidths.PerColumn for more precise control (deprecated.go).Migration Tips:
SetColMinWidth(col, w) with WithColumnWidths(tw.NewMapper[int, int]().Set(col, w)) for fixed column widths or Config.<Section>.ColMaxWidths.PerColumn for content width limits (config.go).Config.Widths.Global or WithColumnMax(w) to set a default fixed width for all columns, ensuring consistency (tablewriter.go).Config.MaxWidth to constrain total table width, especially for wide datasets (config.go).ConfigBuilder.ForColumn(idx).WithMaxWidth(w) to set per-column content width limits across sections (config.go). (Note: This sets it for Header, Row, and Footer)Config.Widths before Start() to fix widths, avoiding content-based sizing (stream.go).zoo.go).Potential Pitfalls:
Config.Widths.PerColumn overrides Widths.Global; ColMaxWidths applies within those fixed total widths for content wrapping/truncation (zoo.go).Start() in streaming; inconsistent data may cause truncation (stream.go).Config.Widths; account for tw.CellPadding when setting fixed column widths (zoo.go).Config.MaxWidth may shrink columns unevenly; test with MaxWidth to avoid cramped layouts (zoo.go).Widths or MaxWidth, columns size to content, potentially causing overflow; define limits (zoo.go).Colors in v0.0.5 were applied via specific color-setting methods, while v1.0.x embeds ANSI escape codes in cell content or uses data-driven formatting for greater flexibility.
Old (v0.0.5):
package main
// Assuming tablewriter.Colors and color constants existed in v0.0.5
// This is a mock representation as the actual v0.0.5 definitions are not provided.
// import "github.com/olekukonko/tablewriter"
// import "os"
// type Colors []interface{} // Mock
// const (
// Bold = 1; FgGreenColor = 2; FgRedColor = 3 // Mock constants
// )
func main() {
// table := tablewriter.NewWriter(os.Stdout)
// table.SetColumnColor(
// tablewriter.Colors{tablewriter.Bold, tablewriter.FgGreenColor}, // Column 0
// tablewriter.Colors{tablewriter.FgRedColor}, // Column 1
// )
// table.SetHeader([]string{"Name", "Status"})
// table.Append([]string{"Node1", "Ready"})
// table.Render()
}
New (v1.0.x):
package main
import (
"fmt"
"github.com/olekukonko/tablewriter"
"github.com/olekukonko/tablewriter/tw" // For tw.Formatter
"os"
)
// Direct ANSI Code Embedding
const (
Reset = "\033[0m"
Bold = "\033[1m"
FgGreen = "\033[32m"
FgRed = "\033[31m"
)
// Using tw.Formatter for Custom Types
type Status string
func (s Status) Format() string { // Implements tw.Formatter
color := FgGreen
if s == "Error" || s == "Inactive" {
color = FgRed
}
return color + string(s) + Reset
}
func main() {
// Example 1: Direct ANSI embedding
tableDirectANSI := tablewriter.NewTable(os.Stdout,
tablewriter.WithHeaderAutoFormat(tw.Off), // Keep header text as is for coloring
)
tableDirectANSI.Header(Bold+FgGreen+"Name"+Reset, Bold+FgRed+"Status"+Reset)
tableDirectANSI.Append([]any{"Node1", FgGreen + "Ready" + Reset})
tableDirectANSI.Append([]any{"Node2", FgRed + "Error" + Reset})
tableDirectANSI.Render()
fmt.Println("\n--- Table with Formatter for Colors ---")
// Example 2: Using tw.Formatter
tableFormatter := tablewriter.NewTable(os.Stdout)
tableFormatter.Header("Name", "Status") // AutoFormat will apply to "NAME", "STATUS"
tableFormatter.Append([]any{"Alice", Status("Active")})
tableFormatter.Append([]any{"Bob", Status("Inactive")})
tableFormatter.Render()
fmt.Println("\n--- Table with CellFilter for Colors ---")
// Example 3: Using CellFilter
tableWithFilters := tablewriter.NewTable(os.Stdout,
tablewriter.WithConfig(
tablewriter.NewConfigBuilder().
Row().
Filter().
WithPerColumn([]func(string) string{
nil, // No filter for Item column
func(s string) string { // Status column: apply color
if s == "Ready" || s == "Active" {
return FgGreen + s + Reset
}
return FgRed + s + Reset
},
}).
Build(). // Return to RowConfigBuilder
Build(). // Return to ConfigBuilder
Build(), // Finalize Config
),
)
tableWithFilters.Header("Item", "Availability")
tableWithFilters.Append("ItemA", "Ready")
tableWithFilters.Append("ItemB", "Unavailable")
tableWithFilters.Render()
}
Output (Text Approximation, Colors Not Shown):
┌──────┬────────┐
│ Name │ Status │
├──────┼────────┤
│Node1 │ Ready │
│Node2 │ Error │
└──────┴────────┘
--- Table with Formatter for Colors ---
┌───────┬──────────┐
│ NAME │ STATUS │
├───────┼──────────┤
│ Alice │ Active │
│ Bob │ Inactive │
└───────┴──────────┘
--- Table with CellFilter for Colors ---
┌───────┬────────────┐
│ ITEM │ AVAILABILI │
│ │ TY │
├───────┼────────────┤
│ ItemA │ Ready │
│ ItemB │ Unavailabl │
│ │ e │
└───────┴────────────┘
Key Changes:
SetColumnColor, SetHeaderColor, and SetFooterColor are removed; colors are now applied by embedding ANSI escape codes in cell content or via data-driven mechanisms (tablewriter.go).\033[32m for green) in strings for manual control (zoo.go:convertCellsToStrings).Format() string on custom types to control cell output, including colors (tw/types.go:Formatter).Config.<Section>.Filter.Global or PerColumn to apply transformations like coloring dynamically (tw/cell.go:CellFilter).twdw.Width() correctly calculates display width of ANSI-coded strings, ignoring escape sequences (tw/fn.go:DisplayWidth).tablewriter.Colors, v1.0.x requires manual ANSI code management or external libraries for color constants.Migration Tips:
SetColumnColor with direct ANSI code embedding for simple cases (e.g., FgGreen+"text"+Reset) (zoo.go).tw.Formatter on custom types for reusable, semantic color logic (e.g., Status type above) (tw/types.go).ConfigBuilder.<Section>().Filter().WithPerColumn to apply color filters to specific columns, mimicking v0.0.5’s per-column coloring (config.go).github.com/fatih/color) to simplify color management.AutoFormat or wrapping for consistent styling (zoo.go:prepareContent).Potential Pitfalls:
tw.CellFilter with AutoFormat or other transformations can lead to unexpected results; prioritize filters for coloring (zoo.go).Reset) can skew width calculations; use twdw.Width (tw/fn.go).stream.go).zoo.go).v1.0.x introduces several advanced features that enhance table functionality beyond v0.0.5’s capabilities. This section covers cell merging, captions, filters, stringers, and performance optimizations, providing migration guidance and examples for leveraging these features.
Cell merging combines adjacent cells with identical content, improving readability for grouped data. v1.0.x expands merging options beyond v0.0.5’s horizontal merging.
Old (v0.0.5):
package main
import (
"github.com/olekukonko/tablewriter"
"os"
)
func main() {
table := tablewriter.NewWriter(os.Stdout)
// table.SetAutoMergeCells(true) // Enable horizontal merging
table.SetHeader([]string{"Category", "Item", "Notes"})
table.Append([]string{"Fruit", "Apple", "Red"})
table.Append([]string{"Fruit", "Apple", "Green"}) // "Apple" might merge if SetAutoMergeCells was on
table.Render()
}
New (v1.0.x):
package main
import (
"github.com/olekukonko/tablewriter"
"github.com/olekukonko/tablewriter/renderer"
"github.com/olekukonko/tablewriter/tw"
"os"
)
func main() {
// Horizontal Merging (Similar to v0.0.5)
tableH := tablewriter.NewTable(os.Stdout,
tablewriter.WithConfig(tablewriter.Config{Row: tw.CellConfig{Merging: tw.CellMerging{Mode: tw.MergeHorizontal}}}),
tablewriter.WithRenderer(renderer.NewBlueprint(tw.Rendition{Symbols: tw.NewSymbols(tw.StyleASCII)})), // Specify renderer for symbols
)
tableH.Header("Category", "Item", "Item", "Notes") // Note: Two "Item" headers for demo
tableH.Append("Fruit", "Apple", "Apple", "Red") // "Apple" cells merge
tableH.Render()
// Vertical Merging
tableV := tablewriter.NewTable(os.Stdout,
tablewriter.WithConfig(tablewriter.Config{Row: tw.CellConfig{Merging: tw.CellMerging{Mode: tw.MergeVertical}}}),
tablewriter.WithRenderer(renderer.NewBlueprint(tw.Rendition{Symbols: tw.NewSymbols(tw.StyleASCII)})),
)
tableV.Header("User", "Permission")
tableV.Append("Alice", "Read")
tableV.Append("Alice", "Write") // "Alice" cells merge vertically
tableV.Append("Bob", "Read")
tableV.Render()
// Hierarchical Merging
tableHier := tablewriter.NewTable(os.Stdout,
tablewriter.WithConfig(tablewriter.Config{Row: tw.CellConfig{Merging: tw.CellMerging{Mode: tw.MergeHierarchical}}}),
tablewriter.WithRenderer(renderer.NewBlueprint(tw.Rendition{Symbols: tw.NewSymbols(tw.StyleASCII)})),
)
tableHier.Header("Group", "SubGroup", "Item")
tableHier.Append("Tech", "CPU", "i7")
tableHier.Append("Tech", "CPU", "i9") // "Tech" and "CPU" merge
tableHier.Append("Tech", "RAM", "16GB") // "Tech" merges, "RAM" is new
tableHier.Append("Office", "CPU", "i5") // "Office" is new
tableHier.Render()
}
Output (Horizontal):
+----------+-------+-------+-------+
| CATEGORY | ITEM | ITEM | NOTES |
+----------+-------+-------+-------+
| Fruit | Apple | Red |
+----------+---------------+-------+
Output (Vertical):
+-------+------------+
| USER | PERMISSION |
+-------+------------+
| Alice | Read |
| | Write |
+-------+------------+
| Bob | Read |
+-------+------------+
Output (Hierarchical):
+---------+----------+------+
| GROUP | SUBGROUP | ITEM |
+---------+----------+------+
| Tech | CPU | i7 |
| | | i9 |
| +----------+------+
| | RAM | 16GB |
+---------+----------+------+
| Office | CPU | i5 |
+---------+----------+------+
Key Changes:
SetAutoMergeCells is replaced by WithRowMergeMode(int_merge_mode) or Config.Row.Formatting.MergeMode (config.go). Uses tw.Merge... constants.tw.MergeMode constants (tw.MergeNone, tw.MergeHorizontal, tw.MergeVertical, tw.MergeHierarchical) define behavior (tw/tw.go).Header, Row, or Footer via Config.<Section>.Formatting.MergeMode (tw/cell.go).zoo.go:prepareWithMerges, zoo.go:applyVerticalMerges, zoo.go:applyHierarchicalMerges).zoo.go:applyHorizontalMergeWidths).tw.MergeState in tw.CellContext ensures correct border drawing for merged cells (tw/cell.go:CellContext).stream.go:streamAppendRow).Migration Tips:
SetAutoMergeCells(true) with WithRowMergeMode(tw.MergeHorizontal) to maintain v0.0.5’s horizontal merging behavior (config.go).tw.MergeVertical for vertical grouping (e.g., repeated user names) or tw.MergeHierarchical for nested data structures (tw/tw.go).ConfigBuilder.<Section>().Formatting().WithMergeMode(int_merge_mode) (config.go).Config.Widths supports merged cell widths to avoid truncation (stream.go).WithDebug(true) to log merge processing for troubleshooting (config.go).Potential Pitfalls:
stream.go).Config.Widths or ColMaxWidths (zoo.go).zoo.go).tw.MergeState handling in custom renderers can break merged cell borders; test thoroughly (tw/cell.go).zoo.go).Captions add descriptive text above or below the table, a new feature in v1.0.x not present in v0.0.5.
Old (v0.0.5):
package main
// No direct caption support in v0.0.5. Users might have printed text manually.
// import "github.com/olekukonko/tablewriter"
// import "os"
// import "fmt"
func main() {
// fmt.Println("Movie ratings.") // Manual caption
// table := tablewriter.NewWriter(os.Stdout)
// table.SetHeader([]string{"Name", "Sign", "Rating"})
// table.Render()
}
New (v1.0.x):
package main
import (
"github.com/olekukonko/tablewriter"
"github.com/olekukonko/tablewriter/tw"
"os"
)
func main() {
table := tablewriter.NewTable(os.Stdout)
table.Caption(tw.Caption{ // tw/types.go:Caption
Text: "System Status Overview - A Very Long Caption Example To Demonstrate Wrapping Behavior",
Spot: tw.SpotTopCenter, // Placement: TopLeft, TopCenter, TopRight, BottomLeft, BottomCenter, BottomRight
Align: tw.AlignCenter, // Text alignment within caption width
Width: 30, // Fixed width for caption text wrapping; if 0, wraps to table width
})
table.Header("Name", "Status")
table.Append("Node1", "Ready")
table.Append("SuperLongNodeNameHere", "ActiveNow")
table.Render()
}
Output:
System Status Overview - A Very
Long Caption Example To Demonst…
┌─────────────────────┬──────────┐
│ NAME │ STATUS │
├─────────────────────┼──────────┤
│ Node1 │ Ready │
│ SuperLongNodeNameHe │ ActiveNo │
│ re │ w │
└─────────────────────┴──────────┘
Key Changes:
Table.Caption(tw.Caption) introduces captions, absent in v0.0.5 (tablewriter.go:Caption).tw.Caption (tw/types.go:Caption) includes:
Text: Caption content.Spot: Placement (tw.SpotTopLeft, tw.SpotBottomCenter, etc.); defaults to tw.SpotBottomCenter if tw.SpotNone.Align: Text alignment (tw.AlignLeft, tw.AlignCenter, tw.AlignRight).Width: Optional fixed width for wrapping; defaults to table width.printTopBottomCaption before or after the table based on Spot (tablewriter.go:printTopBottomCaption).Close() in streaming mode if placed at the bottom (stream.go).Migration Tips:
tw/types.go).tw.SpotTopCenter for prominent placement above the table, aligning with common report formats.Align to match table aesthetics (e.g., tw.AlignCenter for balanced appearance).Width for consistent wrapping, especially with long captions or narrow tables (tablewriter.go).Potential Pitfalls:
Spot is tw.SpotNone, caption defaults to tw.SpotBottomCenter, which may surprise users expecting no caption (tablewriter.go).Width, captions wrap to table width, potentially causing misalignment; set explicitly for control (tw/types.go).Close(); ensure Close() is called (stream.go).Align is independent of table cell alignment; verify separately (tw/cell.go).Filters allow dynamic transformation of cell content during rendering, a new feature in v1.0.x for tasks like formatting, coloring, or sanitizing data.
Old (v0.0.5):
package main
// No direct support for cell content transformation in v0.0.5.
// Users would typically preprocess data before appending.
// import "github.com/olekukonko/tablewriter"
// import "os"
// import "strings"
func main() {
// table := tablewriter.NewWriter(os.Stdout)
// table.SetHeader([]string{"Name", "Status"})
// status := " Ready "
// preprocessedStatus := "Status: " + strings.TrimSpace(status)
// table.Append([]string{"Node1", preprocessedStatus})
// table.Render()
}
New (v1.0.x):
package main
import (
"github.com/olekukonko/tablewriter"
"github.com/olekukonko/tablewriter/tw" // For tw.CellConfig etc.
"os"
"strings"
)
func main() {
// Per-Column Filter for Specific Transformations
cfgBuilder := tablewriter.NewConfigBuilder()
cfgBuilder.Row().Filter().WithPerColumn([]func(string) string{
nil, // No filter for Name column
func(s string) string { // Status column: prefix and trim
return "Status: " + strings.TrimSpace(s)
},
})
tableWithFilter := tablewriter.NewTable(os.Stdout, tablewriter.WithConfig(cfgBuilder.Build()))
tableWithFilter.Header("Name", "Status")
tableWithFilter.Append("Node1", " Ready ") // Note the extra spaces
tableWithFilter.Append("Node2", "Pending")
tableWithFilter.Render()
// Global filter example (applied to all cells in the Row section)
cfgGlobalFilter := tablewriter.NewConfigBuilder()
cfgGlobalFilter.Row().Filter().WithGlobal(func(s string) string {
return "[" + s + "]" // Wrap all row cells in brackets
})
tableGlobalFilter := tablewriter.NewTable(os.Stdout, tablewriter.WithConfig(cfgGlobalFilter.Build()))
tableGlobalFilter.Header("Item", "Count")
tableGlobalFilter.Append("Apple", "5")
tableGlobalFilter.Render()
}
Output (Per-Column Filter):
┌───────┬─────────────────┐
│ NAME │ STATUS │
├───────┼─────────────────┤
│ Node1 │ Status: Ready │
│ Node2 │ Status: Pending │
└───────┴─────────────────┘
Output (Global Filter):
┌───────┬─────────┐
│ ITEM │ COUNT │
├───────┼─────────┤
│[Apple]│ [5] │
└───────┴─────────┘
Key Changes:
tw.CellFilter (tw/cell.go:CellFilter) introduces:
Global: A func(s []string) []string applied to entire rows (all cells in that row) of a section.PerColumn: A slice of func(string) string for column-specific transformations on individual cells.Config.<Section>.Filter (Header, Row, Footer) using ConfigBuilder or direct Config (config.go).AutoFormat but before rendering (zoo.go:convertCellsToStrings calls prepareContent which applies some transformations, filters are applied in convertCellsToStrings itself).Migration Tips:
Append).Global filters for uniform transformations across all cells of rows in a section (e.g., uppercasing all row data) (tw/cell.go).PerColumn filters for column-specific formatting (e.g., adding prefixes to status columns) (config.go).tw.Formatter for complex types or ANSI coloring for visual enhancements (tw/cell.go).zoo.go).Potential Pitfalls:
zoo.go).nil) are ignored, but incorrect indexing in PerColumn can skip columns (tw/cell.go).Start() (stream.go).Stringers allow custom string conversion for data types, with v1.0.x adding caching for performance.
Old (v0.0.5):
package main
// v0.0.5 primarily relied on fmt.Stringer for custom types.
// import "fmt"
// import "github.com/olekukonko/tablewriter"
// import "os"
// type MyCustomType struct {
// Value string
// }
// func (m MyCustomType) String() string { return "Formatted: " + m.Value }
func main() {
// table := tablewriter.NewWriter(os.Stdout)
// table.SetHeader([]string{"Custom Data"})
// table.Append([]string{MyCustomType{"test"}.String()}) // Manual call to String()
// table.Render()
}
New (v1.0.x):
package main
import (
"fmt"
"github.com/olekukonko/tablewriter"
"github.com/olekukonko/tablewriter/tw" // For tw.Formatter
"os"
"strings" // For Person example
)
// Example 1: Using WithStringer for general conversion
type CustomInt int
func main() {
// Table with a general stringer (func(any) []string)
tableWithStringer := tablewriter.NewTable(os.Stdout,
tablewriter.WithStringer(func(v any) []string { // Must return []string
if ci, ok := v.(CustomInt); ok {
return []string{fmt.Sprintf("CustomInt Value: %d!", ci)}
}
return []string{fmt.Sprintf("Value: %v", v)} // Fallback
}),
tablewriter.WithDebug(true), // Enable caching if WithStringerCache() is also used
// tablewriter.WithStringerCache(), // Optional: enable caching
)
tableWithStringer.Header("Data")
tableWithStringer.Append(123)
tableWithStringer.Append(CustomInt(456))
tableWithStringer.Render()
fmt.Println("\n--- Table with Type-Specific Stringer for Structs ---")
// Example 2: Stringer for a specific struct type
type Person struct {
ID int
Name string
City string
}
// Stringer for Person to produce 3 cells
personToStrings := func(p Person) []string {
return []string{
fmt.Sprintf("ID: %d", p.ID),
p.Name,
strings.ToUpper(p.City),
}
}
tablePersonStringer := tablewriter.NewTable(os.Stdout,
tablewriter.WithStringer(personToStrings), // Pass the type-specific function
)
tablePersonStringer.Header("User ID", "Full Name", "Location")
tablePersonStringer.Append(Person{1, "Alice", "New York"})
tablePersonStringer.Append(Person{2, "Bob", "London"})
tablePersonStringer.Render()
fmt.Println("\n--- Table with tw.Formatter ---")
// Example 3: Using tw.Formatter for types
type Product struct {
Name string
Price float64
}
func (p Product) Format() string { // Implements tw.Formatter
return fmt.Sprintf("%s - $%.2f", p.Name, p.Price)
}
tableFormatter := tablewriter.NewTable(os.Stdout)
tableFormatter.Header("Product Details")
tableFormatter.Append(Product{"Laptop", 1200.99}) // Will use Format()
tableFormatter.Render()
}
Output (Stringer Examples):
┌─────────────────────┐
│ DATA │
├─────────────────────┤
│ Value: 123 │
│ CustomInt Value: 456! │
└─────────────────────┘
--- Table with Type-Specific Stringer for Structs ---
┌─────────┬───────────┬──────────┐
│ USER ID │ FULL NAME │ LOCATION │
├─────────┼───────────┼──────────┤
│ ID: 1 │ Alice │ NEW YORK │
│ ID: 2 │ Bob │ LONDON │
└─────────┴───────────┴──────────┘
--- Table with tw.Formatter ---
┌─────────────────┐
│ PRODUCT DETAILS │
├─────────────────┤
│ Laptop - $1200… │
└─────────────────┘
Key Changes:
WithStringer(fn any) sets a table-wide string conversion function. This function must have a signature like func(SomeType) []string or func(any) []string. It's used to convert an input item (e.g., a struct) into a slice of strings, where each string is a cell for the row (tablewriter.go:WithStringer).WithStringerCache() enables caching for the function provided via WithStringer, improving performance for repeated conversions of the same input type (tablewriter.go:WithStringerCache).tw.Formatter interface (Format() string) allows types to define their own single-string representation for a cell. This is checked before fmt.Stringer (tw/types.go:Formatter).WithStringer (if provided and compatible with the item's type).WithStringer, or if WithStringer is not set:
tw.Formatter or fmt.Stringer, its exported fields are reflected into multiple cells.tw.Formatter (Format() string), that's used for a single cell.fmt.Stringer (String() string), that's used for a single cell.fmt.Sprintf("%v", ...) for a single cell.
(zoo.go:convertCellsToStrings, zoo.go:convertItemToCells)Migration Tips:
tw.Formatter.WithStringer with a function like func(MyType) []string.WithStringer(func(any) []string).WithStringerCache for large datasets with repetitive data types if using WithStringer.zoo.go).Potential Pitfalls:
WithStringerCache may increase memory usage for diverse data; disable for small tables or if not using WithStringer (tablewriter.go).WithStringer must return []string. A function returning string will lead to a warning and fallback behavior.WithStringer.stream.go).This section provides practical examples to demonstrate v1.0.x features, covering common and advanced use cases to aid migration. Each example includes code, output, and notes to illustrate functionality.
A basic table with default settings, ideal for quick setups.
package main
import (
"github.com/olekukonko/tablewriter"
"os"
)
func main() {
table := tablewriter.NewTable(os.Stdout)
table.Header("Name", "Status")
table.Append("Node1", "Ready")
table.Render()
}
Output:
┌───────┬────────┐
│ NAME │ STATUS │
├───────┼────────┤
│ Node1 │ Ready │
└───────┴────────┘
Notes:
renderer.NewBlueprint() and defaultConfig() settings (tablewriter.go, config.go).Header: tw.AlignCenter, Row: tw.AlignLeft (config.go:defaultConfig).NewWriter and SetHeader/Append.Demonstrates streaming mode for real-time data output.
package main
import (
"fmt"
"github.com/olekukonko/tablewriter"
"github.com/olekukonko/tablewriter/tw"
"log"
"os"
)
func main() {
table := tablewriter.NewTable(os.Stdout,
tablewriter.WithStreaming(tw.StreamConfig{Enable: true}),
tablewriter.WithColumnMax(10), // Sets Config.Widths.Global
)
if err := table.Start(); err != nil {
log.Fatalf("Start failed: %v", err)
}
table.Header("Name", "Status")
for i := 0; i < 2; i++ {
err := table.Append(fmt.Sprintf("Node%d", i+1), "Ready")
if err != nil {
log.Printf("Append failed: %v", err)
}
}
if err := table.Close(); err != nil {
log.Fatalf("Close failed: %v", err)
}
}
Output:
┌──────────┬──────────┐
│ NAME │ STATUS │
├──────────┼──────────┤
│ Node1 │ Ready │
│ Node2 │ Ready │
└──────────┴──────────┘
Notes:
Start() and Close(); Config.Widths (here set via WithColumnMax) fixes widths (stream.go).Start(), Append(), and Close().Creates a Markdown-compatible table for documentation.
package main
import (
"fmt"
"github.com/olekukonko/tablewriter"
"github.com/olekukonko/tablewriter/renderer" // Import renderer
"github.com/olekukonko/tablewriter/tw"
"os"
)
func main() {
// Example 1: Using Blueprint renderer with Markdown symbols
tableBlueprintMarkdown := tablewriter.NewTable(os.Stdout,
tablewriter.WithRenderer(renderer.NewBlueprint()), // Use Blueprint
tablewriter.WithRendition(tw.Rendition{
Symbols: tw.NewSymbols(tw.StyleMarkdown),
Borders: tw.Border{Left: tw.On, Right: tw.On}, // Markdown needs left/right borders
}),
tablewriter.WithRowAlignment(tw.AlignLeft), // Common for Markdown
tablewriter.WithHeaderAlignment(tw.AlignCenter), // Center align headers
)
tableBlueprintMarkdown.Header("Name", "Status")
tableBlueprintMarkdown.Append("Node1", "Ready")
tableBlueprintMarkdown.Append("Node2", "NotReady")
tableBlueprintMarkdown.Render()
fmt.Println("\n--- Using dedicated Markdown Renderer (if one exists or is built) ---")
// Example 2: Assuming a dedicated Markdown renderer (hypothetical example)
// If a `renderer.NewMarkdown()` existed that directly outputs GitHub Flavored Markdown table syntax:
/*
tableDedicatedMarkdown := tablewriter.NewTable(os.Stdout,
tablewriter.WithRenderer(renderer.NewMarkdown()), // Hypothetical Markdown renderer
)
tableDedicatedMarkdown.Header("Name", "Status")
tableDedicatedMarkdown.Append("Node1", "Ready")
tableDedicatedMarkdown.Append("Node2", "NotReady")
tableDedicatedMarkdown.Render()
*/
// Since `renderer.NewMarkdown()` isn't shown in the provided code,
// the first example (Blueprint with StyleMarkdown) is the current viable way.
}
Output (Blueprint with StyleMarkdown):
| NAME | STATUS |
|--------|----------|
| Node1 | Ready |
| Node2 | NotReady |
Notes:
StyleMarkdown ensures compatibility with Markdown parsers (tw/symbols.go).config.go).|:---:|:---|). With Blueprint and StyleMarkdown, alignment is visual within the text rather than Markdown syntax.Uses StyleASCII for maximum terminal compatibility.
package main
import (
"github.com/olekukonko/tablewriter"
"github.com/olekukonko/tablewriter/renderer"
"github.com/olekukonko/tablewriter/tw"
"os"
)
func main() {
table := tablewriter.NewTable(os.Stdout,
tablewriter.WithRenderer(renderer.NewBlueprint()),
tablewriter.WithRendition(tw.Rendition{
Symbols: tw.NewSymbols(tw.StyleASCII),
}),
tablewriter.WithRowAlignment(tw.AlignLeft),
)
table.Header("ID", "Value")
table.Append("1", "Test")
table.Render()
}
Output:
+----+-------+
│ ID │ VALUE │
+----+-------+
│ 1 │ Test │
+----+-------+
Notes:
StyleASCII is robust for all terminals (tw/symbols.go).Creates a borderless, minimal table similar to kubectl command output, emphasizing simplicity and readability.
package main
import (
"os"
"sync"
"github.com/olekukonko/tablewriter"
"github.com/olekukonko/tablewriter/renderer"
"github.com/olekukonko/tablewriter/tw"
)
var wg sync.WaitGroup
func main() {
data := [][]any{
{"node1.example.com", "Ready", "compute", "1.11"},
{"node2.example.com", "Ready", "compute", "1.11"},
{"node3.example.com", "Ready", "compute", "1.11"},
{"node4.example.com", "NotReady", "compute", "1.11"},
}
table := tablewriter.NewTable(os.Stdout,
tablewriter.WithRenderer(renderer.NewBlueprint(tw.Rendition{
Borders: tw.BorderNone,
Settings: tw.Settings{
Separators: tw.SeparatorsNone,
Lines: tw.LinesNone,
},
})),
tablewriter.WithConfig(tablewriter.Config{
Header: tw.CellConfig{
Formatting: tw.CellFormatting{Alignment: tw.AlignLeft},
},
Row: tw.CellConfig{
Formatting: tw.CellFormatting{Alignment: tw.AlignLeft},
Padding: tw.CellPadding{Global: tw.PaddingNone},
},
}),
)
table.Header("Name", "Status", "Role", "Version")
table.Bulk(data)
table.Render()
}
Output:
NAME STATUS ROLE VERSION
node1.example.com Ready compute 1.21.3
node2.example.com Ready infra 1.21.3
node3.example.com NotReady compute 1.20.7
Notes:
tw.BorderNone, tw.LinesNone, tw.SeparatorsNone, tw.NewSymbols(tw.StyleNone) and specific padding (Padding{Right:" "}) for a minimal, borderless layout.SetBorder(false) and manual spacing with tw.Rendition and Config settings, achieving a cleaner kubectl-like output.config.go).WithTrimSpace(tw.Off) preserves spacing (config.go).Bulk efficiently adds multiple rows (tablewriter.go).Right: " " is used to create space between columns as separators are off.WithDebug(true) for layout issues (config.go).Config.Widths could be used for more control if needed.Demonstrates hierarchical cell merging for nested data structures, a new feature in v1.0.x.
package main
import (
"github.com/olekukonko/tablewriter"
"github.com/olekukonko/tablewriter/renderer"
"github.com/olekukonko/tablewriter/tw"
"os"
)
func main() {
data := [][]string{
// Header row is separate
{"table\nwriter", "v0.0.1", "legacy"},
{"table\nwriter", "v0.0.2", "legacy"},
{"table\nwriter", "v0.0.2", "legacy"}, // Duplicate for testing merge
{"table\nwriter", "v0.0.2", "legacy"}, // Duplicate for testing merge
{"table\nwriter", "v0.0.5", "legacy"},
{"table\nwriter", "v1.0.6", "latest"},
}
rendition := tw.Rendition{
Symbols: tw.NewSymbols(tw.StyleLight), // Use light for clearer merge lines
Settings: tw.Settings{
Separators: tw.Separators{BetweenRows: tw.On},
Lines: tw.Lines{ShowHeaderLine: tw.On, ShowFooterLine: tw.On}, // Show header line
},
Borders: tw.Border{Left:tw.On, Right:tw.On, Top:tw.On, Bottom:tw.On},
}
tableHier := tablewriter.NewTable(os.Stdout,
tablewriter.WithRenderer(renderer.NewBlueprint()),
tablewriter.WithRendition(rendition),
tablewriter.WithConfig(tablewriter.Config{
Row: tw.CellConfig{
Formatting: tw.CellFormatting{
MergeMode: tw.MergeHierarchical,
// Alignment: tw.AlignCenter, // Default is Left, often better for hierarchical
AutoWrap: tw.WrapNormal, // Allow wrapping for "table\nwriter"
},
},
}),
)
tableHier.Header("Package", "Version", "Status") // Header
tableHier.Bulk(data) // Bulk data
tableHier.Render()
// --- Vertical Merging Example for Contrast ---
tableVert := tablewriter.NewTable(os.Stdout,
tablewriter.WithRenderer(renderer.NewBlueprint()),
tablewriter.WithRendition(rendition), // Reuse same rendition
tablewriter.WithConfig(tablewriter.Config{
Row: tw.CellConfig{
Formatting: tw.CellFormatting{
MergeMode: tw.MergeVertical,
AutoWrap: tw.WrapNormal,
},
},
}),
)
tableVert.Header("Package", "Version", "Status")
tableVert.Bulk(data)
tableVert.Render()
}
Output (Hierarchical):
┌─────────┬─────────┬────────┐
│ PACKAGE │ VERSION │ STATUS │
├─────────┼─────────┼────────┤
│ table │ v0.0.1 │ legacy │
│ writer │ │ │
│ ├─────────┼────────┤
│ │ v0.0.2 │ legacy │
│ │ │ │
│ │ │ │
│ │ │ │
│ │ │ │
│ ├─────────┼────────┤
│ │ v0.0.5 │ legacy │
│ ├─────────┼────────┤
│ │ v1.0.6 │ latest │
└─────────┴─────────┴────────┘
Output (Vertical):
┌─────────┬─────────┬────────┐
│ PACKAGE │ VERSION │ STATUS │
├─────────┼─────────┼────────┤
│ table │ v0.0.1 │ legacy │
│ writer │ │ │
│ ├─────────┤ │
│ │ v0.0.2 │ │
│ │ │ │
│ │ │ │
│ │ │ │
│ │ │ │
│ ├─────────┤ │
│ │ v0.0.5 │ │
│ ├─────────┼────────┤
│ │ v1.0.6 │ latest │
└─────────┴─────────┴────────┘
Notes:
tw.MergeHierarchical to merge cells based on matching values in preceding columns (tw/tw.go).SetAutoMergeCells(true) (horizontal only) with hierarchical merging for complex data (tablewriter.go).StyleLight for clear visuals (tw/symbols.go).Bulk for efficient data loading (tablewriter.go).stream.go).zoo.go). AutoWrap: tw.WrapNormal helps with multi-line cell content like "table\nwriter".Applies ANSI colors to highlight status values, replacing v0.0.5’s SetColumnColor.
package main
import (
"github.com/olekukonko/tablewriter"
"github.com/olekukonko/tablewriter/tw" // For tw.State, tw.CellConfig etc.
"os"
)
func main() {
const (
FgGreen = "\033[32m"
FgRed = "\033[31m"
Reset = "\033[0m"
)
cfgBuilder := tablewriter.NewConfigBuilder()
cfgBuilder.Row().Filter().WithPerColumn([]func(string) string{
nil, // No filter for Name
func(s string) string { // Color Status
if s == "Ready" {
return FgGreen + s + Reset
}
return FgRed + s + Reset
},
})
table := tablewriter.NewTable(os.Stdout, tablewriter.WithConfig(cfgBuilder.Build()))
table.Header("Name", "Status")
table.Append("Node1", "Ready")
table.Append("Node2", "Error")
table.Render()
}
Output (Text Approximation, Colors Not Shown):
┌───────┬────────┐
│ NAME │ STATUS │
├───────┼────────┤
│ Node1 │ Ready │
│ Node2 │ Error │
└───────┴────────┘
Notes:
tw.CellFilter for per-column coloring, embedding ANSI codes (tw/cell.go).SetColumnColor with dynamic filters (tablewriter.go).twdw.Width handles ANSI codes correctly (tw/fn.go).tw/fn.go).Shows vertical cell merging for repeated values in a column. (This example was very similar to the hierarchical one, so I'll ensure it's distinct by using simpler data).
package main
import (
"github.com/olekukonko/tablewriter"
"github.com/olekukonko/tablewriter/renderer"
"github.com/olekukonko/tablewriter/tw"
"os"
)
func main() {
table := tablewriter.NewTable(os.Stdout,
tablewriter.WithRenderer(renderer.NewBlueprint()), // Default renderer
tablewriter.WithRendition(tw.Rendition{
Symbols: tw.NewSymbols(tw.StyleLight),
Settings: tw.Settings{Separators: tw.Separators{BetweenRows: tw.On}},
Borders: tw.Border{Left:tw.On, Right:tw.On, Top:tw.On, Bottom:tw.On},
}),
tablewriter.WithConfig(
tablewriter.NewConfigBuilder().
Row().Formatting().WithMergeMode(tw.MergeVertical).Build(). // Enable Vertical Merge
Build(),
),
)
table.Header("User", "Permission", "Target")
table.Append("Alice", "Read", "FileA")
table.Append("Alice", "Write", "FileA") // Alice and FileA will merge vertically
table.Append("Alice", "Read", "FileB")
table.Append("Bob", "Read", "FileA")
table.Append("Bob", "Read", "FileC") // Bob and Read will merge
table.Render()
}
Output:
┌───────┬────────────┬────────┐
│ USER │ PERMISSION │ TARGET │
├───────┼────────────┼────────┤
│ Alice │ Read │ FileA │
│ │ │ │
│ ├────────────┤ │
│ │ Write │ │
│ ├────────────┼────────┤
│ │ Read │ FileB │
├───────┼────────────┼────────┤
│ Bob │ Read │ FileA │
│ │ ├────────┤
│ │ │ FileC │
└───────┴────────────┴────────┘
Notes:
tw.MergeVertical merges identical cells vertically (tw/tw.go).SetAutoMergeCells with vertical merging (tablewriter.go).stream.go).StyleLight for clarity (tw/symbols.go).stream.go).Implements a custom renderer for CSV output.
package main
import (
"fmt"
"github.com/olekukonko/ll" // For logger type
"github.com/olekukonko/tablewriter"
"github.com/olekukonko/tablewriter/tw"
"io"
"os"
"strings" // For CSV escaping
)
// CSVRenderer implements tw.Renderer
type CSVRenderer struct {
writer io.Writer
config tw.Rendition // Store the rendition
logger *ll.Logger
err error
}
func (r *CSVRenderer) Start(w io.Writer) error {
r.writer = w
return nil // No initial output for CSV typically
}
func (r *CSVRenderer) escapeCSVCell(data string) string {
// Basic CSV escaping: double quotes if it contains comma, newline, or quote
if strings.ContainsAny(data, ",\"\n") {
return `"` + strings.ReplaceAll(data, `"`, `""`) + `"`
}
return data
}
func (r *CSVRenderer) writeLine(cells map[int]tw.CellContext, numCols int) {
if r.err != nil { return }
var lineParts []string
// Need to iterate in column order for CSV
keys := make([]int, 0, len(cells))
for k := range cells {
keys = append(keys, k)
}
// This simple sort works for int keys 0,1,2...
// For more complex scenarios, a proper sort might be needed if keys aren't sequential.
for i := 0; i < numCols; i++ { // Assume numCols reflects the intended max columns
if cellCtx, ok := cells[i]; ok {
lineParts = append(lineParts, r.escapeCSVCell(cellCtx.Data))
} else {
lineParts = append(lineParts, "") // Empty cell if not present
}
}
_, r.err = r.writer.Write([]byte(strings.Join(lineParts, ",") + "\n"))
}
func (r *CSVRenderer) Header(headers [][]string, ctx tw.Formatting) {
// For CSV, usually only the first line of headers is relevant
// The ctx.Row.Current will contain the cells for the first line of the header being processed
r.writeLine(ctx.Row.Current, len(ctx.Row.Current))
}
func (r *CSVRenderer) Row(row []string, ctx tw.Formatting) {
// ctx.Row.Current contains the cells for the current row line
r.writeLine(ctx.Row.Current, len(ctx.Row.Current))
}
func (r *CSVRenderer) Footer(footers [][]string, ctx tw.Formatting) {
// Similar to Header/Row, using ctx.Row.Current for the footer line data
r.writeLine(ctx.Row.Current, len(ctx.Row.Current))
}
func (r *CSVRenderer) Line(ctx tw.Formatting) { /* No separator lines in CSV */ }
func (r *CSVRenderer) Close() error { return r.err }
func (r *CSVRenderer) Config() tw.Rendition { return r.config }
func (r *CSVRenderer) Logger(logger *ll.Logger) { r.logger = logger }
func main() {
table := tablewriter.NewTable(os.Stdout, tablewriter.WithRenderer(&CSVRenderer{
// config can be minimal for CSV as symbols/borders aren't used
config: tw.Rendition{},
}))
table.Header("Name", "Status", "Notes, with comma")
table.Append("Node1", "Ready", "All systems \"go\"!")
table.Append("Node2", "Error", "Needs\nattention")
table.Footer("Summary", "2 Nodes", "Check logs")
table.Render()
}
Output:
Name,Status,"Notes, with comma"
Node1,Ready,"All systems ""go""!"
Node2,Error,"Needs
attention"
Summary,2 Nodes,Check logs
Notes:
CSVRenderer implements tw.Renderer for CSV output (tw/renderer.go).tablewriter.go).Append is called multiple times. ctx.Row.Current (map[int]tw.CellContext) is used to access cell data.encoding/csv package.tw.Formatting and tw.RowContext. This example's writeLine assumes columns are 0-indexed and contiguous for simplicity.This section addresses common migration issues with detailed solutions, covering 30+ scenarios to reduce support tickets.
| Issue | Cause/Solution |
|---|---|
No output from Render() | Cause: Missing Start()/Close() in streaming mode or invalid io.Writer. Solution: Ensure Start() and Close() are called in streaming; verify io.Writer (stream.go). |
| Incorrect column widths | Cause: Missing Config.Widths in streaming or content-based sizing. Solution: Set Config.Widths before Start(); use WithColumnMax (stream.go). |
| Merging not working | Cause: Streaming mode or mismatched data. Solution: Use batch mode for vertical/hierarchical merging; ensure identical content (zoo.go). |
| Alignment ignored | Cause: PerColumn overrides Global. Solution: Check Config.Section.Alignment.PerColumn settings or ConfigBuilder calls (tw/cell.go). |
| Padding affects widths | Cause: Padding included in Config.Widths. Solution: Adjust Config.Widths to account for tw.CellPadding (zoo.go). |
| Colors not rendering | Cause: Non-ANSI terminal or incorrect codes. Solution: Test in ANSI-compatible terminal; use twdw.Width (tw/fn.go). |
| Caption missing | Cause: Close() not called in streaming or incorrect Spot. Solution: Ensure Close(); verify tw.Caption.Spot (tablewriter.go). |
| Filters not applied | Cause: Incorrect PerColumn indexing or nil filters. Solution: Set filters correctly; test with sample data (tw/cell.go). |
| Stringer cache overhead | Cause: Large datasets with diverse types. Solution: Disable WithStringerCache for small tables if not using WithStringer or if types vary greatly (tablewriter.go). |
| Deprecated methods used | Cause: Using WithBorders, old tablewriter.Behavior constants. Solution: Migrate to WithRendition, tw.Behavior struct (tablewriter.go, deprecated.go). |
| Streaming footer missing | Cause: Close() not called. Solution: Always call Close() (stream.go). |
| Hierarchical merging fails | Cause: Unsorted data or streaming mode. Solution: Sort data; use batch mode (zoo.go). |
| Custom renderer errors | Cause: Incomplete method implementation or misinterpreting tw.Formatting. Solution: Implement all tw.Renderer methods; test thoroughly (tw/renderer.go). |
| Width overflow | Cause: No MaxWidth or wide content. Solution: Set Config.MaxWidth (config.go). |
| Truncated content | Cause: Narrow Config.Widths or tw.WrapTruncate. Solution: Widen columns or use tw.WrapNormal (zoo.go). |
| Debug logs absent | Cause: Debug = false. Solution: Enable WithDebug(true) (config.go). |
| Alignment mismatch across sections | Cause: Different defaults. Solution: Set uniform alignment options (e.g., via ConfigBuilder.<Section>.Alignment()) (config.go). |
| ANSI code artifacts | Cause: Non-ANSI terminal. Solution: Provide non-colored fallback (tw/fn.go). |
| Slow rendering | Cause: Complex filters or merging. Solution: Optimize logic; limit merging (zoo.go). |
| Uneven cell counts | Cause: Mismatched rows/headers. Solution: Pad with "" (zoo.go). |
| Border inconsistencies | Cause: Mismatched Borders/Symbols. Solution: Align settings (tw/renderer.go). |
| Streaming width issues | Cause: No Config.Widths. Solution: Set before Start() (stream.go). |
| Formatter ignored | Cause: WithStringer might take precedence if compatible. Solution: Review conversion priority; tw.Formatter is high-priority for single-item-to-single-cell conversion (zoo.go). |
| Caption misalignment | Cause: Incorrect Width or Align. Solution: Set tw.Caption.Width/Align (tablewriter.go). |
| Per-column padding errors | Cause: Incorrect indexing in Padding.PerColumn. Solution: Verify indices (tw/cell.go). |
| Vertical merging in streaming | Cause: Unsupported. Solution: Use batch mode (stream.go). |
| Filter performance | Cause: Complex logic. Solution: Simplify filters (zoo.go). |
| Custom symbols incomplete | Cause: Missing characters. Solution: Define all symbols (tw/symbols.go). |
| Table too wide | Cause: No MaxWidth. Solution: Set Config.MaxWidth (config.go). |
| Streaming errors | Cause: Missing Start(). Solution: Call Start() before data input (stream.go). |
WithStringerCache for repetitive data types when using WithStringer; optimize filters and merging for large datasets (tablewriter.go, zoo.go).WithDebug(true) and table.Debug() to log configuration and rendering details; invaluable for troubleshooting (config.go).tests/ directory contains examples of various configurations.tablewriter repository.deprecated.go (e.g., WithBorders) are slated for removal in future releases; migrate promptly to ensure compatibility.This guide aims to cover all migration scenarios comprehensively. For highly specific or advanced use cases, refer to the source files (config.go, tablewriter.go, stream.go, tw/*) or engage with the tablewriter community for support.