Merge pull request #156 from alexflint/usage-for-subcommands
add FailSubcommand, WriteUsageForSubcommand, WriteHelpForSubcommand
This commit is contained in:
commit
a4afd6a849
|
@ -254,7 +254,7 @@ func Example_helpTextWithSubcommand() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// This example shows the usage string generated by go-arg when using subcommands
|
// This example shows the usage string generated by go-arg when using subcommands
|
||||||
func Example_helpTextForSubcommand() {
|
func Example_helpTextWhenUsingSubcommand() {
|
||||||
// These are the args you would pass in on the command line
|
// These are the args you would pass in on the command line
|
||||||
os.Args = split("./example get --help")
|
os.Args = split("./example get --help")
|
||||||
|
|
||||||
|
@ -290,6 +290,99 @@ func Example_helpTextForSubcommand() {
|
||||||
// --help, -h display this help and exit
|
// --help, -h display this help and exit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This example shows how to print help for an explicit subcommand
|
||||||
|
func Example_writeHelpForSubcommand() {
|
||||||
|
// 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) {}
|
||||||
|
stdout = os.Stdout
|
||||||
|
|
||||||
|
p, err := NewParser(Config{}, &args)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = p.WriteHelpForSubcommand(os.Stdout, "list")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// output:
|
||||||
|
// Usage: example list [--format FORMAT] [--limit LIMIT]
|
||||||
|
//
|
||||||
|
// Options:
|
||||||
|
// --format FORMAT output format
|
||||||
|
// --limit LIMIT
|
||||||
|
//
|
||||||
|
// Global options:
|
||||||
|
// --verbose
|
||||||
|
// --help, -h display this help and exit
|
||||||
|
}
|
||||||
|
|
||||||
|
// This example shows how to print help for a subcommand that is nested several levels deep
|
||||||
|
func Example_writeHelpForSubcommandNested() {
|
||||||
|
// These are the args you would pass in on the command line
|
||||||
|
os.Args = split("./example get --help")
|
||||||
|
|
||||||
|
type mostNestedCmd struct {
|
||||||
|
Item string
|
||||||
|
}
|
||||||
|
|
||||||
|
type nestedCmd struct {
|
||||||
|
MostNested *mostNestedCmd `arg:"subcommand"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type topLevelCmd struct {
|
||||||
|
Nested *nestedCmd `arg:"subcommand"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var args struct {
|
||||||
|
TopLevel *topLevelCmd `arg:"subcommand"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is only necessary when running inside golang's runnable example harness
|
||||||
|
osExit = func(int) {}
|
||||||
|
stdout = os.Stdout
|
||||||
|
|
||||||
|
p, err := NewParser(Config{}, &args)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = p.WriteHelpForSubcommand(os.Stdout, "toplevel", "nested", "mostnested")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// output:
|
||||||
|
// Usage: example toplevel nested mostnested [--item ITEM]
|
||||||
|
//
|
||||||
|
// Options:
|
||||||
|
// --item ITEM
|
||||||
|
// --help, -h display this help and exit
|
||||||
|
}
|
||||||
|
|
||||||
// This example shows the error string generated by go-arg when an invalid option is provided
|
// This example shows the error string generated by go-arg when an invalid option is provided
|
||||||
func Example_errorText() {
|
func Example_errorText() {
|
||||||
// These are the args you would pass in on the command line
|
// These are the args you would pass in on the command line
|
||||||
|
|
4
parse.go
4
parse.go
|
@ -85,13 +85,13 @@ func MustParse(dest ...interface{}) *Parser {
|
||||||
err = p.Parse(flags())
|
err = p.Parse(flags())
|
||||||
switch {
|
switch {
|
||||||
case err == ErrHelp:
|
case err == ErrHelp:
|
||||||
p.writeHelpForCommand(stdout, p.lastCmd)
|
p.writeHelpForSubcommand(stdout, p.lastCmd)
|
||||||
osExit(0)
|
osExit(0)
|
||||||
case err == ErrVersion:
|
case err == ErrVersion:
|
||||||
fmt.Fprintln(stdout, p.version)
|
fmt.Fprintln(stdout, p.version)
|
||||||
osExit(0)
|
osExit(0)
|
||||||
case err != nil:
|
case err != nil:
|
||||||
p.failWithCommand(err.Error(), p.lastCmd)
|
p.failWithSubcommand(err.Error(), p.lastCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
return p
|
return p
|
||||||
|
|
85
usage.go
85
usage.go
|
@ -19,12 +19,27 @@ var (
|
||||||
|
|
||||||
// Fail prints usage information to stderr and exits with non-zero status
|
// Fail prints usage information to stderr and exits with non-zero status
|
||||||
func (p *Parser) Fail(msg string) {
|
func (p *Parser) Fail(msg string) {
|
||||||
p.failWithCommand(msg, p.cmd)
|
p.failWithSubcommand(msg, p.cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
// failWithCommand prints usage information for the given subcommand to stderr and exits with non-zero status
|
// FailSubcommand prints usage information for a specified subcommand to stderr,
|
||||||
func (p *Parser) failWithCommand(msg string, cmd *command) {
|
// then exits with non-zero status. To write usage information for a top-level
|
||||||
p.writeUsageForCommand(stderr, cmd)
|
// subcommand, provide just the name of that subcommand. To write usage
|
||||||
|
// information for a subcommand that is nested under another subcommand, provide
|
||||||
|
// a sequence of subcommand names starting with the top-level subcommand and so
|
||||||
|
// on down the tree.
|
||||||
|
func (p *Parser) FailSubcommand(msg string, subcommand ...string) error {
|
||||||
|
cmd, err := p.lookupCommand(subcommand...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.failWithSubcommand(msg, cmd)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// failWithSubcommand prints usage information for the given subcommand to stderr and exits with non-zero status
|
||||||
|
func (p *Parser) failWithSubcommand(msg string, cmd *command) {
|
||||||
|
p.writeUsageForSubcommand(stderr, cmd)
|
||||||
fmt.Fprintln(stderr, "error:", msg)
|
fmt.Fprintln(stderr, "error:", msg)
|
||||||
osExit(-1)
|
osExit(-1)
|
||||||
}
|
}
|
||||||
|
@ -35,11 +50,25 @@ func (p *Parser) WriteUsage(w io.Writer) {
|
||||||
if p.lastCmd != nil {
|
if p.lastCmd != nil {
|
||||||
cmd = p.lastCmd
|
cmd = p.lastCmd
|
||||||
}
|
}
|
||||||
p.writeUsageForCommand(w, cmd)
|
p.writeUsageForSubcommand(w, cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeUsageForCommand writes usage information for the given subcommand
|
// WriteUsageForSubcommand writes the usage information for a specified
|
||||||
func (p *Parser) writeUsageForCommand(w io.Writer, cmd *command) {
|
// subcommand. To write usage information for a top-level subcommand, provide
|
||||||
|
// just the name of that subcommand. To write usage information for a subcommand
|
||||||
|
// that is nested under another subcommand, provide a sequence of subcommand
|
||||||
|
// names starting with the top-level subcommand and so on down the tree.
|
||||||
|
func (p *Parser) WriteUsageForSubcommand(w io.Writer, subcommand ...string) error {
|
||||||
|
cmd, err := p.lookupCommand(subcommand...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.writeUsageForSubcommand(w, cmd)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeUsageForSubcommand writes usage information for the given subcommand
|
||||||
|
func (p *Parser) writeUsageForSubcommand(w io.Writer, cmd *command) {
|
||||||
var positionals, longOptions, shortOptions []*spec
|
var positionals, longOptions, shortOptions []*spec
|
||||||
for _, spec := range cmd.specs {
|
for _, spec := range cmd.specs {
|
||||||
switch {
|
switch {
|
||||||
|
@ -158,11 +187,25 @@ func (p *Parser) WriteHelp(w io.Writer) {
|
||||||
if p.lastCmd != nil {
|
if p.lastCmd != nil {
|
||||||
cmd = p.lastCmd
|
cmd = p.lastCmd
|
||||||
}
|
}
|
||||||
p.writeHelpForCommand(w, cmd)
|
p.writeHelpForSubcommand(w, cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteHelpForSubcommand writes the usage string followed by the full help
|
||||||
|
// string for a specified subcommand. To write help for a top-level subcommand,
|
||||||
|
// provide just the name of that subcommand. To write help for a subcommand that
|
||||||
|
// is nested under another subcommand, provide a sequence of subcommand names
|
||||||
|
// starting with the top-level subcommand and so on down the tree.
|
||||||
|
func (p *Parser) WriteHelpForSubcommand(w io.Writer, subcommand ...string) error {
|
||||||
|
cmd, err := p.lookupCommand(subcommand...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.writeHelpForSubcommand(w, cmd)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// writeHelp writes the usage string for the given subcommand
|
// writeHelp writes the usage string for the given subcommand
|
||||||
func (p *Parser) writeHelpForCommand(w io.Writer, cmd *command) {
|
func (p *Parser) writeHelpForSubcommand(w io.Writer, cmd *command) {
|
||||||
var positionals, longOptions, shortOptions []*spec
|
var positionals, longOptions, shortOptions []*spec
|
||||||
for _, spec := range cmd.specs {
|
for _, spec := range cmd.specs {
|
||||||
switch {
|
switch {
|
||||||
|
@ -178,7 +221,7 @@ func (p *Parser) writeHelpForCommand(w io.Writer, cmd *command) {
|
||||||
if p.description != "" {
|
if p.description != "" {
|
||||||
fmt.Fprintln(w, p.description)
|
fmt.Fprintln(w, p.description)
|
||||||
}
|
}
|
||||||
p.writeUsageForCommand(w, cmd)
|
p.writeUsageForSubcommand(w, cmd)
|
||||||
|
|
||||||
// write the list of positionals
|
// write the list of positionals
|
||||||
if len(positionals) > 0 {
|
if len(positionals) > 0 {
|
||||||
|
@ -252,6 +295,28 @@ func (p *Parser) printOption(w io.Writer, spec *spec) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// lookupCommand finds a subcommand based on a sequence of subcommand names. The
|
||||||
|
// first string should be a top-level subcommand, the next should be a child
|
||||||
|
// subcommand of that subcommand, and so on. If no strings are given then the
|
||||||
|
// root command is returned. If no such subcommand exists then an error is
|
||||||
|
// returned.
|
||||||
|
func (p *Parser) lookupCommand(path ...string) (*command, error) {
|
||||||
|
cmd := p.cmd
|
||||||
|
for _, name := range path {
|
||||||
|
var found *command
|
||||||
|
for _, child := range cmd.subcommands {
|
||||||
|
if child.name == name {
|
||||||
|
found = child
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if found == nil {
|
||||||
|
return nil, fmt.Errorf("%q is not a subcommand of %s", name, cmd.name)
|
||||||
|
}
|
||||||
|
cmd = found
|
||||||
|
}
|
||||||
|
return cmd, nil
|
||||||
|
}
|
||||||
|
|
||||||
func synopsis(spec *spec, form string) string {
|
func synopsis(spec *spec, form string) string {
|
||||||
if spec.cardinality == zero {
|
if spec.cardinality == zero {
|
||||||
return form
|
return form
|
||||||
|
|
|
@ -352,9 +352,45 @@ Global options:
|
||||||
p.WriteHelp(&help)
|
p.WriteHelp(&help)
|
||||||
assert.Equal(t, expectedHelp[1:], help.String())
|
assert.Equal(t, expectedHelp[1:], help.String())
|
||||||
|
|
||||||
|
var help2 bytes.Buffer
|
||||||
|
p.WriteHelpForSubcommand(&help2, "child", "nested")
|
||||||
|
assert.Equal(t, expectedHelp[1:], help2.String())
|
||||||
|
|
||||||
var usage bytes.Buffer
|
var usage bytes.Buffer
|
||||||
p.WriteUsage(&usage)
|
p.WriteUsage(&usage)
|
||||||
assert.Equal(t, expectedUsage, strings.TrimSpace(usage.String()))
|
assert.Equal(t, expectedUsage, strings.TrimSpace(usage.String()))
|
||||||
|
|
||||||
|
var usage2 bytes.Buffer
|
||||||
|
p.WriteUsageForSubcommand(&usage2, "child", "nested")
|
||||||
|
assert.Equal(t, expectedUsage, strings.TrimSpace(usage2.String()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNonexistentSubcommand(t *testing.T) {
|
||||||
|
var args struct {
|
||||||
|
sub *struct{} `arg:"subcommand"`
|
||||||
|
}
|
||||||
|
p, err := NewParser(Config{}, &args)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
|
||||||
|
err = p.WriteUsageForSubcommand(&b, "does_not_exist")
|
||||||
|
assert.Error(t, err)
|
||||||
|
|
||||||
|
err = p.WriteHelpForSubcommand(&b, "does_not_exist")
|
||||||
|
assert.Error(t, err)
|
||||||
|
|
||||||
|
err = p.FailSubcommand("something went wrong", "does_not_exist")
|
||||||
|
assert.Error(t, err)
|
||||||
|
|
||||||
|
err = p.WriteUsageForSubcommand(&b, "sub", "does_not_exist")
|
||||||
|
assert.Error(t, err)
|
||||||
|
|
||||||
|
err = p.WriteHelpForSubcommand(&b, "sub", "does_not_exist")
|
||||||
|
assert.Error(t, err)
|
||||||
|
|
||||||
|
err = p.FailSubcommand("something went wrong", "sub", "does_not_exist")
|
||||||
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUsageWithoutLongNames(t *testing.T) {
|
func TestUsageWithoutLongNames(t *testing.T) {
|
||||||
|
@ -468,3 +504,35 @@ error: something went wrong
|
||||||
assert.Equal(t, expectedStdout[1:], b.String())
|
assert.Equal(t, expectedStdout[1:], b.String())
|
||||||
assert.Equal(t, -1, exitCode)
|
assert.Equal(t, -1, exitCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFailSubcommand(t *testing.T) {
|
||||||
|
originalStderr := stderr
|
||||||
|
originalExit := osExit
|
||||||
|
defer func() {
|
||||||
|
stderr = originalStderr
|
||||||
|
osExit = originalExit
|
||||||
|
}()
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
stderr = &b
|
||||||
|
|
||||||
|
var exitCode int
|
||||||
|
osExit = func(code int) { exitCode = code }
|
||||||
|
|
||||||
|
expectedStdout := `
|
||||||
|
Usage: example sub
|
||||||
|
error: something went wrong
|
||||||
|
`
|
||||||
|
|
||||||
|
var args struct {
|
||||||
|
Sub *struct{} `arg:"subcommand"`
|
||||||
|
}
|
||||||
|
p, err := NewParser(Config{Program: "example"}, &args)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = p.FailSubcommand("something went wrong", "sub")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, expectedStdout[1:], b.String())
|
||||||
|
assert.Equal(t, -1, exitCode)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue