Documentation
¶
Overview ¶
Package cli is a small, generics-based framework for building command-line applications whose flags, positional arguments, environment bindings, and validation rules are declared once as struct tags.
A command is a struct that embeds BaseCommand (parameterized by its config type) and implements Run. The config type's fields, tagged with arg/short/env/default/help/rules, define everything the framework needs to parse, validate, and document the command:
type GreetConfig struct {
Name string `arg:"0" env:"GREET_NAME" help:"Name to greet" rules:"required"`
Shout bool `arg:"shout" short:"s" help:"Uppercase the greeting"`
}
type GreetCommand struct {
cli.BaseCommand[GreetConfig]
}
func (c *GreetCommand) Run(_ cli.GlobalFlags, _ cli.Unknowns) error {
fmt.Printf("hello, %s!\n", c.Inputs.Name)
return nil
}
Build an App with NewApp, register commands with App.Add, App.Default, and App.Help, then dispatch os.Args with App.Run. The framework merges values from struct defaults, the resolver chain (see Resolver), environment variables, and parsed flags, in that order of increasing precedence, before calling Run.
Help (-h/--help) and version (-V/--version) are built in. Run returns the ErrShowingHelp / ErrShowingVersion sentinels once it has handled those requests itself; use IsRealError to filter them at the call site.
File-backed configuration, output codecs, the help command, and the docs generator live in sub-packages so the core stays dependency-light. See the runnable programs under examples/ for complete applications.
Example ¶
Example builds a one-command app and dispatches a positional argument to it.
package main
import (
"fmt"
"strings"
"github.com/toaweme/cli"
)
// helloConfig declares the hello command's flags and positional args via struct tags.
type helloConfig struct {
Name string `arg:"0" env:"HELLO_NAME" help:"Who to greet"`
Shout bool `arg:"shout" short:"s" help:"Uppercase the greeting"`
}
// helloCommand greets someone by name, optionally shouting.
type helloCommand struct {
cli.BaseCommand[helloConfig]
}
func (c *helloCommand) Run(_ cli.GlobalFlags, _ cli.Unknowns) error {
name := c.Inputs.Name
if name == "" {
name = "world"
}
msg := fmt.Sprintf("hello, %s!", name)
if c.Inputs.Shout {
msg = strings.ToUpper(msg)
}
fmt.Println(msg)
return nil
}
func (c *helloCommand) Help() string { return "Greet someone by name" }
func main() {
app := cli.NewApp(
cli.Config{Name: "greet", Version: "1.0.0"},
cli.GlobalFlags{},
)
app.Add("hello", &helloCommand{BaseCommand: cli.NewBaseCommand[helloConfig]()})
if err := app.Run([]string{"hello", "Ada"}); cli.IsRealError(err) {
fmt.Println("error:", err)
}
}
Output: hello, Ada!
Index ¶
- Variables
- func FormatAliases(codec OutputCodec) []string
- func GetDotEnv(file string) (map[string]string, error)
- func GetDotEnvs(files ...string) (map[string]string, error)
- func IsRealError(err error) bool
- func LoadDotEnv(paths ...string) error
- type App
- type BaseCommand
- func (c *BaseCommand[T]) Add(name string, cmd Command[any])
- func (c *BaseCommand[T]) Args() map[int][]string
- func (c *BaseCommand[T]) Commands() []Command[any]
- func (c *BaseCommand[T]) Description() string
- func (c *BaseCommand[T]) Examples() [][]string
- func (c *BaseCommand[T]) Flags() map[string][]string
- func (c *BaseCommand[T]) Name(name string) string
- func (c *BaseCommand[T]) Options() any
- func (c *BaseCommand[T]) Validate(options map[string]any) error
- type Command
- type Config
- type GlobalFlags
- type OutputCodec
- type Resolver
- type Unknowns
- type Verbosity
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ErrCommandNotFound = errors.New("command not found")
ErrCommandNotFound is returned when no registered command matches the args.
var ErrDisplaySubCommands = errors.New("print sub commands")
ErrDisplaySubCommands signals that sub commands should be printed.
var ErrDotenvNotFound = errors.New("dotenv file not found")
ErrDotenvNotFound is returned when a requested .env file does not exist.
var ErrNoCommands = errors.New("no commands registered")
ErrNoCommands is returned when the app has no commands registered.
var ErrShowingHelp = errors.New("showing help")
ErrShowingHelp signals that help output was shown instead of running a command.
var ErrShowingVersion = errors.New("showing version")
ErrShowingVersion signals that the version was shown instead of running a command.
var ErrValidationFailed = errors.New("validation failed")
ErrValidationFailed is returned by Validate when one or more struct rules fail.
Functions ¶
func FormatAliases ¶
func FormatAliases(codec OutputCodec) []string
FormatAliases returns every --help-format name a codec answers to: each extension it reports (Extensions() when implemented, otherwise its Extension()), with the leading dot trimmed and empties dropped. The first is the primary, used for the help hint and for writing; the rest are accepted aliases.
func GetDotEnv ¶
GetDotEnv parses a single .env file and returns its key/value pairs without touching the process environment. Returns ErrDotenvNotFound if the file does not exist.
func GetDotEnvs ¶
GetDotEnvs parses several .env files and merges them into a single map without touching the process environment. Earlier files take precedence: a key set by an earlier file is not overwritten by a later one. Returns the first error encountered, including ErrDotenvNotFound for a missing file.
func IsRealError ¶
IsRealError reports whether err is a genuine failure worth surfacing, as opposed to a clean-exit sentinel the framework returns once it has already handled the request itself (printing help or the version). It returns false for nil and for the ErrShowingHelp / ErrShowingVersion sentinels, and true for anything else, so a caller need not enumerate the sentinels by hand:
if err := app.Run(os.Args[1:]); cli.IsRealError(err) {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
Example ¶
ExampleIsRealError filters the clean-exit sentinels from genuine failures.
package main
import (
"errors"
"fmt"
"github.com/toaweme/cli"
)
func main() {
fmt.Println(cli.IsRealError(nil))
fmt.Println(cli.IsRealError(cli.ErrShowingHelp))
fmt.Println(cli.IsRealError(cli.ErrShowingVersion))
fmt.Println(cli.IsRealError(errors.New("boom")))
}
Output: false false false true
func LoadDotEnv ¶
LoadDotEnv reads .env files and sets environment variables that are not already set. With no arguments, it loads ".env" from the current working directory. Silently skips files that do not exist.
Types ¶
type App ¶
type App interface {
// Commands returns the registered top-level commands.
Commands() []Command[any]
// DefaultCommand returns the command registered via Default, or nil when none is set.
DefaultCommand() Command[any]
// Config returns the app identity (the serializable DTO).
Config() Config
// OutputFormats returns the registered help output codecs, in registration order.
OutputFormats() []OutputCodec
// Resolve appends config Resolvers to the chain used to populate each command's Options() before Run,
// and returns the app for chaining. Resolvers run in the order registered (across all Resolve calls),
// lowest precedence first, then env, then flags. With none registered, only env and flags apply.
Resolve(resolvers ...Resolver) App
// HelpOutputs registers additional help output codecs (e.g. the yaml/toml addons) and returns the app for chaining.
// Each codec's name, derived from its Extension (".yml" -> "yml"), becomes a valid --help-format value
// and is advertised in help.
HelpOutputs(formats ...OutputCodec) App
// Default sets the command run when no arguments are given; it returns cmd.
Default(cmd Command[any]) Command[any]
// Add registers cmd under name and returns it, so subcommands chain off the result.
Add(name string, cmd Command[any]) Command[any]
// Run parses osArgs and dispatches to the matched command.
// Help and version requests surface as the ErrShowingHelp/ErrShowingVersion sentinels.
Run(osArgs []string) error
// Help registers cmd as the command that renders help, so callers never have to know the reserved name.
// Use it instead of Add: app.Help(help.NewHelpCommand(...)).
Help(cmd Command[any]) Command[any]
}
App is the top-level CLI application. It owns the command set, global flags, and an ordered chain of config Resolvers, and dispatches osArgs to the matched command.
func NewApp ¶
func NewApp(config Config, opts GlobalFlags) App
NewApp creates an application from config (the serializable identity and merge strategy) and the default values for global flags. Attach config Resolvers and output Formats with the chainable setters (Resolve and HelpOutputs), then register commands with Add, Default, and Help, and dispatch with Run.
Example ¶
ExampleNewApp shows the minimal app: identity, one command, dispatch.
package main
import (
"fmt"
"strings"
"github.com/toaweme/cli"
)
// helloConfig declares the hello command's flags and positional args via struct tags.
type helloConfig struct {
Name string `arg:"0" env:"HELLO_NAME" help:"Who to greet"`
Shout bool `arg:"shout" short:"s" help:"Uppercase the greeting"`
}
// helloCommand greets someone by name, optionally shouting.
type helloCommand struct {
cli.BaseCommand[helloConfig]
}
func (c *helloCommand) Run(_ cli.GlobalFlags, _ cli.Unknowns) error {
name := c.Inputs.Name
if name == "" {
name = "world"
}
msg := fmt.Sprintf("hello, %s!", name)
if c.Inputs.Shout {
msg = strings.ToUpper(msg)
}
fmt.Println(msg)
return nil
}
func (c *helloCommand) Help() string { return "Greet someone by name" }
func main() {
app := cli.NewApp(
cli.Config{Name: "greet", Version: "1.0.0"},
cli.GlobalFlags{},
)
app.Add("hello", &helloCommand{BaseCommand: cli.NewBaseCommand[helloConfig]()})
_ = app.Run([]string{"hello", "Ada", "--shout"})
}
Output: HELLO, ADA!
type BaseCommand ¶
type BaseCommand[T any] struct { Inputs *T // contains filtered or unexported fields }
BaseCommand provides default implementations for the Command interface. Embed it in your command struct to get name management, subcommand registration, config struct handling, validation, and no-op help providers for free. Override Description/Examples/Args/Flags to enrich help output.
func NewBaseCommand ¶
func NewBaseCommand[T any]() BaseCommand[T]
NewBaseCommand returns a BaseCommand with an initialized subcommand slice.
func (*BaseCommand[T]) Add ¶
func (c *BaseCommand[T]) Add(name string, cmd Command[any])
Add registers cmd as a subcommand under the given name.
func (*BaseCommand[T]) Args ¶
func (c *BaseCommand[T]) Args() map[int][]string
Args returns no positional-argument descriptions by default. Override to provide them.
func (*BaseCommand[T]) Commands ¶
func (c *BaseCommand[T]) Commands() []Command[any]
Commands returns the registered subcommands.
func (*BaseCommand[T]) Description ¶
func (c *BaseCommand[T]) Description() string
Description returns no long-form description by default. Override to provide one.
func (*BaseCommand[T]) Examples ¶
func (c *BaseCommand[T]) Examples() [][]string
Examples returns no usage examples by default. Override to provide them.
func (*BaseCommand[T]) Flags ¶
func (c *BaseCommand[T]) Flags() map[string][]string
Flags returns no flag descriptions by default. Override to provide them.
func (*BaseCommand[T]) Name ¶
func (c *BaseCommand[T]) Name(name string) string
Name returns the command's name when called with an empty string, or sets and returns it otherwise.
func (*BaseCommand[T]) Options ¶
func (c *BaseCommand[T]) Options() any
Options returns the pointer the parser fills (and the merge populates). It allocates a fresh T only when Inputs is unset, so an app can make a command operate on a slice of a larger config struct by assigning Inputs before Run:
cmd.Inputs = &appCfg.Server // flags, env, and merge now write into appCfg
That is the top-down "single source of truth" pattern: one app config struct, with each command viewing the field it owns. Leaving Inputs nil keeps the command's config independent and portable, which is the default.
type Command ¶
type Command[T any] interface { // Name gets or sets the command name. Pass "" to get, non-empty to set. Name(name string) string // Add registers a subcommand under this command. Add(name string, cmd Command[any]) // Options returns a pointer to the config struct for flag parsing. Options() any // Commands returns the list of registered subcommands. Commands() []Command[any] // Run executes the command logic with parsed global options and unknown args. Run(options GlobalFlags, unknowns Unknowns) error // Validate checks the parsed options map against struct validation rules. Validate(options map[string]any) error // Help returns a short one-line description shown in command listings. Help() string // Description returns a longer, multi-line description shown in detailed and agent help. // Help stays the one-line listing summary; Description carries the richer body // (paragraphs, install instructions, ...). Empty by default. Description() string // Examples returns usage examples shown in detailed and agent help. // Each example is a slice of lines: the first is the invocation, // any following lines are sample output shown beneath it. Nil by default. Examples() [][]string // Args returns multi-line descriptions for positional arguments, keyed by zero-based position. // Augments the single-line `help:` tag. Nil by default. Args() map[int][]string // Flags returns multi-line descriptions for flags, keyed by the flag as written (e.g. "--query, -q"). // Augments the single-line `help:` tag. Nil by default. Flags() map[string][]string }
Command is the interface every CLI command must implement. T is the config struct type whose fields define the command's flags and positional args.
type Config ¶
type Config struct {
// Name is the application binary name, shown in help and usage output.
Name string `json:"name" yaml:"name"`
// Version is the semantic version string printed by the built-in --version / -V flag.
Version string `json:"version" yaml:"version"`
}
Config is the serializable application identity: plain values only, so it stays a light DTO that round-trips through json/yaml. Config resolution is attached to the App separately via the App.Resolve setter; output codecs via App.HelpOutputs.
type GlobalFlags ¶
type GlobalFlags struct {
// Cwd overrides the working directory for the command.
// Long-only: "cwd" is too niche to justify squatting on -c, the most common short for a "config" flag.
Cwd string `arg:"cwd" env:"CWD" help:"Current working directory"`
// Help triggers help display instead of running the matched command.
// -h/--help is the one short the whole ecosystem reserves, so it keeps its short.
Help bool `arg:"help" short:"h" env:"HELP" help:"Show help"`
// HelpValues, with --help, annotates each flag with its resolved value
// (the merge of defaults, config, env, and flags for the invoked command).
// Values are prefix-masked so secrets sourced from env or a .env file are not
// exposed in full in help that gets pasted into logs, issues, or screenshots.
// Off by default; passing --help-values implies --help.
HelpValues bool `arg:"help-values" help:"With --help, show each flag's resolved value (prefix-masked)"`
// HelpFormat controls help output. It is --help-format, not --format:
// the bare name is the one apps most often want for their own command output (json/yaml/table/csv),
// and squatting on it also meant the framework rejected any unrecognized value app-wide before the command ran.
// The allowed values come from the oneof rule, which also drives the "(one of: ...)" hint shown in help.
HelpFormat string `arg:"help-format" help:"Help output format" rules:"oneof:plain,plain-flags,pretty,md,json,jsonschema"`
// Version prints the application version and exits.
// Short is capital -V (clap-style) so lowercase -v stays free for the author's own "verbose" flag,
// which is what users overwhelmingly expect -v to mean.
Version bool `arg:"version" short:"V" env:"VERSION" help:"Show version"`
}
GlobalFlags are built-in flags available to every command. These are parsed before command dispatch and passed to every command's Run method.
type OutputCodec ¶
type OutputCodec interface {
// Marshal encodes v into bytes.
Marshal(v any) ([]byte, error)
// Extension returns the file extension for this codec (e.g. ".yml").
Extension() string
}
OutputCodec renders help output for a custom --help-format value. It is satisfied structurally by the yaml/toml/json config addon codecs (which also implement config.Codec), so registering one never pulls an encoding library into the core. The format name a user types is derived from Extension by trimming the leading dot (".yml" -> "yml").
type Resolver ¶
type Resolver interface {
// Resolve overlays this resolver's layer onto values, the map accumulated by
// earlier resolvers in the chain, and returns it. cmd is the command path
// (space-joined, e.g. "db migrate"). The first resolver receives an empty map.
Resolve(cmd string, values map[string]any) (map[string]any, error)
}
Resolver contributes values to a command's Options() before Run. Resolvers compose like middleware: the framework registers any number on the App, then runs them in order, threading each one's output into the next. After the chain it folds in env, then overlays parsed flags, so a typed flag always wins. File-backed resolution lives in the config package (config.NewResolver, one per Store), which satisfies this interface structurally so core never imports config.
type Unknowns ¶
type Unknowns struct {
// Args are positional arguments not matched to numbered struct tags.
Args []string
// Options are key-value flags not defined in the command's config struct.
Options map[string]any
}
Unknowns holds arguments and options that were not matched to any defined field. Commands receive these to support pass-through or dynamic flag handling.
type Verbosity ¶
type Verbosity struct {
V1 bool `short:"v" help:"Verbose output (-v)"`
V2 bool `short:"vv" help:"More verbose output (-vv)"`
V3 bool `short:"vvv" help:"Most verbose output (-vvv)"`
}
Verbosity is an optional embeddable flag group giving an author the conventional -v/-vv/-vvv switches without the framework imposing any verbosity policy of its own. Embed it in a command's input struct to get the flags and the level query for free:
type MyInputs struct {
cli.Verbosity
Name string `arg:"name"`
}
The three flags are independent switches matched as their own short tokens (-v, -vv, -vvv), not a true repeat-counter. Use Level to read the highest one the user passed rather than touching the bool fields directly.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
commands
|
|
|
completion
Package completion provides a command that generates shell completion scripts.
|
Package completion provides a command that generates shell completion scripts. |
|
gendocs
Package gendocs provides a command that renders the application's command tree to documentation files.
|
Package gendocs provides a command that renders the application's command tree to documentation files. |
|
help
Package help provides a command that displays usage information for the application.
|
Package help provides a command that displays usage information for the application. |
|
addons/json
Package json provides a config codec that serializes values as JSON.
|
Package json provides a config codec that serializes values as JSON. |
|
addons/yaml
module
|
|
|
examples
|
|
|
basic
command
basic demonstrates the minimal setup for a CLI app: creating an app, registering built-in commands, and adding a custom command.
|
basic demonstrates the minimal setup for a CLI app: creating an app, registering built-in commands, and adding a custom command. |
|
deploy
command
deploy demonstrates parent commands with subcommands.
|
deploy demonstrates parent commands with subcommands. |
|
full
command
full demonstrates every framework feature: dotenv loading, default commands, shell completion, config store, subcommands, ExampleProvider, and all help formats.
|
full demonstrates every framework feature: dotenv loading, default commands, shell completion, config store, subcommands, ExampleProvider, and all help formats. |
|
greet
command
greet demonstrates positional args, named flags, short flags, environment variable binding, and validation rules.
|
greet demonstrates positional args, named flags, short flags, environment variable binding, and validation rules. |
|
server
command
server demonstrates a CLI app that starts an HTTP server with graceful shutdown, dotenv loading, config persistence, and signal handling.
|
server demonstrates a CLI app that starts an HTTP server with graceful shutdown, dotenv loading, config persistence, and signal handling. |
|
Package help renders command usage information in several formats: plain text, agent-oriented markdown, JSON, and JSON schema.
|
Package help renders command usage information in several formats: plain text, agent-oriented markdown, JSON, and JSON schema. |
|
gendocs
Package gendocs renders an application's command tree to documentation files on disk, in every help format the app supports.
|
Package gendocs renders an application's command tree to documentation files on disk, in every help format the app supports. |
