...

Source file src/github.com/urfave/cli/v2/flag.go

Documentation: github.com/urfave/cli/v2

     1  package cli
     2  
     3  import (
     4  	"errors"
     5  	"flag"
     6  	"fmt"
     7  	"io"
     8  	"os"
     9  	"regexp"
    10  	"runtime"
    11  	"strings"
    12  	"syscall"
    13  	"time"
    14  )
    15  
    16  const defaultPlaceholder = "value"
    17  
    18  const (
    19  	defaultSliceFlagSeparator = ","
    20  	disableSliceFlagSeparator = false
    21  )
    22  
    23  var (
    24  	slPfx = fmt.Sprintf("sl:::%d:::", time.Now().UTC().UnixNano())
    25  
    26  	commaWhitespace = regexp.MustCompile("[, ]+.*")
    27  )
    28  
    29  // BashCompletionFlag enables bash-completion for all commands and subcommands
    30  var BashCompletionFlag Flag = &BoolFlag{
    31  	Name:   "generate-bash-completion",
    32  	Hidden: true,
    33  }
    34  
    35  // VersionFlag prints the version for the application
    36  var VersionFlag Flag = &BoolFlag{
    37  	Name:               "version",
    38  	Aliases:            []string{"v"},
    39  	Usage:              "print the version",
    40  	DisableDefaultText: true,
    41  }
    42  
    43  // HelpFlag prints the help for all commands and subcommands.
    44  // Set to nil to disable the flag.  The subcommand
    45  // will still be added unless HideHelp or HideHelpCommand is set to true.
    46  var HelpFlag Flag = &BoolFlag{
    47  	Name:               "help",
    48  	Aliases:            []string{"h"},
    49  	Usage:              "show help",
    50  	DisableDefaultText: true,
    51  }
    52  
    53  // FlagStringer converts a flag definition to a string. This is used by help
    54  // to display a flag.
    55  var FlagStringer FlagStringFunc = stringifyFlag
    56  
    57  // Serializer is used to circumvent the limitations of flag.FlagSet.Set
    58  type Serializer interface {
    59  	Serialize() string
    60  }
    61  
    62  // FlagNamePrefixer converts a full flag name and its placeholder into the help
    63  // message flag prefix. This is used by the default FlagStringer.
    64  var FlagNamePrefixer FlagNamePrefixFunc = prefixedNames
    65  
    66  // FlagEnvHinter annotates flag help message with the environment variable
    67  // details. This is used by the default FlagStringer.
    68  var FlagEnvHinter FlagEnvHintFunc = withEnvHint
    69  
    70  // FlagFileHinter annotates flag help message with the environment variable
    71  // details. This is used by the default FlagStringer.
    72  var FlagFileHinter FlagFileHintFunc = withFileHint
    73  
    74  // FlagsByName is a slice of Flag.
    75  type FlagsByName []Flag
    76  
    77  func (f FlagsByName) Len() int {
    78  	return len(f)
    79  }
    80  
    81  func (f FlagsByName) Less(i, j int) bool {
    82  	if len(f[j].Names()) == 0 {
    83  		return false
    84  	} else if len(f[i].Names()) == 0 {
    85  		return true
    86  	}
    87  	return lexicographicLess(f[i].Names()[0], f[j].Names()[0])
    88  }
    89  
    90  func (f FlagsByName) Swap(i, j int) {
    91  	f[i], f[j] = f[j], f[i]
    92  }
    93  
    94  // ActionableFlag is an interface that wraps Flag interface and RunAction operation.
    95  type ActionableFlag interface {
    96  	Flag
    97  	RunAction(*Context) error
    98  }
    99  
   100  // Flag is a common interface related to parsing flags in cli.
   101  // For more advanced flag parsing techniques, it is recommended that
   102  // this interface be implemented.
   103  type Flag interface {
   104  	fmt.Stringer
   105  	// Apply Flag settings to the given flag set
   106  	Apply(*flag.FlagSet) error
   107  	Names() []string
   108  	IsSet() bool
   109  }
   110  
   111  // RequiredFlag is an interface that allows us to mark flags as required
   112  // it allows flags required flags to be backwards compatible with the Flag interface
   113  type RequiredFlag interface {
   114  	Flag
   115  
   116  	IsRequired() bool
   117  }
   118  
   119  // DocGenerationFlag is an interface that allows documentation generation for the flag
   120  type DocGenerationFlag interface {
   121  	Flag
   122  
   123  	// TakesValue returns true if the flag takes a value, otherwise false
   124  	TakesValue() bool
   125  
   126  	// GetUsage returns the usage string for the flag
   127  	GetUsage() string
   128  
   129  	// GetValue returns the flags value as string representation and an empty
   130  	// string if the flag takes no value at all.
   131  	GetValue() string
   132  
   133  	// GetDefaultText returns the default text for this flag
   134  	GetDefaultText() string
   135  
   136  	// GetEnvVars returns the env vars for this flag
   137  	GetEnvVars() []string
   138  }
   139  
   140  // DocGenerationSliceFlag extends DocGenerationFlag for slice-based flags.
   141  type DocGenerationSliceFlag interface {
   142  	DocGenerationFlag
   143  
   144  	// IsSliceFlag returns true for flags that can be given multiple times.
   145  	IsSliceFlag() bool
   146  }
   147  
   148  // VisibleFlag is an interface that allows to check if a flag is visible
   149  type VisibleFlag interface {
   150  	Flag
   151  
   152  	// IsVisible returns true if the flag is not hidden, otherwise false
   153  	IsVisible() bool
   154  }
   155  
   156  // CategorizableFlag is an interface that allows us to potentially
   157  // use a flag in a categorized representation.
   158  type CategorizableFlag interface {
   159  	VisibleFlag
   160  
   161  	GetCategory() string
   162  }
   163  
   164  // Countable is an interface to enable detection of flag values which support
   165  // repetitive flags
   166  type Countable interface {
   167  	Count() int
   168  }
   169  
   170  func flagSet(name string, flags []Flag, spec separatorSpec) (*flag.FlagSet, error) {
   171  	set := flag.NewFlagSet(name, flag.ContinueOnError)
   172  
   173  	for _, f := range flags {
   174  		if c, ok := f.(customizedSeparator); ok {
   175  			c.WithSeparatorSpec(spec)
   176  		}
   177  		if err := f.Apply(set); err != nil {
   178  			return nil, err
   179  		}
   180  	}
   181  	set.SetOutput(io.Discard)
   182  	return set, nil
   183  }
   184  
   185  func copyFlag(name string, ff *flag.Flag, set *flag.FlagSet) {
   186  	switch ff.Value.(type) {
   187  	case Serializer:
   188  		_ = set.Set(name, ff.Value.(Serializer).Serialize())
   189  	default:
   190  		_ = set.Set(name, ff.Value.String())
   191  	}
   192  }
   193  
   194  func normalizeFlags(flags []Flag, set *flag.FlagSet) error {
   195  	visited := make(map[string]bool)
   196  	set.Visit(func(f *flag.Flag) {
   197  		visited[f.Name] = true
   198  	})
   199  	for _, f := range flags {
   200  		parts := f.Names()
   201  		if len(parts) == 1 {
   202  			continue
   203  		}
   204  		var ff *flag.Flag
   205  		for _, name := range parts {
   206  			name = strings.Trim(name, " ")
   207  			if visited[name] {
   208  				if ff != nil {
   209  					return errors.New("Cannot use two forms of the same flag: " + name + " " + ff.Name)
   210  				}
   211  				ff = set.Lookup(name)
   212  			}
   213  		}
   214  		if ff == nil {
   215  			continue
   216  		}
   217  		for _, name := range parts {
   218  			name = strings.Trim(name, " ")
   219  			if !visited[name] {
   220  				copyFlag(name, ff, set)
   221  			}
   222  		}
   223  	}
   224  	return nil
   225  }
   226  
   227  func visibleFlags(fl []Flag) []Flag {
   228  	var visible []Flag
   229  	for _, f := range fl {
   230  		if vf, ok := f.(VisibleFlag); ok && vf.IsVisible() {
   231  			visible = append(visible, f)
   232  		}
   233  	}
   234  	return visible
   235  }
   236  
   237  func prefixFor(name string) (prefix string) {
   238  	if len(name) == 1 {
   239  		prefix = "-"
   240  	} else {
   241  		prefix = "--"
   242  	}
   243  
   244  	return
   245  }
   246  
   247  // Returns the placeholder, if any, and the unquoted usage string.
   248  func unquoteUsage(usage string) (string, string) {
   249  	for i := 0; i < len(usage); i++ {
   250  		if usage[i] == '`' {
   251  			for j := i + 1; j < len(usage); j++ {
   252  				if usage[j] == '`' {
   253  					name := usage[i+1 : j]
   254  					usage = usage[:i] + name + usage[j+1:]
   255  					return name, usage
   256  				}
   257  			}
   258  			break
   259  		}
   260  	}
   261  	return "", usage
   262  }
   263  
   264  func prefixedNames(names []string, placeholder string) string {
   265  	var prefixed string
   266  	for i, name := range names {
   267  		if name == "" {
   268  			continue
   269  		}
   270  
   271  		prefixed += prefixFor(name) + name
   272  		if placeholder != "" {
   273  			prefixed += " " + placeholder
   274  		}
   275  		if i < len(names)-1 {
   276  			prefixed += ", "
   277  		}
   278  	}
   279  	return prefixed
   280  }
   281  
   282  func envFormat(envVars []string, prefix, sep, suffix string) string {
   283  	if len(envVars) > 0 {
   284  		return fmt.Sprintf(" [%s%s%s]", prefix, strings.Join(envVars, sep), suffix)
   285  	}
   286  	return ""
   287  }
   288  
   289  func defaultEnvFormat(envVars []string) string {
   290  	return envFormat(envVars, "$", ", $", "")
   291  }
   292  
   293  func withEnvHint(envVars []string, str string) string {
   294  	envText := ""
   295  	if runtime.GOOS != "windows" || os.Getenv("PSHOME") != "" {
   296  		envText = defaultEnvFormat(envVars)
   297  	} else {
   298  		envText = envFormat(envVars, "%", "%, %", "%")
   299  	}
   300  	return str + envText
   301  }
   302  
   303  func FlagNames(name string, aliases []string) []string {
   304  	var ret []string
   305  
   306  	for _, part := range append([]string{name}, aliases...) {
   307  		// v1 -> v2 migration warning zone:
   308  		// Strip off anything after the first found comma or space, which
   309  		// *hopefully* makes it a tiny bit more obvious that unexpected behavior is
   310  		// caused by using the v1 form of stringly typed "Name".
   311  		ret = append(ret, commaWhitespace.ReplaceAllString(part, ""))
   312  	}
   313  
   314  	return ret
   315  }
   316  
   317  func withFileHint(filePath, str string) string {
   318  	fileText := ""
   319  	if filePath != "" {
   320  		fileText = fmt.Sprintf(" [%s]", filePath)
   321  	}
   322  	return str + fileText
   323  }
   324  
   325  func formatDefault(format string) string {
   326  	return " (default: " + format + ")"
   327  }
   328  
   329  func stringifyFlag(f Flag) string {
   330  	// enforce DocGeneration interface on flags to avoid reflection
   331  	df, ok := f.(DocGenerationFlag)
   332  	if !ok {
   333  		return ""
   334  	}
   335  
   336  	placeholder, usage := unquoteUsage(df.GetUsage())
   337  	needsPlaceholder := df.TakesValue()
   338  
   339  	if needsPlaceholder && placeholder == "" {
   340  		placeholder = defaultPlaceholder
   341  	}
   342  
   343  	defaultValueString := ""
   344  
   345  	// set default text for all flags except bool flags
   346  	// for bool flags display default text if DisableDefaultText is not
   347  	// set
   348  	if bf, ok := f.(*BoolFlag); !ok || !bf.DisableDefaultText {
   349  		if s := df.GetDefaultText(); s != "" {
   350  			defaultValueString = fmt.Sprintf(formatDefault("%s"), s)
   351  		}
   352  	}
   353  
   354  	usageWithDefault := strings.TrimSpace(usage + defaultValueString)
   355  
   356  	pn := prefixedNames(df.Names(), placeholder)
   357  	sliceFlag, ok := f.(DocGenerationSliceFlag)
   358  	if ok && sliceFlag.IsSliceFlag() {
   359  		pn = pn + " [ " + pn + " ]"
   360  	}
   361  
   362  	return withEnvHint(df.GetEnvVars(), fmt.Sprintf("%s\t%s", pn, usageWithDefault))
   363  }
   364  
   365  func hasFlag(flags []Flag, fl Flag) bool {
   366  	for _, existing := range flags {
   367  		if fl == existing {
   368  			return true
   369  		}
   370  	}
   371  
   372  	return false
   373  }
   374  
   375  // Return the first value from a list of environment variables and files
   376  // (which may or may not exist), a description of where the value was found,
   377  // and a boolean which is true if a value was found.
   378  func flagFromEnvOrFile(envVars []string, filePath string) (value string, fromWhere string, found bool) {
   379  	for _, envVar := range envVars {
   380  		envVar = strings.TrimSpace(envVar)
   381  		if value, found := syscall.Getenv(envVar); found {
   382  			return value, fmt.Sprintf("environment variable %q", envVar), true
   383  		}
   384  	}
   385  	for _, fileVar := range strings.Split(filePath, ",") {
   386  		if fileVar != "" {
   387  			if data, err := os.ReadFile(fileVar); err == nil {
   388  				return string(data), fmt.Sprintf("file %q", filePath), true
   389  			}
   390  		}
   391  	}
   392  	return "", "", false
   393  }
   394  
   395  type customizedSeparator interface {
   396  	WithSeparatorSpec(separatorSpec)
   397  }
   398  
   399  type separatorSpec struct {
   400  	sep        string
   401  	disabled   bool
   402  	customized bool
   403  }
   404  
   405  func (s separatorSpec) flagSplitMultiValues(val string) []string {
   406  	var (
   407  		disabled bool   = s.disabled
   408  		sep      string = s.sep
   409  	)
   410  	if !s.customized {
   411  		disabled = disableSliceFlagSeparator
   412  		sep = defaultSliceFlagSeparator
   413  	}
   414  	if disabled {
   415  		return []string{val}
   416  	}
   417  
   418  	return strings.Split(val, sep)
   419  }
   420  

View as plain text