github.com/diamondburned/arikawa/v2@v2.1.0/bot/extras/shellwords/shellwords.go (about) 1 package shellwords 2 3 import ( 4 "strings" 5 ) 6 7 var escaper = strings.NewReplacer( 8 "__", "\\_\\_", 9 "\\", "\\\\", 10 ) 11 12 // ErrMissingClose is returned when the parsed line is missing a closing quote. 13 type ErrMissingClose struct { 14 Position int 15 Words string // joined 16 } 17 18 func (e ErrMissingClose) Error() string { 19 // Underline 7 characters around. 20 var start = e.Position 21 22 errstr := strings.Builder{} 23 errstr.WriteString("missing quote close") 24 25 if e.Words[start:] != "" { 26 errstr.WriteString(": ") 27 errstr.WriteString(escaper.Replace(e.Words[:start])) 28 errstr.WriteString("__") 29 errstr.WriteString(escaper.Replace(e.Words[start:])) 30 errstr.WriteString("__") 31 } 32 33 return errstr.String() 34 } 35 36 // Parse parses the given text to a slice of words. 37 func Parse(line string) ([]string, error) { 38 var args []string 39 var escaped, doubleQuoted, singleQuoted bool 40 41 var buf strings.Builder 42 buf.Grow(len(line)) 43 44 got := false 45 cursor := 0 46 47 runes := []rune(line) 48 49 for _, r := range runes { 50 if escaped { 51 buf.WriteRune(r) 52 escaped = false 53 continue 54 } 55 56 if r == '\\' { 57 if singleQuoted { 58 buf.WriteRune(r) 59 } else { 60 escaped = true 61 } 62 continue 63 } 64 65 if isSpace(r) { 66 switch { 67 case singleQuoted, doubleQuoted: 68 buf.WriteRune(r) 69 case got: 70 cursor += buf.Len() 71 args = append(args, buf.String()) 72 buf.Reset() 73 got = false 74 } 75 continue 76 } 77 78 switch r { 79 case '"', '“', '”': 80 if !singleQuoted { 81 if doubleQuoted { 82 got = true 83 } 84 doubleQuoted = !doubleQuoted 85 continue 86 } 87 case '\'', '`', '‘', '’': 88 if !doubleQuoted { 89 if singleQuoted { 90 got = true 91 } 92 93 singleQuoted = !singleQuoted 94 continue 95 } 96 } 97 98 got = true 99 buf.WriteRune(r) 100 } 101 102 if got { 103 args = append(args, buf.String()) 104 } 105 106 if escaped || singleQuoted || doubleQuoted { 107 return args, ErrMissingClose{ 108 Position: cursor + buf.Len(), 109 Words: strings.Join(args, " "), 110 } 111 } 112 113 return args, nil 114 } 115 116 func isSpace(r rune) bool { 117 switch r { 118 case ' ', '\t', '\r', '\n', ' ': 119 return true 120 } 121 return false 122 }