aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIan Lewis <ianlewis@google.com>2019-05-09 01:05:03 +0900
committerAndrew Jackura <ajackura@google.com>2019-05-08 09:05:03 -0700
commit636abe8753b82e6eefa1beca9f46e49b470aa3d7 (patch)
treeea0df52969a988d1dc83687340a7698ce55d51ce
parentd47216cd17848d55a33e6f651cbe408243ed55b8 (diff)
downloadgo-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.go87
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
}
}