|
|
|
// Copyright 2020 The go-ethereum Authors
|
|
|
|
// This file is part of the go-ethereum library.
|
|
|
|
//
|
|
|
|
// 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
|
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
// (at your option) any later version.
|
|
|
|
//
|
|
|
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
// GNU Lesser General Public License for more details.
|
|
|
|
//
|
|
|
|
// 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/>.
|
|
|
|
|
|
|
|
package flags
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/ethereum/go-ethereum/internal/version"
|
|
|
|
"github.com/ethereum/go-ethereum/params"
|
|
|
|
"github.com/urfave/cli/v2"
|
|
|
|
)
|
|
|
|
|
|
|
|
// NewApp creates an app with sane defaults.
|
|
|
|
func NewApp(usage string) *cli.App {
|
|
|
|
git, _ := version.VCS()
|
|
|
|
app := cli.NewApp()
|
|
|
|
app.EnableBashCompletion = true
|
|
|
|
app.Version = params.VersionWithCommit(git.Commit, git.Date)
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
|
|
|
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:
|
|
|
|
//
|
|
|
|
// geth account new --keystore /tmp/mykeystore --lightkdf
|
|
|
|
//
|
|
|
|
// is equivalent after calling this method with:
|
|
|
|
//
|
|
|
|
// geth --keystore /tmp/mykeystore --lightkdf account new
|
|
|
|
//
|
|
|
|
// 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) {
|
|
|
|
if cmd.Action == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
action := cmd.Action
|
|
|
|
cmd.Action = func(ctx *cli.Context) error {
|
|
|
|
doMigrateFlags(ctx)
|
|
|
|
return action(ctx)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func doMigrateFlags(ctx *cli.Context) {
|
|
|
|
for _, name := range ctx.FlagNames() {
|
|
|
|
for _, parent := range ctx.Lineage()[1:] {
|
|
|
|
if parent.IsSet(name) {
|
|
|
|
ctx.Set(name, parent.String(name))
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
cli.FlagStringer = FlagString
|
|
|
|
}
|
|
|
|
|
|
|
|
// FlagString prints a single flag in help.
|
|
|
|
func FlagString(f cli.Flag) string {
|
|
|
|
df, ok := f.(cli.DocGenerationFlag)
|
|
|
|
if !ok {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
needsPlaceholder := df.TakesValue()
|
|
|
|
placeholder := ""
|
|
|
|
if needsPlaceholder {
|
|
|
|
placeholder = "value"
|
|
|
|
}
|
|
|
|
|
|
|
|
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))
|
|
|
|
}
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
|
|
|
func indent(s string, nspace int) string {
|
|
|
|
ind := strings.Repeat(" ", nspace)
|
|
|
|
return ind + strings.ReplaceAll(s, "\n", "\n"+ind)
|
|
|
|
}
|
|
|
|
|
|
|
|
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++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
output.WriteString(word)
|
|
|
|
lineLength += wlen
|
|
|
|
|
|
|
|
if sp == -1 {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
s = s[wlen+1:]
|
|
|
|
}
|
|
|
|
|
|
|
|
return output.String()
|
|
|
|
}
|