2
0
This commit is contained in:
eschao 2017-12-10 11:33:03 +08:00
parent 8f6edca654
commit 26b2fecb36
9 changed files with 477 additions and 157 deletions

View File

@ -1,118 +0,0 @@
package cli
import (
"flag"
"fmt"
"reflect"
"strings"
)
type CliFlag struct {
Name string
Usage string
DefValue *string
CliFlags *[]CliFlag
}
type ValueFlag struct {
Value reflect.Value
}
func (this *ValueFlag) String() string {
return this.Value.Kind().String()
}
func (this *ValueFlag) Set(v string) error {
return nil
}
func Parse(i interface{}, cliFlag *CliFlag) 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 parseValue(valueOfStruct, cliFlag)
}
func parseValue(v reflect.Value, cliParent *CliFlag) error {
cliFlags := []CliFlag{}
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() {
cliFlag := createCliFlagFromTag(structOfField.Tag)
if cliFlag != nil {
cliFlags = append(cliFlags, *cliFlag)
} else {
cliFlag = cliParent
}
Parse(valueOfField.Interface(), cliFlag)
} else {
continue
}
} else if kindOfField == reflect.Struct {
cliFlag := createCliFlagFromTag(structOfField.Tag)
if cliFlag != nil {
cliFlags = append(cliFlags, *cliFlag)
} else {
cliFlag = cliParent
}
parseValue(valueOfField, cliFlag)
}
if cliFlag := installFlag(valueOfField, structOfField); cliFlag != nil {
cliFlags = append(cliFlags, *cliFlag)
}
}
if len(cliFlags) > 0 {
cliParent.CliFlags = &cliFlags
}
return nil
}
func installFlag(v reflect.Value, f reflect.StructField) *CliFlag {
cliFlag := createCliFlagFromTag(f.Tag)
if cliFlag != nil {
vFlag := ValueFlag{Value: v}
flag.Var(&vFlag, cliFlag.Name, cliFlag.Usage)
}
fmt.Printf("Installed flag: %d", cliFlag.Name)
return cliFlag
}
func createCliFlagFromTag(tag reflect.StructTag) *CliFlag {
cliTag, ok := tag.Lookup("cli")
if !ok || cliTag == "" {
return nil
}
cliFlag := CliFlag{}
firstSpace := strings.Index(cliTag, " ")
cliFlag.Name = cliTag
if firstSpace > 0 {
cliFlag.Name = cliTag[0:firstSpace]
cliFlag.Usage = cliTag[firstSpace+1:]
}
defValue, ok := tag.Lookup("default")
if !ok {
cliFlag.DefValue = &defValue
}
return &cliFlag
}

View File

@ -1,38 +0,0 @@
package cli
import (
"flag"
"fmt"
"testing"
)
type DBCli struct {
Host string `cli:"hostname database server hostname"`
Port string `cli:"port database server port"`
User string `cli:"user database username"`
Password string `cli:"password database user password"`
}
type MyCli struct {
Server string `cli:"-server server URL"`
DBCli DBCli `cli:"database database information"`
}
func TestMain(m *testing.M) {
}
func TestCli(t *testing.T) {
hostname := flag.String("hostanme", "127.0.0.1", "hostanme value")
fmt.Printf("Hostname: %s", hostname)
cli := DBCli{}
root := CliFlag{}
err := Parse(&cli, &root)
if err != nil {
t.Errorf("Can't parse cli. %s", err.Error())
}
flag.Parse()
}

BIN
cmd/.cli.go.swp Normal file

Binary file not shown.

BIN
cmd/.cli_test.go.swp Normal file

Binary file not shown.

139
cmd/cmd.go Normal file
View File

@ -0,0 +1,139 @@
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 Normal file
View File

@ -0,0 +1,120 @@
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)
}
}

BIN
main Executable file

Binary file not shown.

106
main.go
View File

@ -1,10 +1,114 @@
package main package main
import ( import (
"flag"
"fmt" "fmt"
"os"
//"github.com/eschao/Config/cli"
) )
type DBCli struct {
Host string `cli:"hostname database server hostname"`
Port string `cli:"port database server port"`
User string `cli:"user database username"`
Password string `cli:"password database user password"`
}
func main() { func main() {
cli := DBCli{}
countCommand := flag.NewFlagSet("count", flag.ExitOnError)
listCommand := flag.NewFlagSet("list", flag.ExitOnError)
// Count subcommand flag pointers
// Adding a new choice for --metric of 'substring' and a new --substring flag
countTextPtr := countCommand.String("text", "", "Text to parse. (Required)")
countMetricPtr := countCommand.String("metric", "chars", "Metric {chars|words|lines|substring}. (Required)")
countSubstringPtr := countCommand.String("substring", "", "The substring to be counted. Required for --metric=substring")
countUniquePtr := countCommand.Bool("unique", false, "Measure unique values of a metric.")
// List subcommand flag pointers
listTextPtr := listCommand.String("text", "", "Text to parse. (Required)")
listMetricPtr := listCommand.String("metric", "chars", "Metric <chars|words|lines>. (Required)")
listUniquePtr := listCommand.Bool("unique", false, "Measure unique values of a metric.")
// Verify that a subcommand has been provided
// os.Arg[0] is the main command
// os.Arg[1] will be the subcommand
if len(os.Args) < 2 {
//fmt.Println("list or count subcommand is required")
flag.PrintDefaults()
os.Exit(1)
}
// Switch on the subcommand
// Parse the flags for appropriate FlagSet
// FlagSet.Parse() requires a set of arguments to parse as input
// os.Args[2:] will be all arguments starting after the subcommand at os.Args[1]
switch os.Args[1] {
case "list":
listCommand.Parse(os.Args[2:])
case "count":
countCommand.Parse(os.Args[2:])
default:
flag.PrintDefaults()
os.Exit(1)
}
// Check which subcommand was Parsed using the FlagSet.Parsed() function. Handle each case accordingly.
// FlagSet.Parse() will evaluate to false if no flags were parsed (i.e. the user did not provide any flags)
if listCommand.Parsed() {
// Required Flags
if *listTextPtr == "" {
listCommand.PrintDefaults()
os.Exit(1)
}
//Choice flag
metricChoices := map[string]bool{"chars": true, "words": true, "lines": true}
if _, validChoice := metricChoices[*listMetricPtr]; !validChoice {
listCommand.PrintDefaults()
os.Exit(1)
}
// Print
fmt.Printf("textPtr: %s, metricPtr: %s, uniquePtr: %t\n", *listTextPtr, *listMetricPtr, *listUniquePtr)
}
if countCommand.Parsed() {
// Required Flags
if *countTextPtr == "" {
countCommand.PrintDefaults()
os.Exit(1)
}
// If the metric flag is substring, the substring flag is required
if *countMetricPtr == "substring" && *countSubstringPtr == "" {
countCommand.PrintDefaults()
os.Exit(1)
}
//If the metric flag is not substring, the substring flag must not be used
if *countMetricPtr != "substring" && *countSubstringPtr != "" {
fmt.Println("--substring may only be used with --metric=substring.")
countCommand.PrintDefaults()
os.Exit(1)
}
//Choice flag
metricChoices := map[string]bool{"chars": true, "words": true, "lines": true, "substring": true}
if _, validChoice := metricChoices[*listMetricPtr]; !validChoice {
countCommand.PrintDefaults()
os.Exit(1)
}
//Print
fmt.Printf("textPtr: %s, metricPtr: %s, substringPtr: %v, uniquePtr: %t\n", *countTextPtr, *countMetricPtr, *countSubstringPtr, *countUniquePtr)
}
/*
config := DBCli{}
root := cli.CliFlag{}
err := cli.Parse(&config, &root)
if err != nil {
fmt.Printf("Can't parse cli. %s", err.Error())
}
flag.Parse()
fmt.Printf("Host=%s\n", config.Host)
*/
} }

113
util/util.go Normal file
View File

@ -0,0 +1,113 @@
package util
import (
"reflect"
"strconv"
)
func SetValueWithBool(v reflect.Value, boolValue string) error {
value, err := strconv.ParseBool(boolValue)
if err != nil {
return err
}
v.SetBool(value)
return nil
}
func SetValueWithFloatX(v reflect.Value, floatValue string, bitSize int) error {
value, err := strconv.ParseFloat(floatValue, bitSize)
if err != nil {
return err
}
v.SetFloat(value)
return nil
}
func SetValueWithIntX(v reflect.Value, intValue string, bitSize int) error {
value, err := strconv.ParseInt(envValue, 10, bitSize)
if err != nil {
return err
}
v.SetInt(value)
return nil
}
func SetValueWithUintX(v reflect.Value, envValue string, bitSize int) error {
value, err := strconv.ParseUint(envValue, 10, bitSize)
if err != nil {
return err
}
v.SetUint(value)
return nil
}
/*
func setFieldValueWithSlice(name string, v reflect.Value, envValue string,
separator string) error {
data := strings.Split(envValue, separator)
size := len(data)
if size > 0 {
slice := reflect.MakeSlice(v.Type(), size, size)
for i := 0; i < size; i++ {
ele := slice.Index(i)
kind := ele.Kind()
switch kind {
case reflect.Bool:
if err := setFieldValueWithBool(name, ele, data[i]); err != nil {
return err
}
case reflect.String:
ele.SetString(data[i])
case reflect.Uint8:
if err := setFieldValueWithUintX(name, ele, data[i], 8); err != nil {
return err
}
case reflect.Uint16:
if err := setFieldValueWithUintX(name, ele, data[i], 16); err != nil {
return err
}
case reflect.Uint, reflect.Uint32:
if err := setFieldValueWithUintX(name, ele, data[i], 32); err != nil {
return err
}
case reflect.Uint64:
if err := setFieldValueWithUintX(name, ele, data[i], 64); err != nil {
return err
}
case reflect.Int8:
if err := setFieldValueWithIntX(name, ele, data[i], 8); err != nil {
return err
}
case reflect.Int16:
if err := setFieldValueWithIntX(name, ele, data[i], 16); err != nil {
return err
}
case reflect.Int, reflect.Int32:
if err := setFieldValueWithIntX(name, ele, data[i], 32); err != nil {
return err
}
case reflect.Int64:
if err := setFieldValueWithIntX(name, ele, data[i], 64); err != nil {
return err
}
case reflect.Float32:
if err := setFieldValueWithFloatX(name, ele, data[i], 32); err != nil {
return err
}
case reflect.Float64:
if err := setFieldValueWithFloatX(name, ele, data[i], 64); err != nil {
return err
}
default:
return fmt.Errorf("%s: can't support type: %s", name, kind.String())
}
}
v.Set(slice)
}
return nil
}*/