Skip to content

Commit

Permalink
Release 1.1.0 (#4)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
fred1268 committed May 17, 2023
1 parent 4da0ae6 commit 6229f01
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 22 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@
# Go workspace file
go.work
.devcontainer
.vscode
17 changes: 12 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ silently collaborating with your code.

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

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

> As a bonus, clap is about 350 LoC (excluding comments and tests)
---

## Installation
Expand Down Expand Up @@ -61,19 +63,24 @@ Very easy to start with:
}
```

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

```go
Name Type `clap:"longName[,shortName][,mandatory]"`
```

longName is a... well... long name, like --recursive or --credentials
longName is a... well... long name, like `--recursive` or `--credentials`

shortName is a single letter name, like -R or -c
shortName is a single letter name, like `-R` or `-c`

mandatory can be added to make the non-optional parameters

In your main, just make a call to clap.Parse():
In your main, just make a call to `clap.Parse()`:

```go
func main() {
Expand Down Expand Up @@ -131,7 +138,7 @@ You will get the following struct:

The following parameter types are supported by clap:

- bool: `--param`
- bool: `--param` or `--no-param`
- string: `--param test`
- int: `--param 10`
- float: `--param 12.3`
Expand Down
27 changes: 19 additions & 8 deletions clap/args.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,19 @@ func argsToFields(args []string, fieldDescs map[string]*fieldDescription, cfg an
continue
}
switch desc.Type.Kind() {
case reflect.Int, reflect.String, reflect.Float64:
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
fallthrough
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
fallthrough
case reflect.String, reflect.Float32, reflect.Float64:
i++
if i >= len(args) || strings.HasPrefix(args[i], "-") {
results.Missing = append(results.Missing, arg)
return results, fmt.Errorf("argument '%s': %w (missing argument)", arg, ErrMissingArgumentValue)
}
desc.Args = append(desc.Args, args[i])
case reflect.Bool:
desc.Args = append(desc.Args, "true")
desc.Args = append(desc.Args, fmt.Sprintf("%v", !strings.HasPrefix(arg, "--no-")))
case reflect.Slice, reflect.Array:
var values []string
count := len(args)
Expand Down Expand Up @@ -124,21 +128,30 @@ func fillStruct(args []string, fieldDescs map[string]*fieldDescription, cfg any)
reflectValue := reflect.ValueOf(cfg).Elem()
for name, desc := range fieldDescs {
field := reflectValue.Field(desc.Field)
if !field.CanSet() || len(desc.Args) == 0 {
if !field.CanSet() || len(desc.Args) == 0 || desc.Visited {
continue
}
desc.Visited = true
switch desc.Type.Kind() {
case reflect.String:
field.SetString(desc.Args[0])
case reflect.Int:
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
val, err := strconv.ParseInt(desc.Args[0], 10, 64)
if err != nil {
results.Unexpected = append(results.Unexpected, name)
return results, fmt.Errorf("argument '%s': %w (got '%s', expected integer)", name,
ErrUnexpectedArgument, desc.Args[0])
}
field.SetInt(val)
case reflect.Float64:
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
val, err := strconv.ParseInt(desc.Args[0], 10, 64)
if err != nil {
results.Unexpected = append(results.Unexpected, name)
return results, fmt.Errorf("argument '%s': %w (got '%s', expected integer)", name,
ErrUnexpectedArgument, desc.Args[0])
}
field.SetUint(uint64(val))
case reflect.Float32, reflect.Float64:
val, err := strconv.ParseFloat(desc.Args[0], 64)
if err != nil {
results.Unexpected = append(results.Unexpected, name)
Expand All @@ -147,9 +160,7 @@ func fillStruct(args []string, fieldDescs map[string]*fieldDescription, cfg any)
}
field.SetFloat(val)
case reflect.Bool:
if desc.Args != nil {
field.SetBool(true)
}
field.SetBool(desc.Args[0] == "true")
case reflect.Slice:
if desc.Type.Elem().Kind() == reflect.String {
field.Set(reflect.ValueOf(desc.Args))
Expand Down
72 changes: 65 additions & 7 deletions clap/clap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,45 +182,92 @@ func TestShortAndLong(t *testing.T) {
func TestComplete(t *testing.T) {
type config struct {
Extensions []string `clap:"--extensions,-e,mandatory"`
Recursive bool `clap:"--recusrive,-r"`
Recursive bool `clap:"--recursive,-r"`
Verbose bool `clap:"--verbose,-v"`
Size int `clap:"--size,-s"`
Directories []string `clap:"trailing"`
}
cfg := &config{}
var err error
var results *clap.Results
if results, err = clap.Parse([]string{
"--extensions", "jpg", "png", "bmp", "-v", "$home/temp", "$home/tmp", "/tmp",
"--extensions", "jpg", "png", "bmp", "-v", "-s", "10", "$home/temp", "$home/tmp", "/tmp",
}, cfg); err != nil {
t.Errorf("parsing error: %s", err)
}
t.Logf("t: %v\n", results)
wanted := &config{
Extensions: []string{"jpg", "png", "bmp"}, Verbose: true,
Extensions: []string{"jpg", "png", "bmp"}, Verbose: true, Size: 10,
Directories: []string{"$home/temp", "$home/tmp", "/tmp"},
}
if !reflect.DeepEqual(cfg, wanted) {
t.Errorf("wanted: '%v', got '%v'", wanted, cfg)
}
}

func TestBooleans(t *testing.T) {
type config struct {
Recursive bool `clap:"--recursive,-R"`
}
cfg := &config{
Recursive: false,
}
var err error
var results *clap.Results
if results, err = clap.Parse([]string{"--recursive"}, cfg); err != nil {
t.Errorf("parsing error: %s", err)
}
t.Logf("t: %v\n", results)
wanted := &config{Recursive: true}
if !reflect.DeepEqual(cfg, wanted) {
t.Errorf("wanted: '%v', got '%v'", wanted, cfg)
}
// with --no-recursive
cfg = &config{
Recursive: true,
}
if results, err = clap.Parse([]string{"--no-recursive"}, cfg); err != nil {
t.Errorf("parsing error: %s", err)
}
t.Logf("t: %v\n", results)
wanted = &config{Recursive: false}
if !reflect.DeepEqual(cfg, wanted) {
t.Errorf("wanted: '%v', got '%v'", wanted, cfg)
}
}

func TestTypes(t *testing.T) {
type config struct {
String string `clap:"--string"`
Int int `clap:"--int"`
Float float64 `clap:"--float"`
Int8 int8 `clap:"--int8"`
Int16 int16 `clap:"--int16"`
Int32 int32 `clap:"--int32"`
Int64 int64 `clap:"--int64"`
UInt uint `clap:"--uint"`
UInt8 uint8 `clap:"--uint8"`
UInt16 uint16 `clap:"--uint16"`
UInt32 uint32 `clap:"--uint32"`
UInt64 uint64 `clap:"--uint64"`
Float32 float32 `clap:"--float32"`
Float64 float64 `clap:"--float64"`
Bool bool `clap:"--bool"`
DefaultTrue bool `clap:"--defaulttrue"`
StringSlice []string `clap:"--string-slice"`
IntSlice []int `clap:"--int-slice"`
StringArray [2]string `clap:"--string-array"`
IntArray [3]int `clap:"--int-array"`
Trailing []string `clap:"trailing"`
}
cfg := &config{}
cfg := &config{
DefaultTrue: true,
}
var err error
var results *clap.Results
if results, err = clap.Parse([]string{
"--string", "str", "--int", "10", "--float", "12.3", "--bool", "--string-slice", "a", "b", "c",
"--string", "str", "--int", "10", "--int8", "8", "--int16", "16", "--int32", "32", "--int64", "64",
"--uint", "12", "--uint8", "65535", "--uint16", "65535", "--uint32", "65535", "--uint64", "65535",
"--float32", "12.32", "--float64", "12.64", "--bool", "--no-defaulttrue", "--string-slice", "a", "b", "c",
"--int-slice", "10", "11", "12", "--string-array", "a", "b", "--int-array", "10", "11", "12",
"w", "x", "y", "z",
}, cfg); err != nil {
Expand All @@ -230,8 +277,19 @@ func TestTypes(t *testing.T) {
wanted := &config{
String: "str",
Int: 10,
Float: 12.3,
Int8: 8,
Int16: 16,
Int32: 32,
Int64: 64,
UInt: 12,
UInt8: 255,
UInt16: 65535,
UInt32: 65535,
UInt64: 65535,
Float32: 12.32,
Float64: 12.64,
Bool: true,
DefaultTrue: false,
StringSlice: []string{"a", "b", "c"},
IntSlice: []int{10, 11, 12},
StringArray: [2]string{"a", "b"},
Expand Down
5 changes: 3 additions & 2 deletions clap/fielddescription.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ type fieldDescription struct {
Field int
ShortName string
LongName string
Mandatory bool
Found bool
Type reflect.Type
Args []string
Mandatory bool
Found bool
Visited bool
}
3 changes: 3 additions & 0 deletions clap/tag.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ func computeFieldDescriptions(t reflect.Type) (map[string]*fieldDescription, err
fieldDesc.LongName = strings.Trim(tag, "-")
if fieldDesc.LongName != "" {
fieldDescs["--"+fieldDesc.LongName] = fieldDesc
if field.Type.Kind() == reflect.Bool {
fieldDescs["--no-"+fieldDesc.LongName] = fieldDesc
}
}
if fieldDesc.ShortName != "" {
fieldDescs["-"+fieldDesc.ShortName] = fieldDesc
Expand Down

0 comments on commit 6229f01

Please sign in to comment.