parse.go 1.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980
  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. func parseIter(ip iterativeParser, args []string) (*flag.FlagSet, error) {
  15. for {
  16. set, err := ip.newFlagSet()
  17. if err != nil {
  18. return nil, err
  19. }
  20. err = set.Parse(args)
  21. if !ip.useShortOptionHandling() || err == nil {
  22. return set, err
  23. }
  24. errStr := err.Error()
  25. trimmed := strings.TrimPrefix(errStr, "flag provided but not defined: ")
  26. if errStr == trimmed {
  27. return nil, err
  28. }
  29. // regenerate the initial args with the split short opts
  30. newArgs := []string{}
  31. for i, arg := range args {
  32. if arg != trimmed {
  33. newArgs = append(newArgs, arg)
  34. continue
  35. }
  36. shortOpts := splitShortOptions(set, trimmed)
  37. if len(shortOpts) == 1 {
  38. return nil, err
  39. }
  40. // add each short option and all remaining arguments
  41. newArgs = append(newArgs, shortOpts...)
  42. newArgs = append(newArgs, args[i+1:]...)
  43. args = newArgs
  44. }
  45. }
  46. }
  47. func splitShortOptions(set *flag.FlagSet, arg string) []string {
  48. shortFlagsExist := func(s string) bool {
  49. for _, c := range s[1:] {
  50. if f := set.Lookup(string(c)); f == nil {
  51. return false
  52. }
  53. }
  54. return true
  55. }
  56. if !isSplittable(arg) || !shortFlagsExist(arg) {
  57. return []string{arg}
  58. }
  59. separated := make([]string, 0, len(arg)-1)
  60. for _, flagChar := range arg[1:] {
  61. separated = append(separated, "-"+string(flagChar))
  62. }
  63. return separated
  64. }
  65. func isSplittable(flagArg string) bool {
  66. return strings.HasPrefix(flagArg, "-") && !strings.HasPrefix(flagArg, "--") && len(flagArg) > 2
  67. }