Skip to content
This repository was archived by the owner on Oct 17, 2021. It is now read-only.

Commit 471603a

Browse files
author
Nathan Potter
authored
Merge pull request #9 from cdr/pflag
Support POSIX flags
2 parents 481899a + 28a0ece commit 471603a

File tree

9 files changed

+88
-57
lines changed

9 files changed

+88
-57
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
vendor

README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ A minimal, command-oriented CLI package.
77
## Features
88

99
- Very small, simple API.
10-
- Uses standard `flag` package as much as possible.
11-
- No external dependencies.
10+
- Support for POSIX flags.
11+
- Only external dependency is [spf13/pflag](https://github.com/spf13/pflag).
1212
- Subcommands.
1313
- Auto-generated help.
1414

@@ -37,7 +37,7 @@ type cmd struct {
3737
verbose bool
3838
}
3939

40-
func (c *cmd) Run(fl *flag.FlagSet) {
40+
func (c *cmd) Run(fl *pflag.FlagSet) {
4141
if c.verbose {
4242
fmt.Println("verbose enabled")
4343
}
@@ -52,7 +52,7 @@ func (c *cmd) Spec() cli.CommandSpec {
5252
}
5353
}
5454

55-
func (c *cmd) RegisterFlags(fl *flag.FlagSet) {
55+
func (c *cmd) RegisterFlags(fl *pflag.FlagSet) {
5656
fl.BoolVar(&c.verbose, "v", false, "sets verbose mode")
5757
}
5858

@@ -87,7 +87,7 @@ import (
8787
type subcmd struct {
8888
}
8989

90-
func (c *subcmd) Run(fl *flag.FlagSet) {
90+
func (c *subcmd) Run(fl *pflag.FlagSet) {
9191
fmt.Println("subcommand invoked")
9292
}
9393

@@ -102,7 +102,7 @@ func (c *subcmd) Spec() cli.CommandSpec {
102102
type cmd struct {
103103
}
104104

105-
func (c *cmd) Run(fl *flag.FlagSet) {
105+
func (c *cmd) Run(fl *pflag.FlagSet) {
106106
// This root command has no default action, so print the help.
107107
fl.Usage()
108108
}

command.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
package cli
22

33
import (
4-
"flag"
54
"strings"
5+
6+
"github.com/spf13/pflag"
67
)
78

89
// CommandSpec describes a Command's usage.
@@ -38,7 +39,7 @@ type Command interface {
3839
// Spec returns metadata about the command.
3940
Spec() CommandSpec
4041
// Run invokes the command's main routine with parsed flags.
41-
Run(fl *flag.FlagSet)
42+
Run(fl *pflag.FlagSet)
4243
}
4344

4445
// ParentCommand is an optional interface for commands that have subcommands.
@@ -52,5 +53,5 @@ type ParentCommand interface {
5253
// FlaggedCommand is an optional interface for commands that have flags.
5354
type FlaggedCommand interface {
5455
// RegisterFlags lets the command register flags which be sent to Handle.
55-
RegisterFlags(fl *flag.FlagSet)
56+
RegisterFlags(fl *pflag.FlagSet)
5657
}

examples/simple/main.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
package main
22

33
import (
4-
"flag"
54
"fmt"
65

6+
"github.com/spf13/pflag"
77
"go.coder.com/cli"
88
)
99

1010
type cmd struct {
1111
verbose bool
1212
}
1313

14-
func (c *cmd) Run(fl *flag.FlagSet) {
14+
func (c *cmd) Run(fl *pflag.FlagSet) {
1515
if c.verbose {
1616
fmt.Println("verbose enabled")
1717
}
@@ -26,8 +26,8 @@ func (c *cmd) Spec() cli.CommandSpec {
2626
}
2727
}
2828

29-
func (c *cmd) RegisterFlags(fl *flag.FlagSet) {
30-
fl.BoolVar(&c.verbose, "v", false, "sets verbose mode")
29+
func (c *cmd) RegisterFlags(fl *pflag.FlagSet) {
30+
fl.BoolVarP(&c.verbose, "verbose", "v", false, "sets verbose mode")
3131
}
3232

3333
func main() {

examples/subcommands/main.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
package main
22

33
import (
4-
"flag"
54
"fmt"
65

6+
"github.com/spf13/pflag"
77
"go.coder.com/cli"
88
)
99

1010
type subcmd struct {
1111
}
1212

13-
func (c *subcmd) Run(fl *flag.FlagSet) {
13+
func (c *subcmd) Run(fl *pflag.FlagSet) {
1414
fmt.Println("subcommand invoked")
1515
}
1616

@@ -25,7 +25,7 @@ func (c *subcmd) Spec() cli.CommandSpec {
2525
type cmd struct {
2626
}
2727

28-
func (c *cmd) Run(fl *flag.FlagSet) {
28+
func (c *cmd) Run(fl *pflag.FlagSet) {
2929
// This root command has no default action, so print the help.
3030
fl.Usage()
3131
}

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
module go.coder.com/cli
22

33
go 1.12
4+
5+
require github.com/spf13/pflag v1.0.3

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
2+
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=

help.go

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
package cli
22

33
import (
4-
"flag"
54
"fmt"
65
"io"
76
"strings"
87
"text/tabwriter"
98
"unicode"
109
"unicode/utf8"
10+
11+
"github.com/spf13/pflag"
1112
)
1213

1314
func flagDashes(name string) string {
@@ -36,35 +37,26 @@ func fmtDefValue(value interface{}) string {
3637
// \xFF is used to escape a \t so tabwriter ignores it.
3738
const tabEscape = "\xFF"
3839

39-
func renderFlagHelp(fl *flag.FlagSet, w io.Writer) {
40-
var count int
41-
fl.VisitAll(func(f *flag.Flag) {
42-
if count == 0 {
43-
fmt.Fprintf(w, "\n%v flags:\n", fl.Name())
44-
}
45-
46-
count++
47-
if f.DefValue == "" {
48-
fmt.Fprintf(w, tabEscape+"\t%v%v\t%v\n", flagDashes(f.Name), f.Name, f.Usage)
49-
} else {
50-
fmt.Fprintf(w, tabEscape+"\t%v%v=%v\t%v\n", flagDashes(f.Name), f.Name, fmtDefValue(f.DefValue), f.Usage)
51-
}
52-
})
40+
func renderFlagHelp(fullName string, fl *pflag.FlagSet, w io.Writer) {
41+
if fl.HasFlags() {
42+
fmt.Fprintf(w, "\n%s flags:\n", fullName)
43+
fmt.Fprint(w, fl.FlagUsages())
44+
}
5345
}
5446

5547
// renderHelp generates a command's help page.
56-
func renderHelp(cmd Command, fl *flag.FlagSet, w io.Writer) {
48+
func renderHelp(fullName string, cmd Command, fl *pflag.FlagSet, w io.Writer) {
5749
// Render usage and description.
5850
fmt.Fprintf(w, "Usage: %v %v\n\n",
59-
fl.Name(), cmd.Spec().Usage,
51+
fullName, cmd.Spec().Usage,
6052
)
6153
fmt.Fprintf(w, "%v\n", cmd.Spec().Desc)
6254

6355
tw := tabwriter.NewWriter(w, 0, 4, 2, ' ', tabwriter.StripEscape)
6456
defer tw.Flush()
6557

6658
// Render flag help.
67-
renderFlagHelp(fl, tw)
59+
renderFlagHelp(fullName, fl, tw)
6860

6961
// Render subcommand summaries.
7062
pc, ok := cmd.(ParentCommand)

run.go

Lines changed: 56 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,36 @@
11
package cli
22

33
import (
4-
"flag"
54
"os"
5+
"strings"
6+
7+
"github.com/spf13/pflag"
68
)
79

810
func appendParent(parent string, add string) string {
9-
if parent == "" {
10-
return add + " "
11-
}
1211
return parent + add + " "
1312
}
1413

14+
// splitArgs tries to split the args between the parent command's flags/args, and the subcommand's
15+
// flags/args. If a subcommand is found, the parent args, subcommand args, and the subcommand will
16+
// be returned. If a subcommand isn't found, the args will be returned as is, the subcommand args
17+
// will be empty, and the subcommand will be nil.
18+
func splitArgs(subCmds []Command, args []string) (cmdArgs, subArgs []string, subCmd Command) {
19+
for i, arg := range args {
20+
if strings.HasPrefix(arg, "-") {
21+
continue
22+
}
23+
24+
for _, subCommand := range subCmds {
25+
if subCommand.Spec().Name == arg {
26+
return args[:i], args[i+1:], subCommand
27+
}
28+
}
29+
}
30+
31+
return args, []string{}, nil
32+
}
33+
1534
// Run sets up flags, helps, and executes the command with the provided
1635
// arguments.
1736
//
@@ -20,37 +39,51 @@ func appendParent(parent string, add string) string {
2039
//
2140
// Use RunRoot if this package is managing the entire CLI.
2241
func Run(cmd Command, args []string, parent string) {
23-
fl := flag.NewFlagSet(parent+""+cmd.Spec().Name, flag.ExitOnError)
42+
name := parent + cmd.Spec().Name
43+
fl := pflag.NewFlagSet(name, pflag.ContinueOnError)
44+
// Ensure pflag library doesn't print usage for us automatically,
45+
// we'll override this below.
46+
fl.Usage = func() {}
2447

2548
if fc, ok := cmd.(FlaggedCommand); ok {
2649
fc.RegisterFlags(fl)
2750
}
2851

29-
fl.Usage = func() {
30-
renderHelp(cmd, fl, os.Stderr)
31-
}
32-
3352
if cmd.Spec().RawArgs {
3453
// Use `--` to return immediately when parsing the flags.
3554
args = append([]string{"--"}, args...)
3655
}
37-
_ = fl.Parse(args)
3856

39-
subcommandArg := fl.Arg(0)
57+
var (
58+
cmdArgs, subArgs []string
59+
subCmd Command
60+
)
61+
pc, isParentCmd := cmd.(ParentCommand)
62+
if isParentCmd {
63+
cmdArgs, subArgs, subCmd = splitArgs(pc.Subcommands(), args)
64+
if subCmd != nil {
65+
args = cmdArgs
66+
}
67+
}
4068

41-
// Route to subcommand.
42-
if pc, ok := cmd.(ParentCommand); ok && subcommandArg != "" {
43-
for _, subcommand := range pc.Subcommands() {
44-
if subcommand.Spec().Name != subcommandArg {
45-
continue
46-
}
69+
err := fl.Parse(args)
70+
// Reassign the usage now that we've parsed the args
71+
// so that we can render it manually.
72+
fl.Usage = func() {
73+
renderHelp(name, cmd, fl, os.Stderr)
74+
}
75+
if err != nil {
76+
fl.Usage()
77+
os.Exit(2)
78+
}
4779

48-
Run(
49-
subcommand, fl.Args()[1:],
50-
appendParent(parent, cmd.Spec().Name),
51-
)
52-
return
53-
}
80+
// Route to subcommand.
81+
if isParentCmd && subCmd != nil {
82+
Run(
83+
subCmd, subArgs,
84+
appendParent(parent, cmd.Spec().Name),
85+
)
86+
return
5487
}
5588

5689
cmd.Run(fl)

0 commit comments

Comments
 (0)