{"id":13459852,"url":"https://github.com/olekukonko/tablewriter","last_synced_at":"2025-05-12T16:30:05.228Z","repository":{"id":13815101,"uuid":"16511484","full_name":"olekukonko/tablewriter","owner":"olekukonko","description":"ASCII table in golang","archived":false,"fork":false,"pushed_at":"2025-05-12T14:04:22.000Z","size":1347,"stargazers_count":4413,"open_issues_count":4,"forks_count":375,"subscribers_count":44,"default_branch":"master","last_synced_at":"2025-05-12T14:52:41.346Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/olekukonko.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2014-02-04T12:47:48.000Z","updated_at":"2025-05-12T13:45:36.000Z","dependencies_parsed_at":"2024-04-09T19:58:19.266Z","dependency_job_id":"9da9a37b-aa0a-4136-b9a3-384a2f2e44b3","html_url":"https://github.com/olekukonko/tablewriter","commit_stats":{"total_commits":155,"total_committers":59,"mean_commits":"2.6271186440677967","dds":0.7548387096774194,"last_synced_commit":"f6b4e4ae60d8d6993c8f70eb704850d16f124db8"},"previous_names":["olekukonko/table"],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/olekukonko%2Ftablewriter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/olekukonko%2Ftablewriter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/olekukonko%2Ftablewriter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/olekukonko%2Ftablewriter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/olekukonko","download_url":"https://codeload.github.com/olekukonko/tablewriter/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253776558,"owners_count":21962506,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-31T10:00:30.780Z","updated_at":"2025-05-12T16:30:05.217Z","avatar_url":"https://github.com/olekukonko.png","language":"Go","funding_links":[],"categories":["开源类库","Misc","Go","Open source library","语言资源库","Go 🐹"],"sub_categories":["命令行","Command Line","go"],"readme":"# TableWriter for Go\n\n[![Go](https://github.com/olekukonko/tablewriter/actions/workflows/go.yml/badge.svg)](https://github.com/olekukonko/tablewriter/actions/workflows/go.yml)\n[![Go Reference](https://pkg.go.dev/badge/github.com/olekukonko/tablewriter.svg)](https://pkg.go.dev/github.com/olekukonko/tablewriter)\n[![Go Report Card](https://goreportcard.com/badge/github.com/olekukonko/tablewriter)](https://goreportcard.com/report/github.com/olekukonko/tablewriter)\n[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)\n[![Benchmarks](https://img.shields.io/badge/benchmarks-included-success)](README.md#benchmarks)\n\n`tablewriter` is a Go library for generating **rich text-based tables** with support for multiple output formats, including ASCII, Unicode, Markdown, HTML, and colorized terminals. Perfect for CLI tools, logs, and web applications.\n\n### Key Features\n- **Multi-format rendering**: ASCII, Unicode, Markdown, HTML, ANSI-colored\n- **Advanced styling**: Cell merging, alignment, padding, borders\n- **Flexible input**: CSV, structs, slices, or streaming data\n- **High performance**: Minimal allocations, buffer reuse\n- **Modern features**: Generics support, hierarchical merging, real-time streaming\n\n---\n\n### Installation\n\n#### Legacy Version (v0.0.5)\nFor use with legacy applications:\n```bash\ngo get github.com/olekukonko/tablewriter@v0.0.5\n```\n\n#### Latest  Version\nThe latest stable version\n```bash\ngo get github.com/olekukonko/tablewriter@v1.0.3\n```\n\n**Warning:** Version `v1.0.0` contains missing functionality and should not be used.\n\n\n\u003e **Version Guidance**\n\u003e - Production: Use `v0.0.5` (stable)\n\u003e - New Features: Use `@latest` (includes generics, super fast streaming APIs)\n\u003e - Legacy Docs: See [README_LEGACY.md](README_LEGACY.md)\n\n---\n\n### Why TableWriter?\n- **CLI Ready**: Instant compatibility with terminal outputs\n- **Database Friendly**: Native support for `sql.Null*` types\n- **Secure**: Auto-escaping for HTML/Markdown\n- **Extensible**: Custom renderers and formatters\n\n---\n\n### Quick Example\n```go\npackage main\n\nimport (\n\t\"github.com/olekukonko/tablewriter\"\n\t\"os\"\n)\n\nfunc main() {\n\tdata := [][]string{\n\t\t{\"Package\", \"Version\", \"Status\"},\n\t\t{\"tablewriter\", \"v0.0.5\", \"legacy\"},\n\t\t{\"tablewriter\", \"v1.0.0\", \"latest\"},\n\t}\n\n\ttable := tablewriter.NewWriter(os.Stdout)\n\ttable.Header(data[0])\n\ttable.Bulk(data[1:])\n\ttable.Render()\n}\n```\n**Output**:\n```\n┌─────────────┬─────────┬────────┐\n│   PACKAGE   │ VERSION │ STATUS │\n├─────────────┼─────────┼────────┤\n│ tablewriter │ v0.0.5  │ legacy │\n│ tablewriter │ v1.0.3  │ latest │\n└─────────────┴─────────┴────────┘\n```\n\n\n## Detailed Usage\n\nCreate a table with `NewTable` or `NewWriter`, configure it using options or a `Config` struct, add data with `Append` or `Bulk`, and render to an `io.Writer`. Use renderers like `Blueprint` (ASCII), `HTML`, `Markdown`, `Colorized`, or `Ocean` (streaming).\n\n## Examples\n\n### Basic Examples\n\n#### 1. Simple Tables\n\nCreate a basic table with headers and rows.\n\n\n##### default \n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"github.com/olekukonko/tablewriter\"\n\t\"os\"\n)\n\ntype Age int\n\nfunc (a Age) String() string {\n\treturn fmt.Sprintf(\"%d yrs\", a)\n}\n\nfunc main() {\n\tdata := [][]any{\n\t\t{\"Alice\", Age(25), \"New York\"},\n\t\t{\"Bob\", Age(30), \"Boston\"},\n\t}\n\n\ttable := tablewriter.NewTable(os.Stdout)\n\ttable.Header(\"Name\", \"Age\", \"City\")\n\ttable.Bulk(data)\n\ttable.Render()\n}\n```\n\n**Output**:\n\n```\n┌───────┬────────┬──────────┐\n│ NAME  │  AGE   │   CITY   │\n├───────┼────────┼──────────┤\n│ Alice │ 25 yrs │ New York │\n│ Bob   │ 30 yrs │ Boston   │\n└───────┴────────┴──────────┘\n\n```\n\n\n##### with customization \n\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"github.com/olekukonko/tablewriter\"\n\t\"github.com/olekukonko/tablewriter/renderer\"\n\t\"github.com/olekukonko/tablewriter/tw\"\n\t\"os\"\n)\n\ntype Age int\n\nfunc (a Age) String() string {\n\treturn fmt.Sprintf(\"%d yrs\", a)\n}\n\nfunc main() {\n\tdata := [][]any{\n\t\t{\"Alice\", Age(25), \"New York\"},\n\t\t{\"Bob\", Age(30), \"Boston\"},\n\t}\n\n\tsymbols := tw.NewSymbolCustom(\"Nature\").\n\t\tWithRow(\"~\").\n\t\tWithColumn(\"|\").\n\t\tWithTopLeft(\"🌱\").\n\t\tWithTopMid(\"🌿\").\n\t\tWithTopRight(\"🌱\").\n\t\tWithMidLeft(\"🍃\").\n\t\tWithCenter(\"❀\").\n\t\tWithMidRight(\"🍃\").\n\t\tWithBottomLeft(\"🌻\").\n\t\tWithBottomMid(\"🌾\").\n\t\tWithBottomRight(\"🌻\")\n\n\ttable := tablewriter.NewTable(os.Stdout, tablewriter.WithRenderer(renderer.NewBlueprint(tw.Rendition{Symbols: symbols})))\n\ttable.Header(\"Name\", \"Age\", \"City\")\n\ttable.Bulk(data)\n\ttable.Render()\n}\n```\n\n```\n🌱~~~~~~❀~~~~~~~~❀~~~~~~~~~🌱\n| NAME  |  AGE   |   CITY   |\n🍃~~~~~~❀~~~~~~~~❀~~~~~~~~~🍃\n| Alice | 25 yrs | New York |\n| Bob   | 30 yrs | Boston   |\n🌻~~~~~~❀~~~~~~~~❀~~~~~~~~~🌻\n```\n\nSee [symbols example](https://github.com/olekukonko/tablewriter/blob/master/_example/symbols/main.go) for more\n\n#### 2. Markdown Table\n\nGenerate a Markdown table for documentation.\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"github.com/olekukonko/tablewriter\"\n\t\"github.com/olekukonko/tablewriter/renderer\"\n\t\"os\"\n\t\"strings\"\n\t\"unicode\"\n)\n\ntype Name struct {\n\tFirst string\n\tLast  string\n}\n\n// this will be ignored since  Format() is present\nfunc (n Name) String() string {\n\treturn fmt.Sprintf(\"%s %s\", n.First, n.Last)\n}\n\n// Note: Format() overrides String() if both exist.\nfunc (n Name) Format() string {\n\treturn fmt.Sprintf(\"%s %s\", n.clean(n.First), n.clean(n.Last))\n}\n\n// clean ensures the first letter is capitalized and the rest are lowercase\nfunc (n Name) clean(s string) string {\n\ts = strings.TrimSpace(strings.ToLower(s))\n\twords := strings.Fields(s)\n\ts = strings.Join(words, \"\")\n\n\tif s == \"\" {\n\t\treturn s\n\t}\n\t// Capitalize the first letter\n\trunes := []rune(s)\n\trunes[0] = unicode.ToUpper(runes[0])\n\treturn string(runes)\n}\n\ntype Age int\n\n// Age int will be ignore and string will be used\nfunc (a Age) String() string {\n\treturn fmt.Sprintf(\"%d yrs\", a)\n}\n\nfunc main() {\n\tdata := [][]any{\n\t\t{Name{\"Al  i  CE\", \" Ma  SK\"}, Age(25), \"New York\"},\n\t\t{Name{\"bOb\", \"mar   le   y\"}, Age(30), \"Boston\"},\n\t}\n\n\ttable := tablewriter.NewTable(os.Stdout,\n\t\ttablewriter.WithRenderer(renderer.NewMarkdown()),\n\t)\n\n\ttable.Header([]string{\"Name\", \"Age\", \"City\"})\n\ttable.Bulk(data)\n\ttable.Render()\n}\n```\n\n**Output**:\n\n```\n|    NAME    |  AGE   |   CITY   |\n|:----------:|:------:|:--------:|\n| Alice Mask | 25 yrs | New York |\n| Bob Marley | 30 yrs | Boston   |\n\n\n```\n\n#### 3. CSV Input\n\nCreate a table from a CSV file with custom row alignment.\n\n```go\npackage main\n\nimport (\n\t\"github.com/olekukonko/tablewriter\"\n\t\"github.com/olekukonko/tablewriter/tw\"\n\t\"log\"\n\t\"os\"\n)\n\nfunc main() {\n\t// Assuming \"test.csv\" contains: \"First Name,Last Name,SSN\\nJohn,Barry,123456\\nKathy,Smith,687987\"\n\ttable, err := tablewriter.NewCSV(os.Stdout, \"test.csv\", true)\n\tif err != nil {\n\t\tlog.Fatalf(\"Error: %v\", err)\n\t}\n\n\ttable.Configure(func(config *tablewriter.Config) {\n\t\tconfig.Row.Formatting.Alignment = tw.AlignLeft\n\t})\n\ttable.Render()\n}\n```\n\n**Output**:\n\n```\n┌────────────┬───────────┬─────────┐\n│ FIRST NAME │ LAST NAME │   SSN   │\n├────────────┼───────────┼─────────┤\n│ John       │ Barry     │ 123456  │\n│ Kathy      │ Smith     │ 687987  │\n└────────────┴───────────┴─────────┘\n```\n\n### Advanced Examples\n\n#### 4. Colorized Table with Long Values\n\nCreate a colorized table with wrapped long values, per-column colors, and a styled footer (inspired by `TestColorizedLongValues` and `TestColorizedCustomColors`).\n\n```go\npackage main\n\nimport (\n\t\"github.com/fatih/color\"\n\t\"github.com/olekukonko/tablewriter\"\n\t\"github.com/olekukonko/tablewriter/renderer\"\n\t\"github.com/olekukonko/tablewriter/tw\"\n\t\"os\"\n)\n\nfunc main() {\n\tdata := [][]string{\n\t\t{\"1\", \"This is a very long description that needs wrapping for readability\", \"OK\"},\n\t\t{\"2\", \"Short description\", \"DONE\"},\n\t\t{\"3\", \"Another lengthy description requiring truncation or wrapping\", \"ERROR\"},\n\t}\n\n\t// Configure colors: green headers, cyan/magenta rows, yellow footer\n\tcolorCfg := renderer.ColorizedConfig{\n\t\tHeader: renderer.Tint{\n\t\t\tFG: renderer.Colors{color.FgGreen, color.Bold}, // Green bold headers\n\t\t\tBG: renderer.Colors{color.BgHiWhite},\n\t\t},\n\t\tColumn: renderer.Tint{\n\t\t\tFG: renderer.Colors{color.FgCyan}, // Default cyan for rows\n\t\t\tColumns: []renderer.Tint{\n\t\t\t\t{FG: renderer.Colors{color.FgMagenta}}, // Magenta for column 0\n\t\t\t\t{},                                     // Inherit default (cyan)\n\t\t\t\t{FG: renderer.Colors{color.FgHiRed}},   // High-intensity red for column 2\n\t\t\t},\n\t\t},\n\t\tFooter: renderer.Tint{\n\t\t\tFG: renderer.Colors{color.FgYellow, color.Bold}, // Yellow bold footer\n\t\t\tColumns: []renderer.Tint{\n\t\t\t\t{},                                      // Inherit default\n\t\t\t\t{FG: renderer.Colors{color.FgHiYellow}}, // High-intensity yellow for column 1\n\t\t\t\t{},                                      // Inherit default\n\t\t\t},\n\t\t},\n\t\tBorder:    renderer.Tint{FG: renderer.Colors{color.FgWhite}}, // White borders\n\t\tSeparator: renderer.Tint{FG: renderer.Colors{color.FgWhite}}, // White separators\n\t}\n\n\ttable := tablewriter.NewTable(os.Stdout,\n\t\ttablewriter.WithRenderer(renderer.NewColorized(colorCfg)),\n\t\ttablewriter.WithConfig(tablewriter.Config{\n\t\t\tRow: tw.CellConfig{\n\t\t\t\tFormatting: tw.CellFormatting{\n\t\t\t\t\tMaxWidth:  25,            // Limit column width\n\t\t\t\t\tAutoWrap:  tw.WrapNormal, // Wrap long content\n\t\t\t\t\tAlignment: tw.AlignLeft,  // Left-align rows\n\t\t\t\t},\n\t\t\t},\n\t\t\tFooter: tw.CellConfig{\n\t\t\t\tFormatting: tw.CellFormatting{Alignment: tw.AlignRight},\n\t\t\t},\n\t\t}),\n\t)\n\n\ttable.Header([]string{\"ID\", \"Description\", \"Status\"})\n\ttable.Bulk(data)\n\ttable.Footer([]string{\"\", \"Total\", \"3\"})\n\ttable.Render()\n}\n```\n\n**Output** (colors visible in ANSI-compatible terminals):\n\n![Colorized Table with Long Values](_readme/color_1.png \"Title\")\n\n#### 5. Streaming Table with Truncation\n\nStream a table incrementally with truncation and a footer, simulating a real-time data feed (inspired by `TestOceanStreamTruncation` and `TestOceanStreamSlowOutput`).\n\n```go\npackage main\n\nimport (\n\t\"github.com/olekukonko/tablewriter\"\n\t\"github.com/olekukonko/tablewriter/tw\"\n\t\"log\"\n\t\"os\"\n\t\"time\"\n)\n\nfunc main() {\n\ttable := tablewriter.NewTable(os.Stdout, tablewriter.WithStreaming(tw.StreamConfig{Enable: true}))\n\n\t// Start streaming\n\tif err := table.Start(); err != nil {\n\t\tlog.Fatalf(\"Start failed: %v\", err)\n\t}\n\n\tdefer table.Close()\n\n\t// Stream header\n\ttable.Header([]string{\"ID\", \"Description\", \"Status\"})\n\n\t// Stream rows with simulated delay\n\tdata := [][]string{\n\t\t{\"1\", \"This description is too long\", \"OK\"},\n\t\t{\"2\", \"Short desc\", \"DONE\"},\n\t\t{\"3\", \"Another long description here\", \"ERROR\"},\n\t}\n\tfor _, row := range data {\n\t\ttable.Append(row)\n\t\ttime.Sleep(500 * time.Millisecond) // Simulate real-time data feed\n\t}\n\n\t// Stream footer\n\ttable.Footer([]string{\"\", \"Total\", \"3\"})\n}\n```\n\n**Output** (appears incrementally):\n\n```\n┌────────┬───────────────┬──────────┐\n│   ID   │  DESCRIPTION  │  STATUS  │\n├────────┼───────────────┼──────────┤\n│ 1      │ This          │ OK       │\n│        │ description   │          │\n│        │ is too long   │          │\n│ 2      │ Short desc    │ DONE     │\n│ 3      │ Another long  │ ERROR    │\n│        │ description   │          │\n│        │ here          │          │\n├────────┼───────────────┼──────────┤\n│        │         Total │        3 │\n└────────┴───────────────┴──────────┘\n```\n\n**Note**: Long descriptions are truncated with `…` due to fixed column widths. The output appears row-by-row, simulating a real-time feed.\n\n#### 6. Hierarchical Merging for Organizational Data\n\nShow hierarchical merging for a tree-like structure, such as an organizational hierarchy (inspired by `TestMergeHierarchicalUnicode`).\n\n```go\npackage main\n\nimport (\n\t\"github.com/olekukonko/tablewriter\"\n\t\"github.com/olekukonko/tablewriter/renderer\"\n\t\"github.com/olekukonko/tablewriter/tw\"\n\t\"os\"\n)\n\nfunc main() {\n\tdata := [][]string{\n\t\t{\"Engineering\", \"Backend\", \"API Team\", \"Alice\"},\n\t\t{\"Engineering\", \"Backend\", \"Database Team\", \"Bob\"},\n\t\t{\"Engineering\", \"Frontend\", \"UI Team\", \"Charlie\"},\n\t\t{\"Marketing\", \"Digital\", \"SEO Team\", \"Dave\"},\n\t\t{\"Marketing\", \"Digital\", \"Content Team\", \"Eve\"},\n\t}\n\n\ttable := tablewriter.NewTable(os.Stdout,\n\t\ttablewriter.WithRenderer(renderer.NewBlueprint(tw.Rendition{\n\t\t\tSettings: tw.Settings{\n\t\t\t\tSeparators: tw.Separators{BetweenRows: tw.On},\n\t\t\t},\n\t\t})),\n\t\ttablewriter.WithConfig(tablewriter.Config{\n\t\t\tHeader: tw.CellConfig{\n\t\t\t\tFormatting: tw.CellFormatting{Alignment: tw.AlignCenter},\n\t\t\t},\n\t\t\tRow: tw.CellConfig{\n\t\t\t\tFormatting: tw.CellFormatting{\n\t\t\t\t\tMergeMode: tw.MergeHierarchical,\n\t\t\t\t\tAlignment: tw.AlignLeft,\n\t\t\t\t},\n\t\t\t},\n\t\t}),\n\t)\n\ttable.Header([]string{\"Department\", \"Division\", \"Team\", \"Lead\"})\n\ttable.Bulk(data)\n\ttable.Render()\n}\n```\n\n**Output**:\n\n```\n┌────────────┬──────────┬──────────────┬────────┐\n│ DEPARTMENT │ DIVISION │    TEAM      │  LEAD  │\n├────────────┼──────────┼──────────────┼────────┤\n│ Engineering│ Backend  │ API Team     │ Alice  │\n│            │          ├──────────────┼────────┤\n│            │          │ Database Team│ Bob    │\n│            │ Frontend ├──────────────┼────────┤\n│            │          │ UI Team      │ Charlie│\n├────────────┼──────────┼──────────────┼────────┤\n│ Marketing  │ Digital  │ SEO Team     │ Dave   │\n│            │          ├──────────────┼────────┤\n│            │          │ Content Team │ Eve    │\n└────────────┴──────────┴──────────────┴────────┘\n```\n\n**Note**: Hierarchical merging groups repeated values (e.g., \"Engineering\" spans multiple rows, \"Backend\" spans two teams), creating a tree-like structure.\n\n#### 7. Custom Padding with Merging\n\nShowcase custom padding and combined horizontal/vertical merging (inspired by `TestMergeWithPadding` in `merge_test.go`).\n\n```go\npackage main\n\nimport (\n\t\"github.com/olekukonko/tablewriter\"\n\t\"github.com/olekukonko/tablewriter/renderer\"\n\t\"github.com/olekukonko/tablewriter/tw\"\n\t\"os\"\n)\n\nfunc main() {\n\tdata := [][]string{\n\t\t{\"1/1/2014\", \"Domain name\", \"Successful\", \"Successful\"},\n\t\t{\"1/1/2014\", \"Domain name\", \"Pending\", \"Waiting\"},\n\t\t{\"1/1/2014\", \"Domain name\", \"Successful\", \"Rejected\"},\n\t\t{\"\", \"\", \"TOTAL\", \"$145.93\"},\n\t}\n\n\ttable := tablewriter.NewTable(os.Stdout,\n\t\ttablewriter.WithRenderer(renderer.NewBlueprint(tw.Rendition{\n\t\t\tSettings: tw.Settings{\n\t\t\t\tSeparators: tw.Separators{BetweenRows: tw.On},\n\t\t\t},\n\t\t})),\n\t\ttablewriter.WithConfig(tablewriter.Config{\n\t\t\tRow: tw.CellConfig{\n\t\t\t\tFormatting:   tw.CellFormatting{MergeMode: tw.MergeBoth},\n\t\t\t\tColumnAligns: []tw.Align{tw.Skip, tw.Skip, tw.AlignRight, tw.AlignLeft},\n\t\t\t},\n\t\t\tFooter: tw.CellConfig{\n\t\t\t\tPadding: tw.CellPadding{\n\t\t\t\t\tGlobal:    tw.Padding{Left: \"*\", Right: \"*\"},\n\t\t\t\t\tPerColumn: []tw.Padding{{}, {}, {Bottom: \"^\"}, {Bottom: \"^\"}},\n\t\t\t\t},\n\t\t\t\tColumnAligns: []tw.Align{tw.Skip, tw.Skip, tw.AlignRight, tw.AlignLeft},\n\t\t\t},\n\t\t}),\n\t)\n\ttable.Header([]string{\"Date\", \"Description\", \"Status\", \"Conclusion\"})\n\ttable.Bulk(data)\n\ttable.Render()\n}\n```\n\n**Output**:\n\n```\n┌──────────┬─────────────┬────────────┬────────────┐\n│   DATE   │ DESCRIPTION │   STATUS   │ CONCLUSION │\n├──────────┼─────────────┼────────────┴────────────┤\n│ 1/1/2014 │ Domain name │              Successful │\n│          │             ├────────────┬────────────┤\n│          │             │    Pending │ Waiting    │\n│          │             ├────────────┼────────────┤\n│          │             │ Successful │ Rejected   │\n├──────────┼─────────────┼────────────┼────────────┤\n│          │             │      TOTAL │ $145.93    │\n│          │             │^^^^^^^^^^^^│^^^^^^^^^^^^│\n└──────────┴─────────────┴────────────┴────────────┘\n```\n\n#### 8. Nested Tables\n\nCreate a table with nested sub-tables for complex layouts (inspired by `TestMasterClass` in `extra_test.go`).\n\n```go\npackage main\n\nimport (\n\t\"bytes\"\n\t\"github.com/olekukonko/tablewriter\"\n\t\"github.com/olekukonko/tablewriter/renderer\"\n\t\"github.com/olekukonko/tablewriter/tw\"\n\t\"os\"\n)\n\nfunc main() {\n\t// Helper to create a sub-table\n\tcreateSubTable := func(s string) string {\n\t\tvar buf bytes.Buffer\n\t\ttable := tablewriter.NewTable(\u0026buf,\n\t\t\ttablewriter.WithRenderer(renderer.NewBlueprint(tw.Rendition{\n\t\t\t\tBorders: tw.BorderNone,\n\t\t\t\tSymbols: tw.NewSymbols(tw.StyleASCII),\n\t\t\t\tSettings: tw.Settings{\n\t\t\t\t\tSeparators: tw.Separators{BetweenRows: tw.On},\n\t\t\t\t\tLines:      tw.Lines{ShowFooterLine: tw.On},\n\t\t\t\t},\n\t\t\t})),\n\t\t\ttablewriter.WithConfig(tablewriter.Config{\n\t\t\t\tMaxWidth: 10,\n\t\t\t\tRow: tw.CellConfig{\n\t\t\t\t\tFormatting: tw.CellFormatting{Alignment: tw.AlignCenter},\n\t\t\t\t},\n\t\t\t}),\n\t\t)\n\t\ttable.Append([]string{s, s})\n\t\ttable.Append([]string{s, s})\n\t\ttable.Render()\n\t\treturn buf.String()\n\t}\n\n\t// Main table\n\ttable := tablewriter.NewTable(os.Stdout,\n\t\ttablewriter.WithRenderer(renderer.NewBlueprint(tw.Rendition{\n\t\t\tBorders: tw.BorderNone,\n\t\t\tSettings: tw.Settings{\n\t\t\t\tSeparators: tw.Separators{BetweenColumns: tw.On},\n\t\t\t},\n\t\t})),\n\t\ttablewriter.WithConfig(tablewriter.Config{\n\t\t\tMaxWidth: 30,\n\t\t\tRow: tw.CellConfig{\n\t\t\t\tFormatting: tw.CellFormatting{Alignment: tw.AlignCenter},\n\t\t\t},\n\t\t}),\n\t)\n\ttable.Append([]string{createSubTable(\"A\"), createSubTable(\"B\")})\n\ttable.Append([]string{createSubTable(\"C\"), createSubTable(\"D\")})\n\ttable.Render()\n}\n```\n\n**Output**:\n\n```\n  A | A  │  B | B  \n ---+--- │ ---+--- \n  A | A  │  B | B  \n ---+--- │ ---+--- \n         │         \n  C | C  │  D | D  \n ---+--- │ ---+--- \n  C | C  │  D | D  \n ---+--- │ ---+--- \n         │         \n```\n\n#### 9. Structs with Database\n\nRender a table from a slice of structs, simulating a database query (inspired by `TestStructTableWithDB` in `struct_test.go`).\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"github.com/olekukonko/tablewriter\"\n\t\"github.com/olekukonko/tablewriter/renderer\"\n\t\"github.com/olekukonko/tablewriter/tw\"\n\t\"os\"\n)\n\ntype Employee struct {\n\tID         int\n\tName       string\n\tAge        int\n\tDepartment string\n\tSalary     float64\n}\n\nfunc employeeStringer(e interface{}) []string {\n\temp, ok := e.(Employee)\n\tif !ok {\n\t\treturn []string{\"Error: Invalid type\"}\n\t}\n\treturn []string{\n\t\tfmt.Sprintf(\"%d\", emp.ID),\n\t\temp.Name,\n\t\tfmt.Sprintf(\"%d\", emp.Age),\n\t\temp.Department,\n\t\tfmt.Sprintf(\"%.2f\", emp.Salary),\n\t}\n}\n\nfunc main() {\n\temployees := []Employee{\n\t\t{ID: 1, Name: \"Alice Smith\", Age: 28, Department: \"Engineering\", Salary: 75000.50},\n\t\t{ID: 2, Name: \"Bob Johnson\", Age: 34, Department: \"Marketing\", Salary: 62000.00},\n\t\t{ID: 3, Name: \"Charlie Brown\", Age: 45, Department: \"HR\", Salary: 80000.75},\n\t}\n\n\ttable := tablewriter.NewTable(os.Stdout,\n\t\ttablewriter.WithRenderer(renderer.NewBlueprint(tw.Rendition{\n\t\t\tSymbols: tw.NewSymbols(tw.StyleRounded),\n\t\t})),\n\t\ttablewriter.WithStringer(employeeStringer),\n\t\ttablewriter.WithConfig(tablewriter.Config{\n\t\t\tHeader: tw.CellConfig{\n\t\t\t\tFormatting: tw.CellFormatting{Alignment: tw.AlignCenter, AutoFormat: true},\n\t\t\t},\n\t\t\tRow: tw.CellConfig{\n\t\t\t\tFormatting: tw.CellFormatting{Alignment: tw.AlignLeft},\n\t\t\t},\n\t\t\tFooter: tw.CellConfig{\n\t\t\t\tFormatting: tw.CellFormatting{Alignment: tw.AlignRight},\n\t\t\t},\n\t\t}),\n\t)\n\ttable.Header([]string{\"ID\", \"Name\", \"Age\", \"Department\", \"Salary\"})\n\n\tfor _, emp := range employees {\n\t\ttable.Append(emp)\n\t}\n\n\ttotalSalary := 0.0\n\tfor _, emp := range employees {\n\t\ttotalSalary += emp.Salary\n\t}\n\ttable.Footer([]string{\"\", \"\", \"\", \"Total\", fmt.Sprintf(\"%.2f\", totalSalary)})\n\ttable.Render()\n}\n```\n\n**Output**:\n\n```\n╭────┬───────────────┬─────┬─────────────┬───────────╮\n│ ID │     NAME      │ AGE │ DEPARTMENT  │  SALARY   │\n├────┼───────────────┼─────┼─────────────┼───────────┤\n│ 1  │ Alice Smith   │ 28  │ Engineering │ 75000.50  │\n│ 2  │ Bob Johnson   │ 34  │ Marketing   │ 62000.00  │\n│ 3  │ Charlie Brown │ 45  │ HR          │ 80000.75  │\n├────┼───────────────┼─────┼─────────────┼───────────┤\n│    │               │     │       Total │ 217001.25 │\n╰────┴───────────────┴─────┴─────────────┴───────────╯\n```\n\n\n#### 10. Simple Html Table\n\n\n```go\npackage main\n\nimport (\n\t\"github.com/olekukonko/tablewriter\"\n\t\"github.com/olekukonko/tablewriter/renderer\"\n\t\"github.com/olekukonko/tablewriter/tw\"\n\t\"os\"\n)\n\nfunc main() {\n\tdata := [][]string{\n\t\t{\"North\", \"Q1 \u0026 Q2\", \"Q1 \u0026 Q2\", \"$2200.00\"},\n\t\t{\"South\", \"Q1\", \"Q1\", \"$1000.00\"},\n\t\t{\"South\", \"Q2\", \"Q2\", \"$1200.00\"},\n\t}\n\n\t// Configure HTML with custom CSS classes and content escaping\n\thtmlCfg := renderer.HTMLConfig{\n\t\tTableClass:     \"sales-table\",\n\t\tHeaderClass:    \"table-header\",\n\t\tBodyClass:      \"table-body\",\n\t\tFooterClass:    \"table-footer\",\n\t\tRowClass:       \"table-row\",\n\t\tHeaderRowClass: \"header-row\",\n\t\tFooterRowClass: \"footer-row\",\n\t\tEscapeContent:  true, // Escape HTML characters (e.g., \"\u0026\" to \"\u0026\")\n\t}\n\n\ttable := tablewriter.NewTable(os.Stdout,\n\t\ttablewriter.WithRenderer(renderer.NewHTML(os.Stdout, false, htmlCfg)),\n\t\ttablewriter.WithConfig(tablewriter.Config{\n\t\t\tHeader: tw.CellConfig{\n\t\t\t\tFormatting: tw.CellFormatting{\n\t\t\t\t\tAlignment: tw.AlignCenter,\n\t\t\t\t\tMergeMode: tw.MergeHorizontal, // Merge identical header cells\n\t\t\t\t},\n\t\t\t},\n\t\t\tRow: tw.CellConfig{\n\t\t\t\tFormatting: tw.CellFormatting{\n\t\t\t\t\tMergeMode: tw.MergeHorizontal, // Merge identical row cells\n\t\t\t\t\tAlignment: tw.AlignLeft,\n\t\t\t\t},\n\t\t\t},\n\t\t\tFooter: tw.CellConfig{\n\t\t\t\tFormatting: tw.CellFormatting{Alignment: tw.AlignRight},\n\t\t\t},\n\t\t}),\n\t)\n\n\ttable.Header([]string{\"Region\", \"Quarter\", \"Quarter\", \"Sales\"})\n\ttable.Bulk(data)\n\ttable.Footer([]string{\"\", \"\", \"Total\", \"$4400.00\"})\n\ttable.Render()\n}\n```\n\n**Output**:\n\n```\n\u003ctable class=\"sales-table\"\u003e\n    \u003cthead class=\"table-header\"\u003e\n        \u003ctr class=\"header-row\"\u003e\n            \u003cth style=\"text-align: center;\"\u003eREGION\u003c/th\u003e\n            \u003cth colspan=\"2\" style=\"text-align: center;\"\u003eQUARTER\u003c/th\u003e\n            \u003cth style=\"text-align: center;\"\u003eSALES\u003c/th\u003e\n        \u003c/tr\u003e\n    \u003c/thead\u003e\n    \u003ctbody class=\"table-body\"\u003e\n        \u003ctr class=\"table-row\"\u003e\n            \u003ctd style=\"text-align: left;\"\u003eNorth\u003c/td\u003e\n            \u003ctd colspan=\"2\" style=\"text-align: left;\"\u003eQ1 \u0026amp; Q2\u003c/td\u003e\n            \u003ctd style=\"text-align: left;\"\u003e$2200.00\u003c/td\u003e\n        \u003c/tr\u003e\n        \u003ctr class=\"table-row\"\u003e\n            \u003ctd style=\"text-align: left;\"\u003eSouth\u003c/td\u003e\n            \u003ctd colspan=\"2\" style=\"text-align: left;\"\u003eQ1\u003c/td\u003e\n            \u003ctd style=\"text-align: left;\"\u003e$1000.00\u003c/td\u003e\n        \u003c/tr\u003e\n        \u003ctr class=\"table-row\"\u003e\n            \u003ctd style=\"text-align: left;\"\u003eSouth\u003c/td\u003e\n            \u003ctd colspan=\"2\" style=\"text-align: left;\"\u003eQ2\u003c/td\u003e\n            \u003ctd style=\"text-align: left;\"\u003e$1200.00\u003c/td\u003e\n        \u003c/tr\u003e\n    \u003c/tbody\u003e\n    \u003ctfoot class=\"table-footer\"\u003e\n        \u003ctr class=\"footer-row\"\u003e\n            \u003ctd style=\"text-align: right;\"\u003e\u003c/td\u003e\n            \u003ctd style=\"text-align: right;\"\u003e\u003c/td\u003e\n            \u003ctd style=\"text-align: right;\"\u003eTotal\u003c/td\u003e\n            \u003ctd style=\"text-align: right;\"\u003e$4400.00\u003c/td\u003e\n        \u003c/tr\u003e\n    \u003c/tfoot\u003e\n\u003c/table\u003e\n\n```\n\n#### 11. SVG Support \n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"github.com/olekukonko/ll\"\n\t\"github.com/olekukonko/tablewriter\"\n\t\"github.com/olekukonko/tablewriter/renderer\"\n\t\"os\"\n)\n\ntype Age int\n\nfunc (a Age) String() string {\n\treturn fmt.Sprintf(\"%d yrs\", a)\n}\n\nfunc main() {\n\tdata := [][]any{\n\t\t{\"Alice\", Age(25), \"New York\"},\n\t\t{\"Bob\", Age(30), \"Boston\"},\n\t}\n\n\tfile, err := os.OpenFile(\"out.svg\", os.O_CREATE|os.O_WRONLY, 0644)\n\tif err != nil {\n\t\tll.Fatal(err)\n\t}\n\tdefer file.Close()\n\n\ttable := tablewriter.NewTable(file, tablewriter.WithRenderer(renderer.NewSVG()))\n\ttable.Header(\"Name\", \"Age\", \"City\")\n\ttable.Bulk(data)\n\ttable.Render()\n}\n```\n\n```go\n\u003csvg xmlns=\"http://www.w3.org/2000/svg\" width=\"170.80\" height=\"84.40\" font-family=\"sans-serif\" font-size=\"12.00\"\u003e\n\u003cstyle\u003etext { stroke: none; }\u003c/style\u003e\n  \u003crect x=\"1.00\" y=\"1.00\" width=\"46.00\" height=\"26.80\" fill=\"#F0F0F0\"/\u003e\n  \u003ctext x=\"24.00\" y=\"14.40\" fill=\"black\" text-anchor=\"middle\" dominant-baseline=\"middle\"\u003eNAME\u003c/text\u003e\n  \u003crect x=\"48.00\" y=\"1.00\" width=\"53.20\" height=\"26.80\" fill=\"#F0F0F0\"/\u003e\n  \u003ctext x=\"74.60\" y=\"14.40\" fill=\"black\" text-anchor=\"middle\" dominant-baseline=\"middle\"\u003eAGE\u003c/text\u003e\n  \u003crect x=\"102.20\" y=\"1.00\" width=\"67.60\" height=\"26.80\" fill=\"#F0F0F0\"/\u003e\n  \u003ctext x=\"136.00\" y=\"14.40\" fill=\"black\" text-anchor=\"middle\" dominant-baseline=\"middle\"\u003eCITY\u003c/text\u003e\n  \u003crect x=\"1.00\" y=\"28.80\" width=\"46.00\" height=\"26.80\" fill=\"white\"/\u003e\n  \u003ctext x=\"6.00\" y=\"42.20\" fill=\"black\" text-anchor=\"start\" dominant-baseline=\"middle\"\u003eAlice\u003c/text\u003e\n  \u003crect x=\"48.00\" y=\"28.80\" width=\"53.20\" height=\"26.80\" fill=\"white\"/\u003e\n  \u003ctext x=\"53.00\" y=\"42.20\" fill=\"black\" text-anchor=\"start\" dominant-baseline=\"middle\"\u003e25 yrs\u003c/text\u003e\n  \u003crect x=\"102.20\" y=\"28.80\" width=\"67.60\" height=\"26.80\" fill=\"white\"/\u003e\n  \u003ctext x=\"107.20\" y=\"42.20\" fill=\"black\" text-anchor=\"start\" dominant-baseline=\"middle\"\u003eNew York\u003c/text\u003e\n  \u003crect x=\"1.00\" y=\"56.60\" width=\"46.00\" height=\"26.80\" fill=\"#F9F9F9\"/\u003e\n  \u003ctext x=\"6.00\" y=\"70.00\" fill=\"black\" text-anchor=\"start\" dominant-baseline=\"middle\"\u003eBob\u003c/text\u003e\n  \u003crect x=\"48.00\" y=\"56.60\" width=\"53.20\" height=\"26.80\" fill=\"#F9F9F9\"/\u003e\n  \u003ctext x=\"53.00\" y=\"70.00\" fill=\"black\" text-anchor=\"start\" dominant-baseline=\"middle\"\u003e30 yrs\u003c/text\u003e\n  \u003crect x=\"102.20\" y=\"56.60\" width=\"67.60\" height=\"26.80\" fill=\"#F9F9F9\"/\u003e\n  \u003ctext x=\"107.20\" y=\"70.00\" fill=\"black\" text-anchor=\"start\" dominant-baseline=\"middle\"\u003eBoston\u003c/text\u003e\n  \u003cg class=\"table-borders\" stroke=\"black\" stroke-width=\"1.00\" stroke-linecap=\"square\"\u003e\n    \u003cline x1=\"0.50\" y1=\"0.50\" x2=\"170.30\" y2=\"0.50\" /\u003e\n    \u003cline x1=\"0.50\" y1=\"28.30\" x2=\"170.30\" y2=\"28.30\" /\u003e\n    \u003cline x1=\"0.50\" y1=\"56.10\" x2=\"170.30\" y2=\"56.10\" /\u003e\n    \u003cline x1=\"0.50\" y1=\"83.90\" x2=\"170.30\" y2=\"83.90\" /\u003e\n    \u003cline x1=\"0.50\" y1=\"0.50\" x2=\"0.50\" y2=\"83.90\" /\u003e\n    \u003cline x1=\"47.50\" y1=\"0.50\" x2=\"47.50\" y2=\"83.90\" /\u003e\n    \u003cline x1=\"101.70\" y1=\"0.50\" x2=\"101.70\" y2=\"83.90\" /\u003e\n    \u003cline x1=\"170.30\" y1=\"0.50\" x2=\"170.30\" y2=\"83.90\" /\u003e\n  \u003c/g\u003e\n\u003c/svg\u003e\n\n```\n\n#### 12 Simple Application\n\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"github.com/olekukonko/tablewriter\"\n\t\"github.com/olekukonko/tablewriter/tw\"\n\t\"io/fs\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"time\"\n)\n\nconst (\n\tfolder    = \"📁\"\n\tfile      = \"📄\"\n\tbaseDir   = \"../\"\n\tindentStr = \"    \"\n)\n\nfunc main() {\n\ttable := tablewriter.NewTable(os.Stdout, tablewriter.WithTrimSpace(tw.Off))\n\ttable.Header([]string{\"Tree\", \"Size\", \"Permissions\", \"Modified\"})\n\terr := filepath.WalkDir(baseDir, func(path string, d fs.DirEntry, err error) error {\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tif d.Name() == \".\" || d.Name() == \"..\" {\n\t\t\treturn nil\n\t\t}\n\n\t\t// Calculate relative path depth\n\t\trelPath, err := filepath.Rel(baseDir, path)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tdepth := 0\n\t\tif relPath != \".\" {\n\t\t\tdepth = len(strings.Split(relPath, string(filepath.Separator))) - 1\n\t\t}\n\n\t\tindent := strings.Repeat(indentStr, depth)\n\n\t\tvar name string\n\t\tif d.IsDir() {\n\t\t\tname = fmt.Sprintf(\"%s%s %s\", indent, folder, d.Name())\n\t\t} else {\n\t\t\tname = fmt.Sprintf(\"%s%s %s\", indent, file, d.Name())\n\t\t}\n\n\t\tinfo, err := d.Info()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\ttable.Append([]string{\n\t\t\tname,\n\t\t\tSize(info.Size()).String(),\n\t\t\tinfo.Mode().String(),\n\t\t\tTime(info.ModTime()).Format(),\n\t\t})\n\n\t\treturn nil\n\t})\n\n\tif err != nil {\n\t\tfmt.Fprintf(os.Stdout, \"Error: %v\\n\", err)\n\t\treturn\n\t}\n\n\ttable.Render()\n}\n\nconst (\n\tKB = 1024\n\tMB = KB * 1024\n\tGB = MB * 1024\n\tTB = GB * 1024\n)\n\ntype Size int64\n\nfunc (s Size) String() string {\n\tswitch {\n\tcase s \u003c KB:\n\t\treturn fmt.Sprintf(\"%d B\", s)\n\tcase s \u003c MB:\n\t\treturn fmt.Sprintf(\"%.2f KB\", float64(s)/KB)\n\tcase s \u003c GB:\n\t\treturn fmt.Sprintf(\"%.2f MB\", float64(s)/MB)\n\tcase s \u003c TB:\n\t\treturn fmt.Sprintf(\"%.2f GB\", float64(s)/GB)\n\tdefault:\n\t\treturn fmt.Sprintf(\"%.2f TB\", float64(s)/TB)\n\t}\n}\n\ntype Time time.Time\n\nfunc (t Time) Format() string {\n\tnow := time.Now()\n\tdiff := now.Sub(time.Time(t))\n\n\tif diff.Seconds() \u003c 60 {\n\t\treturn \"just now\"\n\t} else if diff.Minutes() \u003c 60 {\n\t\treturn fmt.Sprintf(\"%d minutes ago\", int(diff.Minutes()))\n\t} else if diff.Hours() \u003c 24 {\n\t\treturn fmt.Sprintf(\"%d hours ago\", int(diff.Hours()))\n\t} else if diff.Hours() \u003c 24*7 {\n\t\treturn fmt.Sprintf(\"%d days ago\", int(diff.Hours()/24))\n\t} else {\n\t\treturn time.Time(t).Format(\"Jan 2, 2006\")\n\t}\n}\n\n```\n\n```\n┌──────────────────┬─────────┬─────────────┬──────────────┐\n│       TREE       │  SIZE   │ PERMISSIONS │   MODIFIED   │\n├──────────────────┼─────────┼─────────────┼──────────────┤\n│ 📁 filetable     │ 160 B   │ drwxr-xr-x  │ just now     │\n│     📄 main.go   │ 2.19 KB │ -rw-r--r--  │ 22 hours ago │\n│     📄 out.txt   │ 0 B     │ -rw-r--r--  │ just now     │\n│     📁 testdata  │ 128 B   │ drwxr-xr-x  │ 1 days ago   │\n│         📄 a.txt │ 11 B    │ -rw-r--r--  │ 1 days ago   │\n│         📄 b.txt │ 17 B    │ -rw-r--r--  │ 1 days ago   │\n│ 📁 symbols       │ 128 B   │ drwxr-xr-x  │ just now     │\n│     📄 main.go   │ 4.58 KB │ -rw-r--r--  │ 1 hours ago  │\n│     📄 out.txt   │ 8.72 KB │ -rw-r--r--  │ just now     │\n└──────────────────┴─────────┴─────────────┴──────────────┘\n```\n\n## Command-Line Tool\n\nThe `csv2table` tool converts CSV files to ASCII tables. See `cmd/csv2table/csv2table.go` for details.\n\nExample usage:\n\n```bash\ncsv2table -f test.csv -h true -a left\n```\n\n## Contributing\n\nContributions are welcome! Submit issues or pull requests to the [GitHub repository](https://github.com/olekukonko/tablewriter).\n\n## License\n\nMIT License. See the [LICENSE](LICENSE) file for details.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Folekukonko%2Ftablewriter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Folekukonko%2Ftablewriter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Folekukonko%2Ftablewriter/lists"}