-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathargs.go
284 lines (244 loc) · 6.16 KB
/
args.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
package gommander
import (
"fmt"
"os"
"regexp"
"strconv"
"strings"
)
type argumentType string
const (
integer argumentType = "int"
uinteger argumentType = "uint"
float argumentType = "float"
boolean argumentType = "bool"
str argumentType = "str"
filename argumentType = "file"
)
type Argument struct {
Name string
HelpStr string
RawValue string
ArgType argumentType
IsVariadic bool
IsRequired bool
ValidValues []string
DefaultValue string
ValidatorFns [](func(string) error)
ValidatorRe *regexp.Regexp
}
// A Builder method for creating a new argument. Valid values include <arg>, [arg] or simply the name of the arg
func NewArgument(name string) *Argument {
arg := Argument{Name: name, ArgType: str}
var delimiters []string
if strings.HasPrefix(name, "<") {
arg.IsRequired = true
delimiters = []string{"<", ">"}
} else if strings.HasPrefix(name, "[") {
delimiters = []string{"[", "]"}
}
if len(delimiters) > 0 {
arg.Name = strings.TrimPrefix(arg.Name, delimiters[0])
arg.Name = strings.TrimSuffix(arg.Name, delimiters[1])
}
if strings.ContainsRune(arg.Name, ':') {
values := strings.Split(arg.Name, ":")
arg.Name = values[1]
arg.ArgType = argumentType(values[0])
arg.addValidatorFns() // add default validator funcs
}
if strings.HasSuffix(arg.Name, "...") {
arg.IsVariadic = true
arg.Name = strings.ReplaceAll(arg.Name, "...", "")
}
return &arg
}
// A method for setting the default value on an argument to be used when no value is provided but the argument value is required
func (a *Argument) Default(val string) *Argument {
// Check if value valid
if len(a.ValidValues) > 0 {
if !a.testValue(val) {
// TODO: consider writing to stderr
fmt.Printf("error occurred when setting default value for argument: %v \n. the passed value %v does not match the valid values: %v", a.Name, val, a.ValidValues)
if !isTestMode() {
os.Exit(1)
}
}
}
// verify value against validator fn if any
for _, fn := range a.ValidatorFns {
err := fn(val)
if err != nil {
fmt.Printf("you tried to set a default value for argument: %v, but the validator function returned an error for values: %v", a.Name, val)
if !isTestMode() {
os.Exit(1)
}
}
}
a.DefaultValue = val
return a
}
// Simply sets the description or help string of the given argument
func (a *Argument) Help(val string) *Argument {
a.HelpStr = val
return a
}
// Sets whether an argument is variadic or not
func (a *Argument) Variadic(val bool) *Argument {
a.IsVariadic = val
return a
}
// Sets whether an argument is required or not
func (a *Argument) Required(val bool) *Argument {
a.IsRequired = val
return a
}
func (a *Argument) Type(val argumentType) *Argument {
a.ArgType = val
a.addValidatorFns()
return a
}
// Configures the valid values for an argument
func (a *Argument) ValidateWith(vals []string) *Argument {
a.ValidValues = vals
return a
}
// A method to pass a custom validator function for arguments passed
func (a *Argument) ValidatorFunc(fn func(string) error) *Argument {
a.ValidatorFns = append(a.ValidatorFns, fn)
return a
}
func (a *Argument) ValidatorRegex(val string) *Argument {
a.ValidatorRe = regexp.MustCompile(val)
return a
}
// A method for setting what the argument should be displayed as when printing help
func (a *Argument) DisplayAs(val string) *Argument {
a.RawValue = val
return a
}
/****************************** Package utilities ********************************/
func (a *Argument) addValidatorFns() {
switch a.ArgType {
case str:
{
}
case integer:
{
a.ValidatorFunc(func(s string) error {
_, err := strconv.Atoi(s)
if err != nil {
return fmt.Errorf("`%v` is not a valid integer", s)
}
return nil
})
}
case uinteger:
{
a.ValidatorFunc(func(s string) error {
_, err := strconv.ParseUint(s, 10, 64)
if err != nil {
return fmt.Errorf("`%v` is not a positive integer", s)
}
return nil
})
}
case float:
{
a.ValidatorFunc(func(s string) error {
_, err := strconv.ParseFloat(s, 64)
if err != nil {
return fmt.Errorf("`%v` is not a valid float", s)
}
return nil
})
}
case boolean:
{
a.ValidatorFunc(func(s string) error {
if s != "true" && s != "false" {
return fmt.Errorf("`%v` is not a valid boolean", s)
}
return nil
})
}
case filename:
{
a.ValidatorFunc(func(s string) error {
if _, e := os.Stat(s); e != nil {
return fmt.Errorf("no such file or directory: `%v`", s)
}
return nil
})
}
default:
{
fmt.Println(fmt.Errorf("found unknown argument type: `%v` for argument: `%v`", a.ArgType, a.getRawValue()))
if !isTestMode() {
os.Exit(1)
}
}
}
}
func (a *Argument) testValue(val string) bool {
valueMatch := false
matchCount := 0
if len(a.ValidValues) == 0 {
valueMatch = true
}
for _, v := range a.ValidValues {
if strings.EqualFold(v, val) {
valueMatch = true
break
}
}
for _, fn := range a.ValidatorFns {
err := fn(val)
if err == nil {
matchCount++
}
}
if a.ValidatorRe != nil && !a.ValidatorRe.MatchString(val) {
return false
}
return valueMatch && matchCount == len(a.ValidatorFns)
}
func (a *Argument) hasDefaultValue() bool {
return len(a.DefaultValue) > 0
}
func newArgument(val string, help string) *Argument {
arg := NewArgument(val)
arg.Help(help)
return arg
}
func (a *Argument) getRawValue() string {
if len(a.RawValue) == 0 {
var value strings.Builder
write := func(first rune, last rune) {
value.WriteRune(first)
value.WriteString(strings.ReplaceAll(a.Name, "_", "-"))
if a.IsVariadic {
value.WriteString("...")
}
value.WriteRune(last)
}
if a.IsRequired {
write('<', '>')
} else {
write('[', ']')
}
return value.String()
}
return a.RawValue
}
/****************************** Interface implementations ********************************/
func (a *Argument) generate(app *Command) (string, string) {
var leading strings.Builder
var floating strings.Builder
leading.WriteString(a.getRawValue())
floating.WriteString(a.HelpStr)
if a.hasDefaultValue() {
floating.WriteString(fmt.Sprintf(" (default: %v)", a.DefaultValue))
}
return leading.String(), floating.String()
}