github.com/ava-labs/subnet-evm@v0.6.4/internal/flags/flags.go (about)

     1  // (c) 2023, 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 2015 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  	"encoding"
    31  	"errors"
    32  	"flag"
    33  	"fmt"
    34  	"math/big"
    35  	"os"
    36  	"os/user"
    37  	"path/filepath"
    38  	"strings"
    39  	"syscall"
    40  
    41  	"github.com/ethereum/go-ethereum/common/math"
    42  	"github.com/urfave/cli/v2"
    43  )
    44  
    45  // DirectoryString is custom type which is registered in the flags library which cli uses for
    46  // argument parsing. This allows us to expand Value to an absolute path when
    47  // the argument is parsed
    48  type DirectoryString string
    49  
    50  func (s *DirectoryString) String() string {
    51  	return string(*s)
    52  }
    53  
    54  func (s *DirectoryString) Set(value string) error {
    55  	*s = DirectoryString(expandPath(value))
    56  	return nil
    57  }
    58  
    59  var (
    60  	_ cli.Flag              = (*DirectoryFlag)(nil)
    61  	_ cli.RequiredFlag      = (*DirectoryFlag)(nil)
    62  	_ cli.VisibleFlag       = (*DirectoryFlag)(nil)
    63  	_ cli.DocGenerationFlag = (*DirectoryFlag)(nil)
    64  	_ cli.CategorizableFlag = (*DirectoryFlag)(nil)
    65  )
    66  
    67  // DirectoryFlag is custom cli.Flag type which expand the received string to an absolute path.
    68  // e.g. ~/.ethereum -> /home/username/.ethereum
    69  type DirectoryFlag struct {
    70  	Name string
    71  
    72  	Category    string
    73  	DefaultText string
    74  	Usage       string
    75  
    76  	Required   bool
    77  	Hidden     bool
    78  	HasBeenSet bool
    79  
    80  	Value DirectoryString
    81  
    82  	Aliases []string
    83  	EnvVars []string
    84  }
    85  
    86  // For cli.Flag:
    87  
    88  func (f *DirectoryFlag) Names() []string { return append([]string{f.Name}, f.Aliases...) }
    89  func (f *DirectoryFlag) IsSet() bool     { return f.HasBeenSet }
    90  func (f *DirectoryFlag) String() string  { return cli.FlagStringer(f) }
    91  
    92  // Apply called by cli library, grabs variable from environment (if in env)
    93  // and adds variable to flag set for parsing.
    94  func (f *DirectoryFlag) Apply(set *flag.FlagSet) error {
    95  	for _, envVar := range f.EnvVars {
    96  		envVar = strings.TrimSpace(envVar)
    97  		if value, found := syscall.Getenv(envVar); found {
    98  			f.Value.Set(value)
    99  			f.HasBeenSet = true
   100  			break
   101  		}
   102  	}
   103  	eachName(f, func(name string) {
   104  		set.Var(&f.Value, f.Name, f.Usage)
   105  	})
   106  	return nil
   107  }
   108  
   109  // For cli.RequiredFlag:
   110  
   111  func (f *DirectoryFlag) IsRequired() bool { return f.Required }
   112  
   113  // For cli.VisibleFlag:
   114  
   115  func (f *DirectoryFlag) IsVisible() bool { return !f.Hidden }
   116  
   117  // For cli.CategorizableFlag:
   118  
   119  func (f *DirectoryFlag) GetCategory() string { return f.Category }
   120  
   121  // For cli.DocGenerationFlag:
   122  
   123  func (f *DirectoryFlag) TakesValue() bool     { return true }
   124  func (f *DirectoryFlag) GetUsage() string     { return f.Usage }
   125  func (f *DirectoryFlag) GetValue() string     { return f.Value.String() }
   126  func (f *DirectoryFlag) GetEnvVars() []string { return f.EnvVars }
   127  
   128  func (f *DirectoryFlag) GetDefaultText() string {
   129  	if f.DefaultText != "" {
   130  		return f.DefaultText
   131  	}
   132  	return f.GetValue()
   133  }
   134  
   135  type TextMarshaler interface {
   136  	encoding.TextMarshaler
   137  	encoding.TextUnmarshaler
   138  }
   139  
   140  // textMarshalerVal turns a TextMarshaler into a flag.Value
   141  type textMarshalerVal struct {
   142  	v TextMarshaler
   143  }
   144  
   145  func (v textMarshalerVal) String() string {
   146  	if v.v == nil {
   147  		return ""
   148  	}
   149  	text, _ := v.v.MarshalText()
   150  	return string(text)
   151  }
   152  
   153  func (v textMarshalerVal) Set(s string) error {
   154  	return v.v.UnmarshalText([]byte(s))
   155  }
   156  
   157  var (
   158  	_ cli.Flag              = (*TextMarshalerFlag)(nil)
   159  	_ cli.RequiredFlag      = (*TextMarshalerFlag)(nil)
   160  	_ cli.VisibleFlag       = (*TextMarshalerFlag)(nil)
   161  	_ cli.DocGenerationFlag = (*TextMarshalerFlag)(nil)
   162  	_ cli.CategorizableFlag = (*TextMarshalerFlag)(nil)
   163  )
   164  
   165  // TextMarshalerFlag wraps a TextMarshaler value.
   166  type TextMarshalerFlag struct {
   167  	Name string
   168  
   169  	Category    string
   170  	DefaultText string
   171  	Usage       string
   172  
   173  	Required   bool
   174  	Hidden     bool
   175  	HasBeenSet bool
   176  
   177  	Value TextMarshaler
   178  
   179  	Aliases []string
   180  	EnvVars []string
   181  }
   182  
   183  // For cli.Flag:
   184  
   185  func (f *TextMarshalerFlag) Names() []string { return append([]string{f.Name}, f.Aliases...) }
   186  func (f *TextMarshalerFlag) IsSet() bool     { return f.HasBeenSet }
   187  func (f *TextMarshalerFlag) String() string  { return cli.FlagStringer(f) }
   188  
   189  func (f *TextMarshalerFlag) Apply(set *flag.FlagSet) error {
   190  	for _, envVar := range f.EnvVars {
   191  		envVar = strings.TrimSpace(envVar)
   192  		if value, found := syscall.Getenv(envVar); found {
   193  			if err := f.Value.UnmarshalText([]byte(value)); err != nil {
   194  				return fmt.Errorf("could not parse %q from environment variable %q for flag %s: %s", value, envVar, f.Name, err)
   195  			}
   196  			f.HasBeenSet = true
   197  			break
   198  		}
   199  	}
   200  	eachName(f, func(name string) {
   201  		set.Var(textMarshalerVal{f.Value}, f.Name, f.Usage)
   202  	})
   203  	return nil
   204  }
   205  
   206  // For cli.RequiredFlag:
   207  
   208  func (f *TextMarshalerFlag) IsRequired() bool { return f.Required }
   209  
   210  // For cli.VisibleFlag:
   211  
   212  func (f *TextMarshalerFlag) IsVisible() bool { return !f.Hidden }
   213  
   214  // For cli.CategorizableFlag:
   215  
   216  func (f *TextMarshalerFlag) GetCategory() string { return f.Category }
   217  
   218  // For cli.DocGenerationFlag:
   219  
   220  func (f *TextMarshalerFlag) TakesValue() bool     { return true }
   221  func (f *TextMarshalerFlag) GetUsage() string     { return f.Usage }
   222  func (f *TextMarshalerFlag) GetEnvVars() []string { return f.EnvVars }
   223  
   224  func (f *TextMarshalerFlag) GetValue() string {
   225  	t, err := f.Value.MarshalText()
   226  	if err != nil {
   227  		return "(ERR: " + err.Error() + ")"
   228  	}
   229  	return string(t)
   230  }
   231  
   232  func (f *TextMarshalerFlag) GetDefaultText() string {
   233  	if f.DefaultText != "" {
   234  		return f.DefaultText
   235  	}
   236  	return f.GetValue()
   237  }
   238  
   239  // GlobalTextMarshaler returns the value of a TextMarshalerFlag from the global flag set.
   240  func GlobalTextMarshaler(ctx *cli.Context, name string) TextMarshaler {
   241  	val := ctx.Generic(name)
   242  	if val == nil {
   243  		return nil
   244  	}
   245  	return val.(textMarshalerVal).v
   246  }
   247  
   248  var (
   249  	_ cli.Flag              = (*BigFlag)(nil)
   250  	_ cli.RequiredFlag      = (*BigFlag)(nil)
   251  	_ cli.VisibleFlag       = (*BigFlag)(nil)
   252  	_ cli.DocGenerationFlag = (*BigFlag)(nil)
   253  	_ cli.CategorizableFlag = (*BigFlag)(nil)
   254  )
   255  
   256  // BigFlag is a command line flag that accepts 256 bit big integers in decimal or
   257  // hexadecimal syntax.
   258  type BigFlag struct {
   259  	Name string
   260  
   261  	Category    string
   262  	DefaultText string
   263  	Usage       string
   264  
   265  	Required   bool
   266  	Hidden     bool
   267  	HasBeenSet bool
   268  
   269  	Value *big.Int
   270  
   271  	Aliases []string
   272  	EnvVars []string
   273  }
   274  
   275  // For cli.Flag:
   276  
   277  func (f *BigFlag) Names() []string { return append([]string{f.Name}, f.Aliases...) }
   278  func (f *BigFlag) IsSet() bool     { return f.HasBeenSet }
   279  func (f *BigFlag) String() string  { return cli.FlagStringer(f) }
   280  
   281  func (f *BigFlag) Apply(set *flag.FlagSet) error {
   282  	for _, envVar := range f.EnvVars {
   283  		envVar = strings.TrimSpace(envVar)
   284  		if value, found := syscall.Getenv(envVar); found {
   285  			if _, ok := f.Value.SetString(value, 10); !ok {
   286  				return fmt.Errorf("could not parse %q from environment variable %q for flag %s", value, envVar, f.Name)
   287  			}
   288  			f.HasBeenSet = true
   289  			break
   290  		}
   291  	}
   292  	eachName(f, func(name string) {
   293  		f.Value = new(big.Int)
   294  		set.Var((*bigValue)(f.Value), f.Name, f.Usage)
   295  	})
   296  
   297  	return nil
   298  }
   299  
   300  // For cli.RequiredFlag:
   301  
   302  func (f *BigFlag) IsRequired() bool { return f.Required }
   303  
   304  // For cli.VisibleFlag:
   305  
   306  func (f *BigFlag) IsVisible() bool { return !f.Hidden }
   307  
   308  // For cli.CategorizableFlag:
   309  
   310  func (f *BigFlag) GetCategory() string { return f.Category }
   311  
   312  // For cli.DocGenerationFlag:
   313  
   314  func (f *BigFlag) TakesValue() bool     { return true }
   315  func (f *BigFlag) GetUsage() string     { return f.Usage }
   316  func (f *BigFlag) GetValue() string     { return f.Value.String() }
   317  func (f *BigFlag) GetEnvVars() []string { return f.EnvVars }
   318  
   319  func (f *BigFlag) GetDefaultText() string {
   320  	if f.DefaultText != "" {
   321  		return f.DefaultText
   322  	}
   323  	return f.GetValue()
   324  }
   325  
   326  // bigValue turns *big.Int into a flag.Value
   327  type bigValue big.Int
   328  
   329  func (b *bigValue) String() string {
   330  	if b == nil {
   331  		return ""
   332  	}
   333  	return (*big.Int)(b).String()
   334  }
   335  
   336  func (b *bigValue) Set(s string) error {
   337  	intVal, ok := math.ParseBig256(s)
   338  	if !ok {
   339  		return errors.New("invalid integer syntax")
   340  	}
   341  	*b = (bigValue)(*intVal)
   342  	return nil
   343  }
   344  
   345  // GlobalBig returns the value of a BigFlag from the global flag set.
   346  func GlobalBig(ctx *cli.Context, name string) *big.Int {
   347  	val := ctx.Generic(name)
   348  	if val == nil {
   349  		return nil
   350  	}
   351  	return (*big.Int)(val.(*bigValue))
   352  }
   353  
   354  // Expands a file path
   355  // 1. replace tilde with users home dir
   356  // 2. expands embedded environment variables
   357  // 3. cleans the path, e.g. /a/b/../c -> /a/c
   358  // Note, it has limitations, e.g. ~someuser/tmp will not be expanded
   359  func expandPath(p string) string {
   360  	// Named pipes are not file paths on windows, ignore
   361  	if strings.HasPrefix(p, `\\.\pipe`) {
   362  		return p
   363  	}
   364  	if strings.HasPrefix(p, "~/") || strings.HasPrefix(p, "~\\") {
   365  		if home := HomeDir(); home != "" {
   366  			p = home + p[1:]
   367  		}
   368  	}
   369  	return filepath.Clean(os.ExpandEnv(p))
   370  }
   371  
   372  func HomeDir() string {
   373  	if home := os.Getenv("HOME"); home != "" {
   374  		return home
   375  	}
   376  	if usr, err := user.Current(); err == nil {
   377  		return usr.HomeDir
   378  	}
   379  	return ""
   380  }
   381  
   382  func eachName(f cli.Flag, fn func(string)) {
   383  	for _, name := range f.Names() {
   384  		name = strings.Trim(name, " ")
   385  		fn(name)
   386  	}
   387  }