-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Related: #4
From within Exec, there's no way to show usage -- users have to do the awkward variable-capture-plus-closure dance:
var cmd *cli.Command
cmd = &cli.Command{
Name: "greet",
Exec: func(ctx context.Context, s *cli.State) error {
if len(s.Args) == 0 {
fmt.Fprintln(s.Stderr, cli.DefaultUsage(cmd))
return errors.New("must supply a name")
}
// ...
},
}This breaks the clean return &cli.Command{...} pattern.
Options considered
A. Expose Cmd on State + s.UsageErrorf convenience
Two layered changes. Cmd *Command on State is the foundational primitive -- the terminal command, set during Parse. UsageErrorf is convenience built on top.
Requires fixing an artificial limitation: DefaultUsage only works with root today because only root gets state assigned. All commands in the path can share the same *State.
// Foundational: direct access to the command
Exec: func(ctx context.Context, s *cli.State) error {
fmt.Fprintln(s.Stderr, cli.DefaultUsage(s.Cmd))
return errors.New("must supply a name")
}
// Convenience: one-liner for the common case
Exec: func(ctx context.Context, s *cli.State) error {
if len(s.Args) == 0 {
return s.UsageErrorf("must supply a name")
}
// ...
}No new types, no framework interception. Users pick the level of control they need.
B. Special error type handled by Run/ParseAndRun
Returns a *UsageError wrapping the real error. Run detects it, prints usage to stderr, returns the unwrapped error.
Exec: func(ctx context.Context, s *cli.State) error {
if len(s.Args) == 0 {
return cli.UsageErrorf(s, "must supply a name")
}
// ...
}Clean separation (no side effects at the call site), but adds a new exported error type and framework interception logic in Run.
C. Expose s.Usage() string and let users format
Most flexible, least magic. But two lines instead of one, and no single "one way to do it" -- users will format differently or mix up stdout/stderr.
Exec: func(ctx context.Context, s *cli.State) error {
if len(s.Args) == 0 {
fmt.Fprintln(s.Stderr, s.Usage())
return errors.New("must supply a name")
}
// ...
}D. s.PrintUsage() plus normal error return
Like C but hides the fmt.Fprintln boilerplate. Still two lines and two concepts.
Exec: func(ctx context.Context, s *cli.State) error {
if len(s.Args) == 0 {
s.PrintUsage()
return errors.New("must supply a name")
}
// ...
}E. Only UsageErrorf without exposing Cmd
Solves the common case but no escape hatch for less common needs (inspecting command name, flags, building custom usage).
Exec: func(ctx context.Context, s *cli.State) error {
if len(s.Args) == 0 {
return s.UsageErrorf("must supply a name")
}
// ...
}Leaning toward A -- Cmd on State is generally useful beyond just usage, and UsageErrorf gives a clean one-liner for the common case.