Skip to content

Commit d9fbb71

Browse files
authored
cmd/geth, internal/flags, go.mod: colorize cli help, support env vars (#28103)
* cmd/geth, internal/flags, go.mod: colorize cli help, support env vars * internal/flags: use stdout, not stderr for terminal detection
1 parent b9b99a1 commit d9fbb71

File tree

5 files changed

+76
-15
lines changed

5 files changed

+76
-15
lines changed

cmd/geth/main.go

+1
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@ func init() {
244244
debug.Flags,
245245
metricsFlags,
246246
)
247+
flags.AutoEnvVars(app.Flags, "GETH")
247248

248249
app.Before = func(ctx *cli.Context) error {
249250
maxprocs.Set() // Automatically set GOMAXPROCS to match Linux container CPU quota.

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ require (
6161
github.com/supranational/blst v0.3.11
6262
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7
6363
github.com/tyler-smith/go-bip39 v1.1.0
64-
github.com/urfave/cli/v2 v2.24.1
64+
github.com/urfave/cli/v2 v2.25.7
6565
go.uber.org/automaxprocs v1.5.2
6666
golang.org/x/crypto v0.12.0
6767
golang.org/x/exp v0.0.0-20230810033253-352e893a4cad

go.sum

+3-3
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.3/go.mod h1:KLF4gFr6DcKFZwSu
3939
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.3.0 h1:Px2UA+2RvSSvv+RvJNuUB6n7rs5Wsel4dXLe90Um2n4=
4040
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.3.0/go.mod h1:tPaiy8S5bQ+S5sOiDlINkp7+Ef339+Nz5L5XO+cnOHo=
4141
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
42-
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
42+
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
4343
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
4444
github.com/CloudyKit/fastprinter v0.0.0-20170127035650-74b38d55f37a/go.mod h1:EFZQ978U7x8IRnstaskI3IysnWY5Ao3QgZUKOXlsAdw=
4545
github.com/CloudyKit/jet v2.1.3-0.20180809161101-62edd43e4f88+incompatible/go.mod h1:HPYO+50pSWkPoj9Q/eq0aRGByCL6ScRlUmiEX5Zgm+w=
@@ -551,8 +551,8 @@ github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3C
551551
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
552552
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
553553
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
554-
github.com/urfave/cli/v2 v2.24.1 h1:/QYYr7g0EhwXEML8jO+8OYt5trPnLHS0p3mrgExJ5NU=
555-
github.com/urfave/cli/v2 v2.24.1/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
554+
github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs=
555+
github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
556556
github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
557557
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
558558
github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w=

internal/flags/flags.go

+6-3
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ type DirectoryFlag struct {
6868
Value DirectoryString
6969

7070
Aliases []string
71+
EnvVars []string
7172
}
7273

7374
// For cli.Flag:
@@ -102,7 +103,7 @@ func (f *DirectoryFlag) GetCategory() string { return f.Category }
102103
func (f *DirectoryFlag) TakesValue() bool { return true }
103104
func (f *DirectoryFlag) GetUsage() string { return f.Usage }
104105
func (f *DirectoryFlag) GetValue() string { return f.Value.String() }
105-
func (f *DirectoryFlag) GetEnvVars() []string { return nil } // env not supported
106+
func (f *DirectoryFlag) GetEnvVars() []string { return f.EnvVars }
106107

107108
func (f *DirectoryFlag) GetDefaultText() string {
108109
if f.DefaultText != "" {
@@ -156,6 +157,7 @@ type TextMarshalerFlag struct {
156157
Value TextMarshaler
157158

158159
Aliases []string
160+
EnvVars []string
159161
}
160162

161163
// For cli.Flag:
@@ -187,7 +189,7 @@ func (f *TextMarshalerFlag) GetCategory() string { return f.Category }
187189

188190
func (f *TextMarshalerFlag) TakesValue() bool { return true }
189191
func (f *TextMarshalerFlag) GetUsage() string { return f.Usage }
190-
func (f *TextMarshalerFlag) GetEnvVars() []string { return nil } // env not supported
192+
func (f *TextMarshalerFlag) GetEnvVars() []string { return f.EnvVars }
191193

192194
func (f *TextMarshalerFlag) GetValue() string {
193195
t, err := f.Value.MarshalText()
@@ -237,6 +239,7 @@ type BigFlag struct {
237239
Value *big.Int
238240

239241
Aliases []string
242+
EnvVars []string
240243
}
241244

242245
// For cli.Flag:
@@ -271,7 +274,7 @@ func (f *BigFlag) GetCategory() string { return f.Category }
271274
func (f *BigFlag) TakesValue() bool { return true }
272275
func (f *BigFlag) GetUsage() string { return f.Usage }
273276
func (f *BigFlag) GetValue() string { return f.Value.String() }
274-
func (f *BigFlag) GetEnvVars() []string { return nil } // env not supported
277+
func (f *BigFlag) GetEnvVars() []string { return f.EnvVars }
275278

276279
func (f *BigFlag) GetDefaultText() string {
277280
if f.DefaultText != "" {

internal/flags/helpers.go

+65-8
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,20 @@ package flags
1818

1919
import (
2020
"fmt"
21+
"os"
22+
"regexp"
2123
"strings"
2224

2325
"github.com/ethereum/go-ethereum/internal/version"
2426
"github.com/ethereum/go-ethereum/params"
27+
"github.com/mattn/go-isatty"
2528
"github.com/urfave/cli/v2"
2629
)
2730

31+
// usecolor defines whether the CLI help should use colored output or normal dumb
32+
// colorless terminal formatting.
33+
var usecolor = (isatty.IsTerminal(os.Stdout.Fd()) || isatty.IsCygwinTerminal(os.Stdout.Fd())) && os.Getenv("TERM") != "dumb"
34+
2835
// NewApp creates an app with sane defaults.
2936
func NewApp(usage string) *cli.App {
3037
git, _ := version.VCS()
@@ -129,6 +136,14 @@ func doMigrateFlags(ctx *cli.Context) {
129136
}
130137

131138
func init() {
139+
if usecolor {
140+
// Annotate all help categories with colors
141+
cli.AppHelpTemplate = regexp.MustCompile("[A-Z ]+:").ReplaceAllString(cli.AppHelpTemplate, "\u001B[33m$0\u001B[0m")
142+
143+
// Annotate flag categories with colors (private template, so need to
144+
// copy-paste the entire thing here...)
145+
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}}")
146+
}
132147
cli.FlagStringer = FlagString
133148
}
134149

@@ -138,30 +153,31 @@ func FlagString(f cli.Flag) string {
138153
if !ok {
139154
return ""
140155
}
141-
142156
needsPlaceholder := df.TakesValue()
143157
placeholder := ""
144158
if needsPlaceholder {
145159
placeholder = "value"
146160
}
147161

148-
namesText := pad(cli.FlagNamePrefixer(df.Names(), placeholder), 30)
162+
namesText := cli.FlagNamePrefixer(df.Names(), placeholder)
149163

150164
defaultValueString := ""
151165
if s := df.GetDefaultText(); s != "" {
152166
defaultValueString = " (default: " + s + ")"
153167
}
154-
155-
usage := strings.TrimSpace(df.GetUsage())
156168
envHint := strings.TrimSpace(cli.FlagEnvHinter(df.GetEnvVars(), ""))
157-
if len(envHint) > 0 {
158-
usage += " " + envHint
169+
if envHint != "" {
170+
envHint = " (" + envHint[1:len(envHint)-1] + ")"
159171
}
160-
172+
usage := strings.TrimSpace(df.GetUsage())
161173
usage = wordWrap(usage, 80)
162174
usage = indent(usage, 10)
163175

164-
return fmt.Sprintf("\n %s%s\n%s", namesText, defaultValueString, usage)
176+
if usecolor {
177+
return fmt.Sprintf("\n \u001B[32m%-35s%-35s\u001B[0m%s\n%s", namesText, defaultValueString, envHint, usage)
178+
} else {
179+
return fmt.Sprintf("\n %-35s%-35s%s\n%s", namesText, defaultValueString, envHint, usage)
180+
}
165181
}
166182

167183
func pad(s string, length int) string {
@@ -213,3 +229,44 @@ func wordWrap(s string, width int) string {
213229

214230
return output.String()
215231
}
232+
233+
// AutoEnvVars extens all the specific CLI flags with automatically generated
234+
// env vars by capitalizing the flag, replacing . with _ and prefixing it with
235+
// the specified string.
236+
//
237+
// Note, the prefix should *not* contain the separator underscore, that will be
238+
// added automatically.
239+
func AutoEnvVars(flags []cli.Flag, prefix string) {
240+
for _, flag := range flags {
241+
envvar := strings.ToUpper(prefix + "_" + strings.ReplaceAll(strings.ReplaceAll(flag.Names()[0], ".", "_"), "-", "_"))
242+
243+
switch flag := flag.(type) {
244+
case *cli.StringFlag:
245+
flag.EnvVars = append(flag.EnvVars, envvar)
246+
247+
case *cli.BoolFlag:
248+
flag.EnvVars = append(flag.EnvVars, envvar)
249+
250+
case *cli.IntFlag:
251+
flag.EnvVars = append(flag.EnvVars, envvar)
252+
253+
case *cli.Uint64Flag:
254+
flag.EnvVars = append(flag.EnvVars, envvar)
255+
256+
case *cli.DurationFlag:
257+
flag.EnvVars = append(flag.EnvVars, envvar)
258+
259+
case *cli.PathFlag:
260+
flag.EnvVars = append(flag.EnvVars, envvar)
261+
262+
case *BigFlag:
263+
flag.EnvVars = append(flag.EnvVars, envvar)
264+
265+
case *TextMarshalerFlag:
266+
flag.EnvVars = append(flag.EnvVars, envvar)
267+
268+
case *DirectoryFlag:
269+
flag.EnvVars = append(flag.EnvVars, envvar)
270+
}
271+
}
272+
}

0 commit comments

Comments
 (0)