lbcd/cmd/btcctl/btcctl.go
Dave Collins 5a800b9580 Rewrite btcctl to use the new features of btcjson.
This commit contains what is essentially a complete rewrite of the btcctl
utility to make use of the new features provided by the latest version
btcjson and improve several things along the way.  The following
summarizes the changes:

- The supported commands and handling now come directly from btcjson, so
  it is no longer necessary to manually add new commands.  Once a command
  has been registered with btcjson, it will automatically become usable by
  btcctl complete with full error handling (once it is re-compiled of
  course)
- Rather than dumping the entire list of commands on every error, the user
  now must specifically request the list of command via the -l option
- The list of commands is now categorized by chain and wallet and
  alphabetized
- The help flag now only shows the help options instead of also dumping
  all of the commands
- The error display on valid commands with invalid parameters has been
  greatly improved to show the specific parameter number, reason, and
  error code
- When a valid command is specified with invalid parameter, only the usage
  for that specific command is shown now
- It is now possible to use a SOCKS5 proxy for connection
- The output of commands has been improved in the following ways:
  - Strings on commands such as getbestblockhash no longer have quotes
    wrapped around them
  - Fields that are integers no longer show in scientific notation when
    they are large (timestamps for example)

This closes #305 as a side effect.
2015-02-25 16:03:31 -06:00

142 lines
4 KiB
Go

package main
import (
"bytes"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/btcsuite/btcd/btcjson/v2/btcjson"
)
const (
showHelpMessage = "Specify -h to show available options"
listCmdMessage = "Specify -l to list available commands"
)
// commandUsage display the usage for a specific command.
func commandUsage(method string) {
usage, err := btcjson.MethodUsageText(method)
if err != nil {
// This should never happen since the method was already checked
// before calling this function, but be safe.
fmt.Fprintln(os.Stderr, "Failed to obtain command usage:", err)
return
}
fmt.Fprintln(os.Stderr, "Usage:")
fmt.Fprintf(os.Stderr, " %s\n", usage)
}
// usage displays the general usage when the help flag is not displayed and
// and an invalid command was specified. The commandUsage function is used
// instead when a valid command was specified.
func usage(errorMessage string) {
appName := filepath.Base(os.Args[0])
appName = strings.TrimSuffix(appName, filepath.Ext(appName))
fmt.Fprintln(os.Stderr, errorMessage)
fmt.Fprintln(os.Stderr, "Usage:")
fmt.Fprintf(os.Stderr, " %s [OPTIONS] <command> <args...>\n\n",
appName)
fmt.Fprintln(os.Stderr, showHelpMessage)
fmt.Fprintln(os.Stderr, listCmdMessage)
}
func main() {
cfg, args, err := loadConfig()
if err != nil {
os.Exit(1)
}
if len(args) < 1 {
usage("No command specified")
os.Exit(1)
}
// Ensure the specified method identifies a valid registered command and
// is one of the usable types.
method := args[0]
usageFlags, err := btcjson.MethodUsageFlags(method)
if err != nil {
fmt.Fprintf(os.Stderr, "Unrecognized command '%s'\n", method)
fmt.Fprintln(os.Stderr, listCmdMessage)
os.Exit(1)
}
if usageFlags&unusableFlags != 0 {
fmt.Fprintf(os.Stderr, "The '%s' command can only be used via "+
"websockets\n", method)
fmt.Fprintln(os.Stderr, listCmdMessage)
os.Exit(1)
}
// Convert remaining command line args to a slice of interface values
// to be passed along as parameters to new command creation function.
params := make([]interface{}, 0, len(args[1:]))
for _, arg := range args[1:] {
params = append(params, arg)
}
// Attempt to create the appropriate command using the arguments
// provided by the user.
cmd, err := btcjson.NewCmd(method, params...)
if err != nil {
// Show the error along with its error code when it's a
// btcjson.Error as it reallistcally will always be since the
// NewCmd function is only supposed to return errors of that
// type.
if jerr, ok := err.(btcjson.Error); ok {
fmt.Fprintf(os.Stderr, "%s command: %v (code: %s)\n",
method, err, jerr.ErrorCode)
commandUsage(method)
os.Exit(1)
}
// The error is not a btcjson.Error and this really should not
// happen. Nevertheless, fallback to just showing the error
// if it should happen due to a bug in the package.
fmt.Fprintf(os.Stderr, "%s command: %v\n", method, err)
commandUsage(method)
os.Exit(1)
}
// Marshal the command into a JSON-RPC byte slice in preparation for
// sending it to the RPC server.
marshalledJSON, err := btcjson.MarshalCmd(1, cmd)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
// Send the JSON-RPC request to the server using the user-specified
// connection configuration.
result, err := sendPostRequest(marshalledJSON, cfg)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
// Choose how to display the result based on its type.
strResult := string(result)
if strings.HasPrefix(strResult, "{") || strings.HasPrefix(strResult, "[") {
var dst bytes.Buffer
if err := json.Indent(&dst, result, "", " "); err != nil {
fmt.Fprintf(os.Stderr, "Failed to format result: %v",
err)
os.Exit(1)
}
fmt.Println(dst.String())
} else if strings.HasPrefix(strResult, `"`) {
var str string
if err := json.Unmarshal(result, &str); err != nil {
fmt.Fprintf(os.Stderr, "Failed to unmarshal result: %v",
err)
os.Exit(1)
}
fmt.Println(str)
} else if strResult != "null" {
fmt.Println(strResult)
}
}