2022-12-19 03:04:34 +00:00
// Copyright © 2022 Roberto Hidalgo <chinampa@un.rob.mx>
2022-12-31 05:53:24 +00:00
// SPDX-License-Identifier: Apache-2.0
2022-12-19 03:04:34 +00:00
package command
import (
"fmt"
"strconv"
"strings"
2022-12-31 05:53:24 +00:00
"git.rob.mx/nidito/chinampa/pkg/errors"
2024-04-20 04:10:38 +00:00
"git.rob.mx/nidito/chinampa/pkg/logger"
2022-12-19 03:04:34 +00:00
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
// Options is a map of name to Option.
type Options map [ string ] * Option
2024-04-20 04:10:38 +00:00
// AllKnown returns a map of option names to their resolved values
2022-12-19 03:04:34 +00:00
func ( opts * Options ) AllKnown ( ) map [ string ] any {
col := map [ string ] any { }
for name , opt := range * opts {
col [ name ] = opt . ToValue ( )
}
return col
}
2024-04-20 04:10:38 +00:00
// AllKnownStr returns a map of option names to their stringified values
2022-12-19 03:04:34 +00:00
func ( opts * Options ) AllKnownStr ( ) map [ string ] string {
col := map [ string ] string { }
for name , opt := range * opts {
col [ name ] = opt . ToString ( )
}
return col
}
2024-04-20 04:10:38 +00:00
// Parse populates values with those supplied in the provided pflag.Flagset
2022-12-19 03:04:34 +00:00
func ( opts * Options ) Parse ( supplied * pflag . FlagSet ) {
2023-03-20 06:15:53 +00:00
// log.Debugf("Parsing supplied flags, %v", supplied)
2022-12-19 03:04:34 +00:00
for name , opt := range * opts {
switch opt . Type {
case ValueTypeBoolean :
if val , err := supplied . GetBool ( name ) ; err == nil {
opt . provided = val
continue
}
2022-12-31 05:53:24 +00:00
case ValueTypeInt :
if val , err := supplied . GetInt ( name ) ; err == nil {
opt . provided = val
continue
}
2022-12-19 03:04:34 +00:00
default :
opt . Type = ValueTypeString
2024-04-20 04:10:38 +00:00
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 {
opt . provided = val
continue
}
2022-12-19 03:04:34 +00:00
}
}
}
}
2024-04-20 04:10:38 +00:00
// AreValid tells if these options are all valid
2022-12-19 03:04:34 +00:00
func ( opts * Options ) AreValid ( ) error {
for name , opt := range * opts {
if err := opt . Validate ( name ) ; err != nil {
return err
}
}
return nil
}
// Option represents a command line flag.
type Option struct {
2024-04-20 04:10:38 +00:00
// 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" `
// Description is a required field that show up during completions and help
Description string ` json:"description" yaml:"description" validate:"required" `
// Default value for this option, if none provided
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" `
// Repeated options may be specified more than once
Repeated bool ` json:"repeated" yaml:"repeated" validate:"omitempty" `
// Command references the Command this Option is defined for
Command * Command ` json:"-" yaml:"-" validate:"-" `
provided any
2022-12-19 03:04:34 +00:00
}
2024-04-20 04:10:38 +00:00
// IsKnown tells if the option was provided by the user
2022-12-19 03:04:34 +00:00
func ( opt * Option ) IsKnown ( ) bool {
return opt . provided != nil
}
2024-04-20 04:10:38 +00:00
// Returns the resolved value for an option
2022-12-19 03:04:34 +00:00
func ( opt * Option ) ToValue ( ) any {
if opt . IsKnown ( ) {
return opt . provided
}
return opt . Default
}
2024-04-20 04:10:38 +00:00
// Returns a string representation of this Option's resolved value
2022-12-19 03:04:34 +00:00
func ( opt * Option ) ToString ( ) string {
value := opt . ToValue ( )
stringValue := ""
2022-12-31 05:53:24 +00:00
switch opt . Type {
case ValueTypeBoolean :
2022-12-19 03:04:34 +00:00
if value == nil {
stringValue = ""
} else {
stringValue = strconv . FormatBool ( value . ( bool ) )
}
2022-12-31 05:53:24 +00:00
case ValueTypeInt :
if value == nil {
stringValue = ""
} else {
stringValue = fmt . Sprintf ( "%i" , value )
}
default :
2022-12-19 03:04:34 +00:00
if value != nil {
stringValue = value . ( string )
}
}
return stringValue
}
2024-04-20 04:10:38 +00:00
func ( opt * Option ) internalValidate ( name , current string ) error {
2022-12-19 03:04:34 +00:00
if current == "" {
return nil
}
validValues , _ , err := opt . Resolve ( current )
if err != nil {
return err
}
if ! contains ( validValues , current ) {
return errors . BadArguments { Msg : fmt . Sprintf ( "%s is not a valid value for option <%s>. Valid options are: %s" , current , name , strings . Join ( validValues , ", " ) ) }
}
return nil
}
2024-04-20 04:10:38 +00:00
// 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
}
2022-12-19 03:04:34 +00:00
// Validates tells if the user-supplied value needs validation.
func ( opt * Option ) Validates ( ) bool {
return opt . Values != nil && opt . Values . Validates ( )
}
// providesAutocomplete tells if this option provides autocomplete values.
func ( opt * Option ) providesAutocomplete ( ) bool {
return opt . Values != nil
}
// Resolve returns autocomplete values for an option.
func ( opt * Option ) Resolve ( currentValue string ) ( values [ ] string , flag cobra . ShellCompDirective , err error ) {
if opt . Values != nil {
if opt . Values . command == nil {
opt . Values . command = opt . Command
}
return opt . Values . Resolve ( currentValue )
}
return
}
// 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 ) {
if ! opt . providesAutocomplete ( ) {
2024-04-20 04:10:38 +00:00
logger . Tracef ( "Option does not provide autocomplete %+v" , opt )
2022-12-19 03:04:34 +00:00
flag = cobra . ShellCompDirectiveNoFileComp
return
}
2023-03-21 04:18:09 +00:00
if err := opt . Command . Arguments . Parse ( args ) ; err != nil {
2024-04-20 04:10:38 +00:00
logger . Errorf ( "Could not parse command arguments %s" , err )
return [ ] string { } , cobra . ShellCompDirectiveDefault
2023-03-21 04:18:09 +00:00
}
2022-12-19 03:04:34 +00:00
opt . Command . Options . Parse ( cmd . Flags ( ) )
var err error
values , flag , err = opt . Resolve ( toComplete )
if err != nil {
return values , cobra . ShellCompDirectiveError
}
2023-01-14 23:10:59 +00:00
if toComplete != "" && flag != cobra . ShellCompDirectiveFilterFileExt && flag != cobra . ShellCompDirectiveFilterDirs {
2022-12-19 03:04:34 +00:00
filtered := [ ] string { }
for _ , value := range values {
if strings . HasPrefix ( value , toComplete ) {
filtered = append ( filtered , value )
}
}
values = filtered
}
return cobra . AppendActiveHelp ( values , opt . Description ) , flag
}