feat(bonfire): Artisan help, examples, JSON listing and error hints
- help <command> and <command> --help print Description, Usage,
Arguments, Options with defaults, Global options, Help and Examples
- Command.Examples and Command.Hidden (appended, additive); list --all
shows hidden commands, list --format=json prints a bonfire.Listing
- CheckDocs reports commands lacking description, long help, examples or
argument and option descriptions
- Error block with the ERROR badge, WithHint explanation and Try lines
({bin} replaced), cobra suggestions as Try, -v error chain, messages
scrubbed by sunscreen; Reported errors print nothing and exit 1
This commit is contained in:
@@ -62,10 +62,16 @@ Both programs list their commands with no arguments, with `--help` and with `lis
|
||||
```sh
|
||||
summer
|
||||
summer make:model --help
|
||||
summer help migrate
|
||||
./bin/acme --help
|
||||
./bin/acme migrate:rollback --help
|
||||
./bin/acme help migrate:rollback
|
||||
./bin/acme list --all
|
||||
./bin/acme list --format=json
|
||||
```
|
||||
|
||||
A command's help shows its description, usage, arguments, options with their defaults, the global options, the long help and examples. `list --all` includes hidden commands, and `list --format=json` prints every command with its arguments, options and examples for scripts and agents.
|
||||
|
||||
A command that `summer` delegates to the application binary, such as `summer serve`, answers `--help` with the binary's own help, so run it inside an application directory. Asking the help command of `summer` about a delegated command instead prints a short description and its common flags without running the binary.
|
||||
|
||||
### Global options
|
||||
|
||||
@@ -16,7 +16,7 @@ A `bonfire.Command` has a name, a description, its positional arguments and flag
|
||||
- Each `bonfire.Arg` is a positional argument with a name, a description and a `Required` marker. The usage line shows required arguments as `<name>` and optional ones as `[name]`.
|
||||
- Each `bonfire.Flag` is a string flag. Set `bonfire.Flag.Bare` for a switch such as `--dry-run` that stores `true` when given alone, and `bonfire.Flag.Repeatable` for a flag that can be given several times.
|
||||
- `bonfire.Command.Run` receives the context, a `bonfire.Input` and a `bonfire.Output`.
|
||||
- `bonfire.Command.Long` is optional extended help shown by the help command; without it the description is shown. Set `bonfire.Command.Passthrough` for a command that forwards to another program: flag parsing is turned off, so every argument after the command name, including `--help`, `-h` and `--`, reaches `bonfire.Input.Args` unchanged. Declared flags then appear in the usage text only, and declared arguments are not count-checked.
|
||||
- `bonfire.Command.Long` is optional extended help, shown under "Help:" by `help <command>` and `<command> --help`. `bonfire.Command.Examples` and `bonfire.Command.Hidden` are described below. Set `bonfire.Command.Passthrough` for a command that forwards to another program: flag parsing is turned off, so every argument after the command name, including `--help`, `-h` and `--`, reaches `bonfire.Input.Args` unchanged. Declared flags then appear in the usage text only, and declared arguments are not count-checked.
|
||||
|
||||
Read arguments with `bonfire.Input.Argument`, scalar and bare flags with `bonfire.Input.Flag`, and repeatable flags with `bonfire.Input.Flags`, which returns the values in the order given:
|
||||
|
||||
@@ -60,7 +60,133 @@ A scaffolded plugin's `Commands` method returns the generated list of everything
|
||||
- widgets: `bonfire.Output.Table`, `bonfire.Output.Spinner` around a function and `bonfire.Output.Progress` for a progress bar, which fall back to plain lines when the output is not a terminal;
|
||||
- prompts: `bonfire.Output.Ask`, `bonfire.Output.Confirm`, `bonfire.Output.Choice` and `bonfire.Output.Secret`. Prompts return their defaults when input ends, so a command run from cron or a script never hangs.
|
||||
|
||||
Return an error from `Run` to fail the command. The binary prints it and exits with status 1.
|
||||
Return an error from `Run` to fail the command. The binary prints it as an error block on stderr and exits with status 1; see [Errors and hints](#errors-and-hints).
|
||||
|
||||
## Examples and hidden commands
|
||||
|
||||
`bonfire.Command.Examples` lists usage examples. Each `bonfire.Example` holds the command line without the binary and a short description; help prints the description as a `#` comment and the line after `bin/<name>` (or `summer` for the tool's own commands), so one example serves both. `bonfire.Command.Hidden` keeps a command that still runs out of the list; `list --all` and `list --format=json` show it:
|
||||
|
||||
```go src=modules/bonfire/example_test.go#ExampleExample
|
||||
export := bonfire.Command{
|
||||
Name: "blog:export",
|
||||
Description: "Export published posts as JSON",
|
||||
Long: "Writes every published post, newest first, to a JSON file.",
|
||||
Flags: []bonfire.Flag{
|
||||
{Name: "path", Description: "Output file", Default: "posts.json"},
|
||||
},
|
||||
Examples: []bonfire.Example{
|
||||
{Command: "blog:export --path=backup.json", Description: "Export to another file"},
|
||||
},
|
||||
Run: func(ctx context.Context, in bonfire.Input, out bonfire.Output) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
// Hidden commands run, but list shows them only with --all.
|
||||
cleanup := bonfire.Command{
|
||||
Name: "blog:cleanup",
|
||||
Description: "Remove orphaned uploads",
|
||||
Hidden: true,
|
||||
}
|
||||
|
||||
root, err := bonfire.NewRootIO("acme", []bonfire.Command{export, cleanup}, strings.NewReader(""), os.Stdout, os.Stdout)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
bonfire.Execute(context.Background(), root, []string{"help", "blog:export"})
|
||||
// Output:
|
||||
// Description:
|
||||
// Export published posts as JSON
|
||||
//
|
||||
// Usage:
|
||||
// blog:export [options]
|
||||
//
|
||||
// Options:
|
||||
// --path=PATH Output file [default: "posts.json"]
|
||||
//
|
||||
// Global options:
|
||||
// -h, --help Display help for the given command
|
||||
// -q, --quiet Do not output any message
|
||||
// -v|vv|vvv Increase verbosity
|
||||
// --ansi|no-ansi Force (or disable) ANSI output
|
||||
// -n, --no-interaction Do not ask any interactive question
|
||||
// --env The environment the command runs under
|
||||
//
|
||||
// Help:
|
||||
// Writes every published post, newest first, to a JSON file.
|
||||
//
|
||||
// Examples:
|
||||
// # Export to another file
|
||||
// bin/acme blog:export --path=backup.json
|
||||
```
|
||||
|
||||
`list --format=json` prints every command with its arguments, options (with defaults) and examples as a `bonfire.Listing`, for documentation checks, scripts and agents. `bonfire.Describe` builds the same listing from a command slice.
|
||||
|
||||
## Errors and hints
|
||||
|
||||
When `Run` returns an error, the binary prints a block on stderr: the ERROR badge and the message, then "Run with -v for details.". With `-v` the block lists the wrapped errors under "Caused by:" instead. Messages pass through `sunscreen.Scrub`, so a token inside an error text is not printed.
|
||||
|
||||
Wrap an error with `bonfire.WithHint` to explain it and to say what to run next. `{bin}` in the Try text becomes `bin/<name>` or `summer`, so the hint names the right program:
|
||||
|
||||
```go src=modules/bonfire/example_test.go#ExampleWithHint
|
||||
publish := bonfire.Command{
|
||||
Name: "blog:publish",
|
||||
Description: "Publish a draft post",
|
||||
Args: []bonfire.Arg{{Name: "slug", Description: "Post slug", Required: true}},
|
||||
Run: func(ctx context.Context, in bonfire.Input, out bonfire.Output) error {
|
||||
slug, _ := in.Argument("slug")
|
||||
return bonfire.WithHint(fmt.Errorf("blog: no draft named %q", slug),
|
||||
"Only drafts can be published; published posts keep their slug.",
|
||||
"{bin} blog:drafts")
|
||||
},
|
||||
}
|
||||
|
||||
root, err := bonfire.NewRootIO("acme", []bonfire.Command{publish}, strings.NewReader(""), os.Stdout, os.Stdout)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
code := bonfire.Execute(context.Background(), root, []string{"blog:publish", "hello-world"})
|
||||
fmt.Println("exit code:", code)
|
||||
// Output:
|
||||
// ERROR blog: no draft named "hello-world"
|
||||
//
|
||||
// Only drafts can be published; published posts keep their slug.
|
||||
//
|
||||
// Try:
|
||||
// bin/acme blog:drafts
|
||||
//
|
||||
// Run with -v for details.
|
||||
//
|
||||
// exit code: 1
|
||||
```
|
||||
|
||||
A command that has already printed its problems, such as a checker that lists every finding, returns `bonfire.Reported(err)`: the binary exits 1 and prints nothing more. A mistake in how the command was called, such as an unknown flag or a missing argument, exits 2 and the block suggests `help <command>`; wrap your own usage checks with `bonfire.Usage` to get the same exit code.
|
||||
|
||||
## Documentation coverage
|
||||
|
||||
`bonfire.CheckDocs` reports each command that lacks a description, long help, an example, or a description of one of its arguments or options. It only reports; the framework's own test fails on any problem, and a plugin repository can call it from a test and log the problems:
|
||||
|
||||
```go src=modules/bonfire/example_test.go#ExampleCheckDocs
|
||||
commands := []bonfire.Command{
|
||||
{
|
||||
Name: "blog:publish",
|
||||
Description: "Publish a draft post",
|
||||
Long: "Publishes the draft with the given slug and notifies subscribers.",
|
||||
Args: []bonfire.Arg{{Name: "slug", Description: "Post slug", Required: true}},
|
||||
Examples: []bonfire.Example{{Command: "blog:publish hello-world"}},
|
||||
},
|
||||
{
|
||||
Name: "blog:reindex",
|
||||
Description: "Rebuild the search index",
|
||||
Flags: []bonfire.Flag{{Name: "batch", Default: "500"}},
|
||||
},
|
||||
}
|
||||
for _, problem := range bonfire.CheckDocs(commands) {
|
||||
fmt.Println(problem)
|
||||
}
|
||||
// Output: blog:reindex: missing long help, examples, option --batch description
|
||||
```
|
||||
|
||||
## Calling commands in-process
|
||||
|
||||
|
||||
Reference in New Issue
Block a user