513 lines
15 KiB
Go
513 lines
15 KiB
Go
/*
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
// Package subcommands implements a simple way for a single command to have many
|
|
// subcommands, each of which takes arguments and so forth.
|
|
package subcommands
|
|
|
|
import (
|
|
"context"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
// A Command represents a single command.
|
|
type Command interface {
|
|
// Name returns the name of the command.
|
|
Name() string
|
|
|
|
// Synopsis returns a short string (less than one line) describing the command.
|
|
Synopsis() string
|
|
|
|
// Usage returns a long string explaining the command and giving usage
|
|
// information.
|
|
Usage() string
|
|
|
|
// SetFlags adds the flags for this command to the specified set.
|
|
SetFlags(*flag.FlagSet)
|
|
|
|
// Execute executes the command and returns an ExitStatus.
|
|
Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) ExitStatus
|
|
}
|
|
|
|
// A Commander represents a set of commands.
|
|
type Commander struct {
|
|
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 {
|
|
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
|
|
|
|
const (
|
|
ExitSuccess ExitStatus = iota
|
|
ExitFailure
|
|
ExitUsageError
|
|
)
|
|
|
|
// NewCommander returns a new commander with the specified top-level
|
|
// flags and command name. The Usage function for the topLevelFlags
|
|
// will be set as well.
|
|
func NewCommander(topLevelFlags *flag.FlagSet, name string) *Commander {
|
|
cdr := &Commander{
|
|
topFlags: topLevelFlags,
|
|
name: name,
|
|
Output: os.Stdout,
|
|
Error: os.Stderr,
|
|
}
|
|
|
|
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
|
|
// explained first before named groups.
|
|
func (cdr *Commander) Register(cmd Command, group string) {
|
|
for _, g := range cdr.commands {
|
|
if g.name == group {
|
|
g.commands = append(g.commands, cmd)
|
|
return
|
|
}
|
|
}
|
|
cdr.commands = append(cdr.commands, &CommandGroup{
|
|
name: group,
|
|
commands: []Command{cmd},
|
|
})
|
|
}
|
|
|
|
// ImportantFlag marks a top-level flag as important, which means it
|
|
// will be printed out as part of the output of an ordinary "help"
|
|
// subcommand. (All flags, important or not, are printed by the
|
|
// "flags" subcommand.)
|
|
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)
|
|
}
|
|
}
|
|
|
|
// countFlags returns the number of top-level flags defined, even those not set.
|
|
func (cdr *Commander) countTopFlags() int {
|
|
count := 0
|
|
cdr.VisitAll(func(*flag.Flag) {
|
|
count++
|
|
})
|
|
return count
|
|
}
|
|
|
|
// 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
|
|
// appropriate message is printed to os.Stderr, and ExitUsageError is
|
|
// returned. The additional args are provided as-is to the Execute method
|
|
// of the selected Command.
|
|
func (cdr *Commander) Execute(ctx context.Context, args ...interface{}) ExitStatus {
|
|
if cdr.topFlags.NArg() < 1 {
|
|
cdr.topFlags.Usage()
|
|
return ExitUsageError
|
|
}
|
|
|
|
name := cdr.topFlags.Arg(0)
|
|
|
|
for _, group := range cdr.commands {
|
|
for _, cmd := range group.commands {
|
|
if name != cmd.Name() {
|
|
continue
|
|
}
|
|
f := flag.NewFlagSet(name, flag.ContinueOnError)
|
|
f.Usage = func() { cdr.ExplainCommand(cdr.Error, cmd) }
|
|
cmd.SetFlags(f)
|
|
if f.Parse(cdr.topFlags.Args()[1:]) != nil {
|
|
return ExitUsageError
|
|
}
|
|
return cmd.Execute(ctx, f, args...)
|
|
}
|
|
}
|
|
|
|
// Cannot find this command.
|
|
cdr.topFlags.Usage()
|
|
return ExitUsageError
|
|
}
|
|
|
|
// Sorting of a slice of command groups.
|
|
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] }
|
|
|
|
// explain prints a brief description of all the subcommands and the
|
|
// important top-level flags.
|
|
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 {
|
|
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 {
|
|
if cdr.countTopFlags() > 0 {
|
|
fmt.Fprintf(w, "\nUse \"%s flags\" for a list of top-level flags\n", cdr.name)
|
|
}
|
|
return
|
|
}
|
|
|
|
fmt.Fprintf(w, "\nTop-level flags (use \"%s flags\" for a full list):\n", cdr.name)
|
|
for _, name := range cdr.important {
|
|
f := cdr.topFlags.Lookup(name)
|
|
if f == nil {
|
|
panic(fmt.Sprintf("Important flag (%s) is not defined", name))
|
|
}
|
|
fmt.Fprintf(w, " -%s=%s: %s\n", f.Name, f.DefValue, f.Usage)
|
|
}
|
|
}
|
|
|
|
// 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] }
|
|
|
|
// explainGroup explains all the subcommands for a particular group.
|
|
func explainGroup(w io.Writer, group *CommandGroup) {
|
|
if len(group.commands) == 0 {
|
|
return
|
|
}
|
|
if group.name == "" {
|
|
fmt.Fprintf(w, "Subcommands:\n")
|
|
} else {
|
|
fmt.Fprintf(w, "Subcommands for %s:\n", group.name)
|
|
}
|
|
sort.Sort(group)
|
|
|
|
aliases := make(map[string][]string)
|
|
for _, cmd := range group.commands {
|
|
if alias, ok := cmd.(*aliaser); ok {
|
|
root := dealias(alias).Name()
|
|
|
|
if _, ok := aliases[root]; !ok {
|
|
aliases[root] = []string{}
|
|
}
|
|
aliases[root] = append(aliases[root], alias.Name())
|
|
}
|
|
}
|
|
|
|
for _, cmd := range group.commands {
|
|
if _, ok := cmd.(*aliaser); ok {
|
|
continue
|
|
}
|
|
|
|
name := cmd.Name()
|
|
names := []string{name}
|
|
|
|
if a, ok := aliases[name]; ok {
|
|
names = append(names, a...)
|
|
}
|
|
|
|
fmt.Fprintf(w, "\t%-15s %s\n", strings.Join(names, ", "), cmd.Synopsis())
|
|
}
|
|
fmt.Fprintln(w)
|
|
}
|
|
|
|
// explainCmd prints a brief description of a single command.
|
|
func explain(w io.Writer, cmd Command) {
|
|
fmt.Fprintf(w, "%s", cmd.Usage())
|
|
subflags := flag.NewFlagSet(cmd.Name(), flag.PanicOnError)
|
|
subflags.SetOutput(w)
|
|
cmd.SetFlags(subflags)
|
|
subflags.PrintDefaults()
|
|
}
|
|
|
|
// A helper is a Command implementing a "help" command for
|
|
// a given Commander.
|
|
type helper Commander
|
|
|
|
func (h *helper) Name() string { return "help" }
|
|
func (h *helper) Synopsis() string { return "describe subcommands and their syntax" }
|
|
func (h *helper) SetFlags(*flag.FlagSet) {}
|
|
func (h *helper) Usage() string {
|
|
return `help [<subcommand>]:
|
|
With an argument, prints detailed information on the use of
|
|
the specified subcommand. With no argument, print a list of
|
|
all commands and a brief description of each.
|
|
`
|
|
}
|
|
func (h *helper) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) ExitStatus {
|
|
switch f.NArg() {
|
|
case 0:
|
|
(*Commander)(h).Explain(h.Output)
|
|
return ExitSuccess
|
|
|
|
case 1:
|
|
for _, group := range h.commands {
|
|
for _, cmd := range group.commands {
|
|
if f.Arg(0) != cmd.Name() {
|
|
continue
|
|
}
|
|
(*Commander)(h).ExplainCommand(h.Output, cmd)
|
|
return ExitSuccess
|
|
}
|
|
}
|
|
fmt.Fprintf(h.Error, "Subcommand %s not understood\n", f.Arg(0))
|
|
}
|
|
|
|
f.Usage()
|
|
return ExitUsageError
|
|
}
|
|
|
|
// HelpCommand returns a Command which implements a "help" subcommand.
|
|
func (cdr *Commander) HelpCommand() Command {
|
|
return (*helper)(cdr)
|
|
}
|
|
|
|
// A flagger is a Command implementing a "flags" command for a given Commander.
|
|
type flagger Commander
|
|
|
|
func (flg *flagger) Name() string { return "flags" }
|
|
func (flg *flagger) Synopsis() string { return "describe all known top-level flags" }
|
|
func (flg *flagger) SetFlags(*flag.FlagSet) {}
|
|
func (flg *flagger) Usage() string {
|
|
return `flags [<subcommand>]:
|
|
With an argument, print all flags of <subcommand>. Else,
|
|
print a description of all known top-level flags. (The basic
|
|
help information only discusses the most generally important
|
|
top-level flags.)
|
|
`
|
|
}
|
|
func (flg *flagger) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) ExitStatus {
|
|
if f.NArg() > 1 {
|
|
f.Usage()
|
|
return ExitUsageError
|
|
}
|
|
|
|
if f.NArg() == 0 {
|
|
if flg.topFlags == nil {
|
|
fmt.Fprintln(flg.Output, "No top-level flags are defined.")
|
|
} else {
|
|
flg.topFlags.PrintDefaults()
|
|
}
|
|
return ExitSuccess
|
|
}
|
|
|
|
for _, group := range flg.commands {
|
|
for _, cmd := range group.commands {
|
|
if f.Arg(0) != cmd.Name() {
|
|
continue
|
|
}
|
|
subflags := flag.NewFlagSet(cmd.Name(), flag.PanicOnError)
|
|
subflags.SetOutput(flg.Output)
|
|
cmd.SetFlags(subflags)
|
|
subflags.PrintDefaults()
|
|
return ExitSuccess
|
|
}
|
|
}
|
|
fmt.Fprintf(flg.Error, "Subcommand %s not understood\n", f.Arg(0))
|
|
return ExitFailure
|
|
}
|
|
|
|
// FlagsCommand returns a Command which implements a "flags" subcommand.
|
|
func (cdr *Commander) FlagsCommand() Command {
|
|
return (*flagger)(cdr)
|
|
}
|
|
|
|
// A lister is a Command implementing a "commands" command for a given Commander.
|
|
type lister Commander
|
|
|
|
func (l *lister) Name() string { return "commands" }
|
|
func (l *lister) Synopsis() string { return "list all command names" }
|
|
func (l *lister) SetFlags(*flag.FlagSet) {}
|
|
func (l *lister) Usage() string {
|
|
return `commands:
|
|
Print a list of all commands.
|
|
`
|
|
}
|
|
func (l *lister) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) ExitStatus {
|
|
if f.NArg() != 0 {
|
|
f.Usage()
|
|
return ExitUsageError
|
|
}
|
|
|
|
for _, group := range l.commands {
|
|
for _, cmd := range group.commands {
|
|
fmt.Fprintf(l.Output, "%s\n", cmd.Name())
|
|
}
|
|
}
|
|
return ExitSuccess
|
|
}
|
|
|
|
// CommandsCommand returns Command which implements a "commands" subcommand.
|
|
func (cdr *Commander) CommandsCommand() Command {
|
|
return (*lister)(cdr)
|
|
}
|
|
|
|
// An aliaser is a Command wrapping another Command but returning a
|
|
// different name as its alias.
|
|
type aliaser struct {
|
|
alias string
|
|
Command
|
|
}
|
|
|
|
func (a *aliaser) Name() string { return a.alias }
|
|
|
|
// Alias returns a Command alias which implements a "commands" subcommand.
|
|
func Alias(alias string, cmd Command) Command {
|
|
return &aliaser{alias, cmd}
|
|
}
|
|
|
|
// dealias recursivly dealiases a command until a non-aliased command
|
|
// is reached.
|
|
func dealias(cmd Command) Command {
|
|
if alias, ok := cmd.(*aliaser); ok {
|
|
return dealias(alias.Command)
|
|
}
|
|
|
|
return cmd
|
|
}
|
|
|
|
// DefaultCommander is the default commander using flag.CommandLine for flags
|
|
// and os.Args[0] for the command name.
|
|
var DefaultCommander *Commander
|
|
|
|
func init() {
|
|
DefaultCommander = NewCommander(flag.CommandLine, path.Base(os.Args[0]))
|
|
}
|
|
|
|
// 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 explained first before named groups. It is a
|
|
// wrapper around DefaultCommander.Register.
|
|
func Register(cmd Command, group string) {
|
|
DefaultCommander.Register(cmd, group)
|
|
}
|
|
|
|
// ImportantFlag marks a top-level flag as important, which means it
|
|
// will be printed out as part of the output of an ordinary "help"
|
|
// subcommand. (All flags, important or not, are printed by the
|
|
// "flags" subcommand.) It is a wrapper around
|
|
// DefaultCommander.ImportantFlag.
|
|
func ImportantFlag(name string) {
|
|
DefaultCommander.ImportantFlag(name)
|
|
}
|
|
|
|
// Execute should be called once the default flags have been
|
|
// initialized by flag.Parse. It finds the correct subcommand and
|
|
// executes it, and returns an ExitStatus with the result. On a usage
|
|
// error, an appropriate message is printed to os.Stderr, and
|
|
// ExitUsageError is returned. The additional args are provided as-is
|
|
// to the Execute method of the selected Command. It is a wrapper
|
|
// around DefaultCommander.Execute.
|
|
func Execute(ctx context.Context, args ...interface{}) ExitStatus {
|
|
return DefaultCommander.Execute(ctx, args...)
|
|
}
|
|
|
|
// HelpCommand returns a Command which implements "help" for the
|
|
// DefaultCommander. Use Register(HelpCommand(), <group>) for it to be
|
|
// recognized.
|
|
func HelpCommand() Command {
|
|
return DefaultCommander.HelpCommand()
|
|
}
|
|
|
|
// FlagsCommand returns a Command which implements "flags" for the
|
|
// DefaultCommander. Use Register(FlagsCommand(), <group>) for it to be
|
|
// recognized.
|
|
func FlagsCommand() Command {
|
|
return DefaultCommander.FlagsCommand()
|
|
}
|
|
|
|
// CommandsCommand returns Command which implements a "commands" subcommand.
|
|
func CommandsCommand() Command {
|
|
return DefaultCommander.CommandsCommand()
|
|
}
|