1package flags 2 3import ( 4 "bytes" 5 "fmt" 6 "reflect" 7 "strings" 8 "syscall" 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// IsSet 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 // os.Getenv() makes no distinction between undefined and 265 // empty values, so we use syscall.Getenv() 266 if value, ok := syscall.Getenv(envKey); ok { 267 if option.EnvDefaultDelim != "" { 268 usedDefault = strings.Split(value, 269 option.EnvDefaultDelim) 270 } else { 271 usedDefault = []string{value} 272 } 273 } 274 } 275 276 option.isSetDefault = true 277 278 if len(usedDefault) > 0 { 279 option.empty() 280 281 for _, d := range usedDefault { 282 option.set(&d) 283 option.isSetDefault = true 284 } 285 } else { 286 tp := option.value.Type() 287 288 switch tp.Kind() { 289 case reflect.Map: 290 if option.value.IsNil() { 291 option.empty() 292 } 293 case reflect.Slice: 294 if option.value.IsNil() { 295 option.empty() 296 } 297 } 298 } 299} 300 301func (option *Option) valueIsDefault() bool { 302 // Check if the value of the option corresponds to its 303 // default value 304 emptyval := option.emptyValue() 305 306 checkvalptr := reflect.New(emptyval.Type()) 307 checkval := reflect.Indirect(checkvalptr) 308 309 checkval.Set(emptyval) 310 311 if len(option.Default) != 0 { 312 for _, v := range option.Default { 313 convert(v, checkval, option.tag) 314 } 315 } 316 317 return reflect.DeepEqual(option.value.Interface(), checkval.Interface()) 318} 319 320func (option *Option) isUnmarshaler() Unmarshaler { 321 v := option.value 322 323 for { 324 if !v.CanInterface() { 325 break 326 } 327 328 i := v.Interface() 329 330 if u, ok := i.(Unmarshaler); ok { 331 return u 332 } 333 334 if !v.CanAddr() { 335 break 336 } 337 338 v = v.Addr() 339 } 340 341 return nil 342} 343 344func (option *Option) isBool() bool { 345 tp := option.value.Type() 346 347 for { 348 switch tp.Kind() { 349 case reflect.Slice, reflect.Ptr: 350 tp = tp.Elem() 351 case reflect.Bool: 352 return true 353 case reflect.Func: 354 return tp.NumIn() == 0 355 default: 356 return false 357 } 358 } 359} 360 361func (option *Option) isSignedNumber() bool { 362 tp := option.value.Type() 363 364 for { 365 switch tp.Kind() { 366 case reflect.Slice, reflect.Ptr: 367 tp = tp.Elem() 368 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Float32, reflect.Float64: 369 return true 370 default: 371 return false 372 } 373 } 374} 375 376func (option *Option) isFunc() bool { 377 return option.value.Type().Kind() == reflect.Func 378} 379 380func (option *Option) call(value *string) error { 381 var retval []reflect.Value 382 383 if value == nil { 384 retval = option.value.Call(nil) 385 } else { 386 tp := option.value.Type().In(0) 387 388 val := reflect.New(tp) 389 val = reflect.Indirect(val) 390 391 if err := convert(*value, val, option.tag); err != nil { 392 return err 393 } 394 395 retval = option.value.Call([]reflect.Value{val}) 396 } 397 398 if len(retval) == 1 && retval[0].Type() == reflect.TypeOf((*error)(nil)).Elem() { 399 if retval[0].Interface() == nil { 400 return nil 401 } 402 403 return retval[0].Interface().(error) 404 } 405 406 return nil 407} 408 409func (option *Option) updateDefaultLiteral() { 410 defs := option.Default 411 def := "" 412 413 if len(defs) == 0 && option.canArgument() { 414 var showdef bool 415 416 switch option.field.Type.Kind() { 417 case reflect.Func, reflect.Ptr: 418 showdef = !option.value.IsNil() 419 case reflect.Slice, reflect.String, reflect.Array: 420 showdef = option.value.Len() > 0 421 case reflect.Map: 422 showdef = !option.value.IsNil() && option.value.Len() > 0 423 default: 424 zeroval := reflect.Zero(option.field.Type) 425 showdef = !reflect.DeepEqual(zeroval.Interface(), option.value.Interface()) 426 } 427 428 if showdef { 429 def, _ = convertToString(option.value, option.tag) 430 } 431 } else if len(defs) != 0 { 432 l := len(defs) - 1 433 434 for i := 0; i < l; i++ { 435 def += quoteIfNeeded(defs[i]) + ", " 436 } 437 438 def += quoteIfNeeded(defs[l]) 439 } 440 441 option.defaultLiteral = def 442} 443 444func (option *Option) shortAndLongName() string { 445 ret := &bytes.Buffer{} 446 447 if option.ShortName != 0 { 448 ret.WriteRune(defaultShortOptDelimiter) 449 ret.WriteRune(option.ShortName) 450 } 451 452 if len(option.LongName) != 0 { 453 if option.ShortName != 0 { 454 ret.WriteRune('/') 455 } 456 457 ret.WriteString(option.LongName) 458 } 459 460 return ret.String() 461} 462