-
Notifications
You must be signed in to change notification settings - Fork 38
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add support for urfave/cli.v3 * Update to latest cli v3
- Loading branch information
Showing
5 changed files
with
1,346 additions
and
1,112 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
package gcli | ||
|
||
import ( | ||
"github.com/octago/sflags" | ||
"github.com/urfave/cli/v3" | ||
) | ||
|
||
type boolFlag interface { | ||
IsBoolFlag() bool | ||
} | ||
|
||
type value struct { | ||
v sflags.Value | ||
} | ||
|
||
func (v value) Get() any { | ||
return v.v | ||
} | ||
|
||
func (v value) Set(s string) error { | ||
return v.v.Set(s) | ||
} | ||
|
||
func (v value) String() string { | ||
return v.v.String() | ||
} | ||
|
||
func (v value) IsBoolFlag() bool { | ||
b, ok := v.v.(boolFlag) | ||
return ok && b.IsBoolFlag() | ||
} | ||
|
||
// GenerateToV3 takes a list of sflag.Flag, | ||
// that are parsed from some config structure, and put it to dst. | ||
func GenerateToV3(src []*sflags.Flag, dst *[]cli.Flag) { | ||
for _, srcFlag := range src { | ||
name := srcFlag.Name | ||
var aliases []string | ||
if srcFlag.Short != "" { | ||
aliases = append(aliases, srcFlag.Short) | ||
} | ||
*dst = append(*dst, &cli.GenericFlag{ | ||
Name: name, | ||
Sources: cli.EnvVars(srcFlag.EnvName), | ||
Aliases: aliases, | ||
Hidden: srcFlag.Hidden, | ||
Usage: srcFlag.Usage, | ||
Value: &value{ | ||
v: srcFlag.Value, | ||
}, | ||
}) | ||
} | ||
} | ||
|
||
// ParseToV3 parses cfg, that is a pointer to some structure, | ||
// and puts it to dst. | ||
func ParseToV3(cfg interface{}, dst *[]cli.Flag, optFuncs ...sflags.OptFunc) error { | ||
flags, err := sflags.ParseStruct(cfg, optFuncs...) | ||
if err != nil { | ||
return err | ||
} | ||
GenerateToV3(flags, dst) | ||
return nil | ||
} | ||
|
||
// ParseV3 parses cfg, that is a pointer to some structure, | ||
// puts it to the new flag.FlagSet and returns it. | ||
func ParseV3(cfg interface{}, optFuncs ...sflags.OptFunc) ([]cli.Flag, error) { | ||
flags := make([]cli.Flag, 0) | ||
err := ParseToV3(cfg, &flags, optFuncs...) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return flags, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
package gcli | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"io" | ||
"testing" | ||
|
||
"github.com/octago/sflags" | ||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
"github.com/urfave/cli/v3" | ||
) | ||
|
||
type cfg2 struct { | ||
StringValue1 string | ||
StringValue2 string `flag:"string-value-two s"` | ||
|
||
CounterValue1 sflags.Counter | ||
|
||
StringSliceValue1 []string | ||
} | ||
|
||
func TestParseV3(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
|
||
cfg interface{} | ||
args []string | ||
expCfg interface{} | ||
expErr1 error // sflag Parse error | ||
expErr2 error // cli Parse error | ||
}{ | ||
{ | ||
name: "Test cfg2", | ||
cfg: &cfg2{ | ||
StringValue1: "string_value1_value", | ||
StringValue2: "string_value2_value", | ||
|
||
CounterValue1: 1, | ||
|
||
StringSliceValue1: []string{"one", "two"}, | ||
}, | ||
expCfg: &cfg2{ | ||
StringValue1: "string_value1_value2", | ||
StringValue2: "string_value2_value2", | ||
|
||
CounterValue1: 3, | ||
|
||
StringSliceValue1: []string{ | ||
"one2", "two2", "three", "4"}, | ||
}, | ||
args: []string{ | ||
"--string-value1", "string_value1_value2", | ||
"--string-value-two", "string_value2_value2", | ||
"--counter-value1", "--counter-value1", | ||
"--string-slice-value1", "one2", | ||
"--string-slice-value1", "two2", | ||
"--string-slice-value1", "three,4", | ||
}, | ||
}, | ||
{ | ||
name: "Test cfg2 no args", | ||
cfg: &cfg2{ | ||
StringValue1: "string_value1_value", | ||
StringValue2: "", | ||
}, | ||
expCfg: &cfg2{ | ||
StringValue1: "string_value1_value", | ||
StringValue2: "", | ||
}, | ||
args: []string{}, | ||
}, | ||
{ | ||
name: "Test cfg2 short option", | ||
cfg: &cfg2{ | ||
StringValue2: "string_value2_value", | ||
}, | ||
expCfg: &cfg2{ | ||
StringValue2: "string_value2_value2", | ||
}, | ||
args: []string{ | ||
"-s=string_value2_value2", | ||
}, | ||
}, | ||
{ | ||
name: "Test cfg2 without default values", | ||
cfg: &cfg2{}, | ||
expCfg: &cfg2{ | ||
StringValue1: "string_value1_value2", | ||
StringValue2: "string_value2_value2", | ||
|
||
CounterValue1: 3, | ||
}, | ||
args: []string{ | ||
"--string-value1", "string_value1_value2", | ||
"--string-value-two", "string_value2_value2", | ||
"--counter-value1=2", "--counter-value1", | ||
}, | ||
}, | ||
{ | ||
name: "Test cfg2 bad option", | ||
cfg: &cfg2{ | ||
StringValue1: "string_value1_value", | ||
}, | ||
args: []string{ | ||
"--bad-value=string_value1_value2", | ||
}, | ||
expErr2: errors.New("flag provided but not defined: -bad-value"), | ||
}, | ||
{ | ||
name: "Test bad cfg value", | ||
cfg: "bad config", | ||
expErr1: errors.New("object must be a pointer to struct or interface"), | ||
}, | ||
} | ||
// forbid urfave/cli to exit | ||
cli.OsExiter = func(i int) {} | ||
for _, test := range tests { | ||
t.Run(test.name, func(t *testing.T) { | ||
flags, err := ParseV3(test.cfg) | ||
if test.expErr1 != nil { | ||
require.Error(t, err) | ||
require.Equal(t, test.expErr1, err) | ||
} else { | ||
require.NoError(t, err) | ||
} | ||
if err != nil { | ||
return | ||
} | ||
cmd := &cli.Command{} | ||
cmd.Action = func(_ context.Context, c *cli.Command) error { | ||
return nil | ||
} | ||
cmd.UseShortOptionHandling = true | ||
cli.ErrWriter = io.Discard | ||
cmd.OnUsageError = func(ctx context.Context, cmd *cli.Command, err error, isSubcommand bool) error { | ||
return err | ||
} | ||
|
||
cmd.Flags = flags | ||
args := append([]string{"cliApp"}, test.args...) | ||
err = cmd.Run(context.Background(), args) | ||
if test.expErr2 != nil { | ||
require.Error(t, err) | ||
require.Equal(t, test.expErr2, err) | ||
} else { | ||
require.NoError(t, err) | ||
} | ||
if err != nil { | ||
return | ||
} | ||
assert.Equal(t, test.expCfg, test.cfg) | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.