1package flags 2 3import ( 4 "bytes" 5 "fmt" 6 "os" 7 "reflect" 8 "strings" 9 "unicode/utf8" 10) 11 12// Option flag information. Contains a description of the option, short and 13// long name as well as a default value and whether an argument for this 14// flag is optional. 15type Option struct { 16 // The description of the option flag. This description is shown 17 // automatically in the built-in help. 18 Description string 19 20 // The short name of the option (a single character). If not 0, the 21 // option flag can be 'activated' using -<ShortName>. Either ShortName 22 // or LongName needs to be non-empty. 23 ShortName rune 24 25 // The long name of the option. If not "", the option flag can be 26 // activated using --<LongName>. Either ShortName or LongName needs 27 // to be non-empty. 28 LongName string 29 30 // The default value of the option. 31 Default []string 32 33 // The optional environment default value key name. 34 EnvDefaultKey string 35 36 // The optional delimiter string for EnvDefaultKey values. 37 EnvDefaultDelim string 38 39 // If true, specifies that the argument to an option flag is optional. 40 // When no argument to the flag is specified on the command line, the 41 // value of OptionalValue will be set in the field this option represents. 42 // This is only valid for non-boolean options. 43 OptionalArgument bool 44 45 // The optional value of the option. The optional value is used when 46 // the option flag is marked as having an OptionalArgument. This means 47 // that when the flag is specified, but no option argument is given, 48 // the value of the field this option represents will be set to 49 // OptionalValue. This is only valid for non-boolean options. 50 OptionalValue []string 51 52 // If true, the option _must_ be specified on the command line. If the 53 // option is not specified, the parser will generate an ErrRequired type 54 // error. 55 Required bool 56 57 // A name for the value of an option shown in the Help as --flag [ValueName] 58 ValueName string 59 60 // A mask value to show in the help instead of the default value. This 61 // is useful for hiding sensitive information in the help, such as 62 // passwords. 63 DefaultMask string 64 65 // If non empty, only a certain set of values is allowed for an option. 66 Choices []string 67 68 // If true, the option is not displayed in the help or man page 69 Hidden bool 70 71 // The group which the option belongs to 72 group *Group 73 74 // The struct field which the option represents. 75 field reflect.StructField 76 77 // The struct field value which the option represents. 78 value reflect.Value 79 80 // Determines if the option will be always quoted in the INI output 81 iniQuote bool 82 83 tag multiTag 84 isSet bool 85 isSetDefault bool 86 preventDefault bool 87 88 defaultLiteral string 89} 90 91// LongNameWithNamespace returns the option's long name with the group namespaces 92// prepended by walking up the option's group tree. Namespaces and the long name 93// itself are separated by the parser's namespace delimiter. If the long name is 94// empty an empty string is returned. 95func (option *Option) LongNameWithNamespace() string { 96 if len(option.LongName) == 0 { 97 return "" 98 } 99 100 // fetch the namespace delimiter from the parser which is always at the 101 // end of the group hierarchy 102 namespaceDelimiter := "" 103 g := option.group 104 105 for { 106 if p, ok := g.parent.(*Parser); ok { 107 namespaceDelimiter = p.NamespaceDelimiter 108 109 break 110 } 111 112 switch i := g.parent.(type) { 113 case *Command: 114 g = i.Group 115 case *Group: 116 g = i 117 } 118 } 119 120 // concatenate long name with namespace 121 longName := option.LongName 122 g = option.group 123 124 for g != nil { 125 if g.Namespace != "" { 126 longName = g.Namespace + namespaceDelimiter + longName 127 } 128 129 switch i := g.parent.(type) { 130 case *Command: 131 g = i.Group 132 case *Group: 133 g = i 134 case *Parser: 135 g = nil 136 } 137 } 138 139 return longName 140} 141 142// String converts an option to a human friendly readable string describing the 143// option. 144func (option *Option) String() string { 145 var s string 146 var short string 147 148 if option.ShortName != 0 { 149 data := make([]byte, utf8.RuneLen(option.ShortName)) 150 utf8.EncodeRune(data, option.ShortName) 151 short = string(data) 152 153 if len(option.LongName) != 0 { 154 s = fmt.Sprintf("%s%s, %s%s", 155 string(defaultShortOptDelimiter), short, 156 defaultLongOptDelimiter, option.LongNameWithNamespace()) 157 } else { 158 s = fmt.Sprintf("%s%s", string(defaultShortOptDelimiter), short) 159 } 160 } else if len(option.LongName) != 0 { 161 s = fmt.Sprintf("%s%s", defaultLongOptDelimiter, option.LongNameWithNamespace()) 162 } 163 164 return s 165} 166 167// Value returns the option value as an interface{}. 168func (option *Option) Value() interface{} { 169 return option.value.Interface() 170} 171 172// Field returns the reflect struct field of the option. 173func (option *Option) Field() reflect.StructField { 174 return option.field 175} 176 177// IsSet returns true if option has been set 178func (option *Option) IsSet() bool { 179 return option.isSet 180} 181 182// IsSetDefault returns true if option has been set via the default option tag 183func (option *Option) IsSetDefault() bool { 184 return option.isSetDefault 185} 186 187// Set the value of an option to the specified value. An error will be returned 188// if the specified value could not be converted to the corresponding option 189// value type. 190func (option *Option) set(value *string) error { 191 kind := option.value.Type().Kind() 192 193 if (kind == reflect.Map || kind == reflect.Slice) && !option.isSet { 194 option.empty() 195 } 196 197 option.isSet = true 198 option.preventDefault = true 199 200 if len(option.Choices) != 0 { 201 found := false 202 203 for _, choice := range option.Choices { 204 if choice == *value { 205 found = true 206 break 207 } 208 } 209 210 if !found { 211 allowed := strings.Join(option.Choices[0:len(option.Choices)-1], ", ") 212 213 if len(option.Choices) > 1 { 214 allowed += " or " + option.Choices[len(option.Choices)-1] 215 } 216 217 return newErrorf(ErrInvalidChoice, 218 "Invalid value `%s' for option `%s'. Allowed values are: %s", 219 *value, option, allowed) 220 } 221 } 222 223 if option.isFunc() { 224 return option.call(value) 225 } else if value != nil { 226 return convert(*value, option.value, option.tag) 227 } 228 229 return convert("", option.value, option.tag) 230} 231 232func (option *Option) canCli() bool { 233 return option.ShortName != 0 || len(option.LongName) != 0 234} 235 236func (option *Option) canArgument() bool { 237 if u := option.isUnmarshaler(); u != nil { 238 return true 239 } 240 241 return !option.isBool() 242} 243 244func (option *Option) emptyValue() reflect.Value { 245 tp := option.value.Type() 246 247 if tp.Kind() == reflect.Map { 248 return reflect.MakeMap(tp) 249 } 250 251 return reflect.Zero(tp) 252} 253 254func (option *Option) empty() { 255 if !option.isFunc() { 256 option.value.Set(option.emptyValue()) 257 } 258} 259 260func (option *Option) clearDefault() { 261 usedDefault := option.Default 262 263 if envKey := option.EnvDefaultKey; envKey != "" { 264 if value, ok := os.LookupEnv(envKey); ok { 265 if option.EnvDefaultDelim != "" { 266 usedDefault = strings.Split(value, 267 option.EnvDefaultDelim) 268 } else { 269 usedDefault = []string{value} 270 } 271 } 272 } 273 274 option.isSetDefault = true 275 276 if len(usedDefault) > 0 { 277 option.empty() 278 279 for _, d := range usedDefault { 280 option.set(&d) 281 option.isSetDefault = true 282 } 283 } else { 284 tp := option.value.Type() 285 286 switch tp.Kind() { 287 case reflect.Map: 288 if option.value.IsNil() { 289 option.empty() 290 } 291 case reflect.Slice: 292 if option.value.IsNil() { 293 option.empty() 294 } 295 } 296 } 297} 298 299func (option *Option) valueIsDefault() bool { 300 // Check if the value of the option corresponds to its 301 // default value 302 emptyval := option.emptyValue() 303 304 checkvalptr := reflect.New(emptyval.Type()) 305 checkval := reflect.Indirect(checkvalptr) 306 307 checkval.Set(emptyval) 308 309 if len(option.Default) != 0 { 310 for _, v := range option.Default { 311 convert(v, checkval, option.tag) 312 } 313 } 314 315 return reflect.DeepEqual(option.value.Interface(), checkval.Interface()) 316} 317 318func (option *Option) isUnmarshaler() Unmarshaler { 319 v := option.value 320 321 for { 322 if !v.CanInterface() { 323 break 324 } 325 326 i := v.Interface() 327 328 if u, ok := i.(Unmarshaler); ok { 329 return u 330 } 331 332 if !v.CanAddr() { 333 break 334 } 335 336 v = v.Addr() 337 } 338 339 return nil 340} 341 342func (option *Option) isBool() bool { 343 tp := option.value.Type() 344 345 for { 346 switch tp.Kind() { 347 case reflect.Slice, reflect.Ptr: 348 tp = tp.Elem() 349 case reflect.Bool: 350 return true 351 case reflect.Func: 352 return tp.NumIn() == 0 353 default: 354 return false 355 } 356 } 357} 358 359func (option *Option) isSignedNumber() bool { 360 tp := option.value.Type() 361 362 for { 363 switch tp.Kind() { 364 case reflect.Slice, reflect.Ptr: 365 tp = tp.Elem() 366 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Float32, reflect.Float64: 367 return true 368 default: 369 return false 370 } 371 } 372} 373 374func (option *Option) isFunc() bool { 375 return option.value.Type().Kind() == reflect.Func 376} 377 378func (option *Option) call(value *string) error { 379 var retval []reflect.Value 380 381 if value == nil { 382 retval = option.value.Call(nil) 383 } else { 384 tp := option.value.Type().In(0) 385 386 val := reflect.New(tp) 387 val = reflect.Indirect(val) 388 389 if err := convert(*value, val, option.tag); err != nil { 390 return err 391 } 392 393 retval = option.value.Call([]reflect.Value{val}) 394 } 395 396 if len(retval) == 1 && retval[0].Type() == reflect.TypeOf((*error)(nil)).Elem() { 397 if retval[0].Interface() == nil { 398 return nil 399 } 400 401 return retval[0].Interface().(error) 402 } 403 404 return nil 405} 406 407func (option *Option) updateDefaultLiteral() { 408 defs := option.Default 409 def := "" 410 411 if len(defs) == 0 && option.canArgument() { 412 var showdef bool 413 414 switch option.field.Type.Kind() { 415 case reflect.Func, reflect.Ptr: 416 showdef = !option.value.IsNil() 417 case reflect.Slice, reflect.String, reflect.Array: 418 showdef = option.value.Len() > 0 419 case reflect.Map: 420 showdef = !option.value.IsNil() && option.value.Len() > 0 421 default: 422 zeroval := reflect.Zero(option.field.Type) 423 showdef = !reflect.DeepEqual(zeroval.Interface(), option.value.Interface()) 424 } 425 426 if showdef { 427 def, _ = convertToString(option.value, option.tag) 428 } 429 } else if len(defs) != 0 { 430 l := len(defs) - 1 431 432 for i := 0; i < l; i++ { 433 def += quoteIfNeeded(defs[i]) + ", " 434 } 435 436 def += quoteIfNeeded(defs[l]) 437 } 438 439 option.defaultLiteral = def 440} 441 442func (option *Option) shortAndLongName() string { 443 ret := &bytes.Buffer{} 444 445 if option.ShortName != 0 { 446 ret.WriteRune(defaultShortOptDelimiter) 447 ret.WriteRune(option.ShortName) 448 } 449 450 if len(option.LongName) != 0 { 451 if option.ShortName != 0 { 452 ret.WriteRune('/') 453 } 454 455 ret.WriteString(option.LongName) 456 } 457 458 return ret.String() 459} 460