Skip to content

Commit

Permalink
Merge pull request #82 from alexflint/subcommand-impl
Browse files Browse the repository at this point in the history
Add support for subcommands
  • Loading branch information
alexflint authored Aug 6, 2019
2 parents 6de9e78 + 11a2707 commit 8baf704
Show file tree
Hide file tree
Showing 10 changed files with 1,220 additions and 233 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
language: go
go:
- "1.10"
- "1.11"
- "1.12"
- tip
env:
- GO111MODULE=on # will only be used in go 1.11
Expand Down
53 changes: 53 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,59 @@ Options:
--help, -h display this help and exit
```
### Subcommands
*Introduced in `v1.1.0`*
Subcommands are commonly used in tools that wish to group multiple functions into a single program. An example is the `git` tool:
```shell
$ git checkout [arguments specific to checking out code]
$ git commit [arguments specific to committing]
$ git push [arguments specific to pushing]
```
The strings "checkout", "commit", and "push" are different from simple positional arguments because the options available to the user change depending on which subcommand they choose.
This can be implemented with `go-arg` as follows:
```go
type CheckoutCmd struct {
Branch string `arg:"positional"`
Track bool `arg:"-t"`
}
type CommitCmd struct {
All bool `arg:"-a"`
Message string `arg:"-m"`
}
type PushCmd struct {
Remote string `arg:"positional"`
Branch string `arg:"positional"`
SetUpstream bool `arg:"-u"`
}
var args struct {
Checkout *CheckoutCmd `arg:"subcommand:checkout"`
Commit *CommitCmd `arg:"subcommand:commit"`
Push *PushCmd `arg:"subcommand:push"`
Quiet bool `arg:"-q"` // this flag is global to all subcommands
}

arg.MustParse(&args)

switch {
case args.Checkout != nil:
fmt.Printf("checkout requested for branch %s\n", args.Checkout.Branch)
case args.Commit != nil:
fmt.Printf("commit requested with message \"%s\"\n", args.Commit.Message)
case args.Push != nil:
fmt.Printf("push requested from %s to %s\n", args.Push.Branch, args.Push.Remote)
}
```
Some additional rules apply when working with subcommands:
* The `subcommand` tag can only be used with fields that are pointers to structs
* Any struct that contains a subcommand must not contain any positionals
### API Documentation
https://godoc.org/github.com/alexflint/go-arg
Expand Down
166 changes: 165 additions & 1 deletion example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down Expand Up @@ -135,3 +135,167 @@ func Example_usageString() {
// optimization level
// --help, -h display this help and exit
}

// 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")

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 [--verbose]
//
// Options:
// --verbose
// --help, -h display this help and exit
//
// Commands:
// 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
}

// This example demonstrates use of subcommands
func Example_subcommand() {
// These are the args you would pass in on the command line
os.Args = split("./example commit -a -m what-this-commit-is-about")

type CheckoutCmd struct {
Branch string `arg:"positional"`
Track bool `arg:"-t"`
}
type CommitCmd struct {
All bool `arg:"-a"`
Message string `arg:"-m"`
}
type PushCmd struct {
Remote string `arg:"positional"`
Branch string `arg:"positional"`
SetUpstream bool `arg:"-u"`
}
var args struct {
Checkout *CheckoutCmd `arg:"subcommand:checkout"`
Commit *CommitCmd `arg:"subcommand:commit"`
Push *PushCmd `arg:"subcommand:push"`
Quiet bool `arg:"-q"` // this flag is global to all subcommands
}

// This is only necessary when running inside golang's runnable example harness
osExit = func(int) {}
stderr = os.Stdout

MustParse(&args)

switch {
case args.Checkout != nil:
fmt.Printf("checkout requested for branch %s\n", args.Checkout.Branch)
case args.Commit != nil:
fmt.Printf("commit requested with message \"%s\"\n", args.Commit.Message)
case args.Push != nil:
fmt.Printf("push requested from %s to %s\n", args.Push.Branch, args.Push.Remote)
}

// output:
// commit requested with message "what-this-commit-is-about"
}
Loading

0 comments on commit 8baf704

Please sign in to comment.