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