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 }