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