buninu 0.4.18 → 0.4.20

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.
Files changed (44) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/apps/bunmsh/CHANGELOG.md +131 -0
  3. package/apps/bunmsh/PORTING.md +133 -15
  4. package/apps/bunmsh/help/awk.md +38 -0
  5. package/apps/bunmsh/help/break.md +21 -0
  6. package/apps/bunmsh/help/chmod.md +8 -2
  7. package/apps/bunmsh/help/cmp.md +29 -0
  8. package/apps/bunmsh/help/continue.md +22 -0
  9. package/apps/bunmsh/help/cp.md +4 -0
  10. package/apps/bunmsh/help/diff.md +30 -0
  11. package/apps/bunmsh/help/egrep.md +26 -0
  12. package/apps/bunmsh/help/export.md +2 -2
  13. package/apps/bunmsh/help/expr.md +30 -0
  14. package/apps/bunmsh/help/fgrep.md +25 -0
  15. package/apps/bunmsh/help/grep.md +4 -0
  16. package/apps/bunmsh/help/ls.md +4 -2
  17. package/apps/bunmsh/help/mkdir.md +1 -1
  18. package/apps/bunmsh/help/print.md +3 -0
  19. package/apps/bunmsh/help/return.md +23 -0
  20. package/apps/bunmsh/help/sed.md +8 -3
  21. package/apps/bunmsh/help/set.md +12 -3
  22. package/apps/bunmsh/help/tr.md +4 -0
  23. package/apps/bunmsh/help/trap.md +22 -0
  24. package/apps/bunmsh/help/uniq.md +27 -0
  25. package/apps/bunmsh/package.json +1 -1
  26. package/apps/bunmsh/src/awk.js +203 -0
  27. package/apps/bunmsh/src/diff.js +222 -0
  28. package/apps/bunmsh/src/fancy-ls.js +15 -4
  29. package/apps/bunmsh/src/main.js +28 -10
  30. package/apps/bunmsh/src/sed.js +159 -0
  31. package/apps/bunmsh/src/shell.js +1714 -462
  32. package/apps/bunmsh/test/awk-reference.test.js +35 -0
  33. package/apps/bunmsh/test/awk.test.js +62 -0
  34. package/apps/bunmsh/test/posix-bootstrap.test.js +595 -0
  35. package/apps/bunmsh/test/shell1.test.js +64 -0
  36. package/apps/bunmsh/test/utility-reference-cases.mjs +98 -0
  37. package/apps/bunmsh/test/utility-reference.test.js +23 -0
  38. package/apps/jsgotty/client-kitty.mjs +61 -0
  39. package/apps/jsgotty/client.mjs +16 -0
  40. package/apps/jsgotty/package-lock.json +2 -2
  41. package/apps/jsgotty/package.json +1 -1
  42. package/apps/jsgotty/tests/client-kitty.test.mjs +43 -0
  43. package/package.json +1 -1
  44. package/apps/bunmsh/test/shell.test.js +0 -2092
@@ -43,8 +43,8 @@ function classifySuffix(stats) {
43
43
  }
44
44
 
45
45
  function displayEntry(entry, options) {
46
- const suffix = options.classify ? classifySuffix(entry.stats) : entry.stats.isDirectory() ? "/" : "";
47
- return `${iconFor(entry.name, entry.stats, entry.path)} ${entry.name}${suffix}`;
46
+ const suffix = options.classify ? classifySuffix(entry.stats) : !options.plain && entry.stats.isDirectory() ? "/" : "";
47
+ return `${options.inode ? `${entry.stats.ino} ` : ""}${options.plain ? "" : `${iconFor(entry.name, entry.stats, entry.path)} `}${entry.name}${suffix}`;
48
48
  }
49
49
 
50
50
  function columns(items, width) {
@@ -98,7 +98,7 @@ function listDirectory(path, options) {
98
98
  .filter((entry) => options.all || options.almostAll || !entry.name.startsWith("."))
99
99
  .map((entry) => {
100
100
  const entryPath = resolve(path, entry.name);
101
- return { name: entry.name, stats: lstatSync(entryPath), path: entryPath };
101
+ return { name: entry.name, stats: options.dereference ? statSync(entryPath) : lstatSync(entryPath), path: entryPath };
102
102
  });
103
103
  if (options.all) entries = [
104
104
  { name: ".", stats: lstatSync(path), path },
@@ -116,6 +116,7 @@ function listDirectory(path, options) {
116
116
 
117
117
  export function fancyLs(argv, state, terminal = Boolean(process.stdout.isTTY)) {
118
118
  const options = {
119
+ plain: argv[0] === "ls" && Boolean(state.shellOptions?.posix),
119
120
  all: false, almostAll: false, directory: false, long: false, human: false,
120
121
  recursive: false, time: false, reverse: false, size: false, one: false, classify: false,
121
122
  };
@@ -128,6 +129,8 @@ export function fancyLs(argv, state, terminal = Boolean(process.stdout.isTTY)) {
128
129
  for (const flag of argument.slice(1)) {
129
130
  if (flag === "a") options.all = true;
130
131
  else if (flag === "A") options.almostAll = true;
132
+ else if (flag === "L") options.dereference = true;
133
+ else if (flag === "i") options.inode = true;
131
134
  else if (flag === "d") options.directory = true;
132
135
  else if (flag === "l") options.long = true;
133
136
  else if (flag === "h") options.human = true;
@@ -148,7 +151,7 @@ export function fancyLs(argv, state, terminal = Boolean(process.stdout.isTTY)) {
148
151
  const show = (operand, heading = operands.length > 1, separate = true) => {
149
152
  const path = isAbsolute(operand) ? operand : resolve(state.cwd, operand);
150
153
  try {
151
- const stats = lstatSync(path);
154
+ const stats = options.dereference ? statSync(path) : lstatSync(path);
152
155
  let listingDirectory = stats.isDirectory() && !options.directory;
153
156
  // GNU/POSIX-style ls follows a command-line symlink to a directory for
154
157
  // an ordinary listing and for -R. -d, -l, and -F request information
@@ -196,6 +199,14 @@ export function fancyLs(argv, state, terminal = Boolean(process.stdout.isTTY)) {
196
199
  stderr += `bunmsh: ${argv[0]}: ${operand}: ${error.message}\n`;
197
200
  }
198
201
  };
202
+ if (options.time || options.size) operands.sort((a,b) => {
203
+ try {
204
+ const stat = options.dereference ? statSync : lstatSync;
205
+ const left=stat(resolve(state.cwd,a)), right=stat(resolve(state.cwd,b));
206
+ const order=(options.size ? right.size-left.size : right.mtimeMs-left.mtimeMs) || a.localeCompare(b);
207
+ return options.reverse ? -order : order;
208
+ } catch { return 0; }
209
+ });
199
210
  for (const operand of operands) show(operand);
200
211
  return { status, stdout, stderr };
201
212
  }
@@ -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,18 +812,22 @@ 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) {
808
819
  const name = argv[i] ?? "bunmsh";
809
- const state = createState({
810
- args: [name, ...argv.slice(i + 1)],
811
- mouseTracking: forceMouse || undefined,
812
- pathSearch: builtinOnly ? false : undefined,
813
- });
820
+ const state = process.env.BUNMSH_INTERNAL_PIPELINE_STATE
821
+ ? pipelineChildState()
822
+ : createState({
823
+ args: [name, ...argv.slice(i + 1)],
824
+ mouseTracking: forceMouse || undefined,
825
+ pathSearch: builtinOnly ? false : undefined,
826
+ });
827
+ Object.assign(state.shellOptions,shellOptions);
828
+ if (state.shellOptions.v) console.error(command);
814
829
  const result = await execute(command, state);
815
- return state.exitRequested ? state.exitStatus : result.status;
830
+ return (await finishShell(state,state.exitRequested ? state.exitStatus : result.status)).status;
816
831
  }
817
832
 
818
833
  if (i < argv.length) {
@@ -824,9 +839,11 @@ async function main(argv) {
824
839
  mouseTracking: forceMouse || undefined,
825
840
  pathSearch: builtinOnly ? false : undefined,
826
841
  });
842
+ Object.assign(state.shellOptions,shellOptions);
843
+ if (state.shellOptions.v) console.error(source);
827
844
  const result = await execute(source, state);
828
845
  if (forceInteractive && !state.exitRequested) return interactive(state);
829
- return state.exitRequested ? state.exitStatus : result.status;
846
+ return (await finishShell(state,state.exitRequested ? state.exitStatus : result.status)).status;
830
847
  } catch (error) {
831
848
  console.error(`bunmsh: ${script}: ${error.message}`);
832
849
  return 1;
@@ -837,10 +854,11 @@ async function main(argv) {
837
854
  mouseTracking: forceMouse || undefined,
838
855
  pathSearch: builtinOnly ? false : undefined,
839
856
  });
857
+ Object.assign(state.shellOptions,shellOptions);
840
858
  if (forceInteractive || process.stdin.isTTY) return interactive(state);
841
859
  const source = await Bun.stdin.text();
842
860
  const result = await execute(source, state);
843
- return state.exitRequested ? state.exitStatus : result.status;
861
+ return (await finishShell(state,state.exitRequested ? state.exitStatus : result.status)).status;
844
862
  }
845
863
 
846
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
+ }