Skip to content

Refactor help API#19

Draft
mfridman wants to merge 8 commits intomainfrom
mf/usage-api
Draft

Refactor help API#19
mfridman wants to merge 8 commits intomainfrom
mf/usage-api

Conversation

@mfridman
Copy link
Copy Markdown
Collaborator

@mfridman mfridman commented Apr 26, 2026

This PR reshapes the help and usage-error API around the default path most programs should use: define commands, call ParseAndRun, and get sane help output without wiring anything else.

The core API stays string-based and automatic. The composable usage/help builder is kept internal in this PR while that API continues to be refined.

Default help

root := &cli.Command{
    Name:    "greet",
    Summary: "Print a greeting",
    Exec: func(ctx context.Context, s *cli.State) error {
        fmt.Fprintln(s.Stdout, "hello, world!")
        return nil
    },
}

if err := cli.ParseAndRun(ctx, root, os.Args[1:], nil); err != nil {
    fmt.Fprintf(os.Stderr, "error: %v\n", err)
    os.Exit(1)
}

That gets help for free:

Print a greeting

Usage:
  greet

Flags

Flags still use the standard library flag package. FlagConfigs adds the small bits this library owns: short aliases, required flags, and local flags.

Flags: cli.FlagsFunc(func(f *flag.FlagSet) {
    f.Bool("verbose", false, "enable verbose output")
    f.String("output", "", "output file")
}),
FlagConfigs: []cli.FlagConfig{
    {Name: "verbose", Short: "v"},
    {Name: "output", Short: "o", Required: true},
},

Help shows both flag forms, marks required flags, and wraps long descriptions:

Usage:
  greet [flags]

Flags:
  -o, --output string    output file (required)
  -v, --verbose          enable verbose output

If setup inside FlagsFunc is invalid, such as defining the same flag twice, Parse returns a normal setup error instead of crashing with a panic stack.

Summary and Description

For commands that need both parent-list text and longer command help, use Summary and Description together:

&cli.Command{
    Name:    "list",
    Summary: "List tasks",
    Description: `List tasks in the current workspace.

By default, completed tasks are hidden.`,
    Exec: runList,
}

Parent help uses the short text:

Available Commands:
  list    List tasks

todo list --help uses the longer description:

List tasks in the current workspace.

By default, completed tasks are hidden.

Usage:
  todo list

If a command only needs one sentence, set Summary and leave Description empty.

Custom help

Most commands should not set Help. When a command needs full control, Help replaces the generated help string:

Help: func(c *cli.Command) string {
    return `Print a greeting.

Usage:
  greet <name>

Examples:
  greet margo`
},

Usage errors

When parsing succeeded but Exec finds invalid args or flag combinations, return UsageErrorf. Run prints the resolved command's help to stderr, then returns the underlying error so callers can print their normal error: ... line.

Exec: func(ctx context.Context, s *cli.State) error {
    if len(s.Args) == 0 {
        return cli.UsageErrorf("must supply a name")
    }
    fmt.Fprintf(s.Stdout, "hello, %s\n", s.Args[0])
    return nil
},

Return a normal error when help should not be printed. State.Cmd is the resolved command, so command code can inspect s.Cmd.Path() when it needs command-aware errors or logging.

Split parse/run

ParseAndRun remains the normal entry point and handles --help automatically. Programs that call Parse directly should check the standard sentinel:

if err := cli.Parse(root, args); err != nil {
    if errors.Is(err, flag.ErrHelp) {
        // Print custom help here, or use ParseAndRun for built-in help.
        return nil
    }
    return err
}

Breaking changes

  • Remove Command.UsageFunc; use Command.Help when replacing help entirely.
  • Replace Command.ShortHelp with Command.Summary for command lists and Command.Description for longer command help.
  • Rename FlagOption to FlagConfig and Command.FlagOptions to Command.FlagConfigs.
  • Remove DefaultUsage and the top-level Usage function from the public API.
  • Remove cli.ErrHelp; check errors.Is(err, flag.ErrHelp) when handling Parse directly.

Why: the library should provide good default help without making usage rendering the center of the API. Custom help remains possible, usage errors become explicit, and the structured help-building API can keep evolving outside the public surface.

Closes #4, #14, #15.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: add ability to get command information from *State

1 participant