管理 app 配置
集成 `Store` 能够提供应用程序配置数据的管理能力
tiny2 Example
通过集成 hedzr/store
1,可以无缝地添加应用程序配置数据的管理功能。
在 tiny1 示例程序中,实际上 cmdr 也隐含地使用了一个 DymmyStore,这个空包将会忽略对配置数据的访问请求。
通过集成 hedzr/cmdr-loaders
提供的插件,可以添加符合 GNU 文件夹规范的外部配置文件自动加载。
下面的示例完成了这些任务:
在 tiny2 示例程序中,我们加上了到 Store 的集成,并且创建了 jump to
这样的两级子命令。
hedzr/cmdr-loaders
提供了一个对 cmdr.Create()
更进一步的包装接口 loaders.Create()
,它将会自动加载恰当位置的外部配置文件或者配置源。这个工具包的目的是整合对 TOML 和 JSON 格式的配置文件进行自动加载,且符合 GNU 以及 Unix-like 习惯的文件布局。
hedzr/cmdr-loaders
作用相同,除了增加了到更多配置文件格式(例如 YAML,HCL,等等)的支持,也带入了更多了到第三方库的依赖关系。
在此基础上,lite.PrepareApp()
/loaders.PrepareApp()
是进一步的包装,目的在于为示例程序提供默认的 Commands 和 Flags 定义。
hedzr/cmdr-tests
延续上述思路,再一次对其包装提供一个 common.PrepareApp()
,这样就能为 examples 示例程序们的编写提供支持。
它们的用途在于三点:
- 自动集成
store.Store
, - 集成 loaders 以便加载环境变量和外部配置文件,
- 扁平化 app name,author 等基础信息。
总的来说,对于常规开发,我们推荐你优先使用 lite.Create()
来简化初始代码,其次的选择是使用 cmdr.Create()
或者 loaders.Create()
。
如果你愿意逐步完成初始化设定工作,那么使用 cmdr.New()
也是合适的。
手动集成 Store
以及 loaders
如果你想自行集成,下面的片段是一个做法:
用 OnAction
为命令关联处理逻辑
通常来说,多级子命令的结构中,只有末级命令应该带有 OnAction
—— 尽管我们允许你在中间层级也同样提供它。
如果你真的这么做了,它也并不会产生什么逻辑错误或者运行错误,只是对于终端用户来说稍微不那么友好,因为他可能并不会意识到 jump
也可以被 执行。
另一个副作用是,在 Shell 执行 app jump
时,原本会自动显示帮助屏,但现在将会执行你所关联的 OnAction
代码了,所以你必须使用app jump -h
来查阅帮助屏。
cmd.Store()
vs cmd.Set()
cmd.Store()
vs cmd.Set()
除此而外,你也可以通过 app.Store()/app.Set()
读写你的数据。
cmd.Store()
返回一个完整 Store 的子集,也就是键值 app.cmd.jump.to
所下辖的子树。所以 cmd.Store().MustBool("full")
就可以直接取得 --full
的状态。
cmd.Set()
相当于调用 app.Set()
,它获取整个 Store 对象。此时,你需要用 cmd.Set().MustBool("app.cmd.jump.to.full")
来取得 --full
的状态。另一个方法是,
类似地,app.Store()
获得的是 "app.cmd" 子树,所以你也可以用 app.Store().MustBool("jump.to.full")
来问询。
当使用 GetXXX
/MustXXX
来获取命令行标志所对应的选项值时,通常采用 cmd.Store()
/app.Store()
以求简化调用语句。
app.cmd
前缀是为了在序列化 Option Store 为 YAML 或者其他外部格式时而特别建立的前缀,这样能够保证 Option Store 的序列化内容能够被恰当地包含到外部配置中心里(无论是 YAML,JSON,TOML,抑或是显式的微服务外部配置中心)。
值得注意的是,当你真正进行序列化时,app
前缀将被自动摘除,因为实际上这个顶级前缀是为了在内存中统领子树的目的,而在外部持久化时去掉它可以节省存储空间。而在编程操作时,app
通常也是对使用者不可见的,这是因为 cmd.Set()
返回的实际上就是带有 app
的子树。
对于那些不与命令行标志相挂钩的 Option Store 选项来说,它们通常被建立在 app
层级之下。
一个 Option Store
在序列化为 YAML 之后能够很好地展示出其层级关系。例如下面的配置文件将被用于载入到 Store 中,
它显示了你的 app 应该如何组织自己的配置设定,又如何揉合了命令行参数的选项值到 Store 之中。
Build()
和 With(cb)
app/b.Cmd("long","short",...)...Build()
是一种 Builder Pattern,末尾的 Build()
调用用于提交前面的一系列构建动作,就像 tiny1 示例程序所做的那样。
但同时,我们也提供 app/b.Flag("long","short",...)...With(cb)
风格,With(cb)
同样起到提交作用,所以这时候就不必在结尾加上 .Build()
调用了。
在 tiny2 中,借助于 With 回调代码块,我们为 to
子命令附着了 full
选项。
所以终端用户可以在命令行加上 --full
或者 -f
来使能这个选项。
类似地,在 jump
的 With 代码块中,可以添加它的下级子命令,当然也包括为其添加关联的选项。
OS 返回值
to
命令也展示了如何设置应用程序返回值。通过 app.SetSuggestRetCode(retCode)
可以设置一个错误码,并在 main() 中利用 os.Exit(app.SuggestRetCode())
将其返回给操作系统。
Why so complex?
对于服务型应用程序来说,在退出程序之前往往需要进行占用资源清理(包括打开的文件、网络连接,以及运行中的 go routines 等等)。
所以在程序的中途执行 panic()
或者 os.Exit(##)
是不恰当的,这会导致资源的异常占用或者泄漏。
如此被推荐的最佳实践是,在需要的运行中途通过 app.SetSuggestRetCode(retCode)
标记一个退出码,然后逐级通过 return err
返回到 main() 函数,此时必要的清理代码都将正常执行,最后再调用 os.Exit(app.SuggestRetCode())
向操作系统返回错误码。
对 Store
的操作
在相应的 OnAction
处理程序中,full 选项将导致在控制台打印 Store 的内部结构。
在上面打印的 app 配置数据集树结构中,app.cmd
包含了 tiny2 的命令行选项集合的映射,以及这些选项的最终值。
app
子树
cmdr
管理的 Store
中,app
子树是自动创建的。
所有的命令行标志将被一一映射到 app.cmd
子树中,例如 jump to
命令的 full
选项,将被映射为 app.cmd.jump.to.full
键值。
所有的外部配置文件将被解析后挂载到 app
子树之下。
所以 app.toml
内容:
将被载入后挂载为 app.general.foo => "bar"
的键值对。
MustXXX()
检测一个选项参数在命令行中被指定过,可以使用 cmd.Store().MustXXX()
函数族。
MustXXX
函数族包含对很多数据类型的抽取。
例如,cmd.MustInt("option-long-name")
得到一个整数值,
而 cmd.MustDuration("option-long-name")
得到一个时间段值,
等等。
Learn More
本节中的示例程序是被推荐的编码风格,它已经集成了外部配置文件的加载能力。
但是相关的内容将在下一节中予以解说。
此外,下一节还提供一个较为冗长的逐步完成初始化的示例程序,用于展示使用 cmdr.New()
的方式自行完成集成工作。
Footnotes
-
hedzr/store
提供应用程序配置数据集的操作能力,以层次化的键值对方式管理数据。 ↩
How is this guide?
Last updated on