mirror of
https://github.com/wavetermdev/backup.git
synced 2026-08-05 13:57:07 -07:00
working on expand
This commit is contained in:
@@ -0,0 +1,205 @@
|
||||
package shparse
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"mvdan.cc/sh/v3/expand"
|
||||
)
|
||||
|
||||
const MaxExpandLen = 64 * 1024
|
||||
|
||||
type ExpandInfo struct {
|
||||
HasTilde bool // only ~ as the first character when SimpleExpandContext.HomeDir is set
|
||||
HasVar bool // $x, $$, ${...}
|
||||
HasGlob bool // *, ?, [, {
|
||||
HasExtGlob bool // ?(...) ... ?*+@!
|
||||
HasHistory bool // ! (anywhere)
|
||||
HasSpecial bool // subshell, arith
|
||||
}
|
||||
|
||||
type ExpandContext struct {
|
||||
HomeDir string
|
||||
}
|
||||
|
||||
func expandSQ(buf *bytes.Buffer, rawLit []rune) {
|
||||
// no info specials
|
||||
buf.WriteString(string(rawLit))
|
||||
}
|
||||
|
||||
// TODO implement our own ANSI single quote formatter
|
||||
func expandANSISQ(buf *bytes.Buffer, rawLit []rune) {
|
||||
// no info specials
|
||||
str, _, _ := expand.Format(nil, string(rawLit), nil)
|
||||
buf.WriteString(str)
|
||||
}
|
||||
|
||||
func expandLiteral(buf *bytes.Buffer, info *ExpandInfo, rawLit []rune) {
|
||||
var lastBackSlash bool
|
||||
var lastExtGlob bool
|
||||
var lastDollar bool
|
||||
for _, ch := range rawLit {
|
||||
if ch == 0 {
|
||||
break
|
||||
}
|
||||
if lastBackSlash {
|
||||
lastBackSlash = false
|
||||
if ch == '\n' {
|
||||
// special case, backslash *and* newline are ignored
|
||||
continue
|
||||
}
|
||||
buf.WriteRune(ch)
|
||||
continue
|
||||
}
|
||||
if ch == '\\' {
|
||||
lastBackSlash = true
|
||||
lastExtGlob = false
|
||||
lastDollar = false
|
||||
continue
|
||||
}
|
||||
if ch == '*' || ch == '?' || ch == '[' || ch == '{' {
|
||||
info.HasGlob = true
|
||||
}
|
||||
if ch == '`' {
|
||||
info.HasSpecial = true
|
||||
}
|
||||
if ch == '!' {
|
||||
info.HasHistory = true
|
||||
}
|
||||
if lastExtGlob && ch == '(' {
|
||||
info.HasExtGlob = true
|
||||
}
|
||||
if lastDollar && (ch != ' ' && ch != '"' && ch != '\'' && ch != '(' || ch != '[') {
|
||||
info.HasVar = true
|
||||
}
|
||||
if lastDollar && (ch == '(' || ch == '[') {
|
||||
info.HasSpecial = true
|
||||
}
|
||||
lastExtGlob = (ch == '?' || ch == '*' || ch == '+' || ch == '@' || ch == '!')
|
||||
lastDollar = (ch == '$')
|
||||
buf.WriteRune(ch)
|
||||
}
|
||||
if lastBackSlash {
|
||||
buf.WriteByte('\\')
|
||||
}
|
||||
}
|
||||
|
||||
// will also work for partial double quoted strings
|
||||
func expandDQLiteral(buf *bytes.Buffer, info *ExpandInfo, rawVal []rune) {
|
||||
var lastBackSlash bool
|
||||
var lastDollar bool
|
||||
for _, ch := range rawVal {
|
||||
if ch == 0 {
|
||||
break
|
||||
}
|
||||
if lastBackSlash {
|
||||
lastBackSlash = false
|
||||
if ch == '"' || ch == '\\' || ch == '$' || ch == '`' {
|
||||
buf.WriteRune(ch)
|
||||
continue
|
||||
}
|
||||
buf.WriteRune('\\')
|
||||
buf.WriteRune(ch)
|
||||
continue
|
||||
}
|
||||
if ch == '\\' {
|
||||
lastBackSlash = true
|
||||
lastDollar = false
|
||||
continue
|
||||
}
|
||||
|
||||
// similar to expandLiteral, but no globbing
|
||||
if ch == '`' {
|
||||
info.HasSpecial = true
|
||||
}
|
||||
if ch == '!' {
|
||||
info.HasHistory = true
|
||||
}
|
||||
if lastDollar && (ch != ' ' && ch != '"' && ch != '\'' && ch != '(' || ch != '[') {
|
||||
info.HasVar = true
|
||||
}
|
||||
if lastDollar && (ch == '(' || ch == '[') {
|
||||
info.HasSpecial = true
|
||||
}
|
||||
lastDollar = (ch == '$')
|
||||
buf.WriteRune(ch)
|
||||
}
|
||||
// in a valid parsed DQ string, you cannot have a trailing backslash (because \" would not end the string)
|
||||
// still putting the case here though in case we ever deal with incomplete strings (e.g. completion)
|
||||
if lastBackSlash {
|
||||
buf.WriteByte('\\')
|
||||
}
|
||||
}
|
||||
|
||||
func simpleExpandSubs(buf *bytes.Buffer, info *ExpandInfo, ectx ExpandContext, word *WordType, pos int) {
|
||||
fmt.Printf("expand subs: %v\n", word)
|
||||
parts := word.Subs
|
||||
startPos := word.contentStartPos()
|
||||
for _, part := range parts {
|
||||
remainingLen := pos - startPos
|
||||
if remainingLen <= 0 {
|
||||
break
|
||||
}
|
||||
simpleExpandWord(buf, info, ectx, part, remainingLen)
|
||||
startPos += len(part.Raw)
|
||||
}
|
||||
}
|
||||
|
||||
func simpleExpandWord(buf *bytes.Buffer, info *ExpandInfo, ectx ExpandContext, word *WordType, pos int) {
|
||||
if pos >= word.contentEndPos() {
|
||||
pos = len(word.Raw)
|
||||
}
|
||||
if pos <= word.contentStartPos() {
|
||||
return
|
||||
}
|
||||
|
||||
switch word.Type {
|
||||
case WordTypeLit:
|
||||
if word.QC.cur() == WordTypeDQ {
|
||||
expandDQLiteral(buf, info, word.Raw[:pos])
|
||||
return
|
||||
}
|
||||
expandLiteral(buf, info, word.Raw[:pos])
|
||||
|
||||
case WordTypeSQ:
|
||||
expandSQ(buf, word.Raw[word.contentStartPos():pos])
|
||||
return
|
||||
|
||||
case WordTypeDSQ:
|
||||
expandANSISQ(buf, word.Raw[word.contentStartPos():pos])
|
||||
return
|
||||
|
||||
case WordTypeDQ, WordTypeDDQ:
|
||||
simpleExpandSubs(buf, info, ectx, word, pos)
|
||||
return
|
||||
|
||||
case WordTypeGroup:
|
||||
simpleExpandSubs(buf, info, ectx, word, pos)
|
||||
return
|
||||
|
||||
case WordTypeSimpleVar:
|
||||
return
|
||||
|
||||
case WordTypeVarBrace:
|
||||
return
|
||||
|
||||
default:
|
||||
if pos > len(word.Raw) {
|
||||
pos = len(word.Raw)
|
||||
}
|
||||
info.HasSpecial = true
|
||||
buf.WriteString(string(word.Raw[:pos]))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func SimpleExpandPrefix(ectx ExpandContext, word *WordType, pos int) (string, ExpandInfo) {
|
||||
var buf bytes.Buffer
|
||||
var info ExpandInfo
|
||||
simpleExpandWord(&buf, &info, ectx, word, pos)
|
||||
return buf.String(), info
|
||||
}
|
||||
|
||||
func SimpleExpand(ectx ExpandContext, word *WordType) (string, ExpandInfo) {
|
||||
return SimpleExpandPrefix(ectx, word, len(word.Raw))
|
||||
}
|
||||
+14
-1
@@ -188,9 +188,22 @@ func (w *WordType) isBlank() bool {
|
||||
return w.Type == WordTypeLit && len(w.Raw) == 0
|
||||
}
|
||||
|
||||
func (w *WordType) contentEndPos() int {
|
||||
if !w.Complete {
|
||||
return len(w.Raw)
|
||||
}
|
||||
wmeta := wordMetaMap[w.Type]
|
||||
return len(w.Raw) - wmeta.SuffixLen
|
||||
}
|
||||
|
||||
func (w *WordType) contentStartPos() int {
|
||||
wmeta := wordMetaMap[w.Type]
|
||||
return wmeta.PrefixLen
|
||||
}
|
||||
|
||||
func (w *WordType) uncompletable() bool {
|
||||
switch w.Type {
|
||||
case WordTypeRaw, WordTypeOp, WordTypeKey, WordTypeDPP, WordTypePP, WordTypeDB:
|
||||
case WordTypeRaw, WordTypeOp, WordTypeKey, WordTypeDPP, WordTypePP, WordTypeDB, WordTypeBQ, WordTypeDP:
|
||||
return true
|
||||
|
||||
default:
|
||||
|
||||
@@ -157,3 +157,34 @@ func TestCompPos(t *testing.T) {
|
||||
|
||||
testCompPos(t, `ls abc"$(ls $"echo $(ls ./[*]x) foo)" `, true, 1, true, false, false)
|
||||
}
|
||||
|
||||
func testExpand(t *testing.T, str string, pos int, expStr string, expInfo *ExpandInfo) {
|
||||
ectx := ExpandContext{HomeDir: "/Users/mike"}
|
||||
words := Tokenize(str)
|
||||
if len(words) == 0 {
|
||||
t.Errorf("could not tokenize any words from %q", str)
|
||||
return
|
||||
}
|
||||
word := words[0]
|
||||
output, info := SimpleExpandPrefix(ectx, word, pos)
|
||||
if output != expStr {
|
||||
t.Errorf("error expanding %q, output:%q exp:%q", str, output, expStr)
|
||||
} else {
|
||||
fmt.Printf("expand: %q (%d) => %q\n", str, pos, output)
|
||||
}
|
||||
if expInfo != nil {
|
||||
if info != *expInfo {
|
||||
t.Errorf("error expanding %q, info:%v exp:%v", str, info, expInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestExpand(t *testing.T) {
|
||||
testExpand(t, "hello", 3, "hel", nil)
|
||||
testExpand(t, "he\\$xabc", 6, "he$xa", nil)
|
||||
testExpand(t, "he${x}abc", 6, "he$xa", nil)
|
||||
testExpand(t, "'hello\"mike'", 8, "hello\"m", nil)
|
||||
testExpand(t, `$'abc\x01def`, 10, "abc\x01d", nil)
|
||||
testExpand(t, `$((2 + 2))`, 6, "$((2 +", &ExpandInfo{HasSpecial: true})
|
||||
testExpand(t, `abc"def"`, 6, "abcde", nil)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user