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 Output io.Writer // Output specifies where the commander should write its output (default: os.Stdout). 59 Error io.Writer // Error specifies where the commander should write its error (default: os.Stderr). 60} 61 62// A commandGroup represents a set of commands about a common topic. 63type commandGroup struct { 64 name string 65 commands []Command 66} 67 68// An ExitStatus represents a Posix exit status that a subcommand 69// expects to be returned to the shell. 70type ExitStatus int 71 72const ( 73 ExitSuccess ExitStatus = iota 74 ExitFailure 75 ExitUsageError 76) 77 78// NewCommander returns a new commander with the specified top-level 79// flags and command name. The Usage function for the topLevelFlags 80// will be set as well. 81func NewCommander(topLevelFlags *flag.FlagSet, name string) *Commander { 82 cdr := &Commander{ 83 topFlags: topLevelFlags, 84 name: name, 85 Output: os.Stdout, 86 Error: os.Stderr, 87 } 88 topLevelFlags.Usage = func() { cdr.explain(cdr.Error) } 89 return cdr 90} 91 92// Register adds a subcommand to the supported subcommands in the 93// specified group. (Help output is sorted and arranged by group name.) 94// The empty string is an acceptable group name; such subcommands are 95// explained first before named groups. 96func (cdr *Commander) Register(cmd Command, group string) { 97 for _, g := range cdr.commands { 98 if g.name == group { 99 g.commands = append(g.commands, cmd) 100 return 101 } 102 } 103 cdr.commands = append(cdr.commands, &commandGroup{ 104 name: group, 105 commands: []Command{cmd}, 106 }) 107} 108 109// ImportantFlag marks a top-level flag as important, which means it 110// will be printed out as part of the output of an ordinary "help" 111// subcommand. (All flags, important or not, are printed by the 112// "flags" subcommand.) 113func (cdr *Commander) ImportantFlag(name string) { 114 cdr.important = append(cdr.important, name) 115} 116 117// Execute should be called once the top-level-flags on a Commander 118// have been initialized. It finds the correct subcommand and executes 119// it, and returns an ExitStatus with the result. On a usage error, an 120// appropriate message is printed to os.Stderr, and ExitUsageError is 121// returned. The additional args are provided as-is to the Execute method 122// of the selected Command. 123func (cdr *Commander) Execute(ctx context.Context, args ...interface{}) ExitStatus { 124 if cdr.topFlags.NArg() < 1 { 125 cdr.topFlags.Usage() 126 return ExitUsageError 127 } 128 129 name := cdr.topFlags.Arg(0) 130 131 for _, group := range cdr.commands { 132 for _, cmd := range group.commands { 133 if name != cmd.Name() { 134 continue 135 } 136 f := flag.NewFlagSet(name, flag.ContinueOnError) 137 f.Usage = func() { explain(cdr.Error, cmd) } 138 cmd.SetFlags(f) 139 if f.Parse(cdr.topFlags.Args()[1:]) != nil { 140 return ExitUsageError 141 } 142 return cmd.Execute(ctx, f, args...) 143 } 144 } 145 146 // Cannot find this command. 147 cdr.topFlags.Usage() 148 return ExitUsageError 149} 150 151// Sorting of a slice of command groups. 152type byGroupName []*commandGroup 153 154func (p byGroupName) Len() int { return len(p) } 155func (p byGroupName) Less(i, j int) bool { return p[i].name < p[j].name } 156func (p byGroupName) Swap(i, j int) { p[i], p[j] = p[j], p[i] } 157 158// explain prints a brief description of all the subcommands and the 159// important top-level flags. 160func (cdr *Commander) explain(w io.Writer) { 161 fmt.Fprintf(w, "Usage: %s <flags> <subcommand> <subcommand args>\n\n", cdr.name) 162 sort.Sort(byGroupName(cdr.commands)) 163 for _, group := range cdr.commands { 164 explainGroup(w, group) 165 } 166 if cdr.topFlags == nil { 167 fmt.Fprintln(w, "\nNo top level flags.") 168 return 169 } 170 if len(cdr.important) == 0 { 171 fmt.Fprintf(w, "\nUse \"%s flags\" for a list of top-level flags\n", cdr.name) 172 return 173 } 174 175 fmt.Fprintf(w, "\nTop-level flags (use \"%s flags\" for a full list):\n", cdr.name) 176 for _, name := range cdr.important { 177 f := cdr.topFlags.Lookup(name) 178 if f == nil { 179 panic(fmt.Sprintf("Important flag (%s) is not defined", name)) 180 } 181 fmt.Fprintf(w, " -%s=%s: %s\n", f.Name, f.DefValue, f.Usage) 182 } 183} 184 185// Sorting of the commands within a group. 186func (g commandGroup) Len() int { return len(g.commands) } 187func (g commandGroup) Less(i, j int) bool { return g.commands[i].Name() < g.commands[j].Name() } 188func (g commandGroup) Swap(i, j int) { g.commands[i], g.commands[j] = g.commands[j], g.commands[i] } 189 190// explainGroup explains all the subcommands for a particular group. 191func explainGroup(w io.Writer, group *commandGroup) { 192 if len(group.commands) == 0 { 193 return 194 } 195 if group.name == "" { 196 fmt.Fprintf(w, "Subcommands:\n") 197 } else { 198 fmt.Fprintf(w, "Subcommands for %s:\n", group.name) 199 } 200 sort.Sort(group) 201 202 aliases := make(map[string][]string) 203 for _, cmd := range group.commands { 204 if alias, ok := cmd.(*aliaser); ok { 205 root := dealias(alias).Name() 206 207 if _, ok := aliases[root]; !ok { 208 aliases[root] = []string{} 209 } 210 aliases[root] = append(aliases[root], alias.Name()) 211 } 212 } 213 214 for _, cmd := range group.commands { 215 if _, ok := cmd.(*aliaser); ok { 216 continue 217 } 218 219 name := cmd.Name() 220 names := []string{name} 221 222 if a, ok := aliases[name]; ok { 223 names = append(names, a...) 224 } 225 226 fmt.Fprintf(w, "\t%-15s %s\n", strings.Join(names, ", "), cmd.Synopsis()) 227 } 228 fmt.Fprintln(w) 229} 230 231// explainCmd prints a brief description of a single command. 232func explain(w io.Writer, cmd Command) { 233 fmt.Fprintf(w, "%s", cmd.Usage()) 234 subflags := flag.NewFlagSet(cmd.Name(), flag.PanicOnError) 235 subflags.SetOutput(w) 236 cmd.SetFlags(subflags) 237 subflags.PrintDefaults() 238} 239 240// A helper is a Command implementing a "help" command for 241// a given Commander. 242type helper Commander 243 244func (h *helper) Name() string { return "help" } 245func (h *helper) Synopsis() string { return "describe subcommands and their syntax" } 246func (h *helper) SetFlags(*flag.FlagSet) {} 247func (h *helper) Usage() string { 248 return `help [<subcommand>]: 249 With an argument, prints detailed information on the use of 250 the specified subcommand. With no argument, print a list of 251 all commands and a brief description of each. 252` 253} 254func (h *helper) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) ExitStatus { 255 switch f.NArg() { 256 case 0: 257 (*Commander)(h).explain(h.Output) 258 return ExitSuccess 259 260 case 1: 261 for _, group := range h.commands { 262 for _, cmd := range group.commands { 263 if f.Arg(0) != cmd.Name() { 264 continue 265 } 266 explain(h.Output, cmd) 267 return ExitSuccess 268 } 269 } 270 fmt.Fprintf(h.Error, "Subcommand %s not understood\n", f.Arg(0)) 271 } 272 273 f.Usage() 274 return ExitUsageError 275} 276 277// HelpCommand returns a Command which implements a "help" subcommand. 278func (cdr *Commander) HelpCommand() Command { 279 return (*helper)(cdr) 280} 281 282// A flagger is a Command implementing a "flags" command for a given Commander. 283type flagger Commander 284 285func (flg *flagger) Name() string { return "flags" } 286func (flg *flagger) Synopsis() string { return "describe all known top-level flags" } 287func (flg *flagger) SetFlags(*flag.FlagSet) {} 288func (flg *flagger) Usage() string { 289 return `flags [<subcommand>]: 290 With an argument, print all flags of <subcommand>. Else, 291 print a description of all known top-level flags. (The basic 292 help information only discusses the most generally important 293 top-level flags.) 294` 295} 296func (flg *flagger) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) ExitStatus { 297 if f.NArg() > 1 { 298 f.Usage() 299 return ExitUsageError 300 } 301 302 if f.NArg() == 0 { 303 if flg.topFlags == nil { 304 fmt.Fprintln(flg.Output, "No top-level flags are defined.") 305 } else { 306 flg.topFlags.PrintDefaults() 307 } 308 return ExitSuccess 309 } 310 311 for _, group := range flg.commands { 312 for _, cmd := range group.commands { 313 if f.Arg(0) != cmd.Name() { 314 continue 315 } 316 subflags := flag.NewFlagSet(cmd.Name(), flag.PanicOnError) 317 subflags.SetOutput(flg.Output) 318 cmd.SetFlags(subflags) 319 subflags.PrintDefaults() 320 return ExitSuccess 321 } 322 } 323 fmt.Fprintf(flg.Error, "Subcommand %s not understood\n", f.Arg(0)) 324 return ExitFailure 325} 326 327// FlagsCommand returns a Command which implements a "flags" subcommand. 328func (cdr *Commander) FlagsCommand() Command { 329 return (*flagger)(cdr) 330} 331 332// A lister is a Command implementing a "commands" command for a given Commander. 333type lister Commander 334 335func (l *lister) Name() string { return "commands" } 336func (l *lister) Synopsis() string { return "list all command names" } 337func (l *lister) SetFlags(*flag.FlagSet) {} 338func (l *lister) Usage() string { 339 return `commands: 340 Print a list of all commands. 341` 342} 343func (l *lister) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) ExitStatus { 344 if f.NArg() != 0 { 345 f.Usage() 346 return ExitUsageError 347 } 348 349 for _, group := range l.commands { 350 for _, cmd := range group.commands { 351 fmt.Fprintf(l.Output, "%s\n", cmd.Name()) 352 } 353 } 354 return ExitSuccess 355} 356 357// CommandsCommand returns Command which implements a "commands" subcommand. 358func (cdr *Commander) CommandsCommand() Command { 359 return (*lister)(cdr) 360} 361 362// An aliaser is a Command wrapping another Command but returning a 363// different name as its alias. 364type aliaser struct { 365 alias string 366 Command 367} 368 369func (a *aliaser) Name() string { return a.alias } 370 371// Alias returns a Command alias which implements a "commands" subcommand. 372func Alias(alias string, cmd Command) Command { 373 return &aliaser{alias, cmd} 374} 375 376// dealias recursivly dealiases a command until a non-aliased command 377// is reached. 378func dealias(cmd Command) Command { 379 if alias, ok := cmd.(*aliaser); ok { 380 return dealias(alias.Command) 381 } 382 383 return cmd 384} 385 386// DefaultCommander is the default commander using flag.CommandLine for flags 387// and os.Args[0] for the command name. 388var DefaultCommander *Commander 389 390func init() { 391 DefaultCommander = NewCommander(flag.CommandLine, path.Base(os.Args[0])) 392} 393 394// Register adds a subcommand to the supported subcommands in the 395// specified group. (Help output is sorted and arranged by group 396// name.) The empty string is an acceptable group name; such 397// subcommands are explained first before named groups. It is a 398// wrapper around DefaultCommander.Register. 399func Register(cmd Command, group string) { 400 DefaultCommander.Register(cmd, group) 401} 402 403// ImportantFlag marks a top-level flag as important, which means it 404// will be printed out as part of the output of an ordinary "help" 405// subcommand. (All flags, important or not, are printed by the 406// "flags" subcommand.) It is a wrapper around 407// DefaultCommander.ImportantFlag. 408func ImportantFlag(name string) { 409 DefaultCommander.ImportantFlag(name) 410} 411 412// Execute should be called once the default flags have been 413// initialized by flag.Parse. It finds the correct subcommand and 414// executes it, and returns an ExitStatus with the result. On a usage 415// error, an appropriate message is printed to os.Stderr, and 416// ExitUsageError is returned. The additional args are provided as-is 417// to the Execute method of the selected Command. It is a wrapper 418// around DefaultCommander.Execute. 419func Execute(ctx context.Context, args ...interface{}) ExitStatus { 420 return DefaultCommander.Execute(ctx, args...) 421} 422 423// HelpCommand returns a Command which implements "help" for the 424// DefaultCommander. Use Register(HelpCommand(), <group>) for it to be 425// recognized. 426func HelpCommand() Command { 427 return DefaultCommander.HelpCommand() 428} 429 430// FlagsCommand returns a Command which implements "flags" for the 431// DefaultCommander. Use Register(FlagsCommand(), <group>) for it to be 432// recognized. 433func FlagsCommand() Command { 434 return DefaultCommander.FlagsCommand() 435} 436 437// CommandsCommand returns Command which implements a "commands" subcommand. 438func CommandsCommand() Command { 439 return DefaultCommander.CommandsCommand() 440} 441