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. 17// Pass `shellComplete` to continue parsing options on failure during shell 18// completion when, the user-supplied options may be incomplete. 19func parseIter(set *flag.FlagSet, ip iterativeParser, args []string, shellComplete bool) error { 20 for { 21 err := set.Parse(args) 22 if !ip.useShortOptionHandling() || err == nil { 23 if shellComplete { 24 return nil 25 } 26 return err 27 } 28 29 errStr := err.Error() 30 trimmed := strings.TrimPrefix(errStr, "flag provided but not defined: -") 31 if errStr == trimmed { 32 return err 33 } 34 35 // regenerate the initial args with the split short opts 36 argsWereSplit := false 37 for i, arg := range args { 38 // skip args that are not part of the error message 39 if name := strings.TrimLeft(arg, "-"); name != trimmed { 40 continue 41 } 42 43 // if we can't split, the error was accurate 44 shortOpts := splitShortOptions(set, arg) 45 if len(shortOpts) == 1 { 46 return err 47 } 48 49 // swap current argument with the split version 50 args = append(args[:i], append(shortOpts, args[i+1:]...)...) 51 argsWereSplit = true 52 break 53 } 54 55 // This should be an impossible to reach code path, but in case the arg 56 // splitting failed to happen, this will prevent infinite loops 57 if !argsWereSplit { 58 return err 59 } 60 61 // Since custom parsing failed, replace the flag set before retrying 62 newSet, err := ip.newFlagSet() 63 if err != nil { 64 return err 65 } 66 *set = *newSet 67 } 68} 69 70func splitShortOptions(set *flag.FlagSet, arg string) []string { 71 shortFlagsExist := func(s string) bool { 72 for _, c := range s[1:] { 73 if f := set.Lookup(string(c)); f == nil { 74 return false 75 } 76 } 77 return true 78 } 79 80 if !isSplittable(arg) || !shortFlagsExist(arg) { 81 return []string{arg} 82 } 83 84 separated := make([]string, 0, len(arg)-1) 85 for _, flagChar := range arg[1:] { 86 separated = append(separated, "-"+string(flagChar)) 87 } 88 89 return separated 90} 91 92func isSplittable(flagArg string) bool { 93 return strings.HasPrefix(flagArg, "-") && !strings.HasPrefix(flagArg, "--") && len(flagArg) > 2 94} 95