output

package
v0.5.2 Latest Latest
Warning

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

Go to latest
Published: Sep 30, 2026 License: MIT Imports: 19 Imported by: 0

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

View Source
const (
	SymOK      = "✔"
	SymFail    = "✘"
	SymWarn    = "!"
	SymSkip    = "·"
	SymPending = "◦"
)

Symbols used across the CLI and the CI progress display.

View Source
const DefaultWatchInterval = 5 * time.Second

DefaultWatchInterval is the refresh rate a watch uses unless asked otherwise.

View Source
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

View Source
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 Truncate

func Truncate(s string, width int) string

Truncate shortens s to width runes, ending with an ellipsis.

func VisibleWidth added in v0.4.0

func VisibleWidth(s string) int

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

type Renderer struct {
	Theme Theme
	Log   *log.Logger
	// contains filtered or unexported fields
}

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

func New(opts Options) *Renderer

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

func (r *Renderer) ColorEnabled() bool

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

func (r *Renderer) Confirm(title, description string, defaultYes, assumeYes bool) error

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

func (r *Renderer) ErrWriter() io.Writer

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) Failure

func (r *Renderer) Failure(format string, a ...any)

Failure writes an error line to stderr without terminating the command.

func (*Renderer) Field

func (r *Renderer) Field(label, value string)

Field writes an aligned "label: value" line to stdout.

func (*Renderer) Heading

func (r *Renderer) Heading(s string)

Heading writes a section heading to stderr.

func (*Renderer) Input

func (r *Renderer) Input(title, def string) (string, error)

Input asks for a free-text value, pre-filled with def.

func (*Renderer) Interactive

func (r *Renderer) Interactive() bool

Interactive reports whether prompting the user is possible.

func (*Renderer) JSON

func (r *Renderer) JSON(v any) error

JSON writes v as the command's single JSON object on stdout.

func (*Renderer) JSONError

func (r *Renderer) JSONError(code, message string)

JSONError writes the error envelope required in --json mode.

func (*Renderer) JSONMode

func (r *Renderer) JSONMode() bool

JSONMode reports whether --json was given.

func (*Renderer) Line

func (r *Renderer) Line(s string)

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) Printf

func (r *Renderer) Printf(format string, a ...any)

Printf writes formatted, styled text to stdout.

func (*Renderer) Println

func (r *Renderer) Println(a ...any)

Println writes a styled line to stdout.

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

func (r *Renderer) ProgramWriter() io.Writer

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

func (r *Renderer) RenderField(label, value string) string

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

func (r *Renderer) RenderTable(headers []string, rows [][]string) string

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

func (r *Renderer) Spin(ctx context.Context, title string, fn func(context.Context) error) error

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) Status

func (r *Renderer) Status(format string, a ...any)

Status writes a progress line to stderr, keeping stdout pipe-clean.

func (*Renderer) Stdin

func (r *Renderer) Stdin() io.Reader

Stdin exposes the input stream for prompts.

func (*Renderer) Success

func (r *Renderer) Success(format string, a ...any)

Success writes a success line to stderr.

func (*Renderer) Table

func (r *Renderer) Table(headers []string, rows [][]string)

Table renders rows with lipgloss/table, which measures rendered width and so aligns correctly even when cells carry ANSI escapes.

func (*Renderer) Warn

func (r *Renderer) Warn(format string, a ...any)

Warn writes a warning line to stderr.

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.

func (*Renderer) Width

func (r *Renderer) Width() int

Width is the usable terminal width, or a sane default when not a terminal.

type SelectOption

type SelectOption struct {
	Label string
	Value string
}

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 NewTheme

func NewTheme(darkBackground bool) Theme

NewTheme builds the theme for a terminal with the given background.

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.

Jump to

Keyboard shortcuts

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