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 }