mirror of
https://github.com/wavetermdev/waveterm.git
synced 2025-03-09 13:00:53 +01:00
move to simpleexpand
This commit is contained in:
parent
01821ca094
commit
da2fe25fb8
@ -683,7 +683,7 @@ func ParseJsonPacket(jsonBuf []byte) (PacketType, error) {
|
|||||||
}
|
}
|
||||||
err = json.Unmarshal(jsonBuf, pk)
|
err = json.Unmarshal(jsonBuf, pk)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("unmarshaling %q packet: %v", bareCmd.Type, err)
|
||||||
}
|
}
|
||||||
return pk, nil
|
return pk, nil
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
|
|
||||||
"github.com/alessio/shellescape"
|
"github.com/alessio/shellescape"
|
||||||
"github.com/scripthaus-dev/mshell/pkg/packet"
|
"github.com/scripthaus-dev/mshell/pkg/packet"
|
||||||
|
"github.com/scripthaus-dev/mshell/pkg/simpleexpand"
|
||||||
"mvdan.cc/sh/v3/expand"
|
"mvdan.cc/sh/v3/expand"
|
||||||
"mvdan.cc/sh/v3/syntax"
|
"mvdan.cc/sh/v3/syntax"
|
||||||
)
|
)
|
||||||
@ -80,155 +81,6 @@ func makeSpaceStr(num int) string {
|
|||||||
return string(barr)
|
return string(barr)
|
||||||
}
|
}
|
||||||
|
|
||||||
type SimpleExpandContext struct {
|
|
||||||
HomeDir string
|
|
||||||
}
|
|
||||||
|
|
||||||
func expandHomeDir(litVal string, multiPart bool, homeDir string) string {
|
|
||||||
if homeDir == "" {
|
|
||||||
return litVal
|
|
||||||
}
|
|
||||||
if litVal == "~" && !multiPart {
|
|
||||||
return homeDir
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(litVal, "~/") {
|
|
||||||
return homeDir + litVal[1:]
|
|
||||||
}
|
|
||||||
return litVal
|
|
||||||
}
|
|
||||||
|
|
||||||
func expandLiteral(buf *bytes.Buffer, litVal string) {
|
|
||||||
var lastBackSlash bool
|
|
||||||
for _, ch := range litVal {
|
|
||||||
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
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
buf.WriteRune(ch)
|
|
||||||
}
|
|
||||||
if lastBackSlash {
|
|
||||||
buf.WriteByte('\\')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// also expands ~
|
|
||||||
func expandLiteralPlus(buf *bytes.Buffer, litVal string, multiPart bool, ectx SimpleExpandContext) {
|
|
||||||
litVal = expandHomeDir(litVal, multiPart, ectx.HomeDir)
|
|
||||||
expandLiteral(buf, litVal)
|
|
||||||
}
|
|
||||||
|
|
||||||
func expandSQANSILiteral(buf *bytes.Buffer, litVal string) {
|
|
||||||
str, _, _ := expand.Format(nil, litVal, nil)
|
|
||||||
buf.WriteString(str)
|
|
||||||
}
|
|
||||||
|
|
||||||
func expandSQLiteral(buf *bytes.Buffer, litVal string) {
|
|
||||||
buf.WriteString(litVal)
|
|
||||||
}
|
|
||||||
|
|
||||||
// will also work for partial double quoted strings
|
|
||||||
func expandDQLiteral(buf *bytes.Buffer, litVal string) {
|
|
||||||
var lastBackSlash bool
|
|
||||||
for _, ch := range litVal {
|
|
||||||
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
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
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 simpleExpandWordInternal(buf *bytes.Buffer, ectx SimpleExpandContext, parts []syntax.WordPart, sourceStr string, inDoubleQuote bool, level int) {
|
|
||||||
for partIdx, untypedPart := range parts {
|
|
||||||
switch part := untypedPart.(type) {
|
|
||||||
case *syntax.Lit:
|
|
||||||
if !inDoubleQuote && partIdx == 0 && level == 1 && ectx.HomeDir != "" {
|
|
||||||
expandLiteralPlus(buf, part.Value, len(parts) > 1, ectx)
|
|
||||||
} else if inDoubleQuote {
|
|
||||||
expandDQLiteral(buf, part.Value)
|
|
||||||
} else {
|
|
||||||
expandLiteral(buf, part.Value)
|
|
||||||
}
|
|
||||||
|
|
||||||
case *syntax.SglQuoted:
|
|
||||||
if part.Dollar {
|
|
||||||
expandSQANSILiteral(buf, part.Value)
|
|
||||||
} else {
|
|
||||||
expandSQLiteral(buf, part.Value)
|
|
||||||
}
|
|
||||||
|
|
||||||
case *syntax.DblQuoted:
|
|
||||||
simpleExpandWordInternal(buf, ectx, part.Parts, sourceStr, true, level+1)
|
|
||||||
|
|
||||||
default:
|
|
||||||
rawStr := sourceStr[part.Pos().Offset():part.End().Offset()]
|
|
||||||
buf.WriteString(rawStr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// simple word expansion
|
|
||||||
// expands: literals, single-quoted strings, double-quoted strings (recursively)
|
|
||||||
// does *not* expand: params (variables), command substitution, arithmetic expressions, process substituions, globs
|
|
||||||
// for the not expands, they will show up as the literal string
|
|
||||||
// this is different than expand.Literal which will replace variables as empty string if they aren't defined.
|
|
||||||
// so "a"'foo'${bar}$x => "afoo${bar}$x", but expand.Literal would produce => "afoo"
|
|
||||||
// note will do ~ expansion (will not do ~user expansion)
|
|
||||||
func SimpleExpandWord(ectx SimpleExpandContext, word *syntax.Word, sourceStr string) string {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
simpleExpandWordInternal(&buf, ectx, word.Parts, sourceStr, false, 1)
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func SimpleExpandPartialWord(ectx SimpleExpandContext, partialWord string, multiPart bool) string {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
if partialWord == "" {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(partialWord, "\"") {
|
|
||||||
expandDQLiteral(&buf, partialWord[1:])
|
|
||||||
} else if strings.HasPrefix(partialWord, "$\"") {
|
|
||||||
expandDQLiteral(&buf, partialWord[2:])
|
|
||||||
} else if strings.HasPrefix(partialWord, "'") {
|
|
||||||
expandSQLiteral(&buf, partialWord[1:])
|
|
||||||
} else if strings.HasPrefix(partialWord, "$'") {
|
|
||||||
expandSQANSILiteral(&buf, partialWord[2:])
|
|
||||||
} else {
|
|
||||||
expandLiteralPlus(&buf, partialWord, multiPart, ectx)
|
|
||||||
}
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://wiki.bash-hackers.org/syntax/shellvars
|
// https://wiki.bash-hackers.org/syntax/shellvars
|
||||||
var NoStoreVarNames = map[string]bool{
|
var NoStoreVarNames = map[string]bool{
|
||||||
"BASH": true,
|
"BASH": true,
|
||||||
@ -460,7 +312,7 @@ func parseDeclareStmt(stmt *syntax.Stmt, src string) (*DeclareDeclType, error) {
|
|||||||
return nil, fmt.Errorf("invalid decl format")
|
return nil, fmt.Errorf("invalid decl format")
|
||||||
}
|
}
|
||||||
if declAssign.Value != nil {
|
if declAssign.Value != nil {
|
||||||
rtn.Value = SimpleExpandWord(SimpleExpandContext{}, declAssign.Value, src)
|
rtn.Value, _ = simpleexpand.SimpleExpandWord(simpleexpand.SimpleExpandContext{}, declAssign.Value, src)
|
||||||
} else if declAssign.Array != nil {
|
} else if declAssign.Array != nil {
|
||||||
rtn.Value = string(src[declAssign.Array.Pos().Offset():declAssign.Array.End().Offset()])
|
rtn.Value = string(src[declAssign.Array.Pos().Offset():declAssign.Array.End().Offset()])
|
||||||
} else {
|
} else {
|
||||||
|
213
pkg/simpleexpand/simpleexpand.go
Normal file
213
pkg/simpleexpand/simpleexpand.go
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
package simpleexpand
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"mvdan.cc/sh/v3/expand"
|
||||||
|
"mvdan.cc/sh/v3/syntax"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SimpleExpandContext struct {
|
||||||
|
HomeDir string
|
||||||
|
}
|
||||||
|
|
||||||
|
type SimpleExpandInfo 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
|
||||||
|
}
|
||||||
|
|
||||||
|
func expandHomeDir(info *SimpleExpandInfo, litVal string, multiPart bool, homeDir string) string {
|
||||||
|
if homeDir == "" {
|
||||||
|
return litVal
|
||||||
|
}
|
||||||
|
if litVal == "~" && !multiPart {
|
||||||
|
return homeDir
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(litVal, "~/") {
|
||||||
|
info.HasTilde = true
|
||||||
|
return homeDir + litVal[1:]
|
||||||
|
}
|
||||||
|
return litVal
|
||||||
|
}
|
||||||
|
|
||||||
|
func expandLiteral(buf *bytes.Buffer, info *SimpleExpandInfo, litVal string) {
|
||||||
|
var lastBackSlash bool
|
||||||
|
var lastExtGlob bool
|
||||||
|
var lastDollar bool
|
||||||
|
for _, ch := range litVal {
|
||||||
|
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('\\')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// also expands ~
|
||||||
|
func expandLiteralPlus(buf *bytes.Buffer, info *SimpleExpandInfo, litVal string, multiPart bool, ectx SimpleExpandContext) {
|
||||||
|
litVal = expandHomeDir(info, litVal, multiPart, ectx.HomeDir)
|
||||||
|
expandLiteral(buf, info, litVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
func expandSQANSILiteral(buf *bytes.Buffer, litVal string) {
|
||||||
|
// no info specials
|
||||||
|
str, _, _ := expand.Format(nil, litVal, nil)
|
||||||
|
buf.WriteString(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
func expandSQLiteral(buf *bytes.Buffer, litVal string) {
|
||||||
|
// no info specials
|
||||||
|
buf.WriteString(litVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
// will also work for partial double quoted strings
|
||||||
|
func expandDQLiteral(buf *bytes.Buffer, info *SimpleExpandInfo, litVal string) {
|
||||||
|
var lastBackSlash bool
|
||||||
|
var lastDollar bool
|
||||||
|
for _, ch := range litVal {
|
||||||
|
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 simpleExpandWordInternal(buf *bytes.Buffer, info *SimpleExpandInfo, ectx SimpleExpandContext, parts []syntax.WordPart, sourceStr string, inDoubleQuote bool, level int) {
|
||||||
|
for partIdx, untypedPart := range parts {
|
||||||
|
switch part := untypedPart.(type) {
|
||||||
|
case *syntax.Lit:
|
||||||
|
if !inDoubleQuote && partIdx == 0 && level == 1 && ectx.HomeDir != "" {
|
||||||
|
expandLiteralPlus(buf, info, part.Value, len(parts) > 1, ectx)
|
||||||
|
} else if inDoubleQuote {
|
||||||
|
expandDQLiteral(buf, info, part.Value)
|
||||||
|
} else {
|
||||||
|
expandLiteral(buf, info, part.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
case *syntax.SglQuoted:
|
||||||
|
if part.Dollar {
|
||||||
|
expandSQANSILiteral(buf, part.Value)
|
||||||
|
} else {
|
||||||
|
expandSQLiteral(buf, part.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
case *syntax.DblQuoted:
|
||||||
|
simpleExpandWordInternal(buf, info, ectx, part.Parts, sourceStr, true, level+1)
|
||||||
|
|
||||||
|
default:
|
||||||
|
rawStr := sourceStr[part.Pos().Offset():part.End().Offset()]
|
||||||
|
buf.WriteString(rawStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// simple word expansion
|
||||||
|
// expands: literals, single-quoted strings, double-quoted strings (recursively)
|
||||||
|
// does *not* expand: params (variables), command substitution, arithmetic expressions, process substituions, globs
|
||||||
|
// for the not expands, they will show up as the literal string
|
||||||
|
// this is different than expand.Literal which will replace variables as empty string if they aren't defined.
|
||||||
|
// so "a"'foo'${bar}$x => "afoo${bar}$x", but expand.Literal would produce => "afoo"
|
||||||
|
// note will do ~ expansion (will not do ~user expansion)
|
||||||
|
func SimpleExpandWord(ectx SimpleExpandContext, word *syntax.Word, sourceStr string) (string, SimpleExpandInfo) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
var info SimpleExpandInfo
|
||||||
|
simpleExpandWordInternal(&buf, &info, ectx, word.Parts, sourceStr, false, 1)
|
||||||
|
return buf.String(), info
|
||||||
|
}
|
||||||
|
|
||||||
|
func SimpleExpandPartialWord(ectx SimpleExpandContext, partialWord string, multiPart bool) (string, SimpleExpandInfo) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
var info SimpleExpandInfo
|
||||||
|
if partialWord == "" {
|
||||||
|
return "", info
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(partialWord, "\"") {
|
||||||
|
expandDQLiteral(&buf, &info, partialWord[1:])
|
||||||
|
} else if strings.HasPrefix(partialWord, "$\"") {
|
||||||
|
expandDQLiteral(&buf, &info, partialWord[2:])
|
||||||
|
} else if strings.HasPrefix(partialWord, "'") {
|
||||||
|
expandSQLiteral(&buf, partialWord[1:])
|
||||||
|
} else if strings.HasPrefix(partialWord, "$'") {
|
||||||
|
expandSQANSILiteral(&buf, partialWord[2:])
|
||||||
|
} else {
|
||||||
|
expandLiteralPlus(&buf, &info, partialWord, multiPart, ectx)
|
||||||
|
}
|
||||||
|
return buf.String(), info
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user