Documentation
¶
Overview ¶
Package cli 提供框架无关、可单测的 CLI 子命令分发原语。
设计目标是「命令逻辑能被单元测试直接调用」:分发入口是 App.Run(ctx, args, stdout, stderr) int —— 参数与输出流全部由调用方传入。 本包既不读取进程参数,也不终止进程(不读 os.Args、不调 os.Exit), 因此测试无需起子进程、无需捕获 stdout,命令体可以像普通函数一样断言。
组成:
- Command:单个子命令(名称 + 一行摘要 + 执行体)
- App:子命令集合的分发器,负责查找子命令、生成用法文本、映射退出码
- NewFlagSet / ParseFlags:标准库 flag 的薄封装,把解析错误与用法 重定向到调用方给的 stderr(标准库默认写 os.Stderr,会破坏「输出流 由调用方给」的约定)
退出码约定写死在包里,调用方不要另立一套:
ExitOK = 0 成功 ExitError = 1 运行失败 ExitUsage = 2 用法错误(缺少子命令、未知子命令、参数不合法)
未知子命令与参数不合法同归 ExitUsage(2);子命令返回什么,整体就返回 什么(0→0、1→1、2→2)。
用法(main 里只做三件事:拼 args、给流、拿退出码):
func main() {
app := &cli.App{
Name: "mytool",
Commands: []cli.Command{
{Name: "build", Summary: "构建产物", Run: runBuild},
{Name: "serve", Summary: "启动服务", Run: runServe},
},
}
os.Exit(app.Run(context.Background(), os.Args[1:], os.Stdout, os.Stderr))
}
命令体(可直接被单测调用,无需起子进程):
func runBuild(ctx context.Context, args []string, stdout, stderr io.Writer) int {
fs := cli.NewFlagSet("build", stderr)
release := fs.Bool("release", false, "发布模式")
rest, code := cli.ParseFlags(fs, args)
if code != cli.ExitOK {
return code
}
_ = release
_ = rest
return cli.ExitOK
}
用法文本由 Command.Summary 自动生成,调用方不需要手写:
- App.Usage(w) 单独打印(宿主想接自己的 --help 分支时写到 stdout)
- App.Run 在「缺少子命令」或「未知子命令」时把用法写到 stderr 并返回 ExitUsage(错误路径的诊断信息一律走 stderr,stdout 只留给命令体的 正常输出,便于管道)
Index ¶
Constants ¶
const ( // ExitOK 成功。 ExitOK = 0 // ExitError 运行失败:子命令执行出错。 ExitError = 1 // ExitUsage 用法错误:缺少子命令、未知子命令、参数不合法。 ExitUsage = 2 )
退出码约定(写死在包里,宿主与子命令共用同一套语义)。
Variables ¶
This section is empty.
Functions ¶
func NewFlagSet ¶
NewFlagSet 创建绑定到 stderr 的标准库 FlagSet。
两个关键点:
- flag.ContinueOnError:解析出错不终止进程,而是返回 error,交由调用方 映射成退出码(ExitUsage)——与本包「不调 os.Exit」的约定一致;
- fs.SetOutput(stderr):把标准库默认写到 os.Stderr 的错误与用法重定向 到调用方给的 stderr,遵守「输出流由调用方给」的约定。
典型用法:
fs := cli.NewFlagSet("build", stderr)
release := fs.Bool("release", false, "发布模式")
rest, code := cli.ParseFlags(fs, args)
if code != cli.ExitOK {
return code
}
注意:FlagSet 不支持并发 Parse;每个子命令自建一个即可。
func ParseFlags ¶
ParseFlags 用 fs 解析 args,返回剩余位置参数与退出码。
- 解析成功:返回 (位置参数, ExitOK)
- -h/--help:标准库已把用法写到 fs 的输出流(调用方给的 stderr), 返回 (位置参数, ExitOK)——帮助请求不算错误
- 解析失败(未定义的 flag、取值类型不匹配等):标准库已把错误信息与 用法写到 fs 的输出流,返回 (nil, ExitUsage)
fs 为 nil 时返回 (nil, ExitUsage)。
Types ¶
type App ¶
type App struct {
// Name 程序名,用于用法文本与错误信息;为空时退化为 "app"。
Name string
// Commands 子命令列表。用法文本按声明顺序展示(作者自己决定顺序),
// 同名子命令以先声明的为准。
Commands []Command
}
App CLI 应用:子命令集合的分发器。
App 本身不含可变状态,零值之外的结构体在 Run 之后仍可复用;并发调用 Run 是安全的(只读 Commands)。
func (*App) Run ¶
Run 分发 args[0] 对应的子命令,返回其退出码。
行为约定:
- args 为空(未给子命令):往 stderr 打印提示与用法,返回 ExitUsage(2)
- 未知子命令:往 stderr 打印错误与用法,返回 ExitUsage(2)
- 子命令名匹配:调用该子命令的 Run,args 去掉子命令名后透传,返回值 原样返回(0→0、1→1、2→2)
- 命令表里有空名子命令(宿主装配错误):往 stderr 打印错误,返回 ExitError(1)——不是用户输入问题,不落到 2
本方法不读进程参数、不终止进程:args 与两个输出流全部由调用方给定, 因此可被单元测试直接调用。stdout/stderr 传 nil 时按丢弃处理,不会 panic。
type Command ¶
type Command struct {
// Name 子命令名,对应 args[0];不可为空(空名视为宿主装配错误)。
Name string
// Summary 一行摘要,用于自动生成用法文本;可为空(此时只列名字)。
Summary string
// Run 执行体。args 是去掉子命令名后的剩余参数,stdout/stderr 由宿主
// 传入。返回值即退出码,原样透传给 App.Run 的调用方。
Run func(ctx context.Context, args []string, stdout, stderr io.Writer) int
}
Command 单个 CLI 子命令。