V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
guonaihong
V2EX  ›  Go 编程语言

超级简单:只要会写结构体,就能解析命令行。clop 发布啦!

  •  
  •   guonaihong ·
    guonaihong · 2020-03-16 09:38:28 +08:00 · 2289 次点击
    这是一个创建于 1499 天前的主题,其中的信息可能已经有所发展或是发生改变。

    clop

    clop 是一款基于 struct 的命令行解析器,麻雀虽小,五脏俱全。

    项目地址

    https://github.com/guonaihong/clop

    clop

    Go codecov Go Report Card

    feature

    • 支持环境变量绑定 env DEBUG=xx ./proc
    • 支持参数搜集 cat a.txt b.txt,可以把a.txt, b.txt散装成员归归类,收集到你指定的结构体成员里
    • 支持短选项proc -d 或者长选项proc --debug不在话下
    • posix 风格命令行支持,支持命令组合ls -ltrls -l -t -r简写形式,方便实现普通 posix 标准命令
    • 子命令支持,方便实现 git 风格子命令git add,简洁的子命令注册方式,只要会写结构提就行,3,4,5 到无穷尽子命令也支持,只要你喜欢,用上 clop 就可以实现
    • 默认值支持default:"1",支持多种数据类型,让你省去类型转换的烦恼
    • 贴心的重复命令报错
    • 严格的短选项,长选项报错。避免二义性选项诞生
    • 效验模式支持,不需要写一堆的if x!= "" or if y!=0浪费青春的代码

    内容

    Installation

    go get github.com/guonaihong/clop
    

    Quick start

    package main
    
    import (
    	"fmt"
    	"github.com/guonaihong/clop"
    )
    
    type Hello struct {
    	File string `clop:"-f; --file" usage:"file"`
    }
    
    func main() {
    
    	h := Hello{}
    	clop.Bind(&h)
    	fmt.Printf("%#v\n", h)
    }
    // ./one -f test
    // main.Hello{File:"test"}
    // ./one --file test
    // main.Hello{File:"test"}
    
    

    example

    required flag

     package main
    
    import (
        "fmt"
        "github.com/guonaihong/clop"
    )
    
    type curl struct {
        Url string `clop:"-u; --url" usage:"url" valid:"required"`
    }
    
    func main() {
    
        c := curl{}
        clop.Bind(&c)
    }
    
    

    set default value

    可以使用 default tag 设置默认值,普通类型直接写,复合类型用 json 表示

    package main
    
    import (
        "fmt"
        "github.com/guonaihong/clop"
    )
    
    type defaultExample struct {
        Int          int       `default:"1"`
        Float64      float64   `default:"3.64"`
        Float32      float32   `default:"3.32"`
        SliceString  []string  `default:"[\"one\", \"two\"]"`
        SliceInt     []int     `default:"[1,2,3,4,5]"`
        SliceFloat64 []float64 `default:"[1.1,2.2,3.3,4.4,5.5]"`
    }
    
    func main() {
        de := defaultExample{}
        clop.Bind(&de)
        fmt.Printf("%v\n", de) 
    }
    // run
    //         ./use_def
    // output:
    //         {1 3.64 3.32 [one two] [1 2 3 4 5] [1.1 2.2 3.3 4.4 5.5]}
    

    Support environment variables

    // file name use_env.go
    package main
    
    import (
    	"fmt"
    	"github.com/guonaihong/clop"
    )
    
    type env struct {
    	OmpNumThread string `clop:"env=omp_num_thread" usage:"omp num thread"`
    	Path         string `clop:"env=XPATH" usage:"xpath"`
    	Max          int    `clop:"env=MAX" usage:"max thread"`
    }
    
    func main() {
    	e := env{}
    	clop.Bind(&e)
    	fmt.Printf("%#v\n", e)
    }
    // run
    // env XPATH=`pwd` omp_num_thread=3 MAX=4 ./use_env 
    // output
    // main.env{OmpNumThread:"3", Path:"/home/guo", Max:4}
    

    subcommand

    package main
    
    import (
    	"fmt"
    	"github.com/guonaihong/clop"
    )
    
    type add struct {
    	All      bool     `clop:"-A; --all" usage:"add changes from all tracked and untracked files"`
    	Force    bool     `clop:"-f; --force" usage:"allow adding otherwise ignored files"`
    	Pathspec []string `clop:"args=pathspec"`
    }
    
    type mv struct {
    	Force bool `clop:"-f; --force" usage:"allow adding otherwise ignored files"`
    }
    
    type git struct {
    	Add add `clop:"subcommand=add" usage:"Add file contents to the index"`
    	Mv  mv  `clop:"subcommand=mv" usage:"Move or rename a file, a directory, or a symlink"`
    }
    
    func main() {
    	g := git{}
    	clop.Bind(&g)
    	fmt.Printf("git:%#v\n", g)
    	fmt.Printf("git:set mv(%t) or set add(%t)\n", clop.IsSetSubcommand("mv"), clop.IsSetSubcommand("add"))
    }
    // run:
    // ./git add -f
    
    // output:
    // git:main.git{Add:main.add{All:false, Force:true, Pathspec:[]string(nil)}, Mv:main.mv{Force:false}}
    // git:set mv(false) or set add(true)
    
    

    Implementing linux command options

    cat

    package main
    
    import (
    	"fmt"
    	"github.com/guonaihong/clop"
    )
    
    type cat struct {
    	NumberNonblank bool `clop:"-c;--number-nonblank" 
    	                     usage:"number nonempty output lines, overrides"`
    
    	ShowEnds bool `clop:"-E;--show-ends" 
    	               usage:"display $ at end of each line"`
    
    	Number bool `clop:"-n;--number" 
    	             usage:"number all output lines"`
    
    	SqueezeBlank bool `clop:"-s;--squeeze-blank" 
    	                   usage:"suppress repeated empty output lines"`
    
    	ShowTab bool `clop:"-T;--show-tabs" 
    	              usage:"display TAB characters as ^I"`
    
    	ShowNonprinting bool `clop:"-v;--show-nonprinting" 
    	                      usage:"use ^ and M- notation, except for LFD and TAB" `
    
    	Files []string `clop:"args=files"`
    }
    
    func main() {
    
    	c := cat{}
    	err := clop.Bind(&c)
    
    	fmt.Printf("%#v, %s\n", c, err)
    }
    
    /*
    Usage:
        ./cat [Flags] <files> 
    
    Flags:
        -E,--show-ends           display $ at end of each line 
        -T,--show-tabs           display TAB characters as ^I 
        -c,--number-nonblank     number nonempty output lines, overrides 
        -n,--number              number all output lines 
        -s,--squeeze-blank       suppress repeated empty output lines 
        -v,--show-nonprinting    use ^ and M- notation, except for LFD and TAB 
    
    Args:
        <files>
    */
    

    题外话

    掐指一算,等会儿会有 viper 的粉丝出现。这里提前预判。为啥要出这个库,大约是 19 年的时候 挑战过 gnu 123 个命令,最后完成了 30-40 的样子。一开始使用函数形式的解析库,过程是这样的
    1.定义结构体
    2.复制命令行选项,复制 usage 信息
    3.把刚刚复制的信息分解到定义解析函数,再配置一堆项
    4.解析
    事是很简单,但是一个命令选项如果不少,定完数据结构,配置好 usage,跑完测试估计 30 分钟时间就没了。这里面发掘了慢的原因--数据离绑定的地方太远,修改返工花时间。 针对上面的问题,一直在思考如何让这个简单的事情变得更快呢?最近选择了 struct 的形式,当然也考虑过 ast,最后排除了。选择 struct 这种形式是经过实战考验的。

    6 条回复    2020-03-17 13:02:48 +08:00
    Ritter
        1
    Ritter  
       2020-03-16 09:53:21 +08:00
    zici
    xiadada
        2
    xiadada  
       2020-03-16 10:03:35 +08:00 via iPhone
    好东西
    vus520
        3
    vus520  
       2020-03-16 10:19:41 +08:00
    先星星再研究
    phpcyy
        4
    phpcyy  
       2020-03-16 17:13:12 +08:00
    非常喜欢这个方式,感觉命令行这样简单了许多,比起来 flag 包和 cobra 简单多了。

    另外问下 validator 可以使用 validator.v8 之类的吗?
    phpcyy
        5
    phpcyy  
       2020-03-16 17:18:58 +08:00
    @phpcyy 看了下 validator,本身就是用的 validator.v10 ,非常好
    guonaihong
        6
    guonaihong  
    OP
       2020-03-17 13:02:48 +08:00
    @Ritter @xiadada @vus520 @phpcyy 感谢支持。。。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   3315 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 32ms · UTC 13:19 · PVG 21:19 · LAX 06:19 · JFK 09:19
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.