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 }