github.com/hasnat/dolt/go@v0.0.0-20210628190320-9eb5d843fbb7/libraries/utils/argparser/parser.go (about) 1 // Copyright 2019 Dolthub, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package argparser 16 17 import ( 18 "errors" 19 "sort" 20 "strings" 21 ) 22 23 const ( 24 optNameValDelimChars = " =:" 25 whitespaceChars = " \r\n\t" 26 27 helpFlag = "help" 28 helpFlagAbbrev = "h" 29 ) 30 31 func ValidatorFromStrList(paramName string, validStrList []string) ValidationFunc { 32 errSuffix := " is not a valid option for '" + paramName + "'. valid options are: " + strings.Join(validStrList, "|") 33 validStrSet := make(map[string]struct{}) 34 35 for _, str := range validStrList { 36 validStrSet[strings.ToLower(str)] = struct{}{} 37 } 38 39 return func(s string) error { 40 _, ok := validStrSet[strings.ToLower(s)] 41 42 if !ok { 43 return errors.New(s + errSuffix) 44 } 45 46 return nil 47 } 48 } 49 50 type ArgParser struct { 51 Supported []*Option 52 NameOrAbbrevToOpt map[string]*Option 53 ArgListHelp [][2]string 54 } 55 56 func NewArgParser() *ArgParser { 57 var supported []*Option 58 nameOrAbbrevToOpt := make(map[string]*Option) 59 return &ArgParser{supported, nameOrAbbrevToOpt, nil} 60 } 61 62 // Adds support for a new argument with the option given. Options must have a unique name and abbreviated name. 63 func (ap *ArgParser) SupportOption(opt *Option) { 64 name := opt.Name 65 abbrev := opt.Abbrev 66 67 _, nameExist := ap.NameOrAbbrevToOpt[name] 68 _, abbrevExist := ap.NameOrAbbrevToOpt[abbrev] 69 70 if name == "" { 71 panic("Name is required") 72 } else if name == "help" || abbrev == "help" || name == "h" || abbrev == "h" { 73 panic(`"help" and "h" are both reserved`) 74 } else if nameExist || abbrevExist { 75 panic("There is a bug. Two supported arguments have the same name or abbreviation") 76 } else if name[0] == '-' || (len(abbrev) > 0 && abbrev[0] == '-') { 77 panic("There is a bug. Option names, and abbreviations should not start with -") 78 } else if strings.IndexAny(name, optNameValDelimChars) != -1 || strings.IndexAny(name, whitespaceChars) != -1 { 79 panic("There is a bug. Option name contains an invalid character") 80 } 81 82 ap.Supported = append(ap.Supported, opt) 83 ap.NameOrAbbrevToOpt[name] = opt 84 85 if abbrev != "" { 86 ap.NameOrAbbrevToOpt[abbrev] = opt 87 } 88 } 89 90 // Adds support for a new flag (argument with no value). See SupportOpt for details on params. 91 func (ap *ArgParser) SupportsFlag(name, abbrev, desc string) *ArgParser { 92 opt := &Option{name, abbrev, "", OptionalFlag, desc, nil} 93 ap.SupportOption(opt) 94 95 return ap 96 } 97 98 // Adds support for a new string argument with the description given. See SupportOpt for details on params. 99 func (ap *ArgParser) SupportsString(name, abbrev, valDesc, desc string) *ArgParser { 100 opt := &Option{name, abbrev, valDesc, OptionalValue, desc, nil} 101 ap.SupportOption(opt) 102 103 return ap 104 } 105 106 func (ap *ArgParser) SupportsValidatedString(name, abbrev, valDesc, desc string, validator ValidationFunc) *ArgParser { 107 opt := &Option{name, abbrev, valDesc, OptionalValue, desc, validator} 108 ap.SupportOption(opt) 109 110 return ap 111 } 112 113 // Adds support for a new uint argument with the description given. See SupportOpt for details on params. 114 func (ap *ArgParser) SupportsUint(name, abbrev, valDesc, desc string) *ArgParser { 115 opt := &Option{name, abbrev, valDesc, OptionalValue, desc, isUintStr} 116 ap.SupportOption(opt) 117 118 return ap 119 } 120 121 // Adds support for a new int argument with the description given. See SupportOpt for details on params. 122 func (ap *ArgParser) SupportsInt(name, abbrev, valDesc, desc string) *ArgParser { 123 opt := &Option{name, abbrev, valDesc, OptionalValue, desc, isIntStr} 124 ap.SupportOption(opt) 125 126 return ap 127 } 128 129 // modal options in order of descending string length 130 func (ap *ArgParser) sortedModalOptions() []string { 131 smo := make([]string, 0, len(ap.Supported)) 132 for s, opt := range ap.NameOrAbbrevToOpt { 133 if opt.OptType == OptionalFlag && s != "" { 134 smo = append(smo, s) 135 } 136 } 137 sort.Slice(smo, func(i, j int) bool { return len(smo[i]) > len(smo[j]) }) 138 return smo 139 } 140 141 func (ap *ArgParser) matchModalOptions(arg string) (matches []*Option, rest string) { 142 rest = arg 143 144 // try to match longest options first 145 candidateFlagNames := ap.sortedModalOptions() 146 147 kontinue := true 148 for kontinue { 149 kontinue = false 150 151 // stop if we see a value option 152 for _, vo := range ap.sortedValueOptions() { 153 lv := len(vo) 154 isValOpt := len(rest) >= lv && rest[:lv] == vo 155 if isValOpt { 156 return matches, rest 157 } 158 } 159 160 for i, on := range candidateFlagNames { 161 lo := len(on) 162 isMatch := len(rest) >= lo && rest[:lo] == on 163 if isMatch { 164 rest = rest[lo:] 165 m := ap.NameOrAbbrevToOpt[on] 166 matches = append(matches, m) 167 168 // only match options once 169 head := candidateFlagNames[:i] 170 var tail []string 171 if i+1 < len(candidateFlagNames) { 172 tail = candidateFlagNames[i+1:] 173 } 174 candidateFlagNames = append(head, tail...) 175 176 kontinue = true 177 break 178 } 179 } 180 } 181 return matches, rest 182 } 183 184 func (ap *ArgParser) sortedValueOptions() []string { 185 vos := make([]string, 0, len(ap.Supported)) 186 for s, opt := range ap.NameOrAbbrevToOpt { 187 if opt.OptType == OptionalValue && s != "" { 188 vos = append(vos, s) 189 } 190 } 191 sort.Slice(vos, func(i, j int) bool { return len(vos[i]) > len(vos[j]) }) 192 return vos 193 } 194 195 func (ap *ArgParser) matchValueOption(arg string) (match *Option, value *string) { 196 for _, on := range ap.sortedValueOptions() { 197 lo := len(on) 198 isMatch := len(arg) >= lo && arg[:lo] == on 199 if isMatch { 200 v := arg[lo:] 201 v = strings.TrimLeft(v, optNameValDelimChars) 202 if len(v) > 0 { 203 value = &v 204 } 205 match = ap.NameOrAbbrevToOpt[on] 206 return match, value 207 } 208 } 209 return nil, nil 210 } 211 212 // Parses the string args given using the configuration previously specified with calls to the various Supports* 213 // methods. Any unrecognized arguments or incorrect types will result in an appropriate error being returned. If the 214 // universal --help or -h flag is found, an ErrHelp error is returned. 215 func (ap *ArgParser) Parse(args []string) (*ArgParseResults, error) { 216 list := make([]string, 0, 16) 217 results := make(map[string]string) 218 219 i := 0 220 for ; i < len(args); i++ { 221 arg := args[i] 222 223 if len(arg) == 0 || arg[0] != '-' || arg == "--" { // empty strings should get passed through like other naked words 224 list = append(list, arg) 225 continue 226 } 227 228 arg = strings.TrimLeft(arg, "-") 229 230 if arg == helpFlag || arg == helpFlagAbbrev { 231 return nil, ErrHelp 232 } 233 234 modalOpts, rest := ap.matchModalOptions(arg) 235 236 for _, opt := range modalOpts { 237 if _, exists := results[opt.Name]; exists { 238 return nil, errors.New("error: multiple values provided for `" + opt.Name + "'") 239 } 240 241 results[opt.Name] = "" 242 } 243 244 opt, value := ap.matchValueOption(rest) 245 246 if opt == nil { 247 if rest == "" { 248 continue 249 } 250 251 if len(modalOpts) > 0 { 252 // value was attached to modal flag 253 // eg: dolt branch -fdmy_branch 254 list = append(list, rest) 255 continue 256 } 257 258 return nil, UnknownArgumentParam{name: arg} 259 } 260 261 if _, exists := results[opt.Name]; exists { 262 //already provided 263 return nil, errors.New("error: multiple values provided for `" + opt.Name + "'") 264 } 265 266 if value == nil { 267 i++ 268 if i >= len(args) { 269 return nil, errors.New("error: no value for option `" + opt.Name + "'") 270 } 271 272 valueStr := args[i] 273 value = &valueStr 274 } 275 276 if opt.Validator != nil { 277 err := opt.Validator(*value) 278 279 if err != nil { 280 return nil, err 281 } 282 } 283 284 results[opt.Name] = *value 285 } 286 287 if i < len(args) { 288 copy(list, args[i:]) 289 } 290 291 return &ArgParseResults{results, list, ap}, nil 292 }