Skip to content

Commit 230739e

Browse files
Support *url.URL in flags as well as arguments (#201)
1 parent 33c6f35 commit 230739e

6 files changed

Lines changed: 59 additions & 6 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ The types you can use for flags currently are:
234234
- `time.Time`
235235
- `time.Duration`
236236
- `net.IP`
237+
- `*url.URL`
237238
- `[]int`
238239
- `[]int8`
239240
- `[]int16`

flag/flag.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package flag
33

44
import (
55
"net"
6+
"net/url"
67
"time"
78
)
89

@@ -46,6 +47,7 @@ type Flaggable interface {
4647
time.Time |
4748
time.Duration |
4849
net.IP |
50+
*url.URL |
4951
[]int |
5052
[]int8 |
5153
[]int16 |

internal/arg/arg.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ func (a Arg[T]) Default() string {
120120
//nolint:cyclop // No other way of doing this realistically
121121
func (a Arg[T]) String() string {
122122
if a.value == nil {
123-
return "<nil>"
123+
return format.Nil
124124
}
125125

126126
switch typ := any(*a.value).(type) {
@@ -174,7 +174,7 @@ func (a Arg[T]) String() string {
174174
//nolint:cyclop // No other way of doing this realistically
175175
func (a Arg[T]) Type() string {
176176
if a.value == nil {
177-
return "<nil>"
177+
return format.Nil
178178
}
179179

180180
switch typ := any(*a.value).(type) {

internal/flag/flag.go

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"errors"
99
"fmt"
1010
"net"
11+
"net/url"
1112
"strconv"
1213
"strings"
1314
"time"
@@ -136,7 +137,7 @@ func (f Flag[T]) NoArgValue() string {
136137
//nolint:cyclop // No other way of doing this realistically
137138
func (f Flag[T]) String() string {
138139
if f.value == nil {
139-
return "<nil>"
140+
return format.Nil
140141
}
141142

142143
switch typ := any(*f.value).(type) {
@@ -179,6 +180,12 @@ func (f Flag[T]) String() string {
179180
case time.Duration:
180181
return typ.String()
181182
case net.IP:
183+
return typ.String()
184+
case *url.URL:
185+
if typ == nil {
186+
return format.Nil
187+
}
188+
182189
return typ.String()
183190
case []int:
184191
return format.Slice(typ)
@@ -212,7 +219,7 @@ func (f Flag[T]) String() string {
212219
// Type returns a string representation of the type of the Flag.
213220
func (f Flag[T]) Type() string { //nolint:cyclop // No other way of doing this realistically
214221
if f.value == nil {
215-
return "<nil>"
222+
return format.Nil
216223
}
217224

218225
switch typ := any(*f.value).(type) {
@@ -256,6 +263,8 @@ func (f Flag[T]) Type() string { //nolint:cyclop // No other way of doing this r
256263
return format.TypeDuration
257264
case net.IP:
258265
return format.TypeIP
266+
case *url.URL:
267+
return format.TypeURL
259268
case []int:
260269
return format.TypeIntSlice
261270
case []int8:
@@ -480,6 +489,15 @@ func (f Flag[T]) Set(str string) error {
480489

481490
*f.value = *parse.Cast[T](&val)
482491

492+
return nil
493+
case *url.URL:
494+
val, err := url.ParseRequestURI(str)
495+
if err != nil {
496+
return parse.Error(parse.KindFlag, f.name, str, typ, err)
497+
}
498+
499+
*f.value = *parse.Cast[T](&val)
500+
483501
return nil
484502
case []int:
485503
// Like Count, a slice flag is a read/write op
@@ -759,6 +777,8 @@ func isZeroIsh[T flag.Flaggable](value T) bool { //nolint:cyclop // Not much els
759777
return len(typ) == 0
760778
case net.IP:
761779
return len(typ) == 0
780+
case *url.URL:
781+
return typ == nil
762782
case []int:
763783
return len(typ) == 0
764784
case []int8:

internal/flag/flag_test.go

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bytes"
55
"errors"
66
"net"
7+
"net/url"
78
"slices"
89
"testing"
910
"time"
@@ -914,6 +915,32 @@ func TestFlaggableTypes(t *testing.T) {
914915
test.Equal(t, sliceFlag.Type(), "[]string")
915916
test.Equal(t, sliceFlag.String(), `["a string", "another string"]`)
916917
})
918+
919+
t.Run("url valid", func(t *testing.T) {
920+
var u *url.URL
921+
922+
urlFlag, err := flag.New(&u, "url", 'u', "Set a URL", flag.Config[*url.URL]{})
923+
test.Ok(t, err)
924+
925+
err = urlFlag.Set("https://example.com/path")
926+
test.Ok(t, err)
927+
test.Equal(t, u.Scheme, "https")
928+
test.Equal(t, u.Host, "example.com")
929+
test.Equal(t, u.Path, "/path")
930+
test.Equal(t, urlFlag.Type(), "url")
931+
test.Equal(t, urlFlag.String(), "https://example.com/path")
932+
})
933+
934+
t.Run("url invalid", func(t *testing.T) {
935+
var u *url.URL
936+
937+
urlFlag, err := flag.New(&u, "url", 'u', "Set a URL", flag.Config[*url.URL]{})
938+
test.Ok(t, err)
939+
940+
err = urlFlag.Set("not a url")
941+
test.Err(t, err)
942+
test.True(t, errors.Is(err, parse.Err))
943+
})
917944
}
918945

919946
func TestFlagValidation(t *testing.T) {
@@ -1045,8 +1072,8 @@ func TestFlagNilSafety(t *testing.T) {
10451072
t.Run("composite literal", func(t *testing.T) {
10461073
// Users doing naughty things, should still be nil safe
10471074
flag := flag.Flag[bool]{}
1048-
test.Equal(t, flag.String(), "<nil>")
1049-
test.Equal(t, flag.Type(), "<nil>")
1075+
test.Equal(t, flag.String(), format.Nil)
1076+
test.Equal(t, flag.Type(), format.Nil)
10501077

10511078
err := flag.Set(format.True)
10521079
test.Err(t, err)

internal/format/format.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ const (
6868
// default values and when flags have a NoArgValue.
6969
const True = "true"
7070

71+
// Nil is the string representation of a Go nil value.
72+
const Nil = "<nil>"
73+
7174
// Int returns a string representation of an integer.
7275
func Int[T constraints.Signed](n T) string {
7376
return strconv.FormatInt(int64(n), base10)

0 commit comments

Comments
 (0)