Skip to content

Commit e200439

Browse files
committed
Deduplicated usageFunc loop, re-added flag parse check, clarified comments
1 parent f3c231b commit e200439

File tree

1 file changed

+42
-49
lines changed

1 file changed

+42
-49
lines changed

cmd/src/cmd.go

Lines changed: 42 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -45,18 +45,19 @@ type commander []*command
4545
// Run the command
4646
func (c commander) run(flagSet *flag.FlagSet, cmdName, usageText string, args []string) {
4747

48-
// Check if --help args are anywhere in the command; if yes, then
49-
// remove it from the list of args at this point to avoid interrupting recursive function calls,
50-
// and append it to the deepest command / subcommand
48+
// Check if --help args are anywhere in the command
49+
// If yes, then remove it from the list of args at this point,
50+
// then append it to the deepest command / subcommand, later,
51+
// to avoid outputting usage text for a commander when a subcommand is specified
5152
filteredArgs := make([]string, 0, len(args))
5253
helpRequested := false
5354

5455
helpFlags := []string{
55-
"--h",
56+
"help",
57+
"-help",
5658
"--help",
5759
"-h",
58-
"-help",
59-
"help",
60+
"--h",
6061
}
6162

6263
for _, arg := range args {
@@ -67,44 +68,19 @@ func (c commander) run(flagSet *flag.FlagSet, cmdName, usageText string, args []
6768
}
6869
}
6970

70-
// Define the usage function from usageText
71+
// Define the usage function for the commander
7172
flagSet.Usage = func() {
7273
_, _ = fmt.Fprint(flag.CommandLine.Output(), usageText)
7374
}
7475

75-
// Parse the command's flags, if not already parsed
76+
// Parse the commander's flags, if not already parsed
7677
if !flagSet.Parsed() {
7778
_ = flagSet.Parse(filteredArgs)
7879
}
7980

80-
// If no subcommands remain (or help requested with no subcommands), print usage
81-
if flagSet.NArg() == 0 {
82-
flagSet.SetOutput(os.Stdout)
83-
flagSet.Usage()
84-
os.Exit(0)
85-
}
86-
87-
// Configure default usage funcs for all commands.
88-
for _, cmd := range c {
89-
cmd := cmd
90-
91-
// If the command / subcommand has defined its own usageFunc, then use it
92-
if cmd.usageFunc != nil {
93-
cmd.flagSet.Usage = cmd.usageFunc
94-
continue
95-
}
96-
97-
// If the command / subcommand has not defined its own usageFunc,
98-
// then generate a basic default usageFunc,
99-
// using the command's defined flagSet and their defaults
100-
cmd.flagSet.Usage = func() {
101-
_, _ = fmt.Fprintf(flag.CommandLine.Output(), "Usage of '%s %s':\n", cmdName, cmd.flagSet.Name())
102-
cmd.flagSet.PrintDefaults()
103-
}
104-
}
105-
10681
// Find the subcommand to execute
107-
// Assume the subcommand is the first arg in the flagSet
82+
// This assumes the subcommand is the first arg in the flagSet,
83+
// i.e. any global args have been removed from the flagSet
10884
name := flagSet.Arg(0)
10985

11086
// Loop through the list of all registered subcommands
@@ -116,57 +92,74 @@ func (c commander) run(flagSet *flag.FlagSet, cmdName, usageText string, args []
11692
}
11793
// If the first arg is this registered commmand in the loop, then try and run it, then exit
11894

95+
// Set up the usage function for this subcommand
96+
// If the subcommand has a usageFunc defined, then use it
97+
if cmd.usageFunc != nil {
98+
cmd.flagSet.Usage = cmd.usageFunc
99+
} else {
100+
// If the subcommand does not have a usageFunc defined,
101+
// then define a simple default one,
102+
// using the list of flags defined in the subcommand, and their description strings
103+
cmd.flagSet.Usage = func() {
104+
_, _ = fmt.Fprintf(flag.CommandLine.Output(), "Usage of '%s %s':\n", cmdName, cmd.flagSet.Name())
105+
cmd.flagSet.PrintDefaults()
106+
}
107+
}
108+
119109
// Read global configuration
120110
var err error
121111
cfg, err = readConfig()
122112
if err != nil {
123113
log.Fatal("reading config: ", err)
124114
}
125115

126-
// Get the remaining args, to pass to the subcommand, as an unparsed array of previously parsed args
116+
// Get the remainder of the args, excluding the first arg / this command name
127117
args := flagSet.Args()[1:]
128118

129-
// Set output to stdout for help (flag package defaults to stderr)
119+
// Set output to stdout, for usage / helper text printed for the --help flag (flag package defaults to stderr)
130120
cmd.flagSet.SetOutput(os.Stdout)
131121
flag.CommandLine.SetOutput(os.Stdout)
132122

133-
// Note: We can't parse flags here because commanders need to pass unparsed args to subcommand handlers
134-
// Each handler is responsible for parsing its own flags
135-
// All commands must use `flagSet := flag.NewFlagSet("<name>", flag.ExitOnError)` to ensure usage helper text is printed automatically on arg parse errors
136-
// Parse the subcommand's args, on its behalf, to test if flag.ExitOnError is not set
137-
// if err := cmd.flagSet.Parse(args); err != nil {
138-
// fmt.Printf("Error parsing subcommand flags: %s\n", err)
139-
// panic(fmt.Sprintf("all registered commands should use flag.ExitOnError: error: %s", err))
140-
// }
141-
142123
// If the --help arg was provided, re-add it here for the lowest command to parse and action
143124
if helpRequested {
144125
args = append(args, "-h")
145126
}
146127

128+
// Parse the subcommand's args, on its behalf, to test and ensure flag.ExitOnError is set
129+
// just in case any future authors of subcommands forget to set flag.ExitOnError
130+
if err := cmd.flagSet.Parse(args); err != nil {
131+
fmt.Printf("Error parsing subcommand flags: %s\n", err)
132+
panic(fmt.Sprintf("all registered commands should use flag.ExitOnError: error: %s", err))
133+
}
134+
147135
// Execute the subcommand
136+
// Handle any errors returned
148137
if err := cmd.handler(args); err != nil {
149138

150-
// If the subcommand returns a UsageError, then print the error and usage helper text
139+
// If the returned error is of type UsageError
151140
if _, ok := err.(*cmderrors.UsageError); ok {
141+
// then print the error and usage helper text, both to stderr
152142
log.Printf("error: %s\n\n", err)
153143
cmd.flagSet.SetOutput(os.Stderr)
154144
flag.CommandLine.SetOutput(os.Stderr)
155145
cmd.flagSet.Usage()
156146
os.Exit(2)
157147
}
158148

159-
// If the subcommand returns any other error, then print the error
149+
// If the returned error is of type ExitCodeError
160150
if e, ok := err.(*cmderrors.ExitCodeError); ok {
151+
// Then log the error and exit with the exit code
161152
if e.HasError() {
162153
log.Println(e)
163154
}
164155
os.Exit(e.Code())
165156
}
157+
158+
// For all other types of errors, log them as fatal, and exit
166159
log.Fatal(err)
167160
}
168161

169-
// If no error was returned, then exit the application
162+
// If no error was returned, then just exit the application cleanly
170163
os.Exit(0)
171164
}
172165

0 commit comments

Comments
 (0)