570 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			570 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
| 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
 | |
| 	clearReferenceBeforeSet 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
 | |
| }
 | |
| 
 | |
| // EnvKeyWithNamespace returns the option's env key with the group namespaces
 | |
| // prepended by walking up the option's group tree. Namespaces and the env key
 | |
| // itself are separated by the parser's namespace delimiter. If the env key is
 | |
| // empty an empty string is returned.
 | |
| func (option *Option) EnvKeyWithNamespace() string {
 | |
| 	if len(option.EnvDefaultKey) == 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.EnvNamespaceDelimiter
 | |
| 
 | |
| 			break
 | |
| 		}
 | |
| 
 | |
| 		switch i := g.parent.(type) {
 | |
| 		case *Command:
 | |
| 			g = i.Group
 | |
| 		case *Group:
 | |
| 			g = i
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// concatenate long name with namespace
 | |
| 	key := option.EnvDefaultKey
 | |
| 	g = option.group
 | |
| 
 | |
| 	for g != nil {
 | |
| 		if g.EnvNamespace != "" {
 | |
| 			key = g.EnvNamespace + namespaceDelimiter + key
 | |
| 		}
 | |
| 
 | |
| 		switch i := g.parent.(type) {
 | |
| 		case *Command:
 | |
| 			g = i.Group
 | |
| 		case *Group:
 | |
| 			g = i
 | |
| 		case *Parser:
 | |
| 			g = nil
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return key
 | |
| }
 | |
| 
 | |
| // 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.clearReferenceBeforeSet {
 | |
| 		option.empty()
 | |
| 	}
 | |
| 
 | |
| 	option.isSet = true
 | |
| 	option.preventDefault = true
 | |
| 	option.clearReferenceBeforeSet = false
 | |
| 
 | |
| 	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) setDefault(value *string) error {
 | |
| 	if option.preventDefault {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	if err := option.set(value); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	option.isSetDefault = true
 | |
| 	option.preventDefault = false
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (option *Option) showInHelp() bool {
 | |
| 	return !option.Hidden && (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() error {
 | |
| 	if option.preventDefault {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	usedDefault := option.Default
 | |
| 
 | |
| 	if envKey := option.EnvKeyWithNamespace(); 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 {
 | |
| 			err := option.setDefault(&d)
 | |
| 
 | |
| 			if err != nil {
 | |
| 				return err
 | |
| 			}
 | |
| 		}
 | |
| 	} 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()
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| 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) isValueValidator() ValueValidator {
 | |
| 	v := option.value
 | |
| 
 | |
| 	for {
 | |
| 		if !v.CanInterface() {
 | |
| 			break
 | |
| 		}
 | |
| 
 | |
| 		i := v.Interface()
 | |
| 
 | |
| 		if u, ok := i.(ValueValidator); 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()
 | |
| }
 | |
| 
 | |
| func (option *Option) isValidValue(arg string) error {
 | |
| 	if validator := option.isValueValidator(); validator != nil {
 | |
| 		return validator.IsValidValue(arg)
 | |
| 	}
 | |
| 	if argumentIsOption(arg) && !(option.isSignedNumber() && len(arg) > 1 && arg[0] == '-' && arg[1] >= '0' && arg[1] <= '9') {
 | |
| 		return fmt.Errorf("expected argument for flag `%s', but got option `%s'", option, arg)
 | |
| 	}
 | |
| 	return nil
 | |
| }
 |