Upstream urfave/cli state of the touched files before 3510d4c9d9
This commit is contained in:
commit
8a9f09fc09
2 files changed
+1246
No files matched your search
+729
@@ -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<value>
|
||||||
|
{
|
||||||
|
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})
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
Reference in new issue
Block a user