Skip to content

Commit 6229f01

Browse files
authored
Release 1.1.0 (#4)
* Improve on booleans * fix tests * Prevent reuse of long and short forms * Add support for all bit size ints and uints * Add support for inverted booleans * Revert the previous change on bools * Complement and tidy up the readme
1 parent 4da0ae6 commit 6229f01

File tree

6 files changed

+103
-22
lines changed

6 files changed

+103
-22
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,4 @@
2020
# Go workspace file
2121
go.work
2222
.devcontainer
23+
.vscode

README.md

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ silently collaborating with your code.
1717

1818
clap is a **non intrusive** Command Line Argument Parser, that will use struct tags to understand
1919
what you want to parse. You don't have to implement interface or use a CLI to scaffold your project:
20-
you just call `clap.Parse(args, &YourConfig)` and you are done. You are notetheless responsible for
20+
you just call `clap.Parse(args, &YourConfig)` and you are done. You are nonetheless responsible for
2121
handling potential commands and subcommands, but clap will fill up your CLI configuration struct
2222
with the values passed on the command line for those commands / subcommands.
2323

@@ -32,6 +32,8 @@ with the values passed on the command line for those commands / subcommands.
3232
- easy to configure
3333
- no CLI nor scaffolding
3434

35+
> As a bonus, clap is about 350 LoC (excluding comments and tests)
36+
3537
---
3638

3739
## Installation
@@ -61,19 +63,24 @@ Very easy to start with:
6163
}
6264
```
6365

66+
> Please note that this automatically generates a `--no-secure` and `--no-httpOnly`
67+
> flags that you can use on the command line to set the corresponding booleans
68+
> to the `false` value. This is useful when you want to give a boolean a `true`
69+
> default value.
70+
6471
A clap struct tag has the following structure:
6572

6673
```go
6774
Name Type `clap:"longName[,shortName][,mandatory]"`
6875
```
6976

70-
longName is a... well... long name, like --recursive or --credentials
77+
longName is a... well... long name, like `--recursive` or `--credentials`
7178

72-
shortName is a single letter name, like -R or -c
79+
shortName is a single letter name, like `-R` or `-c`
7380

7481
mandatory can be added to make the non-optional parameters
7582

76-
In your main, just make a call to clap.Parse():
83+
In your main, just make a call to `clap.Parse()`:
7784

7885
```go
7986
func main() {
@@ -131,7 +138,7 @@ You will get the following struct:
131138

132139
The following parameter types are supported by clap:
133140

134-
- bool: `--param`
141+
- bool: `--param` or `--no-param`
135142
- string: `--param test`
136143
- int: `--param 10`
137144
- float: `--param 12.3`

clap/args.go

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -56,15 +56,19 @@ func argsToFields(args []string, fieldDescs map[string]*fieldDescription, cfg an
5656
continue
5757
}
5858
switch desc.Type.Kind() {
59-
case reflect.Int, reflect.String, reflect.Float64:
59+
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
60+
fallthrough
61+
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
62+
fallthrough
63+
case reflect.String, reflect.Float32, reflect.Float64:
6064
i++
6165
if i >= len(args) || strings.HasPrefix(args[i], "-") {
6266
results.Missing = append(results.Missing, arg)
6367
return results, fmt.Errorf("argument '%s': %w (missing argument)", arg, ErrMissingArgumentValue)
6468
}
6569
desc.Args = append(desc.Args, args[i])
6670
case reflect.Bool:
67-
desc.Args = append(desc.Args, "true")
71+
desc.Args = append(desc.Args, fmt.Sprintf("%v", !strings.HasPrefix(arg, "--no-")))
6872
case reflect.Slice, reflect.Array:
6973
var values []string
7074
count := len(args)
@@ -124,21 +128,30 @@ func fillStruct(args []string, fieldDescs map[string]*fieldDescription, cfg any)
124128
reflectValue := reflect.ValueOf(cfg).Elem()
125129
for name, desc := range fieldDescs {
126130
field := reflectValue.Field(desc.Field)
127-
if !field.CanSet() || len(desc.Args) == 0 {
131+
if !field.CanSet() || len(desc.Args) == 0 || desc.Visited {
128132
continue
129133
}
134+
desc.Visited = true
130135
switch desc.Type.Kind() {
131136
case reflect.String:
132137
field.SetString(desc.Args[0])
133-
case reflect.Int:
138+
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
134139
val, err := strconv.ParseInt(desc.Args[0], 10, 64)
135140
if err != nil {
136141
results.Unexpected = append(results.Unexpected, name)
137142
return results, fmt.Errorf("argument '%s': %w (got '%s', expected integer)", name,
138143
ErrUnexpectedArgument, desc.Args[0])
139144
}
140145
field.SetInt(val)
141-
case reflect.Float64:
146+
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
147+
val, err := strconv.ParseInt(desc.Args[0], 10, 64)
148+
if err != nil {
149+
results.Unexpected = append(results.Unexpected, name)
150+
return results, fmt.Errorf("argument '%s': %w (got '%s', expected integer)", name,
151+
ErrUnexpectedArgument, desc.Args[0])
152+
}
153+
field.SetUint(uint64(val))
154+
case reflect.Float32, reflect.Float64:
142155
val, err := strconv.ParseFloat(desc.Args[0], 64)
143156
if err != nil {
144157
results.Unexpected = append(results.Unexpected, name)
@@ -147,9 +160,7 @@ func fillStruct(args []string, fieldDescs map[string]*fieldDescription, cfg any)
147160
}
148161
field.SetFloat(val)
149162
case reflect.Bool:
150-
if desc.Args != nil {
151-
field.SetBool(true)
152-
}
163+
field.SetBool(desc.Args[0] == "true")
153164
case reflect.Slice:
154165
if desc.Type.Elem().Kind() == reflect.String {
155166
field.Set(reflect.ValueOf(desc.Args))

clap/clap_test.go

Lines changed: 65 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -182,45 +182,92 @@ func TestShortAndLong(t *testing.T) {
182182
func TestComplete(t *testing.T) {
183183
type config struct {
184184
Extensions []string `clap:"--extensions,-e,mandatory"`
185-
Recursive bool `clap:"--recusrive,-r"`
185+
Recursive bool `clap:"--recursive,-r"`
186186
Verbose bool `clap:"--verbose,-v"`
187+
Size int `clap:"--size,-s"`
187188
Directories []string `clap:"trailing"`
188189
}
189190
cfg := &config{}
190191
var err error
191192
var results *clap.Results
192193
if results, err = clap.Parse([]string{
193-
"--extensions", "jpg", "png", "bmp", "-v", "$home/temp", "$home/tmp", "/tmp",
194+
"--extensions", "jpg", "png", "bmp", "-v", "-s", "10", "$home/temp", "$home/tmp", "/tmp",
194195
}, cfg); err != nil {
195196
t.Errorf("parsing error: %s", err)
196197
}
197198
t.Logf("t: %v\n", results)
198199
wanted := &config{
199-
Extensions: []string{"jpg", "png", "bmp"}, Verbose: true,
200+
Extensions: []string{"jpg", "png", "bmp"}, Verbose: true, Size: 10,
200201
Directories: []string{"$home/temp", "$home/tmp", "/tmp"},
201202
}
202203
if !reflect.DeepEqual(cfg, wanted) {
203204
t.Errorf("wanted: '%v', got '%v'", wanted, cfg)
204205
}
205206
}
206207

208+
func TestBooleans(t *testing.T) {
209+
type config struct {
210+
Recursive bool `clap:"--recursive,-R"`
211+
}
212+
cfg := &config{
213+
Recursive: false,
214+
}
215+
var err error
216+
var results *clap.Results
217+
if results, err = clap.Parse([]string{"--recursive"}, cfg); err != nil {
218+
t.Errorf("parsing error: %s", err)
219+
}
220+
t.Logf("t: %v\n", results)
221+
wanted := &config{Recursive: true}
222+
if !reflect.DeepEqual(cfg, wanted) {
223+
t.Errorf("wanted: '%v', got '%v'", wanted, cfg)
224+
}
225+
// with --no-recursive
226+
cfg = &config{
227+
Recursive: true,
228+
}
229+
if results, err = clap.Parse([]string{"--no-recursive"}, cfg); err != nil {
230+
t.Errorf("parsing error: %s", err)
231+
}
232+
t.Logf("t: %v\n", results)
233+
wanted = &config{Recursive: false}
234+
if !reflect.DeepEqual(cfg, wanted) {
235+
t.Errorf("wanted: '%v', got '%v'", wanted, cfg)
236+
}
237+
}
238+
207239
func TestTypes(t *testing.T) {
208240
type config struct {
209241
String string `clap:"--string"`
210242
Int int `clap:"--int"`
211-
Float float64 `clap:"--float"`
243+
Int8 int8 `clap:"--int8"`
244+
Int16 int16 `clap:"--int16"`
245+
Int32 int32 `clap:"--int32"`
246+
Int64 int64 `clap:"--int64"`
247+
UInt uint `clap:"--uint"`
248+
UInt8 uint8 `clap:"--uint8"`
249+
UInt16 uint16 `clap:"--uint16"`
250+
UInt32 uint32 `clap:"--uint32"`
251+
UInt64 uint64 `clap:"--uint64"`
252+
Float32 float32 `clap:"--float32"`
253+
Float64 float64 `clap:"--float64"`
212254
Bool bool `clap:"--bool"`
255+
DefaultTrue bool `clap:"--defaulttrue"`
213256
StringSlice []string `clap:"--string-slice"`
214257
IntSlice []int `clap:"--int-slice"`
215258
StringArray [2]string `clap:"--string-array"`
216259
IntArray [3]int `clap:"--int-array"`
217260
Trailing []string `clap:"trailing"`
218261
}
219-
cfg := &config{}
262+
cfg := &config{
263+
DefaultTrue: true,
264+
}
220265
var err error
221266
var results *clap.Results
222267
if results, err = clap.Parse([]string{
223-
"--string", "str", "--int", "10", "--float", "12.3", "--bool", "--string-slice", "a", "b", "c",
268+
"--string", "str", "--int", "10", "--int8", "8", "--int16", "16", "--int32", "32", "--int64", "64",
269+
"--uint", "12", "--uint8", "65535", "--uint16", "65535", "--uint32", "65535", "--uint64", "65535",
270+
"--float32", "12.32", "--float64", "12.64", "--bool", "--no-defaulttrue", "--string-slice", "a", "b", "c",
224271
"--int-slice", "10", "11", "12", "--string-array", "a", "b", "--int-array", "10", "11", "12",
225272
"w", "x", "y", "z",
226273
}, cfg); err != nil {
@@ -230,8 +277,19 @@ func TestTypes(t *testing.T) {
230277
wanted := &config{
231278
String: "str",
232279
Int: 10,
233-
Float: 12.3,
280+
Int8: 8,
281+
Int16: 16,
282+
Int32: 32,
283+
Int64: 64,
284+
UInt: 12,
285+
UInt8: 255,
286+
UInt16: 65535,
287+
UInt32: 65535,
288+
UInt64: 65535,
289+
Float32: 12.32,
290+
Float64: 12.64,
234291
Bool: true,
292+
DefaultTrue: false,
235293
StringSlice: []string{"a", "b", "c"},
236294
IntSlice: []int{10, 11, 12},
237295
StringArray: [2]string{"a", "b"},

clap/fielddescription.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ type fieldDescription struct {
66
Field int
77
ShortName string
88
LongName string
9-
Mandatory bool
10-
Found bool
119
Type reflect.Type
1210
Args []string
11+
Mandatory bool
12+
Found bool
13+
Visited bool
1314
}

clap/tag.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,9 @@ func computeFieldDescriptions(t reflect.Type) (map[string]*fieldDescription, err
104104
fieldDesc.LongName = strings.Trim(tag, "-")
105105
if fieldDesc.LongName != "" {
106106
fieldDescs["--"+fieldDesc.LongName] = fieldDesc
107+
if field.Type.Kind() == reflect.Bool {
108+
fieldDescs["--no-"+fieldDesc.LongName] = fieldDesc
109+
}
107110
}
108111
if fieldDesc.ShortName != "" {
109112
fieldDescs["-"+fieldDesc.ShortName] = fieldDesc

0 commit comments

Comments
 (0)