shfs 0.3.0 → 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",
|
|
@@ -791,8 +801,12 @@ function compileFind(command) {
|
|
|
791
801
|
function parseFindArgs(argv) {
|
|
792
802
|
const state = {
|
|
793
803
|
action: { ...DEFAULT_ACTION },
|
|
804
|
+
currentBranch: [],
|
|
805
|
+
currentSideAllowsEmptyBranch: false,
|
|
794
806
|
diagnostics: [],
|
|
795
|
-
|
|
807
|
+
lastOrTokenIndex: null,
|
|
808
|
+
predicateBranches: [],
|
|
809
|
+
sawOr: false,
|
|
796
810
|
traversal: { ...DEFAULT_TRAVERSAL }
|
|
797
811
|
};
|
|
798
812
|
const predicateStartIndex = findPredicateStartIndex(argv);
|
|
@@ -807,7 +821,7 @@ function parseFindArgs(argv) {
|
|
|
807
821
|
return {
|
|
808
822
|
action: state.action,
|
|
809
823
|
diagnostics: state.diagnostics,
|
|
810
|
-
|
|
824
|
+
predicateBranches: finalizePredicateBranches(state),
|
|
811
825
|
startPaths,
|
|
812
826
|
traversal: state.traversal,
|
|
813
827
|
usageError: state.diagnostics.length > 0
|
|
@@ -822,20 +836,37 @@ function createDiagnostic$1(code, token, tokenIndex, message) {
|
|
|
822
836
|
function createMissingValueDiagnostic(token, tokenIndex) {
|
|
823
837
|
return createDiagnostic$1("missing-value", token, tokenIndex, `find: missing argument to ${token}`);
|
|
824
838
|
}
|
|
839
|
+
function createMissingExpressionDiagnostic(side, tokenIndex) {
|
|
840
|
+
return createDiagnostic$1("invalid-expression", "-o", tokenIndex, `find: -o is missing a ${side} predicate expression`);
|
|
841
|
+
}
|
|
825
842
|
function findPredicateStartIndex(argv) {
|
|
826
843
|
for (const [index, word] of argv.entries()) if (expandedWordToString(word).startsWith("-")) return index;
|
|
827
844
|
return argv.length;
|
|
828
845
|
}
|
|
829
846
|
function parseFindToken(argv, index, token, state) {
|
|
830
|
-
if (token
|
|
847
|
+
if (isPatternPredicateToken(token)) return parsePatternPredicate(argv, index, token, state);
|
|
831
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
|
+
}
|
|
860
|
+
if (token === "-o" || token === "-or") return parseOrPredicateSeparator(index, state);
|
|
832
861
|
if (token === "-maxdepth" || token === "-mindepth") return parseTraversalOption(argv, index, token, state);
|
|
833
862
|
if (token === "-depth") {
|
|
834
863
|
state.traversal.depth = true;
|
|
864
|
+
state.currentSideAllowsEmptyBranch = true;
|
|
835
865
|
return index + 1;
|
|
836
866
|
}
|
|
837
867
|
if (token === "-print") {
|
|
838
868
|
state.action.explicit = true;
|
|
869
|
+
state.currentSideAllowsEmptyBranch = true;
|
|
839
870
|
return index + 1;
|
|
840
871
|
}
|
|
841
872
|
if (token.startsWith("-")) {
|
|
@@ -845,16 +876,58 @@ function parseFindToken(argv, index, token, state) {
|
|
|
845
876
|
state.diagnostics.push(createDiagnostic$1("unexpected-operand", token, index, `find: unexpected argument: ${token}`));
|
|
846
877
|
return index + 1;
|
|
847
878
|
}
|
|
848
|
-
function
|
|
879
|
+
function isPatternPredicateToken(token) {
|
|
880
|
+
return PATTERN_PREDICATES.has(token);
|
|
881
|
+
}
|
|
882
|
+
function parseOrPredicateSeparator(index, state) {
|
|
883
|
+
state.sawOr = true;
|
|
884
|
+
state.lastOrTokenIndex = index;
|
|
885
|
+
if (state.currentBranch.length === 0 && !state.currentSideAllowsEmptyBranch) {
|
|
886
|
+
state.diagnostics.push(createMissingExpressionDiagnostic("left", index));
|
|
887
|
+
return index + 1;
|
|
888
|
+
}
|
|
889
|
+
state.predicateBranches.push(state.currentBranch);
|
|
890
|
+
state.currentBranch = [];
|
|
891
|
+
state.currentSideAllowsEmptyBranch = false;
|
|
892
|
+
return index + 1;
|
|
893
|
+
}
|
|
894
|
+
function parsePatternPredicate(argv, index, token, state) {
|
|
849
895
|
const valueWord = argv[index + 1];
|
|
850
896
|
if (!valueWord) {
|
|
851
897
|
state.diagnostics.push(createMissingValueDiagnostic(token, index));
|
|
852
898
|
return index + 1;
|
|
853
899
|
}
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
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
|
+
}
|
|
858
931
|
return index + 2;
|
|
859
932
|
}
|
|
860
933
|
function parseTypePredicate(argv, index, state) {
|
|
@@ -865,12 +938,23 @@ function parseTypePredicate(argv, index, state) {
|
|
|
865
938
|
}
|
|
866
939
|
const parsedType = parseFindTypeValue(valueWord, index + 1);
|
|
867
940
|
if ("diagnostic" in parsedType) state.diagnostics.push(parsedType.diagnostic);
|
|
868
|
-
else state.
|
|
941
|
+
else state.currentBranch.push({
|
|
869
942
|
kind: "type",
|
|
870
943
|
types: parsedType.types
|
|
871
944
|
});
|
|
872
945
|
return index + 2;
|
|
873
946
|
}
|
|
947
|
+
function finalizePredicateBranches(state) {
|
|
948
|
+
if (state.currentBranch.length > 0) state.predicateBranches.push(state.currentBranch);
|
|
949
|
+
else if (state.sawOr && state.lastOrTokenIndex !== null) {
|
|
950
|
+
if (state.currentSideAllowsEmptyBranch) {
|
|
951
|
+
state.predicateBranches.push(state.currentBranch);
|
|
952
|
+
return state.predicateBranches;
|
|
953
|
+
}
|
|
954
|
+
state.diagnostics.push(createMissingExpressionDiagnostic("right", state.lastOrTokenIndex));
|
|
955
|
+
}
|
|
956
|
+
return state.predicateBranches;
|
|
957
|
+
}
|
|
874
958
|
function parseTraversalOption(argv, index, token, state) {
|
|
875
959
|
const valueWord = argv[index + 1];
|
|
876
960
|
if (!valueWord) {
|
|
@@ -884,6 +968,7 @@ function parseTraversalOption(argv, index, token, state) {
|
|
|
884
968
|
}
|
|
885
969
|
if (token === "-maxdepth") state.traversal.maxdepth = parsedNumericValue.value;
|
|
886
970
|
else state.traversal.mindepth = parsedNumericValue.value;
|
|
971
|
+
state.currentSideAllowsEmptyBranch = true;
|
|
887
972
|
return index + 2;
|
|
888
973
|
}
|
|
889
974
|
function parseFindTypeValue(word, tokenIndex) {
|
|
@@ -948,7 +1033,9 @@ const parseGrepWords = createWordParser({
|
|
|
948
1033
|
long: "before-context",
|
|
949
1034
|
short: "B"
|
|
950
1035
|
}),
|
|
1036
|
+
binaryFiles: grepValueFlag({ long: "binary-files" }),
|
|
951
1037
|
binaryFile: grepValueFlag({ long: "binary-file" }),
|
|
1038
|
+
binaryWithoutMatch: grepBooleanFlag({ short: "I" }),
|
|
952
1039
|
byteOffset: grepBooleanFlag({
|
|
953
1040
|
long: "byte-offset",
|
|
954
1041
|
short: "b"
|
|
@@ -1085,7 +1172,7 @@ function parseGrepArgs(argv) {
|
|
|
1085
1172
|
applyModeOption(parsed, options);
|
|
1086
1173
|
applyFilenameMode(parsed, options);
|
|
1087
1174
|
applyFileListingMode(parsed, options);
|
|
1088
|
-
applyBinaryFileOption(parsed, argv, options);
|
|
1175
|
+
applyBinaryFileOption(parsed, argv, options, diagnostics);
|
|
1089
1176
|
applyDirectoriesOption(parsed, argv, options);
|
|
1090
1177
|
applyMaxCountOption(parsed, argv, options, diagnostics);
|
|
1091
1178
|
applyContextOptions(parsed, argv, options, diagnostics);
|
|
@@ -1194,8 +1281,58 @@ function applyFileListingMode(parsed, options) {
|
|
|
1194
1281
|
options.listFilesWithMatches = false;
|
|
1195
1282
|
}
|
|
1196
1283
|
}
|
|
1197
|
-
function applyBinaryFileOption(parsed, argv, options) {
|
|
1198
|
-
|
|
1284
|
+
function applyBinaryFileOption(parsed, argv, options, diagnostics) {
|
|
1285
|
+
const modeOccurrences = [];
|
|
1286
|
+
for (const occurrence of getValueOccurrences(parsed, argv, "binaryFile")) {
|
|
1287
|
+
const mode = parseBinaryMode(occurrence.value);
|
|
1288
|
+
if (mode === null) {
|
|
1289
|
+
diagnostics.push(makeDiagnostic("invalid-value", occurrence.token, occurrence.tokenIndex, `Invalid value for option ${splitLongOption(occurrence.token)}.`));
|
|
1290
|
+
continue;
|
|
1291
|
+
}
|
|
1292
|
+
modeOccurrences.push({
|
|
1293
|
+
mode,
|
|
1294
|
+
order: occurrence.order
|
|
1295
|
+
});
|
|
1296
|
+
}
|
|
1297
|
+
for (const occurrence of getValueOccurrences(parsed, argv, "binaryFiles")) {
|
|
1298
|
+
const mode = parseBinaryMode(occurrence.value);
|
|
1299
|
+
if (mode === null) {
|
|
1300
|
+
diagnostics.push(makeDiagnostic("invalid-value", occurrence.token, occurrence.tokenIndex, `Invalid value for option ${splitLongOption(occurrence.token)}.`));
|
|
1301
|
+
continue;
|
|
1302
|
+
}
|
|
1303
|
+
modeOccurrences.push({
|
|
1304
|
+
mode,
|
|
1305
|
+
order: occurrence.order
|
|
1306
|
+
});
|
|
1307
|
+
}
|
|
1308
|
+
for (const order of parsed.flagOccurrenceOrder.binaryWithoutMatch ?? []) modeOccurrences.push({
|
|
1309
|
+
mode: "without-match",
|
|
1310
|
+
order
|
|
1311
|
+
});
|
|
1312
|
+
for (const order of parsed.flagOccurrenceOrder.textMode ?? []) modeOccurrences.push({
|
|
1313
|
+
mode: "text",
|
|
1314
|
+
order
|
|
1315
|
+
});
|
|
1316
|
+
modeOccurrences.sort((a, b) => a.order - b.order);
|
|
1317
|
+
for (const occurrence of modeOccurrences) switch (occurrence.mode) {
|
|
1318
|
+
case "binary":
|
|
1319
|
+
options.binaryWithoutMatch = false;
|
|
1320
|
+
options.textMode = false;
|
|
1321
|
+
break;
|
|
1322
|
+
case "text":
|
|
1323
|
+
options.binaryWithoutMatch = false;
|
|
1324
|
+
options.textMode = true;
|
|
1325
|
+
break;
|
|
1326
|
+
case "without-match":
|
|
1327
|
+
options.binaryWithoutMatch = true;
|
|
1328
|
+
options.textMode = false;
|
|
1329
|
+
break;
|
|
1330
|
+
default: break;
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
function parseBinaryMode(value) {
|
|
1334
|
+
if (value === "binary" || value === "text" || value === "without-match") return value;
|
|
1335
|
+
return null;
|
|
1199
1336
|
}
|
|
1200
1337
|
function applyDirectoriesOption(parsed, argv, options) {
|
|
1201
1338
|
for (const occurrence of getValueOccurrences(parsed, argv, "directories")) options.directories = occurrence.value === "skip" ? "skip" : "read";
|
|
@@ -1724,6 +1861,139 @@ function compileTouch(cmd) {
|
|
|
1724
1861
|
}
|
|
1725
1862
|
};
|
|
1726
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
|
+
}
|
|
1727
1997
|
let CommandHandler;
|
|
1728
1998
|
(function(_CommandHandler) {
|
|
1729
1999
|
const handlers = {
|
|
@@ -1744,7 +2014,8 @@ let CommandHandler;
|
|
|
1744
2014
|
string: compileString,
|
|
1745
2015
|
tail: compileTail,
|
|
1746
2016
|
test: compileTest,
|
|
1747
|
-
touch: compileTouch
|
|
2017
|
+
touch: compileTouch,
|
|
2018
|
+
xargs: compileXargs
|
|
1748
2019
|
};
|
|
1749
2020
|
function get(name) {
|
|
1750
2021
|
const handler = handlers[name];
|
|
@@ -1834,6 +2105,12 @@ var ProgramCompiler = class {
|
|
|
1834
2105
|
compileRedirection(node) {
|
|
1835
2106
|
return {
|
|
1836
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,
|
|
1837
2114
|
target: this.expandWord(node.target)
|
|
1838
2115
|
};
|
|
1839
2116
|
}
|
|
@@ -1910,11 +2187,47 @@ var ProgramCompiler = class {
|
|
|
1910
2187
|
return program.statements.map((statement) => this.serializePipeline(statement.pipeline)).join("; ");
|
|
1911
2188
|
}
|
|
1912
2189
|
serializePipeline(pipeline) {
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
const
|
|
1916
|
-
|
|
1917
|
-
|
|
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)}`;
|
|
1918
2231
|
}
|
|
1919
2232
|
serializeWord(word) {
|
|
1920
2233
|
return word.parts.map((part) => this.serializeWordPart(part)).join("");
|
|
@@ -2839,11 +3152,23 @@ var CommandSubPart = class {
|
|
|
2839
3152
|
*/
|
|
2840
3153
|
var Redirection = class extends ASTNode {
|
|
2841
3154
|
redirectKind;
|
|
3155
|
+
mode;
|
|
3156
|
+
sourceFd;
|
|
3157
|
+
targetFd;
|
|
3158
|
+
append;
|
|
3159
|
+
noclobber;
|
|
3160
|
+
optional;
|
|
2842
3161
|
target;
|
|
2843
|
-
constructor(span, redirectKind, target) {
|
|
3162
|
+
constructor(span, redirectKind, target, options = {}) {
|
|
2844
3163
|
super(span);
|
|
2845
3164
|
this.redirectKind = redirectKind;
|
|
2846
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;
|
|
2847
3172
|
}
|
|
2848
3173
|
accept(visitor) {
|
|
2849
3174
|
return visitor.visitRedirection(this);
|
|
@@ -2856,6 +3181,7 @@ var Redirection = class extends ASTNode {
|
|
|
2856
3181
|
* - Simple commands (name + args)
|
|
2857
3182
|
* - Redirections (< > Phase 2)
|
|
2858
3183
|
*/
|
|
3184
|
+
const DIGITS_ONLY_REGEX = /^[0-9]+$/;
|
|
2859
3185
|
/**
|
|
2860
3186
|
* Parser for commands.
|
|
2861
3187
|
*
|
|
@@ -2904,40 +3230,118 @@ var CommandParser = class {
|
|
|
2904
3230
|
else break;
|
|
2905
3231
|
}
|
|
2906
3232
|
const endPos = this.parser.previousTokenPosition;
|
|
2907
|
-
|
|
3233
|
+
const span = new SourceSpan(startPos, endPos);
|
|
3234
|
+
const normalized = this.normalizeRedirectionPrefixes(args, redirections);
|
|
3235
|
+
return new SimpleCommand(span, name, normalized.args, normalized.redirections);
|
|
2908
3236
|
}
|
|
2909
3237
|
/**
|
|
2910
3238
|
* Parse a redirection if present.
|
|
2911
3239
|
*
|
|
2912
|
-
* Grammar:
|
|
3240
|
+
* Grammar (subset):
|
|
2913
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.
|
|
2914
3245
|
*/
|
|
2915
3246
|
parseRedirection() {
|
|
2916
3247
|
const token = this.parser.currentToken;
|
|
2917
|
-
if (token.kind === TokenKind.LESS)
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
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
|
+
});
|
|
2927
3277
|
}
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
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
|
+
};
|
|
2939
3312
|
}
|
|
2940
|
-
|
|
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);
|
|
2941
3345
|
}
|
|
2942
3346
|
/**
|
|
2943
3347
|
* Check if current token terminates a command.
|
|
@@ -2946,6 +3350,71 @@ var CommandParser = class {
|
|
|
2946
3350
|
const kind = this.parser.currentToken.kind;
|
|
2947
3351
|
return kind === TokenKind.PIPE || kind === TokenKind.SEMICOLON || kind === TokenKind.NEWLINE || kind === TokenKind.EOF;
|
|
2948
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
|
+
}
|
|
2949
3418
|
};
|
|
2950
3419
|
/**
|
|
2951
3420
|
* Error reporter for the Fish subset parser.
|
|
@@ -3117,8 +3586,16 @@ var StatementParser = class {
|
|
|
3117
3586
|
if (!firstCommand) return null;
|
|
3118
3587
|
const commands = [firstCommand];
|
|
3119
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
|
+
}
|
|
3120
3595
|
this.parser.advance();
|
|
3121
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 &|)");
|
|
3122
3599
|
const command = this.commandParser.parseCommand();
|
|
3123
3600
|
if (!command) {
|
|
3124
3601
|
this.parser.syntacticError("Expected command after |", "command");
|
|
@@ -3159,6 +3636,27 @@ var StatementParser = class {
|
|
|
3159
3636
|
isChainKeyword(spelling) {
|
|
3160
3637
|
return spelling === "and" || spelling === "or";
|
|
3161
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
|
+
}
|
|
3162
3660
|
};
|
|
3163
3661
|
/**
|
|
3164
3662
|
* Syntax error exception for the Fish subset parser.
|
|
@@ -3514,8 +4012,20 @@ function formatStdoutRecord(record) {
|
|
|
3514
4012
|
}
|
|
3515
4013
|
//#endregion
|
|
3516
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
|
+
};
|
|
3517
4027
|
function appendStderrLines(context, lines) {
|
|
3518
|
-
context.stderr.
|
|
4028
|
+
context.stderr.appendLines(lines);
|
|
3519
4029
|
}
|
|
3520
4030
|
function formatStderr(lines) {
|
|
3521
4031
|
return lines.join("\n");
|
|
@@ -3527,7 +4037,7 @@ var ShellDiagnosticError = class extends Error {
|
|
|
3527
4037
|
diagnostics;
|
|
3528
4038
|
exitCode;
|
|
3529
4039
|
constructor(diagnostics, exitCode = exitCodeForDiagnostics(diagnostics)) {
|
|
3530
|
-
super(diagnostics.map((diagnostic) => toErrorMessage(diagnostic)).join("\n"));
|
|
4040
|
+
super(diagnostics.map((diagnostic) => toErrorMessage$1(diagnostic)).join("\n"));
|
|
3531
4041
|
this.name = "ShellDiagnosticError";
|
|
3532
4042
|
this.diagnostics = diagnostics;
|
|
3533
4043
|
this.exitCode = exitCode;
|
|
@@ -3579,7 +4089,7 @@ function exitCodeForDiagnostic(diagnostic) {
|
|
|
3579
4089
|
if (diagnostic.phase === "compile" && diagnostic.location.command === "grep") return 2;
|
|
3580
4090
|
return 1;
|
|
3581
4091
|
}
|
|
3582
|
-
function toErrorMessage(diagnostic) {
|
|
4092
|
+
function toErrorMessage$1(diagnostic) {
|
|
3583
4093
|
const command = diagnostic.location.command;
|
|
3584
4094
|
const path = diagnostic.location.path;
|
|
3585
4095
|
if (command && path) return `${command}: ${path}: ${diagnostic.message}`;
|
|
@@ -3589,7 +4099,7 @@ function toErrorMessage(diagnostic) {
|
|
|
3589
4099
|
//#endregion
|
|
3590
4100
|
//#region src/execute/path.ts
|
|
3591
4101
|
const MULTIPLE_SLASH_REGEX$2 = /\/+/g;
|
|
3592
|
-
const ROOT_DIRECTORY$
|
|
4102
|
+
const ROOT_DIRECTORY$3 = "/";
|
|
3593
4103
|
const TRAILING_SLASH_REGEX$2 = /\/+$/;
|
|
3594
4104
|
const VARIABLE_REFERENCE_REGEX = /\$([A-Za-z_][A-Za-z0-9_]*)/g;
|
|
3595
4105
|
const NO_GLOB_MATCH_MESSAGE = "no matches found";
|
|
@@ -3616,7 +4126,7 @@ function expandVariables(input, context) {
|
|
|
3616
4126
|
});
|
|
3617
4127
|
}
|
|
3618
4128
|
function normalizeAbsolutePath(path) {
|
|
3619
|
-
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);
|
|
3620
4130
|
const normalizedSegments = [];
|
|
3621
4131
|
for (const segment of segments) {
|
|
3622
4132
|
if (segment === "" || segment === ".") continue;
|
|
@@ -3626,17 +4136,17 @@ function normalizeAbsolutePath(path) {
|
|
|
3626
4136
|
}
|
|
3627
4137
|
normalizedSegments.push(segment);
|
|
3628
4138
|
}
|
|
3629
|
-
const normalizedPath = `${ROOT_DIRECTORY$
|
|
3630
|
-
return normalizedPath === "" ? ROOT_DIRECTORY$
|
|
4139
|
+
const normalizedPath = `${ROOT_DIRECTORY$3}${normalizedSegments.join(ROOT_DIRECTORY$3)}`;
|
|
4140
|
+
return normalizedPath === "" ? ROOT_DIRECTORY$3 : normalizedPath;
|
|
3631
4141
|
}
|
|
3632
4142
|
function normalizeCwd(cwd) {
|
|
3633
|
-
if (cwd === "") return ROOT_DIRECTORY$
|
|
4143
|
+
if (cwd === "") return ROOT_DIRECTORY$3;
|
|
3634
4144
|
const trimmed = normalizeAbsolutePath(cwd).replace(TRAILING_SLASH_REGEX$2, "");
|
|
3635
|
-
return trimmed === "" ? ROOT_DIRECTORY$
|
|
4145
|
+
return trimmed === "" ? ROOT_DIRECTORY$3 : trimmed;
|
|
3636
4146
|
}
|
|
3637
4147
|
function resolvePathFromCwd(cwd, path) {
|
|
3638
4148
|
if (path === "") return cwd;
|
|
3639
|
-
if (path.startsWith(ROOT_DIRECTORY$
|
|
4149
|
+
if (path.startsWith(ROOT_DIRECTORY$3)) return normalizeAbsolutePath(path);
|
|
3640
4150
|
return normalizeAbsolutePath(`${cwd}/${path}`);
|
|
3641
4151
|
}
|
|
3642
4152
|
function resolvePathsFromCwd(cwd, paths) {
|
|
@@ -3651,7 +4161,7 @@ async function readDirectoryPaths(fs, directoryPath) {
|
|
|
3651
4161
|
children.sort((left, right) => left.localeCompare(right));
|
|
3652
4162
|
return children;
|
|
3653
4163
|
}
|
|
3654
|
-
async function walkFilesystemEntries(fs, rootDir = ROOT_DIRECTORY$
|
|
4164
|
+
async function walkFilesystemEntries(fs, rootDir = ROOT_DIRECTORY$3) {
|
|
3655
4165
|
const normalizedRoot = normalizeAbsolutePath(rootDir);
|
|
3656
4166
|
if (!(await fs.stat(normalizedRoot)).isDirectory) throw new Error(`Not a directory: ${normalizedRoot}`);
|
|
3657
4167
|
const entries = [];
|
|
@@ -3673,12 +4183,12 @@ async function walkFilesystemEntries(fs, rootDir = ROOT_DIRECTORY$2) {
|
|
|
3673
4183
|
return entries;
|
|
3674
4184
|
}
|
|
3675
4185
|
function toRelativePathFromCwd$1(path, cwd) {
|
|
3676
|
-
if (cwd === ROOT_DIRECTORY$
|
|
3677
|
-
if (path === ROOT_DIRECTORY$
|
|
3678
|
-
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;
|
|
3679
4189
|
}
|
|
3680
4190
|
if (path === cwd) return null;
|
|
3681
|
-
const prefix = `${cwd}${ROOT_DIRECTORY$
|
|
4191
|
+
const prefix = `${cwd}${ROOT_DIRECTORY$3}`;
|
|
3682
4192
|
if (!path.startsWith(prefix)) return null;
|
|
3683
4193
|
return path.slice(prefix.length);
|
|
3684
4194
|
}
|
|
@@ -3686,12 +4196,12 @@ function toGlobCandidate(entry, cwd, isAbsolutePattern, directoryOnly) {
|
|
|
3686
4196
|
if (directoryOnly && !entry.isDirectory) return null;
|
|
3687
4197
|
const basePath = isAbsolutePattern ? entry.path : toRelativePathFromCwd$1(entry.path, cwd);
|
|
3688
4198
|
if (!basePath || basePath === "") return null;
|
|
3689
|
-
if (directoryOnly) return `${basePath}${ROOT_DIRECTORY$
|
|
4199
|
+
if (directoryOnly) return `${basePath}${ROOT_DIRECTORY$3}`;
|
|
3690
4200
|
return basePath;
|
|
3691
4201
|
}
|
|
3692
4202
|
async function expandGlobPattern(pattern, fs, context) {
|
|
3693
|
-
const directoryOnly = pattern.endsWith(ROOT_DIRECTORY$
|
|
3694
|
-
const isAbsolutePattern = pattern.startsWith(ROOT_DIRECTORY$
|
|
4203
|
+
const directoryOnly = pattern.endsWith(ROOT_DIRECTORY$3);
|
|
4204
|
+
const isAbsolutePattern = pattern.startsWith(ROOT_DIRECTORY$3);
|
|
3695
4205
|
const matcher = picomatch(pattern, {
|
|
3696
4206
|
bash: true,
|
|
3697
4207
|
dot: false
|
|
@@ -3755,33 +4265,6 @@ async function evaluateExpandedWordPart(part, fs, context) {
|
|
|
3755
4265
|
}
|
|
3756
4266
|
}
|
|
3757
4267
|
//#endregion
|
|
3758
|
-
//#region src/builtin/cd/cd.ts
|
|
3759
|
-
const cd = async (runtime, args) => {
|
|
3760
|
-
const requestedPath = await evaluateExpandedSinglePath("cd", "expected exactly 1 path after expansion", args.path, runtime.fs, runtime.context, { allowEmpty: true });
|
|
3761
|
-
if (requestedPath === "") throw new Error("cd: empty path");
|
|
3762
|
-
const resolvedPath = resolvePathFromCwd(runtime.context.cwd, requestedPath);
|
|
3763
|
-
let stat;
|
|
3764
|
-
try {
|
|
3765
|
-
stat = await runtime.fs.stat(resolvedPath);
|
|
3766
|
-
} catch {
|
|
3767
|
-
throw new Error(`cd: directory does not exist: ${requestedPath}`);
|
|
3768
|
-
}
|
|
3769
|
-
if (!stat.isDirectory) throw new Error(`cd: not a directory: ${requestedPath}`);
|
|
3770
|
-
runtime.context.cwd = resolvedPath;
|
|
3771
|
-
runtime.context.status = 0;
|
|
3772
|
-
};
|
|
3773
|
-
//#endregion
|
|
3774
|
-
//#region src/builtin/echo/echo.ts
|
|
3775
|
-
const echo = (runtime, args) => {
|
|
3776
|
-
return (async function* () {
|
|
3777
|
-
yield {
|
|
3778
|
-
kind: "line",
|
|
3779
|
-
text: (await evaluateExpandedPathWords("echo", args.values, runtime.fs, runtime.context)).join(" ")
|
|
3780
|
-
};
|
|
3781
|
-
runtime.context.status = 0;
|
|
3782
|
-
})();
|
|
3783
|
-
};
|
|
3784
|
-
//#endregion
|
|
3785
4268
|
//#region src/execute/records.ts
|
|
3786
4269
|
async function* toLineStream(fs, input) {
|
|
3787
4270
|
for await (const record of input) {
|
|
@@ -3836,10 +4319,156 @@ async function isDirectoryRecord(fs, record) {
|
|
|
3836
4319
|
return false;
|
|
3837
4320
|
}
|
|
3838
4321
|
}
|
|
3839
|
-
|
|
3840
|
-
|
|
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);
|
|
3841
4443
|
}
|
|
3842
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
|
|
3843
4472
|
//#region src/builtin/read/read.ts
|
|
3844
4473
|
const VARIABLE_NAME_REGEX$1 = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
3845
4474
|
async function readFirstValue(runtime) {
|
|
@@ -4179,9 +4808,9 @@ async function* find(fs, context, args) {
|
|
|
4179
4808
|
writeDiagnosticsToStderr(context, args.diagnostics);
|
|
4180
4809
|
return;
|
|
4181
4810
|
}
|
|
4182
|
-
let
|
|
4811
|
+
let resolvedPredicateBranches;
|
|
4183
4812
|
try {
|
|
4184
|
-
|
|
4813
|
+
resolvedPredicateBranches = await resolvePredicates(args.predicateBranches, fs, context);
|
|
4185
4814
|
} catch (error) {
|
|
4186
4815
|
context.status = diagnosticExitCode(error);
|
|
4187
4816
|
writeErrorToStderr(context, error);
|
|
@@ -4196,6 +4825,7 @@ async function* find(fs, context, args) {
|
|
|
4196
4825
|
return;
|
|
4197
4826
|
}
|
|
4198
4827
|
const state = { hadError: false };
|
|
4828
|
+
const hasEmptyPredicate = resolvedPredicateBranches.some((branch) => branch.some((predicate) => predicate.kind === "empty"));
|
|
4199
4829
|
for (const startPath of startPaths) {
|
|
4200
4830
|
let startStat;
|
|
4201
4831
|
try {
|
|
@@ -4208,77 +4838,111 @@ async function* find(fs, context, args) {
|
|
|
4208
4838
|
yield* walkEntry(fs, context, {
|
|
4209
4839
|
...startPath,
|
|
4210
4840
|
depth: 0,
|
|
4211
|
-
isDirectory: startStat.isDirectory
|
|
4212
|
-
|
|
4841
|
+
isDirectory: startStat.isDirectory,
|
|
4842
|
+
size: startStat.size
|
|
4843
|
+
}, args, resolvedPredicateBranches, state, hasEmptyPredicate);
|
|
4213
4844
|
}
|
|
4214
4845
|
context.status = state.hadError ? 1 : 0;
|
|
4215
4846
|
}
|
|
4216
|
-
async function* walkEntry(fs, context, entry, args,
|
|
4217
|
-
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);
|
|
4218
4858
|
if (!args.traversal.depth && matches) yield toFileRecord(entry);
|
|
4219
|
-
if (
|
|
4220
|
-
let
|
|
4859
|
+
if (shouldRecurse && childPaths !== null) for (const childAbsolutePath of childPaths) {
|
|
4860
|
+
let childStat;
|
|
4221
4861
|
try {
|
|
4222
|
-
|
|
4862
|
+
childStat = await fs.stat(childAbsolutePath);
|
|
4223
4863
|
} catch {
|
|
4224
4864
|
state.hadError = true;
|
|
4225
|
-
writeDiagnosticsToStderr(context, [createRuntimeDiagnostic("find", "
|
|
4226
|
-
|
|
4227
|
-
}
|
|
4228
|
-
for (const childAbsolutePath of childPaths) {
|
|
4229
|
-
let childStat;
|
|
4230
|
-
try {
|
|
4231
|
-
childStat = await fs.stat(childAbsolutePath);
|
|
4232
|
-
} catch {
|
|
4233
|
-
state.hadError = true;
|
|
4234
|
-
writeDiagnosticsToStderr(context, [createRuntimeDiagnostic("find", "missing-path", "No such file or directory", { path: appendDisplayPath(entry.displayPath, basename$2(childAbsolutePath)) })]);
|
|
4235
|
-
continue;
|
|
4236
|
-
}
|
|
4237
|
-
yield* walkEntry(fs, context, {
|
|
4238
|
-
absolutePath: childAbsolutePath,
|
|
4239
|
-
displayPath: appendDisplayPath(entry.displayPath, basename$2(childAbsolutePath)),
|
|
4240
|
-
depth: entry.depth + 1,
|
|
4241
|
-
isDirectory: childStat.isDirectory
|
|
4242
|
-
}, args, predicates, state);
|
|
4865
|
+
writeDiagnosticsToStderr(context, [createRuntimeDiagnostic("find", "missing-path", "No such file or directory", { path: appendDisplayPath(entry.displayPath, basename$2(childAbsolutePath)) })]);
|
|
4866
|
+
continue;
|
|
4243
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);
|
|
4244
4875
|
}
|
|
4245
4876
|
if (args.traversal.depth && matches) yield toFileRecord(entry);
|
|
4246
4877
|
}
|
|
4247
|
-
async function resolvePredicates(
|
|
4878
|
+
async function resolvePredicates(predicateBranches, fs, context) {
|
|
4248
4879
|
const resolved = [];
|
|
4249
|
-
for (const
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4880
|
+
for (const branch of predicateBranches) {
|
|
4881
|
+
const resolvedBranch = [];
|
|
4882
|
+
for (const predicate of branch) switch (predicate.kind) {
|
|
4883
|
+
case "name": {
|
|
4884
|
+
const pattern = await evaluateExpandedWord(predicate.pattern, fs, context);
|
|
4885
|
+
resolvedBranch.push({
|
|
4886
|
+
kind: "name",
|
|
4887
|
+
matcher: picomatch(pattern, {
|
|
4888
|
+
bash: true,
|
|
4889
|
+
dot: true
|
|
4890
|
+
})
|
|
4891
|
+
});
|
|
4892
|
+
break;
|
|
4893
|
+
}
|
|
4894
|
+
case "path": {
|
|
4895
|
+
const pattern = await evaluateExpandedWord(predicate.pattern, fs, context);
|
|
4896
|
+
resolvedBranch.push({
|
|
4897
|
+
kind: "path",
|
|
4898
|
+
matcher: picomatch(pattern, {
|
|
4899
|
+
bash: true,
|
|
4900
|
+
dot: true
|
|
4901
|
+
})
|
|
4902
|
+
});
|
|
4903
|
+
break;
|
|
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;
|
|
4934
|
+
case "type":
|
|
4935
|
+
resolvedBranch.push({
|
|
4936
|
+
kind: "type",
|
|
4937
|
+
types: new Set(predicate.types)
|
|
4938
|
+
});
|
|
4939
|
+
break;
|
|
4940
|
+
default: {
|
|
4941
|
+
const _exhaustive = predicate;
|
|
4942
|
+
throw new Error(`Unsupported find predicate: ${JSON.stringify(_exhaustive)}`);
|
|
4943
|
+
}
|
|
4281
4944
|
}
|
|
4945
|
+
resolved.push(resolvedBranch);
|
|
4282
4946
|
}
|
|
4283
4947
|
return resolved;
|
|
4284
4948
|
}
|
|
@@ -4296,20 +4960,65 @@ async function resolveStartPaths(fs, context, startPathWords) {
|
|
|
4296
4960
|
}
|
|
4297
4961
|
return startPaths;
|
|
4298
4962
|
}
|
|
4299
|
-
function matchesPredicates(entry,
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4963
|
+
function matchesPredicates(entry, predicateBranches, childPaths) {
|
|
4964
|
+
if (predicateBranches.length === 0) return true;
|
|
4965
|
+
const entryType = entry.isDirectory ? "d" : "f";
|
|
4966
|
+
for (const branch of predicateBranches) if (matchesBranch(entry, entryType, branch, childPaths)) return true;
|
|
4967
|
+
return false;
|
|
4968
|
+
}
|
|
4969
|
+
function matchesBranch(entry, entryType, branch, childPaths) {
|
|
4970
|
+
for (const predicate of branch) if (!matchesPredicate(entry, entryType, predicate, childPaths)) return false;
|
|
4971
|
+
return true;
|
|
4972
|
+
}
|
|
4973
|
+
function matchesPredicate(entry, entryType, predicate, childPaths) {
|
|
4974
|
+
if (predicate.kind === "name") return predicate.matcher(basename$2(entry.displayPath));
|
|
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
|
+
}
|
|
4982
|
+
return predicate.types.has(entryType);
|
|
4983
|
+
}
|
|
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 += "\\\\";
|
|
4303
5002
|
continue;
|
|
4304
5003
|
}
|
|
4305
|
-
|
|
4306
|
-
|
|
5004
|
+
index += 1;
|
|
5005
|
+
if (isEmacsEscapedOperatorChar(escapedChar)) {
|
|
5006
|
+
translated += escapedChar;
|
|
4307
5007
|
continue;
|
|
4308
5008
|
}
|
|
4309
|
-
|
|
4310
|
-
if (!predicate.types.has(entryType)) return false;
|
|
5009
|
+
translated += escapeJsRegexLiteralChar(escapedChar);
|
|
4311
5010
|
}
|
|
4312
|
-
return
|
|
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;
|
|
4313
5022
|
}
|
|
4314
5023
|
async function readChildren(fs, path) {
|
|
4315
5024
|
const children = [];
|
|
@@ -4367,53 +5076,6 @@ function trimTrailingSlashes(path) {
|
|
|
4367
5076
|
return path.replace(/\/+$/g, "");
|
|
4368
5077
|
}
|
|
4369
5078
|
//#endregion
|
|
4370
|
-
//#region src/execute/redirection.ts
|
|
4371
|
-
const textEncoder = new TextEncoder();
|
|
4372
|
-
function getRedirect(redirections, kind) {
|
|
4373
|
-
if (!redirections) return null;
|
|
4374
|
-
let redirect = null;
|
|
4375
|
-
for (const redirection of redirections) if (redirection.kind === kind) redirect = redirection;
|
|
4376
|
-
return redirect;
|
|
4377
|
-
}
|
|
4378
|
-
function hasRedirect(redirections, kind) {
|
|
4379
|
-
return getRedirect(redirections, kind) !== null;
|
|
4380
|
-
}
|
|
4381
|
-
async function resolveRedirectPath(command, redirections, kind, fs, context) {
|
|
4382
|
-
const redirect = getRedirect(redirections, kind);
|
|
4383
|
-
if (!redirect) return null;
|
|
4384
|
-
const targetPath = await evaluateExpandedSinglePath(command, "redirection target must expand to exactly 1 path", redirect.target, fs, context);
|
|
4385
|
-
return resolvePathFromCwd(context.cwd, targetPath);
|
|
4386
|
-
}
|
|
4387
|
-
function withInputRedirect(paths, inputPath) {
|
|
4388
|
-
if (paths.length > 0 || !inputPath) return paths;
|
|
4389
|
-
return [inputPath];
|
|
4390
|
-
}
|
|
4391
|
-
function applyOutputRedirect(result, step, fs, context, resolvedOutputPath) {
|
|
4392
|
-
if (!hasRedirect(step.redirections, "output")) return result;
|
|
4393
|
-
if (result.kind === "stream") return {
|
|
4394
|
-
kind: "sink",
|
|
4395
|
-
value: (async () => {
|
|
4396
|
-
const outputPath = resolvedOutputPath ?? await resolveRedirectPath(step.cmd, step.redirections, "output", fs, context);
|
|
4397
|
-
if (!outputPath) throw new Error(`${step.cmd}: output redirection missing target`);
|
|
4398
|
-
await writeStreamToFile(result.value, outputPath, fs);
|
|
4399
|
-
})()
|
|
4400
|
-
};
|
|
4401
|
-
return {
|
|
4402
|
-
kind: "sink",
|
|
4403
|
-
value: (async () => {
|
|
4404
|
-
const outputPath = resolvedOutputPath ?? await resolveRedirectPath(step.cmd, step.redirections, "output", fs, context);
|
|
4405
|
-
if (!outputPath) throw new Error(`${step.cmd}: output redirection missing target`);
|
|
4406
|
-
await result.value;
|
|
4407
|
-
await fs.writeFile(outputPath, textEncoder.encode(""));
|
|
4408
|
-
})()
|
|
4409
|
-
};
|
|
4410
|
-
}
|
|
4411
|
-
async function writeStreamToFile(stream, path, fs) {
|
|
4412
|
-
const outputChunks = [];
|
|
4413
|
-
for await (const record of stream) outputChunks.push(formatRecord(record));
|
|
4414
|
-
await fs.writeFile(path, textEncoder.encode(outputChunks.join("\n")));
|
|
4415
|
-
}
|
|
4416
|
-
//#endregion
|
|
4417
5079
|
//#region src/operator/grep/grep.ts
|
|
4418
5080
|
const UTF8_DECODER = new TextDecoder();
|
|
4419
5081
|
const UTF8_ENCODER = new TextEncoder();
|
|
@@ -4486,23 +5148,21 @@ async function runGrepCommandInner(options) {
|
|
|
4486
5148
|
lines: [],
|
|
4487
5149
|
selectedLineCount: 0
|
|
4488
5150
|
};
|
|
4489
|
-
|
|
5151
|
+
let binaryInput = false;
|
|
5152
|
+
let targetBytes = null;
|
|
5153
|
+
if (target.stdin) targetBytes = stdinBytes ?? new Uint8Array();
|
|
4490
5154
|
else {
|
|
4491
5155
|
if (target.absolutePath === null) continue;
|
|
4492
|
-
let bytes;
|
|
4493
5156
|
try {
|
|
4494
|
-
|
|
5157
|
+
targetBytes = await options.fs.readFile(target.absolutePath);
|
|
4495
5158
|
} catch {
|
|
4496
5159
|
hadError = true;
|
|
4497
5160
|
continue;
|
|
4498
5161
|
}
|
|
4499
|
-
if (parsed.options.binaryWithoutMatch && !parsed.options.textMode && isBinaryBuffer(bytes)) result = {
|
|
4500
|
-
hasSelectedLine: false,
|
|
4501
|
-
lines: [],
|
|
4502
|
-
selectedLineCount: 0
|
|
4503
|
-
};
|
|
4504
|
-
else result = searchBuffer(bytes, target.displayPath, matcherBuild.patterns, parsed.options, displayFilename);
|
|
4505
5162
|
}
|
|
5163
|
+
if (targetBytes === null) continue;
|
|
5164
|
+
binaryInput = shouldTreatAsBinaryInput(targetBytes, parsed.options);
|
|
5165
|
+
if (!(binaryInput && parsed.options.binaryWithoutMatch)) result = searchBuffer(targetBytes, target.displayPath, matcherBuild.patterns, parsed.options, displayFilename);
|
|
4506
5166
|
if (parsed.options.listFilesWithMatches) {
|
|
4507
5167
|
if (result.hasSelectedLine) {
|
|
4508
5168
|
lines.push(target.displayPath);
|
|
@@ -4528,7 +5188,8 @@ async function runGrepCommandInner(options) {
|
|
|
4528
5188
|
continue;
|
|
4529
5189
|
}
|
|
4530
5190
|
if (result.hasSelectedLine) anySelected = true;
|
|
4531
|
-
if (!parsed.options.quiet) lines.push(
|
|
5191
|
+
if (!parsed.options.quiet) if (shouldPrintBinaryMatchMessage(binaryInput, result.hasSelectedLine, parsed.options)) lines.push(`Binary file ${target.displayPath} matches`);
|
|
5192
|
+
else lines.push(...result.lines);
|
|
4532
5193
|
if (parsed.options.quiet && anySelected) break;
|
|
4533
5194
|
}
|
|
4534
5195
|
const corpusOverride = await maybeOverrideWithCorpusStatus(parsed.options.mode, normalized.patterns, searchTargets.targets, options.fs);
|
|
@@ -5287,6 +5948,15 @@ function isValidUtf8(bytes) {
|
|
|
5287
5948
|
function isBinaryBuffer(bytes) {
|
|
5288
5949
|
return bytes.includes(0);
|
|
5289
5950
|
}
|
|
5951
|
+
function shouldTreatAsBinaryInput(bytes, options) {
|
|
5952
|
+
if (options.textMode || options.nullData) return false;
|
|
5953
|
+
return isBinaryBuffer(bytes);
|
|
5954
|
+
}
|
|
5955
|
+
function shouldPrintBinaryMatchMessage(binaryInput, hasSelectedLine, options) {
|
|
5956
|
+
if (!(binaryInput && hasSelectedLine)) return false;
|
|
5957
|
+
if (options.binaryWithoutMatch || options.countOnly || options.listFilesWithMatches || options.listFilesWithoutMatch) return false;
|
|
5958
|
+
return true;
|
|
5959
|
+
}
|
|
5290
5960
|
function byteLengthOfPrefix(text, charIndex) {
|
|
5291
5961
|
return UTF8_ENCODER.encode(text.slice(0, charIndex)).byteLength;
|
|
5292
5962
|
}
|
|
@@ -5443,8 +6113,8 @@ function mv(fs) {
|
|
|
5443
6113
|
}
|
|
5444
6114
|
//#endregion
|
|
5445
6115
|
//#region src/operator/pwd/pwd.ts
|
|
5446
|
-
const ROOT_DIRECTORY$
|
|
5447
|
-
async function* pwd(cwd = ROOT_DIRECTORY$
|
|
6116
|
+
const ROOT_DIRECTORY$2 = "/";
|
|
6117
|
+
async function* pwd(cwd = ROOT_DIRECTORY$2) {
|
|
5448
6118
|
yield {
|
|
5449
6119
|
kind: "line",
|
|
5450
6120
|
text: cwd
|
|
@@ -5500,173 +6170,345 @@ function touch(fs) {
|
|
|
5500
6170
|
};
|
|
5501
6171
|
}
|
|
5502
6172
|
//#endregion
|
|
5503
|
-
//#region src/
|
|
5504
|
-
|
|
5505
|
-
|
|
5506
|
-
|
|
5507
|
-
|
|
5508
|
-
|
|
5509
|
-
|
|
5510
|
-
|
|
5511
|
-
|
|
5512
|
-
|
|
5513
|
-
|
|
5514
|
-
|
|
5515
|
-
|
|
5516
|
-
|
|
5517
|
-
|
|
5518
|
-
"rm",
|
|
5519
|
-
"touch"
|
|
5520
|
-
]);
|
|
5521
|
-
const ROOT_DIRECTORY = "/";
|
|
5522
|
-
const TEXT_ENCODER = new TextEncoder();
|
|
5523
|
-
function isEffectStep(step) {
|
|
5524
|
-
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
|
+
}
|
|
5525
6188
|
}
|
|
5526
|
-
async function
|
|
5527
|
-
|
|
5528
|
-
|
|
5529
|
-
|
|
5530
|
-
|
|
5531
|
-
|
|
5532
|
-
|
|
5533
|
-
|
|
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
|
+
};
|
|
5534
6212
|
}
|
|
5535
|
-
function
|
|
5536
|
-
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;
|
|
5537
6256
|
}
|
|
5538
|
-
function
|
|
5539
|
-
|
|
5540
|
-
|
|
5541
|
-
|
|
5542
|
-
|
|
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;
|
|
5543
6263
|
}
|
|
5544
|
-
function
|
|
5545
|
-
|
|
5546
|
-
|
|
5547
|
-
|
|
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;
|
|
5548
6269
|
}
|
|
5549
|
-
function
|
|
5550
|
-
|
|
5551
|
-
if (
|
|
5552
|
-
return
|
|
6270
|
+
function splitInputLines(input) {
|
|
6271
|
+
const lines = input.split("\n");
|
|
6272
|
+
if (input.endsWith("\n")) lines.pop();
|
|
6273
|
+
return lines;
|
|
5553
6274
|
}
|
|
5554
|
-
function
|
|
5555
|
-
|
|
5556
|
-
|
|
5557
|
-
|
|
5558
|
-
|
|
5559
|
-
|
|
5560
|
-
kind: "sink",
|
|
5561
|
-
value: runScriptToCompletion(script, fs, context)
|
|
6275
|
+
function tokenize(input, eof) {
|
|
6276
|
+
const items = [];
|
|
6277
|
+
const state = {
|
|
6278
|
+
current: "",
|
|
6279
|
+
escaped: false,
|
|
6280
|
+
quote: null
|
|
5562
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");
|
|
5563
6304
|
return {
|
|
5564
|
-
|
|
5565
|
-
|
|
6305
|
+
items,
|
|
6306
|
+
stopped: pushToken(items, state.current, eof)
|
|
5566
6307
|
};
|
|
5567
6308
|
}
|
|
5568
|
-
|
|
5569
|
-
|
|
5570
|
-
|
|
5571
|
-
try {
|
|
5572
|
-
await drainResult(executePipeline(statement.pipeline, fs, context));
|
|
5573
|
-
} catch (error) {
|
|
5574
|
-
context.status = 1;
|
|
5575
|
-
throw error;
|
|
5576
|
-
}
|
|
5577
|
-
}
|
|
6309
|
+
function appendEscapedChar(state, char) {
|
|
6310
|
+
state.current += char;
|
|
6311
|
+
state.escaped = false;
|
|
5578
6312
|
}
|
|
5579
|
-
|
|
5580
|
-
|
|
5581
|
-
|
|
5582
|
-
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
|
|
5587
|
-
}
|
|
5588
|
-
yield* result.value;
|
|
5589
|
-
} catch (error) {
|
|
5590
|
-
context.status = 1;
|
|
5591
|
-
throw error;
|
|
5592
|
-
}
|
|
6313
|
+
function appendQuotedChar(state, char) {
|
|
6314
|
+
if (char === state.quote) {
|
|
6315
|
+
state.quote = null;
|
|
6316
|
+
return;
|
|
6317
|
+
}
|
|
6318
|
+
if (char === "\\") {
|
|
6319
|
+
state.escaped = true;
|
|
6320
|
+
return;
|
|
5593
6321
|
}
|
|
6322
|
+
state.current += char;
|
|
5594
6323
|
}
|
|
5595
|
-
|
|
5596
|
-
if (
|
|
5597
|
-
|
|
6324
|
+
function appendUnquotedChar(state, char) {
|
|
6325
|
+
if (char === "\"" || char === "'") {
|
|
6326
|
+
state.quote = char;
|
|
6327
|
+
return;
|
|
6328
|
+
}
|
|
6329
|
+
if (char === "\\") {
|
|
6330
|
+
state.escaped = true;
|
|
5598
6331
|
return;
|
|
5599
6332
|
}
|
|
5600
|
-
|
|
6333
|
+
state.current += char;
|
|
5601
6334
|
}
|
|
5602
|
-
function
|
|
5603
|
-
if (
|
|
5604
|
-
|
|
5605
|
-
|
|
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: []
|
|
5606
6350
|
};
|
|
5607
|
-
const
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
6351
|
+
const childContext = {
|
|
6352
|
+
cwd: context.cwd,
|
|
6353
|
+
globalVars: context.globalVars,
|
|
6354
|
+
localVars: context.localVars,
|
|
6355
|
+
status: context.status,
|
|
6356
|
+
stderr: new BufferedOutputStream()
|
|
5611
6357
|
};
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
|
|
5622
|
-
|
|
5623
|
-
return {
|
|
5624
|
-
kind: "sink",
|
|
5625
|
-
value: executePipelineToSink(ir.steps, fs, context)
|
|
5626
|
-
};
|
|
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 [];
|
|
5627
6369
|
}
|
|
5628
|
-
|
|
5629
|
-
|
|
5630
|
-
|
|
5631
|
-
|
|
5632
|
-
|
|
5633
|
-
|
|
5634
|
-
|
|
5635
|
-
|
|
5636
|
-
|
|
5637
|
-
|
|
5638
|
-
|
|
5639
|
-
|
|
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
|
|
5640
6383
|
};
|
|
5641
|
-
return applyOutputRedirect({
|
|
5642
|
-
kind: "stream",
|
|
5643
|
-
value: executePipelineToStream(ir.steps, fs, context)
|
|
5644
|
-
}, lastStep, fs, context);
|
|
5645
6384
|
}
|
|
5646
|
-
|
|
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) {
|
|
5647
6414
|
return (async function* () {
|
|
5648
|
-
|
|
5649
|
-
|
|
5650
|
-
|
|
5651
|
-
|
|
5652
|
-
}
|
|
5653
|
-
if (!stream) return;
|
|
5654
|
-
yield* stream;
|
|
6415
|
+
for await (const line of fs.readLines(path)) yield {
|
|
6416
|
+
kind: "line",
|
|
6417
|
+
text: line
|
|
6418
|
+
};
|
|
5655
6419
|
})();
|
|
5656
6420
|
}
|
|
5657
|
-
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
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}`);
|
|
5663
6431
|
}
|
|
5664
|
-
|
|
6432
|
+
for (const command of STREAM_COMMANDS) try {
|
|
6433
|
+
CommandRegistry.getStream(command);
|
|
6434
|
+
} catch {
|
|
6435
|
+
throw new Error(`Missing stream command handler: ${command}`);
|
|
6436
|
+
}
|
|
6437
|
+
commandRegistriesVerified = true;
|
|
6438
|
+
}
|
|
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);
|
|
5665
6476
|
}
|
|
5666
|
-
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
|
|
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* () {
|
|
5670
6512
|
const options = {
|
|
5671
6513
|
numberLines: step.args.numberLines,
|
|
5672
6514
|
numberNonBlank: step.args.numberNonBlank,
|
|
@@ -5686,7 +6528,12 @@ function executeStreamStep(step, fs, input, context, resolvedOutputRedirectPath)
|
|
|
5686
6528
|
if (input) yield* cat(fs, options)(input);
|
|
5687
6529
|
context.status = 0;
|
|
5688
6530
|
})();
|
|
5689
|
-
|
|
6531
|
+
}
|
|
6532
|
+
});
|
|
6533
|
+
CommandRegistry.register("grep", {
|
|
6534
|
+
kind: "stream",
|
|
6535
|
+
handler: ({ step, fs, input, context, resolvedOutputRedirectPath }) => {
|
|
6536
|
+
return (async function* () {
|
|
5690
6537
|
const result = await runGrepCommand({
|
|
5691
6538
|
context,
|
|
5692
6539
|
fs,
|
|
@@ -5696,14 +6543,44 @@ function executeStreamStep(step, fs, input, context, resolvedOutputRedirectPath)
|
|
|
5696
6543
|
resolvedOutputRedirectPath
|
|
5697
6544
|
});
|
|
5698
6545
|
context.status = result.exitCode;
|
|
5699
|
-
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);
|
|
5700
6573
|
for (const text of result.stdout) yield {
|
|
5701
6574
|
kind: "line",
|
|
5702
6575
|
text
|
|
5703
6576
|
};
|
|
5704
6577
|
})();
|
|
5705
|
-
|
|
5706
|
-
|
|
6578
|
+
}
|
|
6579
|
+
});
|
|
6580
|
+
CommandRegistry.register("head", {
|
|
6581
|
+
kind: "stream",
|
|
6582
|
+
handler: ({ step, fs, input, context }) => {
|
|
6583
|
+
return (async function* () {
|
|
5707
6584
|
const inputPath = await resolveRedirectPath(step.cmd, step.redirections, "input", fs, context);
|
|
5708
6585
|
const filePaths = withInputRedirect(resolvePathsFromCwd(context.cwd, await evaluateExpandedPathWords("head", step.args.files, fs, context)), inputPath);
|
|
5709
6586
|
if (filePaths.length > 0) {
|
|
@@ -5714,7 +6591,12 @@ function executeStreamStep(step, fs, input, context, resolvedOutputRedirectPath)
|
|
|
5714
6591
|
if (input) yield* headLines(step.args.n)(toFormattedLineStream(input));
|
|
5715
6592
|
context.status = 0;
|
|
5716
6593
|
})();
|
|
5717
|
-
|
|
6594
|
+
}
|
|
6595
|
+
});
|
|
6596
|
+
CommandRegistry.register("ls", {
|
|
6597
|
+
kind: "stream",
|
|
6598
|
+
handler: ({ step, fs, context }) => {
|
|
6599
|
+
return (async function* () {
|
|
5718
6600
|
const paths = await evaluateExpandedPathWords("ls", step.args.paths, fs, context);
|
|
5719
6601
|
for (const inputPath of paths) {
|
|
5720
6602
|
const resolvedPath = resolveLsPath(inputPath, context.cwd);
|
|
@@ -5732,7 +6614,12 @@ function executeStreamStep(step, fs, input, context, resolvedOutputRedirectPath)
|
|
|
5732
6614
|
}
|
|
5733
6615
|
context.status = 0;
|
|
5734
6616
|
})();
|
|
5735
|
-
|
|
6617
|
+
}
|
|
6618
|
+
});
|
|
6619
|
+
CommandRegistry.register("tail", {
|
|
6620
|
+
kind: "stream",
|
|
6621
|
+
handler: ({ step, fs, input, context }) => {
|
|
6622
|
+
return (async function* () {
|
|
5736
6623
|
const inputPath = await resolveRedirectPath(step.cmd, step.redirections, "input", fs, context);
|
|
5737
6624
|
const filePaths = withInputRedirect(resolvePathsFromCwd(context.cwd, await evaluateExpandedPathWords("tail", step.args.files, fs, context)), inputPath);
|
|
5738
6625
|
if (filePaths.length > 0) {
|
|
@@ -5743,118 +6630,540 @@ function executeStreamStep(step, fs, input, context, resolvedOutputRedirectPath)
|
|
|
5743
6630
|
if (input) yield* tail(step.args.n)(toFormattedLineStream(input));
|
|
5744
6631
|
context.status = 0;
|
|
5745
6632
|
})();
|
|
5746
|
-
|
|
6633
|
+
}
|
|
6634
|
+
});
|
|
6635
|
+
CommandRegistry.register("pwd", {
|
|
6636
|
+
kind: "stream",
|
|
6637
|
+
handler: ({ context }) => {
|
|
6638
|
+
return (async function* () {
|
|
5747
6639
|
yield* pwd(context.cwd);
|
|
5748
6640
|
context.status = 0;
|
|
5749
6641
|
})();
|
|
5750
|
-
case "echo": return echo(builtinRuntime, step.args);
|
|
5751
|
-
case "set": return set(builtinRuntime, step.args);
|
|
5752
|
-
case "test": return test(builtinRuntime, step.args);
|
|
5753
|
-
case "read": return read(builtinRuntime, step.args);
|
|
5754
|
-
case "string": return string(builtinRuntime, step.args);
|
|
5755
|
-
default: {
|
|
5756
|
-
const _exhaustive = step;
|
|
5757
|
-
throw new Error(`Unknown command: ${String(_exhaustive.cmd)}`);
|
|
5758
|
-
}
|
|
5759
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;
|
|
5760
6763
|
}
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
5768
|
-
|
|
5769
|
-
|
|
5770
|
-
|
|
5771
|
-
|
|
5772
|
-
|
|
5773
|
-
|
|
5774
|
-
|
|
5775
|
-
|
|
5776
|
-
|
|
5777
|
-
|
|
5778
|
-
|
|
5779
|
-
|
|
5780
|
-
|
|
5781
|
-
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
|
|
5797
|
-
|
|
5798
|
-
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
|
|
5803
|
-
|
|
5804
|
-
|
|
5805
|
-
|
|
5806
|
-
|
|
5807
|
-
|
|
5808
|
-
|
|
5809
|
-
|
|
5810
|
-
|
|
5811
|
-
|
|
5812
|
-
|
|
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;
|
|
5813
6820
|
}
|
|
5814
|
-
|
|
5815
|
-
|
|
5816
|
-
|
|
5817
|
-
|
|
5818
|
-
|
|
5819
|
-
|
|
5820
|
-
|
|
5821
|
-
|
|
5822
|
-
|
|
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;
|
|
5823
6831
|
}
|
|
5824
|
-
|
|
5825
|
-
|
|
5826
|
-
|
|
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;
|
|
5827
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;
|
|
5828
6869
|
}
|
|
6870
|
+
if (preservedPipelineStatus !== null) context.status = preservedPipelineStatus;
|
|
5829
6871
|
}
|
|
5830
|
-
function
|
|
5831
|
-
|
|
6872
|
+
async function executeEffectStep(params) {
|
|
6873
|
+
const { context, fs, plan, shouldPreserveStatus, step } = params;
|
|
6874
|
+
const childContext = createChildContext(context);
|
|
6875
|
+
await CommandRegistry.executeStep({
|
|
6876
|
+
step,
|
|
5832
6877
|
fs,
|
|
5833
|
-
context
|
|
5834
|
-
|
|
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
|
|
5835
6891
|
};
|
|
5836
6892
|
}
|
|
5837
|
-
function
|
|
5838
|
-
|
|
5839
|
-
|
|
5840
|
-
|
|
5841
|
-
|
|
5842
|
-
|
|
5843
|
-
|
|
5844
|
-
|
|
5845
|
-
|
|
5846
|
-
|
|
5847
|
-
|
|
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
|
+
};
|
|
5848
6917
|
}
|
|
5849
6918
|
function normalizeContext(context) {
|
|
5850
6919
|
context.cwd = normalizeCwd(context.cwd);
|
|
5851
6920
|
context.status ??= 0;
|
|
5852
|
-
context.stderr ??=
|
|
6921
|
+
context.stderr ??= new BufferedOutputStream();
|
|
5853
6922
|
context.globalVars ??= /* @__PURE__ */ new Map();
|
|
5854
6923
|
context.localVars ??= /* @__PURE__ */ new Map();
|
|
5855
6924
|
return context;
|
|
5856
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
|
+
}
|
|
5857
7166
|
//#endregion
|
|
5858
|
-
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 };
|
|
5859
7168
|
|
|
5860
|
-
//# sourceMappingURL=execute-
|
|
7169
|
+
//# sourceMappingURL=execute-D1oWUkmO.mjs.map
|