From 8a9f09fc09375c474c81d187a384f6a80b3d07c3 Mon Sep 17 00:00:00 2001 From: upstream Date: Fri, 4 Sep 2026 09:31:42 +0200 Subject: [PATCH] Upstream urfave/cli state of the touched files before 3510d4c9d9 --- cli_test.go | 729 ++++++++++++++++++++++++++++++++++++++++++++++++++++ flag.go | 517 +++++++++++++++++++++++++++++++++++++ 2 files changed, 1246 insertions(+) create mode 100644 cli_test.go create mode 100644 flag.go diff --git a/cli_test.go b/cli_test.go new file mode 100644 index 0000000..ab79c43 --- /dev/null +++ b/cli_test.go @@ -0,0 +1,729 @@ +package cli + +import ( + "bytes" + "encoding/json" + "fmt" + "math" + "os" + "reflect" + "testing" + + "github.com/labstack/gommon/color" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var donothing = func(*Context) error { return nil } + +func TestTree(t *testing.T) { + cmd := &Command{Name: "cmd", Fn: donothing} + subcmd := &Command{Name: "subcmd", Fn: donothing} + + tree := Tree(cmd, Tree(subcmd)) + assert.Equal(t, tree.command.Name, "cmd") + require.Len(t, tree.forest, 1) + assert.Equal(t, tree.forest[0].command.Name, "subcmd") + assert.Len(t, tree.forest[0].forest, 0) +} + +func TestRoot(t *testing.T) { + donothing := func(*Context) error { return nil } + root := Root(&Command{Name: "cmd", Fn: donothing}, + Tree(&Command{Name: "subcmd", Fn: donothing}), + ) + assert.Equal(t, root.Name, "cmd") + require.Len(t, root.children, 1) + assert.Equal(t, root.children[0].Name, "subcmd") +} + +type argT struct { + Short bool `cli:"s" usage:"short flag"` + Short2 bool `cli:"2" usage:"another short flag"` + ShortAndLong string `cli:"S,long" usage:"short and long flags"` + ShortsAndLong int `cli:"x,y,abcd,omitof" usage:"many short and long flags"` + Long uint `cli:"long-flag" usage:"long flag"` + Required int8 `cli:"*required" usage:"required flag, note the *"` + Default uint8 `cli:"dft,default" usage:"default value" dft:"102"` + UnName uint16 `usage:"unname field"` + + Int8 int8 `cli:"i8" usage:"type int8"` + Uint8 uint8 `cli:"u8" usage:"type uint8"` + Int16 int16 `cli:"i16" usage:"type int16"` + Uint16 uint16 `cli:"u16" usage:"type uint16"` + Int32 int32 `cli:"i32" usage:"type int32"` + Uint32 uint32 `cli:"u32" usage:"type uint32"` + Int64 int64 `cli:"i64" usage:"type int64"` + Uint64 uint64 `cli:"u64" usage:"type uint64"` + Float32 float32 `cli:"f32" usage:"type float32"` + Float64 float64 `cli:"f64" usage:"type float64"` +} + +func toStr(i interface{}) string { + return fmt.Sprintf("%v", i) +} + +func TestParse(t *testing.T) { + clr := color.Color{} + for i, tab := range []struct { + args []string + want argT + isErr bool + freedomArgs []string + }{ + //Case: ENV + { + args: []string{"--required=0"}, + want: argT{Default: 102}, + }, + //Case: missing required + { + args: []string{}, + isErr: true, + }, + //Case: undefined flag + { + args: []string{"--required=0", "-Q"}, + isErr: true, + }, + //Case: undefined flag + { + args: []string{"--required=0", "--KdjiiejdfwkHJH"}, + isErr: true, + }, + //Case: short flag group + { + args: []string{"--required=0", "-s2"}, + want: argT{Default: 102, Short: true, Short2: true}, + }, + //Case: check default + { + args: []string{"--required=0"}, + want: argT{Default: 102}, + }, + //Case: modify default + { + args: []string{"--required=0", "--dft", "55"}, + want: argT{Default: 55}, + }, + //Case: modify default + { + args: []string{"--required=0", "--default", "55"}, + want: argT{Default: 55}, + }, + //Case: UnName + { + args: []string{"--required=0", "--UnName", "64"}, + want: argT{Default: 102, UnName: 64}, + }, + //Case: not a bool + { + args: []string{"--required=0", "-s=not-a-bool"}, + isErr: true, + }, + //Case: "" -> bool + { + args: []string{"--required=0", "-s"}, + want: argT{Default: 102, Short: true}, + }, + //Case: "true" -> bool + { + args: []string{"--required=0", "-s=true"}, + want: argT{Default: 102, Short: true}, + }, + //Case: non-zero integer -> bool + { + args: []string{"--required=0", "-s=1"}, + want: argT{Default: 102, Short: true}, + }, + //Case: zero -> bool + { + args: []string{"--required=0", "-s=0"}, + want: argT{Default: 102}, + }, + //Case: no -> bool + { + args: []string{"--required=0", "-s=no"}, + want: argT{Default: 102}, + }, + //Case: not -> bool + { + args: []string{"--required=0", "-s=not"}, + want: argT{Default: 102}, + }, + //Case: none -> bool + { + args: []string{"--required=0", "-s=none"}, + want: argT{Default: 102}, + }, + //Case: false -> bool + { + args: []string{"--required=0", "-s=false"}, + want: argT{Default: 102}, + }, + //Case: int64 + { + args: []string{"--required=0", "--i64", toStr(12)}, + want: argT{Default: 102, Int64: 12}, + }, + //Case: int64 overflow + { + args: []string{"--required=0", "--i64", toStr(uint64(math.MaxUint64))}, + isErr: true, + }, + //Case: uint64 + { + args: []string{"--required=0", "--u64", toStr(12)}, + want: argT{Default: 102, Uint64: 12}, + }, + //Case: max uint64 + { + args: []string{"--required=0", "--u64", toStr(uint64(math.MaxUint64))}, + want: argT{Default: 102, Uint64: uint64(math.MaxUint64)}, + }, + //Case: negative -> uint64 + { + args: []string{"--required=0", "--u64", "-1"}, + isErr: true, + }, + //Case: int32 + { + args: []string{"--required=0", "--i32", toStr(12)}, + want: argT{Default: 102, Int32: 12}, + }, + //Case: int32 overflow + { + args: []string{"--required=0", "--i32", toStr(uint32(math.MaxUint32))}, + isErr: true, + }, + //Case: uint32 + { + args: []string{"--required=0", "--u32", toStr(12)}, + want: argT{Default: 102, Uint32: 12}, + }, + //Case: max uint32 + { + args: []string{"--required=0", "--u32", toStr(uint32(math.MaxUint32))}, + want: argT{Default: 102, Uint32: uint32(math.MaxUint32)}, + }, + //Case: negative -> uint32 + { + args: []string{"--required=0", "--u32", "-1"}, + isErr: true, + }, + //Case: int16 + { + args: []string{"--required=0", "--i16", toStr(12)}, + want: argT{Default: 102, Int16: 12}, + }, + //Case: int16 overflow + { + args: []string{"--required=0", "--i16", toStr(uint16(math.MaxUint16))}, + isErr: true, + }, + //Case: uint16 + { + args: []string{"--required=0", "--u16", toStr(12)}, + want: argT{Default: 102, Uint16: 12}, + }, + //Case: max uint16 + { + args: []string{"--required=0", "--u16", toStr(uint16(math.MaxUint16))}, + want: argT{Default: 102, Uint16: uint16(math.MaxUint16)}, + }, + //Case: negative -> uint16 + { + args: []string{"--required=0", "--u16", "-1"}, + isErr: true, + }, + //Case: int8 + { + args: []string{"--required=0", "--i8", toStr(12)}, + want: argT{Default: 102, Int8: 12}, + }, + //Case: int8 overflow + { + args: []string{"--required=0", "--i8", toStr(uint8(math.MaxUint8))}, + isErr: true, + }, + //Case: uint8 + { + args: []string{"--required=0", "--u8", toStr(12)}, + want: argT{Default: 102, Uint8: 12}, + }, + //Case: max uint8 + { + args: []string{"--required=0", "--u8", toStr(uint8(math.MaxUint8))}, + want: argT{Default: 102, Uint8: uint8(math.MaxUint8)}, + }, + //Case: negative -> uint8 + { + args: []string{"--required=0", "--u8", "-1"}, + isErr: true, + }, + //Case: many invalid value + { + args: []string{"--required=0", "--u8", "-1", "--u8", "256"}, + isErr: true, + }, + //Case: too many value + { + args: []string{"--required=0=1"}, + isErr: true, + }, + //Case: float32 + { + args: []string{"--required=0", "--f32", "12.34"}, + want: argT{Default: 102, Float32: 12.34}, + }, + //Case: not a float32 + { + args: []string{"--required=0", "--f32", "not-a-float32"}, + isErr: true, + }, + //Case: float32 overflow + { + args: []string{"--required=0", "--f32", "123456789123456789123456789123456789123456789"}, + isErr: true, + }, + //Case: float32 overflow + { + args: []string{"--required=0", "--f32", "-123456789123456789123456789123456789123456789"}, + isErr: true, + }, + //Case: float64 + { + args: []string{"--required=0", "--f64=-1234.5678"}, + want: argT{Default: 102, Float64: -1234.5678}, + }, + //Case: not a float64 + { + args: []string{"--required=0", "--f64=not-a-float64"}, + isErr: true, + }, + //Case: fold flag not boolean + { + args: []string{"--required=0", "-2y"}, + isErr: true, + }, + //Case: test -F + { + args: []string{"--required=0", "-Sshort-and-long"}, + want: argT{Default: 102, ShortAndLong: "short-and-long"}, + }, + //Case: test `--` + { + args: []string{"--required=0", "--", "-Sshort-and-long"}, + want: argT{Default: 102, ShortAndLong: ""}, + freedomArgs: []string{"-Sshort-and-long"}, + }, + //Case: test flags and args + { + args: []string{"--required=0", "-Sshort-and-long", "abc"}, + want: argT{Default: 102, ShortAndLong: "short-and-long"}, + freedomArgs: []string{"abc"}, + }, + { + args: []string{"--required=0", "-Sshort-and-long", "abc", "-s", "xyz"}, + want: argT{Default: 102, Short: true, ShortAndLong: "short-and-long"}, + freedomArgs: []string{"abc", "xyz"}, + }, + } { + if tab.args == nil { + tab.args = []string{} + } + v := new(argT) + flagSet := parseArgv(tab.args, v, clr) + if tab.isErr { + if flagSet.err == nil { + t.Errorf("[%2d] want error, got nil", i) + } + continue + } + if flagSet.err != nil { + t.Errorf("[%2d] parseArgv error: %v", i, flagSet.err) + continue + } + if !reflect.DeepEqual(*v, tab.want) { + t.Errorf("[%2d] want %v, got %v", i, tab.want, *v) + } + if !stringsEqual(tab.freedomArgs, flagSet.args) { + t.Errorf("[%2d] want %v, got %v", i, tab.freedomArgs, flagSet.args) + } + } + + //Case parse non-pointer object + if flagSet := parseArgv([]string{}, argT{}, clr); flagSet.err != errNotAPointer { + t.Errorf("want %v, got %v", errNotAPointer, flagSet.err) + } + if usage([]interface{}{argT{}}, clr, NormalStyle) != "" { + t.Errorf("want usage empty, but not") + } + + //Case parse pointer, but not indirect a struct + tmp := 0 + ptrInt := &tmp + if flagSet := parseArgv([]string{}, ptrInt, clr); flagSet.err != errNotAPointerToStruct { + t.Errorf("want %v, got %v", errNotAPointerToStruct, flagSet.err) + } + if usage([]interface{}{ptrInt}, clr, NormalStyle) != "" { + t.Errorf("want usage empty, but not") + } + + //Case repeat tag + type tmpT struct { + A bool `cli:"a"` + B bool `cli:"a"` + } + if flagSet := parseArgv([]string{}, new(tmpT), clr); flagSet.err == nil { + t.Errorf("want error, got nil") + } + if usage([]interface{}{new(tmpT)}, clr, NormalStyle) != "" { + t.Errorf("want usage empty, but not") + } + + type envT struct { + DefaultEnv string `cli:"default-env" usage:"default value" dft:"$ENV_CLI_TEST"` + } + envV := new(envT) + if flagSet := parseArgv([]string{}, envV, clr); flagSet.err != nil { + t.Errorf(flagSet.err.Error()) + } else { + if want := os.Getenv("ENV_CLI_TEST"); want != envV.DefaultEnv { + t.Errorf("ENV_CLI_TEST want `%s`, got `%s`", want, envV.DefaultEnv) + } + } +} + +func TestUsage(t *testing.T) { + clr := color.Color{} + clr.Disable() + got := usage([]interface{}{new(argT)}, clr, NormalStyle) + want := fmt.Sprintf( + ` -s short flag + -2 another short flag + -S, --long short and long flags + -x, -y, --abcd, --omitof many short and long flags + --long-flag long flag + --required *required flag, note the * + --dft, --default[=102] default value + --UnName unname field + --i8 type int8 + --u8 type uint8 + --i16 type int16 + --u16 type uint16 + --i32 type int32 + --u32 type uint32 + --i64 type int64 + --u64 type uint64 + --f32 type float32 + --f64 type float64 +`) + assert.Equal(t, got, want) + + got = usage([]interface{}{new(argT)}, clr, ManualStyle) + want = ` -s + short flag + + -2 + another short flag + + -S, --long + short and long flags + + -x, -y, --abcd, --omitof + many short and long flags + + --long-flag + long flag + + --required + *required flag, note the * + + --dft, --default[=102] + default value + + --UnName + unname field + + --i8 + type int8 + + --u8 + type uint8 + + --i16 + type int16 + + --u16 + type uint16 + + --i32 + type int32 + + --u32 + type uint32 + + --i64 + type int64 + + --u64 + type uint64 + + --f32 + type float32 + + --f64 + type float64 +` + assert.Equal(t, got, want) +} + +func TestStructField(t *testing.T) { + type BaseT struct { + Help bool `cli:"!h,help" usage:"display help"` + } + type subT struct { + BaseT + Version string `cli:"v" usage:"display version" dft:"v0.0.1"` + } + args := []string{ + "-h", + "-v=v1.1.1", + } + argv := new(subT) + clr := color.Color{} + flagSet := parseArgv(args, argv, clr) + if flagSet.err != nil { + t.Errorf("parseArgv error: %v", flagSet.err) + } else { + if argv.BaseT.Help != true { + t.Errorf("Help want true, but got false") + } + if argv.Version != "v1.1.1" { + t.Errorf("Version want v1.1.1, but got %s", argv.Version) + } + } +} + +func stringsEqual(ss1, ss2 []string) bool { + if len(ss1) != len(ss2) { + return false + } + for i := 0; i < len(ss1); i++ { + if ss1[i] != ss2[i] { + return false + } + } + return true +} + +func intsEqual(ss1, ss2 []uint32) bool { + if ss1 == nil && ss2 == nil { + return true + } + if len(ss1) != len(ss2) { + return false + } + for i := 0; i < len(ss1); i++ { + if ss1[i] != ss2[i] { + return false + } + } + return true +} + +func mapEqual(m1, m2 map[uint32]string) bool { + if m1 == nil && m2 == nil { + return true + } + for k, v := range m1 { + if v2, ok := m2[k]; !ok { + return false + } else if v2 != v { + return false + } + } + for k, v := range m2 { + if v1, ok := m1[k]; !ok { + return false + } else if v1 != v { + return false + } + } + return true +} + +func TestSliceAndMap(t *testing.T) { + clr := color.Color{} + type T struct { + Slice []uint32 `cli:"s,slice"` + Map map[uint32]string `cli:"m,map"` + } + for _, tab := range []struct { + args []string + want T + isErr bool + }{ + { + args: []string{"--slice=12", "--slice", "23"}, + want: T{Slice: []uint32{12, 23}}, + }, + { + args: []string{"-s12", "-s23"}, + want: T{Slice: []uint32{12, 23}}, + }, + { + args: []string{"-s=12", "-s=23"}, + want: T{Slice: []uint32{12, 23}}, + }, + { + args: []string{"-s", "12", "-s", "23"}, + want: T{Slice: []uint32{12, 23}}, + }, + { + args: []string{"-s", "12", "-s", "not-a-number"}, + isErr: true, + }, + { + args: []string{"-m2=s", "-m3=y"}, + want: T{Map: map[uint32]string{ + 2: "s", + 3: "y", + }}, + }, + { + args: []string{"-m2=s", "-m", "3=y"}, + want: T{Map: map[uint32]string{ + 2: "s", + 3: "y", + }}, + }, + { + args: []string{"-m"}, + isErr: true, + }, + { + args: []string{"-ms=2", "-my=3"}, + isErr: true, + }, + } { + v := new(T) + flagSet := parseArgv(tab.args, v, clr) + if tab.isErr { + if flagSet.err == nil { + t.Errorf("want error, but not. tab=%v", tab) + } + continue + } + if flagSet.err != nil { + t.Errorf("TestSliceAndMap: error: %v", flagSet.err) + continue + } + if !intsEqual(tab.want.Slice, v.Slice) { + t.Errorf("want %v, got %v", tab.want.Slice, v.Slice) + } + if !mapEqual(tab.want.Map, v.Map) { + t.Errorf("want %v, got %v", tab.want.Map, v.Map) + } + } +} + +func TestErrorSliceType(t *testing.T) { + type A struct { + Value string + } + type T struct { + Slice []A `cli:"s"` + } + clr := color.Color{} + v := new(T) + flagSet := parseArgv([]string{"-s4"}, v, clr) + if flagSet.err == nil { + t.Errorf("want error, but not") + } +} + +func TestErrorMapType(t *testing.T) { + type A struct { + Value string + } + type T struct { + Map map[string]A `cli:"m"` + } + clr := color.Color{} + v := new(T) + flagSet := parseArgv([]string{"-mkey=val"}, v, clr) + if flagSet.err == nil { + t.Errorf("want error, but not") + } +} + +func TestIsSet(t *testing.T) { + type argT struct { + A int `cli:"a,aa" dft:"1"` + B int `cli:"b"` + } + for i, tt := range []struct { + args []string + isSetA bool + isSetB bool + }{ + {[]string{"app", "-a=1", "-b=1"}, true, true}, + {[]string{"app", "--aa=1", "-b=1"}, true, true}, + {[]string{"app", "-a=1"}, true, false}, + {[]string{"app", "-b=1"}, false, true}, + {[]string{"app"}, false, false}, + } { + RunWithArgs(new(argT), tt.args, func(ctx *Context) error { + assert.Equal(t, ctx.IsSet("-a"), tt.isSetA, "case %d", i) + assert.Equal(t, ctx.IsSet("-a", "--aa"), tt.isSetA, "case %d", i) + assert.Equal(t, ctx.IsSet("-b"), tt.isSetB, "case %d", i) + return nil + }) + } +} + +func TestHelpCommand(t *testing.T) { + w := bytes.NewBufferString("") + root := &Command{Name: "root"} + help := HelpCommand("help command") + root.Register(help) + assert.Nil(t, root.RunWith([]string{"help"}, w, nil)) + assert.Equal(t, w.String(), "Commands:\n\n help help command\n") + assert.Error(t, root.RunWith([]string{"help", "not-found"}, nil, nil)) +} + +func TestError(t *testing.T) { + assert.Equal(t, ExitError.Error(), "exit") + assert.Equal(t, throwCommandNotFound("cmd").Error(), "command cmd not found") + assert.Equal(t, throwMethodNotAllowed("POST").Error(), "method POST not allowed") + assert.Equal(t, throwRouterRepeat("R").Error(), "router R repeat") + clr := color.Color{} + clr.Disable() + assert.Equal(t, wrapErr(throwCommandNotFound("cmd"), "_end", clr).Error(), `ERR! command cmd not found_end`) + + assert.Equal(t, argvError{isEmpty: true}.Error(), "argv list is empty") + assert.Equal(t, argvError{isOutOfRange: true}.Error(), "argv list out of range") + assert.Equal(t, argvError{ith: 1, msg: "ERROR MSG"}.Error(), "1th argv: ERROR MSG") +} + +type customT struct { + K1 string + K2 int +} + +func (t *customT) Decode(s string) error { + return json.Unmarshal([]byte(s), t) +} + +func TestDecoder(t *testing.T) { + type argT struct { + D customT `cli:"d"` + } + v := new(argT) + clr := color.Color{} + flagSet := parseArgv([]string{`-d`, `{"k1": "string", "k2": 2}`}, v, clr) + assert.Nil(t, flagSet.err) + assert.Equal(t, v.D, customT{K1: "string", K2: 2}) +} diff --git a/flag.go b/flag.go new file mode 100644 index 0000000..55b71cf --- /dev/null +++ b/flag.go @@ -0,0 +1,517 @@ +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 +}