clop

package module
v0.2.11 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 17, 2023 License: Apache-2.0 Imports: 24 Imported by: 24

README

clop

Go codecov Go Report Card

clop (Command Line Option Parse)是一款基于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 标准命令
  • 子命令(subcommand)支持,方便实现git风格子命令git add ,简洁的子命令注册方式,只要会写结构体就行,3,4,5到无穷尽子命令也支持,只要你喜欢,用上clop就可以实现
  • 默认值支持default:"1",支持多种数据类型,让你省去类型转换的烦恼
  • 贴心的重复命令报错
  • 严格的短选项,长选项报错。避免二义性选项诞生
  • 效验模式支持,不需要写一堆的if x!= "" or if y!=0浪费青春的代码
  • 可以获取命令优先级别,方便设置命令别名
  • 解析flag包代码生成clop代码
  • 指定解析函数, 自动绑定数据

feature list

内容

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.SetVersion("v0.2.0")
	clop.SetVersionOption("", "version")
	clop.SetAbout("这是一个简单的示例demo")
	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
custom environment variable name
// 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}
Quick writing of environment variables

使用env tag会根据结构体名, 生成一个环境变量名, 规则就是驼峰命令名, 改成大写下划线

// file name use_env.go
package main

import (
	"fmt"
	"github.com/guonaihong/clop"
)

type env struct {
	OmpNumThread string `clop:"env" usage:"omp num thread"`
	Xpath         string `clop:"env" usage:"xpath"`
	Max          int    `clop:"env" 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", Xpath:"/home/guo", Max:4}
subcommand
Sub command implementation method 1
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

Sub command implementation method 2

使用clop实现子命令的第2种做法, 子命令结构体只要实现SubMain方法, 该方法clop库会帮你自动调用. 省去在main里面写一堆if else判断(相对方法1来说), 特别是子命令特别多的情况, 推荐用这种方法.

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"`
}

func (a *add) SubMain() {
// 当add子命令被设置时
// clop会自动调用这个函数
}

type mv struct {
	Force bool `clop:"-f; --force" usage:"allow adding otherwise ignored files"`
}

func (m *mv) SubMain() {
// 当mv 子命令被设置时
// clop会自动调用这个函数
}

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)
}

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)
}

Multi structure series

多结构体串联功能. 多结构体统一组成一个命令行视图

如果命令行解析是要怼到多个(>=2)结构体里面, 可以使用结构体串联功能, 前面几个结构体使用clop.Register()接口, 最后一个结构体使用clop.Bind()函数.

/*
┌────────────────┐
│                │
│                │
│  ServerAddress │                        ┌─────────────────────┐
├────────────────┤                        │                     │
│                │   ──────────────────►  │                     │
│                │                        │  clop.MustRegitser()│
│     Rate       │                        │                     │
│                │                        └─────────────────────┘
└────────────────┘



┌────────────────┐
│                │
│   ThreadNum    │
│                │                        ┌─────────────────────┐
│                │                        │                     │
├────────────────┤   ──────────────────►  │                     │
│                │                        │ clop.Bind()         │
│   OpenVad      │                        │                     │
│                │                        │                     │
└────────────────┘                        └─────────────────────┘
 */

type Server struct {
	ServerAddress string `clop:"long" usage:"Server address"`
	Rate time.Duration `clop:"long" usage:"The speed at which audio is sent"`
}

type Asr struct{
	ThreadNum int `clop:"long" usage:"thread number"`
	OpenVad bool `clop:"long" usage:"open vad"`
}

 func main() {
	 asr := Asr{}
	 ser := Server{}
	 clop.MustRegister(&asr)
	 clop.Bind(&ser)
 }

 // 可以使用如下命令行参数测试下效果
 // ./example --server-address", ":8080", "--rate", "1s", "--thread-num", "20", "--open-vad"

Support callback function parsing

  • 使用callback=name的写法, 其中name就是需要调用的解析函数。
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

让你爽翻天, 如果你的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>
*/

faq

The subcommand or option does not take effect

q: 关于子命令或者命令行命令没有生效
a: 可以检查下结构体的字段是否是大写开头。

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrDuplicateOptions = errors.New("is already in use")
	//ErrUsageEmpty       = errors.New("usage cannot be empty")
	ErrUnsupported  = errors.New("unsupported clop command")
	ErrNotFoundName = errors.New("no command line options found")
	ErrOptionName   = errors.New("Illegal option name")
)
View Source
var CommandLine = New(os.Args[1:])
View Source
var ErrNotPointerType = errors.New("Not pointer type")
View Source
var ErrUnsupportedType = errors.New("Unsupported type")
View Source
var (
	// 显示usage信息里面的[default: xxx]信息,如果为false,就不显示
	ShowUsageDefault = true
)

Functions

func Bind

func Bind(x interface{}) error

Bind接口, 包含以下功能 结构体字段注册 命令行解析

func GetIndex added in v0.0.3

func GetIndex(optName string) uint64

func IsSetSubcommand

func IsSetSubcommand(subcommand string) bool

func MustBind added in v0.1.3

func MustBind(x interface{})

Bind必须成功的版本

func MustRegister added in v0.1.6

func MustRegister(x interface{})

func SetAbout added in v0.2.3

func SetAbout(about string)

func SetVersion added in v0.2.3

func SetVersion(version string)

设置版本号

func SetVersionOption added in v0.2.9

func SetVersionOption(short, long string)

设置版本号选项,覆盖默认的V和Version

func StringToBytes

func StringToBytes(s string) (b []byte)

func Unquote

func Unquote(s string) (string, error)

本函数来自stdlib, 简单修改了下

func Usage

func Usage()

打印帮助信息

Types

type Clop

type Clop struct {
	// contains filtered or unexported fields
}

func New

func New(args []string) *Clop

func (*Clop) Bind

func (c *Clop) Bind(x interface{}) (err error)

func (*Clop) GetIndex added in v0.0.3

func (c *Clop) GetIndex(optName string) (index uint64)

func (*Clop) GetIndexEx added in v0.2.10

func (c *Clop) GetIndexEx(optName string) (index uint64, ok bool)

TODO 补充下更多单元测试

func (*Clop) IsSetSubcommand

func (c *Clop) IsSetSubcommand(subcommand string) bool

func (*Clop) MustBind added in v0.1.3

func (c *Clop) MustBind(x interface{})

MustBind 和Bind函数类似, 出错直接panic

func (*Clop) Register added in v0.1.6

func (c *Clop) Register(x interface{}) error

只注册结构体信息, 不解析

func (*Clop) SetAbout added in v0.2.3

func (c *Clop) SetAbout(about string) *Clop

设置about相关信息

func (*Clop) SetExit

func (c *Clop) SetExit(exit bool) *Clop

设置出错行为,默认出错会退出进程(true), 为false则不会

func (*Clop) SetOutput

func (c *Clop) SetOutput(w io.Writer) *Clop

func (*Clop) SetProcName

func (c *Clop) SetProcName(procName string) *Clop

设置进程名

func (*Clop) SetVersion added in v0.2.3

func (c *Clop) SetVersion(version string) *Clop

设置版本相关信息

func (*Clop) SetVersionOption added in v0.2.9

func (c *Clop) SetVersionOption(short, long string) *Clop

设置版本相关信息

func (*Clop) Usage

func (c *Clop) Usage()

type Help

type Help struct {
	ProcessName      string
	Version          string
	About            string
	Flags            []showOption
	Options          []showOption
	Args             []showOption
	Envs             []showOption
	Subcommand       []showOption
	MaxNameLen       int
	ShowUsageDefault bool
}

type Option

type Option struct {
	// contains filtered or unexported fields
}

type ParseFlag added in v0.1.1

type ParseFlag struct {
	// contains filtered or unexported fields
}

解析flag

func NewParseFlag added in v0.1.1

func NewParseFlag() *ParseFlag

构造函数

func (*ParseFlag) All added in v0.1.1

func (p *ParseFlag) All() *ParseFlag

生成所有

func (*ParseFlag) FromFile added in v0.1.1

func (p *ParseFlag) FromFile(fileName string) *ParseFlag

设置文件名

func (*ParseFlag) OnlyStruct added in v0.1.1

func (p *ParseFlag) OnlyStruct() *ParseFlag

仅生成struct

func (*ParseFlag) Parse added in v0.1.1

func (p *ParseFlag) Parse() ([]byte, error)

解析

type Subcommand

type Subcommand struct {
	*Clop
	// contains filtered or unexported fields
}

使用递归定义,可以很轻松地解决subcommand嵌套的情况

type Tag

type Tag string

func (Tag) Get

func (tag Tag) Get(key string) (value string)

func (Tag) Lookup

func (tag Tag) Lookup(key string) (value string, ok bool)

该函数原版本来自stdlib, 简单修改了下

Directories

Path Synopsis
cmd
clop Module

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL