1package cli 2 3import ( 4 "flag" 5 "strings" 6) 7 8type iterativeParser interface { 9 newFlagSet() (*flag.FlagSet, error) 10 useShortOptionHandling() bool 11} 12 13// To enable short-option handling (e.g., "-it" vs "-i -t") we have to 14// iteratively catch parsing errors. This way we achieve LR parsing without 15// transforming any arguments. Otherwise, there is no way we can discriminate 16// combined short options from common arguments that should be left untouched. 17func parseIter(ip iterativeParser, args []string) (*flag.FlagSet, error) { 18 for { 19 set, err := ip.newFlagSet() 20 if err != nil { 21 return nil, err 22 } 23 24 err = set.Parse(args) 25 if !ip.useShortOptionHandling() || err == nil { 26 return set, err 27 } 28 29 errStr := err.Error() 30 trimmed := strings.TrimPrefix(errStr, "flag provided but not defined: ") 31 if errStr == trimmed { 32 return nil, err 33 } 34 35 // regenerate the initial args with the split short opts 36 newArgs := []string{} 37 for i, arg := range args { 38 if arg != trimmed { 39 newArgs = append(newArgs, arg) 40 continue 41 } 42 43 shortOpts := splitShortOptions(set, trimmed) 44 if len(shortOpts) == 1 { 45 return nil, err 46 } 47 48 // add each short option and all remaining arguments 49 newArgs = append(newArgs, shortOpts...) 50 newArgs = append(newArgs, args[i+1:]...) 51 args = newArgs 52 } 53 } 54} 55 56func splitShortOptions(set *flag.FlagSet, arg string) []string { 57 shortFlagsExist := func(s string) bool { 58 for _, c := range s[1:] { 59 if f := set.Lookup(string(c)); f == nil { 60 return false 61 } 62 } 63 return true 64 } 65 66 if !isSplittable(arg) || !shortFlagsExist(arg) { 67 return []string{arg} 68 } 69 70 separated := make([]string, 0, len(arg)-1) 71 for _, flagChar := range arg[1:] { 72 separated = append(separated, "-"+string(flagChar)) 73 } 74 75 return separated 76} 77 78func isSplittable(flagArg string) bool { 79 return strings.HasPrefix(flagArg, "-") && !strings.HasPrefix(flagArg, "--") && len(flagArg) > 2 80} 81