Merge remote-tracking branch 'origin/master' into usage-for-subcommands

This commit is contained in:
Alex Flint 2021-09-18 08:55:40 -07:00
commit f2f876420c
6 changed files with 117 additions and 4 deletions

View File

@ -169,6 +169,17 @@ arg.Foo = "abc"
arg.MustParse(&args)
```
### Combining command line options, environment variables, and default values
You can combine command line arguments, environment variables, and default values. Command line arguments take precedence over environment variables, which take precedence over default values. This means that we check whether a certain option was provided on the command line, then if not, we check for an environment variable (only if an `env` tag was provided), then if none is found, we check for a `default` tag containing a default value.
```go
var args struct {
Test string `arg:"-t,env:TEST" default:"something"`
}
arg.MustParse(&args)
```
### Arguments with multiple values
```go
var args struct {
@ -308,6 +319,22 @@ func main() {
As usual, any field tagged with `arg:"-"` is ignored.
### Supported types
The following types may be used as arguments:
- built-in integer types: `int, int8, int16, int32, int64, byte, rune`
- built-in floating point types: `float32, float64`
- strings
- booleans
- URLs represented as `url.URL`
- time durations represented as `time.Duration`
- email addresses represented as `mail.Address`
- MAC addresses represented as `net.HardwareAddr`
- pointers to any of the above
- slices of any of the above
- maps using any of the above as keys and values
- any type that implements `encoding.TextUnmarshaler`
### Custom parsing
Implement `encoding.TextUnmarshaler` to define your own parsing logic.

View File

@ -2,8 +2,12 @@ package arg
import (
"fmt"
"net"
"net/mail"
"net/url"
"os"
"strings"
"time"
)
func split(s string) []string {
@ -470,3 +474,34 @@ func Example_subcommand() {
// output:
// commit requested with message "what-this-commit-is-about"
}
func Example_allSupportedTypes() {
// These are the args you would pass in on the command line
os.Args = []string{}
var args struct {
Bool bool
Byte byte
Rune rune
Int int
Int8 int8
Int16 int16
Int32 int32
Int64 int64
Float32 float32
Float64 float64
String string
Duration time.Duration
URL url.URL
Email mail.Address
MAC net.HardwareAddr
}
// go-arg supports each of the types above, as well as pointers to any of
// the above and slices of any of the above. It also supports any types that
// implements encoding.TextUnmarshaler.
MustParse(&args)
// output:
}

2
go.mod
View File

@ -1,7 +1,7 @@
module github.com/alexflint/go-arg
require (
github.com/alexflint/go-scalar v1.0.0
github.com/alexflint/go-scalar v1.1.0
github.com/stretchr/testify v1.2.2
)

2
go.sum
View File

@ -1,5 +1,7 @@
github.com/alexflint/go-scalar v1.0.0 h1:NGupf1XV/Xb04wXskDFzS0KWOLH632W/EO4fAFi+A70=
github.com/alexflint/go-scalar v1.0.0/go.mod h1:GpHzbCOZXEKMEcygYQ5n/aa4Aq84zbxjy3MxYW0gjYw=
github.com/alexflint/go-scalar v1.1.0 h1:aaAouLLzI9TChcPXotr6gUhq+Scr8rl0P9P4PnltbhM=
github.com/alexflint/go-scalar v1.1.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=

View File

@ -257,17 +257,24 @@ func cmdFromStruct(name string, dest path, t reflect.Type) (*command, error) {
var errs []string
walkFields(t, func(field reflect.StructField, t reflect.Type) bool {
// Check for the ignore switch in the tag
// check for the ignore switch in the tag
tag := field.Tag.Get("arg")
if tag == "-" || !isExported(field.Name) {
if tag == "-" {
return false
}
// If this is an embedded struct then recurse into its fields
// if this is an embedded struct then recurse into its fields, even if
// it is unexported, because exported fields on unexported embedded
// structs are still writable
if field.Anonymous && field.Type.Kind() == reflect.Struct {
return true
}
// ignore any other unexported field
if !isExported(field.Name) {
return false
}
// duplicate the entire path to avoid slice overwrites
subdest := dest.Child(field)
spec := spec{

View File

@ -5,6 +5,7 @@ import (
"fmt"
"net"
"net/mail"
"net/url"
"os"
"strings"
"testing"
@ -961,6 +962,24 @@ func TestPtrToIP(t *testing.T) {
assert.Equal(t, "192.168.0.1", args.Host.String())
}
func TestURL(t *testing.T) {
var args struct {
URL url.URL
}
err := parse("--url https://example.com/get?item=xyz", &args)
require.NoError(t, err)
assert.Equal(t, "https://example.com/get?item=xyz", args.URL.String())
}
func TestPtrToURL(t *testing.T) {
var args struct {
URL *url.URL
}
err := parse("--url http://example.com/#xyz", &args)
require.NoError(t, err)
assert.Equal(t, "http://example.com/#xyz", args.URL.String())
}
func TestIPSlice(t *testing.T) {
var args struct {
Host []net.IP
@ -1095,6 +1114,29 @@ func TestEmbeddedWithDuplicateField2(t *testing.T) {
assert.Equal(t, "", args.U.A)
}
func TestUnexportedEmbedded(t *testing.T) {
type embeddedArgs struct {
Foo string
}
var args struct {
embeddedArgs
}
err := parse("--foo bar", &args)
require.NoError(t, err)
assert.Equal(t, "bar", args.Foo)
}
func TestIgnoredEmbedded(t *testing.T) {
type embeddedArgs struct {
Foo string
}
var args struct {
embeddedArgs `arg:"-"`
}
err := parse("--foo bar", &args)
require.Error(t, err)
}
func TestEmptyArgs(t *testing.T) {
origArgs := os.Args