Documentation
¶
Overview ¶
Package output owns every byte flow writes to a terminal. It is the only package outside internal/ciwait permitted to import a Charm library: commands ask the renderer for output, they never construct a style themselves. That is what keeps a later theme change, or a full TUI, a single-file change.
Index ¶
- Constants
- Variables
- func FangColorScheme(plain bool) func(lipgloss.LightDarkFunc) fang.ColorScheme
- func FangErrorHandler(aborted, alreadyReported func(error) bool) fang.ErrorHandler
- func Truncate(s string, width int) string
- func VisibleWidth(s string) int
- type ErrNotInteractive
- type Options
- type Renderer
- func (r *Renderer) ColorEnabled() bool
- func (r *Renderer) Confirm(title, description string, defaultYes, assumeYes bool) error
- func (r *Renderer) ErrWriter() io.Writer
- func (r *Renderer) Failure(format string, a ...any)
- func (r *Renderer) Field(label, value string)
- func (r *Renderer) Heading(s string)
- func (r *Renderer) Input(title, def string) (string, error)
- func (r *Renderer) Interactive() bool
- func (r *Renderer) JSON(v any) error
- func (r *Renderer) JSONError(code, message string)
- func (r *Renderer) JSONMode() bool
- func (r *Renderer) Line(s string)
- func (r *Renderer) Printf(format string, a ...any)
- func (r *Renderer) Println(a ...any)
- func (r *Renderer) Profile() colorprofile.Profile
- func (r *Renderer) ProgramWriter() io.Writer
- func (r *Renderer) RenderField(label, value string) string
- func (r *Renderer) RenderTable(headers []string, rows [][]string) string
- func (r *Renderer) Select(title string, options []SelectOption) (string, error)
- func (r *Renderer) Spin(ctx context.Context, title string, fn func(context.Context) error) error
- func (r *Renderer) Status(format string, a ...any)
- func (r *Renderer) Stdin() io.Reader
- func (r *Renderer) Success(format string, a ...any)
- func (r *Renderer) Table(headers []string, rows [][]string)
- func (r *Renderer) Warn(format string, a ...any)
- func (r *Renderer) Watch(ctx context.Context, interval time.Duration, ...) error
- func (r *Renderer) Width() int
- type SelectOption
- type Theme
Constants ¶
const ( SymOK = "✔" SymFail = "✘" SymWarn = "!" SymSkip = "·" SymPending = "◦" )
Symbols used across the CLI and the CI progress display.
const DefaultWatchInterval = 5 * time.Second
DefaultWatchInterval is the refresh rate a watch uses unless asked otherwise.
const MinWatchInterval = time.Second
MinWatchInterval is the floor on a watch refresh. Every tick spends two or three subprocesses — `helm get metadata`, sometimes `helm status`, and `kubectl get pods` — against a real cluster, and refreshing faster than this only adds API-server load without telling anyone anything new.
Variables ¶
var ErrAborted = errors.New("aborted by user")
ErrAborted is returned when the user declines a confirmation or presses ctrl-c at a prompt. Callers map it to exit code 6.
Functions ¶
func FangColorScheme ¶
func FangColorScheme(plain bool) func(lipgloss.LightDarkFunc) fang.ColorScheme
FangColorScheme derives fang's help and error styling from flow's own palette, so help pages and command output never disagree about color.
plain is decided by main before fang parses flags, because fang renders before any command's pre-run hook has looked at --no-color or --json.
func FangErrorHandler ¶
func FangErrorHandler(aborted, alreadyReported func(error) bool) fang.ErrorHandler
FangErrorHandler builds fang's error handler. It lives here rather than in internal/cli because its signature names fang's own types, and internal/cli must not import a Charm library.
aborted and alreadyReported are injected so this file needs no knowledge of flow's error taxonomy.
func VisibleWidth ¶ added in v0.4.0
VisibleWidth is the width s occupies on screen, discounting the ANSI escapes a styled cell carries. It is exported so a caller assembling a frame can size a column without importing lipgloss itself.
Types ¶
type ErrNotInteractive ¶
type ErrNotInteractive struct {
// Flag is the escape hatch, e.g. "--yes".
Flag string
// What describes the decision that could not be made.
What string
}
ErrNotInteractive is returned when a prompt is required but stdin is not a terminal. The message always names the flag that would have avoided it.
func (*ErrNotInteractive) Error ¶
func (e *ErrNotInteractive) Error() string
type Options ¶
type Options struct {
JSON bool
NoColor bool
Verbose int
// Writers default to the process's own when nil. Tests inject buffers.
Stdout io.Writer
Stderr io.Writer
Stdin io.Reader
}
Options describe how a Renderer should behave. They come straight from the global flags on the root command.
type Renderer ¶
Renderer is the single sink for user-facing output. The primary result goes to stdout; progress, warnings and logs go to stderr, so that `cd $(flow path)` keeps working.
func New ¶
New builds a Renderer, deciding the color profile exactly once. --no-color, NO_COLOR, --json and a non-TTY writer each force plain ASCII; because the decision is made here and shared with huh, charmbracelet/log and fang, all four can never disagree about whether color is on.
func (*Renderer) ColorEnabled ¶
ColorEnabled reports whether the terminal can render color. It is false for --no-color, NO_COLOR, --json and every non-terminal writer, which together are exactly the cases where a live progress display must fall back to plain appended lines.
func (*Renderer) Confirm ¶
Confirm asks a yes/no question. assumeYes short-circuits it (--yes), and a non-interactive session fails with the flag that would have answered it.
func (*Renderer) ErrWriter ¶
ErrWriter exposes the profile-aware stderr writer, for the plain CI progress display that appends lines rather than driving a bubbletea program. Anything that does drive one wants ProgramWriter instead.
func (*Renderer) Interactive ¶
Interactive reports whether prompting the user is possible.
func (*Renderer) Line ¶
Line writes one bare line to stdout with no styling. This is the contract for `flow path` and `flow assets`: exactly one line, nothing else.
func (*Renderer) Profile ¶
func (r *Renderer) Profile() colorprofile.Profile
Profile reports the resolved color profile, so nested programs (bubbletea, huh) render with the same capabilities as plain output.
func (*Renderer) ProgramWriter ¶ added in v0.3.0
ProgramWriter is the writer every bubbletea program must be given: huh's forms and spinner here, and the live CI display in internal/ciwait. It is the terminal file itself, not r.err, because bubbletea measures its window only when the output satisfies term.File. A *colorprofile.Writer does not, so the program sits at a 0x0 window and paints nothing at all — an empty frame that looks exactly like a hang at a prompt. Pass tea.WithColorProfile(r.Profile()) alongside it and bypassing r.err costs nothing.
func (*Renderer) RenderField ¶ added in v0.4.0
RenderField returns the same line Field writes, for callers assembling a frame to repaint rather than a stream to append — Watch's, in particular.
func (*Renderer) RenderTable ¶ added in v0.4.0
RenderTable returns the table Table writes, for frame-assembling callers.
func (*Renderer) Select ¶
func (r *Renderer) Select(title string, options []SelectOption) (string, error)
Select asks the user to choose one of options.
The list is capped in height and filterable. Both matter once a list is long: a repository's workflow list runs to dozens of entries, and an uncapped one scrolls the answer off the screen while the user hunts for it with the arrow keys.
func (*Renderer) Spin ¶
Spin runs fn while showing a one-line indeterminate spinner on stderr. In JSON or non-interactive mode it just runs fn, so scripted output stays clean.
fn owns a context of its own, and Spin always waits for fn to return before it does. Both matter because of what ctrl+c does here: the spinner puts the terminal in raw mode, so the key arrives as a key press rather than as SIGINT to the process group — it never reaches a subprocess fn started — and huh answers it by tearing the program down while the action is still running. Cancelling fn's context is therefore the only thing that makes the interrupt mean anything, and waiting for fn to notice is what lets the caller read whatever fn wrote without racing it.
func (*Renderer) Table ¶
Table renders rows with lipgloss/table, which measures rendered width and so aligns correctly even when cells carry ANSI escapes.
func (*Renderer) Watch ¶ added in v0.4.0
func (r *Renderer) Watch(ctx context.Context, interval time.Duration, frame func(context.Context) string) error
Watch repaints frame's output in place until the user quits or ctx ends.
frame is called on a bubbletea command goroutine, once immediately and then every interval, and must return the whole view as a string — the same split internal/ciwait uses, where the frame is pure and the model around it is thin. It is never called concurrently with itself.
Outside an interactive terminal there is nothing to repaint in place, so the frame is written once and Watch returns; callers get one snapshot rather than an error. Quitting deliberately — q, esc or ctrl+c — is success.
type SelectOption ¶
SelectOption is one choice in a Select prompt.
type Theme ¶
type Theme struct {
Success lipgloss.Style
Warn lipgloss.Style
Error lipgloss.Style
Muted lipgloss.Style
Heading lipgloss.Style
Ticket lipgloss.Style
Path lipgloss.Style
Bold lipgloss.Style
// Raw colors, for the widgets that take a color rather than a style.
AccentColor color.Color
SuccessColor color.Color
WarnColor color.Color
ErrorColor color.Color
MutedColor color.Color
}
Theme holds every style flow uses. Construct it once, from a LightDarkFunc, and pass it around: lipgloss v2 dropped AdaptiveColor in favour of resolving light/dark up front, so styles cannot be package-level values any more.
func NewThemeFrom ¶ added in v0.2.0
func NewThemeFrom(ld lipgloss.LightDarkFunc) Theme
NewThemeFrom builds the theme from an existing resolver. fang hands its color-scheme callback one it has already derived, and reusing it is what keeps fang's help pages on the same light/dark branch as everything else.