Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add flags support #39

Merged
merged 3 commits into from Sep 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
47 changes: 28 additions & 19 deletions GUIDE.md
Expand Up @@ -64,40 +64,49 @@ func run() error {
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.

```go
type commonFlags struct {
// generalFlags can be used as flags for all command
type generalFlags struct {
IsVerbose bool
Dir string
}

// NOTE: should be added before flag.FlagSet method Parse().
func withCommonFlags(fs *flag.FlagSet) *commonFlags {
c := &commonFlags{}
func (c *generalFlags) Flags() *flag.FlagSet {
fs := flag.NewFlagSet("", flag.ContinueOnError)
fs.BoolVar(&c.IsVerbose, "verbose", false, "should app be verbose")
return c
fs.StringVar(&c.Dir, "dir", ".", "directory to process")
return fs
}

func cmdFoo(ctx context.Context, args []string) error {
fs := flag.NewFlagSet("foo", flag.ContinueOnError)
// NOTE: here add flags for cmdBar as always
// commandFlags is a flags for a command
// using struct embedding we can inherit other flags
type commandFlags struct {
generalFlags
File string
}

func (c *commandFlags) Flags() *flag.FlagSet {
fs := c.generalFlags.Flags()
fs.StringVar(&c.File, "file", "input.txt", "file to process")
return fs
}

// add common flags, make sure it's before Parse but after all defined flags
common := withCommonFlags(fs)
if err := fs.Parse(args); err != nil {
func cmdFoo(ctx context.Context, args []string) error {
var cfg generalFlags
if fs := cfg.Flags().Parse(args); err != nil{
return err
}
// use commonFlags fields or any other flags that you have defined

// use cfg fields or any other flags that you have defined
return nil
}

func cmdBar(ctx context.Context, args []string) error {
fs := flag.NewFlagSet("bar", flag.ContinueOnError)
// NOTE: here add flags for cmdFoo as always

// add common flags, make sure it's before Parse but after all defined flags
common := withCommonFlags(fs)
if err := fs.Parse(args); err != nil {
var cfg commandFlags
if fs := cfg.Flags().Parse(args); err != nil{
return err
}
// use commonFlags fields or any other flags that you have defined

// use cfg fields or any other flags that you have defined
return nil
}
```
Expand Down
34 changes: 29 additions & 5 deletions acmd.go
Expand Up @@ -3,6 +3,7 @@ package acmd
import (
"context"
"errors"
"flag"
"fmt"
"io"
"os"
Expand Down Expand Up @@ -53,6 +54,15 @@ type Command struct {

// IsHidden reports whether command should not be show in help. Default false.
IsHidden bool

// FlagSet is an optional field where you can provide command's flags.
// Is used for autocomplete. Works best with https://github.com/cristalhq/flagx
FlagSet FlagSetter
}

// FlagSetter returns flags for the command. See examples.
type FlagSetter interface {
Flags() *flag.FlagSet
}

// simple way to get exec function
Expand Down Expand Up @@ -101,6 +111,9 @@ type Config struct {

// Usage of the application, if nil default will be used.
Usage func(cfg Config, cmds []Command)

// VerboseHelp if "./app help -v" is passed, default is false.
VerboseHelp bool
}

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

if r.cfg.Usage == nil {
r.cfg.Usage = defaultUsage(r.cfg.Output)
r.cfg.Usage = defaultUsage(r)
}

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

func defaultUsage(w io.Writer) func(cfg Config, cmds []Command) {
func defaultUsage(r *Runner) func(cfg Config, cmds []Command) {
return func(cfg Config, cmds []Command) {
w := r.cfg.Output
if cfg.AppDescription != "" {
fmt.Fprintf(w, "%s\n\n", cfg.AppDescription)
}

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

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

// printCommands in a table form (Name and Description)
func printCommands(w io.Writer, cmds []Command) {
func printCommands(cfg Config, cmds []Command) {
minwidth, tabwidth, padding, padchar, flags := 0, 0, 11, byte(' '), uint(0)
tw := tabwriter.NewWriter(w, minwidth, tabwidth, padding, padchar, flags)
tw := tabwriter.NewWriter(cfg.Output, minwidth, tabwidth, padding, padchar, flags)
for _, cmd := range cmds {
if cmd.IsHidden {
continue
}

desc := cmd.Description
if desc == "" {
desc = "<no description>"
}
fmt.Fprintf(tw, " %s\t%s\n", cmd.Name, desc)

if cfg.VerboseHelp && cmd.FlagSet != nil {
fset := cmd.FlagSet.Flags()
old := fset.Output()
fmt.Fprintf(tw, " ")
fset.SetOutput(tw)
fset.Usage()
fset.SetOutput(old)
}
}
fmt.Fprint(tw, "\n")
tw.Flush()
Expand Down
46 changes: 28 additions & 18 deletions example_test.go
Expand Up @@ -42,6 +42,7 @@ func ExampleRunner() {
}
return nil
},
FlagSet: &commandFlags{},
},
{
Name: "status",
Expand Down Expand Up @@ -97,6 +98,7 @@ func ExampleHelp() {
{
Name: "boom",
ExecFunc: nopFunc,
FlagSet: &generalFlags{},
},
}

Expand All @@ -113,7 +115,8 @@ func ExampleHelp() {
panic(err)
}

// Output: Example of acmd package
// Output:
// Example of acmd package
//
// Usage:
//
Expand Down Expand Up @@ -308,27 +311,24 @@ func ExamplePropagateFlags() {
cmds := []acmd.Command{
{
Name: "foo", ExecFunc: func(ctx context.Context, args []string) error {
fs := flag.NewFlagSet("foo", flag.ContinueOnError)
isRecursive := fs.Bool("r", false, "should file list be recursive")
common := withCommonFlags(fs)
if err := fs.Parse(args); err != nil {
var cfg generalFlags
if err := cfg.Flags().Parse(args); err != nil {
return err
}
if common.IsVerbose {
fmt.Fprintf(buf, "TODO: dir %q, is recursive = %v\n", common.Dir, *isRecursive)
if cfg.IsVerbose {
fmt.Fprintf(buf, "TODO: dir %q, is verbose = %v\n", cfg.Dir, cfg.IsVerbose)
}
return nil
},
},
{
Name: "bar", ExecFunc: func(ctx context.Context, args []string) error {
fs := flag.NewFlagSet("bar", flag.ContinueOnError)
common := withCommonFlags(fs)
if err := fs.Parse(args); err != nil {
var cfg commandFlags
if err := cfg.Flags().Parse(args); err != nil {
return err
}
if common.IsVerbose {
fmt.Fprintf(buf, "TODO: dir %q\n", common.Dir)
if cfg.IsVerbose {
fmt.Fprintf(buf, "TODO: dir %q\n", cfg.Dir)
}
return nil
},
Expand All @@ -348,18 +348,28 @@ func ExamplePropagateFlags() {
}
fmt.Println(buf.String())

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

type commonFlags struct {
type generalFlags struct {
IsVerbose bool
Dir string
}

// NOTE: should be added before flag.FlagSet method Parse().
func withCommonFlags(fs *flag.FlagSet) *commonFlags {
c := &commonFlags{}
func (c *generalFlags) Flags() *flag.FlagSet {
fs := flag.NewFlagSet("", flag.ContinueOnError)
fs.BoolVar(&c.IsVerbose, "verbose", false, "should app be verbose")
fs.StringVar(&c.Dir, "dir", ".", "directory to process")
return c
return fs
}

type commandFlags struct {
generalFlags
File string
}

func (c *commandFlags) Flags() *flag.FlagSet {
fs := c.generalFlags.Flags()
fs.StringVar(&c.File, "file", "input.txt", "file to process")
return fs
}