超级简单:只要会写结构体,就能解析命令行。clop 发布啦! - V2EX
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
guonaihong

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

  •  
  •   guonaihong
    guonaihong Mar 16, 2020 3068 views
    This topic created in 2268 days ago, the information mentioned may be changed or developed.

    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 replies    2020-03-17 13:02:48 +08:00
    Ritter
        1
    Ritter  
       Mar 16, 2020
    zici
    xiadada
        2
    xiadada  
       Mar 16, 2020 via iPhone
    好东西
    vus520
        3
    vus520  
       Mar 16, 2020
    先星星再研究
    phpcyy
        4
    phpcyy  
       Mar 16, 2020
    非常喜欢这个方式,感觉命令行这样简单了许多,比起来 flag 包和 cobra 简单多了。

    另外问下 validator 可以使用 validator.v8 之类的吗?
    phpcyy
        5
    phpcyy  
       Mar 16, 2020
    @phpcyy 看了下 validator,本身就是用的 validator.v10 ,非常好
    guonaihong
        6
    guonaihong  
    OP
       Mar 17, 2020
    @Ritter @xiadada @vus520 @phpcyy 感谢支持。。。
    About     Help     Advertise     Blog     API     FAQ     Solana     4587 Online   Highest 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 66ms UTC 04:09 PVG 12:09 LAX 21:09 JFK 00:09
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86