WIP: Waifufetch by JGH0 working in windows-bash
This commit is contained in:
@@ -2,6 +2,7 @@ package shell
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -85,6 +86,125 @@ func (s *Shell) executeIf(block string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// executeCase handles: case WORD in PAT1) BODY1 ;; PAT2) BODY2 ;; esac
|
||||
// splitStatements emits ";;" as a separate token, so we can parse arms directly.
|
||||
func (s *Shell) executeCase(block string) error {
|
||||
stmts := splitStatements(block)
|
||||
if len(stmts) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// First stmt: "case WORD in"
|
||||
caseHeader := stmts[0]
|
||||
caseRest := strings.TrimSpace(strings.TrimPrefix(strings.TrimSpace(caseHeader), "case"))
|
||||
|
||||
var word string
|
||||
var startIdx int
|
||||
|
||||
// Find trailing " in" to separate word from "in"
|
||||
inIdx := strings.LastIndex(caseRest, " in")
|
||||
if inIdx >= 0 && strings.TrimSpace(caseRest[inIdx+3:]) == "" {
|
||||
word = s.expandWord(strings.TrimSpace(caseRest[:inIdx]))
|
||||
startIdx = 1
|
||||
} else {
|
||||
word = s.expandWord(caseRest)
|
||||
startIdx = 1
|
||||
if startIdx < len(stmts) && strings.TrimSpace(stmts[startIdx]) == "in" {
|
||||
startIdx++
|
||||
}
|
||||
}
|
||||
|
||||
// Parse arms from remaining stmts
|
||||
// stmts now include ";;" as explicit tokens
|
||||
type arm struct {
|
||||
patterns []string
|
||||
body []string
|
||||
}
|
||||
var arms []arm
|
||||
var curArm *arm
|
||||
|
||||
for _, stmt := range stmts[startIdx:] {
|
||||
stmt = strings.TrimSpace(stmt)
|
||||
if stmt == "" {
|
||||
continue
|
||||
}
|
||||
if stmt == "esac" {
|
||||
break
|
||||
}
|
||||
if stmt == ";;" || stmt == ";&" || stmt == ";;&" {
|
||||
curArm = nil
|
||||
continue
|
||||
}
|
||||
|
||||
if curArm == nil {
|
||||
// Expect a pattern: PAT) [body]
|
||||
parenIdx := findCasePatternEnd(stmt)
|
||||
if parenIdx < 0 {
|
||||
continue
|
||||
}
|
||||
patStr := strings.TrimSpace(stmt[:parenIdx])
|
||||
bodyStr := strings.TrimSpace(stmt[parenIdx+1:])
|
||||
|
||||
rawPats := strings.Split(patStr, "|")
|
||||
var pats []string
|
||||
for _, p := range rawPats {
|
||||
p = strings.TrimSpace(p)
|
||||
if p != "" {
|
||||
pats = append(pats, p)
|
||||
}
|
||||
}
|
||||
arms = append(arms, arm{patterns: pats})
|
||||
curArm = &arms[len(arms)-1]
|
||||
if bodyStr != "" {
|
||||
curArm.body = append(curArm.body, bodyStr)
|
||||
}
|
||||
} else {
|
||||
curArm.body = append(curArm.body, stmt)
|
||||
}
|
||||
}
|
||||
|
||||
// Execute matching arm
|
||||
for _, a := range arms {
|
||||
for _, pat := range a.patterns {
|
||||
expandedPat := s.expandWord(pat)
|
||||
matched := false
|
||||
if expandedPat == "*" {
|
||||
matched = true
|
||||
} else {
|
||||
if m, err := filepath.Match(expandedPat, word); err == nil {
|
||||
matched = m
|
||||
} else {
|
||||
matched = expandedPat == word
|
||||
}
|
||||
}
|
||||
if matched {
|
||||
body := strings.Join(a.body, "\n")
|
||||
return s.Execute(body)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// findCasePatternEnd finds the ) that ends the pattern in a case arm.
|
||||
// Handles quoted strings.
|
||||
func findCasePatternEnd(chunk string) int {
|
||||
inSingle := false
|
||||
inDouble := false
|
||||
for i := 0; i < len(chunk); i++ {
|
||||
c := chunk[i]
|
||||
switch {
|
||||
case c == '\'' && !inDouble:
|
||||
inSingle = !inSingle
|
||||
case c == '"' && !inSingle:
|
||||
inDouble = !inDouble
|
||||
case c == ')' && !inSingle && !inDouble:
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// executeFor handles: for VAR in WORDS; do BODY; done
|
||||
// (also: for VAR; do BODY; done — iterates positional params)
|
||||
func (s *Shell) executeFor(block string) error {
|
||||
@@ -95,7 +215,8 @@ func (s *Shell) executeFor(block string) error {
|
||||
|
||||
// Parse "for VAR in WORDS"
|
||||
header := stmts[0]
|
||||
fields := strings.Fields(header)
|
||||
// Use tokenize so array expansion works in "for x in ${arr[@]}"
|
||||
fields := s.tokenize(header)
|
||||
if len(fields) < 2 {
|
||||
return fmt.Errorf("for: bad syntax")
|
||||
}
|
||||
@@ -110,10 +231,8 @@ func (s *Shell) executeFor(block string) error {
|
||||
}
|
||||
}
|
||||
if inIdx >= 0 {
|
||||
for _, raw := range fields[inIdx+1:] {
|
||||
expanded := s.expandWord(raw)
|
||||
items = append(items, s.expandGlob(expanded)...)
|
||||
}
|
||||
// Items are already expanded by tokenize
|
||||
items = fields[inIdx+1:]
|
||||
} else {
|
||||
// for var; do ... → iterate positional params
|
||||
items = s.args
|
||||
@@ -164,6 +283,169 @@ func (s *Shell) executeFor(block string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// executeForC handles C-style: for ((init; cond; incr)); do BODY; done
|
||||
func (s *Shell) executeForC(block string) error {
|
||||
stmts := splitStatements(block)
|
||||
if len(stmts) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Extract the ((...)) header
|
||||
header := stmts[0]
|
||||
// Find "for" keyword and then "(("
|
||||
trimmed := strings.TrimSpace(header)
|
||||
// Strip "for" keyword
|
||||
trimmed = strings.TrimSpace(trimmed[3:]) // skip "for"
|
||||
// Expect "(("
|
||||
if !strings.HasPrefix(trimmed, "((") {
|
||||
return fmt.Errorf("for: bad C-style syntax")
|
||||
}
|
||||
trimmed = trimmed[2:] // skip "(("
|
||||
// Find closing "))"
|
||||
endIdx := strings.Index(trimmed, "))")
|
||||
if endIdx < 0 {
|
||||
return fmt.Errorf("for: missing '))'")
|
||||
}
|
||||
inner := trimmed[:endIdx]
|
||||
|
||||
// Split on ; to get init, cond, incr
|
||||
parts := strings.SplitN(inner, ";", 3)
|
||||
init := ""
|
||||
cond := ""
|
||||
incr := ""
|
||||
if len(parts) >= 1 {
|
||||
init = strings.TrimSpace(parts[0])
|
||||
}
|
||||
if len(parts) >= 2 {
|
||||
cond = strings.TrimSpace(parts[1])
|
||||
}
|
||||
if len(parts) >= 3 {
|
||||
incr = strings.TrimSpace(parts[2])
|
||||
}
|
||||
|
||||
// Execute init as arithmetic assignment
|
||||
if init != "" {
|
||||
s.execArithAssign(init)
|
||||
}
|
||||
|
||||
// Collect body between "do" and "done"
|
||||
var bodyStmts []string
|
||||
inBody := false
|
||||
for _, stmt := range stmts[1:] {
|
||||
w := firstWord(stmt)
|
||||
if !inBody {
|
||||
if w == "do" {
|
||||
inBody = true
|
||||
if rest := afterWord(stmt); rest != "" {
|
||||
bodyStmts = append(bodyStmts, rest)
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
if w == "done" {
|
||||
break
|
||||
}
|
||||
bodyStmts = append(bodyStmts, stmt)
|
||||
}
|
||||
|
||||
body := strings.Join(bodyStmts, "\n")
|
||||
|
||||
for {
|
||||
// Evaluate condition
|
||||
if cond != "" {
|
||||
if s.evalArith(cond) == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if err := s.Execute(body); err != nil {
|
||||
if be, ok := err.(breakErr); ok {
|
||||
if be.n <= 1 {
|
||||
break
|
||||
}
|
||||
return breakErr{be.n - 1}
|
||||
}
|
||||
if ce, ok := err.(continueErr); ok {
|
||||
if ce.n <= 1 {
|
||||
// continue — execute incr then re-check cond
|
||||
if incr != "" {
|
||||
s.execArithAssign(incr)
|
||||
}
|
||||
continue
|
||||
}
|
||||
return continueErr{ce.n - 1}
|
||||
}
|
||||
if _, ok := err.(returnErr); ok {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Execute increment
|
||||
if incr != "" {
|
||||
s.execArithAssign(incr)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// execArithAssign handles arithmetic assignment expressions like i=0, i++, i+=1, ((i++))
|
||||
func (s *Shell) execArithAssign(expr string) {
|
||||
expr = strings.TrimSpace(expr)
|
||||
if expr == "" {
|
||||
return
|
||||
}
|
||||
|
||||
// Handle i++ and i--
|
||||
if strings.HasSuffix(expr, "++") {
|
||||
varName := strings.TrimSpace(expr[:len(expr)-2])
|
||||
if isValidIdentifier(varName) {
|
||||
n := s.evalArith(varName)
|
||||
s.vars[varName] = fmt.Sprintf("%d", n+1)
|
||||
return
|
||||
}
|
||||
}
|
||||
if strings.HasSuffix(expr, "--") {
|
||||
varName := strings.TrimSpace(expr[:len(expr)-2])
|
||||
if isValidIdentifier(varName) {
|
||||
n := s.evalArith(varName)
|
||||
s.vars[varName] = fmt.Sprintf("%d", n-1)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Handle i+=N
|
||||
if idx := strings.Index(expr, "+="); idx > 0 {
|
||||
varName := strings.TrimSpace(expr[:idx])
|
||||
if isValidIdentifier(varName) {
|
||||
delta := s.evalArith(expr[idx+2:])
|
||||
n := s.evalArith(varName)
|
||||
s.vars[varName] = fmt.Sprintf("%d", n+delta)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Handle i-=N
|
||||
if idx := strings.Index(expr, "-="); idx > 0 {
|
||||
varName := strings.TrimSpace(expr[:idx])
|
||||
if isValidIdentifier(varName) {
|
||||
delta := s.evalArith(expr[idx+2:])
|
||||
n := s.evalArith(varName)
|
||||
s.vars[varName] = fmt.Sprintf("%d", n-delta)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Handle i=expr
|
||||
if idx := strings.Index(expr, "="); idx > 0 {
|
||||
varName := strings.TrimSpace(expr[:idx])
|
||||
if isValidIdentifier(varName) {
|
||||
n := s.evalArith(expr[idx+1:])
|
||||
s.vars[varName] = fmt.Sprintf("%d", n)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// executeWhileUntil handles while/until loops.
|
||||
func (s *Shell) executeWhileUntil(block string, isUntil bool) error {
|
||||
stmts := splitStatements(block)
|
||||
|
||||
Reference in New Issue
Block a user