You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

parse.go 2.4KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. package cli
  2. import (
  3. "flag"
  4. "strings"
  5. )
  6. type iterativeParser interface {
  7. newFlagSet() (*flag.FlagSet, error)
  8. useShortOptionHandling() bool
  9. }
  10. // To enable short-option handling (e.g., "-it" vs "-i -t") we have to
  11. // iteratively catch parsing errors. This way we achieve LR parsing without
  12. // transforming any arguments. Otherwise, there is no way we can discriminate
  13. // combined short options from common arguments that should be left untouched.
  14. // Pass `shellComplete` to continue parsing options on failure during shell
  15. // completion when, the user-supplied options may be incomplete.
  16. func parseIter(set *flag.FlagSet, ip iterativeParser, args []string, shellComplete bool) error {
  17. for {
  18. err := set.Parse(args)
  19. if !ip.useShortOptionHandling() || err == nil {
  20. if shellComplete {
  21. return nil
  22. }
  23. return err
  24. }
  25. errStr := err.Error()
  26. trimmed := strings.TrimPrefix(errStr, "flag provided but not defined: -")
  27. if errStr == trimmed {
  28. return err
  29. }
  30. // regenerate the initial args with the split short opts
  31. argsWereSplit := false
  32. for i, arg := range args {
  33. // skip args that are not part of the error message
  34. if name := strings.TrimLeft(arg, "-"); name != trimmed {
  35. continue
  36. }
  37. // if we can't split, the error was accurate
  38. shortOpts := splitShortOptions(set, arg)
  39. if len(shortOpts) == 1 {
  40. return err
  41. }
  42. // swap current argument with the split version
  43. args = append(args[:i], append(shortOpts, args[i+1:]...)...)
  44. argsWereSplit = true
  45. break
  46. }
  47. // This should be an impossible to reach code path, but in case the arg
  48. // splitting failed to happen, this will prevent infinite loops
  49. if !argsWereSplit {
  50. return err
  51. }
  52. // Since custom parsing failed, replace the flag set before retrying
  53. newSet, err := ip.newFlagSet()
  54. if err != nil {
  55. return err
  56. }
  57. *set = *newSet
  58. }
  59. }
  60. func splitShortOptions(set *flag.FlagSet, arg string) []string {
  61. shortFlagsExist := func(s string) bool {
  62. for _, c := range s[1:] {
  63. if f := set.Lookup(string(c)); f == nil {
  64. return false
  65. }
  66. }
  67. return true
  68. }
  69. if !isSplittable(arg) || !shortFlagsExist(arg) {
  70. return []string{arg}
  71. }
  72. separated := make([]string, 0, len(arg)-1)
  73. for _, flagChar := range arg[1:] {
  74. separated = append(separated, "-"+string(flagChar))
  75. }
  76. return separated
  77. }
  78. func isSplittable(flagArg string) bool {
  79. return strings.HasPrefix(flagArg, "-") && !strings.HasPrefix(flagArg, "--") && len(flagArg) > 2
  80. }