Documentation ¶
Overview ¶
Package vocab handles flag parsing and dispatch for a nested language of commands and subcommands. In this model, a command-line is treated as a phrase in a simple grammar:
command = name [flags] [command]
Each name may be either a command in itself, or a group of subcommands with a shared set of flags, or both.
You describe command vocabulary with nested struct values, whose fields define flags and subcommands to be executed. The implementation of a command is provided by by implementing the vocab.Runner interface. Commands may pass shared state to their subcommands by attaching it to a context value that is propagated down the vocabulary tree.
Basic usage outline:
itm, err := vocab.New("toolname", v) ... if err := itm.Dispatch(ctx, args); err != nil { log.Fatalf("Dispatch failed: %v, err) }
Example ¶
package main import ( "context" "fmt" "log" "os" "strings" "bitbucket.org/creachadair/shell" "github.com/creachadair/vocab" ) func main() { v, err := vocab.New("tool", &tool{ WorkDir: "/tmp", }) if err != nil { log.Fatalf("New failed: %v", err) } v.SetOutput(os.Stdout) // default is os.Stderr ctx := context.Background() for _, cmd := range []string{ "", // simple help, short form "help", // global help, long form "help file", // group help "help file ls", // subcommand help "file list", // execute "-workdir /var/log file list", // global flags "file list -a", // local flags `text echo "all your base" are "belong to us"`, } { args, _ := shell.Split(cmd) fmt.Printf("------ %q\n", args) if err := v.Dispatch(ctx, args); err != nil { log.Fatalf("ERROR: %q: %v", cmd, err) } } } type list struct { All bool `flag:"a,List all files, including dotfiles"` } func (ls list) Run(ctx context.Context, args []string) error { if ls.All { fmt.Println(".config") } fmt.Println("src") fmt.Println("docs") return nil } type echo struct{} func (echo) Run(ctx context.Context, args []string) error { fmt.Println(strings.Join(args, " ")) return nil } type tool struct { WorkDir string `flag:"workdir,Set working directory to this path"` // tool files ls <path> ... Files struct { List list `vocab:"ls,list,dir" help-summary:"List file metadata"` _ struct{} `help-summary:"Subcommands pertaining to files"` } `vocab:"file,files"` // tool text echo <args> ... Text struct { Echo echo `vocab:"echo,print,type" help-summary:"Concatenate and print arguments"` _ struct{} `help-summary:"Subcommands pertaining to text"` } `vocab:"text"` // tool help [command] Help vocab.Help `vocab:"help,wtf"` _ struct{} `help-summary:"A trivial command-line shell"` _ struct{} `help-long:"Demonstrate the use of the vocab package by wiring up\na simple command-line with nested commands."` } var _ vocab.Initializer = (*tool)(nil) // Init sets up the context for subcommands run via t. In this case, it ensures // the specified working directory exists and that $PWD points to it. func (t tool) Init(ctx context.Context, name string, args []string) (context.Context, error) { if err := os.MkdirAll(t.WorkDir, 0755); err != nil { return nil, err } return ctx, os.Chdir(t.WorkDir) }
Output: ------ [] tool: A trivial command-line shell Subcommands: file Subcommands pertaining to files (alias: files) help Show help for a command (alias: wtf) text Subcommands pertaining to text ------ ["help"] tool: A trivial command-line shell Demonstrate the use of the vocab package by wiring up a simple command-line with nested commands. Flags: -workdir string Set working directory to this path (default "/tmp") Subcommands: file Subcommands pertaining to files (alias: files) help Show help for a command (alias: wtf) text Subcommands pertaining to text ------ ["help" "file"] file: Subcommands pertaining to files Subcommands: ls List file metadata (alias: dir, list) ------ ["help" "file" "ls"] ls: List file metadata Flags: -a List all files, including dotfiles ------ ["file" "list"] src docs ------ ["-workdir" "/var/log" "file" "list"] src docs ------ ["file" "list" "-a"] .config src docs ------ ["text" "echo" "all your base" "are" "belong to us"] all your base are belong to us
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Help ¶
type Help struct {
// contains filtered or unexported fields
}
Help implements a generic "help" subcommand that prints out the full help text from the command described by its arguments.
An instance of Help may be embedded into a command struct to provide the help subcommand.
type Helper ¶
type Helper interface { // Help returns long help text for the receiver. Help() string }
The Helper interface may be optionally implemented by a command to generate long help text.
type Initializer ¶
type Initializer interface { // Init prepares a command for execution of the named subcommand with the // given arguments, prior to parsing the subcommand's flags. The name is the // resolved canonical name of the subcommand, and the first element of args // is the name as written (which may be an alias). // // If the returned context is not nil, it replaces ctx in the subcommand; // otherwise ctx is used. If init reports an error, the command execution // will fail. Init(ctx context.Context, name string, args []string) (context.Context, error) }
An Initializer sets up the environment for a subcommand. If a command implements the Init method, it will be called before dispatching control to a subcommand.
type Item ¶
type Item struct {
// contains filtered or unexported fields
}
An Item represents a parsed command tree.
func New ¶
New constructs a vocabulary item from the given value. The root value must either itself implement the vocab.Runner interface, or be a (pointer to a) struct value whose field annotations describe subcommand vocabulary.
To define a field as implementing a subcommand, use the "vocab:" tag to define its name:
type Cmd struct{ A Type1 `vocab:"first"` B *Type2 `vocab:"second"` }
The field types in this example must similarly implement vocab.Runner, or be structs with their own corresponding annotations. During dispatch, an argument list beginning with "first" will dispatch through A, and an argument list beginning with "second" will dispatch through B. The nesting may occur to arbitrary depth, but note that New does not handle cycles.
A subcommand may also have aliases, specified as:
vocab:"name,alias1,alias2,..."
The names and aliases must be unique within a given value.
You can also attach flag to struct fields using the "flag:" tag:
flag:"name,description"
The name becomes the flag string, and the description its help text. The field must either be one of the standard types understood by the flag package, or its pointer must implement the flag.Value interface. In each case the default value for the flag is the current value of the field.
Documentation ¶
In addition to its name, each command has "summary" and "help" strings. The summary is a short (typically one-line) synopsis, and help is a longer and more explanatory (possibly multi-line) description. There are three ways to associate these strings with a command:
If the command implements vocab.Summarizer, its Summary method is used to generate the summary string. Otherwise, if the command has a blank field ("_") whose tag begins with "help-summary:", the rest of that tag is used as the summary string. Otherwise, if the command's type is used as a field of an enclosing command type with a "help-summary:" comment tag, that text is used as the summary string for the command.
If the type implements vocab.Helper, its Help method is used to generate the full help string. Otherwise, if the command has a blank field ("_") whose tag begins with "help-long:", the rest of that tag is used as the long help string. Otherwise, if the command's type is used as a field of an enclosing command type with a "help-log:" comment tag, that text is used as the long help string for the command.
Caveat: Although the Go grammar allows arbitrary string literals as struct field tags, there is a strong convention supported by the reflect package and "go vet" for single-line tags with key:"value" structure. This package will accept multi-line unquoted tags, but be aware that some lint tools may complain if you use them. You can use standard string escapes (e.g., "\n") in the quoted values of tags to avoid this, at the cost of a long line.
func (*Item) Dispatch ¶
Dispatch traverses the vocabulary of m parsing and executing the described commands.
func (*Item) Resolve ¶
Resolve traverses the vocabulary of m to find the command described by path. The path should contain only names; Resolve does not parse flags. It returns the last item successfully reached, along with the unresolved tail of the path (which is empty if the path was fully resolved).
type RunFunc ¶
RunFunc implements the vocab.Runner interface by calling a function with the matching signature. This can be used to embed command implementations into the fields of a struct type with corresponding signatures.
type Runner ¶
type Runner interface { // Run executes the command with the specified arguments. // // The context passed to run contains any values attached by the Init // methods of enclosing commands. Run(ctx context.Context, args []string) error }
A Runner executes the behaviour of a command. If a command implements the Run method, it will be used to invoke the command after flag parsing.
type Summarizer ¶
type Summarizer interface { // Summary returns the summary help text for the receiver. Summary() string }
The Summarizer interface may be optionally implemented by a command to generate summary help text.