@sema-agent/core 7.45.0 → 7.46.0
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.
- package/CHANGELOG.md +246 -1
- package/dist/agents/extra-tool-names.d.ts +65 -0
- package/dist/agents/extra-tool-names.js +124 -0
- package/dist/agents/subagent.d.ts +17 -5
- package/dist/agents/subagent.js +33 -25
- package/dist/agents/tool-filter.d.ts +17 -1
- package/dist/agents/tool-filter.js +7 -0
- package/dist/core/agent-config-dir.d.ts +20 -0
- package/dist/core/agent-config-dir.js +49 -0
- package/dist/core/allow-entry.d.ts +5 -3
- package/dist/core/allow-entry.js +3 -1
- package/dist/core/ask-origin.js +1 -1
- package/dist/core/checkpoint-store.d.ts +39 -2
- package/dist/core/effective-path-target.d.ts +136 -4
- package/dist/core/effective-path-target.js +110 -2
- package/dist/core/engine-notice.d.ts +5 -0
- package/dist/core/gate-fold.js +3 -0
- package/dist/core/gate-lanes.js +9 -3
- package/dist/core/governance-codes.d.ts +3 -1
- package/dist/core/governance-codes.js +3 -0
- package/dist/core/hooks.d.ts +3 -0
- package/dist/core/path-rule-binding.d.ts +65 -0
- package/dist/core/path-rule-binding.js +99 -0
- package/dist/core/permission-rule-model.d.ts +47 -18
- package/dist/core/permission-rule-model.js +38 -42
- package/dist/core/permission-rule-provider.d.ts +7 -0
- package/dist/core/permission-rule-provider.js +10 -3
- package/dist/core/permission-rule-store.d.ts +8 -0
- package/dist/core/permission-rule-store.js +6 -2
- package/dist/core/permission-rule-sync.js +2 -2
- package/dist/core/permission-rules.d.ts +22 -40
- package/dist/core/permission-rules.js +50 -43
- package/dist/core/runner/child-tree.d.ts +97 -0
- package/dist/core/runner/child-tree.js +104 -0
- package/dist/core/runner/construction-doors.d.ts +5 -0
- package/dist/core/runner/construction-doors.js +6 -0
- package/dist/core/runner/contracts.d.ts +46 -0
- package/dist/core/runner/permission-rule-lanes.d.ts +34 -25
- package/dist/core/runner/permission-rule-lanes.js +18 -12
- package/dist/core/runner/persisted-path-rows.d.ts +31 -7
- package/dist/core/runner/persisted-path-rows.js +31 -12
- package/dist/core/runner/prepare-ask-lane.d.ts +4 -0
- package/dist/core/runner/prepare-ask-lane.js +4 -2
- package/dist/core/runner/prepare-execution-env.d.ts +5 -2
- package/dist/core/runner/prepare-execution-env.js +10 -3
- package/dist/core/runner/prepare-gate-stations.d.ts +3 -0
- package/dist/core/runner/prepare-gate-stations.js +2 -1
- package/dist/core/runner/prepare-hands-readface.d.ts +8 -4
- package/dist/core/runner/prepare-hands-readface.js +6 -6
- package/dist/core/runner/prepare-inherited-gate.d.ts +8 -1
- package/dist/core/runner/prepare-inherited-gate.js +8 -2
- package/dist/core/runner/prepare-park-ask.d.ts +3 -0
- package/dist/core/runner/prepare-park-ask.js +5 -3
- package/dist/core/runner/prepare-policy-chain.d.ts +14 -2
- package/dist/core/runner/prepare-policy-chain.js +11 -4
- package/dist/core/runner/prepare-suspend-saga.d.ts +10 -0
- package/dist/core/runner/prepare-suspend-saga.js +7 -1
- package/dist/core/runner/prepare-task.js +37 -18
- package/dist/core/runner/prepare-turn-wiring.d.ts +2 -0
- package/dist/core/runner/prepare-turn-wiring.js +2 -1
- package/dist/core/runner/prepare-workspace-restore.d.ts +3 -0
- package/dist/core/runner/prepare-workspace-restore.js +1 -0
- package/dist/core/runner/resume-apply.js +17 -3
- package/dist/core/runner/resume-checkpoint-screen.js +13 -0
- package/dist/core/runner/run-leg.js +5 -1
- package/dist/core/runner/runtask.js +2 -2
- package/dist/core/runner/sub-agent-deny-lists.d.ts +1 -1
- package/dist/core/runner/write-protection-station.d.ts +3 -2
- package/dist/core/runner/write-protection-station.js +1 -0
- package/dist/core/runner-deps.d.ts +14 -0
- package/dist/core/sensitive-path-policy.d.ts +8 -0
- package/dist/core/sensitive-path-policy.js +18 -2
- package/dist/core/shell-wrapper-projection.d.ts +11 -0
- package/dist/core/shell-wrapper-projection.js +56 -0
- package/dist/core/shell-wrapper-table.d.ts +144 -34
- package/dist/core/shell-wrapper-table.js +40 -20
- package/dist/core/synthetic-read-verdict.d.ts +3 -0
- package/dist/core/synthetic-read-verdict.js +3 -1
- package/dist/core/tool-catalog-entries.d.ts +21 -0
- package/dist/core/tool-catalog-entries.js +9 -0
- package/dist/core/tool-policy.d.ts +30 -8
- package/dist/core/tool-policy.js +9 -7
- package/dist/core/tool-spec.d.ts +3 -2
- package/dist/core/write-protect.d.ts +4 -0
- package/dist/core/write-protect.js +30 -9
- package/dist/index.d.ts +5 -3
- package/dist/index.js +4 -2
- package/dist/orchestration/run-spec.js +8 -7
- package/dist/orchestration/workflow.js +1 -1
- package/dist/shell/lex.d.ts +7 -0
- package/dist/shell/lex.js +17 -0
- package/dist/tools/fs/bash-program-position.d.ts +8 -87
- package/dist/tools/fs/bash-program-position.js +33 -69
- package/dist/tools/fs/bash-readonly-classifier.d.ts +17 -444
- package/dist/tools/fs/bash-readonly-classifier.js +169 -1225
- package/dist/tools/fs/index.d.ts +3 -2
- package/dist/tools/fs/program-read-model.d.ts +694 -0
- package/dist/tools/fs/program-read-model.js +1252 -0
- package/dist/tools/fs/program-read-model.rows.d.ts +18 -0
- package/dist/tools/fs/program-read-model.rows.js +613 -0
- package/dist/tools/fs/read-deny-home.d.ts +19 -1
- package/dist/tools/fs/read-deny-home.js +62 -24
- package/dist/tools/fs/read-deny.d.ts +54 -14
- package/dist/tools/fs/read-deny.js +104 -50
- package/dist/tools/fs/removal-targets.d.ts +7 -6
- package/dist/tools/fs/removal-targets.js +22 -22
- package/dist/tools/fs/safety.d.ts +3 -0
- package/dist/tools/fs/safety.js +10 -3
- package/dist/tools/fs/shell-read-argv.d.ts +85 -0
- package/dist/tools/fs/shell-read-argv.js +386 -0
- package/dist/tools/fs/shell-read-bindings.d.ts +273 -0
- package/dist/tools/fs/shell-read-bindings.js +746 -0
- package/dist/tools/fs/shell-read-check.js +23 -15
- package/dist/tools/fs/shell-read-establish.js +79 -5
- package/dist/tools/fs/shell-read-judge.d.ts +11 -1
- package/dist/tools/fs/shell-read-judge.js +9 -1
- package/dist/tools/fs/shell-read-plan.d.ts +31 -7
- package/dist/tools/fs/shell-read-plan.js +299 -393
- package/dist/tools/fs/shell-read-word.d.ts +29 -26
- package/dist/tools/fs/shell-read-word.js +30 -38
- package/dist/tools/fs/shell-read-write-targets.d.ts +15 -64
- package/dist/tools/fs/shell-read-write-targets.js +83 -136
- package/dist/tools/fs/shell-reentry-text.d.ts +38 -6
- package/dist/tools/fs/shell-reentry-text.js +90 -53
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +17 -3
- package/wire-changes/core@7.46.0.json +1522 -0
|
@@ -4,24 +4,38 @@
|
|
|
4
4
|
* (the home-directory word spelled with the declared home, as bash hands it to the program), and what the shell
|
|
5
5
|
* does to the word before the program sees it.
|
|
6
6
|
*/
|
|
7
|
-
import { type LexedCommand, type
|
|
7
|
+
import { type LexedCommand, type LexedWord, type WordPiece } from "../../shell/lex.js";
|
|
8
|
+
import { type BindingTable, type BoundValue } from "./shell-read-bindings.js";
|
|
8
9
|
/** `"glob"` = an unquoted pathname-expansion character; `"unresolvable"` = the shell rewrites the word (a brace,
|
|
9
10
|
* variable or command substitution) so its text is not the path. The read candidate's own field, spelled here. */
|
|
10
11
|
export type WordExpands = false | "glob" | "unresolvable";
|
|
11
|
-
/** A word as the candidate readers name it: the lexer's word, the
|
|
12
|
-
*
|
|
13
|
-
*
|
|
12
|
+
/** A word as the candidate readers name it: the lexer's word, the spelling the program receives — the names the word
|
|
13
|
+
* refers to read through the binding table ({@link wordValues}: `"$F"` after `F=/x` is `/x`) — and what the shell does to
|
|
14
|
+
* it before the program sees it. */
|
|
14
15
|
export interface Read {
|
|
15
16
|
readonly word: LexedWord;
|
|
16
17
|
readonly raw: string;
|
|
17
18
|
readonly folded: string;
|
|
18
19
|
readonly expands: WordExpands;
|
|
19
|
-
/** The word
|
|
20
|
-
|
|
20
|
+
/** The names the word refers to were read through the table: its expansion IS the spelling above (and, where the
|
|
21
|
+
* names may hold several values, {@link values}). */
|
|
22
|
+
readonly settled?: true;
|
|
23
|
+
/** Every spelling the word may expand to (the first is {@link folded}); present only where there are several. */
|
|
24
|
+
readonly values?: readonly string[];
|
|
25
|
+
/** The word refers to `HOME` and `HOME` may hold something other than the declared home here: the spelling the
|
|
26
|
+
* word has with the declared home, for the deny judge alone. */
|
|
27
|
+
readonly homeFloor?: string;
|
|
28
|
+
/** The word's names are unknown here, but their bindings left spellings for the deny judge (an array's elements):
|
|
29
|
+
* what the word may then spell, for the deny judge alone. */
|
|
30
|
+
readonly floorValues?: readonly BoundValue[];
|
|
21
31
|
}
|
|
22
|
-
|
|
32
|
+
/** The reads of a segment's words against the binding table at that segment (`homeDir` = the declared home, the
|
|
33
|
+
* state `HOME` starts in). A word that refers to no name, or only to a `HOME` the text has not touched (`~/x`, placed
|
|
34
|
+
* by the establishment at the declared home), reads as written. */
|
|
35
|
+
export declare function readsOf(words: readonly LexedWord[], table: BindingTable | undefined, homeDir: string | undefined): Read[];
|
|
23
36
|
/** The shell REWRITES this read's word before the program sees it (a parameter, command, process, arithmetic or
|
|
24
|
-
* brace expansion, or the character of one) — the
|
|
37
|
+
* brace expansion, or the character of one) — a word whose names the table settled excepted: its expansion is its
|
|
38
|
+
* spelling. */
|
|
25
39
|
export declare const rewritten: (r: Read) => boolean;
|
|
26
40
|
/** The reading of a text that came out of a word (a sed script's file operand): one shell word, or unresolvable. */
|
|
27
41
|
export declare function readOfText(text: string): Read;
|
|
@@ -31,35 +45,24 @@ export declare function readOfText(text: string): Read;
|
|
|
31
45
|
* the word is assignment-shaped (`A="$HOME/x"`, `dd if="$HOME/x"`: the candidate is the value, quotes kept);
|
|
32
46
|
* `undefined` for any other (`~user`, a word that is no home word). */
|
|
33
47
|
export declare function spelledWithHome(spelled: string, raw: string, home: string): string | undefined;
|
|
34
|
-
/** A function definition's head names nothing that runs here and is dropped from a segment's tokens, in place:
|
|
35
|
-
* `NAME ( )` (an identifier), and `function NAME [( )]` — a bare `function` at command position, a name of any
|
|
36
|
-
* spelling. The lexer puts the body's compound command back at command position after the head, so a definition
|
|
37
|
-
* nested in a body is dropped the same way; after a drop the token that moved up is looked at again. */
|
|
38
|
-
export declare function dropDefinitionHeads(tokens: LexedToken[]): void;
|
|
39
48
|
/** Why a lexed text's reads cannot all be named — the plan's `unreadable` floor, or `undefined`: the first word whose
|
|
40
49
|
* value the text does not contain (an unresolved word), or an unquoted extended-glob opener anywhere a word, a
|
|
41
50
|
* redirection target or a here-document delimiter stands (`pack@(age.json)`, `<<@(x)`). After `shopt -s extglob`
|
|
42
51
|
* bash expands the pattern (or reads another delimiter) where the lexer reads a split word, so what the text opens is
|
|
43
52
|
* not what the plan names: the whole text asks, and a deny hit anywhere still refuses. `globs: false` = the text is an
|
|
44
|
-
* arithmetic expression (`$((2*(3+4)))`), which bash evaluates and never globs.
|
|
53
|
+
* arithmetic expression (`$((2*(3+4)))`), which bash evaluates and never globs. A compound assignment's value is no
|
|
54
|
+
* such word: the assignment reads nothing. */
|
|
45
55
|
export declare function unreadableSentenceOf(lexed: LexedCommand, text: string, globs?: boolean): string | undefined;
|
|
46
|
-
/** The elements of a compound array value `(e1 [k]=e2 …)`, read with the one lexer as the words bash assigns (a
|
|
47
|
-
* `[k]=` subscript prefix dropped): each is a value spelling the deny judge reads, as a scalar value is. The list is
|
|
48
|
-
* words across lines and comments, so every segment's words count, whatever else a segment carries (a word bash
|
|
49
|
-
* would reject leaves its siblings readable). None when the list does not lex — the value stays the unread list. */
|
|
50
|
-
export declare function compoundArrayElements(value: {
|
|
51
|
-
readonly raw: string;
|
|
52
|
-
}): readonly (Read & {
|
|
53
|
-
readonly denyOnly?: true;
|
|
54
|
-
})[];
|
|
55
56
|
/** An `$((…))` body the shell reads as ARITHMETIC: one parenthesised group spanning the whole body (`(2*(3+4))`),
|
|
56
57
|
* its bounds read off the lexer's structure — a quoted, escaped or commented parenthesis is text, not a bound — across
|
|
57
58
|
* the segments the lexer cuts at `&&` `||` `&` `|` and line breaks (operators arithmetic shares with the shell).
|
|
58
59
|
* `$((cat x); :)` opens the same way but its first group closes early: a command substitution holding a subshell. */
|
|
59
60
|
export declare function isArithmeticBody(body: string): boolean;
|
|
60
|
-
/** What the shell does to a word (or an assignment's value) before the program sees it, read off its pieces
|
|
61
|
+
/** What the shell does to a word (or an assignment's value) before the program sees it, read off its pieces: the
|
|
62
|
+
* lexer's expansion facts alone. A quoted text that an evaluator or an interpreter later RUNS (an arithmetic
|
|
63
|
+
* subscript, an inline program) is no rewrite of the word: the slot it stands in says what it is (the program read
|
|
64
|
+
* model's role for it, and the arithmetic rule of `shell-read-bindings.ts`). A substitution is an expansion PIECE
|
|
65
|
+
* (command / process / arithmetic), never the placeholder text it leaves in the folded spelling. */
|
|
61
66
|
export declare function expandsOf(w: {
|
|
62
67
|
readonly pieces: readonly WordPiece[];
|
|
63
|
-
readonly raw: string;
|
|
64
|
-
readonly folded: string;
|
|
65
68
|
}): WordExpands;
|
|
@@ -1,14 +1,34 @@
|
|
|
1
|
-
import { argvOf, lexShell,
|
|
1
|
+
import { argvOf, lexShell, UNRESOLVED_SENTENCE } from "../../shell/lex.js";
|
|
2
2
|
import { expansionInWord, extglobOpenerIn, globInWord, homeExpansionOf } from "./bash-readonly-classifier.js";
|
|
3
|
-
|
|
3
|
+
import { wordFloorValues, wordValues } from "./shell-read-bindings.js";
|
|
4
|
+
export function readsOf(words, table, homeDir) {
|
|
4
5
|
return words.map((word) => {
|
|
5
|
-
const
|
|
6
|
-
if (
|
|
7
|
-
return { word, raw:
|
|
8
|
-
|
|
6
|
+
const refersToName = word.pieces.some((p) => p.expansion?.kind === "parameter");
|
|
7
|
+
if (!refersToName && table?.homeTouched !== true)
|
|
8
|
+
return { word, raw: word.raw, folded: word.folded, expands: expandsOf(word) };
|
|
9
|
+
const values = wordValues(word, table, false);
|
|
10
|
+
if (values === undefined)
|
|
11
|
+
return { word, raw: word.raw, folded: word.folded, expands: expandsOf(word) };
|
|
12
|
+
if (values === "unknown") {
|
|
13
|
+
const floors = refersToName ? wordFloorValues(word, table) : [];
|
|
14
|
+
return { word, raw: word.raw, folded: word.folded, expands: refersToName ? "unresolvable" : expandsOf(word), ...(floors.length > 0 ? { floorValues: floors } : {}) };
|
|
15
|
+
}
|
|
16
|
+
const glob = values.some((v) => v.glob === true) || globInWord(word);
|
|
17
|
+
const refersToHome = word.pieces.some((p) => p.expansion?.kind === "tilde" || (p.expansion?.kind === "parameter" && p.expansion.name === "HOME"));
|
|
18
|
+
const declared = refersToHome && homeDir !== undefined && table?.homeTouched === true ? wordValues(word, table, false, new Map([["HOME", [{ spelled: homeDir }]]])) : undefined;
|
|
19
|
+
const floor = declared !== undefined && declared !== "unknown" ? declared[0]?.spelled : undefined;
|
|
20
|
+
return {
|
|
21
|
+
word,
|
|
22
|
+
raw: word.raw,
|
|
23
|
+
folded: values[0].spelled,
|
|
24
|
+
expands: glob ? "glob" : false,
|
|
25
|
+
settled: true,
|
|
26
|
+
...(values.length > 1 ? { values: values.map((v) => v.spelled) } : {}),
|
|
27
|
+
...(floor !== undefined && !values.every((v) => v.spelled === floor) ? { homeFloor: floor } : {}),
|
|
28
|
+
};
|
|
9
29
|
});
|
|
10
30
|
}
|
|
11
|
-
export const rewritten = (r) => r.
|
|
31
|
+
export const rewritten = (r) => r.settled !== true && expansionInWord(r.word);
|
|
12
32
|
export function readOfText(text) {
|
|
13
33
|
const l = lexShell(text);
|
|
14
34
|
const words = "parseFailure" in l || l.segments.length !== 1 ? [] : argvOf(l.segments[0]);
|
|
@@ -28,46 +48,18 @@ export function spelledWithHome(spelled, raw, home) {
|
|
|
28
48
|
const w = readOfText(raw).word;
|
|
29
49
|
return homeExpansionOf(w.assignment !== undefined ? { ...w, pieces: w.assignment.value.pieces } : w, home)?.spelled;
|
|
30
50
|
}
|
|
31
|
-
export function dropDefinitionHeads(tokens) {
|
|
32
|
-
for (let k = 0; k + 1 < tokens.length; k++) {
|
|
33
|
-
const [a, b, c, d] = [tokens[k], tokens[k + 1], tokens[k + 2], tokens[k + 3]];
|
|
34
|
-
if (a.kind === "word" && a.raw === "function" && a.atCommandPosition && b.kind === "word")
|
|
35
|
-
tokens.splice(k--, c?.kind === "group" && c.op === "(" && d?.kind === "group" && d.op === ")" ? 4 : 2);
|
|
36
|
-
}
|
|
37
|
-
for (let k = 0; k + 2 < tokens.length; k++) {
|
|
38
|
-
const [a, b, c] = [tokens[k], tokens[k + 1], tokens[k + 2]];
|
|
39
|
-
if (a.kind === "word" && b.kind === "group" && b.op === "(" && c.kind === "group" && c.op === ")" && /^[A-Za-z_][A-Za-z0-9_]*$/.test(a.folded))
|
|
40
|
-
tokens.splice(k, 3);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
51
|
export function unreadableSentenceOf(lexed, text, globs = true) {
|
|
44
52
|
for (const segment of lexed.segments) {
|
|
45
53
|
for (const token of segment.tokens) {
|
|
46
54
|
const word = token.kind === "word" ? token : token.kind === "redirect" ? token.target : undefined;
|
|
55
|
+
if (word?.unresolved === "compound_array_value" && word.assignment?.form === "compound")
|
|
56
|
+
continue;
|
|
47
57
|
if (word?.unresolved !== undefined)
|
|
48
58
|
return UNRESOLVED_SENTENCE[word.unresolved];
|
|
49
59
|
}
|
|
50
60
|
}
|
|
51
61
|
return globs && extglobOpenerIn(lexed, text) ? "an extended pattern (`?(…)` `*(…)` `+(…)` `@(…)` `!(…)`) is one this reader does not expand" : undefined;
|
|
52
62
|
}
|
|
53
|
-
export function compoundArrayElements(value) {
|
|
54
|
-
const inner = /^\(([\s\S]*)\)$/.exec(value.raw)?.[1];
|
|
55
|
-
const l = inner === undefined ? undefined : lexShell(inner);
|
|
56
|
-
if (l === undefined || "parseFailure" in l)
|
|
57
|
-
return [];
|
|
58
|
-
return l.segments.flatMap((sg) => argvOf(sg)).flatMap((element) => {
|
|
59
|
-
const sub = /^\[[^\]]*\]\+?=/.exec(element.raw)?.[0];
|
|
60
|
-
const value = sub === undefined ? undefined : readOfText(`V=${element.raw.slice(sub.length)}`).word.assignment?.value;
|
|
61
|
-
const word = value === undefined ? element : { ...element, raw: value.raw, folded: value.folded, pieces: value.pieces };
|
|
62
|
-
const tilde = word.pieces.some((q) => q.expansion?.kind === "tilde");
|
|
63
|
-
const read = { word, raw: word.raw, folded: tilde ? word.folded : literalTildeSpelling(word.folded), expands: expandsOf(word) };
|
|
64
|
-
const prefix = /^\[[^\]]*\]\+?=/.exec(element.folded)?.[0];
|
|
65
|
-
if (prefix === undefined)
|
|
66
|
-
return [read];
|
|
67
|
-
const spelled = element.pieces.some((q) => q.expansion?.kind === "tilde") ? element.folded.slice(prefix.length) : literalTildeSpelling(element.folded.slice(prefix.length));
|
|
68
|
-
return [read, { word: element, raw: element.raw, folded: spelled, expands: expandsOf(element), denyOnly: true }];
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
63
|
export function isArithmeticBody(body) {
|
|
72
64
|
const l = body.startsWith("(") && body.endsWith(")") ? lexShell(body) : undefined;
|
|
73
65
|
if (l === undefined || "parseFailure" in l)
|
|
@@ -87,7 +79,7 @@ export function isArithmeticBody(body) {
|
|
|
87
79
|
return false;
|
|
88
80
|
}
|
|
89
81
|
export function expandsOf(w) {
|
|
90
|
-
if (expansionInWord(w)
|
|
82
|
+
if (expansionInWord(w))
|
|
91
83
|
return "unresolvable";
|
|
92
84
|
if (globInWord(w))
|
|
93
85
|
return "glob";
|
|
@@ -1,25 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* WRITE TARGETS — which words of a shell segment a program writes, under which names, and at which seat, read off the
|
|
3
|
-
* plan's text alone (no filesystem): the `>`-family redirections and a `sed` script's `w` files, the
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
3
|
+
* plan's text alone (no filesystem): the `>`-family redirections and a `sed` script's `w` files, and the words the
|
|
4
|
+
* segment's program writes by the program read model's ROLES (`writeReadings`: an operand or option value whose role
|
|
5
|
+
* writes, the operands an option made edited in place, a copy's destination and its children, an in-place edit's
|
|
6
|
+
* backups), at the seats the role and the selected options name ({@link WriteSeat}: the directory entry, or where the
|
|
7
|
+
* path lands). The establisher (`shell-read-establish.ts`) places what this module names; the judge reads the result
|
|
8
|
+
* on the deny table's write face. No table of programs or options lives here: what a program writes is its row's.
|
|
8
9
|
*/
|
|
9
10
|
import type { ReadCandidate, ShellReadSegment } from "./shell-read-plan.js";
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
* is a write or a create (`mkdir` / `touch` create; `rm` / `rmdir` / `mv` / `cp` / `tee` write; `sed` writes when it
|
|
13
|
-
* edits in place). Every operand counts (a `cp` source is judged as a write too, as the reference judges it): the write
|
|
14
|
-
* face only ever REFUSES, so a word read as a target it is not costs a refusal of a command that names a denied path.
|
|
15
|
-
*/
|
|
16
|
-
export declare const SHELL_WRITE_COMMANDS: readonly ["mkdir", "touch", "rm", "rmdir", "mv", "cp", "tee", "sed"];
|
|
17
|
-
/**
|
|
18
|
-
* Does an option WORD spell this option — the long name or any abbreviation of it GNU getopt accepts (`--in-p`, `--fi`,
|
|
19
|
-
* `--par`: an unambiguous prefix, `=value` allowed), or the short letter alone or inside a bundle (`-i`, `-ni`, `-i.bak`)?
|
|
20
|
-
* Read in the tightening direction: a prefix that another long option shares still counts (the program would refuse it).
|
|
21
|
-
*/
|
|
22
|
-
export declare function spellsOption(word: string, long: string, short: string): boolean;
|
|
11
|
+
import { type WriteSeat } from "./program-read-model.js";
|
|
12
|
+
export type { WriteSeat } from "./program-read-model.js";
|
|
23
13
|
/** A word of a program's argv: its raw spelling (the plan's candidates are keyed by it) and its folded spelling. */
|
|
24
14
|
export interface ArgWord {
|
|
25
15
|
readonly raw: string;
|
|
@@ -27,41 +17,6 @@ export interface ArgWord {
|
|
|
27
17
|
}
|
|
28
18
|
/** The words a segment hands its program — the argv after the program word (a launcher's own words are not the program's). */
|
|
29
19
|
export declare function programArgs(segment: ShellReadSegment): ArgWord[];
|
|
30
|
-
/**
|
|
31
|
-
* One program's option GRAMMAR, as its getopt reads it: the short letters taking a REQUIRED value (the rest of the word,
|
|
32
|
-
* or the next word), those taking an OPTIONAL one (the rest of the word only), and the long names taking a required value
|
|
33
|
-
* (any unambiguous prefix; `=` or the next word) — absent for a program with no long options (a `--name` word is then an
|
|
34
|
-
* error the program stops on, and names nothing here). The GNU and BSD programs of one name read the same argv
|
|
35
|
-
* differently (`sed -l` takes a value in GNU and none in BSD; `cp -S` a suffix in GNU and none in BSD), so every question
|
|
36
|
-
* below reads the argv under BOTH grammars and takes the union of what they name.
|
|
37
|
-
*/
|
|
38
|
-
export interface OptionGrammar {
|
|
39
|
-
readonly required: string;
|
|
40
|
-
readonly optional: string;
|
|
41
|
-
readonly longRequired?: readonly string[];
|
|
42
|
-
}
|
|
43
|
-
/** One option read off an argv: a short letter or a long name as written (a prefix), and its value when it has one. */
|
|
44
|
-
interface OptionRead {
|
|
45
|
-
readonly name: string;
|
|
46
|
-
readonly long: boolean;
|
|
47
|
-
readonly value?: ArgWord;
|
|
48
|
-
}
|
|
49
|
-
/** An argv read under one grammar: its options in order, and its operands (every word after `--` included). Options are
|
|
50
|
-
* read anywhere among the operands, as GNU getopt permutes them — for a BSD getopt, which stops at the first operand,
|
|
51
|
-
* that is a superset of its options (the tightening direction). */
|
|
52
|
-
export declare function readArgv(words: readonly ArgWord[], grammar: OptionGrammar): {
|
|
53
|
-
readonly options: readonly OptionRead[];
|
|
54
|
-
readonly operands: readonly ArgWord[];
|
|
55
|
-
};
|
|
56
|
-
/** What a `sed` invocation's option words say about writing, under either grammar ({@link SED_GRAMMARS}): whether it edits
|
|
57
|
-
* in place (`-i` / `-I` / `--in-place`), the backup suffixes it is given (their values; an empty value keeps no backup),
|
|
58
|
-
* and whether GNU `--follow-symlinks` is on. */
|
|
59
|
-
export interface SedOptions {
|
|
60
|
-
readonly inPlace: boolean;
|
|
61
|
-
readonly suffixes: readonly string[];
|
|
62
|
-
readonly followsLinks: boolean;
|
|
63
|
-
}
|
|
64
|
-
export declare function sedOptionsOf(segment: ShellReadSegment): SedOptions;
|
|
65
20
|
/**
|
|
66
21
|
* The BACKUP names an in-place `sed` gives the file it knows as `name` — both readings a suffix has: GNU sed replaces every
|
|
67
22
|
* `*` in the suffix with the name (`bak/*` on `d/f` is `bak/d/f`, `/elsewhere/*` is `/elsewhere/d/f`) and appends a suffix
|
|
@@ -69,20 +24,16 @@ export declare function sedOptionsOf(segment: ShellReadSegment): SedOptions;
|
|
|
69
24
|
* the working directory like the operand itself, so a suffix carrying a separator places the backup wherever it points.
|
|
70
25
|
*/
|
|
71
26
|
export declare function sedBackupNames(name: string, suffix: string): string[];
|
|
72
|
-
/**
|
|
73
|
-
* WHERE a write reaches a path it names. `landing` — the path followed through every link to where it lands: the program
|
|
74
|
-
* opens the path and writes through it. `entry` — the directory entry itself, the canonical parent joined with the last
|
|
75
|
-
* name: the program renames over the entry, unlinks it or replaces it, and a link there is not followed (a name ending in
|
|
76
|
-
* a separator, `.` or `..` resolves itself, so it lands).
|
|
77
|
-
*/
|
|
78
|
-
export type WriteSeat = "landing" | "entry";
|
|
79
27
|
/** One candidate handed to the judge. `seats`: where a write target is established ({@link WriteSeat}; absent ⇒ it lands —
|
|
80
28
|
* every read does). `written`: the seats among `seats` the program itself WRITES the target at (absent ⇒ all of them;
|
|
81
29
|
* `[]` ⇒ none — a copy's source, judged on the write face as a write path and read by the program); the write guards are
|
|
82
30
|
* asked about these alone. `child`: a copy's or a move's destination child, written only where the destination is a
|
|
83
|
-
* directory when the program runs. `followed`:
|
|
84
|
-
* of the operand's FOLLOWED name, which only the filesystem answers (the candidate's own
|
|
85
|
-
* written, the identity the pre-spawn re-check compares).
|
|
31
|
+
* directory when the program runs. `followed`: an in-place edit's backup where the operand is edited where it LANDS — its
|
|
32
|
+
* names are {@link sedBackupNames} of the operand's FOLLOWED name, which only the filesystem answers (the candidate's own
|
|
33
|
+
* spelling is the operand's as written, the identity the pre-spawn re-check compares). `onlyIfExists`: written only under
|
|
34
|
+
* SOME implementation's reading of the command, and only by editing an existing file — the program edits nothing that is
|
|
35
|
+
* not there, so where the filesystem confirms this candidate missing nothing is written (a lexical establishment cannot
|
|
36
|
+
* confirm it, and keeps the write). */
|
|
86
37
|
export interface JudgedCandidate {
|
|
87
38
|
readonly candidate: ReadCandidate;
|
|
88
39
|
readonly seats?: readonly WriteSeat[];
|
|
@@ -92,9 +43,9 @@ export interface JudgedCandidate {
|
|
|
92
43
|
readonly operand: ReadCandidate;
|
|
93
44
|
readonly suffix: string;
|
|
94
45
|
};
|
|
46
|
+
readonly onlyIfExists?: ReadCandidate;
|
|
95
47
|
}
|
|
96
48
|
/** Every candidate a segment hands the judge: its reads minus the write targets, plus its `cd` target (the plan already
|
|
97
49
|
* lists it among the reads), plus its WRITE targets for the write face ({@link writeTargetsOf}), plus the deny-floor
|
|
98
50
|
* shadow of every unplaced home word ({@link denyFloorShadowsOf}) on both faces. */
|
|
99
51
|
export declare function judgedCandidatesOf(segment: ShellReadSegment): readonly JudgedCandidate[];
|
|
100
|
-
export {};
|
|
@@ -1,134 +1,106 @@
|
|
|
1
1
|
import { denyFloorShadowsOf } from "./shell-read-judge.js";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
export function spellsOption(word, long, short) {
|
|
5
|
-
if (word.startsWith("--")) {
|
|
6
|
-
const name = word.slice(2).split("=")[0];
|
|
7
|
-
return name.length > 0 && long.startsWith(name);
|
|
8
|
-
}
|
|
9
|
-
return new RegExp(`^-[A-Za-z]*${short}`).test(word);
|
|
10
|
-
}
|
|
2
|
+
import { implementationsOf, programRow, selectedOptions, writeReadings } from "./program-read-model.js";
|
|
3
|
+
import { programBasename, readProgramPosition } from "./bash-program-position.js";
|
|
11
4
|
export function programArgs(segment) {
|
|
12
5
|
const words = segment.argv.map((w) => ({ raw: w.raw, spelled: w.folded }));
|
|
13
6
|
const at = words.findIndex((w) => w.spelled === segment.verb?.name);
|
|
14
7
|
return at < 0 ? [] : words.slice(at + 1);
|
|
15
8
|
}
|
|
16
|
-
const SED_GRAMMARS = [
|
|
17
|
-
{ required: "efl", optional: "i", longRequired: ["expression", "file", "line-length"] },
|
|
18
|
-
{ required: "efiI", optional: "" },
|
|
19
|
-
];
|
|
20
|
-
const COPY_GRAMMARS = [
|
|
21
|
-
{ required: "St", optional: "", longRequired: ["target-directory", "suffix", "no-preserve", "sparse"] },
|
|
22
|
-
{ required: "", optional: "" },
|
|
23
|
-
];
|
|
24
|
-
export function readArgv(words, grammar) {
|
|
25
|
-
const options = [];
|
|
26
|
-
const operands = [];
|
|
27
|
-
let endOfOptions = false;
|
|
28
|
-
for (let k = 0; k < words.length; k++) {
|
|
29
|
-
const w = words[k];
|
|
30
|
-
const s = w.spelled;
|
|
31
|
-
if (endOfOptions || !s.startsWith("-") || s === "-") {
|
|
32
|
-
operands.push(w);
|
|
33
|
-
continue;
|
|
34
|
-
}
|
|
35
|
-
if (s === "--") {
|
|
36
|
-
endOfOptions = true;
|
|
37
|
-
continue;
|
|
38
|
-
}
|
|
39
|
-
if (s.startsWith("--")) {
|
|
40
|
-
const eq = s.indexOf("=");
|
|
41
|
-
const name = s.slice(2, eq < 0 ? undefined : eq);
|
|
42
|
-
if (grammar.longRequired === undefined || name.length === 0)
|
|
43
|
-
continue;
|
|
44
|
-
const value = eq >= 0 ? { raw: w.raw, spelled: s.slice(eq + 1) } : grammar.longRequired.some((long) => long.startsWith(name)) ? words[++k] : undefined;
|
|
45
|
-
options.push({ name, long: true, ...(value !== undefined ? { value } : {}) });
|
|
46
|
-
continue;
|
|
47
|
-
}
|
|
48
|
-
for (let c = 1; c < s.length; c++) {
|
|
49
|
-
const letter = s[c];
|
|
50
|
-
const rest = s.slice(c + 1);
|
|
51
|
-
if (grammar.required.includes(letter)) {
|
|
52
|
-
const value = rest.length > 0 ? { raw: w.raw, spelled: rest } : words[++k];
|
|
53
|
-
options.push({ name: letter, long: false, ...(value !== undefined ? { value } : {}) });
|
|
54
|
-
break;
|
|
55
|
-
}
|
|
56
|
-
if (grammar.optional.includes(letter)) {
|
|
57
|
-
options.push({ name: letter, long: false, ...(rest.length > 0 ? { value: { raw: w.raw, spelled: rest } } : {}) });
|
|
58
|
-
break;
|
|
59
|
-
}
|
|
60
|
-
options.push({ name: letter, long: false });
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
return { options, operands };
|
|
64
|
-
}
|
|
65
|
-
export function sedOptionsOf(segment) {
|
|
66
|
-
const words = programArgs(segment);
|
|
67
|
-
let inPlace = false;
|
|
68
|
-
let followsLinks = false;
|
|
69
|
-
const suffixes = new Set();
|
|
70
|
-
for (const grammar of SED_GRAMMARS) {
|
|
71
|
-
for (const o of readArgv(words, grammar).options) {
|
|
72
|
-
if (o.long ? "in-place".startsWith(o.name) : o.name === "i" || o.name === "I") {
|
|
73
|
-
inPlace = true;
|
|
74
|
-
if (o.value !== undefined && o.value.spelled.length > 0)
|
|
75
|
-
suffixes.add(o.value.spelled);
|
|
76
|
-
}
|
|
77
|
-
if (o.long && "follow-symlinks".startsWith(o.name))
|
|
78
|
-
followsLinks = true;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
return { inPlace, suffixes: [...suffixes], followsLinks };
|
|
82
|
-
}
|
|
83
9
|
export function sedBackupNames(name, suffix) {
|
|
84
10
|
const appended = `${name}${suffix}`;
|
|
85
11
|
return suffix.includes("*") ? [suffix.replace(/\*/g, () => name), appended] : [appended];
|
|
86
12
|
}
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
return
|
|
102
|
-
? [{ candidate: { ...f, spelled: `${f.spelled}${suffix}`, face: "write" }, seats: ["entry"], followed: { operand: f, suffix } }]
|
|
103
|
-
: sedBackupNames(f.spelled, suffix).map((spelled) => ({ candidate: { ...f, spelled, face: "write" }, seats: ["entry"] }))));
|
|
13
|
+
const WORD_PROVENANCES = new Set(["operand", "unlisted_word", "option_value"]);
|
|
14
|
+
function writeOwnersOf(segment) {
|
|
15
|
+
const pos = readProgramPosition(segment.argv);
|
|
16
|
+
const word = (i) => ({ raw: segment.argv[i].raw, spelled: segment.argv[i].folded });
|
|
17
|
+
const owners = [];
|
|
18
|
+
pos.launchers.forEach((l, i) => {
|
|
19
|
+
const row = programRow(l.name);
|
|
20
|
+
const end = pos.launchers[i + 1]?.index ?? pos.program?.index ?? segment.argv.length;
|
|
21
|
+
if (row !== undefined)
|
|
22
|
+
owners.push({ row, words: Array.from({ length: Math.max(0, end - l.index - 1) }, (_, k) => word(l.index + 1 + k)), program: false });
|
|
23
|
+
});
|
|
24
|
+
const program = pos.program !== undefined ? programRow(programBasename(pos.program.name)) : undefined;
|
|
25
|
+
if (program !== undefined && segment.verb !== undefined)
|
|
26
|
+
owners.push({ row: program, words: programArgs(segment), program: true });
|
|
27
|
+
return owners;
|
|
104
28
|
}
|
|
105
|
-
function
|
|
29
|
+
function writeTargetsOf(segment) {
|
|
106
30
|
const out = [];
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
31
|
+
const write = (c, seats, written, child, onlyIfExists) => void out.push({ candidate: { ...c, face: "write" }, ...(seats !== undefined ? { seats } : {}), ...(written !== undefined ? { written } : {}), ...(child === true ? { child } : {}), ...(onlyIfExists !== undefined ? { onlyIfExists } : {}) });
|
|
32
|
+
for (const r of segment.redirects)
|
|
33
|
+
if (r.direction === "out" && r.target !== undefined)
|
|
34
|
+
write(r.target);
|
|
35
|
+
for (const c of segment.reads)
|
|
36
|
+
if (c.provenance === "write_redirect_target")
|
|
37
|
+
write(c);
|
|
38
|
+
const owners = writeOwnersOf(segment).map(({ row, words, program }) => {
|
|
39
|
+
const args = words.map((w) => w.spelled);
|
|
40
|
+
const all = writeReadings(row, args);
|
|
41
|
+
const moved = [...new Set(implementationsOf(row).flatMap((view) => selectedOptions(view, args, "one-implementation")).flatMap((o) => o.writesAt ?? []))];
|
|
42
|
+
return { words, program, all, readings: all.filter((r) => r.written.length > 0 || (r.unexplained && r.operandRole !== undefined)), override: moved.length > 0 ? moved : undefined };
|
|
43
|
+
});
|
|
44
|
+
const edited = new Map();
|
|
45
|
+
for (const c of segment.reads) {
|
|
46
|
+
if (!WORD_PROVENANCES.has(c.provenance))
|
|
47
|
+
continue;
|
|
48
|
+
const placed = owners.filter((o) => o.words.some((w) => w.raw === c.raw));
|
|
49
|
+
const fallback = placed.length === 0 ? owners.filter((o) => o.program) : [];
|
|
50
|
+
for (const o of [...placed, ...fallback]) {
|
|
51
|
+
if (o.readings.length === 0)
|
|
52
|
+
continue;
|
|
53
|
+
const at = new Set(o.words.flatMap((w, i) => (w.raw === c.raw ? [i] : [])));
|
|
54
|
+
const roles = [];
|
|
55
|
+
let readings = 0;
|
|
56
|
+
let unexplained = false;
|
|
57
|
+
for (const r of o.readings) {
|
|
58
|
+
const unplaced = (r.unexplained || at.size === 0) && r.operandRole !== undefined;
|
|
59
|
+
const found = unplaced ? [r.operandRole] : r.written.filter((w) => at.has(w.at)).map((w) => w.role);
|
|
60
|
+
if (found.length === 0)
|
|
61
|
+
continue;
|
|
62
|
+
roles.push(...found);
|
|
63
|
+
readings += 1;
|
|
64
|
+
unexplained ||= unplaced;
|
|
65
|
+
}
|
|
66
|
+
if (roles.length === 0)
|
|
67
|
+
continue;
|
|
68
|
+
const stated = roles.some((role) => (o.override ?? role.seats) !== undefined);
|
|
69
|
+
const seats = stated ? [...new Set(roles.flatMap((role) => o.override ?? role.seats ?? ["landing"]))] : undefined;
|
|
70
|
+
const written = unexplained || roles.some((role) => role.writtenAt === undefined) ? undefined : [...new Set(roles.flatMap((role) => role.writtenAt ?? []))];
|
|
71
|
+
const speculative = readings < o.all.length && roles.every((role) => role.effect === "edit-in-place") ? c : undefined;
|
|
72
|
+
write(c, seats, written, undefined, speculative);
|
|
73
|
+
if (roles.some((role) => role.effect === "edit-in-place") && c.expands === false)
|
|
74
|
+
edited.set(o, [...(edited.get(o) ?? []), { candidate: c, ...(speculative !== undefined ? { onlyIfExists: speculative } : {}) }]);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
for (const o of owners) {
|
|
78
|
+
for (const c of copyDestinationChildren(segment, o.words, o.readings))
|
|
79
|
+
write(c, ["landing", "entry"], undefined, true);
|
|
80
|
+
out.push(...backupTargetsOf(edited.get(o) ?? [], o.readings, o.override));
|
|
113
81
|
}
|
|
114
82
|
return out;
|
|
115
83
|
}
|
|
116
|
-
function
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
return new Set([...first].filter((raw) => !dests.has(raw) && rest.every((s) => s.has(raw))));
|
|
84
|
+
function backupTargetsOf(edited, readings, override) {
|
|
85
|
+
const suffixes = [...new Set(readings.flatMap((r) => r.suffixes))];
|
|
86
|
+
const follows = override !== undefined && !override.includes("entry");
|
|
87
|
+
return edited.flatMap(({ candidate: f, onlyIfExists }) => suffixes.flatMap((suffix) => follows
|
|
88
|
+
? [{ candidate: { ...f, spelled: `${f.spelled}${suffix}`, face: "write" }, seats: ["entry"], followed: { operand: f, suffix }, ...(onlyIfExists !== undefined ? { onlyIfExists } : {}) }]
|
|
89
|
+
: sedBackupNames(f.spelled, suffix).map((spelled) => ({ candidate: { ...f, spelled, face: "write" }, seats: ["entry"], ...(onlyIfExists !== undefined ? { onlyIfExists } : {}) }))));
|
|
123
90
|
}
|
|
124
|
-
function copyDestinationChildren(segment) {
|
|
125
|
-
const candidateOf = (raw) => segment.reads.find((c) => c.raw === raw && (c.provenance
|
|
91
|
+
function copyDestinationChildren(segment, words, readings) {
|
|
92
|
+
const candidateOf = (raw) => segment.reads.find((c) => c.raw === raw && WORD_PROVENANCES.has(c.provenance));
|
|
126
93
|
const children = new Map();
|
|
127
|
-
for (const
|
|
94
|
+
for (const r of readings) {
|
|
95
|
+
if (r.copy === undefined)
|
|
96
|
+
continue;
|
|
97
|
+
const destWord = words[r.copy.destination.at];
|
|
98
|
+
const dest = { raw: destWord.raw, spelled: r.copy.destination.value ?? destWord.spelled };
|
|
128
99
|
const destCandidate = candidateOf(dest.raw);
|
|
129
100
|
if (destCandidate !== undefined && destCandidate.expands !== false)
|
|
130
101
|
continue;
|
|
131
|
-
for (const
|
|
102
|
+
for (const at of r.copy.sources) {
|
|
103
|
+
const src = words[at];
|
|
132
104
|
const source = candidateOf(src.raw);
|
|
133
105
|
if (source !== undefined && source.expands !== false)
|
|
134
106
|
continue;
|
|
@@ -140,31 +112,6 @@ function copyDestinationChildren(segment) {
|
|
|
140
112
|
}
|
|
141
113
|
return [...children.values()];
|
|
142
114
|
}
|
|
143
|
-
function writeTargetsOf(segment) {
|
|
144
|
-
const out = [];
|
|
145
|
-
const write = (c, seats, written, child) => void out.push({ candidate: { ...c, face: "write" }, ...(seats !== undefined ? { seats } : {}), ...(written !== undefined ? { written } : {}), ...(child === true ? { child } : {}) });
|
|
146
|
-
for (const r of segment.redirects)
|
|
147
|
-
if (r.direction === "out" && r.target !== undefined)
|
|
148
|
-
write(r.target);
|
|
149
|
-
for (const c of segment.reads)
|
|
150
|
-
if (c.provenance === "write_redirect_target")
|
|
151
|
-
write(c);
|
|
152
|
-
const verb = segment.verb?.basename;
|
|
153
|
-
if (verb !== undefined && WRITE_COMMAND_SET.has(verb) && (verb !== "sed" || sedOptionsOf(segment).inPlace)) {
|
|
154
|
-
const seats = OPERAND_SEATS[verb]?.(segment);
|
|
155
|
-
const sourceWritten = SOURCE_WRITTEN[verb];
|
|
156
|
-
const sources = sourceWritten !== undefined ? copySourceWordsOf(segment) : undefined;
|
|
157
|
-
for (const c of segment.reads)
|
|
158
|
-
if (c.provenance === "operand" || c.provenance === "unlisted_word" || c.provenance === "option_value")
|
|
159
|
-
write(c, seats, sources?.has(c.raw) === true ? sourceWritten : undefined);
|
|
160
|
-
if (verb === "cp" || verb === "mv")
|
|
161
|
-
for (const c of copyDestinationChildren(segment))
|
|
162
|
-
write(c, seats, undefined, true);
|
|
163
|
-
if (verb === "sed")
|
|
164
|
-
out.push(...sedBackupTargets(segment));
|
|
165
|
-
}
|
|
166
|
-
return out;
|
|
167
|
-
}
|
|
168
115
|
export function judgedCandidatesOf(segment) {
|
|
169
116
|
const reads = segment.reads.filter((c) => c.provenance !== "write_redirect_target");
|
|
170
117
|
const writes = writeTargetsOf(segment);
|
|
@@ -1,10 +1,42 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The shell text a program hands back to the shell: the text a shell program runs INSTEAD of its argv (`sh -c S`,
|
|
3
|
-
* `eval S
|
|
4
|
-
* ({@link codeBodies}). The plan marks a segment carrying either as a shell
|
|
5
|
-
* module is the one reader of which operand that text is.
|
|
3
|
+
* `eval S`, or its standard input where the argv names no script — {@link reentryText}), and the code bodies a builtin
|
|
4
|
+
* keeps AMONG its operands to run later ({@link codeBodies}). The plan marks a segment carrying either as a shell
|
|
5
|
+
* re-entry (`shell-read-plan.ts`); this module is the one reader of which operand — or which input — that text is.
|
|
6
|
+
*/
|
|
7
|
+
import { type LexedSegment, type LexedWord } from "../../shell/lex.js";
|
|
8
|
+
/**
|
|
9
|
+
* The code bodies a builtin keeps among its operands, one per body; empty for every other program. The program read
|
|
10
|
+
* model's row says where the body is: the action of a `trap` (its first operand after the options), the value of an
|
|
11
|
+
* `alias` definition, the shell-text option of `mapfile` / `readarray` / `compgen` (`-C`; compgen runs it at once, to
|
|
12
|
+
* list completions). The text is re-entry text as `eval`'s operand is — the segment re-enters the shell, the command's
|
|
13
|
+
* read set is not established, and each body is its own nested plan whose deny hits still refuse (one unreadable body
|
|
14
|
+
* cannot hide another's). What a body does when it runs is not read from the text, so a body of any content asks: a
|
|
15
|
+
* harmless `trap 'echo bye' EXIT` in front of a read asks as `eval 'echo bye'` does. The builtin's own words stay the
|
|
16
|
+
* candidates they are for any unlisted program. An absent body is no re-entry (`trap - SIG` resets, `trap '' SIG`
|
|
17
|
+
* ignores, `mapfile` without `-C`); an EMPTY alias value or `-C` value is still one. Over-reading is the direction
|
|
18
|
+
* taken: a display option (`trap -p SIG`) and a lone operand (`trap SIG`) still count their operand as the body.
|
|
6
19
|
*/
|
|
7
|
-
/** The code bodies a builtin keeps among its operands, one per body; empty for every other program. */
|
|
8
20
|
export declare function codeBodies(basename: string, args: readonly string[]): readonly string[];
|
|
9
|
-
/** The shell text a re-entry program runs:
|
|
10
|
-
|
|
21
|
+
/** The shell text a re-entry program runs, by its row: the shell-text value its walk reads for a shell (`-c`'s), its
|
|
22
|
+
* words joined for `eval`, its non-option words for `parallel`; none for `source` / `.` (a script file operand), for a
|
|
23
|
+
* shell whose first operand (a script file) or `--` comes before any text, or for a program that is no re-entry. Where
|
|
24
|
+
* the argv names no text and the program runs its standard input ({@link scriptSourceOf}: the row runs it unnamed, or
|
|
25
|
+
* the script file named is standard input), the text is `stdin` — one text that may arrive there, as the caller spells it. */
|
|
26
|
+
export declare function reentryText(basename: string, args: readonly string[], stdin?: string): string | undefined;
|
|
27
|
+
/** The argv index of the SCRIPT FILE a shell runs where its argv carries no text ({@link scriptSourceOf}). */
|
|
28
|
+
export declare function scriptFileAt(basename: string, args: readonly string[]): number | undefined;
|
|
29
|
+
/** One text that may arrive on a segment's standard input: `from` = its source as written (a here-document's body,
|
|
30
|
+
* `<<<WORD`, `<FILE`, `<&N`, the segment the pipe comes from); `literal` = the text, where the command spells it. */
|
|
31
|
+
export interface StdinText {
|
|
32
|
+
readonly from: string;
|
|
33
|
+
readonly literal?: string;
|
|
34
|
+
}
|
|
35
|
+
/** What may arrive on a segment's standard input: each here-document (its descriptor is not read: every one counts) —
|
|
36
|
+
* its body, literal when quoted or when nothing in it expands — each here-string (literal when its word is), `<` file
|
|
37
|
+
* or `<&` descriptor written to no other descriptor, and the pipe it is reached through (`piped`) unless one of those
|
|
38
|
+
* three replaces it. */
|
|
39
|
+
export declare function stdinTextsOf(segment: LexedSegment, piped: StdinText | undefined): StdinText[];
|
|
40
|
+
/** What one segment (its argv) writes into the pipe after it: the text a printer spells ({@link printedText}) when its
|
|
41
|
+
* program word and every argument are literal, else only the segment as written. */
|
|
42
|
+
export declare function pipedTextOf(segment: LexedSegment, argv: readonly LexedWord[]): StdinText;
|