Documentation
¶
Overview ¶
Package output renders command results as human-readable text or JSON. Commands build explicit DTOs for their output — never genqlient-generated structs — so the --json contract stays stable across schema regenerations.
Index ¶
- func Dash(s *string) string
- func SafeBlock(s string, maxRunes int, indent string) string
- func SafeCell(s string) string
- func SafeJSON(raw []byte) (string, error)
- func SafeLine(s string, maxRunes int) string
- func Tracked(w io.Writer) io.Writer
- func Write(s *IOStreams, asJSON bool, v any, table func(w io.Writer) error) error
- func WriteJSON(w io.Writer, v any) error
- type IOStreams
- type Table
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Dash ¶ added in v0.6.0
Dash renders an optional string cell: the value, or an em-dash placeholder when the pointer is nil or empty. Keeps "no value" columns consistent across the table-rendering commands.
func SafeBlock ¶ added in v0.34.0
SafeBlock bounds server-authored multiline text without flattening its lines. Escape sequences are removed before counting runes. CRLF becomes LF; every other control/format/separator becomes a space. Continuation indentation is CLI-owned. JSON callers must retain the original value. Like SafeCell, a cut may split a combining sequence; there is no separate byte bound.
func SafeCell ¶ added in v0.29.0
SafeCell makes a server-supplied string safe to print as one table cell (#1065). Every control character (C0, C1, DEL, ESC, tab, CR, LF) and every Unicode format or line/paragraph separator (Cf, Zl, Zp: bidi overrides, zero-width characters, BOM, U+2028/2029) becomes a single space, so a value can neither forge a row nor recolor or reorder the terminal. Ordinary Unicode passes unchanged. The text is cut by rune at cellMaxRunes, which can split a combining sequence. ZWJ (U+200D) is Cf, so a ZWJ emoji sequence renders as separate emoji; that is the accepted cost of the stricter rule.
Text mode only: --json carries the original value. Other commands' tables are unchanged (roster, automation and member keep their own guards).
func SafeJSON ¶ added in v0.35.0
SafeJSON renders a server-supplied JSON value for a terminal while keeping it valid JSON with the SAME value (hadron-cli#1524: `app properties get` prints the object so `> props.json` is a working file). It is indented two spaces, and inside strings every control character and every Unicode format or line/paragraph separator (Cf, Zl, Zp: bidi overrides, zero-width characters, U+2028/2029) is written as a \uXXXX escape (a surrogate pair above the BMP), so the text can neither recolor nor reorder the terminal. Structural whitespace from the indent is kept. Invalid JSON is an error.
func SafeLine ¶ added in v0.32.0
SafeLine is SafeCell with the caller's rune bound, for one-line messages (an error refusal) that need more room than a table cell.
func Tracked ¶ added in v0.14.0
Tracked wraps w so an IOStreams built around it can answer Wrote(). Exported so a test factory can give commands the same stdout they get in production — an untracked stream always reports "clean", which would let a test pass on a path the binary handles differently (review:a-test-double-must-satisfy-the-real-access-pattern).
Types ¶
type IOStreams ¶
type IOStreams struct {
In io.Reader
Out io.Writer
ErrOut io.Writer
// contains filtered or unexported fields
}
IOStreams bundles the CLI's input/output handles so commands never touch os.Stdin/Stdout directly, keeping them testable.
func Test ¶
Test returns IOStreams backed by buffers, plus the stdout and stderr buffers for assertions.
func TestTTY ¶ added in v0.13.0
TestTTY returns IOStreams whose stdin is an answerable terminal (#525), with `answers` standing in for what a person types — one line per prompt, in order. Stdout is left non-terminal. Returns the stdout and stderr buffers; a prompt renders on stderr.
Why it exists: cmdutil.Confirm and ConfirmDeletion refuse as non-interactive BEFORE composing their prompt, and Test() has no way to say otherwise, so every confirmation in the CLI was beyond the reach of a command-level test — a door with no handle that each new confirmation inherits.
Three properties, each load-bearing:
- The answers are a CONSTRUCTOR argument, not a field set afterwards. Test()'s In is an empty buffer, so flipping the TTY bit alone makes scanner.Scan() return false and Confirm returns Cancelled — a different unreachable branch, indistinguishable from a person answering "no". A seam that can be half-used yields a green test proving nothing.
- One line PER READ, via lineReader rather than strings.NewReader. See lineReader for why; a command that prompts twice is the case.
- Stdout stays non-terminal. Confirm reads stdin only, and interactive stdin with redirected stdout is a real process state (`hadron … | less`), so this is an honest configuration rather than a test-only shape. Widen it when something needs the stdout branch, not before.
func (*IOStreams) IsInputTerminal ¶
IsInputTerminal reports whether stdin is a TTY (i.e. a prompt can actually be answered).
func (*IOStreams) IsTerminal ¶
IsTerminal reports whether stdout is a TTY.
func (*IOStreams) Wrote ¶ added in v0.14.0
Wrote reports whether anything has been written to stdout yet.
FALSE for an untracked stream, deliberately: "nothing has been written" is the state in which the envelope goes to stdout, which is the behaviour this change is for. A stream nobody wrapped therefore degrades to the new behaviour rather than silently keeping the old one.