The Renderer façade¶
output deliberately exposes one public entry point — output.New returning a
*Renderer — rather than a scatter of independent constructors
(NewWriter, NewTableWriter, NewStatus, NewProgress, Spin). This page
explains why.
Configure once, read everywhere¶
A CLI's output concerns share four decisions: where output goes, which format
it is in, how it is styled, and whether the destination is interactive. Those
belong together and rarely change within a command. The Renderer fixes them at
construction, and every method reads them:
r := output.New(
output.WithWriter(os.Stdout),
output.WithFormat(output.FormatJSON),
)
r.Emit(resp) // knows the writer and that we are in JSON mode
r.Table(rows) // same writer, same format — no re-passing
The alternative — threading (writer, format) through every call — is what the
predecessor package did, and it meant every call site re-derived the same two
values, usually from a *cobra.Command. The façade removes that repetition and
the coupling behind it.
No hidden global state¶
The predecessor reached for os.Stderr, os.Stdout, os.Stdin and the CI
environment variable inside its render calls. That made behaviour depend on
process globals and forced tests to swap os.Stdout or set env vars — hostile to
parallelism.
The Renderer takes its writer and interactivity as injected values. Tests
construct output.New(output.WithWriter(&buf), output.WithInteractive(true)) and
assert on the buffer, fully in parallel, with no global mutation. IsInteractive()
remains exported as the default detector the renderer falls back to when
WithInteractive is not supplied — so production behaviour is unchanged.
One format matrix¶
Because the format lives on the renderer, Write can honour the whole matrix from
a single method: text via a caller function, JSON/YAML by marshalling, and
CSV/TSV/Markdown by tabulating. Table is the specialised entry point for tabular
data with column options; Write is the general envelope. Both funnel structured
formats through the same rendering core, so a value renders identically whichever
door it comes through.
The progress family shares the renderer¶
Status(), Progress() and Spin() inherit the renderer's writer, theme and
interactivity. A tool configures the destination and palette once and every piece
of feedback is consistent — and every piece degrades to plain lines together when
output is not a terminal.