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