diff --git a/cli/cli.go b/cli/cli.go deleted file mode 100644 index d5a9cde..0000000 --- a/cli/cli.go +++ /dev/null @@ -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 -} diff --git a/cli/cli_test.go b/cli/cli_test.go deleted file mode 100644 index 3688c5a..0000000 --- a/cli/cli_test.go +++ /dev/null @@ -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() - -} diff --git a/cmd/.cli.go.swp b/cmd/.cli.go.swp new file mode 100644 index 0000000..be02622 Binary files /dev/null and b/cmd/.cli.go.swp differ diff --git a/cmd/.cli_test.go.swp b/cmd/.cli_test.go.swp new file mode 100644 index 0000000..bfbceae Binary files /dev/null and b/cmd/.cli_test.go.swp differ diff --git a/cmd/cmd.go b/cmd/cmd.go new file mode 100644 index 0000000..9bc562e --- /dev/null +++ b/cmd/cmd.go @@ -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 +} diff --git a/cmd/cmd_test.go b/cmd/cmd_test.go new file mode 100644 index 0000000..1307f9c --- /dev/null +++ b/cmd/cmd_test.go @@ -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) + } +} diff --git a/main b/main new file mode 100755 index 0000000..1f4cd64 Binary files /dev/null and b/main differ diff --git a/main.go b/main.go index 4bce14b..100a24a 100644 --- a/main.go +++ b/main.go @@ -1,10 +1,114 @@ package main import ( + "flag" "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() { - 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 . (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) + */ } diff --git a/util/util.go b/util/util.go new file mode 100644 index 0000000..76011b4 --- /dev/null +++ b/util/util.go @@ -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 +}*/