| package flags |
| |
| import ( |
| "bytes" |
| "fmt" |
| "os" |
| "reflect" |
| "strings" |
| "unicode/utf8" |
| ) |
| |
| // Option flag information. Contains a description of the option, short and |
| // long name as well as a default value and whether an argument for this |
| // flag is optional. |
| type Option struct { |
| // The description of the option flag. This description is shown |
| // automatically in the built-in help. |
| Description string |
| |
| // The short name of the option (a single character). If not 0, the |
| // option flag can be 'activated' using -<ShortName>. Either ShortName |
| // or LongName needs to be non-empty. |
| ShortName rune |
| |
| // The long name of the option. If not "", the option flag can be |
| // activated using --<LongName>. Either ShortName or LongName needs |
| // to be non-empty. |
| LongName string |
| |
| // The default value of the option. |
| Default []string |
| |
| // The optional environment default value key name. |
| EnvDefaultKey string |
| |
| // The optional delimiter string for EnvDefaultKey values. |
| EnvDefaultDelim string |
| |
| // If true, specifies that the argument to an option flag is optional. |
| // When no argument to the flag is specified on the command line, the |
| // value of OptionalValue will be set in the field this option represents. |
| // This is only valid for non-boolean options. |
| OptionalArgument bool |
| |
| // The optional value of the option. The optional value is used when |
| // the option flag is marked as having an OptionalArgument. This means |
| // that when the flag is specified, but no option argument is given, |
| // the value of the field this option represents will be set to |
| // OptionalValue. This is only valid for non-boolean options. |
| OptionalValue []string |
| |
| // If true, the option _must_ be specified on the command line. If the |
| // option is not specified, the parser will generate an ErrRequired type |
| // error. |
| Required bool |
| |
| // A name for the value of an option shown in the Help as --flag [ValueName] |
| ValueName string |
| |
| // A mask value to show in the help instead of the default value. This |
| // is useful for hiding sensitive information in the help, such as |
| // passwords. |
| DefaultMask string |
| |
| // If non empty, only a certain set of values is allowed for an option. |
| Choices []string |
| |
| // If true, the option is not displayed in the help or man page |
| Hidden bool |
| |
| // The group which the option belongs to |
| group *Group |
| |
| // The struct field which the option represents. |
| field reflect.StructField |
| |
| // The struct field value which the option represents. |
| value reflect.Value |
| |
| // Determines if the option will be always quoted in the INI output |
| iniQuote bool |
| |
| tag multiTag |
| isSet bool |
| isSetDefault bool |
| preventDefault bool |
| |
| defaultLiteral string |
| } |
| |
| // LongNameWithNamespace returns the option's long name with the group namespaces |
| // prepended by walking up the option's group tree. Namespaces and the long name |
| // itself are separated by the parser's namespace delimiter. If the long name is |
| // empty an empty string is returned. |
| func (option *Option) LongNameWithNamespace() string { |
| if len(option.LongName) == 0 { |
| return "" |
| } |
| |
| // fetch the namespace delimiter from the parser which is always at the |
| // end of the group hierarchy |
| namespaceDelimiter := "" |
| g := option.group |
| |
| for { |
| if p, ok := g.parent.(*Parser); ok { |
| namespaceDelimiter = p.NamespaceDelimiter |
| |
| break |
| } |
| |
| switch i := g.parent.(type) { |
| case *Command: |
| g = i.Group |
| case *Group: |
| g = i |
| } |
| } |
| |
| // concatenate long name with namespace |
| longName := option.LongName |
| g = option.group |
| |
| for g != nil { |
| if g.Namespace != "" { |
| longName = g.Namespace + namespaceDelimiter + longName |
| } |
| |
| switch i := g.parent.(type) { |
| case *Command: |
| g = i.Group |
| case *Group: |
| g = i |
| case *Parser: |
| g = nil |
| } |
| } |
| |
| return longName |
| } |
| |
| // String converts an option to a human friendly readable string describing the |
| // option. |
| func (option *Option) String() string { |
| var s string |
| var short string |
| |
| if option.ShortName != 0 { |
| data := make([]byte, utf8.RuneLen(option.ShortName)) |
| utf8.EncodeRune(data, option.ShortName) |
| short = string(data) |
| |
| if len(option.LongName) != 0 { |
| s = fmt.Sprintf("%s%s, %s%s", |
| string(defaultShortOptDelimiter), short, |
| defaultLongOptDelimiter, option.LongNameWithNamespace()) |
| } else { |
| s = fmt.Sprintf("%s%s", string(defaultShortOptDelimiter), short) |
| } |
| } else if len(option.LongName) != 0 { |
| s = fmt.Sprintf("%s%s", defaultLongOptDelimiter, option.LongNameWithNamespace()) |
| } |
| |
| return s |
| } |
| |
| // Value returns the option value as an interface{}. |
| func (option *Option) Value() interface{} { |
| return option.value.Interface() |
| } |
| |
| // Field returns the reflect struct field of the option. |
| func (option *Option) Field() reflect.StructField { |
| return option.field |
| } |
| |
| // IsSet returns true if option has been set |
| func (option *Option) IsSet() bool { |
| return option.isSet |
| } |
| |
| // IsSetDefault returns true if option has been set via the default option tag |
| func (option *Option) IsSetDefault() bool { |
| return option.isSetDefault |
| } |
| |
| // Set the value of an option to the specified value. An error will be returned |
| // if the specified value could not be converted to the corresponding option |
| // value type. |
| func (option *Option) set(value *string) error { |
| kind := option.value.Type().Kind() |
| |
| if (kind == reflect.Map || kind == reflect.Slice) && !option.isSet { |
| option.empty() |
| } |
| |
| option.isSet = true |
| option.preventDefault = true |
| |
| if len(option.Choices) != 0 { |
| found := false |
| |
| for _, choice := range option.Choices { |
| if choice == *value { |
| found = true |
| break |
| } |
| } |
| |
| if !found { |
| allowed := strings.Join(option.Choices[0:len(option.Choices)-1], ", ") |
| |
| if len(option.Choices) > 1 { |
| allowed += " or " + option.Choices[len(option.Choices)-1] |
| } |
| |
| return newErrorf(ErrInvalidChoice, |
| "Invalid value `%s' for option `%s'. Allowed values are: %s", |
| *value, option, allowed) |
| } |
| } |
| |
| if option.isFunc() { |
| return option.call(value) |
| } else if value != nil { |
| return convert(*value, option.value, option.tag) |
| } |
| |
| return convert("", option.value, option.tag) |
| } |
| |
| func (option *Option) canCli() bool { |
| return option.ShortName != 0 || len(option.LongName) != 0 |
| } |
| |
| func (option *Option) canArgument() bool { |
| if u := option.isUnmarshaler(); u != nil { |
| return true |
| } |
| |
| return !option.isBool() |
| } |
| |
| func (option *Option) emptyValue() reflect.Value { |
| tp := option.value.Type() |
| |
| if tp.Kind() == reflect.Map { |
| return reflect.MakeMap(tp) |
| } |
| |
| return reflect.Zero(tp) |
| } |
| |
| func (option *Option) empty() { |
| if !option.isFunc() { |
| option.value.Set(option.emptyValue()) |
| } |
| } |
| |
| func (option *Option) clearDefault() { |
| usedDefault := option.Default |
| |
| if envKey := option.EnvDefaultKey; envKey != "" { |
| if value, ok := os.LookupEnv(envKey); ok { |
| if option.EnvDefaultDelim != "" { |
| usedDefault = strings.Split(value, |
| option.EnvDefaultDelim) |
| } else { |
| usedDefault = []string{value} |
| } |
| } |
| } |
| |
| option.isSetDefault = true |
| |
| if len(usedDefault) > 0 { |
| option.empty() |
| |
| for _, d := range usedDefault { |
| option.set(&d) |
| option.isSetDefault = true |
| } |
| } else { |
| tp := option.value.Type() |
| |
| switch tp.Kind() { |
| case reflect.Map: |
| if option.value.IsNil() { |
| option.empty() |
| } |
| case reflect.Slice: |
| if option.value.IsNil() { |
| option.empty() |
| } |
| } |
| } |
| } |
| |
| func (option *Option) valueIsDefault() bool { |
| // Check if the value of the option corresponds to its |
| // default value |
| emptyval := option.emptyValue() |
| |
| checkvalptr := reflect.New(emptyval.Type()) |
| checkval := reflect.Indirect(checkvalptr) |
| |
| checkval.Set(emptyval) |
| |
| if len(option.Default) != 0 { |
| for _, v := range option.Default { |
| convert(v, checkval, option.tag) |
| } |
| } |
| |
| return reflect.DeepEqual(option.value.Interface(), checkval.Interface()) |
| } |
| |
| func (option *Option) isUnmarshaler() Unmarshaler { |
| v := option.value |
| |
| for { |
| if !v.CanInterface() { |
| break |
| } |
| |
| i := v.Interface() |
| |
| if u, ok := i.(Unmarshaler); ok { |
| return u |
| } |
| |
| if !v.CanAddr() { |
| break |
| } |
| |
| v = v.Addr() |
| } |
| |
| return nil |
| } |
| |
| func (option *Option) isBool() bool { |
| tp := option.value.Type() |
| |
| for { |
| switch tp.Kind() { |
| case reflect.Slice, reflect.Ptr: |
| tp = tp.Elem() |
| case reflect.Bool: |
| return true |
| case reflect.Func: |
| return tp.NumIn() == 0 |
| default: |
| return false |
| } |
| } |
| } |
| |
| func (option *Option) isSignedNumber() bool { |
| tp := option.value.Type() |
| |
| for { |
| switch tp.Kind() { |
| case reflect.Slice, reflect.Ptr: |
| tp = tp.Elem() |
| case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Float32, reflect.Float64: |
| return true |
| default: |
| return false |
| } |
| } |
| } |
| |
| func (option *Option) isFunc() bool { |
| return option.value.Type().Kind() == reflect.Func |
| } |
| |
| func (option *Option) call(value *string) error { |
| var retval []reflect.Value |
| |
| if value == nil { |
| retval = option.value.Call(nil) |
| } else { |
| tp := option.value.Type().In(0) |
| |
| val := reflect.New(tp) |
| val = reflect.Indirect(val) |
| |
| if err := convert(*value, val, option.tag); err != nil { |
| return err |
| } |
| |
| retval = option.value.Call([]reflect.Value{val}) |
| } |
| |
| if len(retval) == 1 && retval[0].Type() == reflect.TypeOf((*error)(nil)).Elem() { |
| if retval[0].Interface() == nil { |
| return nil |
| } |
| |
| return retval[0].Interface().(error) |
| } |
| |
| return nil |
| } |
| |
| func (option *Option) updateDefaultLiteral() { |
| defs := option.Default |
| def := "" |
| |
| if len(defs) == 0 && option.canArgument() { |
| var showdef bool |
| |
| switch option.field.Type.Kind() { |
| case reflect.Func, reflect.Ptr: |
| showdef = !option.value.IsNil() |
| case reflect.Slice, reflect.String, reflect.Array: |
| showdef = option.value.Len() > 0 |
| case reflect.Map: |
| showdef = !option.value.IsNil() && option.value.Len() > 0 |
| default: |
| zeroval := reflect.Zero(option.field.Type) |
| showdef = !reflect.DeepEqual(zeroval.Interface(), option.value.Interface()) |
| } |
| |
| if showdef { |
| def, _ = convertToString(option.value, option.tag) |
| } |
| } else if len(defs) != 0 { |
| l := len(defs) - 1 |
| |
| for i := 0; i < l; i++ { |
| def += quoteIfNeeded(defs[i]) + ", " |
| } |
| |
| def += quoteIfNeeded(defs[l]) |
| } |
| |
| option.defaultLiteral = def |
| } |
| |
| func (option *Option) shortAndLongName() string { |
| ret := &bytes.Buffer{} |
| |
| if option.ShortName != 0 { |
| ret.WriteRune(defaultShortOptDelimiter) |
| ret.WriteRune(option.ShortName) |
| } |
| |
| if len(option.LongName) != 0 { |
| if option.ShortName != 0 { |
| ret.WriteRune('/') |
| } |
| |
| ret.WriteString(option.LongName) |
| } |
| |
| return ret.String() |
| } |