diff --git a/cmd/abci-cli/abci-cli.go b/cmd/abci-cli/abci-cli.go index c7316949..acee3ea6 100644 --- a/cmd/abci-cli/abci-cli.go +++ b/cmd/abci-cli/abci-cli.go @@ -7,13 +7,15 @@ import ( "fmt" "io" "os" + "os/exec" "strings" abcicli "github.com/tendermint/abci/client" "github.com/tendermint/abci/types" - "github.com/tendermint/abci/version" + //"github.com/tendermint/abci/version" "github.com/tendermint/tmlibs/log" - "github.com/urfave/cli" + + "github.com/spf13/cobra" ) // Structure for data passed to print response. @@ -38,149 +40,135 @@ var client abcicli.Client var logger log.Logger -func main() { +// flags +var ( + address string + abci string + verbose bool - //workaround for the cli library (https://github.com/urfave/cli/issues/565) - cli.OsExiter = func(_ int) {} + path string + height int + prove bool +) - app := cli.NewApp() - app.Name = "abci-cli" - app.Usage = "abci-cli [command] [args...]" - app.Version = version.Version - app.Flags = []cli.Flag{ - cli.StringFlag{ - Name: "address", - Value: "tcp://127.0.0.1:46658", - Usage: "address of application socket", - }, - cli.StringFlag{ - Name: "abci", - Value: "socket", - Usage: "socket or grpc", - }, - cli.BoolFlag{ - Name: "verbose", - Usage: "print the command and results as if it were a console session", - }, - } - app.Commands = []cli.Command{ - { - Name: "batch", - Usage: "Run a batch of abci commands against an application", - Action: func(c *cli.Context) error { - return cmdBatch(app, c) - }, - }, - { - Name: "console", - Usage: "Start an interactive abci console for multiple commands", - Action: func(c *cli.Context) error { - return cmdConsole(app, c) - }, - }, - { - Name: "echo", - Usage: "Have the application echo a message", - Action: func(c *cli.Context) error { - return cmdEcho(c) - }, - }, - { - Name: "info", - Usage: "Get some info about the application", - Action: func(c *cli.Context) error { - return cmdInfo(c) - }, - }, - { - Name: "set_option", - Usage: "Set an option on the application", - Action: func(c *cli.Context) error { - return cmdSetOption(c) - }, - }, - { - Name: "deliver_tx", - Usage: "Deliver a new tx to application", - Action: func(c *cli.Context) error { - return cmdDeliverTx(c) - }, - }, - { - Name: "check_tx", - Usage: "Validate a tx", - Action: func(c *cli.Context) error { - return cmdCheckTx(c) - }, - }, - { - Name: "commit", - Usage: "Commit the application state and return the Merkle root hash", - Action: func(c *cli.Context) error { - return cmdCommit(c) - }, - }, - { - Name: "query", - Usage: "Query application state", - Action: func(c *cli.Context) error { - return cmdQuery(c) - }, - Flags: []cli.Flag{ - cli.StringFlag{ - Name: "path", - Value: "/store", - Usage: "Path to prefix the query with", - }, - cli.IntFlag{ - Name: "height", - Value: 0, - Usage: "Height to query the blockchain at", - }, - cli.BoolFlag{ - Name: "prove", - Usage: "Whether or not to return a merkle proof of the query result", - }, - }, - }, - } - app.Before = before - err := app.Run(os.Args) - if err != nil { - logger.Error(err.Error()) - os.Exit(1) - } - -} - -func before(c *cli.Context) error { - if logger == nil { - logger = log.NewFilter(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), log.AllowError()) - } - if client == nil { - var err error - client, err = abcicli.NewClient(c.GlobalString("address"), c.GlobalString("abci"), false) - if err != nil { - logger.Error(err.Error()) - os.Exit(1) +var RootCmd = &cobra.Command{ + Use: "abci-cli", + Short: "", + Long: "", + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + if logger == nil { + logger = log.NewFilter(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), log.AllowError()) } - client.SetLogger(logger.With("module", "abci-client")) - if _, err := client.Start(); err != nil { - return err + if client == nil { + var err error + client, err = abcicli.NewClient(address, abci, false) + if err != nil { + logger.Error(err.Error()) + os.Exit(1) + } + client.SetLogger(logger.With("module", "abci-client")) + if _, err := client.Start(); err != nil { + return err + } } - } - return nil + return nil + }, } -// badCmd is called when we invoke with an invalid first argument (just for console for now) -func badCmd(c *cli.Context, cmd string) { - fmt.Println("Unknown command:", cmd) - fmt.Println("Please try one of the following:") - fmt.Println("") - cli.DefaultAppComplete(c) +func Execute() { + addGlobalFlags() + addCommands() + RootCmd.Execute() //err? } -//Generates new Args array based off of previous call args to maintain flag persistence +func addGlobalFlags() { + RootCmd.PersistentFlags().StringVarP(&address, "address", "", "tcp://127.0.0.1:46658", "address of application socket") + RootCmd.PersistentFlags().StringVarP(&abci, "abci", "", "socket", "socket or grpc") + RootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "print the command and results as if it were a console session") +} + +func addQueryFlags() { + queryCmd.PersistentFlags().StringVarP(&path, "path", "", "/store", "Path to prefix query with") + queryCmd.PersistentFlags().IntVarP(&height, "height", "", 0, "Height to query the blockchain at") + queryCmd.PersistentFlags().BoolVarP(&prove, "prove", "", false, "Whether or not to return a merkle proof of the query result") +} + +func addCommands() { + RootCmd.AddCommand(batchCmd) + RootCmd.AddCommand(consoleCmd) + RootCmd.AddCommand(echoCmd) + RootCmd.AddCommand(infoCmd) + RootCmd.AddCommand(setOptionCmd) + RootCmd.AddCommand(deliverTxCmd) + RootCmd.AddCommand(checkTxCmd) + RootCmd.AddCommand(commitCmd) + addQueryFlags() + RootCmd.AddCommand(queryCmd) +} + +var batchCmd = &cobra.Command{ + Use: "batch", + Short: "Run a batch of abci commands against an application", + Long: "", + Run: cmdBatch, +} + +var consoleCmd = &cobra.Command{ + Use: "console", + Short: "Start an interactive abci console for multiple commands", + Long: "", + ValidArgs: []string{"batch", "echo", "info", "set_option", "deliver_tx", "check_tx", "commit", "query"}, + Run: cmdConsole, +} + +var echoCmd = &cobra.Command{ + Use: "echo", + Short: "", + Long: "Have the application echo a message", + Run: cmdEcho, +} +var infoCmd = &cobra.Command{ + Use: "info", + Short: "Get some info about the application", + Long: "", + Run: cmdInfo, +} +var setOptionCmd = &cobra.Command{ + Use: "set_option", + Short: "Set an options on the application", + Long: "", + Run: cmdSetOption, +} + +var deliverTxCmd = &cobra.Command{ + Use: "deliver_tx", + Short: "Deliver a new tx to the application", + Long: "", + Run: cmdDeliverTx, +} + +var checkTxCmd = &cobra.Command{ + Use: "check_tx", + Short: "Validate a tx", + Long: "", + Run: cmdCheckTx, +} + +var commitCmd = &cobra.Command{ + Use: "commit", + Short: "Commit the application state and return the Merkle root hash", + Long: "", + Run: cmdCommit, +} + +var queryCmd = &cobra.Command{ + Use: "query", + Short: "Query the application state", + Long: "", + Run: cmdQuery, +} + +// Generates new Args array based off of previous call args to maintain flag persistence func persistentArgs(line []byte) []string { // generate the arguments to run from original os.Args @@ -196,154 +184,142 @@ func persistentArgs(line []byte) []string { //-------------------------------------------------------------------------------- -func cmdBatch(app *cli.App, c *cli.Context) error { +func cmdBatch(cmd *cobra.Command, args []string) { bufReader := bufio.NewReader(os.Stdin) for { line, more, err := bufReader.ReadLine() if more { - return errors.New("Input line is too long") + ifExit(errors.New("Input line is too long")) } else if err == io.EOF { break } else if len(line) == 0 { continue } else if err != nil { - return err + ifExit(err) } - args := persistentArgs(line) - app.Run(args) //cli prints error within its func call + pArgs := persistentArgs(line) + out, err := exec.Command(pArgs[0], pArgs[1:]...).Output() + if err != nil { + panic(err) + } + fmt.Println(string(out)) } - return nil } -func cmdConsole(app *cli.App, c *cli.Context) error { - // don't hard exit on mistyped commands (eg. check vs check_tx) - app.CommandNotFound = badCmd +func cmdConsole(cmd *cobra.Command, args []string) { for { fmt.Printf("\n> ") bufReader := bufio.NewReader(os.Stdin) line, more, err := bufReader.ReadLine() if more { - return errors.New("Input is too long") + ifExit(errors.New("Input is too long")) } else if err != nil { - return err + ifExit(err) } - args := persistentArgs(line) - app.Run(args) //cli prints error within its func call + pArgs := persistentArgs(line) + out, err := exec.Command(pArgs[0], pArgs[1:]...).Output() + if err != nil { + panic(err) + } + fmt.Println(string(out)) } } // Have the application echo a message -func cmdEcho(c *cli.Context) error { - args := c.Args() +func cmdEcho(cmd *cobra.Command, args []string) { if len(args) != 1 { - return errors.New("Command echo takes 1 argument") + ifExit(errors.New("Command echo takes 1 argument")) } resEcho := client.EchoSync(args[0]) - printResponse(c, response{ + printResponse(cmd, args, response{ Data: resEcho.Data, }) - return nil } // Get some info from the application -func cmdInfo(c *cli.Context) error { - args := c.Args() +func cmdInfo(cmd *cobra.Command, args []string) { var version string if len(args) == 1 { version = args[0] } resInfo, err := client.InfoSync(types.RequestInfo{version}) if err != nil { - return err + ifExit(err) } - printResponse(c, response{ + printResponse(cmd, args, response{ Data: []byte(resInfo.Data), }) - return nil } // Set an option on the application -func cmdSetOption(c *cli.Context) error { - args := c.Args() +func cmdSetOption(cmd *cobra.Command, args []string) { if len(args) != 2 { - return errors.New("Command set_option takes 2 arguments (key, value)") + ifExit(errors.New("Command set_option takes 2 arguments (key, value)")) } resSetOption := client.SetOptionSync(args[0], args[1]) - printResponse(c, response{ + printResponse(cmd, args, response{ Log: resSetOption.Log, }) - return nil } // Append a new tx to application -func cmdDeliverTx(c *cli.Context) error { - args := c.Args() +func cmdDeliverTx(cmd *cobra.Command, args []string) { if len(args) != 1 { - return errors.New("Command deliver_tx takes 1 argument") + ifExit(errors.New("Command deliver_tx takes 1 argument")) } - txBytes, err := stringOrHexToBytes(c.Args()[0]) + txBytes, err := stringOrHexToBytes(args[0]) if err != nil { - return err + ifExit(err) } res := client.DeliverTxSync(txBytes) - printResponse(c, response{ + printResponse(cmd, args, response{ Code: res.Code, Data: res.Data, Log: res.Log, }) - return nil } // Validate a tx -func cmdCheckTx(c *cli.Context) error { - args := c.Args() +func cmdCheckTx(cmd *cobra.Command, args []string) { if len(args) != 1 { - return errors.New("Command check_tx takes 1 argument") + ifExit(errors.New("Command check_tx takes 1 argument")) } - txBytes, err := stringOrHexToBytes(c.Args()[0]) + txBytes, err := stringOrHexToBytes(args[0]) if err != nil { - return err + ifExit(err) } res := client.CheckTxSync(txBytes) - printResponse(c, response{ + printResponse(cmd, args, response{ Code: res.Code, Data: res.Data, Log: res.Log, }) - return nil } // Get application Merkle root hash -func cmdCommit(c *cli.Context) error { +func cmdCommit(cmd *cobra.Command, args []string) { res := client.CommitSync() - printResponse(c, response{ + printResponse(cmd, args, response{ Code: res.Code, Data: res.Data, Log: res.Log, }) - return nil } // Query application state -func cmdQuery(c *cli.Context) error { - args := c.Args() - +func cmdQuery(cmd *cobra.Command, args []string) { if len(args) != 1 { - return errors.New("Command query takes 1 argument, the query bytes") + ifExit(errors.New("Command query takes 1 argument, the query bytes")) } queryBytes, err := stringOrHexToBytes(args[0]) if err != nil { - return err + ifExit(err) } - path := c.String("path") - height := c.Int("height") - prove := c.Bool("prove") - resQuery, err := client.QuerySync(types.RequestQuery{ Data: queryBytes, Path: path, @@ -351,9 +327,9 @@ func cmdQuery(c *cli.Context) error { Prove: prove, }) if err != nil { - return err + ifExit(err) } - printResponse(c, response{ + printResponse(cmd, args, response{ Code: resQuery.Code, Log: resQuery.Log, Query: &queryResponse{ @@ -363,17 +339,14 @@ func cmdQuery(c *cli.Context) error { Proof: resQuery.Proof, }, }) - return nil } //-------------------------------------------------------------------------------- -func printResponse(c *cli.Context, rsp response) { - - verbose := c.GlobalBool("verbose") +func printResponse(cmd *cobra.Command, args []string, rsp response) { if verbose { - fmt.Println(">", c.Command.Name, strings.Join(c.Args(), " ")) + fmt.Println(">", cmd.Use, strings.Join(args, " ")) } if !rsp.Code.IsOK() { @@ -401,11 +374,6 @@ func printResponse(c *cli.Context, rsp response) { fmt.Printf("-> proof: %X\n", rsp.Query.Proof) } } - - if verbose { - fmt.Println("") - } - } // NOTE: s is interpreted as a string unless prefixed with 0x @@ -426,3 +394,10 @@ func stringOrHexToBytes(s string) ([]byte, error) { return []byte(s[1 : len(s)-1]), nil } + +func ifExit(err error) { + if err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} diff --git a/cmd/abci-cli/main.go b/cmd/abci-cli/main.go new file mode 100644 index 00000000..736ef310 --- /dev/null +++ b/cmd/abci-cli/main.go @@ -0,0 +1,5 @@ +package main + +func main() { + Execute() +} diff --git a/glide.yaml b/glide.yaml index e8cf8302..cd8c994c 100644 --- a/glide.yaml +++ b/glide.yaml @@ -18,7 +18,7 @@ import: - log - merkle - process -- package: github.com/urfave/cli +- package: github.com/spf13/cobra - package: golang.org/x/net subpackages: - context