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.
InterruptibleReader is cancellation-aware; the underlying Read() call can still block.
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 ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ErrInterrupted = errors.New("interrupted")
Functions ¶
func IsInterrupted ¶
IsInterrupted checks if the error is related to an interruption (Context Canceled, ErrInterrupted, or EOF).
func IsTerminal ¶
IsTerminal checks if the given file is a terminal. This is used to decide whether to apply terminal enhancements like raw mode.
Types ¶
type Console ¶ added in v0.3.0
type Console struct {
// contains filtered or unexported fields
}
Console provides platform-specific terminal mode management.
It abstracts raw mode toggling and terminal size queries across Windows (Console API) and POSIX (termios) platforms.
Acquire a Console with NewConsole. Always defer Console.Restore to return the terminal to its original state.
func NewConsole ¶ added in v0.3.0
NewConsole wraps f as a Console. f must be a file descriptor that is connected to a terminal (e.g. os.Stdin, os.Stdout, or a PTY controller).
Returns an error if f is not a recognized terminal file descriptor or if the underlying terminal state cannot be read.
func (*Console) EnableRawMode ¶ added in v0.3.0
EnableRawMode puts the terminal into raw mode: input is passed through character by character, echoing is disabled, and signal generation from special keys (Ctrl+C, Ctrl+Z, etc.) is suppressed.
Call Console.Restore (typically via defer) to return to the previous mode.
func (*Console) Restore ¶ added in v0.3.0
Restore returns the terminal to the state it had when NewConsole was called.
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.
Example ¶
package main
import (
"context"
"fmt"
"io"
"strings"
"time"
"github.com/aretw0/procio/termio"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
// A reader that simulates continuous input
reader := strings.NewReader("hello\nworld\n")
// Wrap it with InterruptibleReader
ir := termio.NewInterruptibleReader(reader, ctx.Done())
// Read in a loop
go func() {
buf := make([]byte, 10)
for {
n, err := ir.Read(buf)
if err != nil {
if err == io.EOF {
fmt.Println("EOF reached")
} else {
// Likely context cancellation propagated
fmt.Printf("Read error: %v\n", err)
}
return
}
fmt.Printf("Read %d bytes\n", n)
}
}()
// Process for a bit, then cancel the context
time.Sleep(10 * time.Millisecond)
cancel()
// Wait for goroutine to yield
time.Sleep(50 * time.Millisecond)
// Since NewReader is fast, it might just hit EOF before the cancel.
// This example demonstrates structure rather than precise timing.
}
func (*InterruptibleReader) ReadInteractive ¶
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.