1
1
mirror of https://github.com/wader/fq.git synced 2024-11-29 23:27:12 +03:00
fq/internal/shquote/shquote.go
2022-01-25 17:23:41 +01:00

163 lines
2.8 KiB
Go

package shquote
import (
"strings"
)
type Token struct {
Start int
End int
Str string
Separator bool
}
func Parse(s string) []Token {
type splitState int
const (
whitespace splitState = iota
escape
word
singleQuote
doubleQuote
doubleQuoteEscape
)
var tokens []Token
sb := &strings.Builder{}
ss := whitespace
start := 0
for i, r := range s {
switch ss {
case escape:
sb.WriteRune(r)
ss = word
continue
case doubleQuoteEscape:
if r != '"' {
sb.WriteRune('\\')
}
sb.WriteRune(r)
ss = doubleQuote
continue
default:
// nop
}
switch r {
case '\'':
switch ss {
case whitespace:
ss = singleQuote
start = i
case word:
tokens = append(tokens, Token{Start: start, End: i, Str: sb.String()})
sb.Reset()
ss = singleQuote
start = i
case singleQuote:
tokens = append(tokens, Token{Start: start, End: i, Str: sb.String()})
sb.Reset()
ss = whitespace
start = i
default:
sb.WriteRune(r)
}
case '"':
switch ss {
case whitespace:
ss = doubleQuote
start = i
case word:
tokens = append(tokens, Token{Start: start, End: i, Str: sb.String()})
sb.Reset()
ss = doubleQuote
start = i
case doubleQuote:
tokens = append(tokens, Token{Start: start, End: i, Str: sb.String()})
sb.Reset()
ss = whitespace
start = i
default:
sb.WriteRune(r)
}
case '\\':
switch ss {
case whitespace, word:
ss = escape
start = i
case doubleQuote:
ss = doubleQuoteEscape
start = i
default:
sb.WriteRune(r)
}
case ' ':
switch ss {
case whitespace:
tokens = append(tokens, Token{Separator: true})
case word:
tokens = append(tokens, Token{Start: start, End: i, Str: sb.String()})
tokens = append(tokens, Token{Separator: true})
sb.Reset()
ss = whitespace
start = i
default:
sb.WriteRune(r)
}
default:
switch ss {
case whitespace:
ss = word
start = i
default:
// nop
}
sb.WriteRune(r)
}
}
if sb.Len() > 0 {
switch ss {
case word, singleQuote, doubleQuote:
tokens = append(tokens, Token{Start: start, End: len(s), Str: sb.String()})
default:
// nop
}
}
tokens = append(tokens, Token{Separator: true})
var filtered []Token
prevSep := true
for _, t := range tokens {
if prevSep && t.Separator {
prevSep = true
continue
}
prevSep = t.Separator
filtered = append(filtered, t)
}
if len(filtered) > 0 && filtered[len(filtered)-1].Separator {
filtered = filtered[0 : len(filtered)-1]
}
return filtered
}
func Split(s string) []string {
var ss []string
sb := &strings.Builder{}
for _, t := range Parse(s) {
sb.WriteString(t.Str)
if t.Separator && sb.Len() > 0 {
ss = append(ss, sb.String())
sb.Reset()
}
}
if sb.Len() > 0 {
ss = append(ss, sb.String())
}
return ss
}