-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcommand.go
386 lines (329 loc) · 8.74 KB
/
command.go
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
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
/*
bobra 是一个模仿了 github.com/spf13/cobra 的包。
bobra 中实现了精简版的 cobra 的功能, 使得命令行程序开发者能够快速的建立耦合度低,高度模块化的命令行程序。
*/
package bobra
import (
"bytes"
"fmt"
"os"
"strings"
flag "github.com/spf13/pflag"
)
type Command struct {
// 命令的使用名称
Use string
// 命令的较短介绍
Short string
// 命令的较长介绍
Long string
// 命令使用介绍
Example string
// 这个命令对应的全部flags,为 globalflags + localflags
flags *flag.FlagSet
// 这个命令集合对应的全部全局可用的flag
globalflags *flag.FlagSet
// 这个命令集合对应的局部可用的flag,即仅当前命令可以使用的flag
localflags *flag.FlagSet
// 存放FlagSet错误输出的缓冲区
flagErrorBuf *bytes.Buffer
// 命令的介绍模版
usageTemplate string
// 子命令的列表
commands []*Command
// 父命令的指针
parent *Command
// 运行这个命令执行的函数
Run func(cmd *Command, args []string)
// 该 Command 的使用方法介绍
usageFunc func(*Command) error
}
// 将args参数转换为flags参数
func (c *Command) ParseFlags(args []string) error {
if c.flagErrorBuf == nil {
c.flagErrorBuf = new(bytes.Buffer)
}
beforeBufferLen := c.flagErrorBuf.Len()
c.inheritGlobalFlags()
err := c.Flags().Parse(args)
if c.flagErrorBuf.Len()-beforeBufferLen > 0 && err == nil {
fmt.Println(c.flagErrorBuf.String())
}
return err
}
// 根据flag参数执行该命令
func (c *Command) execute(a []string) error {
err := c.ParseFlags(a)
if err != nil {
return err
}
c.Run(c, a)
return nil
}
// 找到要执行的命令,或者抛出异常
func (c *Command) ExecuteC() (err error) {
args := os.Args
cmd, flags, err := c.Find(args)
if err == FoundHelp {
cmd.Usage()
return nil
}
if err != nil {
LogError(err)
return err
}
return cmd.execute(flags)
}
// 返回当前命令的父命令
func (c *Command) Parent() *Command {
return c.parent
}
// 执行命令,调用链为:Execute--->ExecuteC--->execute
func (c *Command) Execute() error {
err := c.ExecuteC()
if err != nil {
return err
}
return nil
}
// 设置全局可用的flags
func (c *Command) SetGlobalFlags(flags *flag.FlagSet) {
c.globalflags = flags
}
// 获取全局的flags
func (c *Command) GlobalFlags() *flag.FlagSet {
c.inheritGlobalFlags()
if c.globalflags == nil {
c.globalflags = flag.NewFlagSet(c.Name(), flag.ContinueOnError)
if c.flagErrorBuf == nil {
c.flagErrorBuf = new(bytes.Buffer)
}
c.globalflags.SetOutput(c.flagErrorBuf)
}
return c.globalflags
}
// 继承了全局的flags
func (c *Command) inheritGlobalFlags() {
// 如果为根命令,终止
if c.Parent() == nil {
return
}
// 否则继承父亲的globalflags, 一个指令集下应当维护一个全局唯一的globalflags指针
c.globalflags = c.Parent().GlobalFlags()
}
// 返回仅子命令可以使用的局部flags
func (c *Command) LocalFlags() *flag.FlagSet {
c.inheritGlobalFlags()
if c.localflags == nil {
c.localflags = flag.NewFlagSet(c.Name(), flag.ContinueOnError)
if c.flagErrorBuf == nil {
c.flagErrorBuf = new(bytes.Buffer)
}
c.localflags.SetOutput(c.flagErrorBuf)
}
return c.localflags
}
// 返回命令的参数列表, 如果 flags 为空则初始化这个flag
func (c *Command) Flags() *flag.FlagSet {
c.inheritGlobalFlags()
if c.flags == nil {
c.flags = flag.NewFlagSet(c.Name(), flag.ContinueOnError)
if c.flagErrorBuf == nil {
c.flagErrorBuf = new(bytes.Buffer)
}
c.flags.SetOutput(c.flagErrorBuf)
}
c.flags.AddFlagSet(c.localflags)
c.flags.AddFlagSet(c.globalflags)
return c.flags
}
// 添加子命令
func (c *Command) AddCommand(cmds ...*Command) {
for i, x := range cmds {
if cmds[i] == c {
panic("Command can't be a child of itself")
}
cmds[i].parent = c
c.commands = append(c.commands, x)
}
}
// 递归寻找下一个要执行的子命令,如果找不到则抛出异常
func innerFind(cmd *Command, innerArgs []string) (*Command, []string, error) {
// 参数列表中的第一个一定与cmd的 Name 相同
if innerArgs[0] != cmd.Name() {
return cmd, nil, ObjectNotFound{Type: "Command", Name: innerArgs[0]}
}
innerArgsWithoutFlags := stripFlags(innerArgs[1:], cmd)
// 如果发现有help输入,则不向下继续执行子命令,而是输出usage信息
if len(innerArgsWithoutFlags) > 0 && innerArgsWithoutFlags[0] == "help" {
return cmd, nil, FoundHelp
}
// 如果此时已经没有向下的子命令了
if len(innerArgsWithoutFlags) == 0 {
return cmd, innerArgs[1:], nil
}
// 否则此时已经有一个子命令了
sub := innerArgsWithoutFlags[0]
subCmd := cmd.findSubCmd(sub)
if subCmd == nil {
return cmd, nil, ObjectNotFound{Type: "Command", Name: sub}
}
return innerFind(subCmd, innerArgs[1:])
}
// 从参数中找到要执行的子命令, 如果没有子命令则返回这个命令本身,如果找不到则返回错误
func (c *Command) Find(args []string) (*Command, []string, error) {
cmd, flags, err := innerFind(c, args)
if err == FoundHelp {
return cmd, []string{}, FoundHelp
}
if err != nil {
return cmd, flags, err
}
return cmd, flags, nil
}
// 返回命令的名字
func (c *Command) Name() string {
name := c.Use
i := strings.Index(name, " ")
if i >= 0 {
name = name[:i]
}
return name
}
// 返回这条命令的完整介绍,应放在 Usage 的开头
func (c *Command) LongIntroduction() string {
return c.Long
}
// 返回这条命令的简短介绍,会返回在命令Usage的子命令列表中
func (c *Command) ShortIntroduction() string {
return c.Short
}
// 返回该命令的根命令
func (c *Command) Root() *Command {
p := c
for p.parent != nil {
p = c.parent
}
return p
}
func (c *Command) Commands() []*Command {
return c.commands
}
// 返回这条命令从根命令开始向下,直到当前命令c的命令名称组合,用 ' ' 分割
func (c *Command) CommandPath() string {
if c.HasParent() {
return c.Parent().CommandPath() + " " + c.Name()
}
return c.Name()
}
// 输出对于这条命令的完整描述
func (c *Command) UseLine() string {
var useline string
if c.HasParent() {
useline = c.parent.CommandPath() + " " + c.Use
} else {
useline = c.Use
}
if c.HasAvailableFlags() && !strings.Contains(useline, "[flags]") {
useline += " [flags]"
}
return useline
}
// 根据命令的名称寻找子命令
func (c *Command) findSubCmd(cmdUse string) *Command {
for _, cmd := range c.commands {
if cmd.Name() == cmdUse {
return cmd
}
}
return nil
}
// 根据是否存在 Run 函数指针来判断这个命令能否运行
func (c *Command) Runnable() bool {
return c.Run != nil
}
// 判断该命令是否有效
func (c *Command) IsAvailable() bool {
if c.Runnable() || c.HasAvailableSubCmds() {
return true
}
return false
}
// 判断该命令是否有有效的子命令
func (c *Command) HasAvailableSubCmds() bool {
for _, sub := range c.commands {
if sub.IsAvailable() {
return true
}
}
return false
}
// 判断 c 是否有子命令
func (c *Command) HasSubCommands() bool {
return len(c.commands) > 0
}
// 判断 c 是否有父命令
func (c *Command) HasParent() bool {
if c.parent != nil {
return true
}
return false
}
// 判断命令是否存在有效的flags
func (c *Command) HasAvailableFlags() bool {
c.inheritGlobalFlags()
return c.Flags().HasAvailableFlags()
}
// 判断命令是否存在全局有效的flags
func (c *Command) HasAvailableGlobalFlags() bool {
c.inheritGlobalFlags()
return c.GlobalFlags().HasAvailableFlags()
}
// 判断命令是否存在局部有效的flags
func (c *Command) HasAvailableLocalFlags() bool {
return c.LocalFlags().HasAvailableFlags()
}
// 显示命令的使用方法
func (c *Command) Usage() error {
return c.UsageFunc()(c)
}
// 返回能够用于输出【使用方法】的函数
func (c *Command) UsageFunc() (f func(*Command) error) {
if c.usageFunc != nil {
return c.usageFunc
}
if c.HasParent() {
return c.Parent().UsageFunc()
}
return func(c *Command) error {
c.inheritGlobalFlags()
err := templify(os.Stdout, c.UsageTemplate(), c)
if err != nil {
LogError(err)
}
return err
}
}
func (c *Command) UsageTemplate() string {
if c.usageTemplate != "" {
return c.usageTemplate
}
if c.HasParent() {
return c.parent.UsageTemplate()
}
return `
{{.LongIntroduction}}
Usage:{{if .Runnable}}
{{.UseLine}}{{end}}{{if .HasAvailableSubCmds}}
{{.CommandPath}} [command]
Available Commands:{{range .Commands}}{{if .IsAvailable}}
{{.Name}}: {{.ShortIntroduction}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}}
LocalFlags:
{{.LocalFlags.FlagUsages}}
{{end}}{{if .HasAvailableGlobalFlags}}
GlobalFlags:
{{.GlobalFlags.FlagUsages}}
{{end}} {{if .HasAvailableSubCmds}}
Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}}
`
}