github.com/kristofferahl/go-centry@v1.5.0/internal/pkg/cmd/option.go (about)

     1  package cmd
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  	"strconv"
     7  	"strings"
     8  )
     9  
    10  // OptionsSet represents a set of flags that can be passed to the cli
    11  type OptionsSet struct {
    12  	Name  string
    13  	items map[string]*Option
    14  }
    15  
    16  // OptionType defines the type of an option
    17  type OptionType string
    18  
    19  // StringOption defines a string value option
    20  const StringOption OptionType = "string"
    21  
    22  // BoolOption defines a boolean value option
    23  const BoolOption OptionType = "bool"
    24  
    25  // IntegerOption defines an interger value option
    26  const IntegerOption OptionType = "integer"
    27  
    28  // SelectOption defines a select value option
    29  const SelectOption OptionType = "select"
    30  
    31  // SelectOptionV2 defines a select value option
    32  const SelectOptionV2 OptionType = "select/v2"
    33  
    34  // StringToOptionType returns the OptionType matching the provided string
    35  func StringToOptionType(s string) OptionType {
    36  	s = strings.ToLower(s)
    37  	switch s {
    38  	case "string":
    39  		return StringOption
    40  	case "bool":
    41  		return BoolOption
    42  	case "integer":
    43  		return IntegerOption
    44  	case "select":
    45  		return SelectOption
    46  	case "select/v2":
    47  		return SelectOptionV2
    48  	default:
    49  		return StringOption
    50  	}
    51  }
    52  
    53  // Option represents a flag that can be passed to the cli
    54  type Option struct {
    55  	Type        OptionType
    56  	Name        string
    57  	Short       string
    58  	EnvName     string
    59  	Description string
    60  	Required    bool
    61  	Hidden      bool
    62  	Internal    bool
    63  	Values      []OptionValue
    64  	Default     interface{}
    65  }
    66  
    67  type OptionValue struct {
    68  	Name  string `json:"name,omitempty"`
    69  	Short string `json:"short,omitempty"`
    70  	Value string `json:"value,omitempty"`
    71  }
    72  
    73  func (ov OptionValue) ResolveValue() string {
    74  	if len(ov.Value) > 0 {
    75  		return ov.Value
    76  	}
    77  	return ov.Name
    78  }
    79  
    80  // Validate returns true if the option is considered valid
    81  func (o *Option) Validate() error {
    82  	if o.Name == "" {
    83  		return fmt.Errorf("missing option name")
    84  	}
    85  
    86  	if o.Type == "" {
    87  		return fmt.Errorf("missing option type")
    88  	}
    89  
    90  	for _, ov := range o.Values {
    91  		if ov.Name == "" {
    92  			return fmt.Errorf("missing option value name")
    93  		}
    94  	}
    95  
    96  	return nil
    97  }
    98  
    99  // NewOptionsSet creates a new set of options
   100  func NewOptionsSet(name string) *OptionsSet {
   101  	return &OptionsSet{
   102  		Name:  name,
   103  		items: make(map[string]*Option, 0),
   104  	}
   105  }
   106  
   107  // Add adds options to the set
   108  func (s *OptionsSet) Add(option *Option) error {
   109  	if option == nil {
   110  		return fmt.Errorf("an option is required")
   111  	}
   112  
   113  	err := option.Validate()
   114  	if err != nil {
   115  		return err
   116  	}
   117  
   118  	err = convertDefaultValueToCorrectType(option)
   119  	if err != nil {
   120  		return err
   121  	}
   122  
   123  	names := make([]string, 0)
   124  	for k, o := range s.items {
   125  		names = append(names, k)
   126  		for _, ov := range o.Values {
   127  			names = append(names, ov.Name)
   128  		}
   129  	}
   130  
   131  	shortNames := make([]string, 0)
   132  	for _, o := range s.items {
   133  		if len(o.Short) > 0 {
   134  			shortNames = append(shortNames, o.Short)
   135  		}
   136  		for _, ov := range o.Values {
   137  			if len(ov.Short) > 0 {
   138  				shortNames = append(shortNames, ov.Short)
   139  			}
   140  		}
   141  	}
   142  
   143  	if contains(names, option.Name) {
   144  		return fmt.Errorf("an option with the name \"%s\" has already been added", option.Name)
   145  	}
   146  
   147  	if len(option.Short) > 0 && contains(shortNames, option.Short) {
   148  		return fmt.Errorf("an option with the short name \"%s\" has already been added", option.Short)
   149  	}
   150  
   151  	for _, ov := range option.Values {
   152  		if contains(names, ov.Name) {
   153  			return fmt.Errorf("an option value with the name \"%s\" has already been added", ov.Name)
   154  		}
   155  
   156  		if len(ov.Short) > 0 && contains(shortNames, ov.Short) {
   157  			return fmt.Errorf("an option with the short name \"%s\" has already been added", ov.Short)
   158  		}
   159  	}
   160  
   161  	s.items[option.Name] = option
   162  
   163  	return nil
   164  }
   165  
   166  // Sorted returns the options sorted by it's key
   167  func (s *OptionsSet) Sorted() []*Option {
   168  	keys := make([]string, 0, len(s.items))
   169  	for key := range s.items {
   170  		keys = append(keys, key)
   171  	}
   172  
   173  	sort.Strings(keys)
   174  
   175  	options := make([]*Option, 0)
   176  	for _, key := range keys {
   177  		options = append(options, s.items[key])
   178  	}
   179  
   180  	return options
   181  }
   182  
   183  func convertDefaultValueToCorrectType(option *Option) error {
   184  	var def interface{}
   185  
   186  	switch option.Type {
   187  	case SelectOption:
   188  		def = false
   189  	case SelectOptionV2:
   190  		def = false
   191  	case IntegerOption:
   192  		def = 0
   193  		switch option.Default.(type) {
   194  		case string:
   195  			if option.Default != "" {
   196  				val, err := strconv.Atoi(option.Default.(string))
   197  				if err != nil {
   198  					return err
   199  				}
   200  				def = val
   201  			}
   202  		}
   203  	case BoolOption:
   204  		def = false
   205  	case StringOption:
   206  		def = option.Default
   207  	default:
   208  		return fmt.Errorf("default value conversion not registered for type \"%s\"", option.Type)
   209  	}
   210  
   211  	option.Default = def
   212  
   213  	return nil
   214  }
   215  
   216  func contains[T comparable](s []T, e T) bool {
   217  	for _, v := range s {
   218  		if v == e {
   219  			return true
   220  		}
   221  	}
   222  	return false
   223  }