Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add custom flag parser for extensions (backport #4269) #4271

Merged
merged 3 commits into from
Jul 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
### Changes

- [#4262](https://github.com/ignite/cli/pull/4262) Bring back relayer command
- [#4269](https://github.com/ignite/cli/pull/4269) Add custom flag parser for extensions

## [`v28.5.0`](https://github.com/ignite/cli/releases/tag/v28.5.0)

Expand Down
18 changes: 10 additions & 8 deletions ignite/cmd/plugin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ func buildRootCmd(ctx context.Context) *cobra.Command {
return rootCmd
}

func assertFlags(t *testing.T, expectedFlags []*plugin.Flag, execCmd *plugin.ExecutedCommand) {
func assertFlags(t *testing.T, expectedFlags plugin.Flags, execCmd *plugin.ExecutedCommand) {
t.Helper()
var (
have []string
expected []string
Expand Down Expand Up @@ -78,7 +79,7 @@ func TestLinkPluginCmds(t *testing.T) {
// define a plugin with command flags
pluginWithFlags = &plugin.Command{
Use: "flaggy",
Flags: []*plugin.Flag{
Flags: plugin.Flags{
{Name: "flag1", Type: plugin.FlagTypeString},
{Name: "flag2", Type: plugin.FlagTypeInt, DefaultValue: "0"},
},
Expand Down Expand Up @@ -412,7 +413,8 @@ func TestLinkPluginHooks(t *testing.T) {

// helper to assert pluginInterface.ExecuteHook*() calls in expected order
// (pre, then post, then cleanup)
expectExecuteHook = func(t *testing.T, p *mocks.PluginInterface, expectedFlags []*plugin.Flag, hooks ...*plugin.Hook) {
expectExecuteHook = func(t *testing.T, p *mocks.PluginInterface, expectedFlags plugin.Flags, hooks ...*plugin.Hook) {
t.Helper()
matcher := func(hook *plugin.Hook) any {
return mock.MatchedBy(func(execHook *plugin.ExecutedHook) bool {
return hook.Name == execHook.Hook.Name &&
Expand Down Expand Up @@ -506,7 +508,7 @@ func TestLinkPluginHooks(t *testing.T) {
p.EXPECT().
Manifest(ctx).
Return(&plugin.Manifest{Hooks: []*plugin.Hook{hook}}, nil)
expectExecuteHook(t, p, []*plugin.Flag{{Name: "path"}}, hook)
expectExecuteHook(t, p, plugin.Flags{{Name: "path"}}, hook)
},
},
{
Expand All @@ -523,7 +525,7 @@ func TestLinkPluginHooks(t *testing.T) {
p.EXPECT().
Manifest(ctx).
Return(&plugin.Manifest{Hooks: []*plugin.Hook{hook1, hook2}}, nil)
expectExecuteHook(t, p, []*plugin.Flag{{Name: "path"}}, hook1, hook2)
expectExecuteHook(t, p, plugin.Flags{{Name: "path"}}, hook1, hook2)
},
},
{
Expand All @@ -544,7 +546,7 @@ func TestLinkPluginHooks(t *testing.T) {
p.EXPECT().
Manifest(ctx).
Return(&plugin.Manifest{Hooks: []*plugin.Hook{hookChain1, hookChain2, hookModule}}, nil)
expectExecuteHook(t, p, []*plugin.Flag{{Name: "path"}}, hookChain1, hookChain2)
expectExecuteHook(t, p, plugin.Flags{{Name: "path"}}, hookChain1, hookChain2)
expectExecuteHook(t, p, nil, hookModule)
},
},
Expand All @@ -564,7 +566,7 @@ func TestLinkPluginHooks(t *testing.T) {
p.EXPECT().
Manifest(ctx).
Return(&plugin.Manifest{Hooks: hooks}, nil)
expectExecuteHook(t, p, []*plugin.Flag{{Name: "path"}}, hooks...)
expectExecuteHook(t, p, plugin.Flags{{Name: "path"}}, hooks...)
},
},
{
Expand All @@ -581,7 +583,7 @@ func TestLinkPluginHooks(t *testing.T) {
p.EXPECT().
Manifest(ctx).
Return(&plugin.Manifest{Hooks: []*plugin.Hook{hookChain, hookModule}}, nil)
expectExecuteHook(t, p, []*plugin.Flag{{Name: "path"}}, hookChain)
expectExecuteHook(t, p, plugin.Flags{{Name: "path"}}, hookChain)
expectExecuteHook(t, p, nil, hookModule)
},
},
Expand Down
162 changes: 162 additions & 0 deletions ignite/services/plugin/flag.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
package plugin

import (
"strconv"
"strings"

"github.com/ignite/cli/v28/ignite/pkg/errors"
)

var (
// ErrFlagNotFound error key flag not found.
ErrFlagNotFound = errors.New("flag not found")
// ErrInvalidFlagType error invalid flag type.
ErrInvalidFlagType = errors.New("invalid flag type")
// ErrFlagAssertion error flag type assertion failed.
ErrFlagAssertion = errors.New("flag type assertion failed")
)

// Flags represents a slice of Flag pointers.
type Flags []*Flag

// getValue returns the value of the flag with the specified key and type.
// It uses the provided conversion function to convert the string value to the desired type.
func (f Flags) getValue(key string, flagType FlagType, convFunc func(v string) (interface{}, error)) (interface{}, error) {
for _, flag := range f {
if flag.Name == key {
if flag.Type != flagType {
return nil, errors.Wrapf(ErrInvalidFlagType, "invalid flag type %v for key %s", flag.Type, key)
}
return convFunc(flagValue(flag))
}
}
return nil, errors.Wrap(ErrFlagNotFound, key)
}

// GetString retrieves the string value of the flag with the specified key.
func (f Flags) GetString(key string) (string, error) {
v, err := f.getValue(key, FlagTypeString, func(v string) (interface{}, error) {
return strings.TrimSpace(v), nil
})
if err != nil {
return "", err
}
result, ok := v.(string)
if !ok {
return "", errors.Wrapf(ErrFlagAssertion, "invalid assertion type %T for key %s", v, key)
}
return result, nil
}

// GetStringSlice retrieves the string slice value of the flag with the specified key.
func (f Flags) GetStringSlice(key string) ([]string, error) {
v, err := f.getValue(key, FlagTypeStringSlice, func(v string) (interface{}, error) {
v = strings.Trim(v, "[]")
s := strings.Split(v, ",")
if len(s) == 0 || (len(s) == 1 && s[0] == "") {
return []string{}, nil
}
return s, nil
})
if err != nil {
return []string{}, err
}
result, ok := v.([]string)
if !ok {
return []string{}, errors.Wrapf(ErrFlagAssertion, "invalid string slice assertion type %T for key %s", v, key)
}
return result, nil
}

// GetBool retrieves the boolean value of the flag with the specified key.
func (f Flags) GetBool(key string) (bool, error) {
v, err := f.getValue(key, FlagTypeBool, func(v string) (interface{}, error) {
return strconv.ParseBool(v)
})
if err != nil {
return false, err
}
result, ok := v.(bool)
if !ok {
return false, errors.Wrapf(ErrFlagAssertion, "invalid bool assertion type %T for key %s", v, key)
}
return result, nil
}

// GetInt retrieves the integer value of the flag with the specified key.
func (f Flags) GetInt(key string) (int, error) {
v, err := f.getValue(key, FlagTypeInt, func(v string) (interface{}, error) {
return strconv.Atoi(v)
})
if err != nil {
return 0, err
}
result, ok := v.(int)
if !ok {
return 0, errors.Wrapf(ErrFlagAssertion, "invalid int assertion type %T for key %s", v, key)
}
return result, nil
}

// GetInt64 retrieves the int64 value of the flag with the specified key.
func (f Flags) GetInt64(key string) (int64, error) {
v, err := f.getValue(key, FlagTypeInt64, func(v string) (interface{}, error) {
return strconv.ParseInt(v, 10, 64)
})
if err != nil {
return int64(0), err
}
result, ok := v.(int64)
if !ok {
return int64(0), errors.Wrapf(ErrFlagAssertion, "invalid int64 assertion type %T for key %s", v, key)
}
return result, nil
}

// GetUint retrieves the uint value of the flag with the specified key.
func (f Flags) GetUint(key string) (uint, error) {
v, err := f.getValue(key, FlagTypeUint, func(v string) (interface{}, error) {
return strconv.ParseUint(v, 10, 64)
})
if err != nil {
return uint(0), err
}
result, ok := v.(uint64)
if !ok {
return uint(0), errors.Wrapf(ErrFlagAssertion, "invalid uint assertion type %T for key %s", v, key)
}
return uint(result), nil
}

// GetUint64 retrieves the uint64 value of the flag with the specified key.
func (f Flags) GetUint64(key string) (uint64, error) {
v, err := f.getValue(key, FlagTypeUint64, func(v string) (interface{}, error) {
return strconv.ParseUint(v, 10, 64)
})
if err != nil {
return uint64(0), err
}
result, ok := v.(uint64)
if !ok {
return uint64(0), errors.Wrapf(ErrFlagAssertion, "invalid uint64 assertion type %T for key %s", v, key)
}
return result, nil
}

// flagValue returns the value of the flag if set, otherwise returns the default value.
func flagValue(flag *Flag) string {
if flag.Value != "" {
return flag.Value
}
if flag.DefaultValue != "" {
return flag.DefaultValue
}
if flag.Type == FlagTypeBool ||
flag.Type == FlagTypeInt ||
flag.Type == FlagTypeInt64 ||
flag.Type == FlagTypeUint ||
flag.Type == FlagTypeUint64 {
return "0"
}
return ""
}
Loading
Loading