diff options
author | Ian Lewis <ianlewis@google.com> | 2019-05-09 01:05:03 +0900 |
---|---|---|
committer | Andrew Jackura <ajackura@google.com> | 2019-05-08 09:05:03 -0700 |
commit | 636abe8753b82e6eefa1beca9f46e49b470aa3d7 (patch) | |
tree | ea0df52969a988d1dc83687340a7698ce55d51ce | |
parent | d47216cd17848d55a33e6f651cbe408243ed55b8 (diff) | |
download | go-subcommands-636abe8753b82e6eefa1beca9f46e49b470aa3d7.tar.gz |
Allow users to override default help behavior. (#23)
- CommandGroup is now public.
- Commander and CommandGroup name is accessible by Name()
- Commander, CommandGroup, and Command explations are now printed by
overridable functions set on the Commander.
- Subcommands are visitable by the VisitCommands() method.
- Command groups are visitable by the VisitGroups() method.
- Important flags are visitable by the VisitAllImportant() method.
- Top level flags are visitable by the VisitAll() method.
-rw-r--r-- | subcommands.go | 87 |
1 files changed, 74 insertions, 13 deletions
diff --git a/subcommands.go b/subcommands.go index 9cb98e5..3997d51 100644 --- a/subcommands.go +++ b/subcommands.go @@ -50,21 +50,30 @@ type Command interface { // A Commander represents a set of commands. type Commander struct { - commands []*commandGroup + commands []*CommandGroup topFlags *flag.FlagSet // top-level flags important []string // important top-level flags name string // normally path.Base(os.Args[0]) + Explain func(io.Writer) // A function to print a top level usage explanation. Can be overridden. + ExplainGroup func(io.Writer, *CommandGroup) // A function to print a command group's usage explanation. Can be overridden. + ExplainCommand func(io.Writer, Command) // A function to print a command usage explanation. Can be overridden. + Output io.Writer // Output specifies where the commander should write its output (default: os.Stdout). Error io.Writer // Error specifies where the commander should write its error (default: os.Stderr). } // A commandGroup represents a set of commands about a common topic. -type commandGroup struct { +type CommandGroup struct { name string commands []Command } +// Name returns the group name +func (g *CommandGroup) Name() string { + return g.name +} + // An ExitStatus represents a Posix exit status that a subcommand // expects to be returned to the shell. type ExitStatus int @@ -85,10 +94,19 @@ func NewCommander(topLevelFlags *flag.FlagSet, name string) *Commander { Output: os.Stdout, Error: os.Stderr, } - topLevelFlags.Usage = func() { cdr.explain(cdr.Error) } + + cdr.Explain = cdr.explain + cdr.ExplainGroup = explainGroup + cdr.ExplainCommand = explain + topLevelFlags.Usage = func() { cdr.Explain(cdr.Error) } return cdr } +// Name returns the commander's name +func (cdr *Commander) Name() string { + return cdr.name +} + // Register adds a subcommand to the supported subcommands in the // specified group. (Help output is sorted and arranged by group name.) // The empty string is an acceptable group name; such subcommands are @@ -100,7 +118,7 @@ func (cdr *Commander) Register(cmd Command, group string) { return } } - cdr.commands = append(cdr.commands, &commandGroup{ + cdr.commands = append(cdr.commands, &CommandGroup{ name: group, commands: []Command{cmd}, }) @@ -114,6 +132,46 @@ func (cdr *Commander) ImportantFlag(name string) { cdr.important = append(cdr.important, name) } +// VisitGroups visits each command group in lexicographical order, calling +// fn for each. +func (cdr *Commander) VisitGroups(fn func(*CommandGroup)) { + sort.Sort(byGroupName(cdr.commands)) + for _, g := range cdr.commands { + fn(g) + } +} + +// VisitCommands visits each command in registered order grouped by +// command group in lexicographical order, calling fn for each. +func (cdr *Commander) VisitCommands(fn func(*CommandGroup, Command)) { + cdr.VisitGroups(func(g *CommandGroup) { + for _, cmd := range g.commands { + fn(g, cmd) + } + }) +} + +// VisitAllImportant visits the important top level flags in lexicographical +// order, calling fn for each. It visits all flags, even those not set. +func (cdr *Commander) VisitAllImportant(fn func(*flag.Flag)) { + sort.Strings(cdr.important) + for _, name := range cdr.important { + f := cdr.topFlags.Lookup(name) + if f == nil { + panic(fmt.Sprintf("Important flag (%s) is not defined", name)) + } + fn(f) + } +} + +// VisitAll visits the top level flags in lexicographical order, calling fn +// for each. It visits all flags, even those not set. +func (cdr *Commander) VisitAll(fn func(*flag.Flag)) { + if cdr.topFlags != nil { + cdr.topFlags.VisitAll(fn) + } +} + // Execute should be called once the top-level-flags on a Commander // have been initialized. It finds the correct subcommand and executes // it, and returns an ExitStatus with the result. On a usage error, an @@ -134,7 +192,7 @@ func (cdr *Commander) Execute(ctx context.Context, args ...interface{}) ExitStat continue } f := flag.NewFlagSet(name, flag.ContinueOnError) - f.Usage = func() { explain(cdr.Error, cmd) } + f.Usage = func() { cdr.ExplainCommand(cdr.Error, cmd) } cmd.SetFlags(f) if f.Parse(cdr.topFlags.Args()[1:]) != nil { return ExitUsageError @@ -149,8 +207,9 @@ func (cdr *Commander) Execute(ctx context.Context, args ...interface{}) ExitStat } // Sorting of a slice of command groups. -type byGroupName []*commandGroup +type byGroupName []*CommandGroup +// TODO Sort by function rather than implement sortable? func (p byGroupName) Len() int { return len(p) } func (p byGroupName) Less(i, j int) bool { return p[i].name < p[j].name } func (p byGroupName) Swap(i, j int) { p[i], p[j] = p[j], p[i] } @@ -161,12 +220,14 @@ func (cdr *Commander) explain(w io.Writer) { fmt.Fprintf(w, "Usage: %s <flags> <subcommand> <subcommand args>\n\n", cdr.name) sort.Sort(byGroupName(cdr.commands)) for _, group := range cdr.commands { - explainGroup(w, group) + cdr.ExplainGroup(w, group) } if cdr.topFlags == nil { fmt.Fprintln(w, "\nNo top level flags.") return } + + sort.Strings(cdr.important) if len(cdr.important) == 0 { fmt.Fprintf(w, "\nUse \"%s flags\" for a list of top-level flags\n", cdr.name) return @@ -183,12 +244,12 @@ func (cdr *Commander) explain(w io.Writer) { } // Sorting of the commands within a group. -func (g commandGroup) Len() int { return len(g.commands) } -func (g commandGroup) Less(i, j int) bool { return g.commands[i].Name() < g.commands[j].Name() } -func (g commandGroup) Swap(i, j int) { g.commands[i], g.commands[j] = g.commands[j], g.commands[i] } +func (g CommandGroup) Len() int { return len(g.commands) } +func (g CommandGroup) Less(i, j int) bool { return g.commands[i].Name() < g.commands[j].Name() } +func (g CommandGroup) Swap(i, j int) { g.commands[i], g.commands[j] = g.commands[j], g.commands[i] } // explainGroup explains all the subcommands for a particular group. -func explainGroup(w io.Writer, group *commandGroup) { +func explainGroup(w io.Writer, group *CommandGroup) { if len(group.commands) == 0 { return } @@ -254,7 +315,7 @@ func (h *helper) Usage() string { func (h *helper) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) ExitStatus { switch f.NArg() { case 0: - (*Commander)(h).explain(h.Output) + (*Commander)(h).Explain(h.Output) return ExitSuccess case 1: @@ -263,7 +324,7 @@ func (h *helper) Execute(_ context.Context, f *flag.FlagSet, args ...interface{} if f.Arg(0) != cmd.Name() { continue } - explain(h.Output, cmd) + (*Commander)(h).ExplainCommand(h.Output, cmd) return ExitSuccess } } |