args

package
v2.13.0 Latest Latest
Warning

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

Go to latest
Published: Nov 20, 2023 License: MIT Imports: 9 Imported by: 2

README

Args Manual

无外部依赖的命令行参数解析器

无外部依赖的命令行参数解析器

开始

此应用程序将正确运行并显示 Hello World.

package main

import (
	"fmt"

	"github.com/o8x/jk/args"
)

func main() {
	a := args.Args{}

	if err := a.Parse(); err != nil {
		a.PrintErrorExit(err)
	}

	fmt.Println("Hello World.")
}

也可以使用既定的 cmdline 进行解析,此命令的原理是使用 strings.Fields 对 cmdline 实参进行解析,得到的结果类似 os.Args

if err := a.ParseCmdline("-h -v"); err != nil {
	a.PrintErrorExit(err)
}

在错误时同时显示帮助文本

if err := a.Parse(); err != nil {
	a.PrintHelpExit(err)
}

解析 os.Args 可能不像你想象的那么简单,但是也不会像你想象的那么复杂。有时候,可能只需要一个纯粹的 flag.Parse(),但更多的时候我们并不喜欢这样的虽然纯粹但无比复杂的方式。

Args 提供了大量的功能来让解析 os.Args 变得简单,例如对 Flag 和 PropertyMode 等内容的支持,下面将介这些内容。

示例

首先我们要创建一个名为 greet 的目录,并在其中创建一个名为 main.go 的文件

mkdir greet
touch greet/main.go

main.go 中将包含以下代码

package main

import (
	"fmt"

	"github.com/o8x/jk/args"
)

func main() {
	a := args.Args{
		App: &args.App{
			Name:  "Greet",
			Usage: "Greet -h",
		},
	}

	if err := a.Parse(); err != nil {
		a.PrintErrorExit(err)
	}

	fmt.Println("Hello Friend.")
}

将我们的命令安装到 $GOPATH/bin

go install

运行我们的新命令

> greet
Hello Friend.
Flags

通过使用 Args 的实例来设置和查询 Flag 都很容易,使用内置方法取值时可以省略 Flag 前导的 -。

package main

import (
	"fmt"

	"github.com/o8x/jk/args"
)

func main() {
	a := args.Args{
		App: &args.App{
			Name:  "Greet",
			Usage: "Greet -h",
		},
		Flags: []*args.Flag{
			{
				Name:        []string{"-language", "-lang", "-l"},
				Description: "Greet 的语言参数",
				Default:     []string{"zh-CN"},
			},
		},
	}

	if err := a.Parse(); err != nil {
		a.PrintErrorExit(err)
	}

	name := "o8x"

	switch a.Get("lang") {
	case "zh-CN":
		fmt.Printf("你好,")
	case "english":
		fmt.Printf("Hello,")
	case "spanish":
		fmt.Printf("Hola,")
	}

	fmt.Printf("%s\n", name)
}

运行它将会得到如下结果

> greet
你好,o8x
> greet -l spanish
Hola,o8x
> greet -lang english
Hello,o8x
未定义

如果输入一个未定义的参数,将会的到一个错误

> greet -s
error: flag -s is not defined

Flag

定义

type Properties map[string]any

type Flag struct {
	Name             []string `json:"name"`
	Description      string   `json:"description"`
	PropertyMode     bool     `json:"property_mode"`
	Default          []string `json:"default"`
	Required         bool     `json:"required"`
	Env              []string `json:"env"`
	Error            error    `json:"error_message"`
	NoValue          bool     `json:"no_value"`
	ValuesOnlyInEnum []string `json:"value_only_in_enum"`
	SingleValue      bool     `json:"single_value"`
	values           []string
	properties       Properties
	exist            bool
}
Name
Flag.Name []string

Flag 的名称和别名,必须以 - 或 -- 开头,否则将不会被作为 Flag 进行解析。

Description
Flag.Description string

Flag 的描述信息或 Usage,被用于输出 Help

PropertyMode
Flag.PropertyMode bool

为该 Flag 启用 Property 模式

SingleValue
Flag.SingleValue bool

默认允许重复使用 Flag,可以通过设置 SingleValue 为 true 禁止这一行为

Default
Flag.Default []string

为 Flag 设置默认值,允许设置多个值,但当 SingleValue 为 true 时只会使用第一个值。

Required
Flag.Required bool

声明 Flag 是必填的,在没有设置 Flag、Env、Default 时将会得到错误。

Env
Flag.Env []string

该 Flag 可以从环境变量中取值,优先级最低。如果设置了多个变量名,将会依次取值,结果集形式上类似 Default。

Error
Flag.Error error

当 Flag 不符合属性规定时,使用自定义错误进行报错,暂时仅支持 Required。

NoValue
Flag.NoValue bool

该 Flag 仅需要存在,无需设置值

ValuesOnlyInEnum
Flag.ValuesOnlyInEnum []string

Flag 的实际参数必须在这个数组中取值,无论 SingleValue 是否为 true

优先级

cmdline Value -> Default -> Env

取值

Args 提供了一系列取值 API

判断 Flag 是否存在
func (a *Args) IsSet(name string) bool

示例

if a.IsSet("flag") {
	fmt.Println("已设置 flag 属性")
}
获取 Int64 Flag

在未设置 SingleValue 时,会取出第一个 Flag 设置的实际参数

func (a *Args) GetInt64(name string) (int64, bool)

示例

package main

import (
	"fmt"
	"github.com/o8x/jk/args"
)

func main() {
	a := args.Args{
		Flags: []*args.Flag{
			{
				Name: []string{"-int", "-i"},
			},
		},
	}

	if err := a.Parse(); err != nil {
		a.PrintErrorExit(err)
	}

	fmt.Println(a.GetInt("int"))
}

运行它

> go run . -int 3
3 true
> go run . -int 3 -i 6
3 true
获取 Int64 数组 Flag
func (a *Args) GetInt64s(name string) ([]int64, error)

示例

fmt.Println(a.GetInt64s("int"))

运行它

> go run . -int 3 -i 6
[3 6] <nil>
获取 Int Flag
func (a *Args) GetInt(name string) (int, bool)

等同 GetInt64

获取 Int 数组 Flag
func (a *Args) GetInts(name string) ([]int)

等同 GetInt64s

获取 字符串 Flag
func (a *Args) Get(name string) (string, bool) 

示例

package main

import (
	"fmt"
	"github.com/o8x/jk/args"
)

func main() {
	a := args.Args{
		Flags: []*args.Flag{
			{
				Name: []string{"-str", "-s"},
			},
		},
	}

	if err := a.Parse(); err != nil {
		a.PrintErrorExit(err)
	}

	fmt.Println(a.Get("str"))
}

运行它

> go run . -s jk
jk true
> go run . -s jk -s ef -s abc
jk true
获取不到有效 字符串 Flag 时 panic
func (a *Args) GetX(name string) string

示例

fmt.Println(a.GetX("undefined"))

运行它

go run .
panic: flag -undefined is not defined
获取 字符串 数组 Flag
func (a *Args) Gets(name string) []string

示例

fmt.Println(a.Gets("str"))

运行它

> go run . -s jk -s ef -s abc
[jk ef abc]
获取 Bool Flag

第一个返回值为参数的实际值,如果参数存在且实际值为 true 或 false,则第二个返回值为 true,否则为 false

func (a *Args) GetBool(name string) (bool, bool)

示例

fmt.Println(a.Gets("bool"))

运行它

> go run . -b true
true true
> go run . -b false
false true
> go run . -b 1
false false

Property 模式

即将形式是 K=V 的 Flag 值自动格式化为 map 类型,在设置了 Flag.PropertyMode 为 true 时将会自动为该 Flag 开启 Property 模式。

示例

package main

import (
	"fmt"
	"github.com/o8x/jk/args"
)

func main() {
	a := args.Args{
		Flags: []*args.Flag{
			{
				Name:         []string{"-property", "-p"},
				PropertyMode: true,
			},
		},
	}

	if err := a.Parse(); err != nil {
		a.PrintErrorExit(err)
	}

	fmt.Println(a.GetProperties("property"))
}

运行它

> go run . -property app.name=app -property app.port=8080 -property app.workdir=/sbin
map[app.name:app app.port:8080 app.workdir:/sbin]
获取 Property Flag 的 Properties

返回值为 Properties 类型,内部类型为 map[string]string

func (a *Args) GetProperties(name string) Properties
获取 Flag 的 Property 的值
func (a *Args) GetProperty(name string, property string) (string, bool)

示例

fmt.Println(a.GetProperty("property", "app.workdir"))

运行它

> go run . -property app.workdir=/sbin
/sbin true
在获取不到有效 Property 时 panic
func (a *Args) GetPropertyX(name string, property string) string

示例

fmt.Println(a.GetPropertyX("property", "app.name"))

运行它

> go run . -p app.port=:8080
panic: property property.app.name not found
取值方法

用法参考 Args,不再赘述

func (p Properties) GetInt(name string) (int, bool)
func (p Properties) GetInt64(name string) (int64, bool)
func (p Properties) IsSet(name string) bool
func (p Properties) Get(name string) (string, bool)
func (p Properties) GetBool(name string) (bool, bool)

帮助

args 会生成整洁的帮助文本,未提供 Flag 时只有 help 和 version 两个 command

package main

import (
	"fmt"
	"github.com/o8x/jk/args"
)

func main() {
	a := args.Args{
		App: &args.App{
			Name:  "Greet",
			Usage: "Greet -h",
		},
		Flags: []*args.Flag{
			{
				Name:        []string{"-language", "-lang", "-l"},
				Description: "Greet 的语言参数",
				Default:     []string{"zh-CN"},
				Required:    true,
				Error:       fmt.Errorf("{{name}} is required"),
			},
		},
	}

	if err := a.Parse(); err != nil {
		a.PrintErrorExit(err)
	}
}

运行它

> greet -h
Usage of greet

Greet v0.0.1 (darwin/arm64) go1.19
usage: Greet -h

commands: 
    -language|-lang|-l: required
        Greet 的语言参数 (default: zh-CN)
    -help|-h
        print this help and exit
    -version|-v
        print version info and exit

版本

args 会生成整洁的版本信息

package main

import (
	"fmt"
	"time"

	"github.com/o8x/jk/args"
)

var Version = "0.0.1"
var Changelog = "版本更新日志"
var Banner = ` ________                      __   
 /  _____/______   ____   _____/  |_ 
/   \  __\_  __ \_/ __ \_/ __ \   __\
\    \_\  \  | \/\  ___/\  ___/|  |  
 \______  /__|    \___  >\___  >__|  
        \/            \/     \/`
var CommitHash = "138152a4247d2cedd930e8c437917d8ad4c2c674"
var Date = time.Now().Local().String()

func main() {
	a := args.Args{
		App: &args.App{
			Name:       "Greet",
			Usage:      "Greet -h",
			Copyright:  "Copyright © 2023 Alex.",
			Version:    &Version,
			Changelog:  &Changelog,
			Banner:     &Banner,
			CommitHash: &CommitHash,
			Date:       &Date,
		}
	}

	if err := a.Parse(); err != nil {
		a.PrintErrorExit(err)
	}
}

运行它

> greet -v
 ________                      __   
 /  _____/______   ____   _____/  |_ 
/   \  __\_  __ \_/ __ \_/ __ \   __\
\    \_\  \  | \/\  ___/\  ___/|  |  
 \______  /__|    \___  >\___  >__|  
        \/            \/     \/

Greet v0.0.1 (darwin/arm64) go1.19

Release-Date: 2023-01-16 11:40:46.2418 +0800 CST
Commit Hash: 138152a4247d2cedd930e8c437917d8ad4c2c674
Changelog: 版本更新日志
Copyright © 2023 Alex.
注入版本信息

可能你已经注意到 Version、Changelog、Banner、CommitHash、Date 等属性是指针类型。这么做的原因是,通常我们并不会直接将版本信息作为常量写入代码,而是从外部获取或在编译期使用 ldflags -X 注入。

因为 ldflags 不接受带有空格的数据,所以 Args 对于上述的指针属性提供了 base64 支持,带有 base64:// 前缀的数据会被自动进行 decode,同时也会删除数据中前导和末尾的 \n。

package main

import (
	"github.com/o8x/jk/args"
)

var (
	Version    = ""
	Changelog  = ""
	Banner     = ""
	CommitHash = ""
	Date       = ""
)

func main() {
	a := args.Args{
		App: &args.App{
			Name:       "Greet",
			Usage:      "Greet -h",
			Copyright:  "Copyright © 2023 Alex.",
			Version:    &Version,
			Changelog:  &Changelog,
			Banner:     &Banner,
			CommitHash: &CommitHash,
			Date:       &Date,
		},
	}

	if err := a.Parse(); err != nil {
		a.PrintErrorExit(err)
	}
}

重新安装

set -v

Banner=$(echo '  ________                      __   
 /  _____/______   ____   _____/  |_ 
/   \  __\_  __ \_/ __ \_/ __ \   __\
\    \_\  \  | \/\  ___/\  ___/|  |  
 \______  /__|    \___  >\___  >__|  
        \/            \/     \/' | gbase64 -w 0)
Version=0.0.1 
CommitHash=7fee7dfb2d17d9ac2bb8a30cc7b7605d3f94f543
Changelog=$(echo '版本更新日志' | gbase64 -w 0)
Date=$(date "+%Y-%m-%d %H:%M:%S" | gbase64 -w 0)

go install -ldflags "
    -X main.Version=$Version
    -X main.Banner=base64://$Banner
    -X main.Changelog=base64://$Changelog
    -X main.CommitHash=$CommitHash
    -X main.Date=base64://$Date" .

运行它

> greet -v             
  ________                      __   
 /  _____/______   ____   _____/  |_ 
/   \  __\_  __ \_/ __ \_/ __ \   __\
\    \_\  \  | \/\  ___/\  ___/|  |  
 \______  /__|    \___  >\___  >__|  
        \/            \/     \/

Greet v0.0.1 (darwin/arm64) go1.19

Release-Date: 2023-01-16 14:19:05
Commit Hash: 7fee7dfb2d17d9ac2bb8a30cc7b7605d3f94f543
Changelog: 版本更新日志
Copyright © 2023 Alex.

其他 API

阻塞主 goroutine

该方法会阻塞主 goroutine 直到 USR1、USR2、INT、TERM、QUIT 其中之一的信号被触发

func (a *Args) WaitSignal() os.Signal

示例

a.WaitSignal()
fmt.Println("app shutdown")
退出

该方法会退出应用程序,用法与 os.Exit() 一致

func (a *Args) Exit(code int)

示例

a.Exit(0)
打印调试日志并退出
func (a *Args) DumpExit()

示例

a.DumpExit()

脚手架

如果你只想从命令行获取一些简单的参数,不想大动干戈构造 Args 实例,Args 也提供了 Option API

ArgFunc

注入方法类型,可以按此类型限定自由扩展 Option Function

type ArgFunc func(a *Args)
生成基本的 Args 实例
func New(fns ...ArgFunc) *Args
注入应用程序名称和使用示例
func WithApp(name, usage string) ArgFunc
增加基本的 Flag
func AddFlag(name string) ArgFunc
增加带有默认值的 Flag
func AddDefaultFlag(name, def string) ArgFunc
增加 Enum Flag
func AddEnumFlag(name, def string, enum ...string) ArgFunc
增加没有值的 Flag
func AddNoValueFlag(name string) ArgFu
手动扩展 Option

任何符合 args.ArgFunc 类型的方法都可以作为 Args 的 Option

func WithAppCopyright(c string) args.ArgFunc {
    return func(a *args.Args) {
        if a.App != nil {
            a.App.Copyright = c
            return
        }

        a.App = &args.App{
            Copyright: c,
        }
    }
}
调用示例
package main

import (
	"github.com/o8x/jk/args"
)

func main() {
	a := args.New(
		args.WithApp("user", "./app -name Alex -dryrun -time 2"),
		args.AddFlag("-name"),
		args.AddDefaultFlag("-def", "Def"),
		args.AddNoValueFlag("-dryrun"),
		args.AddEnumFlag("-ts", "now", "nextDay", "now"),
		WithAppCopyright("Alex"),
	)

	if err := a.Parse(); err != nil {
		a.PrintHelpExit(err)
	}

	a.DumpExit()
}

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type App

type App struct {
	Name       string  `json:"name"`
	Usage      string  `json:"usage"`
	Copyright  string  `json:"copyright"`
	Version    *string `json:"version"`
	Changelog  *string `json:"changelog"`
	Banner     *string `json:"banner"`
	CommitHash *string `json:"hash"`
	Date       *string `json:"date"`
}

func NewApp added in v2.12.0

func NewApp(name, usage, version string) *App

func (App) AppFullVersion

func (a App) AppFullVersion() string

type ArgFunc

type ArgFunc func(a *Args)

func AddDefaultFlag

func AddDefaultFlag(name, def string) ArgFunc

func AddEnumFlag

func AddEnumFlag(name, def string, enum ...string) ArgFunc

func AddEnvFlag added in v2.5.0

func AddEnvFlag(name string, env ...string) ArgFunc

func AddFlag

func AddFlag(name string) ArgFunc

func AddNoValueFlag

func AddNoValueFlag(name string) ArgFunc

func AddPropertyFlag added in v2.11.0

func AddPropertyFlag(name string) ArgFunc

func WithApp

func WithApp(name, usage string) ArgFunc

type Args

type Args struct {
	Executable string   `json:"executable"`
	App        *App     `json:"app"`
	Source     []string `json:"source"`
	Flags      []*Flag  `json:"args"`

	HelpFunc func() string
	// contains filtered or unexported fields
}

func New

func New(fns ...ArgFunc) *Args

func NewArgs added in v2.12.0

func NewArgs(app *App, args ...*Flag) *Args

func (*Args) Bind added in v2.11.0

func (a *Args) Bind(v any) error

func (*Args) Bytes added in v2.11.0

func (a *Args) Bytes() []byte

func (*Args) Copy added in v2.11.0

func (a *Args) Copy() Readonly

func (*Args) DumpExit

func (a *Args) DumpExit()

func (*Args) Exit

func (a *Args) Exit(code int)

func (*Args) GenerateUsage added in v2.13.0

func (a *Args) GenerateUsage() string

func (*Args) Help

func (a *Args) Help(err error) string

func (*Args) Parse

func (a *Args) Parse() error

func (*Args) ParseCmdline

func (a *Args) ParseCmdline(cmdline string) error

func (*Args) PrintErrorExit

func (a *Args) PrintErrorExit(err error)

func (*Args) PrintHelpExit

func (a *Args) PrintHelpExit(err error)

func (*Args) PrintVersionExit

func (a *Args) PrintVersionExit()

func (*Args) UpdateDescript added in v2.11.0

func (a *Args) UpdateDescript(name, desc string)

func (*Args) UpdateDescripts added in v2.11.0

func (a *Args) UpdateDescripts(descs map[string]string)

func (*Args) WaitSignal

func (a *Args) WaitSignal() os.Signal

type Flag

type Flag struct {
	Name             []string `json:"name"`
	Description      string   `json:"description"`
	PropertyMode     bool     `json:"property_mode"`
	Default          []string `json:"default"`
	Required         bool     `json:"required"`
	Env              []string `json:"env"`
	Error            error    `json:"error_message"`
	NoValue          bool     `json:"no_value"`
	ValuesOnlyInEnum []string `json:"value_only_in_enum"`
	SingleValue      bool     `json:"single_value"`
	HookFunc         FlagHookFunc
	// contains filtered or unexported fields
}

func NewDefaultFlag added in v2.12.0

func NewDefaultFlag(name string, def []string, desc string, fn FlagHookFunc) *Flag

func NewFlag added in v2.12.0

func NewFlag(name string, def string, desc string, fn FlagHookFunc) *Flag

func NewNoValueFlag added in v2.12.0

func NewNoValueFlag(name string, desc string, required bool) *Flag

func NewOptionFlag added in v2.13.0

func NewOptionFlag(name string, def string, desc string, fn FlagHookFunc) *Flag

func NewRequiredFlag added in v2.13.0

func NewRequiredFlag(name string, desc string, fn FlagHookFunc) *Flag

func (Flag) JoinDefault

func (a Flag) JoinDefault() string

func (Flag) JoinEnum

func (a Flag) JoinEnum() string

func (Flag) JoinName

func (a Flag) JoinName() string

type FlagHookFunc added in v2.13.0

type FlagHookFunc func(int, []string) error

type Properties

type Properties map[string]any

func (Properties) Get

func (p Properties) Get(name string) (string, bool)

func (Properties) GetBool

func (p Properties) GetBool(name string) (bool, bool)

func (Properties) GetBoolDefault

func (p Properties) GetBoolDefault(name string, def bool) bool

func (Properties) GetDefault

func (p Properties) GetDefault(name, def string) string

func (Properties) GetInt

func (p Properties) GetInt(name string) (int, bool)

func (Properties) GetInt64

func (p Properties) GetInt64(name string) (int64, bool)

func (Properties) GetInt64Default

func (p Properties) GetInt64Default(name string, def int64) int64

func (Properties) GetIntDefault

func (p Properties) GetIntDefault(name string, def int) int

func (Properties) IsSet

func (p Properties) IsSet(name string) bool

type Readonly added in v2.11.0

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

func (*Readonly) Get added in v2.11.0

func (a *Readonly) Get(name string) (string, bool)

func (*Readonly) GetBool added in v2.11.0

func (a *Readonly) GetBool(name string) (bool, bool)

func (*Readonly) GetInt added in v2.11.0

func (a *Readonly) GetInt(name string) (int, bool)

func (*Readonly) GetInt64 added in v2.11.0

func (a *Readonly) GetInt64(name string) (int64, bool)

func (*Readonly) GetInt64s added in v2.11.0

func (a *Readonly) GetInt64s(name string) ([]int64, error)

func (*Readonly) GetInts added in v2.11.0

func (a *Readonly) GetInts(name string) []int

func (*Readonly) GetProperties added in v2.11.0

func (a *Readonly) GetProperties(name string) Properties

func (*Readonly) GetProperty added in v2.11.0

func (a *Readonly) GetProperty(name string, property string) (string, bool)

func (*Readonly) GetPropertyX added in v2.11.0

func (a *Readonly) GetPropertyX(name string, property string) string

func (*Readonly) GetX added in v2.11.0

func (a *Readonly) GetX(name string) string

func (*Readonly) Gets added in v2.11.0

func (a *Readonly) Gets(name string) []string

func (*Readonly) IsSet added in v2.11.0

func (a *Readonly) IsSet(name string) bool

func (*Readonly) Unmarshal added in v2.11.0

func (a *Readonly) Unmarshal(v any) error

Jump to

Keyboard shortcuts

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