progress

package
v1.2.1 Latest Latest
Warning

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

Go to latest
Published: Apr 14, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Overview

Package progress provides a flexible progress reporting system with handler-based architecture for composable output handling (logging, console, Bubble Tea UI).

The package follows ADR-012 (Structured Output and Shadow Logging) by ensuring all progress events are logged to the audit stream while providing visual feedback through the status stream.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func IsInteractive

func IsInteractive(w io.Writer) bool

IsInteractive determines if the given writer is an interactive terminal. This is used to decide whether to use Bubble Tea (interactive) or simple console output (non-interactive).

Returns false for: - Piped output - Redirected output - Non-file writers (like bytes.Buffer)

Per ADR-012, this enables automatic switching between: - Interactive TTY: Bubble Tea with animations - Non-interactive (CI/piped): Simple line-based output

func IsStderrInteractive

func IsStderrInteractive() bool

IsStderrInteractive checks if stderr is an interactive terminal. This is a convenience function since progress typically outputs to stderr.

func IsStdoutInteractive

func IsStdoutInteractive() bool

IsStdoutInteractive checks if stdout is an interactive terminal.

Types

type CompositeHandler

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

CompositeHandler combines multiple handlers into one. Events are dispatched to all handlers sequentially. This enables patterns like: log + render + metrics simultaneously.

Example:

handler := NewCompositeHandler(
    NewLogHandler(),      // Shadow logging (always)
    NewConsoleHandler(os.Stderr), // Simple output
)

func NewCompositeHandler

func NewCompositeHandler(handlers ...Handler) *CompositeHandler

NewCompositeHandler creates a new CompositeHandler with the given handlers.

func (*CompositeHandler) Add

func (h *CompositeHandler) Add(handler Handler)

Add adds a handler to the composite (thread-safe).

func (*CompositeHandler) Handlers

func (h *CompositeHandler) Handlers() []Handler

Handlers returns a copy of the handlers slice (thread-safe).

func (*CompositeHandler) Len

func (h *CompositeHandler) Len() int

Len returns the number of handlers (thread-safe).

func (*CompositeHandler) OnProgress

func (h *CompositeHandler) OnProgress(ctx context.Context, event Event)

OnProgress implements Handler by dispatching to all handlers.

func (*CompositeHandler) RemoveAt

func (h *CompositeHandler) RemoveAt(index int) bool

RemoveAt removes a handler at the given index (thread-safe). Returns true if the handler was removed, false if index out of bounds.

type ConsoleHandler

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

ConsoleHandler writes simple progress output to a writer (typically stderr). This provides non-interactive progress feedback for terminals that don't support Bubble Tea or when running in CI/piped environments.

func NewConsoleHandler

func NewConsoleHandler(w io.Writer) *ConsoleHandler

NewConsoleHandler creates a new ConsoleHandler writing to the given writer.

func (*ConsoleHandler) OnProgress

func (h *ConsoleHandler) OnProgress(ctx context.Context, event Event)

OnProgress implements Handler by writing formatted progress to the writer. Respects context cancellation.

type Event

type Event struct {
	// Type indicates what kind of event this is.
	Type EventType

	// Phase identifies the current phase of a multi-phase operation.
	// Examples: "downloading", "validating", "installing"
	Phase string

	// Task identifies the specific task within a phase.
	// Examples: "file1.zip", "package.json"
	Task string

	// Current progress value (0 to Total).
	Current int64

	// Total expected value (for percentage calculation).
	// If Total is 0, progress is indeterminate.
	Total int64

	// Message is a human-readable description of the current state.
	Message string

	// Error contains the error if Type is EventError.
	Error error

	// Timestamp when this event was created.
	Timestamp time.Time

	// Metadata contains arbitrary key-value pairs for extensibility.
	// Examples: {"bytes_per_second": 1024, "eta_seconds": 30}
	Metadata map[string]interface{}
}

Event represents a progress event that handlers can observe. Events are immutable once created and should be passed by value.

func NewEvent

func NewEvent(eventType EventType, message string) Event

NewEvent creates a new Event with the given type and message. Timestamp is set to the current time.

func (Event) IsIndeterminate

func (e Event) IsIndeterminate() bool

IsIndeterminate returns true if progress cannot be calculated.

func (Event) Percentage

func (e Event) Percentage() float64

Percentage returns the progress as a percentage (0-100). Returns -1 if progress is indeterminate (Total <= 0).

func (Event) WithError

func (e Event) WithError(err error) Event

WithError returns a new Event with the error set.

func (Event) WithMeta

func (e Event) WithMeta(key string, value interface{}) Event

WithMeta returns a new Event with an additional metadata key-value pair.

func (Event) WithPhase

func (e Event) WithPhase(phase string) Event

WithPhase returns a new Event with the phase set.

func (Event) WithProgress

func (e Event) WithProgress(current, total int64) Event

WithProgress returns a new Event with current/total progress set.

func (Event) WithTask

func (e Event) WithTask(task string) Event

WithTask returns a new Event with the task set.

type EventType

type EventType int

EventType represents the type of progress event.

const (
	// EventStart indicates the beginning of an operation.
	EventStart EventType = iota
	// EventProgress indicates progress update during an operation.
	EventProgress
	// EventComplete indicates successful completion.
	EventComplete
	// EventError indicates an error occurred.
	EventError
	// EventWarning indicates a non-fatal warning.
	EventWarning
)

func (EventType) String

func (e EventType) String() string

String returns the string representation of EventType.

type Handler

type Handler interface {
	// OnProgress is called for each progress event.
	// Context can be used for cancellation or deadline propagation.
	OnProgress(ctx context.Context, event Event)
}

Handler is the core interface for receiving progress events. Implementations can render to terminal, log to file, emit metrics, etc.

Design follows the Observer pattern: handlers subscribe to events emitted by the Reporter, decoupling event production from consumption.

Implementations should be safe for concurrent calls from multiple goroutines.

type HandlerFunc

type HandlerFunc func(ctx context.Context, event Event)

HandlerFunc is an adapter to allow ordinary functions as handlers. This follows the same pattern as http.HandlerFunc.

func (HandlerFunc) OnProgress

func (f HandlerFunc) OnProgress(ctx context.Context, event Event)

OnProgress implements Handler interface by calling the function.

type LogHandler

type LogHandler struct {
	// Logger to use (defaults to global log.Logger)
	Logger zerolog.Logger

	// Component name for log entries
	Component string
}

LogHandler writes progress events to the structured log (Audit Stream). This implements ADR-012's shadow logging pattern, ensuring all progress events are recorded for debugging and auditing purposes.

func NewLogHandler

func NewLogHandler(opts ...LogHandlerOption) *LogHandler

NewLogHandler creates a new LogHandler with the given options.

func (*LogHandler) OnProgress

func (h *LogHandler) OnProgress(ctx context.Context, event Event)

OnProgress implements Handler by logging the event to the audit stream. Respects context cancellation.

type LogHandlerOption

type LogHandlerOption func(*LogHandler)

LogHandlerOption configures a LogHandler.

func WithComponent

func WithComponent(name string) LogHandlerOption

WithComponent sets the component name for log entries.

func WithLogger

func WithLogger(l zerolog.Logger) LogHandlerOption

WithLogger sets a custom zerolog.Logger.

type MockHandler

type MockHandler struct {
	Events []Event
	// contains filtered or unexported fields
}

MockHandler records all events for testing. This follows the same pattern as internal/ui/mock.go (ADR-003 compliant).

func NewMockHandler

func NewMockHandler() *MockHandler

NewMockHandler creates a new MockHandler.

func (*MockHandler) EventCount

func (h *MockHandler) EventCount() int

EventCount returns the number of recorded events.

func (*MockHandler) EventsOfType

func (h *MockHandler) EventsOfType(t EventType) []Event

EventsOfType returns all events of a specific type.

func (*MockHandler) GetEvents

func (h *MockHandler) GetEvents() []Event

GetEvents returns a copy of all events (thread-safe).

func (*MockHandler) HasEventWithMessage

func (h *MockHandler) HasEventWithMessage(message string) bool

HasEventWithMessage checks if any event contains the given message.

func (*MockHandler) LastEvent

func (h *MockHandler) LastEvent() (Event, bool)

LastEvent returns the most recent event. Returns empty Event and false if no events recorded.

func (*MockHandler) OnProgress

func (h *MockHandler) OnProgress(_ context.Context, event Event)

OnProgress implements Handler by recording the event.

func (*MockHandler) Reset

func (h *MockHandler) Reset()

Reset clears recorded events.

type NopHandler

type NopHandler struct{}

NopHandler is a handler that does nothing. Useful for testing or when progress reporting should be disabled.

This implements the Null Object pattern to avoid nil checks.

func NewNopHandler

func NewNopHandler() *NopHandler

NewNopHandler creates a new NopHandler.

func (*NopHandler) OnProgress

func (h *NopHandler) OnProgress(_ context.Context, _ Event)

OnProgress implements Handler by doing nothing.

type Option

type Option func(*Reporter)

Option configures a Reporter.

func WithHandler

func WithHandler(h Handler) Option

WithHandler sets the handler for the reporter.

func WithInteractive

func WithInteractive(interactive bool) Option

WithInteractive is a convenience option that uses stderr with the given interactivity setting.

func WithOutput

func WithOutput(w io.Writer, interactive bool) Option

WithOutput configures the reporter to output to the given writer. If interactive is true, uses TeaHandler; otherwise uses ConsoleHandler. LogHandler is always included for shadow logging (ADR-012 compliance).

type Reporter

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

Reporter provides a convenient API for emitting progress events. It manages handlers and provides helper methods for common patterns.

Example usage:

reporter := progress.NewReporter(
    progress.WithOutput(cmd.ErrOrStderr()),
    progress.WithInteractive(useUI),
)
reporter.Start(ctx, "Processing files")
for i, file := range files {
    reporter.Progress(ctx, int64(i+1), int64(len(files)), file.Name)
}
reporter.Complete(ctx, "Done")

func NewReporter

func NewReporter(opts ...Option) *Reporter

NewReporter creates a new Reporter with the given options. If no handler is provided, a default non-interactive handler is used.

func (*Reporter) Complete

func (r *Reporter) Complete(ctx context.Context, message string)

Complete emits a completion event.

func (*Reporter) Emit

func (r *Reporter) Emit(ctx context.Context, event Event)

Emit allows emitting custom events.

func (*Reporter) Error

func (r *Reporter) Error(ctx context.Context, err error, message string)

Error emits an error event.

func (*Reporter) Phase

func (r *Reporter) Phase() string

Phase returns the current phase.

func (*Reporter) Progress

func (r *Reporter) Progress(ctx context.Context, current, total int64, message string)

Progress emits a progress event with current/total values.

func (*Reporter) ProgressFunc

func (r *Reporter) ProgressFunc(ctx context.Context, message string) func(current, total int64)

ProgressFunc returns a function suitable for passing to operations that report progress via callback.

Example:

progressFn := reporter.ProgressFunc(ctx, "Downloading")
download(url, progressFn) // calls progressFn(bytesRead, totalBytes)

func (*Reporter) SetHandler

func (r *Reporter) SetHandler(h Handler)

SetHandler changes the handler (thread-safe).

func (*Reporter) SetPhase

func (r *Reporter) SetPhase(phase string)

SetPhase sets the current phase for subsequent events.

func (*Reporter) Start

func (r *Reporter) Start(ctx context.Context, message string)

Start emits a start event.

func (*Reporter) Warning

func (r *Reporter) Warning(ctx context.Context, message string)

Warning emits a warning event.

type Style

type Style struct {
	// Spinner configuration
	SpinnerFrames   []string
	SpinnerInterval time.Duration
	SpinnerStyle    lipgloss.Style

	// Progress bar configuration
	BarWidth      int
	BarChar       string
	BarEmptyChar  string
	BarStyle      lipgloss.Style
	BarEmptyStyle lipgloss.Style

	// Status styles
	SuccessStyle lipgloss.Style
	ErrorStyle   lipgloss.Style
	WarningStyle lipgloss.Style

	// Text styles
	PhaseStyle   lipgloss.Style
	TaskStyle    lipgloss.Style
	MessageStyle lipgloss.Style
	CounterStyle lipgloss.Style
}

Style defines the visual appearance of progress elements.

func DefaultStyle

func DefaultStyle() *Style

DefaultStyle returns the default progress style.

func MinimalStyle

func MinimalStyle() *Style

MinimalStyle returns a minimal style with fewer visual elements.

type TeaHandler

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

TeaHandler renders progress events using Bubble Tea. It provides animated spinners, progress bars, and real-time updates.

This handler is designed to be used as part of a CompositeHandler, typically alongside LogHandler for shadow logging (ADR-012 compliance).

func NewTeaHandler

func NewTeaHandler(out io.Writer) *TeaHandler

NewTeaHandler creates a new Bubble Tea based progress handler.

func (*TeaHandler) OnProgress

func (h *TeaHandler) OnProgress(ctx context.Context, event Event)

OnProgress implements Handler by sending events to the Bubble Tea model. Respects context cancellation.

func (*TeaHandler) Stop

func (h *TeaHandler) Stop()

Stop gracefully stops the Bubble Tea program.

Jump to

Keyboard shortcuts

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