Skip to content

Commit bf7d1f2

Browse files
authored
Add flags support (#39)
1 parent e0b9131 commit bf7d1f2

File tree

3 files changed

+85
-42
lines changed

3 files changed

+85
-42
lines changed

GUIDE.md

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -64,40 +64,49 @@ func run() error {
6464
There is no special methods, config fields to propagate flags to subcommands. However it's not hard to make this, because every command can access predefined flags, which are shared across handlers.
6565

6666
```go
67-
type commonFlags struct {
67+
// generalFlags can be used as flags for all command
68+
type generalFlags struct {
6869
IsVerbose bool
70+
Dir string
6971
}
7072

71-
// NOTE: should be added before flag.FlagSet method Parse().
72-
func withCommonFlags(fs *flag.FlagSet) *commonFlags {
73-
c := &commonFlags{}
73+
func (c *generalFlags) Flags() *flag.FlagSet {
74+
fs := flag.NewFlagSet("", flag.ContinueOnError)
7475
fs.BoolVar(&c.IsVerbose, "verbose", false, "should app be verbose")
75-
return c
76+
fs.StringVar(&c.Dir, "dir", ".", "directory to process")
77+
return fs
7678
}
7779

78-
func cmdFoo(ctx context.Context, args []string) error {
79-
fs := flag.NewFlagSet("foo", flag.ContinueOnError)
80-
// NOTE: here add flags for cmdBar as always
80+
// commandFlags is a flags for a command
81+
// using struct embedding we can inherit other flags
82+
type commandFlags struct {
83+
generalFlags
84+
File string
85+
}
86+
87+
func (c *commandFlags) Flags() *flag.FlagSet {
88+
fs := c.generalFlags.Flags()
89+
fs.StringVar(&c.File, "file", "input.txt", "file to process")
90+
return fs
91+
}
8192

82-
// add common flags, make sure it's before Parse but after all defined flags
83-
common := withCommonFlags(fs)
84-
if err := fs.Parse(args); err != nil {
93+
func cmdFoo(ctx context.Context, args []string) error {
94+
var cfg generalFlags
95+
if fs := cfg.Flags().Parse(args); err != nil{
8596
return err
8697
}
87-
// use commonFlags fields or any other flags that you have defined
98+
99+
// use cfg fields or any other flags that you have defined
88100
return nil
89101
}
90102

91103
func cmdBar(ctx context.Context, args []string) error {
92-
fs := flag.NewFlagSet("bar", flag.ContinueOnError)
93-
// NOTE: here add flags for cmdFoo as always
94-
95-
// add common flags, make sure it's before Parse but after all defined flags
96-
common := withCommonFlags(fs)
97-
if err := fs.Parse(args); err != nil {
104+
var cfg commandFlags
105+
if fs := cfg.Flags().Parse(args); err != nil{
98106
return err
99107
}
100-
// use commonFlags fields or any other flags that you have defined
108+
109+
// use cfg fields or any other flags that you have defined
101110
return nil
102111
}
103112
```

acmd.go

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package acmd
33
import (
44
"context"
55
"errors"
6+
"flag"
67
"fmt"
78
"io"
89
"os"
@@ -53,6 +54,15 @@ type Command struct {
5354

5455
// IsHidden reports whether command should not be show in help. Default false.
5556
IsHidden bool
57+
58+
// FlagSet is an optional field where you can provide command's flags.
59+
// Is used for autocomplete. Works best with https://github.com/cristalhq/flagx
60+
FlagSet FlagSetter
61+
}
62+
63+
// FlagSetter returns flags for the command. See examples.
64+
type FlagSetter interface {
65+
Flags() *flag.FlagSet
5666
}
5767

5868
// simple way to get exec function
@@ -101,6 +111,9 @@ type Config struct {
101111

102112
// Usage of the application, if nil default will be used.
103113
Usage func(cfg Config, cmds []Command)
114+
115+
// VerboseHelp if "./app help -v" is passed, default is false.
116+
VerboseHelp bool
104117
}
105118

106119
// HasHelpFlag reports whether help flag is presented in args.
@@ -150,7 +163,7 @@ func (r *Runner) init() error {
150163
}
151164

152165
if r.cfg.Usage == nil {
153-
r.cfg.Usage = defaultUsage(r.cfg.Output)
166+
r.cfg.Usage = defaultUsage(r)
154167
}
155168

156169
r.args = r.cfg.Args
@@ -344,14 +357,15 @@ func suggestCommand(got string, cmds []Command) string {
344357
return match
345358
}
346359

347-
func defaultUsage(w io.Writer) func(cfg Config, cmds []Command) {
360+
func defaultUsage(r *Runner) func(cfg Config, cmds []Command) {
348361
return func(cfg Config, cmds []Command) {
362+
w := r.cfg.Output
349363
if cfg.AppDescription != "" {
350364
fmt.Fprintf(w, "%s\n\n", cfg.AppDescription)
351365
}
352366

353367
fmt.Fprintf(w, "Usage:\n\n %s <command> [arguments...]\n\nThe commands are:\n\n", cfg.AppName)
354-
printCommands(w, cmds)
368+
printCommands(r.cfg, cmds)
355369

356370
if cfg.PostDescription != "" {
357371
fmt.Fprintf(w, "%s\n\n", cfg.PostDescription)
@@ -363,18 +377,28 @@ func defaultUsage(w io.Writer) func(cfg Config, cmds []Command) {
363377
}
364378

365379
// printCommands in a table form (Name and Description)
366-
func printCommands(w io.Writer, cmds []Command) {
380+
func printCommands(cfg Config, cmds []Command) {
367381
minwidth, tabwidth, padding, padchar, flags := 0, 0, 11, byte(' '), uint(0)
368-
tw := tabwriter.NewWriter(w, minwidth, tabwidth, padding, padchar, flags)
382+
tw := tabwriter.NewWriter(cfg.Output, minwidth, tabwidth, padding, padchar, flags)
369383
for _, cmd := range cmds {
370384
if cmd.IsHidden {
371385
continue
372386
}
387+
373388
desc := cmd.Description
374389
if desc == "" {
375390
desc = "<no description>"
376391
}
377392
fmt.Fprintf(tw, " %s\t%s\n", cmd.Name, desc)
393+
394+
if cfg.VerboseHelp && cmd.FlagSet != nil {
395+
fset := cmd.FlagSet.Flags()
396+
old := fset.Output()
397+
fmt.Fprintf(tw, " ")
398+
fset.SetOutput(tw)
399+
fset.Usage()
400+
fset.SetOutput(old)
401+
}
378402
}
379403
fmt.Fprint(tw, "\n")
380404
tw.Flush()

example_test.go

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ func ExampleRunner() {
4242
}
4343
return nil
4444
},
45+
FlagSet: &commandFlags{},
4546
},
4647
{
4748
Name: "status",
@@ -97,6 +98,7 @@ func ExampleHelp() {
9798
{
9899
Name: "boom",
99100
ExecFunc: nopFunc,
101+
FlagSet: &generalFlags{},
100102
},
101103
}
102104

@@ -113,7 +115,8 @@ func ExampleHelp() {
113115
panic(err)
114116
}
115117

116-
// Output: Example of acmd package
118+
// Output:
119+
// Example of acmd package
117120
//
118121
// Usage:
119122
//
@@ -308,27 +311,24 @@ func ExamplePropagateFlags() {
308311
cmds := []acmd.Command{
309312
{
310313
Name: "foo", ExecFunc: func(ctx context.Context, args []string) error {
311-
fs := flag.NewFlagSet("foo", flag.ContinueOnError)
312-
isRecursive := fs.Bool("r", false, "should file list be recursive")
313-
common := withCommonFlags(fs)
314-
if err := fs.Parse(args); err != nil {
314+
var cfg generalFlags
315+
if err := cfg.Flags().Parse(args); err != nil {
315316
return err
316317
}
317-
if common.IsVerbose {
318-
fmt.Fprintf(buf, "TODO: dir %q, is recursive = %v\n", common.Dir, *isRecursive)
318+
if cfg.IsVerbose {
319+
fmt.Fprintf(buf, "TODO: dir %q, is verbose = %v\n", cfg.Dir, cfg.IsVerbose)
319320
}
320321
return nil
321322
},
322323
},
323324
{
324325
Name: "bar", ExecFunc: func(ctx context.Context, args []string) error {
325-
fs := flag.NewFlagSet("bar", flag.ContinueOnError)
326-
common := withCommonFlags(fs)
327-
if err := fs.Parse(args); err != nil {
326+
var cfg commandFlags
327+
if err := cfg.Flags().Parse(args); err != nil {
328328
return err
329329
}
330-
if common.IsVerbose {
331-
fmt.Fprintf(buf, "TODO: dir %q\n", common.Dir)
330+
if cfg.IsVerbose {
331+
fmt.Fprintf(buf, "TODO: dir %q\n", cfg.Dir)
332332
}
333333
return nil
334334
},
@@ -348,18 +348,28 @@ func ExamplePropagateFlags() {
348348
}
349349
fmt.Println(buf.String())
350350

351-
// Output: TODO: dir "test-dir", is recursive = false
351+
// Output: TODO: dir "test-dir", is verbose = true
352352
}
353353

354-
type commonFlags struct {
354+
type generalFlags struct {
355355
IsVerbose bool
356356
Dir string
357357
}
358358

359-
// NOTE: should be added before flag.FlagSet method Parse().
360-
func withCommonFlags(fs *flag.FlagSet) *commonFlags {
361-
c := &commonFlags{}
359+
func (c *generalFlags) Flags() *flag.FlagSet {
360+
fs := flag.NewFlagSet("", flag.ContinueOnError)
362361
fs.BoolVar(&c.IsVerbose, "verbose", false, "should app be verbose")
363362
fs.StringVar(&c.Dir, "dir", ".", "directory to process")
364-
return c
363+
return fs
364+
}
365+
366+
type commandFlags struct {
367+
generalFlags
368+
File string
369+
}
370+
371+
func (c *commandFlags) Flags() *flag.FlagSet {
372+
fs := c.generalFlags.Flags()
373+
fs.StringVar(&c.File, "file", "input.txt", "file to process")
374+
return fs
365375
}

0 commit comments

Comments
 (0)