WIP: Waifufetch by JGH0 working in windows-bash
This commit is contained in:
@@ -60,7 +60,17 @@ func (s *Shell) expandWord(word string) string {
|
||||
i += 2
|
||||
}
|
||||
} else {
|
||||
result.WriteByte(next)
|
||||
// Only consume the backslash when escaping a shell
|
||||
// metacharacter. Before regular path characters (letters,
|
||||
// digits, etc.) keep it literal so Windows paths like
|
||||
// C:\workspace work unquoted.
|
||||
const metachars = " \t\n$*?[\"'\\|&;()<>{}!#~`"
|
||||
if strings.ContainsRune(metachars, rune(next)) {
|
||||
result.WriteByte(next)
|
||||
} else {
|
||||
result.WriteByte('\\')
|
||||
result.WriteByte(next)
|
||||
}
|
||||
i += 2
|
||||
}
|
||||
} else {
|
||||
@@ -72,6 +82,82 @@ func (s *Shell) expandWord(word string) string {
|
||||
result.WriteByte('$')
|
||||
break
|
||||
}
|
||||
|
||||
// $'...' ANSI C string
|
||||
if word[i] == '\'' {
|
||||
i++ // skip opening '
|
||||
for i < len(word) && word[i] != '\'' {
|
||||
if word[i] == '\\' && i+1 < len(word) {
|
||||
i++ // skip backslash, now at escape char
|
||||
switch word[i] {
|
||||
case 'n':
|
||||
result.WriteByte('\n')
|
||||
case 't':
|
||||
result.WriteByte('\t')
|
||||
case 'r':
|
||||
result.WriteByte('\r')
|
||||
case '\\':
|
||||
result.WriteByte('\\')
|
||||
case '\'':
|
||||
result.WriteByte('\'')
|
||||
case '"':
|
||||
result.WriteByte('"')
|
||||
case 'a':
|
||||
result.WriteByte('\a')
|
||||
case 'b':
|
||||
result.WriteByte('\b')
|
||||
case 'f':
|
||||
result.WriteByte('\f')
|
||||
case 'v':
|
||||
result.WriteByte('\v')
|
||||
case 'e', 'E':
|
||||
result.WriteByte(0x1b)
|
||||
case '0', '1', '2', '3', '4', '5', '6', '7':
|
||||
// Octal \NNN — up to 3 digits
|
||||
oct := 0
|
||||
for k := 0; k < 3 && i < len(word) && word[i] >= '0' && word[i] <= '7'; k++ {
|
||||
oct = oct*8 + int(word[i]-'0')
|
||||
i++
|
||||
}
|
||||
result.WriteByte(byte(oct))
|
||||
continue
|
||||
case 'x':
|
||||
// Hex \xNN — up to 2 digits
|
||||
i++ // skip 'x'
|
||||
hexv := 0
|
||||
for k := 0; k < 2 && i < len(word); k++ {
|
||||
d := word[i]
|
||||
if d >= '0' && d <= '9' {
|
||||
hexv = hexv*16 + int(d-'0')
|
||||
i++
|
||||
} else if d >= 'a' && d <= 'f' {
|
||||
hexv = hexv*16 + int(d-'a'+10)
|
||||
i++
|
||||
} else if d >= 'A' && d <= 'F' {
|
||||
hexv = hexv*16 + int(d-'A'+10)
|
||||
i++
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
result.WriteByte(byte(hexv))
|
||||
continue
|
||||
default:
|
||||
result.WriteByte('\\')
|
||||
result.WriteByte(word[i])
|
||||
}
|
||||
i++
|
||||
} else {
|
||||
result.WriteByte(word[i])
|
||||
i++
|
||||
}
|
||||
}
|
||||
if i < len(word) {
|
||||
i++ // skip closing '
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
switch word[i] {
|
||||
case '(':
|
||||
if i+1 < len(word) && word[i+1] == '(' {
|
||||
@@ -146,7 +232,7 @@ func (s *Shell) expandWord(word string) string {
|
||||
result.WriteString(s.vars["#"])
|
||||
i++
|
||||
case '@':
|
||||
result.WriteString(s.vars["@"])
|
||||
result.WriteString(strings.Join(s.args, "\x01"))
|
||||
i++
|
||||
case '*':
|
||||
result.WriteString(s.vars["*"])
|
||||
@@ -172,17 +258,52 @@ func (s *Shell) expandWord(word string) string {
|
||||
}
|
||||
|
||||
func (s *Shell) getVar(name string) string {
|
||||
if v, ok := s.vars[name]; ok {
|
||||
// Resolve nameref chain
|
||||
resolved := s.resolveNR(name)
|
||||
if v, ok := s.vars[resolved]; ok {
|
||||
return v
|
||||
}
|
||||
return os.Getenv(name)
|
||||
return os.Getenv(resolved)
|
||||
}
|
||||
|
||||
func (s *Shell) evalVarExpr(expr string) string {
|
||||
// ${#VAR} — string length
|
||||
// ${#arr[@]} or ${#arr[*]} → length of array
|
||||
if strings.HasPrefix(expr, "#") {
|
||||
return strconv.Itoa(len(s.getVar(expr[1:])))
|
||||
rest := expr[1:]
|
||||
if strings.HasSuffix(rest, "[@]") || strings.HasSuffix(rest, "[*]") {
|
||||
arrName := rest[:len(rest)-3]
|
||||
return strconv.Itoa(len(s.getArray(arrName)))
|
||||
}
|
||||
// ${#VAR} — string length
|
||||
return strconv.Itoa(len(s.getVar(rest)))
|
||||
}
|
||||
|
||||
// Array indexing: ${arr[@]}, ${arr[*]}, ${arr[N]}
|
||||
if bracketIdx := strings.Index(expr, "["); bracketIdx >= 0 && strings.HasSuffix(expr, "]") {
|
||||
// Make sure there's no operator before the bracket
|
||||
prefix := expr[:bracketIdx]
|
||||
hasOp := strings.ContainsAny(prefix, ":-:=:+%#")
|
||||
if !hasOp {
|
||||
arrName := prefix
|
||||
idx := expr[bracketIdx+1 : len(expr)-1]
|
||||
if idx == "@" {
|
||||
arr := s.getArray(arrName)
|
||||
return strings.Join(arr, "\x01")
|
||||
}
|
||||
if idx == "*" {
|
||||
arr := s.getArray(arrName)
|
||||
return strings.Join(arr, " ")
|
||||
}
|
||||
// Numeric index
|
||||
n := s.evalArith(idx)
|
||||
arr := s.getArray(arrName)
|
||||
if n >= 0 && n < len(arr) {
|
||||
return arr[n]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// ${VAR:-default}
|
||||
if idx := strings.Index(expr, ":-"); idx >= 0 {
|
||||
varName := expr[:idx]
|
||||
@@ -285,7 +406,48 @@ func evalArithExpr(expr string) int {
|
||||
if strings.HasPrefix(expr, "(") && strings.HasSuffix(expr, ")") {
|
||||
return evalArithExpr(expr[1 : len(expr)-1])
|
||||
}
|
||||
// Operators in precedence order (lowest first so we split on last occurrence)
|
||||
|
||||
// Comparison operators (lowest precedence) — multi-char first
|
||||
for _, op := range []string{"<=", ">=", "==", "!=", "<", ">"} {
|
||||
if idx := findBinaryOpStr(expr, op); idx >= 0 {
|
||||
left := evalArithExpr(expr[:idx])
|
||||
right := evalArithExpr(expr[idx+len(op):])
|
||||
switch op {
|
||||
case "<":
|
||||
if left < right {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
case ">":
|
||||
if left > right {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
case "<=":
|
||||
if left <= right {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
case ">=":
|
||||
if left >= right {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
case "==":
|
||||
if left == right {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
case "!=":
|
||||
if left != right {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Arithmetic operators in precedence order (lowest first so we split on last occurrence)
|
||||
for _, op := range []string{"+", "-", "*", "/", "%"} {
|
||||
if idx := findBinaryOp(expr, op); idx >= 0 {
|
||||
left := evalArithExpr(expr[:idx])
|
||||
@@ -313,6 +475,39 @@ func evalArithExpr(expr string) int {
|
||||
return 0
|
||||
}
|
||||
|
||||
// findBinaryOpStr finds the rightmost occurrence of a multi-character binary operator.
|
||||
func findBinaryOpStr(expr, op string) int {
|
||||
depth := 0
|
||||
// Search right-to-left
|
||||
for i := len(expr) - len(op); i >= 0; i-- {
|
||||
switch expr[i] {
|
||||
case ')':
|
||||
depth++
|
||||
case '(':
|
||||
depth--
|
||||
}
|
||||
if depth != 0 {
|
||||
continue
|
||||
}
|
||||
if expr[i:i+len(op)] == op {
|
||||
// Make sure it's not part of a longer operator
|
||||
// e.g. don't match < in <=
|
||||
if len(op) == 1 {
|
||||
// For < and >, make sure next char is not =
|
||||
if i+1 < len(expr) && (expr[i+1] == '=' || (op == "<" && expr[i+1] == '<') || (op == ">" && expr[i+1] == '>')) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
// For single char ops, make sure previous char is not the same op (e.g. << or >>)
|
||||
if len(op) == 1 && i > 0 && expr[i-1] == expr[i] {
|
||||
continue
|
||||
}
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func findBinaryOp(expr, op string) int {
|
||||
depth := 0
|
||||
for i := len(expr) - 1; i >= 0; i-- {
|
||||
@@ -353,7 +548,7 @@ func (s *Shell) tokenize(input string) []string {
|
||||
current := strings.Builder{}
|
||||
inSingle := false
|
||||
inDouble := false
|
||||
parenDepth := 0 // nesting depth inside $(...) or $((...))
|
||||
parenDepth := 0 // nesting depth inside $(...) or $((...))
|
||||
pendingDollar := false // true after $ when next char is (
|
||||
wasQuoted := false
|
||||
|
||||
@@ -420,7 +615,7 @@ doneTokenizing:
|
||||
if eqIdx := strings.Index(clean, "="); eqIdx > 0 {
|
||||
name := clean[:eqIdx]
|
||||
if isValidIdentifier(name) && !strings.Contains(clean[:eqIdx], "$") {
|
||||
value := s.expandWord(clean[eqIdx+1:])
|
||||
value := strings.ReplaceAll(s.expandWord(clean[eqIdx+1:]), "\x01", " ")
|
||||
s.vars[name] = value
|
||||
os.Setenv(name, value)
|
||||
rawTokens = rawTokens[1:]
|
||||
@@ -438,6 +633,16 @@ doneTokenizing:
|
||||
tok = tok[2:]
|
||||
}
|
||||
expanded := s.expandWord(tok)
|
||||
// Handle multi-word expansion from $@ and ${arr[@]}
|
||||
if strings.Contains(expanded, "\x01") {
|
||||
parts := strings.Split(expanded, "\x01")
|
||||
for _, p := range parts {
|
||||
if p != "" {
|
||||
result = append(result, p)
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
if !quoted && strings.ContainsAny(expanded, "*?[") {
|
||||
result = append(result, s.expandGlob(expanded)...)
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user