Documentation
¶
Overview ¶
Package writ implements command line decoding according to GNU getopt_long conventions: http://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html All long and short-form option variations are supported: --with-x, --name Sam, --day=Friday, -i FILE, -vvv, etc.
Additionally, writ supports subcommands, customizable help output generation, and default values. However, writ is purely a decoder package. Command dispatch and execution are intentionally omitted.
Basics ¶
Writ is modeled after the Go encoding/* packages. The New() function reads an input struct with relevant field tags and builds a Command that is capable of decoding short and long-form command-line options into the input struct's fields.
Options are specified via "flag" and "option" field tags, and subcommands are specified via "command" field tags. Additional field tags may be used to specify command aliases, default values, and help content.
Once a Command is constructed, it's Decode() method is used to parse an input []string of arguments (e.g. os.Args[1:]). The return values of Decode() specify the parsed command path, remaining positional arguments, and any parse/decode errors encountered. The command path is a slice of []*Command representing the invoked command hierarchy. This can be used to differentiate between user selection of top-level commands vs. subcommands.
Options ¶
Options are specified via the "option" and "flag" struct tags. Both are considered options, but fields marked "option" take arguments, whereas fields marked "flag" do not.
Field values are decoded via implementations of OptionDecoder. Writ provides OptionDecoder implementations for most basic field types along with several convenience types. See the NewOptionDecoder() documentation for a list of supported types. If a field implements the OptionDecoder interface, the Decode() method from the field will be used. Otherwise, writ will attempt to generate an OptionDecoder for known field types. If a field marked "flag" or "option" is of an unsupported type and doesn't implement OptionDecoder, then New() will panic when parsing the field.
Since flags take no arguments, they are valid only with bool, int, and OptionDecoder fields. Bool flags are true if the option is parsed, and int flags maintain a count of the number of times the option is parsed.
Commands ¶
New() parses an input struct with relevant field tags to build a top-level Command. Subcommands are supported by using the "command" field tag. Fields marked with "command" must be of struct type, and are parsed the same way as top-level commands. The resulting subcommands are then assigned to the parent's Subcommand field. The process repeats recursively.
Help Output ¶
Writ provides helper methods for generating help output. Command.WriteHelp() generates help content and writes to a given io.Writer. Command.ExitHelp() goes a step further. If the error argument is non-nil, it writes both the help content and error message to Stderr, and then terminates the program with an exit code of 1. If the error argument is nil, it writes the help content to os.Stdout and terminates the program with an exit code of 0.
The help output is designed to mimic the --help output for common GNU programs. New() parses command and option descriptions from the "description" field tag. Options may also specify the "placeholder" tag.
Help content is generated via the text/template package. Writ provides a default template that can be overridden on a per-Command basis. See the documentation of the Help type for more details.
Field Tag Reference ¶
The New() function recognizes the following combinations of field tags:
Option Fields: - option (required): a comma-separated list of names for the option - description: the description to display for help output - placeholder: the placeholder value to use next to the option names (e.g. FILE) - default: the default value for the field - env: the name of an environment variable, the value of which is used as a default for the field Flag fields: - flag (required): a comma-separated list of names for the flag - description: the description to display for help output Command fields: - name (required): a name for the command - aliases: a comma-separated list of alias names for the command - description: the description to display for help output
If both "default" and "env" are specified for an option field, the environment variable is consulted first. If the environment variable is present and decodes without error, that value is used. Otherwise, the value for the "default" tag is used. Values specified via parsed arguments take precedence over both types of defaults.
Example (Basic) ¶
package main
import (
"fmt"
"github.com/ziuchkovski/writ"
"strings"
)
// The Greeter's field tags are parsed into flags and options by writ.New()
type Greeter struct {
HelpFlag bool `flag:"help" description:"display this help message"`
Verbosity int `flag:"v, verbose" description:"display verbose output"`
Name string `option:"n, name" default:"Everyone" description:"the person to greet"`
}
func main() {
// First, the Greeter is parsed into a *Command by writ.New()
greeter := &Greeter{}
cmd := writ.New("greeter", greeter)
// Next, the input arguments are decoded.
// Use cmd.Decode(os.Args[1:]) in a real application
_, positional, err := cmd.Decode([]string{"-vvv", "--name", "Sam", "How's it going?"})
if err != nil || greeter.HelpFlag {
cmd.ExitHelp(err)
}
message := strings.Join(positional, " ")
fmt.Printf("Hi %s! %s\n", greeter.Name, message)
if greeter.Verbosity > 0 {
fmt.Printf("I'm feeling re%slly chatty today!\n", strings.Repeat("a", greeter.Verbosity))
}
}
Output: Hi Sam! How's it going? I'm feeling reaaally chatty today!
Example (Explicit) ¶
This example demonstrates explicit Command and Option construction without the use of writ.New()
package main
import (
"github.com/ziuchkovski/writ"
"os"
"runtime"
)
type Config struct {
help bool
verbosity int
// A hidden flag
tolerance float32
// A dynamically added option for Mac OS
useQuartz bool
}
// This example demonstrates explicit Command and Option construction
// without the use of writ.New()
func main() {
config := &Config{}
cmd := &writ.Command{Name: "explicit"}
cmd.Options = []*writ.Option{
{
Names: []string{"h", "help"},
Description: "Display this help text and exit",
Decoder: writ.NewFlagDecoder(&config.help),
Flag: true,
},
{
Names: []string{"v"},
Description: "Increase verbosity; may be specified more than once",
Decoder: writ.NewFlagAccumulator(&config.verbosity),
Flag: true,
Plural: true,
},
{
Names: []string{"t", "tolerance"},
Decoder: writ.NewOptionDecoder(&config.tolerance),
},
}
cmd.Help = writ.Help{
Usage: "Usage: explicit [OPTION]...",
Header: "Explicit demonstrates explicit commands and options",
Footer: "This method is flexible but more verbose than using writ.New()",
}
general := cmd.GroupOptions("help", "v")
general.Header = "General Options:"
cmd.Help.OptionGroups = append(cmd.Help.OptionGroups, general)
if runtime.GOOS == "darwin" {
cmd.Options = append(cmd.Options, &writ.Option{
Names: []string{"use-quartz"},
Description: "Use Quartz display on Mac",
Decoder: writ.NewFlagDecoder(&config.useQuartz),
Flag: true,
})
platform := cmd.GroupOptions("use-quartz")
platform.Header = "Platform Options:"
cmd.Help.OptionGroups = append(cmd.Help.OptionGroups, platform)
}
// Decode the options
_, _, err := cmd.Decode(os.Args[1:])
if err != nil || config.help {
cmd.ExitHelp(err)
}
}
Output:
Example (Features) ¶
package main
import (
"bufio"
"errors"
"fmt"
"github.com/ziuchkovski/writ"
"io"
"os"
"strings"
)
// This example demonstrates some of the convenience features offered by writ
// It replaces user-specified words in an input file and writes the results to
// an output file. By default, the input is read from os.Stdin and written to
// os.Stdout.
type ReplacerCmd struct {
Input io.Reader `option:"i" description:"Read input values from FILE (default: stdin)" default:"-" placeholder:"FILE"`
Output io.WriteCloser `option:"o" description:"Write rendered output to FILE (default: stdout)" default:"-" placeholder:"FILE"`
Replacements map[string]string `option:"r, replace" description:"Replace occurrences of ORIG with NEW" placeholder:"ORIG=NEW"`
HelpFlag bool `flag:"h, help" description:"Display this help text and exit"`
}
func (r ReplacerCmd) Replace() error {
var pairs []string
for k, v := range r.Replacements {
pairs = append(pairs, k, v)
}
replacer := strings.NewReplacer(pairs...)
scanner := bufio.NewScanner(r.Input)
for scanner.Scan() {
line := scanner.Text()
_, err := io.WriteString(r.Output, replacer.Replace(line)+"\n")
if err != nil {
return err
}
}
err := scanner.Err()
if err != nil {
return err
}
return r.Output.Close()
}
func main() {
// Construct the command
replacer := &ReplacerCmd{}
cmd := writ.New("replacer", replacer)
cmd.Help.Usage = "Usage: replacer [OPTION]..."
cmd.Help.Header = "Perform text replacement according to the -r/--replace option"
cmd.Help.Footer = "By default, replacer reads from stdin and write to stdout. Use the -i and -o options to override."
// Decode input arguments
_, positional, err := cmd.Decode(os.Args[1:])
if err != nil || replacer.HelpFlag {
cmd.ExitHelp(err)
}
if len(positional) > 0 {
cmd.ExitHelp(errors.New("replacer does not accept positional arguments"))
}
// At this point, the ReplacerCmd's Input, Output, and Replacements fields are all
// known-valid, so we can run the replacement.
err = replacer.Replace()
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %s\n", err)
os.Exit(1)
}
}
Output:
Index ¶
- Constants
- Variables
- type Command
- func (c *Command) Decode(args []string) (path Path, positional []string, err error)
- func (c *Command) ExitHelp(err error)
- func (c *Command) GroupCommands(names ...string) CommandGroup
- func (c *Command) GroupOptions(names ...string) OptionGroup
- func (c *Command) Option(name string) *Option
- func (c *Command) String() string
- func (c *Command) Subcommand(name string) *Command
- func (c *Command) WriteHelp(w io.Writer) error
- type CommandGroup
- type Help
- type Option
- type OptionDecoder
- type OptionDefaulter
- type OptionGroup
- type Path
Examples ¶
Constants ¶
const HelpText = `` /* 1337-byte string literal not displayed */
HelpText is used by Command.WriteHelp() and Command.ExitHelp() to generate help content.
This copy of the template is used when compiling with go 1.6+. See template_legacy.go for the go < 1.6 template. Output is the same for both templates, but this one is easier to read.
Variables ¶
var Version = struct { Major int Minor int Patch int }{0, 8, 4}
Version records the writ package version
Functions ¶
This section is empty.
Types ¶
type Command ¶
type Command struct {
// Required
Name string
// Optional
Aliases []string
Options []*Option
Subcommands []*Command
// Optional; used for help output only
Help Help
Description string // Commands without descriptions are hidden from help output
}
Command is used to specify program options and subcommands. Commands are generally constructed with New() but can be created directly if added flexibility or runtime dynamism is required.
func New ¶
New reads the input spec argument, searching for fields tagged with "option", "flag", or "command". The field type and tags are used to construct a corresponding Command instance, which can be used to decode program arguments. See the package overview documentation for details.
NOTE: The spec value must be a pointer to a struct
func (*Command) Decode ¶
Decode parses the given arguments according to GNU getopt_long conventions. It matches Option arguments, both short and long-form, and decodes those arguments with the matched Option's Decoder field. If the Command has associated subcommands, the subcommand names are matched and extracted from the start of the positional arguments. Decode returns a Path that identifies the selected command hierarchy, along with the remaining unmatched positional arguments.
To avoid ambiguity, subcommand matching terminates at the first unmatched positional argument. Similarly, option names are matched against the command hierarchy as it exists at the point the option is encounter. If command "first" has a subcommand "second", and "second" has an option "foo", then "first second --foo" is valid but "first --foo second" returns an error. If the two commands, "first" and "second", both specify a "bar" option, then "first --bar second" decodes "bar" on "first", whereas "first second --bar" decodes "bar" on "second".
As with GNU getopt_long, a bare "--" argument terminates argument parsing. All arguments after the first "--" argument are considered positional parameters.
func (*Command) ExitHelp ¶
ExitHelp writes help output and terminates the program. If err is nil, the output is written to os.Stdout and the program terminates with a 0 exit code. Otherwise, both the help output and error message are written to os.Stderr and the program terminates with a 1 exit code.
func (*Command) GroupCommands ¶
func (c *Command) GroupCommands(names ...string) CommandGroup
GroupCommands is a convenience method for building CommandGroups, which are used to customize help output. It searches the method receiver for the named subcommands and returns a corresponding CommandGroup. Subcommands are matched if their name or any of their aliases match. If a subcommand is not found, GroupCommands panics.
func (*Command) GroupOptions ¶
func (c *Command) GroupOptions(names ...string) OptionGroup
GroupOptions is a convenience method for building OptionGroups, which are used to customize help output. It searches the method receiver for the named options and returns a corresponding OptionGroup. Options are matched if any of the Option's names match. If an option is not found, GroupOptions panics.
func (*Command) Option ¶
Option is a convenience method to locate options by name. If the current Command has an Option with the given name, that Option is returned. Otherwise, nil is returned. Options are matched if any of the Option's names match. Options are searched only on the method receiver, not any of its subcommands.
func (*Command) Subcommand ¶
Subcommand is a convenience method to locate subcommands by name. If the method receiver has a subcommand with the given name, the subcommand is returned. Otherwise, nil is returned. Subcommands are matched if the the name or any of its aliases match.
type CommandGroup ¶
type CommandGroup struct {
Commands []*Command
// Optional
Name string // Not displayed; for matching purposes within the template
Header string // Optional message displayed before the group
}
CommandGroup is used to customize help output. It groups related Commands for output by Command.WriteHelp() and Command.ExitHelp(). When New() parses an input spec, it creates a single CommandGroup for all parsed Subcommands that have non-empty descriptions.
type Help ¶
type Help struct {
OptionGroups []OptionGroup // Defaults to a single OptionGroup with all parsed spec option fields
CommandGroups []CommandGroup // Defaults to a single CommandGroup with all parsed spec command fields
// Optional
Template *template.Template // Used to render output
Usage string // Short message displayed at the top of output. Typically one line.
Header string // Optional message displayed after Usage.
}
The Help type is used for presentation purposes only, and does not affect argument parsing.
It is used by Command.ExitHelp() and Command.WriteHelp() to render help output. The default behavior renders output similar to GNU commands, but is fully customizable. Every command has an associated Help field.
The Command.ExitHelp() and Command.WriteHelp() methods execute the template assigned to the Template field, passing the Command as input. If the Template field is nil, the writ package's default template is used.
The New() function creates a single CommandGroup for all commands it parses and a single OptionGroup for all options it parses.
type Option ¶
type Option struct {
// Required
Names []string // All of the names for the Option, long and short alike
Decoder OptionDecoder // The Decoder is responsible for parsing the Option's arguments
// Optional
Flag bool // If Flag is set, the Option takes no arguments
Plural bool // If Plural is set, the Option may be specified multiple times
// Optional; used for help output only
Description string // Options without descriptions are hidden from help output
Placeholder string
}
Option is used to specify program options and flags. Options are created implicitly when New() parses a command spec. They may also be created directly if added flexibility or runtime dynamism is required.
func (*Option) LongNames ¶
LongNames returns a filtered slice of the names that are longer than one rune in length
func (*Option) ShortNames ¶
ShortNames returns a filtered slice of the names that are exactly one rune in length
type OptionDecoder ¶
OptionDecoder is the interface used for decoding Option arguments. Every Option must have an OptionDecoder assigned. New() constructs and assigns OptionDecoders automatically for supported field types.
func NewDefaulter ¶
func NewDefaulter(d OptionDecoder, defaultArg string) OptionDecoder
NewDefaulter builds an OptionDecoder that implements the OptionDefaulter interface. The value for defaultArg is supplied to the underlying OptionDecoder's Decode() method when SetDefault() is called. If the value fails to decode, the OptionDefaulter will panic.
func NewEnvDefaulter ¶
func NewEnvDefaulter(d OptionDecoder, key string) OptionDecoder
NewEnvDefaulter builds an OptionDecoder that implements the OptionDefaulter interface. The key's value is used to specify an environment variable. If the environment variable is set, the value is supplied to the underlying OptionDecoder's Decode() method when SetDefault() is called.s If the environment variable isn't set or fails to decode, the underlying OptionDecoder's SetDefault() method will be invoked (if the Decoder implements the OptionDefaulter interface). Otherwise, no action is taken.
func NewFlagAccumulator ¶
func NewFlagAccumulator(val *int) OptionDecoder
NewFlagAccumulator builds an OptionDecoder for int flag values. The int value is incremented every time the option is decoded.
func NewFlagDecoder ¶
func NewFlagDecoder(val *bool) OptionDecoder
NewFlagDecoder builds an OptionDecoder for boolean flag values. The boolean value is set when the option is decoded.
func NewOptionDecoder ¶
func NewOptionDecoder(val interface{}) OptionDecoder
NewOptionDecoder builds an OptionDecoder for supported value types. The val parameter must be a pointer to one of the supported types, or else NewOptionDecoder will panic.
Supported types
int, int8, int16, int32, int64, uint, uint8, iunt16, uint32, uint64 float32, float64 string, []string map[string]string Argument must be in key=value format. io.Reader, io.ReadCloser Argument must be a path to an existing file, or "-" to specify os.Stdin io.Writer, io.WriteCloser Argument will be used to create a new file, or "-" to specify os.Stdout. If a file already exists at the path specified, it will be overwritten.
type OptionDefaulter ¶
type OptionDefaulter interface {
SetDefault()
}
OptionDefaulter is used to initialize default option values. If an OptionDecoder implemented the OptionDefaulter interface, its SetDefault() method is called prior to decoding options.
type OptionGroup ¶
type OptionGroup struct {
Options []*Option
// Optional
Name string // Not displayed; for matching purposes within the temp
Header string // Optional message displayed before the group
}
OptionGroup is used to customize help output. It groups related Options for output by Command.WriteHelp() and Command.ExitHelp(). When New() parses an input spec, it creates a single OptionGroup for all parsed options that have non-empty descriptions.
type Path ¶
type Path []*Command
A Path represents a parsed Command list as returned by Command.Decode().
func (Path) First ¶
First returns the first command of the path. This is the top-level/root command.
