-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcmd.go
More file actions
233 lines (213 loc) · 4.98 KB
/
cmd.go
File metadata and controls
233 lines (213 loc) · 4.98 KB
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
// Package cmd implements a command-line parser.
package cmd
import (
"errors"
"fmt"
"os"
"strings"
)
// A Cmd represents a command with flags and positional arguments.
//
// The *Arg methods will panic if adding the argument would mean the command-line can’t be parsed
// unambiguously anymore. For example, successive calls to OptionalArg, Arg, OptionalArg mean that
// if the use passes two arguments, there’s no way to tell which optional argument they’re trying to
// specify.
//
// The Summary and Details fields are printed at the beginning and end, respectively, of the help
// message. They won’t be printed if left empty.
type Cmd struct {
Flags
Summary, Details string
name string
f func()
args []arg
argsState int
}
type arg struct {
name string
optional bool
single *string
multi *[]string
}
const (
argsInitial = iota
argsRegular
argsRegularOptional
argsRegularMulti
argsMulti
argsMultiRegular
argsOptinal
argsOptinalRegular
)
// New returns a new command that calls the given function after parsing arguments. The name is used
// in help and error messages.
func New(name string, f func()) *Cmd {
return &Cmd{
Flags: newFlags(),
name: name,
f: f,
}
}
func ambiguousArgs() {
panic("Cmd: ambiguous sequence of positional arguments")
}
// Arg defines a positional argument.
func (c *Cmd) Arg(name string, p *string) {
switch c.argsState {
case argsInitial, argsRegular:
c.argsState = argsRegular
case argsMulti:
c.argsState = argsMultiRegular
case argsOptinal:
c.argsState = argsOptinalRegular
default:
ambiguousArgs()
}
c.args = append(c.args, arg{
name: name,
single: p,
})
}
// OptionalArg defines an optional positional argument.
func (c *Cmd) OptionalArg(name string, p *string) {
switch c.argsState {
case argsInitial, argsOptinal:
c.argsState = argsOptinal
case argsRegular:
c.argsState = argsRegularOptional
default:
ambiguousArgs()
}
c.args = append(c.args, arg{
name: name,
optional: true,
single: p,
})
}
// RepeatedArg defines an argument that can be present one or more times.
func (c *Cmd) RepeatedArg(name string, p *[]string) {
c.addArgs(name, p, false)
}
// OptionalRepeatedArg defines an argument that can be present zero or more times.
func (c *Cmd) OptionalRepeatedArg(name string, p *[]string) {
c.addArgs(name, p, true)
}
func (c *Cmd) addArgs(name string, p *[]string, optional bool) {
switch c.argsState {
case argsInitial:
c.argsState = argsMulti
case argsRegular:
c.argsState = argsRegularMulti
default:
ambiguousArgs()
}
c.args = append(c.args, arg{
name: name,
optional: optional,
multi: p,
})
}
func (c *Cmd) errorAndExit(err error) {
w := os.Stderr
fmt.Fprintf(w, "%s: %s\n", c.name, err)
fmt.Fprintf(w, "Try '%s --help' for more information.\n", c.name)
os.Exit(2)
}
func (c *Cmd) helpAndExit() {
fmt.Fprintf(os.Stdout, c.Help())
os.Exit(0)
}
// Help returns a help message.
func (c *Cmd) Help() string {
defs := []*definitionList{
{
title: "Options",
definitions: c.Flags.defs,
},
}
return formatHelp(c.usage(), c.Summary, c.Details, defs)
}
func (c *Cmd) usage() string {
line := []string{"Usage:", c.name}
if s := c.Flags.usage(); s != "" {
line = append(line, s)
}
for _, arg := range c.args {
var s string
if arg.optional {
s = fmt.Sprintf("[%s]", arg.name)
} else {
s = fmt.Sprintf("%s", arg.name)
}
if arg.multi != nil {
s += "..."
}
line = append(line, s)
}
return strings.Join(line, " ")
}
// Run parses the given command-line arguments, sets values for given flags and runs the function
// provided to New. It’s usually called with os.Args[1:].
func (c *Cmd) Run(args []string) {
help, err := c.parse(args)
if err != nil {
c.errorAndExit(err)
}
if help {
c.helpAndExit()
}
c.f()
}
func (c *Cmd) parse(args []string) (help bool, err error) {
// parse flags
help, args, err = c.Flags.parse(args)
if err != nil || help {
return help, err
}
if c.argsState >= argsMulti {
// parse positional arguments in reverse order
for i := len(c.args) - 1; i >= 0; i-- {
a := c.args[i]
if len(args) == 0 {
if !a.optional {
return false, fmt.Errorf("missing %s argument", a.name)
}
return false, nil
}
if a.single != nil {
*a.single = args[len(args)-1]
args = args[:len(args)-1]
} else {
*a.multi = make([]string, len(args))
for i, arg := range args {
(*a.multi)[i] = arg
}
args = nil
}
}
} else {
// parse positional arguments in-order
for _, a := range c.args {
if len(args) == 0 {
if !a.optional {
return false, fmt.Errorf("missing %s argument", a.name)
}
return false, nil
}
if a.single != nil {
*a.single = args[0]
args = args[1:]
} else {
*a.multi = make([]string, len(args))
for i, arg := range args {
(*a.multi)[i] = arg
}
args = nil
}
}
}
if len(args) > 0 {
return false, errors.New("extra arguments on command-line")
}
return false, nil
}