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)