Skip to content

Commit

Permalink
支持调用回调函数解析 (#99)
Browse files Browse the repository at this point in the history
* 支持调用回调函数解析

* 补下panic的测试代码
  • Loading branch information
guonaihong authored Jul 17, 2022
1 parent ec09cf3 commit 3d9cd3d
Show file tree
Hide file tree
Showing 3 changed files with 145 additions and 21 deletions.
23 changes: 22 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
78 changes: 58 additions & 20 deletions clop.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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 //子命令, 保存结构体当层的所有子命令的信息

Expand Down Expand Up @@ -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 //环境变量
Expand All @@ -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 {
Expand Down Expand Up @@ -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)
}

Expand Down Expand Up @@ -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 != "" {
Expand All @@ -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
}
}
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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:]
}

Expand Down Expand Up @@ -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)

Expand Down
65 changes: 65 additions & 0 deletions clop_callback_test.go
Original file line number Diff line number Diff line change
@@ -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)
})

}

0 comments on commit 3d9cd3d

Please sign in to comment.