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  }