actually allow repeated options
This commit is contained in:
parent
614119b22d
commit
e1fb0ef7f1
@ -64,7 +64,7 @@ description: {{ .Command.Short }}
|
|||||||
## Options
|
## Options
|
||||||
|
|
||||||
{{ range $name, $opt := .Spec.Options -}}
|
{{ range $name, $opt := .Spec.Options -}}
|
||||||
- `--{{ $name }}` (_{{$opt.Type}}_): {{ trimSuffix $opt.Description "."}}.{{ if $opt.Default }} Default: _{{ $opt.Default }}_.{{ end }}
|
- `--{{ $name }}` (_{{if $opt.Repeated}}[]{{end}}{{$opt.Type}}_): {{ trimSuffix $opt.Description "."}}.{{if $opt.Repeated}} May be specified more than once. {{end}}{{ if $opt.Default }} Default: _{{ $opt.Default }}_.{{ end }}
|
||||||
{{ end -}}
|
{{ end -}}
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
|
|
||||||
|
@ -98,11 +98,31 @@ func (cmd *Command) FlagSet() *pflag.FlagSet {
|
|||||||
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
|
||||||
|
if opt.Repeated {
|
||||||
|
def := []string{}
|
||||||
|
if opt.Default != nil {
|
||||||
|
switch defV := opt.Default.(type) {
|
||||||
|
case []any:
|
||||||
|
for _, v := range defV {
|
||||||
|
def = append(def, fmt.Sprintf("%s", v))
|
||||||
|
}
|
||||||
|
case []string:
|
||||||
|
def = defV
|
||||||
|
case string:
|
||||||
|
def = []string{defV}
|
||||||
|
default:
|
||||||
|
logger.Errorf("Invalid default for repeated option %s configuration: %+v", name, defV)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.StringArrayP(name, opt.ShortName, def, opt.Description)
|
||||||
|
} else {
|
||||||
def := ""
|
def := ""
|
||||||
if opt.Default != nil {
|
if opt.Default != nil {
|
||||||
def = fmt.Sprintf("%s", opt.Default)
|
def = fmt.Sprintf("%s", opt.Default)
|
||||||
}
|
}
|
||||||
fs.StringP(name, opt.ShortName, def, opt.Description)
|
fs.StringP(name, opt.ShortName, def, opt.Description)
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
// ignore flag
|
// ignore flag
|
||||||
log.Warnf("Ignoring unknown option type <%s> for option <%s>", opt.Type, name)
|
log.Warnf("Ignoring unknown option type <%s> for option <%s>", opt.Type, name)
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.rob.mx/nidito/chinampa/pkg/errors"
|
"git.rob.mx/nidito/chinampa/pkg/errors"
|
||||||
|
"git.rob.mx/nidito/chinampa/pkg/logger"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
)
|
)
|
||||||
@ -15,6 +16,7 @@ import (
|
|||||||
// Options is a map of name to Option.
|
// Options is a map of name to Option.
|
||||||
type Options map[string]*Option
|
type Options map[string]*Option
|
||||||
|
|
||||||
|
// AllKnown returns a map of option names to their resolved values
|
||||||
func (opts *Options) AllKnown() map[string]any {
|
func (opts *Options) AllKnown() map[string]any {
|
||||||
col := map[string]any{}
|
col := map[string]any{}
|
||||||
for name, opt := range *opts {
|
for name, opt := range *opts {
|
||||||
@ -23,6 +25,7 @@ func (opts *Options) AllKnown() map[string]any {
|
|||||||
return col
|
return col
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AllKnownStr returns a map of option names to their stringified values
|
||||||
func (opts *Options) AllKnownStr() map[string]string {
|
func (opts *Options) AllKnownStr() map[string]string {
|
||||||
col := map[string]string{}
|
col := map[string]string{}
|
||||||
for name, opt := range *opts {
|
for name, opt := range *opts {
|
||||||
@ -31,6 +34,7 @@ func (opts *Options) AllKnownStr() map[string]string {
|
|||||||
return col
|
return col
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse populates values with those supplied in the provided pflag.Flagset
|
||||||
func (opts *Options) Parse(supplied *pflag.FlagSet) {
|
func (opts *Options) Parse(supplied *pflag.FlagSet) {
|
||||||
// log.Debugf("Parsing supplied flags, %v", supplied)
|
// log.Debugf("Parsing supplied flags, %v", supplied)
|
||||||
for name, opt := range *opts {
|
for name, opt := range *opts {
|
||||||
@ -47,14 +51,24 @@ func (opts *Options) Parse(supplied *pflag.FlagSet) {
|
|||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
opt.Type = ValueTypeString
|
opt.Type = ValueTypeString
|
||||||
|
if opt.Repeated {
|
||||||
|
if val, err := supplied.GetStringArray(name); err == nil {
|
||||||
|
opt.provided = val
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
logger.Errorf("Invalid option configuration: %s", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
if val, err := supplied.GetString(name); err == nil {
|
if val, err := supplied.GetString(name); err == nil {
|
||||||
opt.provided = val
|
opt.provided = val
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AreValid tells if these options are all valid
|
||||||
func (opts *Options) AreValid() error {
|
func (opts *Options) AreValid() error {
|
||||||
for name, opt := range *opts {
|
for name, opt := range *opts {
|
||||||
if err := opt.Validate(name); err != nil {
|
if err := opt.Validate(name); err != nil {
|
||||||
@ -67,20 +81,29 @@ func (opts *Options) AreValid() error {
|
|||||||
|
|
||||||
// Option represents a command line flag.
|
// Option represents a command line flag.
|
||||||
type Option struct {
|
type Option struct {
|
||||||
ShortName string `json:"short-name,omitempty" yaml:"short-name,omitempty"` // nolint:tagliatelle
|
// Type represents the type of value expected to be provided for this option
|
||||||
Type ValueType `json:"type" yaml:"type" validate:"omitempty,oneof=string bool int"`
|
Type ValueType `json:"type" yaml:"type" validate:"omitempty,oneof=string bool int"`
|
||||||
|
// Description is a required field that show up during completions and help
|
||||||
Description string `json:"description" yaml:"description" validate:"required"`
|
Description string `json:"description" yaml:"description" validate:"required"`
|
||||||
|
// Default value for this option, if none provided
|
||||||
Default any `json:"default,omitempty" yaml:"default,omitempty"`
|
Default any `json:"default,omitempty" yaml:"default,omitempty"`
|
||||||
|
// ShortName When set, enables representing this Option as a short flag (-x)
|
||||||
|
ShortName string `json:"short-name,omitempty" yaml:"short-name,omitempty"` // nolint:tagliatelle
|
||||||
|
// Values denote the source for completion/validation values of this option
|
||||||
Values *ValueSource `json:"values,omitempty" yaml:"values,omitempty" validate:"omitempty"`
|
Values *ValueSource `json:"values,omitempty" yaml:"values,omitempty" validate:"omitempty"`
|
||||||
|
// Repeated options may be specified more than once
|
||||||
Repeated bool `json:"repeated" yaml:"repeated" validate:"omitempty"`
|
Repeated bool `json:"repeated" yaml:"repeated" validate:"omitempty"`
|
||||||
|
// Command references the Command this Option is defined for
|
||||||
Command *Command `json:"-" yaml:"-" validate:"-"`
|
Command *Command `json:"-" yaml:"-" validate:"-"`
|
||||||
provided any
|
provided any
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsKnown tells if the option was provided by the user
|
||||||
func (opt *Option) IsKnown() bool {
|
func (opt *Option) IsKnown() bool {
|
||||||
return opt.provided != nil
|
return opt.provided != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns the resolved value for an option
|
||||||
func (opt *Option) ToValue() any {
|
func (opt *Option) ToValue() any {
|
||||||
if opt.IsKnown() {
|
if opt.IsKnown() {
|
||||||
return opt.provided
|
return opt.provided
|
||||||
@ -88,6 +111,7 @@ func (opt *Option) ToValue() any {
|
|||||||
return opt.Default
|
return opt.Default
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns a string representation of this Option's resolved value
|
||||||
func (opt *Option) ToString() string {
|
func (opt *Option) ToString() string {
|
||||||
value := opt.ToValue()
|
value := opt.ToValue()
|
||||||
stringValue := ""
|
stringValue := ""
|
||||||
@ -113,13 +137,7 @@ func (opt *Option) ToString() string {
|
|||||||
return stringValue
|
return stringValue
|
||||||
}
|
}
|
||||||
|
|
||||||
func (opt *Option) Validate(name string) error {
|
func (opt *Option) internalValidate(name, current string) error {
|
||||||
if !opt.Validates() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
current := opt.ToString() // nolint:ifshort
|
|
||||||
|
|
||||||
if current == "" {
|
if current == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -136,6 +154,24 @@ func (opt *Option) Validate(name string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate validates the provided value if a value source
|
||||||
|
func (opt *Option) Validate(name string) error {
|
||||||
|
if !opt.Validates() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if opt.Repeated {
|
||||||
|
values := opt.ToValue().([]string)
|
||||||
|
for _, current := range values {
|
||||||
|
opt.internalValidate(name, current)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
opt.internalValidate(name, opt.ToString()) // nolint:ifshort
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Validates tells if the user-supplied value needs validation.
|
// Validates tells if the user-supplied value needs validation.
|
||||||
func (opt *Option) Validates() bool {
|
func (opt *Option) Validates() bool {
|
||||||
return opt.Values != nil && opt.Values.Validates()
|
return opt.Values != nil && opt.Values.Validates()
|
||||||
@ -161,12 +197,14 @@ func (opt *Option) Resolve(currentValue string) (values []string, flag cobra.She
|
|||||||
// CompletionFunction is called by cobra when asked to complete an option.
|
// CompletionFunction is called by cobra when asked to complete an option.
|
||||||
func (opt *Option) CompletionFunction(cmd *cobra.Command, args []string, toComplete string) (values []string, flag cobra.ShellCompDirective) {
|
func (opt *Option) CompletionFunction(cmd *cobra.Command, args []string, toComplete string) (values []string, flag cobra.ShellCompDirective) {
|
||||||
if !opt.providesAutocomplete() {
|
if !opt.providesAutocomplete() {
|
||||||
|
logger.Tracef("Option does not provide autocomplete %+v", opt)
|
||||||
flag = cobra.ShellCompDirectiveNoFileComp
|
flag = cobra.ShellCompDirectiveNoFileComp
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := opt.Command.Arguments.Parse(args); err != nil {
|
if err := opt.Command.Arguments.Parse(args); err != nil {
|
||||||
return []string{err.Error()}, cobra.ShellCompDirectiveDefault
|
logger.Errorf("Could not parse command arguments %s", err)
|
||||||
|
return []string{}, cobra.ShellCompDirectiveDefault
|
||||||
}
|
}
|
||||||
opt.Command.Options.Parse(cmd.Flags())
|
opt.Command.Options.Parse(cmd.Flags())
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user