github.com/devwanda/aphelion-staking@v0.33.9/libs/cli/setup.go (about) 1 package cli 2 3 import ( 4 "fmt" 5 "os" 6 "path/filepath" 7 "strings" 8 9 "github.com/pkg/errors" 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 fmt.Fprintf(os.Stderr, "ERROR: %+v\n", err) 93 } else { 94 fmt.Fprintf(os.Stderr, "ERROR: %v\n", err) 95 } 96 97 // return error code 1 by default, can override it with a special error type 98 exitCode := 1 99 if ec, ok := err.(ExitCoder); ok { 100 exitCode = ec.ExitCode() 101 } 102 e.Exit(exitCode) 103 } 104 return err 105 } 106 107 type cobraCmdFunc func(cmd *cobra.Command, args []string) error 108 109 // Returns a single function that calls each argument function in sequence 110 // RunE, PreRunE, PersistentPreRunE, etc. all have this same signature 111 func concatCobraCmdFuncs(fs ...cobraCmdFunc) cobraCmdFunc { 112 return func(cmd *cobra.Command, args []string) error { 113 for _, f := range fs { 114 if f != nil { 115 if err := f(cmd, args); err != nil { 116 return err 117 } 118 } 119 } 120 return nil 121 } 122 } 123 124 // Bind all flags and read the config into viper 125 func bindFlagsLoadViper(cmd *cobra.Command, args []string) error { 126 // cmd.Flags() includes flags from this command and all persistent flags from the parent 127 if err := viper.BindPFlags(cmd.Flags()); err != nil { 128 return err 129 } 130 131 homeDir := viper.GetString(HomeFlag) 132 viper.Set(HomeFlag, homeDir) 133 viper.SetConfigName("config") // name of config file (without extension) 134 viper.AddConfigPath(homeDir) // search root directory 135 viper.AddConfigPath(filepath.Join(homeDir, "config")) // search root directory /config 136 137 // If a config file is found, read it in. 138 if err := viper.ReadInConfig(); err == nil { 139 // stderr, so if we redirect output to json file, this doesn't appear 140 // fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed()) 141 } else if _, ok := err.(viper.ConfigFileNotFoundError); !ok { 142 // ignore not found error, return other errors 143 return err 144 } 145 return nil 146 } 147 148 func validateOutput(cmd *cobra.Command, args []string) error { 149 // validate output format 150 output := viper.GetString(OutputFlag) 151 switch output { 152 case "text", "json": 153 default: 154 return errors.Errorf("unsupported output format: %s", output) 155 } 156 return nil 157 }