-
Notifications
You must be signed in to change notification settings - Fork 1
/
argument-builder.go
302 lines (263 loc) · 8.94 KB
/
argument-builder.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
package argo
import (
"errors"
"reflect"
"github.com/Foxcapades/Argonaut/internal/unmarshal"
"github.com/Foxcapades/Argonaut/internal/xarg"
)
// An ArgumentBuilder instance is used to construct a CLI argument that may be
// attached to a Flag or CommandLeaf.
type ArgumentBuilder interface {
// WithName sets the name for this argument.
//
// The name value is used when rendering help information about this argument.
WithName(name string) ArgumentBuilder
// WithDescription sets the description of this argument to be shown in
// rendered help text.
WithDescription(desc string) ArgumentBuilder
// Require marks the output Argument as being required.
Require() ArgumentBuilder
isRequired() bool
// WithBinding sets the bind value for the Argument.
//
// The bind value may be one of a value pointer, a consumer function, or an
// Unmarshaler instance. For demonstrations of each, see the examples below.
//
// If the bind value is a pointer, the Argument's value unmarshaler will be
// called to unmarshal the raw string value into a value of the type passed
// to this method.
//
// If the bind value is a consumer function, that function will be called with
// the parsed value from the CLI. The consumer function may optionally return
// an error which, if not nil, will be passed up as a parsing error.
//
// If the bind value is an Unmarshaler instance, that instance's Unmarshal
// method will be called with the raw input from the CLI.
//
// Setting this value to anything other than a pointer or an Unmarshaler
// instance will result in an error being returned when building the argument
// is attempted.
//
// Example 1 (a simple var binding):
// var myValue time.Duration
// cli.Argument.WithBinding(&myValue)
//
// Example 2 (an unmarshaler func):
// cli.Argument.WithBinding(UnmarshalerFunc(func(raw string) error {
// fmt.Println(raw)
// return nil
// }))
//
// Example 3 (lets get silly with it):
// var myValue map[bool]**string
// cli.Argument().WithBinding(&myValue)
//
// Example 4 (custom type)
// type Foo struct {
// // some fields
// }
//
// func (f *Foo) Unmarshal(raw string) error {
// // parse the given string
// return nil
// }
//
// func main() {
// var foo Foo
// cli.Argument().WithBinding(&foo)
// }
//
// Example 5 (plain consumer func which returns an error):
// cli.Argument().WithBinding(func(value int) error { do something })
//
// Example 6 (plain consumer func which returns nothing):
// cli.Argument().WithBinding(func(value int) { do something })
//
WithBinding(pointer any) ArgumentBuilder
getBinding() any
// WithDefault sets the default value for the argument to be used if the
// argument is not provided on the command line.
//
// Setting this value without providing a binding value using `Bind()` will
// mean that the given default will not be set to anything when the CLI input
// is parsed.
//
// When used, the type of this value must meet one of the following criteria:
// 1. `val` is compatible with the type of the value used with
// WithBinding.
// 2. `val` is a string that may be parsed into a value of the type used
// with WithBinding.
// 3. `val` is a function which returns a type that is compatible with the
// type of the value used with WithBinding
// 4. `val` is a function which returns a type that is compatible with the
// type of the value used with WithBinding in addition to returning an
// error as the second return value.
//
// Examples:
// arg.WithBinding(&fooString).WithDefault(3) // Type mismatch
//
// arg.WithBinding(&fooInt).WithDefault(3) // OK
//
// arg.WithBinding(&fooInt).
// WithDefault(func() int {return 3}) // OK
//
// arg.WithBinding(&fooInt).
// WithDefault(func() (int, error) {
// return 3, nil
// }) // OK
//
// If the value provided to this method is a pointer to the type of the bind
// value it will be dereferenced to set the bind value.
WithDefault(def any) ArgumentBuilder
getDefault() any
// WithUnmarshaler allows providing a custom ValueUnmarshaler instance that
// will be used to unmarshal string values into the binding type.
//
// If no binding is set on this argument, the provided ValueUnmarshaler will
// not be used.
//
// If a custom unmarshaler is not provided by way of this method, then the
// internal magic unmarshaler will be used to parse raw argument values.
WithUnmarshaler(fn ValueUnmarshaler) ArgumentBuilder
// WithValidator appends the given validator function to the argument's
// internal slice of validators.
//
// There are 2 types of validators that may be set here, each of which going
// to a separate slice. Type 1 is a pre-parse validator which will be called
// when an argument is first hit, but before it is parsed. Type 2 is a
// post-parse validator which will be called immediately after an argument is
// parsed to validate the parsed value.
//
// When appending a validator function, if it is of type 1 it will go to the
// pre-parse validator slice, and if it is of type 2 it will go to the
// post-parse validator slice.
//
// Pre-parse (type 1) validators must match the following function signature:
// func(string) error
//
// The value that is passed to the function will be the raw value that was
// passed to the command on the CLI. If an error is returned, CLI parsing
// will halt, and the returned error will be passed up.
//
// Post-parse (type 2) validators must match the following function signature:
// func(T, string) error
//
// Two values are passed to the function, the parsed value, and the raw value
// that was passed to the command ont he CLI. If an error is returned, CLI
// parsing will halt, and the returned error will be passed up.
//
// Validators will be executed in the order they are appended.
WithValidator(validatorFn any) ArgumentBuilder
// Build attempts to build an Argument instance out of the configuration given
// to this ArgumentBuilder instance.
//
// This function shouldn't need to be called in normal use of this library.
Build(ctx *WarningContext) (Argument, error)
}
func NewArgumentBuilder() ArgumentBuilder {
return &argumentBuilder{
marsh: NewDefaultMagicUnmarshaler(),
}
}
type argumentBuilder struct {
name string
desc string
required bool
bindKind xarg.BindKind
defaultKind xarg.DefaultKind
def any
bind any
rootDef reflect.Value
rootBind reflect.Value
marsh ValueUnmarshaler
validators []any
errors []error
}
func (a *argumentBuilder) WithName(name string) ArgumentBuilder {
a.name = name
return a
}
func (a *argumentBuilder) WithDescription(desc string) ArgumentBuilder {
a.desc = desc
return a
}
func (a *argumentBuilder) Require() ArgumentBuilder {
a.required = true
return a
}
func (a argumentBuilder) isRequired() bool {
return a.required
}
func (a *argumentBuilder) WithBinding(binding any) ArgumentBuilder {
a.bindKind = xarg.BindKindUnknown
a.bind = binding
return a
}
func (a *argumentBuilder) getBinding() any {
return a.bind
}
func (a *argumentBuilder) WithDefault(def any) ArgumentBuilder {
a.defaultKind = xarg.DefaultKindUnknown
a.def = def
return a
}
func (a argumentBuilder) getDefault() any {
return a.def
}
func (a *argumentBuilder) WithUnmarshaler(fn ValueUnmarshaler) ArgumentBuilder {
a.marsh = fn
return a
}
func (a *argumentBuilder) WithValidator(fn any) ArgumentBuilder {
a.validators = append(a.validators, fn)
return a
}
func (a *argumentBuilder) Build(warnings *WarningContext) (Argument, error) {
errs := newMultiError()
if a.bindKind != xarg.BindKindNone {
kind, err := xarg.DetermineBindKind(a.bind, unmarshalerType)
a.bindKind = kind
if err != nil {
errs.AppendError(err)
} else {
a.rootBind = unmarshal.GetRootValue(reflect.ValueOf(a.bind), unmarshalerType)
}
}
if a.defaultKind != xarg.DefaultKindNone {
if a.bindKind == xarg.BindKindNone {
errs.AppendError(errors.New("default value set with no binding"))
} else if a.bindKind != xarg.BindKindInvalid {
kind, err := xarg.DetermineDefaultKind(a.bind, a.def)
a.defaultKind = kind
if err != nil {
errs.AppendError(err)
} else {
a.rootDef = reflect.ValueOf(a.def)
}
}
}
var pre, post []any
var err error
pre, post, err = xarg.SiftValidators(a.validators, &a.rootBind, a.bindKind)
if err != nil {
errs.AppendError(err)
}
if len(errs.Errors()) > 0 {
return nil, errs
}
return &argument{
warnings: warnings,
name: a.name,
desc: a.desc,
required: a.required,
bindingKind: a.bindKind,
defaultKind: a.defaultKind,
bindVal: a.bind,
defVal: a.def,
rootBind: a.rootBind,
rootDef: a.rootDef,
unmarshal: a.marsh,
preParseValidators: pre,
postParseValidators: post,
}, nil
}