github.com/btcsuite/btcd@v0.24.0/cmd/btcctl/btcctl.go (about) 1 package main 2 3 import ( 4 "bufio" 5 "bytes" 6 "encoding/json" 7 "fmt" 8 "io" 9 "os" 10 "path/filepath" 11 "strings" 12 13 "github.com/btcsuite/btcd/btcjson" 14 ) 15 16 const ( 17 showHelpMessage = "Specify -h to show available options" 18 listCmdMessage = "Specify -l to list available commands" 19 ) 20 21 // commandUsage display the usage for a specific command. 22 func commandUsage(method string) { 23 usage, err := btcjson.MethodUsageText(method) 24 if err != nil { 25 // This should never happen since the method was already checked 26 // before calling this function, but be safe. 27 fmt.Fprintln(os.Stderr, "Failed to obtain command usage:", err) 28 return 29 } 30 31 fmt.Fprintln(os.Stderr, "Usage:") 32 fmt.Fprintf(os.Stderr, " %s\n", usage) 33 } 34 35 // usage displays the general usage when the help flag is not displayed and 36 // and an invalid command was specified. The commandUsage function is used 37 // instead when a valid command was specified. 38 func usage(errorMessage string) { 39 appName := filepath.Base(os.Args[0]) 40 appName = strings.TrimSuffix(appName, filepath.Ext(appName)) 41 fmt.Fprintln(os.Stderr, errorMessage) 42 fmt.Fprintln(os.Stderr, "Usage:") 43 fmt.Fprintf(os.Stderr, " %s [OPTIONS] <command> <args...>\n\n", 44 appName) 45 fmt.Fprintln(os.Stderr, showHelpMessage) 46 fmt.Fprintln(os.Stderr, listCmdMessage) 47 } 48 49 func main() { 50 cfg, args, err := loadConfig() 51 if err != nil { 52 os.Exit(1) 53 } 54 if len(args) < 1 { 55 usage("No command specified") 56 os.Exit(1) 57 } 58 59 // Ensure the specified method identifies a valid registered command and 60 // is one of the usable types. 61 method := args[0] 62 usageFlags, err := btcjson.MethodUsageFlags(method) 63 if err != nil { 64 fmt.Fprintf(os.Stderr, "Unrecognized command '%s'\n", method) 65 fmt.Fprintln(os.Stderr, listCmdMessage) 66 os.Exit(1) 67 } 68 if usageFlags&unusableFlags != 0 { 69 fmt.Fprintf(os.Stderr, "The '%s' command can only be used via "+ 70 "websockets\n", method) 71 fmt.Fprintln(os.Stderr, listCmdMessage) 72 os.Exit(1) 73 } 74 75 // Convert remaining command line args to a slice of interface values 76 // to be passed along as parameters to new command creation function. 77 // 78 // Since some commands, such as submitblock, can involve data which is 79 // too large for the Operating System to allow as a normal command line 80 // parameter, support using '-' as an argument to allow the argument 81 // to be read from a stdin pipe. 82 bio := bufio.NewReader(os.Stdin) 83 params := make([]interface{}, 0, len(args[1:])) 84 for _, arg := range args[1:] { 85 if arg == "-" { 86 param, err := bio.ReadString('\n') 87 if err != nil && err != io.EOF { 88 fmt.Fprintf(os.Stderr, "Failed to read data "+ 89 "from stdin: %v\n", err) 90 os.Exit(1) 91 } 92 if err == io.EOF && len(param) == 0 { 93 fmt.Fprintln(os.Stderr, "Not enough lines "+ 94 "provided on stdin") 95 os.Exit(1) 96 } 97 param = strings.TrimRight(param, "\r\n") 98 params = append(params, param) 99 continue 100 } 101 102 params = append(params, arg) 103 } 104 105 // Attempt to create the appropriate command using the arguments 106 // provided by the user. 107 cmd, err := btcjson.NewCmd(method, params...) 108 if err != nil { 109 // Show the error along with its error code when it's a 110 // btcjson.Error as it reallistcally will always be since the 111 // NewCmd function is only supposed to return errors of that 112 // type. 113 if jerr, ok := err.(btcjson.Error); ok { 114 fmt.Fprintf(os.Stderr, "%s command: %v (code: %s)\n", 115 method, err, jerr.ErrorCode) 116 commandUsage(method) 117 os.Exit(1) 118 } 119 120 // The error is not a btcjson.Error and this really should not 121 // happen. Nevertheless, fallback to just showing the error 122 // if it should happen due to a bug in the package. 123 fmt.Fprintf(os.Stderr, "%s command: %v\n", method, err) 124 commandUsage(method) 125 os.Exit(1) 126 } 127 128 // Marshal the command into a JSON-RPC byte slice in preparation for 129 // sending it to the RPC server. 130 marshalledJSON, err := btcjson.MarshalCmd(btcjson.RpcVersion1, 1, cmd) 131 if err != nil { 132 fmt.Fprintln(os.Stderr, err) 133 os.Exit(1) 134 } 135 136 // Send the JSON-RPC request to the server using the user-specified 137 // connection configuration. 138 result, err := sendPostRequest(marshalledJSON, cfg) 139 if err != nil { 140 fmt.Fprintln(os.Stderr, err) 141 os.Exit(1) 142 } 143 144 // Choose how to display the result based on its type. 145 strResult := string(result) 146 if strings.HasPrefix(strResult, "{") || strings.HasPrefix(strResult, "[") { 147 var dst bytes.Buffer 148 if err := json.Indent(&dst, result, "", " "); err != nil { 149 fmt.Fprintf(os.Stderr, "Failed to format result: %v", 150 err) 151 os.Exit(1) 152 } 153 fmt.Println(dst.String()) 154 155 } else if strings.HasPrefix(strResult, `"`) { 156 var str string 157 if err := json.Unmarshal(result, &str); err != nil { 158 fmt.Fprintf(os.Stderr, "Failed to unmarshal result: %v", 159 err) 160 os.Exit(1) 161 } 162 fmt.Println(str) 163 164 } else if strResult != "null" { 165 fmt.Println(strResult) 166 } 167 }