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 (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 = `` /* 1497-byte string literal not displayed */
Variables ¶
var Version = struct { Major int Minor int Patch int }{0, 8, 0}
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 are 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 ¶
The New() function reads the input spec, 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 a 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
}
CommandGroups are used to customize help output. They group related Commands and support added Header and Footer sections for notes. When New() parses an input spec, it creates a single CommandGroup for all parsed Subcommands.
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
Placeholder string
}
Options are 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 ¶
Every Option has an associated OptionDecoder that is responsible for decoding its arguments. For flags, Decode is called with an empty string.
func NewDefaulter ¶
func NewDefaulter(d OptionDecoder, defaultArg string) OptionDecoder
Constructs a new 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
Constructs a new 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
Constructs an OptionDecoder for int flag values. The int value is incremented every time the option is decoded.
func NewFlagDecoder ¶
func NewFlagDecoder(val *bool) OptionDecoder
Constructs an OptionDecoder for boolean flag values. The boolean value is set when the option is decoded.
func NewOptionDecoder ¶
func NewOptionDecoder(val interface{}) (decoder OptionDecoder, err error)
Construct an OptionDecoder for a supported type. The val parameter must be a pointer to a value of that type.
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()
}
If an OptionDecoder also implements 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
}
OptionGroups are used to customize help output. They group related Options and support added Header and Footer sections for notes. When New() parses an input spec, it creates a single OptionGroup for all parsed options.