Skip to content

Commit fbcbd0f

Browse files
committed
feat: add TextFlag
* Added `TextFlag` which supports setting values for types that satisfies the `encoding.TextMarshaller` and `encoding.TextUnmarshaller` which is very handy when you want to set log levels or string-like types that satifies the interfaces. Fixes: #2051 Signed-off-by: Tobias Dahlberg <lokskada@live.se>
1 parent 706f78e commit fbcbd0f

File tree

2 files changed

+163
-0
lines changed

2 files changed

+163
-0
lines changed

flag_text.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package cli
2+
3+
import (
4+
"encoding"
5+
"strings"
6+
)
7+
8+
type TextMarshalUnMarshaller interface {
9+
encoding.TextMarshaler
10+
encoding.TextUnmarshaler
11+
}
12+
13+
// TextFlag enables you to set types that satisfies [TextMarshalUnMarshaller] using flags such as log levels.
14+
type TextFlag = FlagBase[TextMarshalUnMarshaller, StringConfig, TextValue]
15+
16+
type TextValue struct {
17+
Value TextMarshalUnMarshaller
18+
Config StringConfig
19+
}
20+
21+
func (v TextValue) String() string {
22+
text, err := v.Value.MarshalText()
23+
if err != nil {
24+
return ""
25+
}
26+
27+
return string(text)
28+
}
29+
30+
func (v TextValue) Set(s string) error {
31+
if v.Config.TrimSpace {
32+
return v.Value.UnmarshalText([]byte(strings.TrimSpace(s)))
33+
}
34+
35+
return v.Value.UnmarshalText([]byte(s))
36+
}
37+
38+
func (v TextValue) Get() any {
39+
return v.Value
40+
}
41+
42+
func (v TextValue) Create(t TextMarshalUnMarshaller, _ *TextMarshalUnMarshaller, c StringConfig) Value {
43+
return &TextValue{
44+
Value: t,
45+
Config: c,
46+
}
47+
}
48+
49+
func (v TextValue) ToString(t TextMarshalUnMarshaller) string {
50+
text, err := t.MarshalText()
51+
if err != nil {
52+
return ""
53+
}
54+
55+
return string(text)
56+
}

flag_text_test.go

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package cli
2+
3+
import (
4+
"errors"
5+
"flag"
6+
"io"
7+
"log/slog"
8+
"slices"
9+
"testing"
10+
)
11+
12+
func TestTextFlag(t *testing.T) {
13+
tests := []struct {
14+
name string
15+
flag TextFlag
16+
args []string
17+
want string
18+
wantErr bool
19+
}{
20+
{
21+
name: "empty",
22+
flag: TextFlag{
23+
Name: "log-level",
24+
Value: &slog.LevelVar{},
25+
},
26+
want: "INFO",
27+
},
28+
{
29+
name: "info",
30+
flag: TextFlag{
31+
Name: "log-level",
32+
Value: &slog.LevelVar{},
33+
Validator: func(v TextMarshalUnMarshaller) error {
34+
text, err := v.MarshalText()
35+
if err != nil {
36+
return err
37+
}
38+
39+
if slices.Compare(text, []byte("INFO")) != 0 {
40+
return errors.New("expected empty string")
41+
}
42+
43+
return nil
44+
},
45+
},
46+
args: []string{"--log-level", "info"},
47+
want: "INFO",
48+
},
49+
{
50+
name: "debug",
51+
flag: TextFlag{
52+
Name: "log-level",
53+
Value: &slog.LevelVar{},
54+
},
55+
args: []string{"--log-level", "debug"},
56+
want: "DEBUG",
57+
},
58+
{
59+
name: "debug_with_trim",
60+
flag: TextFlag{
61+
Name: "log-level",
62+
Value: &slog.LevelVar{},
63+
Config: StringConfig{TrimSpace: true},
64+
},
65+
args: []string{"--log-level", " debug "},
66+
want: "DEBUG",
67+
},
68+
{
69+
name: "invalid",
70+
flag: TextFlag{
71+
Name: "log-level",
72+
Value: &slog.LevelVar{},
73+
},
74+
args: []string{"--log-level", "invalid"},
75+
wantErr: true,
76+
},
77+
}
78+
79+
t.Parallel()
80+
81+
for _, tt := range tests {
82+
t.Run(tt.name, func(t *testing.T) {
83+
set := flag.NewFlagSet(tt.name, flag.ContinueOnError)
84+
if tt.wantErr {
85+
set.SetOutput(io.Discard)
86+
}
87+
88+
if err := tt.flag.Apply(set); err != nil {
89+
t.Fatalf("Apply(%v) failed: %v", tt.args, err)
90+
}
91+
92+
err := set.Parse(tt.args)
93+
if (err != nil) != tt.wantErr {
94+
t.Errorf("Parse() error = %v, wantErr %v", err, tt.wantErr)
95+
96+
return
97+
} else if (err != nil) == tt.wantErr {
98+
// Expected error.
99+
return
100+
}
101+
102+
if got := tt.flag.GetValue(); got != tt.want {
103+
t.Errorf("Value = %v, want %v", got, tt.want)
104+
}
105+
})
106+
}
107+
}

0 commit comments

Comments
 (0)