-
Notifications
You must be signed in to change notification settings - Fork 41
/
base.go
414 lines (345 loc) · 8.84 KB
/
base.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
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
package gcli
import (
"context"
"os"
"path"
"path/filepath"
"runtime"
"strconv"
"strings"
"github.com/gookit/color"
"github.com/gookit/goutil/maputil"
"github.com/gookit/goutil/structs"
"github.com/gookit/goutil/sysutil"
)
/*************************************************************
* Command Line: command data
*************************************************************/
// Context struct
type Context struct {
maputil.Data
context.Context
// PID value
pid int
// OsName os name.
osName string
// WorkDir the CLI app work dir path. by `os.Getwd()`
workDir string
// BinFile bin script file, by `os.Args[0]`. eg "./path/to/cliapp"
binFile string
// BinDir bin script dir path. eg "./path/to"
binDir string
// BinName bin script filename. eg "cliapp"
binName string
// ArgLine os.Args to string, but no binName.
argLine string
}
// NewCtx instance
func NewCtx() *Context {
return &Context{
Data: make(maputil.Data),
Context: context.Background(),
}
}
// Value get by key
func (ctx *Context) Value(key any) any {
return ctx.Data.Get(key.(string))
}
// InitCtx some common info
func (ctx *Context) InitCtx() *Context {
binFile := os.Args[0]
workDir, _ := os.Getwd()
ctx.pid = os.Getpid()
// more info
ctx.osName = runtime.GOOS
ctx.workDir = workDir
ctx.binFile = binFile
// with path
if strings.ContainsRune(binFile, os.PathSeparator) {
ctx.binDir = filepath.Dir(binFile)
ctx.binName = filepath.Base(binFile)
} else {
ctx.binName = binFile
if fpath, err := sysutil.FindExecutable(binFile); err == nil {
ctx.binFile = fpath
ctx.binDir = filepath.Dir(fpath)
}
}
ctx.argLine = strings.Join(os.Args[1:], " ")
return ctx
}
// PID get pid
func (ctx *Context) PID() int {
return ctx.pid
}
// PIDString get pid as string
func (ctx *Context) PIDString() string {
return strconv.Itoa(ctx.pid)
}
// OsName is equals to `runtime.GOOS`
func (ctx *Context) OsName() string {
return ctx.osName
}
// OsArgs is equals to `os.Args`
func (ctx *Context) OsArgs() []string {
return os.Args
}
// BinFile get bin script file
func (ctx *Context) BinFile() string {
return ctx.binFile
}
// BinName get bin script name
func (ctx *Context) BinName() string {
return ctx.binName
}
// BinDir get bin script dirname
func (ctx *Context) BinDir() string {
return path.Dir(ctx.binFile)
}
// WorkDir get work dirname
func (ctx *Context) WorkDir() string {
return ctx.workDir
}
// ChWorkDir work dir path
func (ctx *Context) ChWorkDir(dir string) {
if len(dir) > 0 {
ctx.workDir = dir
}
}
// ArgLine os.Args to string, but no binName.
func (ctx *Context) ArgLine() string {
return ctx.argLine
}
func (ctx *Context) hasHelpKeywords() bool {
if ctx.argLine == "" {
return false
}
return strings.HasSuffix(ctx.argLine, " -h") || strings.HasSuffix(ctx.argLine, " --help")
}
// ResetData from ctx
func (ctx *Context) ResetData() {
ctx.Data = make(maputil.Data)
}
/*************************************************************
* command base
*************************************************************/
// will inject to every Command
type base struct {
color.SimplePrinter
// Hooks manage. allowed hooks: "init", "before", "after", "error"
*Hooks
// HelpReplacer help message replace pairs.
HelpReplacer
// helpVars custom add vars for render help template.
helpVars map[string]any
// Ctx data for command, allow add custom context data.
Ctx *Context
// Logo ASCII logo setting
Logo *Logo
// Version app version. like "1.0.1"
Version string
// ExitOnEnd call os.Exit on running end
ExitOnEnd bool
// ExitFunc default is os.Exit
ExitFunc func(int)
// all commands for the group
commands map[string]*Command
// command names. key is name, value is name string length
// eg. {"test": 4, "example": 7}
cmdNames map[string]int
// sub command aliases map. {alias: name}
cmdAliases *structs.Aliases
// raw input command name
inputName string
// current command name
commandName string
// the max width for added command names. default set 12.
nameMaxWidth int
// has sub-commands on the app
hasSubcommands bool
// Whether it has been initialized
initialized bool
// store some runtime errors
errors []error
}
func newBase() base {
return base{
Hooks: &Hooks{},
Logo: &Logo{Style: "info"},
// init mapping
cmdNames: make(map[string]int),
// name2idx: make(map[string]int),
commands: make(map[string]*Command),
// set an default value.
nameMaxWidth: 12,
// cmdAliases: make(maputil.Aliases),
cmdAliases: structs.NewAliases(aliasNameCheck),
// ExitOnEnd: false,
helpVars: make(map[string]any),
// Context: NewCtx(),
}
}
// init common basic help vars
func (b *base) initHelpReplacer() {
b.AddReplaces(map[string]string{
"pid": b.Ctx.PIDString(),
"workDir": b.Ctx.workDir,
"binFile": b.Ctx.binFile,
"binName": b.Ctx.binName,
})
}
// BinName get bin script name
func (b *base) BinName() string { return b.Ctx.binName }
// BinDir get bin script dirname
func (b *base) BinDir() string { return b.Ctx.BinDir() }
// WorkDir get work dirname
func (b *base) WorkDir() string { return b.Ctx.workDir }
// ChWorkDir work dir path
func (b *base) ChWorkDir(dir string) {
b.Ctx.ChWorkDir(dir)
}
// ResetData from ctx
func (b *base) ResetData() {
if b.Ctx != nil {
b.Ctx.ResetData()
}
}
// GetCommand get a command by top name
func (b *base) GetCommand(name string) *Command {
return b.commands[name]
}
// Command get a command by top name
func (b *base) Command(name string) (c *Command, exist bool) {
c, exist = b.commands[name]
return
}
// IsAlias name check
func (b *base) IsAlias(alias string) bool {
return b.cmdAliases.HasAlias(alias)
}
// ResolveAlias get real command name by alias
func (b *base) ResolveAlias(alias string) string {
return b.cmdAliases.ResolveAlias(alias)
}
// HasSubcommands on the app
func (b *base) HasSubcommands() bool {
return b.hasSubcommands
}
// HasCommands on the cmd/app
func (b *base) HasCommands() bool {
return len(b.cmdNames) > 0
}
// HasCommand top command name check
func (b *base) HasCommand(name string) bool {
_, has := b.cmdNames[name]
return has
}
// IsCommand top command name check. alias of the HasCommand()
func (b *base) IsCommand(name string) bool {
_, has := b.cmdNames[name]
return has
}
// add Command to the group
func (b *base) addCommand(pName string, c *Command) {
// ensure init command
c.initialize()
cName := c.Name
if _, ok := b.cmdNames[cName]; ok {
panicf("The command name '%s' is already added", cName)
}
if b.cmdAliases.HasAlias(cName) {
panicf("The name '%s' is already used as an alias", cName)
}
if c.IsDisabled() {
Debugf("command '%s' has been disabled, skip add", cName)
return
}
nameLen := len(cName)
// add command to app
b.cmdNames[cName] = nameLen
if c.HasCommands() {
b.hasSubcommands = true
}
// record command name max length
if nameLen > b.nameMaxWidth {
b.nameMaxWidth = nameLen
}
// add aliases for the command
Logf(VerbCrazy, "register command '%s'(parent: %s), aliases: %v", cName, pName, c.Aliases)
b.cmdAliases.AddAliases(c.Name, c.Aliases)
b.commands[cName] = c
}
// Match command by path names. eg: ["top", "sub"]
func (b *base) Match(names []string) *Command {
ln := len(names)
if ln == 0 {
panic("the command names is required")
}
top := names[0]
top = b.ResolveAlias(top)
c, ok := b.commands[top]
if !ok {
return nil
}
// sub-sub commands
if ln > 1 {
return c.Match(names[1:])
}
// current command
return c
}
// FindCommand command by path. eg: "top:sub" or "top sub"
func (b *base) FindCommand(path string) *Command {
return b.Match(splitPath2names(path))
}
// FindByPath command by path. eg: "top:sub" or "top sub"
func (b *base) FindByPath(path string) *Command {
return b.Match(splitPath2names(path))
}
// MatchByPath command by path. eg: "top:sub" or "top sub"
func (b *base) MatchByPath(path string) *Command {
return b.Match(splitPath2names(path))
}
// SetLogo text and color style
func (b *base) SetLogo(logo string, style ...string) {
b.Logo.Text = logo
if len(style) > 0 {
b.Logo.Style = style[0]
}
}
// AddError to the application
func (b *base) AddError(err error) {
b.errors = append(b.errors, err)
}
// Commands get all commands
func (b *base) Commands() map[string]*Command {
return b.commands
}
// CmdNames get all command names
func (b *base) CmdNames() []string {
return b.CommandNames()
}
// CommandNames get all command names
func (b *base) CommandNames() []string {
var ss []string
for n := range b.cmdNames {
ss = append(ss, n)
}
return ss
}
// CmdNameMap get all command names
func (b *base) CmdNameMap() map[string]int {
return b.cmdNames
}
// CmdAliases get cmd aliases
func (b *base) CmdAliases() *structs.Aliases {
return b.cmdAliases
}
// AliasesMapping get cmd aliases mapping
func (b *base) AliasesMapping() map[string]string {
return b.cmdAliases.Mapping()
}
// AddHelpVar to instance.
func (b *base) AddHelpVar(key string, val any) {
b.helpVars[key] = val
}