exec

package
v0.0.0-...-167ac4f Latest Latest
Warning

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

Go to latest
Published: Oct 7, 2025 License: MIT Imports: 14 Imported by: 0

Documentation

Overview

Package exec provides utilities for executing external commands with beautiful UX.

The exec package is completely domain-agnostic and provides three main components:

1. Executor - Runs system commands with context support, streaming output, and spinners 2. CommandRegistry - Plugin system for domain packages to register custom command wrappers 3. GenericCommand - Fluent API for building and executing commands

Basic Usage

Create an executor and run commands:

executor := exec.NewExecutor(nil)
err := executor.Run(ctx, "echo", "Hello, World!")

Command Registry Pattern

Domain packages (like Firebird) implement CommandWrapper to register commands:

type MigrateCommand struct {
    migrationsDir string
    databaseURL   string
}

func (m MigrateCommand) Name() string { return "migrate" }
func (m MigrateCommand) Description() string { return "Run database migrations" }
func (m MigrateCommand) Execute(ctx context.Context, exec *exec.Executor) error {
    return exec.RunWithSpinner(ctx, "Running migrations", "migrate", "up")
}

Commands receive an Executor at execution time (not construction), enabling: - Clean dependency injection - Easy testing with mocked executors - No tight coupling between packages

Register commands globally:

exec.RegisterCommand(MigrateCommand{migrationsDir: "./migrations", databaseURL: "..."})

Execute registered commands:

exec.Execute(ctx, "migrate", executor)

Design Principles

- Domain Agnostic: This package knows nothing about specific tools (migrate, sqlc, etc.) - Dependency Injection: Commands receive executors, they don't store them - Beautiful UX: Spinners, colored output, and helpful error messages built-in - Testable: Full mocking support for testing command execution

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func ClearRegistry

func ClearRegistry()

ClearRegistry clears all registered commands (useful for testing)

func Execute

func Execute(ctx context.Context, name string, exec *Executor) error

Execute runs a registered command from the global registry with the given executor

func ListCommands

func ListCommands() []string

ListCommands returns all registered command names from the global registry

func RegisterCommand

func RegisterCommand(cmd CommandWrapper) error

RegisterCommand registers a command wrapper in the global registry

Types

type CommandRegistry

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

CommandRegistry manages registered command wrappers

Example
// This example shows how domain packages would register their commands
// The exec package itself doesn't know about these specific commands

// Register the command
if err := RegisterCommand(ExampleMyCommand{args: []string{"hello", "world"}}); err != nil {
	fmt.Printf("Registration failed: %v\n", err)
}

// Later, retrieve and execute with an executor
if cmd, ok := GetCommand("my-command"); ok {
	executor := NewExecutor(nil)
	if err := cmd.Execute(context.Background(), executor); err != nil {
		fmt.Printf("Execution failed: %v\n", err)
	}
}

func NewCommandRegistry

func NewCommandRegistry() *CommandRegistry

NewCommandRegistry creates a new command registry instance

func (*CommandRegistry) Clear

func (r *CommandRegistry) Clear()

Clear removes all registered commands

func (*CommandRegistry) Execute

func (r *CommandRegistry) Execute(ctx context.Context, name string, exec *Executor) error

Execute runs a command by name if it exists

func (*CommandRegistry) Get

func (r *CommandRegistry) Get(name string) (CommandWrapper, bool)

Get retrieves a command wrapper by name

func (*CommandRegistry) Has

func (r *CommandRegistry) Has(name string) bool

Has checks if a command is registered

func (*CommandRegistry) List

func (r *CommandRegistry) List() []string

List returns all registered command names in sorted order

func (*CommandRegistry) ListWithDescriptions

func (r *CommandRegistry) ListWithDescriptions() map[string]string

ListWithDescriptions returns all registered commands with their descriptions

func (*CommandRegistry) Register

func (r *CommandRegistry) Register(cmd CommandWrapper) error

Register adds a command wrapper to the registry

func (*CommandRegistry) Size

func (r *CommandRegistry) Size() int

Size returns the number of registered commands

func (*CommandRegistry) Unregister

func (r *CommandRegistry) Unregister(name string) bool

Unregister removes a command from the registry

type CommandWrapper

type CommandWrapper interface {
	// Name returns the command name for registry lookup
	Name() string
	// Description returns a brief description of what the command does
	Description() string
	// Execute runs the command with the given context and executor
	Execute(ctx context.Context, exec *Executor) error
}

CommandWrapper is the interface that domain-specific commands must implement to be registered in the command registry

func GetCommand

func GetCommand(name string) (CommandWrapper, bool)

GetCommand retrieves a command wrapper from the global registry

type Executor

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

Executor runs external commands with beautiful UX

func NewExecutor

func NewExecutor(opts *Options) *Executor

NewExecutor creates an executor with sensible defaults

Example

Example usage for documentation

executor := NewExecutor(&Options{
	Stdout: os.Stdout,
	Stderr: os.Stderr,
})

ctx := context.Background()
if err := executor.Run(ctx, "echo", "Hello, World!"); err != nil {
	fmt.Printf("Error: %v\n", err)
}

func (*Executor) MustRun

func (e *Executor) MustRun(ctx context.Context, name string, args ...string)

MustRun runs a command and panics on error (for use in main)

func (*Executor) Run

func (e *Executor) Run(ctx context.Context, name string, args ...string) error

Run executes a command with beautiful output

func (*Executor) RunWithSpinner

func (e *Executor) RunWithSpinner(ctx context.Context, message string, name string, args ...string) error

RunWithSpinner runs a command with a progress spinner

type GenericCommand

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

GenericCommand provides a fluent API for building and executing commands

Example
executor := NewExecutor(nil)

// Build and run a command with fluent API
err := NewGenericCommand(executor, "echo").
	WithArgs("Hello", "World").
	WithEnv("USER=test").
	WithDir("/tmp").
	WithSpinner("Processing").
	Run(context.Background())

if err != nil {
	fmt.Printf("Error: %v\n", err)
}

func NewGenericCommand

func NewGenericCommand(executor *Executor, command string) *GenericCommand

NewGenericCommand creates a new generic command builder

func (*GenericCommand) MustRun

func (g *GenericCommand) MustRun(ctx context.Context)

MustRun executes the command and panics on error

func (*GenericCommand) Run

func (g *GenericCommand) Run(ctx context.Context) error

Run executes the command

func (*GenericCommand) String

func (g *GenericCommand) String() string

String returns the command string representation for debugging

func (*GenericCommand) WithArgs

func (g *GenericCommand) WithArgs(args ...string) *GenericCommand

WithArgs adds arguments to the command

func (*GenericCommand) WithDir

func (g *GenericCommand) WithDir(dir string) *GenericCommand

WithDir sets the working directory

func (*GenericCommand) WithEnv

func (g *GenericCommand) WithEnv(env ...string) *GenericCommand

WithEnv adds environment variables

func (*GenericCommand) WithSpinner

func (g *GenericCommand) WithSpinner(message string) *GenericCommand

WithSpinner enables spinner with the given message

type Options

type Options struct {
	Stdout      io.Writer
	Stderr      io.Writer
	Env         []string      // Additional environment variables
	Dir         string        // Working directory
	Timeout     time.Duration // Command timeout
	ShowCommand bool          // Print command before running
	Spinner     bool          // Show spinner for long-running commands
}

Options configures command execution

type PrefixWriter

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

PrefixWriter adds a prefix to each line of output

func NewPrefixWriter

func NewPrefixWriter(writer io.Writer, prefix string) *PrefixWriter

NewPrefixWriter creates a writer that prefixes each line

func (*PrefixWriter) Write

func (p *PrefixWriter) Write(data []byte) (n int, err error)

Write adds prefix to each line

type StreamingWriter

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

StreamingWriter wraps output with beautiful formatting

func NewStreamingWriter

func NewStreamingWriter(writer io.Writer, prefix string, color lipgloss.Color) *StreamingWriter

NewStreamingWriter creates a formatted output writer

func (*StreamingWriter) Flush

func (s *StreamingWriter) Flush() error

Flush writes any remaining buffered content

func (*StreamingWriter) Write

func (s *StreamingWriter) Write(p []byte) (n int, err error)

Write formats and writes output line by line

type TeeWriter

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

TeeWriter writes to multiple writers simultaneously

func NewTeeWriter

func NewTeeWriter(writers ...io.Writer) *TeeWriter

NewTeeWriter creates a writer that duplicates output to multiple writers

func (*TeeWriter) Write

func (t *TeeWriter) Write(p []byte) (n int, err error)

Write writes to all underlying writers

Jump to

Keyboard shortcuts

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