# 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]
1

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

你可以混合多级子命令的任何 flagscmdr 将会采用从底层到顶层的顺序去尝试完成 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
		})
1
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
)
1
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) 被找到。

# 🔚