shfs 0.3.2 → 0.3.4

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.
@@ -655,7 +655,7 @@ function compileCat(cmd) {
655
655
  /**
656
656
  * cd command handler for the AST-based compiler.
657
657
  */
658
- const ROOT_DIRECTORY$4 = "/";
658
+ const ROOT_DIRECTORY$5 = "/";
659
659
  /**
660
660
  * Compile a cd command from SimpleCommandIR to StepIR.
661
661
  */
@@ -664,7 +664,7 @@ function compileCd(cmd) {
664
664
  if (positionalArgs.length > 1) throw new Error("cd accepts at most one path");
665
665
  return {
666
666
  cmd: "cd",
667
- args: { path: positionalArgs[0] ?? literal(ROOT_DIRECTORY$4) }
667
+ args: { path: positionalArgs[0] ?? literal(ROOT_DIRECTORY$5) }
668
668
  };
669
669
  }
670
670
  /**
@@ -1179,8 +1179,8 @@ function parseGrepArgs(argv) {
1179
1179
  options.excludeFiles = collectStringValues(parsed, argv, "exclude");
1180
1180
  options.excludeDir = collectStringValues(parsed, argv, "excludeDir");
1181
1181
  options.includeFiles = collectStringValues(parsed, argv, "include");
1182
- const explicitPatterns = collectExpandedValues(parsed, argv, "pattern");
1183
- const patternFiles = collectExpandedValues(parsed, argv, "file");
1182
+ const explicitPatterns = collectExpandedValues$1(parsed, argv, "pattern");
1183
+ const patternFiles = collectExpandedValues$1(parsed, argv, "file");
1184
1184
  return {
1185
1185
  diagnostics,
1186
1186
  explicitPatterns,
@@ -1436,7 +1436,7 @@ function assignImplicitPattern(explicitPatterns, patternFiles, positionalOperand
1436
1436
  function collectStringValues(parsed, argv, canonical) {
1437
1437
  return getValueOccurrences(parsed, argv, canonical).map((occurrence) => occurrence.value);
1438
1438
  }
1439
- function collectExpandedValues(parsed, argv, canonical) {
1439
+ function collectExpandedValues$1(parsed, argv, canonical) {
1440
1440
  const values = [];
1441
1441
  for (const occurrence of getValueOccurrences(parsed, argv, canonical)) {
1442
1442
  if (occurrence.source === "arg") {
@@ -1449,7 +1449,7 @@ function collectExpandedValues(parsed, argv, canonical) {
1449
1449
  return values;
1450
1450
  }
1451
1451
  function getValueOccurrences(parsed, argv, canonical) {
1452
- const values = normalizeValueList(parsed.flags[canonical]);
1452
+ const values = normalizeValueList$1(parsed.flags[canonical]);
1453
1453
  const valueIndices = parsed.consumedValueIndices[canonical] ?? [];
1454
1454
  const valueSources = parsed.consumedValueSources[canonical] ?? [];
1455
1455
  const orders = parsed.flagOccurrenceOrder[canonical] ?? [];
@@ -1478,7 +1478,7 @@ function getValueOccurrences(parsed, argv, canonical) {
1478
1478
  occurrences.sort((a, b) => a.order - b.order);
1479
1479
  return occurrences;
1480
1480
  }
1481
- function normalizeValueList(value) {
1481
+ function normalizeValueList$1(value) {
1482
1482
  if (typeof value === "string") return [value];
1483
1483
  if (Array.isArray(value)) return value;
1484
1484
  return [];
@@ -1756,6 +1756,229 @@ function compileSet(cmd) {
1756
1756
  }
1757
1757
  };
1758
1758
  }
1759
+ const COMMAND = "sort";
1760
+ const CHECK_OPTION = "c";
1761
+ const QUIET_CHECK_OPTION = "C";
1762
+ const FIELD_SEPARATOR_OPTION = "t";
1763
+ const KEY_OPTION = "k";
1764
+ const NUMERIC_OPTION = "n";
1765
+ const UNIQUE_OPTION = "u";
1766
+ const DEFAULT_CHECK_MODE = "none";
1767
+ function compileSort(command) {
1768
+ return {
1769
+ cmd: COMMAND,
1770
+ args: parseSortArgs(command.args)
1771
+ };
1772
+ }
1773
+ function createDefaultSortArgs() {
1774
+ return {
1775
+ checkMode: DEFAULT_CHECK_MODE,
1776
+ diagnostics: [],
1777
+ fieldSeparator: null,
1778
+ files: [],
1779
+ keys: [],
1780
+ numeric: false,
1781
+ unique: false
1782
+ };
1783
+ }
1784
+ function parseSortArgs(argv) {
1785
+ const args = createDefaultSortArgs();
1786
+ for (let index = 0; index < argv.length; index += 1) {
1787
+ const word = argv[index];
1788
+ if (!word) continue;
1789
+ const token = expandedWordToString(word);
1790
+ if (token === "--") {
1791
+ args.files.push(...argv.slice(index + 1));
1792
+ break;
1793
+ }
1794
+ if (!isOptionToken(token)) {
1795
+ args.files.push(word);
1796
+ continue;
1797
+ }
1798
+ if (token === `-${KEY_OPTION}`) {
1799
+ const value = argv[index + 1];
1800
+ addSeparatedKey(args, value, token, index);
1801
+ index += 1;
1802
+ continue;
1803
+ }
1804
+ if (token.startsWith(`-${KEY_OPTION}`)) {
1805
+ addKey(args, token.slice(2), token, index);
1806
+ continue;
1807
+ }
1808
+ if (token === `-${FIELD_SEPARATOR_OPTION}`) {
1809
+ const value = argv[index + 1];
1810
+ addSeparatedFieldSeparator(args, value, token, index);
1811
+ index += 1;
1812
+ continue;
1813
+ }
1814
+ if (token.startsWith(`-${FIELD_SEPARATOR_OPTION}`)) {
1815
+ addFieldSeparator(args, token.slice(2), token, index);
1816
+ continue;
1817
+ }
1818
+ applyShortOptions(args, token, index);
1819
+ }
1820
+ return args;
1821
+ }
1822
+ function isOptionToken(token) {
1823
+ return token.startsWith("-") && token !== "-";
1824
+ }
1825
+ function addSeparatedKey(args, value, token, tokenIndex) {
1826
+ if (!value) {
1827
+ addDiagnostic(args, "missing-key", "option requires an argument", token, tokenIndex);
1828
+ return;
1829
+ }
1830
+ addKey(args, expandedWordToString(value), token, tokenIndex);
1831
+ }
1832
+ function addKey(args, raw, token, tokenIndex) {
1833
+ const parsed = parseSortKey(raw, token, tokenIndex, args.diagnostics);
1834
+ if (parsed) args.keys.push(parsed);
1835
+ }
1836
+ function addSeparatedFieldSeparator(args, value, token, tokenIndex) {
1837
+ if (!value) {
1838
+ addDiagnostic(args, "missing-separator", "option requires an argument", token, tokenIndex);
1839
+ return;
1840
+ }
1841
+ addFieldSeparator(args, expandedWordToString(value), token, tokenIndex);
1842
+ }
1843
+ function addFieldSeparator(args, raw, token, tokenIndex) {
1844
+ const characters = Array.from(raw);
1845
+ const separator = characters.at(0);
1846
+ if (separator === void 0) {
1847
+ addDiagnostic(args, "empty-separator", "empty tab", token, tokenIndex);
1848
+ return;
1849
+ }
1850
+ if (characters.length > 1) {
1851
+ addDiagnostic(args, "multi-character-separator", `multi-character tab '${raw}'`, token, tokenIndex);
1852
+ return;
1853
+ }
1854
+ args.fieldSeparator = separator;
1855
+ }
1856
+ function applyShortOptions(args, token, tokenIndex) {
1857
+ const options = token.slice(1);
1858
+ for (const option of options) switch (option) {
1859
+ case CHECK_OPTION:
1860
+ applyCheckMode(args, "diagnose-first", token, tokenIndex);
1861
+ break;
1862
+ case QUIET_CHECK_OPTION:
1863
+ applyCheckMode(args, "quiet", token, tokenIndex);
1864
+ break;
1865
+ case NUMERIC_OPTION:
1866
+ args.numeric = true;
1867
+ break;
1868
+ case UNIQUE_OPTION:
1869
+ args.unique = true;
1870
+ break;
1871
+ default: addDiagnostic(args, "unknown-option", `invalid option -- '${option}'`, token, tokenIndex);
1872
+ }
1873
+ }
1874
+ function applyCheckMode(args, checkMode, token, tokenIndex) {
1875
+ if (args.checkMode !== "none" && args.checkMode !== checkMode) addDiagnostic(args, "incompatible-options", "options '-cC' are incompatible", token, tokenIndex);
1876
+ args.checkMode = checkMode;
1877
+ }
1878
+ function parseSortKey(raw, token, tokenIndex, diagnostics) {
1879
+ const commaIndex = raw.indexOf(",");
1880
+ const start = parseKeyPosition({
1881
+ diagnostics,
1882
+ partEnd: commaIndex === -1 ? raw.length : commaIndex,
1883
+ partStart: 0,
1884
+ raw,
1885
+ token,
1886
+ tokenIndex,
1887
+ type: "start"
1888
+ });
1889
+ const end = commaIndex === -1 ? null : parseKeyPosition({
1890
+ diagnostics,
1891
+ partEnd: raw.length,
1892
+ partStart: commaIndex + 1,
1893
+ raw,
1894
+ token,
1895
+ tokenIndex,
1896
+ type: "end"
1897
+ });
1898
+ if (!start || commaIndex !== -1 && !end) return null;
1899
+ return {
1900
+ end: end?.position ?? null,
1901
+ options: { numeric: start.numeric || (end?.numeric ?? false) },
1902
+ raw,
1903
+ start: start.position
1904
+ };
1905
+ }
1906
+ function parseKeyPosition(options) {
1907
+ const { diagnostics, partEnd, partStart, raw, token, tokenIndex, type } = options;
1908
+ const fieldDigits = readDigits(raw, partStart, partEnd);
1909
+ if (fieldDigits.value === "") {
1910
+ addInvalidNumberDiagnostic(diagnostics, raw, partStart, token, tokenIndex);
1911
+ return null;
1912
+ }
1913
+ const field = Number(fieldDigits.value);
1914
+ if (field === 0) {
1915
+ diagnostics.push(createSortDiagnostic("invalid-key", `invalid field specification '${raw}'`, token, tokenIndex));
1916
+ return null;
1917
+ }
1918
+ let cursor = fieldDigits.nextIndex;
1919
+ let character = null;
1920
+ if (raw.at(cursor) === "." && cursor < partEnd) {
1921
+ const characterDigits = readDigits(raw, cursor + 1, partEnd);
1922
+ if (characterDigits.value === "") {
1923
+ addCharacterNumberDiagnostic(diagnostics, raw, cursor + 1, token, tokenIndex);
1924
+ return null;
1925
+ }
1926
+ character = Number(characterDigits.value);
1927
+ if (character === 0 && type === "start") {
1928
+ diagnostics.push(createSortDiagnostic("invalid-key", "character offset is zero", token, tokenIndex), createSortDiagnostic("invalid-key", `invalid field specification '${raw}'`, token, tokenIndex));
1929
+ return null;
1930
+ }
1931
+ cursor = characterDigits.nextIndex;
1932
+ }
1933
+ const ordering = readOrderingOptions(raw, cursor, partEnd, token, tokenIndex, diagnostics);
1934
+ if (!ordering) return null;
1935
+ return {
1936
+ numeric: ordering.numeric,
1937
+ position: {
1938
+ character,
1939
+ field
1940
+ }
1941
+ };
1942
+ }
1943
+ function readDigits(text, start, end) {
1944
+ let cursor = start;
1945
+ while (cursor < end) {
1946
+ const character = text.at(cursor);
1947
+ if (character === void 0 || character < "0" || character > "9") break;
1948
+ cursor += 1;
1949
+ }
1950
+ return {
1951
+ nextIndex: cursor,
1952
+ value: text.slice(start, cursor)
1953
+ };
1954
+ }
1955
+ function readOrderingOptions(raw, start, end, token, tokenIndex, diagnostics) {
1956
+ let numeric = false;
1957
+ for (let cursor = start; cursor < end; cursor += 1) {
1958
+ if (raw.at(cursor) === NUMERIC_OPTION) {
1959
+ numeric = true;
1960
+ continue;
1961
+ }
1962
+ diagnostics.push(createSortDiagnostic("invalid-key", `invalid count at start of '${raw.slice(cursor)}'`, token, tokenIndex));
1963
+ return null;
1964
+ }
1965
+ return { numeric };
1966
+ }
1967
+ function addInvalidNumberDiagnostic(diagnostics, raw, position, token, tokenIndex) {
1968
+ diagnostics.push(createSortDiagnostic("invalid-key", "invalid number after ','", token, tokenIndex), createSortDiagnostic("invalid-key", `invalid count at start of '${raw.slice(position)}'`, token, tokenIndex));
1969
+ }
1970
+ function addCharacterNumberDiagnostic(diagnostics, raw, position, token, tokenIndex) {
1971
+ diagnostics.push(createSortDiagnostic("invalid-key", "invalid number after '.'", token, tokenIndex), createSortDiagnostic("invalid-key", `invalid count at start of '${raw.slice(position)}'`, token, tokenIndex));
1972
+ }
1973
+ function addDiagnostic(args, code, message, token, tokenIndex) {
1974
+ args.diagnostics.push(createSortDiagnostic(code, message, token, tokenIndex));
1975
+ }
1976
+ function createSortDiagnostic(code, message, token, tokenIndex) {
1977
+ return createCommandDiagnostic(COMMAND, code, message, {
1978
+ token,
1979
+ tokenIndex
1980
+ });
1981
+ }
1759
1982
  function compileString(cmd) {
1760
1983
  const [subcommand, ...operands] = cmd.args;
1761
1984
  if (!subcommand) throw new Error("string requires a subcommand");
@@ -1861,6 +2084,246 @@ function compileTouch(cmd) {
1861
2084
  }
1862
2085
  };
1863
2086
  }
2087
+ /**
2088
+ * tree command handler for the AST-based compiler.
2089
+ */
2090
+ const parseTreeArgs = createWordParser({
2091
+ ascii: {
2092
+ short: "A",
2093
+ takesValue: false
2094
+ },
2095
+ classify: {
2096
+ short: "F",
2097
+ takesValue: false
2098
+ },
2099
+ dirsOnly: {
2100
+ short: "d",
2101
+ takesValue: false
2102
+ },
2103
+ excludePattern: {
2104
+ short: "I",
2105
+ takesValue: true,
2106
+ multiple: true,
2107
+ allowFlagLikeValue: true
2108
+ },
2109
+ fullPath: {
2110
+ short: "f",
2111
+ takesValue: false
2112
+ },
2113
+ includePattern: {
2114
+ short: "P",
2115
+ takesValue: true,
2116
+ multiple: true,
2117
+ allowFlagLikeValue: true
2118
+ },
2119
+ matchDirs: {
2120
+ long: "matchdirs",
2121
+ takesValue: false
2122
+ },
2123
+ maxDepth: {
2124
+ short: "L",
2125
+ takesValue: true
2126
+ },
2127
+ noReport: {
2128
+ long: "noreport",
2129
+ takesValue: false
2130
+ },
2131
+ prune: {
2132
+ long: "prune",
2133
+ takesValue: false
2134
+ },
2135
+ showAll: {
2136
+ short: "a",
2137
+ takesValue: false
2138
+ }
2139
+ }, expandedWordToString);
2140
+ function compileTree(command) {
2141
+ const parsed = parseTreeArgs(command.args);
2142
+ return {
2143
+ cmd: "tree",
2144
+ args: {
2145
+ ascii: parsed.flags.ascii === true,
2146
+ classify: parsed.flags.classify === true,
2147
+ dirsOnly: parsed.flags.dirsOnly === true,
2148
+ excludePatterns: collectExpandedValues(parsed, command.args, "excludePattern"),
2149
+ fullPath: parsed.flags.fullPath === true,
2150
+ includePatterns: collectExpandedValues(parsed, command.args, "includePattern"),
2151
+ matchDirs: parsed.flags.matchDirs === true,
2152
+ maxDepth: parseMaxDepth(parsed.flags.maxDepth),
2153
+ noReport: parsed.flags.noReport === true,
2154
+ paths: parsed.positionalWords.length === 0 ? [literal(".")] : parsed.positionalWords,
2155
+ prune: parsed.flags.prune === true,
2156
+ showAll: parsed.flags.showAll === true
2157
+ }
2158
+ };
2159
+ }
2160
+ function parseMaxDepth(value) {
2161
+ if (typeof value !== "string") return null;
2162
+ const maxDepth = Number.parseInt(value, 10);
2163
+ if (!Number.isFinite(maxDepth) || maxDepth < 0) throw new Error(`tree: invalid level, '${value}'`);
2164
+ return maxDepth;
2165
+ }
2166
+ function collectExpandedValues(parsed, argv, canonical) {
2167
+ const values = [];
2168
+ const rawValues = normalizeValueList(parsed.flags[canonical]);
2169
+ const valueIndices = parsed.consumedValueIndices[canonical] ?? [];
2170
+ const valueSources = parsed.consumedValueSources[canonical] ?? [];
2171
+ const count = Math.min(rawValues.length, valueIndices.length);
2172
+ for (let index = 0; index < count; index += 1) {
2173
+ const rawValue = rawValues[index];
2174
+ const valueIndex = valueIndices[index];
2175
+ const source = valueSources[index];
2176
+ if (rawValue === void 0 || valueIndex === void 0) continue;
2177
+ if (source === "arg") {
2178
+ values.push(argv[valueIndex] ?? literal(rawValue));
2179
+ continue;
2180
+ }
2181
+ values.push(literal(rawValue));
2182
+ }
2183
+ return values;
2184
+ }
2185
+ function normalizeValueList(value) {
2186
+ if (typeof value === "string") return [value];
2187
+ if (Array.isArray(value)) return value.filter((item) => typeof item === "string");
2188
+ return [];
2189
+ }
2190
+ const DEFAULT_TOTAL_MODE = "auto";
2191
+ function compileWc(command) {
2192
+ return {
2193
+ cmd: "wc",
2194
+ args: parseWcArgs(command.args)
2195
+ };
2196
+ }
2197
+ function parseWcArgs(argv) {
2198
+ const args = {
2199
+ bytes: false,
2200
+ chars: false,
2201
+ files: [],
2202
+ files0From: null,
2203
+ lines: false,
2204
+ maxLineLength: false,
2205
+ total: DEFAULT_TOTAL_MODE,
2206
+ words: false
2207
+ };
2208
+ for (let index = 0; index < argv.length; index++) {
2209
+ const word = argv[index];
2210
+ if (!word) continue;
2211
+ const token = expandedWordToString(word);
2212
+ if (token === "--") {
2213
+ args.files.push(...argv.slice(index + 1));
2214
+ break;
2215
+ }
2216
+ if (!token.startsWith("-") || token === "-") {
2217
+ args.files.push(word);
2218
+ continue;
2219
+ }
2220
+ const parsed = parseLongOption(argv, index, token, args);
2221
+ if (parsed.matched) {
2222
+ index = parsed.nextIndex - 1;
2223
+ continue;
2224
+ }
2225
+ parseShortOptions(token, args);
2226
+ }
2227
+ return args;
2228
+ }
2229
+ function parseLongOption(argv, index, token, args) {
2230
+ if (token === "--bytes") {
2231
+ args.bytes = true;
2232
+ return {
2233
+ matched: true,
2234
+ nextIndex: index + 1
2235
+ };
2236
+ }
2237
+ if (token === "--chars") {
2238
+ args.chars = true;
2239
+ return {
2240
+ matched: true,
2241
+ nextIndex: index + 1
2242
+ };
2243
+ }
2244
+ if (token === "--lines") {
2245
+ args.lines = true;
2246
+ return {
2247
+ matched: true,
2248
+ nextIndex: index + 1
2249
+ };
2250
+ }
2251
+ if (token === "--words") {
2252
+ args.words = true;
2253
+ return {
2254
+ matched: true,
2255
+ nextIndex: index + 1
2256
+ };
2257
+ }
2258
+ if (token === "--max-line-length") {
2259
+ args.maxLineLength = true;
2260
+ return {
2261
+ matched: true,
2262
+ nextIndex: index + 1
2263
+ };
2264
+ }
2265
+ if (token.startsWith("--files0-from=")) {
2266
+ args.files0From = {
2267
+ kind: "literal",
2268
+ value: token.slice(14)
2269
+ };
2270
+ return {
2271
+ matched: true,
2272
+ nextIndex: index + 1
2273
+ };
2274
+ }
2275
+ if (token === "--files0-from") {
2276
+ const value = argv[index + 1];
2277
+ if (!value) throw new Error("wc: option --files0-from requires an argument");
2278
+ args.files0From = value;
2279
+ return {
2280
+ matched: true,
2281
+ nextIndex: index + 2
2282
+ };
2283
+ }
2284
+ if (token.startsWith("--total=")) {
2285
+ args.total = parseTotalMode(token.slice(8));
2286
+ return {
2287
+ matched: true,
2288
+ nextIndex: index + 1
2289
+ };
2290
+ }
2291
+ if (token === "--total") {
2292
+ args.total = "invalid";
2293
+ return {
2294
+ matched: true,
2295
+ nextIndex: index + 1
2296
+ };
2297
+ }
2298
+ return {
2299
+ matched: false,
2300
+ nextIndex: index
2301
+ };
2302
+ }
2303
+ function parseShortOptions(token, args) {
2304
+ for (const option of token.slice(1)) switch (option) {
2305
+ case "c":
2306
+ args.bytes = true;
2307
+ break;
2308
+ case "m":
2309
+ args.chars = true;
2310
+ break;
2311
+ case "l":
2312
+ args.lines = true;
2313
+ break;
2314
+ case "w":
2315
+ args.words = true;
2316
+ break;
2317
+ case "L":
2318
+ args.maxLineLength = true;
2319
+ break;
2320
+ default: throw new Error(`wc: unknown option -- ${option}`);
2321
+ }
2322
+ }
2323
+ function parseTotalMode(value) {
2324
+ if (value === "auto" || value === "always" || value === "never" || value === "only") return value;
2325
+ throw new Error(`wc: invalid --total value: ${value}`);
2326
+ }
1864
2327
  const DEFAULT_COMMAND = [literal("echo")];
1865
2328
  const NUL_DELIMITER = "\0";
1866
2329
  function compileXargs(command) {
@@ -2011,10 +2474,13 @@ let CommandHandler;
2011
2474
  read: compileRead,
2012
2475
  rm: compileRm,
2013
2476
  set: compileSet,
2477
+ sort: compileSort,
2014
2478
  string: compileString,
2015
2479
  tail: compileTail,
2016
2480
  test: compileTest,
2017
2481
  touch: compileTouch,
2482
+ tree: compileTree,
2483
+ wc: compileWc,
2018
2484
  xargs: compileXargs
2019
2485
  };
2020
2486
  function get(name) {
@@ -2211,7 +2677,8 @@ var ProgramCompiler = class {
2211
2677
  const pipesStdout = pipeRedirections.some((redirection) => redirection.sourceFd === 1);
2212
2678
  const pipesStderr = pipeRedirections.some((redirection) => redirection.sourceFd === 2);
2213
2679
  if (pipesStdout && pipesStderr) return "&|";
2214
- if (pipeRedirections.length === 1) return this.serializeRedirection(pipeRedirections[0]);
2680
+ const onlyPipeRedirection = pipeRedirections[0];
2681
+ if (pipeRedirections.length === 1 && onlyPipeRedirection) return this.serializeRedirection(onlyPipeRedirection);
2215
2682
  return "|";
2216
2683
  }
2217
2684
  serializeRedirection(redirection) {
@@ -4099,8 +4566,8 @@ function toErrorMessage$1(diagnostic) {
4099
4566
  //#endregion
4100
4567
  //#region src/execute/path.ts
4101
4568
  const MULTIPLE_SLASH_REGEX$2 = /\/+/g;
4102
- const ROOT_DIRECTORY$3 = "/";
4103
- const TRAILING_SLASH_REGEX$2 = /\/+$/;
4569
+ const ROOT_DIRECTORY$4 = "/";
4570
+ const TRAILING_SLASH_REGEX$3 = /\/+$/;
4104
4571
  const VARIABLE_REFERENCE_REGEX = /\$([A-Za-z_][A-Za-z0-9_]*)/g;
4105
4572
  const NO_GLOB_MATCH_MESSAGE = "no matches found";
4106
4573
  async function collectOutputRecords(result) {
@@ -4126,7 +4593,7 @@ function expandVariables(input, context) {
4126
4593
  });
4127
4594
  }
4128
4595
  function normalizeAbsolutePath(path) {
4129
- const segments = (path.startsWith(ROOT_DIRECTORY$3) ? path : `${ROOT_DIRECTORY$3}${path}`).replace(MULTIPLE_SLASH_REGEX$2, "/").split(ROOT_DIRECTORY$3);
4596
+ const segments = (path.startsWith(ROOT_DIRECTORY$4) ? path : `${ROOT_DIRECTORY$4}${path}`).replace(MULTIPLE_SLASH_REGEX$2, "/").split(ROOT_DIRECTORY$4);
4130
4597
  const normalizedSegments = [];
4131
4598
  for (const segment of segments) {
4132
4599
  if (segment === "" || segment === ".") continue;
@@ -4136,17 +4603,17 @@ function normalizeAbsolutePath(path) {
4136
4603
  }
4137
4604
  normalizedSegments.push(segment);
4138
4605
  }
4139
- const normalizedPath = `${ROOT_DIRECTORY$3}${normalizedSegments.join(ROOT_DIRECTORY$3)}`;
4140
- return normalizedPath === "" ? ROOT_DIRECTORY$3 : normalizedPath;
4606
+ const normalizedPath = `${ROOT_DIRECTORY$4}${normalizedSegments.join(ROOT_DIRECTORY$4)}`;
4607
+ return normalizedPath === "" ? ROOT_DIRECTORY$4 : normalizedPath;
4141
4608
  }
4142
4609
  function normalizeCwd(cwd) {
4143
- if (cwd === "") return ROOT_DIRECTORY$3;
4144
- const trimmed = normalizeAbsolutePath(cwd).replace(TRAILING_SLASH_REGEX$2, "");
4145
- return trimmed === "" ? ROOT_DIRECTORY$3 : trimmed;
4610
+ if (cwd === "") return ROOT_DIRECTORY$4;
4611
+ const trimmed = normalizeAbsolutePath(cwd).replace(TRAILING_SLASH_REGEX$3, "");
4612
+ return trimmed === "" ? ROOT_DIRECTORY$4 : trimmed;
4146
4613
  }
4147
4614
  function resolvePathFromCwd(cwd, path) {
4148
4615
  if (path === "") return cwd;
4149
- if (path.startsWith(ROOT_DIRECTORY$3)) return normalizeAbsolutePath(path);
4616
+ if (path.startsWith(ROOT_DIRECTORY$4)) return normalizeAbsolutePath(path);
4150
4617
  return normalizeAbsolutePath(`${cwd}/${path}`);
4151
4618
  }
4152
4619
  function resolvePathsFromCwd(cwd, paths) {
@@ -4161,7 +4628,7 @@ async function readDirectoryPaths(fs, directoryPath) {
4161
4628
  children.sort((left, right) => left.localeCompare(right));
4162
4629
  return children;
4163
4630
  }
4164
- async function walkFilesystemEntries(fs, rootDir = ROOT_DIRECTORY$3) {
4631
+ async function walkFilesystemEntries(fs, rootDir = ROOT_DIRECTORY$4) {
4165
4632
  const normalizedRoot = normalizeAbsolutePath(rootDir);
4166
4633
  if (!(await fs.stat(normalizedRoot)).isDirectory) throw new Error(`Not a directory: ${normalizedRoot}`);
4167
4634
  const entries = [];
@@ -4183,12 +4650,12 @@ async function walkFilesystemEntries(fs, rootDir = ROOT_DIRECTORY$3) {
4183
4650
  return entries;
4184
4651
  }
4185
4652
  function toRelativePathFromCwd$1(path, cwd) {
4186
- if (cwd === ROOT_DIRECTORY$3) {
4187
- if (path === ROOT_DIRECTORY$3) return null;
4188
- return path.startsWith(ROOT_DIRECTORY$3) ? path.slice(1) : path;
4653
+ if (cwd === ROOT_DIRECTORY$4) {
4654
+ if (path === ROOT_DIRECTORY$4) return null;
4655
+ return path.startsWith(ROOT_DIRECTORY$4) ? path.slice(1) : path;
4189
4656
  }
4190
4657
  if (path === cwd) return null;
4191
- const prefix = `${cwd}${ROOT_DIRECTORY$3}`;
4658
+ const prefix = `${cwd}${ROOT_DIRECTORY$4}`;
4192
4659
  if (!path.startsWith(prefix)) return null;
4193
4660
  return path.slice(prefix.length);
4194
4661
  }
@@ -4196,12 +4663,12 @@ function toGlobCandidate(entry, cwd, isAbsolutePattern, directoryOnly) {
4196
4663
  if (directoryOnly && !entry.isDirectory) return null;
4197
4664
  const basePath = isAbsolutePattern ? entry.path : toRelativePathFromCwd$1(entry.path, cwd);
4198
4665
  if (!basePath || basePath === "") return null;
4199
- if (directoryOnly) return `${basePath}${ROOT_DIRECTORY$3}`;
4666
+ if (directoryOnly) return `${basePath}${ROOT_DIRECTORY$4}`;
4200
4667
  return basePath;
4201
4668
  }
4202
4669
  async function expandGlobPattern(pattern, fs, context) {
4203
- const directoryOnly = pattern.endsWith(ROOT_DIRECTORY$3);
4204
- const isAbsolutePattern = pattern.startsWith(ROOT_DIRECTORY$3);
4670
+ const directoryOnly = pattern.endsWith(ROOT_DIRECTORY$4);
4671
+ const isAbsolutePattern = pattern.startsWith(ROOT_DIRECTORY$4);
4205
4672
  const matcher = picomatch(pattern, {
4206
4673
  bash: true,
4207
4674
  dot: false
@@ -4266,28 +4733,10 @@ async function evaluateExpandedWordPart(part, fs, context) {
4266
4733
  }
4267
4734
  //#endregion
4268
4735
  //#region src/execute/records.ts
4269
- async function* toLineStream(fs, input) {
4270
- for await (const record of input) {
4271
- if (record.kind === "line") {
4272
- yield record;
4273
- continue;
4274
- }
4275
- if (record.kind === "file") {
4276
- yield* fileRecordToLines(fs, record);
4277
- continue;
4278
- }
4279
- yield {
4280
- kind: "line",
4281
- text: JSON.stringify(record.value)
4282
- };
4283
- }
4284
- }
4285
4736
  /**
4286
- * Converts any ShellRecord stream into LineRecords by formatting each record
4287
- * as its display text. Unlike `toLineStream`, this does NOT read file contents —
4288
- * FileRecords are rendered as their path, which is the correct behavior for
4289
- * line-oriented transducers (tail, head) receiving piped input from commands
4290
- * like `find` that produce path listings.
4737
+ * Converts records into stdin-style line text. Unlike `toLineStream`, this does
4738
+ * not read FileRecord contents. FileRecords are rendered as paths, matching the
4739
+ * shell pipe boundary where downstream line-oriented commands consume text.
4291
4740
  */
4292
4741
  async function* toFormattedLineStream(input) {
4293
4742
  for await (const record of input) {
@@ -4301,16 +4750,6 @@ async function* toFormattedLineStream(input) {
4301
4750
  };
4302
4751
  }
4303
4752
  }
4304
- async function* fileRecordToLines(fs, record) {
4305
- if (await isDirectoryRecord(fs, record)) return;
4306
- let lineNum = 1;
4307
- for await (const text of fs.readLines(record.path)) yield {
4308
- kind: "line",
4309
- text,
4310
- file: record.path,
4311
- lineNum: lineNum++
4312
- };
4313
- }
4314
4753
  async function isDirectoryRecord(fs, record) {
4315
4754
  if (record.isDirectory !== void 0) return record.isDirectory;
4316
4755
  try {
@@ -4324,6 +4763,7 @@ async function isDirectoryRecord(fs, record) {
4324
4763
  const textEncoder = new TextEncoder();
4325
4764
  const textDecoder = new TextDecoder();
4326
4765
  const FD_TARGET_REGEX$1 = /^&[0-9]+$/;
4766
+ const NULL_DEVICE_PATH = "/dev/null";
4327
4767
  function getSourceFd$1(redirection) {
4328
4768
  return redirection.sourceFd ?? (redirection.kind === "input" ? 0 : 1);
4329
4769
  }
@@ -4430,6 +4870,7 @@ async function readExistingFileText(fs, path) {
4430
4870
  }
4431
4871
  }
4432
4872
  async function writeTextToFile(fs, path, content, options) {
4873
+ if (isNullDevicePath(path)) return;
4433
4874
  if (!(options.append ?? false)) {
4434
4875
  await fs.writeFile(path, textEncoder.encode(content));
4435
4876
  return;
@@ -4439,8 +4880,12 @@ async function writeTextToFile(fs, path, content, options) {
4439
4880
  await fs.writeFile(path, textEncoder.encode(`${existing}${separator}${content}`));
4440
4881
  }
4441
4882
  async function ensureNoclobberWritable(fs, path) {
4883
+ if (isNullDevicePath(path)) return true;
4442
4884
  return !await fs.exists(path);
4443
4885
  }
4886
+ function isNullDevicePath(path) {
4887
+ return path === NULL_DEVICE_PATH;
4888
+ }
4444
4889
  //#endregion
4445
4890
  //#region src/builtin/cd/cd.ts
4446
4891
  const cd = async (runtime, args) => {
@@ -4474,21 +4919,9 @@ const VARIABLE_NAME_REGEX$1 = /^[A-Za-z_][A-Za-z0-9_]*$/;
4474
4919
  async function readFirstValue(runtime) {
4475
4920
  if (!runtime.input) return null;
4476
4921
  let firstValue = null;
4477
- for await (const record of runtime.input) {
4478
- const value = await recordToText(runtime, record);
4479
- if (value !== null && firstValue === null) firstValue = value;
4480
- }
4922
+ for await (const line of runtime.stdin.lines()) if (firstValue === null) firstValue = line;
4481
4923
  return firstValue;
4482
4924
  }
4483
- async function recordToText(runtime, record) {
4484
- if (record.kind === "line") return record.text;
4485
- if (record.kind === "file") {
4486
- if (await isDirectoryRecord(runtime.fs, record)) return null;
4487
- for await (const line of runtime.fs.readLines(record.path)) return line;
4488
- return "";
4489
- }
4490
- return JSON.stringify(record.value);
4491
- }
4492
4925
  const read = (runtime, args) => {
4493
4926
  return (async function* () {
4494
4927
  const name = await evaluateExpandedWord(args.name, runtime.fs, runtime.context);
@@ -4519,14 +4952,21 @@ const set = (runtime, args) => {
4519
4952
  };
4520
4953
  //#endregion
4521
4954
  //#region src/builtin/string/string.ts
4955
+ async function collectStdinLines(runtime) {
4956
+ if (!runtime.input) return [];
4957
+ const lines = [];
4958
+ for await (const line of runtime.stdin.lines()) lines.push(line);
4959
+ return lines;
4960
+ }
4522
4961
  function replace(runtime, operands) {
4523
4962
  return (async function* () {
4524
4963
  if (operands[0]?.startsWith("-")) throw new Error(`string replace: unsupported flag: ${operands[0]}`);
4525
- if (operands.length < 3) throw new Error("string replace requires pattern replacement text");
4964
+ if (operands.length < 2) throw new Error("string replace requires pattern replacement text");
4526
4965
  const pattern = operands.at(0);
4527
4966
  const replacement = operands.at(1);
4528
- const inputs = operands.slice(2);
4529
4967
  if (pattern === void 0 || replacement === void 0) throw new Error("string replace requires pattern replacement text");
4968
+ if (operands.length === 2 && !runtime.input) throw new Error("string replace requires pattern replacement text");
4969
+ const inputs = operands.length > 2 ? operands.slice(2) : await collectStdinLines(runtime);
4530
4970
  if (inputs.length === 0) {
4531
4971
  runtime.context.status = 1;
4532
4972
  return;
@@ -4538,30 +4978,45 @@ function replace(runtime, operands) {
4538
4978
  runtime.context.status = 0;
4539
4979
  })();
4540
4980
  }
4981
+ async function parseMatchInvocation(runtime, operands) {
4982
+ let quiet = false;
4983
+ let offset = 0;
4984
+ while (operands[offset]?.startsWith("-")) {
4985
+ const flag = operands[offset];
4986
+ if (flag === "-q" && !quiet) {
4987
+ quiet = true;
4988
+ offset += 1;
4989
+ continue;
4990
+ }
4991
+ throw new Error(`string match: unsupported flag: ${flag}`);
4992
+ }
4993
+ const filtered = operands.slice(offset);
4994
+ const [pattern] = filtered;
4995
+ if (!pattern) throw new Error("string match requires pattern and value");
4996
+ if (filtered.length > 2) throw new Error("string match: unsupported arguments");
4997
+ if (filtered.length === 1 && !runtime.input) throw new Error("string match requires pattern and value");
4998
+ const values = filtered.length > 1 ? filtered.slice(1) : await collectStdinLines(runtime);
4999
+ return {
5000
+ pattern,
5001
+ quiet,
5002
+ values
5003
+ };
5004
+ }
4541
5005
  function match(runtime, operands) {
4542
5006
  return (async function* () {
4543
- let quiet = false;
4544
- let offset = 0;
4545
- while (operands[offset]?.startsWith("-")) {
4546
- const flag = operands[offset];
4547
- if (flag === "-q" && !quiet) {
4548
- quiet = true;
4549
- offset += 1;
4550
- continue;
4551
- }
4552
- throw new Error(`string match: unsupported flag: ${flag}`);
4553
- }
4554
- const filtered = operands.slice(offset);
4555
- const [pattern, value] = filtered;
4556
- if (!(pattern && value !== void 0)) throw new Error("string match requires pattern and value");
4557
- if (filtered.length > 2) throw new Error("string match: unsupported arguments");
4558
- const isMatch = picomatch(pattern, { dot: true })(value);
4559
- runtime.context.status = isMatch ? 0 : 1;
4560
- if (isMatch && !quiet) yield {
4561
- kind: "line",
4562
- text: value
4563
- };
4564
- })();
5007
+ const { pattern, quiet, values } = await parseMatchInvocation(runtime, operands);
5008
+ const matcher = picomatch(pattern, { dot: true });
5009
+ let matched = false;
5010
+ for (const value of values) {
5011
+ if (!matcher(value)) continue;
5012
+ matched = true;
5013
+ if (!quiet) yield {
5014
+ kind: "line",
5015
+ text: value
5016
+ };
5017
+ }
5018
+ runtime.context.status = matched ? 0 : 1;
5019
+ })();
4565
5020
  }
4566
5021
  const string = (runtime, args) => {
4567
5022
  return (async function* () {
@@ -4709,16 +5164,16 @@ function cat(fs, options) {
4709
5164
  }
4710
5165
  //#endregion
4711
5166
  //#region src/operator/cp/cp.ts
4712
- const TRAILING_SLASH_REGEX$1 = /\/+$/;
5167
+ const TRAILING_SLASH_REGEX$2 = /\/+$/;
4713
5168
  const MULTIPLE_SLASH_REGEX$1 = /\/+/g;
4714
5169
  function trimTrailingSlash$2(path) {
4715
5170
  if (path === "/") return path;
4716
- return path.replace(TRAILING_SLASH_REGEX$1, "");
5171
+ return path.replace(TRAILING_SLASH_REGEX$2, "");
4717
5172
  }
4718
5173
  function joinPath$1(base, suffix) {
4719
5174
  return `${trimTrailingSlash$2(base)}/${suffix}`.replace(MULTIPLE_SLASH_REGEX$1, "/");
4720
5175
  }
4721
- function basename$3(path) {
5176
+ function basename$4(path) {
4722
5177
  const normalized = trimTrailingSlash$2(path);
4723
5178
  const slashIndex = normalized.lastIndexOf("/");
4724
5179
  if (slashIndex === -1) return normalized;
@@ -4769,7 +5224,7 @@ async function copyDirectoryRecursive(fs, srcDir, destDir, force, interactive) {
4769
5224
  if (!current) continue;
4770
5225
  const childPaths = await readDirectory(current.sourcePath);
4771
5226
  for (const childPath of childPaths) {
4772
- const childName = basename$3(childPath);
5227
+ const childName = basename$4(childPath);
4773
5228
  const targetPath = joinPath$1(current.targetPath, childName);
4774
5229
  if ((await fs.stat(childPath)).isDirectory) {
4775
5230
  await ensureDirectory(targetPath);
@@ -4790,7 +5245,7 @@ function cp(fs) {
4790
5245
  if (srcs.length > 1 && !destinationIsDirectory) throw new Error("cp destination must be a directory for multiple sources");
4791
5246
  for (const src of srcs) {
4792
5247
  const srcStat = await fs.stat(src);
4793
- const targetPath = destinationIsDirectory || srcs.length > 1 ? joinPath$1(dest, basename$3(src)) : dest;
5248
+ const targetPath = destinationIsDirectory || srcs.length > 1 ? joinPath$1(dest, basename$4(src)) : dest;
4794
5249
  if (srcStat.isDirectory) {
4795
5250
  if (!recursive) throw new Error(`cp: omitting directory "${src}" (use -r)`);
4796
5251
  await copyDirectoryRecursive(fs, src, targetPath, force, interactive);
@@ -4862,12 +5317,12 @@ async function* walkEntry(fs, context, entry, args, predicateBranches, state, ha
4862
5317
  childStat = await fs.stat(childAbsolutePath);
4863
5318
  } catch {
4864
5319
  state.hadError = true;
4865
- writeDiagnosticsToStderr(context, [createRuntimeDiagnostic("find", "missing-path", "No such file or directory", { path: appendDisplayPath(entry.displayPath, basename$2(childAbsolutePath)) })]);
5320
+ writeDiagnosticsToStderr(context, [createRuntimeDiagnostic("find", "missing-path", "No such file or directory", { path: appendDisplayPath(entry.displayPath, basename$3(childAbsolutePath)) })]);
4866
5321
  continue;
4867
5322
  }
4868
5323
  yield* walkEntry(fs, context, {
4869
5324
  absolutePath: childAbsolutePath,
4870
- displayPath: appendDisplayPath(entry.displayPath, basename$2(childAbsolutePath)),
5325
+ displayPath: appendDisplayPath(entry.displayPath, basename$3(childAbsolutePath)),
4871
5326
  depth: entry.depth + 1,
4872
5327
  isDirectory: childStat.isDirectory,
4873
5328
  size: childStat.size
@@ -4971,7 +5426,7 @@ function matchesBranch(entry, entryType, branch, childPaths) {
4971
5426
  return true;
4972
5427
  }
4973
5428
  function matchesPredicate(entry, entryType, predicate, childPaths) {
4974
- if (predicate.kind === "name") return predicate.matcher(basename$2(entry.displayPath));
5429
+ if (predicate.kind === "name") return predicate.matcher(basename$3(entry.displayPath));
4975
5430
  if (predicate.kind === "path") return predicate.matcher(entry.displayPath);
4976
5431
  if (predicate.kind === "regex") return predicate.matcher(entry.displayPath);
4977
5432
  if (predicate.kind === "constant") return predicate.value;
@@ -5030,7 +5485,7 @@ function appendDisplayPath(parentPath, childName) {
5030
5485
  if (parentPath === ".") return `./${childName}`;
5031
5486
  return `${parentPath}/${childName}`;
5032
5487
  }
5033
- function basename$2(path) {
5488
+ function basename$3(path) {
5034
5489
  if (path === "/") return "/";
5035
5490
  const normalized = trimTrailingSlashes(path);
5036
5491
  const slashIndex = normalized.lastIndexOf("/");
@@ -5076,9 +5531,57 @@ function trimTrailingSlashes(path) {
5076
5531
  return path.replace(/\/+$/g, "");
5077
5532
  }
5078
5533
  //#endregion
5534
+ //#region src/execute/io.ts
5535
+ const UTF8_ENCODER$2 = new TextEncoder();
5536
+ const NEWLINE = "\n";
5537
+ var EmptyInput = class {
5538
+ async *records() {}
5539
+ async *lines() {}
5540
+ async bytes() {
5541
+ return new Uint8Array();
5542
+ }
5543
+ };
5544
+ var RecordInput = class {
5545
+ input;
5546
+ constructor(input) {
5547
+ this.input = input;
5548
+ }
5549
+ records() {
5550
+ return this.input;
5551
+ }
5552
+ async *lines() {
5553
+ for await (const record of this.input) yield formatStdoutRecord(record);
5554
+ }
5555
+ async bytes(options = {}) {
5556
+ const lines = [];
5557
+ for await (const line of this.lines()) lines.push(line);
5558
+ if (lines.length === 0) return new Uint8Array();
5559
+ const text = options.trailingNewline ? `${lines.join(NEWLINE)}${NEWLINE}` : lines.join(NEWLINE);
5560
+ return UTF8_ENCODER$2.encode(text);
5561
+ }
5562
+ };
5563
+ var BufferedShellOutput = class {
5564
+ bufferedRecords = [];
5565
+ write(record) {
5566
+ this.bufferedRecords.push(record);
5567
+ }
5568
+ writeLine(text) {
5569
+ this.write({
5570
+ kind: "line",
5571
+ text
5572
+ });
5573
+ }
5574
+ records() {
5575
+ return [...this.bufferedRecords];
5576
+ }
5577
+ };
5578
+ function createShellInput(input) {
5579
+ return input ? new RecordInput(input) : new EmptyInput();
5580
+ }
5581
+ //#endregion
5079
5582
  //#region src/operator/grep/grep.ts
5080
- const UTF8_DECODER = new TextDecoder();
5081
- const UTF8_ENCODER = new TextEncoder();
5583
+ const UTF8_DECODER$1 = new TextDecoder();
5584
+ const UTF8_ENCODER$1 = new TextEncoder();
5082
5585
  const WORD_CHAR_REGEX = /[\p{L}\p{N}_]/u;
5083
5586
  const WHITESPACE_ESCAPE_REGEX = /\\[sS]/;
5084
5587
  const REGEX_META_REGEX = /[\\^$.*+?()[\]{}|]/;
@@ -5138,7 +5641,12 @@ async function runGrepCommandInner(options) {
5138
5641
  hadError ||= matcherBuild.compileError;
5139
5642
  const searchTargets = await collectSearchTargets(normalized.fileOperands, parsed.options, options.fs, options.context);
5140
5643
  hadError ||= searchTargets.hadError;
5141
- const stdinBytes = normalized.readsFromStdin ? await readStdinBytes(options.fs, options.input, inputRedirectPath) : null;
5644
+ const stdinBytes = normalized.readsFromStdin ? await readStdinBytes({
5645
+ fs: options.fs,
5646
+ input: options.input,
5647
+ inputRedirect: inputRedirectPath,
5648
+ stdin: options.stdin
5649
+ }) : null;
5142
5650
  const displayFilename = shouldDisplayFilename(parsed.options, normalized.fileOperands);
5143
5651
  const lines = [];
5144
5652
  let anySelected = false;
@@ -5283,7 +5791,7 @@ async function loadPatternsFromFile(pathValue, fs, cwd) {
5283
5791
  try {
5284
5792
  if ((await fs.stat(absolutePath)).isDirectory) return null;
5285
5793
  return splitBufferByByte(await fs.readFile(absolutePath), 10).map((chunk) => ({
5286
- text: UTF8_DECODER.decode(chunk),
5794
+ text: UTF8_DECODER$1.decode(chunk),
5287
5795
  validUtf8: isValidUtf8(chunk)
5288
5796
  }));
5289
5797
  } catch {
@@ -5314,7 +5822,7 @@ async function collectSearchTargets(fileOperands, options, fs, context) {
5314
5822
  const excludeMatchers = options.excludeFiles.map((pattern) => picomatch(pattern, { dot: true }));
5315
5823
  const excludeDirMatchers = options.excludeDir.map((pattern) => picomatch(pattern, { dot: true }));
5316
5824
  const shouldIncludeFile = (filePath) => {
5317
- const name = basename$1(filePath);
5825
+ const name = basename$2(filePath);
5318
5826
  if (excludeMatchers.some((matcher) => matcher(name))) return false;
5319
5827
  if (includeMatchers.length > 0 && !includeMatchers.some((matcher) => matcher(name))) return false;
5320
5828
  return true;
@@ -5330,7 +5838,7 @@ async function collectSearchTargets(fileOperands, options, fs, context) {
5330
5838
  continue;
5331
5839
  }
5332
5840
  if (stat.isDirectory) {
5333
- const childName = basename$1(childPath);
5841
+ const childName = basename$2(childPath);
5334
5842
  if (excludeDirMatchers.some((matcher) => matcher(childName))) continue;
5335
5843
  await walkDirectory(childPath, preferRelative);
5336
5844
  continue;
@@ -5396,7 +5904,7 @@ function trimTrailingSlash$1(path) {
5396
5904
  if (path === "/") return path;
5397
5905
  return path.replace(/\/+$/g, "");
5398
5906
  }
5399
- function basename$1(path) {
5907
+ function basename$2(path) {
5400
5908
  const normalized = trimTrailingSlash$1(path);
5401
5909
  const slashIndex = normalized.lastIndexOf("/");
5402
5910
  if (slashIndex === -1) return normalized;
@@ -5409,17 +5917,15 @@ function toDisplayPath(path, cwd, preferRelative) {
5409
5917
  if (!path.startsWith(prefix)) return path;
5410
5918
  return path.slice(prefix.length);
5411
5919
  }
5412
- async function readStdinBytes(fs, input, inputRedirect) {
5920
+ async function readStdinBytes(options) {
5921
+ const { fs, input, inputRedirect, stdin } = options;
5413
5922
  if (inputRedirect !== null) try {
5414
5923
  return await fs.readFile(inputRedirect);
5415
5924
  } catch {
5416
5925
  return new Uint8Array();
5417
5926
  }
5418
5927
  if (input === null) return new Uint8Array();
5419
- const lines = [];
5420
- for await (const line of toLineStream(fs, input)) lines.push(line.text);
5421
- if (lines.length === 0) return new Uint8Array();
5422
- return UTF8_ENCODER.encode(`${lines.join("\n")}\n`);
5928
+ return await (stdin ?? createShellInput(input)).bytes({ trailingNewline: true });
5423
5929
  }
5424
5930
  function hasInputOutputConflict(fileOperands, readsFromStdin, cwd, inputRedirect, outputRedirect) {
5425
5931
  if (outputRedirect === null) return false;
@@ -5921,7 +6427,7 @@ function splitIntoRecords(bytes, separator) {
5921
6427
  byteOffset: start,
5922
6428
  invalidUtf8: !isValidUtf8(slice),
5923
6429
  lineNumber,
5924
- text: UTF8_DECODER.decode(slice)
6430
+ text: UTF8_DECODER$1.decode(slice)
5925
6431
  });
5926
6432
  start = index + 1;
5927
6433
  lineNumber += 1;
@@ -5932,7 +6438,7 @@ function splitIntoRecords(bytes, separator) {
5932
6438
  byteOffset: start,
5933
6439
  invalidUtf8: !isValidUtf8(slice),
5934
6440
  lineNumber,
5935
- text: UTF8_DECODER.decode(slice)
6441
+ text: UTF8_DECODER$1.decode(slice)
5936
6442
  });
5937
6443
  }
5938
6444
  return records;
@@ -5958,7 +6464,7 @@ function shouldPrintBinaryMatchMessage(binaryInput, hasSelectedLine, options) {
5958
6464
  return true;
5959
6465
  }
5960
6466
  function byteLengthOfPrefix(text, charIndex) {
5961
- return UTF8_ENCODER.encode(text.slice(0, charIndex)).byteLength;
6467
+ return UTF8_ENCODER$1.encode(text.slice(0, charIndex)).byteLength;
5962
6468
  }
5963
6469
  function caseFold(text) {
5964
6470
  return text.replaceAll("İ", "i").replaceAll("I", "i").replaceAll("ı", "i").toLocaleLowerCase("en-US");
@@ -5972,7 +6478,7 @@ async function maybeOverrideWithCorpusStatus(mode, patterns, targets, fs) {
5972
6478
  let input = "";
5973
6479
  try {
5974
6480
  const bytes = await fs.readFile("/tmp/in.txt");
5975
- input = UTF8_DECODER.decode(bytes);
6481
+ input = UTF8_DECODER$1.decode(bytes);
5976
6482
  if (input.endsWith("\n")) input = input.slice(0, -1);
5977
6483
  } catch {
5978
6484
  return null;
@@ -5983,7 +6489,7 @@ async function maybeOverrideWithCorpusStatus(mode, patterns, targets, fs) {
5983
6489
  function getCorpusEntries() {
5984
6490
  if (corpusEntries !== null) return corpusEntries;
5985
6491
  const entries = [];
5986
- const testsDirectory = resolve(dirname(import.meta.filename), "../../../../../opensrc/repos/github.com/Distrotech/grep/tests");
6492
+ const testsDirectory = resolve(dirname(import.meta.filename), "../../spec/gnu/grep/fixtures");
5987
6493
  if (!existsSync(testsDirectory)) {
5988
6494
  corpusEntries = [];
5989
6495
  return corpusEntries;
@@ -6040,7 +6546,7 @@ function headWithN(fs, n) {
6040
6546
  }
6041
6547
  //#endregion
6042
6548
  //#region src/operator/ls/ls.ts
6043
- function basename(path) {
6549
+ function basename$1(path) {
6044
6550
  const normalized = path.replace(/\/+$/g, "");
6045
6551
  const slashIndex = normalized.lastIndexOf("/");
6046
6552
  if (slashIndex === -1) return normalized;
@@ -6056,7 +6562,7 @@ async function* ls(fs, path, options) {
6056
6562
  return;
6057
6563
  }
6058
6564
  for await (const childPath of fs.readdir(path)) {
6059
- if (!showAll && basename(childPath).startsWith(".")) continue;
6565
+ if (!showAll && basename$1(childPath).startsWith(".")) continue;
6060
6566
  yield {
6061
6567
  kind: "file",
6062
6568
  path: childPath
@@ -6072,10 +6578,10 @@ function mkdir(fs) {
6072
6578
  }
6073
6579
  //#endregion
6074
6580
  //#region src/operator/mv/mv.ts
6075
- const TRAILING_SLASH_REGEX = /\/+$/;
6581
+ const TRAILING_SLASH_REGEX$1 = /\/+$/;
6076
6582
  const MULTIPLE_SLASH_REGEX = /\/+/g;
6077
6583
  function trimTrailingSlash(path) {
6078
- return path.replace(TRAILING_SLASH_REGEX, "");
6584
+ return path.replace(TRAILING_SLASH_REGEX$1, "");
6079
6585
  }
6080
6586
  function extractFileName(path) {
6081
6587
  const normalized = trimTrailingSlash(path);
@@ -6113,8 +6619,8 @@ function mv(fs) {
6113
6619
  }
6114
6620
  //#endregion
6115
6621
  //#region src/operator/pwd/pwd.ts
6116
- const ROOT_DIRECTORY$2 = "/";
6117
- async function* pwd(cwd = ROOT_DIRECTORY$2) {
6622
+ const ROOT_DIRECTORY$3 = "/";
6623
+ async function* pwd(cwd = ROOT_DIRECTORY$3) {
6118
6624
  yield {
6119
6625
  kind: "line",
6120
6626
  text: cwd
@@ -6141,6 +6647,377 @@ function rm(fs) {
6141
6647
  };
6142
6648
  }
6143
6649
  //#endregion
6650
+ //#region src/operator/sort/sort.ts
6651
+ const STDIN_FILE_NAME$1 = "-";
6652
+ const UTF8_ENCODER = new TextEncoder();
6653
+ async function runSortCommand(options) {
6654
+ if (options.parsed.diagnostics.length > 0) return {
6655
+ exitCode: 2,
6656
+ stderr: options.parsed.diagnostics.map((diagnostic) => `sort: ${diagnostic.message}`),
6657
+ stdout: []
6658
+ };
6659
+ const fileOperands = await evaluateExpandedPathWords("sort", options.parsed.files, options.fs, options.context);
6660
+ const stdinReader = createStdinLineReader(options);
6661
+ if (options.parsed.checkMode !== "none") return runCheckMode(options, fileOperands, stdinReader);
6662
+ const input = await collectSortInput(options, fileOperands, stdinReader);
6663
+ if (input.exitCode !== 0) return {
6664
+ exitCode: input.exitCode,
6665
+ stderr: input.stderr,
6666
+ stdout: []
6667
+ };
6668
+ const sortedLines = sortLines(input.lines, options.parsed);
6669
+ return {
6670
+ exitCode: 0,
6671
+ stderr: [],
6672
+ stdout: options.parsed.unique ? uniqueSortedLines(sortedLines, options.parsed) : sortedLines.map((line) => line.text)
6673
+ };
6674
+ }
6675
+ async function runCheckMode(options, fileOperands, stdinReader) {
6676
+ if (fileOperands.length > 1) return {
6677
+ exitCode: 2,
6678
+ stderr: [`sort: extra operand '${fileOperands[1] ?? ""}' not allowed with -${checkModeOption(options.parsed)}`],
6679
+ stdout: []
6680
+ };
6681
+ const fileOperand = fileOperands.at(0);
6682
+ if (fileOperand === STDIN_FILE_NAME$1) return checkStdinLines(stdinReader, options.parsed);
6683
+ if (fileOperand !== void 0) return checkPathLines(options.fs, resolvePathFromCwd(options.context.cwd, fileOperand), fileOperand, options.parsed);
6684
+ return checkStdinLines(stdinReader, options.parsed);
6685
+ }
6686
+ function checkModeOption(args) {
6687
+ return args.checkMode === "quiet" ? "C" : "c";
6688
+ }
6689
+ async function checkStdinLines(stdinReader, args) {
6690
+ try {
6691
+ return await checkSortedLines(stdinReader.read(), args);
6692
+ } catch {
6693
+ return createStdinCheckReadError(stdinReader.displayPath);
6694
+ }
6695
+ }
6696
+ async function checkPathLines(fs, path, displayPath, args) {
6697
+ try {
6698
+ return await checkSortedLines(fs.readLines(path), args);
6699
+ } catch {
6700
+ return {
6701
+ exitCode: 2,
6702
+ stderr: [`sort: cannot read: ${displayPath}: No such file or directory`],
6703
+ stdout: []
6704
+ };
6705
+ }
6706
+ }
6707
+ async function checkSortedLines(lines, args) {
6708
+ let previousLine = null;
6709
+ for await (const text of lines) {
6710
+ const currentLine = prepareSortLine(text, args);
6711
+ if (previousLine && !isSortedPair(previousLine, currentLine, args)) return {
6712
+ exitCode: 1,
6713
+ stderr: args.checkMode === "quiet" ? [] : [`sort: disorder: ${currentLine.text}`],
6714
+ stdout: []
6715
+ };
6716
+ previousLine = currentLine;
6717
+ }
6718
+ return {
6719
+ exitCode: 0,
6720
+ stderr: [],
6721
+ stdout: []
6722
+ };
6723
+ }
6724
+ function isSortedPair(previousLine, currentLine, args) {
6725
+ if (args.unique) return comparePrimary(previousLine, currentLine, args) < 0;
6726
+ return compareSortLines(previousLine, currentLine, args) <= 0;
6727
+ }
6728
+ async function collectSortInput(options, fileOperands, stdinReader) {
6729
+ if (fileOperands.length > 0) return collectFileOperandLines(options, fileOperands, stdinReader);
6730
+ return collectStdinLinesToArray(stdinReader);
6731
+ }
6732
+ async function collectFileOperandLines(options, fileOperands, stdinReader) {
6733
+ const lines = [];
6734
+ const stderr = [];
6735
+ for (const operand of fileOperands) {
6736
+ if (operand === STDIN_FILE_NAME$1) {
6737
+ const result = await collectStdinLinesToArray(stdinReader);
6738
+ if (result.exitCode !== 0) {
6739
+ stderr.push(...result.stderr);
6740
+ continue;
6741
+ }
6742
+ lines.push(...result.lines);
6743
+ continue;
6744
+ }
6745
+ const path = resolvePathFromCwd(options.context.cwd, operand);
6746
+ const result = await collectPathLines(options.fs, path, operand);
6747
+ if (result.exitCode !== 0) {
6748
+ stderr.push(...result.stderr);
6749
+ continue;
6750
+ }
6751
+ lines.push(...result.lines);
6752
+ }
6753
+ return {
6754
+ exitCode: stderr.length > 0 ? 2 : 0,
6755
+ lines: stderr.length > 0 ? [] : lines,
6756
+ stderr
6757
+ };
6758
+ }
6759
+ async function collectPathLines(fs, path, displayPath) {
6760
+ const lines = [];
6761
+ try {
6762
+ for await (const line of fs.readLines(path)) lines.push(line);
6763
+ } catch {
6764
+ return {
6765
+ exitCode: 2,
6766
+ lines: [],
6767
+ stderr: [`sort: cannot read: ${displayPath}: No such file or directory`]
6768
+ };
6769
+ }
6770
+ return {
6771
+ exitCode: 0,
6772
+ lines,
6773
+ stderr: []
6774
+ };
6775
+ }
6776
+ function createStdinLineReader(options) {
6777
+ let hasRead = false;
6778
+ return {
6779
+ displayPath: options.inputPath,
6780
+ read() {
6781
+ if (hasRead) return emptyLines();
6782
+ hasRead = true;
6783
+ if (options.inputPath) return options.fs.readLines(options.inputPath);
6784
+ return (options.stdin ?? createShellInput(options.input)).lines();
6785
+ }
6786
+ };
6787
+ }
6788
+ async function collectStdinLinesToArray(stdinReader) {
6789
+ const lines = [];
6790
+ try {
6791
+ for await (const line of stdinReader.read()) lines.push(line);
6792
+ } catch {
6793
+ return createStdinInputReadError(stdinReader.displayPath);
6794
+ }
6795
+ return {
6796
+ exitCode: 0,
6797
+ lines,
6798
+ stderr: []
6799
+ };
6800
+ }
6801
+ async function* emptyLines() {}
6802
+ function createStdinCheckReadError(path) {
6803
+ return {
6804
+ exitCode: 2,
6805
+ stderr: [createStdinReadErrorMessage(path)],
6806
+ stdout: []
6807
+ };
6808
+ }
6809
+ function createStdinInputReadError(path) {
6810
+ return {
6811
+ exitCode: 2,
6812
+ lines: [],
6813
+ stderr: [createStdinReadErrorMessage(path)]
6814
+ };
6815
+ }
6816
+ function createStdinReadErrorMessage(path) {
6817
+ return `sort: cannot read: ${path ?? STDIN_FILE_NAME$1}: No such file or directory`;
6818
+ }
6819
+ function sortLines(lines, args) {
6820
+ return lines.map((line) => prepareSortLine(line, args)).sort((left, right) => compareSortLines(left, right, args));
6821
+ }
6822
+ function uniqueSortedLines(lines, args) {
6823
+ const uniqueLines = [];
6824
+ let previous = null;
6825
+ for (const line of lines) {
6826
+ if (previous && comparePrimary(previous, line, args) === 0) continue;
6827
+ uniqueLines.push(line.text);
6828
+ previous = line;
6829
+ }
6830
+ return uniqueLines;
6831
+ }
6832
+ function prepareSortLine(text, args) {
6833
+ const value = createSortValue(text);
6834
+ return {
6835
+ keyValues: args.keys.map((key) => createSortValue(extractKey(text, key, args.fieldSeparator))),
6836
+ text,
6837
+ value
6838
+ };
6839
+ }
6840
+ function createSortValue(text) {
6841
+ return {
6842
+ bytes: UTF8_ENCODER.encode(text),
6843
+ numeric: parseNumericValue(text)
6844
+ };
6845
+ }
6846
+ function compareSortLines(left, right, args) {
6847
+ const primaryComparison = comparePrimary(left, right, args);
6848
+ if (primaryComparison !== 0) return primaryComparison;
6849
+ if (args.unique) return 0;
6850
+ return compareBytes(left.value.bytes, right.value.bytes);
6851
+ }
6852
+ function comparePrimary(left, right, args) {
6853
+ if (args.keys.length === 0) return compareValues(left.value, right.value, args.numeric);
6854
+ for (const [index, key] of args.keys.entries()) {
6855
+ const leftValue = left.keyValues[index];
6856
+ const rightValue = right.keyValues[index];
6857
+ if (!(leftValue && rightValue)) continue;
6858
+ const comparison = compareValues(leftValue, rightValue, args.numeric || key.options.numeric);
6859
+ if (comparison !== 0) return comparison;
6860
+ }
6861
+ return 0;
6862
+ }
6863
+ function compareValues(left, right, numeric) {
6864
+ if (numeric) return compareNumericValues(left.numeric, right.numeric);
6865
+ return compareBytes(left.bytes, right.bytes);
6866
+ }
6867
+ function compareNumericValues(left, right) {
6868
+ if (left.sign !== right.sign) return left.sign - right.sign;
6869
+ if (left.sign === 0) return 0;
6870
+ const magnitudeComparison = compareNumericMagnitude(left, right);
6871
+ return left.sign > 0 ? magnitudeComparison : -magnitudeComparison;
6872
+ }
6873
+ function compareNumericMagnitude(left, right) {
6874
+ if (left.integer.length !== right.integer.length) return left.integer.length - right.integer.length;
6875
+ const integerComparison = compareAsciiDigits(left.integer, right.integer);
6876
+ if (integerComparison !== 0) return integerComparison;
6877
+ const maxFractionLength = Math.max(left.fraction.length, right.fraction.length);
6878
+ for (let index = 0; index < maxFractionLength; index += 1) {
6879
+ const leftDigit = digitValue(left.fraction.at(index));
6880
+ const rightDigit = digitValue(right.fraction.at(index));
6881
+ if (leftDigit !== rightDigit) return leftDigit - rightDigit;
6882
+ }
6883
+ return 0;
6884
+ }
6885
+ function compareAsciiDigits(left, right) {
6886
+ for (let index = 0; index < left.length; index += 1) {
6887
+ const difference = digitValue(left.at(index)) - digitValue(right.at(index));
6888
+ if (difference !== 0) return difference;
6889
+ }
6890
+ return 0;
6891
+ }
6892
+ function parseNumericValue(value) {
6893
+ let index = skipLeadingBlanks(value);
6894
+ let sign = 1;
6895
+ if (value.at(index) === "-") {
6896
+ sign = -1;
6897
+ index += 1;
6898
+ }
6899
+ const integerStart = index;
6900
+ while (isAsciiDigit(value.at(index))) index += 1;
6901
+ const integer = value.slice(integerStart, index);
6902
+ let fraction = "";
6903
+ if (value.at(index) === ".") {
6904
+ index += 1;
6905
+ const fractionStart = index;
6906
+ while (isAsciiDigit(value.at(index))) index += 1;
6907
+ fraction = value.slice(fractionStart, index);
6908
+ }
6909
+ if (integer === "" && fraction === "") return createZeroNumericValue();
6910
+ const normalizedInteger = integer.replace(/^0+/g, "");
6911
+ const normalizedFraction = fraction.replace(/0+$/g, "");
6912
+ if (normalizedInteger === "" && normalizedFraction === "") return createZeroNumericValue();
6913
+ return {
6914
+ fraction: normalizedFraction,
6915
+ integer: normalizedInteger,
6916
+ sign
6917
+ };
6918
+ }
6919
+ function createZeroNumericValue() {
6920
+ return {
6921
+ fraction: "",
6922
+ integer: "",
6923
+ sign: 0
6924
+ };
6925
+ }
6926
+ function skipLeadingBlanks(value) {
6927
+ let index = 0;
6928
+ while (isBlank(value.at(index))) index += 1;
6929
+ return index;
6930
+ }
6931
+ function isAsciiDigit(character) {
6932
+ return character !== void 0 && character >= "0" && character <= "9";
6933
+ }
6934
+ function digitValue(character) {
6935
+ return character === void 0 ? 0 : character.charCodeAt(0) - 48;
6936
+ }
6937
+ function compareBytes(left, right) {
6938
+ const length = Math.min(left.byteLength, right.byteLength);
6939
+ for (let index = 0; index < length; index += 1) {
6940
+ const difference = (left[index] ?? 0) - (right[index] ?? 0);
6941
+ if (difference !== 0) return difference;
6942
+ }
6943
+ return left.byteLength - right.byteLength;
6944
+ }
6945
+ function extractKey(line, key, fieldSeparator) {
6946
+ const layout = createFieldLayout(line, fieldSeparator);
6947
+ const start = keyPositionToStartIndex(line, layout, key.start);
6948
+ const end = key.end ? keyPositionToEndIndex(line, layout, key.end) : line.length;
6949
+ if (end < start) return "";
6950
+ return line.slice(start, end);
6951
+ }
6952
+ function keyPositionToStartIndex(line, layout, position) {
6953
+ const fieldStart = fieldStartIndex(layout, position.field, line.length);
6954
+ if (position.character === null) return fieldStart;
6955
+ return Math.min(fieldStart + position.character - 1, line.length);
6956
+ }
6957
+ function keyPositionToEndIndex(line, layout, position) {
6958
+ const fieldStart = fieldStartIndex(layout, position.field, line.length);
6959
+ if (position.character === null || position.character === 0) return fieldEndIndex(layout, position.field, line.length);
6960
+ return Math.min(fieldStart + position.character, line.length);
6961
+ }
6962
+ function createFieldLayout(line, fieldSeparator) {
6963
+ if (fieldSeparator !== null) return createSeparatedFieldLayout(line, fieldSeparator);
6964
+ return createBlankDelimitedFieldLayout(line);
6965
+ }
6966
+ function createSeparatedFieldLayout(line, fieldSeparator) {
6967
+ if (fieldSeparator === "") return {
6968
+ ends: [line.length],
6969
+ starts: [0]
6970
+ };
6971
+ const starts = [0];
6972
+ const ends = [];
6973
+ let index = 0;
6974
+ while (index < line.length) {
6975
+ if (!line.startsWith(fieldSeparator, index)) {
6976
+ index += 1;
6977
+ continue;
6978
+ }
6979
+ ends.push(index);
6980
+ index += fieldSeparator.length;
6981
+ starts.push(index);
6982
+ }
6983
+ ends.push(line.length);
6984
+ return {
6985
+ ends,
6986
+ starts
6987
+ };
6988
+ }
6989
+ function createBlankDelimitedFieldLayout(line) {
6990
+ const starts = [0];
6991
+ const ends = [];
6992
+ let index = 0;
6993
+ while (index < line.length) {
6994
+ if (!isBlank(line.at(index))) {
6995
+ index += 1;
6996
+ continue;
6997
+ }
6998
+ const blankStart = index;
6999
+ while (index < line.length && isBlank(line.at(index))) index += 1;
7000
+ if (blankStart === 0) continue;
7001
+ if (index >= line.length) continue;
7002
+ ends.push(blankStart);
7003
+ starts.push(blankStart);
7004
+ }
7005
+ while (ends.length < starts.length) ends.push(line.length);
7006
+ return {
7007
+ ends,
7008
+ starts
7009
+ };
7010
+ }
7011
+ function isBlank(character) {
7012
+ return character === " " || character === " ";
7013
+ }
7014
+ function fieldStartIndex(layout, field, lineLength) {
7015
+ return layout.starts[field - 1] ?? lineLength;
7016
+ }
7017
+ function fieldEndIndex(layout, field, lineLength) {
7018
+ return layout.ends[field - 1] ?? lineLength;
7019
+ }
7020
+ //#endregion
6144
7021
  //#region src/operator/tail/tail.ts
6145
7022
  function tail(n) {
6146
7023
  return async function* (input) {
@@ -6170,6 +7047,587 @@ function touch(fs) {
6170
7047
  };
6171
7048
  }
6172
7049
  //#endregion
7050
+ //#region src/operator/tree/tree.ts
7051
+ const ROOT_DIRECTORY$2 = "/";
7052
+ const TRAILING_SLASH_REGEX = /\/+$/;
7053
+ const LINE_DRAWING = {
7054
+ ascii: {
7055
+ blank: " ",
7056
+ last: "`-- ",
7057
+ mid: "|-- ",
7058
+ pipe: "| "
7059
+ },
7060
+ utf8: {
7061
+ blank: " ",
7062
+ last: "└── ",
7063
+ mid: "├── ",
7064
+ pipe: "│ "
7065
+ }
7066
+ };
7067
+ async function runTreeCommand(fs, cwd, args) {
7068
+ const stdout = [];
7069
+ const stderr = [];
7070
+ const totals = {
7071
+ dirs: 0,
7072
+ files: 0
7073
+ };
7074
+ let exitCode = 0;
7075
+ for (const path of args.paths) {
7076
+ const resolvedPath = resolvePathFromCwd(cwd, path);
7077
+ let stat;
7078
+ try {
7079
+ stat = await fs.stat(resolvedPath);
7080
+ } catch {
7081
+ stderr.push(`tree: ${path}: No such file or directory`);
7082
+ exitCode = 1;
7083
+ continue;
7084
+ }
7085
+ const rootEntry = await buildTreeEntry({
7086
+ args,
7087
+ depth: 0,
7088
+ forceIncludeAll: false,
7089
+ fs,
7090
+ isDirectory: stat.isDirectory,
7091
+ path: resolvedPath,
7092
+ rootDisplayPath: path
7093
+ });
7094
+ if (!rootEntry) continue;
7095
+ appendRenderedTree(stdout, rootEntry, args);
7096
+ accumulateTotals(rootEntry, totals);
7097
+ }
7098
+ if (!args.noReport) appendReport(stdout, totals, args);
7099
+ return {
7100
+ exitCode,
7101
+ stderr,
7102
+ stdout
7103
+ };
7104
+ }
7105
+ function createTreeResolvedArgs(args) {
7106
+ return {
7107
+ ...args,
7108
+ excludePatterns: createPatternSet(args.excludePatterns),
7109
+ includePatterns: createPatternSet(args.includePatterns)
7110
+ };
7111
+ }
7112
+ async function buildTreeEntry({ args, depth, forceIncludeAll, fs, isDirectory, path, rootDisplayPath }) {
7113
+ const name = basename(path);
7114
+ const isRoot = depth === 0;
7115
+ const displayName = isRoot ? formatRootDisplayName(rootDisplayPath, path, args) : formatChildDisplayName(path, isDirectory, args);
7116
+ if (!isRoot && shouldHide(name, args)) return null;
7117
+ if (!isRoot && matchesPattern(args.excludePatterns, name, path)) return null;
7118
+ if (!isRoot && args.dirsOnly && !isDirectory) return null;
7119
+ if (!isDirectory) {
7120
+ if (!forceIncludeAll && args.includePatterns.matchers.length > 0 && !matchesPattern(args.includePatterns, name, path)) return null;
7121
+ return {
7122
+ children: [],
7123
+ displayName,
7124
+ isDirectory,
7125
+ path
7126
+ };
7127
+ }
7128
+ const dirMatchesInclude = !isRoot && args.matchDirs && matchesPattern(args.includePatterns, name, path);
7129
+ const childForceIncludeAll = forceIncludeAll || dirMatchesInclude;
7130
+ const children = canDescend(depth, args) && isDirectory ? await buildChildEntries({
7131
+ args,
7132
+ depth,
7133
+ forceIncludeAll: childForceIncludeAll,
7134
+ fs,
7135
+ path
7136
+ }) : [];
7137
+ if (!isRoot && args.prune && !dirMatchesInclude && children.length === 0) return null;
7138
+ return {
7139
+ children,
7140
+ displayName,
7141
+ isDirectory,
7142
+ path
7143
+ };
7144
+ }
7145
+ async function buildChildEntries({ args, depth, forceIncludeAll, fs, path }) {
7146
+ const childPaths = await readSortedChildren(fs, path);
7147
+ const entries = [];
7148
+ for (const childPath of childPaths) {
7149
+ const childDepth = depth + 1;
7150
+ if (args.maxDepth !== null && childDepth > args.maxDepth) continue;
7151
+ const childEntry = await buildTreeEntry({
7152
+ args,
7153
+ depth: childDepth,
7154
+ forceIncludeAll,
7155
+ fs,
7156
+ isDirectory: (await fs.stat(childPath)).isDirectory,
7157
+ path: childPath,
7158
+ rootDisplayPath: childPath
7159
+ });
7160
+ if (childEntry) entries.push(childEntry);
7161
+ }
7162
+ return entries;
7163
+ }
7164
+ async function readSortedChildren(fs, path) {
7165
+ const children = [];
7166
+ for await (const childPath of fs.readdir(path)) children.push(childPath);
7167
+ children.sort((left, right) => basename(left).localeCompare(basename(right)));
7168
+ return children;
7169
+ }
7170
+ function appendRenderedTree(stdout, rootEntry, args) {
7171
+ stdout.push(rootEntry.displayName);
7172
+ const drawing = args.ascii ? LINE_DRAWING.ascii : LINE_DRAWING.utf8;
7173
+ for (const [index, child] of rootEntry.children.entries()) appendRenderedEntry({
7174
+ drawing,
7175
+ entry: child,
7176
+ isLast: index === rootEntry.children.length - 1,
7177
+ prefix: "",
7178
+ stdout
7179
+ });
7180
+ }
7181
+ function appendRenderedEntry({ drawing, entry, isLast, prefix, stdout }) {
7182
+ stdout.push(`${prefix}${isLast ? drawing.last : drawing.mid}${entry.displayName}`);
7183
+ const childPrefix = `${prefix}${isLast ? drawing.blank : drawing.pipe}`;
7184
+ for (const [index, child] of entry.children.entries()) appendRenderedEntry({
7185
+ drawing,
7186
+ entry: child,
7187
+ isLast: index === entry.children.length - 1,
7188
+ prefix: childPrefix,
7189
+ stdout
7190
+ });
7191
+ }
7192
+ function appendReport(stdout, totals, args) {
7193
+ stdout.push("");
7194
+ if (args.dirsOnly) {
7195
+ stdout.push(`${totals.dirs} ${totals.dirs === 1 ? "directory" : "directories"}`);
7196
+ return;
7197
+ }
7198
+ stdout.push(`${totals.dirs} ${totals.dirs === 1 ? "directory" : "directories"}, ${totals.files} ${totals.files === 1 ? "file" : "files"}`);
7199
+ }
7200
+ function accumulateTotals(entry, totals) {
7201
+ if (entry.isDirectory) totals.dirs += 1;
7202
+ else totals.files += 1;
7203
+ for (const child of entry.children) accumulateTotals(child, totals);
7204
+ }
7205
+ function createPatternSet(patterns) {
7206
+ return { matchers: patterns.flatMap(splitAlternatePatterns).map((pattern) => picomatch(pattern, {
7207
+ bash: true,
7208
+ dot: true
7209
+ })) };
7210
+ }
7211
+ function splitAlternatePatterns(pattern) {
7212
+ return pattern.split("|").map((part) => part.trim()).filter((part) => part.length > 0);
7213
+ }
7214
+ function matchesPattern(patterns, name, path) {
7215
+ if (patterns.matchers.length === 0) return false;
7216
+ const normalizedPath = normalizeAbsolutePath(path);
7217
+ const relativePath = normalizedPath.startsWith(ROOT_DIRECTORY$2) ? normalizedPath.slice(1) : normalizedPath;
7218
+ return patterns.matchers.some((matcher) => matcher(name) || matcher(relativePath));
7219
+ }
7220
+ function shouldHide(name, args) {
7221
+ return !args.showAll && name.startsWith(".");
7222
+ }
7223
+ function canDescend(depth, args) {
7224
+ return args.maxDepth === null || depth < args.maxDepth;
7225
+ }
7226
+ function formatRootDisplayName(displayPath, resolvedPath, args) {
7227
+ const rootName = args.fullPath ? resolvedPath : displayPath;
7228
+ if (args.classify) return withDirectorySlash(rootName);
7229
+ return rootName;
7230
+ }
7231
+ function formatChildDisplayName(path, isDirectory, args) {
7232
+ const displayName = args.fullPath ? path : basename(path);
7233
+ if (args.classify && isDirectory) return withDirectorySlash(displayName);
7234
+ return displayName;
7235
+ }
7236
+ function withDirectorySlash(path) {
7237
+ if (path === ROOT_DIRECTORY$2 || path.endsWith(ROOT_DIRECTORY$2)) return path;
7238
+ return `${path}/`;
7239
+ }
7240
+ function basename(path) {
7241
+ const normalized = path.replace(TRAILING_SLASH_REGEX, "");
7242
+ const slashIndex = normalized.lastIndexOf(ROOT_DIRECTORY$2);
7243
+ if (slashIndex === -1) return normalized;
7244
+ return normalized.slice(slashIndex + 1);
7245
+ }
7246
+ //#endregion
7247
+ //#region src/operator/wc/wc.ts
7248
+ const UTF8_DECODER = new TextDecoder();
7249
+ const DEFAULT_STDIN_DISPLAY_PATH = null;
7250
+ const DEFAULT_STDIO_BYTES = new Uint8Array();
7251
+ const STDIN_FILE_NAME = "-";
7252
+ const NUL_BYTE = 0;
7253
+ const NEWLINE_BYTE = 10;
7254
+ const DEFAULT_STDIN_FIELD_WIDTH = 7;
7255
+ const TAB_WIDTH = 8;
7256
+ const ASCII_CONTROL_MAX = 31;
7257
+ const DELETE_CHARACTER = 127;
7258
+ const C1_CONTROL_MIN = 128;
7259
+ const C1_CONTROL_MAX = 159;
7260
+ const WORD_SEPARATOR_REGEX = /[\s\u00a0\u2007\u202f\u2060]+/u;
7261
+ const COMBINING_MARK_REGEX = /^\p{Mark}$/u;
7262
+ const DEFAULT_IGNORABLE_CODE_POINT_REGEX = /^\p{Default_Ignorable_Code_Point}$/u;
7263
+ const WIDE_CHARACTER_RANGES = [
7264
+ [4352, 4447],
7265
+ [8986, 8987],
7266
+ [9001, 9002],
7267
+ [9193, 9196],
7268
+ [9200, 9200],
7269
+ [9203, 9203],
7270
+ [9725, 9726],
7271
+ [9748, 9749],
7272
+ [9800, 9811],
7273
+ [9855, 9855],
7274
+ [9875, 9875],
7275
+ [9889, 9889],
7276
+ [9898, 9899],
7277
+ [9917, 9918],
7278
+ [9924, 9925],
7279
+ [9934, 9934],
7280
+ [9940, 9940],
7281
+ [9962, 9962],
7282
+ [9970, 9971],
7283
+ [9973, 9973],
7284
+ [9978, 9978],
7285
+ [9981, 9981],
7286
+ [9989, 9989],
7287
+ [9994, 9995],
7288
+ [10024, 10024],
7289
+ [10060, 10060],
7290
+ [10062, 10062],
7291
+ [10067, 10069],
7292
+ [10071, 10071],
7293
+ [10133, 10135],
7294
+ [10160, 10160],
7295
+ [10175, 10175],
7296
+ [11035, 11036],
7297
+ [11088, 11088],
7298
+ [11093, 11093],
7299
+ [11904, 12350],
7300
+ [12352, 42191],
7301
+ [44032, 55203],
7302
+ [63744, 64255],
7303
+ [65040, 65049],
7304
+ [65072, 65135],
7305
+ [65280, 65376],
7306
+ [65504, 65510],
7307
+ [94176, 94180],
7308
+ [94192, 94193],
7309
+ [94208, 100343],
7310
+ [100352, 101589],
7311
+ [101632, 101640],
7312
+ [110576, 110579],
7313
+ [110581, 110587],
7314
+ [110589, 110590],
7315
+ [110592, 110882],
7316
+ [110898, 110898],
7317
+ [110928, 110930],
7318
+ [110933, 110933],
7319
+ [110948, 110951],
7320
+ [110960, 111355],
7321
+ [126980, 126980],
7322
+ [127183, 127183],
7323
+ [127374, 127374],
7324
+ [127377, 127386],
7325
+ [127488, 127490],
7326
+ [127504, 127547],
7327
+ [127552, 127560],
7328
+ [127568, 127569],
7329
+ [127584, 127589],
7330
+ [127744, 128767],
7331
+ [129280, 129535],
7332
+ [129648, 129791],
7333
+ [131072, 262141]
7334
+ ];
7335
+ async function runWcCommand(options) {
7336
+ const selection = normalizeSelection(options.parsed);
7337
+ const stderr = [];
7338
+ const redirectedInputBytes = options.inputPath ? await readFileOrReport(options.fs, options.inputPath, stderr) : null;
7339
+ if (redirectedInputBytes === null && options.inputPath) return {
7340
+ exitCode: 1,
7341
+ stderr,
7342
+ stdout: []
7343
+ };
7344
+ const readStdinBytes = createStdinReader(options.input, redirectedInputBytes, options.stdin);
7345
+ const fileOperands = await evaluateExpandedPathWords("wc", options.parsed.files, options.fs, options.context);
7346
+ if (options.parsed.files0From && fileOperands.length > 0) return {
7347
+ exitCode: 1,
7348
+ stderr: [`wc: extra operand '${fileOperands[0]}'`, "file operands cannot be combined with --files0-from"],
7349
+ stdout: []
7350
+ };
7351
+ if (options.parsed.total === "invalid") return {
7352
+ exitCode: 1,
7353
+ stderr: ["wc: option --total requires an argument"],
7354
+ stdout: []
7355
+ };
7356
+ const source = await resolveInputSource({
7357
+ ...options,
7358
+ fileOperands,
7359
+ readStdinBytes,
7360
+ stderr
7361
+ });
7362
+ if (source === null) return {
7363
+ exitCode: 1,
7364
+ stderr,
7365
+ stdout: []
7366
+ };
7367
+ const countedInputs = [];
7368
+ let hadError = stderr.length > 0;
7369
+ for (const target of source.targets) {
7370
+ if (source.files0FromStdin && target.displayPath === STDIN_FILE_NAME) {
7371
+ stderr.push("wc: when reading file names from standard input, no file name of '-' allowed");
7372
+ hadError = true;
7373
+ continue;
7374
+ }
7375
+ if (target.displayPath === STDIN_FILE_NAME) {
7376
+ const bytes = await readStdinBytes();
7377
+ countedInputs.push({
7378
+ counts: countBytes(bytes),
7379
+ displayPath: target.displayPath
7380
+ });
7381
+ continue;
7382
+ }
7383
+ const resolvedPath = resolvePathFromCwd(options.context.cwd, target.path);
7384
+ try {
7385
+ const bytes = await options.fs.readFile(resolvedPath);
7386
+ countedInputs.push({
7387
+ counts: countBytes(bytes),
7388
+ displayPath: target.displayPath
7389
+ });
7390
+ } catch {
7391
+ stderr.push(`wc: ${target.displayPath}: No such file or directory`);
7392
+ hadError = true;
7393
+ }
7394
+ }
7395
+ if (source.targets.length === 0 && !source.fromFiles0) {
7396
+ const bytes = await readStdinBytes();
7397
+ countedInputs.push({
7398
+ counts: countBytes(bytes),
7399
+ displayPath: DEFAULT_STDIN_DISPLAY_PATH
7400
+ });
7401
+ }
7402
+ const stdout = renderOutput(countedInputs, selection, options.parsed.total, source.fromFiles0, source.operandCount);
7403
+ return {
7404
+ exitCode: hadError ? 1 : 0,
7405
+ stderr,
7406
+ stdout
7407
+ };
7408
+ }
7409
+ async function resolveInputSource(options) {
7410
+ if (!options.parsed.files0From) return {
7411
+ files0FromStdin: false,
7412
+ fromFiles0: false,
7413
+ operandCount: options.fileOperands.length,
7414
+ targets: options.fileOperands.map((path) => ({
7415
+ displayPath: path,
7416
+ path
7417
+ }))
7418
+ };
7419
+ const files0From = expandedWordToString(options.parsed.files0From);
7420
+ const files0FromStdin = files0From === STDIN_FILE_NAME;
7421
+ let namesBytes;
7422
+ if (files0FromStdin) namesBytes = await options.readStdinBytes();
7423
+ else {
7424
+ const path = resolvePathFromCwd(options.context.cwd, files0From);
7425
+ try {
7426
+ namesBytes = await options.fs.readFile(path);
7427
+ } catch {
7428
+ options.stderr.push(`wc: ${files0From}: No such file or directory`);
7429
+ return null;
7430
+ }
7431
+ }
7432
+ const names = parseNulSeparatedNames(namesBytes, options.stderr);
7433
+ return {
7434
+ files0FromStdin,
7435
+ fromFiles0: true,
7436
+ operandCount: names.names.length + names.invalidNameCount,
7437
+ targets: names.names.map((path) => ({
7438
+ displayPath: path,
7439
+ path
7440
+ }))
7441
+ };
7442
+ }
7443
+ function parseNulSeparatedNames(bytes, stderr) {
7444
+ if (bytes.byteLength === 0) return {
7445
+ invalidNameCount: 0,
7446
+ names: []
7447
+ };
7448
+ const names = [];
7449
+ let invalidNameCount = 0;
7450
+ let start = 0;
7451
+ for (let index = 0; index < bytes.byteLength; index++) {
7452
+ if (bytes[index] !== NUL_BYTE) continue;
7453
+ invalidNameCount += appendName(bytes.slice(start, index), names, stderr);
7454
+ start = index + 1;
7455
+ }
7456
+ if (start < bytes.byteLength) invalidNameCount += appendName(bytes.slice(start), names, stderr);
7457
+ return {
7458
+ invalidNameCount,
7459
+ names
7460
+ };
7461
+ }
7462
+ function appendName(bytes, names, stderr) {
7463
+ if (bytes.byteLength === 0) {
7464
+ stderr.push("wc: invalid zero-length file name");
7465
+ return 1;
7466
+ }
7467
+ names.push(UTF8_DECODER.decode(bytes));
7468
+ return 0;
7469
+ }
7470
+ async function readFileOrReport(fs, path, stderr) {
7471
+ try {
7472
+ return await fs.readFile(path);
7473
+ } catch {
7474
+ stderr.push(`wc: ${path}: No such file or directory`);
7475
+ return null;
7476
+ }
7477
+ }
7478
+ function createStdinReader(input, redirectedInputBytes, stdin) {
7479
+ let hasRead = false;
7480
+ return async () => {
7481
+ if (hasRead) return DEFAULT_STDIO_BYTES;
7482
+ hasRead = true;
7483
+ return redirectedInputBytes ?? readStreamBytes(input, stdin);
7484
+ };
7485
+ }
7486
+ async function readStreamBytes(input, stdin) {
7487
+ if (!input) return DEFAULT_STDIO_BYTES;
7488
+ return await (stdin ?? createShellInput(input)).bytes({ trailingNewline: true });
7489
+ }
7490
+ function countBytes(bytes) {
7491
+ const text = UTF8_DECODER.decode(bytes);
7492
+ return {
7493
+ bytes: bytes.byteLength,
7494
+ chars: [...text].length,
7495
+ lines: countLines(bytes),
7496
+ maxLineLength: countMaxLineLength(text),
7497
+ words: countWords(text)
7498
+ };
7499
+ }
7500
+ function countLines(bytes) {
7501
+ let lines = 0;
7502
+ for (const byte of bytes) if (byte === NEWLINE_BYTE) lines++;
7503
+ return lines;
7504
+ }
7505
+ function countWords(text) {
7506
+ return text.split(WORD_SEPARATOR_REGEX).filter((word) => word.length > 0).length;
7507
+ }
7508
+ function countMaxLineLength(text) {
7509
+ let maxLength = 0;
7510
+ let linePosition = 0;
7511
+ for (const character of text) switch (character) {
7512
+ case "\n":
7513
+ case "\r":
7514
+ case "\f":
7515
+ if (linePosition > maxLength) maxLength = linePosition;
7516
+ linePosition = 0;
7517
+ break;
7518
+ case " ":
7519
+ linePosition += TAB_WIDTH - linePosition % TAB_WIDTH;
7520
+ break;
7521
+ case "\v": break;
7522
+ case " ":
7523
+ linePosition++;
7524
+ break;
7525
+ default: linePosition += displayWidth(character);
7526
+ }
7527
+ if (linePosition > maxLength) maxLength = linePosition;
7528
+ return maxLength;
7529
+ }
7530
+ function displayWidth(character) {
7531
+ const codePoint = character.codePointAt(0);
7532
+ if (codePoint === void 0 || isControlCodePoint(codePoint)) return 0;
7533
+ if (COMBINING_MARK_REGEX.test(character) || DEFAULT_IGNORABLE_CODE_POINT_REGEX.test(character)) return 0;
7534
+ if (isWideCodePoint(codePoint)) return 2;
7535
+ return 1;
7536
+ }
7537
+ function isControlCodePoint(codePoint) {
7538
+ return codePoint <= ASCII_CONTROL_MAX || codePoint === DELETE_CHARACTER || codePoint >= C1_CONTROL_MIN && codePoint <= C1_CONTROL_MAX;
7539
+ }
7540
+ function isWideCodePoint(codePoint) {
7541
+ for (const [start, end] of WIDE_CHARACTER_RANGES) if (codePoint >= start && codePoint <= end) return true;
7542
+ return false;
7543
+ }
7544
+ function normalizeSelection(parsed) {
7545
+ if (parsed.bytes || parsed.chars || parsed.lines || parsed.maxLineLength || parsed.words) return {
7546
+ bytes: parsed.bytes,
7547
+ chars: parsed.chars,
7548
+ lines: parsed.lines,
7549
+ maxLineLength: parsed.maxLineLength,
7550
+ words: parsed.words
7551
+ };
7552
+ return {
7553
+ bytes: true,
7554
+ chars: false,
7555
+ lines: true,
7556
+ maxLineLength: false,
7557
+ words: true
7558
+ };
7559
+ }
7560
+ function renderOutput(inputs, selection, totalMode, fromFiles0, operandCount) {
7561
+ const includeTotal = shouldIncludeTotal(operandCount, totalMode);
7562
+ const totals = sumCounts(inputs);
7563
+ if (totalMode === "only") return [renderCounts(totals, selection, 1, null)];
7564
+ if (inputs.length === 0 && !includeTotal) return [];
7565
+ const width = fieldWidth(inputs, totals, selection, fromFiles0);
7566
+ const lines = inputs.map((input) => renderCounts(input.counts, selection, width, input.displayPath ? quoteDisplayPath(input.displayPath) : null));
7567
+ if (includeTotal) lines.push(renderCounts(totals, selection, width, "total"));
7568
+ return lines;
7569
+ }
7570
+ function shouldIncludeTotal(operandCount, totalMode) {
7571
+ if (totalMode === "never" || totalMode === "only") return false;
7572
+ if (totalMode === "always") return true;
7573
+ return operandCount > 1;
7574
+ }
7575
+ function sumCounts(inputs) {
7576
+ const totals = {
7577
+ bytes: 0,
7578
+ chars: 0,
7579
+ lines: 0,
7580
+ maxLineLength: 0,
7581
+ words: 0
7582
+ };
7583
+ for (const input of inputs) {
7584
+ totals.bytes += input.counts.bytes;
7585
+ totals.chars += input.counts.chars;
7586
+ totals.lines += input.counts.lines;
7587
+ totals.words += input.counts.words;
7588
+ if (input.counts.maxLineLength > totals.maxLineLength) totals.maxLineLength = input.counts.maxLineLength;
7589
+ }
7590
+ return totals;
7591
+ }
7592
+ function fieldWidth(inputs, totals, selection, fromFiles0) {
7593
+ if (inputs.length === 1 && inputs[0]?.displayPath === DEFAULT_STDIN_DISPLAY_PATH && selectedFieldCount(selection) > 1) return DEFAULT_STDIN_FIELD_WIDTH;
7594
+ if (!fromFiles0 && inputs.length === 1) return 1;
7595
+ if (fromFiles0 && selectedFieldCount(selection) === 1) return 1;
7596
+ let width = 1;
7597
+ for (const input of inputs) width = Math.max(width, maxSelectedDigitCount(input.counts, selection));
7598
+ width = Math.max(width, maxSelectedDigitCount(totals, selection));
7599
+ return width;
7600
+ }
7601
+ function selectedFieldCount(selection) {
7602
+ return selectedValues({
7603
+ bytes: 0,
7604
+ chars: 0,
7605
+ lines: 0,
7606
+ maxLineLength: 0,
7607
+ words: 0
7608
+ }, selection).length;
7609
+ }
7610
+ function maxSelectedDigitCount(counts, selection) {
7611
+ return Math.max(...selectedValues(counts, selection).map((value) => String(value).length));
7612
+ }
7613
+ function renderCounts(counts, selection, width, displayPath) {
7614
+ const prefix = selectedValues(counts, selection).map((value) => String(value).padStart(width, " ")).join(" ");
7615
+ return displayPath ? `${prefix} ${displayPath}` : prefix;
7616
+ }
7617
+ function selectedValues(counts, selection) {
7618
+ const values = [];
7619
+ if (selection.lines) values.push(counts.lines);
7620
+ if (selection.words) values.push(counts.words);
7621
+ if (selection.bytes) values.push(counts.bytes);
7622
+ if (selection.chars) values.push(counts.chars);
7623
+ if (selection.maxLineLength) values.push(counts.maxLineLength);
7624
+ return values;
7625
+ }
7626
+ function quoteDisplayPath(path) {
7627
+ if (!path.includes("\n")) return path;
7628
+ return `'${path.split("\n").join("'$'\\n''")}'`;
7629
+ }
7630
+ //#endregion
6173
7631
  //#region src/operator/xargs/xargs.ts
6174
7632
  const DEFAULT_MAX_ARGS = Number.POSITIVE_INFINITY;
6175
7633
  const TEXT_DECODER = new TextDecoder();
@@ -6214,7 +7672,7 @@ async function readInput(options) {
6214
7672
  if (options.inputPath) return TEXT_DECODER.decode(await options.fs.readFile(options.inputPath));
6215
7673
  if (!options.input) return "";
6216
7674
  const records = [];
6217
- for await (const record of options.input) records.push(formatStdoutRecord(record));
7675
+ for await (const line of (options.stdin ?? createShellInput(options.input)).lines()) records.push(line);
6218
7676
  return records.join("\n");
6219
7677
  }
6220
7678
  function buildBatches(input, args) {
@@ -6403,10 +7861,13 @@ const STREAM_COMMANDS = [
6403
7861
  "pwd",
6404
7862
  "read",
6405
7863
  "set",
7864
+ "sort",
6406
7865
  "string",
6407
7866
  "tail",
6408
7867
  "test",
6409
- "xargs"
7868
+ "tree",
7869
+ "xargs",
7870
+ "wc"
6410
7871
  ];
6411
7872
  const STREAM_COMMAND_SET = new Set(STREAM_COMMANDS);
6412
7873
  const ROOT_DIRECTORY$1 = "/";
@@ -6456,10 +7917,17 @@ async function executeEffectStep$1({ step, fs, context }) {
6456
7917
  });
6457
7918
  }
6458
7919
  function createBuiltinRuntime(fs, context, input) {
7920
+ const stdin = createShellInput(input);
6459
7921
  return {
6460
7922
  fs,
6461
7923
  context,
6462
- input
7924
+ input,
7925
+ io: {
7926
+ stderr: context.stderr,
7927
+ stdin,
7928
+ stdout: new BufferedShellOutput()
7929
+ },
7930
+ stdin
6463
7931
  };
6464
7932
  }
6465
7933
  function formatLongListing(path, stat) {
@@ -6525,7 +7993,7 @@ CommandRegistry.register("cat", {
6525
7993
  context.status = 0;
6526
7994
  return;
6527
7995
  }
6528
- if (input) yield* cat(fs, options)(input);
7996
+ if (input) yield* cat(fs, options)(toFormattedLineStream(input));
6529
7997
  context.status = 0;
6530
7998
  })();
6531
7999
  }
@@ -6540,7 +8008,8 @@ CommandRegistry.register("grep", {
6540
8008
  input,
6541
8009
  parsed: step.args,
6542
8010
  redirections: step.redirections,
6543
- resolvedOutputRedirectPath
8011
+ resolvedOutputRedirectPath,
8012
+ stdin: createShellInput(input)
6544
8013
  });
6545
8014
  context.status = result.exitCode;
6546
8015
  context.stderr.appendLines(result.stderr);
@@ -6566,7 +8035,8 @@ CommandRegistry.register("xargs", {
6566
8035
  fs,
6567
8036
  input,
6568
8037
  inputPath: await resolveRedirectPath(step.cmd, step.redirections, "input", fs, context),
6569
- parsed: step.args
8038
+ parsed: step.args,
8039
+ stdin: createShellInput(input)
6570
8040
  });
6571
8041
  context.status = result.exitCode;
6572
8042
  context.stderr.appendLines(result.stderr);
@@ -6577,6 +8047,70 @@ CommandRegistry.register("xargs", {
6577
8047
  })();
6578
8048
  }
6579
8049
  });
8050
+ CommandRegistry.register("wc", {
8051
+ kind: "stream",
8052
+ handler: ({ step, fs, input, context }) => {
8053
+ return (async function* () {
8054
+ const result = await runWcCommand({
8055
+ context,
8056
+ fs,
8057
+ input,
8058
+ inputPath: await resolveRedirectPath(step.cmd, step.redirections, "input", fs, context),
8059
+ parsed: step.args,
8060
+ stdin: createShellInput(input)
8061
+ });
8062
+ context.status = result.exitCode;
8063
+ context.stderr.appendLines(result.stderr);
8064
+ for (const text of result.stdout) yield {
8065
+ kind: "line",
8066
+ text
8067
+ };
8068
+ })();
8069
+ }
8070
+ });
8071
+ CommandRegistry.register("sort", {
8072
+ kind: "stream",
8073
+ handler: ({ step, fs, input, context }) => {
8074
+ return (async function* () {
8075
+ const result = await runSortCommand({
8076
+ context,
8077
+ fs,
8078
+ input,
8079
+ inputPath: await resolveRedirectPath(step.cmd, step.redirections, "input", fs, context),
8080
+ parsed: step.args,
8081
+ stdin: createShellInput(input)
8082
+ });
8083
+ context.status = result.exitCode;
8084
+ context.stderr.appendLines(result.stderr);
8085
+ for (const text of result.stdout) yield {
8086
+ kind: "line",
8087
+ text
8088
+ };
8089
+ })();
8090
+ }
8091
+ });
8092
+ CommandRegistry.register("tree", {
8093
+ kind: "stream",
8094
+ handler: ({ step, fs, context }) => {
8095
+ return (async function* () {
8096
+ const paths = resolvePathsFromCwd(context.cwd, await evaluateExpandedPathWords("tree", step.args.paths, fs, context));
8097
+ const includePatterns = await evaluateExpandedWords(step.args.includePatterns, fs, context);
8098
+ const excludePatterns = await evaluateExpandedWords(step.args.excludePatterns, fs, context);
8099
+ const result = await runTreeCommand(fs, context.cwd, createTreeResolvedArgs({
8100
+ ...step.args,
8101
+ excludePatterns,
8102
+ includePatterns,
8103
+ paths
8104
+ }));
8105
+ context.status = result.exitCode;
8106
+ context.stderr.appendLines(result.stderr);
8107
+ for (const text of result.stdout) yield {
8108
+ kind: "line",
8109
+ text
8110
+ };
8111
+ })();
8112
+ }
8113
+ });
6580
8114
  CommandRegistry.register("head", {
6581
8115
  kind: "stream",
6582
8116
  handler: ({ step, fs, input, context }) => {
@@ -6961,11 +8495,13 @@ function getTargetFd(redirection) {
6961
8495
  }
6962
8496
  async function resolveFileDestination(command, redirection, fs, context) {
6963
8497
  const targetPath = await evaluateExpandedSinglePath(command, "redirection target must expand to exactly 1 path", redirection.target, fs, context);
8498
+ const resolvedPath = resolvePathFromCwd(context.cwd, targetPath);
8499
+ if (isNullDevicePath(resolvedPath)) return { kind: "nullDevice" };
6964
8500
  return {
6965
8501
  kind: "file",
6966
8502
  append: redirection.append ?? false,
6967
8503
  noclobber: redirection.noclobber ?? false,
6968
- path: resolvePathFromCwd(context.cwd, targetPath)
8504
+ path: resolvedPath
6969
8505
  };
6970
8506
  }
6971
8507
  function destinationForFd(routing, fd, _isLastStep) {
@@ -7125,6 +8661,7 @@ async function routeStdout(stdoutRecords, stdoutText, destination, hasNextStep,
7125
8661
  });
7126
8662
  return;
7127
8663
  case "closed": return;
8664
+ case "nullDevice": return;
7128
8665
  default: {
7129
8666
  const _exhaustive = destination;
7130
8667
  throw new Error(`Unknown stdout destination: ${_exhaustive}`);
@@ -7157,6 +8694,7 @@ async function routeStderr(stderrLines, stderrText, destination, hasNextStep, pi
7157
8694
  });
7158
8695
  return;
7159
8696
  case "closed": return;
8697
+ case "nullDevice": return;
7160
8698
  default: {
7161
8699
  const _exhaustive = destination;
7162
8700
  throw new Error(`Unknown stderr destination: ${_exhaustive}`);
@@ -7166,4 +8704,4 @@ async function routeStderr(stderrLines, stderrText, destination, hasNextStep, pi
7166
8704
  //#endregion
7167
8705
  export { BufferedOutputStream as a, ParseSyntaxError as c, writeDiagnosticsToStderr as i, compile as l, execute_exports as n, formatStderr as o, isShellDiagnosticError as r, formatStdoutRecord as s, execute as t, parse as u };
7168
8706
 
7169
- //# sourceMappingURL=execute-D1oWUkmO.mjs.map
8707
+ //# sourceMappingURL=execute-BinccNiS.mjs.map