summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--example_test.go95
-rw-r--r--parse.go4
-rw-r--r--usage.go85
-rw-r--r--usage_test.go68
4 files changed, 239 insertions, 13 deletions
diff --git a/example_test.go b/example_test.go
index 8394289..20b9225 100644
--- a/example_test.go
+++ b/example_test.go
@@ -254,7 +254,7 @@ func Example_helpTextWithSubcommand() {
}
// 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
os.Args = split("./example get --help")
@@ -290,6 +290,99 @@ func Example_helpTextForSubcommand() {
// --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
func Example_errorText() {
// These are the args you would pass in on the command line
diff --git a/parse.go b/parse.go
index d76ef0f..1ebb2a4 100644
--- a/parse.go
+++ b/parse.go
@@ -85,13 +85,13 @@ func MustParse(dest ...interface{}) *Parser {
err = p.Parse(flags())
switch {
case err == ErrHelp:
- p.writeHelpForCommand(stdout, p.lastCmd)
+ p.writeHelpForSubcommand(stdout, p.lastCmd)
osExit(0)
case err == ErrVersion:
fmt.Fprintln(stdout, p.version)
osExit(0)
case err != nil:
- p.failWithCommand(err.Error(), p.lastCmd)
+ p.failWithSubcommand(err.Error(), p.lastCmd)
}
return p
diff --git a/usage.go b/usage.go
index c121c45..860dc15 100644
--- a/usage.go
+++ b/usage.go
@@ -19,12 +19,27 @@ var (
// Fail prints usage information to stderr and exits with non-zero status
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
-func (p *Parser) failWithCommand(msg string, cmd *command) {
- p.writeUsageForCommand(stderr, cmd)
+// FailSubcommand prints usage information for a specified subcommand to stderr,
+// then exits with non-zero status. 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) 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)
osExit(-1)
}
@@ -35,11 +50,25 @@ func (p *Parser) WriteUsage(w io.Writer) {
if p.lastCmd != nil {
cmd = p.lastCmd
}
- p.writeUsageForCommand(w, cmd)
+ p.writeUsageForSubcommand(w, cmd)
+}
+
+// WriteUsageForSubcommand writes the usage information for a specified
+// 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
}
-// writeUsageForCommand writes usage information for the given subcommand
-func (p *Parser) writeUsageForCommand(w io.Writer, cmd *command) {
+// writeUsageForSubcommand writes usage information for the given subcommand
+func (p *Parser) writeUsageForSubcommand(w io.Writer, cmd *command) {
var positionals, longOptions, shortOptions []*spec
for _, spec := range cmd.specs {
switch {
@@ -158,11 +187,25 @@ func (p *Parser) WriteHelp(w io.Writer) {
if p.lastCmd != nil {
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
-func (p *Parser) writeHelpForCommand(w io.Writer, cmd *command) {
+func (p *Parser) writeHelpForSubcommand(w io.Writer, cmd *command) {
var positionals, longOptions, shortOptions []*spec
for _, spec := range cmd.specs {
switch {
@@ -178,7 +221,7 @@ func (p *Parser) writeHelpForCommand(w io.Writer, cmd *command) {
if p.description != "" {
fmt.Fprintln(w, p.description)
}
- p.writeUsageForCommand(w, cmd)
+ p.writeUsageForSubcommand(w, cmd)
// write the list of positionals
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 {
if spec.cardinality == zero {
return form
diff --git a/usage_test.go b/usage_test.go
index 1b6c475..f790e34 100644
--- a/usage_test.go
+++ b/usage_test.go
@@ -352,9 +352,45 @@ Global options:
p.WriteHelp(&help)
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
p.WriteUsage(&usage)
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) {
@@ -468,3 +504,35 @@ error: something went wrong
assert.Equal(t, expectedStdout[1:], b.String())
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)
+}