github.com/vipernet-xyz/tm@v0.34.24/libs/cli/setup.go (about)

     1  package cli
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path/filepath"
     7  	"runtime"
     8  	"strings"
     9  
    10  	"github.com/spf13/cobra"
    11  	"github.com/spf13/viper"
    12  )
    13  
    14  const (
    15  	HomeFlag     = "home"
    16  	TraceFlag    = "trace"
    17  	OutputFlag   = "output"
    18  	EncodingFlag = "encoding"
    19  )
    20  
    21  // Executable is the minimal interface to *corba.Command, so we can
    22  // wrap if desired before the test
    23  type Executable interface {
    24  	Execute() error
    25  }
    26  
    27  // PrepareBaseCmd is meant for tendermint and other servers
    28  func PrepareBaseCmd(cmd *cobra.Command, envPrefix, defaultHome string) Executor {
    29  	cobra.OnInitialize(func() { initEnv(envPrefix) })
    30  	cmd.PersistentFlags().StringP(HomeFlag, "", defaultHome, "directory for config and data")
    31  	cmd.PersistentFlags().Bool(TraceFlag, false, "print out full stack trace on errors")
    32  	cmd.PersistentPreRunE = concatCobraCmdFuncs(bindFlagsLoadViper, cmd.PersistentPreRunE)
    33  	return Executor{cmd, os.Exit}
    34  }
    35  
    36  // PrepareMainCmd is meant for client side libs that want some more flags
    37  //
    38  // This adds --encoding (hex, btc, base64) and --output (text, json) to
    39  // the command.  These only really make sense in interactive commands.
    40  func PrepareMainCmd(cmd *cobra.Command, envPrefix, defaultHome string) Executor {
    41  	cmd.PersistentFlags().StringP(EncodingFlag, "e", "hex", "Binary encoding (hex|b64|btc)")
    42  	cmd.PersistentFlags().StringP(OutputFlag, "o", "text", "Output format (text|json)")
    43  	cmd.PersistentPreRunE = concatCobraCmdFuncs(validateOutput, cmd.PersistentPreRunE)
    44  	return PrepareBaseCmd(cmd, envPrefix, defaultHome)
    45  }
    46  
    47  // initEnv sets to use ENV variables if set.
    48  func initEnv(prefix string) {
    49  	copyEnvVars(prefix)
    50  
    51  	// env variables with TM prefix (eg. TM_ROOT)
    52  	viper.SetEnvPrefix(prefix)
    53  	viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_"))
    54  	viper.AutomaticEnv()
    55  }
    56  
    57  // This copies all variables like TMROOT to TM_ROOT,
    58  // so we can support both formats for the user
    59  func copyEnvVars(prefix string) {
    60  	prefix = strings.ToUpper(prefix)
    61  	ps := prefix + "_"
    62  	for _, e := range os.Environ() {
    63  		kv := strings.SplitN(e, "=", 2)
    64  		if len(kv) == 2 {
    65  			k, v := kv[0], kv[1]
    66  			if strings.HasPrefix(k, prefix) && !strings.HasPrefix(k, ps) {
    67  				k2 := strings.Replace(k, prefix, ps, 1)
    68  				os.Setenv(k2, v)
    69  			}
    70  		}
    71  	}
    72  }
    73  
    74  // Executor wraps the cobra Command with a nicer Execute method
    75  type Executor struct {
    76  	*cobra.Command
    77  	Exit func(int) // this is os.Exit by default, override in tests
    78  }
    79  
    80  type ExitCoder interface {
    81  	ExitCode() int
    82  }
    83  
    84  // execute adds all child commands to the root command sets flags appropriately.
    85  // This is called by main.main(). It only needs to happen once to the rootCmd.
    86  func (e Executor) Execute() error {
    87  	e.SilenceUsage = true
    88  	e.SilenceErrors = true
    89  	err := e.Command.Execute()
    90  	if err != nil {
    91  		if viper.GetBool(TraceFlag) {
    92  			const size = 64 << 10
    93  			buf := make([]byte, size)
    94  			buf = buf[:runtime.Stack(buf, false)]
    95  			fmt.Fprintf(os.Stderr, "ERROR: %v\n%s\n", err, buf)
    96  		} else {
    97  			fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
    98  		}
    99  
   100  		// return error code 1 by default, can override it with a special error type
   101  		exitCode := 1
   102  		if ec, ok := err.(ExitCoder); ok {
   103  			exitCode = ec.ExitCode()
   104  		}
   105  		e.Exit(exitCode)
   106  	}
   107  	return err
   108  }
   109  
   110  type cobraCmdFunc func(cmd *cobra.Command, args []string) error
   111  
   112  // Returns a single function that calls each argument function in sequence
   113  // RunE, PreRunE, PersistentPreRunE, etc. all have this same signature
   114  func concatCobraCmdFuncs(fs ...cobraCmdFunc) cobraCmdFunc {
   115  	return func(cmd *cobra.Command, args []string) error {
   116  		for _, f := range fs {
   117  			if f != nil {
   118  				if err := f(cmd, args); err != nil {
   119  					return err
   120  				}
   121  			}
   122  		}
   123  		return nil
   124  	}
   125  }
   126  
   127  // Bind all flags and read the config into viper
   128  func bindFlagsLoadViper(cmd *cobra.Command, args []string) error {
   129  	// cmd.Flags() includes flags from this command and all persistent flags from the parent
   130  	if err := viper.BindPFlags(cmd.Flags()); err != nil {
   131  		return err
   132  	}
   133  
   134  	homeDir := viper.GetString(HomeFlag)
   135  	viper.Set(HomeFlag, homeDir)
   136  	viper.SetConfigName("config")                         // name of config file (without extension)
   137  	viper.AddConfigPath(homeDir)                          // search root directory
   138  	viper.AddConfigPath(filepath.Join(homeDir, "config")) // search root directory /config
   139  
   140  	// If a config file is found, read it in.
   141  	if err := viper.ReadInConfig(); err == nil {
   142  		// stderr, so if we redirect output to json file, this doesn't appear
   143  		// fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed())
   144  	} else if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
   145  		// ignore not found error, return other errors
   146  		return err
   147  	}
   148  	return nil
   149  }
   150  
   151  func validateOutput(cmd *cobra.Command, args []string) error {
   152  	// validate output format
   153  	output := viper.GetString(OutputFlag)
   154  	switch output {
   155  	case "text", "json":
   156  	default:
   157  		return fmt.Errorf("unsupported output format: %s", output)
   158  	}
   159  	return nil
   160  }