R4R: Support "unknown commands" for subcommands (#4465)

Fixes #4284
Now prints:

gaiacli query distr comission --trust-node cosmos1234
ERROR: unknown command "comission" for "distr"

Did you mean this?
	commission

Adds custom argument validation for subcommands with subcommands. Doesn't affect "query" or "tx" subcommands since they reside in gaia repo. All flags except help are disabled for these commands.
This commit is contained in:
colin axner 2019-06-04 10:07:12 -07:00 committed by Alessio Treglia
parent 75de63ce31
commit 6e2f5f3102
9 changed files with 136 additions and 22 deletions

View File

@ -0,0 +1 @@
#4465 Unknown subcommands print relevant error message

View File

@ -7,6 +7,7 @@ import (
"os"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/tendermint/tendermint/libs/common"
@ -352,3 +353,33 @@ func isTxSigner(user sdk.AccAddress, signers []sdk.AccAddress) bool {
return false
}
// ValidateCmd returns unknown command error or Help display if help flag set
func ValidateCmd(cmd *cobra.Command, args []string) error {
var cmds []string
var help bool
// construct array of commands and search for help flag
for _, arg := range args {
if arg == "--help" || arg == "-h" {
help = true
} else if len(arg) > 0 && !(arg[0] == '-') {
cmds = append(cmds, arg)
}
}
if !help && len(cmds) > 0 {
err := fmt.Sprintf("unknown command \"%s\" for \"%s\"", cmds[0], cmd.CalledAs())
// build suggestions for unknown argument
if suggestions := cmd.SuggestionsFor(cmds[0]); 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()
}

View File

@ -7,6 +7,7 @@ import (
"os"
"testing"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/crypto/ed25519"
@ -112,6 +113,47 @@ func TestReadStdTxFromFile(t *testing.T) {
require.Equal(t, decodedTx.Memo, "foomemo")
}
func TestValidateCmd(t *testing.T) {
// Setup root and subcommands
rootCmd := &cobra.Command{
Use: "root",
}
queryCmd := &cobra.Command{
Use: "query",
}
rootCmd.AddCommand(queryCmd)
// Command being tested
distCmd := &cobra.Command{
Use: "distr",
DisableFlagParsing: true,
SuggestionsMinimumDistance: 2,
}
queryCmd.AddCommand(distCmd)
commissionCmd := &cobra.Command{
Use: "commission",
}
distCmd.AddCommand(commissionCmd)
tests := []struct {
reason string
args []string
wantErr bool
}{
{"misspelled command", []string{"comission"}, true},
{"no command provided", []string{}, false},
{"help flag", []string{"comission", "--help"}, false},
{"shorthand help flag", []string{"comission", "-h"}, false},
}
for _, tt := range tests {
err := ValidateCmd(distCmd, tt.args)
assert.Equal(t, tt.wantErr, err != nil, tt.reason)
}
}
// aux functions
func compareEncoders(t *testing.T, expected sdk.TxEncoder, actual sdk.TxEncoder) {

View File

@ -5,6 +5,7 @@ import (
amino "github.com/tendermint/go-amino"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/utils"
"github.com/cosmos/cosmos-sdk/x/crisis"
"github.com/cosmos/cosmos-sdk/x/crisis/client/cli"
)
@ -31,8 +32,11 @@ func (ModuleClient) GetQueryCmd() *cobra.Command {
// GetTxCmd returns the transaction commands for this module
func (mc ModuleClient) GetTxCmd() *cobra.Command {
txCmd := &cobra.Command{
Use: crisis.ModuleName,
Short: "crisis transactions subcommands",
Use: crisis.ModuleName,
Short: "crisis transactions subcommands",
DisableFlagParsing: true,
SuggestionsMinimumDistance: 2,
RunE: utils.ValidateCmd,
}
txCmd.AddCommand(client.PostCommands(

View File

@ -5,6 +5,8 @@ import (
amino "github.com/tendermint/go-amino"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/utils"
dist "github.com/cosmos/cosmos-sdk/x/distribution"
distCmds "github.com/cosmos/cosmos-sdk/x/distribution/client/cli"
)
@ -21,8 +23,11 @@ func NewModuleClient(storeKey string, cdc *amino.Codec) ModuleClient {
// GetQueryCmd returns the cli query commands for this module
func (mc ModuleClient) GetQueryCmd() *cobra.Command {
distQueryCmd := &cobra.Command{
Use: "distr",
Short: "Querying commands for the distribution module",
Use: dist.ModuleName,
Short: "Querying commands for the distribution module",
DisableFlagParsing: true,
SuggestionsMinimumDistance: 2,
RunE: utils.ValidateCmd,
}
distQueryCmd.AddCommand(client.GetCommands(
@ -40,8 +45,11 @@ func (mc ModuleClient) GetQueryCmd() *cobra.Command {
// GetTxCmd returns the transaction commands for this module
func (mc ModuleClient) GetTxCmd() *cobra.Command {
distTxCmd := &cobra.Command{
Use: "distr",
Short: "Distribution transactions subcommands",
Use: dist.ModuleName,
Short: "Distribution transactions subcommands",
DisableFlagParsing: true,
SuggestionsMinimumDistance: 2,
RunE: utils.ValidateCmd,
}
distTxCmd.AddCommand(client.PostCommands(

View File

@ -5,6 +5,7 @@ import (
amino "github.com/tendermint/go-amino"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/utils"
"github.com/cosmos/cosmos-sdk/x/gov"
govCli "github.com/cosmos/cosmos-sdk/x/gov/client/cli"
)
@ -28,8 +29,11 @@ func NewModuleClient(storeKey string, cdc *amino.Codec, pcmds ...*cobra.Command)
func (mc ModuleClient) GetQueryCmd() *cobra.Command {
// Group gov queries under a subcommand
govQueryCmd := &cobra.Command{
Use: gov.ModuleName,
Short: "Querying commands for the governance module",
Use: gov.ModuleName,
Short: "Querying commands for the governance module",
DisableFlagParsing: true,
SuggestionsMinimumDistance: 2,
RunE: utils.ValidateCmd,
}
govQueryCmd.AddCommand(client.GetCommands(
@ -50,8 +54,11 @@ func (mc ModuleClient) GetQueryCmd() *cobra.Command {
// GetTxCmd returns the transaction commands for this module
func (mc ModuleClient) GetTxCmd() *cobra.Command {
govTxCmd := &cobra.Command{
Use: gov.ModuleName,
Short: "Governance transactions subcommands",
Use: gov.ModuleName,
Short: "Governance transactions subcommands",
DisableFlagParsing: true,
SuggestionsMinimumDistance: 2,
RunE: utils.ValidateCmd,
}
cmdSubmitProp := govCli.GetCmdSubmitProposal(mc.cdc)

View File

@ -5,6 +5,7 @@ import (
amino "github.com/tendermint/go-amino"
sdkclient "github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/utils"
"github.com/cosmos/cosmos-sdk/x/mint"
"github.com/cosmos/cosmos-sdk/x/mint/client/cli"
)
@ -21,8 +22,11 @@ func NewModuleClient(storeKey string, cdc *amino.Codec) ModuleClient {
// GetQueryCmd returns the cli query commands for the minting module.
func (mc ModuleClient) GetQueryCmd() *cobra.Command {
mintingQueryCmd := &cobra.Command{
Use: mint.ModuleName,
Short: "Querying commands for the minting module",
Use: mint.ModuleName,
Short: "Querying commands for the minting module",
DisableFlagParsing: true,
SuggestionsMinimumDistance: 2,
RunE: utils.ValidateCmd,
}
mintingQueryCmd.AddCommand(
@ -39,8 +43,11 @@ func (mc ModuleClient) GetQueryCmd() *cobra.Command {
// GetTxCmd returns the transaction commands for the minting module.
func (mc ModuleClient) GetTxCmd() *cobra.Command {
mintTxCmd := &cobra.Command{
Use: mint.ModuleName,
Short: "Minting transaction subcommands",
Use: mint.ModuleName,
Short: "Minting transaction subcommands",
DisableFlagParsing: true,
SuggestionsMinimumDistance: 2,
RunE: utils.ValidateCmd,
}
return mintTxCmd

View File

@ -5,6 +5,7 @@ import (
amino "github.com/tendermint/go-amino"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/utils"
"github.com/cosmos/cosmos-sdk/x/slashing"
"github.com/cosmos/cosmos-sdk/x/slashing/client/cli"
)
@ -23,8 +24,11 @@ func NewModuleClient(storeKey string, cdc *amino.Codec) ModuleClient {
func (mc ModuleClient) GetQueryCmd() *cobra.Command {
// Group slashing queries under a subcommand
slashingQueryCmd := &cobra.Command{
Use: slashing.ModuleName,
Short: "Querying commands for the slashing module",
Use: slashing.ModuleName,
Short: "Querying commands for the slashing module",
DisableFlagParsing: true,
SuggestionsMinimumDistance: 2,
RunE: utils.ValidateCmd,
}
slashingQueryCmd.AddCommand(
@ -41,8 +45,11 @@ func (mc ModuleClient) GetQueryCmd() *cobra.Command {
// GetTxCmd returns the transaction commands for this module
func (mc ModuleClient) GetTxCmd() *cobra.Command {
slashingTxCmd := &cobra.Command{
Use: slashing.ModuleName,
Short: "Slashing transactions subcommands",
Use: slashing.ModuleName,
Short: "Slashing transactions subcommands",
DisableFlagParsing: true,
SuggestionsMinimumDistance: 2,
RunE: utils.ValidateCmd,
}
slashingTxCmd.AddCommand(client.PostCommands(

View File

@ -5,6 +5,7 @@ import (
amino "github.com/tendermint/go-amino"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/utils"
"github.com/cosmos/cosmos-sdk/x/staking/client/cli"
"github.com/cosmos/cosmos-sdk/x/staking/types"
)
@ -22,8 +23,11 @@ func NewModuleClient(storeKey string, cdc *amino.Codec) ModuleClient {
// GetQueryCmd returns the cli query commands for this module
func (mc ModuleClient) GetQueryCmd() *cobra.Command {
stakingQueryCmd := &cobra.Command{
Use: types.ModuleName,
Short: "Querying commands for the staking module",
Use: types.ModuleName,
Short: "Querying commands for the staking module",
DisableFlagParsing: true,
SuggestionsMinimumDistance: 2,
RunE: utils.ValidateCmd,
}
stakingQueryCmd.AddCommand(client.GetCommands(
cli.GetCmdQueryDelegation(mc.storeKey, mc.cdc),
@ -47,8 +51,11 @@ func (mc ModuleClient) GetQueryCmd() *cobra.Command {
// GetTxCmd returns the transaction commands for this module
func (mc ModuleClient) GetTxCmd() *cobra.Command {
stakingTxCmd := &cobra.Command{
Use: types.ModuleName,
Short: "Staking transaction subcommands",
Use: types.ModuleName,
Short: "Staking transaction subcommands",
DisableFlagParsing: true,
SuggestionsMinimumDistance: 2,
RunE: utils.ValidateCmd,
}
stakingTxCmd.AddCommand(client.PostCommands(