github.com/ethxdao/go-ethereum@v0.0.0-20221218102228-5ae34a9cc189/internal/flags/helpers.go (about) 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 17 package flags 18 19 import ( 20 "fmt" 21 "strings" 22 23 "github.com/ethxdao/go-ethereum/params" 24 ) 25 26 // NewApp creates an app with sane defaults. 27 func NewApp(gitCommit, gitDate, usage string) *cli.App { 28 app := cli.NewApp() 29 app.EnableBashCompletion = true 30 app.Version = params.VersionWithCommit(gitCommit, gitDate) 31 app.Usage = usage 32 app.Copyright = "Copyright 2013-2022 The go-ethereum Authors" 33 app.Before = func(ctx *cli.Context) error { 34 MigrateGlobalFlags(ctx) 35 return nil 36 } 37 return app 38 } 39 40 // Merge merges the given flag slices. 41 func Merge(groups ...[]cli.Flag) []cli.Flag { 42 var ret []cli.Flag 43 for _, group := range groups { 44 ret = append(ret, group...) 45 } 46 return ret 47 } 48 49 var migrationApplied = map[*cli.Command]struct{}{} 50 51 // MigrateGlobalFlags makes all global flag values available in the 52 // context. This should be called as early as possible in app.Before. 53 // 54 // Example: 55 // 56 // geth account new --keystore /tmp/mykeystore --lightkdf 57 // 58 // is equivalent after calling this method with: 59 // 60 // geth --keystore /tmp/mykeystore --lightkdf account new 61 // 62 // i.e. in the subcommand Action function of 'account new', ctx.Bool("lightkdf) 63 // will return true even if --lightkdf is set as a global option. 64 // 65 // This function may become unnecessary when https://github.com/urfave/cli/pull/1245 is merged. 66 func MigrateGlobalFlags(ctx *cli.Context) { 67 var iterate func(cs []*cli.Command, fn func(*cli.Command)) 68 iterate = func(cs []*cli.Command, fn func(*cli.Command)) { 69 for _, cmd := range cs { 70 if _, ok := migrationApplied[cmd]; ok { 71 continue 72 } 73 migrationApplied[cmd] = struct{}{} 74 fn(cmd) 75 iterate(cmd.Subcommands, fn) 76 } 77 } 78 79 // This iterates over all commands and wraps their action function. 80 iterate(ctx.App.Commands, func(cmd *cli.Command) { 81 if cmd.Action == nil { 82 return 83 } 84 85 action := cmd.Action 86 cmd.Action = func(ctx *cli.Context) error { 87 doMigrateFlags(ctx) 88 return action(ctx) 89 } 90 }) 91 } 92 93 func doMigrateFlags(ctx *cli.Context) { 94 for _, name := range ctx.FlagNames() { 95 for _, parent := range ctx.Lineage()[1:] { 96 if parent.IsSet(name) { 97 ctx.Set(name, parent.String(name)) 98 break 99 } 100 } 101 } 102 } 103 104 func init() { 105 cli.FlagStringer = FlagString 106 } 107 108 // FlagString prints a single flag in help. 109 func FlagString(f cli.Flag) string { 110 df, ok := f.(cli.DocGenerationFlag) 111 if !ok { 112 return "" 113 } 114 115 needsPlaceholder := df.TakesValue() 116 placeholder := "" 117 if needsPlaceholder { 118 placeholder = "value" 119 } 120 121 namesText := pad(cli.FlagNamePrefixer(df.Names(), placeholder), 30) 122 123 defaultValueString := "" 124 if s := df.GetDefaultText(); s != "" { 125 defaultValueString = " (default: " + s + ")" 126 } 127 128 usage := strings.TrimSpace(df.GetUsage()) 129 envHint := strings.TrimSpace(cli.FlagEnvHinter(df.GetEnvVars(), "")) 130 if len(envHint) > 0 { 131 usage += " " + envHint 132 } 133 134 usage = wordWrap(usage, 80) 135 usage = indent(usage, 10) 136 137 return fmt.Sprintf("\n %s%s\n%s", namesText, defaultValueString, usage) 138 } 139 140 func pad(s string, length int) string { 141 if len(s) < length { 142 s += strings.Repeat(" ", length-len(s)) 143 } 144 return s 145 } 146 147 func indent(s string, nspace int) string { 148 ind := strings.Repeat(" ", nspace) 149 return ind + strings.ReplaceAll(s, "\n", "\n"+ind) 150 } 151 152 func wordWrap(s string, width int) string { 153 var ( 154 output strings.Builder 155 lineLength = 0 156 ) 157 158 for { 159 sp := strings.IndexByte(s, ' ') 160 var word string 161 if sp == -1 { 162 word = s 163 } else { 164 word = s[:sp] 165 } 166 wlen := len(word) 167 over := lineLength+wlen >= width 168 if over { 169 output.WriteByte('\n') 170 lineLength = 0 171 } else { 172 if lineLength != 0 { 173 output.WriteByte(' ') 174 lineLength++ 175 } 176 } 177 178 output.WriteString(word) 179 lineLength += wlen 180 181 if sp == -1 { 182 break 183 } 184 s = s[wlen+1:] 185 } 186 187 return output.String() 188 }