table

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: May 19, 2026 License: MIT Imports: 16 Imported by: 0

Documentation

Overview

Package table renders typed iterators of struct values as styled CLI tables.

Each column is derived by reflection from an exported struct field of the type parameter T: the header from the field's name (CamelCase split and uppercased, or overridden via a `table:"NAME"` struct tag), and the cell formatter from the field's type. Numeric fields are right-aligned; time.Time and time.Duration get dedicated human-friendly formats; any type implementing encoding.TextMarshaler, fmt.Formatter, or fmt.Stringer is honoured. Tag modifiers (`table:",bytes"`, `table:",%"`, `table:",%%"`) pin a specific formatter and alignment for cases the type system can't infer.

The API mirrors what callers usually want: Write for streaming an iter.Seq2 of values + errors to an io.Writer, Format for a quick string, and NewWriter / NewFormatter for the precomputed-schema forms used in hot loops.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Format

func Format[T any](seq iter.Seq[T], opts ...Option) string

Format renders seq into a string. Equivalent to NewFormatter[T](opts...)(seq).

func Write

func Write[T any](w io.Writer, seq iter.Seq2[T, error], opts ...Option) error

Write renders seq into w. Equivalent to NewWriter[T](opts...)(w, seq).

Types

type Column

type Column struct {
	// Header is the column heading. It is rendered as-is; no case
	// transformation is applied (unlike struct-field-derived headers).
	Header string

	// Modifier optionally pins a formatter, mirroring the struct-tag
	// modifiers: "bytes", "count", "{min}-{max}%" (e.g. "0-100%" identity,
	// "0-1%" ratio-to-percent), or "" to use the element type's default
	// formatter.
	Modifier string

	// Suffix is appended to every non-empty formatted cell, mirroring the
	// optional literal suffix in struct tags (e.g. `table:",count,/s"`).
	// Empty cells are left empty so column alignment is preserved.
	Suffix string
}

Column describes one column when the row type isn't a struct. Each non-struct row (e.g. []string, []float64, []any) needs the caller to provide the column count and headers up front; cell formatters are otherwise derived from the element type or — for []any — from the dynamic type of each cell.

type Formatter

type Formatter[T any] func(iter.Seq[T]) string

Formatter renders an iter.Seq of T-values into a string.

func NewFormatter

func NewFormatter[T any](opts ...Option) Formatter[T]

NewFormatter precomputes the column schema for T and returns a Formatter that reuses it across calls. If the schema is invalid (non-struct T, unknown tag modifier, ...), the error message is returned as the table body — the Formatter signature has no other channel.

type Option

type Option func(*Options)

Option configures Options.

func WithBorder

func WithBorder(b lipgloss.Border) Option

WithBorder enables a border around and within the table using the supplied lipgloss.Border (e.g. lipgloss.NormalBorder(), lipgloss.RoundedBorder()). Tables are borderless by default.

func WithColumnStyle

func WithColumnStyle(fn func(col int, val string) lipgloss.Style) Option

WithColumnStyle registers a per-cell style callback for data rows. fn is invoked for every data cell with its zero-based column index and rendered text; the returned style composes with Styles.Rows (and any RowStyle) via Inherit, so fields the caller sets win and unset fields fall back to the defaults.

func WithColumns

func WithColumns(cols ...Column) Option

WithColumns sets explicit column metadata, required when the row type T is a slice or array (untyped rows). It is rejected at schema-build time when T is a struct.

func WithHeaderStyle

func WithHeaderStyle(fn func(col int, val string) lipgloss.Style) Option

WithHeaderStyle registers a per-cell style callback for header cells. fn is invoked for every header cell with its zero-based column index and header text; the returned style composes with Styles.Columns via Inherit.

func WithHeaders

func WithHeaders(names ...string) Option

WithHeaders is sugar for WithColumns when only headers are needed — every cell's formatter is then derived from the row element type.

func WithNow

func WithNow(now func() time.Time) Option

WithNow enables relative-time rendering for time.Time fields, using the supplied clock as "now". Pass time.Now in production; pin a fixed time.Time in tests.

func WithRowSelector

func WithRowSelector(fn func(row int) bool) Option

WithRowSelector sets the row-selection predicate. When non-nil it enables the one-cell left gutter: matching rows render the indicator (default "❯", overridable via WithSelectedIndicator), the header and non-matching rows render a single space.

The `row` argument is the absolute index into the input sequence, unaffected by WithViewport's top offset. Compose multiple conditions inside the predicate when needed.

func WithRowStyle

func WithRowStyle(fn func(row int) lipgloss.Style) Option

WithRowStyle registers a per-row style callback. fn is invoked once per data row with its zero-based index (the header is not counted); the returned style composes with Styles.Rows via Inherit and runs before any ColumnStyle.

func WithSelectedIndicator

func WithSelectedIndicator(s string) Option

WithSelectedIndicator sets the gutter glyph for rows matched by the WithRowSelector predicate. Defaults to "❯". Must be a single visual cell (e.g. "❯", "▶", "→"); multi-cell strings will misalign columns.

func WithStyles

func WithStyles(s *stripes.Styles) Option

WithStyles overrides the rendering styles.

func WithViewport

func WithViewport(height, top int) Option

WithViewport restricts rendering to `height` rows starting at row `top` (0-indexed, absolute). When totalRows > height a one-cell scrollbar is drawn on the right of every line (track "│", thumb "▌") with the thumb's position computed from (top, height, totalRows). When the data fits, no scrollbar is drawn. height must be >= 1; top is clamped to [0, max(0, totalRows-height)].

type Options

type Options struct {
	// Styles supplies colours and padding used by the underlying lipgloss
	// table. Defaults to stripes.DefaultStyles.
	Styles *stripes.Styles

	// Now, when non-nil, switches time.Time fields to relative rendering
	// ("5m ago", "3h ago", "5d ago") anchored at the returned instant.
	// When nil (the default) time.Time renders as an absolute timestamp.
	Now func() time.Time

	// Border draws table borders using the supplied lipgloss.Border
	// (e.g. lipgloss.NormalBorder(), lipgloss.RoundedBorder()). The zero
	// value renders borderless — only padding separates cells.
	Border lipgloss.Border

	// Columns supplies explicit column metadata. Required when T is a
	// slice or array (untyped rows); rejected when T is a struct, where
	// the schema is derived from struct fields and tags instead.
	Columns []Column

	// ColumnStyle, when non-nil, is called once per data cell. The
	// returned lipgloss.Style is composed with Styles.Rows via Inherit:
	// fields set on the returned style win, defaults fill the rest. col
	// is the zero-based column index; val is the post-width-fit,
	// post-internal-colorize cell text.
	ColumnStyle func(col int, val string) lipgloss.Style

	// HeaderStyle, when non-nil, is called once per header cell. The
	// returned lipgloss.Style is composed with Styles.Columns via
	// Inherit: fields set on the returned style win, defaults (e.g.
	// bold) fill the rest. col is the zero-based column index; val is
	// the header text.
	HeaderStyle func(col int, val string) lipgloss.Style

	// RowStyle, when non-nil, is called once per data row before any
	// per-cell ColumnStyle is applied. The returned lipgloss.Style is
	// composed with Styles.Rows via Inherit. row is the zero-based data
	// row index (the header row is not counted).
	RowStyle func(row int) lipgloss.Style

	// RowSelector, when non-nil, draws a one-cell gutter on the left:
	// rows for which the predicate returns true render
	// SelectedIndicator, other rows and the header render a single
	// space. The `row` argument is the absolute row index (unaffected
	// by ViewportTop).
	RowSelector func(row int) bool

	// SelectedIndicator is the gutter glyph drawn for rows matched by
	// RowSelector. Empty means use the package default ("❯").
	SelectedIndicator string

	// ViewportHeight, when > 0, restricts rendering to that many rows
	// starting at ViewportTop. When totalRows > ViewportHeight a
	// one-cell scrollbar is drawn on the right. Zero disables the
	// viewport — all rows render.
	ViewportHeight int

	// ViewportTop is the absolute row index of the first visible row
	// when ViewportHeight > 0. Clamped to [0, max(0, totalRows-height)].
	ViewportTop int
}

Options controls table rendering.

type Writer

type Writer[T any] func(io.Writer, iter.Seq2[T, error]) error

Writer renders an iter.Seq2 of T-values (with optional per-row errors) into the destination io.Writer.

func NewWriter

func NewWriter[T any](opts ...Option) Writer[T]

NewWriter precomputes the column schema for T and returns a Writer that reuses it across calls.

Jump to

Keyboard shortcuts

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