# CLI app layout for cmdr
The user's command line input of a CLI application should have the form like this:
app commands [flags] [arguments]
app
is the executable's name.
# Commands
对于 cmdr
构建的 CLI 应用程序来说,commands
是由你定义的子命令构成,例如:
app ca print
app ca create
app cert create
# Flags
flags
由任意多个标志组成。例如:
app list --retry 5 -rv -n "front-end" --help
你可以混合多级子命令的任何 flags
,cmdr
将会采用从底层到顶层的顺序去尝试完成 flags
的匹配。如果你在具有从属关系的子命令层级中定义了相同的标志的话,要注意到顶层的标志将无法被匹配到。对于这类重复定义的标志,cmdr
会打印一条警告信息,提醒你判断是否需要调整相关设计。
直接属于一个命令的子命令彼此之间也不能重复,同样地会有一个警告信息输出。
# Arguments
arguments
表示当 commands
, flags
被匹配和处理完毕之后,命令行的剩余参数列表。例如你需要一个像 gcc 一样所的文件列表。在 cmdr
中,往往会将其称作 remained args
or tail arguments
,你可以在 Action 所定义的回调函数中直接取得这个数组:
root.NewSubCommand("soundex", "snd", "sndx", "sound").
Action(func(cmd *cmdr.Command, args []string) (err error) {
for ix, s := range args {
fmt.Printf("%5d. %s => %s\n", ix, s, cmdr.Soundex(s))
}
return
})
2
3
4
5
6
7
你会发现,我们在定义一条命令的同时,也通过 Action 为其指定响应函数。该函数被回调时,能够得到一个 cmd
对象以及 remained args
数组。
此时得到的
cmd
对象实际上就是 "soundex" 这个*cmdr.Command
对象。对于非 inline 方式单独定义的 Action 函数来说,
cmd
可能是有用的。考虑几个命令共用同一个 Action 回调函数的情况,cmd
将有助于区别是哪一个 Command 对象被命中了。
# App Layout
综上,一个 CLI 应用程序的典型编码布局可以是这样:
package main
import (
"fmt"
"github.com/hedzr/cmdr"
cmdr_examples "github.com/hedzr/cmdr-examples"
"gopkg.in/hedzr/errors.v2"
)
func main() {
Entry()
}
func Entry() {
if err := cmdr.Exec(buildRootCmd(),
cmdr.WithUnhandledErrorHandler(onUnhandledErrorHandler),
); err != nil {
fmt.Printf("error: %+v\n", err)
}
}
func onUnhandledErrorHandler(err interface{}) {
if cmdr.GetBoolR("enable-ueh") {
dumpStacks()
return
}
panic(err)
}
func dumpStacks() {
fmt.Printf("=== BEGIN goroutine stack dump ===\n%s\n=== END goroutine stack dump ===\n", errors.DumpStacksAsString(true))
}
func buildRootCmd() (rootCmd *cmdr.RootCommand) {
root := cmdr.Root(appName, cmdr_examples.Version).
Copyright(copyright, "hedzr").
Description(desc, longDesc).
Examples(examples)
rootCmd = root.RootCommand()
cmdr.NewBool(false).
Titles("enable-ueh", "ueh").
EnvKeys("ENABLE_UEH").
Description("Enables the unhandled exception handler?").
AttachTo(root)
soundex(root)
panicTest(root)
return
}
func soundex(root cmdr.OptCmd) {
// soundex
root.NewSubCommand("soundex", "snd", "sndx", "sound").
Description("soundex test").
Group("Test").
TailPlaceholder("[text1, text2, ...]").
Action(func(cmd *cmdr.Command, args []string) (err error) {
for ix, s := range args {
fmt.Printf("%5d. %s => %s\n", ix, s, cmdr.Soundex(s))
}
return
})
}
func panicTest(root cmdr.OptCmd) {
// panic test
pa := root.NewSubCommand("panic-test", "pa").
Description("test panic inside cmdr actions", "").
Group("Test")
val := 9
zeroVal := zero
pa.NewSubCommand("division-by-zero", "dz").
Description("").
Group("Test").
Action(func(cmd *cmdr.Command, args []string) (err error) {
fmt.Println(val / zeroVal)
return
})
pa.NewSubCommand("panic", "pa").
Description("").
Group("Test").
Action(raisePanic)
}
func raisePanic(cmd *cmdr.Command, args []string) (err error) {
panic(9)
return
}
const (
appName = "simple"
copyright = "simple is an effective devops tool"
desc = "simple is an effective devops tool. It make an demo application for `cmdr`."
longDesc = "simple is an effective devops tool. It make an demo application for `cmdr`."
examples = `
$ {{.AppName}} gen shell [--bash|--zsh|--auto]
generate bash/shell completion scripts
$ {{.AppName}} gen man
generate linux man page 1
$ {{.AppName}} --help
show help screen.
`
overview = ``
zero = 0
)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
这个示例可以在 https://github.com/hedzr/cmdr-examples/blob/master/examples/simple/main.go (opens new window) 被找到。
# 🔚
← Overview Sub-commands →