1/*
2Copyright 2016 Google Inc. All Rights Reserved.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17// Package subcommands implements a simple way for a single command to have many
18// subcommands, each of which takes arguments and so forth.
19package subcommands
20
21import (
22	"context"
23	"flag"
24	"fmt"
25	"io"
26	"os"
27	"path"
28	"sort"
29	"strings"
30)
31
32// A Command represents a single command.
33type Command interface {
34	// Name returns the name of the command.
35	Name() string
36
37	// Synopsis returns a short string (less than one line) describing the command.
38	Synopsis() string
39
40	// Usage returns a long string explaining the command and giving usage
41	// information.
42	Usage() string
43
44	// SetFlags adds the flags for this command to the specified set.
45	SetFlags(*flag.FlagSet)
46
47	// Execute executes the command and returns an ExitStatus.
48	Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) ExitStatus
49}
50
51// A Commander represents a set of commands.
52type Commander struct {
53	commands  []*CommandGroup
54	topFlags  *flag.FlagSet // top-level flags
55	important []string      // important top-level flags
56	name      string        // normally path.Base(os.Args[0])
57
58	Explain        func(io.Writer)                // A function to print a top level usage explanation. Can be overridden.
59	ExplainGroup   func(io.Writer, *CommandGroup) // A function to print a command group's usage explanation. Can be overridden.
60	ExplainCommand func(io.Writer, Command)       // A function to print a command usage explanation. Can be overridden.
61
62	Output io.Writer // Output specifies where the commander should write its output (default: os.Stdout).
63	Error  io.Writer // Error specifies where the commander should write its error (default: os.Stderr).
64}
65
66// A CommandGroup represents a set of commands about a common topic.
67type CommandGroup struct {
68	name     string
69	commands []Command
70}
71
72// Name returns the group name
73func (g *CommandGroup) Name() string {
74	return g.name
75}
76
77// An ExitStatus represents a Posix exit status that a subcommand
78// expects to be returned to the shell.
79type ExitStatus int
80
81const (
82	ExitSuccess ExitStatus = iota
83	ExitFailure
84	ExitUsageError
85)
86
87// NewCommander returns a new commander with the specified top-level
88// flags and command name. The Usage function for the topLevelFlags
89// will be set as well.
90func NewCommander(topLevelFlags *flag.FlagSet, name string) *Commander {
91	cdr := &Commander{
92		topFlags: topLevelFlags,
93		name:     name,
94		Output:   os.Stdout,
95		Error:    os.Stderr,
96	}
97
98	cdr.Explain = cdr.explain
99	cdr.ExplainGroup = explainGroup
100	cdr.ExplainCommand = explain
101	topLevelFlags.Usage = func() { cdr.Explain(cdr.Error) }
102	return cdr
103}
104
105// Name returns the commander's name
106func (cdr *Commander) Name() string {
107	return cdr.name
108}
109
110// Register adds a subcommand to the supported subcommands in the
111// specified group. (Help output is sorted and arranged by group name.)
112// The empty string is an acceptable group name; such subcommands are
113// explained first before named groups.
114func (cdr *Commander) Register(cmd Command, group string) {
115	for _, g := range cdr.commands {
116		if g.name == group {
117			g.commands = append(g.commands, cmd)
118			return
119		}
120	}
121	cdr.commands = append(cdr.commands, &CommandGroup{
122		name:     group,
123		commands: []Command{cmd},
124	})
125}
126
127// ImportantFlag marks a top-level flag as important, which means it
128// will be printed out as part of the output of an ordinary "help"
129// subcommand.  (All flags, important or not, are printed by the
130// "flags" subcommand.)
131func (cdr *Commander) ImportantFlag(name string) {
132	cdr.important = append(cdr.important, name)
133}
134
135// VisitGroups visits each command group in lexicographical order, calling
136// fn for each.
137func (cdr *Commander) VisitGroups(fn func(*CommandGroup)) {
138	sort.Sort(byGroupName(cdr.commands))
139	for _, g := range cdr.commands {
140		fn(g)
141	}
142}
143
144// VisitCommands visits each command in registered order grouped by
145// command group in lexicographical order, calling fn for each.
146func (cdr *Commander) VisitCommands(fn func(*CommandGroup, Command)) {
147	cdr.VisitGroups(func(g *CommandGroup) {
148		for _, cmd := range g.commands {
149			fn(g, cmd)
150		}
151	})
152}
153
154// VisitAllImportant visits the important top level flags in lexicographical
155// order, calling fn for each. It visits all flags, even those not set.
156func (cdr *Commander) VisitAllImportant(fn func(*flag.Flag)) {
157	sort.Strings(cdr.important)
158	for _, name := range cdr.important {
159		f := cdr.topFlags.Lookup(name)
160		if f == nil {
161			panic(fmt.Sprintf("Important flag (%s) is not defined", name))
162		}
163		fn(f)
164	}
165}
166
167// VisitAll visits the top level flags in lexicographical order, calling fn
168// for each. It visits all flags, even those not set.
169func (cdr *Commander) VisitAll(fn func(*flag.Flag)) {
170	if cdr.topFlags != nil {
171		cdr.topFlags.VisitAll(fn)
172	}
173}
174
175// countFlags returns the number of top-level flags defined, even those not set.
176func (cdr *Commander) countTopFlags() int {
177	count := 0
178	cdr.VisitAll(func(*flag.Flag) {
179		count++
180	})
181	return count
182}
183
184// Execute should be called once the top-level-flags on a Commander
185// have been initialized. It finds the correct subcommand and executes
186// it, and returns an ExitStatus with the result. On a usage error, an
187// appropriate message is printed to os.Stderr, and ExitUsageError is
188// returned. The additional args are provided as-is to the Execute method
189// of the selected Command.
190func (cdr *Commander) Execute(ctx context.Context, args ...interface{}) ExitStatus {
191	if cdr.topFlags.NArg() < 1 {
192		cdr.topFlags.Usage()
193		return ExitUsageError
194	}
195
196	name := cdr.topFlags.Arg(0)
197
198	for _, group := range cdr.commands {
199		for _, cmd := range group.commands {
200			if name != cmd.Name() {
201				continue
202			}
203			f := flag.NewFlagSet(name, flag.ContinueOnError)
204			f.Usage = func() { cdr.ExplainCommand(cdr.Error, cmd) }
205			cmd.SetFlags(f)
206			if f.Parse(cdr.topFlags.Args()[1:]) != nil {
207				return ExitUsageError
208			}
209			return cmd.Execute(ctx, f, args...)
210		}
211	}
212
213	// Cannot find this command.
214	cdr.topFlags.Usage()
215	return ExitUsageError
216}
217
218// Sorting of a slice of command groups.
219type byGroupName []*CommandGroup
220
221// TODO Sort by function rather than implement sortable?
222func (p byGroupName) Len() int           { return len(p) }
223func (p byGroupName) Less(i, j int) bool { return p[i].name < p[j].name }
224func (p byGroupName) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }
225
226// explain prints a brief description of all the subcommands and the
227// important top-level flags.
228func (cdr *Commander) explain(w io.Writer) {
229	fmt.Fprintf(w, "Usage: %s <flags> <subcommand> <subcommand args>\n\n", cdr.name)
230	sort.Sort(byGroupName(cdr.commands))
231	for _, group := range cdr.commands {
232		cdr.ExplainGroup(w, group)
233	}
234	if cdr.topFlags == nil {
235		fmt.Fprintln(w, "\nNo top level flags.")
236		return
237	}
238
239	sort.Strings(cdr.important)
240	if len(cdr.important) == 0 {
241		if cdr.countTopFlags() > 0 {
242			fmt.Fprintf(w, "\nUse \"%s flags\" for a list of top-level flags\n", cdr.name)
243		}
244		return
245	}
246
247	fmt.Fprintf(w, "\nTop-level flags (use \"%s flags\" for a full list):\n", cdr.name)
248	for _, name := range cdr.important {
249		f := cdr.topFlags.Lookup(name)
250		if f == nil {
251			panic(fmt.Sprintf("Important flag (%s) is not defined", name))
252		}
253		fmt.Fprintf(w, "  -%s=%s: %s\n", f.Name, f.DefValue, f.Usage)
254	}
255}
256
257// Sorting of the commands within a group.
258func (g CommandGroup) Len() int           { return len(g.commands) }
259func (g CommandGroup) Less(i, j int) bool { return g.commands[i].Name() < g.commands[j].Name() }
260func (g CommandGroup) Swap(i, j int)      { g.commands[i], g.commands[j] = g.commands[j], g.commands[i] }
261
262// explainGroup explains all the subcommands for a particular group.
263func explainGroup(w io.Writer, group *CommandGroup) {
264	if len(group.commands) == 0 {
265		return
266	}
267	if group.name == "" {
268		fmt.Fprintf(w, "Subcommands:\n")
269	} else {
270		fmt.Fprintf(w, "Subcommands for %s:\n", group.name)
271	}
272	sort.Sort(group)
273
274	aliases := make(map[string][]string)
275	for _, cmd := range group.commands {
276		if alias, ok := cmd.(*aliaser); ok {
277			root := dealias(alias).Name()
278
279			if _, ok := aliases[root]; !ok {
280				aliases[root] = []string{}
281			}
282			aliases[root] = append(aliases[root], alias.Name())
283		}
284	}
285
286	for _, cmd := range group.commands {
287		if _, ok := cmd.(*aliaser); ok {
288			continue
289		}
290
291		name := cmd.Name()
292		names := []string{name}
293
294		if a, ok := aliases[name]; ok {
295			names = append(names, a...)
296		}
297
298		fmt.Fprintf(w, "\t%-15s  %s\n", strings.Join(names, ", "), cmd.Synopsis())
299	}
300	fmt.Fprintln(w)
301}
302
303// explainCmd prints a brief description of a single command.
304func explain(w io.Writer, cmd Command) {
305	fmt.Fprintf(w, "%s", cmd.Usage())
306	subflags := flag.NewFlagSet(cmd.Name(), flag.PanicOnError)
307	subflags.SetOutput(w)
308	cmd.SetFlags(subflags)
309	subflags.PrintDefaults()
310}
311
312// A helper is a Command implementing a "help" command for
313// a given Commander.
314type helper Commander
315
316func (h *helper) Name() string           { return "help" }
317func (h *helper) Synopsis() string       { return "describe subcommands and their syntax" }
318func (h *helper) SetFlags(*flag.FlagSet) {}
319func (h *helper) Usage() string {
320	return `help [<subcommand>]:
321	With an argument, prints detailed information on the use of
322	the specified subcommand. With no argument, print a list of
323	all commands and a brief description of each.
324`
325}
326func (h *helper) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) ExitStatus {
327	switch f.NArg() {
328	case 0:
329		(*Commander)(h).Explain(h.Output)
330		return ExitSuccess
331
332	case 1:
333		for _, group := range h.commands {
334			for _, cmd := range group.commands {
335				if f.Arg(0) != cmd.Name() {
336					continue
337				}
338				(*Commander)(h).ExplainCommand(h.Output, cmd)
339				return ExitSuccess
340			}
341		}
342		fmt.Fprintf(h.Error, "Subcommand %s not understood\n", f.Arg(0))
343	}
344
345	f.Usage()
346	return ExitUsageError
347}
348
349// HelpCommand returns a Command which implements a "help" subcommand.
350func (cdr *Commander) HelpCommand() Command {
351	return (*helper)(cdr)
352}
353
354// A flagger is a Command implementing a "flags" command for a given Commander.
355type flagger Commander
356
357func (flg *flagger) Name() string           { return "flags" }
358func (flg *flagger) Synopsis() string       { return "describe all known top-level flags" }
359func (flg *flagger) SetFlags(*flag.FlagSet) {}
360func (flg *flagger) Usage() string {
361	return `flags [<subcommand>]:
362	With an argument, print all flags of <subcommand>. Else,
363	print a description of all known top-level flags.  (The basic
364	help information only discusses the most generally important
365	top-level flags.)
366`
367}
368func (flg *flagger) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) ExitStatus {
369	if f.NArg() > 1 {
370		f.Usage()
371		return ExitUsageError
372	}
373
374	if f.NArg() == 0 {
375		if flg.topFlags == nil {
376			fmt.Fprintln(flg.Output, "No top-level flags are defined.")
377		} else {
378			flg.topFlags.PrintDefaults()
379		}
380		return ExitSuccess
381	}
382
383	for _, group := range flg.commands {
384		for _, cmd := range group.commands {
385			if f.Arg(0) != cmd.Name() {
386				continue
387			}
388			subflags := flag.NewFlagSet(cmd.Name(), flag.PanicOnError)
389			subflags.SetOutput(flg.Output)
390			cmd.SetFlags(subflags)
391			subflags.PrintDefaults()
392			return ExitSuccess
393		}
394	}
395	fmt.Fprintf(flg.Error, "Subcommand %s not understood\n", f.Arg(0))
396	return ExitFailure
397}
398
399// FlagsCommand returns a Command which implements a "flags" subcommand.
400func (cdr *Commander) FlagsCommand() Command {
401	return (*flagger)(cdr)
402}
403
404// A lister is a Command implementing a "commands" command for a given Commander.
405type lister Commander
406
407func (l *lister) Name() string           { return "commands" }
408func (l *lister) Synopsis() string       { return "list all command names" }
409func (l *lister) SetFlags(*flag.FlagSet) {}
410func (l *lister) Usage() string {
411	return `commands:
412	Print a list of all commands.
413`
414}
415func (l *lister) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) ExitStatus {
416	if f.NArg() != 0 {
417		f.Usage()
418		return ExitUsageError
419	}
420
421	for _, group := range l.commands {
422		for _, cmd := range group.commands {
423			fmt.Fprintf(l.Output, "%s\n", cmd.Name())
424		}
425	}
426	return ExitSuccess
427}
428
429// CommandsCommand returns Command which implements a "commands" subcommand.
430func (cdr *Commander) CommandsCommand() Command {
431	return (*lister)(cdr)
432}
433
434// An aliaser is a Command wrapping another Command but returning a
435// different name as its alias.
436type aliaser struct {
437	alias string
438	Command
439}
440
441func (a *aliaser) Name() string { return a.alias }
442
443// Alias returns a Command alias which implements a "commands" subcommand.
444func Alias(alias string, cmd Command) Command {
445	return &aliaser{alias, cmd}
446}
447
448// dealias recursivly dealiases a command until a non-aliased command
449// is reached.
450func dealias(cmd Command) Command {
451	if alias, ok := cmd.(*aliaser); ok {
452		return dealias(alias.Command)
453	}
454
455	return cmd
456}
457
458// DefaultCommander is the default commander using flag.CommandLine for flags
459// and os.Args[0] for the command name.
460var DefaultCommander *Commander
461
462func init() {
463	DefaultCommander = NewCommander(flag.CommandLine, path.Base(os.Args[0]))
464}
465
466// Register adds a subcommand to the supported subcommands in the
467// specified group. (Help output is sorted and arranged by group
468// name.)  The empty string is an acceptable group name; such
469// subcommands are explained first before named groups. It is a
470// wrapper around DefaultCommander.Register.
471func Register(cmd Command, group string) {
472	DefaultCommander.Register(cmd, group)
473}
474
475// ImportantFlag marks a top-level flag as important, which means it
476// will be printed out as part of the output of an ordinary "help"
477// subcommand.  (All flags, important or not, are printed by the
478// "flags" subcommand.) It is a wrapper around
479// DefaultCommander.ImportantFlag.
480func ImportantFlag(name string) {
481	DefaultCommander.ImportantFlag(name)
482}
483
484// Execute should be called once the default flags have been
485// initialized by flag.Parse. It finds the correct subcommand and
486// executes it, and returns an ExitStatus with the result. On a usage
487// error, an appropriate message is printed to os.Stderr, and
488// ExitUsageError is returned. The additional args are provided as-is
489// to the Execute method of the selected Command. It is a wrapper
490// around DefaultCommander.Execute.
491func Execute(ctx context.Context, args ...interface{}) ExitStatus {
492	return DefaultCommander.Execute(ctx, args...)
493}
494
495// HelpCommand returns a Command which implements "help" for the
496// DefaultCommander. Use Register(HelpCommand(), <group>) for it to be
497// recognized.
498func HelpCommand() Command {
499	return DefaultCommander.HelpCommand()
500}
501
502// FlagsCommand returns a Command which implements "flags" for the
503// DefaultCommander. Use Register(FlagsCommand(), <group>) for it to be
504// recognized.
505func FlagsCommand() Command {
506	return DefaultCommander.FlagsCommand()
507}
508
509// CommandsCommand returns Command which implements a "commands" subcommand.
510func CommandsCommand() Command {
511	return DefaultCommander.CommandsCommand()
512}
513