You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
167 lines
4.7 KiB
167 lines
4.7 KiB
package main |
|
|
|
import ( |
|
"bufio" |
|
"bytes" |
|
"encoding/json" |
|
"fmt" |
|
"io" |
|
"os" |
|
"path/filepath" |
|
"strings" |
|
|
|
"github.com/btcsuite/btcd/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. |
|
// |
|
// Since some commands, such as submitblock, can involve data which is |
|
// too large for the Operating System to allow as a normal command line |
|
// parameter, support using '-' as an argument to allow the argument |
|
// to be read from a stdin pipe. |
|
bio := bufio.NewReader(os.Stdin) |
|
params := make([]interface{}, 0, len(args[1:])) |
|
for _, arg := range args[1:] { |
|
if arg == "-" { |
|
param, err := bio.ReadString('\n') |
|
if err != nil && err != io.EOF { |
|
fmt.Fprintf(os.Stderr, "Failed to read data "+ |
|
"from stdin: %v\n", err) |
|
os.Exit(1) |
|
} |
|
if err == io.EOF && len(param) == 0 { |
|
fmt.Fprintln(os.Stderr, "Not enough lines "+ |
|
"provided on stdin") |
|
os.Exit(1) |
|
} |
|
param = strings.TrimRight(param, "\r\n") |
|
params = append(params, param) |
|
continue |
|
} |
|
|
|
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) |
|
} |
|
}
|
|
|