github.com/ava-labs/subnet-evm@v0.6.4/internal/flags/helpers.go (about) 1 // (c) 2019-2020, Ava Labs, Inc. 2 // 3 // This file is a derived work, based on the go-ethereum library whose original 4 // notices appear below. 5 // 6 // It is distributed under a license compatible with the licensing terms of the 7 // original code from which it is derived. 8 // 9 // Much love to the original authors for their work. 10 // ********** 11 // Copyright 2020 The go-ethereum Authors 12 // This file is part of the go-ethereum library. 13 // 14 // The go-ethereum library is free software: you can redistribute it and/or modify 15 // it under the terms of the GNU Lesser General Public License as published by 16 // the Free Software Foundation, either version 3 of the License, or 17 // (at your option) any later version. 18 // 19 // The go-ethereum library is distributed in the hope that it will be useful, 20 // but WITHOUT ANY WARRANTY; without even the implied warranty of 21 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 // GNU Lesser General Public License for more details. 23 // 24 // You should have received a copy of the GNU Lesser General Public License 25 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 26 27 package flags 28 29 import ( 30 "fmt" 31 "os" 32 "regexp" 33 "sort" 34 "strings" 35 36 "github.com/ava-labs/subnet-evm/internal/version" 37 "github.com/ava-labs/subnet-evm/params" 38 "github.com/ethereum/go-ethereum/log" 39 "github.com/mattn/go-isatty" 40 "github.com/urfave/cli/v2" 41 ) 42 43 // usecolor defines whether the CLI help should use colored output or normal dumb 44 // colorless terminal formatting. 45 var usecolor = (isatty.IsTerminal(os.Stdout.Fd()) || isatty.IsCygwinTerminal(os.Stdout.Fd())) && os.Getenv("TERM") != "dumb" 46 47 // NewApp creates an app with sane defaults. 48 func NewApp(usage string) *cli.App { 49 git, _ := version.VCS() 50 app := cli.NewApp() 51 app.EnableBashCompletion = true 52 app.Version = params.VersionWithCommit(git.Commit, git.Date) 53 app.Usage = usage 54 app.Copyright = "Copyright 2013-2023 The go-ethereum Authors" 55 app.Before = func(ctx *cli.Context) error { 56 MigrateGlobalFlags(ctx) 57 return nil 58 } 59 return app 60 } 61 62 // Merge merges the given flag slices. 63 func Merge(groups ...[]cli.Flag) []cli.Flag { 64 var ret []cli.Flag 65 for _, group := range groups { 66 ret = append(ret, group...) 67 } 68 return ret 69 } 70 71 var migrationApplied = map[*cli.Command]struct{}{} 72 73 // MigrateGlobalFlags makes all global flag values available in the 74 // context. This should be called as early as possible in app.Before. 75 // 76 // Example: 77 // 78 // geth account new --keystore /tmp/mykeystore --lightkdf 79 // 80 // is equivalent after calling this method with: 81 // 82 // geth --keystore /tmp/mykeystore --lightkdf account new 83 // 84 // i.e. in the subcommand Action function of 'account new', ctx.Bool("lightkdf) 85 // will return true even if --lightkdf is set as a global option. 86 // 87 // This function may become unnecessary when https://github.com/urfave/cli/pull/1245 is merged. 88 func MigrateGlobalFlags(ctx *cli.Context) { 89 var iterate func(cs []*cli.Command, fn func(*cli.Command)) 90 iterate = func(cs []*cli.Command, fn func(*cli.Command)) { 91 for _, cmd := range cs { 92 if _, ok := migrationApplied[cmd]; ok { 93 continue 94 } 95 migrationApplied[cmd] = struct{}{} 96 fn(cmd) 97 iterate(cmd.Subcommands, fn) 98 } 99 } 100 101 // This iterates over all commands and wraps their action function. 102 iterate(ctx.App.Commands, func(cmd *cli.Command) { 103 if cmd.Action == nil { 104 return 105 } 106 107 action := cmd.Action 108 cmd.Action = func(ctx *cli.Context) error { 109 doMigrateFlags(ctx) 110 return action(ctx) 111 } 112 }) 113 } 114 115 func doMigrateFlags(ctx *cli.Context) { 116 // Figure out if there are any aliases of commands. If there are, we want 117 // to ignore them when iterating over the flags. 118 var aliases = make(map[string]bool) 119 for _, fl := range ctx.Command.Flags { 120 for _, alias := range fl.Names()[1:] { 121 aliases[alias] = true 122 } 123 } 124 for _, name := range ctx.FlagNames() { 125 for _, parent := range ctx.Lineage()[1:] { 126 if parent.IsSet(name) { 127 // When iterating across the lineage, we will be served both 128 // the 'canon' and alias formats of all commmands. In most cases, 129 // it's fine to set it in the ctx multiple times (one for each 130 // name), however, the Slice-flags are not fine. 131 // The slice-flags accumulate, so if we set it once as 132 // "foo" and once as alias "F", then both will be present in the slice. 133 if _, isAlias := aliases[name]; isAlias { 134 continue 135 } 136 // If it is a string-slice, we need to set it as 137 // "alfa, beta, gamma" instead of "[alfa beta gamma]", in order 138 // for the backing StringSlice to parse it properly. 139 if result := parent.StringSlice(name); len(result) > 0 { 140 ctx.Set(name, strings.Join(result, ",")) 141 } else { 142 ctx.Set(name, parent.String(name)) 143 } 144 break 145 } 146 } 147 } 148 } 149 150 func init() { 151 if usecolor { 152 // Annotate all help categories with colors 153 cli.AppHelpTemplate = regexp.MustCompile("[A-Z ]+:").ReplaceAllString(cli.AppHelpTemplate, "\u001B[33m$0\u001B[0m") 154 155 // Annotate flag categories with colors (private template, so need to 156 // copy-paste the entire thing here...) 157 cli.AppHelpTemplate = strings.ReplaceAll(cli.AppHelpTemplate, "{{template \"visibleFlagCategoryTemplate\" .}}", "{{range .VisibleFlagCategories}}\n {{if .Name}}\u001B[33m{{.Name}}\u001B[0m\n\n {{end}}{{$flglen := len .Flags}}{{range $i, $e := .Flags}}{{if eq (subtract $flglen $i) 1}}{{$e}}\n{{else}}{{$e}}\n {{end}}{{end}}{{end}}") 158 } 159 cli.FlagStringer = FlagString 160 } 161 162 // FlagString prints a single flag in help. 163 func FlagString(f cli.Flag) string { 164 df, ok := f.(cli.DocGenerationFlag) 165 if !ok { 166 return "" 167 } 168 needsPlaceholder := df.TakesValue() 169 placeholder := "" 170 if needsPlaceholder { 171 placeholder = "value" 172 } 173 174 namesText := cli.FlagNamePrefixer(df.Names(), placeholder) 175 176 defaultValueString := "" 177 if s := df.GetDefaultText(); s != "" { 178 defaultValueString = " (default: " + s + ")" 179 } 180 envHint := strings.TrimSpace(cli.FlagEnvHinter(df.GetEnvVars(), "")) 181 if envHint != "" { 182 envHint = " (" + envHint[1:len(envHint)-1] + ")" 183 } 184 usage := strings.TrimSpace(df.GetUsage()) 185 usage = wordWrap(usage, 80) 186 usage = indent(usage, 10) 187 188 if usecolor { 189 return fmt.Sprintf("\n \u001B[32m%-35s%-35s\u001B[0m%s\n%s", namesText, defaultValueString, envHint, usage) 190 } else { 191 return fmt.Sprintf("\n %-35s%-35s%s\n%s", namesText, defaultValueString, envHint, usage) 192 } 193 } 194 195 func indent(s string, nspace int) string { 196 ind := strings.Repeat(" ", nspace) 197 return ind + strings.ReplaceAll(s, "\n", "\n"+ind) 198 } 199 200 func wordWrap(s string, width int) string { 201 var ( 202 output strings.Builder 203 lineLength = 0 204 ) 205 206 for { 207 sp := strings.IndexByte(s, ' ') 208 var word string 209 if sp == -1 { 210 word = s 211 } else { 212 word = s[:sp] 213 } 214 wlen := len(word) 215 over := lineLength+wlen >= width 216 if over { 217 output.WriteByte('\n') 218 lineLength = 0 219 } else { 220 if lineLength != 0 { 221 output.WriteByte(' ') 222 lineLength++ 223 } 224 } 225 226 output.WriteString(word) 227 lineLength += wlen 228 229 if sp == -1 { 230 break 231 } 232 s = s[wlen+1:] 233 } 234 235 return output.String() 236 } 237 238 // AutoEnvVars extends all the specific CLI flags with automatically generated 239 // env vars by capitalizing the flag, replacing . with _ and prefixing it with 240 // the specified string. 241 // 242 // Note, the prefix should *not* contain the separator underscore, that will be 243 // added automatically. 244 func AutoEnvVars(flags []cli.Flag, prefix string) { 245 for _, flag := range flags { 246 envvar := strings.ToUpper(prefix + "_" + strings.ReplaceAll(strings.ReplaceAll(flag.Names()[0], ".", "_"), "-", "_")) 247 248 switch flag := flag.(type) { 249 case *cli.StringFlag: 250 flag.EnvVars = append(flag.EnvVars, envvar) 251 252 case *cli.BoolFlag: 253 flag.EnvVars = append(flag.EnvVars, envvar) 254 255 case *cli.IntFlag: 256 flag.EnvVars = append(flag.EnvVars, envvar) 257 258 case *cli.Uint64Flag: 259 flag.EnvVars = append(flag.EnvVars, envvar) 260 261 case *cli.DurationFlag: 262 flag.EnvVars = append(flag.EnvVars, envvar) 263 264 case *cli.PathFlag: 265 flag.EnvVars = append(flag.EnvVars, envvar) 266 267 case *BigFlag: 268 flag.EnvVars = append(flag.EnvVars, envvar) 269 270 case *TextMarshalerFlag: 271 flag.EnvVars = append(flag.EnvVars, envvar) 272 273 case *DirectoryFlag: 274 flag.EnvVars = append(flag.EnvVars, envvar) 275 } 276 } 277 } 278 279 // CheckEnvVars iterates over all the environment variables and checks if any of 280 // them look like a CLI flag but is not consumed. This can be used to detect old 281 // or mistyped names. 282 func CheckEnvVars(ctx *cli.Context, flags []cli.Flag, prefix string) { 283 known := make(map[string]string) 284 for _, flag := range flags { 285 docflag, ok := flag.(cli.DocGenerationFlag) 286 if !ok { 287 continue 288 } 289 for _, envvar := range docflag.GetEnvVars() { 290 known[envvar] = flag.Names()[0] 291 } 292 } 293 keyvals := os.Environ() 294 sort.Strings(keyvals) 295 296 for _, keyval := range keyvals { 297 key := strings.Split(keyval, "=")[0] 298 if !strings.HasPrefix(key, prefix) { 299 continue 300 } 301 if flag, ok := known[key]; ok { 302 if ctx.Count(flag) > 0 { 303 log.Info("Config environment variable found", "envvar", key, "shadowedby", "--"+flag) 304 } else { 305 log.Info("Config environment variable found", "envvar", key) 306 } 307 } else { 308 log.Warn("Unknown config environment variable", "envvar", key) 309 } 310 } 311 }