github.com/carter-ya/go-ethereum@v0.0.0-20230628080049-d2309be3983b/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/ethereum/go-ethereum/internal/version"
    24  	"github.com/ethereum/go-ethereum/params"
    25  	"github.com/urfave/cli/v2"
    26  )
    27  
    28  // NewApp creates an app with sane defaults.
    29  func NewApp(usage string) *cli.App {
    30  	git, _ := version.VCS()
    31  	app := cli.NewApp()
    32  	app.EnableBashCompletion = true
    33  	app.Version = params.VersionWithCommit(git.Commit, git.Date)
    34  	app.Usage = usage
    35  	app.Copyright = "Copyright 2013-2022 The go-ethereum Authors"
    36  	app.Before = func(ctx *cli.Context) error {
    37  		MigrateGlobalFlags(ctx)
    38  		return nil
    39  	}
    40  	return app
    41  }
    42  
    43  // Merge merges the given flag slices.
    44  func Merge(groups ...[]cli.Flag) []cli.Flag {
    45  	var ret []cli.Flag
    46  	for _, group := range groups {
    47  		ret = append(ret, group...)
    48  	}
    49  	return ret
    50  }
    51  
    52  var migrationApplied = map[*cli.Command]struct{}{}
    53  
    54  // MigrateGlobalFlags makes all global flag values available in the
    55  // context. This should be called as early as possible in app.Before.
    56  //
    57  // Example:
    58  //
    59  //	geth account new --keystore /tmp/mykeystore --lightkdf
    60  //
    61  // is equivalent after calling this method with:
    62  //
    63  //	geth --keystore /tmp/mykeystore --lightkdf account new
    64  //
    65  // i.e. in the subcommand Action function of 'account new', ctx.Bool("lightkdf)
    66  // will return true even if --lightkdf is set as a global option.
    67  //
    68  // This function may become unnecessary when https://github.com/urfave/cli/pull/1245 is merged.
    69  func MigrateGlobalFlags(ctx *cli.Context) {
    70  	var iterate func(cs []*cli.Command, fn func(*cli.Command))
    71  	iterate = func(cs []*cli.Command, fn func(*cli.Command)) {
    72  		for _, cmd := range cs {
    73  			if _, ok := migrationApplied[cmd]; ok {
    74  				continue
    75  			}
    76  			migrationApplied[cmd] = struct{}{}
    77  			fn(cmd)
    78  			iterate(cmd.Subcommands, fn)
    79  		}
    80  	}
    81  
    82  	// This iterates over all commands and wraps their action function.
    83  	iterate(ctx.App.Commands, func(cmd *cli.Command) {
    84  		if cmd.Action == nil {
    85  			return
    86  		}
    87  
    88  		action := cmd.Action
    89  		cmd.Action = func(ctx *cli.Context) error {
    90  			doMigrateFlags(ctx)
    91  			return action(ctx)
    92  		}
    93  	})
    94  }
    95  
    96  func doMigrateFlags(ctx *cli.Context) {
    97  	// Figure out if there are any aliases of commands. If there are, we want
    98  	// to ignore them when iterating over the flags.
    99  	var aliases = make(map[string]bool)
   100  	for _, fl := range ctx.Command.Flags {
   101  		for _, alias := range fl.Names()[1:] {
   102  			aliases[alias] = true
   103  		}
   104  	}
   105  	for _, name := range ctx.FlagNames() {
   106  		for _, parent := range ctx.Lineage()[1:] {
   107  			if parent.IsSet(name) {
   108  				// When iterating across the lineage, we will be served both
   109  				// the 'canon' and alias formats of all commmands. In most cases,
   110  				// it's fine to set it in the ctx multiple times (one for each
   111  				// name), however, the Slice-flags are not fine.
   112  				// The slice-flags accumulate, so if we set it once as
   113  				// "foo" and once as alias "F", then both will be present in the slice.
   114  				if _, isAlias := aliases[name]; isAlias {
   115  					continue
   116  				}
   117  				// If it is a string-slice, we need to set it as
   118  				// "alfa, beta, gamma" instead of "[alfa beta gamma]", in order
   119  				// for the backing StringSlice to parse it properly.
   120  				if result := parent.StringSlice(name); len(result) > 0 {
   121  					ctx.Set(name, strings.Join(result, ","))
   122  				} else {
   123  					ctx.Set(name, parent.String(name))
   124  				}
   125  				break
   126  			}
   127  		}
   128  	}
   129  }
   130  
   131  func init() {
   132  	cli.FlagStringer = FlagString
   133  }
   134  
   135  // FlagString prints a single flag in help.
   136  func FlagString(f cli.Flag) string {
   137  	df, ok := f.(cli.DocGenerationFlag)
   138  	if !ok {
   139  		return ""
   140  	}
   141  
   142  	needsPlaceholder := df.TakesValue()
   143  	placeholder := ""
   144  	if needsPlaceholder {
   145  		placeholder = "value"
   146  	}
   147  
   148  	namesText := pad(cli.FlagNamePrefixer(df.Names(), placeholder), 30)
   149  
   150  	defaultValueString := ""
   151  	if s := df.GetDefaultText(); s != "" {
   152  		defaultValueString = " (default: " + s + ")"
   153  	}
   154  
   155  	usage := strings.TrimSpace(df.GetUsage())
   156  	envHint := strings.TrimSpace(cli.FlagEnvHinter(df.GetEnvVars(), ""))
   157  	if len(envHint) > 0 {
   158  		usage += " " + envHint
   159  	}
   160  
   161  	usage = wordWrap(usage, 80)
   162  	usage = indent(usage, 10)
   163  
   164  	return fmt.Sprintf("\n    %s%s\n%s", namesText, defaultValueString, usage)
   165  }
   166  
   167  func pad(s string, length int) string {
   168  	if len(s) < length {
   169  		s += strings.Repeat(" ", length-len(s))
   170  	}
   171  	return s
   172  }
   173  
   174  func indent(s string, nspace int) string {
   175  	ind := strings.Repeat(" ", nspace)
   176  	return ind + strings.ReplaceAll(s, "\n", "\n"+ind)
   177  }
   178  
   179  func wordWrap(s string, width int) string {
   180  	var (
   181  		output     strings.Builder
   182  		lineLength = 0
   183  	)
   184  
   185  	for {
   186  		sp := strings.IndexByte(s, ' ')
   187  		var word string
   188  		if sp == -1 {
   189  			word = s
   190  		} else {
   191  			word = s[:sp]
   192  		}
   193  		wlen := len(word)
   194  		over := lineLength+wlen >= width
   195  		if over {
   196  			output.WriteByte('\n')
   197  			lineLength = 0
   198  		} else {
   199  			if lineLength != 0 {
   200  				output.WriteByte(' ')
   201  				lineLength++
   202  			}
   203  		}
   204  
   205  		output.WriteString(word)
   206  		lineLength += wlen
   207  
   208  		if sp == -1 {
   209  			break
   210  		}
   211  		s = s[wlen+1:]
   212  	}
   213  
   214  	return output.String()
   215  }