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: implement autocli customization #13251

Merged
merged 33 commits into from
Oct 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
b490535
feat: implement autocli customization
aaronc Sep 12, 2022
f8dd7d2
WIP
aaronc Sep 12, 2022
749b7d1
integrate simple options
aaronc Sep 12, 2022
3eca431
skip positional fields
aaronc Sep 12, 2022
68c8ecd
WIP
aaronc Sep 12, 2022
ce6d619
WIP
aaronc Sep 12, 2022
989305d
WIP
aaronc Sep 12, 2022
5c85a8b
trying to simplify diff, fixing errors
aaronc Sep 12, 2022
20a3807
WIP
aaronc Sep 12, 2022
3d438ea
WIP
aaronc Sep 12, 2022
5a6ea19
tests passing again
aaronc Sep 13, 2022
bc746b4
positional params working
aaronc Sep 13, 2022
ed3897e
add TODO
aaronc Sep 13, 2022
97fbbc9
WIP on tests
aaronc Sep 13, 2022
98cd77f
WIP on tests
aaronc Sep 13, 2022
b61be02
working tests
aaronc Sep 13, 2022
ca9c904
doc strings
aaronc Sep 13, 2022
7fbd044
docs
aaronc Sep 13, 2022
12d2a4a
tests
aaronc Sep 13, 2022
38f31e7
tests
aaronc Sep 13, 2022
3dc0e96
Merge branch 'main' of github.com:cosmos/cosmos-sdk into aaronc/autoc…
aaronc Sep 14, 2022
5ff5c3d
simplify API
aaronc Sep 14, 2022
81dd210
proper validation
aaronc Sep 14, 2022
06fb364
Merge branch 'main' of github.com:cosmos/cosmos-sdk into aaronc/autoc…
aaronc Sep 27, 2022
19b5b5b
fix import
aaronc Sep 27, 2022
fb9312b
address review comments
aaronc Oct 10, 2022
da02463
Merge branch 'main' of github.com:cosmos/cosmos-sdk into aaronc/autoc…
aaronc Oct 10, 2022
1782e7b
address review comments
aaronc Oct 10, 2022
0272ea8
Merge branch 'main' into aaronc/autocli-customize
aaronc Oct 10, 2022
e73b2bb
Merge branch 'main' into aaronc/autocli-customize
tac0turtle Oct 11, 2022
060dcb5
Merge branch 'main' into aaronc/autocli-customize
aaronc Oct 12, 2022
2bd696c
Merge branch 'main' into aaronc/autocli-customize
aaronc Oct 13, 2022
d9f3b6a
Merge branch 'main' into aaronc/autocli-customize
aaronc Oct 13, 2022
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
9 changes: 5 additions & 4 deletions api/cosmos/auth/v1beta1/query.pulsar.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 5 additions & 4 deletions api/cosmos/nft/v1beta1/tx.pulsar.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion api/cosmos/query/v1/query.pulsar.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 5 additions & 4 deletions api/cosmos/tx/v1beta1/service.pulsar.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

60 changes: 60 additions & 0 deletions client/v2/cli/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package cli

import (
"errors"
"fmt"
"strings"

"github.com/spf13/cobra"
)

// NOTE: this was copied from client/cmd.go to avoid introducing a dependency
// on the v1 client package.

// validateCmd returns unknown command error or Help display if help flag set
func validateCmd(cmd *cobra.Command, args []string) error {
var unknownCmd string
var skipNext bool

for _, arg := range args {
// search for help flag
if arg == "--help" || arg == "-h" {
return cmd.Help()
}

// check if the current arg is a flag
switch {
case len(arg) > 0 && (arg[0] == '-'):
// the next arg should be skipped if the current arg is a
// flag and does not use "=" to assign the flag's value
if !strings.Contains(arg, "=") {
skipNext = true
} else {
skipNext = false
}
case skipNext:
// skip current arg
skipNext = false
case unknownCmd == "":
// unknown command found
// continue searching for help flag
unknownCmd = arg
}
}

// return the help screen if no unknown command is found
if unknownCmd != "" {
err := fmt.Sprintf("unknown command \"%s\" for \"%s\"", unknownCmd, cmd.CalledAs())

// build suggestions for unknown argument
if suggestions := cmd.SuggestionsFor(unknownCmd); len(suggestions) > 0 {
err += "\n\nDid you mean this?\n"
for _, s := range suggestions {
err += fmt.Sprintf("\t%v\n", s)
}
}
return errors.New(err)
}

return cmd.Help()
}
7 changes: 3 additions & 4 deletions client/v2/cli/flag/address.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@ package flag
import (
"context"

"github.com/spf13/pflag"
"google.golang.org/protobuf/reflect/protoreflect"
)

type addressStringType struct{}

func (a addressStringType) NewValue(_ context.Context, _ *Builder) pflag.Value {
func (a addressStringType) NewValue(_ context.Context, _ *Builder) Value {
return &addressValue{}
}

Expand All @@ -21,8 +20,8 @@ type addressValue struct {
value string
}

func (a addressValue) Get() protoreflect.Value {
return protoreflect.ValueOfString(a.value)
func (a addressValue) Get(protoreflect.Value) (protoreflect.Value, error) {
return protoreflect.ValueOfString(a.value), nil
}

func (a addressValue) String() string {
Expand Down
4 changes: 4 additions & 0 deletions client/v2/cli/flag/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Package flag defines functionality for automatically managing command
// line flags as well positional arguments that are based on protobuf message
// fields.
package flag
11 changes: 5 additions & 6 deletions client/v2/cli/flag/duration.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,13 @@ import (
"context"
"time"

"github.com/spf13/pflag"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/types/known/durationpb"
)

type durationType struct{}

func (t durationType) NewValue(context.Context, *Builder) pflag.Value {
func (t durationType) NewValue(context.Context, *Builder) Value {
return &durationValue{}
}

Expand All @@ -23,11 +22,11 @@ type durationValue struct {
value *durationpb.Duration
}

func (t durationValue) Get() protoreflect.Value {
if t.value == nil {
return protoreflect.Value{}
func (a durationValue) Get(protoreflect.Value) (protoreflect.Value, error) {
if a.value == nil {
return protoreflect.Value{}, nil
}
return protoreflect.ValueOfMessage(t.value.ProtoReflect())
return protoreflect.ValueOfMessage(a.value.ProtoReflect()), nil
}

func (v durationValue) String() string {
Expand Down
7 changes: 3 additions & 4 deletions client/v2/cli/flag/enum.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,14 @@ import (
"strings"

"github.com/iancoleman/strcase"
"github.com/spf13/pflag"
"google.golang.org/protobuf/reflect/protoreflect"
)

type enumType struct {
enum protoreflect.EnumDescriptor
}

func (b enumType) NewValue(context.Context, *Builder) pflag.Value {
func (b enumType) NewValue(context.Context, *Builder) Value {
val := &enumValue{
enum: b.enum,
valMap: map[string]protoreflect.EnumValueDescriptor{},
Expand All @@ -41,8 +40,8 @@ type enumValue struct {
valMap map[string]protoreflect.EnumValueDescriptor
}

func (e enumValue) Get() protoreflect.Value {
return protoreflect.ValueOfEnum(e.value)
func (e enumValue) Get(protoreflect.Value) (protoreflect.Value, error) {
return protoreflect.ValueOfEnum(e.value), nil
}

func enumValueName(enum protoreflect.EnumDescriptor, enumValue protoreflect.EnumValueDescriptor) string {
Expand Down
94 changes: 41 additions & 53 deletions client/v2/cli/flag/field.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package flag

import (
"context"
"fmt"

autocliv1 "cosmossdk.io/api/cosmos/autocli/v1"
cosmos_proto "github.com/cosmos/cosmos-proto"
"github.com/spf13/pflag"
"google.golang.org/protobuf/proto"
Expand All @@ -12,59 +12,68 @@ import (
"cosmossdk.io/client/v2/internal/util"
)

// FieldValueBinder wraps a flag value in a way that allows it to be bound
// to a particular field in a protobuf message.
type FieldValueBinder interface {
Bind(message protoreflect.Message, field protoreflect.FieldDescriptor)
}

// Options specifies options for specific flags.
type Options struct {
// namingOptions specifies internal naming options for flags.
type namingOptions struct {
// Prefix is a prefix to prepend to all flags.
Prefix string
}

// AddFieldFlag adds a flag for the provided field to the flag set.
func (b *Builder) AddFieldFlag(ctx context.Context, flagSet *pflag.FlagSet, field protoreflect.FieldDescriptor, options Options) FieldValueBinder {
// addFieldFlag adds a flag for the provided field to the flag set.
func (b *Builder) addFieldFlag(ctx context.Context, flagSet *pflag.FlagSet, field protoreflect.FieldDescriptor, opts *autocliv1.FlagOptions, options namingOptions) (name string, hasValue HasValue, err error) {
if opts == nil {
opts = &autocliv1.FlagOptions{}
}

if field.Kind() == protoreflect.MessageKind && field.Message().FullName() == "cosmos.base.query.v1beta1.PageRequest" {
return b.bindPageRequest(ctx, flagSet, field)
hasValue, err := b.bindPageRequest(ctx, flagSet, field)
return "", hasValue, err
}

name := options.Prefix + util.DescriptorKebabName(field)
usage := util.DescriptorDocs(field)
shorthand := ""
name = opts.Name
if name == "" {
name = options.Prefix + util.DescriptorKebabName(field)
}

usage := opts.Usage
if usage == "" {
usage = util.DescriptorDocs(field)
}

shorthand := opts.Shorthand
defaultValue := opts.DefaultValue

if typ := b.resolveFlagType(field); typ != nil {
if defaultValue == "" {
defaultValue = typ.DefaultValue()
}

val := typ.NewValue(ctx, b)
flagSet.AddFlag(&pflag.Flag{
Name: name,
Shorthand: shorthand,
Usage: usage,
DefValue: typ.DefaultValue(),
DefValue: defaultValue,
Value: val,
})
switch val := val.(type) {
case SimpleValue:
return simpleValueBinder{val}
case ListValue:
return listValueBinder{val}
default:
panic(fmt.Errorf("%T does not implement SimpleValue or ListValue", val))
}
return name, val, nil
}

// use the built-in pflag StringP, Int32P, etc. functions
var val HasValue
if field.IsList() {
if value := bindSimpleListFlag(flagSet, field.Kind(), name, shorthand, usage); value != nil {
return listValueBinder{value}
}
return nil
}
val = bindSimpleListFlag(flagSet, field.Kind(), name, shorthand, usage)

if value := bindSimpleFlag(flagSet, field.Kind(), name, shorthand, usage); value != nil {
return simpleValueBinder{value}
} else {
val = bindSimpleFlag(flagSet, field.Kind(), name, shorthand, usage)
}

return nil
// This is a bit of hacking around the pflag API, but the
// defaultValue is set in this way because this is much easier than trying
// to parse the string into the types that StringSliceP, Int32P, etc. expect
if defaultValue != "" {
err = flagSet.Set(name, defaultValue)
}
return name, val, err
}

func (b *Builder) resolveFlagType(field protoreflect.FieldDescriptor) Type {
Expand Down Expand Up @@ -105,24 +114,3 @@ func (b *Builder) resolveFlagTypeBasic(field protoreflect.FieldDescriptor) Type
return nil
}
}

type simpleValueBinder struct {
SimpleValue
}

func (s simpleValueBinder) Bind(message protoreflect.Message, field protoreflect.FieldDescriptor) {
val := s.Get()
if val.IsValid() {
message.Set(field, val)
} else {
message.Clear(field)
}
}

type listValueBinder struct {
ListValue
}

func (s listValueBinder) Bind(message protoreflect.Message, field protoreflect.FieldDescriptor) {
s.AppendTo(message.NewField(field).List())
}
Loading