helpers.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. // Copyright 2020 The go-ethereum Authors
  2. // This file is part of the go-ethereum library.
  3. //
  4. // The go-ethereum library is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU Lesser General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // The go-ethereum library is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU Lesser General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU Lesser General Public License
  15. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
  16. package flags
  17. import (
  18. "fmt"
  19. "strings"
  20. "github.com/ethereum/go-ethereum/params"
  21. "github.com/urfave/cli/v2"
  22. )
  23. // NewApp creates an app with sane defaults.
  24. func NewApp(gitCommit, gitDate, usage string) *cli.App {
  25. app := cli.NewApp()
  26. app.EnableBashCompletion = true
  27. app.Version = params.VersionWithCommit(gitCommit, gitDate)
  28. app.Usage = usage
  29. app.Copyright = "Copyright 2013-2022 The go-ethereum Authors"
  30. app.Before = func(ctx *cli.Context) error {
  31. MigrateGlobalFlags(ctx)
  32. return nil
  33. }
  34. return app
  35. }
  36. // Merge merges the given flag slices.
  37. func Merge(groups ...[]cli.Flag) []cli.Flag {
  38. var ret []cli.Flag
  39. for _, group := range groups {
  40. ret = append(ret, group...)
  41. }
  42. return ret
  43. }
  44. var migrationApplied = map[*cli.Command]struct{}{}
  45. // MigrateGlobalFlags makes all global flag values available in the
  46. // context. This should be called as early as possible in app.Before.
  47. //
  48. // Example:
  49. //
  50. // geth account new --keystore /tmp/mykeystore --lightkdf
  51. //
  52. // is equivalent after calling this method with:
  53. //
  54. // geth --keystore /tmp/mykeystore --lightkdf account new
  55. //
  56. // i.e. in the subcommand Action function of 'account new', ctx.Bool("lightkdf)
  57. // will return true even if --lightkdf is set as a global option.
  58. //
  59. // This function may become unnecessary when https://github.com/urfave/cli/pull/1245 is merged.
  60. func MigrateGlobalFlags(ctx *cli.Context) {
  61. var iterate func(cs []*cli.Command, fn func(*cli.Command))
  62. iterate = func(cs []*cli.Command, fn func(*cli.Command)) {
  63. for _, cmd := range cs {
  64. if _, ok := migrationApplied[cmd]; ok {
  65. continue
  66. }
  67. migrationApplied[cmd] = struct{}{}
  68. fn(cmd)
  69. iterate(cmd.Subcommands, fn)
  70. }
  71. }
  72. // This iterates over all commands and wraps their action function.
  73. iterate(ctx.App.Commands, func(cmd *cli.Command) {
  74. if cmd.Action == nil {
  75. return
  76. }
  77. action := cmd.Action
  78. cmd.Action = func(ctx *cli.Context) error {
  79. doMigrateFlags(ctx)
  80. return action(ctx)
  81. }
  82. })
  83. }
  84. func doMigrateFlags(ctx *cli.Context) {
  85. for _, name := range ctx.FlagNames() {
  86. for _, parent := range ctx.Lineage()[1:] {
  87. if parent.IsSet(name) {
  88. ctx.Set(name, parent.String(name))
  89. break
  90. }
  91. }
  92. }
  93. }
  94. func init() {
  95. cli.FlagStringer = FlagString
  96. }
  97. // FlagString prints a single flag in help.
  98. func FlagString(f cli.Flag) string {
  99. df, ok := f.(cli.DocGenerationFlag)
  100. if !ok {
  101. return ""
  102. }
  103. needsPlaceholder := df.TakesValue()
  104. placeholder := ""
  105. if needsPlaceholder {
  106. placeholder = "value"
  107. }
  108. namesText := pad(cli.FlagNamePrefixer(df.Names(), placeholder), 30)
  109. defaultValueString := ""
  110. if s := df.GetDefaultText(); s != "" {
  111. defaultValueString = " (default: " + s + ")"
  112. }
  113. usage := strings.TrimSpace(df.GetUsage())
  114. envHint := strings.TrimSpace(cli.FlagEnvHinter(df.GetEnvVars(), ""))
  115. if len(envHint) > 0 {
  116. usage += " " + envHint
  117. }
  118. usage = wordWrap(usage, 80)
  119. usage = indent(usage, 10)
  120. return fmt.Sprintf("\n %s%s\n%s", namesText, defaultValueString, usage)
  121. }
  122. func pad(s string, length int) string {
  123. if len(s) < length {
  124. s += strings.Repeat(" ", length-len(s))
  125. }
  126. return s
  127. }
  128. func indent(s string, nspace int) string {
  129. ind := strings.Repeat(" ", nspace)
  130. return ind + strings.ReplaceAll(s, "\n", "\n"+ind)
  131. }
  132. func wordWrap(s string, width int) string {
  133. var (
  134. output strings.Builder
  135. lineLength = 0
  136. )
  137. for {
  138. sp := strings.IndexByte(s, ' ')
  139. var word string
  140. if sp == -1 {
  141. word = s
  142. } else {
  143. word = s[:sp]
  144. }
  145. wlen := len(word)
  146. over := lineLength+wlen >= width
  147. if over {
  148. output.WriteByte('\n')
  149. lineLength = 0
  150. } else {
  151. if lineLength != 0 {
  152. output.WriteByte(' ')
  153. lineLength++
  154. }
  155. }
  156. output.WriteString(word)
  157. lineLength += wlen
  158. if sp == -1 {
  159. break
  160. }
  161. s = s[wlen+1:]
  162. }
  163. return output.String()
  164. }