package line
import (
"bytes"
"errors"
"strings"
"unicode/utf8"
"github.com/kballard/go-shellquote"
"mvdan.cc/sh/v3/syntax"
)
var (
SplitChars = " \n\t"
SingleChar = '\''
DoubleChar = '"'
EscapeChar = '\\'
DoubleEscapeChars = "$`\"\n\\"
)
var (
ErrUnterminatedSingleQuote = errors .New ("unterminated single-quoted string" )
ErrUnterminatedDoubleQuote = errors .New ("unterminated double-quoted string" )
ErrUnterminatedEscape = errors .New ("unterminated backslash-escape" )
)
func Parse (line string ) (args []string , err error ) {
lineReader := strings .NewReader (line )
parser := syntax .NewParser (syntax .KeepComments (false ))
stmts , err := parser .Parse (lineReader , "" )
if err != nil {
return nil , err
}
var parsedLine bytes .Buffer
err = syntax .NewPrinter ().Print (&parsedLine , stmts )
if err != nil {
return nil , err
}
return shellquote .Split (parsedLine .String ())
}
func AcceptMultiline (line []rune ) (accept bool ) {
_ , _ , err := Split (string (line ), false )
if err == nil {
return true
}
switch err {
case ErrUnterminatedDoubleQuote , ErrUnterminatedSingleQuote :
return false
case ErrUnterminatedEscape :
if len (line ) > 0 && line [len (line )-1 ] == '\\' {
return false
}
return true
}
return true
}
func IsEmpty (line string , emptyChars ...rune ) bool {
empty := true
for _ , r := range line {
if !strings .ContainsRune (string (emptyChars ), r ) {
empty = false
break
}
}
return empty
}
func UnescapeValue (prefixComp , prefixLine , val string ) string {
quoted := strings .HasPrefix (prefixLine , "\"" ) ||
strings .HasPrefix (prefixLine , "'" )
if quoted {
val = strings .ReplaceAll (val , "\\ " , " " )
}
return val
}
func TrimSpaces (remain []string ) (trimmed []string ) {
for _ , word := range remain {
trimmed = append (trimmed , strings .TrimSpace (word ))
}
return
}
func Split (input string , hl bool ) (words []string , remainder string , err error ) {
var buf bytes .Buffer
words = make ([]string , 0 )
for len (input ) > 0 {
c , l := utf8 .DecodeRuneInString (input )
if strings .ContainsRune (SplitChars , c ) {
if hl {
if len (words ) == 0 {
words = append (words , string (c ))
} else {
words [len (words )-1 ] += string (c )
}
}
input = input [l :]
continue
} else if c == EscapeChar {
next := input [l :]
if len (next ) == 0 {
if hl {
remainder = string (EscapeChar )
}
err = ErrUnterminatedEscape
return words , remainder , err
}
c2 , l2 := utf8 .DecodeRuneInString (next )
if c2 == '\n' {
if hl {
if len (words ) == 0 {
words = append (words , string (c )+string (c2 ))
} else {
words [len (words )-1 ] += string (c ) + string (c2 )
}
}
input = next [l2 :]
continue
}
}
var word string
word , input , err = splitWord (input , &buf , hl )
if err != nil {
remainder = input
return words , remainder , err
}
words = append (words , word )
}
return words , remainder , err
}
func splitWord(input string , buf *bytes .Buffer , hl bool ) (word string , remainder string , err error ) {
buf .Reset ()
raw :
{
cur := input
for len (cur ) > 0 {
c , l := utf8 .DecodeRuneInString (cur )
cur = cur [l :]
if c == SingleChar {
buf .WriteString (input [0 : len (input )-len (cur )-l ])
input = cur
goto single
} else if c == DoubleChar {
buf .WriteString (input [0 : len (input )-len (cur )-l ])
input = cur
goto double
} else if c == EscapeChar {
buf .WriteString (input [0 : len (input )-len (cur )-l ])
if hl {
buf .WriteRune (c )
}
input = cur
goto escape
} else if strings .ContainsRune (SplitChars , c ) {
buf .WriteString (input [0 : len (input )-len (cur )-l ])
if hl {
buf .WriteRune (c )
}
return buf .String (), cur , nil
}
}
if len (input ) > 0 {
buf .WriteString (input )
input = ""
}
goto done
}
escape :
{
if len (input ) == 0 {
if hl {
input = buf .String () + input
}
return "" , input , ErrUnterminatedEscape
}
c , l := utf8 .DecodeRuneInString (input )
if c == '\n' {
} else {
buf .WriteString (input [:l ])
}
input = input [l :]
}
goto raw
single :
{
i := strings .IndexRune (input , SingleChar )
if i == -1 {
if hl {
input = buf .String () + YellowFG + string (SingleChar ) + input
}
return "" , input , ErrUnterminatedSingleQuote
}
if hl {
buf .WriteString (YellowFG )
buf .WriteRune (SingleChar )
}
buf .WriteString (input [0 :i ])
input = input [i +1 :]
if hl {
buf .WriteRune (SingleChar )
buf .WriteString (ResetFG )
}
goto raw
}
double :
{
cur := input
for len (cur ) > 0 {
c , l := utf8 .DecodeRuneInString (cur )
cur = cur [l :]
if c == DoubleChar {
if hl {
buf .WriteString (YellowFG )
buf .WriteRune (c )
}
buf .WriteString (input [0 : len (input )-len (cur )-l ])
if hl {
buf .WriteRune (c )
buf .WriteString (ResetFG )
}
input = cur
goto raw
} else if c == EscapeChar && !hl {
c2 , l2 := utf8 .DecodeRuneInString (cur )
cur = cur [l2 :]
if strings .ContainsRune (DoubleEscapeChars , c2 ) {
buf .WriteString (input [0 : len (input )-len (cur )-l -l2 ])
if c2 == '\n' {
} else {
buf .WriteRune (c2 )
}
input = cur
}
}
}
if hl {
input = buf .String () + YellowFG + string (DoubleChar ) + input
}
return "" , input , ErrUnterminatedDoubleQuote
}
done :
return buf .String (), input , nil
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .