termio

package
v1.4.1 Latest Latest
Warning

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

Go to latest
Published: Feb 1, 2026 License: AGPL-3.0 Imports: 7 Imported by: 0

Documentation

Overview

Package termio provides interruptible I/O primitives and terminal handling.

It solves common issues with blocking I/O in Go CLI tools, particularly on Windows, where a blocked read from stdin can prevent signal delivery or cause hangs.

Key features:

  • InterruptibleReader: A reader that respects context cancellation.
  • Open: Platform-safe terminal opening (uses CONIN$ on Windows).
  • Upgrade: Automatic detection and upgrade of readers to terminal-aware handles.

Safety

The InterruptibleReader uses a "Shielded Return" strategy. If data arrives exactly as the context is cancelled, the reader prioritizes returning the data (Data First, Error Second). The *next* read will then check for cancellation. This ensures no data loss occurs when reading from pipes or streams.

For interactive prompts (e.g., "Confirm Delete y/N"), use InterruptibleReader.ReadInteractive. This method enforces "Error First" logic, discarding potential race-condition input to prioritize safety.

Index

Constants

This section is empty.

Variables

View Source
var ErrInterrupted = errors.New("interrupted")

Functions

func IsInterrupted

func IsInterrupted(err error) bool

IsInterrupted checks if the error is related to an interruption (Context Canceled, ErrInterrupted, or EOF).

func IsTerminal

func IsTerminal(f *os.File) bool

IsTerminal checks if the given file is a terminal. For now, we rely on basic checks or x/term if needed, but for simple cases we just assume it's okay. We might add x/term dependency later if we need IsTerminal check.

func Open

func Open() (io.ReadCloser, error)

Open returns the standard input reader. On POSIX, it simply returns os.Stdin.

func Upgrade added in v0.1.1

func Upgrade(r io.Reader) (io.Reader, error)

Upgrade checks if the provided reader is a file-based terminal. If it is, it upgrades it to a safe terminal reader (like CONIN$ on Windows) using Open(). If it is not (e.g. pipe, file, buffer), it returns the original reader.

Types

type InterruptibleReader

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

InterruptibleReader wraps an io.Reader and checks for cancellation before and after reads. Note: The underlying Read() call may still block! This wrapper primarily ensures that if the context is cancelled *before* we read, we return immediately, and if cancelled *during* the read (and the read returns), we prioritize the cancellation error.

func NewInterruptibleReader

func NewInterruptibleReader(base io.Reader, cancel <-chan struct{}) *InterruptibleReader

NewInterruptibleReader returns a reader that checks the cancel channel.

func (*InterruptibleReader) Read

func (r *InterruptibleReader) Read(p []byte) (n int, err error)

func (*InterruptibleReader) ReadInteractive added in v1.4.0

func (r *InterruptibleReader) ReadInteractive(p []byte) (n int, err error)

ReadInteractive reads from the underlying source but enforces a "Strict Cancel" policy. Unlike Read() (which prioritizes Data over Error to prevent data loss), ReadInteractive prioritizes the Cancellation Error over Data.

If the context is cancelled while reading (or immediately after), any data read from the OS buffer is DISCARDED, and ErrInterrupted is returned.

Use this for interactive prompts (e.g. "Do you want to continue? y/N") where a User Interrupt (Ctrl+C) should always take precedence over the input "y", preventing accidental execution of dangerous actions.

Jump to

Keyboard shortcuts

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