Files
summercms/bonfire/command.go
Jakub Zych a6004e490f 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
2026-09-16 13:54:44 +02:00

119 lines
2.4 KiB
Go

package bonfire
import (
"context"
"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
Description string
Flags []Flag
Args []Arg
Run func(ctx context.Context, in Input, out Output) error
}
// Flag describes a string flag passed to a command.
type Flag struct {
Name string
Shorthand string
Description string
Default string
}
// Arg describes a positional argument.
type Arg struct {
Name string
Description string
Required bool
}
// Input is the parsed argument and flag view handed to Command.Run.
type Input interface {
Args() []string
Argument(name string) (string, bool)
Flag(name string) (string, bool)
}
type cobraInput struct {
cmd *cobra.Command
spec Command
args []string
}
func (in cobraInput) Args() []string { return in.args }
func (in cobraInput) Argument(name string) (string, bool) {
for i, arg := range in.spec.Args {
if arg.Name == name {
if i < len(in.args) {
return in.args[i], true
}
return "", false
}
}
return "", false
}
func (in cobraInput) Flag(name string) (string, bool) {
if in.cmd == nil || in.cmd.Flags() == nil {
return "", false
}
if !in.cmd.Flags().Changed(name) {
val, err := in.cmd.Flags().GetString(name)
if err != nil || val == "" {
return "", false
}
return val, true
}
val, err := in.cmd.Flags().GetString(name)
if err != nil {
return "", false
}
return val, true
}
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 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 strings.Join(parts, " ")
}
func positionalArgs(args []Arg) cobra.PositionalArgs {
required := 0
for _, a := range args {
if a.Required {
required++
}
}
if required == 0 {
return cobra.ArbitraryArgs
}
if required == len(args) {
return cobra.ExactArgs(required)
}
return cobra.MinimumNArgs(required)
}