github.com/ari-anchor/sei-tendermint@v0.0.0-20230519144642-dc826b7b56bb/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" // used in the cli
    18  )
    19  
    20  type Executable interface {
    21  	Execute() error
    22  }
    23  
    24  // PrepareBaseCmd is meant for tendermint and other servers
    25  func PrepareBaseCmd(cmd *cobra.Command, envPrefix, defaultHome string) *cobra.Command {
    26  	// the primary caller of this command is in the SDK and
    27  	// returning the cobra.Command object avoids breaking that
    28  	// code. In the long term, the SDK could avoid this entirely.
    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 cmd
    34  }
    35  
    36  // InitEnv sets to use ENV variables if set.
    37  func InitEnv(prefix string) {
    38  	// This copies all variables like TMROOT to TM_ROOT,
    39  	// so we can support both formats for the user
    40  	prefix = strings.ToUpper(prefix)
    41  	ps := prefix + "_"
    42  	for _, e := range os.Environ() {
    43  		kv := strings.SplitN(e, "=", 2)
    44  		if len(kv) == 2 {
    45  			k, v := kv[0], kv[1]
    46  			if strings.HasPrefix(k, prefix) && !strings.HasPrefix(k, ps) {
    47  				k2 := strings.Replace(k, prefix, ps, 1)
    48  				os.Setenv(k2, v)
    49  			}
    50  		}
    51  	}
    52  
    53  	// env variables with TM prefix (eg. TM_ROOT)
    54  	viper.SetEnvPrefix(prefix)
    55  	viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_"))
    56  	viper.AutomaticEnv()
    57  }
    58  
    59  type cobraCmdFunc func(cmd *cobra.Command, args []string) error
    60  
    61  // Returns a single function that calls each argument function in sequence
    62  // RunE, PreRunE, PersistentPreRunE, etc. all have this same signature
    63  func concatCobraCmdFuncs(fs ...cobraCmdFunc) cobraCmdFunc {
    64  	return func(cmd *cobra.Command, args []string) error {
    65  		for _, f := range fs {
    66  			if f != nil {
    67  				if err := f(cmd, args); err != nil {
    68  					return err
    69  				}
    70  			}
    71  		}
    72  		return nil
    73  	}
    74  }
    75  
    76  // Bind all flags and read the config into viper
    77  func BindFlagsLoadViper(cmd *cobra.Command, args []string) error {
    78  	// cmd.Flags() includes flags from this command and all persistent flags from the parent
    79  	if err := viper.BindPFlags(cmd.Flags()); err != nil {
    80  		return err
    81  	}
    82  
    83  	homeDir := viper.GetString(HomeFlag)
    84  	viper.Set(HomeFlag, homeDir)
    85  	viper.SetConfigName("config")                         // name of config file (without extension)
    86  	viper.AddConfigPath(homeDir)                          // search root directory
    87  	viper.AddConfigPath(filepath.Join(homeDir, "config")) // search root directory /config
    88  
    89  	// If a config file is found, read it in.
    90  	if err := viper.ReadInConfig(); err == nil {
    91  		// stderr, so if we redirect output to json file, this doesn't appear
    92  		// fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed())
    93  	} else if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
    94  		// ignore not found error, return other errors
    95  		return err
    96  	}
    97  	return nil
    98  }
    99  
   100  // Executor wraps the cobra Command with a nicer Execute method
   101  type Executor struct {
   102  	*cobra.Command
   103  	Exit func(int) // this is os.Exit by default, override in tests
   104  }
   105  
   106  type ExitCoder interface {
   107  	ExitCode() int
   108  }
   109  
   110  // execute adds all child commands to the root command sets flags appropriately.
   111  // This is called by main.main(). It only needs to happen once to the rootCmd.
   112  func (e Executor) Execute() error {
   113  	e.SilenceUsage = true
   114  	e.SilenceErrors = true
   115  	err := e.Command.Execute()
   116  	if err != nil {
   117  		if viper.GetBool(TraceFlag) {
   118  			const size = 64 << 10
   119  			buf := make([]byte, size)
   120  			buf = buf[:runtime.Stack(buf, false)]
   121  			fmt.Fprintf(os.Stderr, "ERROR: %v\n%s\n", err, buf)
   122  		} else {
   123  			fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
   124  		}
   125  
   126  		// return error code 1 by default, can override it with a special error type
   127  		exitCode := 1
   128  		if ec, ok := err.(ExitCoder); ok {
   129  			exitCode = ec.ExitCode()
   130  		}
   131  		e.Exit(exitCode)
   132  	}
   133  	return err
   134  }
   135  
   136  // Bind all flags and read the config into viper
   137  func bindFlagsLoadViper(cmd *cobra.Command, args []string) error {
   138  	// cmd.Flags() includes flags from this command and all persistent flags from the parent
   139  	if err := viper.BindPFlags(cmd.Flags()); err != nil {
   140  		return err
   141  	}
   142  
   143  	homeDir := viper.GetString(HomeFlag)
   144  	viper.Set(HomeFlag, homeDir)
   145  	viper.SetConfigName("config")                         // name of config file (without extension)
   146  	viper.AddConfigPath(homeDir)                          // search root directory
   147  	viper.AddConfigPath(filepath.Join(homeDir, "config")) // search root directory /config
   148  
   149  	// If a config file is found, read it in.
   150  	if err := viper.ReadInConfig(); err == nil {
   151  		// stderr, so if we redirect output to json file, this doesn't appear
   152  		// fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed())
   153  	} else if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
   154  		// ignore not found error, return other errors
   155  		return err
   156  	}
   157  	return nil
   158  }
   159  
   160  func validateOutput(cmd *cobra.Command, args []string) error {
   161  	// validate output format
   162  	output := viper.GetString(OutputFlag)
   163  	switch output {
   164  	case "text", "json":
   165  	default:
   166  		return fmt.Errorf("unsupported output format: %s", output)
   167  	}
   168  	return nil
   169  }