output

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Jul 21, 2026 License: MIT Imports: 21 Imported by: 0

README

output

Structured, themeable output formatting for CLI tools — one Renderer façade for text, JSON, YAML, CSV, TSV and Markdown, plus tables, spinners, progress bars and status lines

Go Reference Pipeline Coverage phpboyscout Go toolkit

Part of the phpboyscout Go toolkit — small, framework-free Go modules extracted from go-tool-base. Docs: output.go.phpboyscout.uk


CLI tools need to speak two languages at once: human-readable text for a terminal and machine-readable JSON/YAML for a pipeline. output gives you one configured Renderer that does both — plus tables, spinners, progress bars and live status lines — and degrades gracefully to plain output when it is not attached to a terminal.

r := output.New(output.WithWriter(os.Stdout), output.WithFormat(format))

// Format-aware: text calls your function, JSON/YAML marshal, CSV/TSV/Markdown tabulate.
_ = r.Write(result, func(w io.Writer) {
    fmt.Fprintf(w, "%s is up to date (%s)\n", result.Name, result.Version)
})

Why

  • Configure once, render anywhere. The writer, format, theme and interactivity are fixed on the Renderer; every method reads them. No globals, no reaching for os.Stderr inside a helper — so it is trivially testable with a bytes.Buffer.
  • One façade, six formats. Write, Table, Render (Markdown→ANSI via glamour), Emit/EmitError (the JSON envelope), Status, Progress and Spin all share the same configured destination and format.
  • Unicode-correct. Column widths and truncation use display width (charmbracelet/x/ansi), so CJK, emoji and combining characters never corrupt a table or a status line.
  • Framework-free core. Importing output never pulls in cobra or pflag. The cobra wiring — reading the --output flag and the command's writer — lives in the opt-in output/cobra subpackage, and a depfootprint_test.go guard keeps the core clean.

Install

go get gitlab.com/phpboyscout/go/output

Interactive feedback

r := output.New(output.WithWriter(os.Stderr))

// Spinner while work runs; plain "…done"/"…failed" lines under CI.
err := r.Spin(ctx, "Fetching release", func(ctx context.Context) error {
    return download(ctx)
})

// Or a multi-step status display / a known-total progress bar.
st := r.Status()
st.Update("Verifying signature"); st.Success("Signature valid"); st.Done()

cobra CLIs

import ocobra "gitlab.com/phpboyscout/go/output/cobra"

ocobra.RegisterOutputFlag(rootCmd) // --output text|json|yaml|csv|tsv|markdown

// In a RunE — reads --output and cmd.OutOrStdout():
return ocobra.NewRenderer(cmd).Emit(output.Response{
    Status: output.StatusSuccess, Command: "update", Data: result,
})

Documentation

Full guides: output.go.phpboyscout.uk. API reference: pkg.go.dev.

License

See LICENSE.

Documentation

Overview

Package output provides structured, themeable output formatting for CLI tools.

A single Renderer façade is the entry point. It is configured once — with a destination writer, an output Format, a Theme, and whether output is interactive — and every method reads that configuration:

The package is framework-agnostic: it takes an io.Writer and a Format, not a CLI command. Integration with spf13/cobra — reading the --output flag and the command's writer — lives in the opt-in subpackage gitlab.com/phpboyscout/go/output/cobra so that importing the core never pulls cobra (or pflag) into the dependency graph.

Index

Examples

Constants

View Source
const (
	StatusSuccess = "success"
	StatusError   = "error"
	StatusWarning = "warning"
)

Status constants for the Response envelope.

Variables

This section is empty.

Functions

func IsInteractive

func IsInteractive() bool

IsInteractive reports whether stdout is a TTY and CI mode is not active. It is the default interactivity detector a Renderer uses when WithInteractive is not supplied, and is exported so callers can reuse the same heuristic.

func RenderMarkdown

func RenderMarkdown(content string) string

RenderMarkdown renders Markdown content to styled ANSI terminal output via glamour. It detects the terminal width automatically, falling back to 80 columns, and returns the original content unchanged if glamour fails.

func SpinWithResult

func SpinWithResult[T any](r *Renderer, ctx context.Context, msg string, fn func(ctx context.Context) (T, error)) (T, error)

SpinWithResult is like Renderer.Spin but returns a value alongside the error. It is a free function because Go methods cannot introduce a type parameter.

As with Spin, a Ctrl-C interrupt while the spinner is active cancels fn's context and returns (zero, context.Canceled).

Example
package main

import (
	"context"

	"gitlab.com/phpboyscout/go/output"
)

func main() {
	r := output.New()

	result, err := output.SpinWithResult(r, context.Background(), "Fetching version",
		func(_ context.Context) (string, error) {
			return "v1.2.3", nil
		})
	if err == nil {
		_ = result
	}
}

Types

type Column

type Column struct {
	// Header is the display name shown in the table header row.
	Header string
	// Field is the struct field name or map key to extract the value from.
	Field string
	// Width is the fixed column width. Zero means auto-sized to content.
	Width int
	// Sortable indicates this column can be used as a sort key.
	Sortable bool
	// Formatter is an optional function to format the cell value.
	Formatter func(any) string
}

Column defines a single table column for Renderer.Table.

type Format

type Format string

Format represents the output format for a Renderer.

const (
	// FormatText is the default human-readable output format.
	FormatText Format = "text"
	// FormatJSON produces machine-readable JSON output.
	FormatJSON Format = "json"
	// FormatYAML produces machine-readable YAML output.
	FormatYAML Format = "yaml"
	// FormatCSV produces comma-separated values output.
	FormatCSV Format = "csv"
	// FormatMarkdown produces a pipe-delimited Markdown table with header separators.
	FormatMarkdown Format = "markdown"
	// FormatTSV produces tab-separated values output for shell pipelines.
	FormatTSV Format = "tsv"
)

func Formats

func Formats() []Format

Formats returns every supported Format in a stable order. It is the canonical value set for a CLI's --output flag.

func ParseFormat

func ParseFormat(s string) (Format, bool)

ParseFormat maps a case-insensitive string to a Format, falling back to FormatText for the empty string or any unrecognised value. The second return value reports whether the input named a known format.

type Option

type Option func(*config)

Option configures a Renderer.

func WithFormat

func WithFormat(f Format) Option

WithFormat sets the output Format. The default is FormatText.

func WithInteractive

func WithInteractive(v bool) Option

WithInteractive forces interactive (v true) or plain (v false) rendering, overriding the automatic IsInteractive detection.

func WithTheme

func WithTheme(t Theme) Option

WithTheme overrides the DefaultTheme.

func WithWriter

func WithWriter(w io.Writer) Option

WithWriter sets the destination writer. The default is os.Stderr for the progress-family renderers; structured output (Renderer.Write, Renderer.Table, Renderer.Emit) also writes here, so a caller emitting machine-readable output should point this at stdout.

type Progress

type Progress struct {
	// contains filtered or unexported fields
}

Progress tracks progress of a known-total operation. In interactive terminals it draws an animated bar; in non-interactive environments it logs periodic status lines. Obtain one from Renderer.Progress. Safe for concurrent use from multiple goroutines.

func (*Progress) Done

func (p *Progress) Done()

Done marks the progress as complete and cleans up the display.

func (*Progress) Increment

func (p *Progress) Increment()

Increment advances the progress by one unit.

func (*Progress) IncrementBy

func (p *Progress) IncrementBy(n int)

IncrementBy advances the progress by n units.

type Renderer

type Renderer struct {
	// contains filtered or unexported fields
}

Renderer is the configured façade for all output. Construct it with New and reuse it; every method reads the writer, format, theme and interactivity fixed at construction. A Renderer is safe for concurrent use only insofar as its underlying writer is; the progress-family helpers it returns carry their own locking.

func New

func New(opts ...Option) *Renderer

New builds a Renderer from the default configuration and the given options. Defaults: writer os.Stderr, format FormatText, DefaultTheme, and interactivity auto-detected via IsInteractive.

func (*Renderer) Emit

func (r *Renderer) Emit(resp Response) error

Emit writes resp as an indented JSON envelope to the configured writer when the format is FormatJSON; otherwise it is a no-op. It is the machine-readable counterpart to a command's human-readable text output.

Example
package main

import (
	"bytes"
	"fmt"

	"gitlab.com/phpboyscout/go/output"
)

func main() {
	var buf bytes.Buffer
	r := output.New(output.WithWriter(&buf), output.WithFormat(output.FormatJSON))

	_ = r.Emit(output.Response{
		Status:  output.StatusSuccess,
		Command: "version",
		Data:    map[string]string{"version": "v1.2.3"},
	})

	fmt.Print(buf.String())
}
Output:
{
  "status": "success",
  "command": "version",
  "data": {
    "version": "v1.2.3"
  }
}

func (*Renderer) EmitError

func (r *Renderer) EmitError(command string, err error) error

EmitError builds an error Response envelope for command and emits it via Renderer.Emit.

func (*Renderer) Format

func (r *Renderer) Format() Format

Format returns the renderer's configured Format.

func (*Renderer) IsJSON

func (r *Renderer) IsJSON() bool

IsJSON reports whether the configured format is FormatJSON.

func (*Renderer) Progress

func (r *Renderer) Progress(total int, description string) *Progress

Progress returns a progress indicator bound to the renderer's writer, theme and interactivity, with the given total and description.

func (*Renderer) Render

func (r *Renderer) Render(markdown string) error

Render writes Markdown to the configured writer using glamour styling. In JSON mode it is a no-op — callers should use Renderer.Write or Renderer.Emit for machine-readable output.

func (*Renderer) Spin

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

Spin shows a spinner with a message while fn executes, using the renderer's writer, interactivity and theme. In interactive terminals it animates; in non-interactive environments (CI, piped output) it prints plain status lines. The context is passed to fn and used for cancellation.

If the user interrupts an active spinner with Ctrl-C, fn's context is cancelled and Spin returns context.Canceled rather than reporting a false success.

Example
package main

import (
	"context"
	"fmt"
	"time"

	"gitlab.com/phpboyscout/go/output"
)

func main() {
	// Spin shows a spinner while the function executes. In non-interactive
	// environments (CI, piped output) it prints plain status lines.
	r := output.New()

	err := r.Spin(context.Background(), "Loading data", func(_ context.Context) error {
		time.Sleep(time.Millisecond)

		return nil
	})
	if err != nil {
		fmt.Println("Error:", err)
	}
}

func (*Renderer) Status

func (r *Renderer) Status() *Status

Status returns a live status display bound to the renderer's writer, theme and interactivity.

func (*Renderer) Table

func (r *Renderer) Table(rows any, opts ...TableOption) error

Table renders rows as an aligned table (text) or a machine-readable format, honouring the renderer's configured Format. rows must be a slice of structs (columns derived from `table:` tags) or a slice of maps with explicit WithColumns.

Example
package main

import (
	"bytes"
	"fmt"

	"gitlab.com/phpboyscout/go/output"
)

func main() {
	type release struct {
		Name    string `table:"NAME,sortable"`
		Version string `table:"VERSION"`
		Status  string `table:"STATUS"`
	}

	rows := []release{
		{Name: "my-tool", Version: "v1.2.3", Status: "up to date"},
		{Name: "other", Version: "v0.9.1", Status: "update available"},
	}

	var buf bytes.Buffer
	r := output.New(output.WithWriter(&buf), output.WithFormat(output.FormatText))

	if err := r.Table(rows, output.WithNoTruncation()); err != nil {
		fmt.Println("Error:", err)

		return
	}

	fmt.Print(buf.String())
}
Output:
NAME      VERSION   STATUS
my-tool   v1.2.3    up to date
other     v0.9.1    update available

func (*Renderer) Write

func (r *Renderer) Write(data any, textFunc func(io.Writer)) error

Write emits data in the configured format:

textFunc may be nil when the configured format is not text.

type Response

type Response struct {
	Status  string `json:"status"`
	Command string `json:"command"`
	Data    any    `json:"data,omitempty"`
	Error   string `json:"error,omitempty"`
}

Response is the standard JSON envelope for machine-readable command output.

type Status

type Status struct {
	// contains filtered or unexported fields
}

Status displays a live-updating status message for multi-step operations. In interactive terminals it updates the current line in place; in non-interactive environments it prints sequential lines. Obtain one from Renderer.Status. Safe for concurrent use from multiple goroutines.

func (*Status) Done

func (s *Status) Done()

Done cleans up the status display.

func (*Status) Fail

func (s *Status) Fail(msg string)

Fail marks the current step as failed and moves to the next line.

func (*Status) Success

func (s *Status) Success(msg string)

Success marks the current step as successful and moves to the next line.

func (*Status) Update

func (s *Status) Update(msg string)

Update replaces the current status message, prefixed with a spinner frame.

func (*Status) Warn

func (s *Status) Warn(msg string)

Warn marks the current step as a warning and moves to the next line.

type TableOption

type TableOption func(*tableConfig)

TableOption configures a Renderer.Table call.

func WithColumns

func WithColumns(cols ...Column) TableOption

WithColumns explicitly defines the table columns. When not provided, columns are derived from `table:` struct tags on the row data type.

func WithMaxWidth

func WithMaxWidth(width int) TableOption

WithMaxWidth overrides automatic terminal width detection.

func WithNoHeader

func WithNoHeader() TableOption

WithNoHeader suppresses the header row in text table output.

func WithNoTruncation

func WithNoTruncation() TableOption

WithNoTruncation disables terminal-width truncation. Useful when output is piped to a file or another process.

func WithSortBy

func WithSortBy(field string) TableOption

WithSortBy sets the column to sort rows by. The column must be marked Sortable.

func WithSortDescending

func WithSortDescending() TableOption

WithSortDescending reverses the sort order.

type Theme

type Theme struct {
	// SuccessIcon, WarnIcon and FailIcon mark a completed step's outcome.
	SuccessIcon string
	WarnIcon    string
	FailIcon    string

	// SuccessStyle, WarnStyle and FailStyle style the icon + message of a
	// completed step.
	SuccessStyle lipgloss.Style
	WarnStyle    lipgloss.Style
	FailStyle    lipgloss.Style

	// SpinnerStyle styles the animated spinner frames.
	SpinnerStyle lipgloss.Style

	// SpinnerFrames is the animation frame set shared by [Renderer.Spin] and the
	// working state of [Renderer.Status]. It must contain at least one frame.
	SpinnerFrames []string

	// ProgressFilled and ProgressEmpty are the runes used to draw a progress bar.
	ProgressFilled rune
	ProgressEmpty  rune
	// ProgressHead is the rune drawn at the leading edge of a partially-filled
	// bar. A zero value falls back to ProgressFilled.
	ProgressHead rune
}

Theme controls the icons and styles the progress-family renderers (Renderer.Status, Renderer.Progress, Renderer.Spin) use. Downstream tools override it via WithTheme to match their own palette.

func DefaultTheme

func DefaultTheme() Theme

DefaultTheme returns the built-in theme: check/warning/cross icons, a magenta spinner, and an ASCII progress bar. It is used when WithTheme is not given.

Directories

Path Synopsis
Package cobra binds gitlab.com/phpboyscout/go/output to spf13/cobra commands.
Package cobra binds gitlab.com/phpboyscout/go/output to spf13/cobra commands.

Jump to

Keyboard shortcuts

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