github.com/klaytn/klaytn@v1.12.1/cmd/utils/customflags.go (about)

     1  // Modifications Copyright 2018 The klaytn Authors
     2  // Copyright 2015 The go-ethereum Authors
     3  // This file is part of go-ethereum.
     4  //
     5  // go-ethereum is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // go-ethereum is distributed in the hope that it will be useful,
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    13  // GNU General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU General Public License
    16  // along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
    17  //
    18  // This file is derived from cmd/utils/customflags.go (2018/06/04).
    19  // Modified and improved for the klaytn development.
    20  
    21  package utils
    22  
    23  import (
    24  	"encoding"
    25  	"errors"
    26  	"flag"
    27  	"fmt"
    28  	"math/big"
    29  	"os"
    30  	"os/user"
    31  	"path"
    32  	"strings"
    33  	"syscall"
    34  
    35  	"github.com/klaytn/klaytn/common/math"
    36  	"github.com/klaytn/klaytn/datasync/downloader"
    37  	"github.com/urfave/cli/v2"
    38  	"github.com/urfave/cli/v2/altsrc"
    39  )
    40  
    41  // NOTE-klaytn: The custom directoryFlag deprecated.
    42  // urfave.v2 new flag, PathFlag, replaced the directoryFlag.
    43  
    44  // Custom type which is registered in the flags library which cli uses for
    45  // argument parsing. This allows us to expand Value to an absolute path when
    46  // the argument is parsed
    47  type DirectoryString struct {
    48  	Value string
    49  }
    50  
    51  func (self *DirectoryString) String() string {
    52  	return self.Value
    53  }
    54  
    55  func (self *DirectoryString) Set(value string) error {
    56  	self.Value = expandPath(value)
    57  	return nil
    58  }
    59  
    60  type WrappedDirectoryFlag struct {
    61  	DirectoryFlag
    62  	set *flag.FlagSet
    63  }
    64  
    65  func NewWrappedDirectoryFlag(fl DirectoryFlag) *WrappedDirectoryFlag {
    66  	return &WrappedDirectoryFlag{DirectoryFlag: fl, set: nil}
    67  }
    68  
    69  func (f *WrappedDirectoryFlag) Apply(set *flag.FlagSet) {
    70  	f.set = set
    71  	f.DirectoryFlag.Apply(set)
    72  }
    73  
    74  func (f *WrappedDirectoryFlag) ApplyInputSourceValue(context *cli.Context, isc altsrc.InputSourceContext) error {
    75  	if f.set != nil {
    76  		if !isEnvVarSet(f.EnvVar) {
    77  			value, err := isc.String(f.DirectoryFlag.Name)
    78  			if err != nil {
    79  				return err
    80  			}
    81  			if value != "" {
    82  				eachName(f.Name, func(name string) {
    83  					f.set.Set(f.Name, value)
    84  				})
    85  			}
    86  		}
    87  	}
    88  	return nil
    89  }
    90  
    91  // Custom cli.Flag type which expand the received string to an absolute path.
    92  // e.g. ~/.ethereum -> /home/username/.ethereum
    93  type DirectoryFlag struct {
    94  	Name   string
    95  	Value  DirectoryString
    96  	Usage  string
    97  	EnvVar string
    98  }
    99  
   100  func (self DirectoryFlag) String() string {
   101  	fmtString := "%s %v\t%v"
   102  	if len(self.Value.Value) > 0 {
   103  		fmtString = "%s \"%v\"\t%v"
   104  	}
   105  	return fmt.Sprintf(fmtString, prefixedNames(self.Name), self.Value.Value, self.Usage)
   106  }
   107  
   108  // called by cli library, grabs variable from environment (if in env)
   109  // and adds variable to flag set for parsing.
   110  func (self DirectoryFlag) Apply(set *flag.FlagSet) {
   111  	if self.EnvVar != "" {
   112  		if envVal, ok := syscall.Getenv(self.EnvVar); ok {
   113  			self.Value.Value = envVal
   114  		}
   115  	}
   116  	eachName(self.Name, func(name string) {
   117  		set.Var(&self.Value, self.Name, self.Usage)
   118  	})
   119  }
   120  
   121  func eachName(longName string, fn func(string)) {
   122  	parts := strings.Split(longName, ",")
   123  	for _, name := range parts {
   124  		name = strings.Trim(name, " ")
   125  		fn(name)
   126  	}
   127  }
   128  
   129  func isEnvVarSet(envVars string) bool {
   130  	for _, envVar := range strings.Split(envVars, ",") {
   131  		envVar = strings.TrimSpace(envVar)
   132  		if env, ok := syscall.Getenv(envVar); ok {
   133  			// TODO: Can't use this for bools as
   134  			// set means that it was true or false based on
   135  			// Bool flag type, should work for other types
   136  			logger.Info("env", "env", env)
   137  			return true
   138  		}
   139  	}
   140  	return false
   141  }
   142  
   143  type TextMarshaler interface {
   144  	encoding.TextMarshaler
   145  	encoding.TextUnmarshaler
   146  }
   147  
   148  // textMarshalerVal turns a TextMarshaler into a flag.Value
   149  type textMarshalerVal struct {
   150  	v TextMarshaler
   151  }
   152  
   153  func (v textMarshalerVal) String() string {
   154  	if v.v == nil {
   155  		return ""
   156  	}
   157  	text, _ := v.v.MarshalText()
   158  	return string(text)
   159  }
   160  
   161  func (v textMarshalerVal) Set(s string) error {
   162  	return v.v.UnmarshalText([]byte(s))
   163  }
   164  
   165  // TextMarshalerFlag wraps a TextMarshaler value.
   166  type TextMarshalerFlag struct {
   167  	Name string
   168  
   169  	Category string
   170  	Usage    string
   171  
   172  	Required   bool
   173  	Hidden     bool
   174  	HasBeenSet bool
   175  
   176  	Value       TextMarshaler
   177  	Destination *TextMarshaler
   178  
   179  	Aliases []string
   180  	EnvVars []string
   181  
   182  	Action func(*cli.Context, TextMarshaler) error
   183  }
   184  
   185  // IsSet returns whether or not the flag has been set through env or file
   186  func (f *TextMarshalerFlag) IsSet() bool {
   187  	return f.HasBeenSet
   188  }
   189  
   190  // Names returns the names of the flag
   191  func (f *TextMarshalerFlag) Names() []string {
   192  	return cli.FlagNames(f.Name, f.Aliases)
   193  }
   194  
   195  // IsRequired returns whether or not the flag is required
   196  func (f *TextMarshalerFlag) IsRequired() bool {
   197  	return f.Required
   198  }
   199  
   200  // IsVisible returns true if the flag is not hidden, otherwise false
   201  func (f *TextMarshalerFlag) IsVisible() bool {
   202  	return !f.Hidden
   203  }
   204  
   205  func (f *TextMarshalerFlag) String() string {
   206  	return cli.FlagStringer(f)
   207  }
   208  
   209  // TakesValue returns true of the flag takes a value, otherwise false
   210  func (f *TextMarshalerFlag) TakesValue() bool {
   211  	return true
   212  }
   213  
   214  // GetUsage returns the usage string for the flag
   215  func (f *TextMarshalerFlag) GetUsage() string {
   216  	return f.Usage
   217  }
   218  
   219  // GetCategory returns the category for the flag
   220  func (f *TextMarshalerFlag) GetCategory() string {
   221  	return f.Category
   222  }
   223  
   224  // GetValue returns the flags value as string representation and an empty
   225  // string if the flag takes no value at all.
   226  func (f *TextMarshalerFlag) GetValue() string {
   227  	return fmt.Sprintf("%q", f.Value)
   228  }
   229  
   230  // GetDefaultText returns the default text for this flag
   231  func (f *TextMarshalerFlag) GetDefaultText() string {
   232  	return fmt.Sprintf("%q", f.Value)
   233  }
   234  
   235  // GetEnvVars returns the env vars for this flag
   236  func (f *TextMarshalerFlag) GetEnvVars() []string {
   237  	return f.EnvVars
   238  }
   239  
   240  func (f *TextMarshalerFlag) Apply(set *flag.FlagSet) error {
   241  	if f.EnvVars[0] != "" && f.Value != nil {
   242  		if envVal, ok := syscall.Getenv(f.EnvVars[0]); ok {
   243  			var mode downloader.SyncMode
   244  			switch envVal {
   245  			case "full":
   246  				mode = downloader.FullSync
   247  			case "fast":
   248  				mode = downloader.FastSync
   249  			case "snap":
   250  				mode = downloader.SnapSync
   251  			case "light":
   252  				mode = downloader.LightSync
   253  			}
   254  			f.Value = &mode
   255  		}
   256  	}
   257  	eachName(f.Name, func(name string) {
   258  		set.Var(textMarshalerVal{f.Value}, f.Name, f.Usage)
   259  	})
   260  
   261  	return nil
   262  }
   263  
   264  // Get returns the flag’s value in the given Context.
   265  func (f *TextMarshalerFlag) Get(ctx *cli.Context) string {
   266  	return ctx.Path(f.Name)
   267  }
   268  
   269  // RunAction executes flag action if set
   270  func (f *TextMarshalerFlag) RunAction(c *cli.Context) error {
   271  	if f.Action != nil {
   272  		return f.Action(c, GlobalTextMarshaler(c, f.Name))
   273  	}
   274  	return nil
   275  }
   276  
   277  type WrappedTextMarshalerFlag struct {
   278  	*TextMarshalerFlag
   279  	set *flag.FlagSet
   280  }
   281  
   282  func NewWrappedTextMarshalerFlag(fl *TextMarshalerFlag) *WrappedTextMarshalerFlag {
   283  	return &WrappedTextMarshalerFlag{TextMarshalerFlag: fl, set: nil}
   284  }
   285  
   286  func (f *WrappedTextMarshalerFlag) Apply(set *flag.FlagSet) error {
   287  	f.set = set
   288  	return f.TextMarshalerFlag.Apply(set)
   289  }
   290  
   291  func (f *WrappedTextMarshalerFlag) ApplyInputSourceValue(context *cli.Context, isc altsrc.InputSourceContext) error {
   292  	if f.set != nil {
   293  		if !context.IsSet(f.Name) && !isEnvVarSet(f.EnvVars[0]) {
   294  			value, err := isc.String(f.TextMarshalerFlag.Name)
   295  			if err != nil {
   296  				return err
   297  			}
   298  			if value != "" {
   299  				eachName(f.Name, func(name string) {
   300  					f.set.Set(f.Name, value)
   301  				})
   302  			}
   303  		}
   304  	}
   305  	return nil
   306  }
   307  
   308  // GlobalTextMarshaler returns the value of a TextMarshalerFlag from the global flag set.
   309  func GlobalTextMarshaler(ctx *cli.Context, name string) TextMarshaler {
   310  	val := ctx.Generic(name)
   311  	if val == nil {
   312  		return nil
   313  	}
   314  	return val.(textMarshalerVal).v
   315  }
   316  
   317  // BigFlag is a command line flag that accepts 256 bit big integers in decimal or
   318  // hexadecimal syntax.
   319  type BigFlag struct {
   320  	Name  string
   321  	Value *big.Int
   322  	Usage string
   323  }
   324  
   325  // bigValue turns *big.Int into a flag.Value
   326  type bigValue big.Int
   327  
   328  func (b *bigValue) String() string {
   329  	if b == nil {
   330  		return ""
   331  	}
   332  	return (*big.Int)(b).String()
   333  }
   334  
   335  func (b *bigValue) Set(s string) error {
   336  	int, ok := math.ParseBig256(s)
   337  	if !ok {
   338  		return errors.New("invalid integer syntax")
   339  	}
   340  	*b = (bigValue)(*int)
   341  	return nil
   342  }
   343  
   344  func (f BigFlag) GetName() string {
   345  	return f.Name
   346  }
   347  
   348  func (f BigFlag) String() string {
   349  	fmtString := "%s %v\t%v"
   350  	if f.Value != nil {
   351  		fmtString = "%s \"%v\"\t%v"
   352  	}
   353  	return fmt.Sprintf(fmtString, prefixedNames(f.Name), f.Value, f.Usage)
   354  }
   355  
   356  func (f BigFlag) Apply(set *flag.FlagSet) {
   357  	eachName(f.Name, func(name string) {
   358  		set.Var((*bigValue)(f.Value), f.Name, f.Usage)
   359  	})
   360  }
   361  
   362  // GlobalBig returns the value of a BigFlag from the global flag set.
   363  func GlobalBig(ctx *cli.Context, name string) *big.Int {
   364  	val := ctx.Generic(name)
   365  	if val == nil {
   366  		return nil
   367  	}
   368  	return (*big.Int)(val.(*bigValue))
   369  }
   370  
   371  func prefixFor(name string) (prefix string) {
   372  	if len(name) == 1 {
   373  		prefix = "-"
   374  	} else {
   375  		prefix = "--"
   376  	}
   377  
   378  	return
   379  }
   380  
   381  func prefixedNames(fullName string) (prefixed string) {
   382  	parts := strings.Split(fullName, ",")
   383  	for i, name := range parts {
   384  		name = strings.Trim(name, " ")
   385  		prefixed += prefixFor(name) + name
   386  		if i < len(parts)-1 {
   387  			prefixed += ", "
   388  		}
   389  	}
   390  	return
   391  }
   392  
   393  // Expands a file path
   394  // 1. replace tilde with users home dir
   395  // 2. expands embedded environment variables
   396  // 3. cleans the path, e.g. /a/b/../c -> /a/c
   397  // Note, it has limitations, e.g. ~someuser/tmp will not be expanded
   398  func expandPath(p string) string {
   399  	if strings.HasPrefix(p, "~/") || strings.HasPrefix(p, "~\\") {
   400  		if home := homeDir(); home != "" {
   401  			p = home + p[1:]
   402  		}
   403  	}
   404  	return path.Clean(os.ExpandEnv(p))
   405  }
   406  
   407  func homeDir() string {
   408  	if home := os.Getenv("HOME"); home != "" {
   409  		return home
   410  	}
   411  	if usr, err := user.Current(); err == nil {
   412  		return usr.HomeDir
   413  	}
   414  	return ""
   415  }