print help and usage at subcommand level if necessary
This commit is contained in:
parent
15bf383f1d
commit
b83047068d
|
@ -104,7 +104,7 @@ func Example_multipleMixed() {
|
|||
}
|
||||
|
||||
// This example shows the usage string generated by go-arg
|
||||
func Example_usageString() {
|
||||
func Example_helpText() {
|
||||
// These are the args you would pass in on the command line
|
||||
os.Args = split("./example --help")
|
||||
|
||||
|
@ -136,8 +136,8 @@ func Example_usageString() {
|
|||
// --help, -h display this help and exit
|
||||
}
|
||||
|
||||
// This example shows the usage string generated by go-arg
|
||||
func Example_usageStringWithSubcommand() {
|
||||
// This example shows the usage string generated by go-arg when using subcommands
|
||||
func Example_helpTextWithSubcommand() {
|
||||
// These are the args you would pass in on the command line
|
||||
os.Args = split("./example --help")
|
||||
|
||||
|
@ -172,3 +172,86 @@ func Example_usageStringWithSubcommand() {
|
|||
// get fetch an item and print it
|
||||
// list list available items
|
||||
}
|
||||
|
||||
// This example shows the usage string generated by go-arg when using subcommands
|
||||
func Example_helpTextForSubcommand() {
|
||||
// These are the args you would pass in on the command line
|
||||
os.Args = split("./example get --help")
|
||||
|
||||
type getCmd struct {
|
||||
Item string `arg:"positional" help:"item to fetch"`
|
||||
}
|
||||
|
||||
type listCmd struct {
|
||||
Format string `help:"output format"`
|
||||
Limit int
|
||||
}
|
||||
|
||||
var args struct {
|
||||
Verbose bool
|
||||
Get *getCmd `arg:"subcommand" help:"fetch an item and print it"`
|
||||
List *listCmd `arg:"subcommand" help:"list available items"`
|
||||
}
|
||||
|
||||
// This is only necessary when running inside golang's runnable example harness
|
||||
osExit = func(int) {}
|
||||
|
||||
MustParse(&args)
|
||||
|
||||
// output:
|
||||
// Usage: example get ITEM
|
||||
//
|
||||
// Positional arguments:
|
||||
// ITEM item to fetch
|
||||
//
|
||||
// Options:
|
||||
// --help, -h display this help and exit
|
||||
}
|
||||
|
||||
// This example shows the error string generated by go-arg when an invalid option is provided
|
||||
func Example_errorText() {
|
||||
// These are the args you would pass in on the command line
|
||||
os.Args = split("./example --optimize INVALID")
|
||||
|
||||
var args struct {
|
||||
Input string `arg:"positional"`
|
||||
Output []string `arg:"positional"`
|
||||
Verbose bool `arg:"-v" help:"verbosity level"`
|
||||
Dataset string `help:"dataset to use"`
|
||||
Optimize int `arg:"-O,help:optimization level"`
|
||||
}
|
||||
|
||||
// This is only necessary when running inside golang's runnable example harness
|
||||
osExit = func(int) {}
|
||||
stderr = os.Stdout
|
||||
|
||||
MustParse(&args)
|
||||
|
||||
// output:
|
||||
// Usage: example [--verbose] [--dataset DATASET] [--optimize OPTIMIZE] INPUT [OUTPUT [OUTPUT ...]]
|
||||
// error: error processing --optimize: strconv.ParseInt: parsing "INVALID": invalid syntax
|
||||
}
|
||||
|
||||
// This example shows the error string generated by go-arg when an invalid option is provided
|
||||
func Example_errorTextForSubcommand() {
|
||||
// These are the args you would pass in on the command line
|
||||
os.Args = split("./example get --count INVALID")
|
||||
|
||||
type getCmd struct {
|
||||
Count int
|
||||
}
|
||||
|
||||
var args struct {
|
||||
Get *getCmd `arg:"subcommand"`
|
||||
}
|
||||
|
||||
// This is only necessary when running inside golang's runnable example harness
|
||||
osExit = func(int) {}
|
||||
stderr = os.Stdout
|
||||
|
||||
MustParse(&args)
|
||||
|
||||
// output:
|
||||
// Usage: example get [--count COUNT]
|
||||
// error: error processing --count: strconv.ParseInt: parsing "INVALID": invalid syntax
|
||||
}
|
||||
|
|
44
parse.go
44
parse.go
|
@ -63,6 +63,7 @@ type command struct {
|
|||
dest path
|
||||
specs []*spec
|
||||
subcommands []*command
|
||||
parent *command
|
||||
}
|
||||
|
||||
// ErrHelp indicates that -h or --help were provided
|
||||
|
@ -77,18 +78,19 @@ func MustParse(dest ...interface{}) *Parser {
|
|||
if err != nil {
|
||||
fmt.Println(err)
|
||||
osExit(-1)
|
||||
return nil // just in case osExit was monkey-patched
|
||||
}
|
||||
|
||||
err = p.Parse(flags())
|
||||
switch {
|
||||
case err == ErrHelp:
|
||||
p.WriteHelp(os.Stdout)
|
||||
p.writeHelpForCommand(os.Stdout, p.lastCmd)
|
||||
osExit(0)
|
||||
case err == ErrVersion:
|
||||
fmt.Println(p.version)
|
||||
osExit(0)
|
||||
case err != nil:
|
||||
p.Fail(err.Error())
|
||||
p.failWithCommand(err.Error(), p.lastCmd)
|
||||
}
|
||||
|
||||
return p
|
||||
|
@ -123,6 +125,9 @@ type Parser struct {
|
|||
config Config
|
||||
version string
|
||||
description string
|
||||
|
||||
// the following fields change curing processing of command line arguments
|
||||
lastCmd *command
|
||||
}
|
||||
|
||||
// Versioned is the interface that the destination struct should implement to
|
||||
|
@ -297,6 +302,7 @@ func cmdFromStruct(name string, dest path, t reflect.Type) (*command, error) {
|
|||
return false
|
||||
}
|
||||
|
||||
subcmd.parent = &cmd
|
||||
subcmd.help = field.Tag.Get("help")
|
||||
|
||||
cmd.subcommands = append(cmd.subcommands, subcmd)
|
||||
|
@ -349,21 +355,19 @@ func cmdFromStruct(name string, dest path, t reflect.Type) (*command, error) {
|
|||
// Parse processes the given command line option, storing the results in the field
|
||||
// of the structs from which NewParser was constructed
|
||||
func (p *Parser) Parse(args []string) error {
|
||||
// If -h or --help were specified then print usage
|
||||
for _, arg := range args {
|
||||
if arg == "-h" || arg == "--help" {
|
||||
return ErrHelp
|
||||
}
|
||||
if arg == "--version" {
|
||||
return ErrVersion
|
||||
}
|
||||
if arg == "--" {
|
||||
break
|
||||
err := p.process(args)
|
||||
if err != nil {
|
||||
// If -h or --help were specified then make sure help text supercedes other errors
|
||||
for _, arg := range args {
|
||||
if arg == "-h" || arg == "--help" {
|
||||
return ErrHelp
|
||||
}
|
||||
if arg == "--" {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process all command line arguments
|
||||
return p.process(args)
|
||||
return err
|
||||
}
|
||||
|
||||
// process environment vars for the given arguments
|
||||
|
@ -415,6 +419,7 @@ func (p *Parser) process(args []string) error {
|
|||
|
||||
// union of specs for the chain of subcommands encountered so far
|
||||
curCmd := p.cmd
|
||||
p.lastCmd = curCmd
|
||||
|
||||
// make a copy of the specs because we will add to this list each time we expand a subcommand
|
||||
specs := make([]*spec, len(curCmd.specs))
|
||||
|
@ -465,9 +470,18 @@ func (p *Parser) process(args []string) error {
|
|||
}
|
||||
|
||||
curCmd = subcmd
|
||||
p.lastCmd = curCmd
|
||||
continue
|
||||
}
|
||||
|
||||
// check for special --help and --version flags
|
||||
switch arg {
|
||||
case "-h", "--help":
|
||||
return ErrHelp
|
||||
case "--version":
|
||||
return ErrVersion
|
||||
}
|
||||
|
||||
// check for an equals sign, as in "--foo=bar"
|
||||
var value string
|
||||
opt := strings.TrimLeft(arg, "-")
|
||||
|
|
48
usage.go
48
usage.go
|
@ -12,17 +12,30 @@ import (
|
|||
// the width of the left column
|
||||
const colWidth = 25
|
||||
|
||||
// to allow monkey patching in tests
|
||||
var stderr = os.Stderr
|
||||
|
||||
// Fail prints usage information to stderr and exits with non-zero status
|
||||
func (p *Parser) Fail(msg string) {
|
||||
p.WriteUsage(os.Stderr)
|
||||
fmt.Fprintln(os.Stderr, "error:", msg)
|
||||
os.Exit(-1)
|
||||
p.failWithCommand(msg, p.cmd)
|
||||
}
|
||||
|
||||
// failWithCommand prints usage information for the given subcommand to stderr and exits with non-zero status
|
||||
func (p *Parser) failWithCommand(msg string, cmd *command) {
|
||||
p.writeUsageForCommand(stderr, cmd)
|
||||
fmt.Fprintln(stderr, "error:", msg)
|
||||
osExit(-1)
|
||||
}
|
||||
|
||||
// WriteUsage writes usage information to the given writer
|
||||
func (p *Parser) WriteUsage(w io.Writer) {
|
||||
p.writeUsageForCommand(w, p.cmd)
|
||||
}
|
||||
|
||||
// writeUsageForCommand writes usage information for the given subcommand
|
||||
func (p *Parser) writeUsageForCommand(w io.Writer, cmd *command) {
|
||||
var positionals, options []*spec
|
||||
for _, spec := range p.cmd.specs {
|
||||
for _, spec := range cmd.specs {
|
||||
if spec.positional {
|
||||
positionals = append(positionals, spec)
|
||||
} else {
|
||||
|
@ -34,7 +47,19 @@ func (p *Parser) WriteUsage(w io.Writer) {
|
|||
fmt.Fprintln(w, p.version)
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, "Usage: %s", p.cmd.name)
|
||||
// make a list of ancestor commands so that we print with full context
|
||||
var ancestors []string
|
||||
ancestor := cmd
|
||||
for ancestor != nil {
|
||||
ancestors = append(ancestors, ancestor.name)
|
||||
ancestor = ancestor.parent
|
||||
}
|
||||
|
||||
// print the beginning of the usage string
|
||||
fmt.Fprintf(w, "Usage:")
|
||||
for i := len(ancestors) - 1; i >= 0; i-- {
|
||||
fmt.Fprint(w, " "+ancestors[i])
|
||||
}
|
||||
|
||||
// write the option component of the usage message
|
||||
for _, spec := range options {
|
||||
|
@ -88,8 +113,13 @@ func printTwoCols(w io.Writer, left, help string, defaultVal *string) {
|
|||
|
||||
// WriteHelp writes the usage string followed by the full help string for each option
|
||||
func (p *Parser) WriteHelp(w io.Writer) {
|
||||
p.writeHelpForCommand(w, p.cmd)
|
||||
}
|
||||
|
||||
// writeHelp writes the usage string for the given subcommand
|
||||
func (p *Parser) writeHelpForCommand(w io.Writer, cmd *command) {
|
||||
var positionals, options []*spec
|
||||
for _, spec := range p.cmd.specs {
|
||||
for _, spec := range cmd.specs {
|
||||
if spec.positional {
|
||||
positionals = append(positionals, spec)
|
||||
} else {
|
||||
|
@ -100,7 +130,7 @@ func (p *Parser) WriteHelp(w io.Writer) {
|
|||
if p.description != "" {
|
||||
fmt.Fprintln(w, p.description)
|
||||
}
|
||||
p.WriteUsage(w)
|
||||
p.writeUsageForCommand(w, cmd)
|
||||
|
||||
// write the list of positionals
|
||||
if len(positionals) > 0 {
|
||||
|
@ -132,9 +162,9 @@ func (p *Parser) WriteHelp(w io.Writer) {
|
|||
}
|
||||
|
||||
// write the list of subcommands
|
||||
if len(p.cmd.subcommands) > 0 {
|
||||
if len(cmd.subcommands) > 0 {
|
||||
fmt.Fprint(w, "\nCommands:\n")
|
||||
for _, subcmd := range p.cmd.subcommands {
|
||||
for _, subcmd := range cmd.subcommands {
|
||||
printTwoCols(w, subcmd.name, subcmd.help, nil)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue