Documentation
¶
Overview ¶
Package termio provides a dependency-light bundle of terminal I/O primitives for Go CLI programs.
Overview ¶
Streams is the central type. It bundles an input reader (In) and two output writers (Out and ErrOut) together with TTY detection and terminal width querying. Call System to get streams backed by the real OS file descriptors:
s := termio.System() fmt.Fprintln(s.Out, "hello, world")
Architecture ¶
The three streams are independent: Out and ErrOut are *Writer values rather than plain io.Writer values. Each Writer layers three concerns on top of its raw stream:
- Color adaptation: an optional ColorPolicy intercepts writes and strips, translates, or passes through ANSI sequences. When no policy is configured, bytes reach the raw stream unmodified.
- Sticky errors: the first write error is latched per stream. An error on Out does not affect ErrOut, and vice versa.
- FD preservation: Writer.Fd returns the original file descriptor, so downstream libraries (bubbletea, glamour, lipgloss) can detect the terminal even when they receive the wrapped value.
The core package depends only on golang.org/x/term. Color adaptation is opt-in and lives in the gopherly.dev/termio/colorprofile sub-package; callers that do not need color never compile that dependency.
Quick start — with color adaptation ¶
import (
"os"
"gopherly.dev/termio"
"gopherly.dev/termio/colorprofile"
)
s := termio.System(
termio.WithColorPolicy(colorprofile.Detect(os.Stdout, os.Environ())),
)
fmt.Fprintln(s.Out, "\x1b[32mgreen\x1b[0m or plain, depending on the terminal")
Quick start — without color ¶
s := termio.System() // no color dep compiled fmt.Fprintln(s.Out, "plain output")
Testing ¶
The gopherly.dev/termio/termiotest package provides buffer-backed helpers that return the underlying *bytes.Buffer values for assertion:
s, _, out, _ := termiotest.New() fmt.Fprintln(s.Out, "test output") assert.Equal(t, "test output\n", out.String())
Exported surface ¶
Type Description Streams central I/O bundle (In, Out, ErrOut) Writer stream wrapper (color + sticky error + FD) ColorPolicy interface for ANSI color adaptation Option functional option for New / System Constructor Description System streams from os.Stdin / os.Stdout / os.Stderr New streams from caller-supplied readers/writers Option Description WithColorPolicy inject a ColorPolicy into Out and ErrOut Constant Description DefaultWidth fallback column width (80) when width is unknown InvalidFd sentinel Fd value (^uintptr(0)) for non-file streams
Index ¶
- Constants
- type ColorPolicy
- type Option
- type Streams
- func (s *Streams) Err() error
- func (s *Streams) IsInteractive() bool
- func (s *Streams) IsStderrTTY() bool
- func (s *Streams) IsStdinTTY() bool
- func (s *Streams) IsStdoutTTY() bool
- func (s *Streams) RawErrOut() io.Writer
- func (s *Streams) RawIn() io.Reader
- func (s *Streams) RawOut() io.Writer
- func (s *Streams) SetStderrTTY(v bool)
- func (s *Streams) SetStdinTTY(v bool)
- func (s *Streams) SetStdoutTTY(v bool)
- func (s *Streams) TerminalWidth() int
- type Writer
Examples ¶
Constants ¶
const DefaultWidth = 80
DefaultWidth is the terminal column width assumed when the underlying stream is not a terminal or its size cannot be determined.
const InvalidFd = ^uintptr(0)
InvalidFd is the sentinel value returned by Writer.Fd when the underlying stream is not backed by a real file descriptor (for example, when a bytes.Buffer is supplied in tests). It equals ^uintptr(0), the maximum uintptr value, which never refers to a valid OS descriptor on any supported platform.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type ColorPolicy ¶
type ColorPolicy interface {
// Apply wraps w with a writer that enforces this policy's color
// translation rules. The returned writer must forward all writes to
// w, potentially transforming ANSI sequences in transit. Apply must
// not retain w beyond the lifetime of the returned writer.
Apply(w io.Writer) io.Writer
}
ColorPolicy adapts ANSI escape sequences for a specific terminal capability. Implementations intercept writes and strip, translate, or pass through color codes based on what the destination terminal supports.
The core termio package does not ship a built-in implementation; callers who need color adaptation import gopherly.dev/termio/colorprofile and pass the result of [colorprofile.Detect] via WithColorPolicy. When no policy is configured, writes reach the raw stream unmodified.
Implementors must be safe for concurrent use from multiple goroutines.
type Option ¶
type Option func(*config)
Option configures a Streams at construction time via New or System. Use the With* helpers to build options; do not implement Option directly.
func WithColorPolicy ¶
func WithColorPolicy(p ColorPolicy) Option
WithColorPolicy sets the ColorPolicy applied to Out and ErrOut during Writer construction. When not set (or set to nil), writes reach the raw stream unmodified.
Example using the bundled colorprofile adapter:
import "gopherly.dev/termio/colorprofile"
s := termio.System(
termio.WithColorPolicy(colorprofile.Detect(os.Stdout, os.Environ())),
)
type Streams ¶
type Streams struct {
// In is the input stream. It is the user-supplied [io.Reader] (typically
// [os.Stdin]) and is not wrapped. Treat as read-only after construction.
In io.Reader
// Out is the primary output stream. Writes are color-adapted (when a
// [ColorPolicy] is set), the first error is latched, and the original
// FD is preserved for terminal-detection by downstream libraries.
Out *Writer
// ErrOut is the diagnostics stream. It has the same wrapping policy as
// Out, but its sticky error is independent.
ErrOut *Writer
// contains filtered or unexported fields
}
Streams bundles the three standard I/O channels a CLI program needs — input, output, and diagnostics — together with terminal capability detection. It is the central type of the termio package.
Out and ErrOut are *Writer values rather than plain io.Writer values: the concrete type surfaces per-stream sticky-error state and FD preservation without type assertions. Color adaptation is injected via WithColorPolicy; when no policy is configured the raw stream is used directly. An error on Out does not affect ErrOut, and vice versa.
Streams is safe to construct concurrently with other operations but is not safe for concurrent mutation via the SetXxxTTY methods.
func New ¶
New returns a Streams over the supplied streams. Pass *os.File values for production code (TTY detection works against the real file descriptor). For tests, prefer gopherly.dev/termio/termiotest.New, which also returns the underlying buffers for assertion.
Nil arguments are replaced with safe no-op streams: a reader that returns io.EOF immediately and a writer that discards all bytes.
Example ¶
ExampleNew shows how to supply your own readers and writers. This is the pattern used in tests and in code that needs to redirect output.
package main
import (
"fmt"
"gopherly.dev/termio/termiotest"
)
func main() {
s, _, out, errOut := termiotest.New()
fmt.Fprintln(s.Out, "product output") //nolint:errcheck
fmt.Fprintln(s.ErrOut, "diagnostic message") //nolint:errcheck
fmt.Print("out: ", out.String())
fmt.Print("err: ", errOut.String())
}
Output: out: product output err: diagnostic message
func System ¶
System returns a Streams backed by os.Stdin, os.Stdout, and os.Stderr. TTY status and terminal width are detected against the real file descriptors. Apply functional options to configure color adaptation.
Example ¶
ExampleSystem demonstrates the zero-config constructor. In production code this connects to os.Stdin, os.Stdout, and os.Stderr with TTY detection performed against the real file descriptors.
package main
import (
"fmt"
"gopherly.dev/termio/termiotest"
)
func main() {
// For reproducible example output we use termiotest.New() here; real
// programs would call termio.System().
s, _, out, _ := termiotest.New()
fmt.Fprintln(s.Out, "hello from termio") //nolint:errcheck
fmt.Print(out.String())
}
Output: hello from termio
func (*Streams) Err ¶
Err returns the first write error encountered by either Out or ErrOut, or nil when all writes have succeeded so far. When both streams have latched errors, both are reported joined with errors.Join.
func (*Streams) IsInteractive ¶
IsInteractive reports whether both stdin and stdout are terminals. When true, the program may safely display interactive prompts.
func (*Streams) IsStderrTTY ¶
IsStderrTTY reports whether the diagnostics stream is a terminal.
func (*Streams) IsStdinTTY ¶
IsStdinTTY reports whether the input stream is a terminal.
func (*Streams) IsStdoutTTY ¶
IsStdoutTTY reports whether the primary output stream is a terminal.
func (*Streams) RawErrOut ¶
RawErrOut returns the unwrapped diagnostics stream supplied to New or System.
func (*Streams) SetStderrTTY ¶
SetStderrTTY overrides the cached stderr TTY status. See Streams.SetStdinTTY for typical usage.
func (*Streams) SetStdinTTY ¶
SetStdinTTY overrides the cached stdin TTY status. Use in tests to test interactive code paths with a buffer-backed stream.
func (*Streams) SetStdoutTTY ¶
SetStdoutTTY overrides the cached stdout TTY status. See Streams.SetStdinTTY for typical usage.
func (*Streams) TerminalWidth ¶
TerminalWidth returns the column width of the controlling terminal, or DefaultWidth when stdout is not a terminal or its size cannot be queried.
type Writer ¶
type Writer struct {
// contains filtered or unexported fields
}
Writer is a stream wrapper that layers three concerns on top of a raw io.Writer:
- Color adaptation: an optional ColorPolicy intercepts writes and strips, translates, or passes through ANSI sequences.
- Sticky error: the first write error is latched and returned by all subsequent writes, preventing silent partial output. The error is independent per Writer, so an error on Out does not affect ErrOut.
- FD preservation: Writer.Fd returns the file descriptor of the original underlying stream, letting downstream libraries (bubbletea, glamour, lipgloss) detect the terminal even when they receive a wrapped value.
Writer satisfies io.Writer and interface{ Fd() uintptr }. Obtain a Writer via New or System; do not construct one directly.
func (*Writer) Err ¶
Err returns the first write error recorded by this Writer, or nil when all writes have succeeded so far. The error is latched: once set it is returned by every subsequent Writer.Write call and remains accessible via Err.
Example ¶
ExampleWriter_Err illustrates the per-stream sticky error. After a write fails, the error is latched on that Writer only — the other stream continues to work normally.
package main
import (
"errors"
"fmt"
"io"
"gopherly.dev/termio"
)
func main() {
errWriter := &alwaysFailWriter{}
s := termio.New(nil, errWriter, io.Discard)
fmt.Fprintln(s.Out, "this will fail") //nolint:errcheck
fmt.Println("Out.Err:", s.Out.Err())
fmt.Println("ErrOut.Err:", s.ErrOut.Err())
}
// alwaysFailWriter returns an error on every write call.
type alwaysFailWriter struct{}
func (a *alwaysFailWriter) Write(_ []byte) (int, error) {
return 0, errors.New("always fails")
}
Output: Out.Err: always fails ErrOut.Err: <nil>
func (*Writer) Fd ¶
Fd returns the file descriptor of the underlying stream, or InvalidFd when the stream is not backed by a real OS file descriptor (for example, when a bytes.Buffer was supplied in tests).
The returned value is a bare uintptr so that the Go garbage collector does not treat it as a live reference to the os.File, matching the contract of os.File.Fd. Callers that need to call syscalls must ensure the File (or the Streams that owns this Writer) remains open for the duration of the operation.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package colorprofile provides a termio.ColorPolicy implementation backed by github.com/charmbracelet/colorprofile.
|
Package colorprofile provides a termio.ColorPolicy implementation backed by github.com/charmbracelet/colorprofile. |
|
Package termiotest provides *termio.Streams test helpers with buffer- backed streams and convenience accessors for the underlying buffers.
|
Package termiotest provides *termio.Streams test helpers with buffer- backed streams and convenience accessors for the underlying buffers. |