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:
@@ -97,10 +97,19 @@ func NewRootIO(name string, commands []Command, in io.Reader, out, errW io.Write
|
||||
root.SetErr(errW)
|
||||
flags := &globalFlags{}
|
||||
flags.register(root.PersistentFlags())
|
||||
root.SetFlagErrorFunc(func(_ *cobra.Command, err error) error {
|
||||
return Usage(err)
|
||||
root.SetFlagErrorFunc(func(c *cobra.Command, err error) error {
|
||||
return usageFor(c, root, err)
|
||||
})
|
||||
|
||||
st := &rootState{
|
||||
root: root,
|
||||
name: name,
|
||||
commands: commands,
|
||||
version: cfg.version,
|
||||
out: out,
|
||||
console: output,
|
||||
flags: flags,
|
||||
}
|
||||
seen := map[string]bool{}
|
||||
for _, b := range builtinCommands() {
|
||||
seen[b.Name] = true
|
||||
@@ -110,21 +119,28 @@ func NewRootIO(name string, commands []Command, in io.Reader, out, errW io.Write
|
||||
return nil, fmt.Errorf("%w: %q", ErrDuplicateCommand, command.Name)
|
||||
}
|
||||
seen[command.Name] = true
|
||||
cmd, err := wrap(command, output)
|
||||
cmd, err := st.wrap(command, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
root.AddCommand(cmd)
|
||||
}
|
||||
|
||||
lister := &lister{name: name, commands: commands, version: cfg.version, out: out, console: output}
|
||||
listCmd, err := wrapCommand(lister.command(), output, false)
|
||||
listCmd, err := st.wrap(st.listCommand(), false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
root.AddCommand(listCmd)
|
||||
helpCmd, err := st.wrap(st.helpCommand(), false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
root.SetHelpCommand(helpCmd)
|
||||
root.SetHelpFunc(func(*cobra.Command, []string) {
|
||||
_ = st.renderList(st.globals(), false)
|
||||
})
|
||||
root.RunE = func(cmd *cobra.Command, _ []string) error {
|
||||
return lister.render(output.globals, false)
|
||||
return st.renderList(st.globals(), false)
|
||||
}
|
||||
root.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
|
||||
var g Globals
|
||||
@@ -144,6 +160,36 @@ func NewRootIO(name string, commands []Command, in io.Reader, out, errW io.Write
|
||||
return root, nil
|
||||
}
|
||||
|
||||
// rootState is what the list, help and error rendering of one root share.
|
||||
type rootState struct {
|
||||
root *cobra.Command
|
||||
name string
|
||||
commands []Command
|
||||
version Version
|
||||
out io.Writer
|
||||
console *console
|
||||
flags *globalFlags
|
||||
}
|
||||
|
||||
// globals returns the parsed global options of the current run.
|
||||
func (st *rootState) globals() Globals {
|
||||
return st.flags.globals()
|
||||
}
|
||||
|
||||
// wrap turns a command value into a cobra command. validate checks the
|
||||
// name form (the builtins list and help skip it).
|
||||
func (st *rootState) wrap(c Command, validate bool) (*cobra.Command, error) {
|
||||
cmd, err := wrapCommand(c, st.console, validate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cmd.Hidden = c.Hidden
|
||||
cmd.SetHelpFunc(func(*cobra.Command, []string) {
|
||||
_ = st.renderHelp(c, st.globals())
|
||||
})
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
func wrap(c Command, out Output) (*cobra.Command, error) {
|
||||
return wrapCommand(c, out, true)
|
||||
}
|
||||
@@ -211,6 +257,23 @@ func wrapCommand(c Command, out Output, validate bool) (*cobra.Command, error) {
|
||||
// usageArgs marks argument-count failures as usage errors (exit code 2).
|
||||
func usageArgs(fn cobra.PositionalArgs) cobra.PositionalArgs {
|
||||
return func(cmd *cobra.Command, args []string) error {
|
||||
return Usage(fn(cmd, args))
|
||||
return usageFor(cmd, cmd.Root(), fn(cmd, args))
|
||||
}
|
||||
}
|
||||
|
||||
// usageFor wraps err as a UsageError that remembers the command, so the
|
||||
// error block can suggest its help.
|
||||
func usageFor(cmd, root *cobra.Command, err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
var ue *UsageError
|
||||
if errors.As(err, &ue) {
|
||||
return err
|
||||
}
|
||||
ue = &UsageError{Err: err}
|
||||
if cmd != nil && cmd != root {
|
||||
ue.command = cmd.Name()
|
||||
}
|
||||
return ue
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user