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  }