-
Notifications
You must be signed in to change notification settings - Fork 1
/
app.go
178 lines (154 loc) · 4.16 KB
/
app.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
package kubo
import (
"fmt"
"io"
"os"
)
// App represents a command line app.
type App struct {
Root *Command // root command
Stdin io.Reader // default is os.Stdin
Stdout io.Writer // default is os.Stdout
}
// NewApp creates a new app with the given root command.
func NewApp(root *Command) *App {
return &App{
Root: root,
Stdin: os.Stdin,
Stdout: os.Stdout,
}
}
// Run runs the app with the given arguments.
func (a *App) Run(args []string) error {
cmd := a.Root
for {
tmpArgs := args[1:]
// Verify that multiple is only used once in the arguments
for i, arg := range cmd.Arguments {
if arg.Multiple && i != len(cmd.Arguments)-1 {
panic(fmt.Errorf("command %s: multiple can only be used in last argument", cmd.Name))
}
}
// Create the context to pass to the command
ctx := Context{
arguments: make(map[string]string),
flags: make(map[string]string),
stdin: a.Stdin,
stdout: a.Stdout,
}
// flagErr is used to hold the flag not found error, which can
// only be returned if no subcommand is found
var flagErr error
// Parse all the flags in the arguments
for i := 0; i < len(tmpArgs); i++ {
arg := tmpArgs[i]
name, ok := parseFlagName(arg)
if ok {
// Try to find the flag definition
flag, err := cmd.flag(name)
if err != nil {
// Since it is not found, hold the flag
// not found error for later and simply
// let it parse as per normal
flagErr = err
flag.Bool = true
}
var value string
if flag.Bool {
value = fmt.Sprint(true)
tmpArgs = append(append([]string{}, tmpArgs[:i]...), tmpArgs[i+1:]...)
} else if i+1 < len(tmpArgs) {
value = tmpArgs[i+1]
tmpArgs = append(append([]string{}, tmpArgs[:i]...), tmpArgs[i+2:]...)
} else {
return fmt.Errorf("no value found for flag: %s", name)
}
i-- // decrement to keep index correct
// Don't set the flag in the context since it
// was not defined in the command
if flagErr == nil {
ctx.flags[flag.Name] = value
}
}
}
// Set all flags with Bool to false if not set to true
for _, flag := range cmd.Flags {
if !flag.Bool {
continue
}
// Since flag is a bool flag and it is not set to true,
// set it to false
if _, err := ctx.Flag(flag.Name); err != nil {
ctx.flags[flag.Name] = "false"
}
}
// Parse raw arguments as child command
if len(tmpArgs) > 0 {
// Try to find child command
child, err := cmd.command(tmpArgs[0])
if err != nil {
// If no child command is found and it not possibly
// an argument, then return the command not found
// error
if len(cmd.Arguments) == 0 {
return err
}
} else {
// Set the command to the child and pop the first
// argument that matches the child command name
cmd = child
for i, arg := range args {
if arg == tmpArgs[0] {
args = append(append([]string{}, args[:i]...), args[i+1:]...)
break
}
}
continue
}
}
// Since no subcommand is found, the flag not found error should
// be returned if it is not nil
if flagErr != nil {
return flagErr
}
// Parse raw arguments as arguments
for _, arg := range cmd.Arguments {
if len(tmpArgs) == 0 {
return fmt.Errorf("argument not found: %s", arg.Name)
}
if arg.Multiple {
ctx.argumentMultipleName = arg.Name
ctx.argumentMultipleValue = tmpArgs
tmpArgs = nil
break
}
ctx.arguments[arg.Name] = tmpArgs[0]
tmpArgs = tmpArgs[1:]
}
if len(tmpArgs) > 0 {
return fmt.Errorf("extra arguments supplied")
}
// If there is no run, but help is available, then set it to the
// help command
if cmd.Run == nil {
if helpCmd, err := cmd.command("help"); err == nil {
cmd.Run = helpCmd.Run
}
}
// Run the command
return cmd.Run(&ctx)
}
}
// parseFlagName parses the given argument for a flag name, returning the name
// and a flag whether it was found.
func parseFlagName(arg string) (string, bool) {
matches := longFlagRegexp.FindStringSubmatch(arg)
if len(matches) > 1 {
return matches[1], true
}
matches = shortFlagRegexp.FindStringSubmatch(arg)
if len(matches) > 1 {
return matches[1], true
}
return "", false
}