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 }