WIP: Waifufetch by JGH0 working in windows-bash
This commit is contained in:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user