518 lines
12 KiB
Go
518 lines
12 KiB
Go
package cli
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/labstack/gommon/color"
|
|
"github.com/mkideal/expr"
|
|
)
|
|
|
|
type flag struct {
|
|
field reflect.StructField
|
|
value reflect.Value
|
|
|
|
// isAssigned indicates whether the flag is set(contains default value)
|
|
isAssigned bool
|
|
|
|
// isSet indicates whether the flag is set
|
|
isSet bool
|
|
|
|
// tag properties
|
|
tag tagProperty
|
|
|
|
// actual flag name
|
|
actualFlagName string
|
|
|
|
isNeedDelaySet bool
|
|
|
|
// last value for need delay set
|
|
// flag maybe assigned too many times, like:
|
|
// -f xx -f yy -f zz
|
|
// `zz` is the last value
|
|
lastValue string
|
|
}
|
|
|
|
func newFlag(field reflect.StructField, value reflect.Value, tag *tagProperty, clr color.Color, dontSetValue bool) (fl *flag, err error) {
|
|
fl = &flag{field: field, value: value}
|
|
if !fl.value.CanSet() {
|
|
return nil, fmt.Errorf("field %s can not set", clr.Bold(fl.field.Name))
|
|
}
|
|
fl.tag = *tag
|
|
if fl.isPtr() && fl.value.IsNil() {
|
|
fl.value.Set(reflect.New(fl.field.Type.Elem()))
|
|
}
|
|
isSliceDecoder := fl.value.Type().Implements(reflect.TypeOf((*SliceDecoder)(nil)).Elem())
|
|
if !isSliceDecoder && fl.value.CanAddr() {
|
|
isSliceDecoder = fl.value.Addr().Type().Implements(reflect.TypeOf((*SliceDecoder)(nil)).Elem())
|
|
}
|
|
fl.isNeedDelaySet = fl.tag.parserCreator != nil ||
|
|
(fl.field.Type.Kind() != reflect.Slice && fl.field.Type.Kind() != reflect.Map && !isSliceDecoder)
|
|
err = fl.init(clr, dontSetValue)
|
|
return
|
|
}
|
|
|
|
func (fl *flag) init(clr color.Color, dontSetValue bool) error {
|
|
var (
|
|
isNumber = fl.isInteger() || fl.isFloat()
|
|
isDecoder = fl.value.Type().Implements(reflect.TypeOf((*Decoder)(nil)).Elem())
|
|
dft string
|
|
err error
|
|
)
|
|
if !isDecoder && fl.value.CanAddr() {
|
|
isDecoder = fl.value.Addr().Type().Implements(reflect.TypeOf((*Decoder)(nil)).Elem())
|
|
}
|
|
dft, err = parseExpression(fl.tag.dft, isNumber)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if isNumber && !isDecoder {
|
|
v, err := expr.Eval(dft, nil, nil)
|
|
if err == nil {
|
|
if fl.isInteger() {
|
|
dft = fmt.Sprintf("%d", v.Int())
|
|
} else if fl.isFloat() {
|
|
dft = fmt.Sprintf("%f", v.Float())
|
|
}
|
|
}
|
|
}
|
|
if !dontSetValue && fl.tag.dft != "" && dft != "" {
|
|
if fl.isPtr() || isDecoder || isEmpty(fl.value) {
|
|
return fl.setDefault(dft, clr)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func isEmpty(v reflect.Value) bool {
|
|
switch v.Kind() {
|
|
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
|
|
return v.Len() == 0
|
|
case reflect.Bool:
|
|
return !v.Bool()
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
return v.Int() == 0
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
|
return v.Uint() == 0
|
|
case reflect.Float32, reflect.Float64:
|
|
return v.Float() == 0
|
|
case reflect.Interface, reflect.Ptr:
|
|
return v.IsNil()
|
|
}
|
|
return false
|
|
}
|
|
|
|
func isWordByte(b byte) bool {
|
|
return (b >= 'a' && b <= 'z') ||
|
|
(b >= 'A' && b <= 'Z') ||
|
|
(b >= '0' && b <= '9') ||
|
|
b == '_'
|
|
}
|
|
|
|
const (
|
|
builtinVar_EXEC_PATH = "__EXEC_PATH"
|
|
builtinVar_EXEC_FILENAME = "__EXEC_FILENAME"
|
|
)
|
|
|
|
func parseExpression(s string, isNumber bool) (string, error) {
|
|
const escapeByte = '$'
|
|
var (
|
|
src = []byte(s)
|
|
escaping = false
|
|
exprBuf bytes.Buffer
|
|
envvarBuf bytes.Buffer
|
|
)
|
|
writeEnv := func(envName string) error {
|
|
if envName == "" {
|
|
return fmt.Errorf("unexpected end after %v", escapeByte)
|
|
}
|
|
var value string
|
|
switch envName {
|
|
case builtinVar_EXEC_PATH:
|
|
filename, err := os.Executable()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
value = filepath.Dir(filename)
|
|
case builtinVar_EXEC_FILENAME:
|
|
filename, err := os.Executable()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if runtime.GOOS == "windows" {
|
|
value = strings.TrimSuffix(filename, ".exe")
|
|
} else {
|
|
value = filename
|
|
}
|
|
default:
|
|
value = os.Getenv(envName)
|
|
if value == "" && isNumber {
|
|
value = "0"
|
|
}
|
|
}
|
|
exprBuf.WriteString(value)
|
|
return nil
|
|
}
|
|
for i, b := range src {
|
|
if b == escapeByte {
|
|
if escaping && envvarBuf.Len() == 0 {
|
|
exprBuf.WriteByte(b)
|
|
escaping = false
|
|
} else {
|
|
escaping = true
|
|
if i+1 == len(src) {
|
|
return "", fmt.Errorf("unexpected end after %v", escapeByte)
|
|
}
|
|
envvarBuf.Reset()
|
|
}
|
|
continue
|
|
}
|
|
if escaping {
|
|
if isWordByte(b) {
|
|
envvarBuf.WriteByte(b)
|
|
if i+1 == len(src) {
|
|
if err := writeEnv(envvarBuf.String()); err != nil {
|
|
return "", err
|
|
}
|
|
}
|
|
} else {
|
|
if err := writeEnv(envvarBuf.String()); err != nil {
|
|
return "", err
|
|
}
|
|
exprBuf.WriteByte(b)
|
|
envvarBuf.Reset()
|
|
escaping = false
|
|
}
|
|
} else {
|
|
exprBuf.WriteByte(b)
|
|
}
|
|
}
|
|
return exprBuf.String(), nil
|
|
}
|
|
|
|
func (fl *flag) name() string {
|
|
if fl.actualFlagName != "" {
|
|
return fl.actualFlagName
|
|
}
|
|
if len(fl.tag.longNames) > 0 {
|
|
return fl.tag.longNames[0]
|
|
}
|
|
if len(fl.tag.shortNames) > 0 {
|
|
return fl.tag.shortNames[0]
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (fl *flag) isBoolean() bool {
|
|
return fl.field.Type.Kind() == reflect.Bool
|
|
}
|
|
|
|
func (fl *flag) isInteger() bool {
|
|
switch fl.field.Type.Kind() {
|
|
case reflect.Int,
|
|
reflect.Int8,
|
|
reflect.Int16,
|
|
reflect.Int32,
|
|
reflect.Int64,
|
|
reflect.Uint,
|
|
reflect.Uint8,
|
|
reflect.Uint16,
|
|
reflect.Uint32,
|
|
reflect.Uint64:
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (fl *flag) isSlice() bool {
|
|
return fl.field.Type.Kind() == reflect.Slice
|
|
}
|
|
|
|
func (fl *flag) isMap() bool {
|
|
return fl.field.Type.Kind() == reflect.Map
|
|
}
|
|
|
|
func (fl *flag) isFloat() bool {
|
|
kind := fl.field.Type.Kind()
|
|
return kind == reflect.Float32 || kind == reflect.Float64
|
|
}
|
|
|
|
func (fl *flag) isString() bool {
|
|
return fl.field.Type.Kind() == reflect.String
|
|
}
|
|
|
|
func (fl *flag) isPtr() bool {
|
|
return fl.field.Type.Kind() == reflect.Ptr
|
|
}
|
|
|
|
func (fl *flag) getBool() bool {
|
|
if !fl.isBoolean() {
|
|
return false
|
|
}
|
|
return fl.value.Bool()
|
|
}
|
|
|
|
func (fl *flag) setDefault(s string, clr color.Color) error {
|
|
fl.isAssigned = true
|
|
if fl.isNeedDelaySet {
|
|
fl.lastValue = s
|
|
return nil
|
|
}
|
|
return setWithProperType(fl, fl.field.Type, fl.value, s, clr, false)
|
|
}
|
|
|
|
func (fl *flag) set(actualFlagName, s string, clr color.Color) error {
|
|
fl.isSet = true
|
|
fl.isAssigned = true
|
|
fl.actualFlagName = actualFlagName
|
|
if fl.isNeedDelaySet {
|
|
fl.lastValue = s
|
|
return nil
|
|
}
|
|
return setWithProperType(fl, fl.field.Type, fl.value, s, clr, false)
|
|
}
|
|
|
|
func (fl *flag) counterIncr(s string, clr color.Color) error {
|
|
return setWithProperType(fl, fl.field.Type, fl.value, s, clr, false)
|
|
}
|
|
|
|
func (fl *flag) isCounter() bool {
|
|
if decoder := tryGetDecoder(fl.value.Type().Kind(), fl.value); decoder != nil {
|
|
if _, ok := decoder.(CounterDecoder); ok {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (fl *flag) setWithNoDelay(actualFlagName, s string, clr color.Color) error {
|
|
fl.isSet = true
|
|
fl.isAssigned = true
|
|
fl.actualFlagName = actualFlagName
|
|
return setWithProperType(fl, fl.field.Type, fl.value, s, clr, false)
|
|
}
|
|
|
|
func tryGetDecoder(kind reflect.Kind, val reflect.Value) Decoder {
|
|
if val.CanInterface() {
|
|
var addrVal = val
|
|
if kind != reflect.Ptr && val.CanAddr() {
|
|
addrVal = val.Addr()
|
|
}
|
|
// try Decoder
|
|
if addrVal.CanInterface() {
|
|
if i := addrVal.Interface(); i != nil {
|
|
if decoder, ok := i.(Decoder); ok {
|
|
return decoder
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func setWithProperType(fl *flag, typ reflect.Type, val reflect.Value, s string, clr color.Color, isSubField bool) error {
|
|
kind := typ.Kind()
|
|
|
|
// try parser first of all
|
|
if fl.tag.parserCreator != nil && val.CanInterface() {
|
|
if kind != reflect.Ptr && val.CanAddr() {
|
|
val = val.Addr()
|
|
}
|
|
return fl.tag.parserCreator(val.Interface()).Parse(s)
|
|
}
|
|
|
|
if decoder := tryGetDecoder(kind, val); decoder != nil {
|
|
return decoder.Decode(s)
|
|
}
|
|
|
|
switch kind {
|
|
case reflect.Bool:
|
|
if v, err := getBool(s, clr); err == nil {
|
|
val.SetBool(v)
|
|
} else {
|
|
return err
|
|
}
|
|
|
|
case reflect.String:
|
|
val.SetString(s)
|
|
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
if v, err := getInt(s, clr); err == nil {
|
|
if minmaxIntCheck(kind, v) {
|
|
val.SetInt(v)
|
|
} else {
|
|
return errors.New(clr.Red("value overflow"))
|
|
}
|
|
} else {
|
|
return err
|
|
}
|
|
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
if v, err := getUint(s, clr); err == nil {
|
|
if minmaxUintCheck(kind, v) {
|
|
val.SetUint(uint64(v))
|
|
} else {
|
|
return errors.New(clr.Red("value overflow"))
|
|
}
|
|
} else {
|
|
return err
|
|
}
|
|
|
|
case reflect.Float32, reflect.Float64:
|
|
if v, err := getFloat(s, clr); err == nil {
|
|
if minmaxFloatCheck(kind, v) {
|
|
val.SetFloat(float64(v))
|
|
} else {
|
|
return errors.New(clr.Red("value overflow"))
|
|
}
|
|
} else {
|
|
return err
|
|
}
|
|
|
|
case reflect.Slice:
|
|
if isSubField {
|
|
return fmt.Errorf("unsupported type %s as a sub field", kind.String())
|
|
}
|
|
sliceOf := typ.Elem()
|
|
if val.IsNil() {
|
|
slice := reflect.MakeSlice(typ, 0, 4)
|
|
val.Set(slice)
|
|
}
|
|
index := val.Len()
|
|
sliceCap := val.Cap()
|
|
if index+1 <= sliceCap {
|
|
val.SetLen(index + 1)
|
|
} else {
|
|
slice := reflect.MakeSlice(typ, index+1, index+sliceCap/2+1)
|
|
for k := 0; k < index; k++ {
|
|
slice.Index(k).Set(val.Index(k))
|
|
}
|
|
val.Set(slice)
|
|
}
|
|
return setWithProperType(fl, sliceOf, val.Index(index), s, clr, true)
|
|
|
|
case reflect.Map:
|
|
if isSubField {
|
|
return fmt.Errorf("unsupported type %s as a sub field", kind.String())
|
|
}
|
|
keyString, valString, err := splitKeyVal(s, fl.tag.sep)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
keyType := typ.Key()
|
|
valType := typ.Elem()
|
|
if val.IsNil() {
|
|
val.Set(reflect.MakeMap(typ))
|
|
}
|
|
k, v := reflect.New(keyType), reflect.New(valType)
|
|
if err := setWithProperType(fl, keyType, k.Elem(), keyString, clr, true); err != nil {
|
|
return err
|
|
}
|
|
if err := setWithProperType(fl, valType, v.Elem(), valString, clr, true); err != nil {
|
|
return err
|
|
}
|
|
val.SetMapIndex(k.Elem(), v.Elem())
|
|
|
|
default:
|
|
return fmt.Errorf("unsupported type: %s", kind.String())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func splitKeyVal(s, sep string) (key, val string, err error) {
|
|
if s == "" {
|
|
err = fmt.Errorf("empty key,val pair")
|
|
return
|
|
}
|
|
index := strings.Index(s, sep)
|
|
if index == -1 {
|
|
return s, "", nil
|
|
}
|
|
return s[:index], s[index+1:], nil
|
|
}
|
|
|
|
func minmaxIntCheck(kind reflect.Kind, v int64) bool {
|
|
switch kind {
|
|
case reflect.Int, reflect.Int64:
|
|
return v >= int64(math.MinInt64) && v <= int64(math.MaxInt64)
|
|
case reflect.Int8:
|
|
return v >= int64(math.MinInt8) && v <= int64(math.MaxInt8)
|
|
case reflect.Int16:
|
|
return v >= int64(math.MinInt16) && v <= int64(math.MaxInt16)
|
|
case reflect.Int32:
|
|
return v >= int64(math.MinInt32) && v <= int64(math.MaxInt32)
|
|
}
|
|
return true
|
|
}
|
|
|
|
func minmaxUintCheck(kind reflect.Kind, v uint64) bool {
|
|
switch kind {
|
|
case reflect.Uint, reflect.Uint64:
|
|
return v <= math.MaxUint64
|
|
case reflect.Uint8:
|
|
return v <= math.MaxUint8
|
|
case reflect.Uint16:
|
|
return v <= math.MaxUint16
|
|
case reflect.Uint32:
|
|
return v <= math.MaxUint32
|
|
}
|
|
return true
|
|
}
|
|
|
|
func minmaxFloatCheck(kind reflect.Kind, v float64) bool {
|
|
switch kind {
|
|
case reflect.Float32:
|
|
return v >= -float64(math.MaxFloat32) && v <= float64(math.MaxFloat32)
|
|
case reflect.Float64:
|
|
return v >= -float64(math.MaxFloat64) && v <= float64(math.MaxFloat64)
|
|
}
|
|
return true
|
|
}
|
|
|
|
func getBool(s string, clr color.Color) (bool, error) {
|
|
s = strings.ToLower(s)
|
|
if s == "true" || s == "yes" || s == "y" || s == "" {
|
|
return true, nil
|
|
}
|
|
if s == "false" || s == "none" || s == "no" || s == "not" || s == "n" {
|
|
return false, nil
|
|
}
|
|
i, err := strconv.Atoi(s)
|
|
if err != nil {
|
|
return false, fmt.Errorf("`%s' couldn't converted to a %s", s, clr.Bold("bool"))
|
|
}
|
|
return i != 0, nil
|
|
}
|
|
|
|
func getInt(s string, clr color.Color) (int64, error) {
|
|
i, err := strconv.ParseInt(s, 0, 64)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("`%s' couldn't converted to an %s", s, clr.Bold("int"))
|
|
}
|
|
return i, nil
|
|
}
|
|
|
|
func getUint(s string, clr color.Color) (uint64, error) {
|
|
i, err := strconv.ParseUint(s, 0, 64)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("`%s' couldn't converted to an %s", s, clr.Bold("uint"))
|
|
}
|
|
return i, nil
|
|
}
|
|
|
|
func getFloat(s string, clr color.Color) (float64, error) {
|
|
f, err := strconv.ParseFloat(s, 64)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("`%s' couldn't converted to a %s", s, clr.Bold("float"))
|
|
}
|
|
return f, nil
|
|
}
|