Skip to content

Commit cdb5dbf

Browse files
Eliminate a few more allocs (#214)
1 parent 46ef736 commit cdb5dbf

2 files changed

Lines changed: 234 additions & 255 deletions

File tree

command.go

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,7 @@ func New(name string, options ...Option) (*Command, error) {
6565
return nil, fmt.Errorf("bad arguments expected [<command> <args>...], got %v", os.Args)
6666
}
6767

68-
// Default implementation
69-
cfg := config{
68+
cmd := &Command{
7069
flags: flag.NewSet(),
7170
stdin: os.Stdin,
7271
stdout: os.Stdout,
@@ -81,30 +80,47 @@ func New(name string, options ...Option) (*Command, error) {
8180
// to report in one go
8281
var errs error
8382
for _, option := range options {
84-
errs = errors.Join(errs, option.apply(&cfg))
83+
errs = errors.Join(errs, option.apply(cmd))
8584
}
8685

87-
// Ensure we always have at least help and version flags
88-
err := Flag(&cfg.helpCalled, "help", 'h', "Show help for "+name).apply(&cfg)
89-
errs = errors.Join(errs, err) // nil errors are discarded in join
90-
91-
err = Flag(&cfg.versionCalled, "version", 'V', "Show version info for "+name).apply(&cfg)
92-
93-
errs = errors.Join(errs, err)
86+
// Ensure we always have at least help and version flags. Wired in
87+
// directly rather than via the Flag option to skip the closure +
88+
// Option interface boxing on every cli.New.
89+
errs = errors.Join(
90+
errs,
91+
addAutoBoolFlag(cmd.flags, &cmd.helpCalled, "help", 'h', "Show help for "+name),
92+
addAutoBoolFlag(cmd.flags, &cmd.versionCalled, "version", 'V', "Show version info for "+name),
93+
)
9494
if errs != nil {
9595
return nil, errs
9696
}
9797

9898
// Additional validation that can't be done per-option
9999
// A command cannot have no subcommands and no run function, it must define one or the other
100-
if cfg.run == nil && len(cfg.subcommands) == 0 {
100+
if cmd.run == nil && len(cmd.subcommands) == 0 {
101101
return nil, fmt.Errorf(
102102
"command %s has no subcommands and no run function, a command must either be runnable or have subcommands",
103-
cfg.name,
103+
cmd.name,
104104
)
105105
}
106106

107-
return cfg.build(), nil
107+
// Loop through each subcommand and set this command as their immediate parent
108+
for _, subcommand := range cmd.subcommands {
109+
subcommand.parent = cmd
110+
}
111+
112+
return cmd, nil
113+
}
114+
115+
// addAutoBoolFlag wires the implicit --help / --version flags onto the
116+
// flag set without going through the Flag option.
117+
func addAutoBoolFlag(set *flag.Set, target *bool, name string, short rune, usage string) error {
118+
f, err := flag.New(target, name, short, usage, flag.Config[bool]{})
119+
if err != nil {
120+
return err
121+
}
122+
123+
return flag.AddToSet(set, f)
108124
}
109125

110126
// Command represents a CLI command.

0 commit comments

Comments
 (0)