github.com/MagHErmit/tendermint@v0.282.1/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 }