package console
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 (c *Console ) 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 (c *Console ) 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 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 () + seqFgYellow + string (singleChar ) + input
}
return "" , input , errUnterminatedSingleQuote
}
if hl {
buf .WriteString (seqFgYellow )
buf .WriteRune (singleChar )
}
buf .WriteString (input [0 :i ])
input = input [i +1 :]
if hl {
buf .WriteRune (singleChar )
buf .WriteString (seqFgReset )
}
goto raw
}
double :
{
cur := input
for len (cur ) > 0 {
c , l := utf8 .DecodeRuneInString (cur )
cur = cur [l :]
if c == doubleChar {
if hl {
buf .WriteString (seqFgYellow )
buf .WriteRune (c )
}
buf .WriteString (input [0 : len (input )-len (cur )-l ])
if hl {
buf .WriteRune (c )
buf .WriteString (seqFgReset )
}
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 () + seqFgYellow + string (doubleChar ) + input
}
return "" , input , errUnterminatedDoubleQuote
}
done :
return buf .String (), input , nil
}
func trimSpacesMatch(remain []string ) (trimmed []string ) {
for _ , word := range remain {
trimmed = append (trimmed , strings .TrimSpace (word ))
}
return
}
func (c *Console ) lineEmpty (line string ) bool {
empty := true
for _ , r := range line {
if !strings .ContainsRune (string (c .EmptyChars ), r ) {
empty = false
break
}
}
return empty
}
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 .