/
parse-with.go
349 lines (295 loc) · 10.2 KB
/
parse-with.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
// Copyright (C) 2023-2024 Takayuki Sato. All Rights Reserved.
// This program is free software under MIT License.
// See the file LICENSE in this distribution for more details.
package cliargs
import (
"fmt"
"path"
)
// StoreKeyIsDuplicated is the error which indicates that a store key in an
// option configuration is duplicated another among all option configurations.
type StoreKeyIsDuplicated struct{ StoreKey string }
// Error is the method to retrieve the message of this error.
func (e StoreKeyIsDuplicated) Error() string {
return fmt.Sprintf("StoreKeyIsDuplicated{StoreKey:%s}", e.StoreKey)
}
// GetOpt is the method to retrieve the store key that caused this error.
func (e StoreKeyIsDuplicated) GetOpt() string {
return e.StoreKey
}
// ConfigIsArrayButHasNoArg is the error which indicates that an option
// configuration contradicts that the option must be an array
// (.IsArray = true) but must have no option argument (.HasArg = false).
type ConfigIsArrayButHasNoArg struct{ StoreKey string }
// Error is the method to retrieve the message of this error.
func (e ConfigIsArrayButHasNoArg) Error() string {
return fmt.Sprintf("ConfigIsArrayButHasNoArg{StoreKey:%s}", e.StoreKey)
}
// GetOpt is the method to retrieve the store key that caused this error.
func (e ConfigIsArrayButHasNoArg) GetOpt() string {
return e.StoreKey
}
// ConfigHasDefaultsButHasNoArg is the error which indicates that an option
// configuration contradicts that the option has default value
// (.Defaults != nil) but must have no option argument (.HasArg = false).
type ConfigHasDefaultsButHasNoArg struct{ StoreKey string }
// Error is the method to retrieve the message of this error.
func (e ConfigHasDefaultsButHasNoArg) Error() string {
return fmt.Sprintf("ConfigHasDefaultsButHasNoArg{StoreKey:%s}", e.StoreKey)
}
// GetOpt is the method to retrieve the store key that caused this error.
func (e ConfigHasDefaultsButHasNoArg) GetOpt() string {
return e.StoreKey
}
// OptionNameIsDuplicated is the error which indicates that an option name
// in Names field is duplicated another among all option configurations.
type OptionNameIsDuplicated struct{ Name, StoreKey string }
// Error is the method to retrieve the message of this error.
func (e OptionNameIsDuplicated) Error() string {
return fmt.Sprintf("OptionNameIsDuplicated{Name:%s,StoreKey:%s}",
e.Name, e.StoreKey)
}
// GetOpt is the method to retrieve the store key that caused this error.
func (e OptionNameIsDuplicated) GetOpt() string {
return e.Name
}
// UnconfiguredOption is the error which indicates that there is no
// configuration about the input option.
type UnconfiguredOption struct{ Name string }
// Error is the method to retrieve the message of this error.
func (e UnconfiguredOption) Error() string {
return fmt.Sprintf("UnconfiguredOption{Name:%s}", e.Name)
}
// GetOpt is the method to retrieve the store key that caused this error.
func (e UnconfiguredOption) GetOpt() string {
return e.Name
}
// OptionNeedsArg is the error which indicates that an option is input with
// no option argument though its option configuration requires option
// argument (.HasArg = true).
type OptionNeedsArg struct{ Name, StoreKey string }
// Error is the method to retrieve the message of this error.
func (e OptionNeedsArg) Error() string {
return fmt.Sprintf("OptionNeedsArg{Name:%s,StoreKey:%s}",
e.Name, e.StoreKey)
}
// GetOpt is the method to retrieve the store key that caused this error.
func (e OptionNeedsArg) GetOpt() string {
return e.Name
}
// OptionTakesNoArg is the error which indicates that an option is input with
// an option argument though its option configuration does not accept option
// arguments (.HasArg = false).
type OptionTakesNoArg struct{ Name, StoreKey string }
// Error is the method to retrieve the message of this error.
func (e OptionTakesNoArg) Error() string {
return fmt.Sprintf("OptionTakesNoArg{Name:%s,StoreKey:%s}",
e.Name, e.StoreKey)
}
// GetOpt is the method to retrieve the store key that caused this error.
func (e OptionTakesNoArg) GetOpt() string {
return e.Name
}
// OptionIsNotArray is the error which indicates that an option is input with
// an option argument multiple times though its option configuration specifies
// the option is not an array (.IsArray = false).
type OptionIsNotArray struct{ Name, StoreKey string }
// Error is the method to retrieve the message of this error.
func (e OptionIsNotArray) Error() string {
return fmt.Sprintf("OptionIsNotArray{Name:%s,StoreKey:%s}",
e.Name, e.StoreKey)
}
// GetOpt is the method to retrieve the store key that caused this error.
func (e OptionIsNotArray) GetOpt() string {
return e.Name
}
const anyOption = "*"
// OptCfg is the struct that represents an option configuration.
// An option configuration consists of fields: StoreKey, Names, HasArg,
// IsArray, Defaults, Desc, and ArgInHelp.
//
// The StoreKey field is the key to store a option value(s) in the option map.
// If this key is not specified or empty, the first element of Names field is
// used instead.
//
// The Names field is the array for specifing the option name and the aliases.
// The order of the names in this array are used in a help text.
//
// HasArg and IsArray are flags which allow the option to take option
// arguments.
// If both HasArg and IsArray are true, the option can take one or multiple
// option arguments.
// If HasArg is true and IsArray is false, the option can take only one option
// arguments.
// If both HasArg and IsArray are false, the option can take no option
// argument.
//
// Defaults is the field to specified the default value for when the option is
// not given in command line arguments.
//
// OnParsed is the field for a function which is called when the option has
// been parsed.
//
// Desc is the field to set the description of the option.
//
// ArgInHelp is a display of the argument of this option in a help text.
// The example of the display is like: -o, --option <value>.
type OptCfg struct {
StoreKey string
Names []string
HasArg bool
IsArray bool
Defaults []string
OnParsed *func([]string) error
Desc string
ArgInHelp string
}
// ParseWith is the function which parses command line arguments with option
// configurations.
// This function divides command line arguments to command arguments and
// options. And an option consists of a name and an option argument.
// Options are divided to long format options and short format options.
// About long/short format options, since they are same with Parse function,
// see the comment of that function.
//
// This function allows only options declared in option configurations.
// An option configuration has fields: StoreKey, Names, HasArg, IsArray,
// Defaults, Desc and ArgInHelp.
// When an option matches one of the Names in an option configuration, the
// option is registered into Cmd with StoreKey.
// If both HasArg and IsArray are true, the option can have one or multiple
// option argumentsr, and if HasArg is true and IsArray is false, the option
// can have only one option argument, otherwise the option cannot have option
// arguments.
// If Defaults field is specified and no option value is given in command line
// arguments, the value of Defaults is set as the option arguments.
//
// If options not declared in option configurationsi are given in command line
// arguments, this function basically returns UnconfiguradOption error.
// However, if you want to allow other options, add an option configuration of
// which StoreKey or the first element of Names is "*".
func ParseWith(osArgs []string, optCfgs []OptCfg) (Cmd, error) {
var cmdName string
if len(osArgs) > 0 {
cmdName = path.Base(osArgs[0])
}
var opts = make(map[string][]string)
var err error = nil
cfgMap := make(map[string]int)
hasAnyOpt := false
for i, cfg := range optCfgs {
storeKey := cfg.StoreKey
if len(storeKey) == 0 && len(cfg.Names) > 0 {
storeKey = cfg.Names[0]
}
if len(storeKey) == 0 {
continue
}
if storeKey == anyOption {
hasAnyOpt = true
continue
}
_, exists := opts[storeKey]
if exists {
err = StoreKeyIsDuplicated{StoreKey: storeKey}
return Cmd{Name: cmdName, args: empty}, err
}
opts[storeKey] = nil
if !cfg.HasArg {
if cfg.IsArray {
err = ConfigIsArrayButHasNoArg{StoreKey: storeKey}
return Cmd{Name: cmdName, args: empty}, err
}
if cfg.Defaults != nil {
err = ConfigHasDefaultsButHasNoArg{StoreKey: storeKey}
return Cmd{Name: cmdName, args: empty}, err
}
}
for _, nm := range cfg.Names {
_, exists := cfgMap[nm]
if exists {
err = OptionNameIsDuplicated{Name: nm, StoreKey: storeKey}
return Cmd{Name: cmdName, args: empty}, err
}
cfgMap[nm] = i
}
}
var takeArgs = func(opt string) bool {
i, exists := cfgMap[opt]
if exists {
return optCfgs[i].HasArg
}
return false
}
args := make([]string, 0)
opts = make(map[string][]string)
var collectArgs = func(a ...string) {
args = append(args, a...)
}
var collectOpts = func(name string, a ...string) error {
i, exists := cfgMap[name]
if !exists {
if !hasAnyOpt {
return UnconfiguredOption{Name: name}
}
arr := opts[name]
if arr == nil {
arr = empty
}
opts[name] = append(arr, a...)
return nil
}
cfg := optCfgs[i]
storeKey := cfg.StoreKey
if len(storeKey) == 0 {
storeKey = cfg.Names[0]
}
if !cfg.HasArg {
if len(a) > 0 {
return OptionTakesNoArg{Name: name, StoreKey: storeKey}
}
} else {
if len(a) == 0 {
return OptionNeedsArg{Name: name, StoreKey: storeKey}
}
}
arr := opts[storeKey]
if arr == nil {
arr = empty
}
arr = append(arr, a...)
if !cfg.IsArray {
if len(arr) > 1 {
return OptionIsNotArray{Name: name, StoreKey: storeKey}
}
}
opts[storeKey] = arr
return nil
}
var osArgs1 []string
if len(osArgs) > 1 {
osArgs1 = osArgs[1:]
}
err = parseArgs(osArgs1, collectArgs, collectOpts, takeArgs)
for _, cfg := range optCfgs {
if len(cfg.Names) == 0 {
continue
}
storeKey := cfg.StoreKey
if len(storeKey) == 0 {
storeKey = cfg.Names[0]
}
arr, exists := opts[storeKey]
if !exists && cfg.Defaults != nil {
arr = cfg.Defaults
opts[storeKey] = arr
}
if cfg.OnParsed != nil {
e := (*cfg.OnParsed)(arr)
if e != nil && err == nil {
err = e
}
}
}
return Cmd{Name: cmdName, args: args, opts: opts}, err
}