@poe-platform/safe-bash 0.1.788 → 0.1.789

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 (180) hide show
  1. package/README.md +1 -1
  2. package/dist/safe-bash/chunks/{chunk-WHURJBET.js → chunk-2MUJ7RLZ.js} +1 -1
  3. package/dist/safe-bash/chunks/{chunk-PYTLIUST.js → chunk-CTUHLNJM.js} +2 -2
  4. package/dist/safe-bash/chunks/{chunk-Z3HX3ZBA.js → chunk-D4PHJK4T.js} +1 -1
  5. package/dist/safe-bash/chunks/{chunk-O6356Z27.js → chunk-H7MMJS26.js} +2 -2
  6. package/dist/safe-bash/chunks/{chunk-Q3E6C3DX.js → chunk-KHJGUF2W.js} +14 -0
  7. package/dist/safe-bash/chunks/{chunk-FJZNUUOQ.js → chunk-VUVKC6HV.js} +1 -1
  8. package/dist/safe-bash/chunks/{chunk-ZMYVCRAF.js → chunk-X5ZCECM6.js} +2 -2
  9. package/dist/safe-bash/chunks/{git-UFNPAOYZ.js → git-HO7WDPHZ.js} +2 -2
  10. package/dist/safe-bash/command-chunks/{chunk-PFBJR2PY.js → chunk-2KRKKW5S.js} +2 -2
  11. package/dist/safe-bash/command-chunks/{chunk-4DSJDVBM.js → chunk-32F3RBUW.js} +10 -749
  12. package/dist/safe-bash/command-chunks/{chunk-L3MLMXL4.js → chunk-3ZK5F23O.js} +1 -1
  13. package/dist/safe-bash/command-chunks/{chunk-4QX6XCDD.js → chunk-4RNIZ3RT.js} +79 -0
  14. package/dist/safe-bash/command-chunks/{chunk-D7TADAYG.js → chunk-64LG3EVJ.js} +17 -0
  15. package/dist/safe-bash/command-chunks/{chunk-JUDOV5MJ.js → chunk-6F7QJMJ3.js} +1 -1
  16. package/dist/safe-bash/command-chunks/{chunk-IJ3FXP4Q.js → chunk-A3ZX6SCY.js} +1 -1
  17. package/dist/safe-bash/command-chunks/{chunk-YNT4ZJHW.js → chunk-AT6ZSOE6.js} +3 -3
  18. package/dist/safe-bash/command-chunks/{chunk-KSIGF4AG.js → chunk-BK4CMEE6.js} +1 -1
  19. package/dist/safe-bash/command-chunks/{chunk-XGMWWUHV.js → chunk-CZ5DAK4K.js} +1 -1
  20. package/dist/safe-bash/command-chunks/{chunk-Z57TV6TC.js → chunk-EZ2DTOLI.js} +4 -2
  21. package/dist/safe-bash/command-chunks/{chunk-ADCGH6A7.js → chunk-FH3D75FC.js} +1 -1
  22. package/dist/safe-bash/command-chunks/{chunk-WYJTWJZ5.js → chunk-G3G6W5ZE.js} +1 -1
  23. package/dist/safe-bash/command-chunks/{chunk-3HDIDEUJ.js → chunk-GF54KE2M.js} +1 -1
  24. package/dist/safe-bash/command-chunks/{chunk-Z7TYZG3A.js → chunk-IHHTTQX6.js} +5 -3
  25. package/dist/safe-bash/command-chunks/{chunk-INDIFLBW.js → chunk-JTAGNROA.js} +4 -0
  26. package/dist/safe-bash/command-chunks/chunk-PGEYORBZ.js +25 -0
  27. package/dist/safe-bash/command-chunks/chunk-QM4UZF3C.js +753 -0
  28. package/dist/safe-bash/command-chunks/{chunk-TOSM3T3A.js → chunk-UJOO6DKY.js} +20 -9
  29. package/dist/safe-bash/command-chunks/{chunk-2LP3VMPA.js → chunk-WFDW4XXD.js} +1 -17
  30. package/dist/safe-bash/command-chunks/chunk-WR37IS4O.js +318 -0
  31. package/dist/safe-bash/command-chunks/{chunk-Y5IHIAY3.js → chunk-XNHSCU6I.js} +7 -3
  32. package/dist/safe-bash/commands/basic.js +148 -48
  33. package/dist/safe-bash/commands/bytes/compression/index.js +5 -6
  34. package/dist/safe-bash/commands/cmp.d.ts +2 -3
  35. package/dist/safe-bash/commands/cmp.js +2 -689
  36. package/dist/safe-bash/commands/docx/index.browser.js +3 -46
  37. package/dist/safe-bash/commands/docx/index.d.ts +2 -24
  38. package/dist/safe-bash/commands/docx/index.js +2 -41
  39. package/dist/safe-bash/commands/docx/io.d.ts +1 -14
  40. package/dist/safe-bash/commands/docx/io.js +1 -15
  41. package/dist/safe-bash/commands/llm/index.browser.js +3 -3
  42. package/dist/safe-bash/commands/network/index.browser.js +3 -3
  43. package/dist/safe-bash/commands/node/index.browser.js +3 -3
  44. package/dist/safe-bash/commands/pptx/index.d.ts +2 -28
  45. package/dist/safe-bash/commands/pptx/index.js +2 -152
  46. package/dist/safe-bash/commands/printf-float.d.ts +1 -0
  47. package/dist/safe-bash/commands/printf-float.js +48 -38
  48. package/dist/safe-bash/commands/printf-format.js +9 -6
  49. package/dist/safe-bash/commands/printf-quote.d.ts +3 -0
  50. package/dist/safe-bash/commands/printf-quote.js +62 -0
  51. package/dist/safe-bash/commands/printf-wide.d.ts +5 -0
  52. package/dist/safe-bash/commands/printf-wide.js +41 -0
  53. package/dist/safe-bash/commands/python/index.browser.js +2 -2
  54. package/dist/safe-bash/commands/truncate.d.ts +2 -3
  55. package/dist/safe-bash/commands/truncate.js +2 -686
  56. package/dist/safe-bash/contracts/diagnostics.d.ts +2 -0
  57. package/dist/safe-bash/contracts/diagnostics.js +2 -0
  58. package/dist/safe-bash/contracts/runtime-control.d.ts +2 -0
  59. package/dist/safe-bash/contracts/runtime-control.js +2 -0
  60. package/dist/safe-bash/core.browser.d.ts +1 -1
  61. package/dist/safe-bash/core.browser.js +2651 -4871
  62. package/dist/safe-bash/core.d.ts +10 -0
  63. package/dist/safe-bash/opt-in/commands/cmp/index.d.ts +6 -2
  64. package/dist/safe-bash/opt-in/commands/cmp/index.js +776 -79
  65. package/dist/safe-bash/opt-in/commands/cmp/options.d.ts +0 -12
  66. package/dist/safe-bash/opt-in/commands/cmp/options.js +0 -153
  67. package/dist/safe-bash/opt-in/commands/truncate/index.d.ts +8 -3
  68. package/dist/safe-bash/opt-in/commands/truncate/index.js +790 -272
  69. package/dist/safe-bash/opt-in/commands/truncate/sync.d.ts +1 -0
  70. package/dist/safe-bash/opt-in/commands/truncate/sync.js +60 -0
  71. package/dist/safe-bash/optional-host.browser.js +9 -1
  72. package/dist/safe-bash/optional-host.d.ts +3 -1
  73. package/dist/safe-bash/optional-host.js +3 -1
  74. package/dist/safe-bash/shell/runtime.js +10 -6
  75. package/dist/safe-bash-checksum-engine/index.d.ts +1 -1
  76. package/dist/safe-bash-checksum-engine/index.js +14 -2
  77. package/dist/safe-bash-command-bzip2/index.js +2 -1
  78. package/dist/safe-bash-command-cmp/index.d.ts +10 -0
  79. package/dist/safe-bash-command-cmp/index.js +796 -0
  80. package/dist/safe-bash-command-cmp/options.d.ts +12 -0
  81. package/dist/safe-bash-command-cmp/options.js +42 -0
  82. package/dist/safe-bash-command-csvcut/index.js +11 -8
  83. package/dist/safe-bash-command-csvgrep/index.js +35 -7
  84. package/dist/safe-bash-command-csvkit/codecs/python.js +4 -3
  85. package/dist/safe-bash-command-csvkit/codecs/utf8.js +3 -2
  86. package/dist/safe-bash-command-csvkit/index.js +80 -324
  87. package/dist/safe-bash-command-csvkit/python-regex.d.ts +5 -0
  88. package/dist/safe-bash-command-csvkit/python-regex.js +14 -0
  89. package/dist/safe-bash-command-csvkit/runtime.d.ts +1 -0
  90. package/dist/safe-bash-command-csvkit/sync-support.js +4 -2
  91. package/dist/safe-bash-command-diff3/index.js +3 -0
  92. package/dist/safe-bash-command-docx/LICENSE +21 -0
  93. package/dist/safe-bash-command-docx/index.d.ts +30 -0
  94. package/dist/safe-bash-command-docx/index.js +67 -0
  95. package/dist/safe-bash-command-docx/io.d.ts +14 -0
  96. package/dist/safe-bash-command-docx/io.js +26 -0
  97. package/dist/safe-bash-command-envsubst/index.js +34 -3
  98. package/dist/safe-bash-command-exiftool/index.js +3 -0
  99. package/dist/safe-bash-command-ffmpeg/index.js +1102 -1088
  100. package/dist/safe-bash-command-fold/family.js +1 -1
  101. package/dist/safe-bash-command-fold/index.js +1 -1
  102. package/dist/safe-bash-command-htmlq/index.js +5 -0
  103. package/dist/safe-bash-command-imagemagick/index.d.ts +25 -21
  104. package/dist/safe-bash-command-imagemagick/index.js +669 -271
  105. package/dist/safe-bash-command-mmdc/index.js +48 -10
  106. package/dist/safe-bash-command-mmdc/svg.d.ts +1 -0
  107. package/dist/safe-bash-command-op/index.js +19 -14
  108. package/dist/safe-bash-command-pandoc/command.js +1 -1
  109. package/dist/safe-bash-command-pandoc/index.js +1 -1
  110. package/dist/safe-bash-command-pdfimages/index.d.ts +7 -1
  111. package/dist/safe-bash-command-pdfimages/index.js +63 -33
  112. package/dist/safe-bash-command-pdfinfo/index.d.ts +24 -20
  113. package/dist/safe-bash-command-pdfinfo/index.js +370 -161
  114. package/dist/safe-bash-command-pdftk/index.d.ts +7 -3
  115. package/dist/safe-bash-command-pdftk/index.js +114 -45
  116. package/dist/safe-bash-command-pdftoppm/index.d.ts +15 -7
  117. package/dist/safe-bash-command-pdftoppm/index.js +179 -109
  118. package/dist/safe-bash-command-pdftotext/index.d.ts +9 -5
  119. package/dist/safe-bash-command-pdftotext/index.js +90 -27
  120. package/dist/safe-bash-command-pptx/LICENSE +21 -0
  121. package/dist/safe-bash-command-pptx/index.d.ts +33 -0
  122. package/dist/safe-bash-command-pptx/index.js +168 -0
  123. package/dist/safe-bash-command-qpdf/index.d.ts +9 -3
  124. package/dist/safe-bash-command-qpdf/index.js +80 -29
  125. package/dist/safe-bash-command-sha512sum/index.js +12 -475
  126. package/dist/safe-bash-command-sips/index.d.ts +9 -5
  127. package/dist/safe-bash-command-sips/index.js +63 -44
  128. package/dist/safe-bash-command-soffice/index.d.ts +9 -3
  129. package/dist/safe-bash-command-soffice/index.js +65 -24
  130. package/dist/safe-bash-command-sqlite3/engine.d.ts +9 -0
  131. package/dist/safe-bash-command-sqlite3/index.d.ts +1 -0
  132. package/dist/safe-bash-command-sqlite3/index.js +851 -352
  133. package/dist/safe-bash-command-ssconvert/formats/xls.js +3 -3
  134. package/dist/safe-bash-command-timeout/index.js +3 -3
  135. package/dist/safe-bash-command-truncate/index.d.ts +8 -3
  136. package/dist/safe-bash-command-truncate/index.js +790 -272
  137. package/dist/safe-bash-command-truncate/sync.d.ts +1 -0
  138. package/dist/safe-bash-command-truncate/sync.js +60 -0
  139. package/dist/safe-bash-command-wkhtmltopdf/index.js +92 -38
  140. package/dist/safe-bash-command-wkhtmltopdf/pdf-renderer.d.ts +1 -1
  141. package/dist/safe-bash-command-xxd/index.js +78 -20
  142. package/dist/safe-bash-command-xz/command.d.ts +4 -0
  143. package/dist/safe-bash-command-xz/index.d.ts +1 -1
  144. package/dist/safe-bash-command-xz/index.js +1 -1
  145. package/dist/safe-bash-compression-engine/bounded-codec.js +3 -3
  146. package/dist/safe-bash-compression-engine/codec-loader.js +2 -2
  147. package/dist/safe-bash-compression-engine/files.js +6 -6
  148. package/dist/safe-bash-compression-engine/index.js +5 -5
  149. package/dist/safe-bash-compression-engine/operand.js +6 -6
  150. package/dist/safe-bash-compression-engine/stream.js +5 -5
  151. package/dist/safe-bash-compression-engine/zstd-decode.js +4 -4
  152. package/dist/safe-bash-contracts/command.js +3 -3
  153. package/dist/safe-bash-contracts/diagnostics.js +3 -3
  154. package/dist/safe-bash-contracts/escaping.js +9 -9
  155. package/dist/safe-bash-contracts/filesystem-descriptor.js +3 -3
  156. package/dist/safe-bash-contracts/filesystem-output.js +8 -8
  157. package/dist/safe-bash-contracts/index.js +21 -19
  158. package/dist/safe-bash-contracts/io.d.ts +12 -0
  159. package/dist/safe-bash-contracts/io.js +4 -1
  160. package/dist/safe-bash-contracts/output.js +3 -2
  161. package/dist/safe-bash-contracts/runtime-control.js +2 -2
  162. package/dist/safe-bash-contracts/signals.js +1 -1
  163. package/dist/safe-bash-contracts/yield.d.ts +1 -0
  164. package/dist/safe-bash-contracts/yield.js +3 -1
  165. package/dist/safe-bash-csv-engine/index.d.ts +10 -1
  166. package/dist/safe-bash-csv-engine/index.js +78 -6
  167. package/dist/safe-bash-io-engine/commands/search/requirements.js +3 -3
  168. package/dist/safe-bash-io-engine/commands/text-programs/shared.js +3 -3
  169. package/dist/safe-bash-io-engine/gnu-information.js +1 -1
  170. package/dist/safe-bash-io-engine/internal.d.ts +1 -1
  171. package/dist/safe-bash-io-engine/internal.js +2 -2
  172. package/dist/safe-bash-query-engine/index.js +4 -4
  173. package/dist/safe-bash-query-engine/interpreter.js +2 -2
  174. package/dist/safe-bash-query-engine/query-core.js +4 -4
  175. package/package.json +2 -2
  176. package/dist/safe-bash/opt-in/commands/cmp/compare.js +0 -95
  177. package/dist/safe-bash/opt-in/commands/cmp/io.js +0 -218
  178. package/dist/safe-bash/command-chunks/{chunk-FXYWQ6S4.js → chunk-2VTMZTV3.js} +0 -0
  179. package/dist/safe-bash/command-chunks/{chunk-RTOJNFFL.js → chunk-BJ6DGYJA.js} +4 -4
  180. package/dist/safe-bash/command-chunks/{chunk-DEH3CMGL.js → chunk-QJ3ONEBI.js} +3 -3
@@ -1,99 +1,796 @@
1
- import { commandRuntimeIdentity } from "@poe-platform/safe-bash/contracts";
2
- import { compare } from "./compare.js";
3
- import { errorText, InputError, inputDiagnostic, Session } from "./io.js";
4
- import { limitsFor, parseArguments, UsageError } from "./options.js";
5
- export function createCmpCommand(options = {}) {
6
- const limits = limitsFor(options);
7
- const comparisonBlockBytes = options.comparisonBlockBytes;
8
- if (comparisonBlockBytes !== undefined && (!Number.isSafeInteger(comparisonBlockBytes) || comparisonBlockBytes < 1)) {
9
- throw new RangeError("cmp comparisonBlockBytes must be a positive safe integer");
1
+ import { commandRuntimeIdentity, FsError, getCommandArguments, isFsError } from "@poe-platform/safe-bash/contracts";
2
+ import { yieldTurn } from "@poe-platform/safe-bash/contracts/yield";
3
+ import { assertCommandRequirements } from "@poe-platform/safe-bash/contracts/command-requirements";
4
+ import { PublicDiagnostic, publicDiagnosticMessage } from "@poe-platform/safe-bash/contracts/diagnostics";
5
+ import { ByteInputBudget } from "@poe-platform/safe-bash/optional-host";
6
+ import { registerDefaultExecutor, output, pathOf, UsageError } from "@poe-platform/safe-bash/optional-host";
7
+ import { inputRequirements } from "@poe-platform/safe-bash/optional-host";
8
+ import { compareCopyIdentity, compareObservedEntries } from "@poe-platform/safe-bash/contracts/filesystem-identity";
9
+ import { limitsFor, quote } from "./options.js";
10
+ const defaultBlockBytes = 4096;
11
+ const maxCount = 18446744073709551616n;
12
+ function byteCount(text, option = "bytes", legacy = true) {
13
+ const invalid = () => { throw new UsageError(`invalid --${option} value '${text}'`); };
14
+ let position = 0;
15
+ while (text[position] === " " || text.charCodeAt(position) >= 9 && text.charCodeAt(position) <= 13)
16
+ position++;
17
+ if (text[position] === "+")
18
+ position++;
19
+ let base = 10;
20
+ if (text[position] === "0") {
21
+ base = 8;
22
+ if (text[position + 1]?.toLowerCase() === "x") {
23
+ base = 16;
24
+ position += 2;
25
+ }
26
+ }
27
+ const start = position;
28
+ let count = 0n;
29
+ const maximum = option === "bytes" ? 18446744073709551615n : 9223372036854775807n;
30
+ while (position < text.length) {
31
+ const digit = "0123456789abcdef".indexOf(text[position].toLowerCase());
32
+ if (digit < 0 || digit >= base)
33
+ break;
34
+ count = count * BigInt(base) + BigInt(digit);
35
+ if (legacy && count > maximum)
36
+ invalid();
37
+ if (!legacy && count > 9223372036854775807n)
38
+ count = 9223372036854775808n;
39
+ position++;
40
+ }
41
+ const suffix = text.slice(position);
42
+ if (position === start) {
43
+ if (position !== 0 || !"kKMGTPEZY".includes(suffix[0] ?? " "))
44
+ invalid();
45
+ count = 1n;
46
+ }
47
+ if (suffix) {
48
+ const power = "KMGTPEZY".indexOf(suffix[0] === "k" ? "K" : suffix[0]) + 1;
49
+ if (!power || !["", "B", "D", "iB"].includes(suffix.slice(1)))
50
+ invalid();
51
+ count *= (suffix.endsWith("B") && !suffix.endsWith("iB") || suffix.endsWith("D") ? 1000n : 1024n) ** BigInt(power);
52
+ }
53
+ if (legacy && count > maximum)
54
+ invalid();
55
+ return !legacy && count > 9223372036854775807n ? 9223372036854775808n : count;
56
+ }
57
+ function parse(context, legacy) {
58
+ const parsed = { names: [], skips: [0n, 0n], limit: maxCount, silent: false, verbose: false, printBytes: false };
59
+ const long = { "print-bytes": "b", "print-chars": "c", "ignore-initial": "i", verbose: "l", bytes: "n", silent: "s", quiet: "s", version: "v", help: "help" };
60
+ const operands = [];
61
+ const argumentsWithBytes = getCommandArguments(context);
62
+ const args = context.args.map((_argument, index) => {
63
+ const bytes = argumentsWithBytes.bytes(index);
64
+ return Array.from(bytes, byte => String.fromCharCode(byte)).join("");
65
+ });
66
+ let ended = false;
67
+ const apply = (key, argument) => {
68
+ if (key === "s" || key === "l") {
69
+ if (key === "s" ? parsed.verbose : parsed.silent)
70
+ throw new UsageError("options -l and -s are incompatible");
71
+ if (key === "s")
72
+ parsed.silent = true;
73
+ else
74
+ parsed.verbose = true;
75
+ }
76
+ else if (key === "b" || key === "c")
77
+ parsed.printBytes = true;
78
+ else if (key === "n") {
79
+ const next = byteCount(argument, "bytes", legacy);
80
+ if (next < parsed.limit)
81
+ parsed.limit = next;
82
+ }
83
+ else if (key === "i") {
84
+ const delimiter = argument.indexOf(":");
85
+ const first = delimiter < 0 ? argument : argument.slice(0, delimiter);
86
+ let firstSkip;
87
+ try {
88
+ firstSkip = byteCount(first, "ignore-initial", legacy);
89
+ }
90
+ catch {
91
+ throw new UsageError(`invalid --ignore-initial value '${argument}'`);
92
+ }
93
+ if (firstSkip > parsed.skips[0])
94
+ parsed.skips[0] = firstSkip;
95
+ const secondSkip = delimiter < 0 ? firstSkip : byteCount(argument.slice(delimiter + 1), "ignore-initial", legacy);
96
+ if (secondSkip > parsed.skips[1])
97
+ parsed.skips[1] = secondSkip;
98
+ }
99
+ else
100
+ parsed.information = key === "v" ? "version" : "help";
101
+ };
102
+ for (let index = 0; index < args.length; index++) {
103
+ const argument = args[index];
104
+ if (ended || argument === "-" || !argument.startsWith("-")) {
105
+ operands.push(argument);
106
+ if (Object.hasOwn(context.env, "POSIXLY_CORRECT"))
107
+ ended = true;
108
+ }
109
+ else if (argument === "--")
110
+ ended = true;
111
+ else if (argument.startsWith("--")) {
112
+ const equals = argument.indexOf("=");
113
+ const name = argument.slice(2, equals < 0 ? undefined : equals);
114
+ const matches = Object.keys(long).filter(candidate => candidate.startsWith(name));
115
+ const selected = Object.hasOwn(long, name) ? name : matches[0];
116
+ if (!Object.hasOwn(long, name) && matches.length > 1 && new Set(matches.map(match => long[match])).size > 1) {
117
+ throw new UsageError(`option '${argument}' is ambiguous; possibilities: ${matches.map(match => `'--${match}'`).join(" ")}`);
118
+ }
119
+ if (!selected)
120
+ throw new UsageError(`unrecognized option '${argument}'`);
121
+ const key = long[selected];
122
+ let value;
123
+ if (key === "i" || key === "n") {
124
+ value = equals < 0 ? args[++index] : argument.slice(equals + 1);
125
+ if (value === undefined)
126
+ throw new UsageError(`option '--${selected}' requires an argument`);
127
+ }
128
+ else if (equals >= 0)
129
+ throw new UsageError(`option '--${selected}' doesn't allow an argument`);
130
+ apply(key, value);
131
+ }
132
+ else {
133
+ for (let offset = 1; offset < argument.length; offset++) {
134
+ const key = argument[offset];
135
+ if (!"bcilnsv".includes(key))
136
+ throw new UsageError(`invalid option -- '${key}'`);
137
+ let value;
138
+ if (key === "i" || key === "n") {
139
+ value = argument.slice(offset + 1) || args[++index];
140
+ if (value === undefined)
141
+ throw new UsageError(`option requires an argument -- '${key}'`);
142
+ offset = argument.length;
143
+ }
144
+ apply(key, value);
145
+ if (parsed.information)
146
+ return parsed;
147
+ }
148
+ }
149
+ if (parsed.information)
150
+ return parsed;
151
+ }
152
+ if (!operands.length)
153
+ throw new UsageError(`missing operand after '${args.at(-1) ?? "cmp"}'`);
154
+ parsed.names = [operands[0], operands[1] ?? "-"];
155
+ for (const index of [0, 1]) {
156
+ if (operands[index + 2] !== undefined) {
157
+ const next = byteCount(operands[index + 2], "ignore-initial", legacy);
158
+ if (next > parsed.skips[index])
159
+ parsed.skips[index] = next;
160
+ }
161
+ }
162
+ if (operands.length > 4)
163
+ throw new UsageError(`extra operand '${operands[4]}'`);
164
+ return parsed;
165
+ }
166
+ async function observe(operation, signal) {
167
+ signal.throwIfAborted();
168
+ return new Promise((resolve, reject) => {
169
+ const abort = () => { signal.removeEventListener("abort", abort); reject(signal.reason); };
170
+ signal.addEventListener("abort", abort, { once: true });
171
+ Promise.resolve().then(() => { signal.throwIfAborted(); return operation(); }).then(value => {
172
+ signal.removeEventListener("abort", abort);
173
+ resolve(value);
174
+ }, error => { signal.removeEventListener("abort", abort); reject(error); });
175
+ });
176
+ }
177
+ class InvocationClosed extends Error {
178
+ }
179
+ class InputError extends PublicDiagnostic {
180
+ suppressInSilent;
181
+ constructor(message, suppressInSilent) {
182
+ super(message);
183
+ this.suppressInSilent = suppressInSilent;
10
184
  }
11
- return {
12
- name: "cmp", runtimeIdentity: commandRuntimeIdentity, description: "Compare two inputs byte by byte",
13
- async execute(context) {
185
+ }
186
+ function inputError(error, name, opening = false) {
187
+ if (!(error instanceof FsError))
188
+ return error;
189
+ const messages = {
190
+ EACCES: "Permission denied", EBADF: "Bad file descriptor", EIO: "Input/output error", EISDIR: "Is a directory",
191
+ ELOOP: "Too many levels of symbolic links", ENAMETOOLONG: "File name too long", ENOENT: "No such file or directory",
192
+ ENOTDIR: "Not a directory", ENOTSUP: "Operation not supported", EPERM: "Operation not permitted",
193
+ };
194
+ const detail = messages[error.code];
195
+ return detail ? new InputError(`${name}: ${detail}`, opening) : error;
196
+ }
197
+ function printByte(byte) {
198
+ const prefix = byte >= 128 ? "M-" : "";
199
+ const character = byte & 127;
200
+ return prefix + (character < 32 ? `^${String.fromCharCode(character + 64)}` : character === 127 ? "^?" : String.fromCharCode(character));
201
+ }
202
+ async function discardsOutput(context) {
203
+ if (!context.stdoutFile)
204
+ return false;
205
+ const options = { signal: context.signal };
206
+ try {
207
+ const paths = [context.stdoutFile.path, "/dev/null"];
208
+ for (const path of paths) {
209
+ const capabilities = await context.fs.capabilitiesFor?.(path, options) ?? context.fs.capabilities;
14
210
  context.signal.throwIfAborted();
15
- const session = new Session(context, limits, comparisonBlockBytes);
16
- let silent = false, exitCode = 0;
17
- let failure;
211
+ if (capabilities.stat === false)
212
+ return false;
213
+ }
214
+ const target = await context.fs.stat(paths[0], options);
215
+ const empty = await context.fs.stat(paths[1], options);
216
+ return await compareObservedEntries(context.fs, paths[0], target, context.fs, paths[1], empty, options) === "same";
217
+ }
218
+ catch (error) {
219
+ context.signal.throwIfAborted();
220
+ if (error instanceof FsError)
221
+ return false;
222
+ throw error;
223
+ }
224
+ }
225
+ class Cursor {
226
+ name;
227
+ context;
228
+ budget;
229
+ signal;
230
+ chargeChunk;
231
+ blockBytes;
232
+ limits;
233
+ legacy;
234
+ bytes = new Uint8Array();
235
+ offset = 0;
236
+ size = Infinity;
237
+ stat;
238
+ position = 0n;
239
+ skip = 0;
240
+ retained;
241
+ requestBytes = defaultBlockBytes;
242
+ reader;
243
+ iterator;
244
+ source;
245
+ finished = false;
246
+ retirement;
247
+ handle;
248
+ acquisition;
249
+ handleClosing;
250
+ operations = new Set();
251
+ constructor(name, context, budget, signal, chargeChunk, blockBytes, limits, legacy) {
252
+ this.name = name;
253
+ this.context = context;
254
+ this.budget = budget;
255
+ this.signal = signal;
256
+ this.chargeChunk = chargeChunk;
257
+ this.blockBytes = blockBytes;
258
+ this.limits = limits;
259
+ this.legacy = legacy;
260
+ }
261
+ async open(limit, skip) {
262
+ this.signal.throwIfAborted();
263
+ const numericSkip = skip > BigInt(Number.MAX_SAFE_INTEGER) ? Number.MAX_SAFE_INTEGER : Number(skip);
264
+ this.skip = numericSkip;
265
+ if (this.name === "-") {
266
+ const input = this.context.stdinInput;
267
+ this.stat = input?.stat;
268
+ this.position = BigInt(input?.position ?? 0);
269
+ if (this.stat?.type === "file" && Number.isSafeInteger(this.stat.size) && this.stat.size >= 0) {
270
+ const remaining = BigInt(this.stat.size) - this.position - skip;
271
+ this.size = remaining > 0n ? Number(remaining) : 0;
272
+ }
273
+ this.source = input ? () => ({ [Symbol.asyncIterator]: () => ({ next: () => input.read(this.requestBytes, this.signal) }) }) : () => this.context.stdin;
274
+ }
275
+ else {
276
+ let name;
18
277
  try {
19
- const args = parseArguments(context.args, context.env.POSIXLY_CORRECT !== undefined);
20
- silent = args.mode === "silent";
21
- if (args.information) {
22
- await session.output(args.information === "version" ? "cmp (virtual-bash, GNU diffutils 3.12 profile)\n"
23
- : `Usage: cmp [OPTION]... FILE1 [FILE2 [SKIP1 [SKIP2]]]
24
- Compare two files byte by byte.
25
-
26
- The optional SKIP1 and SKIP2 specify the number of bytes to skip
27
- at the beginning of each file (zero by default).
28
-
29
- Mandatory arguments to long options are mandatory for short options too.
30
- -b, --print-bytes print differing bytes
31
- -i, --ignore-initial=SKIP skip first SKIP bytes of both inputs
32
- -i, --ignore-initial=SKIP1:SKIP2 skip first SKIP1 bytes of FILE1 and
33
- first SKIP2 bytes of FILE2
34
- -l, --verbose output byte numbers and differing byte values
35
- -n, --bytes=LIMIT compare at most LIMIT bytes
36
- -s, --quiet, --silent suppress all normal output
37
- --help display this help and exit
38
- -v, --version output version information and exit
39
-
40
- SKIP values may be followed by the following multiplicative suffixes:
41
- kB 1000, K 1024, MB 1,000,000, M 1,048,576,
42
- GB 1,000,000,000, G 1,073,741,824, and so on for T, P, E, Z, Y.
43
-
44
- If a FILE is '-' or missing, read standard input.
45
- Exit status is 0 if inputs are the same, 1 if different, 2 if trouble.
46
-
47
- Report bugs to: bug-diffutils@gnu.org
48
- GNU diffutils home page: <https://www.gnu.org/software/diffutils/>
49
- General help using GNU software: <https://www.gnu.org/gethelp/>
50
- `);
51
- }
52
- else
53
- exitCode = await compare(session, args);
278
+ name = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(Uint8Array.from(this.name, unit => unit.charCodeAt(0)));
279
+ }
280
+ catch {
281
+ throw new FsError("ENOENT");
282
+ }
283
+ const path = pathOf(this.context, name);
284
+ const capabilities = await this.context.fs.capabilitiesFor?.(path, { signal: this.signal }) ?? this.context.fs.capabilities;
285
+ this.signal.throwIfAborted();
286
+ assertCommandRequirements({ ...this.context, signal: this.signal }, inputRequirements, ["file"], capabilities);
287
+ let stat;
288
+ if (capabilities.stat !== false) {
289
+ try {
290
+ stat = await observe(() => this.context.fs.stat(path, { signal: this.signal }), this.signal);
291
+ }
292
+ catch (error) {
293
+ this.signal.throwIfAborted();
294
+ if (!isFsError(error, "ENOTSUP"))
295
+ throw error;
296
+ }
297
+ }
298
+ this.signal.throwIfAborted();
299
+ if (stat?.type !== "directory" && capabilities.retainedRead === true && this.context.fs.openReadFile) {
300
+ this.acquisition = this.context.fs.openReadFile(path, { signal: this.signal }).then(handle => { this.handle = handle; return handle; });
301
+ try {
302
+ const handle = await this.acquisition;
303
+ this.signal.throwIfAborted();
304
+ stat = await this.work(() => handle.stat({ signal: this.signal }));
305
+ }
306
+ catch (error) {
307
+ this.signal.throwIfAborted();
308
+ if (this.handle || !isFsError(error, "ENOTSUP"))
309
+ throw error;
310
+ }
311
+ }
312
+ if (!this.handle && capabilities.access !== false) {
313
+ try {
314
+ await observe(() => this.context.fs.access(path, 4, { signal: this.signal }), this.signal);
315
+ }
316
+ catch (error) {
317
+ this.signal.throwIfAborted();
318
+ if (!isFsError(error, "ENOTSUP"))
319
+ throw error;
320
+ }
321
+ }
322
+ this.signal.throwIfAborted();
323
+ if (!stat && limit === 0)
324
+ throw new FsError("ENOTSUP", { path, message: "zero-byte comparison requires metadata admission" });
325
+ this.stat = stat;
326
+ this.position = skip;
327
+ if (stat?.type === "file" && Number.isSafeInteger(stat.size) && stat.size >= 0) {
328
+ const remaining = BigInt(stat.size) - skip;
329
+ this.size = remaining > 0n ? Number(remaining) : 0;
330
+ }
331
+ if (limit === 0 && (this.handle || Number.isFinite(this.size) || this.skip === 0)) {
332
+ this.skip = 0;
333
+ return;
334
+ }
335
+ if (!this.legacy && stat?.type === "file" && skip > BigInt(Number.MAX_SAFE_INTEGER)) {
336
+ this.skip = 0;
337
+ this.source = () => ({ async *[Symbol.asyncIterator]() { } });
338
+ }
339
+ else if (this.handle) {
340
+ const handle = this.handle;
341
+ let position = Number.isFinite(this.size) ? Math.min(numericSkip, stat.size) : numericSkip;
342
+ this.skip = 0;
343
+ this.source = () => ({ [Symbol.asyncIterator]: () => ({ next: async () => {
344
+ const bytes = await this.work(() => handle.read(position, this.requestBytes, { signal: this.signal }));
345
+ if (!(bytes instanceof Uint8Array) || bytes.length > this.requestBytes)
346
+ throw new FsError("EIO", { path, message: "invalid retained read size" });
347
+ position += bytes.length;
348
+ return bytes.length ? { done: false, value: bytes } : { done: true, value: undefined };
349
+ } }) });
350
+ }
351
+ else if (this.context.fs.readStream && capabilities.streamingRead !== false) {
352
+ const start = Number.isFinite(this.size) ? Math.min(numericSkip, stat.size) : 0;
353
+ if (Number.isFinite(this.size))
354
+ this.skip = 0;
355
+ this.source = () => this.context.fs.readStream(path, { signal: this.signal, chunkSize: Math.min(this.blockBytes, this.limits.maxChunkBytes, limit || this.skip),
356
+ ...(start ? { start } : {}),
357
+ ...(limit === Infinity || this.skip ? {} : { endExclusive: Math.min(Number.MAX_SAFE_INTEGER, start + limit) }) });
358
+ }
359
+ else {
360
+ if (capabilities.read === false)
361
+ throw new FsError("ENOTSUP", { syscall: "readFile", path });
362
+ const context = this.context;
363
+ const signal = this.signal;
364
+ const maximum = this.limits.maxFallbackBytes;
365
+ if (!this.legacy && (!stat || !Number.isSafeInteger(stat.size) || stat.size < 0 || stat.size > maximum)) {
366
+ throw new InputError(this.name + ": bounded comparison requires readStream or a file within maxFallbackBytes", false);
367
+ }
368
+ const chunkBytes = this.limits.maxChunkBytes;
369
+ this.source = () => ({ async *[Symbol.asyncIterator]() {
370
+ const bytes = await context.fs.readFile(path, { signal, ...(maximum === Infinity ? {} : { maxBytes: maximum }) });
371
+ if (bytes.byteLength > maximum)
372
+ throw new FsError("EFBIG", { message: "cmp fallback byte limit exceeded" });
373
+ for (let offset = 0; offset < bytes.length; offset += chunkBytes)
374
+ yield bytes.subarray(offset, offset + chunkBytes);
375
+ } });
376
+ }
377
+ }
378
+ }
379
+ async prepare() {
380
+ const input = this.name === "-" ? this.context.stdinInput : undefined;
381
+ if (input?.seek) {
382
+ this.signal.throwIfAborted();
383
+ const targetPosition = Math.min(Number.MAX_SAFE_INTEGER, input.position + this.skip);
384
+ this.position = BigInt(input.position) + BigInt(this.skip);
385
+ if (this.skip) {
386
+ try {
387
+ await input.seek(targetPosition, this.signal);
388
+ }
389
+ catch (error) {
390
+ throw inputError(error, this.name);
391
+ }
392
+ }
393
+ this.signal.throwIfAborted();
394
+ this.skip = 0;
395
+ if (this.stat?.type === "file") {
396
+ const remaining = BigInt(this.stat.size) - this.position;
397
+ this.size = remaining > 0n ? Number(remaining) : 0;
398
+ }
399
+ }
400
+ }
401
+ async fill(remaining) {
402
+ this.signal.throwIfAborted();
403
+ if (remaining === 0 && this.skip === 0)
404
+ return false;
405
+ if (!this.reader) {
406
+ try {
407
+ this.iterator = this.source()[Symbol.asyncIterator]();
54
408
  }
55
409
  catch (error) {
56
- failure = { reason: error };
410
+ throw inputError(error, this.name);
57
411
  }
412
+ this.reader = this.budget.read({ [Symbol.asyncIterator]: () => ({
413
+ next: async () => {
414
+ let result;
415
+ try {
416
+ result = await this.iterator.next();
417
+ }
418
+ catch (error) {
419
+ throw inputError(error, this.name);
420
+ }
421
+ if (result.done)
422
+ this.finished = true;
423
+ return result;
424
+ },
425
+ return: async () => { await this.retire(); return { done: true, value: undefined }; },
426
+ }) }, this.signal)[Symbol.asyncIterator]();
427
+ }
428
+ while (this.offset === this.bytes.length) {
429
+ this.signal.throwIfAborted();
430
+ if (remaining === 0 && this.skip === 0)
431
+ return false;
432
+ this.requestBytes = Math.min(this.blockBytes, this.limits.maxChunkBytes, this.skip || remaining);
433
+ const item = await this.reader.next();
434
+ if (item.done)
435
+ return false;
436
+ await this.chargeChunk();
437
+ if (!this.legacy && item.value.byteLength >= 65536)
438
+ await yieldTurn(this.signal);
439
+ if (item.value.byteLength > this.limits.maxChunkBytes)
440
+ throw new FsError("EFBIG", { message: "cmp chunk byte limit exceeded" });
441
+ this.bytes = new Uint8Array(item.value);
442
+ this.offset = Math.min(this.skip, this.bytes.length);
443
+ this.skip -= this.offset;
444
+ }
445
+ return true;
446
+ }
447
+ async block(count) {
448
+ if (this.retained) {
449
+ this.bytes = this.retained.bytes;
450
+ this.offset = this.retained.offset;
451
+ }
452
+ const bytes = new Uint8Array(count);
453
+ let size = 0;
454
+ while (size < count && await this.fill(count - size)) {
455
+ const length = Math.min(count - size, this.bytes.length - this.offset);
456
+ bytes.set(this.bytes.subarray(this.offset, this.offset + length), size);
457
+ this.offset += length;
458
+ size += length;
459
+ }
460
+ this.retained = { bytes: this.bytes, offset: this.offset };
461
+ this.bytes = bytes.subarray(0, size);
462
+ this.offset = 0;
463
+ }
464
+ retire() {
465
+ this.retirement ??= Promise.resolve().then(async () => {
58
466
  try {
59
- await session.close(false);
467
+ if (!this.finished)
468
+ await this.iterator?.return?.();
60
469
  }
61
470
  catch (error) {
62
- failure ??= { reason: error };
471
+ throw !this.legacy && error instanceof Error ? new InputError(this.name + ": " + error.message, false) : inputError(error, this.name);
63
472
  }
64
- context.signal.throwIfAborted();
65
- session.signal.throwIfAborted();
66
- if (!failure)
67
- return { exitCode };
68
- const error = failure.reason;
69
- if (error instanceof UsageError)
70
- await session.output(`cmp: ${error.message}\ncmp: Try 'cmp --help' for more information.\n`, true);
71
- else if (error instanceof InputError) {
72
- if (!silent || !error.opening)
73
- await session.output(inputDiagnostic(error), true);
473
+ });
474
+ return this.retirement;
475
+ }
476
+ work(operation) {
477
+ this.signal.throwIfAborted();
478
+ if (this.handleClosing)
479
+ throw new FsError("EBADF", { path: this.name });
480
+ const pending = Promise.resolve().then(() => {
481
+ this.signal.throwIfAborted();
482
+ return operation();
483
+ });
484
+ this.operations.add(pending);
485
+ void pending.then(() => { this.operations.delete(pending); }, () => { this.operations.delete(pending); });
486
+ return pending;
487
+ }
488
+ async close() {
489
+ this.handleClosing ??= Promise.resolve().then(async () => {
490
+ await this.acquisition?.catch(() => undefined);
491
+ await Promise.allSettled(this.operations);
492
+ try {
493
+ await this.handle?.close();
74
494
  }
75
- else
76
- await session.output(`cmp: ${errorText(error)}\n`, true);
77
- return { exitCode: 2 };
78
- },
495
+ catch (error) {
496
+ throw inputError(error, this.name);
497
+ }
498
+ });
499
+ const results = await Promise.allSettled([this.retire(), this.reader?.return?.(), this.handleClosing]);
500
+ this.bytes = new Uint8Array();
501
+ for (const result of results)
502
+ if (result.status === "rejected")
503
+ throw result.reason;
504
+ }
505
+ }
506
+ async function compare(context, parsed, blockBytes, limits, legacy, preferredBlock) {
507
+ const { names, skips, silent, verbose, printBytes } = parsed;
508
+ const limit = parsed.limit >= maxCount ? Infinity : parsed.limit > BigInt(Number.MAX_SAFE_INTEGER) ? Number.MAX_SAFE_INTEGER : Number(parsed.limit);
509
+ const controller = new AbortController();
510
+ const signal = AbortSignal.any([context.signal, controller.signal]);
511
+ const budget = new ByteInputBudget(Infinity);
512
+ let chunks = 0;
513
+ const chargeChunk = async () => {
514
+ if (++chunks % 64 === 0)
515
+ await yieldTurn(signal);
516
+ };
517
+ const cursors = names.map(name => new Cursor(name, context, budget, signal, chargeChunk, blockBytes, limits, legacy));
518
+ let closing;
519
+ const cleanupErrors = [];
520
+ const close = () => {
521
+ if (!closing) {
522
+ controller.abort(legacy ? new FsError("EPIPE", { message: "cmp input closed" }) : new InvocationClosed("cmp invocation closed"));
523
+ closing = Promise.allSettled(cursors.map(cursor => cursor.close())).then(results => {
524
+ for (const result of results)
525
+ if (result.status === "rejected")
526
+ cleanupErrors.push(result.reason);
527
+ });
528
+ }
529
+ return closing;
530
+ };
531
+ context.registerCleanup?.(close);
532
+ let duplicateStdinClose = false;
533
+ const run = async () => {
534
+ assertCommandRequirements({ ...context, signal }, inputRequirements, [names.some(name => name !== "-") ? "file" : "stdin"]);
535
+ const sharedStdin = names.every(name => name === "-");
536
+ if (sharedStdin && (!legacy || skips[0] === skips[1] || !context.stdinInput?.seek && context.stdinInput?.stat?.type !== "file"))
537
+ return 0;
538
+ for (const index of [0, 1]) {
539
+ const cursor = cursors[index];
540
+ try {
541
+ await cursor.open(limit, skips[index]);
542
+ }
543
+ catch (error) {
544
+ throw inputError(error, cursor.name, true);
545
+ }
546
+ if (index === 0 && names[0] === names[1] && skips[0] === skips[1] && cursor.stat)
547
+ return 0;
548
+ }
549
+ const [left, right] = cursors;
550
+ if (!legacy && preferredBlock) {
551
+ blockBytes = left.stat?.ioBlockSize ?? 65536;
552
+ if (!Number.isSafeInteger(blockBytes) || blockBytes < 1)
553
+ throw new InputError(left.name + ": invalid preferred I/O block size", false);
554
+ }
555
+ for (const cursor of cursors)
556
+ await cursor.prepare();
557
+ if (!sharedStdin && left.position === right.position && compareCopyIdentity(left.stat, right.stat) === "same")
558
+ return 0;
559
+ if (silent && Number.isFinite(left.size) && Number.isFinite(right.size) && left.size !== right.size && Math.min(left.size, right.size) < limit)
560
+ return 1;
561
+ for (const cursor of cursors)
562
+ if (cursor.stat?.type === "directory")
563
+ throw inputError(new FsError("EISDIR"), cursor.name);
564
+ const noStdout = !silent && await discardsOutput(context);
565
+ const known = Math.min(left.size, right.size, limit);
566
+ const width = Number.isFinite(known) ? String(known).length : 19;
567
+ duplicateStdinClose = sharedStdin;
568
+ let compared = 0;
569
+ let newlines = 0;
570
+ let lastByte = -1;
571
+ let different = false;
572
+ let report = "";
573
+ const flush = async () => {
574
+ if (report) {
575
+ await output(context, report);
576
+ report = "";
577
+ }
578
+ };
579
+ for (const cursor of cursors)
580
+ await cursor.fill(0);
581
+ let blockRemaining = 0;
582
+ while (compared < limit || !legacy && blockRemaining === blockBytes) {
583
+ if (!legacy) {
584
+ const requested = Math.min(blockBytes, limit - compared);
585
+ await flush();
586
+ await left.block(requested);
587
+ await right.block(requested);
588
+ different = false;
589
+ blockRemaining = Math.min(left.bytes.length, right.bytes.length);
590
+ }
591
+ const hasLeft = (legacy ? await left.fill(limit - compared) : left.bytes.length > 0);
592
+ const hasRight = (legacy ? await right.fill(limit - compared) : right.bytes.length > 0);
593
+ if (!hasLeft || !hasRight) {
594
+ await flush();
595
+ if (hasLeft === hasRight)
596
+ return different ? 1 : 0;
597
+ if (!silent) {
598
+ const shorter = hasLeft ? right.name : left.name;
599
+ const detail = compared === 0 ? "which is empty" : `after byte ${compared}${verbose || noStdout ? "" : `, ${lastByte === 10 ? "" : "in "}line ${newlines + (lastByte === 10 ? 0 : 1)}`}`;
600
+ await output({ ...context, stdout: context.stderr }, Uint8Array.from(`cmp: EOF on ${legacy ? shorter : quote(shorter)} ${detail}\n`, unit => unit.charCodeAt(0)));
601
+ }
602
+ return 1;
603
+ }
604
+ const count = Math.min(left.bytes.length - left.offset, right.bytes.length - right.offset, limit - compared, blockBytes - compared % blockBytes);
605
+ for (let index = 0; index < count; index++) {
606
+ const leftByte = left.bytes[left.offset++];
607
+ const rightByte = right.bytes[right.offset++];
608
+ compared++;
609
+ if (leftByte !== rightByte) {
610
+ different = true;
611
+ if (silent || noStdout)
612
+ return 1;
613
+ if (!verbose) {
614
+ const locale = context.env.LC_ALL || context.env.LC_MESSAGES || context.env.LANG;
615
+ const unit = !printBytes && (!locale || locale === "C" || locale === "POSIX") ? "char" : "byte";
616
+ const detail = printBytes ? ` is ${leftByte.toString(8).padStart(3)} ${printByte(leftByte)} ${rightByte.toString(8).padStart(3)} ${printByte(rightByte)}` : "";
617
+ await output(context, Uint8Array.from(`${left.name} ${right.name} differ: ${unit} ${compared}, line ${newlines + 1}${detail}\n`, character => character.charCodeAt(0)));
618
+ return 1;
619
+ }
620
+ report += `${String(compared).padStart(width)} ${leftByte.toString(8).padStart(3)} ${printBytes ? `${printByte(leftByte).padEnd(4)} ` : ""}${rightByte.toString(8).padStart(3)}${printBytes ? ` ${printByte(rightByte)}` : ""}\n`;
621
+ if (report.length >= 16384)
622
+ await flush();
623
+ }
624
+ if (leftByte === 10)
625
+ newlines++;
626
+ lastByte = leftByte;
627
+ }
628
+ if (!legacy && left.bytes.length !== right.bytes.length) {
629
+ await flush();
630
+ if (!silent) {
631
+ const shorter = left.bytes.length < right.bytes.length ? left.name : right.name;
632
+ const detail = compared === 0 ? "which is empty" : `after byte ${compared}${verbose || noStdout ? "" : `, ${lastByte === 10 ? "" : "in "}line ${newlines + (lastByte === 10 ? 0 : 1)}`}`;
633
+ await output({ ...context, stdout: context.stderr }, `cmp: EOF on ${quote(shorter)} ${detail}\n`);
634
+ }
635
+ return 1;
636
+ }
637
+ if (!legacy && blockRemaining !== blockBytes) {
638
+ await flush();
639
+ return different ? 1 : 0;
640
+ }
641
+ if (compared % blockBytes === 0)
642
+ await yieldTurn(signal);
643
+ }
644
+ await flush();
645
+ return different ? 1 : 0;
79
646
  };
647
+ let result;
648
+ try {
649
+ result = await run();
650
+ }
651
+ catch (error) {
652
+ await close();
653
+ throw error;
654
+ }
655
+ await close();
656
+ if (cleanupErrors.length)
657
+ throw cleanupErrors[0];
658
+ if (duplicateStdinClose)
659
+ throw inputError(new FsError("EBADF"), "-");
660
+ return result;
80
661
  }
81
- export function createCmpCommands(options = {}) {
82
- return Object.freeze([createCmpCommand(options)]);
662
+ function buildCmpCommand(legacy, options = {}) {
663
+ const limits = limitsFor(options);
664
+ const blockBytes = options.comparisonBlockBytes ?? (legacy ? defaultBlockBytes : 65536);
665
+ if (!Number.isSafeInteger(blockBytes) || blockBytes < 1)
666
+ throw new RangeError("cmp comparisonBlockBytes must be positive");
667
+ const definition = { name: "cmp", runtimeIdentity: commandRuntimeIdentity, filesystemRequirements: inputRequirements, async execute(context) {
668
+ context.signal.throwIfAborted();
669
+ let silent = false;
670
+ try {
671
+ const parsed = parse(context, legacy);
672
+ if (parsed.information) {
673
+ await output(context, parsed.information === "version" ? (legacy ? "cmp (virtual-bash)\n" : "cmp (virtual-bash, GNU diffutils 3.12 profile)\n") : "Usage: cmp [OPTION]... FILE1 [FILE2 [SKIP1 [SKIP2]]]\nCompare bytes in the virtual filesystem; omitted FILE2 or '-' reads stdin.\n -s, --silent, --quiet report status only\n -l, --verbose list differing bytes in octal\n -b, -c, --print-bytes also display byte characters\n -i, --ignore-initial=SKIP[:SKIP2] skip initial bytes\n -n, --bytes=LIMIT compare at most LIMIT bytes\n --help, -v, --version show virtual-bash command information\n");
674
+ return { exitCode: 0 };
675
+ }
676
+ silent = parsed.silent;
677
+ return { exitCode: await compare(context, parsed, blockBytes, limits, legacy, options.comparisonBlockBytes === undefined) };
678
+ }
679
+ catch (error) {
680
+ context.signal.throwIfAborted();
681
+ if (error instanceof InvocationClosed)
682
+ throw error;
683
+ if (!silent || !(error instanceof InputError) || !error.suppressInSilent) {
684
+ const message = !legacy && error instanceof Error ? error.message : publicDiagnosticMessage(error, context.onInternalError);
685
+ const diagnostic = `cmp: ${message}\n${error instanceof UsageError ? "cmp: Try 'cmp --help' for more information.\n" : ""}`;
686
+ await output({ ...context, stdout: context.stderr }, error instanceof UsageError || error instanceof InputError ? Uint8Array.from(diagnostic, unit => unit.charCodeAt(0)) : diagnostic);
687
+ }
688
+ return { exitCode: 2 };
689
+ }
690
+ } };
691
+ return registerDefaultExecutor(definition, options);
692
+ }
693
+ export function evalSyncCmp(inBytes, opArgs, readFileSync) {
694
+ try {
695
+ const operands = [];
696
+ const skips = [0n, 0n];
697
+ let count = 9223372036854775807n;
698
+ let stopped = false;
699
+ const setSkip = (idx, text) => {
700
+ const p = byteCount(text, "ignore-initial");
701
+ if (p > skips[idx])
702
+ skips[idx] = p;
703
+ };
704
+ for (let i = 0; i < opArgs.length; i++) {
705
+ const a = opArgs[i];
706
+ if (stopped || a === "-" || !a.startsWith("-")) {
707
+ operands.push(a);
708
+ continue;
709
+ }
710
+ if (a === "--") {
711
+ stopped = true;
712
+ continue;
713
+ }
714
+ if (a === "-s" || a === "--silent" || a === "--quiet" || a === "-b" || a === "-c" || a === "--print-bytes" || a === "-l" || a === "--verbose")
715
+ continue;
716
+ if (a === "-i" || a === "--ignore-initial" || a.startsWith("-i") || a.startsWith("--ignore-initial=")) {
717
+ const v = (a === "-i" || a === "--ignore-initial") ? opArgs[++i] : (a.startsWith("--ignore-initial=") ? a.slice(17) : a.slice(2));
718
+ if (v === undefined || v === "")
719
+ return undefined;
720
+ const d = v.indexOf(":");
721
+ if (d < 0) {
722
+ setSkip(0, v);
723
+ if (skips[0] > skips[1])
724
+ skips[1] = skips[0];
725
+ }
726
+ else {
727
+ setSkip(0, v.slice(0, d));
728
+ setSkip(1, v.slice(d + 1));
729
+ }
730
+ continue;
731
+ }
732
+ if (a === "-n" || a === "--bytes" || a.startsWith("-n") || a.startsWith("--bytes=")) {
733
+ const v = (a === "-n" || a === "--bytes") ? opArgs[++i] : (a.startsWith("--bytes=") ? a.slice(8) : a.slice(2));
734
+ if (v === undefined || v === "")
735
+ return undefined;
736
+ const p = byteCount(v, "bytes");
737
+ if (p < count)
738
+ count = p;
739
+ continue;
740
+ }
741
+ return undefined;
742
+ }
743
+ if (operands.length < 1 || operands.length > 4)
744
+ return undefined;
745
+ if (operands[2] !== undefined)
746
+ setSkip(0, operands[2]);
747
+ if (operands[3] !== undefined)
748
+ setSkip(1, operands[3]);
749
+ const f0 = operands[0];
750
+ const f1 = operands[1] ?? "-";
751
+ if (f0 === "-" && f1 === "-") {
752
+ if (skips[0] === skips[1])
753
+ return "";
754
+ return undefined;
755
+ }
756
+ const b0 = f0 === "-" ? inBytes : readFileSync?.(f0);
757
+ const b1 = f1 === "-" ? inBytes : readFileSync?.(f1);
758
+ if (!b0 || !b1 || b0.byteLength > 16384 || b1.byteLength > 16384)
759
+ return undefined;
760
+ if (skips[0] > BigInt(b0.byteLength) || skips[1] > BigInt(b1.byteLength))
761
+ return undefined;
762
+ const s0 = Number(skips[0]);
763
+ const s1 = Number(skips[1]);
764
+ const rem0 = b0.byteLength - s0;
765
+ const rem1 = b1.byteLength - s1;
766
+ const limit = count < BigInt(Math.max(rem0, rem1)) ? Number(count) : Math.max(rem0, rem1);
767
+ const len0 = Math.min(rem0, limit);
768
+ const len1 = Math.min(rem1, limit);
769
+ if (len0 !== len1)
770
+ return undefined;
771
+ for (let i = 0; i < len0; i++) {
772
+ if (b0[s0 + i] !== b1[s1 + i])
773
+ return undefined;
774
+ }
775
+ return "";
776
+ }
777
+ catch {
778
+ return undefined;
779
+ }
83
780
  }
781
+ /** Opt-in GNU comparison-block behavior; producer fragments do not define blocks. */
782
+ export const createCmpCommand = buildCmpCommand.bind(undefined, false);
783
+ /** Existing default-shell behavior, sharing the same retained-input engine. */
784
+ export const cmpCommand = buildCmpCommand.bind(undefined, true);
785
+ export function createCmpCommands(options = {}) { return [createCmpCommand(options)]; }
84
786
  export function cmpCommands(options = {}) {
85
787
  const commands = createCmpCommands(options);
86
- const replace = options.replace ?? false;
87
- return {
88
- name: "cmp-commands",
89
- setup(host) {
90
- if (!replace)
91
- for (const command of commands) {
788
+ return { name: "cmp-commands", setup(host) {
789
+ if (!options.replace)
790
+ for (const command of commands)
92
791
  if (host.commands.has(command.name))
93
792
  throw new Error(`Command already registered: ${command.name}`);
94
- }
95
793
  for (const command of commands)
96
- host.commands.register(command, { replace });
97
- },
98
- };
794
+ host.commands.register(command, { replace: options.replace ?? false });
795
+ } };
99
796
  }