fmter

package module
v0.0.0-...-d97bc68 Latest Latest
Warning

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

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

README

fmter

Go Reference Go Report Card CI codecov

Multi-format output renderer for Go CLI tools. One type, many formats — like the AWS CLI's --output flag.

Define your data type once, implement a few small interfaces, and let fmter render it as JSON, YAML, a rich table, CSV, Markdown, a flat list, env vars, Plain text, TSV, JSONL, HTML tables, or a custom Go template.

Install

go get github.com/bjaus/fmter

Quick Start

type Service struct {
    Name   string `json:"name" yaml:"name"`
    Status string `json:"status" yaml:"status"`
    Port   int    `json:"port" yaml:"port"`
}

// Rower unlocks CSV, Table, TSV, and HTML formats.
func (s Service) Row() []string { return []string{s.Name, s.Status, fmt.Sprint(s.Port)} }

// Headed adds column headers to CSV, Table, TSV, HTML, and Markdown.
func (s Service) Header() []string { return []string{"Name", "Status", "Port"} }

Now render in any format based on a CLI flag:

f, err := fmter.ParseFormat(flagValue) // "json", "table", "csv", "html", etc.
if err != nil {
    log.Fatal(err)
}

services := []Service{
    {Name: "api", Status: "running", Port: 8080},
    {Name: "web", Status: "stopped", Port: 3000},
}

fmter.Write(os.Stdout, f, services...)

JSON (works on any value, no interface needed):

[{"name":"api","status":"running","port":8080},{"name":"web","status":"stopped","port":3000}]

Table (requires Rower):

╭──────┬─────────┬──────╮
│ Name │ Status  │ Port │
├──────┼─────────┼──────┤
│ api  │ running │ 8080 │
│ web  │ stopped │ 3000 │
╰──────┴─────────┴──────╯

CSV (requires Rower):

Name,Status,Port
api,running,8080
web,stopped,3000

HTML (requires Rower):

<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Status</th>
      <th>Port</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>api</td>
      <td>running</td>
      <td>8080</td>
    </tr>
  </tbody>
</table>

How It Works

The package uses a progressive interface design. A minimal interface gets you working, and optional interfaces enhance the output:

JSON / YAML / JSONL / Plain ── any value (no interface needed)
CSV / Table / TSV / HTML ────── Rower (row data)
Markdown ────────────────────── Rower + Headed
List ────────────────────────── Lister
ENV ─────────────────────────── Mappable
GoTemplate ──────────────────── any value

Implement more interfaces to unlock more features — each one is independent and optional:

// Add a title bar above the table.
func (s Service) Title() string { return "Services" }

// Use ASCII border characters instead of Unicode.
func (s Service) Border() fmter.BorderStyle { return fmter.BorderASCII }

// Right-align the Port column.
func (s Service) Alignments() []fmter.Alignment {
    return []fmter.Alignment{fmter.AlignLeft, fmter.AlignLeft, fmter.AlignRight}
}

// Add a footer row.
func (s Service) Footer() []string { return []string{"Total", "", "2"} }

// Add automatic row numbers.
func (s Service) NumberHeader() string { return "#" }

// Style columns with ANSI colors.
func (s Service) Styles() []func(string) string {
    return []func(string) string{bold, nil, green}
}

// Group rows with separators.
func (s Service) Group() string { return s.Status }

// Repeat headers every 20 rows.
func (s Service) PageSize() int { return 20 }

// Wrap long cells at 30 characters.
func (s Service) WrapWidths() []int { return []int{30, 0, 0} }

Formats

Format Required Description
json any value Compact JSON (implement Indented for pretty-print)
yaml any value YAML via gopkg.in/yaml.v3
csv Rower RFC 4180 CSV (+ Headed, Delimited)
table Rower Rich bordered table with many options
markdown Rower + Headed GitHub-flavored Markdown table
list Lister Flat string list (+ Separator)
env Mappable KEY=VALUE pairs (+ Exported, Quoted)
plain any value One item per line via fmt.Stringer or %v
tsv Rower Tab-delimited, no quoting (+ Headed)
jsonl any value One JSON object per line (+ Indented)
html Rower Semantic HTML table (+ Headed, Titled, Footered, Aligned)
go-template=... any value Custom Go text/template

Interfaces

Required (one per format family)
Interface Method Used By
Rower Row() []string CSV, Table, Markdown, TSV, HTML
Lister List() []string List
Mappable Pairs() []KeyValue ENV
Optional (enhance any format)
Interface Method Effect
Headed Header() []string Column headers (CSV, Table, Markdown, TSV, HTML)
Indented Indent() string Pretty-print indent (JSON, YAML, JSONL)
Titled Title() string Title bar above table / HTML <caption>
Bordered Border() BorderStyle Table border style
Aligned Alignments() []Alignment Per-column alignment (Table, Markdown, HTML)
Footered Footer() []string Footer row below table / HTML <tfoot>
Numbered NumberHeader() string Auto row numbers
Captioned Caption() string Text below table
Truncated MaxWidths() []int Max column widths with ...
Delimited Delimiter() rune Custom CSV delimiter
Separator Sep() string Custom list separator
Exported Export() bool export prefix for ENV
Quoted Quote() bool Double-quote ENV values
Styled Styles() []func(string) string Per-column style functions (ANSI colors)
Sorted Sort() (int, bool) Metadata: default sort column (no auto-sort)
Grouped Group() string Separator between row groups
Wrapped WrapWidths() []int Per-column wrap widths (multi-line cells)
Paged PageSize() int Repeat header every N rows
Formatter Format(Format) ([]byte, error) Per-item escape hatch

Table Border Styles

fmter.BorderRounded  // ╭─╮╰╯│  (default)
fmter.BorderASCII    // +-+|
fmter.BorderHeavy    // ┏━┓┗┛┃
fmter.BorderDouble   // ╔═╗╚╝║
fmter.BorderNone     // No borders, space-separated

API

// Write formats items and writes to w.
fmter.Write(w, fmter.JSON, items...)

// Marshal returns the formatted bytes.
data, err := fmter.Marshal(fmter.Table, items...)

// ParseFormat converts a CLI flag string to a Format.
f, err := fmter.ParseFormat("table")

// GoTemplate creates a parameterized format.
fmter.Write(w, fmter.GoTemplate("{{.Name}}: {{.Status}}"), items...)

// IsSupported checks if a type implements the required interfaces.
if fmter.IsSupported[Service](fmter.CSV) { ... }

// Formats returns all static format names.
for _, f := range fmter.Formats() { ... }

// WriteIter streams items from an iterator.
fmter.WriteIter(w, fmter.JSONL, seq)

// WriteChan streams items from a channel.
fmter.WriteChan(w, fmter.Plain, ch)

Streaming

WriteIter and WriteChan write items as they arrive for formats that render independently (Plain, JSONL, CSV, TSV, GoTemplate). Formats that need all data for layout (Table, Markdown, HTML) collect items first.

// Iterator-based streaming.
seq := func(yield func(Service) bool) {
    for _, s := range services {
        if !yield(s) { return }
    }
}
fmter.WriteIter(os.Stdout, fmter.JSONL, seq)

// Channel-based streaming.
ch := make(chan Service)
go func() {
    for _, s := range services {
        ch <- s
    }
    close(ch)
}()
fmter.WriteChan(os.Stdout, fmter.Plain, ch)

Errors

All errors wrap sentinel values for errors.Is checks:

errors.Is(err, fmter.ErrUnsupportedFormat) // unknown format string
errors.Is(err, fmter.ErrMissingInterface)  // type doesn't implement required interface
errors.Is(err, fmter.ErrInvalidTemplate)   // bad go-template syntax

Contributing

See CONTRIBUTING.md for guidelines.

License

MIT

Documentation

Overview

Package fmter renders structured data in multiple output formats.

Supported formats are JSON, YAML, CSV, Table, Markdown, List, ENV, Plain, TSV, JSONL, HTML, and GoTemplate. The central entry points are Write and Marshal, which accept a Format constant and variadic items of any type. JSON, YAML, Plain, and JSONL work on any value; other formats require the items to implement specific interfaces.

Interface Design

The package uses a layered interface design. A minimal interface unlocks a format, and optional interfaces enhance the rendering:

  • Rower → CSV, Table, Markdown, TSV, HTML (row data)
  • Headed → adds column headers to CSV, Table, Markdown, TSV, HTML
  • Lister → List format
  • Mappable → ENV format

Use IsSupported to check at runtime whether a type implements the required interfaces for a given format:

if fmter.IsSupported[MyType](fmter.CSV) { ... }

JSON and YAML

Any value works. Implement Indented to control indentation:

fmter.Write(os.Stdout, fmter.JSON, myStruct)
fmter.Write(os.Stdout, fmter.YAML, items...)

CSV

Requires Rower. Optional interfaces:

  • Headed — header row
  • Delimited — custom field delimiter (default comma)

TSV

Requires Rower. Tab-delimited output with no quoting. Optional:

Table

Requires Rower. Optional interfaces control every aspect of rendering:

Markdown

Requires Rower and Headed. Renders a GitHub-flavored Markdown table. Implement Aligned to set column alignment markers.

HTML

Requires Rower. Renders a semantic HTML table. Optional interfaces:

List

Requires Lister. Implement Separator to control the delimiter between items (default newline).

ENV

Requires Mappable. Optional interfaces:

  • Exported — prefix lines with "export "
  • Quoted — wrap values in double quotes

Plain

Works on any value. Uses fmt.Stringer if available, otherwise fmt.Sprintf("%v"). One item per line.

JSONL

Works on any value. One JSON object per line (no array wrapping). Implement Indented for per-line indentation.

GoTemplate

Use GoTemplate to create a parameterized format that renders each item using a Go text/template:

fmter.Write(os.Stdout, fmter.GoTemplate("{{.Name}}: {{.Age}}"), items...)

Streaming

WriteIter and WriteChan support streaming output for iterator and channel sources. Formats that render items independently (Plain, JSONL, CSV, TSV, GoTemplate) write each item as it arrives. Formats that need all data for layout (Table, Markdown, HTML) collect items first.

Formatter

Implement Formatter for per-item control. If Format returns non-nil bytes, they are written directly; returning (nil, nil) falls through to default rendering.

Format Selection

Use ParseFormat to convert a CLI flag string into a Format. It recognizes all static formats and "go-template=<tmpl>" strings:

f, err := fmter.ParseFormat(flagValue)
fmter.Write(os.Stdout, f, items...)

Errors

The package exports sentinel errors for programmatic handling:

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrUnsupportedFormat = errors.New("unsupported format")
	ErrMissingInterface  = errors.New("missing required interface")
	ErrInvalidTemplate   = errors.New("invalid template")
)

Sentinel errors for programmatic error handling.

Functions

func IsSupported

func IsSupported[T any](f Format) bool

IsSupported reports whether type T implements the interfaces required by format f. JSON, YAML, and GoTemplate always return true.

func Marshal

func Marshal[T any](f Format, items ...T) ([]byte, error)

Marshal formats items and returns the bytes.

func Write

func Write[T any](w io.Writer, f Format, items ...T) error

Write formats items and writes to w.

func WriteChan

func WriteChan[T any](w io.Writer, f Format, ch <-chan T) error

WriteChan formats items from a channel and writes them to w. It is a thin wrapper around WriteIter.

func WriteIter

func WriteIter[T any](w io.Writer, f Format, seq iter.Seq[T]) error

WriteIter formats items from an iterator and writes them to w as they arrive. For formats where items are independent (JSONL, CSV, TSV, List, ENV, GoTemplate, Plain), each item is written immediately. For formats that need all data for layout (Table, Markdown, HTML), items are collected into a slice first. For JSON, items are streamed as array elements. For YAML, items are collected (the encoder needs a complete document).

Types

type Aligned

type Aligned interface {
	Alignments() []Alignment
}

Aligned sets per-column alignment. Default: AlignLeft. Also used by Markdown for alignment markers.

type Alignment

type Alignment int

Alignment controls column text alignment.

const (
	AlignLeft Alignment = iota
	AlignCenter
	AlignRight
)

type BorderStyle

type BorderStyle int

BorderStyle controls table border characters.

const (
	BorderRounded BorderStyle = iota // ╭─╮╰╯│┬┴├┤┼
	BorderNone                       // No borders, space-separated columns
	BorderASCII                      // +-+|
	BorderHeavy                      // ┏━┓┗┛┃┳┻┣┫╋
	BorderDouble                     // ╔═╗╚╝║╦╩╠╣╬
)

type Bordered

type Bordered interface {
	Border() BorderStyle
}

Bordered controls the table border style. Default: BorderRounded.

type Captioned

type Captioned interface {
	Caption() string
}

Captioned renders a line below the table. Default: no caption.

type Delimited

type Delimited interface {
	Delimiter() rune
}

Delimited controls the CSV field delimiter. Default: comma.

type Exported

type Exported interface {
	Export() bool
}

Exported prefixes env pairs with "export ". Default: no prefix. Use with Quoted for shell-safe output.

type Footered

type Footered interface {
	Footer() []string
}

Footered renders a footer row below the table. Default: no footer.

type Format

type Format string

Format represents an output format.

const (
	JSON     Format = "json"
	YAML     Format = "yaml"
	CSV      Format = "csv"
	Table    Format = "table"
	Markdown Format = "markdown"
	List     Format = "list"
	ENV      Format = "env"
	Plain    Format = "plain"
	TSV      Format = "tsv"
	JSONL    Format = "jsonl"
	HTML     Format = "html"
)

func Formats

func Formats() []Format

Formats returns all supported static format names. GoTemplate is not included because it is parameterized.

func GoTemplate

func GoTemplate(tmpl string) Format

GoTemplate returns a Format that renders items using a Go text/template. Each item is executed against the template and written on its own line.

func ParseFormat

func ParseFormat(s string) (Format, error)

ParseFormat parses a format string. Recognizes all static formats and go-template=<tmpl> strings.

func (Format) String

func (f Format) String() string

String returns the format name.

type Formatter

type Formatter interface {
	Format(Format) ([]byte, error)
}

Formatter is an escape hatch checked per-item. If Format returns non-nil bytes, those bytes are written directly. If it returns (nil, nil), the item falls through to default rendering.

type Grouped

type Grouped interface {
	Group() string
}

Grouped returns a group key for the item. When consecutive items have different group keys, a separator line is inserted between them in Table format.

type Headed

type Headed interface {
	Header() []string
}

Headed provides column headers for CSV, Table, and Markdown. Without it, CSV has no header row and Table renders without column headers.

type Indented

type Indented interface {
	Indent() string
}

Indented controls JSON/YAML indentation. Without it, JSON is compact and YAML uses its default indent.

type KeyValue

type KeyValue struct {
	Key   string
	Value string
}

KeyValue is a single key-value pair.

type Lister

type Lister interface {
	List() []string
}

Lister provides a flat list of strings. Required for List format.

type Mappable

type Mappable interface {
	Pairs() []KeyValue
}

Mappable provides key-value pairs. Required for ENV format.

type Numbered

type Numbered interface {
	NumberHeader() string
}

Numbered prepends a row number column. Default: no row numbers.

type Paged

type Paged interface {
	PageSize() int
}

Paged controls header repetition for Table format. The header row is re-printed every PageSize data rows.

type Quoted

type Quoted interface {
	Quote() bool
}

Quoted wraps env values in double quotes. Default: unquoted. Useful for values that may contain spaces or special characters.

type Rower

type Rower interface {
	Row() []string
}

Rower provides row data. Required for CSV, Table, and Markdown formats.

type Separator

type Separator interface {
	Sep() string
}

Separator controls the delimiter between list items. Default: newline.

type Sorted

type Sorted interface {
	Sort() (column int, descending bool)
}

Sorted is a metadata-only interface that declares a default sort column. The package does NOT sort; callers (CLI frameworks) can read this to apply sorting before rendering.

type Styled

type Styled interface {
	Styles() []func(string) string
}

Styled provides per-column style functions for Table format. Each function wraps the fully formatted cell string (after truncation and alignment). Nil entries mean no styling for that column. Style functions are applied as the last step before writing, so ANSI codes never affect width calculations.

type Titled

type Titled interface {
	Title() string
}

Titled renders a title above the table. Default: no title.

type Truncated

type Truncated interface {
	MaxWidths() []int
}

Truncated sets maximum column widths for Table format. Cells exceeding the max are truncated with "...". A zero value means no limit for that column.

type Wrapped

type Wrapped interface {
	WrapWidths() []int
}

Wrapped provides per-column maximum widths for text wrapping in Table format. Cells exceeding the width wrap to multiple visual lines within the same row. A zero value means no wrapping for that column.

Jump to

Keyboard shortcuts

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