output

package module
v0.30.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 11, 2026 License: MIT Imports: 11 Imported by: 0

README

go-output

CI Go Report Card Go Reference

Write your data once. Render it anywhere.

One library. Sixteen formats. Three data shapes. Zero lock-in.

go-output is a Go library that turns your structured data into 16 output formats — tables, trees, and diagrams — with type-safe enums, branded IDs, and zero-config color support. It also includes NOM-style real-time progress visualization for long-running workflows, inspired by nix-output-monitor.

import "github.com/larsartmann/go-output"

The root module has zero heavy dependencies — only golang.org/x/term. YAML, lipgloss, bubbletea, and diagram renderers live in isolated sub-modules you import only when you need them.

Requires Go 1.26+.


Quick Start

Build tabular data once, render it in any format:

import (
    "os"

    "github.com/larsartmann/go-output"
    "github.com/larsartmann/go-output/delimited"
    "github.com/larsartmann/go-output/markdown"
    "github.com/larsartmann/go-output/serialization"
)

// Define your data once
data := output.NewTable([]string{"Name", "Health", "Complexity"})
data.AddRow([]string{"Alpha", "90%", "7/10"})
data.AddRow([]string{"Beta", "75%", "5/10"})
data.SetFooter([]string{"Total", "2", "-"})

// Render to Markdown
md := markdown.NewMarkdownTable()
md.SetHeaders(data.GetHeaders())
for _, row := range data.GetRows() { md.AddRow(row) }
out, _ := md.Render()

// Or serialize to JSON
jtr := serialization.NewJSONTableRenderer()
jtr.SetData(data)
out, _ = jtr.Render()

// Or stream to CSV
csv := delimited.NewCSVWriter(os.Stdout)
_ = csv.WriteHeader(data.GetHeaders())
for _, row := range data.GetRows() { _ = csv.WriteRow(row) }
csv.Flush()

Sub-module imports (e.g., delimited, markdown, serialization) require cloning the repo and setting up the workspace — see Installation. Root-only usage works with just go get.

Use the Format enum for runtime format selection — perfect for CLI flags:

format, _ := output.ParseFormat("json") // validates input
fmt.Println(format.Supports(output.ShapeTable)) // true
fmt.Println(format.Shapes())                     // [table tree graph]

Or dispatch through the unified renderer:

output.RenderTable(data, output.FormatHTML, output.RenderOptions{
    ColorMode: output.ColorModeAuto,
})
CQRS: Build, Freeze, Render

v0.30.0 introduces a CQRS (Command-Query Responsibility Segregation) architecture: builders are mutable, snapshots are immutable, and renderers are pure functions.

import (
    "github.com/larsartmann/go-output"
    "github.com/larsartmann/go-output/graph"
)

// 1. Build — mutable
b := output.NewGraphBuilder()
b.AddNode(*output.NewGraphNode("compile", "Compile"))
b.AddNode(*output.NewGraphNode("test", "Test"))
b.AddEdge(*output.NewGraphEdge("compile", "test"))

// 2. Freeze — immutable snapshot
g := b.Build()

// 3. Render — pure functions, same Graph, multiple formats
dot,     _ := graph.RenderDOT(g)
mermaid, _ := graph.RenderMermaid(g)

The same pattern works for tables and trees:

// Table
tbl := output.NewTableBuilder().
    SetHeaders("Name", "Status").
    AddRow("Compile", "done").
    Build()

csv, _ := delimited.RenderCSV(tbl)

// Tree
root := output.NewTreeBuilder().
    SetRoot("build", "Build").
    AddChild("build", "compile", "Compile").
    Build()

ascii, _ := tree.RenderASCII(root)

Cross-shape projections convert between data shapes as pure functions:

g := output.TableToGraph(tbl)  // Table → Graph
t := output.GraphToTree(g)     // Graph → Tree

Why go-output?

  • 16 formats, one API — Same Table, TreeNode, or GraphNode. No format-specific code paths.
  • Type-safe everythingFormat, ColorMode, ActivityStatus — all validated at parse time. Branded IDs prevent mixing D2NodeID with TreeNodeID at compile time.
  • Zero heavy deps in rootgo get go-output pulls only x/term. YAML, lipgloss, bubbletea, and diagram renderers are opt-in sub-modules.
  • NOM real-time progress — Dependency trees, activity counts, timing estimates, and inline terminal rendering. O(1) summary bars even at 10,000 activities.
  • Streaming for large dataStreamingHTMLRenderer writes incrementally with minimal memory.
  • Zero-config colorColorModeAuto detects TTY, respects NO_COLOR, CI, FORCE_COLOR.
  • API stable — ADR 006 locks core interfaces. Breaking changes are documented and versioned.

Same data, rendered six ways — all from one Table:

Markdown table:

| Name  | Health | Complexity |
|-------|--------|------------|
| Alpha | 90%    | 7/10       |
| Beta  | 75%    | 5/10       |
| Gamma | 85%    | 8/10       |

CSV:

Name,Health,Complexity
Alpha,90%,7/10
Beta,75%,5/10
Gamma,85%,8/10
TOTAL,3,-

Tree:

└── Projects
    ├── Alpha (health: 90%, complexity: 7)
    ├── Beta (health: 75%, complexity: 5)
    └── Gamma (health: 85%, complexity: 8)

D2 diagram:

projects: {
  shape: sql_table
  id: serial {constraint: primary_key}
  name: varchar(255)
  health: int
}
Alpha: { shape: circle }
projects -> Alpha { target-arrowhead.shape: cf-many }

Mermaid flowchart:

flowchart TD
    Alpha[Alpha]
    Beta[Beta]
    Alpha --> Beta

DOT / Graphviz:

digraph G {
  rankdir=LR;
  "Alpha" [label="Alpha"];
  "Beta"  [label="Beta"];
  "Alpha" -> "Beta";
}

Run go run ./examples/basic <format> to see them all.


Installation

go get github.com/larsartmann/go-output

Sub-modules (diagram renderers, progress visualization, etc.) are build-boundary optimizations within this repo — they are NOT independently go get-able. To use them, clone the repo and set up the workspace:

git clone https://github.com/larsartmann/go-output.git
cd go-output
nix run .#setup-workspace   # generates go.work from go.work.example

Then import what you need. The replace directives in each module's go.mod resolve siblings locally. See ADR 009 for the rationale.

What you want Import path
Core types, enums, registries github.com/larsartmann/go-output
CSV + TSV writers github.com/larsartmann/go-output/delimited
JSON + YAML + TOML + JSONL github.com/larsartmann/go-output/serialization
XML + HTML + AsciiDoc github.com/larsartmann/go-output/markup
Terminal tables (lipgloss) github.com/larsartmann/go-output/table
Markdown tables github.com/larsartmann/go-output/markdown
ASCII trees github.com/larsartmann/go-output/tree
D2 diagrams github.com/larsartmann/go-output/d2
DOT + Mermaid github.com/larsartmann/go-output/graph
PlantUML github.com/larsartmann/go-output/plantuml
NOM-style progress github.com/larsartmann/go-output/nom
Bubble Tea TUI github.com/larsartmann/go-output/tui

Supported Formats

Format Table Tree Graph Module Notes
json root Shape-agnostic serialization
yaml serialization Shape-agnostic serialization
toml serialization Shape-agnostic serialization
csv delimited Streaming writer with auto-quoting
tsv delimited Tab-separated with type-switch marshaling
jsonl serialization One JSON object per line
xml markup Structured <table> with XML escaping
html markup Styled tables + collapsible trees
asciidoc markup |=== borders with pipe escaping
markdown root Auto column widths, alignment, bold headers
table table Lipgloss terminal tables with rounded borders
tree root ASCII box-drawing (├──, └──) with color cycling
d2 d2 SQL tables, 20 node shapes, grid layouts, style classes
mermaid graph Flowcharts with 8 node shapes
dot graph Graphviz directed graphs
plantuml plantuml Component diagrams with Table→graph conversion
Data Shape Capabilities
// Check what a format can do
format, _ := output.ParseFormat("d2")
format.Supports(output.ShapeTable) // true (SQL tables)
format.Supports(output.ShapeGraph) // true (node-edge diagrams)
format.Supports(output.ShapeTree)  // false

// Find all formats for a shape
for _, f := range output.FormatsForShape(output.ShapeGraph) {
    fmt.Println(f) // json, yaml, d2, mermaid, dot
}

Table Formats

// Terminal table with lipgloss styling (requires go-output/table)
tbl := table.New()
tbl.SetHeaders("Name", "Health")
tbl.AddRow("Alpha", "90%")
out, _ := tbl.Render()

// Markdown table (requires go-output/markdown)
md := markdown.NewMarkdownTable()
md.SetHeaders([]string{"Name", "Health"})
md.AddRow([]string{"Alpha", "90%"})
out, _ = md.Render()

// JSON table (array of objects — requires go-output/serialization)
jtr := serialization.NewJSONTableRenderer()
jtr.SetHeaders([]string{"Name", "Health"})
jtr.AddRow([]string{"Alpha", "90%"})
out, _ = jtr.Render()
// [{"Name": "Alpha", "Health": "90%"}]

// CSV streaming writer (requires go-output/delimited)
csv := delimited.NewCSVWriter(os.Stdout)
_ = csv.WriteHeader([]string{"Name", "Value"})
_ = csv.WriteRow([]string{"Item", "123"})
csv.Flush()

// HTML table with footer (requires go-output/markup)
ht := markup.NewHTMLRenderer()
ht.SetHeaders([]string{"Name", "Value"})
ht.AddRow([]string{"Item", "123"})
ht.SetFooter([]string{"Total", "1"})
out, _ = ht.Render()

Set Table.Footer for an optional totals/summary row:

data := output.NewTable([]string{"Name", "Score"})
data.AddRow([]string{"Alice", "95"})
data.AddRow([]string{"Bob", "87"})
data.SetFooter([]string{"Total", "182"})
Format Footer Behavior
table Bold-styled footer row
markdown Second separator + bold footer row
csv / tsv Appended as last data row
html <tfoot> section with footer-cell class
xml <footer> element
asciidoc Footer row cells
json / yaml / toml / jsonl Data serialization — footer skipped
tree / d2 / mermaid / dot / plantuml Non-tabular formats

Tree Formats

root := output.NewTreeNode("root", "Projects")
root.AddChild(output.NewTreeNode("alpha", "Alpha"))
root.AddChild(output.NewTreeNode("beta", "Beta"))

// ASCII tree with box-drawing chars (requires go-output/tree)
tree := tree.NewASCIITreeRenderer()
tree.SetRoot(root)
tree.SetColorMode(output.ColorModeAuto)
out, _ := tree.Render()
// Projects
// ├── Alpha
// └── Beta

// JSON tree (requires go-output/serialization)
jtr := serialization.NewJSONTreeRenderer()
jtr.SetRoot(root)
out, _ = jtr.Render()

// HTML collapsible tree (requires go-output/markup)
ht := markup.NewHTMLTreeRenderer()
ht.SetRoot(root)
out, _ = ht.Render()

Graph / Diagram Formats

import (
    "github.com/larsartmann/go-output"
    "github.com/larsartmann/go-output/d2"
    "github.com/larsartmann/go-output/graph"
)

nodes := []output.GraphNode{
    output.NewGraphNode("a", "API Gateway"),
    output.NewGraphNode("b", "Backend"),
}
edges := []output.GraphEdge{
    output.NewGraphEdge("a", "b"),
}

// DOT / Graphviz (requires go-output/graph)
dot := graph.NewDOTRenderer()
dot.SetNodes(nodes)
dot.SetEdges(edges)
out, _ := dot.Render()

// Mermaid flowchart (requires go-output/graph)
mmd := graph.NewMermaidRenderer()
mmd.SetNodes(nodes)
mmd.SetEdges(edges)
out, _ = mmd.Render()

// D2 diagrams with SQL tables (requires go-output/d2)
diagram := d2.NewDiagram().
    AddNodeWithShape("api", "API Gateway", d2.D2ShapeHexagon).
    AddEdgeSimple("api", "backend")
out, _ = diagram.Render()
D2 Advanced Features

SQL tables, constraints, grid layouts, and nested containers:

table := d2.D2Table{
    Name: "users",
    Columns: []d2.D2Column{
        {Name: "id", Type: "INT", Constraint: d2.D2ConstraintPrimary},
        {Name: "email", Type: "VARCHAR(255)", Constraint: d2.D2ConstraintUnique},
    },
}

node := d2.Node{
    ID:          output.NewBrandedID[output.D2NodeIDBrand]("dashboard"),
    Label:       output.NewBrandedID[output.D2NodeLabelBrand]("Dashboard"),
    GridRows:    3,
    GridColumns: 2,
}
Cross-Shape Conversion

Convert between data shapes without rewriting your data:

// Table → Graph (auto-generates edges between consecutive rows)
dot := graph.NewDOTFromTable(data)
mmd := graph.NewMermaidFromTable(data)
plantuml := plantuml.NewPlantUMLFromTable(data)
d2Diagram := d2.NewD2FromTable(data)

// Table → Tree (hierarchical from tabular data; requires go-output/tree)
tree := tree.NewTreeRendererFromTable(data)

// Tree → Graph
d2Diagram := d2.NewD2FromTree(root)
dot := graph.NewDOTFromTree(root)
mmd := graph.NewMermaidFromTree(root)
plantuml := plantuml.NewPlantUMLFromTree(root)

NOM Real-Time Progress

Track and visualize long-running workflows with dependency trees, inspired by nix-output-monitor.

Static rendering (one-shot)

Build state, take a snapshot, render once — useful for logs, CI output, or testing:

import (
    "context"
    "fmt"
    "time"

    "github.com/larsartmann/go-output/nom"
)

ctx := context.Background()
sub := nom.NewNOMSubscriber()

// Fire lifecycle events
sub.OnEvent(ctx, nom.WorkflowStarted{
    ID:   nom.NewWorkflowID("build"),
    Name: nom.NewWorkflowName("Build Project"),
})
sub.OnEvent(ctx, nom.ActivityStarted{
    ID:   nom.NewActivityID("compile"),
    Name: nom.NewActivityName("Compile"),
})
sub.OnEvent(ctx, nom.ActivityStarted{
    ID:   nom.NewActivityID("test"),
    Name: nom.NewActivityName("Run Tests"),
    Deps: []nom.ActivityID{nom.NewActivityID("compile")},
})
sub.OnEvent(ctx, nom.ActivityCompleted{
    ID:       nom.NewActivityID("compile"),
    Name:     nom.NewActivityName("Compile"),
    Duration: 5 * time.Second,
})

// Render a snapshot of the current state
snaps := sub.SnapshotActivities()
fmt.Println(sub.DependencyTree().RenderWithSnapshots(snaps, 20, 0))

// O(1) summary counts
counts := sub.GetActivityCounts()
fmt.Printf("Running: %d, Completed: %d, Failed: %d, Pending: %d\n",
    counts.Running, counts.Completed, counts.Failed, counts.Pending)

sub.OnEvent(ctx, nom.WorkflowCompleted{ID: nom.NewWorkflowID("build")})
Live inline rendering (terminal)

For real-time updates, the InlineRenderer redraws the tree in-place using ANSI escape codes. Events typically arrive from concurrent workers:

sub := nom.NewNOMSubscriber()
renderer := nom.NewInlineRenderer(sub, os.Stdout, 20) // maxHeight 20 lines

ctx := context.Background()
renderer.Start(ctx, 100*time.Millisecond) // redraw every 100ms
defer renderer.Finish(nil)

// In your workers (goroutines):
sub.OnEvent(ctx, nom.ActivityStarted{
    ID:   nom.NewActivityID("compile"),
    Name: nom.NewActivityName("Compile"),
})
// ... do work ...
sub.OnEvent(ctx, nom.ActivityCompleted{
    ID:       nom.NewActivityID("compile"),
    Name:     nom.NewActivityName("Compile"),
    Duration: 5 * time.Second,
})
NOM Features
  • Dependency trees — Hierarchical parent/child relationships with UTF-8 box-drawing
  • O(1) activity counts — Summary bar updates in constant time, even with 10,000+ activities
  • Timing cache — Persists duration history to ~/.cache/nom-timing.csv for ETA estimates
  • Progress sub-stepsActivityProgress events render a dim → message sub-line beneath each activity
  • Retry visibilityActivityRetrying events transition failed activities back to running, rendering ⟳N (reason)
  • Estimated remaining timeEstimatedTotalRemaining() powers a ~Xm left summary segment from per-activity estimates
  • Snapshot-based rendering — Race-free: renderers read immutable value copies, not shared pointers
  • CI-safe degradation — Auto-detects CI environments; appends frames line-by-line instead of ANSI cursor codes
  • Height-pressure collapse — When the tree exceeds maxHeight, completed children collapse with a ⋯ N completed marker
  • Per-activity progress — Download bars (▕████░░░░▏ 45%) and host tags (@host)
  • Node classes — Root/twig/leaf styling with bold roots for top-level visibility
NOM Diagram Export

Export live NOM state as DOT or Mermaid diagrams:

// Project subscriber state into graph nodes/edges
reader := sub.Store()
nodes := reader.Nodes()
edges := reader.Edges()

// Render as DOT
dot := graph.NewDOTRenderer()
dot.SetNodes(nodes)
dot.SetEdges(edges)
out, _ := dot.Render()

Bubble Tea TUI

Full-screen interactive TUI built on Bubble Tea v2, with two display modes:

import (
    "context"

    "github.com/larsartmann/go-output/nom"
    "github.com/larsartmann/go-output/tui"
)

reporter := tui.NewBubbleTeaProgressReporter()
reporter.SetDisplayMode(tui.DisplayModeNOM) // or DisplayModeUniversal

// Optional: wire ctrl+c to cancel a context
ctx, cancel := context.WithCancel(context.Background())
reporter.SetCancelFunc(cancel)

// Report progress
reporter.ReportStep(1, 5, "Building...")
reporter.ReportProgress(0.35)
reporter.ReportMessage("Compiling module Alpha")

// Or drive via NOM events
reporter.Subscriber().OnEvent(ctx, nom.ActivityStarted{
    ID:   nom.NewActivityID("a1"),
    Name: nom.NewActivityName("Build"),
})

reporter.Start()
defer reporter.Stop()
TUI Controls
Key Action
j / Scroll down
k / Scroll up
pgdown Scroll half page down
pgup Scroll half page up
g / Home Jump to top
G / End Jump to bottom
? Toggle help overlay
q Quit (workflow continues)
ctrl+c Cancel workflow and quit
Mouse Wheel scroll, click to select nodes

Type-Safe Enums

All configuration types provide validation and conversion:

format, err := output.ParseFormat("json")
if format.IsValid() {
    fmt.Println(format.String())      // "json"
    fmt.Println(format.Shapes())     // [table tree graph]
}
allowed := format.AllowedValues() // ["table", "json", "csv", ...]

Available enums: Format (16 values), Shape (3 values), ColorMode (auto/always/never), NodeShape (8 shapes), D2NodeShape (20 shapes), D2ArrowType (11 types), D2Constraint (3 constraints), Alignment (left/right/center).


Branded IDs

Phantom types prevent mixing different ID types at compile time:

nodeID := output.NewBrandedID[output.D2NodeIDBrand]("node-1")
treeID := output.NewBrandedID[output.TreeNodeIDBrand]("root")

// nodeID = treeID  // COMPILE ERROR: different branded types

Define your own:

type ProjectIDBrand struct{}
projectID := output.NewBrandedID[ProjectIDBrand]("proj-123")

Color Modes

All terminal renderers support ColorMode for controlling ANSI output:

// Terminal table: functional option
tbl := table.New(table.WithColorMode(output.ColorModeAlways))

// ASCII tree: setter
tree := tree.NewASCIITreeRenderer()
tree.SetColorMode(output.ColorModeAlways)

// Markdown: chaining setter
md := markdown.NewMarkdownTable().SetColorMode(output.ColorModeAlways)

// Unified dispatch: RenderOptions
output.RenderTable(data, output.FormatTable,
    output.RenderOptions{ColorMode: output.ColorModeAuto})

ColorModeAuto detects TTY via golang.org/x/term, respects NO_COLOR, CI, GITHUB_ACTIONS, GITLAB_CI, JENKINS_URL, BUILDKITE, GO_OUTPUT_FORCE_COLOR, FORCE_COLOR.


Streaming Renderer

For large datasets, stream output incrementally:

data := output.NewTable([]string{"Name", "Value"})
data.AddRow([]string{"Item", "123"})

renderer := markup.NewStreamingHTMLRenderer()
renderer.SetData(data)
_ = renderer.Stream(os.Stdout)

Escape Functions

The escape/ subpackage provides safe escaping for each format:

import "github.com/larsartmann/go-output/escape"

safe := escape.HTML("<script>alert('xss')</script>")
safeID := escape.D2("my-node.with.dots")
Function Purpose
escape.HTML HTML special characters
escape.XML XML special characters
escape.D2 D2 diagram identifiers
escape.DOT DOT graph identifiers
escape.MermaidID Mermaid node IDs
escape.MermaidText Mermaid labels
escape.MermaidSlug Mermaid slug fallback
escape.PlantUML PlantUML labels
escape.SlugifyID Cross-format diagram identifiers

Examples

Run the basic example to see all 16 formats:

go run ./examples/basic markdown              # auto color
go run ./examples/basic tree --color always    # force colors
go run ./examples/basic table --color never    # no colors

Other examples:

Example What it demonstrates
examples/basic/ All 16 formats with a Project dataset
examples/nom_progress/ NOM workflow events, dependency trees, activity counts
examples/tui_progress/ Bubble Tea TUI with step reporting and NOM display mode
examples/d2/ D2 microservice architecture with SQL tables and shapes
examples/diagram_export/ Export NOM live state as DOT/Mermaid diagrams

Development

nix develop                    # Enter dev shell (Go 1.26, golangci-lint, gopls)
nix run .#build              # Build all 20 modules
nix run .#test               # Test all 20 modules
nix run .#test-race          # Race-test nom + tui
nix run .#lint               # golangci-lint across all modules
nix run .#tidy               # go mod tidy all modules
nix run .#govulncheck        # Vulnerability scan
nix fmt                      # Format .nix files
nix flake check              # Formatting + pre-commit hooks
Go toolchain
go build ./...                  # Build all workspace modules
go test ./...                   # Test all modules
go test -race ./...             # Race detector
go test -cover ./...            # Coverage report
golangci-lint run --fix ./...   # Lint

API Stability

This library is pre-v1. The following guarantees apply:

  • Root module (github.com/larsartmann/go-output): Public API is stable. Breaking changes documented in CHANGELOG.md.
  • Sub-modules (d2, graph, table, nom, tui, etc.): May evolve independently. Import them explicitly to opt in.
  • Renderer interface: Stable — all formats implement Render() (string, error).
Frozen Interfaces (v1 locked)
Interface Methods Implementations
Renderer Render() (string, error) All 16 formats
TableRenderer SetHeaders([]string), AddRow([]string), Render() JSON, YAML, TOML, JSONL, HTML, Streaming HTML, AsciiDoc, Markdown, Table
TreeRenderer SetRoot(*TreeNode), Render() ASCII Tree, JSON Tree, YAML Tree, TOML Tree, HTML Tree
GraphRenderer SetNodes([]GraphNode), SetEdges([]GraphEdge), Render() D2, DOT, Mermaid, PlantUML, JSON Graph, YAML Graph, TOML Graph
StreamingRenderer Stream(io.Writer) error, Render() Streaming HTML

Non-breaking changes until v1: adding new formats, shapes, methods, sub-modules, and renderers.


Architecture

20 modules in a multi-module Go workspace. The root package has zero imports of any sub-module — this is the load-bearing architectural guarantee.

  • go get go-output pulls no lipgloss, bubbletea, yaml, d2, graph, table, nom, or tui deps.
  • Sub-modules self-register into root's registries via their own init().
  • Import a sub-module to activate its renderers automatically.

Read docs/FORMAT_ARCHITECTURE.md, docs/DOMAIN_LANGUAGE.md, and docs/adr/ for the full design.


Migration from v0.23.x

Type renames
Old New
output.TableData output.Table
output.TableDataStore output.TableStore
output.GraphStyle output.NodeStyle
output.GraphRendererState output.GraphBuilder
output.TreeOutputRenderer output.TreeRenderer
nom.NOMStyleSubscriber nom.NOMSubscriber
d2.D2Diagram d2.Diagram
All d2.D2Xxx types d2.Xxx (prefix dropped)
Function renames
Old New
output.RenderTableData() output.RenderTable()
nom.NewNOMStyleSubscriber nom.NewNOMSubscriber
All XxxFromTableData() NewXxxFromTable()

The old mutable renderer structs still work but are now implementation detail. The canonical API is pure functions:

// Old (still works)
r := graph.NewDOTFromTable(data)
out, _ := r.Render()

// New CQRS (recommended)
g := output.TableToGraph(data)
out, _ := graph.RenderDOT(g)
Deleted symbols

NodeShapeRect, EdgeStyle.ArrowHead/.ArrowTail, nom.ErrActivityNotFound, nom.TimingFormat, nom.Activity.IsPhase(), StreamingRendererFromRenderer() — see CHANGELOG.md for the full list.

Registry dispatch now streams (trailing-newline change)

output.RenderTable(data, output.FormatJSON, opts) now uses the same streaming encoders as the CQRS API (json.NewEncoder, yaml.NewEncoder, etc.). The standard encoders append a trailing \n that the old MarshalIndent + Fprint path did not.

If you do exact-byte output comparison (e.g., golden-file tests in your project), add strings.TrimSpace(result) or update your golden files to include the trailing newline. This affects JSON, YAML, TOML, JSONL, CSV, TSV, and XML.


Contributing

See CONTRIBUTING.md. The full version history is in CHANGELOG.md, long-term direction in ROADMAP.md.


License

MIT

Documentation

Overview

Package output provides a reusable Go library for CLI applications offering consistent output formatting across 16 formats (Table, JSON, CSV, TSV, Markdown, XML, YAML, HTML, Tree, D2, Mermaid, DOT, JSONL, AsciiDoc, TOML, PlantUML) with type-safe enum-based configuration and a Shape capability matrix.

Quick Start

Use Table as the single source of truth for tabular data:

data := output.NewTable([]string{"Name", "Status"})
data.AddRow([]string{"Project A", "Active"})
data.Footer = []string{"Total", "1"}

Render to any supported format:
var buf bytes.Buffer
_ = output.RenderTable(data, output.FormatMarkdown, output.RenderOptions{Writer: &buf})

Architecture

The library uses a multi-module workspace where each format family is an independent Go module. Users import only the formats they need:

import "github.com/larsartmann/go-output"                     // core + Markdown + Tree
import "github.com/larsartmann/go-output/table"               // terminal tables (optional)
import "github.com/larsartmann/go-output/serialization"       // JSON + YAML + TOML + JSONL (optional)
import "github.com/larsartmann/go-output/delimited"           // CSV + TSV (optional)
import "github.com/larsartmann/go-output/markup"              // XML + HTML + AsciiDoc (optional)
import "github.com/larsartmann/go-output/d2"                  // D2 diagrams (optional)
import "github.com/larsartmann/go-output/graph"               // DOT + Mermaid (optional)
import "github.com/larsartmann/go-output/plantuml"            // PlantUML diagrams (optional)

The root module has ZERO imports from sub-modules, ensuring users get zero transitive dependencies from formats they don't use.

Index

Examples

Constants

This section is empty.

Variables

AllFormats contains all valid output format values. Use this for testing AllowedValues or generating help text.

View Source
var AllShapes = []Shape{
	ShapeTable,
	ShapeTree,
	ShapeGraph,
}

AllShapes contains all valid data shape values.

View Source
var CIEnvVars = []string{
	"CI",
	"GITHUB_ACTIONS",
	"GITLAB_CI",
	"JENKINS_URL",
	"BUILDKITE",
}

CIEnvVars are the environment variables that indicate execution inside a common CI provider. Exported so tests and downstream code can keep their own list of "known suppressors" aligned with this one.

Functions

func AddTreeNodes

func AddTreeNodes(
	a NodeEdgeAppender,
	node *TreeNode, parentID string,
	idFunc TreeNodeIDFunc, shape NodeShape,
)

AddTreeNodes recursively adds tree nodes and edges to the provided appender.

func ContainsEnum added in v0.18.0

func ContainsEnum[T comparable](values []T, v T) bool

ContainsEnum checks if a value is in the list of allowed values.

func DefaultGraphNodeLabel

func DefaultGraphNodeLabel(header, cell string) string

DefaultGraphNodeLabel returns a label in the format "header: cell".

func EnumAllowedStrings added in v0.18.0

func EnumAllowedStrings[T any](values []T, toString func(T) string) []string

EnumAllowedStrings returns the string representations of all enum values.

func EnumAllowedValues added in v0.18.0

func EnumAllowedValues[T StringEnum](values []T) []string

EnumAllowedValues returns the string representations of all enum values for types that implement the StringEnum interface.

func IsCI added in v0.18.0

func IsCI() bool

IsCI reports whether the process appears to be running inside a CI provider by checking the well-known CI environment variables.

func IsNoColor added in v0.18.0

func IsNoColor() bool

IsNoColor reports whether color output should be suppressed per the NO_COLOR convention (https://no-color.org) or a TERM=dumb terminal.

func MarshalJSONIndent

func MarshalJSONIndent(v any, prefix, indent string) ([]byte, error)

MarshalJSONIndent encodes v to indented JSON.

func NewBrandedID

func NewBrandedID[Brand any](value string) id.ID[Brand, string]

NewBrandedID creates a new branded ID from a string value.

func ParseEnum added in v0.18.0

func ParseEnum[T comparable](values []T, s string, toString func(T) string) (T, error)

ParseEnum parses a string into an enum value, returning an error if invalid.

func RegisterFormatShapes added in v0.7.0

func RegisterFormatShapes(format Format, shapes ...Shape)

RegisterFormatShapes registers the data shapes a format supports. Sub-modules call this from their init() to declare capabilities.

func RegisterTableMarshaler added in v0.30.0

func RegisterTableMarshaler(format Format, renderer TableMarshaler)

RegisterTableMarshaler registers a renderer for a format. Sub-modules call this from their init() to enable RenderTable dispatch.

func RegisterUnknownRenderer added in v0.30.0

func RegisterUnknownRenderer(format Format, renderer UnknownRenderer)

RegisterUnknownRenderer registers a renderer for arbitrary (non-Table) data. Sub-modules call this from their init() to enable RenderUnknown dispatch.

func RenderTable added in v0.30.0

func RenderTable(data *Table, format Format, opts RenderOptions) error

RenderTable renders Table in the given format and writes to w (or os.Stdout). It supports all registered formats when respective sub-modules are imported. With all sub-modules imported, all 16 formats are available: table, json, csv, tsv, markdown, xml, d2, yaml, html, tree, mermaid, dot, jsonl, asciidoc, toml, plantuml.

func RenderUnknown added in v0.30.0

func RenderUnknown(data any, format Format, opts RenderOptions) error

RenderUnknown renders arbitrary data in the given format and writes to w (or os.Stdout). Supports formats that registered an UnknownRenderer (typically JSON, YAML, TOML). Returns UnsupportedFormatError if no renderer is registered for the format.

func TreeToRenderer added in v0.19.0

func TreeToRenderer[R, ID any](
	newRenderer func() R,
	addNodes func(R, *TreeNode, ID),
	root *TreeNode,
) R

TreeToRenderer runs the standard tree entrypoint prelude used by every sub-module's public XFromTree: instantiate the renderer, skip population when root is nil, otherwise call the renderer-specific addNodes with the given zero parent ID (every XFromTree starts a fresh subtree). Centralising this in root means each sub-module's entrypoint is a single line and the constructor + nil-check + populate prelude lives in exactly one place.

The ID type is renderer-specific: graph and plantuml use TreeNodeID; d2 and mermaid use plain string. Both shapes share this single generic helper.

Types

type BrandedID

type BrandedID[Brand any] = id.ID[Brand, string]

BrandedID re-export for backward compatibility. Use id.ID[Brand, string] for new code.

type ColorMode

type ColorMode string

ColorMode controls terminal color output.

Example
package main

import (
	"fmt"

	"github.com/larsartmann/go-output"
)

func main() {
	fmt.Println(output.ColorModeAuto.ShouldColor())
	fmt.Println(output.ColorModeNever.ShouldColor())
}
Output:
false
false
const (
	ColorModeAuto   ColorMode = "auto"
	ColorModeAlways ColorMode = "always"
	ColorModeNever  ColorMode = "never"
)

Terminal color output modes.

func ParseColorMode

func ParseColorMode(s string) (ColorMode, error)

ParseColorMode converts a string to ColorMode, returning an error if invalid.

func (ColorMode) AllowedValues

func (c ColorMode) AllowedValues() []string

AllowedValues returns all valid color mode values.

func (ColorMode) IsValid

func (c ColorMode) IsValid() bool

IsValid checks if the color mode is valid.

func (ColorMode) ShouldColor

func (c ColorMode) ShouldColor() bool

ShouldColor returns true if colors should be enabled.

func (ColorMode) String

func (c ColorMode) String() string

String returns the string representation of the color mode.

type D2NodeID

type D2NodeID = id.ID[D2NodeIDBrand, string]

D2NodeID is a branded identifier for D2 diagram nodes. Canonical import path: github.com/larsartmann/go-output (root). The d2 module re-exports this as d2.D2NodeID for convenience.

type D2NodeIDBrand

type D2NodeIDBrand struct{}

D2NodeIDBrand is the brand type for D2 node IDs. Lives in root so the type alias D2NodeID is visible to both root and d2/ callers without a circular import. Split-brain finding m6: root is the canonical home; d2.D2NodeID is a convenience re-export (type alias), not a second definition.

type D2NodeLabel

type D2NodeLabel = id.ID[D2NodeLabelBrand, string]

D2NodeLabel is a branded identifier for D2 diagram node labels. Canonical import path: github.com/larsartmann/go-output (root). The d2 module re-exports this as d2.D2NodeLabel for convenience.

type D2NodeLabelBrand

type D2NodeLabelBrand struct{}

D2NodeLabelBrand is the brand type for D2 node labels. See D2NodeIDBrand for why brand types live in root rather than d2/.

type Direction added in v0.13.0

type Direction string

Direction represents a canonical layout direction for diagrams. It bridges D2 vocabulary ("down"/"right") and DOT vocabulary ("TB"/"LR") through a single canonical type.

const (
	DirectionDown  Direction = "down"
	DirectionUp    Direction = "up"
	DirectionLeft  Direction = "left"
	DirectionRight Direction = "right"
)

func (Direction) IsValid added in v0.18.0

func (d Direction) IsValid() bool

IsValid reports whether d is a recognized Direction.

func (Direction) ToD2Direction added in v0.13.0

func (d Direction) ToD2Direction() string

ToD2Direction converts Direction to D2's direction string. D2 uses "" for default (down), "right", "left", "up".

Returns a string (not d2.Direction) because root cannot import the d2/ sub-module (Core Invariant). The d2/ module's own D2Direction type is a separate type for D2-specific APIs; use output.Direction for cross-format portability and convert at the d2/ call site.

func (Direction) ToRankDir added in v0.13.0

func (d Direction) ToRankDir() string

ToRankDir converts Direction to DOT's rankdir string. DOT uses "TB" (top-to-bottom), "LR" (left-to-right), "BT", "RL".

type EdgeStyle

type EdgeStyle struct {
	// Color is the edge line color.
	Color string
	// Line is the line style (solid, dashed, dotted).
	Line LineStyle
}

EdgeStyle represents styling attributes for an edge.

type Format

type Format string

Format represents the available output format options for CLI applications.

const (
	FormatTable    Format = "table"
	FormatJSON     Format = "json"
	FormatCSV      Format = "csv"
	FormatTSV      Format = "tsv"
	FormatMarkdown Format = "markdown"
	FormatXML      Format = "xml"
	FormatD2       Format = "d2"
	FormatYAML     Format = "yaml"
	FormatHTML     Format = "html"
	FormatTree     Format = "tree"
	FormatMermaid  Format = "mermaid"
	FormatDOT      Format = "dot"
	FormatJSONL    Format = "jsonl"
	FormatAsciiDoc Format = "asciidoc"
	FormatTOML     Format = "toml"
	FormatPlantUML Format = "plantuml"
)

Output format constants.

func FormatsForShape added in v0.5.0

func FormatsForShape(s Shape) []Format

FormatsForShape returns all formats that support the given data shape.

func ParseFormat

func ParseFormat(s string) (Format, error)

ParseFormat converts a string to Format, returning an error if invalid.

Example
package main

import (
	"fmt"
	"os"

	"github.com/larsartmann/go-output"
)

func main() {
	f, err := output.ParseFormat("json")
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
	}

	fmt.Println(f)
}
Output:
json

func RegisteredTableMarshalFormats added in v0.30.0

func RegisteredTableMarshalFormats() []Format

RegisteredTableMarshalFormats returns all formats with registered TableMarshalers.

func RegisteredUnknownFormats added in v0.30.0

func RegisteredUnknownFormats() []Format

RegisteredUnknownFormats returns all formats with registered UnknownRenderers.

func (Format) AllowedValues

func (f Format) AllowedValues() []string

AllowedValues returns all valid output format values for CLI help text.

func (Format) IsValid

func (f Format) IsValid() bool

IsValid returns true if the format is a valid Format value.

Example
package main

import (
	"fmt"

	"github.com/larsartmann/go-output"
)

func main() {
	fmt.Println(output.FormatCSV.IsValid())
	fmt.Println(output.Format("unknown").IsValid())
}
Output:
true
false

func (Format) Shapes added in v0.5.0

func (f Format) Shapes() []Shape

Shapes returns all data shapes this format supports.

func (Format) String

func (f Format) String() string

String returns the string representation of the format.

func (Format) Supports added in v0.5.0

func (f Format) Supports(s Shape) bool

Supports returns true if the format can render the given data shape.

type Graph added in v0.30.0

type Graph struct {
	// contains filtered or unexported fields
}

Graph is an immutable snapshot of graph data — the frozen result of GraphBuilder.Build(). It is safe for concurrent access and can be passed to multiple renderers (DOT, Mermaid, D2, PlantUML) without copying or locking.

func TableToGraph added in v0.30.0

func TableToGraph(data *Table, opts ...TableToGraphOption) Graph

TableToGraph converts a Table into a Graph by creating one node per row and connecting consecutive rows with directed edges. Each node is labeled with all cell values from its row. This is a pure projection — the input Table is not modified.

Example
package main

import (
	"fmt"

	"github.com/larsartmann/go-output"
)

func main() {
	tbl := output.NewTableBuilder().
		SetHeaders("Step").
		AddRow("Fetch").
		AddRow("Build").
		AddRow("Deploy").
		Build()

	g := output.TableToGraph(tbl)

	fmt.Printf("%d nodes\n", len(g.Nodes()))
}

func (Graph) Edges added in v0.30.0

func (g Graph) Edges() []GraphEdge

Edges returns the graph edges (read-only).

func (Graph) Nodes added in v0.30.0

func (g Graph) Nodes() []GraphNode

Nodes returns the graph nodes (read-only).

type GraphBuilder added in v0.30.0

type GraphBuilder struct {
	// contains filtered or unexported fields
}

GraphBuilder contains shared fields and methods for graph renderers.

D2 does not use this mixin because it has richer domain-specific types (D2Node, D2Edge with classes, SQL tables, shapes, arrow types, etc.) that do not map to the simpler GraphNode/GraphEdge model.

func NewGraphBuilder added in v0.30.0

func NewGraphBuilder() GraphBuilder

NewGraphBuilder creates a new GraphBuilder with initialized slices.

Example
package main

import (
	"fmt"

	"github.com/larsartmann/go-output"
)

func main() {
	b := output.NewGraphBuilder()
	b.AddNode(*output.NewGraphNode("a", "Alpha"))
	b.AddNode(*output.NewGraphNode("b", "Beta"))
	b.AddEdge(*output.NewGraphEdge("a", "b"))

	g := b.Build()

	fmt.Printf("%d nodes, %d edges\n", len(g.Nodes()), len(g.Edges()))
}

func (*GraphBuilder) AddEdge added in v0.30.0

func (m *GraphBuilder) AddEdge(edge GraphEdge)

AddEdge appends an edge to the graph.

func (*GraphBuilder) AddNode added in v0.30.0

func (m *GraphBuilder) AddNode(node GraphNode)

AddNode appends a node to the graph.

func (*GraphBuilder) AddRowEdges added in v0.30.0

func (m *GraphBuilder) AddRowEdges(data *Table)

AddRowEdges adds edges from data.CreateRowEdges() to the graph.

func (*GraphBuilder) Build added in v0.30.0

func (m *GraphBuilder) Build() Graph

Build freezes the builder's state into an immutable Graph. After Build, the Graph is a snapshot — further mutations to the builder do not affect it.

func (*GraphBuilder) DedupEdges added in v0.30.0

func (m *GraphBuilder) DedupEdges()

DedupEdges removes duplicate edges in-place. Two edges are considered duplicates if they share the same From and To node IDs. The first occurrence is kept; subsequent duplicates are silently discarded. Edges with different labels between the same node pair are also considered duplicates — if you need parallel edges, do not call this method.

func (*GraphBuilder) Edges added in v0.30.0

func (m *GraphBuilder) Edges() []GraphEdge

Edges returns the graph edges.

func (*GraphBuilder) Nodes added in v0.30.0

func (m *GraphBuilder) Nodes() []GraphNode

Nodes returns the graph nodes.

func (*GraphBuilder) SetEdges added in v0.30.0

func (m *GraphBuilder) SetEdges(edges []GraphEdge)

SetEdges sets the graph edges.

func (*GraphBuilder) SetNodes added in v0.30.0

func (m *GraphBuilder) SetNodes(nodes []GraphNode)

SetNodes sets the graph nodes.

func (*GraphBuilder) SetNodesFromTable added in v0.30.0

func (m *GraphBuilder) SetNodesFromTable(
	data *Table,
	modifyNode func(i int, n *GraphNode),
)

SetNodesFromTable creates nodes from Table, applies per-node modifications, adds them to the graph, and adds row edges.

type GraphEdge

type GraphEdge struct {
	// From is the source node ID.
	From GraphNodeID
	// To is the target node ID.
	To GraphNodeID
	// Label is the optional display text on the edge.
	Label GraphNodeLabel
	// Style contains optional visual styling attributes.
	Style EdgeStyle
}

GraphEdge represents an edge between two nodes.

func NewGraphEdge

func NewGraphEdge(from, to string) *GraphEdge

NewGraphEdge creates a new GraphEdge.

type GraphNode

type GraphNode struct {
	// ID is the unique identifier for the node.
	ID GraphNodeID
	// Label is the display text for the node.
	Label GraphNodeLabel
	// Shape defines the visual shape (box, ellipse, diamond, etc.).
	Shape NodeShape
	// Style contains optional visual styling attributes.
	Style NodeStyle
	// Metadata holds arbitrary key-value pairs for custom data.
	Metadata map[string]string
}

GraphNode represents a node in a graph.

func NewGraphNode

func NewGraphNode(id, label string) *GraphNode

NewGraphNode creates a new GraphNode.

func NodesFromTable added in v0.30.0

func NodesFromTable(data *Table, labelFn GraphNodeLabelFunc) []GraphNode

NodesFromTable creates GraphNodes from Table using the provided label function.

type GraphNodeID

type GraphNodeID = id.ID[GraphNodeIDBrand, string]

GraphNodeID is a branded identifier for graph nodes.

type GraphNodeIDBrand

type GraphNodeIDBrand struct{}

GraphNodeIDBrand is the brand type for graph node IDs.

type GraphNodeLabel

type GraphNodeLabel = id.ID[GraphNodeLabelBrand, string]

GraphNodeLabel is a branded identifier for graph node labels.

type GraphNodeLabelBrand

type GraphNodeLabelBrand struct{}

GraphNodeLabelBrand is the brand type for graph node labels.

type GraphNodeLabelFunc

type GraphNodeLabelFunc func(header, cell string) string

GraphNodeLabelFunc is a function that formats a cell value with its header into a label.

type GraphRenderer

type GraphRenderer interface {
	Renderer
	// SetNodes sets the graph nodes.
	SetNodes(nodes []GraphNode)
	// SetEdges sets the graph edges.
	SetEdges(edges []GraphEdge)
}

GraphRenderer defines the interface for graph format renderers.

type InvalidColorModeError added in v0.5.0

type InvalidColorModeError struct {
	Value string
}

InvalidColorModeError is returned when an invalid color mode is provided.

func (*InvalidColorModeError) Error added in v0.5.0

func (e *InvalidColorModeError) Error() string

Error returns a descriptive error message for the invalid color mode.

type InvalidFormatError

type InvalidFormatError struct {
	Value   string
	Allowed []Format
}

InvalidFormatError represents an invalid format error.

func (*InvalidFormatError) Error

func (e *InvalidFormatError) Error() string

Error returns a descriptive error message including allowed values.

type InvalidLineStyleError added in v0.18.0

type InvalidLineStyleError struct {
	Value   string
	Allowed []LineStyle
}

InvalidLineStyleError is returned when an invalid line style is provided.

func (*InvalidLineStyleError) Error added in v0.18.0

func (e *InvalidLineStyleError) Error() string

Error returns a descriptive error message for the invalid line style.

type InvalidNodeShapeError added in v0.13.0

type InvalidNodeShapeError struct {
	Value string
}

InvalidNodeShapeError is returned when an invalid graph shape is provided.

func (*InvalidNodeShapeError) Error added in v0.13.0

func (e *InvalidNodeShapeError) Error() string

Error returns a descriptive error message for the invalid graph shape.

type InvalidShapeError added in v0.5.0

type InvalidShapeError struct {
	Value   string
	Allowed []Shape
}

InvalidShapeError is returned when an invalid data shape is provided.

func (*InvalidShapeError) Error added in v0.5.0

func (e *InvalidShapeError) Error() string

Error returns a descriptive error message for the invalid shape.

type LineStyle added in v0.13.0

type LineStyle string

LineStyle represents the visual style of a line (edge).

const (
	LineStyleSolid  LineStyle = "solid"
	LineStyleDashed LineStyle = "dashed"
	LineStyleDotted LineStyle = "dotted"
)

func ParseLineStyle added in v0.18.0

func ParseLineStyle(s string) (LineStyle, error)

ParseLineStyle converts a string to LineStyle, returning an error if invalid.

func (LineStyle) AllowedValues added in v0.18.0

func (l LineStyle) AllowedValues() []string

AllowedValues returns all valid line style values for CLI help text.

func (LineStyle) IsValid added in v0.13.0

func (l LineStyle) IsValid() bool

IsValid returns true if the LineStyle is a recognized value.

func (LineStyle) String added in v0.13.0

func (l LineStyle) String() string

String returns the string representation of the LineStyle.

type NodeEdgeAppender added in v0.7.0

type NodeEdgeAppender interface {
	AddNode(node GraphNode)
	AddEdge(edge GraphEdge)
}

NodeEdgeAppender is implemented by types that can add nodes and edges.

type NodeShape added in v0.13.0

type NodeShape string

NodeShape represents the shape of a graph node.

const (
	NodeShapeBox           NodeShape = "box"
	NodeShapeEllipse       NodeShape = "ellipse"
	NodeShapeDiamond       NodeShape = "diamond"
	NodeShapeCircle        NodeShape = "circle"
	NodeShapeCylinder      NodeShape = "cylinder"
	NodeShapeHexagon       NodeShape = "hexagon"
	NodeShapeParallelogram NodeShape = "parallelogram"
)

NodeShape constants define the available shapes for graph nodes.

func ParseNodeShape added in v0.13.0

func ParseNodeShape(s string) (NodeShape, error)

ParseNodeShape converts a string to NodeShape, returning an error if invalid.

func (NodeShape) AllowedValues added in v0.13.0

func (s NodeShape) AllowedValues() []string

AllowedValues returns all valid graph shape values.

func (NodeShape) IsValid added in v0.13.0

func (s NodeShape) IsValid() bool

IsValid checks if the graph shape is valid.

func (NodeShape) String added in v0.13.0

func (s NodeShape) String() string

String returns the string representation of the graph shape.

type NodeStyle added in v0.30.0

type NodeStyle struct {
	// Fill is the background color (e.g., "#f9f9f9").
	Fill string
	// Stroke is the border color.
	Stroke string
	// FontColor is the text color.
	FontColor string
	// FontSize is the text size in points.
	FontSize int
}

NodeStyle represents styling attributes for a graph node.

type ParseError added in v0.18.0

type ParseError struct {
	Value  string
	Values []string
}

ParseError represents a parse error for enum types.

func (*ParseError) Error added in v0.18.0

func (e *ParseError) Error() string

Error returns a descriptive error message including the invalid value and allowed values.

type RenderOptions added in v0.5.0

type RenderOptions struct {
	// Title is used as the document title for HTML output and as a header for Markdown.
	Title string

	// Writer overrides the default os.Stdout output destination.
	Writer io.Writer

	// ColorMode controls terminal color output. Defaults to ColorModeAuto.
	ColorMode ColorMode
}

RenderOptions configures optional behavior for RenderTable.

type Renderer

type Renderer interface {
	// Render returns the formatted output as a string.
	Render() (string, error)
}

Renderer defines the interface for output format renderers.

type RowEdge

type RowEdge struct {
	From GraphNodeID
	To   GraphNodeID
}

RowEdge represents a directed edge between two row identifiers.

type Shape added in v0.5.0

type Shape string

Shape represents a data shape that a format can render.

Example
package main

import (
	"fmt"

	"github.com/larsartmann/go-output"
)

func main() {
	fmt.Println(output.FormatCSV.Supports(output.ShapeTable))
	fmt.Println(output.FormatCSV.Supports(output.ShapeTree))
}
Output:
true
false
const (
	ShapeTable Shape = "table" // Tabular data with headers and rows
	ShapeTree  Shape = "tree"  // Hierarchical data with parent-child nodes
	ShapeGraph Shape = "graph" // Network data with nodes and edges
)

Data shape constants for format capability classification.

func ParseShape added in v0.5.0

func ParseShape(s string) (Shape, error)

ParseShape converts a string to Shape, returning an error if invalid.

func (Shape) AllowedValues added in v0.5.0

func (s Shape) AllowedValues() []string

AllowedValues returns all valid data shape values for CLI help text.

func (Shape) IsValid added in v0.5.0

func (s Shape) IsValid() bool

IsValid returns true if the shape is a valid Shape value.

func (Shape) String added in v0.5.0

func (s Shape) String() string

String returns the string representation of the data shape.

type StreamingRenderer

type StreamingRenderer interface {
	Renderer
	// Stream writes the rendered output to an io.Writer in chunks.
	Stream(w io.Writer) error
}

StreamingRenderer is an interface for renderers that support streaming output. This is useful for rendering large datasets without loading everything into memory.

Important: Not all implementations provide true streaming. The adapter returned by RendererAsWriter collects output before writing. Only StreamingHTMLRenderer (in the markup sub-module) provides genuine streaming behavior.

func RendererAsWriter added in v0.30.0

func RendererAsWriter(r Renderer) StreamingRenderer

RendererAsWriter wraps a standard Renderer to implement StreamingRenderer. The name is honest: it renders the full output via Render() then writes it to the writer in one shot — NOT true streaming. For genuine streaming, use StreamingHTMLRenderer (markup sub-module).

type StringEnum added in v0.18.0

type StringEnum interface {
	String() string
}

StringEnum is an interface for string-based enum types.

type Table added in v0.30.0

type Table struct {
	// Headers are the column header labels.
	Headers []string
	// Rows contains the data rows, each a slice of cell values.
	Rows [][]string
	// Footer is an optional totals/summary row rendered after all data rows.
	// Tabular formats (CSV, TSV, Markdown, HTML, XML, AsciiDoc, Table) render it visually.
	// Data formats (JSON, YAML, TOML, JSONL) and non-tabular formats skip it.
	Footer []string
}

Table represents tabular data with headers, rows, and an optional footer.

func GraphToTable added in v0.30.0

func GraphToTable(g Graph) *Table

GraphToTable converts a Graph into a Table with columns "ID" and "Label", one row per node. Edges are not represented in the table format. This is a pure projection — the input Graph is not modified.

func NewTable added in v0.30.0

func NewTable(headers []string) *Table

NewTable creates a new Table with the given headers.

func (*Table) AddRow added in v0.30.0

func (d *Table) AddRow(row []string)

AddRow adds a row to the table data.

This method does NOT validate the row's column count. Use AddRowChecked to return an error, or call Validate() before rendering to surface mismatched rows. Existing callers that rely on silent acceptance are preserved; new code should prefer AddRowChecked for fail-fast behavior.

func (*Table) AddRowChecked added in v0.30.0

func (d *Table) AddRowChecked(row []string) error

AddRowChecked adds a row to the table data and returns an error if the row's column count does not match the header count.

Returns nil if no headers are set, deferring validation to Validate(). Returns ErrColumnMismatch if the row length differs from len(Headers).

func (*Table) ColCount added in v0.30.0

func (d *Table) ColCount() int

ColCount returns the number of columns (based on headers).

func (*Table) CreateRowEdges added in v0.30.0

func (d *Table) CreateRowEdges() []RowEdge

CreateRowEdges generates edge data connecting consecutive rows. Used by graph renderers to create edges between table rows.

func (*Table) GetFooter added in v0.30.0

func (d *Table) GetFooter() []string

GetFooter returns the footer row, or nil if none is set. Satisfies the table.FooterProvider optional interface.

func (*Table) GetHeaders added in v0.30.0

func (d *Table) GetHeaders() []string

GetHeaders returns the column headers. Satisfies the table.TableProvider interface.

func (*Table) GetRows added in v0.30.0

func (d *Table) GetRows() [][]string

GetRows returns the data rows. Satisfies the table.TableProvider interface.

func (*Table) HasFooter added in v0.30.0

func (d *Table) HasFooter() bool

HasFooter returns true if a footer row is present.

func (*Table) RowCount added in v0.30.0

func (d *Table) RowCount() int

RowCount returns the number of data rows.

func (*Table) SetFooter added in v0.30.0

func (d *Table) SetFooter(footer []string)

SetFooter sets the footer row.

func (*Table) ToMapSlice added in v0.30.0

func (d *Table) ToMapSlice() []map[string]string

ToMapSlice converts Table to a slice of maps (header→cell). Returns nil if data is nil or has no headers.

func (*Table) Validate added in v0.30.0

func (d *Table) Validate() error

Validate checks the Table for consistency. Returns an error if rows are nil, or if the footer column count does not match the header count.

Example
package main

import (
	"fmt"

	"github.com/larsartmann/go-output"
)

func main() {
	data := output.NewTable([]string{"Name", "Count"})
	data.AddRow([]string{"Alice", "10"})
	data.SetFooter([]string{"Total", "10"})

	fmt.Println(data.Validate())
}
Output:
<nil>

type TableBuilder added in v0.30.0

type TableBuilder struct {
	// contains filtered or unexported fields
}

TableBuilder is the CQRS write-side builder for tabular data. It provides a fluent construction API, then copies the result into a *Table via Build(). The returned Table is a defensive copy — further mutations to the builder do not affect previously built Tables. Note: *Table has exported fields; callers SHOULD treat it as read-only after Build(), but Go does not enforce this at the type level.

Usage:

t := NewTableBuilder().
    SetHeaders("Name", "Status").
    AddRow("Compile", "done").
    AddRow("Test", "done").
    SetFooter("Total", "2 tasks").
    Build()

func NewTableBuilder added in v0.30.0

func NewTableBuilder() *TableBuilder

NewTableBuilder creates a new TableBuilder.

Example
package main

import (
	"fmt"

	"github.com/larsartmann/go-output"
)

func main() {
	tbl := output.NewTableBuilder().
		SetHeaders("Name", "Status", "Duration").
		AddRow("Compile", "done", "2.1s").
		AddRow("Test", "done", "5.3s").
		SetFooter("Total", "", "7.4s").
		Build()

	fmt.Printf("%d rows, %d cols\n", tbl.RowCount(), tbl.ColCount())
}

func (*TableBuilder) AddRow added in v0.30.0

func (b *TableBuilder) AddRow(row ...string) *TableBuilder

AddRow appends a data row.

func (*TableBuilder) AddRows added in v0.30.0

func (b *TableBuilder) AddRows(rows ...[]string) *TableBuilder

AddRows appends multiple data rows.

func (*TableBuilder) Build added in v0.30.0

func (b *TableBuilder) Build() *Table

Build returns a defensive copy of the builder's data as a *Table. The returned slices are copied — further mutations to the builder do not affect previously built Tables. Callers SHOULD treat the result as read-only, though Go does not enforce immutability on *Table.

func (*TableBuilder) SetFooter added in v0.30.0

func (b *TableBuilder) SetFooter(footer ...string) *TableBuilder

SetFooter sets the footer row.

func (*TableBuilder) SetHeaders added in v0.30.0

func (b *TableBuilder) SetHeaders(headers ...string) *TableBuilder

SetHeaders sets the column headers.

type TableMarshaler added in v0.30.0

type TableMarshaler func(w io.Writer, data *Table, opts RenderOptions) error

TableMarshaler renders Table in a specific format to a writer.

type TableRenderer

type TableRenderer interface {
	Renderer
	// SetHeaders sets the column headers.
	SetHeaders(headers []string)
	// AddRow adds a data row.
	AddRow(row []string)
}

TableRenderer defines the interface for table format renderers.

type TableStore added in v0.30.0

type TableStore struct {
	// contains filtered or unexported fields
}

TableStore provides common table data storage for renderers.

func (*TableStore) AddRow added in v0.30.0

func (b *TableStore) AddRow(row []string)

AddRow adds a data row.

func (*TableStore) Data added in v0.30.0

func (b *TableStore) Data() *Table

Data returns the underlying Table.

func (*TableStore) HasFooter added in v0.30.0

func (b *TableStore) HasFooter() bool

HasFooter returns true if a footer row is present.

func (*TableStore) SetData added in v0.30.0

func (b *TableStore) SetData(data *Table)

SetData sets the table data directly.

func (*TableStore) SetFooter added in v0.30.0

func (b *TableStore) SetFooter(footer []string)

SetFooter sets the footer row.

func (*TableStore) SetHeaders added in v0.30.0

func (b *TableStore) SetHeaders(headers []string)

SetHeaders sets the column headers.

type TableToGraphOption added in v0.30.0

type TableToGraphOption func(*tableToGraphConfig)

TableToGraphOption configures a TableToGraph projection.

func WithGraphNodeLabelFunc added in v0.30.0

func WithGraphNodeLabelFunc(fn GraphNodeLabelFunc) TableToGraphOption

WithGraphNodeLabelFunc overrides the default label function used by TableToGraph to derive each node's label from its row.

type TreeBuilder added in v0.30.0

type TreeBuilder struct {
	// contains filtered or unexported fields
}

TreeBuilder is the CQRS write-side builder for tree data. It provides a fluent construction API for assembling a tree, then returns the assembled *TreeNode (root) via Build(). Note: *TreeNode has exported fields and an AddChild method; callers SHOULD treat the result as read-only after Build(), but Go does not enforce this at the type level.

Usage:

root := NewTreeBuilder().
    SetRoot("build", "Build").
    AddChild("build", "compile", "Compile").
    AddChild("build", "lint", "Lint").
    AddChild("compile", "test", "Test").
    Build()

func NewTreeBuilder added in v0.30.0

func NewTreeBuilder() *TreeBuilder

NewTreeBuilder creates a new TreeBuilder.

Example
package main

import (
	"fmt"

	"github.com/larsartmann/go-output"
)

func main() {
	root := output.NewTreeBuilder().
		SetRoot("build", "Build").
		AddChild("build", "compile", "Compile").
		AddChild("build", "test", "Test").
		AddChild("compile", "lint", "Lint").
		Build()

	fmt.Println(root.Label.Get())
}

func (*TreeBuilder) AddChild added in v0.30.0

func (b *TreeBuilder) AddChild(parentID, id, label string) *TreeBuilder

AddChild adds a child node under the specified parent ID. If the parent ID is not found, the child is silently skipped.

func (*TreeBuilder) AddChildren added in v0.30.0

func (b *TreeBuilder) AddChildren(parentID string, children ...[2]string) *TreeBuilder

AddChildren adds multiple child nodes under the specified parent ID. Each child is specified as an (id, label) pair. If the parent ID is not found, all children are silently skipped.

func (*TreeBuilder) Build added in v0.30.0

func (b *TreeBuilder) Build() *TreeNode

Build returns the root node of the assembled tree. Returns nil if SetRoot was never called.

func (*TreeBuilder) SetRoot added in v0.30.0

func (b *TreeBuilder) SetRoot(id, label string) *TreeBuilder

SetRoot creates and sets the root node.

type TreeNode

type TreeNode struct {
	// ID is the unique identifier for the node.
	ID TreeNodeID
	// Label is the display text for the node.
	Label TreeNodeLabel
	// Children holds the child nodes.
	Children []*TreeNode
	// Metadata holds arbitrary key-value pairs for custom data.
	Metadata map[string]string
	// contains filtered or unexported fields
}

TreeNode represents a node in a tree structure.

func GraphToTree added in v0.30.0

func GraphToTree(g Graph) *TreeNode

GraphToTree converts a Graph into a TreeNode tree. The first node with no incoming edges becomes the root; outgoing edges define parent→child relationships. If the graph contains cycles, each node is visited at most once (cycle guard). If the graph has multiple roots, only the first root's subtree is returned. This is a pure projection — the input Graph is not modified.

Example
package main

import (
	"fmt"

	"github.com/larsartmann/go-output"
)

func main() {
	b := output.NewGraphBuilder()
	b.AddNode(*output.NewGraphNode("root", "Root"))
	b.AddNode(*output.NewGraphNode("child", "Child"))
	b.AddEdge(*output.NewGraphEdge("root", "child"))

	g := b.Build()
	root := output.GraphToTree(g)

	fmt.Println(root.Label.Get())
}

func NewTreeNode

func NewTreeNode(id, label string) *TreeNode

NewTreeNode creates a new TreeNode with the given ID and label.

func (*TreeNode) AddChild

func (n *TreeNode) AddChild(child *TreeNode)

AddChild adds a child node to this node.

func (*TreeNode) Depth

func (n *TreeNode) Depth() int

Depth returns the depth of this node in the tree (root = 0).

func (*TreeNode) Parent

func (n *TreeNode) Parent() *TreeNode

Parent returns the parent node (nil for root).

type TreeNodeID

type TreeNodeID = id.ID[TreeNodeIDBrand, string]

TreeNodeID is a branded identifier for tree nodes.

type TreeNodeIDBrand

type TreeNodeIDBrand struct{}

TreeNodeIDBrand is the brand type for tree node IDs.

type TreeNodeIDFunc

type TreeNodeIDFunc func(*TreeNode) string

TreeNodeIDFunc resolves a TreeNode's ID for a specific graph format.

type TreeNodeLabel

type TreeNodeLabel = id.ID[TreeNodeLabelBrand, string]

TreeNodeLabel is a branded identifier for tree node labels.

type TreeNodeLabelBrand

type TreeNodeLabelBrand struct{}

TreeNodeLabelBrand is the brand type for tree node labels.

type TreeRenderer added in v0.30.0

type TreeRenderer interface {
	Renderer
	// SetRoot sets the root node of the tree.
	SetRoot(node *TreeNode)
}

TreeRenderer defines the interface for tree format renderers.

type UnknownRenderer added in v0.30.0

type UnknownRenderer func(w io.Writer, data any, opts RenderOptions) error

UnknownRenderer renders arbitrary data (any) in a specific format to a writer.

type UnsupportedFormatError added in v0.5.0

type UnsupportedFormatError struct {
	Format Format
}

UnsupportedFormatError is returned when RenderTable cannot handle a format.

func (*UnsupportedFormatError) Error added in v0.5.0

func (e *UnsupportedFormatError) Error() string

Directories

Path Synopsis
d2 module
daghtml module
delimited module
enum module
envdetect module
escape module
graph module
markdown module
markup module
nom module
plantuml module
sort module
table module
testhelpers module
graphtest module
tree module
tui module

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL