diff --git a/README.md b/README.md index ef83c74..247309d 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ clop (Command Line Option Parse)是一款基于struct的命令行解析器,麻 * 效验模式支持,不需要写一堆的```if x!= "" ``` or ```if y!=0```浪费青春的代码 * 可以获取命令优先级别,方便设置命令别名 * 解析flag包代码生成clop代码 +* 指定`解析`函数, 自动绑定数据 ![feature list](https://github.com/guonaihong/images/blob/master/clop/featurelist.png) @@ -49,6 +50,7 @@ clop (Command Line Option Parse)是一款基于struct的命令行解析器,麻 - [6. Can only be set once](#can-only-be-set-once) - [7. Quick write](#quick-write) - [8. Multi structure series](#multi-structure-series) + - [9. Support callback function parsing](#support-callback-function-parsing) - [Advanced features](#Advanced-features) - [Parsing flag code to generate clop code](#Parsing-flag-code-to-generate-clop-code) - [Implementing linux command options](#Implementing-linux-command-options) @@ -584,7 +586,26 @@ type Asr struct{ // 可以使用如下命令行参数测试下效果 // ./example --server-address", ":8080", "--rate", "1s", "--thread-num", "20", "--open-vad" ``` - +## Support callback function parsing +* 使用callback=name的写法, 其中name就是需要调用的解析函数。 +```go +type TestCallback struct { + Size int `clop:"short;long;callback=ParseSize" usage:"parse size"` + Max int `clop:"short;long"` +} + +func (t *TestCallback) ParseSize(val string) { + // 做些解析工作 + // t.Size = 解析之后的值 +} + +func main() { + t := TestCallback{} + err := clop.Bind(&t) + + fmt.Printf("%#v, %s\n", t, err) +} +``` ## Advanced features 高级功能里面有一些clop包比较有特色的功能 ### Parsing flag code to generate clop code diff --git a/clop.go b/clop.go index 9c3e154..8b76ecb 100644 --- a/clop.go +++ b/clop.go @@ -20,13 +20,31 @@ var ( ErrOptionName = errors.New("Illegal option name") ) -const defautlVersion = "v0.0.1" - var ( // 显示usage信息里面的[default: xxx]信息,如果为false,就不显示 ShowUsageDefault = true ) +const ( + defautlVersion = "v0.0.1" + defautlCallbackName = "Parse" + defaultSubMain = "SubMain" +) + +const ( + optGreedy = "greedy" + optOnce = "once" + optEnv = "env" + optEnvEqual = "env=" + optSubcommand = "subcommand" + optSubcommandEqual = "subcommand=" + optShort = "short" + optLong = "long" + optCallback = "callback" + optCallbackEqual = "callback=" + optSpace = " " +) + /* type SubMain interface { SubMain() @@ -53,7 +71,8 @@ type Clop struct { about string //about信息 version string //版本信息 - subMain reflect.Value //子命令带SubMain方法, 就会自动调用 + subMain reflect.Value //子命令带SubMain方法, 就会自动调用 + structAddr reflect.Value exit bool //测试需要用, 控制-h --help 是否退出进程 subcommand map[string]*Subcommand //子命令, 保存结构体当层的所有子命令的信息 @@ -85,8 +104,9 @@ type Subcommand struct { type Option struct { pointer reflect.Value //存放需要修改的值的reflect.Value类型变量 - usage string //帮助信息 - showDefValue string //显示默认值 + fn reflect.Value + usage string //帮助信息 + showDefValue string //显示默认值 //表示参数优先级, 高4字节存放args顺序, 低4字节存放命令组合的顺序(ls -ltr),这里的l的高4字节的值就是0 index uint64 envName string //环境变量 @@ -96,18 +116,18 @@ type Option struct { // 对slice变量无效 once bool //只能设置一次,如果设置once标记,命令行传了两次选项会报错 - set bool //是否通过命令行设置过值 + cmdSet bool //是否通过命令行设置过值 showShort []string //help显示的短选项 showLong []string //help显示的长选项 } func (o *Option) onceResetValue() { - if len(o.showDefValue) > 0 && !o.pointer.IsZero() && !o.set { + if len(o.showDefValue) > 0 && !o.pointer.IsZero() && !o.cmdSet { resetValue(o.pointer) } - o.set = true + o.cmdSet = true } func New(args []string) *Clop { @@ -197,6 +217,12 @@ func setValueAndIndex(val string, option *Option, index int, lowIndex int) error option.onceResetValue() option.index = uint64(index) << 31 option.index |= uint64(lowIndex) + if option.fn.IsValid() { + // 如果定义callback, 就不会走默认形为 + option.fn.Call([]reflect.Value{reflect.ValueOf(val)}) + return nil + } + return setBase(val, option.pointer) } @@ -688,9 +714,9 @@ func (c *Clop) parseSubcommandTag(clop string, v reflect.Value, usage string, fi for _, opt := range options { var name string switch { - case strings.HasPrefix(opt, "subcommand="): - name = opt[len("subcommand="):] - case opt == "subcommand": + case strings.HasPrefix(opt, optSubcommandEqual): + name = opt[len(optSubcommandEqual):] + case opt == optSubcommand: name = strings.ToLower(fieldName) } if name != "" { @@ -705,7 +731,7 @@ func (c *Clop) parseSubcommandTag(clop string, v reflect.Value, usage string, fi c.subcommand[name] = &Subcommand{Clop: newClop, usage: usage} newClop.fieldName = fieldName - newClop.subMain = v.Addr().MethodByName("SubMain") + newClop.subMain = v.Addr().MethodByName(defaultSubMain) return newClop, true } } @@ -727,18 +753,29 @@ func (c *Clop) parseTagAndSetOption(clop string, usage string, def string, field flags := 0 for _, opt := range options { - opt = strings.TrimLeft(opt, " ") + opt = strings.TrimLeft(opt, optSpace) if len(opt) == 0 { continue //跳过空值 } name := "" // TODO 检查name的长度 switch { + case strings.HasPrefix(opt, optCallback): + funcName := defautlCallbackName + if strings.HasPrefix(opt, optCallbackEqual) { + funcName = opt[len(optCallbackEqual):] + } + option.fn = c.structAddr.MethodByName(funcName) + // 检查callback的参数长度 + if option.fn.Type().NumIn() != 1 { + panic(fmt.Sprintf("Required function parameters->%s(val string)", funcName)) + } + //注册长选项 --name case strings.HasPrefix(opt, "--"): name = opt[2:] fallthrough - case strings.HasPrefix(opt, "long"): + case strings.HasPrefix(opt, optLong): if !strings.HasPrefix(opt, "--") { if name, err = gnuOptionName(fieldName); err != nil { return err @@ -754,7 +791,7 @@ func (c *Clop) parseTagAndSetOption(clop string, usage string, def string, field case strings.HasPrefix(opt, "-"): name = opt[1:] fallthrough - case strings.HasPrefix(opt, "short"): + case strings.HasPrefix(opt, optShort): if !strings.HasPrefix(opt, "-") { if name, err = gnuOptionName(fieldName); err != nil { return err @@ -767,18 +804,18 @@ func (c *Clop) parseTagAndSetOption(clop string, usage string, def string, field } option.showShort = append(option.showShort, name) flags |= isLong - case strings.HasPrefix(opt, "greedy"): + case strings.HasPrefix(opt, optGreedy): option.greedy = true - case strings.HasPrefix(opt, "once"): + case strings.HasPrefix(opt, optOnce): option.once = true - case opt == "env": + case opt == optEnv: if name, err = envOptionName(fieldName); err != nil { return err } fallthrough - case strings.HasPrefix(opt, "env="): + case strings.HasPrefix(opt, optEnvEqual): flags |= isEnv - if strings.HasPrefix(opt, "env=") { + if strings.HasPrefix(opt, optEnvEqual) { name = opt[4:] } @@ -878,6 +915,7 @@ func (c *Clop) registerCore(v reflect.Value, sf reflect.StructField) error { } typ := v.Type() + c.structAddr = v.Addr() for i := 0; i < v.NumField(); i++ { sf := typ.Field(i) diff --git a/clop_callback_test.go b/clop_callback_test.go new file mode 100644 index 0000000..572800a --- /dev/null +++ b/clop_callback_test.go @@ -0,0 +1,65 @@ +package clop + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +type TestCallback struct { + Size int `clop:"short;long;callback=ParseSize" usage:"parse size"` + Max int `clop:"short;long"` +} + +func (t *TestCallback) ParseSize(val string) { + t.Size = 1024 * 1024 +} + +// 指定函数名 +func Test_Callback_SpecifyTheFunctionName(t *testing.T) { + got := TestCallback{} + need := TestCallback{Size: 1024 * 1024, Max: 10} + p := New([]string{"--size", "1MB", "--max", "10"}).SetExit(false) + err := p.Bind(&got) + + assert.Equal(t, got, need) + assert.NoError(t, err) +} + +type TestCallbackDefault struct { + Size int `clop:"short;long;callback" usage:"parse size"` +} + +// 这是默认函数名 +func (t *TestCallbackDefault) Parse(val string) { + t.Size = 1024 * 1024 +} + +// 指定函数名 +func Test_Callback_Default(t *testing.T) { + got := TestCallback{} + need := TestCallback{Size: 1024 * 1024, Max: 10} + p := New([]string{"--size", "1MB", "--max", "10"}).SetExit(false) + err := p.Bind(&got) + + assert.Equal(t, got, need) + assert.NoError(t, err) +} + +type TestCallbackPanic struct { + Size int `clop:"short;long;callback" usage:"parse size"` +} + +// 这是默认函数名 +func (t *TestCallbackPanic) Parse() { + t.Size = 1024 * 1024 +} + +func Test_Callback_Panic(t *testing.T) { + got := TestCallbackPanic{} + assert.Panics(t, func() { + p := New([]string{"--size", "1MB", "--max", "10"}).SetExit(false) + p.Bind(&got) + }) + +}