Skip to content

What this does not do

output is a small module with a deliberately narrow surface. This page lists what it will not do, so you can stop looking for an option that is not there and decide whether to work around it or reach for something else.

Each entry says whether the absence is a deliberate boundary or simply unimplemented.

Can I change the format for a single call?

No. The format is fixed on the Renderer at construction and every method reads it. There is no per-call format argument and no setter.

Deliberate. A command that emitted JSON from one call and text from the next would produce a stream no consumer can parse. When you genuinely need two formats at once — machine-readable results on stdout, human feedback on stderr — build two renderers, which is the same idiom used to separate the streams:

out  := output.New(output.WithWriter(os.Stdout), output.WithFormat(f))
feed := output.New(output.WithWriter(os.Stderr))

Can I get the Response envelope in YAML?

No. Emit and EmitError write JSON or nothing; in every other format they return nil having written nothing.

Deliberate. The envelope exists to give scripts one stable shape, and one shape means one encoding. If you need status and command in YAML, put them on a struct of your own and pass it to Write.

Can I turn colour off?

Not with a flag or an environment variable. NO_COLOR, TERM=dumb and WithInteractive(false) all leave the ANSI escape sequences in place, because styling comes from the theme's lipgloss.Style values and those are rendered unconditionally.

Unimplemented. The workaround is a theme whose styles are bare lipgloss.NewStyle() values — see theming.

Can I choose the Markdown style Render uses?

No. Render and RenderMarkdown pick glamour's built-in dark or light style from the detected terminal background, defaulting to dark. There is no option for a custom glamour stylesheet, and the Theme does not affect Markdown rendering at all.

Unimplemented. Call glamour directly if you need a specific stylesheet.

Can I change the progress bar's width or reporting interval?

No. The bar is a fixed 40 characters wide, and the non-interactive form logs one line at each 10% boundary. Neither is configurable; only the glyphs are, via ProgressFilled, ProgressEmpty and ProgressHead.

Unimplemented. The bar does not adapt to terminal width either — on a narrow terminal the line wraps.

Can I right-align a column, or wrap a long cell?

No to both. Every cell in a text or Markdown table is left-aligned, and content too wide for its column is truncated, never wrapped onto a second line. There is no alignment field on Column and no wrapping option.

Unimplemented. Column.Width sets a fixed width, not a minimum or a maximum, and truncates the header to match.

Can I sort by a field that is not a displayed column?

No. WithSortBy matches a column Header in the resolved column set, and sorting happens on the rendered cell text after any Formatter has run. A value that is not a column cannot be a sort key.

Deliberate for the column part — the table sorts what it shows — but the consequence is a sharp edge: a Formatter that appends a unit turns a numeric sort lexical. Sort on a raw column and display a formatted one.

Can I use a dotted path to a nested field?

No. Column.Field is a single struct field name or a single map key. Field: "User.Name" finds no field called User.Name and renders an empty cell; Field: "User" renders the struct with %v, giving {a}.

Unimplemented. Flatten the row into a view struct before rendering, or supply a Formatter that does the reach:

output.Column{Header: "User", Field: "User", Formatter: func(v any) string {
    u, _ := v.(inner)
    return u.Name
}}

Embedded (promoted) fields are the exception — they are visible to the tag scan and work without a path.

Can I pass table options through Write?

No. When the format is csv, tsv or markdown, Write calls Table with no options. Column definitions, sorting and header suppression are unavailable on that path.

Unimplemented. Call Table directly when you need options, and use Write for the general case:

if r.Format() == output.FormatText {
    _ = r.Write(data, textFunc)
} else {
    _ = r.Table(rows, output.WithSortBy("Name"))
}

Can I render a table one row at a time?

No. Table takes a whole slice, reflects over it, extracts every cell into strings and only then measures and writes. There is no streaming or incremental API, and no row limit — a million-row slice is a million rows in memory twice.

Deliberate for the aligned text format, which cannot know a column's width until it has seen every value. It is a real cost for csv, tsv and json, which could in principle stream; for large exports, write those formats yourself.

Can two goroutines render at once?

Not safely, in general. Status and Progress each carry a mutex and are safe for concurrent use individually. A Renderer is only as safe as the writer it was given, and nothing coordinates between helpers: two Status instances, or a Status and a Progress, sharing a writer will interleave and corrupt each other's in-place redraws.

Run one piece of live feedback at a time. Spin in particular takes over the terminal with a Bubble Tea program and must not overlap with another spinner.

Can I page long output, or add borders and footers to a table?

No. There is no pager integration, no table borders, no footer or summary row, no column grouping and no cell spanning. The text table is header, rows, three spaces between columns.

Deliberate. The module's job is to make one value renderable in six formats, not to be a terminal layout engine. Reach for lipgloss/table or bubbletea when the output is really a UI.

Does it validate the --output flag value?

Only if you check. ParseFormat returns (FormatText, false) for anything it does not recognise and never errors, and cobra.Format discards that boolean — so --output jsno silently produces text.

Unimplemented. Register the flag with RegisterOutputFlag so the usage string lists the valid values, and validate in your own PreRunE if a typo should be fatal:

if _, ok := output.ParseFormat(val); !ok {
    return errors.Newf("unknown --output value %q", val)
}

Is anything localised?

No. Formats() values, the Response envelope's status strings, the progress bar's n/total (pct%) suffix and the .../... done/... failed spinner lines are fixed English ASCII. There is no message catalogue and no hook to supply one.

Deliberate for the machine-facing stringsstatus: "success" is a contract, not prose. Unimplemented for the human-facing ones.