WIP: Waifufetch by JGH0 working in windows-bash

This commit is contained in:
Cametendo
2026-05-28 21:00:34 +02:00
parent 114cbf43bd
commit 7b3a101946
8 changed files with 1354 additions and 105 deletions

View File

@@ -6,6 +6,7 @@ import (
"io"
"os"
"path/filepath"
"regexp"
"runtime"
"sort"
"strconv"
@@ -18,59 +19,67 @@ var aliases = make(map[string]string)
func (s *Shell) initBuiltins() {
s.builtins = map[string]func([]string) error{
// Shell builtins
"cd": s.builtinCd,
"pwd": s.builtinPwd,
"echo": s.builtinEcho,
"exit": s.builtinExit,
"export": s.builtinExport,
"source": s.builtinSource,
".": s.builtinSource,
"alias": s.builtinAlias,
"unalias": s.builtinUnalias,
"type": s.builtinType,
"test": s.builtinTest,
"[": s.builtinTest,
"read": s.builtinRead,
"printf": s.builtinPrintf,
"true": s.builtinTrue,
"false": s.builtinFalse,
"set": s.builtinSet,
"unset": s.builtinUnset,
"env": s.builtinEnv,
"which": s.builtinWhich,
"return": s.builtinReturn,
"break": s.builtinBreak,
"continue": s.builtinContinue,
"shift": s.builtinShift,
"declare": s.builtinDeclare,
"local": s.builtinDeclare,
"command": s.builtinCommand,
"jobs": s.builtinJobs,
"cd": s.builtinCd,
"pwd": s.builtinPwd,
"echo": s.builtinEcho,
"exit": s.builtinExit,
"export": s.builtinExport,
"source": s.builtinSource,
".": s.builtinSource,
"alias": s.builtinAlias,
"unalias": s.builtinUnalias,
"type": s.builtinType,
"test": s.builtinTest,
"[": s.builtinTest,
"[[": s.builtinDoubleBracket,
"read": s.builtinRead,
"printf": s.builtinPrintf,
"true": s.builtinTrue,
"false": s.builtinFalse,
"set": s.builtinSet,
"unset": s.builtinUnset,
"env": s.builtinEnv,
"which": s.builtinWhich,
"return": s.builtinReturn,
"break": s.builtinBreak,
"continue": s.builtinContinue,
"shift": s.builtinShift,
"declare": s.builtinDeclare,
"local": s.builtinDeclare,
"command": s.builtinCommand,
"jobs": s.builtinJobs,
"disown": s.builtinDisown,
"mktemp": s.builtinMktemp,
"uname": s.builtinUname,
"whoami": s.builtinWhoami,
"hostname": s.builtinHostname,
"mapfile": s.builtinMapfile,
"readarray": s.builtinMapfile,
// Coreutils
"ls": s.cmdLs,
"cat": s.cmdCat,
"grep": s.cmdGrep,
"head": s.cmdHead,
"tail": s.cmdTail,
"sort": s.cmdSort,
"wc": s.cmdWc,
"find": s.cmdFind,
"cp": s.cmdCp,
"mv": s.cmdMv,
"rm": s.cmdRm,
"mkdir": s.cmdMkdir,
"touch": s.cmdTouch,
"clear": s.cmdClear,
"cut": s.cmdCut,
"tr": s.cmdTr,
"uniq": s.cmdUniq,
"tee": s.cmdTee,
"date": s.cmdDate,
"sleep": s.cmdSleep,
"ls": s.cmdLs,
"cat": s.cmdCat,
"grep": s.cmdGrep,
"head": s.cmdHead,
"tail": s.cmdTail,
"sort": s.cmdSort,
"wc": s.cmdWc,
"find": s.cmdFind,
"cp": s.cmdCp,
"mv": s.cmdMv,
"rm": s.cmdRm,
"mkdir": s.cmdMkdir,
"touch": s.cmdTouch,
"clear": s.cmdClear,
"cut": s.cmdCut,
"tr": s.cmdTr,
"uniq": s.cmdUniq,
"tee": s.cmdTee,
"date": s.cmdDate,
"sleep": s.cmdSleep,
"basename": s.cmdBasename,
"dirname": s.cmdDirname,
"sed": s.cmdSed,
"xargs": s.cmdXargs,
"dirname": s.cmdDirname,
"sed": s.cmdSed,
"xargs": s.cmdXargs,
}
}
@@ -532,9 +541,58 @@ func (s *Shell) builtinSet(args []string) error {
}
return nil
}
// Handle positional params: set -- a b c
if args[0] == "--" {
s.SetArgs(args[1:])
i := 0
for i < len(args) {
switch args[i] {
case "--":
s.SetArgs(args[i+1:])
return nil
case "-e":
s.errexit = true
case "+e":
s.errexit = false
case "-u":
s.nounset = true
case "+u":
s.nounset = false
case "-x":
// xtrace — ignore
case "+x":
// xtrace off — ignore
case "-o":
if i+1 < len(args) {
switch args[i+1] {
case "pipefail":
s.pipefail = true
i++
case "errexit":
s.errexit = true
i++
case "nounset":
s.nounset = true
i++
}
}
case "+o":
if i+1 < len(args) {
i++ // ignore value
}
default:
// Combined flags like -euo
if strings.HasPrefix(args[i], "-") {
for _, c := range args[i][1:] {
switch c {
case 'e':
s.errexit = true
case 'u':
s.nounset = true
case 'x':
// xtrace — ignore
}
}
}
}
i++
}
return nil
}
@@ -619,14 +677,71 @@ func (s *Shell) builtinShift(args []string) error {
}
func (s *Shell) builtinDeclare(args []string) error {
for _, arg := range args {
// Parse flags
nameref := false
isArray := false
isExport := false
nonFlagStart := 0
for i, arg := range args {
if !strings.HasPrefix(arg, "-") {
nonFlagStart = i
break
}
nonFlagStart = i + 1
for _, ch := range arg[1:] {
switch ch {
case 'n':
nameref = true
case 'a':
isArray = true
case 'i':
// integer — treat as scalar
case 'r':
// readonly — ignore
case 'x':
isExport = true
case 'g':
// global — ignore
}
}
}
for _, arg := range args[nonFlagStart:] {
if strings.HasPrefix(arg, "-") {
continue
}
if idx := strings.Index(arg, "="); idx > 0 {
name := arg[:idx]
if isValidIdentifier(name) {
s.vars[name] = arg[idx+1:]
if !isValidIdentifier(name) {
continue
}
value := s.expandWord(arg[idx+1:])
if nameref {
s.namerefs[name] = value
} else if isArray {
if strings.HasPrefix(value, "(") && strings.HasSuffix(value, ")") {
inner := value[1 : len(value)-1]
elems := s.tokenize(inner)
s.setArray(name, elems)
} else {
s.setArray(name, []string{value})
}
} else {
s.vars[name] = value
if isExport {
os.Setenv(name, value)
}
}
} else {
// No = — just declare
if !isValidIdentifier(arg) {
continue
}
if isArray {
if s.arrays[arg] == nil {
s.arrays[arg] = []string{}
}
}
}
}
@@ -634,6 +749,23 @@ func (s *Shell) builtinDeclare(args []string) error {
}
func (s *Shell) builtinCommand(args []string) error {
if len(args) > 0 && args[0] == "-v" {
found := true
for _, name := range args[1:] {
if _, ok := s.builtins[name]; ok {
fmt.Fprintln(s.Stdout, name)
} else if p := findExecutable(name); p != "" {
fmt.Fprintln(s.Stdout, p)
} else {
found = false
fmt.Fprintf(s.Stderr, "bash: command not found: %s\n", name)
}
}
if !found {
return exitCodeErr{1}
}
return nil
}
if len(args) == 0 {
return nil
}
@@ -646,6 +778,291 @@ func (s *Shell) builtinJobs(_ []string) error {
return nil
}
func (s *Shell) builtinDisown(_ []string) error { return nil }
func (s *Shell) builtinMktemp(args []string) error {
template := ""
for _, a := range args {
if !strings.HasPrefix(a, "-") {
template = a
break
}
}
// Use Windows temp dir regardless of the /tmp/ prefix in template
tmpDir := os.Getenv("TEMP")
if tmpDir == "" {
tmpDir = os.Getenv("TMP")
}
if tmpDir == "" {
tmpDir = os.TempDir()
}
// Extract the base pattern (strip directory prefix)
base := filepath.Base(template)
if base == "" || base == "." {
base = "tmp.XXXXXX"
}
// Replace trailing X's with * for os.CreateTemp
xCount := 0
for i := len(base) - 1; i >= 0 && base[i] == 'X'; i-- {
xCount++
}
goPattern := base
if xCount > 0 {
goPattern = base[:len(base)-xCount] + "*"
}
f, err := os.CreateTemp(tmpDir, goPattern)
if err != nil {
return fmt.Errorf("mktemp: %v", err)
}
f.Close()
fmt.Fprintln(s.Stdout, f.Name())
return nil
}
func (s *Shell) builtinUname(args []string) error {
showSys := len(args) == 0
showRelease := false
for _, a := range args {
switch a {
case "-s":
showSys = true
case "-r":
showRelease = true
case "-a":
showSys = true
showRelease = true
}
}
if showSys {
fmt.Fprintln(s.Stdout, "Windows_NT")
}
if showRelease {
kernelRel := os.Getenv("OS_VERSION")
if kernelRel == "" {
kernelRel = "10.0"
}
fmt.Fprintln(s.Stdout, kernelRel)
}
return nil
}
func (s *Shell) builtinWhoami(_ []string) error {
user := os.Getenv("USERNAME")
if user == "" {
user = os.Getenv("USER")
}
if user == "" {
user = "unknown"
}
fmt.Fprintln(s.Stdout, user)
return nil
}
func (s *Shell) builtinHostname(_ []string) error {
h, err := os.Hostname()
if err != nil {
h = os.Getenv("COMPUTERNAME")
}
if h == "" {
h = "unknown"
}
fmt.Fprintln(s.Stdout, h)
return nil
}
func (s *Shell) builtinMapfile(args []string) error {
varName := ""
trimNewlines := false
for _, a := range args {
if a == "-t" {
trimNewlines = true
} else if !strings.HasPrefix(a, "-") {
varName = a
}
}
if varName == "" {
varName = "MAPFILE"
}
var lines []string
scanner := bufio.NewScanner(s.Stdin)
for scanner.Scan() {
line := scanner.Text()
if !trimNewlines {
line += "\n"
}
lines = append(lines, line)
}
s.setArray(varName, lines)
return nil
}
// builtinDoubleBracket implements [[ ... ]]
func (s *Shell) builtinDoubleBracket(args []string) error {
// Strip trailing ]] if present
if len(args) > 0 && args[len(args)-1] == "]]" {
args = args[:len(args)-1]
}
if !s.evalDB(args) {
return exitCodeErr{1}
}
return nil
}
// evalDB evaluates a [[ ... ]] expression.
func (s *Shell) evalDB(args []string) bool {
if len(args) == 0 {
return false
}
// 1. || — lowest precedence, scan right-to-left
for i := len(args) - 1; i >= 0; i-- {
if args[i] == "||" {
return s.evalDB(args[:i]) || s.evalDB(args[i+1:])
}
}
// 2. &&
for i := len(args) - 1; i >= 0; i-- {
if args[i] == "&&" {
return s.evalDB(args[:i]) && s.evalDB(args[i+1:])
}
}
// 3. ! prefix
if args[0] == "!" {
return !s.evalDB(args[1:])
}
// 4. Unary flags
if len(args) == 2 {
val := args[1]
switch args[0] {
case "-f":
info, err := os.Stat(val)
return err == nil && info.Mode().IsRegular()
case "-e":
_, err := os.Stat(val)
return err == nil
case "-d":
info, err := os.Stat(val)
return err == nil && info.IsDir()
case "-s":
info, err := os.Stat(val)
return err == nil && info.Size() > 0
case "-r":
f, err := os.Open(val)
if err != nil {
return false
}
f.Close()
return true
case "-w":
f, err := os.OpenFile(val, os.O_WRONLY, 0)
if err != nil {
return false
}
f.Close()
return true
case "-x":
info, err := os.Stat(val)
return err == nil && (info.Mode()&0111 != 0 || runtime.GOOS == "windows")
case "-n":
return val != ""
case "-z":
return val == ""
case "-L":
_, err := os.Lstat(val)
return err == nil
case "-v":
_, ok := s.vars[val]
if !ok {
_, ok = s.arrays[val]
}
return ok
}
}
// 5. Binary operators
if len(args) == 3 {
lhs, op, rhs := args[0], args[1], args[2]
switch op {
case "==":
matched, err := filepath.Match(rhs, lhs)
if err != nil {
return lhs == rhs
}
return matched
case "!=":
matched, err := filepath.Match(rhs, lhs)
if err != nil {
return lhs != rhs
}
return !matched
case "=~":
matched, err := regexp.MatchString(rhs, lhs)
return err == nil && matched
case "<":
return lhs < rhs
case ">":
return lhs > rhs
case "-eq":
ln, le := strconv.Atoi(lhs)
rn, re := strconv.Atoi(rhs)
if le != nil || re != nil {
return false
}
return ln == rn
case "-ne":
ln, le := strconv.Atoi(lhs)
rn, re := strconv.Atoi(rhs)
if le != nil || re != nil {
return false
}
return ln != rn
case "-lt":
ln, le := strconv.Atoi(lhs)
rn, re := strconv.Atoi(rhs)
if le != nil || re != nil {
return false
}
return ln < rn
case "-le":
ln, le := strconv.Atoi(lhs)
rn, re := strconv.Atoi(rhs)
if le != nil || re != nil {
return false
}
return ln <= rn
case "-gt":
ln, le := strconv.Atoi(lhs)
rn, re := strconv.Atoi(rhs)
if le != nil || re != nil {
return false
}
return ln > rn
case "-ge":
ln, le := strconv.Atoi(lhs)
rn, re := strconv.Atoi(rhs)
if le != nil || re != nil {
return false
}
return ln >= rn
}
}
// 6. Single arg truthy test
if len(args) == 1 {
return args[0] != ""
}
return false
}
// ─── Coreutils ────────────────────────────────────────────────────────────────
func (s *Shell) cmdLs(args []string) error {