allow setting group-scoped flags

This commit is contained in:
Roberto Hidalgo 2023-04-03 18:13:42 -06:00
parent fef0a55288
commit 7b17646738
3 changed files with 75 additions and 20 deletions

View File

@ -54,7 +54,7 @@ func newCobraRoot(root *command.Command) *cobra.Command {
} }
} }
func ToCobra(cmd *command.Command, globalOptions command.Options) *cobra.Command { func ToCobra(cmd *command.Command, globalOptions command.Options, parent *cobra.Command) *cobra.Command {
localName := cmd.Name() localName := cmd.Name()
useSpec := []string{localName, "[options]"} useSpec := []string{localName, "[options]"}
for _, arg := range cmd.Arguments { for _, arg := range cmd.Arguments {
@ -97,8 +97,20 @@ func ToCobra(cmd *command.Command, globalOptions command.Options) *cobra.Command
log.Errorf("Failed setting up autocompletion for option <%s> of command <%s>", name, cmd.FullName()) log.Errorf("Failed setting up autocompletion for option <%s> of command <%s>", name, cmd.FullName())
} }
} }
parent.AddCommand(cc)
cc.SetHelpFunc(cmd.HelpRenderer(globalOptions)) cmdGlobalOptions := globalOptions
if parent != cc.Root() {
cmdGlobalOptions = subOptions(globalOptions)
log.Tracef("Adding subflags from %s to child %s", parent.Name(), cmd.FullName())
if p := FromCobra(parent); p != nil {
for key, opt := range p.Options {
cmdGlobalOptions[key] = opt
}
}
}
cc.SetHelpFunc(cmd.HelpRenderer(cmdGlobalOptions))
cmd.SetCobra(cc) cmd.SetCobra(cc)
return cc return cc
} }

View File

@ -62,6 +62,14 @@ func CommandList() []*command.Command {
return registry.byPath return registry.byPath
} }
func subOptions(m command.Options) command.Options {
m2 := make(map[string]*command.Option, len(m))
for id, opt := range m {
m2[id] = opt
}
return m2
}
func Execute(version string) error { func Execute(version string) error {
log.Debug("starting execution") log.Debug("starting execution")
cmdRoot := command.Root cmdRoot := command.Root
@ -97,10 +105,15 @@ func Execute(version string) error {
container := ccRoot container := ccRoot
for idx, cp := range cmd.Path { for idx, cp := range cmd.Path {
if idx == len(cmd.Path)-1 { if idx == len(cmd.Path)-1 {
leaf := ToCobra(cmd, globalOptions) if cmd.Action != nil {
container.AddCommand(leaf) // nil actions come when the current command consists only
log.Tracef("cobra: %s => %s", leaf.Name(), container.CommandPath()) // of metadata for a "group parent" command
break // and we don't wanna cobraize it like a regular, actionable one
leaf := ToCobra(cmd, globalOptions, container)
log.Tracef("cobra: %s => %s", leaf.Name(), container.CommandPath())
break
}
log.Tracef("Found command with no action: %s, assuming group parent action", cmd.Path)
} }
query := []string{cp} query := []string{cp}
@ -112,6 +125,11 @@ func Execute(version string) error {
for _, sub := range container.Commands() { for _, sub := range container.Commands() {
if sub.Name() == cp { if sub.Name() == cp {
if parent := FromCobra(container); parent != nil {
log.Tracef("pflags to %s from %s", sub.Name(), parent.FullName())
fs := parent.FlagSet()
sub.PersistentFlags().AddFlagSet(fs)
}
container = sub container = sub
found = true found = true
} }
@ -119,16 +137,35 @@ func Execute(version string) error {
if !found { if !found {
groupName := strings.Join(query, " ") groupName := strings.Join(query, " ")
groupPath := append(cmdRoot.Path, append(cmd.Path[0:idx], query...)...) // nolint:gocritic cleanPath := append(cmd.Path[0:idx], query...)
groupPath := append(cmdRoot.Path, cleanPath...) // nolint:gocritic
cmdGlobalOptions := globalOptions
groupParent := Get(groupName)
if groupParent == nil {
log.Tracef("creating group parent for %s", groupPath)
groupParent = &command.Command{
Path: cleanPath,
Summary: fmt.Sprintf("%s subcommands", groupName),
Description: fmt.Sprintf("Runs subcommands within %s", groupName),
Arguments: command.Arguments{},
Options: command.Options{},
}
Register(groupParent)
} else {
log.Tracef("using pre-existing group parent for %s (%s)", groupPath, groupParent.Path)
}
cc := &cobra.Command{ cc := &cobra.Command{
Use: cp, Use: cp,
Short: fmt.Sprintf("%s subcommands", groupName), Short: groupParent.Summary,
DisableAutoGenTag: true, DisableAutoGenTag: true,
SuggestionsMinimumDistance: 2, SuggestionsMinimumDistance: 2,
SilenceUsage: true, SilenceUsage: true,
SilenceErrors: true, SilenceErrors: true,
Annotations: map[string]string{ Annotations: map[string]string{
ContextKeyRuntimeIndex: strings.Join(groupPath, " "), ContextKeyRuntimeIndex: strings.Join(cleanPath, " "),
}, },
ValidArgs: []string{}, ValidArgs: []string{},
Args: func(cmd *cobra.Command, args []string) error { Args: func(cmd *cobra.Command, args []string) error {
@ -161,15 +198,23 @@ func Execute(version string) error {
}, },
} }
groupParent := &command.Command{ if len(groupParent.Options) > 0 {
Path: groupPath, fs := cmd.FlagSet()
Summary: fmt.Sprintf("%s subcommands", groupName), cc.PersistentFlags().AddFlagSet(fs)
Description: fmt.Sprintf("Runs subcommands within %s", groupName), log.Debugf("adding sub-global option set to %s", groupName)
Arguments: command.Arguments{},
Options: command.Options{},
} }
Register(groupParent)
cc.SetHelpFunc(groupParent.HelpRenderer(globalOptions)) if container != cc.Root() {
cmdGlobalOptions = subOptions(globalOptions)
if p := FromCobra(container); p != nil {
for key, opt := range p.Options {
cmdGlobalOptions[key] = opt
}
}
}
cc.PersistentFlags().AddFlagSet(container.PersistentFlags())
cc.SetHelpFunc(groupParent.HelpRenderer(cmdGlobalOptions))
cc.SetHelpCommand(commands.Help) cc.SetHelpCommand(commands.Help)
container.AddCommand(cc) container.AddCommand(cc)
container = cc container = cc

View File

@ -73,7 +73,6 @@ func (cmd *Command) FlagSet() *pflag.FlagSet {
fs := pflag.NewFlagSet(strings.Join(cmd.Path, " "), pflag.ContinueOnError) fs := pflag.NewFlagSet(strings.Join(cmd.Path, " "), pflag.ContinueOnError)
fs.SortFlags = false fs.SortFlags = false
fs.Usage = func() {} fs.Usage = func() {}
for name, opt := range cmd.Options { for name, opt := range cmd.Options {
switch opt.Type { switch opt.Type {
case ValueTypeBoolean: case ValueTypeBoolean:
@ -81,7 +80,6 @@ func (cmd *Command) FlagSet() *pflag.FlagSet {
if opt.Default != nil { if opt.Default != nil {
def = opt.Default.(bool) def = opt.Default.(bool)
} }
fs.BoolP(name, opt.ShortName, def, opt.Description) fs.BoolP(name, opt.ShortName, def, opt.Description)
case ValueTypeInt: case ValueTypeInt:
def := -1 def := -1
@ -97,7 +95,6 @@ func (cmd *Command) FlagSet() *pflag.FlagSet {
def = casted def = casted
} }
} }
fs.IntP(name, opt.ShortName, def, opt.Description) fs.IntP(name, opt.ShortName, def, opt.Description)
case ValueTypeDefault, ValueTypeString: case ValueTypeDefault, ValueTypeString:
opt.Type = ValueTypeString opt.Type = ValueTypeString
@ -112,6 +109,7 @@ func (cmd *Command) FlagSet() *pflag.FlagSet {
continue continue
} }
} }
cmd.runtimeFlags = fs cmd.runtimeFlags = fs
} }
return cmd.runtimeFlags return cmd.runtimeFlags