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:
@@ -2,12 +2,15 @@ package bonfire
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// ErrCommandName is returned when a plugin command is missing namespace:verb.
|
||||
var ErrCommandName = errors.New("bonfire: plugin command name must be namespace:verb")
|
||||
|
||||
// Command is a console command value collected from plugins or the tool.
|
||||
type Command struct {
|
||||
Name string
|
||||
@@ -39,13 +42,6 @@ type Input interface {
|
||||
Flag(name string) (string, bool)
|
||||
}
|
||||
|
||||
// Output is an injected writer used by commands instead of package-level stdout.
|
||||
type Output interface {
|
||||
io.Writer
|
||||
Println(a ...any)
|
||||
Printf(format string, a ...any)
|
||||
}
|
||||
|
||||
type cobraInput struct {
|
||||
cmd *cobra.Command
|
||||
spec Command
|
||||
@@ -84,27 +80,39 @@ func (in cobraInput) Flag(name string) (string, bool) {
|
||||
return val, true
|
||||
}
|
||||
|
||||
type writerOutput struct {
|
||||
w io.Writer
|
||||
func validCommandName(name string) bool {
|
||||
switch name {
|
||||
case "build", "dev":
|
||||
return true
|
||||
}
|
||||
ns, verb, ok := strings.Cut(name, ":")
|
||||
return ok && ns != "" && verb != "" && !strings.Contains(verb, " ")
|
||||
}
|
||||
|
||||
func (o writerOutput) Write(p []byte) (int, error) {
|
||||
if o.w == nil {
|
||||
return 0, io.ErrClosedPipe
|
||||
func commandUsage(c Command) string {
|
||||
parts := []string{c.Name}
|
||||
for _, a := range c.Args {
|
||||
if a.Required {
|
||||
parts = append(parts, "<"+a.Name+">")
|
||||
} else {
|
||||
parts = append(parts, "["+a.Name+"]")
|
||||
}
|
||||
}
|
||||
return o.w.Write(p)
|
||||
return strings.Join(parts, " ")
|
||||
}
|
||||
|
||||
func (o writerOutput) Println(a ...any) {
|
||||
if o.w == nil {
|
||||
return
|
||||
func positionalArgs(args []Arg) cobra.PositionalArgs {
|
||||
required := 0
|
||||
for _, a := range args {
|
||||
if a.Required {
|
||||
required++
|
||||
}
|
||||
}
|
||||
fmt.Fprintln(o.w, a...)
|
||||
}
|
||||
|
||||
func (o writerOutput) Printf(format string, a ...any) {
|
||||
if o.w == nil {
|
||||
return
|
||||
if required == 0 {
|
||||
return cobra.ArbitraryArgs
|
||||
}
|
||||
fmt.Fprintf(o.w, format, a...)
|
||||
if required == len(args) {
|
||||
return cobra.ExactArgs(required)
|
||||
}
|
||||
return cobra.MinimumNArgs(required)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user