helpers.go 5.51 KB
Newer Older
1
// Copyright 2020 The go-ethereum Authors
2
// This file is part of the go-ethereum library.
3
//
4 5
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
6 7 8
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
9
// The go-ethereum library is distributed in the hope that it will be useful,
10 11
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
// GNU Lesser General Public License for more details.
13
//
14 15
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16 17 18 19

package flags

import (
20 21
	"fmt"
	"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 30
func NewApp(usage string) *cli.App {
	git, _ := version.VCS()
31 32
	app := cli.NewApp()
	app.EnableBashCompletion = true
33
	app.Version = params.VersionWithCommit(git.Commit, git.Date)
34 35 36 37 38 39 40 41 42
	app.Usage = usage
	app.Copyright = "Copyright 2013-2022 The go-ethereum Authors"
	app.Before = func(ctx *cli.Context) error {
		MigrateGlobalFlags(ctx)
		return nil
	}
	return app
}

43 44 45 46 47 48 49 50 51
// Merge merges the given flag slices.
func Merge(groups ...[]cli.Flag) []cli.Flag {
	var ret []cli.Flag
	for _, group := range groups {
		ret = append(ret, group...)
	}
	return ret
}

52 53 54 55 56 57 58
var migrationApplied = map[*cli.Command]struct{}{}

// MigrateGlobalFlags makes all global flag values available in the
// context. This should be called as early as possible in app.Before.
//
// Example:
//
59
//	geth account new --keystore /tmp/mykeystore --lightkdf
60 61 62
//
// is equivalent after calling this method with:
//
63
//	geth --keystore /tmp/mykeystore --lightkdf account new
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
//
// i.e. in the subcommand Action function of 'account new', ctx.Bool("lightkdf)
// will return true even if --lightkdf is set as a global option.
//
// This function may become unnecessary when https://github.com/urfave/cli/pull/1245 is merged.
func MigrateGlobalFlags(ctx *cli.Context) {
	var iterate func(cs []*cli.Command, fn func(*cli.Command))
	iterate = func(cs []*cli.Command, fn func(*cli.Command)) {
		for _, cmd := range cs {
			if _, ok := migrationApplied[cmd]; ok {
				continue
			}
			migrationApplied[cmd] = struct{}{}
			fn(cmd)
			iterate(cmd.Subcommands, fn)
		}
	}

	// This iterates over all commands and wraps their action function.
	iterate(ctx.App.Commands, func(cmd *cli.Command) {
84 85 86 87
		if cmd.Action == nil {
			return
		}

88 89 90 91 92 93 94
		action := cmd.Action
		cmd.Action = func(ctx *cli.Context) error {
			doMigrateFlags(ctx)
			return action(ctx)
		}
	})
}
95

96
func doMigrateFlags(ctx *cli.Context) {
97 98 99 100 101 102 103 104
	// Figure out if there are any aliases of commands. If there are, we want
	// to ignore them when iterating over the flags.
	var aliases = make(map[string]bool)
	for _, fl := range ctx.Command.Flags {
		for _, alias := range fl.Names()[1:] {
			aliases[alias] = true
		}
	}
105 106 107
	for _, name := range ctx.FlagNames() {
		for _, parent := range ctx.Lineage()[1:] {
			if parent.IsSet(name) {
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
				// When iterating across the lineage, we will be served both
				// the 'canon' and alias formats of all commmands. In most cases,
				// it's fine to set it in the ctx multiple times (one for each
				// name), however, the Slice-flags are not fine.
				// The slice-flags accumulate, so if we set it once as
				// "foo" and once as alias "F", then both will be present in the slice.
				if _, isAlias := aliases[name]; isAlias {
					continue
				}
				// If it is a string-slice, we need to set it as
				// "alfa, beta, gamma" instead of "[alfa beta gamma]", in order
				// for the backing StringSlice to parse it properly.
				if result := parent.StringSlice(name); len(result) > 0 {
					ctx.Set(name, strings.Join(result, ","))
				} else {
					ctx.Set(name, parent.String(name))
				}
125 126 127 128
				break
			}
		}
	}
129 130
}

131 132
func init() {
	cli.FlagStringer = FlagString
133 134
}

135 136 137 138 139 140
// FlagString prints a single flag in help.
func FlagString(f cli.Flag) string {
	df, ok := f.(cli.DocGenerationFlag)
	if !ok {
		return ""
	}
141

142 143 144 145 146
	needsPlaceholder := df.TakesValue()
	placeholder := ""
	if needsPlaceholder {
		placeholder = "value"
	}
147

148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
	namesText := pad(cli.FlagNamePrefixer(df.Names(), placeholder), 30)

	defaultValueString := ""
	if s := df.GetDefaultText(); s != "" {
		defaultValueString = " (default: " + s + ")"
	}

	usage := strings.TrimSpace(df.GetUsage())
	envHint := strings.TrimSpace(cli.FlagEnvHinter(df.GetEnvVars(), ""))
	if len(envHint) > 0 {
		usage += " " + envHint
	}

	usage = wordWrap(usage, 80)
	usage = indent(usage, 10)

	return fmt.Sprintf("\n    %s%s\n%s", namesText, defaultValueString, usage)
}

func pad(s string, length int) string {
	if len(s) < length {
		s += strings.Repeat(" ", length-len(s))
170
	}
171 172
	return s
}
173

174 175 176
func indent(s string, nspace int) string {
	ind := strings.Repeat(" ", nspace)
	return ind + strings.ReplaceAll(s, "\n", "\n"+ind)
177 178
}

179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
func wordWrap(s string, width int) string {
	var (
		output     strings.Builder
		lineLength = 0
	)

	for {
		sp := strings.IndexByte(s, ' ')
		var word string
		if sp == -1 {
			word = s
		} else {
			word = s[:sp]
		}
		wlen := len(word)
		over := lineLength+wlen >= width
		if over {
			output.WriteByte('\n')
			lineLength = 0
		} else {
			if lineLength != 0 {
				output.WriteByte(' ')
				lineLength++
202 203
			}
		}
204 205 206 207 208 209 210 211

		output.WriteString(word)
		lineLength += wlen

		if sp == -1 {
			break
		}
		s = s[wlen+1:]
212 213
	}

214
	return output.String()
215
}