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

2020-03-16 09:38:28 +08:00
 guonaihong

clop

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

项目地址

https://github.com/guonaihong/clop

clop

feature

内容

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 这种形式是经过实战考验的。

2291 次点击
所在节点    Go 编程语言
6 条回复
Ritter
2020-03-16 09:53:21 +08:00
zici
xiadada
2020-03-16 10:03:35 +08:00
好东西
vus520
2020-03-16 10:19:41 +08:00
先星星再研究
phpcyy
2020-03-16 17:13:12 +08:00
非常喜欢这个方式,感觉命令行这样简单了许多,比起来 flag 包和 cobra 简单多了。

另外问下 validator 可以使用 validator.v8 之类的吗?
phpcyy
2020-03-16 17:18:58 +08:00
@phpcyy 看了下 validator,本身就是用的 validator.v10 ,非常好
guonaihong
2020-03-17 13:02:48 +08:00
@Ritter @xiadada @vus520 @phpcyy 感谢支持。。。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/653151

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX