feat(01-03): add deterministic rich CLI output
- Inject bonfire.Output from stdin/stdout/stderr with stdlib widgets and x/term - Degrade spinner, progress, table and prompts without a TTY or color - Reject plugin command names that are not namespace:verb
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package bonfire
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
@@ -9,10 +10,22 @@ import (
|
||||
|
||||
// NewRoot builds the shared cobra root used by the summer tool and app binaries.
|
||||
// It accepts command values rather than a party registry to avoid an import cycle.
|
||||
func NewRoot(name string, commands []Command, out io.Writer) *cobra.Command {
|
||||
func NewRoot(name string, commands []Command, out io.Writer) (*cobra.Command, error) {
|
||||
return NewRootIO(name, commands, os.Stdin, out, out)
|
||||
}
|
||||
|
||||
// NewRootIO is NewRoot with injected stdin/stdout/stderr for tests.
|
||||
func NewRootIO(name string, commands []Command, in io.Reader, out, errW io.Writer) (*cobra.Command, error) {
|
||||
if out == nil {
|
||||
out = os.Stdout
|
||||
}
|
||||
if errW == nil {
|
||||
errW = out
|
||||
}
|
||||
if in == nil {
|
||||
in = os.Stdin
|
||||
}
|
||||
output := NewOutput(in, out, errW)
|
||||
root := &cobra.Command{
|
||||
Use: name,
|
||||
SilenceUsage: true,
|
||||
@@ -21,30 +34,32 @@ func NewRoot(name string, commands []Command, out io.Writer) *cobra.Command {
|
||||
DisableDefaultCmd: true,
|
||||
},
|
||||
}
|
||||
root.SetIn(in)
|
||||
root.SetOut(out)
|
||||
root.SetErr(out)
|
||||
root.SetErr(errW)
|
||||
for _, command := range commands {
|
||||
root.AddCommand(wrap(command, out))
|
||||
cmd, err := wrap(command, output)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
root.AddCommand(cmd)
|
||||
}
|
||||
return root
|
||||
return root, nil
|
||||
}
|
||||
|
||||
func wrap(c Command, out io.Writer) *cobra.Command {
|
||||
func wrap(c Command, out Output) (*cobra.Command, error) {
|
||||
if !validCommandName(c.Name) {
|
||||
return nil, fmt.Errorf("%w: %q", ErrCommandName, c.Name)
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: c.Name,
|
||||
Use: commandUsage(c),
|
||||
Short: c.Description,
|
||||
Args: cobra.ArbitraryArgs,
|
||||
Args: positionalArgs(c.Args),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if c.Run == nil {
|
||||
return nil
|
||||
}
|
||||
writer := out
|
||||
if cmd != nil {
|
||||
if w := cmd.OutOrStdout(); w != nil {
|
||||
writer = w
|
||||
}
|
||||
}
|
||||
return c.Run(cmd.Context(), cobraInput{cmd: cmd, spec: c, args: args}, writerOutput{w: writer})
|
||||
return c.Run(cmd.Context(), cobraInput{cmd: cmd, spec: c, args: args}, out)
|
||||
},
|
||||
}
|
||||
for _, flag := range c.Flags {
|
||||
@@ -54,5 +69,5 @@ func wrap(c Command, out io.Writer) *cobra.Command {
|
||||
}
|
||||
cmd.Flags().String(flag.Name, flag.Default, flag.Description)
|
||||
}
|
||||
return cmd
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user