README

vocab: Command line argument and flag routing

http://godoc.org/bitbucket.org/creachadair/vocab

Go Report Card

This repository defines a Go package that implements an API for describing and processing command-line arguments and flags in shell-like tools.

Expand ▾ Collapse ▴

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

Code:

package main

import (
	"context"
	"fmt"
	"log"
	"os"
	"strings"

	"bitbucket.org/creachadair/shell"
	"bitbucket.org/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 a simple command-line with nested commands."`
}

// 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, args []string) (context.Context, error) {
	if err := os.MkdirAll(t.WorkDir, 0755); err != nil {
		return nil, err
	}
	return ctx, os.Chdir(t.WorkDir)
}
------ []
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.

func (Help) Run

func (Help) Run(ctx context.Context, args []string) error

Run implements 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

func New(name string, root interface{}) (*Item, error)

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 cannot 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

func (m *Item) Dispatch(ctx context.Context, args []string) error

Dispatch traverses the vocabulary of m parsing and executing the described commands.

func (*Item) Resolve

func (m *Item) Resolve(path []string) (*Item, []string)

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).

func (*Item) SetOutput

func (m *Item) SetOutput(w io.Writer)

SetOutput sets the output writer for m and all its nested subcommands to w. It will panic if w == nil.

type RunFunc

type RunFunc func(context.Context, []string) error

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.

func (RunFunc) Run

func (rf RunFunc) Run(ctx context.Context, args []string) error

Run satisfies the vocab.Runner interface.

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.

Source Files