shfs 0.3.1 → 0.3.2
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,139 @@ function compileTouch(cmd) {
|
|
|
1810
1861
|
}
|
|
1811
1862
|
};
|
|
1812
1863
|
}
|
|
1864
|
+
const DEFAULT_COMMAND = [literal("echo")];
|
|
1865
|
+
const NUL_DELIMITER = "\0";
|
|
1866
|
+
function compileXargs(command) {
|
|
1867
|
+
return {
|
|
1868
|
+
cmd: "xargs",
|
|
1869
|
+
args: parseXargsArgs(command.args)
|
|
1870
|
+
};
|
|
1871
|
+
}
|
|
1872
|
+
function parseXargsArgs(argv) {
|
|
1873
|
+
const args = {
|
|
1874
|
+
command: DEFAULT_COMMAND,
|
|
1875
|
+
delimiter: null,
|
|
1876
|
+
eof: null,
|
|
1877
|
+
maxArgs: null,
|
|
1878
|
+
maxLines: null,
|
|
1879
|
+
noRunIfEmpty: false,
|
|
1880
|
+
replace: null
|
|
1881
|
+
};
|
|
1882
|
+
let index = 0;
|
|
1883
|
+
while (index < argv.length) {
|
|
1884
|
+
const word = argv[index];
|
|
1885
|
+
if (!word) break;
|
|
1886
|
+
const token = expandedWordToString(word);
|
|
1887
|
+
const parsed = parseOption(argv, index, token, args);
|
|
1888
|
+
if (!parsed.matched) break;
|
|
1889
|
+
index = parsed.nextIndex;
|
|
1890
|
+
}
|
|
1891
|
+
const commandWords = argv.slice(index);
|
|
1892
|
+
if (commandWords.length > 0) args.command = commandWords;
|
|
1893
|
+
return args;
|
|
1894
|
+
}
|
|
1895
|
+
function parseOption(argv, index, token, args) {
|
|
1896
|
+
if (token === "--") return {
|
|
1897
|
+
matched: true,
|
|
1898
|
+
nextIndex: index + 1
|
|
1899
|
+
};
|
|
1900
|
+
if (token === "-0" || token === "--null") {
|
|
1901
|
+
args.delimiter = NUL_DELIMITER;
|
|
1902
|
+
args.eof = null;
|
|
1903
|
+
return {
|
|
1904
|
+
matched: true,
|
|
1905
|
+
nextIndex: index + 1
|
|
1906
|
+
};
|
|
1907
|
+
}
|
|
1908
|
+
if (token === "-r" || token === "--no-run-if-empty") {
|
|
1909
|
+
args.noRunIfEmpty = true;
|
|
1910
|
+
return {
|
|
1911
|
+
matched: true,
|
|
1912
|
+
nextIndex: index + 1
|
|
1913
|
+
};
|
|
1914
|
+
}
|
|
1915
|
+
if (token === "-n" || token.startsWith("-n")) {
|
|
1916
|
+
const value = optionValue(argv, index, token, "-n");
|
|
1917
|
+
setMaxArgsMode(args, parsePositiveInteger(value.value, "-n"));
|
|
1918
|
+
return {
|
|
1919
|
+
matched: true,
|
|
1920
|
+
nextIndex: value.nextIndex
|
|
1921
|
+
};
|
|
1922
|
+
}
|
|
1923
|
+
if (token === "-L" || token.startsWith("-L")) {
|
|
1924
|
+
const value = optionValue(argv, index, token, "-L");
|
|
1925
|
+
setMaxLinesMode(args, parsePositiveInteger(value.value, "-L"));
|
|
1926
|
+
return {
|
|
1927
|
+
matched: true,
|
|
1928
|
+
nextIndex: value.nextIndex
|
|
1929
|
+
};
|
|
1930
|
+
}
|
|
1931
|
+
if (token === "-E" || token.startsWith("-E")) {
|
|
1932
|
+
const value = optionValue(argv, index, token, "-E");
|
|
1933
|
+
args.eof = value.value;
|
|
1934
|
+
return {
|
|
1935
|
+
matched: true,
|
|
1936
|
+
nextIndex: value.nextIndex
|
|
1937
|
+
};
|
|
1938
|
+
}
|
|
1939
|
+
if (token === "-I" || token.startsWith("-I")) {
|
|
1940
|
+
const value = optionValue(argv, index, token, "-I");
|
|
1941
|
+
setReplaceMode(args, value.value);
|
|
1942
|
+
return {
|
|
1943
|
+
matched: true,
|
|
1944
|
+
nextIndex: value.nextIndex
|
|
1945
|
+
};
|
|
1946
|
+
}
|
|
1947
|
+
if (token === "-d" || token.startsWith("-d")) {
|
|
1948
|
+
const value = optionValue(argv, index, token, "-d");
|
|
1949
|
+
args.delimiter = decodeDelimiter(value.value);
|
|
1950
|
+
args.eof = null;
|
|
1951
|
+
return {
|
|
1952
|
+
matched: true,
|
|
1953
|
+
nextIndex: value.nextIndex
|
|
1954
|
+
};
|
|
1955
|
+
}
|
|
1956
|
+
return {
|
|
1957
|
+
matched: false,
|
|
1958
|
+
nextIndex: index
|
|
1959
|
+
};
|
|
1960
|
+
}
|
|
1961
|
+
function setMaxArgsMode(args, maxArgs) {
|
|
1962
|
+
args.maxArgs = maxArgs;
|
|
1963
|
+
args.maxLines = null;
|
|
1964
|
+
args.replace = null;
|
|
1965
|
+
}
|
|
1966
|
+
function setMaxLinesMode(args, maxLines) {
|
|
1967
|
+
args.maxLines = maxLines;
|
|
1968
|
+
args.maxArgs = null;
|
|
1969
|
+
args.replace = null;
|
|
1970
|
+
}
|
|
1971
|
+
function setReplaceMode(args, replace) {
|
|
1972
|
+
args.replace = replace;
|
|
1973
|
+
args.maxLines = 1;
|
|
1974
|
+
args.maxArgs = null;
|
|
1975
|
+
}
|
|
1976
|
+
function optionValue(argv, index, token, option) {
|
|
1977
|
+
if (token.length > option.length) return {
|
|
1978
|
+
nextIndex: index + 1,
|
|
1979
|
+
value: token.slice(option.length)
|
|
1980
|
+
};
|
|
1981
|
+
const next = argv[index + 1];
|
|
1982
|
+
if (!next) throw new Error(`xargs: option ${option} requires a value`);
|
|
1983
|
+
return {
|
|
1984
|
+
nextIndex: index + 2,
|
|
1985
|
+
value: expandedWordToString(next)
|
|
1986
|
+
};
|
|
1987
|
+
}
|
|
1988
|
+
function parsePositiveInteger(value, option) {
|
|
1989
|
+
const parsed = Number.parseInt(value, 10);
|
|
1990
|
+
if (!Number.isInteger(parsed) || parsed < 1) throw new Error(`xargs: invalid value for ${option}: ${value}`);
|
|
1991
|
+
return parsed;
|
|
1992
|
+
}
|
|
1993
|
+
function decodeDelimiter(value) {
|
|
1994
|
+
if (value === "\\0") return NUL_DELIMITER;
|
|
1995
|
+
return value.at(0) ?? "";
|
|
1996
|
+
}
|
|
1813
1997
|
let CommandHandler;
|
|
1814
1998
|
(function(_CommandHandler) {
|
|
1815
1999
|
const handlers = {
|
|
@@ -1830,7 +2014,8 @@ let CommandHandler;
|
|
|
1830
2014
|
string: compileString,
|
|
1831
2015
|
tail: compileTail,
|
|
1832
2016
|
test: compileTest,
|
|
1833
|
-
touch: compileTouch
|
|
2017
|
+
touch: compileTouch,
|
|
2018
|
+
xargs: compileXargs
|
|
1834
2019
|
};
|
|
1835
2020
|
function get(name) {
|
|
1836
2021
|
const handler = handlers[name];
|
|
@@ -1920,6 +2105,12 @@ var ProgramCompiler = class {
|
|
|
1920
2105
|
compileRedirection(node) {
|
|
1921
2106
|
return {
|
|
1922
2107
|
kind: node.redirectKind,
|
|
2108
|
+
mode: node.mode,
|
|
2109
|
+
sourceFd: node.sourceFd,
|
|
2110
|
+
targetFd: node.targetFd,
|
|
2111
|
+
append: node.append,
|
|
2112
|
+
noclobber: node.noclobber,
|
|
2113
|
+
optional: node.optional,
|
|
1923
2114
|
target: this.expandWord(node.target)
|
|
1924
2115
|
};
|
|
1925
2116
|
}
|
|
@@ -1996,11 +2187,47 @@ var ProgramCompiler = class {
|
|
|
1996
2187
|
return program.statements.map((statement) => this.serializePipeline(statement.pipeline)).join("; ");
|
|
1997
2188
|
}
|
|
1998
2189
|
serializePipeline(pipeline) {
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
const
|
|
2002
|
-
|
|
2003
|
-
|
|
2190
|
+
const segments = [];
|
|
2191
|
+
for (let index = 0; index < pipeline.commands.length; index++) {
|
|
2192
|
+
const command = pipeline.commands[index];
|
|
2193
|
+
if (!command) continue;
|
|
2194
|
+
const hasNextCommand = index < pipeline.commands.length - 1;
|
|
2195
|
+
segments.push(this.serializeCommand(command, { omitPipeRedirections: hasNextCommand }));
|
|
2196
|
+
if (hasNextCommand) segments.push(this.serializePipelineOperator(command));
|
|
2197
|
+
}
|
|
2198
|
+
return segments.join(" ");
|
|
2199
|
+
}
|
|
2200
|
+
serializeCommand(command, options = {}) {
|
|
2201
|
+
const segments = [this.serializeWord(command.name)];
|
|
2202
|
+
for (const arg of command.args) segments.push(this.serializeWord(arg));
|
|
2203
|
+
for (const redirection of command.redirections) {
|
|
2204
|
+
if (options.omitPipeRedirections && redirection.mode === "pipe") continue;
|
|
2205
|
+
segments.push(this.serializeRedirection(redirection));
|
|
2206
|
+
}
|
|
2207
|
+
return segments.join(" ");
|
|
2208
|
+
}
|
|
2209
|
+
serializePipelineOperator(command) {
|
|
2210
|
+
const pipeRedirections = command.redirections.filter((redirection) => redirection.redirectKind === "output" && redirection.mode === "pipe");
|
|
2211
|
+
const pipesStdout = pipeRedirections.some((redirection) => redirection.sourceFd === 1);
|
|
2212
|
+
const pipesStderr = pipeRedirections.some((redirection) => redirection.sourceFd === 2);
|
|
2213
|
+
if (pipesStdout && pipesStderr) return "&|";
|
|
2214
|
+
if (pipeRedirections.length === 1) return this.serializeRedirection(pipeRedirections[0]);
|
|
2215
|
+
return "|";
|
|
2216
|
+
}
|
|
2217
|
+
serializeRedirection(redirection) {
|
|
2218
|
+
const sourceFd = redirection.sourceFd === (redirection.redirectKind === "input" ? 0 : 1) ? "" : String(redirection.sourceFd);
|
|
2219
|
+
if (redirection.redirectKind === "input") {
|
|
2220
|
+
const operator = redirection.optional ? "<?" : "<";
|
|
2221
|
+
if (redirection.mode === "fd") return `${sourceFd}<&${redirection.targetFd}`;
|
|
2222
|
+
if (redirection.mode === "close") return `${sourceFd}<&-`;
|
|
2223
|
+
return `${sourceFd}${operator}${this.serializeWord(redirection.target)}`;
|
|
2224
|
+
}
|
|
2225
|
+
if (redirection.mode === "pipe") return `${sourceFd}>|`;
|
|
2226
|
+
if (redirection.mode === "fd") return `${sourceFd}>&${redirection.targetFd}`;
|
|
2227
|
+
if (redirection.mode === "close") return `${sourceFd}>&-`;
|
|
2228
|
+
let operator = redirection.append ? ">>" : ">";
|
|
2229
|
+
if (redirection.noclobber) operator = `${operator}?`;
|
|
2230
|
+
return `${sourceFd}${operator}${this.serializeWord(redirection.target)}`;
|
|
2004
2231
|
}
|
|
2005
2232
|
serializeWord(word) {
|
|
2006
2233
|
return word.parts.map((part) => this.serializeWordPart(part)).join("");
|
|
@@ -2925,11 +3152,23 @@ var CommandSubPart = class {
|
|
|
2925
3152
|
*/
|
|
2926
3153
|
var Redirection = class extends ASTNode {
|
|
2927
3154
|
redirectKind;
|
|
3155
|
+
mode;
|
|
3156
|
+
sourceFd;
|
|
3157
|
+
targetFd;
|
|
3158
|
+
append;
|
|
3159
|
+
noclobber;
|
|
3160
|
+
optional;
|
|
2928
3161
|
target;
|
|
2929
|
-
constructor(span, redirectKind, target) {
|
|
3162
|
+
constructor(span, redirectKind, target, options = {}) {
|
|
2930
3163
|
super(span);
|
|
2931
3164
|
this.redirectKind = redirectKind;
|
|
2932
3165
|
this.target = target;
|
|
3166
|
+
this.mode = options.mode ?? "file";
|
|
3167
|
+
this.sourceFd = options.sourceFd ?? (redirectKind === "input" ? 0 : 1);
|
|
3168
|
+
this.targetFd = options.targetFd ?? null;
|
|
3169
|
+
this.append = options.append ?? false;
|
|
3170
|
+
this.noclobber = options.noclobber ?? false;
|
|
3171
|
+
this.optional = options.optional ?? false;
|
|
2933
3172
|
}
|
|
2934
3173
|
accept(visitor) {
|
|
2935
3174
|
return visitor.visitRedirection(this);
|
|
@@ -2942,6 +3181,7 @@ var Redirection = class extends ASTNode {
|
|
|
2942
3181
|
* - Simple commands (name + args)
|
|
2943
3182
|
* - Redirections (< > Phase 2)
|
|
2944
3183
|
*/
|
|
3184
|
+
const DIGITS_ONLY_REGEX = /^[0-9]+$/;
|
|
2945
3185
|
/**
|
|
2946
3186
|
* Parser for commands.
|
|
2947
3187
|
*
|
|
@@ -2990,40 +3230,118 @@ var CommandParser = class {
|
|
|
2990
3230
|
else break;
|
|
2991
3231
|
}
|
|
2992
3232
|
const endPos = this.parser.previousTokenPosition;
|
|
2993
|
-
|
|
3233
|
+
const span = new SourceSpan(startPos, endPos);
|
|
3234
|
+
const normalized = this.normalizeRedirectionPrefixes(args, redirections);
|
|
3235
|
+
return new SimpleCommand(span, name, normalized.args, normalized.redirections);
|
|
2994
3236
|
}
|
|
2995
3237
|
/**
|
|
2996
3238
|
* Parse a redirection if present.
|
|
2997
3239
|
*
|
|
2998
|
-
* Grammar:
|
|
3240
|
+
* Grammar (subset):
|
|
2999
3241
|
* redirection ::= '<' word | '>' word | '>>' word
|
|
3242
|
+
*
|
|
3243
|
+
* This parser also supports fish-inspired forms consumed by shfs specs:
|
|
3244
|
+
* <&3, <&-, <?file, >&2, >&-, 2>|, >?file, >>?file.
|
|
3000
3245
|
*/
|
|
3001
3246
|
parseRedirection() {
|
|
3002
3247
|
const token = this.parser.currentToken;
|
|
3003
|
-
if (token.kind === TokenKind.LESS)
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3248
|
+
if (token.kind === TokenKind.LESS) return this.parseInputRedirection(token);
|
|
3249
|
+
if (token.kind === TokenKind.GREAT) return this.parseOutputRedirection(token);
|
|
3250
|
+
return null;
|
|
3251
|
+
}
|
|
3252
|
+
parseInputRedirection(token) {
|
|
3253
|
+
const startPos = token.span.start;
|
|
3254
|
+
this.parser.advance();
|
|
3255
|
+
this.validateInputTargetPrefix();
|
|
3256
|
+
const parsedTarget = this.parseInputTargetAfterLess();
|
|
3257
|
+
const fdMode = this.parseFdMode(parsedTarget.target, "<&N or <&-");
|
|
3258
|
+
const endPos = this.parser.previousTokenPosition;
|
|
3259
|
+
return new Redirection(new SourceSpan(startPos, endPos), "input", parsedTarget.target, {
|
|
3260
|
+
mode: fdMode.mode,
|
|
3261
|
+
optional: parsedTarget.optional,
|
|
3262
|
+
targetFd: fdMode.targetFd
|
|
3263
|
+
});
|
|
3264
|
+
}
|
|
3265
|
+
parseOutputRedirection(token) {
|
|
3266
|
+
const startPos = token.span.start;
|
|
3267
|
+
this.parser.advance();
|
|
3268
|
+
const append = this.consumeAppendMarker();
|
|
3269
|
+
const noclobber = this.consumeNoclobberMarker();
|
|
3270
|
+
if (this.parser.currentToken.kind === TokenKind.PIPE) {
|
|
3271
|
+
const pipeToken = this.parser.currentToken;
|
|
3272
|
+
return new Redirection(new SourceSpan(startPos, this.parser.previousTokenPosition), "output", this.createLiteralWord("|", pipeToken.span), {
|
|
3273
|
+
append,
|
|
3274
|
+
mode: "pipe",
|
|
3275
|
+
noclobber
|
|
3276
|
+
});
|
|
3013
3277
|
}
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3278
|
+
const target = this.wordParser.parseWord();
|
|
3279
|
+
if (!target) this.parser.syntacticError("Expected filename after >", "word");
|
|
3280
|
+
const fdMode = this.parseFdMode(target, ">&N or >&-");
|
|
3281
|
+
const endPos = this.parser.previousTokenPosition;
|
|
3282
|
+
return new Redirection(new SourceSpan(startPos, endPos), "output", target, {
|
|
3283
|
+
append,
|
|
3284
|
+
mode: fdMode.mode,
|
|
3285
|
+
noclobber,
|
|
3286
|
+
targetFd: fdMode.targetFd
|
|
3287
|
+
});
|
|
3288
|
+
}
|
|
3289
|
+
validateInputTargetPrefix() {
|
|
3290
|
+
if (this.parser.currentToken.kind !== TokenKind.WORD) return;
|
|
3291
|
+
const spelling = this.parser.currentToken.spelling;
|
|
3292
|
+
if (spelling.startsWith("?&") || spelling.startsWith("&?")) this.parser.syntacticError("Invalid redirection target after <", "<path, <?path, <&N, or <&-");
|
|
3293
|
+
}
|
|
3294
|
+
parseInputTargetAfterLess() {
|
|
3295
|
+
let optional = false;
|
|
3296
|
+
let target = this.wordParser.parseWord();
|
|
3297
|
+
if (!target) this.parser.syntacticError("Expected filename after <", "word");
|
|
3298
|
+
const targetLiteral = target.literalValue;
|
|
3299
|
+
if (!targetLiteral?.startsWith("?")) return {
|
|
3300
|
+
optional,
|
|
3301
|
+
target
|
|
3302
|
+
};
|
|
3303
|
+
optional = true;
|
|
3304
|
+
if (targetLiteral === "?") {
|
|
3305
|
+
const explicitTarget = this.wordParser.parseWord();
|
|
3306
|
+
if (!explicitTarget) this.parser.syntacticError("Expected filename after <?", "word");
|
|
3307
|
+
target = explicitTarget;
|
|
3308
|
+
return {
|
|
3309
|
+
optional,
|
|
3310
|
+
target
|
|
3311
|
+
};
|
|
3025
3312
|
}
|
|
3026
|
-
|
|
3313
|
+
target = this.cloneLiteralWord(target, targetLiteral.slice(1));
|
|
3314
|
+
return {
|
|
3315
|
+
optional,
|
|
3316
|
+
target
|
|
3317
|
+
};
|
|
3318
|
+
}
|
|
3319
|
+
consumeAppendMarker() {
|
|
3320
|
+
if (this.parser.currentToken.kind !== TokenKind.GREAT) return false;
|
|
3321
|
+
this.parser.advance();
|
|
3322
|
+
return true;
|
|
3323
|
+
}
|
|
3324
|
+
consumeNoclobberMarker() {
|
|
3325
|
+
if (this.parser.currentToken.kind !== TokenKind.WORD || this.parser.currentToken.spelling !== "?") return false;
|
|
3326
|
+
this.parser.advance();
|
|
3327
|
+
return true;
|
|
3328
|
+
}
|
|
3329
|
+
parseFdMode(target, expected) {
|
|
3330
|
+
const targetLiteral = target.literalValue;
|
|
3331
|
+
if (!targetLiteral?.startsWith("&")) return {
|
|
3332
|
+
mode: "file",
|
|
3333
|
+
targetFd: null
|
|
3334
|
+
};
|
|
3335
|
+
const fdTarget = targetLiteral.slice(1);
|
|
3336
|
+
if (fdTarget === "-") return {
|
|
3337
|
+
mode: "close",
|
|
3338
|
+
targetFd: null
|
|
3339
|
+
};
|
|
3340
|
+
if (DIGITS_ONLY_REGEX.test(fdTarget)) return {
|
|
3341
|
+
mode: "fd",
|
|
3342
|
+
targetFd: Number(fdTarget)
|
|
3343
|
+
};
|
|
3344
|
+
this.parser.syntacticError("Invalid file descriptor duplication target", expected);
|
|
3027
3345
|
}
|
|
3028
3346
|
/**
|
|
3029
3347
|
* Check if current token terminates a command.
|
|
@@ -3032,6 +3350,71 @@ var CommandParser = class {
|
|
|
3032
3350
|
const kind = this.parser.currentToken.kind;
|
|
3033
3351
|
return kind === TokenKind.PIPE || kind === TokenKind.SEMICOLON || kind === TokenKind.NEWLINE || kind === TokenKind.EOF;
|
|
3034
3352
|
}
|
|
3353
|
+
cloneLiteralWord(word, literal) {
|
|
3354
|
+
return new Word(word.span, [new LiteralPart(word.span, literal)], word.quoted);
|
|
3355
|
+
}
|
|
3356
|
+
createLiteralWord(literal, span) {
|
|
3357
|
+
return new Word(span, [new LiteralPart(span, literal)]);
|
|
3358
|
+
}
|
|
3359
|
+
cloneRedirection(redirection, options) {
|
|
3360
|
+
return new Redirection(redirection.span, redirection.redirectKind, redirection.target, {
|
|
3361
|
+
append: redirection.append,
|
|
3362
|
+
mode: options.mode ?? redirection.mode,
|
|
3363
|
+
noclobber: redirection.noclobber,
|
|
3364
|
+
optional: redirection.optional,
|
|
3365
|
+
sourceFd: options.sourceFd ?? redirection.sourceFd,
|
|
3366
|
+
targetFd: redirection.targetFd
|
|
3367
|
+
});
|
|
3368
|
+
}
|
|
3369
|
+
normalizeRedirectionPrefixes(args, redirections) {
|
|
3370
|
+
if (args.length === 0 || redirections.length === 0) return {
|
|
3371
|
+
args,
|
|
3372
|
+
redirections
|
|
3373
|
+
};
|
|
3374
|
+
const consumedPrefixArgIndices = /* @__PURE__ */ new Set();
|
|
3375
|
+
const normalizedRedirections = [];
|
|
3376
|
+
for (const redirection of redirections) {
|
|
3377
|
+
const prefixArgIndex = this.findContiguousPrefixArgIndex(args, consumedPrefixArgIndices, redirection.span.start.offset);
|
|
3378
|
+
if (prefixArgIndex === null) {
|
|
3379
|
+
normalizedRedirections.push(redirection);
|
|
3380
|
+
continue;
|
|
3381
|
+
}
|
|
3382
|
+
const prefixArg = args[prefixArgIndex];
|
|
3383
|
+
if (prefixArg?.quoted) {
|
|
3384
|
+
normalizedRedirections.push(redirection);
|
|
3385
|
+
continue;
|
|
3386
|
+
}
|
|
3387
|
+
const prefixLiteral = prefixArg?.literalValue;
|
|
3388
|
+
if (!prefixLiteral) {
|
|
3389
|
+
normalizedRedirections.push(redirection);
|
|
3390
|
+
continue;
|
|
3391
|
+
}
|
|
3392
|
+
if (prefixLiteral === "&" && redirection.redirectKind === "output") {
|
|
3393
|
+
consumedPrefixArgIndices.add(prefixArgIndex);
|
|
3394
|
+
normalizedRedirections.push(this.cloneRedirection(redirection, { sourceFd: 1 }), this.cloneRedirection(redirection, { sourceFd: 2 }));
|
|
3395
|
+
continue;
|
|
3396
|
+
}
|
|
3397
|
+
if (DIGITS_ONLY_REGEX.test(prefixLiteral)) {
|
|
3398
|
+
consumedPrefixArgIndices.add(prefixArgIndex);
|
|
3399
|
+
normalizedRedirections.push(this.cloneRedirection(redirection, { sourceFd: Number(prefixLiteral) }));
|
|
3400
|
+
continue;
|
|
3401
|
+
}
|
|
3402
|
+
normalizedRedirections.push(redirection);
|
|
3403
|
+
}
|
|
3404
|
+
return {
|
|
3405
|
+
args: args.filter((_arg, index) => !consumedPrefixArgIndices.has(index)),
|
|
3406
|
+
redirections: normalizedRedirections
|
|
3407
|
+
};
|
|
3408
|
+
}
|
|
3409
|
+
findContiguousPrefixArgIndex(args, consumedPrefixArgIndices, redirectionStartOffset) {
|
|
3410
|
+
for (let index = args.length - 1; index >= 0; index--) {
|
|
3411
|
+
if (consumedPrefixArgIndices.has(index)) continue;
|
|
3412
|
+
const arg = args[index];
|
|
3413
|
+
if (!arg) continue;
|
|
3414
|
+
if (arg.span.end.offset === redirectionStartOffset) return index;
|
|
3415
|
+
}
|
|
3416
|
+
return null;
|
|
3417
|
+
}
|
|
3035
3418
|
};
|
|
3036
3419
|
/**
|
|
3037
3420
|
* Error reporter for the Fish subset parser.
|
|
@@ -3203,8 +3586,16 @@ var StatementParser = class {
|
|
|
3203
3586
|
if (!firstCommand) return null;
|
|
3204
3587
|
const commands = [firstCommand];
|
|
3205
3588
|
while (this.parser.currentToken.kind === TokenKind.PIPE) {
|
|
3589
|
+
const pipeToken = this.parser.currentToken;
|
|
3590
|
+
const previousCommand = commands.at(-1);
|
|
3591
|
+
if (previousCommand) {
|
|
3592
|
+
const rewrittenPreviousCommand = this.rewriteStderrPipeCommand(previousCommand, pipeToken.span.start.offset, pipeToken.span);
|
|
3593
|
+
if (rewrittenPreviousCommand !== previousCommand) commands[commands.length - 1] = rewrittenPreviousCommand;
|
|
3594
|
+
}
|
|
3206
3595
|
this.parser.advance();
|
|
3207
3596
|
this.skipNewlines();
|
|
3597
|
+
const tokenAfterPipe = this.parser.currentToken;
|
|
3598
|
+
if (tokenAfterPipe.kind === TokenKind.WORD && tokenAfterPipe.spelling === "&") this.parser.syntacticError("Invalid fish pipeline operator", "command after | (|& is unsupported; use &|)");
|
|
3208
3599
|
const command = this.commandParser.parseCommand();
|
|
3209
3600
|
if (!command) {
|
|
3210
3601
|
this.parser.syntacticError("Expected command after |", "command");
|
|
@@ -3245,6 +3636,27 @@ var StatementParser = class {
|
|
|
3245
3636
|
isChainKeyword(spelling) {
|
|
3246
3637
|
return spelling === "and" || spelling === "or";
|
|
3247
3638
|
}
|
|
3639
|
+
rewriteStderrPipeCommand(command, pipeStartOffset, pipeSpan) {
|
|
3640
|
+
const trailingArg = command.args.at(-1);
|
|
3641
|
+
if (!(trailingArg && !trailingArg.quoted && trailingArg.literalValue === "&" && trailingArg.span.end.offset === pipeStartOffset)) return command;
|
|
3642
|
+
const updatedArgs = command.args.slice(0, -1);
|
|
3643
|
+
const pipeTarget = this.createLiteralWord("|", pipeSpan);
|
|
3644
|
+
const updatedRedirections = [
|
|
3645
|
+
...command.redirections,
|
|
3646
|
+
new Redirection(pipeSpan, "output", pipeTarget, {
|
|
3647
|
+
mode: "pipe",
|
|
3648
|
+
sourceFd: 1
|
|
3649
|
+
}),
|
|
3650
|
+
new Redirection(pipeSpan, "output", pipeTarget, {
|
|
3651
|
+
mode: "pipe",
|
|
3652
|
+
sourceFd: 2
|
|
3653
|
+
})
|
|
3654
|
+
];
|
|
3655
|
+
return new SimpleCommand(command.span, command.name, updatedArgs, updatedRedirections);
|
|
3656
|
+
}
|
|
3657
|
+
createLiteralWord(literal, span) {
|
|
3658
|
+
return new Word(span, [new LiteralPart(span, literal)]);
|
|
3659
|
+
}
|
|
3248
3660
|
};
|
|
3249
3661
|
/**
|
|
3250
3662
|
* Syntax error exception for the Fish subset parser.
|
|
@@ -3600,8 +4012,20 @@ function formatStdoutRecord(record) {
|
|
|
3600
4012
|
}
|
|
3601
4013
|
//#endregion
|
|
3602
4014
|
//#region src/stderr.ts
|
|
4015
|
+
var BufferedOutputStream = class {
|
|
4016
|
+
lines = [];
|
|
4017
|
+
append(line) {
|
|
4018
|
+
this.lines.push(line);
|
|
4019
|
+
}
|
|
4020
|
+
appendLines(lines) {
|
|
4021
|
+
for (const line of lines) this.append(line);
|
|
4022
|
+
}
|
|
4023
|
+
snapshot() {
|
|
4024
|
+
return [...this.lines];
|
|
4025
|
+
}
|
|
4026
|
+
};
|
|
3603
4027
|
function appendStderrLines(context, lines) {
|
|
3604
|
-
context.stderr.
|
|
4028
|
+
context.stderr.appendLines(lines);
|
|
3605
4029
|
}
|
|
3606
4030
|
function formatStderr(lines) {
|
|
3607
4031
|
return lines.join("\n");
|
|
@@ -3613,7 +4037,7 @@ var ShellDiagnosticError = class extends Error {
|
|
|
3613
4037
|
diagnostics;
|
|
3614
4038
|
exitCode;
|
|
3615
4039
|
constructor(diagnostics, exitCode = exitCodeForDiagnostics(diagnostics)) {
|
|
3616
|
-
super(diagnostics.map((diagnostic) => toErrorMessage(diagnostic)).join("\n"));
|
|
4040
|
+
super(diagnostics.map((diagnostic) => toErrorMessage$1(diagnostic)).join("\n"));
|
|
3617
4041
|
this.name = "ShellDiagnosticError";
|
|
3618
4042
|
this.diagnostics = diagnostics;
|
|
3619
4043
|
this.exitCode = exitCode;
|
|
@@ -3665,7 +4089,7 @@ function exitCodeForDiagnostic(diagnostic) {
|
|
|
3665
4089
|
if (diagnostic.phase === "compile" && diagnostic.location.command === "grep") return 2;
|
|
3666
4090
|
return 1;
|
|
3667
4091
|
}
|
|
3668
|
-
function toErrorMessage(diagnostic) {
|
|
4092
|
+
function toErrorMessage$1(diagnostic) {
|
|
3669
4093
|
const command = diagnostic.location.command;
|
|
3670
4094
|
const path = diagnostic.location.path;
|
|
3671
4095
|
if (command && path) return `${command}: ${path}: ${diagnostic.message}`;
|
|
@@ -3675,7 +4099,7 @@ function toErrorMessage(diagnostic) {
|
|
|
3675
4099
|
//#endregion
|
|
3676
4100
|
//#region src/execute/path.ts
|
|
3677
4101
|
const MULTIPLE_SLASH_REGEX$2 = /\/+/g;
|
|
3678
|
-
const ROOT_DIRECTORY$
|
|
4102
|
+
const ROOT_DIRECTORY$3 = "/";
|
|
3679
4103
|
const TRAILING_SLASH_REGEX$2 = /\/+$/;
|
|
3680
4104
|
const VARIABLE_REFERENCE_REGEX = /\$([A-Za-z_][A-Za-z0-9_]*)/g;
|
|
3681
4105
|
const NO_GLOB_MATCH_MESSAGE = "no matches found";
|
|
@@ -3702,7 +4126,7 @@ function expandVariables(input, context) {
|
|
|
3702
4126
|
});
|
|
3703
4127
|
}
|
|
3704
4128
|
function normalizeAbsolutePath(path) {
|
|
3705
|
-
const segments = (path.startsWith(ROOT_DIRECTORY$
|
|
4129
|
+
const segments = (path.startsWith(ROOT_DIRECTORY$3) ? path : `${ROOT_DIRECTORY$3}${path}`).replace(MULTIPLE_SLASH_REGEX$2, "/").split(ROOT_DIRECTORY$3);
|
|
3706
4130
|
const normalizedSegments = [];
|
|
3707
4131
|
for (const segment of segments) {
|
|
3708
4132
|
if (segment === "" || segment === ".") continue;
|
|
@@ -3712,17 +4136,17 @@ function normalizeAbsolutePath(path) {
|
|
|
3712
4136
|
}
|
|
3713
4137
|
normalizedSegments.push(segment);
|
|
3714
4138
|
}
|
|
3715
|
-
const normalizedPath = `${ROOT_DIRECTORY$
|
|
3716
|
-
return normalizedPath === "" ? ROOT_DIRECTORY$
|
|
4139
|
+
const normalizedPath = `${ROOT_DIRECTORY$3}${normalizedSegments.join(ROOT_DIRECTORY$3)}`;
|
|
4140
|
+
return normalizedPath === "" ? ROOT_DIRECTORY$3 : normalizedPath;
|
|
3717
4141
|
}
|
|
3718
4142
|
function normalizeCwd(cwd) {
|
|
3719
|
-
if (cwd === "") return ROOT_DIRECTORY$
|
|
4143
|
+
if (cwd === "") return ROOT_DIRECTORY$3;
|
|
3720
4144
|
const trimmed = normalizeAbsolutePath(cwd).replace(TRAILING_SLASH_REGEX$2, "");
|
|
3721
|
-
return trimmed === "" ? ROOT_DIRECTORY$
|
|
4145
|
+
return trimmed === "" ? ROOT_DIRECTORY$3 : trimmed;
|
|
3722
4146
|
}
|
|
3723
4147
|
function resolvePathFromCwd(cwd, path) {
|
|
3724
4148
|
if (path === "") return cwd;
|
|
3725
|
-
if (path.startsWith(ROOT_DIRECTORY$
|
|
4149
|
+
if (path.startsWith(ROOT_DIRECTORY$3)) return normalizeAbsolutePath(path);
|
|
3726
4150
|
return normalizeAbsolutePath(`${cwd}/${path}`);
|
|
3727
4151
|
}
|
|
3728
4152
|
function resolvePathsFromCwd(cwd, paths) {
|
|
@@ -3737,7 +4161,7 @@ async function readDirectoryPaths(fs, directoryPath) {
|
|
|
3737
4161
|
children.sort((left, right) => left.localeCompare(right));
|
|
3738
4162
|
return children;
|
|
3739
4163
|
}
|
|
3740
|
-
async function walkFilesystemEntries(fs, rootDir = ROOT_DIRECTORY$
|
|
4164
|
+
async function walkFilesystemEntries(fs, rootDir = ROOT_DIRECTORY$3) {
|
|
3741
4165
|
const normalizedRoot = normalizeAbsolutePath(rootDir);
|
|
3742
4166
|
if (!(await fs.stat(normalizedRoot)).isDirectory) throw new Error(`Not a directory: ${normalizedRoot}`);
|
|
3743
4167
|
const entries = [];
|
|
@@ -3759,12 +4183,12 @@ async function walkFilesystemEntries(fs, rootDir = ROOT_DIRECTORY$2) {
|
|
|
3759
4183
|
return entries;
|
|
3760
4184
|
}
|
|
3761
4185
|
function toRelativePathFromCwd$1(path, cwd) {
|
|
3762
|
-
if (cwd === ROOT_DIRECTORY$
|
|
3763
|
-
if (path === ROOT_DIRECTORY$
|
|
3764
|
-
return path.startsWith(ROOT_DIRECTORY$
|
|
4186
|
+
if (cwd === ROOT_DIRECTORY$3) {
|
|
4187
|
+
if (path === ROOT_DIRECTORY$3) return null;
|
|
4188
|
+
return path.startsWith(ROOT_DIRECTORY$3) ? path.slice(1) : path;
|
|
3765
4189
|
}
|
|
3766
4190
|
if (path === cwd) return null;
|
|
3767
|
-
const prefix = `${cwd}${ROOT_DIRECTORY$
|
|
4191
|
+
const prefix = `${cwd}${ROOT_DIRECTORY$3}`;
|
|
3768
4192
|
if (!path.startsWith(prefix)) return null;
|
|
3769
4193
|
return path.slice(prefix.length);
|
|
3770
4194
|
}
|
|
@@ -3772,12 +4196,12 @@ function toGlobCandidate(entry, cwd, isAbsolutePattern, directoryOnly) {
|
|
|
3772
4196
|
if (directoryOnly && !entry.isDirectory) return null;
|
|
3773
4197
|
const basePath = isAbsolutePattern ? entry.path : toRelativePathFromCwd$1(entry.path, cwd);
|
|
3774
4198
|
if (!basePath || basePath === "") return null;
|
|
3775
|
-
if (directoryOnly) return `${basePath}${ROOT_DIRECTORY$
|
|
4199
|
+
if (directoryOnly) return `${basePath}${ROOT_DIRECTORY$3}`;
|
|
3776
4200
|
return basePath;
|
|
3777
4201
|
}
|
|
3778
4202
|
async function expandGlobPattern(pattern, fs, context) {
|
|
3779
|
-
const directoryOnly = pattern.endsWith(ROOT_DIRECTORY$
|
|
3780
|
-
const isAbsolutePattern = pattern.startsWith(ROOT_DIRECTORY$
|
|
4203
|
+
const directoryOnly = pattern.endsWith(ROOT_DIRECTORY$3);
|
|
4204
|
+
const isAbsolutePattern = pattern.startsWith(ROOT_DIRECTORY$3);
|
|
3781
4205
|
const matcher = picomatch(pattern, {
|
|
3782
4206
|
bash: true,
|
|
3783
4207
|
dot: false
|
|
@@ -3841,33 +4265,6 @@ async function evaluateExpandedWordPart(part, fs, context) {
|
|
|
3841
4265
|
}
|
|
3842
4266
|
}
|
|
3843
4267
|
//#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
4268
|
//#region src/execute/records.ts
|
|
3872
4269
|
async function* toLineStream(fs, input) {
|
|
3873
4270
|
for await (const record of input) {
|
|
@@ -3922,10 +4319,156 @@ async function isDirectoryRecord(fs, record) {
|
|
|
3922
4319
|
return false;
|
|
3923
4320
|
}
|
|
3924
4321
|
}
|
|
3925
|
-
|
|
3926
|
-
|
|
4322
|
+
//#endregion
|
|
4323
|
+
//#region src/execute/redirection.ts
|
|
4324
|
+
const textEncoder = new TextEncoder();
|
|
4325
|
+
const textDecoder = new TextDecoder();
|
|
4326
|
+
const FD_TARGET_REGEX$1 = /^&[0-9]+$/;
|
|
4327
|
+
function getSourceFd$1(redirection) {
|
|
4328
|
+
return redirection.sourceFd ?? (redirection.kind === "input" ? 0 : 1);
|
|
4329
|
+
}
|
|
4330
|
+
function inferModeFromTarget(redirection) {
|
|
4331
|
+
const targetText = expandedWordToString(redirection.target);
|
|
4332
|
+
if (targetText === "&-") return "close";
|
|
4333
|
+
if (FD_TARGET_REGEX$1.test(targetText)) return "fd";
|
|
4334
|
+
if (redirection.kind === "output" && targetText === "|") return "pipe";
|
|
4335
|
+
return "file";
|
|
4336
|
+
}
|
|
4337
|
+
function getRedirectionMode(redirection) {
|
|
4338
|
+
return redirection.mode ?? inferModeFromTarget(redirection);
|
|
4339
|
+
}
|
|
4340
|
+
function getTargetFd$1(redirection) {
|
|
4341
|
+
if (redirection.targetFd !== void 0 && redirection.targetFd !== null) return redirection.targetFd;
|
|
4342
|
+
const targetText = expandedWordToString(redirection.target);
|
|
4343
|
+
if (FD_TARGET_REGEX$1.test(targetText)) return Number(targetText.slice(1));
|
|
4344
|
+
return null;
|
|
4345
|
+
}
|
|
4346
|
+
function isOptionalInput(redirection) {
|
|
4347
|
+
if (redirection.optional) return true;
|
|
4348
|
+
if (redirection.kind !== "input") return false;
|
|
4349
|
+
return expandedWordToString(redirection.target).startsWith("?");
|
|
4350
|
+
}
|
|
4351
|
+
function isDefaultFileRedirect(redirection, kind) {
|
|
4352
|
+
if (redirection.kind !== kind) return false;
|
|
4353
|
+
if (getRedirectionMode(redirection) !== "file") return false;
|
|
4354
|
+
return getSourceFd$1(redirection) === (kind === "input" ? 0 : 1);
|
|
4355
|
+
}
|
|
4356
|
+
function getLastDefaultFileRedirect(redirections, kind) {
|
|
4357
|
+
if (!redirections) return null;
|
|
4358
|
+
let redirect = null;
|
|
4359
|
+
for (const redirection of redirections) if (isDefaultFileRedirect(redirection, kind)) redirect = redirection;
|
|
4360
|
+
return redirect;
|
|
4361
|
+
}
|
|
4362
|
+
async function resolveFileRedirect(command, redirection, fs, context) {
|
|
4363
|
+
const targetPath = await evaluateExpandedSinglePath(command, "redirection target must expand to exactly 1 path", redirection.target, fs, context);
|
|
4364
|
+
return {
|
|
4365
|
+
path: resolvePathFromCwd(context.cwd, targetPath),
|
|
4366
|
+
append: redirection.append ?? false,
|
|
4367
|
+
noclobber: redirection.noclobber ?? false
|
|
4368
|
+
};
|
|
4369
|
+
}
|
|
4370
|
+
function updateDescriptor(descriptor, path) {
|
|
4371
|
+
descriptor.kind = "path";
|
|
4372
|
+
descriptor.path = path;
|
|
4373
|
+
}
|
|
4374
|
+
function ensureInputDescriptor(descriptors, fd) {
|
|
4375
|
+
const existing = descriptors.get(fd);
|
|
4376
|
+
if (existing) return existing;
|
|
4377
|
+
const descriptor = { kind: "inherit" };
|
|
4378
|
+
descriptors.set(fd, descriptor);
|
|
4379
|
+
return descriptor;
|
|
4380
|
+
}
|
|
4381
|
+
async function resolveInputRedirect(command, redirections, fs, context) {
|
|
4382
|
+
if (!redirections || redirections.length === 0) return {
|
|
4383
|
+
path: null,
|
|
4384
|
+
closed: false
|
|
4385
|
+
};
|
|
4386
|
+
const descriptors = /* @__PURE__ */ new Map();
|
|
4387
|
+
for (const redirection of redirections) {
|
|
4388
|
+
if (redirection.kind !== "input") continue;
|
|
4389
|
+
const sourceFd = getSourceFd$1(redirection);
|
|
4390
|
+
const mode = getRedirectionMode(redirection);
|
|
4391
|
+
if (mode === "close") {
|
|
4392
|
+
descriptors.set(sourceFd, { kind: "closed" });
|
|
4393
|
+
continue;
|
|
4394
|
+
}
|
|
4395
|
+
if (mode === "fd") {
|
|
4396
|
+
const targetFd = getTargetFd$1(redirection);
|
|
4397
|
+
if (targetFd === null) throw new Error(`${command}: invalid file descriptor duplication target`);
|
|
4398
|
+
descriptors.set(sourceFd, ensureInputDescriptor(descriptors, targetFd));
|
|
4399
|
+
continue;
|
|
4400
|
+
}
|
|
4401
|
+
if (mode !== "file") continue;
|
|
4402
|
+
const resolved = await resolveFileRedirect(command, redirection, fs, context);
|
|
4403
|
+
if (isOptionalInput(redirection) && !await fs.exists(resolved.path)) continue;
|
|
4404
|
+
updateDescriptor(ensureInputDescriptor(descriptors, sourceFd), resolved.path);
|
|
4405
|
+
}
|
|
4406
|
+
const stdinDescriptor = ensureInputDescriptor(descriptors, 0);
|
|
4407
|
+
return {
|
|
4408
|
+
path: stdinDescriptor.kind === "path" ? stdinDescriptor.path ?? null : null,
|
|
4409
|
+
closed: stdinDescriptor.kind === "closed"
|
|
4410
|
+
};
|
|
4411
|
+
}
|
|
4412
|
+
function hasRedirect(redirections, kind) {
|
|
4413
|
+
return getLastDefaultFileRedirect(redirections, kind) !== null;
|
|
4414
|
+
}
|
|
4415
|
+
async function resolveRedirectPath(command, redirections, kind, fs, context) {
|
|
4416
|
+
if (kind === "input") return (await resolveInputRedirect(command, redirections, fs, context)).path;
|
|
4417
|
+
const redirect = getLastDefaultFileRedirect(redirections, kind);
|
|
4418
|
+
if (!redirect) return null;
|
|
4419
|
+
return (await resolveFileRedirect(command, redirect, fs, context)).path;
|
|
4420
|
+
}
|
|
4421
|
+
function withInputRedirect(paths, inputPath) {
|
|
4422
|
+
if (paths.length > 0 || !inputPath) return paths;
|
|
4423
|
+
return [inputPath];
|
|
4424
|
+
}
|
|
4425
|
+
async function readExistingFileText(fs, path) {
|
|
4426
|
+
try {
|
|
4427
|
+
return textDecoder.decode(await fs.readFile(path));
|
|
4428
|
+
} catch {
|
|
4429
|
+
return "";
|
|
4430
|
+
}
|
|
4431
|
+
}
|
|
4432
|
+
async function writeTextToFile(fs, path, content, options) {
|
|
4433
|
+
if (!(options.append ?? false)) {
|
|
4434
|
+
await fs.writeFile(path, textEncoder.encode(content));
|
|
4435
|
+
return;
|
|
4436
|
+
}
|
|
4437
|
+
const existing = await readExistingFileText(fs, path);
|
|
4438
|
+
const separator = existing === "" || content === "" ? "" : "\n";
|
|
4439
|
+
await fs.writeFile(path, textEncoder.encode(`${existing}${separator}${content}`));
|
|
4440
|
+
}
|
|
4441
|
+
async function ensureNoclobberWritable(fs, path) {
|
|
4442
|
+
return !await fs.exists(path);
|
|
3927
4443
|
}
|
|
3928
4444
|
//#endregion
|
|
4445
|
+
//#region src/builtin/cd/cd.ts
|
|
4446
|
+
const cd = async (runtime, args) => {
|
|
4447
|
+
const requestedPath = await evaluateExpandedSinglePath("cd", "expected exactly 1 path after expansion", args.path, runtime.fs, runtime.context, { allowEmpty: true });
|
|
4448
|
+
if (requestedPath === "") throw new Error("cd: empty path");
|
|
4449
|
+
const resolvedPath = resolvePathFromCwd(runtime.context.cwd, requestedPath);
|
|
4450
|
+
let stat;
|
|
4451
|
+
try {
|
|
4452
|
+
stat = await runtime.fs.stat(resolvedPath);
|
|
4453
|
+
} catch {
|
|
4454
|
+
throw new Error(`cd: directory does not exist: ${requestedPath}`);
|
|
4455
|
+
}
|
|
4456
|
+
if (!stat.isDirectory) throw new Error(`cd: not a directory: ${requestedPath}`);
|
|
4457
|
+
runtime.context.cwd = resolvedPath;
|
|
4458
|
+
runtime.context.status = 0;
|
|
4459
|
+
};
|
|
4460
|
+
//#endregion
|
|
4461
|
+
//#region src/builtin/echo/echo.ts
|
|
4462
|
+
const echo = (runtime, args) => {
|
|
4463
|
+
return (async function* () {
|
|
4464
|
+
yield {
|
|
4465
|
+
kind: "line",
|
|
4466
|
+
text: (await evaluateExpandedPathWords("echo", args.values, runtime.fs, runtime.context)).join(" ")
|
|
4467
|
+
};
|
|
4468
|
+
runtime.context.status = 0;
|
|
4469
|
+
})();
|
|
4470
|
+
};
|
|
4471
|
+
//#endregion
|
|
3929
4472
|
//#region src/builtin/read/read.ts
|
|
3930
4473
|
const VARIABLE_NAME_REGEX$1 = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
3931
4474
|
async function readFirstValue(runtime) {
|
|
@@ -4282,6 +4825,7 @@ async function* find(fs, context, args) {
|
|
|
4282
4825
|
return;
|
|
4283
4826
|
}
|
|
4284
4827
|
const state = { hadError: false };
|
|
4828
|
+
const hasEmptyPredicate = resolvedPredicateBranches.some((branch) => branch.some((predicate) => predicate.kind === "empty"));
|
|
4285
4829
|
for (const startPath of startPaths) {
|
|
4286
4830
|
let startStat;
|
|
4287
4831
|
try {
|
|
@@ -4294,39 +4838,40 @@ async function* find(fs, context, args) {
|
|
|
4294
4838
|
yield* walkEntry(fs, context, {
|
|
4295
4839
|
...startPath,
|
|
4296
4840
|
depth: 0,
|
|
4297
|
-
isDirectory: startStat.isDirectory
|
|
4298
|
-
|
|
4841
|
+
isDirectory: startStat.isDirectory,
|
|
4842
|
+
size: startStat.size
|
|
4843
|
+
}, args, resolvedPredicateBranches, state, hasEmptyPredicate);
|
|
4299
4844
|
}
|
|
4300
4845
|
context.status = state.hadError ? 1 : 0;
|
|
4301
4846
|
}
|
|
4302
|
-
async function* walkEntry(fs, context, entry, args, predicateBranches, state) {
|
|
4303
|
-
const
|
|
4847
|
+
async function* walkEntry(fs, context, entry, args, predicateBranches, state, hasEmptyPredicate) {
|
|
4848
|
+
const shouldRecurse = entry.isDirectory && (args.traversal.maxdepth === null || entry.depth < args.traversal.maxdepth);
|
|
4849
|
+
const shouldReadChildren = shouldRecurse || entry.isDirectory && hasEmptyPredicate;
|
|
4850
|
+
let childPaths = null;
|
|
4851
|
+
if (shouldReadChildren) try {
|
|
4852
|
+
childPaths = await readChildren(fs, entry.absolutePath);
|
|
4853
|
+
} catch {
|
|
4854
|
+
state.hadError = true;
|
|
4855
|
+
writeDiagnosticsToStderr(context, [createRuntimeDiagnostic("find", "unreadable-directory", "Unable to read directory", { path: entry.displayPath })]);
|
|
4856
|
+
}
|
|
4857
|
+
const matches = entry.depth >= args.traversal.mindepth && matchesPredicates(entry, predicateBranches, childPaths);
|
|
4304
4858
|
if (!args.traversal.depth && matches) yield toFileRecord(entry);
|
|
4305
|
-
if (
|
|
4306
|
-
let
|
|
4859
|
+
if (shouldRecurse && childPaths !== null) for (const childAbsolutePath of childPaths) {
|
|
4860
|
+
let childStat;
|
|
4307
4861
|
try {
|
|
4308
|
-
|
|
4862
|
+
childStat = await fs.stat(childAbsolutePath);
|
|
4309
4863
|
} catch {
|
|
4310
4864
|
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);
|
|
4865
|
+
writeDiagnosticsToStderr(context, [createRuntimeDiagnostic("find", "missing-path", "No such file or directory", { path: appendDisplayPath(entry.displayPath, basename$2(childAbsolutePath)) })]);
|
|
4866
|
+
continue;
|
|
4329
4867
|
}
|
|
4868
|
+
yield* walkEntry(fs, context, {
|
|
4869
|
+
absolutePath: childAbsolutePath,
|
|
4870
|
+
displayPath: appendDisplayPath(entry.displayPath, basename$2(childAbsolutePath)),
|
|
4871
|
+
depth: entry.depth + 1,
|
|
4872
|
+
isDirectory: childStat.isDirectory,
|
|
4873
|
+
size: childStat.size
|
|
4874
|
+
}, args, predicateBranches, state, hasEmptyPredicate);
|
|
4330
4875
|
}
|
|
4331
4876
|
if (args.traversal.depth && matches) yield toFileRecord(entry);
|
|
4332
4877
|
}
|
|
@@ -4357,6 +4902,35 @@ async function resolvePredicates(predicateBranches, fs, context) {
|
|
|
4357
4902
|
});
|
|
4358
4903
|
break;
|
|
4359
4904
|
}
|
|
4905
|
+
case "ipath": {
|
|
4906
|
+
const pattern = await evaluateExpandedWord(predicate.pattern, fs, context);
|
|
4907
|
+
resolvedBranch.push({
|
|
4908
|
+
kind: "path",
|
|
4909
|
+
matcher: picomatch(pattern, {
|
|
4910
|
+
bash: true,
|
|
4911
|
+
dot: true,
|
|
4912
|
+
nocase: true
|
|
4913
|
+
})
|
|
4914
|
+
});
|
|
4915
|
+
break;
|
|
4916
|
+
}
|
|
4917
|
+
case "regex": {
|
|
4918
|
+
const pattern = await evaluateExpandedWord(predicate.pattern, fs, context);
|
|
4919
|
+
resolvedBranch.push({
|
|
4920
|
+
kind: "regex",
|
|
4921
|
+
matcher: compileFindRegexMatcher(pattern, predicate.caseInsensitive)
|
|
4922
|
+
});
|
|
4923
|
+
break;
|
|
4924
|
+
}
|
|
4925
|
+
case "constant":
|
|
4926
|
+
resolvedBranch.push({
|
|
4927
|
+
kind: "constant",
|
|
4928
|
+
value: predicate.value
|
|
4929
|
+
});
|
|
4930
|
+
break;
|
|
4931
|
+
case "empty":
|
|
4932
|
+
resolvedBranch.push({ kind: "empty" });
|
|
4933
|
+
break;
|
|
4360
4934
|
case "type":
|
|
4361
4935
|
resolvedBranch.push({
|
|
4362
4936
|
kind: "type",
|
|
@@ -4386,25 +4960,70 @@ async function resolveStartPaths(fs, context, startPathWords) {
|
|
|
4386
4960
|
}
|
|
4387
4961
|
return startPaths;
|
|
4388
4962
|
}
|
|
4389
|
-
function matchesPredicates(entry, predicateBranches) {
|
|
4963
|
+
function matchesPredicates(entry, predicateBranches, childPaths) {
|
|
4390
4964
|
if (predicateBranches.length === 0) return true;
|
|
4391
4965
|
const entryType = entry.isDirectory ? "d" : "f";
|
|
4392
|
-
for (const branch of predicateBranches) if (matchesBranch(entry, entryType, branch)) return true;
|
|
4966
|
+
for (const branch of predicateBranches) if (matchesBranch(entry, entryType, branch, childPaths)) return true;
|
|
4393
4967
|
return false;
|
|
4394
4968
|
}
|
|
4395
|
-
function matchesBranch(entry, entryType, branch) {
|
|
4396
|
-
for (const predicate of branch) if (!matchesPredicate(entry, entryType, predicate)) return false;
|
|
4969
|
+
function matchesBranch(entry, entryType, branch, childPaths) {
|
|
4970
|
+
for (const predicate of branch) if (!matchesPredicate(entry, entryType, predicate, childPaths)) return false;
|
|
4397
4971
|
return true;
|
|
4398
4972
|
}
|
|
4399
|
-
function matchesPredicate(entry, entryType, predicate) {
|
|
4973
|
+
function matchesPredicate(entry, entryType, predicate, childPaths) {
|
|
4400
4974
|
if (predicate.kind === "name") return predicate.matcher(basename$2(entry.displayPath));
|
|
4401
4975
|
if (predicate.kind === "path") return predicate.matcher(entry.displayPath);
|
|
4976
|
+
if (predicate.kind === "regex") return predicate.matcher(entry.displayPath);
|
|
4977
|
+
if (predicate.kind === "constant") return predicate.value;
|
|
4978
|
+
if (predicate.kind === "empty") {
|
|
4979
|
+
if (entryType === "f") return entry.size === 0;
|
|
4980
|
+
return childPaths !== null && childPaths.length === 0;
|
|
4981
|
+
}
|
|
4402
4982
|
return predicate.types.has(entryType);
|
|
4403
4983
|
}
|
|
4404
|
-
|
|
4405
|
-
const
|
|
4406
|
-
|
|
4407
|
-
|
|
4984
|
+
function compileFindRegexMatcher(pattern, caseInsensitive) {
|
|
4985
|
+
const translatedPattern = translateFindRegexPattern(pattern);
|
|
4986
|
+
const flags = caseInsensitive ? "i" : "";
|
|
4987
|
+
const regex = new RegExp(`^(?:${translatedPattern})$`, flags);
|
|
4988
|
+
return (value) => regex.test(value);
|
|
4989
|
+
}
|
|
4990
|
+
function translateFindRegexPattern(pattern) {
|
|
4991
|
+
let translated = "";
|
|
4992
|
+
for (let index = 0; index < pattern.length; index++) {
|
|
4993
|
+
const char = pattern[index];
|
|
4994
|
+
if (char === void 0) continue;
|
|
4995
|
+
if (char !== "\\") {
|
|
4996
|
+
translated += isEmacsLiteralJsMetaChar(char) ? `\\${char}` : char;
|
|
4997
|
+
continue;
|
|
4998
|
+
}
|
|
4999
|
+
const escapedChar = pattern[index + 1];
|
|
5000
|
+
if (escapedChar === void 0) {
|
|
5001
|
+
translated += "\\\\";
|
|
5002
|
+
continue;
|
|
5003
|
+
}
|
|
5004
|
+
index += 1;
|
|
5005
|
+
if (isEmacsEscapedOperatorChar(escapedChar)) {
|
|
5006
|
+
translated += escapedChar;
|
|
5007
|
+
continue;
|
|
5008
|
+
}
|
|
5009
|
+
translated += escapeJsRegexLiteralChar(escapedChar);
|
|
5010
|
+
}
|
|
5011
|
+
return translated;
|
|
5012
|
+
}
|
|
5013
|
+
function isEmacsEscapedOperatorChar(char) {
|
|
5014
|
+
return char === "(" || char === ")" || char === "|" || char === "+" || char === "?" || char === "{" || char === "}";
|
|
5015
|
+
}
|
|
5016
|
+
function isEmacsLiteralJsMetaChar(char) {
|
|
5017
|
+
return char === "(" || char === ")" || char === "|" || char === "+" || char === "?" || char === "{" || char === "}";
|
|
5018
|
+
}
|
|
5019
|
+
function escapeJsRegexLiteralChar(char) {
|
|
5020
|
+
if (char === "\\" || char === "^" || char === "$" || char === "." || char === "*" || char === "+" || char === "?" || char === "(" || char === ")" || char === "[" || char === "]" || char === "{" || char === "}" || char === "|") return `\\${char}`;
|
|
5021
|
+
return char;
|
|
5022
|
+
}
|
|
5023
|
+
async function readChildren(fs, path) {
|
|
5024
|
+
const children = [];
|
|
5025
|
+
for await (const childPath of fs.readdir(path)) children.push(childPath);
|
|
5026
|
+
return children;
|
|
4408
5027
|
}
|
|
4409
5028
|
function appendDisplayPath(parentPath, childName) {
|
|
4410
5029
|
if (parentPath === "/") return `/${childName}`;
|
|
@@ -4457,53 +5076,6 @@ function trimTrailingSlashes(path) {
|
|
|
4457
5076
|
return path.replace(/\/+$/g, "");
|
|
4458
5077
|
}
|
|
4459
5078
|
//#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
5079
|
//#region src/operator/grep/grep.ts
|
|
4508
5080
|
const UTF8_DECODER = new TextDecoder();
|
|
4509
5081
|
const UTF8_ENCODER = new TextEncoder();
|
|
@@ -5541,8 +6113,8 @@ function mv(fs) {
|
|
|
5541
6113
|
}
|
|
5542
6114
|
//#endregion
|
|
5543
6115
|
//#region src/operator/pwd/pwd.ts
|
|
5544
|
-
const ROOT_DIRECTORY$
|
|
5545
|
-
async function* pwd(cwd = ROOT_DIRECTORY$
|
|
6116
|
+
const ROOT_DIRECTORY$2 = "/";
|
|
6117
|
+
async function* pwd(cwd = ROOT_DIRECTORY$2) {
|
|
5546
6118
|
yield {
|
|
5547
6119
|
kind: "line",
|
|
5548
6120
|
text: cwd
|
|
@@ -5598,173 +6170,345 @@ function touch(fs) {
|
|
|
5598
6170
|
};
|
|
5599
6171
|
}
|
|
5600
6172
|
//#endregion
|
|
5601
|
-
//#region src/
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
|
|
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);
|
|
6173
|
+
//#region src/operator/xargs/xargs.ts
|
|
6174
|
+
const DEFAULT_MAX_ARGS = Number.POSITIVE_INFINITY;
|
|
6175
|
+
const TEXT_DECODER = new TextDecoder();
|
|
6176
|
+
const WHITESPACE_REGEX = /\s/u;
|
|
6177
|
+
const LEADING_WHITESPACE_REGEX = /^\s+/u;
|
|
6178
|
+
async function runXargsCommand(options) {
|
|
6179
|
+
try {
|
|
6180
|
+
return await runXargsCommandInner(options);
|
|
6181
|
+
} catch {
|
|
6182
|
+
return {
|
|
6183
|
+
exitCode: 1,
|
|
6184
|
+
stderr: [],
|
|
6185
|
+
stdout: []
|
|
6186
|
+
};
|
|
6187
|
+
}
|
|
5623
6188
|
}
|
|
5624
|
-
async function
|
|
5625
|
-
|
|
5626
|
-
|
|
5627
|
-
|
|
5628
|
-
|
|
5629
|
-
|
|
5630
|
-
|
|
5631
|
-
|
|
6189
|
+
async function runXargsCommandInner(options) {
|
|
6190
|
+
const input = await readInput(options);
|
|
6191
|
+
const command = await evaluateExpandedWords(options.parsed.command, options.fs, options.context);
|
|
6192
|
+
const batches = buildBatches(input, options.parsed);
|
|
6193
|
+
if (batches.length === 0 && options.parsed.noRunIfEmpty) return {
|
|
6194
|
+
exitCode: 0,
|
|
6195
|
+
stderr: [],
|
|
6196
|
+
stdout: []
|
|
6197
|
+
};
|
|
6198
|
+
const stdout = [];
|
|
6199
|
+
const stderr = [];
|
|
6200
|
+
let exitCode = 0;
|
|
6201
|
+
for (const batch of batches.length > 0 ? batches : [[]]) {
|
|
6202
|
+
const result = await runCommand(buildCommandArgs(command, batch, options.parsed), options.fs, options.context);
|
|
6203
|
+
stdout.push(...result.stdout);
|
|
6204
|
+
stderr.push(...result.stderr);
|
|
6205
|
+
if (result.exitCode !== 0) exitCode = result.exitCode;
|
|
6206
|
+
}
|
|
6207
|
+
return {
|
|
6208
|
+
exitCode,
|
|
6209
|
+
stderr,
|
|
6210
|
+
stdout
|
|
6211
|
+
};
|
|
5632
6212
|
}
|
|
5633
|
-
function
|
|
5634
|
-
return
|
|
6213
|
+
async function readInput(options) {
|
|
6214
|
+
if (options.inputPath) return TEXT_DECODER.decode(await options.fs.readFile(options.inputPath));
|
|
6215
|
+
if (!options.input) return "";
|
|
6216
|
+
const records = [];
|
|
6217
|
+
for await (const record of options.input) records.push(formatStdoutRecord(record));
|
|
6218
|
+
return records.join("\n");
|
|
6219
|
+
}
|
|
6220
|
+
function buildBatches(input, args) {
|
|
6221
|
+
if (args.replace) return replacementBatches(input, args);
|
|
6222
|
+
if (args.maxLines) return lineBatches(input, args);
|
|
6223
|
+
return chunkItems(args.delimiter === null ? tokenize(input, args.eof).items : splitDelimited(input, args.delimiter), args.maxArgs ?? DEFAULT_MAX_ARGS);
|
|
6224
|
+
}
|
|
6225
|
+
function replacementBatches(input, args) {
|
|
6226
|
+
const batches = [];
|
|
6227
|
+
for (const line of splitInputLines(input)) {
|
|
6228
|
+
const item = line.replace(LEADING_WHITESPACE_REGEX, "");
|
|
6229
|
+
if (item === "") continue;
|
|
6230
|
+
if (args.eof !== null && item === args.eof) break;
|
|
6231
|
+
batches.push([item]);
|
|
6232
|
+
}
|
|
6233
|
+
return batches;
|
|
6234
|
+
}
|
|
6235
|
+
function lineBatches(input, args) {
|
|
6236
|
+
const batches = [];
|
|
6237
|
+
let current = [];
|
|
6238
|
+
let lineCount = 0;
|
|
6239
|
+
for (const line of splitInputLines(input)) {
|
|
6240
|
+
const parsed = tokenize(line, args.eof);
|
|
6241
|
+
if (parsed.items.length === 0) {
|
|
6242
|
+
if (parsed.stopped) break;
|
|
6243
|
+
continue;
|
|
6244
|
+
}
|
|
6245
|
+
current.push(...parsed.items);
|
|
6246
|
+
lineCount++;
|
|
6247
|
+
if (lineCount >= (args.maxLines ?? 1)) {
|
|
6248
|
+
batches.push(current);
|
|
6249
|
+
current = [];
|
|
6250
|
+
lineCount = 0;
|
|
6251
|
+
}
|
|
6252
|
+
if (parsed.stopped) break;
|
|
6253
|
+
}
|
|
6254
|
+
if (current.length > 0) batches.push(current);
|
|
6255
|
+
return batches;
|
|
5635
6256
|
}
|
|
5636
|
-
function
|
|
5637
|
-
|
|
5638
|
-
|
|
5639
|
-
|
|
5640
|
-
|
|
6257
|
+
function chunkItems(items, maxArgs) {
|
|
6258
|
+
if (items.length === 0) return [];
|
|
6259
|
+
if (!Number.isFinite(maxArgs)) return [items];
|
|
6260
|
+
const chunks = [];
|
|
6261
|
+
for (let index = 0; index < items.length; index += maxArgs) chunks.push(items.slice(index, index + maxArgs));
|
|
6262
|
+
return chunks;
|
|
5641
6263
|
}
|
|
5642
|
-
function
|
|
5643
|
-
|
|
5644
|
-
|
|
5645
|
-
|
|
6264
|
+
function splitDelimited(input, delimiter) {
|
|
6265
|
+
if (delimiter === "") return input === "" ? [] : [input];
|
|
6266
|
+
const parts = input.split(delimiter);
|
|
6267
|
+
if (input.endsWith(delimiter)) parts.pop();
|
|
6268
|
+
return parts;
|
|
5646
6269
|
}
|
|
5647
|
-
function
|
|
5648
|
-
|
|
5649
|
-
if (
|
|
5650
|
-
return
|
|
6270
|
+
function splitInputLines(input) {
|
|
6271
|
+
const lines = input.split("\n");
|
|
6272
|
+
if (input.endsWith("\n")) lines.pop();
|
|
6273
|
+
return lines;
|
|
5651
6274
|
}
|
|
5652
|
-
function
|
|
5653
|
-
|
|
5654
|
-
|
|
5655
|
-
|
|
5656
|
-
|
|
5657
|
-
|
|
5658
|
-
kind: "sink",
|
|
5659
|
-
value: runScriptToCompletion(script, fs, context)
|
|
6275
|
+
function tokenize(input, eof) {
|
|
6276
|
+
const items = [];
|
|
6277
|
+
const state = {
|
|
6278
|
+
current: "",
|
|
6279
|
+
escaped: false,
|
|
6280
|
+
quote: null
|
|
5660
6281
|
};
|
|
6282
|
+
for (const char of input) {
|
|
6283
|
+
if (state.escaped) {
|
|
6284
|
+
appendEscapedChar(state, char);
|
|
6285
|
+
continue;
|
|
6286
|
+
}
|
|
6287
|
+
if (state.quote) {
|
|
6288
|
+
appendQuotedChar(state, char);
|
|
6289
|
+
continue;
|
|
6290
|
+
}
|
|
6291
|
+
if (WHITESPACE_REGEX.test(char)) {
|
|
6292
|
+
const stopped = pushToken(items, state.current, eof);
|
|
6293
|
+
state.current = "";
|
|
6294
|
+
if (stopped) return {
|
|
6295
|
+
items,
|
|
6296
|
+
stopped: true
|
|
6297
|
+
};
|
|
6298
|
+
continue;
|
|
6299
|
+
}
|
|
6300
|
+
appendUnquotedChar(state, char);
|
|
6301
|
+
}
|
|
6302
|
+
if (state.quote) throw new Error(`xargs: unterminated quote ${state.quote}`);
|
|
6303
|
+
if (state.escaped) throw new Error("xargs: unterminated escape");
|
|
5661
6304
|
return {
|
|
5662
|
-
|
|
5663
|
-
|
|
6305
|
+
items,
|
|
6306
|
+
stopped: pushToken(items, state.current, eof)
|
|
5664
6307
|
};
|
|
5665
6308
|
}
|
|
5666
|
-
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
try {
|
|
5670
|
-
await drainResult(executePipeline(statement.pipeline, fs, context));
|
|
5671
|
-
} catch (error) {
|
|
5672
|
-
context.status = 1;
|
|
5673
|
-
throw error;
|
|
5674
|
-
}
|
|
5675
|
-
}
|
|
6309
|
+
function appendEscapedChar(state, char) {
|
|
6310
|
+
state.current += char;
|
|
6311
|
+
state.escaped = false;
|
|
5676
6312
|
}
|
|
5677
|
-
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
const result = executePipeline(statement.pipeline, fs, context);
|
|
5682
|
-
if (result.kind === "sink") {
|
|
5683
|
-
await result.value;
|
|
5684
|
-
continue;
|
|
5685
|
-
}
|
|
5686
|
-
yield* result.value;
|
|
5687
|
-
} catch (error) {
|
|
5688
|
-
context.status = 1;
|
|
5689
|
-
throw error;
|
|
5690
|
-
}
|
|
6313
|
+
function appendQuotedChar(state, char) {
|
|
6314
|
+
if (char === state.quote) {
|
|
6315
|
+
state.quote = null;
|
|
6316
|
+
return;
|
|
5691
6317
|
}
|
|
6318
|
+
if (char === "\\") {
|
|
6319
|
+
state.escaped = true;
|
|
6320
|
+
return;
|
|
6321
|
+
}
|
|
6322
|
+
state.current += char;
|
|
5692
6323
|
}
|
|
5693
|
-
|
|
5694
|
-
if (
|
|
5695
|
-
|
|
6324
|
+
function appendUnquotedChar(state, char) {
|
|
6325
|
+
if (char === "\"" || char === "'") {
|
|
6326
|
+
state.quote = char;
|
|
5696
6327
|
return;
|
|
5697
6328
|
}
|
|
5698
|
-
|
|
6329
|
+
if (char === "\\") {
|
|
6330
|
+
state.escaped = true;
|
|
6331
|
+
return;
|
|
6332
|
+
}
|
|
6333
|
+
state.current += char;
|
|
5699
6334
|
}
|
|
5700
|
-
function
|
|
5701
|
-
if (
|
|
5702
|
-
|
|
5703
|
-
|
|
6335
|
+
function pushToken(items, token, eof) {
|
|
6336
|
+
if (token === "") return false;
|
|
6337
|
+
if (eof !== null && token === eof) return true;
|
|
6338
|
+
items.push(token);
|
|
6339
|
+
return false;
|
|
6340
|
+
}
|
|
6341
|
+
function buildCommandArgs(command, batch, args) {
|
|
6342
|
+
if (!args.replace) return [...command, ...batch];
|
|
6343
|
+
return command.map((part) => part.replaceAll(args.replace ?? "", batch[0] ?? ""));
|
|
6344
|
+
}
|
|
6345
|
+
async function runCommand(argv, fs, context) {
|
|
6346
|
+
if (argv.length === 0) return {
|
|
6347
|
+
exitCode: 0,
|
|
6348
|
+
stderr: [],
|
|
6349
|
+
stdout: []
|
|
5704
6350
|
};
|
|
5705
|
-
const
|
|
5706
|
-
|
|
5707
|
-
|
|
5708
|
-
|
|
6351
|
+
const childContext = {
|
|
6352
|
+
cwd: context.cwd,
|
|
6353
|
+
globalVars: context.globalVars,
|
|
6354
|
+
localVars: context.localVars,
|
|
6355
|
+
status: context.status,
|
|
6356
|
+
stderr: new BufferedOutputStream()
|
|
5709
6357
|
};
|
|
5710
|
-
|
|
5711
|
-
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
return {
|
|
5722
|
-
kind: "sink",
|
|
5723
|
-
value: executePipelineToSink(ir.steps, fs, context)
|
|
5724
|
-
};
|
|
6358
|
+
const stdout = await collectStdout((await Promise.resolve().then(() => execute_exports)).execute(compile(parse(argv.map(quoteShellWord).join(" "))), fs, childContext));
|
|
6359
|
+
return {
|
|
6360
|
+
exitCode: childContext.status,
|
|
6361
|
+
stderr: [...childContext.stderr.snapshot()],
|
|
6362
|
+
stdout
|
|
6363
|
+
};
|
|
6364
|
+
}
|
|
6365
|
+
async function collectStdout(result) {
|
|
6366
|
+
if (result.kind === "sink") {
|
|
6367
|
+
await result.value;
|
|
6368
|
+
return [];
|
|
5725
6369
|
}
|
|
5726
|
-
|
|
5727
|
-
|
|
5728
|
-
|
|
5729
|
-
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
|
|
6370
|
+
const stdout = [];
|
|
6371
|
+
for await (const record of result.value) stdout.push(formatStdoutRecord(record));
|
|
6372
|
+
return stdout;
|
|
6373
|
+
}
|
|
6374
|
+
function quoteShellWord(value) {
|
|
6375
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
6376
|
+
}
|
|
6377
|
+
//#endregion
|
|
6378
|
+
//#region src/execute/producers.ts
|
|
6379
|
+
async function* files(...paths) {
|
|
6380
|
+
for (const path of paths) yield {
|
|
6381
|
+
kind: "file",
|
|
6382
|
+
path
|
|
5738
6383
|
};
|
|
5739
|
-
return applyOutputRedirect({
|
|
5740
|
-
kind: "stream",
|
|
5741
|
-
value: executePipelineToStream(ir.steps, fs, context)
|
|
5742
|
-
}, lastStep, fs, context);
|
|
5743
6384
|
}
|
|
5744
|
-
|
|
6385
|
+
//#endregion
|
|
6386
|
+
//#region src/execute/registry.ts
|
|
6387
|
+
const EFFECT_COMMANDS = [
|
|
6388
|
+
"cd",
|
|
6389
|
+
"cp",
|
|
6390
|
+
"mkdir",
|
|
6391
|
+
"mv",
|
|
6392
|
+
"rm",
|
|
6393
|
+
"touch"
|
|
6394
|
+
];
|
|
6395
|
+
const EFFECT_COMMAND_SET = new Set(EFFECT_COMMANDS);
|
|
6396
|
+
const STREAM_COMMANDS = [
|
|
6397
|
+
"cat",
|
|
6398
|
+
"echo",
|
|
6399
|
+
"find",
|
|
6400
|
+
"grep",
|
|
6401
|
+
"head",
|
|
6402
|
+
"ls",
|
|
6403
|
+
"pwd",
|
|
6404
|
+
"read",
|
|
6405
|
+
"set",
|
|
6406
|
+
"string",
|
|
6407
|
+
"tail",
|
|
6408
|
+
"test",
|
|
6409
|
+
"xargs"
|
|
6410
|
+
];
|
|
6411
|
+
const STREAM_COMMAND_SET = new Set(STREAM_COMMANDS);
|
|
6412
|
+
const ROOT_DIRECTORY$1 = "/";
|
|
6413
|
+
function lineRecordsFromPath(fs, path) {
|
|
5745
6414
|
return (async function* () {
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
}
|
|
5751
|
-
if (!stream) return;
|
|
5752
|
-
yield* stream;
|
|
6415
|
+
for await (const line of fs.readLines(path)) yield {
|
|
6416
|
+
kind: "line",
|
|
6417
|
+
text: line
|
|
6418
|
+
};
|
|
5753
6419
|
})();
|
|
5754
6420
|
}
|
|
5755
|
-
|
|
5756
|
-
|
|
5757
|
-
|
|
5758
|
-
|
|
5759
|
-
|
|
5760
|
-
|
|
6421
|
+
let commandRegistriesVerified = false;
|
|
6422
|
+
function isStreamCommand(command) {
|
|
6423
|
+
return STREAM_COMMAND_SET.has(command);
|
|
6424
|
+
}
|
|
6425
|
+
function verifyCommandRegistries() {
|
|
6426
|
+
if (commandRegistriesVerified) return;
|
|
6427
|
+
for (const command of EFFECT_COMMANDS) try {
|
|
6428
|
+
CommandRegistry.getEffect(command);
|
|
6429
|
+
} catch {
|
|
6430
|
+
throw new Error(`Missing effect command handler: ${command}`);
|
|
6431
|
+
}
|
|
6432
|
+
for (const command of STREAM_COMMANDS) try {
|
|
6433
|
+
CommandRegistry.getStream(command);
|
|
6434
|
+
} catch {
|
|
6435
|
+
throw new Error(`Missing stream command handler: ${command}`);
|
|
5761
6436
|
}
|
|
5762
|
-
|
|
6437
|
+
commandRegistriesVerified = true;
|
|
5763
6438
|
}
|
|
5764
|
-
function executeStreamStep(step, fs, input, context, resolvedOutputRedirectPath) {
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
6439
|
+
function executeStreamStep$1({ step, fs, input, context, resolvedOutputRedirectPath }) {
|
|
6440
|
+
verifyCommandRegistries();
|
|
6441
|
+
if (!isStreamCommand(step.cmd)) throw new Error(`Command "${step.cmd}" is not a stream command`);
|
|
6442
|
+
return CommandRegistry.getStream(step.cmd)({
|
|
6443
|
+
step,
|
|
6444
|
+
fs,
|
|
6445
|
+
input,
|
|
6446
|
+
context,
|
|
6447
|
+
resolvedOutputRedirectPath
|
|
6448
|
+
});
|
|
6449
|
+
}
|
|
6450
|
+
async function executeEffectStep$1({ step, fs, context }) {
|
|
6451
|
+
verifyCommandRegistries();
|
|
6452
|
+
await CommandRegistry.getEffect(step.cmd)({
|
|
6453
|
+
step,
|
|
6454
|
+
fs,
|
|
6455
|
+
context
|
|
6456
|
+
});
|
|
6457
|
+
}
|
|
6458
|
+
function createBuiltinRuntime(fs, context, input) {
|
|
6459
|
+
return {
|
|
6460
|
+
fs,
|
|
6461
|
+
context,
|
|
6462
|
+
input
|
|
6463
|
+
};
|
|
6464
|
+
}
|
|
6465
|
+
function formatLongListing(path, stat) {
|
|
6466
|
+
return `${stat.isDirectory ? "d" : "-"} ${String(stat.size).padStart(8, " ")} ${stat.mtime.toISOString()} ${path}`;
|
|
6467
|
+
}
|
|
6468
|
+
function normalizeLsPath(path, cwd) {
|
|
6469
|
+
if (path === "." || path === "./") return cwd;
|
|
6470
|
+
if (path.startsWith("./")) return `${cwd}/${path.slice(2)}`;
|
|
6471
|
+
if (path.startsWith(ROOT_DIRECTORY$1)) return path;
|
|
6472
|
+
return `${cwd}/${path}`;
|
|
6473
|
+
}
|
|
6474
|
+
function resolveLsPath(path, cwd) {
|
|
6475
|
+
return normalizeLsPath(path, cwd);
|
|
6476
|
+
}
|
|
6477
|
+
let CommandRegistry;
|
|
6478
|
+
(function(_CommandRegistry) {
|
|
6479
|
+
const commands = /* @__PURE__ */ new Map();
|
|
6480
|
+
function register(command, entry) {
|
|
6481
|
+
commands.set(command, entry);
|
|
6482
|
+
}
|
|
6483
|
+
_CommandRegistry.register = register;
|
|
6484
|
+
function getStream(command) {
|
|
6485
|
+
const entry = commands.get(command);
|
|
6486
|
+
if (!entry) throw new Error(`Unknown command: ${command}`);
|
|
6487
|
+
if (entry.kind !== "stream") throw new Error(`Command "${command}" is not a stream command`);
|
|
6488
|
+
return entry.handler;
|
|
6489
|
+
}
|
|
6490
|
+
_CommandRegistry.getStream = getStream;
|
|
6491
|
+
function getEffect(command) {
|
|
6492
|
+
const entry = commands.get(command);
|
|
6493
|
+
if (!entry) throw new Error(`Unknown command: ${command}`);
|
|
6494
|
+
if (entry.kind !== "effect") throw new Error(`Command "${command}" is not an effect command`);
|
|
6495
|
+
return entry.handler;
|
|
6496
|
+
}
|
|
6497
|
+
_CommandRegistry.getEffect = getEffect;
|
|
6498
|
+
function isEffectStep(step) {
|
|
6499
|
+
return EFFECT_COMMAND_SET.has(step.cmd);
|
|
6500
|
+
}
|
|
6501
|
+
_CommandRegistry.isEffectStep = isEffectStep;
|
|
6502
|
+
function executeStep(params) {
|
|
6503
|
+
if (isEffectStep(params.step)) return executeEffectStep$1(params);
|
|
6504
|
+
return executeStreamStep$1(params);
|
|
6505
|
+
}
|
|
6506
|
+
_CommandRegistry.executeStep = executeStep;
|
|
6507
|
+
})(CommandRegistry || (CommandRegistry = {}));
|
|
6508
|
+
CommandRegistry.register("cat", {
|
|
6509
|
+
kind: "stream",
|
|
6510
|
+
handler: ({ step, fs, input, context }) => {
|
|
6511
|
+
return (async function* () {
|
|
5768
6512
|
const options = {
|
|
5769
6513
|
numberLines: step.args.numberLines,
|
|
5770
6514
|
numberNonBlank: step.args.numberNonBlank,
|
|
@@ -5784,7 +6528,12 @@ function executeStreamStep(step, fs, input, context, resolvedOutputRedirectPath)
|
|
|
5784
6528
|
if (input) yield* cat(fs, options)(input);
|
|
5785
6529
|
context.status = 0;
|
|
5786
6530
|
})();
|
|
5787
|
-
|
|
6531
|
+
}
|
|
6532
|
+
});
|
|
6533
|
+
CommandRegistry.register("grep", {
|
|
6534
|
+
kind: "stream",
|
|
6535
|
+
handler: ({ step, fs, input, context, resolvedOutputRedirectPath }) => {
|
|
6536
|
+
return (async function* () {
|
|
5788
6537
|
const result = await runGrepCommand({
|
|
5789
6538
|
context,
|
|
5790
6539
|
fs,
|
|
@@ -5794,14 +6543,44 @@ function executeStreamStep(step, fs, input, context, resolvedOutputRedirectPath)
|
|
|
5794
6543
|
resolvedOutputRedirectPath
|
|
5795
6544
|
});
|
|
5796
6545
|
context.status = result.exitCode;
|
|
5797
|
-
context.stderr.
|
|
6546
|
+
context.stderr.appendLines(result.stderr);
|
|
6547
|
+
for (const text of result.stdout) yield {
|
|
6548
|
+
kind: "line",
|
|
6549
|
+
text
|
|
6550
|
+
};
|
|
6551
|
+
})();
|
|
6552
|
+
}
|
|
6553
|
+
});
|
|
6554
|
+
CommandRegistry.register("find", {
|
|
6555
|
+
kind: "stream",
|
|
6556
|
+
handler: ({ step, fs, context }) => {
|
|
6557
|
+
return find(fs, context, step.args);
|
|
6558
|
+
}
|
|
6559
|
+
});
|
|
6560
|
+
CommandRegistry.register("xargs", {
|
|
6561
|
+
kind: "stream",
|
|
6562
|
+
handler: ({ step, fs, input, context }) => {
|
|
6563
|
+
return (async function* () {
|
|
6564
|
+
const result = await runXargsCommand({
|
|
6565
|
+
context,
|
|
6566
|
+
fs,
|
|
6567
|
+
input,
|
|
6568
|
+
inputPath: await resolveRedirectPath(step.cmd, step.redirections, "input", fs, context),
|
|
6569
|
+
parsed: step.args
|
|
6570
|
+
});
|
|
6571
|
+
context.status = result.exitCode;
|
|
6572
|
+
context.stderr.appendLines(result.stderr);
|
|
5798
6573
|
for (const text of result.stdout) yield {
|
|
5799
6574
|
kind: "line",
|
|
5800
6575
|
text
|
|
5801
6576
|
};
|
|
5802
6577
|
})();
|
|
5803
|
-
|
|
5804
|
-
|
|
6578
|
+
}
|
|
6579
|
+
});
|
|
6580
|
+
CommandRegistry.register("head", {
|
|
6581
|
+
kind: "stream",
|
|
6582
|
+
handler: ({ step, fs, input, context }) => {
|
|
6583
|
+
return (async function* () {
|
|
5805
6584
|
const inputPath = await resolveRedirectPath(step.cmd, step.redirections, "input", fs, context);
|
|
5806
6585
|
const filePaths = withInputRedirect(resolvePathsFromCwd(context.cwd, await evaluateExpandedPathWords("head", step.args.files, fs, context)), inputPath);
|
|
5807
6586
|
if (filePaths.length > 0) {
|
|
@@ -5812,7 +6591,12 @@ function executeStreamStep(step, fs, input, context, resolvedOutputRedirectPath)
|
|
|
5812
6591
|
if (input) yield* headLines(step.args.n)(toFormattedLineStream(input));
|
|
5813
6592
|
context.status = 0;
|
|
5814
6593
|
})();
|
|
5815
|
-
|
|
6594
|
+
}
|
|
6595
|
+
});
|
|
6596
|
+
CommandRegistry.register("ls", {
|
|
6597
|
+
kind: "stream",
|
|
6598
|
+
handler: ({ step, fs, context }) => {
|
|
6599
|
+
return (async function* () {
|
|
5816
6600
|
const paths = await evaluateExpandedPathWords("ls", step.args.paths, fs, context);
|
|
5817
6601
|
for (const inputPath of paths) {
|
|
5818
6602
|
const resolvedPath = resolveLsPath(inputPath, context.cwd);
|
|
@@ -5830,7 +6614,12 @@ function executeStreamStep(step, fs, input, context, resolvedOutputRedirectPath)
|
|
|
5830
6614
|
}
|
|
5831
6615
|
context.status = 0;
|
|
5832
6616
|
})();
|
|
5833
|
-
|
|
6617
|
+
}
|
|
6618
|
+
});
|
|
6619
|
+
CommandRegistry.register("tail", {
|
|
6620
|
+
kind: "stream",
|
|
6621
|
+
handler: ({ step, fs, input, context }) => {
|
|
6622
|
+
return (async function* () {
|
|
5834
6623
|
const inputPath = await resolveRedirectPath(step.cmd, step.redirections, "input", fs, context);
|
|
5835
6624
|
const filePaths = withInputRedirect(resolvePathsFromCwd(context.cwd, await evaluateExpandedPathWords("tail", step.args.files, fs, context)), inputPath);
|
|
5836
6625
|
if (filePaths.length > 0) {
|
|
@@ -5841,118 +6630,540 @@ function executeStreamStep(step, fs, input, context, resolvedOutputRedirectPath)
|
|
|
5841
6630
|
if (input) yield* tail(step.args.n)(toFormattedLineStream(input));
|
|
5842
6631
|
context.status = 0;
|
|
5843
6632
|
})();
|
|
5844
|
-
|
|
6633
|
+
}
|
|
6634
|
+
});
|
|
6635
|
+
CommandRegistry.register("pwd", {
|
|
6636
|
+
kind: "stream",
|
|
6637
|
+
handler: ({ context }) => {
|
|
6638
|
+
return (async function* () {
|
|
5845
6639
|
yield* pwd(context.cwd);
|
|
5846
6640
|
context.status = 0;
|
|
5847
6641
|
})();
|
|
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
6642
|
}
|
|
6643
|
+
});
|
|
6644
|
+
CommandRegistry.register("echo", {
|
|
6645
|
+
kind: "stream",
|
|
6646
|
+
handler: ({ step, fs, input, context }) => {
|
|
6647
|
+
return echo(createBuiltinRuntime(fs, context, input), step.args);
|
|
6648
|
+
}
|
|
6649
|
+
});
|
|
6650
|
+
CommandRegistry.register("set", {
|
|
6651
|
+
kind: "stream",
|
|
6652
|
+
handler: ({ step, fs, input, context }) => {
|
|
6653
|
+
return set(createBuiltinRuntime(fs, context, input), step.args);
|
|
6654
|
+
}
|
|
6655
|
+
});
|
|
6656
|
+
CommandRegistry.register("test", {
|
|
6657
|
+
kind: "stream",
|
|
6658
|
+
handler: ({ step, fs, input, context }) => {
|
|
6659
|
+
return test(createBuiltinRuntime(fs, context, input), step.args);
|
|
6660
|
+
}
|
|
6661
|
+
});
|
|
6662
|
+
CommandRegistry.register("read", {
|
|
6663
|
+
kind: "stream",
|
|
6664
|
+
handler: ({ step, fs, input, context }) => {
|
|
6665
|
+
return (async function* () {
|
|
6666
|
+
const resolvedInput = await resolveInputRedirect(step.cmd, step.redirections, fs, context);
|
|
6667
|
+
if (resolvedInput.closed) {
|
|
6668
|
+
context.stderr.append("read: stdin is closed");
|
|
6669
|
+
context.status = 1;
|
|
6670
|
+
return;
|
|
6671
|
+
}
|
|
6672
|
+
yield* read(createBuiltinRuntime(fs, context, resolvedInput.path ? lineRecordsFromPath(fs, resolvedInput.path) : input), step.args);
|
|
6673
|
+
})();
|
|
6674
|
+
}
|
|
6675
|
+
});
|
|
6676
|
+
CommandRegistry.register("string", {
|
|
6677
|
+
kind: "stream",
|
|
6678
|
+
handler: ({ step, fs, input, context }) => {
|
|
6679
|
+
return string(createBuiltinRuntime(fs, context, input), step.args);
|
|
6680
|
+
}
|
|
6681
|
+
});
|
|
6682
|
+
CommandRegistry.register("cd", {
|
|
6683
|
+
kind: "effect",
|
|
6684
|
+
handler: async ({ step, fs, context }) => {
|
|
6685
|
+
await cd(createBuiltinRuntime(fs, context, null), step.args);
|
|
6686
|
+
}
|
|
6687
|
+
});
|
|
6688
|
+
CommandRegistry.register("cp", {
|
|
6689
|
+
kind: "effect",
|
|
6690
|
+
handler: async ({ step, fs, context }) => {
|
|
6691
|
+
const srcPaths = resolvePathsFromCwd(context.cwd, await evaluateExpandedPathWords("cp", step.args.srcs, fs, context));
|
|
6692
|
+
const destinationPath = resolvePathsFromCwd(context.cwd, [await evaluateExpandedSinglePath("cp", "destination must expand to exactly 1 path", step.args.dest, fs, context)]).at(0);
|
|
6693
|
+
if (destinationPath === void 0) throw new Error("cp: destination missing after expansion");
|
|
6694
|
+
await cp(fs)({
|
|
6695
|
+
srcs: srcPaths,
|
|
6696
|
+
dest: destinationPath,
|
|
6697
|
+
force: step.args.force,
|
|
6698
|
+
interactive: step.args.interactive,
|
|
6699
|
+
recursive: step.args.recursive
|
|
6700
|
+
});
|
|
6701
|
+
context.status = 0;
|
|
6702
|
+
}
|
|
6703
|
+
});
|
|
6704
|
+
CommandRegistry.register("mkdir", {
|
|
6705
|
+
kind: "effect",
|
|
6706
|
+
handler: async ({ step, fs, context }) => {
|
|
6707
|
+
const paths = resolvePathsFromCwd(context.cwd, await evaluateExpandedPathWords("mkdir", step.args.paths, fs, context));
|
|
6708
|
+
for (const path of paths) await mkdir(fs)({
|
|
6709
|
+
path,
|
|
6710
|
+
recursive: step.args.recursive
|
|
6711
|
+
});
|
|
6712
|
+
context.status = 0;
|
|
6713
|
+
}
|
|
6714
|
+
});
|
|
6715
|
+
CommandRegistry.register("mv", {
|
|
6716
|
+
kind: "effect",
|
|
6717
|
+
handler: async ({ step, fs, context }) => {
|
|
6718
|
+
const srcPaths = resolvePathsFromCwd(context.cwd, await evaluateExpandedPathWords("mv", step.args.srcs, fs, context));
|
|
6719
|
+
const destinationPath = resolvePathsFromCwd(context.cwd, [await evaluateExpandedSinglePath("mv", "destination must expand to exactly 1 path", step.args.dest, fs, context)]).at(0);
|
|
6720
|
+
if (destinationPath === void 0) throw new Error("mv: destination missing after expansion");
|
|
6721
|
+
await mv(fs)({
|
|
6722
|
+
srcs: srcPaths,
|
|
6723
|
+
dest: destinationPath,
|
|
6724
|
+
force: step.args.force,
|
|
6725
|
+
interactive: step.args.interactive
|
|
6726
|
+
});
|
|
6727
|
+
context.status = 0;
|
|
6728
|
+
}
|
|
6729
|
+
});
|
|
6730
|
+
CommandRegistry.register("rm", {
|
|
6731
|
+
kind: "effect",
|
|
6732
|
+
handler: async ({ step, fs, context }) => {
|
|
6733
|
+
const paths = resolvePathsFromCwd(context.cwd, await evaluateExpandedPathWords("rm", step.args.paths, fs, context));
|
|
6734
|
+
for (const path of paths) await rm(fs)({
|
|
6735
|
+
path,
|
|
6736
|
+
force: step.args.force,
|
|
6737
|
+
interactive: step.args.interactive,
|
|
6738
|
+
recursive: step.args.recursive
|
|
6739
|
+
});
|
|
6740
|
+
context.status = 0;
|
|
6741
|
+
}
|
|
6742
|
+
});
|
|
6743
|
+
CommandRegistry.register("touch", {
|
|
6744
|
+
kind: "effect",
|
|
6745
|
+
handler: async ({ step, fs, context }) => {
|
|
6746
|
+
const filePaths = resolvePathsFromCwd(context.cwd, await evaluateExpandedPathWords("touch", step.args.files, fs, context));
|
|
6747
|
+
await touch(fs)({
|
|
6748
|
+
files: filePaths,
|
|
6749
|
+
accessTimeOnly: step.args.accessTimeOnly,
|
|
6750
|
+
modificationTimeOnly: step.args.modificationTimeOnly
|
|
6751
|
+
});
|
|
6752
|
+
context.status = 0;
|
|
6753
|
+
}
|
|
6754
|
+
});
|
|
6755
|
+
//#endregion
|
|
6756
|
+
//#region src/execute/execute.ts
|
|
6757
|
+
var execute_exports = /* @__PURE__ */ __exportAll({ execute: () => execute });
|
|
6758
|
+
const ROOT_DIRECTORY = "/";
|
|
6759
|
+
const FD_TARGET_REGEX = /^&[0-9]+$/;
|
|
6760
|
+
async function* emptyStream() {}
|
|
6761
|
+
function isScriptIR(ir) {
|
|
6762
|
+
return "statements" in ir;
|
|
5858
6763
|
}
|
|
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
|
-
|
|
6764
|
+
function toScriptIR(pipeline) {
|
|
6765
|
+
return { statements: [{
|
|
6766
|
+
chainMode: "always",
|
|
6767
|
+
pipeline
|
|
6768
|
+
}] };
|
|
6769
|
+
}
|
|
6770
|
+
function shouldExecuteStatement(chainMode, previousStatus) {
|
|
6771
|
+
if (chainMode === "always") return true;
|
|
6772
|
+
if (chainMode === "and") return previousStatus === 0;
|
|
6773
|
+
return previousStatus !== 0;
|
|
6774
|
+
}
|
|
6775
|
+
function execute(ir, fs, context = { cwd: ROOT_DIRECTORY }) {
|
|
6776
|
+
const normalizedContext = normalizeContext(context);
|
|
6777
|
+
return executeScript(isScriptIR(ir) ? ir : toScriptIR(ir), fs, normalizedContext);
|
|
6778
|
+
}
|
|
6779
|
+
function isPipelineSink(pipeline) {
|
|
6780
|
+
if (hasMidPipelineStdoutBypass(pipeline.steps)) return false;
|
|
6781
|
+
const finalStep = pipeline.steps.at(-1);
|
|
6782
|
+
if (!finalStep) return false;
|
|
6783
|
+
return CommandRegistry.isEffectStep(finalStep) || hasRedirect(finalStep.redirections, "output");
|
|
6784
|
+
}
|
|
6785
|
+
function hasMidPipelineStdoutBypass(steps) {
|
|
6786
|
+
for (const [index, step] of steps.entries()) {
|
|
6787
|
+
if (index === steps.length - 1) continue;
|
|
6788
|
+
const redirections = step.redirections ?? [];
|
|
6789
|
+
if (!redirections.some((redirection) => {
|
|
6790
|
+
return redirection.kind === "output" && getSourceFd(redirection) !== 1 && getRedirectionMode(redirection) === "pipe";
|
|
6791
|
+
})) continue;
|
|
6792
|
+
if (!redirections.some((redirection) => {
|
|
6793
|
+
return redirection.kind === "output" && getSourceFd(redirection) === 1 && getRedirectionMode(redirection) === "pipe";
|
|
6794
|
+
})) return true;
|
|
6795
|
+
}
|
|
6796
|
+
return false;
|
|
6797
|
+
}
|
|
6798
|
+
function executeScript(script, fs, context) {
|
|
6799
|
+
if (script.statements.length === 0) return {
|
|
6800
|
+
kind: "stream",
|
|
6801
|
+
value: emptyStream()
|
|
6802
|
+
};
|
|
6803
|
+
if (script.statements.every((statement) => isPipelineSink(statement.pipeline))) return {
|
|
6804
|
+
kind: "sink",
|
|
6805
|
+
value: runScriptToCompletion(script, fs, context)
|
|
6806
|
+
};
|
|
6807
|
+
return {
|
|
6808
|
+
kind: "stream",
|
|
6809
|
+
value: runScriptToStream(script, fs, context)
|
|
6810
|
+
};
|
|
6811
|
+
}
|
|
6812
|
+
async function runScriptToCompletion(script, fs, context) {
|
|
6813
|
+
for (const statement of script.statements) {
|
|
6814
|
+
if (!shouldExecuteStatement(statement.chainMode, context.status)) continue;
|
|
6815
|
+
try {
|
|
6816
|
+
await drainStream(runPipeline(statement.pipeline, fs, context));
|
|
6817
|
+
} catch (error) {
|
|
6818
|
+
context.status = 1;
|
|
6819
|
+
throw error;
|
|
5911
6820
|
}
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
|
|
5915
|
-
|
|
5916
|
-
|
|
5917
|
-
|
|
5918
|
-
|
|
5919
|
-
|
|
5920
|
-
|
|
6821
|
+
}
|
|
6822
|
+
}
|
|
6823
|
+
async function* runScriptToStream(script, fs, context) {
|
|
6824
|
+
for (const statement of script.statements) {
|
|
6825
|
+
if (!shouldExecuteStatement(statement.chainMode, context.status)) continue;
|
|
6826
|
+
try {
|
|
6827
|
+
yield* runPipeline(statement.pipeline, fs, context);
|
|
6828
|
+
} catch (error) {
|
|
6829
|
+
context.status = 1;
|
|
6830
|
+
throw error;
|
|
5921
6831
|
}
|
|
5922
|
-
|
|
5923
|
-
|
|
5924
|
-
|
|
6832
|
+
}
|
|
6833
|
+
}
|
|
6834
|
+
async function drainStream(stream) {
|
|
6835
|
+
for await (const _record of stream);
|
|
6836
|
+
}
|
|
6837
|
+
async function* runPipeline(pipeline, fs, context) {
|
|
6838
|
+
if (pipeline.steps.length === 0) return;
|
|
6839
|
+
let pipeInputRecords = null;
|
|
6840
|
+
let preservedPipelineStatus = null;
|
|
6841
|
+
for (const [index, step] of pipeline.steps.entries()) {
|
|
6842
|
+
const isLastStep = index === pipeline.steps.length - 1;
|
|
6843
|
+
if (CommandRegistry.isEffectStep(step) && !isLastStep) throw new Error(`Unsupported pipeline: "${step.cmd}" must be the final command`);
|
|
6844
|
+
const plan = await resolveRoutingPlan(step, fs, context, isLastStep);
|
|
6845
|
+
const shouldPreserveStatus = shouldPreserveProducerStatus(plan, !isLastStep);
|
|
6846
|
+
if (!await preflightNoclobber(plan, fs)) {
|
|
6847
|
+
context.status = 1;
|
|
6848
|
+
context.stderr.append(`${step.cmd}: cannot overwrite existing file`);
|
|
6849
|
+
return;
|
|
5925
6850
|
}
|
|
6851
|
+
const executed = CommandRegistry.isEffectStep(step) ? await executeEffectStep({
|
|
6852
|
+
context,
|
|
6853
|
+
fs,
|
|
6854
|
+
plan,
|
|
6855
|
+
shouldPreserveStatus,
|
|
6856
|
+
step
|
|
6857
|
+
}) : await executeStreamStep({
|
|
6858
|
+
context,
|
|
6859
|
+
fs,
|
|
6860
|
+
hasNextStep: !isLastStep,
|
|
6861
|
+
inputRecords: pipeInputRecords,
|
|
6862
|
+
plan,
|
|
6863
|
+
shouldPreserveStatus,
|
|
6864
|
+
step
|
|
6865
|
+
});
|
|
6866
|
+
if (executed.preservedStatus !== null) preservedPipelineStatus = executed.preservedStatus;
|
|
6867
|
+
if (executed.shellRecords.length > 0) yield* recordsToStream(executed.shellRecords);
|
|
6868
|
+
pipeInputRecords = executed.pipeRecords;
|
|
5926
6869
|
}
|
|
6870
|
+
if (preservedPipelineStatus !== null) context.status = preservedPipelineStatus;
|
|
5927
6871
|
}
|
|
5928
|
-
function
|
|
5929
|
-
|
|
6872
|
+
async function executeEffectStep(params) {
|
|
6873
|
+
const { context, fs, plan, shouldPreserveStatus, step } = params;
|
|
6874
|
+
const childContext = createChildContext(context);
|
|
6875
|
+
await CommandRegistry.executeStep({
|
|
6876
|
+
step,
|
|
5930
6877
|
fs,
|
|
5931
|
-
context
|
|
5932
|
-
|
|
6878
|
+
context: childContext
|
|
6879
|
+
});
|
|
6880
|
+
propagateChildContext(childContext, context);
|
|
6881
|
+
return {
|
|
6882
|
+
...await routeStepOutput({
|
|
6883
|
+
context,
|
|
6884
|
+
fs,
|
|
6885
|
+
hasNextStep: false,
|
|
6886
|
+
plan,
|
|
6887
|
+
stderrLines: childContext.stderr.snapshot(),
|
|
6888
|
+
stdoutRecords: []
|
|
6889
|
+
}),
|
|
6890
|
+
preservedStatus: shouldPreserveStatus ? childContext.status : null
|
|
5933
6891
|
};
|
|
5934
6892
|
}
|
|
5935
|
-
function
|
|
5936
|
-
|
|
5937
|
-
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
|
|
5945
|
-
|
|
6893
|
+
async function executeStreamStep(params) {
|
|
6894
|
+
const { context, fs, hasNextStep, inputRecords, plan, shouldPreserveStatus, step } = params;
|
|
6895
|
+
const childContext = createChildContext(context);
|
|
6896
|
+
const resolvedOutputRedirectPath = step.cmd === "grep" && plan.fd1.kind === "file" ? plan.fd1.path : void 0;
|
|
6897
|
+
const stdoutRecords = await collectRecords(CommandRegistry.executeStep({
|
|
6898
|
+
step,
|
|
6899
|
+
fs,
|
|
6900
|
+
input: inputRecords === null ? null : recordsToStream(inputRecords),
|
|
6901
|
+
context: childContext,
|
|
6902
|
+
resolvedOutputRedirectPath
|
|
6903
|
+
}));
|
|
6904
|
+
const stderrLines = childContext.stderr.snapshot();
|
|
6905
|
+
propagateChildContext(childContext, context);
|
|
6906
|
+
return {
|
|
6907
|
+
...await routeStepOutput({
|
|
6908
|
+
context,
|
|
6909
|
+
fs,
|
|
6910
|
+
hasNextStep,
|
|
6911
|
+
plan,
|
|
6912
|
+
stderrLines,
|
|
6913
|
+
stdoutRecords
|
|
6914
|
+
}),
|
|
6915
|
+
preservedStatus: shouldPreserveStatus ? childContext.status : null
|
|
6916
|
+
};
|
|
5946
6917
|
}
|
|
5947
6918
|
function normalizeContext(context) {
|
|
5948
6919
|
context.cwd = normalizeCwd(context.cwd);
|
|
5949
6920
|
context.status ??= 0;
|
|
5950
|
-
context.stderr ??=
|
|
6921
|
+
context.stderr ??= new BufferedOutputStream();
|
|
5951
6922
|
context.globalVars ??= /* @__PURE__ */ new Map();
|
|
5952
6923
|
context.localVars ??= /* @__PURE__ */ new Map();
|
|
5953
6924
|
return context;
|
|
5954
6925
|
}
|
|
6926
|
+
function createChildContext(context) {
|
|
6927
|
+
return {
|
|
6928
|
+
cwd: context.cwd,
|
|
6929
|
+
globalVars: context.globalVars,
|
|
6930
|
+
localVars: context.localVars,
|
|
6931
|
+
status: context.status,
|
|
6932
|
+
stderr: new BufferedOutputStream()
|
|
6933
|
+
};
|
|
6934
|
+
}
|
|
6935
|
+
function propagateChildContext(child, parent) {
|
|
6936
|
+
parent.cwd = child.cwd;
|
|
6937
|
+
parent.status = child.status;
|
|
6938
|
+
}
|
|
6939
|
+
async function collectRecords(stream) {
|
|
6940
|
+
const records = [];
|
|
6941
|
+
for await (const record of stream) records.push(record);
|
|
6942
|
+
return records;
|
|
6943
|
+
}
|
|
6944
|
+
function recordsToStream(records) {
|
|
6945
|
+
return (async function* () {
|
|
6946
|
+
for (const record of records) yield record;
|
|
6947
|
+
})();
|
|
6948
|
+
}
|
|
6949
|
+
function cloneDestination(destination) {
|
|
6950
|
+
if (destination.kind !== "file") return destination;
|
|
6951
|
+
return { ...destination };
|
|
6952
|
+
}
|
|
6953
|
+
function getSourceFd(redirection) {
|
|
6954
|
+
return redirection.sourceFd ?? 1;
|
|
6955
|
+
}
|
|
6956
|
+
function getTargetFd(redirection) {
|
|
6957
|
+
if (redirection.targetFd !== void 0 && redirection.targetFd !== null) return redirection.targetFd;
|
|
6958
|
+
const targetText = expandedWordToString(redirection.target);
|
|
6959
|
+
if (!FD_TARGET_REGEX.test(targetText)) return null;
|
|
6960
|
+
return Number(targetText.slice(1));
|
|
6961
|
+
}
|
|
6962
|
+
async function resolveFileDestination(command, redirection, fs, context) {
|
|
6963
|
+
const targetPath = await evaluateExpandedSinglePath(command, "redirection target must expand to exactly 1 path", redirection.target, fs, context);
|
|
6964
|
+
return {
|
|
6965
|
+
kind: "file",
|
|
6966
|
+
append: redirection.append ?? false,
|
|
6967
|
+
noclobber: redirection.noclobber ?? false,
|
|
6968
|
+
path: resolvePathFromCwd(context.cwd, targetPath)
|
|
6969
|
+
};
|
|
6970
|
+
}
|
|
6971
|
+
function destinationForFd(routing, fd, _isLastStep) {
|
|
6972
|
+
if (fd === 1) return routing.fd1;
|
|
6973
|
+
if (fd === 2) return routing.fd2;
|
|
6974
|
+
return { kind: "closed" };
|
|
6975
|
+
}
|
|
6976
|
+
function defaultStdoutDestination(isLastStep, hasNonStdoutPipeRedirect, hasStdoutPipeRedirect) {
|
|
6977
|
+
if (isLastStep) return { kind: "shellStdout" };
|
|
6978
|
+
if (hasNonStdoutPipeRedirect && !hasStdoutPipeRedirect) return { kind: "shellStdout" };
|
|
6979
|
+
return { kind: "pipe" };
|
|
6980
|
+
}
|
|
6981
|
+
async function resolveOutputRedirectionDestination(params) {
|
|
6982
|
+
const { context, fs, isLastStep, redirection, routing, step } = params;
|
|
6983
|
+
if (redirection.kind !== "output") return null;
|
|
6984
|
+
const sourceFd = getSourceFd(redirection);
|
|
6985
|
+
if (sourceFd !== 1 && sourceFd !== 2) return null;
|
|
6986
|
+
const mode = getRedirectionMode(redirection);
|
|
6987
|
+
let destination;
|
|
6988
|
+
if (mode === "close") destination = { kind: "closed" };
|
|
6989
|
+
else if (mode === "pipe") destination = { kind: "pipe" };
|
|
6990
|
+
else if (mode === "fd") {
|
|
6991
|
+
const targetFd = getTargetFd(redirection);
|
|
6992
|
+
if (targetFd === null) throw new Error(`${step.cmd}: invalid file descriptor duplication target`);
|
|
6993
|
+
destination = cloneDestination(destinationForFd(routing, targetFd, isLastStep));
|
|
6994
|
+
} else if (mode === "file") destination = await resolveFileDestination(step.cmd, redirection, fs, context);
|
|
6995
|
+
else destination = sourceFd === 1 ? routing.fd1 : routing.fd2;
|
|
6996
|
+
return {
|
|
6997
|
+
destination,
|
|
6998
|
+
sourceFd
|
|
6999
|
+
};
|
|
7000
|
+
}
|
|
7001
|
+
async function resolveRoutingPlan(step, fs, context, isLastStep) {
|
|
7002
|
+
const stepRedirections = step.redirections ?? [];
|
|
7003
|
+
const routing = {
|
|
7004
|
+
fd1: defaultStdoutDestination(isLastStep, stepRedirections.some((redirection) => {
|
|
7005
|
+
return redirection.kind === "output" && getSourceFd(redirection) !== 1 && getRedirectionMode(redirection) === "pipe";
|
|
7006
|
+
}), stepRedirections.some((redirection) => {
|
|
7007
|
+
return redirection.kind === "output" && getSourceFd(redirection) === 1 && getRedirectionMode(redirection) === "pipe";
|
|
7008
|
+
})),
|
|
7009
|
+
fd2: { kind: "shellStderr" }
|
|
7010
|
+
};
|
|
7011
|
+
for (const redirection of stepRedirections) {
|
|
7012
|
+
const resolved = await resolveOutputRedirectionDestination({
|
|
7013
|
+
context,
|
|
7014
|
+
fs,
|
|
7015
|
+
isLastStep,
|
|
7016
|
+
redirection,
|
|
7017
|
+
routing,
|
|
7018
|
+
step
|
|
7019
|
+
});
|
|
7020
|
+
if (!resolved) continue;
|
|
7021
|
+
if (resolved.sourceFd === 1) {
|
|
7022
|
+
routing.fd1 = resolved.destination;
|
|
7023
|
+
continue;
|
|
7024
|
+
}
|
|
7025
|
+
routing.fd2 = resolved.destination;
|
|
7026
|
+
}
|
|
7027
|
+
return routing;
|
|
7028
|
+
}
|
|
7029
|
+
async function preflightNoclobber(plan, fs) {
|
|
7030
|
+
const destinations = [plan.fd1, plan.fd2];
|
|
7031
|
+
const checkedPaths = /* @__PURE__ */ new Set();
|
|
7032
|
+
for (const destination of destinations) {
|
|
7033
|
+
if (destination.kind !== "file" || !destination.noclobber) continue;
|
|
7034
|
+
if (checkedPaths.has(destination.path)) continue;
|
|
7035
|
+
checkedPaths.add(destination.path);
|
|
7036
|
+
if (!await ensureNoclobberWritable(fs, destination.path)) return false;
|
|
7037
|
+
}
|
|
7038
|
+
return true;
|
|
7039
|
+
}
|
|
7040
|
+
function stderrLinesToRecords(lines) {
|
|
7041
|
+
return lines.map((text) => ({
|
|
7042
|
+
kind: "line",
|
|
7043
|
+
text
|
|
7044
|
+
}));
|
|
7045
|
+
}
|
|
7046
|
+
function recordsToText(records) {
|
|
7047
|
+
return records.map((record) => formatStdoutRecord(record)).join("\n");
|
|
7048
|
+
}
|
|
7049
|
+
function linesToText(lines) {
|
|
7050
|
+
return lines.join("\n");
|
|
7051
|
+
}
|
|
7052
|
+
function mergeChannelText(stdoutText, stderrText) {
|
|
7053
|
+
if (stdoutText === "") return stderrText;
|
|
7054
|
+
if (stderrText === "") return stdoutText;
|
|
7055
|
+
return `${stdoutText}\n${stderrText}`;
|
|
7056
|
+
}
|
|
7057
|
+
function toErrorMessage(error) {
|
|
7058
|
+
if (error instanceof Error) return error.message;
|
|
7059
|
+
return String(error);
|
|
7060
|
+
}
|
|
7061
|
+
async function writeToFileOrReport(params) {
|
|
7062
|
+
const { append, content, context, fs, path } = params;
|
|
7063
|
+
try {
|
|
7064
|
+
await writeTextToFile(fs, path, content, { append });
|
|
7065
|
+
} catch (error) {
|
|
7066
|
+
context.status = 1;
|
|
7067
|
+
context.stderr.append(toErrorMessage(error));
|
|
7068
|
+
}
|
|
7069
|
+
}
|
|
7070
|
+
function shouldPipe(destination, hasNextStep) {
|
|
7071
|
+
return destination.kind === "pipe" && hasNextStep;
|
|
7072
|
+
}
|
|
7073
|
+
function shouldPreserveProducerStatus(plan, hasNextStep) {
|
|
7074
|
+
return hasNextStep && plan.fd2.kind === "pipe" && plan.fd1.kind !== "pipe";
|
|
7075
|
+
}
|
|
7076
|
+
async function routeStepOutput(params) {
|
|
7077
|
+
const { context, fs, hasNextStep, plan, stderrLines, stdoutRecords } = params;
|
|
7078
|
+
const pipeRecords = [];
|
|
7079
|
+
const shellRecords = [];
|
|
7080
|
+
const stdoutDestination = plan.fd1;
|
|
7081
|
+
const stderrDestination = plan.fd2;
|
|
7082
|
+
const stdoutAsText = recordsToText(stdoutRecords);
|
|
7083
|
+
const stderrAsText = linesToText(stderrLines);
|
|
7084
|
+
if (stdoutDestination.kind === "file" && stderrDestination.kind === "file" && stdoutDestination.path === stderrDestination.path) {
|
|
7085
|
+
const mergedText = mergeChannelText(stdoutAsText, stderrAsText);
|
|
7086
|
+
await writeToFileOrReport({
|
|
7087
|
+
append: stdoutDestination.append && stderrDestination.append,
|
|
7088
|
+
content: mergedText,
|
|
7089
|
+
context,
|
|
7090
|
+
fs,
|
|
7091
|
+
path: stdoutDestination.path
|
|
7092
|
+
});
|
|
7093
|
+
} else {
|
|
7094
|
+
await routeStdout(stdoutRecords, stdoutAsText, stdoutDestination, hasNextStep, pipeRecords, shellRecords, context, fs);
|
|
7095
|
+
await routeStderr(stderrLines, stderrAsText, stderrDestination, hasNextStep, pipeRecords, shellRecords, context, fs);
|
|
7096
|
+
}
|
|
7097
|
+
return {
|
|
7098
|
+
pipeRecords,
|
|
7099
|
+
shellRecords
|
|
7100
|
+
};
|
|
7101
|
+
}
|
|
7102
|
+
async function routeStdout(stdoutRecords, stdoutText, destination, hasNextStep, pipeRecords, shellRecords, context, fs) {
|
|
7103
|
+
if (stdoutRecords.length === 0 && destination.kind !== "file") return;
|
|
7104
|
+
if (shouldPipe(destination, hasNextStep)) {
|
|
7105
|
+
pipeRecords.push(...stdoutRecords);
|
|
7106
|
+
return;
|
|
7107
|
+
}
|
|
7108
|
+
switch (destination.kind) {
|
|
7109
|
+
case "shellStdout":
|
|
7110
|
+
shellRecords.push(...stdoutRecords);
|
|
7111
|
+
return;
|
|
7112
|
+
case "pipe":
|
|
7113
|
+
shellRecords.push(...stdoutRecords);
|
|
7114
|
+
return;
|
|
7115
|
+
case "shellStderr":
|
|
7116
|
+
if (stdoutText !== "") context.stderr.append(stdoutText);
|
|
7117
|
+
return;
|
|
7118
|
+
case "file":
|
|
7119
|
+
await writeToFileOrReport({
|
|
7120
|
+
append: destination.append,
|
|
7121
|
+
content: stdoutText,
|
|
7122
|
+
context,
|
|
7123
|
+
fs,
|
|
7124
|
+
path: destination.path
|
|
7125
|
+
});
|
|
7126
|
+
return;
|
|
7127
|
+
case "closed": return;
|
|
7128
|
+
default: {
|
|
7129
|
+
const _exhaustive = destination;
|
|
7130
|
+
throw new Error(`Unknown stdout destination: ${_exhaustive}`);
|
|
7131
|
+
}
|
|
7132
|
+
}
|
|
7133
|
+
}
|
|
7134
|
+
async function routeStderr(stderrLines, stderrText, destination, hasNextStep, pipeRecords, shellRecords, context, fs) {
|
|
7135
|
+
if (stderrLines.length === 0 && destination.kind !== "file") return;
|
|
7136
|
+
if (shouldPipe(destination, hasNextStep)) {
|
|
7137
|
+
pipeRecords.push(...stderrLinesToRecords(stderrLines));
|
|
7138
|
+
return;
|
|
7139
|
+
}
|
|
7140
|
+
switch (destination.kind) {
|
|
7141
|
+
case "shellStdout":
|
|
7142
|
+
shellRecords.push(...stderrLinesToRecords(stderrLines));
|
|
7143
|
+
return;
|
|
7144
|
+
case "pipe":
|
|
7145
|
+
shellRecords.push(...stderrLinesToRecords(stderrLines));
|
|
7146
|
+
return;
|
|
7147
|
+
case "shellStderr":
|
|
7148
|
+
if (stderrLines.length > 0) context.stderr.appendLines(stderrLines);
|
|
7149
|
+
return;
|
|
7150
|
+
case "file":
|
|
7151
|
+
await writeToFileOrReport({
|
|
7152
|
+
append: destination.append,
|
|
7153
|
+
content: stderrText,
|
|
7154
|
+
context,
|
|
7155
|
+
fs,
|
|
7156
|
+
path: destination.path
|
|
7157
|
+
});
|
|
7158
|
+
return;
|
|
7159
|
+
case "closed": return;
|
|
7160
|
+
default: {
|
|
7161
|
+
const _exhaustive = destination;
|
|
7162
|
+
throw new Error(`Unknown stderr destination: ${_exhaustive}`);
|
|
7163
|
+
}
|
|
7164
|
+
}
|
|
7165
|
+
}
|
|
5955
7166
|
//#endregion
|
|
5956
|
-
export {
|
|
7167
|
+
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
7168
|
|
|
5958
|
-
//# sourceMappingURL=execute-
|
|
7169
|
+
//# sourceMappingURL=execute-D1oWUkmO.mjs.map
|