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 ¶
- func ClearRegistry()
- func Execute(ctx context.Context, name string, exec *Executor) error
- func ListCommands() []string
- func RegisterCommand(cmd CommandWrapper) error
- type CommandRegistry
- func (r *CommandRegistry) Clear()
- func (r *CommandRegistry) Execute(ctx context.Context, name string, exec *Executor) error
- func (r *CommandRegistry) Get(name string) (CommandWrapper, bool)
- func (r *CommandRegistry) Has(name string) bool
- func (r *CommandRegistry) List() []string
- func (r *CommandRegistry) ListWithDescriptions() map[string]string
- func (r *CommandRegistry) Register(cmd CommandWrapper) error
- func (r *CommandRegistry) Size() int
- func (r *CommandRegistry) Unregister(name string) bool
- type CommandWrapper
- type Executor
- type GenericCommand
- func (g *GenericCommand) MustRun(ctx context.Context)
- func (g *GenericCommand) Run(ctx context.Context) error
- func (g *GenericCommand) String() string
- func (g *GenericCommand) WithArgs(args ...string) *GenericCommand
- func (g *GenericCommand) WithDir(dir string) *GenericCommand
- func (g *GenericCommand) WithEnv(env ...string) *GenericCommand
- func (g *GenericCommand) WithSpinner(message string) *GenericCommand
- type Options
- type PrefixWriter
- type StreamingWriter
- type TeeWriter
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 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) 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 ¶
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)
}
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
type StreamingWriter ¶
type StreamingWriter struct {
// contains filtered or unexported fields
}
StreamingWriter wraps output with beautiful formatting
func NewStreamingWriter ¶
NewStreamingWriter creates a formatted output writer
func (*StreamingWriter) Flush ¶
func (s *StreamingWriter) Flush() error
Flush writes any remaining buffered content
type TeeWriter ¶
type TeeWriter struct {
// contains filtered or unexported fields
}
TeeWriter writes to multiple writers simultaneously
func NewTeeWriter ¶
NewTeeWriter creates a writer that duplicates output to multiple writers