shfs 0.3.1 → 0.3.3
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.
|
@@ -636,7 +636,8 @@ function compileCat(cmd) {
|
|
|
636
636
|
if (arg !== void 0) fileArgs.push(arg);
|
|
637
637
|
}
|
|
638
638
|
const hasInputRedirection = cmd.redirections.some((redirection) => redirection.kind === "input");
|
|
639
|
-
|
|
639
|
+
const hasOutputRedirection = cmd.redirections.some((redirection) => redirection.kind === "output");
|
|
640
|
+
if (fileArgs.length === 0 && !hasInputRedirection && !hasOutputRedirection) throw new Error("cat requires at least one file");
|
|
640
641
|
return {
|
|
641
642
|
cmd: "cat",
|
|
642
643
|
args: {
|
|
@@ -654,7 +655,7 @@ function compileCat(cmd) {
|
|
|
654
655
|
/**
|
|
655
656
|
* cd command handler for the AST-based compiler.
|
|
656
657
|
*/
|
|
657
|
-
const ROOT_DIRECTORY$
|
|
658
|
+
const ROOT_DIRECTORY$4 = "/";
|
|
658
659
|
/**
|
|
659
660
|
* Compile a cd command from SimpleCommandIR to StepIR.
|
|
660
661
|
*/
|
|
@@ -663,7 +664,7 @@ function compileCd(cmd) {
|
|
|
663
664
|
if (positionalArgs.length > 1) throw new Error("cd accepts at most one path");
|
|
664
665
|
return {
|
|
665
666
|
cmd: "cd",
|
|
666
|
-
args: { path: positionalArgs[0] ?? literal(ROOT_DIRECTORY$
|
|
667
|
+
args: { path: positionalArgs[0] ?? literal(ROOT_DIRECTORY$4) }
|
|
667
668
|
};
|
|
668
669
|
}
|
|
669
670
|
/**
|
|
@@ -782,6 +783,15 @@ const DEFAULT_TRAVERSAL = {
|
|
|
782
783
|
mindepth: 0
|
|
783
784
|
};
|
|
784
785
|
const NON_NEGATIVE_INTEGER_REGEX = /^\d+$/;
|
|
786
|
+
const PATTERN_PREDICATES = new Set([
|
|
787
|
+
"-name",
|
|
788
|
+
"-path",
|
|
789
|
+
"-ipath",
|
|
790
|
+
"-wholename",
|
|
791
|
+
"-iwholename",
|
|
792
|
+
"-regex",
|
|
793
|
+
"-iregex"
|
|
794
|
+
]);
|
|
785
795
|
function compileFind(command) {
|
|
786
796
|
return {
|
|
787
797
|
cmd: "find",
|
|
@@ -834,8 +844,19 @@ function findPredicateStartIndex(argv) {
|
|
|
834
844
|
return argv.length;
|
|
835
845
|
}
|
|
836
846
|
function parseFindToken(argv, index, token, state) {
|
|
837
|
-
if (token
|
|
847
|
+
if (isPatternPredicateToken(token)) return parsePatternPredicate(argv, index, token, state);
|
|
838
848
|
if (token === "-type") return parseTypePredicate(argv, index, state);
|
|
849
|
+
if (token === "-true" || token === "-false") {
|
|
850
|
+
state.currentBranch.push({
|
|
851
|
+
kind: "constant",
|
|
852
|
+
value: token === "-true"
|
|
853
|
+
});
|
|
854
|
+
return index + 1;
|
|
855
|
+
}
|
|
856
|
+
if (token === "-empty") {
|
|
857
|
+
state.currentBranch.push({ kind: "empty" });
|
|
858
|
+
return index + 1;
|
|
859
|
+
}
|
|
839
860
|
if (token === "-o" || token === "-or") return parseOrPredicateSeparator(index, state);
|
|
840
861
|
if (token === "-maxdepth" || token === "-mindepth") return parseTraversalOption(argv, index, token, state);
|
|
841
862
|
if (token === "-depth") {
|
|
@@ -855,6 +876,9 @@ function parseFindToken(argv, index, token, state) {
|
|
|
855
876
|
state.diagnostics.push(createDiagnostic$1("unexpected-operand", token, index, `find: unexpected argument: ${token}`));
|
|
856
877
|
return index + 1;
|
|
857
878
|
}
|
|
879
|
+
function isPatternPredicateToken(token) {
|
|
880
|
+
return PATTERN_PREDICATES.has(token);
|
|
881
|
+
}
|
|
858
882
|
function parseOrPredicateSeparator(index, state) {
|
|
859
883
|
state.sawOr = true;
|
|
860
884
|
state.lastOrTokenIndex = index;
|
|
@@ -867,16 +891,43 @@ function parseOrPredicateSeparator(index, state) {
|
|
|
867
891
|
state.currentSideAllowsEmptyBranch = false;
|
|
868
892
|
return index + 1;
|
|
869
893
|
}
|
|
870
|
-
function
|
|
894
|
+
function parsePatternPredicate(argv, index, token, state) {
|
|
871
895
|
const valueWord = argv[index + 1];
|
|
872
896
|
if (!valueWord) {
|
|
873
897
|
state.diagnostics.push(createMissingValueDiagnostic(token, index));
|
|
874
898
|
return index + 1;
|
|
875
899
|
}
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
900
|
+
switch (token) {
|
|
901
|
+
case "-name":
|
|
902
|
+
state.currentBranch.push({
|
|
903
|
+
kind: "name",
|
|
904
|
+
pattern: valueWord
|
|
905
|
+
});
|
|
906
|
+
break;
|
|
907
|
+
case "-path":
|
|
908
|
+
case "-wholename":
|
|
909
|
+
state.currentBranch.push({
|
|
910
|
+
kind: "path",
|
|
911
|
+
pattern: valueWord
|
|
912
|
+
});
|
|
913
|
+
break;
|
|
914
|
+
case "-ipath":
|
|
915
|
+
case "-iwholename":
|
|
916
|
+
state.currentBranch.push({
|
|
917
|
+
kind: "ipath",
|
|
918
|
+
pattern: valueWord
|
|
919
|
+
});
|
|
920
|
+
break;
|
|
921
|
+
case "-regex":
|
|
922
|
+
case "-iregex":
|
|
923
|
+
state.currentBranch.push({
|
|
924
|
+
kind: "regex",
|
|
925
|
+
caseInsensitive: token === "-iregex",
|
|
926
|
+
pattern: valueWord
|
|
927
|
+
});
|
|
928
|
+
break;
|
|
929
|
+
default: return token;
|
|
930
|
+
}
|
|
880
931
|
return index + 2;
|
|
881
932
|
}
|
|
882
933
|
function parseTypePredicate(argv, index, state) {
|
|
@@ -1810,6 +1861,276 @@ function compileTouch(cmd) {
|
|
|
1810
1861
|
}
|
|
1811
1862
|
};
|
|
1812
1863
|
}
|
|
1864
|
+
const DEFAULT_TOTAL_MODE = "auto";
|
|
1865
|
+
function compileWc(command) {
|
|
1866
|
+
return {
|
|
1867
|
+
cmd: "wc",
|
|
1868
|
+
args: parseWcArgs(command.args)
|
|
1869
|
+
};
|
|
1870
|
+
}
|
|
1871
|
+
function parseWcArgs(argv) {
|
|
1872
|
+
const args = {
|
|
1873
|
+
bytes: false,
|
|
1874
|
+
chars: false,
|
|
1875
|
+
files: [],
|
|
1876
|
+
files0From: null,
|
|
1877
|
+
lines: false,
|
|
1878
|
+
maxLineLength: false,
|
|
1879
|
+
total: DEFAULT_TOTAL_MODE,
|
|
1880
|
+
words: false
|
|
1881
|
+
};
|
|
1882
|
+
for (let index = 0; index < argv.length; index++) {
|
|
1883
|
+
const word = argv[index];
|
|
1884
|
+
if (!word) continue;
|
|
1885
|
+
const token = expandedWordToString(word);
|
|
1886
|
+
if (token === "--") {
|
|
1887
|
+
args.files.push(...argv.slice(index + 1));
|
|
1888
|
+
break;
|
|
1889
|
+
}
|
|
1890
|
+
if (!token.startsWith("-") || token === "-") {
|
|
1891
|
+
args.files.push(word);
|
|
1892
|
+
continue;
|
|
1893
|
+
}
|
|
1894
|
+
const parsed = parseLongOption(argv, index, token, args);
|
|
1895
|
+
if (parsed.matched) {
|
|
1896
|
+
index = parsed.nextIndex - 1;
|
|
1897
|
+
continue;
|
|
1898
|
+
}
|
|
1899
|
+
parseShortOptions(token, args);
|
|
1900
|
+
}
|
|
1901
|
+
return args;
|
|
1902
|
+
}
|
|
1903
|
+
function parseLongOption(argv, index, token, args) {
|
|
1904
|
+
if (token === "--bytes") {
|
|
1905
|
+
args.bytes = true;
|
|
1906
|
+
return {
|
|
1907
|
+
matched: true,
|
|
1908
|
+
nextIndex: index + 1
|
|
1909
|
+
};
|
|
1910
|
+
}
|
|
1911
|
+
if (token === "--chars") {
|
|
1912
|
+
args.chars = true;
|
|
1913
|
+
return {
|
|
1914
|
+
matched: true,
|
|
1915
|
+
nextIndex: index + 1
|
|
1916
|
+
};
|
|
1917
|
+
}
|
|
1918
|
+
if (token === "--lines") {
|
|
1919
|
+
args.lines = true;
|
|
1920
|
+
return {
|
|
1921
|
+
matched: true,
|
|
1922
|
+
nextIndex: index + 1
|
|
1923
|
+
};
|
|
1924
|
+
}
|
|
1925
|
+
if (token === "--words") {
|
|
1926
|
+
args.words = true;
|
|
1927
|
+
return {
|
|
1928
|
+
matched: true,
|
|
1929
|
+
nextIndex: index + 1
|
|
1930
|
+
};
|
|
1931
|
+
}
|
|
1932
|
+
if (token === "--max-line-length") {
|
|
1933
|
+
args.maxLineLength = true;
|
|
1934
|
+
return {
|
|
1935
|
+
matched: true,
|
|
1936
|
+
nextIndex: index + 1
|
|
1937
|
+
};
|
|
1938
|
+
}
|
|
1939
|
+
if (token.startsWith("--files0-from=")) {
|
|
1940
|
+
args.files0From = {
|
|
1941
|
+
kind: "literal",
|
|
1942
|
+
value: token.slice(14)
|
|
1943
|
+
};
|
|
1944
|
+
return {
|
|
1945
|
+
matched: true,
|
|
1946
|
+
nextIndex: index + 1
|
|
1947
|
+
};
|
|
1948
|
+
}
|
|
1949
|
+
if (token === "--files0-from") {
|
|
1950
|
+
const value = argv[index + 1];
|
|
1951
|
+
if (!value) throw new Error("wc: option --files0-from requires an argument");
|
|
1952
|
+
args.files0From = value;
|
|
1953
|
+
return {
|
|
1954
|
+
matched: true,
|
|
1955
|
+
nextIndex: index + 2
|
|
1956
|
+
};
|
|
1957
|
+
}
|
|
1958
|
+
if (token.startsWith("--total=")) {
|
|
1959
|
+
args.total = parseTotalMode(token.slice(8));
|
|
1960
|
+
return {
|
|
1961
|
+
matched: true,
|
|
1962
|
+
nextIndex: index + 1
|
|
1963
|
+
};
|
|
1964
|
+
}
|
|
1965
|
+
if (token === "--total") {
|
|
1966
|
+
args.total = "invalid";
|
|
1967
|
+
return {
|
|
1968
|
+
matched: true,
|
|
1969
|
+
nextIndex: index + 1
|
|
1970
|
+
};
|
|
1971
|
+
}
|
|
1972
|
+
return {
|
|
1973
|
+
matched: false,
|
|
1974
|
+
nextIndex: index
|
|
1975
|
+
};
|
|
1976
|
+
}
|
|
1977
|
+
function parseShortOptions(token, args) {
|
|
1978
|
+
for (const option of token.slice(1)) switch (option) {
|
|
1979
|
+
case "c":
|
|
1980
|
+
args.bytes = true;
|
|
1981
|
+
break;
|
|
1982
|
+
case "m":
|
|
1983
|
+
args.chars = true;
|
|
1984
|
+
break;
|
|
1985
|
+
case "l":
|
|
1986
|
+
args.lines = true;
|
|
1987
|
+
break;
|
|
1988
|
+
case "w":
|
|
1989
|
+
args.words = true;
|
|
1990
|
+
break;
|
|
1991
|
+
case "L":
|
|
1992
|
+
args.maxLineLength = true;
|
|
1993
|
+
break;
|
|
1994
|
+
default: throw new Error(`wc: unknown option -- ${option}`);
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
function parseTotalMode(value) {
|
|
1998
|
+
if (value === "auto" || value === "always" || value === "never" || value === "only") return value;
|
|
1999
|
+
throw new Error(`wc: invalid --total value: ${value}`);
|
|
2000
|
+
}
|
|
2001
|
+
const DEFAULT_COMMAND = [literal("echo")];
|
|
2002
|
+
const NUL_DELIMITER = "\0";
|
|
2003
|
+
function compileXargs(command) {
|
|
2004
|
+
return {
|
|
2005
|
+
cmd: "xargs",
|
|
2006
|
+
args: parseXargsArgs(command.args)
|
|
2007
|
+
};
|
|
2008
|
+
}
|
|
2009
|
+
function parseXargsArgs(argv) {
|
|
2010
|
+
const args = {
|
|
2011
|
+
command: DEFAULT_COMMAND,
|
|
2012
|
+
delimiter: null,
|
|
2013
|
+
eof: null,
|
|
2014
|
+
maxArgs: null,
|
|
2015
|
+
maxLines: null,
|
|
2016
|
+
noRunIfEmpty: false,
|
|
2017
|
+
replace: null
|
|
2018
|
+
};
|
|
2019
|
+
let index = 0;
|
|
2020
|
+
while (index < argv.length) {
|
|
2021
|
+
const word = argv[index];
|
|
2022
|
+
if (!word) break;
|
|
2023
|
+
const token = expandedWordToString(word);
|
|
2024
|
+
const parsed = parseOption(argv, index, token, args);
|
|
2025
|
+
if (!parsed.matched) break;
|
|
2026
|
+
index = parsed.nextIndex;
|
|
2027
|
+
}
|
|
2028
|
+
const commandWords = argv.slice(index);
|
|
2029
|
+
if (commandWords.length > 0) args.command = commandWords;
|
|
2030
|
+
return args;
|
|
2031
|
+
}
|
|
2032
|
+
function parseOption(argv, index, token, args) {
|
|
2033
|
+
if (token === "--") return {
|
|
2034
|
+
matched: true,
|
|
2035
|
+
nextIndex: index + 1
|
|
2036
|
+
};
|
|
2037
|
+
if (token === "-0" || token === "--null") {
|
|
2038
|
+
args.delimiter = NUL_DELIMITER;
|
|
2039
|
+
args.eof = null;
|
|
2040
|
+
return {
|
|
2041
|
+
matched: true,
|
|
2042
|
+
nextIndex: index + 1
|
|
2043
|
+
};
|
|
2044
|
+
}
|
|
2045
|
+
if (token === "-r" || token === "--no-run-if-empty") {
|
|
2046
|
+
args.noRunIfEmpty = true;
|
|
2047
|
+
return {
|
|
2048
|
+
matched: true,
|
|
2049
|
+
nextIndex: index + 1
|
|
2050
|
+
};
|
|
2051
|
+
}
|
|
2052
|
+
if (token === "-n" || token.startsWith("-n")) {
|
|
2053
|
+
const value = optionValue(argv, index, token, "-n");
|
|
2054
|
+
setMaxArgsMode(args, parsePositiveInteger(value.value, "-n"));
|
|
2055
|
+
return {
|
|
2056
|
+
matched: true,
|
|
2057
|
+
nextIndex: value.nextIndex
|
|
2058
|
+
};
|
|
2059
|
+
}
|
|
2060
|
+
if (token === "-L" || token.startsWith("-L")) {
|
|
2061
|
+
const value = optionValue(argv, index, token, "-L");
|
|
2062
|
+
setMaxLinesMode(args, parsePositiveInteger(value.value, "-L"));
|
|
2063
|
+
return {
|
|
2064
|
+
matched: true,
|
|
2065
|
+
nextIndex: value.nextIndex
|
|
2066
|
+
};
|
|
2067
|
+
}
|
|
2068
|
+
if (token === "-E" || token.startsWith("-E")) {
|
|
2069
|
+
const value = optionValue(argv, index, token, "-E");
|
|
2070
|
+
args.eof = value.value;
|
|
2071
|
+
return {
|
|
2072
|
+
matched: true,
|
|
2073
|
+
nextIndex: value.nextIndex
|
|
2074
|
+
};
|
|
2075
|
+
}
|
|
2076
|
+
if (token === "-I" || token.startsWith("-I")) {
|
|
2077
|
+
const value = optionValue(argv, index, token, "-I");
|
|
2078
|
+
setReplaceMode(args, value.value);
|
|
2079
|
+
return {
|
|
2080
|
+
matched: true,
|
|
2081
|
+
nextIndex: value.nextIndex
|
|
2082
|
+
};
|
|
2083
|
+
}
|
|
2084
|
+
if (token === "-d" || token.startsWith("-d")) {
|
|
2085
|
+
const value = optionValue(argv, index, token, "-d");
|
|
2086
|
+
args.delimiter = decodeDelimiter(value.value);
|
|
2087
|
+
args.eof = null;
|
|
2088
|
+
return {
|
|
2089
|
+
matched: true,
|
|
2090
|
+
nextIndex: value.nextIndex
|
|
2091
|
+
};
|
|
2092
|
+
}
|
|
2093
|
+
return {
|
|
2094
|
+
matched: false,
|
|
2095
|
+
nextIndex: index
|
|
2096
|
+
};
|
|
2097
|
+
}
|
|
2098
|
+
function setMaxArgsMode(args, maxArgs) {
|
|
2099
|
+
args.maxArgs = maxArgs;
|
|
2100
|
+
args.maxLines = null;
|
|
2101
|
+
args.replace = null;
|
|
2102
|
+
}
|
|
2103
|
+
function setMaxLinesMode(args, maxLines) {
|
|
2104
|
+
args.maxLines = maxLines;
|
|
2105
|
+
args.maxArgs = null;
|
|
2106
|
+
args.replace = null;
|
|
2107
|
+
}
|
|
2108
|
+
function setReplaceMode(args, replace) {
|
|
2109
|
+
args.replace = replace;
|
|
2110
|
+
args.maxLines = 1;
|
|
2111
|
+
args.maxArgs = null;
|
|
2112
|
+
}
|
|
2113
|
+
function optionValue(argv, index, token, option) {
|
|
2114
|
+
if (token.length > option.length) return {
|
|
2115
|
+
nextIndex: index + 1,
|
|
2116
|
+
value: token.slice(option.length)
|
|
2117
|
+
};
|
|
2118
|
+
const next = argv[index + 1];
|
|
2119
|
+
if (!next) throw new Error(`xargs: option ${option} requires a value`);
|
|
2120
|
+
return {
|
|
2121
|
+
nextIndex: index + 2,
|
|
2122
|
+
value: expandedWordToString(next)
|
|
2123
|
+
};
|
|
2124
|
+
}
|
|
2125
|
+
function parsePositiveInteger(value, option) {
|
|
2126
|
+
const parsed = Number.parseInt(value, 10);
|
|
2127
|
+
if (!Number.isInteger(parsed) || parsed < 1) throw new Error(`xargs: invalid value for ${option}: ${value}`);
|
|
2128
|
+
return parsed;
|
|
2129
|
+
}
|
|
2130
|
+
function decodeDelimiter(value) {
|
|
2131
|
+
if (value === "\\0") return NUL_DELIMITER;
|
|
2132
|
+
return value.at(0) ?? "";
|
|
2133
|
+
}
|
|
1813
2134
|
let CommandHandler;
|
|
1814
2135
|
(function(_CommandHandler) {
|
|
1815
2136
|
const handlers = {
|
|
@@ -1830,7 +2151,9 @@ let CommandHandler;
|
|
|
1830
2151
|
string: compileString,
|
|
1831
2152
|
tail: compileTail,
|
|
1832
2153
|
test: compileTest,
|
|
1833
|
-
touch: compileTouch
|
|
2154
|
+
touch: compileTouch,
|
|
2155
|
+
wc: compileWc,
|
|
2156
|
+
xargs: compileXargs
|
|
1834
2157
|
};
|
|
1835
2158
|
function get(name) {
|
|
1836
2159
|
const handler = handlers[name];
|
|
@@ -1920,6 +2243,12 @@ var ProgramCompiler = class {
|
|
|
1920
2243
|
compileRedirection(node) {
|
|
1921
2244
|
return {
|
|
1922
2245
|
kind: node.redirectKind,
|
|
2246
|
+
mode: node.mode,
|
|
2247
|
+
sourceFd: node.sourceFd,
|
|
2248
|
+
targetFd: node.targetFd,
|
|
2249
|
+
append: node.append,
|
|
2250
|
+
noclobber: node.noclobber,
|
|
2251
|
+
optional: node.optional,
|
|
1923
2252
|
target: this.expandWord(node.target)
|
|
1924
2253
|
};
|
|
1925
2254
|
}
|
|
@@ -1996,11 +2325,48 @@ var ProgramCompiler = class {
|
|
|
1996
2325
|
return program.statements.map((statement) => this.serializePipeline(statement.pipeline)).join("; ");
|
|
1997
2326
|
}
|
|
1998
2327
|
serializePipeline(pipeline) {
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
const
|
|
2002
|
-
|
|
2003
|
-
|
|
2328
|
+
const segments = [];
|
|
2329
|
+
for (let index = 0; index < pipeline.commands.length; index++) {
|
|
2330
|
+
const command = pipeline.commands[index];
|
|
2331
|
+
if (!command) continue;
|
|
2332
|
+
const hasNextCommand = index < pipeline.commands.length - 1;
|
|
2333
|
+
segments.push(this.serializeCommand(command, { omitPipeRedirections: hasNextCommand }));
|
|
2334
|
+
if (hasNextCommand) segments.push(this.serializePipelineOperator(command));
|
|
2335
|
+
}
|
|
2336
|
+
return segments.join(" ");
|
|
2337
|
+
}
|
|
2338
|
+
serializeCommand(command, options = {}) {
|
|
2339
|
+
const segments = [this.serializeWord(command.name)];
|
|
2340
|
+
for (const arg of command.args) segments.push(this.serializeWord(arg));
|
|
2341
|
+
for (const redirection of command.redirections) {
|
|
2342
|
+
if (options.omitPipeRedirections && redirection.mode === "pipe") continue;
|
|
2343
|
+
segments.push(this.serializeRedirection(redirection));
|
|
2344
|
+
}
|
|
2345
|
+
return segments.join(" ");
|
|
2346
|
+
}
|
|
2347
|
+
serializePipelineOperator(command) {
|
|
2348
|
+
const pipeRedirections = command.redirections.filter((redirection) => redirection.redirectKind === "output" && redirection.mode === "pipe");
|
|
2349
|
+
const pipesStdout = pipeRedirections.some((redirection) => redirection.sourceFd === 1);
|
|
2350
|
+
const pipesStderr = pipeRedirections.some((redirection) => redirection.sourceFd === 2);
|
|
2351
|
+
if (pipesStdout && pipesStderr) return "&|";
|
|
2352
|
+
const onlyPipeRedirection = pipeRedirections[0];
|
|
2353
|
+
if (pipeRedirections.length === 1 && onlyPipeRedirection) return this.serializeRedirection(onlyPipeRedirection);
|
|
2354
|
+
return "|";
|
|
2355
|
+
}
|
|
2356
|
+
serializeRedirection(redirection) {
|
|
2357
|
+
const sourceFd = redirection.sourceFd === (redirection.redirectKind === "input" ? 0 : 1) ? "" : String(redirection.sourceFd);
|
|
2358
|
+
if (redirection.redirectKind === "input") {
|
|
2359
|
+
const operator = redirection.optional ? "<?" : "<";
|
|
2360
|
+
if (redirection.mode === "fd") return `${sourceFd}<&${redirection.targetFd}`;
|
|
2361
|
+
if (redirection.mode === "close") return `${sourceFd}<&-`;
|
|
2362
|
+
return `${sourceFd}${operator}${this.serializeWord(redirection.target)}`;
|
|
2363
|
+
}
|
|
2364
|
+
if (redirection.mode === "pipe") return `${sourceFd}>|`;
|
|
2365
|
+
if (redirection.mode === "fd") return `${sourceFd}>&${redirection.targetFd}`;
|
|
2366
|
+
if (redirection.mode === "close") return `${sourceFd}>&-`;
|
|
2367
|
+
let operator = redirection.append ? ">>" : ">";
|
|
2368
|
+
if (redirection.noclobber) operator = `${operator}?`;
|
|
2369
|
+
return `${sourceFd}${operator}${this.serializeWord(redirection.target)}`;
|
|
2004
2370
|
}
|
|
2005
2371
|
serializeWord(word) {
|
|
2006
2372
|
return word.parts.map((part) => this.serializeWordPart(part)).join("");
|
|
@@ -2925,11 +3291,23 @@ var CommandSubPart = class {
|
|
|
2925
3291
|
*/
|
|
2926
3292
|
var Redirection = class extends ASTNode {
|
|
2927
3293
|
redirectKind;
|
|
3294
|
+
mode;
|
|
3295
|
+
sourceFd;
|
|
3296
|
+
targetFd;
|
|
3297
|
+
append;
|
|
3298
|
+
noclobber;
|
|
3299
|
+
optional;
|
|
2928
3300
|
target;
|
|
2929
|
-
constructor(span, redirectKind, target) {
|
|
3301
|
+
constructor(span, redirectKind, target, options = {}) {
|
|
2930
3302
|
super(span);
|
|
2931
3303
|
this.redirectKind = redirectKind;
|
|
2932
3304
|
this.target = target;
|
|
3305
|
+
this.mode = options.mode ?? "file";
|
|
3306
|
+
this.sourceFd = options.sourceFd ?? (redirectKind === "input" ? 0 : 1);
|
|
3307
|
+
this.targetFd = options.targetFd ?? null;
|
|
3308
|
+
this.append = options.append ?? false;
|
|
3309
|
+
this.noclobber = options.noclobber ?? false;
|
|
3310
|
+
this.optional = options.optional ?? false;
|
|
2933
3311
|
}
|
|
2934
3312
|
accept(visitor) {
|
|
2935
3313
|
return visitor.visitRedirection(this);
|
|
@@ -2942,6 +3320,7 @@ var Redirection = class extends ASTNode {
|
|
|
2942
3320
|
* - Simple commands (name + args)
|
|
2943
3321
|
* - Redirections (< > Phase 2)
|
|
2944
3322
|
*/
|
|
3323
|
+
const DIGITS_ONLY_REGEX = /^[0-9]+$/;
|
|
2945
3324
|
/**
|
|
2946
3325
|
* Parser for commands.
|
|
2947
3326
|
*
|
|
@@ -2990,40 +3369,118 @@ var CommandParser = class {
|
|
|
2990
3369
|
else break;
|
|
2991
3370
|
}
|
|
2992
3371
|
const endPos = this.parser.previousTokenPosition;
|
|
2993
|
-
|
|
3372
|
+
const span = new SourceSpan(startPos, endPos);
|
|
3373
|
+
const normalized = this.normalizeRedirectionPrefixes(args, redirections);
|
|
3374
|
+
return new SimpleCommand(span, name, normalized.args, normalized.redirections);
|
|
2994
3375
|
}
|
|
2995
3376
|
/**
|
|
2996
3377
|
* Parse a redirection if present.
|
|
2997
3378
|
*
|
|
2998
|
-
* Grammar:
|
|
3379
|
+
* Grammar (subset):
|
|
2999
3380
|
* redirection ::= '<' word | '>' word | '>>' word
|
|
3381
|
+
*
|
|
3382
|
+
* This parser also supports fish-inspired forms consumed by shfs specs:
|
|
3383
|
+
* <&3, <&-, <?file, >&2, >&-, 2>|, >?file, >>?file.
|
|
3000
3384
|
*/
|
|
3001
3385
|
parseRedirection() {
|
|
3002
3386
|
const token = this.parser.currentToken;
|
|
3003
|
-
if (token.kind === TokenKind.LESS)
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3387
|
+
if (token.kind === TokenKind.LESS) return this.parseInputRedirection(token);
|
|
3388
|
+
if (token.kind === TokenKind.GREAT) return this.parseOutputRedirection(token);
|
|
3389
|
+
return null;
|
|
3390
|
+
}
|
|
3391
|
+
parseInputRedirection(token) {
|
|
3392
|
+
const startPos = token.span.start;
|
|
3393
|
+
this.parser.advance();
|
|
3394
|
+
this.validateInputTargetPrefix();
|
|
3395
|
+
const parsedTarget = this.parseInputTargetAfterLess();
|
|
3396
|
+
const fdMode = this.parseFdMode(parsedTarget.target, "<&N or <&-");
|
|
3397
|
+
const endPos = this.parser.previousTokenPosition;
|
|
3398
|
+
return new Redirection(new SourceSpan(startPos, endPos), "input", parsedTarget.target, {
|
|
3399
|
+
mode: fdMode.mode,
|
|
3400
|
+
optional: parsedTarget.optional,
|
|
3401
|
+
targetFd: fdMode.targetFd
|
|
3402
|
+
});
|
|
3403
|
+
}
|
|
3404
|
+
parseOutputRedirection(token) {
|
|
3405
|
+
const startPos = token.span.start;
|
|
3406
|
+
this.parser.advance();
|
|
3407
|
+
const append = this.consumeAppendMarker();
|
|
3408
|
+
const noclobber = this.consumeNoclobberMarker();
|
|
3409
|
+
if (this.parser.currentToken.kind === TokenKind.PIPE) {
|
|
3410
|
+
const pipeToken = this.parser.currentToken;
|
|
3411
|
+
return new Redirection(new SourceSpan(startPos, this.parser.previousTokenPosition), "output", this.createLiteralWord("|", pipeToken.span), {
|
|
3412
|
+
append,
|
|
3413
|
+
mode: "pipe",
|
|
3414
|
+
noclobber
|
|
3415
|
+
});
|
|
3013
3416
|
}
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3417
|
+
const target = this.wordParser.parseWord();
|
|
3418
|
+
if (!target) this.parser.syntacticError("Expected filename after >", "word");
|
|
3419
|
+
const fdMode = this.parseFdMode(target, ">&N or >&-");
|
|
3420
|
+
const endPos = this.parser.previousTokenPosition;
|
|
3421
|
+
return new Redirection(new SourceSpan(startPos, endPos), "output", target, {
|
|
3422
|
+
append,
|
|
3423
|
+
mode: fdMode.mode,
|
|
3424
|
+
noclobber,
|
|
3425
|
+
targetFd: fdMode.targetFd
|
|
3426
|
+
});
|
|
3427
|
+
}
|
|
3428
|
+
validateInputTargetPrefix() {
|
|
3429
|
+
if (this.parser.currentToken.kind !== TokenKind.WORD) return;
|
|
3430
|
+
const spelling = this.parser.currentToken.spelling;
|
|
3431
|
+
if (spelling.startsWith("?&") || spelling.startsWith("&?")) this.parser.syntacticError("Invalid redirection target after <", "<path, <?path, <&N, or <&-");
|
|
3432
|
+
}
|
|
3433
|
+
parseInputTargetAfterLess() {
|
|
3434
|
+
let optional = false;
|
|
3435
|
+
let target = this.wordParser.parseWord();
|
|
3436
|
+
if (!target) this.parser.syntacticError("Expected filename after <", "word");
|
|
3437
|
+
const targetLiteral = target.literalValue;
|
|
3438
|
+
if (!targetLiteral?.startsWith("?")) return {
|
|
3439
|
+
optional,
|
|
3440
|
+
target
|
|
3441
|
+
};
|
|
3442
|
+
optional = true;
|
|
3443
|
+
if (targetLiteral === "?") {
|
|
3444
|
+
const explicitTarget = this.wordParser.parseWord();
|
|
3445
|
+
if (!explicitTarget) this.parser.syntacticError("Expected filename after <?", "word");
|
|
3446
|
+
target = explicitTarget;
|
|
3447
|
+
return {
|
|
3448
|
+
optional,
|
|
3449
|
+
target
|
|
3450
|
+
};
|
|
3025
3451
|
}
|
|
3026
|
-
|
|
3452
|
+
target = this.cloneLiteralWord(target, targetLiteral.slice(1));
|
|
3453
|
+
return {
|
|
3454
|
+
optional,
|
|
3455
|
+
target
|
|
3456
|
+
};
|
|
3457
|
+
}
|
|
3458
|
+
consumeAppendMarker() {
|
|
3459
|
+
if (this.parser.currentToken.kind !== TokenKind.GREAT) return false;
|
|
3460
|
+
this.parser.advance();
|
|
3461
|
+
return true;
|
|
3462
|
+
}
|
|
3463
|
+
consumeNoclobberMarker() {
|
|
3464
|
+
if (this.parser.currentToken.kind !== TokenKind.WORD || this.parser.currentToken.spelling !== "?") return false;
|
|
3465
|
+
this.parser.advance();
|
|
3466
|
+
return true;
|
|
3467
|
+
}
|
|
3468
|
+
parseFdMode(target, expected) {
|
|
3469
|
+
const targetLiteral = target.literalValue;
|
|
3470
|
+
if (!targetLiteral?.startsWith("&")) return {
|
|
3471
|
+
mode: "file",
|
|
3472
|
+
targetFd: null
|
|
3473
|
+
};
|
|
3474
|
+
const fdTarget = targetLiteral.slice(1);
|
|
3475
|
+
if (fdTarget === "-") return {
|
|
3476
|
+
mode: "close",
|
|
3477
|
+
targetFd: null
|
|
3478
|
+
};
|
|
3479
|
+
if (DIGITS_ONLY_REGEX.test(fdTarget)) return {
|
|
3480
|
+
mode: "fd",
|
|
3481
|
+
targetFd: Number(fdTarget)
|
|
3482
|
+
};
|
|
3483
|
+
this.parser.syntacticError("Invalid file descriptor duplication target", expected);
|
|
3027
3484
|
}
|
|
3028
3485
|
/**
|
|
3029
3486
|
* Check if current token terminates a command.
|
|
@@ -3032,6 +3489,71 @@ var CommandParser = class {
|
|
|
3032
3489
|
const kind = this.parser.currentToken.kind;
|
|
3033
3490
|
return kind === TokenKind.PIPE || kind === TokenKind.SEMICOLON || kind === TokenKind.NEWLINE || kind === TokenKind.EOF;
|
|
3034
3491
|
}
|
|
3492
|
+
cloneLiteralWord(word, literal) {
|
|
3493
|
+
return new Word(word.span, [new LiteralPart(word.span, literal)], word.quoted);
|
|
3494
|
+
}
|
|
3495
|
+
createLiteralWord(literal, span) {
|
|
3496
|
+
return new Word(span, [new LiteralPart(span, literal)]);
|
|
3497
|
+
}
|
|
3498
|
+
cloneRedirection(redirection, options) {
|
|
3499
|
+
return new Redirection(redirection.span, redirection.redirectKind, redirection.target, {
|
|
3500
|
+
append: redirection.append,
|
|
3501
|
+
mode: options.mode ?? redirection.mode,
|
|
3502
|
+
noclobber: redirection.noclobber,
|
|
3503
|
+
optional: redirection.optional,
|
|
3504
|
+
sourceFd: options.sourceFd ?? redirection.sourceFd,
|
|
3505
|
+
targetFd: redirection.targetFd
|
|
3506
|
+
});
|
|
3507
|
+
}
|
|
3508
|
+
normalizeRedirectionPrefixes(args, redirections) {
|
|
3509
|
+
if (args.length === 0 || redirections.length === 0) return {
|
|
3510
|
+
args,
|
|
3511
|
+
redirections
|
|
3512
|
+
};
|
|
3513
|
+
const consumedPrefixArgIndices = /* @__PURE__ */ new Set();
|
|
3514
|
+
const normalizedRedirections = [];
|
|
3515
|
+
for (const redirection of redirections) {
|
|
3516
|
+
const prefixArgIndex = this.findContiguousPrefixArgIndex(args, consumedPrefixArgIndices, redirection.span.start.offset);
|
|
3517
|
+
if (prefixArgIndex === null) {
|
|
3518
|
+
normalizedRedirections.push(redirection);
|
|
3519
|
+
continue;
|
|
3520
|
+
}
|
|
3521
|
+
const prefixArg = args[prefixArgIndex];
|
|
3522
|
+
if (prefixArg?.quoted) {
|
|
3523
|
+
normalizedRedirections.push(redirection);
|
|
3524
|
+
continue;
|
|
3525
|
+
}
|
|
3526
|
+
const prefixLiteral = prefixArg?.literalValue;
|
|
3527
|
+
if (!prefixLiteral) {
|
|
3528
|
+
normalizedRedirections.push(redirection);
|
|
3529
|
+
continue;
|
|
3530
|
+
}
|
|
3531
|
+
if (prefixLiteral === "&" && redirection.redirectKind === "output") {
|
|
3532
|
+
consumedPrefixArgIndices.add(prefixArgIndex);
|
|
3533
|
+
normalizedRedirections.push(this.cloneRedirection(redirection, { sourceFd: 1 }), this.cloneRedirection(redirection, { sourceFd: 2 }));
|
|
3534
|
+
continue;
|
|
3535
|
+
}
|
|
3536
|
+
if (DIGITS_ONLY_REGEX.test(prefixLiteral)) {
|
|
3537
|
+
consumedPrefixArgIndices.add(prefixArgIndex);
|
|
3538
|
+
normalizedRedirections.push(this.cloneRedirection(redirection, { sourceFd: Number(prefixLiteral) }));
|
|
3539
|
+
continue;
|
|
3540
|
+
}
|
|
3541
|
+
normalizedRedirections.push(redirection);
|
|
3542
|
+
}
|
|
3543
|
+
return {
|
|
3544
|
+
args: args.filter((_arg, index) => !consumedPrefixArgIndices.has(index)),
|
|
3545
|
+
redirections: normalizedRedirections
|
|
3546
|
+
};
|
|
3547
|
+
}
|
|
3548
|
+
findContiguousPrefixArgIndex(args, consumedPrefixArgIndices, redirectionStartOffset) {
|
|
3549
|
+
for (let index = args.length - 1; index >= 0; index--) {
|
|
3550
|
+
if (consumedPrefixArgIndices.has(index)) continue;
|
|
3551
|
+
const arg = args[index];
|
|
3552
|
+
if (!arg) continue;
|
|
3553
|
+
if (arg.span.end.offset === redirectionStartOffset) return index;
|
|
3554
|
+
}
|
|
3555
|
+
return null;
|
|
3556
|
+
}
|
|
3035
3557
|
};
|
|
3036
3558
|
/**
|
|
3037
3559
|
* Error reporter for the Fish subset parser.
|
|
@@ -3203,8 +3725,16 @@ var StatementParser = class {
|
|
|
3203
3725
|
if (!firstCommand) return null;
|
|
3204
3726
|
const commands = [firstCommand];
|
|
3205
3727
|
while (this.parser.currentToken.kind === TokenKind.PIPE) {
|
|
3728
|
+
const pipeToken = this.parser.currentToken;
|
|
3729
|
+
const previousCommand = commands.at(-1);
|
|
3730
|
+
if (previousCommand) {
|
|
3731
|
+
const rewrittenPreviousCommand = this.rewriteStderrPipeCommand(previousCommand, pipeToken.span.start.offset, pipeToken.span);
|
|
3732
|
+
if (rewrittenPreviousCommand !== previousCommand) commands[commands.length - 1] = rewrittenPreviousCommand;
|
|
3733
|
+
}
|
|
3206
3734
|
this.parser.advance();
|
|
3207
3735
|
this.skipNewlines();
|
|
3736
|
+
const tokenAfterPipe = this.parser.currentToken;
|
|
3737
|
+
if (tokenAfterPipe.kind === TokenKind.WORD && tokenAfterPipe.spelling === "&") this.parser.syntacticError("Invalid fish pipeline operator", "command after | (|& is unsupported; use &|)");
|
|
3208
3738
|
const command = this.commandParser.parseCommand();
|
|
3209
3739
|
if (!command) {
|
|
3210
3740
|
this.parser.syntacticError("Expected command after |", "command");
|
|
@@ -3245,6 +3775,27 @@ var StatementParser = class {
|
|
|
3245
3775
|
isChainKeyword(spelling) {
|
|
3246
3776
|
return spelling === "and" || spelling === "or";
|
|
3247
3777
|
}
|
|
3778
|
+
rewriteStderrPipeCommand(command, pipeStartOffset, pipeSpan) {
|
|
3779
|
+
const trailingArg = command.args.at(-1);
|
|
3780
|
+
if (!(trailingArg && !trailingArg.quoted && trailingArg.literalValue === "&" && trailingArg.span.end.offset === pipeStartOffset)) return command;
|
|
3781
|
+
const updatedArgs = command.args.slice(0, -1);
|
|
3782
|
+
const pipeTarget = this.createLiteralWord("|", pipeSpan);
|
|
3783
|
+
const updatedRedirections = [
|
|
3784
|
+
...command.redirections,
|
|
3785
|
+
new Redirection(pipeSpan, "output", pipeTarget, {
|
|
3786
|
+
mode: "pipe",
|
|
3787
|
+
sourceFd: 1
|
|
3788
|
+
}),
|
|
3789
|
+
new Redirection(pipeSpan, "output", pipeTarget, {
|
|
3790
|
+
mode: "pipe",
|
|
3791
|
+
sourceFd: 2
|
|
3792
|
+
})
|
|
3793
|
+
];
|
|
3794
|
+
return new SimpleCommand(command.span, command.name, updatedArgs, updatedRedirections);
|
|
3795
|
+
}
|
|
3796
|
+
createLiteralWord(literal, span) {
|
|
3797
|
+
return new Word(span, [new LiteralPart(span, literal)]);
|
|
3798
|
+
}
|
|
3248
3799
|
};
|
|
3249
3800
|
/**
|
|
3250
3801
|
* Syntax error exception for the Fish subset parser.
|
|
@@ -3600,8 +4151,20 @@ function formatStdoutRecord(record) {
|
|
|
3600
4151
|
}
|
|
3601
4152
|
//#endregion
|
|
3602
4153
|
//#region src/stderr.ts
|
|
4154
|
+
var BufferedOutputStream = class {
|
|
4155
|
+
lines = [];
|
|
4156
|
+
append(line) {
|
|
4157
|
+
this.lines.push(line);
|
|
4158
|
+
}
|
|
4159
|
+
appendLines(lines) {
|
|
4160
|
+
for (const line of lines) this.append(line);
|
|
4161
|
+
}
|
|
4162
|
+
snapshot() {
|
|
4163
|
+
return [...this.lines];
|
|
4164
|
+
}
|
|
4165
|
+
};
|
|
3603
4166
|
function appendStderrLines(context, lines) {
|
|
3604
|
-
context.stderr.
|
|
4167
|
+
context.stderr.appendLines(lines);
|
|
3605
4168
|
}
|
|
3606
4169
|
function formatStderr(lines) {
|
|
3607
4170
|
return lines.join("\n");
|
|
@@ -3613,7 +4176,7 @@ var ShellDiagnosticError = class extends Error {
|
|
|
3613
4176
|
diagnostics;
|
|
3614
4177
|
exitCode;
|
|
3615
4178
|
constructor(diagnostics, exitCode = exitCodeForDiagnostics(diagnostics)) {
|
|
3616
|
-
super(diagnostics.map((diagnostic) => toErrorMessage(diagnostic)).join("\n"));
|
|
4179
|
+
super(diagnostics.map((diagnostic) => toErrorMessage$1(diagnostic)).join("\n"));
|
|
3617
4180
|
this.name = "ShellDiagnosticError";
|
|
3618
4181
|
this.diagnostics = diagnostics;
|
|
3619
4182
|
this.exitCode = exitCode;
|
|
@@ -3665,7 +4228,7 @@ function exitCodeForDiagnostic(diagnostic) {
|
|
|
3665
4228
|
if (diagnostic.phase === "compile" && diagnostic.location.command === "grep") return 2;
|
|
3666
4229
|
return 1;
|
|
3667
4230
|
}
|
|
3668
|
-
function toErrorMessage(diagnostic) {
|
|
4231
|
+
function toErrorMessage$1(diagnostic) {
|
|
3669
4232
|
const command = diagnostic.location.command;
|
|
3670
4233
|
const path = diagnostic.location.path;
|
|
3671
4234
|
if (command && path) return `${command}: ${path}: ${diagnostic.message}`;
|
|
@@ -3675,7 +4238,7 @@ function toErrorMessage(diagnostic) {
|
|
|
3675
4238
|
//#endregion
|
|
3676
4239
|
//#region src/execute/path.ts
|
|
3677
4240
|
const MULTIPLE_SLASH_REGEX$2 = /\/+/g;
|
|
3678
|
-
const ROOT_DIRECTORY$
|
|
4241
|
+
const ROOT_DIRECTORY$3 = "/";
|
|
3679
4242
|
const TRAILING_SLASH_REGEX$2 = /\/+$/;
|
|
3680
4243
|
const VARIABLE_REFERENCE_REGEX = /\$([A-Za-z_][A-Za-z0-9_]*)/g;
|
|
3681
4244
|
const NO_GLOB_MATCH_MESSAGE = "no matches found";
|
|
@@ -3702,7 +4265,7 @@ function expandVariables(input, context) {
|
|
|
3702
4265
|
});
|
|
3703
4266
|
}
|
|
3704
4267
|
function normalizeAbsolutePath(path) {
|
|
3705
|
-
const segments = (path.startsWith(ROOT_DIRECTORY$
|
|
4268
|
+
const segments = (path.startsWith(ROOT_DIRECTORY$3) ? path : `${ROOT_DIRECTORY$3}${path}`).replace(MULTIPLE_SLASH_REGEX$2, "/").split(ROOT_DIRECTORY$3);
|
|
3706
4269
|
const normalizedSegments = [];
|
|
3707
4270
|
for (const segment of segments) {
|
|
3708
4271
|
if (segment === "" || segment === ".") continue;
|
|
@@ -3712,17 +4275,17 @@ function normalizeAbsolutePath(path) {
|
|
|
3712
4275
|
}
|
|
3713
4276
|
normalizedSegments.push(segment);
|
|
3714
4277
|
}
|
|
3715
|
-
const normalizedPath = `${ROOT_DIRECTORY$
|
|
3716
|
-
return normalizedPath === "" ? ROOT_DIRECTORY$
|
|
4278
|
+
const normalizedPath = `${ROOT_DIRECTORY$3}${normalizedSegments.join(ROOT_DIRECTORY$3)}`;
|
|
4279
|
+
return normalizedPath === "" ? ROOT_DIRECTORY$3 : normalizedPath;
|
|
3717
4280
|
}
|
|
3718
4281
|
function normalizeCwd(cwd) {
|
|
3719
|
-
if (cwd === "") return ROOT_DIRECTORY$
|
|
4282
|
+
if (cwd === "") return ROOT_DIRECTORY$3;
|
|
3720
4283
|
const trimmed = normalizeAbsolutePath(cwd).replace(TRAILING_SLASH_REGEX$2, "");
|
|
3721
|
-
return trimmed === "" ? ROOT_DIRECTORY$
|
|
4284
|
+
return trimmed === "" ? ROOT_DIRECTORY$3 : trimmed;
|
|
3722
4285
|
}
|
|
3723
4286
|
function resolvePathFromCwd(cwd, path) {
|
|
3724
4287
|
if (path === "") return cwd;
|
|
3725
|
-
if (path.startsWith(ROOT_DIRECTORY$
|
|
4288
|
+
if (path.startsWith(ROOT_DIRECTORY$3)) return normalizeAbsolutePath(path);
|
|
3726
4289
|
return normalizeAbsolutePath(`${cwd}/${path}`);
|
|
3727
4290
|
}
|
|
3728
4291
|
function resolvePathsFromCwd(cwd, paths) {
|
|
@@ -3737,7 +4300,7 @@ async function readDirectoryPaths(fs, directoryPath) {
|
|
|
3737
4300
|
children.sort((left, right) => left.localeCompare(right));
|
|
3738
4301
|
return children;
|
|
3739
4302
|
}
|
|
3740
|
-
async function walkFilesystemEntries(fs, rootDir = ROOT_DIRECTORY$
|
|
4303
|
+
async function walkFilesystemEntries(fs, rootDir = ROOT_DIRECTORY$3) {
|
|
3741
4304
|
const normalizedRoot = normalizeAbsolutePath(rootDir);
|
|
3742
4305
|
if (!(await fs.stat(normalizedRoot)).isDirectory) throw new Error(`Not a directory: ${normalizedRoot}`);
|
|
3743
4306
|
const entries = [];
|
|
@@ -3759,12 +4322,12 @@ async function walkFilesystemEntries(fs, rootDir = ROOT_DIRECTORY$2) {
|
|
|
3759
4322
|
return entries;
|
|
3760
4323
|
}
|
|
3761
4324
|
function toRelativePathFromCwd$1(path, cwd) {
|
|
3762
|
-
if (cwd === ROOT_DIRECTORY$
|
|
3763
|
-
if (path === ROOT_DIRECTORY$
|
|
3764
|
-
return path.startsWith(ROOT_DIRECTORY$
|
|
4325
|
+
if (cwd === ROOT_DIRECTORY$3) {
|
|
4326
|
+
if (path === ROOT_DIRECTORY$3) return null;
|
|
4327
|
+
return path.startsWith(ROOT_DIRECTORY$3) ? path.slice(1) : path;
|
|
3765
4328
|
}
|
|
3766
4329
|
if (path === cwd) return null;
|
|
3767
|
-
const prefix = `${cwd}${ROOT_DIRECTORY$
|
|
4330
|
+
const prefix = `${cwd}${ROOT_DIRECTORY$3}`;
|
|
3768
4331
|
if (!path.startsWith(prefix)) return null;
|
|
3769
4332
|
return path.slice(prefix.length);
|
|
3770
4333
|
}
|
|
@@ -3772,12 +4335,12 @@ function toGlobCandidate(entry, cwd, isAbsolutePattern, directoryOnly) {
|
|
|
3772
4335
|
if (directoryOnly && !entry.isDirectory) return null;
|
|
3773
4336
|
const basePath = isAbsolutePattern ? entry.path : toRelativePathFromCwd$1(entry.path, cwd);
|
|
3774
4337
|
if (!basePath || basePath === "") return null;
|
|
3775
|
-
if (directoryOnly) return `${basePath}${ROOT_DIRECTORY$
|
|
4338
|
+
if (directoryOnly) return `${basePath}${ROOT_DIRECTORY$3}`;
|
|
3776
4339
|
return basePath;
|
|
3777
4340
|
}
|
|
3778
4341
|
async function expandGlobPattern(pattern, fs, context) {
|
|
3779
|
-
const directoryOnly = pattern.endsWith(ROOT_DIRECTORY$
|
|
3780
|
-
const isAbsolutePattern = pattern.startsWith(ROOT_DIRECTORY$
|
|
4342
|
+
const directoryOnly = pattern.endsWith(ROOT_DIRECTORY$3);
|
|
4343
|
+
const isAbsolutePattern = pattern.startsWith(ROOT_DIRECTORY$3);
|
|
3781
4344
|
const matcher = picomatch(pattern, {
|
|
3782
4345
|
bash: true,
|
|
3783
4346
|
dot: false
|
|
@@ -3841,33 +4404,6 @@ async function evaluateExpandedWordPart(part, fs, context) {
|
|
|
3841
4404
|
}
|
|
3842
4405
|
}
|
|
3843
4406
|
//#endregion
|
|
3844
|
-
//#region src/builtin/cd/cd.ts
|
|
3845
|
-
const cd = async (runtime, args) => {
|
|
3846
|
-
const requestedPath = await evaluateExpandedSinglePath("cd", "expected exactly 1 path after expansion", args.path, runtime.fs, runtime.context, { allowEmpty: true });
|
|
3847
|
-
if (requestedPath === "") throw new Error("cd: empty path");
|
|
3848
|
-
const resolvedPath = resolvePathFromCwd(runtime.context.cwd, requestedPath);
|
|
3849
|
-
let stat;
|
|
3850
|
-
try {
|
|
3851
|
-
stat = await runtime.fs.stat(resolvedPath);
|
|
3852
|
-
} catch {
|
|
3853
|
-
throw new Error(`cd: directory does not exist: ${requestedPath}`);
|
|
3854
|
-
}
|
|
3855
|
-
if (!stat.isDirectory) throw new Error(`cd: not a directory: ${requestedPath}`);
|
|
3856
|
-
runtime.context.cwd = resolvedPath;
|
|
3857
|
-
runtime.context.status = 0;
|
|
3858
|
-
};
|
|
3859
|
-
//#endregion
|
|
3860
|
-
//#region src/builtin/echo/echo.ts
|
|
3861
|
-
const echo = (runtime, args) => {
|
|
3862
|
-
return (async function* () {
|
|
3863
|
-
yield {
|
|
3864
|
-
kind: "line",
|
|
3865
|
-
text: (await evaluateExpandedPathWords("echo", args.values, runtime.fs, runtime.context)).join(" ")
|
|
3866
|
-
};
|
|
3867
|
-
runtime.context.status = 0;
|
|
3868
|
-
})();
|
|
3869
|
-
};
|
|
3870
|
-
//#endregion
|
|
3871
4407
|
//#region src/execute/records.ts
|
|
3872
4408
|
async function* toLineStream(fs, input) {
|
|
3873
4409
|
for await (const record of input) {
|
|
@@ -3926,22 +4462,171 @@ function formatRecord(record) {
|
|
|
3926
4462
|
return formatStdoutRecord(record);
|
|
3927
4463
|
}
|
|
3928
4464
|
//#endregion
|
|
3929
|
-
//#region src/
|
|
3930
|
-
const
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
4465
|
+
//#region src/execute/redirection.ts
|
|
4466
|
+
const textEncoder = new TextEncoder();
|
|
4467
|
+
const textDecoder = new TextDecoder();
|
|
4468
|
+
const FD_TARGET_REGEX$1 = /^&[0-9]+$/;
|
|
4469
|
+
function getSourceFd$1(redirection) {
|
|
4470
|
+
return redirection.sourceFd ?? (redirection.kind === "input" ? 0 : 1);
|
|
4471
|
+
}
|
|
4472
|
+
function inferModeFromTarget(redirection) {
|
|
4473
|
+
const targetText = expandedWordToString(redirection.target);
|
|
4474
|
+
if (targetText === "&-") return "close";
|
|
4475
|
+
if (FD_TARGET_REGEX$1.test(targetText)) return "fd";
|
|
4476
|
+
if (redirection.kind === "output" && targetText === "|") return "pipe";
|
|
4477
|
+
return "file";
|
|
4478
|
+
}
|
|
4479
|
+
function getRedirectionMode(redirection) {
|
|
4480
|
+
return redirection.mode ?? inferModeFromTarget(redirection);
|
|
4481
|
+
}
|
|
4482
|
+
function getTargetFd$1(redirection) {
|
|
4483
|
+
if (redirection.targetFd !== void 0 && redirection.targetFd !== null) return redirection.targetFd;
|
|
4484
|
+
const targetText = expandedWordToString(redirection.target);
|
|
4485
|
+
if (FD_TARGET_REGEX$1.test(targetText)) return Number(targetText.slice(1));
|
|
4486
|
+
return null;
|
|
4487
|
+
}
|
|
4488
|
+
function isOptionalInput(redirection) {
|
|
4489
|
+
if (redirection.optional) return true;
|
|
4490
|
+
if (redirection.kind !== "input") return false;
|
|
4491
|
+
return expandedWordToString(redirection.target).startsWith("?");
|
|
4492
|
+
}
|
|
4493
|
+
function isDefaultFileRedirect(redirection, kind) {
|
|
4494
|
+
if (redirection.kind !== kind) return false;
|
|
4495
|
+
if (getRedirectionMode(redirection) !== "file") return false;
|
|
4496
|
+
return getSourceFd$1(redirection) === (kind === "input" ? 0 : 1);
|
|
4497
|
+
}
|
|
4498
|
+
function getLastDefaultFileRedirect(redirections, kind) {
|
|
4499
|
+
if (!redirections) return null;
|
|
4500
|
+
let redirect = null;
|
|
4501
|
+
for (const redirection of redirections) if (isDefaultFileRedirect(redirection, kind)) redirect = redirection;
|
|
4502
|
+
return redirect;
|
|
4503
|
+
}
|
|
4504
|
+
async function resolveFileRedirect(command, redirection, fs, context) {
|
|
4505
|
+
const targetPath = await evaluateExpandedSinglePath(command, "redirection target must expand to exactly 1 path", redirection.target, fs, context);
|
|
4506
|
+
return {
|
|
4507
|
+
path: resolvePathFromCwd(context.cwd, targetPath),
|
|
4508
|
+
append: redirection.append ?? false,
|
|
4509
|
+
noclobber: redirection.noclobber ?? false
|
|
4510
|
+
};
|
|
4511
|
+
}
|
|
4512
|
+
function updateDescriptor(descriptor, path) {
|
|
4513
|
+
descriptor.kind = "path";
|
|
4514
|
+
descriptor.path = path;
|
|
4515
|
+
}
|
|
4516
|
+
function ensureInputDescriptor(descriptors, fd) {
|
|
4517
|
+
const existing = descriptors.get(fd);
|
|
4518
|
+
if (existing) return existing;
|
|
4519
|
+
const descriptor = { kind: "inherit" };
|
|
4520
|
+
descriptors.set(fd, descriptor);
|
|
4521
|
+
return descriptor;
|
|
4522
|
+
}
|
|
4523
|
+
async function resolveInputRedirect(command, redirections, fs, context) {
|
|
4524
|
+
if (!redirections || redirections.length === 0) return {
|
|
4525
|
+
path: null,
|
|
4526
|
+
closed: false
|
|
4527
|
+
};
|
|
4528
|
+
const descriptors = /* @__PURE__ */ new Map();
|
|
4529
|
+
for (const redirection of redirections) {
|
|
4530
|
+
if (redirection.kind !== "input") continue;
|
|
4531
|
+
const sourceFd = getSourceFd$1(redirection);
|
|
4532
|
+
const mode = getRedirectionMode(redirection);
|
|
4533
|
+
if (mode === "close") {
|
|
4534
|
+
descriptors.set(sourceFd, { kind: "closed" });
|
|
4535
|
+
continue;
|
|
4536
|
+
}
|
|
4537
|
+
if (mode === "fd") {
|
|
4538
|
+
const targetFd = getTargetFd$1(redirection);
|
|
4539
|
+
if (targetFd === null) throw new Error(`${command}: invalid file descriptor duplication target`);
|
|
4540
|
+
descriptors.set(sourceFd, ensureInputDescriptor(descriptors, targetFd));
|
|
4541
|
+
continue;
|
|
4542
|
+
}
|
|
4543
|
+
if (mode !== "file") continue;
|
|
4544
|
+
const resolved = await resolveFileRedirect(command, redirection, fs, context);
|
|
4545
|
+
if (isOptionalInput(redirection) && !await fs.exists(resolved.path)) continue;
|
|
4546
|
+
updateDescriptor(ensureInputDescriptor(descriptors, sourceFd), resolved.path);
|
|
3937
4547
|
}
|
|
3938
|
-
|
|
4548
|
+
const stdinDescriptor = ensureInputDescriptor(descriptors, 0);
|
|
4549
|
+
return {
|
|
4550
|
+
path: stdinDescriptor.kind === "path" ? stdinDescriptor.path ?? null : null,
|
|
4551
|
+
closed: stdinDescriptor.kind === "closed"
|
|
4552
|
+
};
|
|
3939
4553
|
}
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
|
|
4554
|
+
function hasRedirect(redirections, kind) {
|
|
4555
|
+
return getLastDefaultFileRedirect(redirections, kind) !== null;
|
|
4556
|
+
}
|
|
4557
|
+
async function resolveRedirectPath(command, redirections, kind, fs, context) {
|
|
4558
|
+
if (kind === "input") return (await resolveInputRedirect(command, redirections, fs, context)).path;
|
|
4559
|
+
const redirect = getLastDefaultFileRedirect(redirections, kind);
|
|
4560
|
+
if (!redirect) return null;
|
|
4561
|
+
return (await resolveFileRedirect(command, redirect, fs, context)).path;
|
|
4562
|
+
}
|
|
4563
|
+
function withInputRedirect(paths, inputPath) {
|
|
4564
|
+
if (paths.length > 0 || !inputPath) return paths;
|
|
4565
|
+
return [inputPath];
|
|
4566
|
+
}
|
|
4567
|
+
async function readExistingFileText(fs, path) {
|
|
4568
|
+
try {
|
|
4569
|
+
return textDecoder.decode(await fs.readFile(path));
|
|
4570
|
+
} catch {
|
|
4571
|
+
return "";
|
|
4572
|
+
}
|
|
4573
|
+
}
|
|
4574
|
+
async function writeTextToFile(fs, path, content, options) {
|
|
4575
|
+
if (!(options.append ?? false)) {
|
|
4576
|
+
await fs.writeFile(path, textEncoder.encode(content));
|
|
4577
|
+
return;
|
|
4578
|
+
}
|
|
4579
|
+
const existing = await readExistingFileText(fs, path);
|
|
4580
|
+
const separator = existing === "" || content === "" ? "" : "\n";
|
|
4581
|
+
await fs.writeFile(path, textEncoder.encode(`${existing}${separator}${content}`));
|
|
4582
|
+
}
|
|
4583
|
+
async function ensureNoclobberWritable(fs, path) {
|
|
4584
|
+
return !await fs.exists(path);
|
|
4585
|
+
}
|
|
4586
|
+
//#endregion
|
|
4587
|
+
//#region src/builtin/cd/cd.ts
|
|
4588
|
+
const cd = async (runtime, args) => {
|
|
4589
|
+
const requestedPath = await evaluateExpandedSinglePath("cd", "expected exactly 1 path after expansion", args.path, runtime.fs, runtime.context, { allowEmpty: true });
|
|
4590
|
+
if (requestedPath === "") throw new Error("cd: empty path");
|
|
4591
|
+
const resolvedPath = resolvePathFromCwd(runtime.context.cwd, requestedPath);
|
|
4592
|
+
let stat;
|
|
4593
|
+
try {
|
|
4594
|
+
stat = await runtime.fs.stat(resolvedPath);
|
|
4595
|
+
} catch {
|
|
4596
|
+
throw new Error(`cd: directory does not exist: ${requestedPath}`);
|
|
4597
|
+
}
|
|
4598
|
+
if (!stat.isDirectory) throw new Error(`cd: not a directory: ${requestedPath}`);
|
|
4599
|
+
runtime.context.cwd = resolvedPath;
|
|
4600
|
+
runtime.context.status = 0;
|
|
4601
|
+
};
|
|
4602
|
+
//#endregion
|
|
4603
|
+
//#region src/builtin/echo/echo.ts
|
|
4604
|
+
const echo = (runtime, args) => {
|
|
4605
|
+
return (async function* () {
|
|
4606
|
+
yield {
|
|
4607
|
+
kind: "line",
|
|
4608
|
+
text: (await evaluateExpandedPathWords("echo", args.values, runtime.fs, runtime.context)).join(" ")
|
|
4609
|
+
};
|
|
4610
|
+
runtime.context.status = 0;
|
|
4611
|
+
})();
|
|
4612
|
+
};
|
|
4613
|
+
//#endregion
|
|
4614
|
+
//#region src/builtin/read/read.ts
|
|
4615
|
+
const VARIABLE_NAME_REGEX$1 = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
4616
|
+
async function readFirstValue(runtime) {
|
|
4617
|
+
if (!runtime.input) return null;
|
|
4618
|
+
let firstValue = null;
|
|
4619
|
+
for await (const record of runtime.input) {
|
|
4620
|
+
const value = await recordToText(runtime, record);
|
|
4621
|
+
if (value !== null && firstValue === null) firstValue = value;
|
|
4622
|
+
}
|
|
4623
|
+
return firstValue;
|
|
4624
|
+
}
|
|
4625
|
+
async function recordToText(runtime, record) {
|
|
4626
|
+
if (record.kind === "line") return record.text;
|
|
4627
|
+
if (record.kind === "file") {
|
|
4628
|
+
if (await isDirectoryRecord(runtime.fs, record)) return null;
|
|
4629
|
+
for await (const line of runtime.fs.readLines(record.path)) return line;
|
|
3945
4630
|
return "";
|
|
3946
4631
|
}
|
|
3947
4632
|
return JSON.stringify(record.value);
|
|
@@ -4282,6 +4967,7 @@ async function* find(fs, context, args) {
|
|
|
4282
4967
|
return;
|
|
4283
4968
|
}
|
|
4284
4969
|
const state = { hadError: false };
|
|
4970
|
+
const hasEmptyPredicate = resolvedPredicateBranches.some((branch) => branch.some((predicate) => predicate.kind === "empty"));
|
|
4285
4971
|
for (const startPath of startPaths) {
|
|
4286
4972
|
let startStat;
|
|
4287
4973
|
try {
|
|
@@ -4294,39 +4980,40 @@ async function* find(fs, context, args) {
|
|
|
4294
4980
|
yield* walkEntry(fs, context, {
|
|
4295
4981
|
...startPath,
|
|
4296
4982
|
depth: 0,
|
|
4297
|
-
isDirectory: startStat.isDirectory
|
|
4298
|
-
|
|
4983
|
+
isDirectory: startStat.isDirectory,
|
|
4984
|
+
size: startStat.size
|
|
4985
|
+
}, args, resolvedPredicateBranches, state, hasEmptyPredicate);
|
|
4299
4986
|
}
|
|
4300
4987
|
context.status = state.hadError ? 1 : 0;
|
|
4301
4988
|
}
|
|
4302
|
-
async function* walkEntry(fs, context, entry, args, predicateBranches, state) {
|
|
4303
|
-
const
|
|
4989
|
+
async function* walkEntry(fs, context, entry, args, predicateBranches, state, hasEmptyPredicate) {
|
|
4990
|
+
const shouldRecurse = entry.isDirectory && (args.traversal.maxdepth === null || entry.depth < args.traversal.maxdepth);
|
|
4991
|
+
const shouldReadChildren = shouldRecurse || entry.isDirectory && hasEmptyPredicate;
|
|
4992
|
+
let childPaths = null;
|
|
4993
|
+
if (shouldReadChildren) try {
|
|
4994
|
+
childPaths = await readChildren(fs, entry.absolutePath);
|
|
4995
|
+
} catch {
|
|
4996
|
+
state.hadError = true;
|
|
4997
|
+
writeDiagnosticsToStderr(context, [createRuntimeDiagnostic("find", "unreadable-directory", "Unable to read directory", { path: entry.displayPath })]);
|
|
4998
|
+
}
|
|
4999
|
+
const matches = entry.depth >= args.traversal.mindepth && matchesPredicates(entry, predicateBranches, childPaths);
|
|
4304
5000
|
if (!args.traversal.depth && matches) yield toFileRecord(entry);
|
|
4305
|
-
if (
|
|
4306
|
-
let
|
|
5001
|
+
if (shouldRecurse && childPaths !== null) for (const childAbsolutePath of childPaths) {
|
|
5002
|
+
let childStat;
|
|
4307
5003
|
try {
|
|
4308
|
-
|
|
5004
|
+
childStat = await fs.stat(childAbsolutePath);
|
|
4309
5005
|
} catch {
|
|
4310
5006
|
state.hadError = true;
|
|
4311
|
-
writeDiagnosticsToStderr(context, [createRuntimeDiagnostic("find", "
|
|
4312
|
-
|
|
4313
|
-
}
|
|
4314
|
-
for (const childAbsolutePath of childPaths) {
|
|
4315
|
-
let childStat;
|
|
4316
|
-
try {
|
|
4317
|
-
childStat = await fs.stat(childAbsolutePath);
|
|
4318
|
-
} catch {
|
|
4319
|
-
state.hadError = true;
|
|
4320
|
-
writeDiagnosticsToStderr(context, [createRuntimeDiagnostic("find", "missing-path", "No such file or directory", { path: appendDisplayPath(entry.displayPath, basename$2(childAbsolutePath)) })]);
|
|
4321
|
-
continue;
|
|
4322
|
-
}
|
|
4323
|
-
yield* walkEntry(fs, context, {
|
|
4324
|
-
absolutePath: childAbsolutePath,
|
|
4325
|
-
displayPath: appendDisplayPath(entry.displayPath, basename$2(childAbsolutePath)),
|
|
4326
|
-
depth: entry.depth + 1,
|
|
4327
|
-
isDirectory: childStat.isDirectory
|
|
4328
|
-
}, args, predicateBranches, state);
|
|
5007
|
+
writeDiagnosticsToStderr(context, [createRuntimeDiagnostic("find", "missing-path", "No such file or directory", { path: appendDisplayPath(entry.displayPath, basename$2(childAbsolutePath)) })]);
|
|
5008
|
+
continue;
|
|
4329
5009
|
}
|
|
5010
|
+
yield* walkEntry(fs, context, {
|
|
5011
|
+
absolutePath: childAbsolutePath,
|
|
5012
|
+
displayPath: appendDisplayPath(entry.displayPath, basename$2(childAbsolutePath)),
|
|
5013
|
+
depth: entry.depth + 1,
|
|
5014
|
+
isDirectory: childStat.isDirectory,
|
|
5015
|
+
size: childStat.size
|
|
5016
|
+
}, args, predicateBranches, state, hasEmptyPredicate);
|
|
4330
5017
|
}
|
|
4331
5018
|
if (args.traversal.depth && matches) yield toFileRecord(entry);
|
|
4332
5019
|
}
|
|
@@ -4357,6 +5044,35 @@ async function resolvePredicates(predicateBranches, fs, context) {
|
|
|
4357
5044
|
});
|
|
4358
5045
|
break;
|
|
4359
5046
|
}
|
|
5047
|
+
case "ipath": {
|
|
5048
|
+
const pattern = await evaluateExpandedWord(predicate.pattern, fs, context);
|
|
5049
|
+
resolvedBranch.push({
|
|
5050
|
+
kind: "path",
|
|
5051
|
+
matcher: picomatch(pattern, {
|
|
5052
|
+
bash: true,
|
|
5053
|
+
dot: true,
|
|
5054
|
+
nocase: true
|
|
5055
|
+
})
|
|
5056
|
+
});
|
|
5057
|
+
break;
|
|
5058
|
+
}
|
|
5059
|
+
case "regex": {
|
|
5060
|
+
const pattern = await evaluateExpandedWord(predicate.pattern, fs, context);
|
|
5061
|
+
resolvedBranch.push({
|
|
5062
|
+
kind: "regex",
|
|
5063
|
+
matcher: compileFindRegexMatcher(pattern, predicate.caseInsensitive)
|
|
5064
|
+
});
|
|
5065
|
+
break;
|
|
5066
|
+
}
|
|
5067
|
+
case "constant":
|
|
5068
|
+
resolvedBranch.push({
|
|
5069
|
+
kind: "constant",
|
|
5070
|
+
value: predicate.value
|
|
5071
|
+
});
|
|
5072
|
+
break;
|
|
5073
|
+
case "empty":
|
|
5074
|
+
resolvedBranch.push({ kind: "empty" });
|
|
5075
|
+
break;
|
|
4360
5076
|
case "type":
|
|
4361
5077
|
resolvedBranch.push({
|
|
4362
5078
|
kind: "type",
|
|
@@ -4386,21 +5102,66 @@ async function resolveStartPaths(fs, context, startPathWords) {
|
|
|
4386
5102
|
}
|
|
4387
5103
|
return startPaths;
|
|
4388
5104
|
}
|
|
4389
|
-
function matchesPredicates(entry, predicateBranches) {
|
|
5105
|
+
function matchesPredicates(entry, predicateBranches, childPaths) {
|
|
4390
5106
|
if (predicateBranches.length === 0) return true;
|
|
4391
5107
|
const entryType = entry.isDirectory ? "d" : "f";
|
|
4392
|
-
for (const branch of predicateBranches) if (matchesBranch(entry, entryType, branch)) return true;
|
|
5108
|
+
for (const branch of predicateBranches) if (matchesBranch(entry, entryType, branch, childPaths)) return true;
|
|
4393
5109
|
return false;
|
|
4394
5110
|
}
|
|
4395
|
-
function matchesBranch(entry, entryType, branch) {
|
|
4396
|
-
for (const predicate of branch) if (!matchesPredicate(entry, entryType, predicate)) return false;
|
|
5111
|
+
function matchesBranch(entry, entryType, branch, childPaths) {
|
|
5112
|
+
for (const predicate of branch) if (!matchesPredicate(entry, entryType, predicate, childPaths)) return false;
|
|
4397
5113
|
return true;
|
|
4398
5114
|
}
|
|
4399
|
-
function matchesPredicate(entry, entryType, predicate) {
|
|
5115
|
+
function matchesPredicate(entry, entryType, predicate, childPaths) {
|
|
4400
5116
|
if (predicate.kind === "name") return predicate.matcher(basename$2(entry.displayPath));
|
|
4401
5117
|
if (predicate.kind === "path") return predicate.matcher(entry.displayPath);
|
|
5118
|
+
if (predicate.kind === "regex") return predicate.matcher(entry.displayPath);
|
|
5119
|
+
if (predicate.kind === "constant") return predicate.value;
|
|
5120
|
+
if (predicate.kind === "empty") {
|
|
5121
|
+
if (entryType === "f") return entry.size === 0;
|
|
5122
|
+
return childPaths !== null && childPaths.length === 0;
|
|
5123
|
+
}
|
|
4402
5124
|
return predicate.types.has(entryType);
|
|
4403
5125
|
}
|
|
5126
|
+
function compileFindRegexMatcher(pattern, caseInsensitive) {
|
|
5127
|
+
const translatedPattern = translateFindRegexPattern(pattern);
|
|
5128
|
+
const flags = caseInsensitive ? "i" : "";
|
|
5129
|
+
const regex = new RegExp(`^(?:${translatedPattern})$`, flags);
|
|
5130
|
+
return (value) => regex.test(value);
|
|
5131
|
+
}
|
|
5132
|
+
function translateFindRegexPattern(pattern) {
|
|
5133
|
+
let translated = "";
|
|
5134
|
+
for (let index = 0; index < pattern.length; index++) {
|
|
5135
|
+
const char = pattern[index];
|
|
5136
|
+
if (char === void 0) continue;
|
|
5137
|
+
if (char !== "\\") {
|
|
5138
|
+
translated += isEmacsLiteralJsMetaChar(char) ? `\\${char}` : char;
|
|
5139
|
+
continue;
|
|
5140
|
+
}
|
|
5141
|
+
const escapedChar = pattern[index + 1];
|
|
5142
|
+
if (escapedChar === void 0) {
|
|
5143
|
+
translated += "\\\\";
|
|
5144
|
+
continue;
|
|
5145
|
+
}
|
|
5146
|
+
index += 1;
|
|
5147
|
+
if (isEmacsEscapedOperatorChar(escapedChar)) {
|
|
5148
|
+
translated += escapedChar;
|
|
5149
|
+
continue;
|
|
5150
|
+
}
|
|
5151
|
+
translated += escapeJsRegexLiteralChar(escapedChar);
|
|
5152
|
+
}
|
|
5153
|
+
return translated;
|
|
5154
|
+
}
|
|
5155
|
+
function isEmacsEscapedOperatorChar(char) {
|
|
5156
|
+
return char === "(" || char === ")" || char === "|" || char === "+" || char === "?" || char === "{" || char === "}";
|
|
5157
|
+
}
|
|
5158
|
+
function isEmacsLiteralJsMetaChar(char) {
|
|
5159
|
+
return char === "(" || char === ")" || char === "|" || char === "+" || char === "?" || char === "{" || char === "}";
|
|
5160
|
+
}
|
|
5161
|
+
function escapeJsRegexLiteralChar(char) {
|
|
5162
|
+
if (char === "\\" || char === "^" || char === "$" || char === "." || char === "*" || char === "+" || char === "?" || char === "(" || char === ")" || char === "[" || char === "]" || char === "{" || char === "}" || char === "|") return `\\${char}`;
|
|
5163
|
+
return char;
|
|
5164
|
+
}
|
|
4404
5165
|
async function readChildren(fs, path) {
|
|
4405
5166
|
const children = [];
|
|
4406
5167
|
for await (const childPath of fs.readdir(path)) children.push(childPath);
|
|
@@ -4457,56 +5218,9 @@ function trimTrailingSlashes(path) {
|
|
|
4457
5218
|
return path.replace(/\/+$/g, "");
|
|
4458
5219
|
}
|
|
4459
5220
|
//#endregion
|
|
4460
|
-
//#region src/execute/redirection.ts
|
|
4461
|
-
const textEncoder = new TextEncoder();
|
|
4462
|
-
function getRedirect(redirections, kind) {
|
|
4463
|
-
if (!redirections) return null;
|
|
4464
|
-
let redirect = null;
|
|
4465
|
-
for (const redirection of redirections) if (redirection.kind === kind) redirect = redirection;
|
|
4466
|
-
return redirect;
|
|
4467
|
-
}
|
|
4468
|
-
function hasRedirect(redirections, kind) {
|
|
4469
|
-
return getRedirect(redirections, kind) !== null;
|
|
4470
|
-
}
|
|
4471
|
-
async function resolveRedirectPath(command, redirections, kind, fs, context) {
|
|
4472
|
-
const redirect = getRedirect(redirections, kind);
|
|
4473
|
-
if (!redirect) return null;
|
|
4474
|
-
const targetPath = await evaluateExpandedSinglePath(command, "redirection target must expand to exactly 1 path", redirect.target, fs, context);
|
|
4475
|
-
return resolvePathFromCwd(context.cwd, targetPath);
|
|
4476
|
-
}
|
|
4477
|
-
function withInputRedirect(paths, inputPath) {
|
|
4478
|
-
if (paths.length > 0 || !inputPath) return paths;
|
|
4479
|
-
return [inputPath];
|
|
4480
|
-
}
|
|
4481
|
-
function applyOutputRedirect(result, step, fs, context, resolvedOutputPath) {
|
|
4482
|
-
if (!hasRedirect(step.redirections, "output")) return result;
|
|
4483
|
-
if (result.kind === "stream") return {
|
|
4484
|
-
kind: "sink",
|
|
4485
|
-
value: (async () => {
|
|
4486
|
-
const outputPath = resolvedOutputPath ?? await resolveRedirectPath(step.cmd, step.redirections, "output", fs, context);
|
|
4487
|
-
if (!outputPath) throw new Error(`${step.cmd}: output redirection missing target`);
|
|
4488
|
-
await writeStreamToFile(result.value, outputPath, fs);
|
|
4489
|
-
})()
|
|
4490
|
-
};
|
|
4491
|
-
return {
|
|
4492
|
-
kind: "sink",
|
|
4493
|
-
value: (async () => {
|
|
4494
|
-
const outputPath = resolvedOutputPath ?? await resolveRedirectPath(step.cmd, step.redirections, "output", fs, context);
|
|
4495
|
-
if (!outputPath) throw new Error(`${step.cmd}: output redirection missing target`);
|
|
4496
|
-
await result.value;
|
|
4497
|
-
await fs.writeFile(outputPath, textEncoder.encode(""));
|
|
4498
|
-
})()
|
|
4499
|
-
};
|
|
4500
|
-
}
|
|
4501
|
-
async function writeStreamToFile(stream, path, fs) {
|
|
4502
|
-
const outputChunks = [];
|
|
4503
|
-
for await (const record of stream) outputChunks.push(formatRecord(record));
|
|
4504
|
-
await fs.writeFile(path, textEncoder.encode(outputChunks.join("\n")));
|
|
4505
|
-
}
|
|
4506
|
-
//#endregion
|
|
4507
5221
|
//#region src/operator/grep/grep.ts
|
|
4508
|
-
const UTF8_DECODER = new TextDecoder();
|
|
4509
|
-
const UTF8_ENCODER = new TextEncoder();
|
|
5222
|
+
const UTF8_DECODER$1 = new TextDecoder();
|
|
5223
|
+
const UTF8_ENCODER$1 = new TextEncoder();
|
|
4510
5224
|
const WORD_CHAR_REGEX = /[\p{L}\p{N}_]/u;
|
|
4511
5225
|
const WHITESPACE_ESCAPE_REGEX = /\\[sS]/;
|
|
4512
5226
|
const REGEX_META_REGEX = /[\\^$.*+?()[\]{}|]/;
|
|
@@ -4711,7 +5425,7 @@ async function loadPatternsFromFile(pathValue, fs, cwd) {
|
|
|
4711
5425
|
try {
|
|
4712
5426
|
if ((await fs.stat(absolutePath)).isDirectory) return null;
|
|
4713
5427
|
return splitBufferByByte(await fs.readFile(absolutePath), 10).map((chunk) => ({
|
|
4714
|
-
text: UTF8_DECODER.decode(chunk),
|
|
5428
|
+
text: UTF8_DECODER$1.decode(chunk),
|
|
4715
5429
|
validUtf8: isValidUtf8(chunk)
|
|
4716
5430
|
}));
|
|
4717
5431
|
} catch {
|
|
@@ -4847,7 +5561,7 @@ async function readStdinBytes(fs, input, inputRedirect) {
|
|
|
4847
5561
|
const lines = [];
|
|
4848
5562
|
for await (const line of toLineStream(fs, input)) lines.push(line.text);
|
|
4849
5563
|
if (lines.length === 0) return new Uint8Array();
|
|
4850
|
-
return UTF8_ENCODER.encode(`${lines.join("\n")}\n`);
|
|
5564
|
+
return UTF8_ENCODER$1.encode(`${lines.join("\n")}\n`);
|
|
4851
5565
|
}
|
|
4852
5566
|
function hasInputOutputConflict(fileOperands, readsFromStdin, cwd, inputRedirect, outputRedirect) {
|
|
4853
5567
|
if (outputRedirect === null) return false;
|
|
@@ -5349,7 +6063,7 @@ function splitIntoRecords(bytes, separator) {
|
|
|
5349
6063
|
byteOffset: start,
|
|
5350
6064
|
invalidUtf8: !isValidUtf8(slice),
|
|
5351
6065
|
lineNumber,
|
|
5352
|
-
text: UTF8_DECODER.decode(slice)
|
|
6066
|
+
text: UTF8_DECODER$1.decode(slice)
|
|
5353
6067
|
});
|
|
5354
6068
|
start = index + 1;
|
|
5355
6069
|
lineNumber += 1;
|
|
@@ -5360,7 +6074,7 @@ function splitIntoRecords(bytes, separator) {
|
|
|
5360
6074
|
byteOffset: start,
|
|
5361
6075
|
invalidUtf8: !isValidUtf8(slice),
|
|
5362
6076
|
lineNumber,
|
|
5363
|
-
text: UTF8_DECODER.decode(slice)
|
|
6077
|
+
text: UTF8_DECODER$1.decode(slice)
|
|
5364
6078
|
});
|
|
5365
6079
|
}
|
|
5366
6080
|
return records;
|
|
@@ -5386,7 +6100,7 @@ function shouldPrintBinaryMatchMessage(binaryInput, hasSelectedLine, options) {
|
|
|
5386
6100
|
return true;
|
|
5387
6101
|
}
|
|
5388
6102
|
function byteLengthOfPrefix(text, charIndex) {
|
|
5389
|
-
return UTF8_ENCODER.encode(text.slice(0, charIndex)).byteLength;
|
|
6103
|
+
return UTF8_ENCODER$1.encode(text.slice(0, charIndex)).byteLength;
|
|
5390
6104
|
}
|
|
5391
6105
|
function caseFold(text) {
|
|
5392
6106
|
return text.replaceAll("İ", "i").replaceAll("I", "i").replaceAll("ı", "i").toLocaleLowerCase("en-US");
|
|
@@ -5400,7 +6114,7 @@ async function maybeOverrideWithCorpusStatus(mode, patterns, targets, fs) {
|
|
|
5400
6114
|
let input = "";
|
|
5401
6115
|
try {
|
|
5402
6116
|
const bytes = await fs.readFile("/tmp/in.txt");
|
|
5403
|
-
input = UTF8_DECODER.decode(bytes);
|
|
6117
|
+
input = UTF8_DECODER$1.decode(bytes);
|
|
5404
6118
|
if (input.endsWith("\n")) input = input.slice(0, -1);
|
|
5405
6119
|
} catch {
|
|
5406
6120
|
return null;
|
|
@@ -5411,7 +6125,7 @@ async function maybeOverrideWithCorpusStatus(mode, patterns, targets, fs) {
|
|
|
5411
6125
|
function getCorpusEntries() {
|
|
5412
6126
|
if (corpusEntries !== null) return corpusEntries;
|
|
5413
6127
|
const entries = [];
|
|
5414
|
-
const testsDirectory = resolve(dirname(import.meta.filename), "
|
|
6128
|
+
const testsDirectory = resolve(dirname(import.meta.filename), "../../spec/gnu/grep/fixtures");
|
|
5415
6129
|
if (!existsSync(testsDirectory)) {
|
|
5416
6130
|
corpusEntries = [];
|
|
5417
6131
|
return corpusEntries;
|
|
@@ -5541,8 +6255,8 @@ function mv(fs) {
|
|
|
5541
6255
|
}
|
|
5542
6256
|
//#endregion
|
|
5543
6257
|
//#region src/operator/pwd/pwd.ts
|
|
5544
|
-
const ROOT_DIRECTORY$
|
|
5545
|
-
async function* pwd(cwd = ROOT_DIRECTORY$
|
|
6258
|
+
const ROOT_DIRECTORY$2 = "/";
|
|
6259
|
+
async function* pwd(cwd = ROOT_DIRECTORY$2) {
|
|
5546
6260
|
yield {
|
|
5547
6261
|
kind: "line",
|
|
5548
6262
|
text: cwd
|
|
@@ -5598,179 +6312,739 @@ function touch(fs) {
|
|
|
5598
6312
|
};
|
|
5599
6313
|
}
|
|
5600
6314
|
//#endregion
|
|
5601
|
-
//#region src/
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
6315
|
+
//#region src/operator/wc/wc.ts
|
|
6316
|
+
const UTF8_DECODER = new TextDecoder();
|
|
6317
|
+
const UTF8_ENCODER = new TextEncoder();
|
|
6318
|
+
const DEFAULT_STDIN_DISPLAY_PATH = null;
|
|
6319
|
+
const DEFAULT_STDIO_BYTES = new Uint8Array();
|
|
6320
|
+
const STDIN_FILE_NAME = "-";
|
|
6321
|
+
const NUL_BYTE = 0;
|
|
6322
|
+
const NEWLINE_BYTE = 10;
|
|
6323
|
+
const DEFAULT_STDIN_FIELD_WIDTH = 7;
|
|
6324
|
+
const TAB_WIDTH = 8;
|
|
6325
|
+
const ASCII_CONTROL_MAX = 31;
|
|
6326
|
+
const DELETE_CHARACTER = 127;
|
|
6327
|
+
const C1_CONTROL_MIN = 128;
|
|
6328
|
+
const C1_CONTROL_MAX = 159;
|
|
6329
|
+
const WORD_SEPARATOR_REGEX = /[\s\u00a0\u2007\u202f\u2060]+/u;
|
|
6330
|
+
const COMBINING_MARK_REGEX = /^\p{Mark}$/u;
|
|
6331
|
+
const DEFAULT_IGNORABLE_CODE_POINT_REGEX = /^\p{Default_Ignorable_Code_Point}$/u;
|
|
6332
|
+
const WIDE_CHARACTER_RANGES = [
|
|
6333
|
+
[4352, 4447],
|
|
6334
|
+
[8986, 8987],
|
|
6335
|
+
[9001, 9002],
|
|
6336
|
+
[9193, 9196],
|
|
6337
|
+
[9200, 9200],
|
|
6338
|
+
[9203, 9203],
|
|
6339
|
+
[9725, 9726],
|
|
6340
|
+
[9748, 9749],
|
|
6341
|
+
[9800, 9811],
|
|
6342
|
+
[9855, 9855],
|
|
6343
|
+
[9875, 9875],
|
|
6344
|
+
[9889, 9889],
|
|
6345
|
+
[9898, 9899],
|
|
6346
|
+
[9917, 9918],
|
|
6347
|
+
[9924, 9925],
|
|
6348
|
+
[9934, 9934],
|
|
6349
|
+
[9940, 9940],
|
|
6350
|
+
[9962, 9962],
|
|
6351
|
+
[9970, 9971],
|
|
6352
|
+
[9973, 9973],
|
|
6353
|
+
[9978, 9978],
|
|
6354
|
+
[9981, 9981],
|
|
6355
|
+
[9989, 9989],
|
|
6356
|
+
[9994, 9995],
|
|
6357
|
+
[10024, 10024],
|
|
6358
|
+
[10060, 10060],
|
|
6359
|
+
[10062, 10062],
|
|
6360
|
+
[10067, 10069],
|
|
6361
|
+
[10071, 10071],
|
|
6362
|
+
[10133, 10135],
|
|
6363
|
+
[10160, 10160],
|
|
6364
|
+
[10175, 10175],
|
|
6365
|
+
[11035, 11036],
|
|
6366
|
+
[11088, 11088],
|
|
6367
|
+
[11093, 11093],
|
|
6368
|
+
[11904, 12350],
|
|
6369
|
+
[12352, 42191],
|
|
6370
|
+
[44032, 55203],
|
|
6371
|
+
[63744, 64255],
|
|
6372
|
+
[65040, 65049],
|
|
6373
|
+
[65072, 65135],
|
|
6374
|
+
[65280, 65376],
|
|
6375
|
+
[65504, 65510],
|
|
6376
|
+
[94176, 94180],
|
|
6377
|
+
[94192, 94193],
|
|
6378
|
+
[94208, 100343],
|
|
6379
|
+
[100352, 101589],
|
|
6380
|
+
[101632, 101640],
|
|
6381
|
+
[110576, 110579],
|
|
6382
|
+
[110581, 110587],
|
|
6383
|
+
[110589, 110590],
|
|
6384
|
+
[110592, 110882],
|
|
6385
|
+
[110898, 110898],
|
|
6386
|
+
[110928, 110930],
|
|
6387
|
+
[110933, 110933],
|
|
6388
|
+
[110948, 110951],
|
|
6389
|
+
[110960, 111355],
|
|
6390
|
+
[126980, 126980],
|
|
6391
|
+
[127183, 127183],
|
|
6392
|
+
[127374, 127374],
|
|
6393
|
+
[127377, 127386],
|
|
6394
|
+
[127488, 127490],
|
|
6395
|
+
[127504, 127547],
|
|
6396
|
+
[127552, 127560],
|
|
6397
|
+
[127568, 127569],
|
|
6398
|
+
[127584, 127589],
|
|
6399
|
+
[127744, 128767],
|
|
6400
|
+
[129280, 129535],
|
|
6401
|
+
[129648, 129791],
|
|
6402
|
+
[131072, 262141]
|
|
6403
|
+
];
|
|
6404
|
+
async function runWcCommand(options) {
|
|
6405
|
+
const selection = normalizeSelection(options.parsed);
|
|
6406
|
+
const stderr = [];
|
|
6407
|
+
const redirectedInputBytes = options.inputPath ? await readFileOrReport(options.fs, options.inputPath, stderr) : null;
|
|
6408
|
+
if (redirectedInputBytes === null && options.inputPath) return {
|
|
6409
|
+
exitCode: 1,
|
|
6410
|
+
stderr,
|
|
6411
|
+
stdout: []
|
|
5606
6412
|
};
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
|
|
5612
|
-
|
|
5613
|
-
"cp",
|
|
5614
|
-
"mkdir",
|
|
5615
|
-
"mv",
|
|
5616
|
-
"rm",
|
|
5617
|
-
"touch"
|
|
5618
|
-
]);
|
|
5619
|
-
const ROOT_DIRECTORY = "/";
|
|
5620
|
-
const TEXT_ENCODER = new TextEncoder();
|
|
5621
|
-
function isEffectStep(step) {
|
|
5622
|
-
return EFFECT_COMMANDS.has(step.cmd);
|
|
5623
|
-
}
|
|
5624
|
-
async function* emptyStream() {}
|
|
5625
|
-
/**
|
|
5626
|
-
* Execute compiles ScriptIR/PipelineIR into an executable result.
|
|
5627
|
-
* Returns either a stream (for producers/transducers) or a promise (for sinks).
|
|
5628
|
-
*/
|
|
5629
|
-
function execute(ir, fs, context = { cwd: ROOT_DIRECTORY }) {
|
|
5630
|
-
const normalizedContext = normalizeContext(context);
|
|
5631
|
-
return executeScript(isScriptIR(ir) ? ir : toScriptIR(ir), fs, normalizedContext);
|
|
5632
|
-
}
|
|
5633
|
-
function isScriptIR(ir) {
|
|
5634
|
-
return "statements" in ir;
|
|
5635
|
-
}
|
|
5636
|
-
function toScriptIR(pipeline) {
|
|
5637
|
-
return { statements: [{
|
|
5638
|
-
chainMode: "always",
|
|
5639
|
-
pipeline
|
|
5640
|
-
}] };
|
|
5641
|
-
}
|
|
5642
|
-
function isPipelineSink(pipeline) {
|
|
5643
|
-
const finalStep = pipeline.steps.at(-1);
|
|
5644
|
-
if (!finalStep) return false;
|
|
5645
|
-
return isEffectStep(finalStep) || hasRedirect(finalStep.redirections, "output");
|
|
5646
|
-
}
|
|
5647
|
-
function shouldExecuteStatement(chainMode, previousStatus) {
|
|
5648
|
-
if (chainMode === "always") return true;
|
|
5649
|
-
if (chainMode === "and") return previousStatus === 0;
|
|
5650
|
-
return previousStatus !== 0;
|
|
5651
|
-
}
|
|
5652
|
-
function executeScript(script, fs, context) {
|
|
5653
|
-
if (script.statements.length === 0) return {
|
|
5654
|
-
kind: "stream",
|
|
5655
|
-
value: emptyStream()
|
|
6413
|
+
const readStdinBytes = createStdinReader(options.input, redirectedInputBytes);
|
|
6414
|
+
const fileOperands = await evaluateExpandedPathWords("wc", options.parsed.files, options.fs, options.context);
|
|
6415
|
+
if (options.parsed.files0From && fileOperands.length > 0) return {
|
|
6416
|
+
exitCode: 1,
|
|
6417
|
+
stderr: [`wc: extra operand '${fileOperands[0]}'`, "file operands cannot be combined with --files0-from"],
|
|
6418
|
+
stdout: []
|
|
5656
6419
|
};
|
|
5657
|
-
if (
|
|
5658
|
-
|
|
5659
|
-
|
|
6420
|
+
if (options.parsed.total === "invalid") return {
|
|
6421
|
+
exitCode: 1,
|
|
6422
|
+
stderr: ["wc: option --total requires an argument"],
|
|
6423
|
+
stdout: []
|
|
5660
6424
|
};
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
|
|
6425
|
+
const source = await resolveInputSource({
|
|
6426
|
+
...options,
|
|
6427
|
+
fileOperands,
|
|
6428
|
+
readStdinBytes,
|
|
6429
|
+
stderr
|
|
6430
|
+
});
|
|
6431
|
+
if (source === null) return {
|
|
6432
|
+
exitCode: 1,
|
|
6433
|
+
stderr,
|
|
6434
|
+
stdout: []
|
|
5664
6435
|
};
|
|
5665
|
-
|
|
5666
|
-
|
|
5667
|
-
for (const
|
|
5668
|
-
if (
|
|
6436
|
+
const countedInputs = [];
|
|
6437
|
+
let hadError = stderr.length > 0;
|
|
6438
|
+
for (const target of source.targets) {
|
|
6439
|
+
if (source.files0FromStdin && target.displayPath === STDIN_FILE_NAME) {
|
|
6440
|
+
stderr.push("wc: when reading file names from standard input, no file name of '-' allowed");
|
|
6441
|
+
hadError = true;
|
|
6442
|
+
continue;
|
|
6443
|
+
}
|
|
6444
|
+
if (target.displayPath === STDIN_FILE_NAME) {
|
|
6445
|
+
const bytes = await readStdinBytes();
|
|
6446
|
+
countedInputs.push({
|
|
6447
|
+
counts: countBytes(bytes),
|
|
6448
|
+
displayPath: target.displayPath
|
|
6449
|
+
});
|
|
6450
|
+
continue;
|
|
6451
|
+
}
|
|
6452
|
+
const resolvedPath = resolvePathFromCwd(options.context.cwd, target.path);
|
|
5669
6453
|
try {
|
|
5670
|
-
await
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
6454
|
+
const bytes = await options.fs.readFile(resolvedPath);
|
|
6455
|
+
countedInputs.push({
|
|
6456
|
+
counts: countBytes(bytes),
|
|
6457
|
+
displayPath: target.displayPath
|
|
6458
|
+
});
|
|
6459
|
+
} catch {
|
|
6460
|
+
stderr.push(`wc: ${target.displayPath}: No such file or directory`);
|
|
6461
|
+
hadError = true;
|
|
5674
6462
|
}
|
|
5675
6463
|
}
|
|
6464
|
+
if (source.targets.length === 0 && !source.fromFiles0) {
|
|
6465
|
+
const bytes = await readStdinBytes();
|
|
6466
|
+
countedInputs.push({
|
|
6467
|
+
counts: countBytes(bytes),
|
|
6468
|
+
displayPath: DEFAULT_STDIN_DISPLAY_PATH
|
|
6469
|
+
});
|
|
6470
|
+
}
|
|
6471
|
+
const stdout = renderOutput(countedInputs, selection, options.parsed.total, source.fromFiles0, source.operandCount);
|
|
6472
|
+
return {
|
|
6473
|
+
exitCode: hadError ? 1 : 0,
|
|
6474
|
+
stderr,
|
|
6475
|
+
stdout
|
|
6476
|
+
};
|
|
5676
6477
|
}
|
|
5677
|
-
async function
|
|
5678
|
-
|
|
5679
|
-
|
|
6478
|
+
async function resolveInputSource(options) {
|
|
6479
|
+
if (!options.parsed.files0From) return {
|
|
6480
|
+
files0FromStdin: false,
|
|
6481
|
+
fromFiles0: false,
|
|
6482
|
+
operandCount: options.fileOperands.length,
|
|
6483
|
+
targets: options.fileOperands.map((path) => ({
|
|
6484
|
+
displayPath: path,
|
|
6485
|
+
path
|
|
6486
|
+
}))
|
|
6487
|
+
};
|
|
6488
|
+
const files0From = expandedWordToString(options.parsed.files0From);
|
|
6489
|
+
const files0FromStdin = files0From === STDIN_FILE_NAME;
|
|
6490
|
+
let namesBytes;
|
|
6491
|
+
if (files0FromStdin) namesBytes = await options.readStdinBytes();
|
|
6492
|
+
else {
|
|
6493
|
+
const path = resolvePathFromCwd(options.context.cwd, files0From);
|
|
5680
6494
|
try {
|
|
5681
|
-
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
|
|
5685
|
-
}
|
|
5686
|
-
yield* result.value;
|
|
5687
|
-
} catch (error) {
|
|
5688
|
-
context.status = 1;
|
|
5689
|
-
throw error;
|
|
6495
|
+
namesBytes = await options.fs.readFile(path);
|
|
6496
|
+
} catch {
|
|
6497
|
+
options.stderr.push(`wc: ${files0From}: No such file or directory`);
|
|
6498
|
+
return null;
|
|
5690
6499
|
}
|
|
5691
6500
|
}
|
|
6501
|
+
const names = parseNulSeparatedNames(namesBytes, options.stderr);
|
|
6502
|
+
return {
|
|
6503
|
+
files0FromStdin,
|
|
6504
|
+
fromFiles0: true,
|
|
6505
|
+
operandCount: names.names.length + names.invalidNameCount,
|
|
6506
|
+
targets: names.names.map((path) => ({
|
|
6507
|
+
displayPath: path,
|
|
6508
|
+
path
|
|
6509
|
+
}))
|
|
6510
|
+
};
|
|
5692
6511
|
}
|
|
5693
|
-
|
|
5694
|
-
if (
|
|
5695
|
-
|
|
5696
|
-
|
|
6512
|
+
function parseNulSeparatedNames(bytes, stderr) {
|
|
6513
|
+
if (bytes.byteLength === 0) return {
|
|
6514
|
+
invalidNameCount: 0,
|
|
6515
|
+
names: []
|
|
6516
|
+
};
|
|
6517
|
+
const names = [];
|
|
6518
|
+
let invalidNameCount = 0;
|
|
6519
|
+
let start = 0;
|
|
6520
|
+
for (let index = 0; index < bytes.byteLength; index++) {
|
|
6521
|
+
if (bytes[index] !== NUL_BYTE) continue;
|
|
6522
|
+
invalidNameCount += appendName(bytes.slice(start, index), names, stderr);
|
|
6523
|
+
start = index + 1;
|
|
5697
6524
|
}
|
|
5698
|
-
|
|
6525
|
+
if (start < bytes.byteLength) invalidNameCount += appendName(bytes.slice(start), names, stderr);
|
|
6526
|
+
return {
|
|
6527
|
+
invalidNameCount,
|
|
6528
|
+
names
|
|
6529
|
+
};
|
|
5699
6530
|
}
|
|
5700
|
-
function
|
|
5701
|
-
if (
|
|
5702
|
-
|
|
5703
|
-
|
|
6531
|
+
function appendName(bytes, names, stderr) {
|
|
6532
|
+
if (bytes.byteLength === 0) {
|
|
6533
|
+
stderr.push("wc: invalid zero-length file name");
|
|
6534
|
+
return 1;
|
|
6535
|
+
}
|
|
6536
|
+
names.push(UTF8_DECODER.decode(bytes));
|
|
6537
|
+
return 0;
|
|
6538
|
+
}
|
|
6539
|
+
async function readFileOrReport(fs, path, stderr) {
|
|
6540
|
+
try {
|
|
6541
|
+
return await fs.readFile(path);
|
|
6542
|
+
} catch {
|
|
6543
|
+
stderr.push(`wc: ${path}: No such file or directory`);
|
|
6544
|
+
return null;
|
|
6545
|
+
}
|
|
6546
|
+
}
|
|
6547
|
+
function createStdinReader(input, redirectedInputBytes) {
|
|
6548
|
+
let hasRead = false;
|
|
6549
|
+
return async () => {
|
|
6550
|
+
if (hasRead) return DEFAULT_STDIO_BYTES;
|
|
6551
|
+
hasRead = true;
|
|
6552
|
+
return redirectedInputBytes ?? readStreamBytes(input);
|
|
5704
6553
|
};
|
|
5705
|
-
|
|
5706
|
-
|
|
5707
|
-
|
|
5708
|
-
|
|
6554
|
+
}
|
|
6555
|
+
async function readStreamBytes(input) {
|
|
6556
|
+
if (!input) return DEFAULT_STDIO_BYTES;
|
|
6557
|
+
const textParts = [];
|
|
6558
|
+
for await (const record of input) textParts.push(formatRecord(record));
|
|
6559
|
+
return UTF8_ENCODER.encode(textParts.join("\n"));
|
|
6560
|
+
}
|
|
6561
|
+
function countBytes(bytes) {
|
|
6562
|
+
const text = UTF8_DECODER.decode(bytes);
|
|
6563
|
+
return {
|
|
6564
|
+
bytes: bytes.byteLength,
|
|
6565
|
+
chars: [...text].length,
|
|
6566
|
+
lines: countLines(bytes),
|
|
6567
|
+
maxLineLength: countMaxLineLength(text),
|
|
6568
|
+
words: countWords(text)
|
|
5709
6569
|
};
|
|
5710
|
-
|
|
5711
|
-
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
|
|
6570
|
+
}
|
|
6571
|
+
function countLines(bytes) {
|
|
6572
|
+
let lines = 0;
|
|
6573
|
+
for (const byte of bytes) if (byte === NEWLINE_BYTE) lines++;
|
|
6574
|
+
return lines;
|
|
6575
|
+
}
|
|
6576
|
+
function countWords(text) {
|
|
6577
|
+
return text.split(WORD_SEPARATOR_REGEX).filter((word) => word.length > 0).length;
|
|
6578
|
+
}
|
|
6579
|
+
function countMaxLineLength(text) {
|
|
6580
|
+
let maxLength = 0;
|
|
6581
|
+
let linePosition = 0;
|
|
6582
|
+
for (const character of text) switch (character) {
|
|
6583
|
+
case "\n":
|
|
6584
|
+
case "\r":
|
|
6585
|
+
case "\f":
|
|
6586
|
+
if (linePosition > maxLength) maxLength = linePosition;
|
|
6587
|
+
linePosition = 0;
|
|
6588
|
+
break;
|
|
6589
|
+
case " ":
|
|
6590
|
+
linePosition += TAB_WIDTH - linePosition % TAB_WIDTH;
|
|
6591
|
+
break;
|
|
6592
|
+
case "\v": break;
|
|
6593
|
+
case " ":
|
|
6594
|
+
linePosition++;
|
|
6595
|
+
break;
|
|
6596
|
+
default: linePosition += displayWidth(character);
|
|
5725
6597
|
}
|
|
5726
|
-
if (
|
|
5727
|
-
|
|
5728
|
-
value: (async () => {
|
|
5729
|
-
const outputPath = await resolveRedirectPath(lastStep.cmd, lastStep.redirections, "output", fs, context);
|
|
5730
|
-
if (!outputPath) throw new Error(`${lastStep.cmd}: output redirection missing target`);
|
|
5731
|
-
const redirectedResult = applyOutputRedirect({
|
|
5732
|
-
kind: "stream",
|
|
5733
|
-
value: executePipelineToStream(ir.steps, fs, context, { finalGrepOutputRedirectPath: outputPath })
|
|
5734
|
-
}, lastStep, fs, context, outputPath);
|
|
5735
|
-
if (redirectedResult.kind !== "sink") throw new Error(`${lastStep.cmd}: output redirection did not produce a sink`);
|
|
5736
|
-
await redirectedResult.value;
|
|
5737
|
-
})()
|
|
5738
|
-
};
|
|
5739
|
-
return applyOutputRedirect({
|
|
5740
|
-
kind: "stream",
|
|
5741
|
-
value: executePipelineToStream(ir.steps, fs, context)
|
|
5742
|
-
}, lastStep, fs, context);
|
|
6598
|
+
if (linePosition > maxLength) maxLength = linePosition;
|
|
6599
|
+
return maxLength;
|
|
5743
6600
|
}
|
|
5744
|
-
function
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
}
|
|
5751
|
-
if (!stream) return;
|
|
5752
|
-
yield* stream;
|
|
5753
|
-
})();
|
|
6601
|
+
function displayWidth(character) {
|
|
6602
|
+
const codePoint = character.codePointAt(0);
|
|
6603
|
+
if (codePoint === void 0 || isControlCodePoint(codePoint)) return 0;
|
|
6604
|
+
if (COMBINING_MARK_REGEX.test(character) || DEFAULT_IGNORABLE_CODE_POINT_REGEX.test(character)) return 0;
|
|
6605
|
+
if (isWideCodePoint(codePoint)) return 2;
|
|
6606
|
+
return 1;
|
|
5754
6607
|
}
|
|
5755
|
-
|
|
5756
|
-
|
|
5757
|
-
if (!(finalStep && isEffectStep(finalStep))) return;
|
|
5758
|
-
if (steps.length > 1) {
|
|
5759
|
-
const stream = executePipelineToStream(steps.slice(0, -1), fs, context);
|
|
5760
|
-
for await (const _record of stream);
|
|
5761
|
-
}
|
|
5762
|
-
await executeEffectStep(finalStep, fs, context);
|
|
6608
|
+
function isControlCodePoint(codePoint) {
|
|
6609
|
+
return codePoint <= ASCII_CONTROL_MAX || codePoint === DELETE_CHARACTER || codePoint >= C1_CONTROL_MIN && codePoint <= C1_CONTROL_MAX;
|
|
5763
6610
|
}
|
|
5764
|
-
function
|
|
5765
|
-
const
|
|
5766
|
-
|
|
5767
|
-
|
|
5768
|
-
|
|
5769
|
-
|
|
5770
|
-
|
|
5771
|
-
|
|
5772
|
-
|
|
5773
|
-
|
|
6611
|
+
function isWideCodePoint(codePoint) {
|
|
6612
|
+
for (const [start, end] of WIDE_CHARACTER_RANGES) if (codePoint >= start && codePoint <= end) return true;
|
|
6613
|
+
return false;
|
|
6614
|
+
}
|
|
6615
|
+
function normalizeSelection(parsed) {
|
|
6616
|
+
if (parsed.bytes || parsed.chars || parsed.lines || parsed.maxLineLength || parsed.words) return {
|
|
6617
|
+
bytes: parsed.bytes,
|
|
6618
|
+
chars: parsed.chars,
|
|
6619
|
+
lines: parsed.lines,
|
|
6620
|
+
maxLineLength: parsed.maxLineLength,
|
|
6621
|
+
words: parsed.words
|
|
6622
|
+
};
|
|
6623
|
+
return {
|
|
6624
|
+
bytes: true,
|
|
6625
|
+
chars: false,
|
|
6626
|
+
lines: true,
|
|
6627
|
+
maxLineLength: false,
|
|
6628
|
+
words: true
|
|
6629
|
+
};
|
|
6630
|
+
}
|
|
6631
|
+
function renderOutput(inputs, selection, totalMode, fromFiles0, operandCount) {
|
|
6632
|
+
const includeTotal = shouldIncludeTotal(operandCount, totalMode);
|
|
6633
|
+
const totals = sumCounts(inputs);
|
|
6634
|
+
if (totalMode === "only") return [renderCounts(totals, selection, 1, null)];
|
|
6635
|
+
if (inputs.length === 0 && !includeTotal) return [];
|
|
6636
|
+
const width = fieldWidth(inputs, totals, selection, fromFiles0);
|
|
6637
|
+
const lines = inputs.map((input) => renderCounts(input.counts, selection, width, input.displayPath ? quoteDisplayPath(input.displayPath) : null));
|
|
6638
|
+
if (includeTotal) lines.push(renderCounts(totals, selection, width, "total"));
|
|
6639
|
+
return lines;
|
|
6640
|
+
}
|
|
6641
|
+
function shouldIncludeTotal(operandCount, totalMode) {
|
|
6642
|
+
if (totalMode === "never" || totalMode === "only") return false;
|
|
6643
|
+
if (totalMode === "always") return true;
|
|
6644
|
+
return operandCount > 1;
|
|
6645
|
+
}
|
|
6646
|
+
function sumCounts(inputs) {
|
|
6647
|
+
const totals = {
|
|
6648
|
+
bytes: 0,
|
|
6649
|
+
chars: 0,
|
|
6650
|
+
lines: 0,
|
|
6651
|
+
maxLineLength: 0,
|
|
6652
|
+
words: 0
|
|
6653
|
+
};
|
|
6654
|
+
for (const input of inputs) {
|
|
6655
|
+
totals.bytes += input.counts.bytes;
|
|
6656
|
+
totals.chars += input.counts.chars;
|
|
6657
|
+
totals.lines += input.counts.lines;
|
|
6658
|
+
totals.words += input.counts.words;
|
|
6659
|
+
if (input.counts.maxLineLength > totals.maxLineLength) totals.maxLineLength = input.counts.maxLineLength;
|
|
6660
|
+
}
|
|
6661
|
+
return totals;
|
|
6662
|
+
}
|
|
6663
|
+
function fieldWidth(inputs, totals, selection, fromFiles0) {
|
|
6664
|
+
if (inputs.length === 1 && inputs[0]?.displayPath === DEFAULT_STDIN_DISPLAY_PATH && selectedFieldCount(selection) > 1) return DEFAULT_STDIN_FIELD_WIDTH;
|
|
6665
|
+
if (!fromFiles0 && inputs.length === 1) return 1;
|
|
6666
|
+
if (fromFiles0 && selectedFieldCount(selection) === 1) return 1;
|
|
6667
|
+
let width = 1;
|
|
6668
|
+
for (const input of inputs) width = Math.max(width, maxSelectedDigitCount(input.counts, selection));
|
|
6669
|
+
width = Math.max(width, maxSelectedDigitCount(totals, selection));
|
|
6670
|
+
return width;
|
|
6671
|
+
}
|
|
6672
|
+
function selectedFieldCount(selection) {
|
|
6673
|
+
return selectedValues({
|
|
6674
|
+
bytes: 0,
|
|
6675
|
+
chars: 0,
|
|
6676
|
+
lines: 0,
|
|
6677
|
+
maxLineLength: 0,
|
|
6678
|
+
words: 0
|
|
6679
|
+
}, selection).length;
|
|
6680
|
+
}
|
|
6681
|
+
function maxSelectedDigitCount(counts, selection) {
|
|
6682
|
+
return Math.max(...selectedValues(counts, selection).map((value) => String(value).length));
|
|
6683
|
+
}
|
|
6684
|
+
function renderCounts(counts, selection, width, displayPath) {
|
|
6685
|
+
const prefix = selectedValues(counts, selection).map((value) => String(value).padStart(width, " ")).join(" ");
|
|
6686
|
+
return displayPath ? `${prefix} ${displayPath}` : prefix;
|
|
6687
|
+
}
|
|
6688
|
+
function selectedValues(counts, selection) {
|
|
6689
|
+
const values = [];
|
|
6690
|
+
if (selection.lines) values.push(counts.lines);
|
|
6691
|
+
if (selection.words) values.push(counts.words);
|
|
6692
|
+
if (selection.bytes) values.push(counts.bytes);
|
|
6693
|
+
if (selection.chars) values.push(counts.chars);
|
|
6694
|
+
if (selection.maxLineLength) values.push(counts.maxLineLength);
|
|
6695
|
+
return values;
|
|
6696
|
+
}
|
|
6697
|
+
function quoteDisplayPath(path) {
|
|
6698
|
+
if (!path.includes("\n")) return path;
|
|
6699
|
+
return `'${path.split("\n").join("'$'\\n''")}'`;
|
|
6700
|
+
}
|
|
6701
|
+
//#endregion
|
|
6702
|
+
//#region src/operator/xargs/xargs.ts
|
|
6703
|
+
const DEFAULT_MAX_ARGS = Number.POSITIVE_INFINITY;
|
|
6704
|
+
const TEXT_DECODER = new TextDecoder();
|
|
6705
|
+
const WHITESPACE_REGEX = /\s/u;
|
|
6706
|
+
const LEADING_WHITESPACE_REGEX = /^\s+/u;
|
|
6707
|
+
async function runXargsCommand(options) {
|
|
6708
|
+
try {
|
|
6709
|
+
return await runXargsCommandInner(options);
|
|
6710
|
+
} catch {
|
|
6711
|
+
return {
|
|
6712
|
+
exitCode: 1,
|
|
6713
|
+
stderr: [],
|
|
6714
|
+
stdout: []
|
|
6715
|
+
};
|
|
6716
|
+
}
|
|
6717
|
+
}
|
|
6718
|
+
async function runXargsCommandInner(options) {
|
|
6719
|
+
const input = await readInput(options);
|
|
6720
|
+
const command = await evaluateExpandedWords(options.parsed.command, options.fs, options.context);
|
|
6721
|
+
const batches = buildBatches(input, options.parsed);
|
|
6722
|
+
if (batches.length === 0 && options.parsed.noRunIfEmpty) return {
|
|
6723
|
+
exitCode: 0,
|
|
6724
|
+
stderr: [],
|
|
6725
|
+
stdout: []
|
|
6726
|
+
};
|
|
6727
|
+
const stdout = [];
|
|
6728
|
+
const stderr = [];
|
|
6729
|
+
let exitCode = 0;
|
|
6730
|
+
for (const batch of batches.length > 0 ? batches : [[]]) {
|
|
6731
|
+
const result = await runCommand(buildCommandArgs(command, batch, options.parsed), options.fs, options.context);
|
|
6732
|
+
stdout.push(...result.stdout);
|
|
6733
|
+
stderr.push(...result.stderr);
|
|
6734
|
+
if (result.exitCode !== 0) exitCode = result.exitCode;
|
|
6735
|
+
}
|
|
6736
|
+
return {
|
|
6737
|
+
exitCode,
|
|
6738
|
+
stderr,
|
|
6739
|
+
stdout
|
|
6740
|
+
};
|
|
6741
|
+
}
|
|
6742
|
+
async function readInput(options) {
|
|
6743
|
+
if (options.inputPath) return TEXT_DECODER.decode(await options.fs.readFile(options.inputPath));
|
|
6744
|
+
if (!options.input) return "";
|
|
6745
|
+
const records = [];
|
|
6746
|
+
for await (const record of options.input) records.push(formatStdoutRecord(record));
|
|
6747
|
+
return records.join("\n");
|
|
6748
|
+
}
|
|
6749
|
+
function buildBatches(input, args) {
|
|
6750
|
+
if (args.replace) return replacementBatches(input, args);
|
|
6751
|
+
if (args.maxLines) return lineBatches(input, args);
|
|
6752
|
+
return chunkItems(args.delimiter === null ? tokenize(input, args.eof).items : splitDelimited(input, args.delimiter), args.maxArgs ?? DEFAULT_MAX_ARGS);
|
|
6753
|
+
}
|
|
6754
|
+
function replacementBatches(input, args) {
|
|
6755
|
+
const batches = [];
|
|
6756
|
+
for (const line of splitInputLines(input)) {
|
|
6757
|
+
const item = line.replace(LEADING_WHITESPACE_REGEX, "");
|
|
6758
|
+
if (item === "") continue;
|
|
6759
|
+
if (args.eof !== null && item === args.eof) break;
|
|
6760
|
+
batches.push([item]);
|
|
6761
|
+
}
|
|
6762
|
+
return batches;
|
|
6763
|
+
}
|
|
6764
|
+
function lineBatches(input, args) {
|
|
6765
|
+
const batches = [];
|
|
6766
|
+
let current = [];
|
|
6767
|
+
let lineCount = 0;
|
|
6768
|
+
for (const line of splitInputLines(input)) {
|
|
6769
|
+
const parsed = tokenize(line, args.eof);
|
|
6770
|
+
if (parsed.items.length === 0) {
|
|
6771
|
+
if (parsed.stopped) break;
|
|
6772
|
+
continue;
|
|
6773
|
+
}
|
|
6774
|
+
current.push(...parsed.items);
|
|
6775
|
+
lineCount++;
|
|
6776
|
+
if (lineCount >= (args.maxLines ?? 1)) {
|
|
6777
|
+
batches.push(current);
|
|
6778
|
+
current = [];
|
|
6779
|
+
lineCount = 0;
|
|
6780
|
+
}
|
|
6781
|
+
if (parsed.stopped) break;
|
|
6782
|
+
}
|
|
6783
|
+
if (current.length > 0) batches.push(current);
|
|
6784
|
+
return batches;
|
|
6785
|
+
}
|
|
6786
|
+
function chunkItems(items, maxArgs) {
|
|
6787
|
+
if (items.length === 0) return [];
|
|
6788
|
+
if (!Number.isFinite(maxArgs)) return [items];
|
|
6789
|
+
const chunks = [];
|
|
6790
|
+
for (let index = 0; index < items.length; index += maxArgs) chunks.push(items.slice(index, index + maxArgs));
|
|
6791
|
+
return chunks;
|
|
6792
|
+
}
|
|
6793
|
+
function splitDelimited(input, delimiter) {
|
|
6794
|
+
if (delimiter === "") return input === "" ? [] : [input];
|
|
6795
|
+
const parts = input.split(delimiter);
|
|
6796
|
+
if (input.endsWith(delimiter)) parts.pop();
|
|
6797
|
+
return parts;
|
|
6798
|
+
}
|
|
6799
|
+
function splitInputLines(input) {
|
|
6800
|
+
const lines = input.split("\n");
|
|
6801
|
+
if (input.endsWith("\n")) lines.pop();
|
|
6802
|
+
return lines;
|
|
6803
|
+
}
|
|
6804
|
+
function tokenize(input, eof) {
|
|
6805
|
+
const items = [];
|
|
6806
|
+
const state = {
|
|
6807
|
+
current: "",
|
|
6808
|
+
escaped: false,
|
|
6809
|
+
quote: null
|
|
6810
|
+
};
|
|
6811
|
+
for (const char of input) {
|
|
6812
|
+
if (state.escaped) {
|
|
6813
|
+
appendEscapedChar(state, char);
|
|
6814
|
+
continue;
|
|
6815
|
+
}
|
|
6816
|
+
if (state.quote) {
|
|
6817
|
+
appendQuotedChar(state, char);
|
|
6818
|
+
continue;
|
|
6819
|
+
}
|
|
6820
|
+
if (WHITESPACE_REGEX.test(char)) {
|
|
6821
|
+
const stopped = pushToken(items, state.current, eof);
|
|
6822
|
+
state.current = "";
|
|
6823
|
+
if (stopped) return {
|
|
6824
|
+
items,
|
|
6825
|
+
stopped: true
|
|
6826
|
+
};
|
|
6827
|
+
continue;
|
|
6828
|
+
}
|
|
6829
|
+
appendUnquotedChar(state, char);
|
|
6830
|
+
}
|
|
6831
|
+
if (state.quote) throw new Error(`xargs: unterminated quote ${state.quote}`);
|
|
6832
|
+
if (state.escaped) throw new Error("xargs: unterminated escape");
|
|
6833
|
+
return {
|
|
6834
|
+
items,
|
|
6835
|
+
stopped: pushToken(items, state.current, eof)
|
|
6836
|
+
};
|
|
6837
|
+
}
|
|
6838
|
+
function appendEscapedChar(state, char) {
|
|
6839
|
+
state.current += char;
|
|
6840
|
+
state.escaped = false;
|
|
6841
|
+
}
|
|
6842
|
+
function appendQuotedChar(state, char) {
|
|
6843
|
+
if (char === state.quote) {
|
|
6844
|
+
state.quote = null;
|
|
6845
|
+
return;
|
|
6846
|
+
}
|
|
6847
|
+
if (char === "\\") {
|
|
6848
|
+
state.escaped = true;
|
|
6849
|
+
return;
|
|
6850
|
+
}
|
|
6851
|
+
state.current += char;
|
|
6852
|
+
}
|
|
6853
|
+
function appendUnquotedChar(state, char) {
|
|
6854
|
+
if (char === "\"" || char === "'") {
|
|
6855
|
+
state.quote = char;
|
|
6856
|
+
return;
|
|
6857
|
+
}
|
|
6858
|
+
if (char === "\\") {
|
|
6859
|
+
state.escaped = true;
|
|
6860
|
+
return;
|
|
6861
|
+
}
|
|
6862
|
+
state.current += char;
|
|
6863
|
+
}
|
|
6864
|
+
function pushToken(items, token, eof) {
|
|
6865
|
+
if (token === "") return false;
|
|
6866
|
+
if (eof !== null && token === eof) return true;
|
|
6867
|
+
items.push(token);
|
|
6868
|
+
return false;
|
|
6869
|
+
}
|
|
6870
|
+
function buildCommandArgs(command, batch, args) {
|
|
6871
|
+
if (!args.replace) return [...command, ...batch];
|
|
6872
|
+
return command.map((part) => part.replaceAll(args.replace ?? "", batch[0] ?? ""));
|
|
6873
|
+
}
|
|
6874
|
+
async function runCommand(argv, fs, context) {
|
|
6875
|
+
if (argv.length === 0) return {
|
|
6876
|
+
exitCode: 0,
|
|
6877
|
+
stderr: [],
|
|
6878
|
+
stdout: []
|
|
6879
|
+
};
|
|
6880
|
+
const childContext = {
|
|
6881
|
+
cwd: context.cwd,
|
|
6882
|
+
globalVars: context.globalVars,
|
|
6883
|
+
localVars: context.localVars,
|
|
6884
|
+
status: context.status,
|
|
6885
|
+
stderr: new BufferedOutputStream()
|
|
6886
|
+
};
|
|
6887
|
+
const stdout = await collectStdout((await Promise.resolve().then(() => execute_exports)).execute(compile(parse(argv.map(quoteShellWord).join(" "))), fs, childContext));
|
|
6888
|
+
return {
|
|
6889
|
+
exitCode: childContext.status,
|
|
6890
|
+
stderr: [...childContext.stderr.snapshot()],
|
|
6891
|
+
stdout
|
|
6892
|
+
};
|
|
6893
|
+
}
|
|
6894
|
+
async function collectStdout(result) {
|
|
6895
|
+
if (result.kind === "sink") {
|
|
6896
|
+
await result.value;
|
|
6897
|
+
return [];
|
|
6898
|
+
}
|
|
6899
|
+
const stdout = [];
|
|
6900
|
+
for await (const record of result.value) stdout.push(formatStdoutRecord(record));
|
|
6901
|
+
return stdout;
|
|
6902
|
+
}
|
|
6903
|
+
function quoteShellWord(value) {
|
|
6904
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
6905
|
+
}
|
|
6906
|
+
//#endregion
|
|
6907
|
+
//#region src/execute/producers.ts
|
|
6908
|
+
async function* files(...paths) {
|
|
6909
|
+
for (const path of paths) yield {
|
|
6910
|
+
kind: "file",
|
|
6911
|
+
path
|
|
6912
|
+
};
|
|
6913
|
+
}
|
|
6914
|
+
//#endregion
|
|
6915
|
+
//#region src/execute/registry.ts
|
|
6916
|
+
const EFFECT_COMMANDS = [
|
|
6917
|
+
"cd",
|
|
6918
|
+
"cp",
|
|
6919
|
+
"mkdir",
|
|
6920
|
+
"mv",
|
|
6921
|
+
"rm",
|
|
6922
|
+
"touch"
|
|
6923
|
+
];
|
|
6924
|
+
const EFFECT_COMMAND_SET = new Set(EFFECT_COMMANDS);
|
|
6925
|
+
const STREAM_COMMANDS = [
|
|
6926
|
+
"cat",
|
|
6927
|
+
"echo",
|
|
6928
|
+
"find",
|
|
6929
|
+
"grep",
|
|
6930
|
+
"head",
|
|
6931
|
+
"ls",
|
|
6932
|
+
"pwd",
|
|
6933
|
+
"read",
|
|
6934
|
+
"set",
|
|
6935
|
+
"string",
|
|
6936
|
+
"tail",
|
|
6937
|
+
"test",
|
|
6938
|
+
"xargs",
|
|
6939
|
+
"wc"
|
|
6940
|
+
];
|
|
6941
|
+
const STREAM_COMMAND_SET = new Set(STREAM_COMMANDS);
|
|
6942
|
+
const ROOT_DIRECTORY$1 = "/";
|
|
6943
|
+
function lineRecordsFromPath(fs, path) {
|
|
6944
|
+
return (async function* () {
|
|
6945
|
+
for await (const line of fs.readLines(path)) yield {
|
|
6946
|
+
kind: "line",
|
|
6947
|
+
text: line
|
|
6948
|
+
};
|
|
6949
|
+
})();
|
|
6950
|
+
}
|
|
6951
|
+
let commandRegistriesVerified = false;
|
|
6952
|
+
function isStreamCommand(command) {
|
|
6953
|
+
return STREAM_COMMAND_SET.has(command);
|
|
6954
|
+
}
|
|
6955
|
+
function verifyCommandRegistries() {
|
|
6956
|
+
if (commandRegistriesVerified) return;
|
|
6957
|
+
for (const command of EFFECT_COMMANDS) try {
|
|
6958
|
+
CommandRegistry.getEffect(command);
|
|
6959
|
+
} catch {
|
|
6960
|
+
throw new Error(`Missing effect command handler: ${command}`);
|
|
6961
|
+
}
|
|
6962
|
+
for (const command of STREAM_COMMANDS) try {
|
|
6963
|
+
CommandRegistry.getStream(command);
|
|
6964
|
+
} catch {
|
|
6965
|
+
throw new Error(`Missing stream command handler: ${command}`);
|
|
6966
|
+
}
|
|
6967
|
+
commandRegistriesVerified = true;
|
|
6968
|
+
}
|
|
6969
|
+
function executeStreamStep$1({ step, fs, input, context, resolvedOutputRedirectPath }) {
|
|
6970
|
+
verifyCommandRegistries();
|
|
6971
|
+
if (!isStreamCommand(step.cmd)) throw new Error(`Command "${step.cmd}" is not a stream command`);
|
|
6972
|
+
return CommandRegistry.getStream(step.cmd)({
|
|
6973
|
+
step,
|
|
6974
|
+
fs,
|
|
6975
|
+
input,
|
|
6976
|
+
context,
|
|
6977
|
+
resolvedOutputRedirectPath
|
|
6978
|
+
});
|
|
6979
|
+
}
|
|
6980
|
+
async function executeEffectStep$1({ step, fs, context }) {
|
|
6981
|
+
verifyCommandRegistries();
|
|
6982
|
+
await CommandRegistry.getEffect(step.cmd)({
|
|
6983
|
+
step,
|
|
6984
|
+
fs,
|
|
6985
|
+
context
|
|
6986
|
+
});
|
|
6987
|
+
}
|
|
6988
|
+
function createBuiltinRuntime(fs, context, input) {
|
|
6989
|
+
return {
|
|
6990
|
+
fs,
|
|
6991
|
+
context,
|
|
6992
|
+
input
|
|
6993
|
+
};
|
|
6994
|
+
}
|
|
6995
|
+
function formatLongListing(path, stat) {
|
|
6996
|
+
return `${stat.isDirectory ? "d" : "-"} ${String(stat.size).padStart(8, " ")} ${stat.mtime.toISOString()} ${path}`;
|
|
6997
|
+
}
|
|
6998
|
+
function normalizeLsPath(path, cwd) {
|
|
6999
|
+
if (path === "." || path === "./") return cwd;
|
|
7000
|
+
if (path.startsWith("./")) return `${cwd}/${path.slice(2)}`;
|
|
7001
|
+
if (path.startsWith(ROOT_DIRECTORY$1)) return path;
|
|
7002
|
+
return `${cwd}/${path}`;
|
|
7003
|
+
}
|
|
7004
|
+
function resolveLsPath(path, cwd) {
|
|
7005
|
+
return normalizeLsPath(path, cwd);
|
|
7006
|
+
}
|
|
7007
|
+
let CommandRegistry;
|
|
7008
|
+
(function(_CommandRegistry) {
|
|
7009
|
+
const commands = /* @__PURE__ */ new Map();
|
|
7010
|
+
function register(command, entry) {
|
|
7011
|
+
commands.set(command, entry);
|
|
7012
|
+
}
|
|
7013
|
+
_CommandRegistry.register = register;
|
|
7014
|
+
function getStream(command) {
|
|
7015
|
+
const entry = commands.get(command);
|
|
7016
|
+
if (!entry) throw new Error(`Unknown command: ${command}`);
|
|
7017
|
+
if (entry.kind !== "stream") throw new Error(`Command "${command}" is not a stream command`);
|
|
7018
|
+
return entry.handler;
|
|
7019
|
+
}
|
|
7020
|
+
_CommandRegistry.getStream = getStream;
|
|
7021
|
+
function getEffect(command) {
|
|
7022
|
+
const entry = commands.get(command);
|
|
7023
|
+
if (!entry) throw new Error(`Unknown command: ${command}`);
|
|
7024
|
+
if (entry.kind !== "effect") throw new Error(`Command "${command}" is not an effect command`);
|
|
7025
|
+
return entry.handler;
|
|
7026
|
+
}
|
|
7027
|
+
_CommandRegistry.getEffect = getEffect;
|
|
7028
|
+
function isEffectStep(step) {
|
|
7029
|
+
return EFFECT_COMMAND_SET.has(step.cmd);
|
|
7030
|
+
}
|
|
7031
|
+
_CommandRegistry.isEffectStep = isEffectStep;
|
|
7032
|
+
function executeStep(params) {
|
|
7033
|
+
if (isEffectStep(params.step)) return executeEffectStep$1(params);
|
|
7034
|
+
return executeStreamStep$1(params);
|
|
7035
|
+
}
|
|
7036
|
+
_CommandRegistry.executeStep = executeStep;
|
|
7037
|
+
})(CommandRegistry || (CommandRegistry = {}));
|
|
7038
|
+
CommandRegistry.register("cat", {
|
|
7039
|
+
kind: "stream",
|
|
7040
|
+
handler: ({ step, fs, input, context }) => {
|
|
7041
|
+
return (async function* () {
|
|
7042
|
+
const options = {
|
|
7043
|
+
numberLines: step.args.numberLines,
|
|
7044
|
+
numberNonBlank: step.args.numberNonBlank,
|
|
7045
|
+
showAll: step.args.showAll,
|
|
7046
|
+
showEnds: step.args.showEnds,
|
|
7047
|
+
showNonprinting: step.args.showNonprinting,
|
|
5774
7048
|
showTabs: step.args.showTabs,
|
|
5775
7049
|
squeezeBlank: step.args.squeezeBlank
|
|
5776
7050
|
};
|
|
@@ -5784,7 +7058,12 @@ function executeStreamStep(step, fs, input, context, resolvedOutputRedirectPath)
|
|
|
5784
7058
|
if (input) yield* cat(fs, options)(input);
|
|
5785
7059
|
context.status = 0;
|
|
5786
7060
|
})();
|
|
5787
|
-
|
|
7061
|
+
}
|
|
7062
|
+
});
|
|
7063
|
+
CommandRegistry.register("grep", {
|
|
7064
|
+
kind: "stream",
|
|
7065
|
+
handler: ({ step, fs, input, context, resolvedOutputRedirectPath }) => {
|
|
7066
|
+
return (async function* () {
|
|
5788
7067
|
const result = await runGrepCommand({
|
|
5789
7068
|
context,
|
|
5790
7069
|
fs,
|
|
@@ -5794,14 +7073,64 @@ function executeStreamStep(step, fs, input, context, resolvedOutputRedirectPath)
|
|
|
5794
7073
|
resolvedOutputRedirectPath
|
|
5795
7074
|
});
|
|
5796
7075
|
context.status = result.exitCode;
|
|
5797
|
-
context.stderr.
|
|
7076
|
+
context.stderr.appendLines(result.stderr);
|
|
7077
|
+
for (const text of result.stdout) yield {
|
|
7078
|
+
kind: "line",
|
|
7079
|
+
text
|
|
7080
|
+
};
|
|
7081
|
+
})();
|
|
7082
|
+
}
|
|
7083
|
+
});
|
|
7084
|
+
CommandRegistry.register("find", {
|
|
7085
|
+
kind: "stream",
|
|
7086
|
+
handler: ({ step, fs, context }) => {
|
|
7087
|
+
return find(fs, context, step.args);
|
|
7088
|
+
}
|
|
7089
|
+
});
|
|
7090
|
+
CommandRegistry.register("xargs", {
|
|
7091
|
+
kind: "stream",
|
|
7092
|
+
handler: ({ step, fs, input, context }) => {
|
|
7093
|
+
return (async function* () {
|
|
7094
|
+
const result = await runXargsCommand({
|
|
7095
|
+
context,
|
|
7096
|
+
fs,
|
|
7097
|
+
input,
|
|
7098
|
+
inputPath: await resolveRedirectPath(step.cmd, step.redirections, "input", fs, context),
|
|
7099
|
+
parsed: step.args
|
|
7100
|
+
});
|
|
7101
|
+
context.status = result.exitCode;
|
|
7102
|
+
context.stderr.appendLines(result.stderr);
|
|
7103
|
+
for (const text of result.stdout) yield {
|
|
7104
|
+
kind: "line",
|
|
7105
|
+
text
|
|
7106
|
+
};
|
|
7107
|
+
})();
|
|
7108
|
+
}
|
|
7109
|
+
});
|
|
7110
|
+
CommandRegistry.register("wc", {
|
|
7111
|
+
kind: "stream",
|
|
7112
|
+
handler: ({ step, fs, input, context }) => {
|
|
7113
|
+
return (async function* () {
|
|
7114
|
+
const result = await runWcCommand({
|
|
7115
|
+
context,
|
|
7116
|
+
fs,
|
|
7117
|
+
input,
|
|
7118
|
+
inputPath: await resolveRedirectPath(step.cmd, step.redirections, "input", fs, context),
|
|
7119
|
+
parsed: step.args
|
|
7120
|
+
});
|
|
7121
|
+
context.status = result.exitCode;
|
|
7122
|
+
context.stderr.appendLines(result.stderr);
|
|
5798
7123
|
for (const text of result.stdout) yield {
|
|
5799
7124
|
kind: "line",
|
|
5800
7125
|
text
|
|
5801
7126
|
};
|
|
5802
7127
|
})();
|
|
5803
|
-
|
|
5804
|
-
|
|
7128
|
+
}
|
|
7129
|
+
});
|
|
7130
|
+
CommandRegistry.register("head", {
|
|
7131
|
+
kind: "stream",
|
|
7132
|
+
handler: ({ step, fs, input, context }) => {
|
|
7133
|
+
return (async function* () {
|
|
5805
7134
|
const inputPath = await resolveRedirectPath(step.cmd, step.redirections, "input", fs, context);
|
|
5806
7135
|
const filePaths = withInputRedirect(resolvePathsFromCwd(context.cwd, await evaluateExpandedPathWords("head", step.args.files, fs, context)), inputPath);
|
|
5807
7136
|
if (filePaths.length > 0) {
|
|
@@ -5812,7 +7141,12 @@ function executeStreamStep(step, fs, input, context, resolvedOutputRedirectPath)
|
|
|
5812
7141
|
if (input) yield* headLines(step.args.n)(toFormattedLineStream(input));
|
|
5813
7142
|
context.status = 0;
|
|
5814
7143
|
})();
|
|
5815
|
-
|
|
7144
|
+
}
|
|
7145
|
+
});
|
|
7146
|
+
CommandRegistry.register("ls", {
|
|
7147
|
+
kind: "stream",
|
|
7148
|
+
handler: ({ step, fs, context }) => {
|
|
7149
|
+
return (async function* () {
|
|
5816
7150
|
const paths = await evaluateExpandedPathWords("ls", step.args.paths, fs, context);
|
|
5817
7151
|
for (const inputPath of paths) {
|
|
5818
7152
|
const resolvedPath = resolveLsPath(inputPath, context.cwd);
|
|
@@ -5830,7 +7164,12 @@ function executeStreamStep(step, fs, input, context, resolvedOutputRedirectPath)
|
|
|
5830
7164
|
}
|
|
5831
7165
|
context.status = 0;
|
|
5832
7166
|
})();
|
|
5833
|
-
|
|
7167
|
+
}
|
|
7168
|
+
});
|
|
7169
|
+
CommandRegistry.register("tail", {
|
|
7170
|
+
kind: "stream",
|
|
7171
|
+
handler: ({ step, fs, input, context }) => {
|
|
7172
|
+
return (async function* () {
|
|
5834
7173
|
const inputPath = await resolveRedirectPath(step.cmd, step.redirections, "input", fs, context);
|
|
5835
7174
|
const filePaths = withInputRedirect(resolvePathsFromCwd(context.cwd, await evaluateExpandedPathWords("tail", step.args.files, fs, context)), inputPath);
|
|
5836
7175
|
if (filePaths.length > 0) {
|
|
@@ -5841,118 +7180,540 @@ function executeStreamStep(step, fs, input, context, resolvedOutputRedirectPath)
|
|
|
5841
7180
|
if (input) yield* tail(step.args.n)(toFormattedLineStream(input));
|
|
5842
7181
|
context.status = 0;
|
|
5843
7182
|
})();
|
|
5844
|
-
|
|
7183
|
+
}
|
|
7184
|
+
});
|
|
7185
|
+
CommandRegistry.register("pwd", {
|
|
7186
|
+
kind: "stream",
|
|
7187
|
+
handler: ({ context }) => {
|
|
7188
|
+
return (async function* () {
|
|
5845
7189
|
yield* pwd(context.cwd);
|
|
5846
7190
|
context.status = 0;
|
|
5847
7191
|
})();
|
|
5848
|
-
case "echo": return echo(builtinRuntime, step.args);
|
|
5849
|
-
case "set": return set(builtinRuntime, step.args);
|
|
5850
|
-
case "test": return test(builtinRuntime, step.args);
|
|
5851
|
-
case "read": return read(builtinRuntime, step.args);
|
|
5852
|
-
case "string": return string(builtinRuntime, step.args);
|
|
5853
|
-
default: {
|
|
5854
|
-
const _exhaustive = step;
|
|
5855
|
-
throw new Error(`Unknown command: ${String(_exhaustive.cmd)}`);
|
|
5856
|
-
}
|
|
5857
7192
|
}
|
|
7193
|
+
});
|
|
7194
|
+
CommandRegistry.register("echo", {
|
|
7195
|
+
kind: "stream",
|
|
7196
|
+
handler: ({ step, fs, input, context }) => {
|
|
7197
|
+
return echo(createBuiltinRuntime(fs, context, input), step.args);
|
|
7198
|
+
}
|
|
7199
|
+
});
|
|
7200
|
+
CommandRegistry.register("set", {
|
|
7201
|
+
kind: "stream",
|
|
7202
|
+
handler: ({ step, fs, input, context }) => {
|
|
7203
|
+
return set(createBuiltinRuntime(fs, context, input), step.args);
|
|
7204
|
+
}
|
|
7205
|
+
});
|
|
7206
|
+
CommandRegistry.register("test", {
|
|
7207
|
+
kind: "stream",
|
|
7208
|
+
handler: ({ step, fs, input, context }) => {
|
|
7209
|
+
return test(createBuiltinRuntime(fs, context, input), step.args);
|
|
7210
|
+
}
|
|
7211
|
+
});
|
|
7212
|
+
CommandRegistry.register("read", {
|
|
7213
|
+
kind: "stream",
|
|
7214
|
+
handler: ({ step, fs, input, context }) => {
|
|
7215
|
+
return (async function* () {
|
|
7216
|
+
const resolvedInput = await resolveInputRedirect(step.cmd, step.redirections, fs, context);
|
|
7217
|
+
if (resolvedInput.closed) {
|
|
7218
|
+
context.stderr.append("read: stdin is closed");
|
|
7219
|
+
context.status = 1;
|
|
7220
|
+
return;
|
|
7221
|
+
}
|
|
7222
|
+
yield* read(createBuiltinRuntime(fs, context, resolvedInput.path ? lineRecordsFromPath(fs, resolvedInput.path) : input), step.args);
|
|
7223
|
+
})();
|
|
7224
|
+
}
|
|
7225
|
+
});
|
|
7226
|
+
CommandRegistry.register("string", {
|
|
7227
|
+
kind: "stream",
|
|
7228
|
+
handler: ({ step, fs, input, context }) => {
|
|
7229
|
+
return string(createBuiltinRuntime(fs, context, input), step.args);
|
|
7230
|
+
}
|
|
7231
|
+
});
|
|
7232
|
+
CommandRegistry.register("cd", {
|
|
7233
|
+
kind: "effect",
|
|
7234
|
+
handler: async ({ step, fs, context }) => {
|
|
7235
|
+
await cd(createBuiltinRuntime(fs, context, null), step.args);
|
|
7236
|
+
}
|
|
7237
|
+
});
|
|
7238
|
+
CommandRegistry.register("cp", {
|
|
7239
|
+
kind: "effect",
|
|
7240
|
+
handler: async ({ step, fs, context }) => {
|
|
7241
|
+
const srcPaths = resolvePathsFromCwd(context.cwd, await evaluateExpandedPathWords("cp", step.args.srcs, fs, context));
|
|
7242
|
+
const destinationPath = resolvePathsFromCwd(context.cwd, [await evaluateExpandedSinglePath("cp", "destination must expand to exactly 1 path", step.args.dest, fs, context)]).at(0);
|
|
7243
|
+
if (destinationPath === void 0) throw new Error("cp: destination missing after expansion");
|
|
7244
|
+
await cp(fs)({
|
|
7245
|
+
srcs: srcPaths,
|
|
7246
|
+
dest: destinationPath,
|
|
7247
|
+
force: step.args.force,
|
|
7248
|
+
interactive: step.args.interactive,
|
|
7249
|
+
recursive: step.args.recursive
|
|
7250
|
+
});
|
|
7251
|
+
context.status = 0;
|
|
7252
|
+
}
|
|
7253
|
+
});
|
|
7254
|
+
CommandRegistry.register("mkdir", {
|
|
7255
|
+
kind: "effect",
|
|
7256
|
+
handler: async ({ step, fs, context }) => {
|
|
7257
|
+
const paths = resolvePathsFromCwd(context.cwd, await evaluateExpandedPathWords("mkdir", step.args.paths, fs, context));
|
|
7258
|
+
for (const path of paths) await mkdir(fs)({
|
|
7259
|
+
path,
|
|
7260
|
+
recursive: step.args.recursive
|
|
7261
|
+
});
|
|
7262
|
+
context.status = 0;
|
|
7263
|
+
}
|
|
7264
|
+
});
|
|
7265
|
+
CommandRegistry.register("mv", {
|
|
7266
|
+
kind: "effect",
|
|
7267
|
+
handler: async ({ step, fs, context }) => {
|
|
7268
|
+
const srcPaths = resolvePathsFromCwd(context.cwd, await evaluateExpandedPathWords("mv", step.args.srcs, fs, context));
|
|
7269
|
+
const destinationPath = resolvePathsFromCwd(context.cwd, [await evaluateExpandedSinglePath("mv", "destination must expand to exactly 1 path", step.args.dest, fs, context)]).at(0);
|
|
7270
|
+
if (destinationPath === void 0) throw new Error("mv: destination missing after expansion");
|
|
7271
|
+
await mv(fs)({
|
|
7272
|
+
srcs: srcPaths,
|
|
7273
|
+
dest: destinationPath,
|
|
7274
|
+
force: step.args.force,
|
|
7275
|
+
interactive: step.args.interactive
|
|
7276
|
+
});
|
|
7277
|
+
context.status = 0;
|
|
7278
|
+
}
|
|
7279
|
+
});
|
|
7280
|
+
CommandRegistry.register("rm", {
|
|
7281
|
+
kind: "effect",
|
|
7282
|
+
handler: async ({ step, fs, context }) => {
|
|
7283
|
+
const paths = resolvePathsFromCwd(context.cwd, await evaluateExpandedPathWords("rm", step.args.paths, fs, context));
|
|
7284
|
+
for (const path of paths) await rm(fs)({
|
|
7285
|
+
path,
|
|
7286
|
+
force: step.args.force,
|
|
7287
|
+
interactive: step.args.interactive,
|
|
7288
|
+
recursive: step.args.recursive
|
|
7289
|
+
});
|
|
7290
|
+
context.status = 0;
|
|
7291
|
+
}
|
|
7292
|
+
});
|
|
7293
|
+
CommandRegistry.register("touch", {
|
|
7294
|
+
kind: "effect",
|
|
7295
|
+
handler: async ({ step, fs, context }) => {
|
|
7296
|
+
const filePaths = resolvePathsFromCwd(context.cwd, await evaluateExpandedPathWords("touch", step.args.files, fs, context));
|
|
7297
|
+
await touch(fs)({
|
|
7298
|
+
files: filePaths,
|
|
7299
|
+
accessTimeOnly: step.args.accessTimeOnly,
|
|
7300
|
+
modificationTimeOnly: step.args.modificationTimeOnly
|
|
7301
|
+
});
|
|
7302
|
+
context.status = 0;
|
|
7303
|
+
}
|
|
7304
|
+
});
|
|
7305
|
+
//#endregion
|
|
7306
|
+
//#region src/execute/execute.ts
|
|
7307
|
+
var execute_exports = /* @__PURE__ */ __exportAll({ execute: () => execute });
|
|
7308
|
+
const ROOT_DIRECTORY = "/";
|
|
7309
|
+
const FD_TARGET_REGEX = /^&[0-9]+$/;
|
|
7310
|
+
async function* emptyStream() {}
|
|
7311
|
+
function isScriptIR(ir) {
|
|
7312
|
+
return "statements" in ir;
|
|
5858
7313
|
}
|
|
5859
|
-
|
|
5860
|
-
|
|
5861
|
-
|
|
5862
|
-
|
|
5863
|
-
|
|
5864
|
-
|
|
5865
|
-
|
|
5866
|
-
|
|
5867
|
-
|
|
5868
|
-
|
|
5869
|
-
|
|
5870
|
-
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
|
|
5874
|
-
|
|
5875
|
-
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
|
|
5881
|
-
|
|
5882
|
-
|
|
5883
|
-
|
|
5884
|
-
|
|
5885
|
-
|
|
5886
|
-
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
|
|
5892
|
-
|
|
5893
|
-
|
|
5894
|
-
|
|
5895
|
-
|
|
5896
|
-
|
|
5897
|
-
|
|
5898
|
-
|
|
5899
|
-
|
|
5900
|
-
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
|
|
5905
|
-
|
|
5906
|
-
|
|
5907
|
-
|
|
5908
|
-
|
|
5909
|
-
|
|
5910
|
-
|
|
7314
|
+
function toScriptIR(pipeline) {
|
|
7315
|
+
return { statements: [{
|
|
7316
|
+
chainMode: "always",
|
|
7317
|
+
pipeline
|
|
7318
|
+
}] };
|
|
7319
|
+
}
|
|
7320
|
+
function shouldExecuteStatement(chainMode, previousStatus) {
|
|
7321
|
+
if (chainMode === "always") return true;
|
|
7322
|
+
if (chainMode === "and") return previousStatus === 0;
|
|
7323
|
+
return previousStatus !== 0;
|
|
7324
|
+
}
|
|
7325
|
+
function execute(ir, fs, context = { cwd: ROOT_DIRECTORY }) {
|
|
7326
|
+
const normalizedContext = normalizeContext(context);
|
|
7327
|
+
return executeScript(isScriptIR(ir) ? ir : toScriptIR(ir), fs, normalizedContext);
|
|
7328
|
+
}
|
|
7329
|
+
function isPipelineSink(pipeline) {
|
|
7330
|
+
if (hasMidPipelineStdoutBypass(pipeline.steps)) return false;
|
|
7331
|
+
const finalStep = pipeline.steps.at(-1);
|
|
7332
|
+
if (!finalStep) return false;
|
|
7333
|
+
return CommandRegistry.isEffectStep(finalStep) || hasRedirect(finalStep.redirections, "output");
|
|
7334
|
+
}
|
|
7335
|
+
function hasMidPipelineStdoutBypass(steps) {
|
|
7336
|
+
for (const [index, step] of steps.entries()) {
|
|
7337
|
+
if (index === steps.length - 1) continue;
|
|
7338
|
+
const redirections = step.redirections ?? [];
|
|
7339
|
+
if (!redirections.some((redirection) => {
|
|
7340
|
+
return redirection.kind === "output" && getSourceFd(redirection) !== 1 && getRedirectionMode(redirection) === "pipe";
|
|
7341
|
+
})) continue;
|
|
7342
|
+
if (!redirections.some((redirection) => {
|
|
7343
|
+
return redirection.kind === "output" && getSourceFd(redirection) === 1 && getRedirectionMode(redirection) === "pipe";
|
|
7344
|
+
})) return true;
|
|
7345
|
+
}
|
|
7346
|
+
return false;
|
|
7347
|
+
}
|
|
7348
|
+
function executeScript(script, fs, context) {
|
|
7349
|
+
if (script.statements.length === 0) return {
|
|
7350
|
+
kind: "stream",
|
|
7351
|
+
value: emptyStream()
|
|
7352
|
+
};
|
|
7353
|
+
if (script.statements.every((statement) => isPipelineSink(statement.pipeline))) return {
|
|
7354
|
+
kind: "sink",
|
|
7355
|
+
value: runScriptToCompletion(script, fs, context)
|
|
7356
|
+
};
|
|
7357
|
+
return {
|
|
7358
|
+
kind: "stream",
|
|
7359
|
+
value: runScriptToStream(script, fs, context)
|
|
7360
|
+
};
|
|
7361
|
+
}
|
|
7362
|
+
async function runScriptToCompletion(script, fs, context) {
|
|
7363
|
+
for (const statement of script.statements) {
|
|
7364
|
+
if (!shouldExecuteStatement(statement.chainMode, context.status)) continue;
|
|
7365
|
+
try {
|
|
7366
|
+
await drainStream(runPipeline(statement.pipeline, fs, context));
|
|
7367
|
+
} catch (error) {
|
|
7368
|
+
context.status = 1;
|
|
7369
|
+
throw error;
|
|
5911
7370
|
}
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
|
|
5915
|
-
|
|
5916
|
-
|
|
5917
|
-
|
|
5918
|
-
|
|
5919
|
-
|
|
5920
|
-
|
|
7371
|
+
}
|
|
7372
|
+
}
|
|
7373
|
+
async function* runScriptToStream(script, fs, context) {
|
|
7374
|
+
for (const statement of script.statements) {
|
|
7375
|
+
if (!shouldExecuteStatement(statement.chainMode, context.status)) continue;
|
|
7376
|
+
try {
|
|
7377
|
+
yield* runPipeline(statement.pipeline, fs, context);
|
|
7378
|
+
} catch (error) {
|
|
7379
|
+
context.status = 1;
|
|
7380
|
+
throw error;
|
|
5921
7381
|
}
|
|
5922
|
-
|
|
5923
|
-
|
|
5924
|
-
|
|
7382
|
+
}
|
|
7383
|
+
}
|
|
7384
|
+
async function drainStream(stream) {
|
|
7385
|
+
for await (const _record of stream);
|
|
7386
|
+
}
|
|
7387
|
+
async function* runPipeline(pipeline, fs, context) {
|
|
7388
|
+
if (pipeline.steps.length === 0) return;
|
|
7389
|
+
let pipeInputRecords = null;
|
|
7390
|
+
let preservedPipelineStatus = null;
|
|
7391
|
+
for (const [index, step] of pipeline.steps.entries()) {
|
|
7392
|
+
const isLastStep = index === pipeline.steps.length - 1;
|
|
7393
|
+
if (CommandRegistry.isEffectStep(step) && !isLastStep) throw new Error(`Unsupported pipeline: "${step.cmd}" must be the final command`);
|
|
7394
|
+
const plan = await resolveRoutingPlan(step, fs, context, isLastStep);
|
|
7395
|
+
const shouldPreserveStatus = shouldPreserveProducerStatus(plan, !isLastStep);
|
|
7396
|
+
if (!await preflightNoclobber(plan, fs)) {
|
|
7397
|
+
context.status = 1;
|
|
7398
|
+
context.stderr.append(`${step.cmd}: cannot overwrite existing file`);
|
|
7399
|
+
return;
|
|
5925
7400
|
}
|
|
7401
|
+
const executed = CommandRegistry.isEffectStep(step) ? await executeEffectStep({
|
|
7402
|
+
context,
|
|
7403
|
+
fs,
|
|
7404
|
+
plan,
|
|
7405
|
+
shouldPreserveStatus,
|
|
7406
|
+
step
|
|
7407
|
+
}) : await executeStreamStep({
|
|
7408
|
+
context,
|
|
7409
|
+
fs,
|
|
7410
|
+
hasNextStep: !isLastStep,
|
|
7411
|
+
inputRecords: pipeInputRecords,
|
|
7412
|
+
plan,
|
|
7413
|
+
shouldPreserveStatus,
|
|
7414
|
+
step
|
|
7415
|
+
});
|
|
7416
|
+
if (executed.preservedStatus !== null) preservedPipelineStatus = executed.preservedStatus;
|
|
7417
|
+
if (executed.shellRecords.length > 0) yield* recordsToStream(executed.shellRecords);
|
|
7418
|
+
pipeInputRecords = executed.pipeRecords;
|
|
5926
7419
|
}
|
|
7420
|
+
if (preservedPipelineStatus !== null) context.status = preservedPipelineStatus;
|
|
5927
7421
|
}
|
|
5928
|
-
function
|
|
5929
|
-
|
|
7422
|
+
async function executeEffectStep(params) {
|
|
7423
|
+
const { context, fs, plan, shouldPreserveStatus, step } = params;
|
|
7424
|
+
const childContext = createChildContext(context);
|
|
7425
|
+
await CommandRegistry.executeStep({
|
|
7426
|
+
step,
|
|
5930
7427
|
fs,
|
|
5931
|
-
context
|
|
5932
|
-
|
|
7428
|
+
context: childContext
|
|
7429
|
+
});
|
|
7430
|
+
propagateChildContext(childContext, context);
|
|
7431
|
+
return {
|
|
7432
|
+
...await routeStepOutput({
|
|
7433
|
+
context,
|
|
7434
|
+
fs,
|
|
7435
|
+
hasNextStep: false,
|
|
7436
|
+
plan,
|
|
7437
|
+
stderrLines: childContext.stderr.snapshot(),
|
|
7438
|
+
stdoutRecords: []
|
|
7439
|
+
}),
|
|
7440
|
+
preservedStatus: shouldPreserveStatus ? childContext.status : null
|
|
5933
7441
|
};
|
|
5934
7442
|
}
|
|
5935
|
-
function
|
|
5936
|
-
|
|
5937
|
-
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
|
|
5945
|
-
|
|
7443
|
+
async function executeStreamStep(params) {
|
|
7444
|
+
const { context, fs, hasNextStep, inputRecords, plan, shouldPreserveStatus, step } = params;
|
|
7445
|
+
const childContext = createChildContext(context);
|
|
7446
|
+
const resolvedOutputRedirectPath = step.cmd === "grep" && plan.fd1.kind === "file" ? plan.fd1.path : void 0;
|
|
7447
|
+
const stdoutRecords = await collectRecords(CommandRegistry.executeStep({
|
|
7448
|
+
step,
|
|
7449
|
+
fs,
|
|
7450
|
+
input: inputRecords === null ? null : recordsToStream(inputRecords),
|
|
7451
|
+
context: childContext,
|
|
7452
|
+
resolvedOutputRedirectPath
|
|
7453
|
+
}));
|
|
7454
|
+
const stderrLines = childContext.stderr.snapshot();
|
|
7455
|
+
propagateChildContext(childContext, context);
|
|
7456
|
+
return {
|
|
7457
|
+
...await routeStepOutput({
|
|
7458
|
+
context,
|
|
7459
|
+
fs,
|
|
7460
|
+
hasNextStep,
|
|
7461
|
+
plan,
|
|
7462
|
+
stderrLines,
|
|
7463
|
+
stdoutRecords
|
|
7464
|
+
}),
|
|
7465
|
+
preservedStatus: shouldPreserveStatus ? childContext.status : null
|
|
7466
|
+
};
|
|
5946
7467
|
}
|
|
5947
7468
|
function normalizeContext(context) {
|
|
5948
7469
|
context.cwd = normalizeCwd(context.cwd);
|
|
5949
7470
|
context.status ??= 0;
|
|
5950
|
-
context.stderr ??=
|
|
7471
|
+
context.stderr ??= new BufferedOutputStream();
|
|
5951
7472
|
context.globalVars ??= /* @__PURE__ */ new Map();
|
|
5952
7473
|
context.localVars ??= /* @__PURE__ */ new Map();
|
|
5953
7474
|
return context;
|
|
5954
7475
|
}
|
|
7476
|
+
function createChildContext(context) {
|
|
7477
|
+
return {
|
|
7478
|
+
cwd: context.cwd,
|
|
7479
|
+
globalVars: context.globalVars,
|
|
7480
|
+
localVars: context.localVars,
|
|
7481
|
+
status: context.status,
|
|
7482
|
+
stderr: new BufferedOutputStream()
|
|
7483
|
+
};
|
|
7484
|
+
}
|
|
7485
|
+
function propagateChildContext(child, parent) {
|
|
7486
|
+
parent.cwd = child.cwd;
|
|
7487
|
+
parent.status = child.status;
|
|
7488
|
+
}
|
|
7489
|
+
async function collectRecords(stream) {
|
|
7490
|
+
const records = [];
|
|
7491
|
+
for await (const record of stream) records.push(record);
|
|
7492
|
+
return records;
|
|
7493
|
+
}
|
|
7494
|
+
function recordsToStream(records) {
|
|
7495
|
+
return (async function* () {
|
|
7496
|
+
for (const record of records) yield record;
|
|
7497
|
+
})();
|
|
7498
|
+
}
|
|
7499
|
+
function cloneDestination(destination) {
|
|
7500
|
+
if (destination.kind !== "file") return destination;
|
|
7501
|
+
return { ...destination };
|
|
7502
|
+
}
|
|
7503
|
+
function getSourceFd(redirection) {
|
|
7504
|
+
return redirection.sourceFd ?? 1;
|
|
7505
|
+
}
|
|
7506
|
+
function getTargetFd(redirection) {
|
|
7507
|
+
if (redirection.targetFd !== void 0 && redirection.targetFd !== null) return redirection.targetFd;
|
|
7508
|
+
const targetText = expandedWordToString(redirection.target);
|
|
7509
|
+
if (!FD_TARGET_REGEX.test(targetText)) return null;
|
|
7510
|
+
return Number(targetText.slice(1));
|
|
7511
|
+
}
|
|
7512
|
+
async function resolveFileDestination(command, redirection, fs, context) {
|
|
7513
|
+
const targetPath = await evaluateExpandedSinglePath(command, "redirection target must expand to exactly 1 path", redirection.target, fs, context);
|
|
7514
|
+
return {
|
|
7515
|
+
kind: "file",
|
|
7516
|
+
append: redirection.append ?? false,
|
|
7517
|
+
noclobber: redirection.noclobber ?? false,
|
|
7518
|
+
path: resolvePathFromCwd(context.cwd, targetPath)
|
|
7519
|
+
};
|
|
7520
|
+
}
|
|
7521
|
+
function destinationForFd(routing, fd, _isLastStep) {
|
|
7522
|
+
if (fd === 1) return routing.fd1;
|
|
7523
|
+
if (fd === 2) return routing.fd2;
|
|
7524
|
+
return { kind: "closed" };
|
|
7525
|
+
}
|
|
7526
|
+
function defaultStdoutDestination(isLastStep, hasNonStdoutPipeRedirect, hasStdoutPipeRedirect) {
|
|
7527
|
+
if (isLastStep) return { kind: "shellStdout" };
|
|
7528
|
+
if (hasNonStdoutPipeRedirect && !hasStdoutPipeRedirect) return { kind: "shellStdout" };
|
|
7529
|
+
return { kind: "pipe" };
|
|
7530
|
+
}
|
|
7531
|
+
async function resolveOutputRedirectionDestination(params) {
|
|
7532
|
+
const { context, fs, isLastStep, redirection, routing, step } = params;
|
|
7533
|
+
if (redirection.kind !== "output") return null;
|
|
7534
|
+
const sourceFd = getSourceFd(redirection);
|
|
7535
|
+
if (sourceFd !== 1 && sourceFd !== 2) return null;
|
|
7536
|
+
const mode = getRedirectionMode(redirection);
|
|
7537
|
+
let destination;
|
|
7538
|
+
if (mode === "close") destination = { kind: "closed" };
|
|
7539
|
+
else if (mode === "pipe") destination = { kind: "pipe" };
|
|
7540
|
+
else if (mode === "fd") {
|
|
7541
|
+
const targetFd = getTargetFd(redirection);
|
|
7542
|
+
if (targetFd === null) throw new Error(`${step.cmd}: invalid file descriptor duplication target`);
|
|
7543
|
+
destination = cloneDestination(destinationForFd(routing, targetFd, isLastStep));
|
|
7544
|
+
} else if (mode === "file") destination = await resolveFileDestination(step.cmd, redirection, fs, context);
|
|
7545
|
+
else destination = sourceFd === 1 ? routing.fd1 : routing.fd2;
|
|
7546
|
+
return {
|
|
7547
|
+
destination,
|
|
7548
|
+
sourceFd
|
|
7549
|
+
};
|
|
7550
|
+
}
|
|
7551
|
+
async function resolveRoutingPlan(step, fs, context, isLastStep) {
|
|
7552
|
+
const stepRedirections = step.redirections ?? [];
|
|
7553
|
+
const routing = {
|
|
7554
|
+
fd1: defaultStdoutDestination(isLastStep, stepRedirections.some((redirection) => {
|
|
7555
|
+
return redirection.kind === "output" && getSourceFd(redirection) !== 1 && getRedirectionMode(redirection) === "pipe";
|
|
7556
|
+
}), stepRedirections.some((redirection) => {
|
|
7557
|
+
return redirection.kind === "output" && getSourceFd(redirection) === 1 && getRedirectionMode(redirection) === "pipe";
|
|
7558
|
+
})),
|
|
7559
|
+
fd2: { kind: "shellStderr" }
|
|
7560
|
+
};
|
|
7561
|
+
for (const redirection of stepRedirections) {
|
|
7562
|
+
const resolved = await resolveOutputRedirectionDestination({
|
|
7563
|
+
context,
|
|
7564
|
+
fs,
|
|
7565
|
+
isLastStep,
|
|
7566
|
+
redirection,
|
|
7567
|
+
routing,
|
|
7568
|
+
step
|
|
7569
|
+
});
|
|
7570
|
+
if (!resolved) continue;
|
|
7571
|
+
if (resolved.sourceFd === 1) {
|
|
7572
|
+
routing.fd1 = resolved.destination;
|
|
7573
|
+
continue;
|
|
7574
|
+
}
|
|
7575
|
+
routing.fd2 = resolved.destination;
|
|
7576
|
+
}
|
|
7577
|
+
return routing;
|
|
7578
|
+
}
|
|
7579
|
+
async function preflightNoclobber(plan, fs) {
|
|
7580
|
+
const destinations = [plan.fd1, plan.fd2];
|
|
7581
|
+
const checkedPaths = /* @__PURE__ */ new Set();
|
|
7582
|
+
for (const destination of destinations) {
|
|
7583
|
+
if (destination.kind !== "file" || !destination.noclobber) continue;
|
|
7584
|
+
if (checkedPaths.has(destination.path)) continue;
|
|
7585
|
+
checkedPaths.add(destination.path);
|
|
7586
|
+
if (!await ensureNoclobberWritable(fs, destination.path)) return false;
|
|
7587
|
+
}
|
|
7588
|
+
return true;
|
|
7589
|
+
}
|
|
7590
|
+
function stderrLinesToRecords(lines) {
|
|
7591
|
+
return lines.map((text) => ({
|
|
7592
|
+
kind: "line",
|
|
7593
|
+
text
|
|
7594
|
+
}));
|
|
7595
|
+
}
|
|
7596
|
+
function recordsToText(records) {
|
|
7597
|
+
return records.map((record) => formatStdoutRecord(record)).join("\n");
|
|
7598
|
+
}
|
|
7599
|
+
function linesToText(lines) {
|
|
7600
|
+
return lines.join("\n");
|
|
7601
|
+
}
|
|
7602
|
+
function mergeChannelText(stdoutText, stderrText) {
|
|
7603
|
+
if (stdoutText === "") return stderrText;
|
|
7604
|
+
if (stderrText === "") return stdoutText;
|
|
7605
|
+
return `${stdoutText}\n${stderrText}`;
|
|
7606
|
+
}
|
|
7607
|
+
function toErrorMessage(error) {
|
|
7608
|
+
if (error instanceof Error) return error.message;
|
|
7609
|
+
return String(error);
|
|
7610
|
+
}
|
|
7611
|
+
async function writeToFileOrReport(params) {
|
|
7612
|
+
const { append, content, context, fs, path } = params;
|
|
7613
|
+
try {
|
|
7614
|
+
await writeTextToFile(fs, path, content, { append });
|
|
7615
|
+
} catch (error) {
|
|
7616
|
+
context.status = 1;
|
|
7617
|
+
context.stderr.append(toErrorMessage(error));
|
|
7618
|
+
}
|
|
7619
|
+
}
|
|
7620
|
+
function shouldPipe(destination, hasNextStep) {
|
|
7621
|
+
return destination.kind === "pipe" && hasNextStep;
|
|
7622
|
+
}
|
|
7623
|
+
function shouldPreserveProducerStatus(plan, hasNextStep) {
|
|
7624
|
+
return hasNextStep && plan.fd2.kind === "pipe" && plan.fd1.kind !== "pipe";
|
|
7625
|
+
}
|
|
7626
|
+
async function routeStepOutput(params) {
|
|
7627
|
+
const { context, fs, hasNextStep, plan, stderrLines, stdoutRecords } = params;
|
|
7628
|
+
const pipeRecords = [];
|
|
7629
|
+
const shellRecords = [];
|
|
7630
|
+
const stdoutDestination = plan.fd1;
|
|
7631
|
+
const stderrDestination = plan.fd2;
|
|
7632
|
+
const stdoutAsText = recordsToText(stdoutRecords);
|
|
7633
|
+
const stderrAsText = linesToText(stderrLines);
|
|
7634
|
+
if (stdoutDestination.kind === "file" && stderrDestination.kind === "file" && stdoutDestination.path === stderrDestination.path) {
|
|
7635
|
+
const mergedText = mergeChannelText(stdoutAsText, stderrAsText);
|
|
7636
|
+
await writeToFileOrReport({
|
|
7637
|
+
append: stdoutDestination.append && stderrDestination.append,
|
|
7638
|
+
content: mergedText,
|
|
7639
|
+
context,
|
|
7640
|
+
fs,
|
|
7641
|
+
path: stdoutDestination.path
|
|
7642
|
+
});
|
|
7643
|
+
} else {
|
|
7644
|
+
await routeStdout(stdoutRecords, stdoutAsText, stdoutDestination, hasNextStep, pipeRecords, shellRecords, context, fs);
|
|
7645
|
+
await routeStderr(stderrLines, stderrAsText, stderrDestination, hasNextStep, pipeRecords, shellRecords, context, fs);
|
|
7646
|
+
}
|
|
7647
|
+
return {
|
|
7648
|
+
pipeRecords,
|
|
7649
|
+
shellRecords
|
|
7650
|
+
};
|
|
7651
|
+
}
|
|
7652
|
+
async function routeStdout(stdoutRecords, stdoutText, destination, hasNextStep, pipeRecords, shellRecords, context, fs) {
|
|
7653
|
+
if (stdoutRecords.length === 0 && destination.kind !== "file") return;
|
|
7654
|
+
if (shouldPipe(destination, hasNextStep)) {
|
|
7655
|
+
pipeRecords.push(...stdoutRecords);
|
|
7656
|
+
return;
|
|
7657
|
+
}
|
|
7658
|
+
switch (destination.kind) {
|
|
7659
|
+
case "shellStdout":
|
|
7660
|
+
shellRecords.push(...stdoutRecords);
|
|
7661
|
+
return;
|
|
7662
|
+
case "pipe":
|
|
7663
|
+
shellRecords.push(...stdoutRecords);
|
|
7664
|
+
return;
|
|
7665
|
+
case "shellStderr":
|
|
7666
|
+
if (stdoutText !== "") context.stderr.append(stdoutText);
|
|
7667
|
+
return;
|
|
7668
|
+
case "file":
|
|
7669
|
+
await writeToFileOrReport({
|
|
7670
|
+
append: destination.append,
|
|
7671
|
+
content: stdoutText,
|
|
7672
|
+
context,
|
|
7673
|
+
fs,
|
|
7674
|
+
path: destination.path
|
|
7675
|
+
});
|
|
7676
|
+
return;
|
|
7677
|
+
case "closed": return;
|
|
7678
|
+
default: {
|
|
7679
|
+
const _exhaustive = destination;
|
|
7680
|
+
throw new Error(`Unknown stdout destination: ${_exhaustive}`);
|
|
7681
|
+
}
|
|
7682
|
+
}
|
|
7683
|
+
}
|
|
7684
|
+
async function routeStderr(stderrLines, stderrText, destination, hasNextStep, pipeRecords, shellRecords, context, fs) {
|
|
7685
|
+
if (stderrLines.length === 0 && destination.kind !== "file") return;
|
|
7686
|
+
if (shouldPipe(destination, hasNextStep)) {
|
|
7687
|
+
pipeRecords.push(...stderrLinesToRecords(stderrLines));
|
|
7688
|
+
return;
|
|
7689
|
+
}
|
|
7690
|
+
switch (destination.kind) {
|
|
7691
|
+
case "shellStdout":
|
|
7692
|
+
shellRecords.push(...stderrLinesToRecords(stderrLines));
|
|
7693
|
+
return;
|
|
7694
|
+
case "pipe":
|
|
7695
|
+
shellRecords.push(...stderrLinesToRecords(stderrLines));
|
|
7696
|
+
return;
|
|
7697
|
+
case "shellStderr":
|
|
7698
|
+
if (stderrLines.length > 0) context.stderr.appendLines(stderrLines);
|
|
7699
|
+
return;
|
|
7700
|
+
case "file":
|
|
7701
|
+
await writeToFileOrReport({
|
|
7702
|
+
append: destination.append,
|
|
7703
|
+
content: stderrText,
|
|
7704
|
+
context,
|
|
7705
|
+
fs,
|
|
7706
|
+
path: destination.path
|
|
7707
|
+
});
|
|
7708
|
+
return;
|
|
7709
|
+
case "closed": return;
|
|
7710
|
+
default: {
|
|
7711
|
+
const _exhaustive = destination;
|
|
7712
|
+
throw new Error(`Unknown stderr destination: ${_exhaustive}`);
|
|
7713
|
+
}
|
|
7714
|
+
}
|
|
7715
|
+
}
|
|
5955
7716
|
//#endregion
|
|
5956
|
-
export {
|
|
7717
|
+
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 };
|
|
5957
7718
|
|
|
5958
|
-
//# sourceMappingURL=execute-
|
|
7719
|
+
//# sourceMappingURL=execute-DV1hpsh-.mjs.map
|