buninu 0.4.19 → 0.4.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/apps/bunmsh/CHANGELOG.md +103 -0
- package/apps/bunmsh/PORTING.md +133 -15
- package/apps/bunmsh/help/awk.md +38 -0
- package/apps/bunmsh/help/break.md +21 -0
- package/apps/bunmsh/help/chmod.md +8 -2
- package/apps/bunmsh/help/cmp.md +29 -0
- package/apps/bunmsh/help/continue.md +22 -0
- package/apps/bunmsh/help/cp.md +4 -0
- package/apps/bunmsh/help/diff.md +30 -0
- package/apps/bunmsh/help/egrep.md +26 -0
- package/apps/bunmsh/help/export.md +2 -2
- package/apps/bunmsh/help/expr.md +30 -0
- package/apps/bunmsh/help/fgrep.md +25 -0
- package/apps/bunmsh/help/grep.md +4 -0
- package/apps/bunmsh/help/ls.md +4 -2
- package/apps/bunmsh/help/mkdir.md +1 -1
- package/apps/bunmsh/help/print.md +3 -0
- package/apps/bunmsh/help/return.md +23 -0
- package/apps/bunmsh/help/sed.md +8 -3
- package/apps/bunmsh/help/set.md +12 -3
- package/apps/bunmsh/help/tr.md +4 -0
- package/apps/bunmsh/help/trap.md +22 -0
- package/apps/bunmsh/help/uniq.md +27 -0
- package/apps/bunmsh/package.json +1 -1
- package/apps/bunmsh/src/awk.js +203 -0
- package/apps/bunmsh/src/diff.js +222 -0
- package/apps/bunmsh/src/fancy-ls.js +15 -4
- package/apps/bunmsh/src/main.js +21 -5
- package/apps/bunmsh/src/sed.js +159 -0
- package/apps/bunmsh/src/shell.js +1283 -540
- package/apps/bunmsh/test/awk-reference.test.js +35 -0
- package/apps/bunmsh/test/awk.test.js +62 -0
- package/apps/bunmsh/test/posix-bootstrap.test.js +595 -0
- package/apps/bunmsh/test/utility-reference-cases.mjs +98 -0
- package/apps/bunmsh/test/utility-reference.test.js +23 -0
- package/apps/jsgotty/client-kitty.mjs +61 -0
- package/apps/jsgotty/client.mjs +16 -0
- package/apps/jsgotty/package-lock.json +2 -2
- package/apps/jsgotty/package.json +1 -1
- package/apps/jsgotty/static/js/gotty.js +1 -0
- package/apps/jsgotty/tests/client-kitty.test.mjs +43 -0
- package/package.json +1 -1
- package/apps/bunmsh/test/shell.test.js +0 -2092
package/apps/bunmsh/src/main.js
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
createState,
|
|
7
7
|
execute,
|
|
8
8
|
executeArgv,
|
|
9
|
+
finishShell,
|
|
9
10
|
executeBunShellFallback,
|
|
10
11
|
isJavaScriptMode,
|
|
11
12
|
needsMoreInput,
|
|
@@ -440,6 +441,10 @@ async function interactive(state) {
|
|
|
440
441
|
}
|
|
441
442
|
};
|
|
442
443
|
const interrupt = () => {
|
|
444
|
+
if (Object.hasOwn(state.traps,"SIGINT")) {
|
|
445
|
+
if (state.traps.SIGINT && !state.pendingTraps.includes("SIGINT")) state.pendingTraps.push("SIGINT");
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
443
448
|
state.lastStatus = 130;
|
|
444
449
|
if (foregroundCommand || promptAbort?.signal.aborted) return;
|
|
445
450
|
process.stdout.write("\n");
|
|
@@ -456,6 +461,7 @@ async function interactive(state) {
|
|
|
456
461
|
// periodic autosave.
|
|
457
462
|
const SIGNAL_EXIT_STATUS = { SIGHUP: 129, SIGTERM: 143 };
|
|
458
463
|
const terminate = (signal) => {
|
|
464
|
+
if (Object.hasOwn(state.traps,signal)) return;
|
|
459
465
|
state.exitRequested = true;
|
|
460
466
|
state.exitStatus = SIGNAL_EXIT_STATUS[signal] ?? state.exitStatus;
|
|
461
467
|
if (foregroundCommand) {
|
|
@@ -725,7 +731,7 @@ async function interactive(state) {
|
|
|
725
731
|
process.off("SIGHUP", terminate);
|
|
726
732
|
readline.close();
|
|
727
733
|
}
|
|
728
|
-
return state.exitRequested ? state.exitStatus : state.lastStatus;
|
|
734
|
+
return (await finishShell(state,state.exitRequested ? state.exitStatus : state.lastStatus)).status;
|
|
729
735
|
}
|
|
730
736
|
|
|
731
737
|
async function main(argv) {
|
|
@@ -736,6 +742,7 @@ async function main(argv) {
|
|
|
736
742
|
let forceInteractive = false;
|
|
737
743
|
let forceMouse = false;
|
|
738
744
|
let builtinOnly = false;
|
|
745
|
+
const shellOptions = {};
|
|
739
746
|
let i = 0;
|
|
740
747
|
for (; i < argv.length; i++) {
|
|
741
748
|
const arg = argv[i];
|
|
@@ -785,6 +792,10 @@ async function main(argv) {
|
|
|
785
792
|
await printChangelog();
|
|
786
793
|
return 0;
|
|
787
794
|
}
|
|
795
|
+
if (/^-[eufxv]+$/.test(arg)) {
|
|
796
|
+
for (const key of arg.slice(1)) shellOptions[key]=true;
|
|
797
|
+
continue;
|
|
798
|
+
}
|
|
788
799
|
if (arg.startsWith("-")) {
|
|
789
800
|
console.error(`bunmsh: ${arg}: unknown option`);
|
|
790
801
|
return 2;
|
|
@@ -801,7 +812,7 @@ async function main(argv) {
|
|
|
801
812
|
if (forceMouse) state.mouseTracking = true;
|
|
802
813
|
if (builtinOnly) state.pathSearch = false;
|
|
803
814
|
const result = await executeArgv(forwardedArgv, state);
|
|
804
|
-
return state.exitRequested ? state.exitStatus : result.status;
|
|
815
|
+
return (await finishShell(state,state.exitRequested ? state.exitStatus : result.status)).status;
|
|
805
816
|
}
|
|
806
817
|
|
|
807
818
|
if (command !== null) {
|
|
@@ -813,8 +824,10 @@ async function main(argv) {
|
|
|
813
824
|
mouseTracking: forceMouse || undefined,
|
|
814
825
|
pathSearch: builtinOnly ? false : undefined,
|
|
815
826
|
});
|
|
827
|
+
Object.assign(state.shellOptions,shellOptions);
|
|
828
|
+
if (state.shellOptions.v) console.error(command);
|
|
816
829
|
const result = await execute(command, state);
|
|
817
|
-
return state.exitRequested ? state.exitStatus : result.status;
|
|
830
|
+
return (await finishShell(state,state.exitRequested ? state.exitStatus : result.status)).status;
|
|
818
831
|
}
|
|
819
832
|
|
|
820
833
|
if (i < argv.length) {
|
|
@@ -826,9 +839,11 @@ async function main(argv) {
|
|
|
826
839
|
mouseTracking: forceMouse || undefined,
|
|
827
840
|
pathSearch: builtinOnly ? false : undefined,
|
|
828
841
|
});
|
|
842
|
+
Object.assign(state.shellOptions,shellOptions);
|
|
843
|
+
if (state.shellOptions.v) console.error(source);
|
|
829
844
|
const result = await execute(source, state);
|
|
830
845
|
if (forceInteractive && !state.exitRequested) return interactive(state);
|
|
831
|
-
return state.exitRequested ? state.exitStatus : result.status;
|
|
846
|
+
return (await finishShell(state,state.exitRequested ? state.exitStatus : result.status)).status;
|
|
832
847
|
} catch (error) {
|
|
833
848
|
console.error(`bunmsh: ${script}: ${error.message}`);
|
|
834
849
|
return 1;
|
|
@@ -839,10 +854,11 @@ async function main(argv) {
|
|
|
839
854
|
mouseTracking: forceMouse || undefined,
|
|
840
855
|
pathSearch: builtinOnly ? false : undefined,
|
|
841
856
|
});
|
|
857
|
+
Object.assign(state.shellOptions,shellOptions);
|
|
842
858
|
if (forceInteractive || process.stdin.isTTY) return interactive(state);
|
|
843
859
|
const source = await Bun.stdin.text();
|
|
844
860
|
const result = await execute(source, state);
|
|
845
|
-
return state.exitRequested ? state.exitStatus : result.status;
|
|
861
|
+
return (await finishShell(state,state.exitRequested ? state.exitStatus : result.status)).status;
|
|
846
862
|
}
|
|
847
863
|
|
|
848
864
|
// Lets --define process.env.SERVE_AUTO_OPEN=/index.html, =off, or =yes
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
// The fallback operates on pattern/hold spaces, with a program counter for
|
|
2
|
+
// branches. Parsing delimiters before command separators preserves s/;/x/.
|
|
3
|
+
export function compileSed(script, regexSource) {
|
|
4
|
+
let i = 0, lastPattern = null;
|
|
5
|
+
const commands = [], groups = [], labels = new Map();
|
|
6
|
+
const space = () => { while (/[ \t\r]/.test(script[i] ?? "") && i < script.length) i++; };
|
|
7
|
+
const field = delimiter => {
|
|
8
|
+
let value = "";
|
|
9
|
+
for (; i < script.length; i++) {
|
|
10
|
+
const ch = script[i];
|
|
11
|
+
if (ch === delimiter) { i++; return value; }
|
|
12
|
+
if (ch === "\\" && i + 1 < script.length) {
|
|
13
|
+
const next = script[++i];
|
|
14
|
+
value += next === delimiter && !'.*[]^$\\'.includes(next) ? next : `\\${next}`;
|
|
15
|
+
} else value += ch;
|
|
16
|
+
}
|
|
17
|
+
throw new Error("unterminated sed field");
|
|
18
|
+
};
|
|
19
|
+
const regex = pattern => {
|
|
20
|
+
if (pattern) lastPattern = pattern;
|
|
21
|
+
if (lastPattern === null) throw new Error("no previous regular expression");
|
|
22
|
+
return regexSource(lastPattern);
|
|
23
|
+
};
|
|
24
|
+
const address = () => {
|
|
25
|
+
if (script[i] === "$") { i++; return {last:true}; }
|
|
26
|
+
if (/\d/.test(script[i] ?? "")) { const match = /^\d+/.exec(script.slice(i))[0]; i += match.length; return {line:Number(match)}; }
|
|
27
|
+
if (script[i] === "/") { i++; return {regex:new RegExp(regex(field("/")))}; }
|
|
28
|
+
return null;
|
|
29
|
+
};
|
|
30
|
+
while (i < script.length) {
|
|
31
|
+
space();
|
|
32
|
+
if (";\n".includes(script[i])) { i++; continue; }
|
|
33
|
+
if (script[i] === "#") { while (i < script.length && script[i] !== "\n") i++; continue; }
|
|
34
|
+
if (i >= script.length) break;
|
|
35
|
+
const first = address(); space();
|
|
36
|
+
let second = null;
|
|
37
|
+
if (script[i] === ",") { i++; space(); second = address(); if (!second) throw new Error("missing range address"); }
|
|
38
|
+
space(); const invert = script[i] === "!"; if (invert) { i++; space(); }
|
|
39
|
+
const op = script[i++], command = {op, first, second, invert};
|
|
40
|
+
const index = commands.length;
|
|
41
|
+
if (op === "{") groups.push(index);
|
|
42
|
+
else if (op === "}") { const open = groups.pop(); if (open === undefined) throw new Error("unexpected }"); commands[open].end = index; }
|
|
43
|
+
else if (op === "s" || op === "y") {
|
|
44
|
+
const delimiter = script[i++];
|
|
45
|
+
const pattern = field(delimiter), replacement = field(delimiter);
|
|
46
|
+
if (op === "y") {
|
|
47
|
+
const unescape = s => s.replace(/\\n/g,"\n").replace(/\\(.)/g,"$1");
|
|
48
|
+
command.from = unescape(pattern); command.to = unescape(replacement);
|
|
49
|
+
if (command.from.length !== command.to.length) throw new Error("unequal y strings");
|
|
50
|
+
} else {
|
|
51
|
+
const flags = /^[gp0-9]*/.exec(script.slice(i))[0]; i += flags.length;
|
|
52
|
+
command.regex = new RegExp(regex(pattern), "g");
|
|
53
|
+
command.replacement = replacement;
|
|
54
|
+
command.global = flags.includes("g"); command.print = flags.includes("p");
|
|
55
|
+
command.occurrence = Number(/\d+/.exec(flags)?.[0] ?? 1);
|
|
56
|
+
}
|
|
57
|
+
} else if ([":","b","t","T"].includes(op)) {
|
|
58
|
+
space(); const start = i; while (i < script.length && !";\n}".includes(script[i])) i++;
|
|
59
|
+
command.label = script.slice(start,i).trim();
|
|
60
|
+
if (op === ":") labels.set(command.label,index);
|
|
61
|
+
} else if (op === "r") {
|
|
62
|
+
space(); const start=i; while (i<script.length && script[i]!=="\n") i++;
|
|
63
|
+
command.file=script.slice(start,i);
|
|
64
|
+
if (!command.file) throw new Error("r requires a filename");
|
|
65
|
+
} else if (["a","i","c"].includes(op)) {
|
|
66
|
+
space(); if (script[i] === "\\") { i++; if (script[i] === "\n") i++; }
|
|
67
|
+
let text = "";
|
|
68
|
+
while (i < script.length && script[i] !== "\n") {
|
|
69
|
+
if (script[i] === "\\" && script[i+1] === "\n") { text += "\n"; i+=2; }
|
|
70
|
+
else text += script[i++];
|
|
71
|
+
}
|
|
72
|
+
command.text = text;
|
|
73
|
+
} else if (!"pPdDqnhHgGxN=".includes(op ?? "")) throw new Error(`unsupported sed command: ${op}`);
|
|
74
|
+
commands.push(command);
|
|
75
|
+
}
|
|
76
|
+
if (groups.length) throw new Error("unclosed sed group");
|
|
77
|
+
for (const command of commands) if (["b","t","T"].includes(command.op)) {
|
|
78
|
+
command.target = command.label ? labels.get(command.label) : commands.length;
|
|
79
|
+
if (command.target === undefined) throw new Error(`undefined sed label: ${command.label}`);
|
|
80
|
+
}
|
|
81
|
+
return commands;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function replacementText(template, args) {
|
|
85
|
+
let output = "";
|
|
86
|
+
for (let i = 0; i < template.length; i++) {
|
|
87
|
+
if (template[i] === "&") output += args[0];
|
|
88
|
+
else if (template[i] === "\\" && i + 1 < template.length) {
|
|
89
|
+
const ch = template[++i];
|
|
90
|
+
output += /[1-9]/.test(ch) ? (args[Number(ch)] ?? "") : ch === "n" ? "\n" : ch;
|
|
91
|
+
} else output += template[i];
|
|
92
|
+
}
|
|
93
|
+
return output;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function applySed(text, commands, quiet, readFile = () => "") {
|
|
97
|
+
if (!text) return "";
|
|
98
|
+
const trailing = text.endsWith("\n"), lines = text.split("\n");
|
|
99
|
+
if (trailing) lines.pop();
|
|
100
|
+
const active = new Set(), output = [];
|
|
101
|
+
let hold = "";
|
|
102
|
+
for (let index = 0; index < lines.length; index++) {
|
|
103
|
+
let pattern = lines[index], deleted = false, changed = false, quit = false;
|
|
104
|
+
const appended = [];
|
|
105
|
+
const emit = value => output.push(value + (index < lines.length-1 || trailing ? "\n" : ""));
|
|
106
|
+
const matches = a => a.last ? index === lines.length-1 : a.line !== undefined ? index+1 === a.line : a.regex.test(pattern);
|
|
107
|
+
for (let pc = 0; pc < commands.length; pc++) {
|
|
108
|
+
const c = commands[pc];
|
|
109
|
+
let selected = !c.first || matches(c.first);
|
|
110
|
+
if (c.second) {
|
|
111
|
+
const wasActive = active.has(pc);
|
|
112
|
+
selected ||= wasActive;
|
|
113
|
+
if (selected) {
|
|
114
|
+
active.add(pc);
|
|
115
|
+
if ((wasActive || !c.second.regex) && (c.second.line !== undefined ? index+1 >= c.second.line : matches(c.second))) active.delete(pc);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (c.invert) selected = !selected;
|
|
119
|
+
if (!selected) { if (c.op === "{") pc = c.end; continue; }
|
|
120
|
+
if (c.op === "s") {
|
|
121
|
+
let count = 0, replaced = false;
|
|
122
|
+
pattern = pattern.replace(c.regex,(...args) => {
|
|
123
|
+
count++;
|
|
124
|
+
if (count !== c.occurrence && !(c.global && count >= c.occurrence)) return args[0];
|
|
125
|
+
replaced = true; return replacementText(c.replacement,args);
|
|
126
|
+
});
|
|
127
|
+
changed ||= replaced;
|
|
128
|
+
if (replaced && c.print) emit(pattern);
|
|
129
|
+
} else if (c.op === "y") pattern = [...pattern].map(ch => { const at=c.from.indexOf(ch); return at<0 ? ch : c.to[at]; }).join("");
|
|
130
|
+
else if (c.op === "p") emit(pattern);
|
|
131
|
+
else if (c.op === "P") emit(pattern.split("\n")[0]);
|
|
132
|
+
else if (c.op === "=") emit(String(index+1));
|
|
133
|
+
else if (c.op === "h") hold = pattern;
|
|
134
|
+
else if (c.op === "H") hold += "\n" + pattern;
|
|
135
|
+
else if (c.op === "g") pattern = hold;
|
|
136
|
+
else if (c.op === "G") pattern += "\n" + hold;
|
|
137
|
+
else if (c.op === "x") [pattern,hold] = [hold,pattern];
|
|
138
|
+
else if (c.op === "d") { deleted=true; break; }
|
|
139
|
+
else if (c.op === "D") { const nl=pattern.indexOf("\n"); if (nl<0) { deleted=true; break; } pattern=pattern.slice(nl+1); pc=-1; }
|
|
140
|
+
else if (c.op === "q") { quit=true; break; }
|
|
141
|
+
else if (c.op === "n" || c.op === "N") {
|
|
142
|
+
if (c.op === "n" && !quiet) emit(pattern);
|
|
143
|
+
if (index+1 >= lines.length) { deleted = c.op === "n"; quit=true; break; }
|
|
144
|
+
pattern = (c.op === "N" ? pattern + "\n" : "") + lines[++index]; changed=false;
|
|
145
|
+
} else if (["b","t","T"].includes(c.op)) {
|
|
146
|
+
const take = c.op === "b" || (c.op === "t" ? changed : !changed);
|
|
147
|
+
if (c.op !== "b") changed=false;
|
|
148
|
+
if (take) pc=c.target-1;
|
|
149
|
+
} else if (c.op === "r") appended.push(readFile(c.file));
|
|
150
|
+
else if (c.op === "a") appended.push(c.text+"\n");
|
|
151
|
+
else if (c.op === "i") output.push(c.text+"\n");
|
|
152
|
+
else if (c.op === "c") { if (!c.second || !active.has(pc)) output.push(c.text+"\n"); deleted=true; break; }
|
|
153
|
+
}
|
|
154
|
+
if (!quiet && !deleted) emit(pattern);
|
|
155
|
+
output.push(...appended);
|
|
156
|
+
if (quit) break;
|
|
157
|
+
}
|
|
158
|
+
return output.join("");
|
|
159
|
+
}
|