You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

144 lines
4.2 KiB

  1. package cli
  2. import (
  3. "fmt"
  4. "os"
  5. "strings"
  6. "github.com/pkg/errors"
  7. "github.com/spf13/cobra"
  8. "github.com/spf13/viper"
  9. data "github.com/tendermint/go-wire/data"
  10. "github.com/tendermint/go-wire/data/base58"
  11. )
  12. const (
  13. RootFlag = "root"
  14. HomeFlag = "home"
  15. OutputFlag = "output"
  16. EncodingFlag = "encoding"
  17. )
  18. // PrepareBaseCmd is meant for tendermint and other servers
  19. func PrepareBaseCmd(cmd *cobra.Command, envPrefix, defautRoot string) func() {
  20. cobra.OnInitialize(func() { initEnv(envPrefix) })
  21. cmd.PersistentFlags().StringP(RootFlag, "r", defautRoot, "DEPRECATED. Use --home")
  22. cmd.PersistentFlags().StringP(HomeFlag, "h", defautRoot, "root directory for config and data")
  23. cmd.PersistentPreRunE = multiE(bindFlags, cmd.PersistentPreRunE)
  24. return func() { execute(cmd) }
  25. }
  26. // PrepareMainCmd is meant for client side libs that want some more flags
  27. func PrepareMainCmd(cmd *cobra.Command, envPrefix, defautRoot string) func() {
  28. cmd.PersistentFlags().StringP(EncodingFlag, "e", "hex", "Binary encoding (hex|b64|btc)")
  29. cmd.PersistentFlags().StringP(OutputFlag, "o", "text", "Output format (text|json)")
  30. cmd.PersistentPreRunE = multiE(setEncoding, validateOutput, cmd.PersistentPreRunE)
  31. return PrepareBaseCmd(cmd, envPrefix, defautRoot)
  32. }
  33. // initEnv sets to use ENV variables if set.
  34. func initEnv(prefix string) {
  35. copyEnvVars(prefix)
  36. // env variables with TM prefix (eg. TM_ROOT)
  37. viper.SetEnvPrefix(prefix)
  38. viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
  39. viper.AutomaticEnv()
  40. }
  41. // This copies all variables like TMROOT to TM_ROOT,
  42. // so we can support both formats for the user
  43. func copyEnvVars(prefix string) {
  44. prefix = strings.ToUpper(prefix)
  45. ps := prefix + "_"
  46. for _, e := range os.Environ() {
  47. kv := strings.SplitN(e, "=", 2)
  48. if len(kv) == 2 {
  49. k, v := kv[0], kv[1]
  50. if strings.HasPrefix(k, prefix) && !strings.HasPrefix(k, ps) {
  51. k2 := strings.Replace(k, prefix, ps, 1)
  52. os.Setenv(k2, v)
  53. }
  54. }
  55. }
  56. }
  57. // execute adds all child commands to the root command sets flags appropriately.
  58. // This is called by main.main(). It only needs to happen once to the rootCmd.
  59. func execute(cmd *cobra.Command) {
  60. // TODO: this can do something cooler with debug and log-levels
  61. if err := cmd.Execute(); err != nil {
  62. fmt.Println(err)
  63. os.Exit(-1)
  64. }
  65. }
  66. type wrapE func(cmd *cobra.Command, args []string) error
  67. func multiE(fs ...wrapE) wrapE {
  68. return func(cmd *cobra.Command, args []string) error {
  69. for _, f := range fs {
  70. if f != nil {
  71. if err := f(cmd, args); err != nil {
  72. return err
  73. }
  74. }
  75. }
  76. return nil
  77. }
  78. }
  79. func bindFlags(cmd *cobra.Command, args []string) error {
  80. // cmd.Flags() includes flags from this command and all persistent flags from the parent
  81. if err := viper.BindPFlags(cmd.Flags()); err != nil {
  82. return err
  83. }
  84. // rootDir is command line flag, env variable, or default $HOME/.tlc
  85. // NOTE: we support both --root and --home for now, but eventually only --home
  86. rootDir := viper.GetString(HomeFlag)
  87. if !viper.IsSet(HomeFlag) && viper.IsSet(RootFlag) {
  88. rootDir = viper.GetString(RootFlag)
  89. }
  90. viper.SetConfigName("config") // name of config file (without extension)
  91. viper.AddConfigPath(rootDir) // search root directory
  92. // If a config file is found, read it in.
  93. if err := viper.ReadInConfig(); err == nil {
  94. // stderr, so if we redirect output to json file, this doesn't appear
  95. // fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed())
  96. } else if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
  97. // we ignore not found error, only parse error
  98. // stderr, so if we redirect output to json file, this doesn't appear
  99. fmt.Fprintf(os.Stderr, "%#v", err)
  100. }
  101. return nil
  102. }
  103. // setEncoding reads the encoding flag
  104. func setEncoding(cmd *cobra.Command, args []string) error {
  105. // validate and set encoding
  106. enc := viper.GetString("encoding")
  107. switch enc {
  108. case "hex":
  109. data.Encoder = data.HexEncoder
  110. case "b64":
  111. data.Encoder = data.B64Encoder
  112. case "btc":
  113. data.Encoder = base58.BTCEncoder
  114. default:
  115. return errors.Errorf("Unsupported encoding: %s", enc)
  116. }
  117. return nil
  118. }
  119. func validateOutput(cmd *cobra.Command, args []string) error {
  120. // validate output format
  121. output := viper.GetString(OutputFlag)
  122. switch output {
  123. case "text", "json":
  124. default:
  125. return errors.Errorf("Unsupported output format: %s", output)
  126. }
  127. return nil
  128. }