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

clop 新版本来了, 让你快速从 flag 包迁移过来, 一行命令的事

  •  
  •   guonaihong ·
    guonaihong · 2021-06-02 10:27:06 +08:00 · 1067 次点击
    这是一个创建于 1057 天前的主题,其中的信息可能已经有所发展或是发生改变。

    clop

    一款小巧, 追求开发效果的工具命令行工具包

    地址

    https://github.com/guonaihong/clop

    本次特性

    加入代码生成的思路, 可以快速帮你从 flag 包迁移至 clop

    1.安装 clop 命令

    go get github.com/guonaihong/clop/cmd/clop
    

    2.使用 clop 解析包含 flag 包的代码

    就可以把 main.go 里面的 flag 库转成 clop 包的调用方式

    clop -f main.go
    

    main.go代码如下

    package main
    
    import "flag"
    
    func main() {
    	s := flag.String("string", "", "string usage")
    	i := flag.Int("int", "", "int usage")
    	flag.Parse()
    }
    

    输出代码如下

    package main
    
    import (
    	"github.com/guonaihong/clop"
    )
    
    type flagAutoGen struct {
    	Flag string `clop:"--string" usage:"string usage" `
    	Flag int    `clop:"--int" usage:"int usage" `
    }
    
    func main() {
    	var flagVar flagAutoGen
    	clop.Bind(&flagVar)
    }
    

    完整文档如下

    clop

    Go codecov Go Report Card

    clop 是一款基于 struct 的命令行解析器,麻雀虽小,五脏俱全。(从零实现) clop.png

    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浪费青春的代码
    • 可以获取命令优先级别,方便设置命令别名
    • 解析 flag 包代码生成 clop 代码

    内容

    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

    base type

    int

    package main
    
    import (
            "fmt"
    
            "github.com/guonaihong/clop"
    )
    
    type IntDemo struct {
            Int int `clop:"short;long" usage:"int"`
    }
    
    func main() {
            id := &IntDemo{}
            clop.Bind(id)
            fmt.Printf("id = %v\n", id)
    }
    //  ./int -i 3
    // id = &{3}
    // ./int --int 3
    // id = &{3}
    

    float64

    package main
    
    import (
            "fmt"
    
            "github.com/guonaihong/clop"
    )
    
    type Float64Demo struct {
            Float64 float64 `clop:"short;long" usage:"float64"`
    }
    
    func main() {
            fd := &Float64Demo{}
            clop.Bind(fd)
            fmt.Printf("fd = %v\n", fd)
    }
    // ./float64 -f 3.14
    // fd = &{3.14}
    // ./float64 --float64 3.14
    // fd = &{3.14}
    

    duration

    package main
    
    import (
            "fmt"
            "time"
    
            "github.com/guonaihong/clop"
    )
    
    type DurationDemo struct {
            Duration time.Duration `clop:"short;long" usage:"duration"`
    }
    
    func main() {
            dd := &DurationDemo{}
            clop.Bind(dd)
            fmt.Printf("dd = %v\n", dd)
    }
    // ./duration -d 1h
    // dd = &{1h0m0s}
    // ./duration --duration 1h
    // dd = &{1h0m0s}
    

    string

    package main
    
    import (
            "fmt"
    
            "github.com/guonaihong/clop"
    )
    
    type StringDemo struct {
            String string `clop:"short;long" usage:"string"`
    }
    
    func main() {
            s := &StringDemo{}
            clop.Bind(s)
            fmt.Printf("s = %v\n", s)
    }
    // ./string --string hello
    // s = &{hello}
    // ./string -s hello
    // s = &{hello}
    

    array

    similar to curl command

    package main
    
    import (
            "fmt"
    
            "github.com/guonaihong/clop"
    )
    
    type ArrayDemo struct {
            Header []string `clop:"-H;long" usage:"header"`
    }
    
    func main() {
            h := &ArrayDemo{}
            clop.Bind(h)
            fmt.Printf("h = %v\n", h)
    }
    // ./array -H session:sid --header token:my
    // h = &{[session:sid token:my]}
    

    similar to join command

    加上 greedy 属性,就支持数组贪婪写法。类似 join 命令。

    package main
    
    import (
        "fmt"
    
        "github.com/guonaihong/clop"
    )
    
    type test struct {
        A []int `clop:"-a;greedy" usage:"test array"`
        B int   `clop:"-b" usage:"test int"`
    }
    
    func main() {
        a := &test{}
        clop.Bind(a)
        fmt.Printf("%#v\n", a)
    }
    
    /*
    运行
    ./use_array -a 12 34 56 78 -b 100
    输出
    &main.test{A:[]int{12, 34, 56, 78}, B:100}
    */
    
    

    required flag

    package main
    
    import (
    	"github.com/guonaihong/clop"
    )
    
    type curl struct {
    	Url string `clop:"-u; --url" usage:"url" valid:"required"`
    }
    
    func main() {
    
    	c := curl{}
    	clop.Bind(&c)
    }
    
    // ./required 
    // error: -u; --url must have a value!
    // For more information try --help
    

    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"))
    
    	switch {
    	case clop.IsSetSubcommand("mv"):
    		fmt.Printf("subcommand mv\n")
    	case clop.IsSetSubcommand("add"):
    		fmt.Printf("subcommand add\n")
    	}
    }
    
    // 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)
    // subcommand add
    
    

    Get command priority

    package main
    
    import (
    	"fmt"
    	"github.com/guonaihong/clop"
    )
    
    type cat struct {
    	NumberNonblank bool `clop:"-b;--number-nonblank"
                                 usage:"number nonempty output lines, overrides"`
    
    	ShowEnds bool `clop:"-E;--show-ends"
                           usage:"display $ at end of each line"`
    }
    
    func main() {
    
    	c := cat{}
    	clop.Bind(&c)
    
    	if clop.GetIndex("number-nonblank") < clop.GetIndex("show-ends") {
    		fmt.Printf("cat -b -E\n")
    	} else {
    		fmt.Printf("cat -E -b \n")
    	}
    }
    // cat -be 
    // 输出 cat -b -E
    // cat -Eb
    // 输出 cat -E -b
    

    Can only be set once

    指定选项只能被设置一次,如果命令行选项,使用两次则会报错。

    package main
    
    import (
        "github.com/guonaihong/clop"
    )
    
    type Once struct {
        Debug bool `clop:"-d; --debug; once" usage:"debug mode"`
    }
    
    func main() {
        o := Once{}
        clop.Bind(&o)
    }
    /*
    ./once -debug -debug
    error: The argument '-d' was provided more than once, but cannot be used multiple times
    For more information try --help
    */
    

    quick write

    快速写法,通过使用固定的 short, long tag 生成短,长选项。可以和 cat 例子直观比较下。命令行选项越多,越能节约时间,提升效率。

    package main
    
    import (
        "fmt"
        "github.com/guonaihong/clop"
    )
    
    type cat struct {
    	NumberNonblank bool `clop:"-c;long" 
    	                     usage:"number nonempty output lines, overrides"`
    
    	ShowEnds bool `clop:"-E;long" 
    	               usage:"display $ at end of each line"`
    
    	Number bool `clop:"-n;long" 
    	             usage:"number all output lines"`
    
    	SqueezeBlank bool `clop:"-s;long" 
    	                   usage:"suppress repeated empty output lines"`
    
    	ShowTab bool `clop:"-T;long" 
    	              usage:"display TAB characters as ^I"`
    
    	ShowNonprinting bool `clop:"-v;long" 
    	                      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)
    }
    

    Advanced features

    高级功能里面有一些 clop 包比较有特色的功能

    Parsing flag code to generate clop code

    让你爽翻天, 如果你的 command 想迁移至 clop, 但是全面众多的 flag 代码, 又不想花费太多时间在无谓的人肉 code 转换上, 这时候你就需要 clop 命令, 一行命令解决你的痛点.

    1.安装 clop 命令

    go get github.com/guonaihong/clop/cmd/clop
    

    2.使用 clop 解析包含 flag 包的代码

    就可以把 main.go 里面的 flag 库转成 clop 包的调用方式

    clop -f main.go
    

    main.go代码如下

    package main
    
    import "flag"
    
    func main() {
    	s := flag.String("string", "", "string usage")
    	i := flag.Int("int", "", "int usage")
    	flag.Parse()
    }
    

    输出代码如下

    package main
    
    import (
    	"github.com/guonaihong/clop"
    )
    
    type flagAutoGen struct {
    	Flag string `clop:"--string" usage:"string usage" `
    	Flag int    `clop:"--int" usage:"int usage" `
    }
    
    func main() {
    	var flagVar flagAutoGen
    	clop.Bind(&flagVar)
    }
    

    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>
    */
    
    2 条回复    2021-06-04 22:10:16 +08:00
    czyt
        1
    czyt  
       2021-06-03 22:11:11 +08:00
    已经 star
    guonaihong
        2
    guonaihong  
    OP
       2021-06-04 22:10:16 +08:00
    @czyt 感谢, 也欢迎使用, 如有问题, 可提 issue
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1446 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 23:54 · PVG 07:54 · LAX 16:54 · JFK 19:54
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.