Documentation
¶
Overview ¶
Package commands is a small, zero-dependency framework for building subcommand-style CLIs: verb registration and dispatch, declarative flag parsing (Flagged/SetFlags), nested subcommand reuse (Dispatch), configurable global root flags (a value-carrying RootFlag plus optional valueless bool RootBoolFlags), verb-declared usage errors (UsageError → exit 2 with a usage hint), and hooks for error rendering. It uses only the standard library — error copy, help text, and the root-flag names are product decisions injected by the consumer at assembly time.
Example ¶
app := New()
app.RootFlag = "R" // accept -R/--R anywhere, value lands in Environment.Root
app.Register(&exampleEcho{})
env := &Environment{Stdout: os.Stdout, Stderr: os.Stderr}
fmt.Println("exit:", app.Run(context.Background(), env, []string{"-R", "/root", "echo", "-upper", "hello", "world"}))
Output: HELLO WORLD exit: 0
Index ¶
- Constants
- Variables
- type App
- func (a *App) Dispatch(ctx context.Context, env *Environment, args []string) error
- func (a *App) Lookup(name string) (Command, bool)
- func (a *App) Names() []string
- func (a *App) ParseFlags(cmd Command, env *Environment, rest []string) ([]string, error)
- func (a *App) Register(cmds ...Command)
- func (a *App) Run(ctx context.Context, env *Environment, args []string) int
- func (a *App) SubDispatch(ctx context.Context, env *Environment, args []string) error
- type Command
- type Environment
- type Flagged
- type UnknownVerbError
- type UsageError
Examples ¶
Constants ¶
const ( ExitOK = 0 ExitError = 1 ExitUsage = 2 )
Exit-code convention: 0 success; 1 command error; 2 usage error (unknown verb, flag parse failure, or a verb-declared UsageError).
Variables ¶
var ErrHelp = errors.New("help requested")
ErrHelp reports that help output has already been written to Environment.Stdout: a verb-level -h/-help was parsed in App.ParseFlags. App.Run maps it to ExitOK without rendering; consumers driving Dispatch directly should treat it as success.
Functions ¶
This section is empty.
Types ¶
type App ¶
type App struct {
// RootFlag is the global root-flag name ("R" ⇒ matches -R/--R/-R=/--R=,
// stripped wherever it appears — including before the verb name — with
// the value landing in Environment.Root). Empty = no stripping. Nested
// inner apps should leave it empty — the outer layer already stripped
// it.
RootFlag string
// RootBoolFlags are global valueless bool flags ("json" ⇒ matches
// -json/--json anywhere, landing in Environment.RootBools["json"];
// -json=false/--json=false also parse and set false). Like RootFlag
// they are stripped wherever they appear, last occurrence wins, and a
// "--" terminator ends stripping; an =value that does not parse as a
// boolean is left in place for verb-level parsing. Nested inner apps
// should leave it empty. Run panics on a duplicate entry or a collision
// with RootFlag — both are assembly-time bugs.
RootBoolFlags []string
// FlagError maps a flag parse failure to a business error. Default
// "%s: bad arguments: %v".
FlagError func(verb string, err error) error
// RenderError renders a verb error as a single stderr line. Default
// err.Error().
RenderError func(error) string
// HelpHeader / VerbTitle / HelpFooter compose the help screen
// (printHelp).
HelpHeader string
VerbTitle string
// contains filtered or unexported fields
}
App holds the registered verbs. The zero value is usable; New additionally sets the default verb title.
func (*App) Dispatch ¶
Dispatch dispatches one verb and returns its error (no rendering, no exit code) — nested subcommand tables reuse the same machinery: an outer verb's Run hands its remaining arguments to an inner App.Dispatch and gets the same unknown-verb error shape and flag parsing. args must contain at least one element (empty input is handled by the outer Run or the outer verb itself).
When RootFlag is set but no root flag is present in args, an incoming Environment.Root is preserved (a caller may set it programmatically); if the flag appears more than once, the last occurrence wins.
func (*App) Names ¶
Names returns all verb names sorted alphabetically — help-screen order is independent of registration order.
func (*App) ParseFlags ¶
ParseFlags performs unified flag parsing for verbs implementing Flagged: build FlagSet → SetFlags declares → Parse → FlagError maps. It returns the positional arguments left after parsing (fs.Args()). The flag package's own diagnostics are silenced, making FlagError the single rendering point for parse failures. A parsed -h/-help prints the verb's usage to env.Stdout and returns ErrHelp. Identical to production dispatch; tests can consume it directly.
func (*App) Register ¶
Register adds verbs to the dispatch table. It panics on a verb named "help" (reserved for the built-in help screen) or on a duplicate name — both are assembly-time bugs best caught immediately, in the spirit of regexp.MustCompile.
func (*App) Run ¶
Run is the process top-level entry; it returns the exit code:
- no arguments, "help", or a -h/-help flag prints the help screen to stdout and returns ExitOK; "help <verb>" prints that verb's usage and flag defaults to stdout and returns ExitOK
- an unknown verb (or "help <unknown>") renders the error, appends the help screen to stderr, and returns ExitUsage
- any other verb error is rendered to stderr and returns ExitError
If RootFlag is set, its forms are stripped from the full argument list first, so the root flag may also precede the verb name. A verb-level -h (parsed in ParseFlags) has already printed the verb's help to stdout; Run maps ErrHelp to ExitOK without rendering.
Example ¶
app := New()
app.Register(bareCmd{})
env := &Environment{Stdout: os.Stdout, Stderr: os.Stderr}
fmt.Println("exit:", app.Run(context.Background(), env, []string{"help", "bare"}))
Output: usage: bare exit: 0
func (*App) SubDispatch ¶
SubDispatch dispatches a nested subcommand and rewrites the inner UnknownVerbError — the outer Run's usage-error decision (exit code 2 plus help screen) only recognizes a miss at its own level; a subcommand miss is a command error of the inner table (exit code 1).
type Command ¶
type Command interface {
Name() string
Synopsis() string
Usage() string
Run(ctx context.Context, env *Environment, args []string) error
}
Command is the verb contract. The error returned by Run is rendered to stderr by App.Run via RenderError and ends the process with exit code 1 (both the rendering and the exit policy are hook-customizable).
type Environment ¶
type Environment struct {
Stdout io.Writer
Stderr io.Writer
Root string
// RootBools is nil until a bool root flag is provided (or a caller
// pre-sets it programmatically; Dispatch preserves preset values).
RootBools map[string]bool
}
Environment is the execution environment of one command (injectable in tests). Root is the landing spot of the global root flag (App.RootFlag, e.g. "R") — the framework extracts it without interpreting it; its meaning (repository root, workspace root, …) is defined by the verb. Empty means "not provided". RootBools holds the parsed values of the global bool root flags (App.RootBoolFlags, e.g. "json"): a key is present only when the flag was provided; read it as env.RootBools[name] (a missing key reads as false).
type Flagged ¶
Flagged is implemented by verbs that take flags: SetFlags declares them (pointers stored in the verb struct, fields read inside Run); FlagSet construction and parse-error mapping are owned by App in one place, so the message shape is not copy-pasted per verb. Verbs that do not implement Flagged get their arguments passed through untouched (bare verbs keep their ignore-unknown-arguments semantics).
type UnknownVerbError ¶
type UnknownVerbError struct{ Name string }
UnknownVerbError is the error shape of a dispatch miss. App.Run uses it to pick the usage exit code and append the help screen; its message can be rewritten through the RenderError hook (product voice).
func (*UnknownVerbError) Error ¶
func (e *UnknownVerbError) Error() string
type UsageError ¶ added in v0.2.0
UsageError marks a verb-level usage problem — a missing required flag, a bad positional — as distinct from a command failure: App.Run renders it to stderr like any error and, when Usage is non-empty, appends it as a "usage:" hint line, then exits with ExitUsage (2). Carrying the usage string (not a verb name) keeps the hint intact across nested dispatch — the verb knows its own Usage() at construction. Wrap it (or return it directly) from Run for argument-validation failures; a non-nil Err is required. Usage may be empty to exit 2 without a hint.
func (*UsageError) Error ¶ added in v0.2.0
func (e *UsageError) Error() string
func (*UsageError) Unwrap ¶ added in v0.2.0
func (e *UsageError) Unwrap() error