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  }