Co-authored-by: Julián Toledano <JulianToledano@users.noreply.github.com> Co-authored-by: Julien Robert <julien@rbrt.fr>
This commit is contained in:
parent
96a30160b3
commit
4a73a1ecef
@ -36,6 +36,12 @@ Ref: https://keepachangelog.com/en/1.0.0/
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [v2.0.0-beta.7] - 2024-12-10
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* [#22817](https://github.com/cosmos/cosmos-sdk/pull/22817) Add DecCoin support in autocli flag builder.
|
||||
|
||||
## [v2.0.0-beta.6] - 2024-11-21
|
||||
|
||||
### Improvements
|
||||
|
||||
@ -61,6 +61,7 @@ func (b *Builder) init() {
|
||||
b.messageFlagTypes["google.protobuf.Timestamp"] = timestampType{}
|
||||
b.messageFlagTypes["google.protobuf.Duration"] = durationType{}
|
||||
b.messageFlagTypes["cosmos.base.v1beta1.Coin"] = coinType{}
|
||||
b.messageFlagTypes["cosmos.base.v1beta1.DecCoin"] = decCoinType{}
|
||||
}
|
||||
|
||||
if b.scalarFlagTypes == nil {
|
||||
|
||||
@ -2,13 +2,13 @@ package flag
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
|
||||
basev1beta1 "cosmossdk.io/api/cosmos/base/v1beta1"
|
||||
"cosmossdk.io/core/coins"
|
||||
"cosmossdk.io/client/v2/internal/coins"
|
||||
)
|
||||
|
||||
type coinType struct{}
|
||||
@ -22,8 +22,7 @@ func (c coinType) NewValue(*context.Context, *Builder) Value {
|
||||
}
|
||||
|
||||
func (c coinType) DefaultValue() string {
|
||||
stringCoin, _ := coins.FormatCoins([]*basev1beta1.Coin{}, nil)
|
||||
return stringCoin
|
||||
return "zero"
|
||||
}
|
||||
|
||||
func (c *coinValue) Get(protoreflect.Value) (protoreflect.Value, error) {
|
||||
@ -34,12 +33,16 @@ func (c *coinValue) Get(protoreflect.Value) (protoreflect.Value, error) {
|
||||
}
|
||||
|
||||
func (c *coinValue) String() string {
|
||||
if c.value == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return c.value.String()
|
||||
}
|
||||
|
||||
func (c *coinValue) Set(stringValue string) error {
|
||||
if strings.Contains(stringValue, ",") {
|
||||
return fmt.Errorf("coin flag must be a single coin, specific multiple coins with multiple flags or spaces")
|
||||
return errors.New("coin flag must be a single coin, specific multiple coins with multiple flags or spaces")
|
||||
}
|
||||
|
||||
coin, err := coins.ParseCoin(stringValue)
|
||||
|
||||
58
client/v2/autocli/flag/dec_coin.go
Normal file
58
client/v2/autocli/flag/dec_coin.go
Normal file
@ -0,0 +1,58 @@
|
||||
package flag
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
|
||||
basev1beta1 "cosmossdk.io/api/cosmos/base/v1beta1"
|
||||
"cosmossdk.io/client/v2/internal/coins"
|
||||
)
|
||||
|
||||
type decCoinType struct{}
|
||||
|
||||
type decCoinValue struct {
|
||||
value *basev1beta1.DecCoin
|
||||
}
|
||||
|
||||
func (c decCoinType) NewValue(*context.Context, *Builder) Value {
|
||||
return &decCoinValue{}
|
||||
}
|
||||
|
||||
func (c decCoinType) DefaultValue() string {
|
||||
return "zero"
|
||||
}
|
||||
|
||||
func (c *decCoinValue) Get(protoreflect.Value) (protoreflect.Value, error) {
|
||||
if c.value == nil {
|
||||
return protoreflect.Value{}, nil
|
||||
}
|
||||
return protoreflect.ValueOfMessage(c.value.ProtoReflect()), nil
|
||||
}
|
||||
|
||||
func (c *decCoinValue) String() string {
|
||||
if c.value == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return c.value.String()
|
||||
}
|
||||
|
||||
func (c *decCoinValue) Set(stringValue string) error {
|
||||
if strings.Contains(stringValue, ",") {
|
||||
return errors.New("coin flag must be a single coin, specific multiple coins with multiple flags or spaces")
|
||||
}
|
||||
|
||||
coin, err := coins.ParseDecCoin(stringValue)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.value = coin
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *decCoinValue) Type() string {
|
||||
return "cosmos.base.v1beta1.DecCoin"
|
||||
}
|
||||
@ -13,6 +13,7 @@ require (
|
||||
github.com/cosmos/cosmos-sdk v0.50.6
|
||||
github.com/spf13/cobra v1.8.1
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.9.0
|
||||
google.golang.org/grpc v1.63.2
|
||||
google.golang.org/protobuf v1.34.2
|
||||
gotest.tools/v3 v3.5.1
|
||||
@ -131,7 +132,6 @@ require (
|
||||
github.com/spf13/afero v1.11.0 // indirect
|
||||
github.com/spf13/cast v1.6.0 // indirect
|
||||
github.com/spf13/viper v1.19.0 // indirect
|
||||
github.com/stretchr/testify v1.9.0 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect
|
||||
github.com/tendermint/go-amino v0.16.0 // indirect
|
||||
|
||||
62
client/v2/internal/coins/format.go
Normal file
62
client/v2/internal/coins/format.go
Normal file
@ -0,0 +1,62 @@
|
||||
package coins
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
basev1beta1 "cosmossdk.io/api/cosmos/base/v1beta1"
|
||||
)
|
||||
|
||||
// Amount can be a whole number or a decimal number. Denominations can be 3 ~ 128
|
||||
// characters long and support letters, followed by either a letter, a number or
|
||||
// a separator ('/', ':', '.', '_' or '-').
|
||||
var coinRegex = regexp.MustCompile(`^(\d+(\.\d+)?)([a-zA-Z][a-zA-Z0-9\/\:\._\-]{2,127})$`)
|
||||
|
||||
// ParseCoin parses a coin from a string. The string must be in the format
|
||||
// <amount><denom>, where <amount> is a number and <denom> is a valid denom.
|
||||
func ParseCoin(input string) (*basev1beta1.Coin, error) {
|
||||
amount, denom, err := parseCoin(input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &basev1beta1.Coin{
|
||||
Amount: amount,
|
||||
Denom: denom,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ParseDecCoin parses a decCoin from a string. The string must be in the format
|
||||
// <amount><denom>, where <amount> is a number and <denom> is a valid denom.
|
||||
func ParseDecCoin(input string) (*basev1beta1.DecCoin, error) {
|
||||
amount, denom, err := parseCoin(input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &basev1beta1.DecCoin{
|
||||
Amount: amount,
|
||||
Denom: denom,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// parseCoin parses a coin string into its amount and denom components.
|
||||
// The input string must be in the format <amount><denom>.
|
||||
// It returns the amount string, denom string, and any error encountered.
|
||||
// Returns an error if the input is empty or doesn't match the expected format.
|
||||
func parseCoin(input string) (amount, denom string, err error) {
|
||||
input = strings.TrimSpace(input)
|
||||
|
||||
if input == "" {
|
||||
return "", "", errors.New("empty input when parsing coin")
|
||||
}
|
||||
|
||||
matches := coinRegex.FindStringSubmatch(input)
|
||||
|
||||
if len(matches) == 0 {
|
||||
return "", "", errors.New("invalid input format")
|
||||
}
|
||||
|
||||
return matches[1], matches[3], nil
|
||||
}
|
||||
73
client/v2/internal/coins/format_test.go
Normal file
73
client/v2/internal/coins/format_test.go
Normal file
@ -0,0 +1,73 @@
|
||||
package coins
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_parseCoin(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
amount string
|
||||
denom string
|
||||
err string
|
||||
}{
|
||||
{
|
||||
name: "ok",
|
||||
input: "1000stake",
|
||||
amount: "1000",
|
||||
denom: "stake",
|
||||
},
|
||||
{
|
||||
name: "empty",
|
||||
input: "",
|
||||
err: "empty input when parsing coin",
|
||||
},
|
||||
{
|
||||
name: "empty denom",
|
||||
input: "1000",
|
||||
err: "invalid input format",
|
||||
},
|
||||
{
|
||||
name: "empty amount",
|
||||
input: "stake",
|
||||
err: "invalid input format",
|
||||
},
|
||||
{
|
||||
name: "<denom><amount> format",
|
||||
input: "stake1000",
|
||||
err: "invalid input format",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
amount, denom, err := parseCoin(tt.input)
|
||||
if tt.err != "" {
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), tt.err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tt.amount, amount)
|
||||
require.Equal(t, tt.denom, denom)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseCoin(t *testing.T) {
|
||||
encodedCoin := "1000000000foo"
|
||||
coin, err := ParseCoin(encodedCoin)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "1000000000", coin.Amount)
|
||||
require.Equal(t, "foo", coin.Denom)
|
||||
}
|
||||
|
||||
func TestParseDecCoin(t *testing.T) {
|
||||
encodedCoin := "1000000000foo"
|
||||
coin, err := ParseDecCoin(encodedCoin)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "1000000000", coin.Amount)
|
||||
require.Equal(t, "foo", coin.Denom)
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user