diff --git a/README.md b/README.md index 916c5f1..6a445d4 100644 --- a/README.md +++ b/README.md @@ -281,6 +281,7 @@ The types you can currently use for positional args are: - `float32` - `float64` - `string` +- `*url.URL` - `bool` - `[]byte` (interpreted as a hex string) - `time.Time` diff --git a/arg/arg.go b/arg/arg.go index 5eecee4..12f9334 100644 --- a/arg/arg.go +++ b/arg/arg.go @@ -3,6 +3,7 @@ package arg import ( "net" + "net/url" "time" ) @@ -24,6 +25,7 @@ type Argable interface { float32 | float64 | string | + *url.URL | bool | []byte | time.Time | diff --git a/docs/img/namedargs.gif b/docs/img/namedargs.gif index 90779f9..a5d5ae8 100644 Binary files a/docs/img/namedargs.gif and b/docs/img/namedargs.gif differ diff --git a/docs/img/quickstart.gif b/docs/img/quickstart.gif index 3c28898..98b9732 100644 Binary files a/docs/img/quickstart.gif and b/docs/img/quickstart.gif differ diff --git a/docs/img/subcommands.gif b/docs/img/subcommands.gif index 9388947..e70d455 100644 Binary files a/docs/img/subcommands.gif and b/docs/img/subcommands.gif differ diff --git a/examples/subcommands/cli.go b/examples/subcommands/cli.go index 452b390..a5ad251 100644 --- a/examples/subcommands/cli.go +++ b/examples/subcommands/cli.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "net/url" "strings" "time" @@ -76,7 +77,7 @@ type doOptions struct { func buildDoCommand() (*cli.Command, error) { var options doOptions - var thing string + var thing *url.URL return cli.New( "do", diff --git a/internal/arg/arg.go b/internal/arg/arg.go index 393bbb8..b8782f4 100644 --- a/internal/arg/arg.go +++ b/internal/arg/arg.go @@ -8,6 +8,7 @@ import ( "errors" "fmt" "net" + "net/url" "strconv" "strings" "time" @@ -97,6 +98,8 @@ func (a Arg[T]) Default() string { return format.Float64(typ) case string: return typ + case *url.URL: + return typ.String() case bool: return strconv.FormatBool(typ) case []byte: @@ -149,6 +152,8 @@ func (a Arg[T]) String() string { return format.Float64(typ) case string: return typ + case *url.URL: + return typ.String() case bool: return strconv.FormatBool(typ) case []byte: @@ -201,6 +206,8 @@ func (a Arg[T]) Type() string { return format.TypeFloat64 case string: return format.TypeString + case *url.URL: + return format.TypeURL case bool: return format.TypeBool case []byte: @@ -346,6 +353,15 @@ func (a Arg[T]) Set(str string) error { val := str *a.value = *parse.Cast[T](&val) + return nil + case *url.URL: + val, err := url.ParseRequestURI(str) + if err != nil { + return parse.Error(parse.KindArgument, a.name, str, typ, err) + } + + *a.value = *parse.Cast[T](&val) + return nil case bool: val, err := strconv.ParseBool(str) diff --git a/internal/arg/arg_test.go b/internal/arg/arg_test.go index d87e32c..657bc9f 100644 --- a/internal/arg/arg_test.go +++ b/internal/arg/arg_test.go @@ -4,6 +4,7 @@ import ( "bytes" "errors" "net" + "net/url" "testing" "time" @@ -384,6 +385,39 @@ func TestArgableTypes(t *testing.T) { test.Equal(t, strArg.String(), "newvalue") }) + t.Run("url valid", func(t *testing.T) { + var u *url.URL + + urlArg, err := arg.New(&u, "url", "Set a url value", arg.Config[*url.URL]{}) + test.Ok(t, err) + + rawURL := "https://github.com/FollowTheProcess/cli" + + want, err := url.ParseRequestURI(rawURL) + test.Ok(t, err) + + err = urlArg.Set(rawURL) + test.Ok(t, err) + test.Equal(t, u.Scheme, want.Scheme) + test.Equal(t, u.Host, want.Host) + test.Equal(t, u.Path, want.Path) + + test.Equal(t, urlArg.Type(), format.TypeURL) + test.Equal(t, urlArg.Usage(), "Set a url value") + test.Equal(t, urlArg.String(), rawURL) + }) + + t.Run("url invalid", func(t *testing.T) { + var u *url.URL + + urlArg, err := arg.New(&u, "url", "Set a url value", arg.Config[*url.URL]{}) + test.Ok(t, err) + + err = urlArg.Set("word") + test.Err(t, err) + test.True(t, errors.Is(err, parse.Err)) + }) + t.Run("byte slice valid", func(t *testing.T) { var byt []byte diff --git a/internal/flag/flag.go b/internal/flag/flag.go index 2b60962..08c571c 100644 --- a/internal/flag/flag.go +++ b/internal/flag/flag.go @@ -251,8 +251,6 @@ func (f Flag[T]) Type() string { //nolint:cyclop // No other way of doing this r return format.TypeFloat32Slice case []float64: return format.TypeFloat64Slice - case []string: - return format.TypeStringSlice default: return fmt.Sprintf("%T", typ) } diff --git a/internal/format/format.go b/internal/format/format.go index 6343a95..014e3f4 100644 --- a/internal/format/format.go +++ b/internal/format/format.go @@ -41,6 +41,7 @@ const ( TypeFloat32 = "float32" TypeFloat64 = "float64" TypeString = "string" + TypeURL = "url" TypeBool = "bool" TypeBytesHex = "bytesHex" TypeTime = "time" @@ -58,6 +59,7 @@ const ( TypeFloat32Slice = slice + TypeFloat32 TypeFloat64Slice = slice + TypeFloat64 TypeStringSlice = slice + TypeString + TypeURLSlice = slice + TypeURL ) // True is the literal boolean true as a string.