Documentation ¶
Overview ¶
Package subcmd provides a multi-level command facility of the following form:
Usage of <tool> <sub-command-1> <flags for sub-command-1> <args for sub-comand-1> <sub-command-2-1> <flags for sub-command-2-1> <args for sub-comand-2-1> ... <sub-command-2-2> <flags for sub-command-2-2> <args for sub-comand-2-2> ... <sub-command-n> <flags for sub-command-n> <args for sub-comand-n>
The primary motivation for this package was to avoid the need to use global variables to store flag values packages. Such global variables quickly become a maintenance problem as command line tools evolve and in particular as functions are refactored. The cloudeng.io/cmdutil/flags package provides a means of defining flags as fields in a struct with a struct tag providing the flag name, default value and usage and is used to represent all flags.
subcmd builds on the standard flag package and mirrors its design but without requiring that flag.Parse or any of its global state be used.
Flags are represented by a FlagSet which encapsulates an underlying flag.FlagSet but with flag variables provided via cloudeng.io/cmdutil/flags.
The Command type associates a FlagSet with the function that implements that command as well as documenting the command. This 'runner' takes as an argument the struct used to store its flag values as well as the command line arguments; thus avoiding the need for global flag variables at the cost of a type assertion. A CommandSet is used to create the command hierarchy itself and finally the cmdset can be used to dispatch the appropriate command functions via cmdset.Dispatch or DispatchWithArgs.
type rangeFlags struct { From int `subcmd:"from,1,start value for a range"` To int `subcmd:"to,2,end value for a range "` } func printRange(ctx context.Context, values interface{}, args []string) error { r := values.(*rangeFlags) fmt.Printf("%v..%v\n", r.From, r.To) return nil } func main() { ctx := context.Background() fs := subcmd.MustRegisteredFlags(&rangeFlags{}) // Subcommands are created using subcmd.NewCommandLevel. cmd := subcmd.NewCommand("ranger", fs, printRange, subcmd.WithoutArguments()) cmd.Document("print an integer range") cmdSet := subcmd.NewCommandSet(cmd) cmdSet.MustDispatch(ctx) }
In addition it is possible to register 'global' flags that may be specified before any sub commands on invocation and also to wrap calls to any subcommand's runner function. The former is useful for setting common flags and the latter for acting on those flags and/or implementing common functionality such as profiling or initializing logging etc.
The FromYAML function provides a more convenient and readable means of creating a command tree than using the NewCommand and NewCommandSet functions directly. FromYAML reads a yaml specification of a command tree, its summary documentation and argument specification and calls NewCommand and NewCommandSet internally.
The returned CommandSetYAML type can then be used to 'decorate' the command tree with the runner functions and flag value instances. The YAML mechanism provides identical functionality to calling the functions directly.
The YAML specification is show below and reflects the tree structure of the command tree to be created.
name: command-name summary: description of the command arguments: commands: - name: summary: arguments: commands: - name: summary: ...
The summary and argument values are used in calls in the Command.Document. The arguments: field is a list of the expected arguments that also defines the number of expected arguments.
If the field is missing or the list is empty then no arguments are allowed.
If the list contains n arguments then exactly that number of arguments is expected, unless, the last argument in the list is '...' in which case at least that number is expected. Similarly if an argument ends in '...' then at least the preceding number of arguments is expected.
If there is a single item in the list and it is enclosed in [] (in a quoted string), then 0 or 1 arguments are expected.
Note that the arguments may be structured into a short form name and a description, eg. arg - description, where ' - ' is used to separate the short form and description. The usage displayed will use the short form name to display a summary of the command line and the description will be detailed below, eg:
my-command <arg1> <arg2> <arg1> - description of arg1 <arg2> - description of arg2
To define a simple command line, with no sub-commands, specify only the name:, summary: and arguments: fields.
CommandSet.Dispatch implements support for requesting help information on the top level and subcommands. Running a command with sub-commands without specifying one of those sub-commands results in a 'usage' message showing summary information and the available subcommands. Help on a specific subcommand is available via '<command> help <sub-command>' or for multi-level commands '<command> <sub-command> help <next-sub-command>'. The --help flag can be used to display information on a commands flags and arguments, eg: '<command> --help' or "<command> <sub-command> --help".
Note that this package will never call flag.Parse and will not associate any flags with flag.CommandLine.
Index ¶
- func SanitizeYAML(spec string) string
- type Command
- type CommandOption
- type CommandSet
- func (cmds *CommandSet) Commands() []string
- func (cmds *CommandSet) Defaults(name string) string
- func (cmds *CommandSet) Dispatch(ctx context.Context) error
- func (cmds *CommandSet) DispatchWithArgs(ctx context.Context, usage string, args ...string) error
- func (cmds *CommandSet) Document(doc string)
- func (cmds *CommandSet) MustDispatch(ctx context.Context)
- func (cmds *CommandSet) Output() io.Writer
- func (cmds *CommandSet) SetOutput(out io.Writer)
- func (cmds *CommandSet) Summary() string
- func (cmds *CommandSet) TopLevel(cmd *Command)
- func (cmds *CommandSet) Usage(name string) string
- func (cmds *CommandSet) WithGlobalFlags(global *FlagSet)
- func (cmds *CommandSet) WithMain(m Main)
- type CommandSetYAML
- type CurrentCommand
- func (c *CurrentCommand) MustRunner(runner Runner, fs any)
- func (c *CurrentCommand) MustRunnerAndFlagSet(runner Runner, fs *FlagSet)
- func (c *CurrentCommand) MustRunnerAndFlags(runner Runner, fs *FlagSet)deprecated
- func (c *CurrentCommand) Runner(runner Runner, fs any, defaults ...any) error
- func (c *CurrentCommand) RunnerAndFlagSet(runner Runner, fs *FlagSet) error
- func (c *CurrentCommand) RunnerAndFlags(runner Runner, fs *FlagSet) errordeprecated
- type Extension
- type FlagSet
- func GlobalFlagSet() *FlagSet
- func MustRegisterFlagStruct(flagValues interface{}, valueDefaults map[string]interface{}, ...) *FlagSet
- func MustRegisteredFlagSet(flagValues interface{}, defaults ...interface{}) *FlagSet
- func NewFlagSet() *FlagSet
- func RegisterFlagStruct(flagValues interface{}, valueDefaults map[string]interface{}, ...) (*FlagSet, error)
- func RegisteredFlagSet(flagValues interface{}, defaults ...interface{}) (*FlagSet, error)
- type Main
- type Runner
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func SanitizeYAML ¶
SanitizeYAML replaces tabs with two spaces to make it easier to write YAML in go string literals (where most editors will always use tabs). This does not guarantee correct alignment when spaces and tabs are mixed arbitrarily.
Types ¶
type Command ¶
type Command struct {
// contains filtered or unexported fields
}
Command represents a single command.
func NewCommand ¶
func NewCommand(name string, flags *FlagSet, runner Runner, options ...CommandOption) *Command
NewCommand returns a new instance of Command.
func NewCommandLevel ¶
func NewCommandLevel(name string, subcmds *CommandSet) *Command
NewCommandLevel returns a new instance of Command with subcommands.
type CommandOption ¶
type CommandOption func(*options)
CommandOption represents an option controlling the handling of a given command.
func AtLeastNArguments ¶
func AtLeastNArguments(n int) CommandOption
AtLeastNArguments specifies that the command takes at least N arguments.
func ExactlyNumArguments ¶
func ExactlyNumArguments(n int) CommandOption
ExactlyNumArguments specifies that the command takes exactly the specified number of arguments.
func OptionalSingleArgument ¶
func OptionalSingleArgument() CommandOption
OptionalSingleArg specifies that the command takes an optional single argument.
func WithoutArguments ¶
func WithoutArguments() CommandOption
WithoutArguments specifies that the command takes no arguments.
type CommandSet ¶
type CommandSet struct {
// contains filtered or unexported fields
}
CommandSet represents a set of commands that are peers to each other, that is, the command line must specificy one of them.
Example ¶
package main import ( "context" "fmt" "cloudeng.io/cmdutil/subcmd" ) func main() { ctx := context.Background() type globalFlags struct { Verbosity int `subcmd:"v,0,debugging verbosity"` } var globalValues globalFlags type rangeFlags struct { From int `subcmd:"from,1,start value for a range"` To int `subcmd:"to,2,end value for a range "` } printRange := func(_ context.Context, values interface{}, _ []string) error { r := values.(*rangeFlags) fmt.Printf("%v: %v..%v\n", globalValues.Verbosity, r.From, r.To) return nil } fs := subcmd.MustRegisterFlagStruct(&rangeFlags{}, nil, nil) // Subcommands are added using subcmd.NewCommandLevel. cmd := subcmd.NewCommand("ranger", fs, printRange, subcmd.WithoutArguments()) cmd.Document("print an integer range") cmdSet := subcmd.NewCommandSet(cmd) globals := subcmd.NewFlagSet() globals.MustRegisterFlagStruct(&globalValues, nil, nil) cmdSet.WithGlobalFlags(globals) // Use cmdSet.Dispatch to access os.Args. fmt.Printf("%s", cmdSet.Usage("example-command")) fmt.Printf("%s", cmdSet.Defaults("example-command")) if err := cmdSet.DispatchWithArgs(ctx, "example-command", "ranger"); err != nil { panic(err) } if err := cmdSet.DispatchWithArgs(ctx, "example-command", "-v=3", "ranger", "--from=10", "--to=100"); err != nil { panic(err) } }
Output: Usage of example-command ranger - print an integer range Usage of example-command ranger - print an integer range global flags: [--v=0] -v int debugging verbosity 0: 1..2 3: 10..100
func NewCommandSet ¶
func NewCommandSet(cmds ...*Command) *CommandSet
NewCommandSet creates a new command set.
func (*CommandSet) Commands ¶
func (cmds *CommandSet) Commands() []string
Commands returns the list of available commands.
func (*CommandSet) Defaults ¶
func (cmds *CommandSet) Defaults(name string) string
Defaults returns the usage message and flag defaults.
func (*CommandSet) Dispatch ¶
func (cmds *CommandSet) Dispatch(ctx context.Context) error
Dispatch will dispatch the appropriate sub command or return an error.
func (*CommandSet) DispatchWithArgs ¶
Dispatch determines which top level command has been requested, if any, parses the command line appropriately and then runs its associated function.
func (*CommandSet) Document ¶
func (cmds *CommandSet) Document(doc string)
Document adds a description for the command set.
func (*CommandSet) MustDispatch ¶
func (cmds *CommandSet) MustDispatch(ctx context.Context)
MustDispatch will dispatch the appropriate sub command or exit.
func (*CommandSet) Output ¶
func (cmds *CommandSet) Output() io.Writer
Output is like flag.FlagSet.Output.
func (*CommandSet) SetOutput ¶
func (cmds *CommandSet) SetOutput(out io.Writer)
SetOutput is like flag.FlagSet.SetOutput.
func (*CommandSet) Summary ¶
func (cmds *CommandSet) Summary() string
Summary returns a summary of the command set that includes its top level documentation and a list of its sub-commands.
func (*CommandSet) TopLevel ¶
func (cmds *CommandSet) TopLevel(cmd *Command)
func (*CommandSet) Usage ¶
func (cmds *CommandSet) Usage(name string) string
Usage returns the usage message for the command set.
func (*CommandSet) WithGlobalFlags ¶
func (cmds *CommandSet) WithGlobalFlags(global *FlagSet)
WithGlobalFlags adds top-level/global flags that apply to all commands. They must be specified before a subcommand, ie: command <global-flags>* sub-command <sub-command-pflags>* args
func (*CommandSet) WithMain ¶
func (cmds *CommandSet) WithMain(m Main)
WithMain arranges for Main to be called by Dispatch to wrap the call to the requested RunnerFunc.
type CommandSetYAML ¶
type CommandSetYAML struct { *CommandSet // contains filtered or unexported fields }
Example (Multiple) ¶
package main import ( "context" "fmt" "os" "strings" "cloudeng.io/cmdutil/subcmd" ) type exampleFlags struct { Flag1 int `subcmd:"flag1,12,flag1"` } type runner struct { name string out *strings.Builder } func (r *runner) cmd(_ context.Context, values interface{}, args []string) error { fmt.Fprintf(r.out, "%v: flag: %v, args: %v\n", r.name, values.(*exampleFlags).Flag1, args) return nil } func main() { cmdSet := subcmd.MustFromYAML(`name: l0 summary: documentation for l0 commands: - name: l0.1 summary: summary of l0.1 arguments: # l0.1 expects exactly two arguments. - <arg1> - <arg2> - name: l0.2 summary: l0.2 summary of l0.2 - name: l1 summary: summary of l1 commands: - name: l1.1 summary: describe l1.1 - name: l1.2 summary: describe l1.2 - name: l2 commands: - name: l2.1 commands: - name: l2.1.1 `) out := &strings.Builder{} cmdSet.Set("l0.1").MustRunner( (&runner{name: "l0.1", out: out}).cmd, &exampleFlags{}) cmdSet.Set("l0.2").MustRunner( (&runner{name: "l0.2", out: out}).cmd, &exampleFlags{}) cmdSet.Set("l1", "l1.1").MustRunner( (&runner{name: "l1.2", out: out}).cmd, &exampleFlags{}) cmdSet.Set("l1", "l1.2").MustRunner( (&runner{name: "l1.2", out: out}).cmd, &exampleFlags{}) cmdSet.Set("l2", "l2.1", "l2.1.1").MustRunner( (&runner{name: "l1.2", out: out}).cmd, &exampleFlags{}) if err := cmdSet.DispatchWithArgs(context.Background(), os.Args[0], "l0.1", "-flag1=3", "first-arg", "second-arg"); err != nil { panic(err) } if err := cmdSet.DispatchWithArgs(context.Background(), os.Args[0], "l1", "l1.2", "-flag1=6"); err != nil { panic(err) } fmt.Println(out.String()) }
Output: l0.1: flag: 3, args: [first-arg second-arg] l1.2: flag: 6, args: []
Example (Toplevel) ¶
package main import ( "context" "fmt" "os" "strings" "cloudeng.io/cmdutil/subcmd" ) type exampleFlags struct { Flag1 int `subcmd:"flag1,12,flag1"` } type runner struct { name string out *strings.Builder } func (r *runner) cmd(_ context.Context, values interface{}, args []string) error { fmt.Fprintf(r.out, "%v: flag: %v, args: %v\n", r.name, values.(*exampleFlags).Flag1, args) return nil } func main() { cmdSet := subcmd.MustFromYAML(`name: toplevel summary: overall documentation for toplevel arguments: - "[arg - optional]" `) out := &strings.Builder{} cmdSet.Set("toplevel").MustRunnerAndFlags( (&runner{name: "toplevel", out: out}).cmd, subcmd.MustRegisteredFlagSet(&exampleFlags{})) if err := cmdSet.DispatchWithArgs(context.Background(), os.Args[0], "-flag1=32", "single-arg"); err != nil { panic(err) } fmt.Println(out.String()) }
Output: toplevel: flag: 32, args: [single-arg]
func FromYAML ¶
func FromYAML(spec []byte) (*CommandSetYAML, error)
FromYAML parses a YAML specification of the command tree.
func FromYAMLTemplate ¶
func FromYAMLTemplate(specTpl string, exts ...Extension) (*CommandSetYAML, []byte, error)
FromYAMLTemplate returns a CommandSetYAML using the expanded value of the supplied template and the supplied extensions.
func MustFromYAML ¶
func MustFromYAML(spec string) *CommandSetYAML
MustFromYAML is like FromYAML but will panic if the YAML spec is incorrectly defined. It calls SanitizeYAML on its input before calling FromYAML.
func MustFromYAMLTemplate ¶
func MustFromYAMLTemplate(specTpl string, exts ...Extension) *CommandSetYAML
MustFromYAMLTemplate is like FromYAMLTemplate except that it panics on error. SanitzeYAML is called on the expanded template.
func (*CommandSetYAML) AddExtensions ¶
func (c *CommandSetYAML) AddExtensions() error
AddExtensions calls the Set method on each of the extensions.
func (*CommandSetYAML) MustAddExtensions ¶
func (c *CommandSetYAML) MustAddExtensions()
MustAddExtensions is like AddExtensions but panics on error.
func (*CommandSetYAML) Set ¶
func (c *CommandSetYAML) Set(names ...string) *CurrentCommand
Set looks up the command specified by names. Each sub-command in a multi-level command should be specified separately. The returned CurrentCommand should be used to set the Runner and FlagSet to associate with that command.
type CurrentCommand ¶
type CurrentCommand struct {
// contains filtered or unexported fields
}
func (*CurrentCommand) MustRunner ¶
func (c *CurrentCommand) MustRunner(runner Runner, fs any)
MustRunner is like Runner but will panic on error.
func (*CurrentCommand) MustRunnerAndFlagSet ¶
func (c *CurrentCommand) MustRunnerAndFlagSet(runner Runner, fs *FlagSet)
MustRunnerAndFlagSet is like RunnerAndFlagSet but will panic on error.
func (*CurrentCommand) MustRunnerAndFlags
deprecated
func (c *CurrentCommand) MustRunnerAndFlags(runner Runner, fs *FlagSet)
Deprecated: Use MustRunnerAndFlagSet or MustRunner.
func (*CurrentCommand) Runner ¶
func (c *CurrentCommand) Runner(runner Runner, fs any, defaults ...any) error
Runner specifies the Runner and struct to use as a FlagSet for the currently 'set' command as returned by CommandSetYAML.Set.
func (*CurrentCommand) RunnerAndFlagSet ¶
func (c *CurrentCommand) RunnerAndFlagSet(runner Runner, fs *FlagSet) error
RunnerAndFlagset specifies the Runner and FlagSet for the currently 'set' command as returned by CommandSetYAML.Set.
func (*CurrentCommand) RunnerAndFlags
deprecated
func (c *CurrentCommand) RunnerAndFlags(runner Runner, fs *FlagSet) error
Deprecated: Use RunnerAndFlagSet or Runner.
type Extension ¶
type Extension interface { Name() string YAML() string Set(cmdSet *CommandSetYAML) error }
Extension allows for extending a YAMLCommandSet with additional commands at runtime. Implementations of extension are used in conjunction with a templated version of the YAML command tree spec. The template can refer to an extension using the subcmdExtension function in a template pipeline:
- name: command commands: {{range subcmdExtension "exensionName"}}{{.}} {{end}}
The extensionName is the name of the extension as returned by the Name method, and . refers to results of the YAML method split into single lines. Thus for the above example, the YAML method can return:
`- name: c3.1 - name: c3.2`
The template expansion ensures the correct indentation in the final YAML that's used to create the command tree.
In addition to adding the extension to the YAML used to create the command tree, the Set method is also used to add the extension's commands to the command set. The Set method is called by CommandSetYAML.AddExtensions which should itself be called before the command set is used.
func MergeExtensions ¶
MergeExtensions returns an extension that merges the supplied extensions. Calling the Set method on the returned extension will call the Set method on each of the supplied extensions. The YAML method returns the concatenation of the YAML methods of the supplied extensions in the order that they are specified.
func NewExtension ¶
func NewExtension(name, spec string, appendFn func(cmdSet *CommandSetYAML) error) Extension
NewExtension creates a new Extension with the specified name and spec. The name is used to refer to the extension in the YAML template.
type FlagSet ¶
type FlagSet struct {
// contains filtered or unexported fields
}
FlagSet represents the name, description and flag values for a command.
func GlobalFlagSet ¶
func GlobalFlagSet() *FlagSet
GlobalFlagSet creates a new FlagSet that is to be used for global flags.
func MustRegisterFlagStruct ¶
func MustRegisterFlagStruct(flagValues interface{}, valueDefaults map[string]interface{}, usageDefaults map[string]string) *FlagSet
MustRegisterFlagStruct is like RegisterFlagStruct except that it panics on encountering an error. Its use is encouraged over RegisterFlagStruct from within init functions.
func MustRegisteredFlagSet ¶
func MustRegisteredFlagSet(flagValues interface{}, defaults ...interface{}) *FlagSet
MustRegisteredFlagSet is like RegisteredFlagSet but will panic if defaults contains inappopriate types for the value and usage defaults.
func RegisterFlagStruct ¶
func RegisterFlagStruct(flagValues interface{}, valueDefaults map[string]interface{}, usageDefaults map[string]string) (*FlagSet, error)
RegisterFlagStruct creates a new FlagSet and calls RegisterFlagStruct on it.
func RegisteredFlagSet ¶
RegisteredFlagSet is a convenience function that creates a new FlagSet and calls RegisterFlagStruct on it. The valueDefaults and usageDefaults are extracted from the defaults variadic parameter. MustRegisteredFlagSet will panic if defaults contains inappopriate types for the value and usage defaults.
func (*FlagSet) IsSet ¶
IsSet returns true if the supplied flag variable's value has been set, either via a string literal in the struct or via the valueDefaults argument to RegisterFlagStruct.
func (*FlagSet) MustRegisterFlagStruct ¶
func (cf *FlagSet) MustRegisterFlagStruct(flagValues interface{}, valueDefaults map[string]interface{}, usageDefaults map[string]string) *FlagSet
MustRegisterFlagStruct is like RegisterFlagStruct except that it panics on encountering an error. Its use is encouraged over RegisterFlagStruct from within init functions.
func (*FlagSet) RegisterFlagStruct ¶
func (cf *FlagSet) RegisterFlagStruct(flagValues interface{}, valueDefaults map[string]interface{}, usageDefaults map[string]string) error
RegisterFlagStruct registers a struct, using flags.RegisterFlagsInStructWithSetMap. The struct tag must be 'subcomd'. The returned SetMap can be queried by the IsSet method.