Files
summercms/bonfire/root.go
Jakub Zych 398353b135 feat(08-07): wire repeatable bonfire flags and export client-issuing helpers
- bonfire.wrap registers a Repeatable Flag as a Cobra StringSlice so
  Input.Flags returns every repeated --name=value occurrence in order;
  scalar/bare flags are unaffected (D-19)
- wristband.IssueClientCredentials/RejectRedirectURI export the exact
  random-id/secret/hash and redirect-URI validation RFC 7591
  registration already uses, so the fonoteka:oauth-client operator
  command shares one hash/validation path with DCR (T-08-SECRET-TIMING)
2026-09-23 21:56:00 +02:00

95 lines
2.4 KiB
Go

package bonfire
import (
"fmt"
"io"
"os"
"github.com/spf13/cobra"
)
// 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, 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,
SilenceErrors: true,
CompletionOptions: cobra.CompletionOptions{
DisableDefaultCmd: true,
},
}
root.SetIn(in)
root.SetOut(out)
root.SetErr(errW)
for _, command := range commands {
cmd, err := wrap(command, output)
if err != nil {
return nil, err
}
root.AddCommand(cmd)
}
return root, nil
}
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: commandUsage(c),
Short: c.Description,
Args: positionalArgs(c.Args),
RunE: func(cmd *cobra.Command, args []string) error {
if c.Run == nil {
return nil
}
return c.Run(cmd.Context(), cobraInput{cmd: cmd, spec: c, args: args}, out)
},
}
for _, flag := range c.Flags {
if flag.Repeatable {
// D-19: an ordered, multi-occurrence string flag (Cobra
// StringSlice), read back through Input.Flags. Default is a
// single-element slice only when non-empty, matching the
// scalar path's zero-value convention.
var def []string
if flag.Default != "" {
def = []string{flag.Default}
}
if flag.Shorthand != "" {
cmd.Flags().StringSliceP(flag.Name, flag.Shorthand, def, flag.Description)
} else {
cmd.Flags().StringSlice(flag.Name, def, flag.Description)
}
continue
}
if flag.Shorthand != "" {
cmd.Flags().StringP(flag.Name, flag.Shorthand, flag.Default, flag.Description)
} else {
cmd.Flags().String(flag.Name, flag.Default, flag.Description)
}
if flag.Bare {
if f := cmd.Flags().Lookup(flag.Name); f != nil {
f.NoOptDefVal = "true"
}
}
}
return cmd, nil
}