Skip to content

Commit d62daa3

Browse files
authored
Add support for urfave/cli.v3 (#7)
* Add support for urfave/cli.v3 * Update to latest cli v3
1 parent ad41f69 commit d62daa3

File tree

5 files changed

+1346
-1112
lines changed

5 files changed

+1346
-1112
lines changed

gen/gcli/gcliv3.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package gcli
2+
3+
import (
4+
"github.com/octago/sflags"
5+
"github.com/urfave/cli/v3"
6+
)
7+
8+
type boolFlag interface {
9+
IsBoolFlag() bool
10+
}
11+
12+
type value struct {
13+
v sflags.Value
14+
}
15+
16+
func (v value) Get() any {
17+
return v.v
18+
}
19+
20+
func (v value) Set(s string) error {
21+
return v.v.Set(s)
22+
}
23+
24+
func (v value) String() string {
25+
return v.v.String()
26+
}
27+
28+
func (v value) IsBoolFlag() bool {
29+
b, ok := v.v.(boolFlag)
30+
return ok && b.IsBoolFlag()
31+
}
32+
33+
// GenerateToV3 takes a list of sflag.Flag,
34+
// that are parsed from some config structure, and put it to dst.
35+
func GenerateToV3(src []*sflags.Flag, dst *[]cli.Flag) {
36+
for _, srcFlag := range src {
37+
name := srcFlag.Name
38+
var aliases []string
39+
if srcFlag.Short != "" {
40+
aliases = append(aliases, srcFlag.Short)
41+
}
42+
*dst = append(*dst, &cli.GenericFlag{
43+
Name: name,
44+
Sources: cli.EnvVars(srcFlag.EnvName),
45+
Aliases: aliases,
46+
Hidden: srcFlag.Hidden,
47+
Usage: srcFlag.Usage,
48+
Value: &value{
49+
v: srcFlag.Value,
50+
},
51+
})
52+
}
53+
}
54+
55+
// ParseToV3 parses cfg, that is a pointer to some structure,
56+
// and puts it to dst.
57+
func ParseToV3(cfg interface{}, dst *[]cli.Flag, optFuncs ...sflags.OptFunc) error {
58+
flags, err := sflags.ParseStruct(cfg, optFuncs...)
59+
if err != nil {
60+
return err
61+
}
62+
GenerateToV3(flags, dst)
63+
return nil
64+
}
65+
66+
// ParseV3 parses cfg, that is a pointer to some structure,
67+
// puts it to the new flag.FlagSet and returns it.
68+
func ParseV3(cfg interface{}, optFuncs ...sflags.OptFunc) ([]cli.Flag, error) {
69+
flags := make([]cli.Flag, 0)
70+
err := ParseToV3(cfg, &flags, optFuncs...)
71+
if err != nil {
72+
return nil, err
73+
}
74+
return flags, nil
75+
}

gen/gcli/gcliv3_test.go

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
package gcli
2+
3+
import (
4+
"context"
5+
"errors"
6+
"io"
7+
"testing"
8+
9+
"github.com/octago/sflags"
10+
"github.com/stretchr/testify/assert"
11+
"github.com/stretchr/testify/require"
12+
"github.com/urfave/cli/v3"
13+
)
14+
15+
type cfg2 struct {
16+
StringValue1 string
17+
StringValue2 string `flag:"string-value-two s"`
18+
19+
CounterValue1 sflags.Counter
20+
21+
StringSliceValue1 []string
22+
}
23+
24+
func TestParseV3(t *testing.T) {
25+
tests := []struct {
26+
name string
27+
28+
cfg interface{}
29+
args []string
30+
expCfg interface{}
31+
expErr1 error // sflag Parse error
32+
expErr2 error // cli Parse error
33+
}{
34+
{
35+
name: "Test cfg2",
36+
cfg: &cfg2{
37+
StringValue1: "string_value1_value",
38+
StringValue2: "string_value2_value",
39+
40+
CounterValue1: 1,
41+
42+
StringSliceValue1: []string{"one", "two"},
43+
},
44+
expCfg: &cfg2{
45+
StringValue1: "string_value1_value2",
46+
StringValue2: "string_value2_value2",
47+
48+
CounterValue1: 3,
49+
50+
StringSliceValue1: []string{
51+
"one2", "two2", "three", "4"},
52+
},
53+
args: []string{
54+
"--string-value1", "string_value1_value2",
55+
"--string-value-two", "string_value2_value2",
56+
"--counter-value1", "--counter-value1",
57+
"--string-slice-value1", "one2",
58+
"--string-slice-value1", "two2",
59+
"--string-slice-value1", "three,4",
60+
},
61+
},
62+
{
63+
name: "Test cfg2 no args",
64+
cfg: &cfg2{
65+
StringValue1: "string_value1_value",
66+
StringValue2: "",
67+
},
68+
expCfg: &cfg2{
69+
StringValue1: "string_value1_value",
70+
StringValue2: "",
71+
},
72+
args: []string{},
73+
},
74+
{
75+
name: "Test cfg2 short option",
76+
cfg: &cfg2{
77+
StringValue2: "string_value2_value",
78+
},
79+
expCfg: &cfg2{
80+
StringValue2: "string_value2_value2",
81+
},
82+
args: []string{
83+
"-s=string_value2_value2",
84+
},
85+
},
86+
{
87+
name: "Test cfg2 without default values",
88+
cfg: &cfg2{},
89+
expCfg: &cfg2{
90+
StringValue1: "string_value1_value2",
91+
StringValue2: "string_value2_value2",
92+
93+
CounterValue1: 3,
94+
},
95+
args: []string{
96+
"--string-value1", "string_value1_value2",
97+
"--string-value-two", "string_value2_value2",
98+
"--counter-value1=2", "--counter-value1",
99+
},
100+
},
101+
{
102+
name: "Test cfg2 bad option",
103+
cfg: &cfg2{
104+
StringValue1: "string_value1_value",
105+
},
106+
args: []string{
107+
"--bad-value=string_value1_value2",
108+
},
109+
expErr2: errors.New("flag provided but not defined: -bad-value"),
110+
},
111+
{
112+
name: "Test bad cfg value",
113+
cfg: "bad config",
114+
expErr1: errors.New("object must be a pointer to struct or interface"),
115+
},
116+
}
117+
// forbid urfave/cli to exit
118+
cli.OsExiter = func(i int) {}
119+
for _, test := range tests {
120+
t.Run(test.name, func(t *testing.T) {
121+
flags, err := ParseV3(test.cfg)
122+
if test.expErr1 != nil {
123+
require.Error(t, err)
124+
require.Equal(t, test.expErr1, err)
125+
} else {
126+
require.NoError(t, err)
127+
}
128+
if err != nil {
129+
return
130+
}
131+
cmd := &cli.Command{}
132+
cmd.Action = func(_ context.Context, c *cli.Command) error {
133+
return nil
134+
}
135+
cmd.UseShortOptionHandling = true
136+
cli.ErrWriter = io.Discard
137+
cmd.OnUsageError = func(ctx context.Context, cmd *cli.Command, err error, isSubcommand bool) error {
138+
return err
139+
}
140+
141+
cmd.Flags = flags
142+
args := append([]string{"cliApp"}, test.args...)
143+
err = cmd.Run(context.Background(), args)
144+
if test.expErr2 != nil {
145+
require.Error(t, err)
146+
require.Equal(t, test.expErr2, err)
147+
} else {
148+
require.NoError(t, err)
149+
}
150+
if err != nil {
151+
return
152+
}
153+
assert.Equal(t, test.expCfg, test.cfg)
154+
})
155+
}
156+
}

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ require (
1010
github.com/spf13/pflag v1.0.3
1111
github.com/stretchr/testify v1.9.0
1212
github.com/urfave/cli/v2 v2.27.5
13+
github.com/urfave/cli/v3 v3.0.0-alpha9.3
1314
golang.org/x/text v0.19.0
1415
)
1516

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
2525
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
2626
github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w=
2727
github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
28+
github.com/urfave/cli/v3 v3.0.0-alpha9.3 h1:RfQlgUHMRxDMwEEmGsrHd+mXYJpWpXlcJM8w86cpjGs=
29+
github.com/urfave/cli/v3 v3.0.0-alpha9.3/go.mod h1:FnIeEMYu+ko8zP1F9Ypr3xkZMIDqW3DR92yUtY39q1Y=
2830
github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc=
2931
github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=
3032
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=

0 commit comments

Comments
 (0)