github.com/diamondburned/arikawa@v1.3.14/bot/extras/shellwords/shellwords.go (about) 1 package shellwords 2 3 import ( 4 "fmt" 5 "strings" 6 ) 7 8 // WordOffset is the offset from the position cursor to print on the error. 9 const WordOffset = 7 10 11 var escaper = strings.NewReplacer( 12 "`", "\\`", 13 "@", "\\@", 14 "\\", "\\\\", 15 ) 16 17 type ErrParse struct { 18 Position int 19 Words string // joined 20 } 21 22 func (e ErrParse) Error() string { 23 // Magic number 5. 24 var a = max(0, e.Position-WordOffset) 25 var b = min(len(e.Words), e.Position+WordOffset) 26 var word = e.Words[a:b] 27 var uidx = e.Position - a 28 29 errstr := strings.Builder{} 30 errstr.WriteString("Unexpected quote or escape") 31 32 // Do a bound check. 33 if uidx+1 > len(word) { 34 // Invalid. 35 errstr.WriteString(".") 36 return errstr.String() 37 } 38 39 // Write the pre-underline part. 40 fmt.Fprintf( 41 &errstr, ": %s__%s__", 42 escaper.Replace(word[:uidx]), 43 escaper.Replace(string(word[uidx:])), 44 ) 45 46 return errstr.String() 47 } 48 49 // Parse parses the given text to a slice of words. 50 func Parse(line string) ([]string, error) { 51 var args []string 52 var escaped, doubleQuoted, singleQuoted bool 53 54 var buf strings.Builder 55 buf.Grow(len(line)) 56 57 got := false 58 cursor := 0 59 60 runes := []rune(line) 61 62 for _, r := range runes { 63 if escaped { 64 buf.WriteRune(r) 65 escaped = false 66 continue 67 } 68 69 if r == '\\' { 70 if singleQuoted { 71 buf.WriteRune(r) 72 } else { 73 escaped = true 74 } 75 continue 76 } 77 78 if isSpace(r) { 79 switch { 80 case singleQuoted, doubleQuoted: 81 buf.WriteRune(r) 82 case got: 83 cursor += buf.Len() 84 args = append(args, buf.String()) 85 buf.Reset() 86 got = false 87 } 88 continue 89 } 90 91 switch r { 92 case '"': 93 if !singleQuoted { 94 if doubleQuoted { 95 got = true 96 } 97 doubleQuoted = !doubleQuoted 98 continue 99 } 100 case '\'', '`': 101 if !doubleQuoted { 102 if singleQuoted { 103 got = true 104 } 105 106 singleQuoted = !singleQuoted 107 continue 108 } 109 } 110 111 got = true 112 buf.WriteRune(r) 113 } 114 115 if got { 116 args = append(args, buf.String()) 117 } 118 119 if escaped || singleQuoted || doubleQuoted { 120 return args, &ErrParse{ 121 Position: cursor + buf.Len(), 122 Words: strings.Join(args, " "), 123 } 124 } 125 126 return args, nil 127 } 128 129 func isSpace(r rune) bool { 130 switch r { 131 case ' ', '\t', '\r', '\n', ' ': 132 return true 133 } 134 return false 135 } 136 137 func min(i, j int) int { 138 if i < j { 139 return i 140 } 141 return j 142 } 143 144 func max(i, j int) int { 145 if i < j { 146 return j 147 } 148 return i 149 }