Skip to content

Commit

Permalink
feat: add custom flag parser for extensions (#4269)
Browse files Browse the repository at this point in the history
* add custom flag parser

* add changelog
  • Loading branch information
Pantani authored Jul 19, 2024
1 parent 42860cc commit 8ff971f
Show file tree
Hide file tree
Showing 5 changed files with 752 additions and 9 deletions.
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
- [#4111](https://github.com/ignite/cli/pull/4111) Remove vuex generation
- [#4113](https://github.com/ignite/cli/pull/4113) Generate chain config documentation automatically
- [#4131](https://github.com/ignite/cli/pull/4131) Support `bytes` as data type in the `scaffold` commands
- [#4269](https://github.com/ignite/cli/pull/4269) Add custom flag parser for extensions

### Changes

Expand Down
16 changes: 8 additions & 8 deletions ignite/cmd/plugin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ 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
Expand Down Expand Up @@ -79,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", Value: "0"},
},
Expand Down Expand Up @@ -424,7 +424,7 @@ 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 {
Expand Down Expand Up @@ -522,7 +522,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 @@ -540,7 +540,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 @@ -562,7 +562,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 @@ -583,7 +583,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 @@ -601,7 +601,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/v29/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

0 comments on commit 8ff971f

Please sign in to comment.