option.go 4.07 KB
Newer Older
Steven Allen's avatar
Steven Allen committed
1
package cmds
2 3 4 5

import (
	"fmt"
	"reflect"
6
	"strconv"
7 8 9 10 11 12 13 14 15
	"strings"
)

// Types of Command options
const (
	Invalid = reflect.Invalid
	Bool    = reflect.Bool
	Int     = reflect.Int
	Uint    = reflect.Uint
Overbool's avatar
Overbool committed
16 17
	Int64   = reflect.Int64
	Uint64  = reflect.Uint64
18 19
	Float   = reflect.Float64
	String  = reflect.String
20
	Strings = reflect.Array
21 22 23 24 25 26
)

type OptMap map[string]interface{}

// Option is used to specify a field that will be provided by a consumer
type Option interface {
27 28 29
	Name() string    // the main name of the option
	Names() []string // a list of unique names matched with user-provided flags

30 31 32 33 34
	Type() reflect.Kind  // value must be this type
	Description() string // a short string that describes this option

	WithDefault(interface{}) Option // sets the default value of the option
	Default() interface{}
keks's avatar
keks committed
35

36
	Parse(str string) (interface{}, error)
37 38 39 40 41 42 43
}

type option struct {
	names       []string
	kind        reflect.Kind
	description string
	defaultVal  interface{}
44
}
keks's avatar
keks committed
45

46 47
func (o *option) Name() string {
	return o.names[0]
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
}

func (o *option) Names() []string {
	return o.names
}

func (o *option) Type() reflect.Kind {
	return o.kind
}

func (o *option) Description() string {
	if len(o.description) == 0 {
		return ""
	}
	if !strings.HasSuffix(o.description, ".") {
		o.description += "."
	}
	if o.defaultVal != nil {
		if strings.Contains(o.description, "<<default>>") {
			return strings.Replace(o.description, "<<default>>",
				fmt.Sprintf("Default: %v.", o.defaultVal), -1)
		} else {
			return fmt.Sprintf("%s Default: %v.", o.description, o.defaultVal)
		}
	}
	return o.description
}

76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
type converter func(string) (interface{}, error)

var converters = map[reflect.Kind]converter{
	Bool: func(v string) (interface{}, error) {
		if v == "" {
			return true, nil
		}
		v = strings.ToLower(v)

		return strconv.ParseBool(v)
	},
	Int: func(v string) (interface{}, error) {
		val, err := strconv.ParseInt(v, 0, 32)
		if err != nil {
			return nil, err
		}
		return int(val), err
	},
	Uint: func(v string) (interface{}, error) {
		val, err := strconv.ParseUint(v, 0, 32)
		if err != nil {
			return nil, err
		}
Dirk McCormick's avatar
Dirk McCormick committed
99
		return uint(val), err
100
	},
Overbool's avatar
Overbool committed
101 102 103 104 105 106 107 108 109 110 111 112 113 114
	Int64: func(v string) (interface{}, error) {
		val, err := strconv.ParseInt(v, 0, 64)
		if err != nil {
			return nil, err
		}
		return val, err
	},
	Uint64: func(v string) (interface{}, error) {
		val, err := strconv.ParseUint(v, 0, 64)
		if err != nil {
			return nil, err
		}
		return val, err
	},
115 116 117 118 119 120
	Float: func(v string) (interface{}, error) {
		return strconv.ParseFloat(v, 64)
	},
	String: func(v string) (interface{}, error) {
		return v, nil
	},
121 122 123
	Strings: func(v string) (interface{}, error) {
		return v, nil
	},
124 125 126 127 128
}

func (o *option) Parse(v string) (interface{}, error) {
	conv, ok := converters[o.Type()]
	if !ok {
Steven Allen's avatar
Steven Allen committed
129
		return nil, fmt.Errorf("option %q takes %s arguments, but was passed %q", o.Name(), o.Type(), v)
130 131 132 133 134
	}

	return conv(v)
}

135 136
// constructor helper functions
func NewOption(kind reflect.Kind, names ...string) Option {
keks's avatar
keks committed
137
	var desc string
138

keks's avatar
keks committed
139 140 141 142
	if len(names) >= 2 {
		desc = names[len(names)-1]
		names = names[:len(names)-1]
	}
143 144 145 146 147 148 149 150

	return &option{
		names:       names,
		kind:        kind,
		description: desc,
	}
}

151
func (o *option) WithDefault(v interface{}) Option {
152 153 154 155
	o.defaultVal = v
	return o
}

156
func (o *option) Default() interface{} {
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
	return o.defaultVal
}

// TODO handle description separately. this will take care of the panic case in
// NewOption

// For all func {Type}Option(...string) functions, the last variadic argument
// is treated as the description field.

func BoolOption(names ...string) Option {
	return NewOption(Bool, names...)
}
func IntOption(names ...string) Option {
	return NewOption(Int, names...)
}
func UintOption(names ...string) Option {
	return NewOption(Uint, names...)
}
Overbool's avatar
Overbool committed
175 176 177 178 179 180
func Int64Option(names ...string) Option {
	return NewOption(Int64, names...)
}
func Uint64Option(names ...string) Option {
	return NewOption(Uint64, names...)
}
181 182 183 184 185 186
func FloatOption(names ...string) Option {
	return NewOption(Float, names...)
}
func StringOption(names ...string) Option {
	return NewOption(String, names...)
}
187 188 189
func StringsOption(names ...string) Option {
	return NewOption(Strings, names...)
}