vitess.io/vitess@v0.16.2/go/flagutil/flagutil.go (about)

     1  /*
     2  Copyright 2019 The Vitess Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreedto in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  // Package flagutil contains flags that parse string lists and string
    18  // maps.
    19  package flagutil
    20  
    21  import (
    22  	"errors"
    23  	"fmt"
    24  	"sort"
    25  	"strconv"
    26  	"strings"
    27  	"time"
    28  
    29  	"github.com/spf13/pflag"
    30  
    31  	"vitess.io/vitess/go/vt/log"
    32  )
    33  
    34  var (
    35  	errInvalidKeyValuePair = errors.New("invalid key:value pair")
    36  )
    37  
    38  // StringListValue is a []string flag that accepts a comma separated
    39  // list of elements. To include an element containing a comma, quote
    40  // it with a backslash '\'.
    41  type StringListValue []string
    42  
    43  // Get returns the []string value of this flag.
    44  func (value StringListValue) Get() any {
    45  	return []string(value)
    46  }
    47  
    48  func parseListWithEscapes(v string, delimiter rune) (value []string) {
    49  	var escaped, lastWasDelimiter bool
    50  	var current []rune
    51  
    52  	for _, r := range v {
    53  		lastWasDelimiter = false
    54  		if !escaped {
    55  			switch r {
    56  			case delimiter:
    57  				value = append(value, string(current))
    58  				current = nil
    59  				lastWasDelimiter = true
    60  				continue
    61  			case '\\':
    62  				escaped = true
    63  				continue
    64  			}
    65  		}
    66  		escaped = false
    67  		current = append(current, r)
    68  	}
    69  	if len(current) != 0 || lastWasDelimiter {
    70  		value = append(value, string(current))
    71  	}
    72  	return value
    73  }
    74  
    75  // Set sets the value of this flag from parsing the given string.
    76  func (value *StringListValue) Set(v string) error {
    77  	*value = parseListWithEscapes(v, ',')
    78  	return nil
    79  }
    80  
    81  // String returns the string representation of this flag.
    82  func (value StringListValue) String() string {
    83  	parts := make([]string, len(value))
    84  	for i, v := range value {
    85  		parts[i] = strings.Replace(strings.Replace(v, "\\", "\\\\", -1), ",", `\,`, -1)
    86  	}
    87  	return strings.Join(parts, ",")
    88  }
    89  
    90  func (value StringListValue) Type() string { return "strings" }
    91  
    92  // StringListVar defines a []string flag with the specified name, value and usage
    93  // string. The argument 'p' points to a []string in which to store the value of the flag.
    94  func StringListVar(fs *pflag.FlagSet, p *[]string, name string, defaultValue []string, usage string) {
    95  	*p = defaultValue
    96  	fs.Var((*StringListValue)(p), name, usage)
    97  }
    98  
    99  // StringMapValue is a map[string]string flag. It accepts a
   100  // comma-separated list of key value pairs, of the form key:value. The
   101  // keys cannot contain colons.
   102  //
   103  // TODO (andrew): Look into whether there's a native pflag Flag type that we can
   104  // use/transition to instead.
   105  type StringMapValue map[string]string
   106  
   107  // Set sets the value of this flag from parsing the given string.
   108  func (value *StringMapValue) Set(v string) error {
   109  	dict := make(map[string]string)
   110  	pairs := parseListWithEscapes(v, ',')
   111  	for _, pair := range pairs {
   112  		parts := strings.SplitN(pair, ":", 2)
   113  		if len(parts) != 2 {
   114  			return errInvalidKeyValuePair
   115  		}
   116  		dict[parts[0]] = parts[1]
   117  	}
   118  	*value = dict
   119  	return nil
   120  }
   121  
   122  // Get returns the map[string]string value of this flag.
   123  func (value StringMapValue) Get() any {
   124  	return map[string]string(value)
   125  }
   126  
   127  // String returns the string representation of this flag.
   128  func (value StringMapValue) String() string {
   129  	parts := make([]string, 0)
   130  	for k, v := range value {
   131  		parts = append(parts, k+":"+strings.Replace(v, ",", `\,`, -1))
   132  	}
   133  	// Generate the string deterministically.
   134  	sort.Strings(parts)
   135  	return strings.Join(parts, ",")
   136  }
   137  
   138  // Type is part of the pflag.Value interface.
   139  func (value StringMapValue) Type() string { return "StringMap" }
   140  
   141  // DualFormatStringListVar creates a flag which supports both dashes and underscores
   142  func DualFormatStringListVar(fs *pflag.FlagSet, p *[]string, name string, value []string, usage string) {
   143  	dashes := strings.Replace(name, "_", "-", -1)
   144  	underscores := strings.Replace(name, "-", "_", -1)
   145  
   146  	StringListVar(fs, p, underscores, value, usage)
   147  	if dashes != underscores {
   148  		StringListVar(fs, p, dashes, *p, fmt.Sprintf("Synonym to -%s", underscores))
   149  	}
   150  }
   151  
   152  // DualFormatStringVar creates a flag which supports both dashes and underscores
   153  func DualFormatStringVar(fs *pflag.FlagSet, p *string, name string, value string, usage string) {
   154  	dashes := strings.Replace(name, "_", "-", -1)
   155  	underscores := strings.Replace(name, "-", "_", -1)
   156  
   157  	fs.StringVar(p, underscores, value, usage)
   158  	if dashes != underscores {
   159  		fs.StringVar(p, dashes, *p, fmt.Sprintf("Synonym to -%s", underscores))
   160  	}
   161  }
   162  
   163  // DualFormatInt64Var creates a flag which supports both dashes and underscores
   164  func DualFormatInt64Var(fs *pflag.FlagSet, p *int64, name string, value int64, usage string) {
   165  	dashes := strings.Replace(name, "_", "-", -1)
   166  	underscores := strings.Replace(name, "-", "_", -1)
   167  
   168  	fs.Int64Var(p, underscores, value, usage)
   169  	if dashes != underscores {
   170  		fs.Int64Var(p, dashes, *p, fmt.Sprintf("Synonym to -%s", underscores))
   171  	}
   172  }
   173  
   174  // DualFormatIntVar creates a flag which supports both dashes and underscores
   175  func DualFormatIntVar(fs *pflag.FlagSet, p *int, name string, value int, usage string) {
   176  	dashes := strings.Replace(name, "_", "-", -1)
   177  	underscores := strings.Replace(name, "-", "_", -1)
   178  
   179  	fs.IntVar(p, underscores, value, usage)
   180  	if dashes != underscores {
   181  		fs.IntVar(p, dashes, *p, fmt.Sprintf("Synonym to -%s", underscores))
   182  	}
   183  }
   184  
   185  // DualFormatBoolVar creates a flag which supports both dashes and underscores
   186  func DualFormatBoolVar(fs *pflag.FlagSet, p *bool, name string, value bool, usage string) {
   187  	dashes := strings.Replace(name, "_", "-", -1)
   188  	underscores := strings.Replace(name, "-", "_", -1)
   189  
   190  	fs.BoolVar(p, underscores, value, usage)
   191  	if dashes != underscores {
   192  		fs.BoolVar(p, dashes, *p, fmt.Sprintf("Synonym to -%s", underscores))
   193  	}
   194  }
   195  
   196  // DurationOrIntVar implements pflag.Value for flags that have historically been
   197  // of type IntVar (and then converted to seconds or some other unit) but are
   198  // now transitioning to a proper DurationVar type.
   199  //
   200  // When parsing a command-line argument, it will first attempt to parse the
   201  // argument using time.ParseDuration; if this fails, it will fallback to
   202  // strconv.ParseInt and multiply that value by the `fallback` unit value to get
   203  // a duration. If the initial ParseDuration fails, it will also log a
   204  // deprecation warning.
   205  type DurationOrIntVar struct {
   206  	name     string
   207  	val      time.Duration
   208  	fallback time.Duration
   209  }
   210  
   211  // NewDurationOrIntVar returns a new DurationOrIntVar struct with the given name,
   212  // default value, and fallback unit.
   213  //
   214  // The name is used only when issuing a deprecation warning (so the user knows
   215  // which flag needs its argument format updated).
   216  //
   217  // The `fallback` argument is used when parsing an argument as an int (legacy behavior) as the multiplier
   218  // to get a time.Duration value. As an example, if a flag used to be "the amount
   219  // of time to wait in seconds" with a default of 60, you would do:
   220  //
   221  //	myFlag := flagutil.NewDurationOrIntVar("my-flag", time.Minute /* 60 second default */, time.Second /* fallback unit to multiply by */)
   222  func NewDurationOrIntVar(name string, val time.Duration, fallback time.Duration) *DurationOrIntVar {
   223  	return &DurationOrIntVar{name: name, val: val, fallback: fallback}
   224  }
   225  
   226  // Set is part of the pflag.Value interface.
   227  func (v *DurationOrIntVar) Set(s string) error {
   228  	d, derr := time.ParseDuration(s)
   229  	if derr != nil {
   230  		msg := &strings.Builder{}
   231  		fmt.Fprintf(msg, "non-duration value passed to %s (error: %s)", v.name, derr)
   232  
   233  		i, ierr := strconv.ParseInt(s, 10, 64)
   234  		if ierr != nil {
   235  			log.Warningf("%s; attempted to parse as int in %s, which failed with %s", msg.String(), v.fallback, ierr)
   236  			return ierr
   237  		}
   238  
   239  		d = time.Duration(i) * v.fallback
   240  		log.Warningf("%s; parsed as int to %s, which is deprecated behavior", d)
   241  	}
   242  
   243  	v.val = d
   244  	return nil
   245  }
   246  
   247  // String is part of the pflag.Value interface.
   248  func (v *DurationOrIntVar) String() string { return v.val.String() }
   249  
   250  // Type is part of the pflag.Type interface.
   251  func (v *DurationOrIntVar) Type() string { return "duration" }
   252  
   253  // Value returns the underlying Duration value passed to the flag.
   254  func (v *DurationOrIntVar) Value() time.Duration { return v.val }