update
This commit is contained in:
parent
413d39119d
commit
50ac8e4572
257
cli/cmd.go
Normal file
257
cli/cmd.go
Normal file
@ -0,0 +1,257 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/eschao/config/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type anyValue struct {
|
||||||
|
any reflect.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func newAnyValue(v reflect.Value) *anyValue {
|
||||||
|
return &anyValue{any: v}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *anyValue) String() string {
|
||||||
|
kind := this.any.Kind()
|
||||||
|
switch kind {
|
||||||
|
case reflect.Bool:
|
||||||
|
return strconv.FormatBool(this.any.Bool())
|
||||||
|
case reflect.String:
|
||||||
|
return this.any.String()
|
||||||
|
case reflect.Int8,
|
||||||
|
reflect.Int16,
|
||||||
|
reflect.Int,
|
||||||
|
reflect.Int32,
|
||||||
|
reflect.Int64:
|
||||||
|
return strconv.FormatInt(this.any.Int(), 10)
|
||||||
|
case reflect.Uint8,
|
||||||
|
reflect.Uint16,
|
||||||
|
reflect.Uint,
|
||||||
|
reflect.Uint32,
|
||||||
|
reflect.Uint64:
|
||||||
|
return strconv.FormatUint(this.any.Uint(), 10)
|
||||||
|
case reflect.Float32:
|
||||||
|
return strconv.FormatFloat(this.any.Float(), 'E', -1, 32)
|
||||||
|
case reflect.Float64:
|
||||||
|
return strconv.FormatFloat(this.any.Float(), 'E', -1, 64)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("unsupport type %s", kind.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *anyValue) Set(v string) error {
|
||||||
|
kind := this.any.Kind()
|
||||||
|
switch kind {
|
||||||
|
case reflect.String:
|
||||||
|
this.any.SetString(v)
|
||||||
|
case reflect.Float32:
|
||||||
|
return utils.SetValueWithFloatX(this.any, v, 32)
|
||||||
|
case reflect.Float64:
|
||||||
|
return utils.SetValueWithFloatX(this.any, v, 64)
|
||||||
|
case reflect.Int8:
|
||||||
|
return utils.SetValueWithIntX(this.any, v, 8)
|
||||||
|
case reflect.Int16:
|
||||||
|
return utils.SetValueWithIntX(this.any, v, 16)
|
||||||
|
case reflect.Int, reflect.Int32:
|
||||||
|
return utils.SetValueWithIntX(this.any, v, 32)
|
||||||
|
case reflect.Int64:
|
||||||
|
return utils.SetValueWithIntX(this.any, v, 64)
|
||||||
|
case reflect.Uint8:
|
||||||
|
return utils.SetValueWithUintX(this.any, v, 8)
|
||||||
|
case reflect.Uint16:
|
||||||
|
return utils.SetValueWithUintX(this.any, v, 16)
|
||||||
|
case reflect.Uint, reflect.Uint32:
|
||||||
|
return utils.SetValueWithUintX(this.any, v, 32)
|
||||||
|
case reflect.Uint64:
|
||||||
|
return utils.SetValueWithUintX(this.any, v, 64)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("Can't support type %s", kind.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var errorHandling = flag.ExitOnError
|
||||||
|
|
||||||
|
type UsageFunc func(*Command) func()
|
||||||
|
|
||||||
|
var usageHandler UsageFunc = nil
|
||||||
|
|
||||||
|
type Command struct {
|
||||||
|
Name string
|
||||||
|
FlagSet *flag.FlagSet
|
||||||
|
Usage string
|
||||||
|
SubCommands map[string]*Command
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(name string) *Command {
|
||||||
|
cmd := Command{
|
||||||
|
Name: name,
|
||||||
|
FlagSet: flag.NewFlagSet(name, errorHandling),
|
||||||
|
SubCommands: make(map[string]*Command),
|
||||||
|
}
|
||||||
|
|
||||||
|
return &cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWith(name string, errHandling flag.ErrorHandling,
|
||||||
|
usageHandling UsageFunc) *Command {
|
||||||
|
errorHandling = errHandling
|
||||||
|
usageHandler = usageHandling
|
||||||
|
|
||||||
|
cmd := Command{
|
||||||
|
Name: name,
|
||||||
|
FlagSet: flag.NewFlagSet(name, errorHandling),
|
||||||
|
SubCommands: make(map[string]*Command),
|
||||||
|
}
|
||||||
|
|
||||||
|
if usageHandler != nil {
|
||||||
|
cmd.FlagSet.Usage = usageHandler(&cmd)
|
||||||
|
}
|
||||||
|
return &cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Command) Init(i interface{}) error {
|
||||||
|
ptrRef := reflect.ValueOf(i)
|
||||||
|
|
||||||
|
if ptrRef.IsNil() || ptrRef.Kind() != reflect.Ptr {
|
||||||
|
return fmt.Errorf("Expect a structure pointer type instead of %s",
|
||||||
|
ptrRef.Kind().String())
|
||||||
|
}
|
||||||
|
|
||||||
|
valueOfStruct := ptrRef.Elem()
|
||||||
|
if valueOfStruct.Kind() != reflect.Struct {
|
||||||
|
return fmt.Errorf("Expect a structure type instead of %s",
|
||||||
|
valueOfStruct.Kind().String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.parseValue(valueOfStruct)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Command) parseValue(v reflect.Value) error {
|
||||||
|
typeOfStruct := v.Type()
|
||||||
|
|
||||||
|
for i := 0; i < v.NumField(); i++ {
|
||||||
|
valueOfField := v.Field(i)
|
||||||
|
kindOfField := valueOfField.Kind()
|
||||||
|
structOfField := typeOfStruct.Field(i)
|
||||||
|
|
||||||
|
if kindOfField == reflect.Ptr {
|
||||||
|
if !valueOfField.IsNil() && valueOfField.CanSet() {
|
||||||
|
cmd := this.createCliFlagSet(structOfField.Tag)
|
||||||
|
if err := cmd.Init(valueOfField.Interface()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if kindOfField == reflect.Struct {
|
||||||
|
cmd := this.createCliFlagSet(structOfField.Tag)
|
||||||
|
if err := cmd.parseValue(valueOfField); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := this.addFlag(valueOfField, structOfField); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Command) addFlag(v reflect.Value, f reflect.StructField) error {
|
||||||
|
cmdTag, ok := f.Tag.Lookup("cmd")
|
||||||
|
if !ok || cmdTag == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
firstSpace := strings.Index(cmdTag, " ")
|
||||||
|
name := cmdTag
|
||||||
|
usage := ""
|
||||||
|
if firstSpace > 0 {
|
||||||
|
name = cmdTag[0:firstSpace]
|
||||||
|
usage = cmdTag[firstSpace+1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
kind := v.Kind()
|
||||||
|
switch kind {
|
||||||
|
case reflect.Bool:
|
||||||
|
this.FlagSet.BoolVar((*bool)(unsafe.Pointer(v.UnsafeAddr())), name,
|
||||||
|
false, usage)
|
||||||
|
return nil
|
||||||
|
case reflect.String,
|
||||||
|
reflect.Int8,
|
||||||
|
reflect.Int16,
|
||||||
|
reflect.Int,
|
||||||
|
reflect.Int32,
|
||||||
|
reflect.Int64,
|
||||||
|
reflect.Uint8,
|
||||||
|
reflect.Uint16,
|
||||||
|
reflect.Uint,
|
||||||
|
reflect.Uint32,
|
||||||
|
reflect.Uint64,
|
||||||
|
reflect.Float32,
|
||||||
|
reflect.Float64:
|
||||||
|
anyValue := newAnyValue(v)
|
||||||
|
this.FlagSet.Var(anyValue, name, usage)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("Can't support type %s", kind.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Command) createCliFlagSet(tag reflect.StructTag) *Command {
|
||||||
|
cmdTag, ok := tag.Lookup("cmd")
|
||||||
|
if !ok || cmdTag == "" {
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := Command{SubCommands: make(map[string]*Command)}
|
||||||
|
firstSpace := strings.Index(cmdTag, " ")
|
||||||
|
name := cmdTag
|
||||||
|
usage := ""
|
||||||
|
if firstSpace > 0 {
|
||||||
|
name = cmdTag[0:firstSpace]
|
||||||
|
usage = cmdTag[firstSpace+1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Name = name
|
||||||
|
cmd.FlagSet = flag.NewFlagSet(name, errorHandling)
|
||||||
|
cmd.Usage = usage
|
||||||
|
|
||||||
|
if usageHandler != nil {
|
||||||
|
cmd.FlagSet.Usage = usageHandler(&cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.SubCommands[name] = &cmd
|
||||||
|
return &cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Command) Parse(args []string) error {
|
||||||
|
if err := this.FlagSet.Parse(args); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
unprocessed := this.FlagSet.Args()
|
||||||
|
if len(unprocessed) < 1 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if this.SubCommands == nil {
|
||||||
|
return fmt.Errorf("Command: %s is unsupport", unprocessed[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := this.SubCommands[unprocessed[0]]
|
||||||
|
if cmd == nil {
|
||||||
|
return fmt.Errorf("Command: %s is unsupport", unprocessed[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmd.Parse(unprocessed[1:])
|
||||||
|
}
|
313
cli/cmd_test.go
Normal file
313
cli/cmd_test.go
Normal file
@ -0,0 +1,313 @@
|
|||||||
|
package cli
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
type dbConfig struct {
|
||||||
|
Host string `cmd:"dbHost database server hostname"`
|
||||||
|
Port int `cmd:"dbPort database server port"`
|
||||||
|
User string `cmd:"dbUser database username"`
|
||||||
|
Password string `cmd:"dbPassword database user password"`
|
||||||
|
Log logConfig `cmd:"log database log configuration"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type loginConfig struct {
|
||||||
|
User string `cmd:"user login username"`
|
||||||
|
Password string `cmd:"password login password"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type logConfig struct {
|
||||||
|
Path string `cmd:"path log path"`
|
||||||
|
Level string `cmd:"level log level {debug|warning|error}"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type serviceConfig struct {
|
||||||
|
Host string `cmd:"hostname service hostname"`
|
||||||
|
Port int `cmd:"port service port"`
|
||||||
|
DBConfig dbConfig `cmd:"database database configuration"`
|
||||||
|
Login *loginConfig `cmd:"login login user and password"`
|
||||||
|
Log logConfig `cmd:"log service log configuration"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type typesConfig struct {
|
||||||
|
BoolValue bool `cmd:"bool boolean value"`
|
||||||
|
StrValue string `cmd:"str string value"`
|
||||||
|
Int8Value int8 `cmd:"int8 int8 value"`
|
||||||
|
Int16Value int16 `cmd:"int16 int16 value"`
|
||||||
|
IntValue int `cmd:"int int value"`
|
||||||
|
Int32Value int32 `cmd:"int32 int32 value"`
|
||||||
|
Int64Value int64 `cmd:"int64 int64 value"`
|
||||||
|
Uint8Value uint8 `cmd:"uint8 uint8 value"`
|
||||||
|
Uint16Value uint16 `cmd:"uint16 uint16 value"`
|
||||||
|
UintValue uint `cmd:"uint uint value"`
|
||||||
|
Uint32Value uint32 `cmd:"uint32 uint32 value"`
|
||||||
|
Uint64Value uint64 `cmd:"uint64 uint64 value"`
|
||||||
|
Float32Value float32 `cmd:"float32 float32 value"`
|
||||||
|
Float64Value float64 `cmd:"float64 float64 value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type defValueConfig struct {
|
||||||
|
BoolValue bool `cmd:"bool boolean value" default:"true"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServiceCommand(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
serviceConfig := serviceConfig{}
|
||||||
|
cmd := New("Service")
|
||||||
|
err := cmd.Init(&serviceConfig)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Can't init service command. %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// assert service cmd
|
||||||
|
assert.NotNil(cmd.FlagSet)
|
||||||
|
assert.NotNil(cmd.FlagSet.Lookup("hostname"),
|
||||||
|
"service cmd should have {hostname} parameter")
|
||||||
|
assert.NotNil(cmd.FlagSet.Lookup("port"),
|
||||||
|
"service cmd should have {port} parameter")
|
||||||
|
assert.Equal(2, len(cmd.SubCommands),
|
||||||
|
"service cmd should have 2 sub cmds")
|
||||||
|
assert.Nil(cmd.SubCommands["login"],
|
||||||
|
"service cmd shouldn't have {login} sub cmd")
|
||||||
|
|
||||||
|
// assert database sub cmd
|
||||||
|
dbCmd := cmd.SubCommands["database"]
|
||||||
|
assert.NotNil(dbCmd, "service cmd should have {database} sub cmd")
|
||||||
|
assert.NotNil(dbCmd.FlagSet.Lookup("dbHost"),
|
||||||
|
"database cmd should have {dbHost} parameter")
|
||||||
|
assert.NotNil(dbCmd.FlagSet.Lookup("dbPort"),
|
||||||
|
"database cmd should have {dbPort} parameter")
|
||||||
|
assert.NotNil(dbCmd.FlagSet.Lookup("dbUser"),
|
||||||
|
"database cmd should have {dbUser} parameter")
|
||||||
|
assert.NotNil(dbCmd.FlagSet.Lookup("dbPassword"),
|
||||||
|
"database cmd should have {dbPassword} parameter")
|
||||||
|
|
||||||
|
// assert database log sub cmd
|
||||||
|
dbLogCmd := dbCmd.SubCommands["log"]
|
||||||
|
assert.NotNil(dbCmd, "database cmd should have {log} sub cmd")
|
||||||
|
assert.NotNil(dbLogCmd.FlagSet.Lookup("path"),
|
||||||
|
"database log cmd should have {path} parameter")
|
||||||
|
assert.NotNil(dbLogCmd.FlagSet.Lookup("level"),
|
||||||
|
"database log cmd should have {level} parameter")
|
||||||
|
assert.Equal(0, len(dbLogCmd.SubCommands),
|
||||||
|
"database log cmd shouldn't have sub cmd")
|
||||||
|
|
||||||
|
// assert log cmd
|
||||||
|
logCmd := cmd.SubCommands["log"]
|
||||||
|
assert.NotNil(logCmd, "service cmd should have {log} sub cmd")
|
||||||
|
assert.NotNil(logCmd.FlagSet.Lookup("path"),
|
||||||
|
"log cmd should have {path} parameter")
|
||||||
|
assert.NotNil(logCmd.FlagSet.Lookup("level"),
|
||||||
|
"log cmd should have {level} parameter")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoginSubCommand(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
serviceConfig := serviceConfig{Login: &loginConfig{}}
|
||||||
|
cmd := New("Service")
|
||||||
|
assert.NoError(cmd.Init(&serviceConfig), "Can't init service command")
|
||||||
|
|
||||||
|
// assert login sub command
|
||||||
|
loginCmd := cmd.SubCommands["login"]
|
||||||
|
assert.NotNil(loginCmd, "service cmd should have {login} sub cmd")
|
||||||
|
assert.NotNil(loginCmd.FlagSet.Lookup("user"),
|
||||||
|
"login cmd should have {user} parameter")
|
||||||
|
assert.NotNil(loginCmd.FlagSet.Lookup("password"),
|
||||||
|
"login cmd should have {password} parameter")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoginCommandWithValues(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
loginConfig := loginConfig{}
|
||||||
|
cmd := New("Login")
|
||||||
|
assert.NoError(cmd.Init(&loginConfig), "Can't init login command")
|
||||||
|
|
||||||
|
username := "test-user"
|
||||||
|
password := "test-passwd"
|
||||||
|
args := []string{"-user", username, "--password", password}
|
||||||
|
assert.NoError(cmd.Parse(args), "Can't parse login command")
|
||||||
|
assert.Equal(username, loginConfig.User, "Failed to parse login command")
|
||||||
|
assert.Equal(password, loginConfig.Password, "Failed to parse login command")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServiceCommandWithValues(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
serviceConfig := serviceConfig{Login: &loginConfig{}}
|
||||||
|
cmd := New("Service")
|
||||||
|
assert.NoError(cmd.Init(&serviceConfig), "Can't init service command")
|
||||||
|
|
||||||
|
serviceHost := "service-hostname"
|
||||||
|
servicePort := 8080
|
||||||
|
serviceLogPath := "service-log-path"
|
||||||
|
serviceLogLevel := "service-log-debug"
|
||||||
|
|
||||||
|
dbHost := "database-hostname"
|
||||||
|
dbPort := 9080
|
||||||
|
dbUser := "database-user"
|
||||||
|
dbPassword := "database-passwd"
|
||||||
|
dbLogPath := "database-log-path"
|
||||||
|
dbLogLevel := "database-log-error"
|
||||||
|
|
||||||
|
loginUser := "login-user"
|
||||||
|
loginPassword := "login-passwd"
|
||||||
|
|
||||||
|
serviceArgs := []string{"--hostname", serviceHost, "--port",
|
||||||
|
strconv.Itoa(servicePort), "log", "-path", serviceLogPath, "-level",
|
||||||
|
serviceLogLevel}
|
||||||
|
assert.NoError(cmd.Parse(serviceArgs), "Can't parse service command")
|
||||||
|
assert.Equal(serviceHost, serviceConfig.Host,
|
||||||
|
"Service hostname is not equal")
|
||||||
|
assert.Equal(servicePort, serviceConfig.Port,
|
||||||
|
"Service port is not equal")
|
||||||
|
assert.Equal(serviceLogPath, serviceConfig.Log.Path,
|
||||||
|
"Service log path is not equal")
|
||||||
|
assert.Equal(serviceLogLevel, serviceConfig.Log.Level,
|
||||||
|
"Service log level is not equal")
|
||||||
|
|
||||||
|
dbCmdArgs := []string{"database", "-dbHost", dbHost, "-dbPort",
|
||||||
|
strconv.Itoa(dbPort), "-dbUser", dbUser, "-dbPassword", dbPassword}
|
||||||
|
assert.NoError(cmd.Parse(dbCmdArgs), "Can't parse service database command")
|
||||||
|
assert.Equal(dbHost, serviceConfig.DBConfig.Host,
|
||||||
|
"Database hostname is not equal")
|
||||||
|
assert.Equal(dbPort, serviceConfig.DBConfig.Port,
|
||||||
|
"Database port is not equal")
|
||||||
|
assert.Equal(dbUser, serviceConfig.DBConfig.User,
|
||||||
|
"Database username is not equal")
|
||||||
|
assert.Equal(dbPassword, serviceConfig.DBConfig.Password,
|
||||||
|
"Database password is not equal")
|
||||||
|
|
||||||
|
loginCmdArgs := []string{"login", "--user", loginUser, "-password",
|
||||||
|
loginPassword}
|
||||||
|
assert.NoError(cmd.Parse(loginCmdArgs), "Can't parse service login command")
|
||||||
|
assert.Equal(loginUser, serviceConfig.Login.User,
|
||||||
|
"Login username is not equal")
|
||||||
|
assert.Equal(loginPassword, serviceConfig.Login.Password,
|
||||||
|
"Login password is not equal")
|
||||||
|
|
||||||
|
dbLogCmdArgs := []string{"database", "log", "-path", dbLogPath, "-level",
|
||||||
|
dbLogLevel}
|
||||||
|
assert.NoError(cmd.Parse(dbLogCmdArgs), "Can't parse database log command")
|
||||||
|
assert.Equal(dbLogPath, serviceConfig.DBConfig.Log.Path,
|
||||||
|
"Database log path is not equal")
|
||||||
|
assert.Equal(dbLogLevel, serviceConfig.DBConfig.Log.Level,
|
||||||
|
"Database log level is not equal")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVariousTypeCommand(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
typesConfig := typesConfig{}
|
||||||
|
cmd := NewWith("Types", flag.ContinueOnError, nil)
|
||||||
|
assert.NoError(cmd.Init(&typesConfig))
|
||||||
|
|
||||||
|
// bool value
|
||||||
|
assert.NoError(cmd.Parse([]string{"-bool=true"}),
|
||||||
|
"Can't parse bool value command")
|
||||||
|
assert.Equal(true, typesConfig.BoolValue, "Bool value is not true")
|
||||||
|
assert.NoError(cmd.Parse([]string{"-bool"}),
|
||||||
|
"Can't parse bool value command")
|
||||||
|
assert.Equal(true, typesConfig.BoolValue, "Bool value is not false")
|
||||||
|
assert.Error(cmd.Parse([]string{"-bool=xxx"}),
|
||||||
|
"Parsing string as bool should have an error")
|
||||||
|
|
||||||
|
// string value
|
||||||
|
assert.NoError(cmd.Parse([]string{"-str=xxx"}),
|
||||||
|
"Can't parse string value command")
|
||||||
|
assert.Equal("xxx", typesConfig.StrValue, "String value it not equal")
|
||||||
|
assert.NoError(cmd.Parse([]string{"-str", "yyy"}),
|
||||||
|
"Can't parse string value command")
|
||||||
|
assert.Equal("yyy", typesConfig.StrValue, "String value is not equal")
|
||||||
|
|
||||||
|
// int8 value
|
||||||
|
assert.NoError(cmd.Parse([]string{"-int8=100"}),
|
||||||
|
"Can't parse int8 value command")
|
||||||
|
assert.Equal(int8(100), typesConfig.Int8Value, "Int8 value is not equal")
|
||||||
|
assert.Error(cmd.Parse([]string{"-int8=xxx"}),
|
||||||
|
"Parsing string as int8 should have an error")
|
||||||
|
|
||||||
|
// int16 value
|
||||||
|
assert.NoError(cmd.Parse([]string{"-int16=200"}),
|
||||||
|
"Can't parse int16 value command")
|
||||||
|
assert.Equal(int16(200), typesConfig.Int16Value, "Int16 value is not equal")
|
||||||
|
assert.Error(cmd.Parse([]string{"-int16=xxx"}),
|
||||||
|
"Parsing string as int16 should have an error")
|
||||||
|
|
||||||
|
// int value
|
||||||
|
assert.NoError(cmd.Parse([]string{"-int=300"}),
|
||||||
|
"Can't parse int value command")
|
||||||
|
assert.Equal(int(300), typesConfig.IntValue, "Int value is not equal")
|
||||||
|
assert.Error(cmd.Parse([]string{"-int=xxx"}),
|
||||||
|
"Parsing string as int should have an error")
|
||||||
|
|
||||||
|
// int32 value
|
||||||
|
assert.NoError(cmd.Parse([]string{"-int32=400"}),
|
||||||
|
"Can't parse int32 value command")
|
||||||
|
assert.Equal(int32(400), typesConfig.Int32Value, "Int32 value is not equal")
|
||||||
|
assert.Error(cmd.Parse([]string{"-int32=xxx"}),
|
||||||
|
"Parsing string as int32 should have an error")
|
||||||
|
|
||||||
|
// int64 value
|
||||||
|
assert.NoError(cmd.Parse([]string{"-int64=500"}),
|
||||||
|
"Can't parse int64 value command")
|
||||||
|
assert.Equal(int64(500), typesConfig.Int64Value, "Int64 value is not equal")
|
||||||
|
assert.Error(cmd.Parse([]string{"-int64=xxx"}),
|
||||||
|
"Parsing string as int64 should have an error")
|
||||||
|
|
||||||
|
// uint8 value
|
||||||
|
assert.NoError(cmd.Parse([]string{"-uint8=10"}),
|
||||||
|
"Can't parse uint8 value command")
|
||||||
|
assert.Equal(uint8(10), typesConfig.Uint8Value, "Uint8 value is not equal")
|
||||||
|
assert.Error(cmd.Parse([]string{"-uint8=-10"}),
|
||||||
|
"Parsing string as uint8 should have an error")
|
||||||
|
|
||||||
|
// uint16 value
|
||||||
|
assert.NoError(cmd.Parse([]string{"-uint16=1000"}),
|
||||||
|
"Can't parse uint16 value command")
|
||||||
|
assert.Equal(uint16(1000), typesConfig.Uint16Value,
|
||||||
|
"Uint16 value is not equal")
|
||||||
|
assert.Error(cmd.Parse([]string{"-uint16=xxx"}),
|
||||||
|
"Parsing string as uint16 should have an error")
|
||||||
|
|
||||||
|
// uint value
|
||||||
|
assert.NoError(cmd.Parse([]string{"-uint=2000"}),
|
||||||
|
"Can't parse uint value command")
|
||||||
|
assert.Equal(uint(2000), typesConfig.UintValue, "Uint value is not equal")
|
||||||
|
assert.Error(cmd.Parse([]string{"-uint=xxx"}),
|
||||||
|
"Parsing string as uint should have an error")
|
||||||
|
|
||||||
|
// uint32 value
|
||||||
|
assert.NoError(cmd.Parse([]string{"-uint32=3000"}),
|
||||||
|
"Can't parse uint32 value command")
|
||||||
|
assert.Equal(uint32(3000), typesConfig.Uint32Value,
|
||||||
|
"Uint32 value is not equal")
|
||||||
|
assert.Error(cmd.Parse([]string{"-uint32=xxx"}),
|
||||||
|
"Parsing string as uint32 should have an error")
|
||||||
|
|
||||||
|
// uint64 value
|
||||||
|
assert.NoError(cmd.Parse([]string{"-uint64=4000"}),
|
||||||
|
"Can't parse uint64 value command")
|
||||||
|
assert.Equal(uint64(4000), typesConfig.Uint64Value,
|
||||||
|
"Uint64 value is not equal")
|
||||||
|
assert.Error(cmd.Parse([]string{"-uint64=xxx"}),
|
||||||
|
"Parsing string as uint64 should have an error")
|
||||||
|
|
||||||
|
// float32 value
|
||||||
|
assert.NoError(cmd.Parse([]string{"-float32=1.234"}),
|
||||||
|
"Can't parse float32 value command")
|
||||||
|
assert.Equal(float32(1.234), typesConfig.Float32Value,
|
||||||
|
"Float32 value is not equal")
|
||||||
|
assert.Error(cmd.Parse([]string{"-float32=xxx"}),
|
||||||
|
"Parsing string as float32 should have an error")
|
||||||
|
|
||||||
|
// float64 value
|
||||||
|
assert.NoError(cmd.Parse([]string{"-float64=2.345"}),
|
||||||
|
"Can't parse float64 value command")
|
||||||
|
assert.Equal(float64(2.345), typesConfig.Float64Value,
|
||||||
|
"Float64 value is not equal")
|
||||||
|
assert.Error(cmd.Parse([]string{"-float64=xxx"}),
|
||||||
|
"Parsing string as float64 should have an error")
|
||||||
|
}
|
139
cmd/cmd.go
139
cmd/cmd.go
@ -1,139 +0,0 @@
|
|||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/eschao/config/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
type anyValue struct {
|
|
||||||
any reflect.Value
|
|
||||||
}
|
|
||||||
|
|
||||||
func newAnyValue(v Value) *anyValue {
|
|
||||||
return anyValue{any: v}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *anyValue) String() string {
|
|
||||||
return this.any.Kind().String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *anyValue) Set(v string) error {
|
|
||||||
kind := this.any.Kind()
|
|
||||||
switch kind {
|
|
||||||
case reflect.Bool:
|
|
||||||
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type Command struct {
|
|
||||||
Name string
|
|
||||||
FlagSet *flag.FlagSet
|
|
||||||
Usage string
|
|
||||||
SubCommands map[string]*Command
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(name string) *Command {
|
|
||||||
cmd := Command{
|
|
||||||
Name: name,
|
|
||||||
FlagSet: flag.NewFlagSet(name, flag.ExitOnError),
|
|
||||||
SubCommands: make(map[string]*Command),
|
|
||||||
}
|
|
||||||
return &cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *Command) Init(i interface{}) error {
|
|
||||||
ptrRef := reflect.ValueOf(i)
|
|
||||||
|
|
||||||
if ptrRef.IsNil() || ptrRef.Kind() != reflect.Ptr {
|
|
||||||
return fmt.Errorf("Expect a structure pointer type instead of %s",
|
|
||||||
ptrRef.Kind().String())
|
|
||||||
}
|
|
||||||
|
|
||||||
valueOfStruct := ptrRef.Elem()
|
|
||||||
if valueOfStruct.Kind() != reflect.Struct {
|
|
||||||
return fmt.Errorf("Expect a structure type instead of %s",
|
|
||||||
valueOfStruct.Kind().String())
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.parseValue(valueOfStruct)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *Command) parseValue(v reflect.Value) error {
|
|
||||||
typeOfStruct := v.Type()
|
|
||||||
|
|
||||||
for i := 0; i < v.NumField(); i++ {
|
|
||||||
valueOfField := v.Field(i)
|
|
||||||
kindOfField := valueOfField.Kind()
|
|
||||||
structOfField := typeOfStruct.Field(i)
|
|
||||||
|
|
||||||
if kindOfField == reflect.Ptr {
|
|
||||||
if !valueOfField.IsNil() && valueOfField.CanSet() {
|
|
||||||
cmd := this.createCliFlagSet(structOfField.Tag)
|
|
||||||
if err := cmd.Init(valueOfField.Interface()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if kindOfField == reflect.Struct {
|
|
||||||
cmd := this.createCliFlagSet(structOfField.Tag)
|
|
||||||
if err := cmd.parseValue(valueOfField); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.addFlag(valueOfField, structOfField)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *Command) addFlag(v reflect.Value, f reflect.StructField) {
|
|
||||||
cmdTag, ok := f.Tag.Lookup("cmd")
|
|
||||||
if !ok || cmdTag == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
firstSpace := strings.Index(cmdTag, " ")
|
|
||||||
name := cmdTag
|
|
||||||
usage := ""
|
|
||||||
if firstSpace > 0 {
|
|
||||||
name = cmdTag[0:firstSpace]
|
|
||||||
usage = cmdTag[firstSpace+1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
//defValue, ok := f.Tag.Lookup("default")
|
|
||||||
vFlag := ValueFlag{Value: v}
|
|
||||||
this.FlagSet.Var(&vFlag, name, usage)
|
|
||||||
//fmt.Printf("[%s]: Added Flag: %s\n", this.Name, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *Command) createCliFlagSet(tag reflect.StructTag) *Command {
|
|
||||||
cmdTag, ok := tag.Lookup("cmd")
|
|
||||||
if !ok || cmdTag == "" {
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := Command{SubCommands: make(map[string]*Command)}
|
|
||||||
firstSpace := strings.Index(cmdTag, " ")
|
|
||||||
name := cmdTag
|
|
||||||
usage := ""
|
|
||||||
if firstSpace > 0 {
|
|
||||||
name = cmdTag[0:firstSpace]
|
|
||||||
usage = cmdTag[firstSpace+1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd.Name = name
|
|
||||||
cmd.FlagSet = flag.NewFlagSet(name, flag.ExitOnError)
|
|
||||||
cmd.Usage = usage
|
|
||||||
this.SubCommands[name] = &cmd
|
|
||||||
return &cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *Command) Parse(i interface{}, args []string) error {
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
120
cmd/cmd_test.go
120
cmd/cmd_test.go
@ -1,120 +0,0 @@
|
|||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
type dbConfig struct {
|
|
||||||
Host string `cmd:"dbHost database server hostname"`
|
|
||||||
Port int `cmd:"dbPort database server port"`
|
|
||||||
User string `cmd:"dbUser database username"`
|
|
||||||
Password string `cmd:"dbPassword database user password"`
|
|
||||||
Log logConfig `cmd:"log database log configuration"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type loginConfig struct {
|
|
||||||
User string `cmd:"user login username"`
|
|
||||||
Password string `cmd:"password login password"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type logConfig struct {
|
|
||||||
Path string `cmd:"path log path"`
|
|
||||||
Level string `cmd:"level log level {debug|warning|error}"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type serviceConfig struct {
|
|
||||||
Host string `cmd:"hostname service hostname"`
|
|
||||||
Port int `cmd:"port service port"`
|
|
||||||
DBConfig dbConfig `cmd:"database database configuration"`
|
|
||||||
Login *loginConfig `cmd:"login login user and password"`
|
|
||||||
Log logConfig `cmd:"log service log configuration"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestServiceCommand(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
serviceConfig := serviceConfig{}
|
|
||||||
cmd := New("Service")
|
|
||||||
err := cmd.Init(&serviceConfig)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Can't init service command. %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// assert service cmd
|
|
||||||
assert.NotNil(cmd.FlagSet)
|
|
||||||
assert.NotNil(cmd.FlagSet.Lookup("hostname"),
|
|
||||||
"service cmd should have {hostname} parameter")
|
|
||||||
assert.NotNil(cmd.FlagSet.Lookup("port"),
|
|
||||||
"service cmd should have {port} parameter")
|
|
||||||
assert.Equal(2, len(cmd.SubCommands),
|
|
||||||
"service cmd should have 2 sub cmds")
|
|
||||||
assert.Nil(cmd.SubCommands["login"],
|
|
||||||
"service cmd shouldn't have {login} sub cmd")
|
|
||||||
|
|
||||||
// assert database sub cmd
|
|
||||||
dbCmd := cmd.SubCommands["database"]
|
|
||||||
assert.NotNil(dbCmd, "service cmd should have {database} sub cmd")
|
|
||||||
assert.NotNil(dbCmd.FlagSet.Lookup("dbHost"),
|
|
||||||
"database cmd should have {dbHost} parameter")
|
|
||||||
assert.NotNil(dbCmd.FlagSet.Lookup("dbPort"),
|
|
||||||
"database cmd should have {dbPort} parameter")
|
|
||||||
assert.NotNil(dbCmd.FlagSet.Lookup("dbUser"),
|
|
||||||
"database cmd should have {dbUser} parameter")
|
|
||||||
assert.NotNil(dbCmd.FlagSet.Lookup("dbPassword"),
|
|
||||||
"database cmd should have {dbPassword} parameter")
|
|
||||||
|
|
||||||
// assert database log sub cmd
|
|
||||||
dbLogCmd := dbCmd.SubCommands["log"]
|
|
||||||
assert.NotNil(dbCmd, "database cmd should have {log} sub cmd")
|
|
||||||
assert.NotNil(dbLogCmd.FlagSet.Lookup("path"),
|
|
||||||
"database log cmd should have {path} parameter")
|
|
||||||
assert.NotNil(dbLogCmd.FlagSet.Lookup("level"),
|
|
||||||
"database log cmd should have {level} parameter")
|
|
||||||
assert.Equal(0, len(dbLogCmd.SubCommands),
|
|
||||||
"database log cmd shouldn't have sub cmd")
|
|
||||||
|
|
||||||
// assert log cmd
|
|
||||||
logCmd := cmd.SubCommands["log"]
|
|
||||||
assert.NotNil(logCmd, "service cmd should have {log} sub cmd")
|
|
||||||
assert.NotNil(logCmd.FlagSet.Lookup("path"),
|
|
||||||
"log cmd should have {path} parameter")
|
|
||||||
assert.NotNil(logCmd.FlagSet.Lookup("level"),
|
|
||||||
"log cmd should have {level} parameter")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLoginSubCommand(t *testing.T) {
|
|
||||||
assert := assert.New(t)
|
|
||||||
serviceConfig := serviceConfig{Login: &loginConfig{}}
|
|
||||||
cmd := New("Service")
|
|
||||||
err := cmd.Init(&serviceConfig)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Can't init service command. %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// assert login sub command
|
|
||||||
loginCmd := cmd.SubCommands["login"]
|
|
||||||
assert.NotNil(loginCmd, "service cmd should have {login} sub cmd")
|
|
||||||
assert.NotNil(loginCmd.FlagSet.Lookup("user"),
|
|
||||||
"login cmd should have {user} parameter")
|
|
||||||
assert.NotNil(loginCmd.FlagSet.Lookup("password"),
|
|
||||||
"login cmd should have {password} parameter")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLoginCommand(t *testing.T) {
|
|
||||||
loginConfig := loginConfig{}
|
|
||||||
cmd := New("Login")
|
|
||||||
if err := cmd.Init(&loginConfig); err != nil {
|
|
||||||
t.Errorf("Can't init login command. %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
args := []string{"-user", "test", "-password", "pass", "log", "database"}
|
|
||||||
if err := cmd.FlagSet.Parse(args); err != nil {
|
|
||||||
t.Errorf("Can't parse login command. %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
uknArgs := cmd.FlagSet.Args()
|
|
||||||
for i, arg := range uknArgs {
|
|
||||||
t.Logf("arg[%d]=%s", i, arg)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
package util
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
"reflect"
|
||||||
@ -26,7 +26,7 @@ func SetValueWithFloatX(v reflect.Value, floatValue string, bitSize int) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func SetValueWithIntX(v reflect.Value, intValue string, bitSize int) error {
|
func SetValueWithIntX(v reflect.Value, intValue string, bitSize int) error {
|
||||||
value, err := strconv.ParseInt(envValue, 10, bitSize)
|
value, err := strconv.ParseInt(intValue, 10, bitSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -35,8 +35,8 @@ func SetValueWithIntX(v reflect.Value, intValue string, bitSize int) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetValueWithUintX(v reflect.Value, envValue string, bitSize int) error {
|
func SetValueWithUintX(v reflect.Value, uintValue string, bitSize int) error {
|
||||||
value, err := strconv.ParseUint(envValue, 10, bitSize)
|
value, err := strconv.ParseUint(uintValue, 10, bitSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user