@hawkeyexl/manni 4.3.0 → 4.4.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/README.md +9 -1
- package/dist/batch-DFRCLY77.js +36 -0
- package/dist/build-Q5C6SX5J.js +31 -0
- package/dist/calibrate-XQILKXS3.js +31 -0
- package/dist/capture-JQMOC3BW.js +32 -0
- package/dist/check-HYNGXYJV.js +18 -0
- package/dist/check-HYNGXYJV.js.map +1 -0
- package/dist/check-NFZQLJKN.js +27 -0
- package/dist/check-NFZQLJKN.js.map +1 -0
- package/dist/check-U54XV6BP.js +40 -0
- package/dist/check-U54XV6BP.js.map +1 -0
- package/dist/chunk-252LNOEG.js +622 -0
- package/dist/chunk-252LNOEG.js.map +1 -0
- package/dist/chunk-2H7UOFLK.js +11 -0
- package/dist/chunk-2H7UOFLK.js.map +1 -0
- package/dist/chunk-2U3ORT6D.js +230 -0
- package/dist/chunk-2U3ORT6D.js.map +1 -0
- package/dist/chunk-3AFUD75K.js +23 -0
- package/dist/chunk-3AFUD75K.js.map +1 -0
- package/dist/{chunk-3R23C5DT.js → chunk-4TSWU2SU.js} +2 -14
- package/dist/chunk-4TSWU2SU.js.map +1 -0
- package/dist/chunk-5KHIFVD2.js +1192 -0
- package/dist/chunk-5KHIFVD2.js.map +1 -0
- package/dist/chunk-5YXBLLAC.js +2165 -0
- package/dist/chunk-5YXBLLAC.js.map +1 -0
- package/dist/chunk-644JVKJ4.js +3872 -0
- package/dist/chunk-644JVKJ4.js.map +1 -0
- package/dist/chunk-6OYEI2VV.js +88 -0
- package/dist/chunk-6OYEI2VV.js.map +1 -0
- package/dist/chunk-77AINBYM.js +19 -0
- package/dist/chunk-77AINBYM.js.map +1 -0
- package/dist/chunk-77HX34KD.js +1287 -0
- package/dist/chunk-77HX34KD.js.map +1 -0
- package/dist/{chunk-MOQXWXSG.js → chunk-7QMCHA4N.js} +32 -240
- package/dist/chunk-7QMCHA4N.js.map +1 -0
- package/dist/chunk-BUXDLRDT.js +455 -0
- package/dist/chunk-BUXDLRDT.js.map +1 -0
- package/dist/chunk-C6OH6CNF.js +388 -0
- package/dist/chunk-C6OH6CNF.js.map +1 -0
- package/dist/chunk-CK7SKVHR.js +1042 -0
- package/dist/chunk-CK7SKVHR.js.map +1 -0
- package/dist/chunk-CYWHFSRT.js +6737 -0
- package/dist/chunk-CYWHFSRT.js.map +1 -0
- package/dist/chunk-ERMSO34F.js +17 -0
- package/dist/chunk-ERMSO34F.js.map +1 -0
- package/dist/{chunk-IOYXPBCL.js → chunk-G3T47CFV.js} +1 -7
- package/dist/{chunk-IOYXPBCL.js.map → chunk-G3T47CFV.js.map} +1 -1
- package/dist/chunk-G7NJSREW.js +979 -0
- package/dist/chunk-G7NJSREW.js.map +1 -0
- package/dist/chunk-GLP5PSXB.js +72 -0
- package/dist/chunk-GLP5PSXB.js.map +1 -0
- package/dist/chunk-H2DLH2JI.js +198 -0
- package/dist/chunk-H2DLH2JI.js.map +1 -0
- package/dist/chunk-H6FZ66XR.js +634 -0
- package/dist/chunk-H6FZ66XR.js.map +1 -0
- package/dist/{chunk-CI2GOU3A.js → chunk-IEQHSCZG.js} +56 -137
- package/dist/chunk-IEQHSCZG.js.map +1 -0
- package/dist/chunk-IKEWBMCN.js +98 -0
- package/dist/chunk-IKEWBMCN.js.map +1 -0
- package/dist/chunk-JOHKIAVH.js +5440 -0
- package/dist/chunk-JOHKIAVH.js.map +1 -0
- package/dist/chunk-JVTNMVYU.js +10921 -0
- package/dist/chunk-JVTNMVYU.js.map +1 -0
- package/dist/chunk-K3D6FPV6.js +164 -0
- package/dist/chunk-K3D6FPV6.js.map +1 -0
- package/dist/{chunk-MFDD3YGU.js → chunk-KCGV52AM.js} +69 -48
- package/dist/{chunk-MFDD3YGU.js.map → chunk-KCGV52AM.js.map} +1 -1
- package/dist/chunk-LAKAWRV5.js +1651 -0
- package/dist/chunk-LAKAWRV5.js.map +1 -0
- package/dist/chunk-LJ2RDH3F.js +49 -0
- package/dist/chunk-LJ2RDH3F.js.map +1 -0
- package/dist/chunk-M2JCK5F6.js +805 -0
- package/dist/chunk-M2JCK5F6.js.map +1 -0
- package/dist/chunk-MNH7QO4Z.js +15 -0
- package/dist/chunk-MNH7QO4Z.js.map +1 -0
- package/dist/{chunk-RVVCA4WL.js → chunk-MYC3O5EJ.js} +4 -4
- package/dist/chunk-MYMBCTDE.js +387 -0
- package/dist/chunk-MYMBCTDE.js.map +1 -0
- package/dist/chunk-NCPWXHDO.js +683 -0
- package/dist/chunk-NCPWXHDO.js.map +1 -0
- package/dist/chunk-NWZ2SLYL.js +2957 -0
- package/dist/chunk-NWZ2SLYL.js.map +1 -0
- package/dist/chunk-OBFENF3U.js +934 -0
- package/dist/chunk-OBFENF3U.js.map +1 -0
- package/dist/chunk-OWBRDNRZ.js +16 -0
- package/dist/chunk-OWBRDNRZ.js.map +1 -0
- package/dist/{chunk-KTSJ2ZHA.js → chunk-PAU352SV.js} +5 -3
- package/dist/{chunk-KTSJ2ZHA.js.map → chunk-PAU352SV.js.map} +1 -1
- package/dist/chunk-PCE7QHXJ.js +68 -0
- package/dist/chunk-PCE7QHXJ.js.map +1 -0
- package/dist/chunk-PCHZY2I7.js +299 -0
- package/dist/chunk-PCHZY2I7.js.map +1 -0
- package/dist/chunk-PHF3D7ER.js +1042 -0
- package/dist/chunk-PHF3D7ER.js.map +1 -0
- package/dist/chunk-Q35YIDBV.js +1202 -0
- package/dist/chunk-Q35YIDBV.js.map +1 -0
- package/dist/chunk-RLR4TKUZ.js +4398 -0
- package/dist/chunk-RLR4TKUZ.js.map +1 -0
- package/dist/chunk-RNVYGWIC.js +30 -0
- package/dist/chunk-RNVYGWIC.js.map +1 -0
- package/dist/chunk-SE4JN5XN.js +19 -0
- package/dist/chunk-SE4JN5XN.js.map +1 -0
- package/dist/chunk-TIUTIOFB.js +25 -0
- package/dist/chunk-TIUTIOFB.js.map +1 -0
- package/dist/chunk-TU2WUBVZ.js +2 -0
- package/dist/chunk-TU2WUBVZ.js.map +1 -0
- package/dist/chunk-TZOYZK2E.js +98 -0
- package/dist/chunk-TZOYZK2E.js.map +1 -0
- package/dist/chunk-UKZNLVJW.js +17 -0
- package/dist/chunk-UKZNLVJW.js.map +1 -0
- package/dist/chunk-UZC7LDAQ.js +178 -0
- package/dist/chunk-UZC7LDAQ.js.map +1 -0
- package/dist/chunk-V46OILVC.js +3090 -0
- package/dist/chunk-V46OILVC.js.map +1 -0
- package/dist/chunk-WQE54PNH.js +1975 -0
- package/dist/chunk-WQE54PNH.js.map +1 -0
- package/dist/chunk-X3BPBDGQ.js +52 -0
- package/dist/chunk-X3BPBDGQ.js.map +1 -0
- package/dist/chunk-XSLV76YK.js +54 -0
- package/dist/chunk-XSLV76YK.js.map +1 -0
- package/dist/{chunk-QROCFW5S.js → chunk-XYLUCIKQ.js} +3 -3
- package/dist/chunk-YEUYKR62.js +72 -0
- package/dist/chunk-YEUYKR62.js.map +1 -0
- package/dist/{chunk-ESJ5W7EV.js → chunk-YFQBFNQS.js} +8 -94
- package/dist/chunk-YFQBFNQS.js.map +1 -0
- package/dist/chunk-YIL4XLLJ.js +1661 -0
- package/dist/chunk-YIL4XLLJ.js.map +1 -0
- package/dist/chunk-YNHZHSSM.js +41 -0
- package/dist/chunk-YNHZHSSM.js.map +1 -0
- package/dist/chunk-YTCZRLTU.js +96 -0
- package/dist/chunk-YTCZRLTU.js.map +1 -0
- package/dist/chunk-YYBFZLAJ.js +374 -0
- package/dist/chunk-YYBFZLAJ.js.map +1 -0
- package/dist/chunk-ZXRAE55H.js +31 -0
- package/dist/chunk-ZXRAE55H.js.map +1 -0
- package/dist/cli-2QPR2OZG.js +232 -0
- package/dist/cli-2QPR2OZG.js.map +1 -0
- package/dist/cli-4IOPGZHR.js +1203 -0
- package/dist/cli-4IOPGZHR.js.map +1 -0
- package/dist/cli-5FL73WHA.js +346 -0
- package/dist/cli-5FL73WHA.js.map +1 -0
- package/dist/cli-C262QOWM.js +739 -0
- package/dist/cli-C262QOWM.js.map +1 -0
- package/dist/cli-LJLLIQB2.js +423 -0
- package/dist/cli-LJLLIQB2.js.map +1 -0
- package/dist/cli-OFPZ5MAS.js +626 -0
- package/dist/cli-OFPZ5MAS.js.map +1 -0
- package/dist/cli-SHSMSRQY.js +28 -0
- package/dist/cli-SHSMSRQY.js.map +1 -0
- package/dist/cli-SRHFUZHA.js +473 -0
- package/dist/cli-SRHFUZHA.js.map +1 -0
- package/dist/cli-URZQXL4K.js +552 -0
- package/dist/cli-URZQXL4K.js.map +1 -0
- package/dist/cli.d.ts +12 -2
- package/dist/cli.js +109 -7651
- package/dist/cli.js.map +1 -1
- package/dist/docmeta.js +15 -3
- package/dist/docmeta.js.map +1 -1
- package/dist/github-FE6KCNYJ.js +27 -0
- package/dist/github-FE6KCNYJ.js.map +1 -0
- package/dist/github-QXL3VMK2.js +32 -0
- package/dist/github-QXL3VMK2.js.map +1 -0
- package/dist/index.d.ts +65 -3
- package/dist/index.js +407 -337
- package/dist/index.js.map +1 -1
- package/dist/json-7WV7KL6H.js +17 -0
- package/dist/json-7WV7KL6H.js.map +1 -0
- package/dist/json-SGZ6ZRFD.js +35 -0
- package/dist/json-SGZ6ZRFD.js.map +1 -0
- package/dist/lint-DL4LJRAY.js +28 -0
- package/dist/lint-DL4LJRAY.js.map +1 -0
- package/dist/list-6BAXTZUM.js +29 -0
- package/dist/list-6BAXTZUM.js.map +1 -0
- package/dist/list-7BKNYPSB.js +31 -0
- package/dist/list-7BKNYPSB.js.map +1 -0
- package/dist/page-3U2GCFNE.js +41 -0
- package/dist/page-3U2GCFNE.js.map +1 -0
- package/dist/picker-3XF36MJF.js +14 -0
- package/dist/picker-3XF36MJF.js.map +1 -0
- package/dist/pretty-2H57QVTY.js +43 -0
- package/dist/pretty-2H57QVTY.js.map +1 -0
- package/dist/pretty-N6DO4OOL.js +37 -0
- package/dist/pretty-N6DO4OOL.js.map +1 -0
- package/dist/reporters-M6LJ2IYU.js +21 -0
- package/dist/reporters-M6LJ2IYU.js.map +1 -0
- package/dist/reporters-N6GHXSS7.js +42 -0
- package/dist/reporters-N6GHXSS7.js.map +1 -0
- package/dist/reporters-UCNEV7PH.js +71 -0
- package/dist/reporters-UCNEV7PH.js.map +1 -0
- package/dist/run-XZDAREJE.js +33 -0
- package/dist/run-XZDAREJE.js.map +1 -0
- package/dist/term-5ICGKPC3.js +1592 -0
- package/dist/term-5ICGKPC3.js.map +1 -0
- package/dist/types-Y6TXHM7M.js +10 -0
- package/dist/types-Y6TXHM7M.js.map +1 -0
- package/dist/validate-GQRM77YN.js +16 -0
- package/dist/validate-GQRM77YN.js.map +1 -0
- package/package.json +1 -1
- package/plugin/manni/.claude-plugin/plugin.json +9 -0
- package/plugin/manni/hooks/hooks.json +52 -0
- package/plugin/manni/skills/check/SKILL.md +20 -0
- package/plugin/manni/skills/evals/SKILL.md +27 -0
- package/plugin/manni/skills/fix/SKILL.md +56 -0
- package/plugin/manni/skills/setup/SKILL.md +41 -0
- package/dist/batch-7JJWXWSF.js +0 -18
- package/dist/calibrate-IA2ARFMH.js +0 -13
- package/dist/capture-C32JXIFN.js +0 -15
- package/dist/chunk-3R23C5DT.js.map +0 -1
- package/dist/chunk-CI2GOU3A.js.map +0 -1
- package/dist/chunk-DP2ZA3WH.js +0 -26518
- package/dist/chunk-DP2ZA3WH.js.map +0 -1
- package/dist/chunk-ESJ5W7EV.js.map +0 -1
- package/dist/chunk-MOQXWXSG.js.map +0 -1
- package/dist/chunk-O6RVQKK6.js +0 -27170
- package/dist/chunk-O6RVQKK6.js.map +0 -1
- package/dist/picker-6HRMETE7.js +0 -12
- package/plugin/tracevals/.claude-plugin/plugin.json +0 -9
- package/plugin/tracevals/hooks/hooks.json +0 -17
- /package/dist/{batch-7JJWXWSF.js.map → batch-DFRCLY77.js.map} +0 -0
- /package/dist/{calibrate-IA2ARFMH.js.map → build-Q5C6SX5J.js.map} +0 -0
- /package/dist/{capture-C32JXIFN.js.map → calibrate-XQILKXS3.js.map} +0 -0
- /package/dist/{picker-6HRMETE7.js.map → capture-JQMOC3BW.js.map} +0 -0
- /package/dist/{chunk-RVVCA4WL.js.map → chunk-MYC3O5EJ.js.map} +0 -0
- /package/dist/{chunk-QROCFW5S.js.map → chunk-XYLUCIKQ.js.map} +0 -0
|
@@ -0,0 +1,2165 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
CITATIONS_KEY,
|
|
4
|
+
GIT_UNAVAILABLE_COMMIT,
|
|
5
|
+
SHALLOW_RECOMMIT,
|
|
6
|
+
assertNoOrphanJoins,
|
|
7
|
+
assertNoOrphans,
|
|
8
|
+
buildSourceIndex,
|
|
9
|
+
checkCitations,
|
|
10
|
+
claimWords,
|
|
11
|
+
encryptSourcePath,
|
|
12
|
+
gitClient,
|
|
13
|
+
historyOf,
|
|
14
|
+
joinHits,
|
|
15
|
+
prepareRun,
|
|
16
|
+
readSource,
|
|
17
|
+
readTarget,
|
|
18
|
+
resolveCiteRun,
|
|
19
|
+
sayNotices,
|
|
20
|
+
sharesSentence,
|
|
21
|
+
sidecarsFor,
|
|
22
|
+
sourceIndexFor,
|
|
23
|
+
wordShare
|
|
24
|
+
} from "./chunk-LAKAWRV5.js";
|
|
25
|
+
import {
|
|
26
|
+
applyMarkerMoves,
|
|
27
|
+
blockMatches,
|
|
28
|
+
listOf,
|
|
29
|
+
misplacedMarkers,
|
|
30
|
+
noUnitAt,
|
|
31
|
+
normalizeWhitespace,
|
|
32
|
+
otherClaimSpans,
|
|
33
|
+
shortSrc,
|
|
34
|
+
spellAt,
|
|
35
|
+
spellElsewhere,
|
|
36
|
+
unitAt
|
|
37
|
+
} from "./chunk-252LNOEG.js";
|
|
38
|
+
import {
|
|
39
|
+
ANY_FENCE,
|
|
40
|
+
CiteError,
|
|
41
|
+
MAX_IDS_PER_MARKER,
|
|
42
|
+
anchoredLines,
|
|
43
|
+
bodyLineOf,
|
|
44
|
+
citationInputs,
|
|
45
|
+
detectEol,
|
|
46
|
+
fenceSpanAt,
|
|
47
|
+
formatSrc,
|
|
48
|
+
formatStatement,
|
|
49
|
+
isMarkerLine,
|
|
50
|
+
lineAt,
|
|
51
|
+
markerIndent,
|
|
52
|
+
offsetOfLine,
|
|
53
|
+
parseSrc,
|
|
54
|
+
pickExtractor,
|
|
55
|
+
rangeLines,
|
|
56
|
+
readPage,
|
|
57
|
+
resolveSeverity,
|
|
58
|
+
respellEdit,
|
|
59
|
+
respellStatement,
|
|
60
|
+
sliceLines,
|
|
61
|
+
sourceRange,
|
|
62
|
+
spellSource,
|
|
63
|
+
tooWide,
|
|
64
|
+
unitHolding
|
|
65
|
+
} from "./chunk-G7NJSREW.js";
|
|
66
|
+
import {
|
|
67
|
+
ensureEncryptionKey
|
|
68
|
+
} from "./chunk-CYWHFSRT.js";
|
|
69
|
+
import {
|
|
70
|
+
STDIN_REQUIRES_AS
|
|
71
|
+
} from "./chunk-V46OILVC.js";
|
|
72
|
+
import {
|
|
73
|
+
ENCRYPTION_KEY_ENV,
|
|
74
|
+
hashLines,
|
|
75
|
+
heldManifest,
|
|
76
|
+
isMissing,
|
|
77
|
+
lineSpec,
|
|
78
|
+
parseLines,
|
|
79
|
+
pinOfLines,
|
|
80
|
+
readManifestValue,
|
|
81
|
+
rebaseable,
|
|
82
|
+
removeManifestKey,
|
|
83
|
+
settle,
|
|
84
|
+
spellLines,
|
|
85
|
+
spliceManifestValue,
|
|
86
|
+
splitLines,
|
|
87
|
+
toBodyLines,
|
|
88
|
+
toFileLines
|
|
89
|
+
} from "./chunk-RLR4TKUZ.js";
|
|
90
|
+
import {
|
|
91
|
+
DocmetaError,
|
|
92
|
+
STDIN_LABEL,
|
|
93
|
+
STDIN_TOKEN,
|
|
94
|
+
extractorByName,
|
|
95
|
+
locateFrontmatter,
|
|
96
|
+
writeFileAtomic
|
|
97
|
+
} from "./chunk-JVTNMVYU.js";
|
|
98
|
+
import {
|
|
99
|
+
isEncryptedValue
|
|
100
|
+
} from "./chunk-Q35YIDBV.js";
|
|
101
|
+
|
|
102
|
+
// src/cite/commands/add.ts
|
|
103
|
+
import { readFile as readFile2 } from "node:fs/promises";
|
|
104
|
+
import { relative, resolve } from "node:path";
|
|
105
|
+
|
|
106
|
+
// src/cite/core/mint.ts
|
|
107
|
+
var COMMIT_PATTERN = /^[0-9a-f]{7,64}$/;
|
|
108
|
+
async function commitFor(opts) {
|
|
109
|
+
if (opts.commitSha === false) return void 0;
|
|
110
|
+
if (typeof opts.commitSha === "string") {
|
|
111
|
+
if (!COMMIT_PATTERN.test(opts.commitSha)) {
|
|
112
|
+
throw new CiteError(
|
|
113
|
+
`Invalid commit-sha "${opts.commitSha}": expected 7 to 64 lowercase hex digits.`
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
return opts.commitSha;
|
|
117
|
+
}
|
|
118
|
+
const client = opts.gitClient;
|
|
119
|
+
if (client === void 0 || !await client.available()) return void 0;
|
|
120
|
+
return await client.head() ?? void 0;
|
|
121
|
+
}
|
|
122
|
+
async function assertCommitHolds(opts, commit, path, range, pin, key) {
|
|
123
|
+
const client = opts.gitClient;
|
|
124
|
+
if (client === void 0 || !await client.available()) return;
|
|
125
|
+
const history = await historyOf(client, commit, path, range, pin, key, void 0);
|
|
126
|
+
if (history.kind !== "never-true") return;
|
|
127
|
+
throw new CiteError(
|
|
128
|
+
`${opts.src} is not committed: ${commit.slice(0, 7)} does not contain those lines. Commit the source, or use --no-commit-sha.`
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
function refusalFor(reason, path, encrypted) {
|
|
132
|
+
switch (reason) {
|
|
133
|
+
case "untracked":
|
|
134
|
+
return new CiteError(
|
|
135
|
+
encrypted ? `No tracked file matches ${path} (wrong --root?).` : `Source not found: ${path} is not a tracked file under the root.`
|
|
136
|
+
);
|
|
137
|
+
case "no-key":
|
|
138
|
+
return new CiteError(`${path} is encrypted, and no encryption key is available to decrypt it.`);
|
|
139
|
+
case "undecryptable":
|
|
140
|
+
return new CiteError(`${path} does not decrypt under the current key.`);
|
|
141
|
+
case "unreadable":
|
|
142
|
+
return new CiteError(`Source not readable: ${path} could not be read.`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
async function mintCitation(opts) {
|
|
146
|
+
const range = parseSrc(opts.src);
|
|
147
|
+
const wide = range.start === void 0 || range.end === void 0 ? void 0 : tooWide({ start: range.start, end: range.end });
|
|
148
|
+
if (wide !== void 0) throw new CiteError(`Invalid range "${opts.src}": it ${wide}.`);
|
|
149
|
+
const { key } = opts;
|
|
150
|
+
const encrypt = opts.encrypt === true || range.encrypted;
|
|
151
|
+
if (encrypt && !range.encrypted && key === void 0) {
|
|
152
|
+
throw new CiteError(
|
|
153
|
+
`${opts.src} must be encrypted, and no encryption key is available. Run \`manni key set\`, or set ${ENCRYPTION_KEY_ENV}.`
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
const index = opts.sourceIndex ?? await buildSourceIndex(opts.root, { gitClient: opts.gitClient });
|
|
157
|
+
const read = await readSource(opts.root, index, range, key);
|
|
158
|
+
if (read.kind === "missing") throw refusalFor(read.reason, range.path, range.encrypted);
|
|
159
|
+
const joined = sliceLines(splitLines(read.text), range, range.path);
|
|
160
|
+
const pinKey = encrypt ? key : void 0;
|
|
161
|
+
const file = pinKey !== void 0 && !range.encrypted ? encryptSourcePath(read.resolvedPath, pinKey) : range.path;
|
|
162
|
+
const lines = rangeLines(range);
|
|
163
|
+
const commitSha = await commitFor(opts);
|
|
164
|
+
const integrity = hashLines(joined, pinKey);
|
|
165
|
+
if (commitSha !== void 0) {
|
|
166
|
+
await assertCommitHolds(opts, commitSha, read.resolvedPath, range, integrity, pinKey);
|
|
167
|
+
}
|
|
168
|
+
const source = { file, integrity };
|
|
169
|
+
const ordered = {
|
|
170
|
+
file: source.file,
|
|
171
|
+
...lines === void 0 ? {} : { lines },
|
|
172
|
+
integrity: source.integrity,
|
|
173
|
+
...commitSha === void 0 ? {} : { "commit-sha": commitSha }
|
|
174
|
+
};
|
|
175
|
+
return {
|
|
176
|
+
...opts.id === void 0 ? {} : { id: opts.id },
|
|
177
|
+
...opts.claim === void 0 ? {} : { claim: opts.claim },
|
|
178
|
+
source: ordered,
|
|
179
|
+
...opts.quote === void 0 ? {} : { quote: opts.quote }
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// src/cite/core/shift.ts
|
|
184
|
+
function shiftedEntries(opts) {
|
|
185
|
+
const { citations, at, delta, bodyLine, label } = opts;
|
|
186
|
+
const out = { frontmatter: [], manifest: /* @__PURE__ */ new Map() };
|
|
187
|
+
if (at.length === 0) return out;
|
|
188
|
+
for (const { citation, origin } of citations) {
|
|
189
|
+
const spec = citation.claim?.lines;
|
|
190
|
+
const recorded = spec === void 0 ? void 0 : parseLines(spec);
|
|
191
|
+
if (recorded === void 0) continue;
|
|
192
|
+
const file = toFileLines(recorded, bodyLine);
|
|
193
|
+
const inside = at.find(
|
|
194
|
+
(line) => delta === 1 ? line > file.start && line <= file.end : line >= file.start && line <= file.end
|
|
195
|
+
);
|
|
196
|
+
if (inside !== void 0) {
|
|
197
|
+
const whose = citation.id === void 0 ? `the claim at ${spellAt(file)}` : `the claim of ${citation.id} (${spellAt(file)})`;
|
|
198
|
+
const why = delta === 1 ? "A marker there would change its pin." : "Removing the marker there would change its pin.";
|
|
199
|
+
throw new CiteError(`${label}:${String(inside)} is inside ${whose}. ${why}`);
|
|
200
|
+
}
|
|
201
|
+
const lines = at.filter((line) => delta === 1 ? line <= file.start : line < file.start).length;
|
|
202
|
+
if (lines === 0) continue;
|
|
203
|
+
const moved = lineSpec({
|
|
204
|
+
start: recorded.start + lines * delta,
|
|
205
|
+
end: recorded.end + lines * delta
|
|
206
|
+
});
|
|
207
|
+
if (origin.kind === "manifest") out.manifest.set(origin.index, moved);
|
|
208
|
+
else out.frontmatter.push({ index: origin.index, lines: moved });
|
|
209
|
+
}
|
|
210
|
+
return out;
|
|
211
|
+
}
|
|
212
|
+
function withClaimLines(entry, lines) {
|
|
213
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) return entry;
|
|
214
|
+
const claim = entry.claim;
|
|
215
|
+
if (typeof claim !== "object" || claim === null || Array.isArray(claim)) return entry;
|
|
216
|
+
return { ...entry, claim: { ...claim, lines } };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// src/cite/core/manifest.ts
|
|
220
|
+
import { readFile } from "node:fs/promises";
|
|
221
|
+
import { posix } from "node:path";
|
|
222
|
+
import { LineCounter, isMap, isNode, isScalar, isSeq, parseDocument } from "yaml";
|
|
223
|
+
|
|
224
|
+
// src/cite/core/write.ts
|
|
225
|
+
function extractorFor(format) {
|
|
226
|
+
const extractor = extractorByName(format);
|
|
227
|
+
if (!extractor) throw new CiteError(`Unknown format "${format}".`);
|
|
228
|
+
return extractor;
|
|
229
|
+
}
|
|
230
|
+
function readsFencedFrontmatter(extractor, label) {
|
|
231
|
+
return extractor.extract("---\nprobe: 1\n---\n", label).fenced === true;
|
|
232
|
+
}
|
|
233
|
+
function appendFrontmatterCitation(content, format, citation, filePath) {
|
|
234
|
+
const label = filePath ?? "the page";
|
|
235
|
+
const extractor = extractorFor(format);
|
|
236
|
+
if (!extractor.apply || !readsFencedFrontmatter(extractor, label)) {
|
|
237
|
+
throw new CiteError(
|
|
238
|
+
`${label} has no frontmatter to write to; keep its citations in a manifest instead.`
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
const extracted = extractor.extract(content, label);
|
|
242
|
+
if (locateFrontmatter(content)?.flavor === "toml") {
|
|
243
|
+
throw new CiteError(
|
|
244
|
+
`${label} has TOML frontmatter; manni cannot add a citations table without re-emitting the whole block, which would drop its comments. Add the entry by hand.`
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
const raw = extracted.data.citations ?? [];
|
|
248
|
+
if (!Array.isArray(raw)) {
|
|
249
|
+
throw new CiteError(`${label}: \`citations\` is not a list; edit it by hand.`);
|
|
250
|
+
}
|
|
251
|
+
const existing = raw;
|
|
252
|
+
const entry = entryObject(citation);
|
|
253
|
+
try {
|
|
254
|
+
return extractor.apply(content, { citations: [...existing, entry] });
|
|
255
|
+
} catch (e) {
|
|
256
|
+
if (e instanceof DocmetaError) throw new CiteError(`${label}: ${e.message}`);
|
|
257
|
+
throw e;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
function removeFrontmatterCitations(content, format, drop, filePath) {
|
|
261
|
+
const label = filePath ?? "the page";
|
|
262
|
+
const extractor = extractorFor(format);
|
|
263
|
+
if (!extractor.apply || !readsFencedFrontmatter(extractor, label)) {
|
|
264
|
+
throw new CiteError(
|
|
265
|
+
`${label} has no frontmatter to write to; keep its citations in a manifest instead.`
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
if (locateFrontmatter(content)?.flavor === "toml") {
|
|
269
|
+
throw new CiteError(
|
|
270
|
+
`${label} has TOML frontmatter; manni cannot rewrite a citations table without re-emitting the whole block, which would drop its comments. Remove the entry by hand.`
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
const raw = extractor.extract(content, label).data.citations ?? [];
|
|
274
|
+
if (!Array.isArray(raw)) {
|
|
275
|
+
throw new CiteError(`${label}: \`citations\` is not a list; edit it by hand.`);
|
|
276
|
+
}
|
|
277
|
+
const existing = raw;
|
|
278
|
+
const kept = existing.filter((_entry, index) => !drop.has(index));
|
|
279
|
+
try {
|
|
280
|
+
return kept.length === 0 ? extractor.apply(content, {}, { deletions: ["citations"] }) : extractor.apply(content, { citations: kept });
|
|
281
|
+
} catch (e) {
|
|
282
|
+
if (e instanceof DocmetaError) throw new CiteError(`${label}: ${e.message}`);
|
|
283
|
+
throw e;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
function applyEdits(content, edits) {
|
|
287
|
+
const sorted = [...edits].sort((a, b) => a.start - b.start);
|
|
288
|
+
let out = "";
|
|
289
|
+
let at = 0;
|
|
290
|
+
for (const edit of sorted) {
|
|
291
|
+
if (edit.end < edit.start) {
|
|
292
|
+
throw new Error(`cite: backwards edit [${String(edit.start)}, ${String(edit.end)})`);
|
|
293
|
+
}
|
|
294
|
+
if (edit.start < at) {
|
|
295
|
+
throw new Error(`cite: overlapping edits at ${String(edit.start)}`);
|
|
296
|
+
}
|
|
297
|
+
out += content.slice(at, edit.start) + edit.text;
|
|
298
|
+
at = edit.end;
|
|
299
|
+
}
|
|
300
|
+
return out + content.slice(at);
|
|
301
|
+
}
|
|
302
|
+
function lineRemovals(content, lines) {
|
|
303
|
+
const sorted = [...new Set(lines)].sort((a, b) => a - b);
|
|
304
|
+
const out = [];
|
|
305
|
+
let run;
|
|
306
|
+
for (const line of sorted) {
|
|
307
|
+
if (run !== void 0 && line === run.last + 1) run.last = line;
|
|
308
|
+
else {
|
|
309
|
+
if (run !== void 0) out.push(runRemoval(content, run.first, run.last));
|
|
310
|
+
run = { first: line, last: line };
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
if (run !== void 0) out.push(runRemoval(content, run.first, run.last));
|
|
314
|
+
return out;
|
|
315
|
+
}
|
|
316
|
+
function runRemoval(content, first, last) {
|
|
317
|
+
let start = offsetOfLine(content, first);
|
|
318
|
+
const end = offsetOfLine(content, last + 1);
|
|
319
|
+
if (end === content.length && !content.endsWith("\n") && start > 0) {
|
|
320
|
+
let above = content.lastIndexOf("\n", start - 1);
|
|
321
|
+
if (above > 0 && content.charAt(above - 1) === "\r") above--;
|
|
322
|
+
start = above === -1 ? 0 : above;
|
|
323
|
+
}
|
|
324
|
+
return { start, end, text: "" };
|
|
325
|
+
}
|
|
326
|
+
function removeLine(content, line) {
|
|
327
|
+
return applyEdits(content, lineRemovals(content, [line]));
|
|
328
|
+
}
|
|
329
|
+
function entryObject(citation) {
|
|
330
|
+
const source = { file: citation.source.file };
|
|
331
|
+
if (citation.source.lines !== void 0) source.lines = citation.source.lines;
|
|
332
|
+
source.integrity = citation.source.integrity;
|
|
333
|
+
if (citation.source["commit-sha"] !== void 0) {
|
|
334
|
+
source["commit-sha"] = citation.source["commit-sha"];
|
|
335
|
+
}
|
|
336
|
+
const entry = {};
|
|
337
|
+
if (citation.id !== void 0) entry.id = citation.id;
|
|
338
|
+
if (citation.claim !== void 0) {
|
|
339
|
+
const claim = {};
|
|
340
|
+
if (citation.claim.lines !== void 0) claim.lines = citation.claim.lines;
|
|
341
|
+
claim.integrity = citation.claim.integrity;
|
|
342
|
+
entry.claim = claim;
|
|
343
|
+
}
|
|
344
|
+
entry.source = source;
|
|
345
|
+
if (citation.quote !== void 0) entry.quote = citation.quote;
|
|
346
|
+
return entry;
|
|
347
|
+
}
|
|
348
|
+
function needsYamlQuotes(value) {
|
|
349
|
+
return value === "" || /^[\s!&*?|>'"%@`{}[\],#]/.test(value) || /^-(\s|$)/.test(value) || /:\s|\s#|:$|\s$/.test(value) || /^(?:true|false|null|~|yes|no|on|off|[-+]?(?:\d[\d_]*\.?\d*|\.\d+)(?:e[-+]?\d+)?)$/i.test(value);
|
|
350
|
+
}
|
|
351
|
+
var doubleQuote = (value) => JSON.stringify(value);
|
|
352
|
+
var singleQuote = (value) => `'${value.replace(/'/g, "''")}'`;
|
|
353
|
+
function splitScalarLine(line, field) {
|
|
354
|
+
const keyed = new RegExp(`^(\\s*(?:-\\s+)?"?${field}"?\\s*:\\s*)(.*)$`).exec(line);
|
|
355
|
+
const lead = keyed?.[1];
|
|
356
|
+
const rest = keyed?.[2];
|
|
357
|
+
if (lead === void 0 || rest === void 0) return void 0;
|
|
358
|
+
const quote = rest.charAt(0);
|
|
359
|
+
let end;
|
|
360
|
+
if (quote === '"' || quote === "'") {
|
|
361
|
+
end = -1;
|
|
362
|
+
for (let i = 1; i < rest.length; i++) {
|
|
363
|
+
const ch = rest.charAt(i);
|
|
364
|
+
if (quote === '"' && ch === "\\") {
|
|
365
|
+
i++;
|
|
366
|
+
} else if (ch === quote) {
|
|
367
|
+
if (quote === "'" && rest.charAt(i + 1) === "'") {
|
|
368
|
+
i++;
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
end = i + 1;
|
|
372
|
+
break;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
if (end < 0) return void 0;
|
|
376
|
+
} else {
|
|
377
|
+
const comment = /\s#/.exec(rest);
|
|
378
|
+
end = comment ? comment.index : rest.length;
|
|
379
|
+
while (end > 0 && /\s/.test(rest.charAt(end - 1))) end--;
|
|
380
|
+
}
|
|
381
|
+
return { lead, scalar: rest.slice(0, end), trail: rest.slice(end) };
|
|
382
|
+
}
|
|
383
|
+
function spliceEntryField(content, format, index, path, value) {
|
|
384
|
+
const [which, field] = path;
|
|
385
|
+
const refuse = () => new CiteError(
|
|
386
|
+
`Cannot rewrite citations[${String(index)}].${which}.${field} in ${format} frontmatter; edit it by hand.`
|
|
387
|
+
);
|
|
388
|
+
const extractor = extractorFor(format);
|
|
389
|
+
const pointer = `/citations/${String(index)}/${which}/${field}`;
|
|
390
|
+
const line = extractor.extract(content, "page").lineFor(pointer);
|
|
391
|
+
if (line === void 0) throw refuse();
|
|
392
|
+
const start = offsetOfLine(content, line);
|
|
393
|
+
const nl = content.indexOf("\n", start);
|
|
394
|
+
let end = nl === -1 ? content.length : nl;
|
|
395
|
+
if (end > start && content.charAt(end - 1) === "\r") end--;
|
|
396
|
+
const parts = splitScalarLine(content.slice(start, end), field);
|
|
397
|
+
if (!parts) throw refuse();
|
|
398
|
+
const quote = parts.scalar.charAt(0);
|
|
399
|
+
const spelled = typeof value === "number" ? String(value) : quote === '"' ? doubleQuote(value) : quote === "'" ? singleQuote(value) : needsYamlQuotes(value) ? doubleQuote(value) : value;
|
|
400
|
+
const out = content.slice(0, start) + parts.lead + spelled + parts.trail + content.slice(end);
|
|
401
|
+
const check = extractor.extract(out, "page").data.citations;
|
|
402
|
+
const entry = Array.isArray(check) ? check[index] : void 0;
|
|
403
|
+
const block = isRecord(entry) ? entry[which] : void 0;
|
|
404
|
+
const readBack = isRecord(block) ? block[field] : void 0;
|
|
405
|
+
if (readBack !== value) throw refuse();
|
|
406
|
+
return out;
|
|
407
|
+
}
|
|
408
|
+
function isRecord(value) {
|
|
409
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
410
|
+
}
|
|
411
|
+
function insertStatementBefore(content, offset, statement) {
|
|
412
|
+
const start = offsetOfLine(content, lineAt(content, offset));
|
|
413
|
+
return content.slice(0, start) + statement + detectEol(content) + content.slice(start);
|
|
414
|
+
}
|
|
415
|
+
function unifiedDiff(label, before, after) {
|
|
416
|
+
const a = splitLines(before);
|
|
417
|
+
const b = splitLines(after);
|
|
418
|
+
let head = 0;
|
|
419
|
+
while (head < a.length && head < b.length && a[head] === b[head]) head++;
|
|
420
|
+
let tail = 0;
|
|
421
|
+
while (tail < a.length - head && tail < b.length - head && a[a.length - 1 - tail] === b[b.length - 1 - tail]) tail++;
|
|
422
|
+
const x = a.slice(head, a.length - tail);
|
|
423
|
+
const y = b.slice(head, b.length - tail);
|
|
424
|
+
if (x.length === 0 && y.length === 0) return "";
|
|
425
|
+
const width = y.length + 1;
|
|
426
|
+
const lcs = new Uint32Array((x.length + 1) * width);
|
|
427
|
+
for (let i2 = x.length - 1; i2 >= 0; i2--) {
|
|
428
|
+
for (let j2 = y.length - 1; j2 >= 0; j2--) {
|
|
429
|
+
lcs[i2 * width + j2] = x[i2] === y[j2] ? (lcs[(i2 + 1) * width + j2 + 1] ?? 0) + 1 : Math.max(lcs[(i2 + 1) * width + j2] ?? 0, lcs[i2 * width + j2 + 1] ?? 0);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
const edits = a.slice(0, head).map((text) => ({ op: " ", text }));
|
|
433
|
+
let i = 0;
|
|
434
|
+
let j = 0;
|
|
435
|
+
while (i < x.length || j < y.length) {
|
|
436
|
+
const xi = x[i];
|
|
437
|
+
const yj = y[j];
|
|
438
|
+
if (xi !== void 0 && yj !== void 0 && xi === yj) {
|
|
439
|
+
edits.push({ op: " ", text: xi });
|
|
440
|
+
i++;
|
|
441
|
+
j++;
|
|
442
|
+
} else if (xi !== void 0 && (yj === void 0 || (lcs[(i + 1) * width + j] ?? 0) >= (lcs[i * width + j + 1] ?? 0))) {
|
|
443
|
+
edits.push({ op: "-", text: xi });
|
|
444
|
+
i++;
|
|
445
|
+
} else if (yj !== void 0) {
|
|
446
|
+
edits.push({ op: "+", text: yj });
|
|
447
|
+
j++;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
edits.push(...a.slice(a.length - tail).map((text) => ({ op: " ", text })));
|
|
451
|
+
const CONTEXT = 1;
|
|
452
|
+
const out = [`--- ${label}`, `+++ ${label}`];
|
|
453
|
+
let pos = 0;
|
|
454
|
+
while (pos < edits.length) {
|
|
455
|
+
if (edits[pos]?.op === " ") {
|
|
456
|
+
pos++;
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
459
|
+
const from = Math.max(0, pos - CONTEXT);
|
|
460
|
+
let to = pos;
|
|
461
|
+
for (let k = pos; k < edits.length; k++) {
|
|
462
|
+
if (edits[k]?.op !== " ") to = k;
|
|
463
|
+
else if (k - to > CONTEXT * 2) break;
|
|
464
|
+
}
|
|
465
|
+
to = Math.min(edits.length - 1, to + CONTEXT);
|
|
466
|
+
let oldStart = 1;
|
|
467
|
+
let newStart = 1;
|
|
468
|
+
for (let k = 0; k < from; k++) {
|
|
469
|
+
const op = edits[k]?.op;
|
|
470
|
+
if (op !== "+") oldStart++;
|
|
471
|
+
if (op !== "-") newStart++;
|
|
472
|
+
}
|
|
473
|
+
const slice = edits.slice(from, to + 1);
|
|
474
|
+
const oldCount = slice.filter((e) => e.op !== "+").length;
|
|
475
|
+
const newCount = slice.filter((e) => e.op !== "-").length;
|
|
476
|
+
out.push(
|
|
477
|
+
`@@ -${oldCount === 0 ? oldStart - 1 : oldStart},${oldCount} +${newCount === 0 ? newStart - 1 : newStart},${newCount} @@`,
|
|
478
|
+
...slice.map((e) => e.op + e.text)
|
|
479
|
+
);
|
|
480
|
+
pos = to + 1;
|
|
481
|
+
}
|
|
482
|
+
return out.join("\n") + "\n";
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// src/cite/core/manifest.ts
|
|
486
|
+
var codec = { apply, read: readManifestValue };
|
|
487
|
+
var ManifestSet = class {
|
|
488
|
+
held = /* @__PURE__ */ new Map();
|
|
489
|
+
/** Read a manifest, or hand back the copy this run has already rewritten. */
|
|
490
|
+
async hold(manifest) {
|
|
491
|
+
const already = this.held.get(manifest.path);
|
|
492
|
+
if (already !== void 0) return already;
|
|
493
|
+
let before;
|
|
494
|
+
let absent = false;
|
|
495
|
+
try {
|
|
496
|
+
before = await readFile(manifest.path, "utf8");
|
|
497
|
+
} catch (error) {
|
|
498
|
+
if (manifest.pattern === void 0 || !isMissing(error)) {
|
|
499
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
500
|
+
throw new CiteError(`Manifest ${manifest.file} could not be read: ${reason}`);
|
|
501
|
+
}
|
|
502
|
+
before = "";
|
|
503
|
+
absent = true;
|
|
504
|
+
}
|
|
505
|
+
const fresh = heldManifest(manifest.path, manifest.file, before, absent);
|
|
506
|
+
this.held.set(manifest.path, fresh);
|
|
507
|
+
return fresh;
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Write one page's whole `citations` list into its entry, and answer where
|
|
511
|
+
* the entry at `index` now sits. The manifest is not saved: `changed()`
|
|
512
|
+
* lists what the run would write.
|
|
513
|
+
*/
|
|
514
|
+
async write(manifest, entry, citations, index) {
|
|
515
|
+
const held = await this.hold(manifest);
|
|
516
|
+
const op = {
|
|
517
|
+
kind: "splice",
|
|
518
|
+
entry,
|
|
519
|
+
key: CITATIONS_KEY,
|
|
520
|
+
join: manifest.join,
|
|
521
|
+
file: manifest.file,
|
|
522
|
+
value: [...citations]
|
|
523
|
+
};
|
|
524
|
+
rebaseable(held, op, codec);
|
|
525
|
+
const spliced = splice(held.text, {
|
|
526
|
+
entry,
|
|
527
|
+
key: CITATIONS_KEY,
|
|
528
|
+
value: op.value,
|
|
529
|
+
join: manifest.join,
|
|
530
|
+
file: manifest.file
|
|
531
|
+
});
|
|
532
|
+
held.text = spliced.text;
|
|
533
|
+
held.ops.push(op);
|
|
534
|
+
return { file: manifest.file, line: itemLine(spliced.text, entry, index, manifest.join) ?? spliced.line };
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* Where one item of an entry's `citations` sits in the manifest as this run
|
|
538
|
+
* will write it. `add` names a line in its report, and `commit` may have
|
|
539
|
+
* replayed the entry onto bytes that moved it, so the line is read back from
|
|
540
|
+
* the settled text rather than kept from the splice.
|
|
541
|
+
*/
|
|
542
|
+
lineOf(manifest, entry, index) {
|
|
543
|
+
const held = this.held.get(manifest.path);
|
|
544
|
+
if (held === void 0) return void 0;
|
|
545
|
+
return itemLine(held.text, entry, index, manifest.join);
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Take a page's `citations` key out of the manifest, for the removal that
|
|
549
|
+
* leaves it with no entries. The entry itself goes when that key was all it
|
|
550
|
+
* had, so a manifest never keeps a document nothing is recorded about.
|
|
551
|
+
*/
|
|
552
|
+
async remove(manifest, entry) {
|
|
553
|
+
const held = await this.hold(manifest);
|
|
554
|
+
const op = {
|
|
555
|
+
kind: "remove",
|
|
556
|
+
entry,
|
|
557
|
+
key: CITATIONS_KEY,
|
|
558
|
+
join: manifest.join,
|
|
559
|
+
file: manifest.file
|
|
560
|
+
};
|
|
561
|
+
rebaseable(held, op, codec);
|
|
562
|
+
held.text = apply(held.text, op);
|
|
563
|
+
held.ops.push(op);
|
|
564
|
+
}
|
|
565
|
+
/** The manifests this run rewrote, with the diff of each. */
|
|
566
|
+
changed() {
|
|
567
|
+
return [...this.held.values()].filter((m) => m.text !== m.before).map((m) => ({ ...m, diff: unifiedDiff(m.file, m.before, m.text) }));
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Write every manifest this run rewrote, and report what landed.
|
|
571
|
+
*
|
|
572
|
+
* The write is the last thing a run does, so this is the one place that
|
|
573
|
+
* knows a manifest is about to be replaced by bytes derived from a read
|
|
574
|
+
* that may be seconds old.
|
|
575
|
+
*/
|
|
576
|
+
async commit(io = {}) {
|
|
577
|
+
const write = io.write ?? ((at, text) => writeFileAtomic(at, text, { createParents: true }));
|
|
578
|
+
const read = io.read ?? ((at) => readFile(at, "utf8"));
|
|
579
|
+
const landed = [];
|
|
580
|
+
for (const held of this.held.values()) {
|
|
581
|
+
if (held.text === held.before) continue;
|
|
582
|
+
try {
|
|
583
|
+
await settle(held, { codec, write, read, toError: (message) => new CiteError(message) });
|
|
584
|
+
} catch (error) {
|
|
585
|
+
if (io.onError !== void 0) await io.onError(changeOf(held), error);
|
|
586
|
+
throw error;
|
|
587
|
+
}
|
|
588
|
+
const change = changeOf(held);
|
|
589
|
+
landed.push(change);
|
|
590
|
+
io.after?.(change);
|
|
591
|
+
}
|
|
592
|
+
return landed;
|
|
593
|
+
}
|
|
594
|
+
};
|
|
595
|
+
function changeOf(held) {
|
|
596
|
+
return {
|
|
597
|
+
path: held.path,
|
|
598
|
+
file: held.file,
|
|
599
|
+
before: held.before,
|
|
600
|
+
text: held.text,
|
|
601
|
+
diff: unifiedDiff(held.file, held.before, held.text)
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
function apply(text, op) {
|
|
605
|
+
const where = { entry: op.entry, key: op.key, join: op.join, file: op.file };
|
|
606
|
+
if (op.kind === "splice") return splice(text, { ...where, value: op.value }).text;
|
|
607
|
+
try {
|
|
608
|
+
return removeManifestKey(text, where).text;
|
|
609
|
+
} catch (error) {
|
|
610
|
+
if (error instanceof DocmetaError) throw new CiteError(error.message);
|
|
611
|
+
throw error;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
function splice(text, options) {
|
|
615
|
+
try {
|
|
616
|
+
return spliceManifestValue(text, options);
|
|
617
|
+
} catch (error) {
|
|
618
|
+
if (error instanceof DocmetaError) throw new CiteError(error.message);
|
|
619
|
+
throw error;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
function itemLine(text, entry, index, join) {
|
|
623
|
+
const lc = new LineCounter();
|
|
624
|
+
const doc = parseDocument(text, { lineCounter: lc, uniqueKeys: false });
|
|
625
|
+
if (doc.errors.length > 0) return void 0;
|
|
626
|
+
const root = doc.contents;
|
|
627
|
+
if (!isMap(root)) return void 0;
|
|
628
|
+
const same = (key) => {
|
|
629
|
+
const spelled = isScalar(key) ? String(key.value) : String(key);
|
|
630
|
+
return join === "path" ? posix.normalize(spelled) === posix.normalize(entry) : spelled === entry;
|
|
631
|
+
};
|
|
632
|
+
const pair = root.items.find((p) => same(p.key));
|
|
633
|
+
const value = pair?.value;
|
|
634
|
+
if (!isMap(value)) return void 0;
|
|
635
|
+
const kv = value.items.find(
|
|
636
|
+
(p) => (isScalar(p.key) ? String(p.key.value) : String(p.key)) === CITATIONS_KEY
|
|
637
|
+
);
|
|
638
|
+
const list = kv?.value;
|
|
639
|
+
if (!isSeq(list)) return void 0;
|
|
640
|
+
const item = list.items[index];
|
|
641
|
+
const range = isNode(item) ? item.range : void 0;
|
|
642
|
+
return range ? lc.linePos(range[0]).line : void 0;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// src/cite/commands/add.ts
|
|
646
|
+
var ID = /^[a-z0-9][a-z0-9-]*$/;
|
|
647
|
+
var toPosix = (path) => path.replace(/\\/g, "/");
|
|
648
|
+
function isEnoent(error) {
|
|
649
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
650
|
+
}
|
|
651
|
+
async function readPageFile(path, label) {
|
|
652
|
+
try {
|
|
653
|
+
return await readFile2(path, "utf8");
|
|
654
|
+
} catch (error) {
|
|
655
|
+
if (isEnoent(error)) throw new CiteError(`File not found: "${label}".`);
|
|
656
|
+
throw error;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
async function citedText(root, index, src, key) {
|
|
660
|
+
const range = parseSrc(src);
|
|
661
|
+
const source = await readSource(root, index, range, key);
|
|
662
|
+
if (source.kind === "missing") {
|
|
663
|
+
throw new CiteError(`Source not readable: ${range.path} could not be read.`);
|
|
664
|
+
}
|
|
665
|
+
return sliceLines(splitLines(source.text), range, range.path);
|
|
666
|
+
}
|
|
667
|
+
function entryName(entry) {
|
|
668
|
+
return entry.citation.id ?? `/citations/${String(entry.origin.index)}`;
|
|
669
|
+
}
|
|
670
|
+
function duplicateOf(page, src, pageLines) {
|
|
671
|
+
const wanted = formatSrc(parseSrc(src));
|
|
672
|
+
return page.citations.find((entry) => {
|
|
673
|
+
const { citation } = entry;
|
|
674
|
+
if (spellSource(citation.source) !== wanted) return false;
|
|
675
|
+
if (pageLines === void 0) return citation.claim === void 0;
|
|
676
|
+
const spec = citation.claim?.lines;
|
|
677
|
+
if (spec === void 0) return false;
|
|
678
|
+
const recorded = parseLines(spec);
|
|
679
|
+
if (recorded === void 0) return false;
|
|
680
|
+
const file = toFileLines(recorded, page.bodyLine);
|
|
681
|
+
return file.start === pageLines.start && file.end === pageLines.end;
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
function holdsNoText(text) {
|
|
685
|
+
return text.trim() === "" || ANY_FENCE.test(text);
|
|
686
|
+
}
|
|
687
|
+
function emptyClaimRefusal(lines, pageLines, at) {
|
|
688
|
+
const span = lines.slice(pageLines.start - 1, pageLines.end);
|
|
689
|
+
if (span.some((text) => !holdsNoText(text))) return void 0;
|
|
690
|
+
if (pageLines.start !== pageLines.end) return `${at} holds no claim text.`;
|
|
691
|
+
const only = span[0] ?? "";
|
|
692
|
+
return only.trim() === "" ? `${at} is blank.` : `${at} is a fence line, not claim text.`;
|
|
693
|
+
}
|
|
694
|
+
async function runAdd(opts) {
|
|
695
|
+
const cwd = resolve(opts.cwd ?? process.cwd());
|
|
696
|
+
const run = await resolveCiteRun({
|
|
697
|
+
cwd,
|
|
698
|
+
configPath: opts.configPath,
|
|
699
|
+
noConfig: opts.noConfig,
|
|
700
|
+
inputs: [],
|
|
701
|
+
root: opts.root,
|
|
702
|
+
onConfigLoaded: opts.onConfigLoaded,
|
|
703
|
+
onNotice: opts.onNotice,
|
|
704
|
+
env: opts.env
|
|
705
|
+
});
|
|
706
|
+
const { root } = run;
|
|
707
|
+
const usingStdin = opts.page === STDIN_TOKEN;
|
|
708
|
+
if (usingStdin && opts.as === void 0) {
|
|
709
|
+
throw new CiteError(STDIN_REQUIRES_AS);
|
|
710
|
+
}
|
|
711
|
+
const path = usingStdin ? void 0 : resolve(cwd, opts.page);
|
|
712
|
+
const label = path === void 0 ? STDIN_LABEL : toPosix(relative(cwd, path));
|
|
713
|
+
const content = path === void 0 ? opts.stdinContent ?? "" : await readPageFile(path, label);
|
|
714
|
+
const sidecars = await sidecarsFor(run, {
|
|
715
|
+
base: cwd,
|
|
716
|
+
// A `{page}` manifest (0058) is read for the one page being cited.
|
|
717
|
+
...path === void 0 ? {} : { pages: [path] }
|
|
718
|
+
});
|
|
719
|
+
const firstManifest = sidecars?.manifests[0];
|
|
720
|
+
if (usingStdin && firstManifest !== void 0) {
|
|
721
|
+
const keyedBy = firstManifest.join === "path" ? "path" : firstManifest.join;
|
|
722
|
+
throw new CiteError(
|
|
723
|
+
`A page read from stdin has no path, and its citations live in ${firstManifest.file}, which is keyed by ${keyedBy}.`
|
|
724
|
+
);
|
|
725
|
+
}
|
|
726
|
+
const sidecar = usingStdin ? void 0 : await sidecars?.forPage(label, content, opts.as);
|
|
727
|
+
const owner = sidecar?.owner;
|
|
728
|
+
const page = readPage(label, content, {
|
|
729
|
+
...opts.as === void 0 ? {} : { format: opts.as },
|
|
730
|
+
// The entries already there are the manifest's, so `--id` uniqueness and
|
|
731
|
+
// the marker checks are measured against them and not against a page
|
|
732
|
+
// that carries none.
|
|
733
|
+
...sidecar?.citations === void 0 ? {} : { citations: sidecar.citations },
|
|
734
|
+
...owner === void 0 ? {} : { owned: { file: owner.file, collection: owner.collection } }
|
|
735
|
+
});
|
|
736
|
+
const { format } = page;
|
|
737
|
+
const pageLines = opts.pageLines;
|
|
738
|
+
const marker = opts.marker === true;
|
|
739
|
+
const quote = opts.quote === true;
|
|
740
|
+
const at = pageLines === void 0 ? "" : `${label}:${spellLines(pageLines)}`;
|
|
741
|
+
if (marker && pageLines === void 0) {
|
|
742
|
+
throw new CiteError(`--marker needs the page lines to anchor: ${label}:L.`);
|
|
743
|
+
}
|
|
744
|
+
if (marker && opts.id === void 0) {
|
|
745
|
+
throw new CiteError("--marker needs --id: the marker names the entry.");
|
|
746
|
+
}
|
|
747
|
+
const wide = pageLines === void 0 ? void 0 : tooWide(pageLines);
|
|
748
|
+
if (wide !== void 0) throw new CiteError(`Invalid range "${at}": it ${wide}.`);
|
|
749
|
+
if (quote && pageLines === void 0) {
|
|
750
|
+
throw new CiteError(`--quote needs the block's lines: ${label}:L1-L2.`);
|
|
751
|
+
}
|
|
752
|
+
if (opts.id !== void 0 && !ID.test(opts.id)) {
|
|
753
|
+
throw new CiteError(
|
|
754
|
+
`Invalid id "${opts.id}": use lowercase letters, digits and hyphens, starting with a letter or digit.`
|
|
755
|
+
);
|
|
756
|
+
}
|
|
757
|
+
if (opts.id !== void 0 && page.citations.some((c) => c.citation.id === opts.id)) {
|
|
758
|
+
throw new CiteError(`${label} already has an entry ${opts.id}.`);
|
|
759
|
+
}
|
|
760
|
+
if (marker && opts.id !== void 0) {
|
|
761
|
+
const already = page.statements.find(
|
|
762
|
+
(s) => s.payload.kind === "ref" && s.payload.ids.includes(opts.id ?? "")
|
|
763
|
+
);
|
|
764
|
+
if (already !== void 0) {
|
|
765
|
+
throw new CiteError(
|
|
766
|
+
`${label} already has a marker ${opts.id} at line ${String(already.line)}.`
|
|
767
|
+
);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
const duplicate = marker ? void 0 : duplicateOf(page, opts.src, pageLines);
|
|
771
|
+
if (duplicate !== void 0) {
|
|
772
|
+
const src = shortSrc(spellSource(duplicate.citation.source));
|
|
773
|
+
const what = pageLines === void 0 ? `a bare pin for ${src}` : `an entry for ${spellAt(pageLines)} and ${src}`;
|
|
774
|
+
throw new CiteError(`${label} already has ${what} (${entryName(duplicate)}).`);
|
|
775
|
+
}
|
|
776
|
+
const lines = splitLines(content);
|
|
777
|
+
if (pageLines !== void 0) {
|
|
778
|
+
if (pageLines.end > lines.length) {
|
|
779
|
+
throw new CiteError(`${at} is past the end of the page (${String(lines.length)} lines).`);
|
|
780
|
+
}
|
|
781
|
+
if (pageLines.start < page.bodyLine) {
|
|
782
|
+
throw new CiteError(`${at} is in the frontmatter. A claim is body text.`);
|
|
783
|
+
}
|
|
784
|
+
if (quote) {
|
|
785
|
+
const span = fenceSpanAt(content, pageLines.start, format);
|
|
786
|
+
if (span === void 0 || span.end !== pageLines.end) {
|
|
787
|
+
throw new CiteError(`${at} is not a fenced block, so it cannot be a quote.`);
|
|
788
|
+
}
|
|
789
|
+
} else if (!marker) {
|
|
790
|
+
const empty = emptyClaimRefusal(lines, pageLines, at);
|
|
791
|
+
if (empty !== void 0) throw new CiteError(empty);
|
|
792
|
+
}
|
|
793
|
+
const split = misplacedMarkers(page, lines);
|
|
794
|
+
const held = split.find(
|
|
795
|
+
(found) => found.line >= pageLines.start && found.line <= pageLines.end
|
|
796
|
+
);
|
|
797
|
+
if (held !== void 0) {
|
|
798
|
+
throw new CiteError(
|
|
799
|
+
`${at} holds the marker at line ${String(held.line)}, which splits its paragraph. Run manni cite update first.`
|
|
800
|
+
);
|
|
801
|
+
}
|
|
802
|
+
const inside = marker ? split.find(
|
|
803
|
+
(found) => pageLines.start >= found.unit.start && pageLines.start <= found.unit.end
|
|
804
|
+
) : void 0;
|
|
805
|
+
if (inside !== void 0) {
|
|
806
|
+
throw new CiteError(
|
|
807
|
+
`${at} is in the paragraph at ${spellAt(inside.unit)}, which the marker at line ${String(inside.line)} splits. Run manni cite update first.`
|
|
808
|
+
);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
const client = opts.gitClient ?? gitClient(root);
|
|
812
|
+
const sourceIndex = await sourceIndexFor(root, client);
|
|
813
|
+
let body = content;
|
|
814
|
+
let markerAt;
|
|
815
|
+
let markerJoined = false;
|
|
816
|
+
let claimSpan;
|
|
817
|
+
let claim;
|
|
818
|
+
let elsewhere = [];
|
|
819
|
+
let shifted = { frontmatter: [], manifest: /* @__PURE__ */ new Map() };
|
|
820
|
+
if (marker && pageLines !== void 0) {
|
|
821
|
+
if (isMarkerLine(lines[pageLines.start - 1] ?? "", format)) {
|
|
822
|
+
throw new CiteError(
|
|
823
|
+
`${label}:${String(pageLines.start)} is a marker line. A marker there would change its pin.`
|
|
824
|
+
);
|
|
825
|
+
}
|
|
826
|
+
const holding = unitHolding(content, pageLines.start, format, {
|
|
827
|
+
offset: page.bodyOffset,
|
|
828
|
+
line: page.bodyLine
|
|
829
|
+
});
|
|
830
|
+
if (holding === void 0) {
|
|
831
|
+
throw new CiteError(`${at} has no paragraph or block for a marker to anchor.`);
|
|
832
|
+
}
|
|
833
|
+
if (pageLines.end > holding.end) {
|
|
834
|
+
throw new CiteError(
|
|
835
|
+
`${at} runs past the ${holding.kind} at ${spellAt(holding)}. A marker anchors one paragraph.`
|
|
836
|
+
);
|
|
837
|
+
}
|
|
838
|
+
const unitWide = tooWide({ start: holding.start, end: holding.end });
|
|
839
|
+
if (unitWide !== void 0) {
|
|
840
|
+
throw new CiteError(
|
|
841
|
+
`${at} is in a ${holding.kind} at ${spellAt(holding)} that ${unitWide}. A marker anchors the whole ${holding.kind}.`
|
|
842
|
+
);
|
|
843
|
+
}
|
|
844
|
+
const above = page.statements.filter((s) => s.anchorLine === holding.start);
|
|
845
|
+
const nearest = above[above.length - 1];
|
|
846
|
+
const held = nearest !== void 0 && nearest.payload.kind === "ref" && nearest.payload.ids.length < MAX_IDS_PER_MARKER && !/[\r\n]/.test(nearest.raw) ? nearest.payload.ids : void 0;
|
|
847
|
+
let after2;
|
|
848
|
+
if (nearest !== void 0 && held !== void 0) {
|
|
849
|
+
body = respellStatement(content, nearest, [...held, opts.id ?? ""]);
|
|
850
|
+
after2 = nearest.end + (body.length - content.length);
|
|
851
|
+
markerAt = nearest.line;
|
|
852
|
+
markerJoined = true;
|
|
853
|
+
} else {
|
|
854
|
+
shifted = shiftedEntries({
|
|
855
|
+
citations: page.citations,
|
|
856
|
+
at: [holding.start],
|
|
857
|
+
delta: 1,
|
|
858
|
+
bodyLine: page.bodyLine,
|
|
859
|
+
label
|
|
860
|
+
});
|
|
861
|
+
const statement = markerIndent(content, holding.start, format, page.bodyLine) + formatStatement(format, { kind: "ref", ids: [opts.id ?? ""] });
|
|
862
|
+
const insertAt = offsetOfLine(content, holding.start);
|
|
863
|
+
body = insertStatementBefore(content, insertAt, statement);
|
|
864
|
+
after2 = insertAt + statement.length;
|
|
865
|
+
markerAt = holding.start;
|
|
866
|
+
}
|
|
867
|
+
const unit = anchoredLines(body, after2, format, quote);
|
|
868
|
+
const pin = unit === void 0 ? void 0 : pinOfLines(splitLines(body), unit);
|
|
869
|
+
if (unit === void 0 || pin === void 0) {
|
|
870
|
+
throw new CiteError(`${at} has no paragraph or block for a marker to anchor.`);
|
|
871
|
+
}
|
|
872
|
+
for (const { index: index2, lines: moved } of shifted.frontmatter) {
|
|
873
|
+
body = spliceEntryField(body, format, index2, ["claim", "lines"], moved);
|
|
874
|
+
}
|
|
875
|
+
claimSpan = unit;
|
|
876
|
+
claim = { integrity: pin };
|
|
877
|
+
} else if (pageLines !== void 0) {
|
|
878
|
+
const pin = pinOfLines(lines, pageLines);
|
|
879
|
+
if (pin === void 0) {
|
|
880
|
+
throw new CiteError(`${at} is past the end of the page (${String(lines.length)} lines).`);
|
|
881
|
+
}
|
|
882
|
+
claimSpan = pageLines;
|
|
883
|
+
claim = { lines: lineSpec(toBodyLines(pageLines, page.bodyLine)), integrity: pin };
|
|
884
|
+
elsewhere = otherClaimSpans(lines, pageLines, pin, page.bodyLine);
|
|
885
|
+
}
|
|
886
|
+
const mint = (key, encrypt) => mintCitation({
|
|
887
|
+
root,
|
|
888
|
+
src: opts.src,
|
|
889
|
+
...opts.id === void 0 ? {} : { id: opts.id },
|
|
890
|
+
...claim === void 0 ? {} : { claim },
|
|
891
|
+
...quote ? { quote: true } : {},
|
|
892
|
+
...opts.commitSha === false ? { commitSha: false } : {},
|
|
893
|
+
encrypt,
|
|
894
|
+
...key === void 0 ? {} : { key },
|
|
895
|
+
gitClient: client,
|
|
896
|
+
sourceIndex
|
|
897
|
+
});
|
|
898
|
+
const needsKey = opts.encrypt === true && run.key === void 0;
|
|
899
|
+
let citation = await mint(run.key, run.key !== void 0);
|
|
900
|
+
if (quote && claimSpan !== void 0) {
|
|
901
|
+
const cited = await citedText(root, sourceIndex, opts.src, run.key);
|
|
902
|
+
const inside = splitLines(body).slice(claimSpan.start, claimSpan.end - 1).join("\n");
|
|
903
|
+
if (!blockMatches(inside, cited)) {
|
|
904
|
+
throw new CiteError(`The block at ${at} does not reproduce ${opts.src}.`);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
if (needsKey) {
|
|
908
|
+
const { key } = await ensureEncryptionKey({
|
|
909
|
+
subject: opts.src,
|
|
910
|
+
cwd,
|
|
911
|
+
file: run.configFile ?? null,
|
|
912
|
+
env: opts.env,
|
|
913
|
+
confirm: opts.confirm,
|
|
914
|
+
notice: (message) => {
|
|
915
|
+
opts.onNotice?.(message);
|
|
916
|
+
},
|
|
917
|
+
toError: (message) => new CiteError(message)
|
|
918
|
+
});
|
|
919
|
+
citation = await mint(key, true);
|
|
920
|
+
}
|
|
921
|
+
const srcRange = parseSrc(opts.src);
|
|
922
|
+
let sourceLine;
|
|
923
|
+
if (srcRange.start !== void 0 && !isEncryptedValue(citation.source.file)) {
|
|
924
|
+
const read = await readSource(root, sourceIndex, srcRange, void 0);
|
|
925
|
+
if (read.kind === "ok") sourceLine = splitLines(read.text)[srcRange.start - 1];
|
|
926
|
+
}
|
|
927
|
+
const manifests = new ManifestSet();
|
|
928
|
+
let placed;
|
|
929
|
+
let index = 0;
|
|
930
|
+
if (owner !== void 0) {
|
|
931
|
+
if (sidecar?.entry === void 0) {
|
|
932
|
+
throw new CiteError(
|
|
933
|
+
`${label} carries no ${owner.join}: value, so its citations cannot be keyed in ${owner.file}.`
|
|
934
|
+
);
|
|
935
|
+
}
|
|
936
|
+
const existing = (sidecar.citations ?? []).map((input, index2) => {
|
|
937
|
+
const moved = shifted.manifest.get(index2);
|
|
938
|
+
return moved === void 0 ? input.entry : withClaimLines(input.entry, moved);
|
|
939
|
+
});
|
|
940
|
+
const list = [...existing, entryObject(citation)];
|
|
941
|
+
index = list.length - 1;
|
|
942
|
+
const at2 = await manifests.write(owner, sidecar.entry, list, index);
|
|
943
|
+
const [changed] = manifests.changed();
|
|
944
|
+
placed = {
|
|
945
|
+
file: at2.file,
|
|
946
|
+
line: at2.line,
|
|
947
|
+
content: changed?.text ?? "",
|
|
948
|
+
diff: changed?.diff ?? "",
|
|
949
|
+
written: false
|
|
950
|
+
};
|
|
951
|
+
}
|
|
952
|
+
const after = owner === void 0 ? appendFrontmatterCitation(body, format, citation, label) : body;
|
|
953
|
+
const shift = bodyLineOf(after, locateFrontmatter(after)?.closeEnd ?? 0) - page.bodyLine;
|
|
954
|
+
const result = {
|
|
955
|
+
file: label,
|
|
956
|
+
citation,
|
|
957
|
+
placed: owner === void 0 ? "frontmatter" : "manifest",
|
|
958
|
+
content: after,
|
|
959
|
+
diff: unifiedDiff(label, content, after),
|
|
960
|
+
written: false,
|
|
961
|
+
...sourceLine === void 0 ? {} : { sourceLine },
|
|
962
|
+
...placed === void 0 ? {} : { manifest: placed }
|
|
963
|
+
};
|
|
964
|
+
if (markerAt !== void 0) result.markerLine = markerAt + shift;
|
|
965
|
+
if (markerJoined) result.markerJoined = true;
|
|
966
|
+
if (claimSpan !== void 0) {
|
|
967
|
+
result.claimLines = { start: claimSpan.start + shift, end: claimSpan.end + shift };
|
|
968
|
+
}
|
|
969
|
+
result.written = path !== void 0 && opts.dryRun !== true && after !== content;
|
|
970
|
+
if (result.written && path !== void 0) await writeFileAtomic(path, after);
|
|
971
|
+
if (result.manifest !== void 0 && opts.dryRun !== true) {
|
|
972
|
+
const [settled] = await manifests.commit();
|
|
973
|
+
if (owner !== void 0 && sidecar?.entry !== void 0) {
|
|
974
|
+
result.manifest.line = manifests.lineOf(owner, sidecar.entry, index) ?? result.manifest.line;
|
|
975
|
+
}
|
|
976
|
+
if (settled !== void 0) {
|
|
977
|
+
result.manifest.content = settled.text;
|
|
978
|
+
result.manifest.diff = settled.diff;
|
|
979
|
+
}
|
|
980
|
+
result.manifest.written = true;
|
|
981
|
+
}
|
|
982
|
+
if (elsewhere.length > 0) {
|
|
983
|
+
const named2 = elsewhere.map((span) => ({ start: span.start + shift, end: span.end + shift }));
|
|
984
|
+
const who = citation.id === void 0 ? "" : `${citation.id}: `;
|
|
985
|
+
opts.onNotice?.(
|
|
986
|
+
`${who}the claim's text also appears at ${spellElsewhere(named2)}, so a move would be ambiguous. Use --marker, or pin more lines.`
|
|
987
|
+
);
|
|
988
|
+
}
|
|
989
|
+
if (opts.commitSha !== false && !await client.available()) {
|
|
990
|
+
opts.onNotice?.(GIT_UNAVAILABLE_COMMIT);
|
|
991
|
+
}
|
|
992
|
+
return result;
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
// src/cite/commands/remove.ts
|
|
996
|
+
import { resolve as resolve2 } from "node:path";
|
|
997
|
+
var POINTER = /^\/citations\/(0|[1-9][0-9]*)$/;
|
|
998
|
+
function entryCount(n) {
|
|
999
|
+
return `${String(n)} ${n === 1 ? "entry" : "entries"}`;
|
|
1000
|
+
}
|
|
1001
|
+
function idOf(entry) {
|
|
1002
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) return void 0;
|
|
1003
|
+
const id = entry.id;
|
|
1004
|
+
return typeof id === "string" ? id : void 0;
|
|
1005
|
+
}
|
|
1006
|
+
async function runRemove(opts) {
|
|
1007
|
+
const only = [...new Set(opts.only)];
|
|
1008
|
+
if (only.length === 0) {
|
|
1009
|
+
throw new CiteError("remove needs --only <id>; it never removes every citation on a page.");
|
|
1010
|
+
}
|
|
1011
|
+
const prepared = await prepareRun({ ...opts, checkSources: false }, "removed", "remove", {
|
|
1012
|
+
resolve: false
|
|
1013
|
+
});
|
|
1014
|
+
const { run, files, usingStdin, forced } = prepared;
|
|
1015
|
+
await assertNoOrphans(prepared);
|
|
1016
|
+
const hits = joinHits();
|
|
1017
|
+
const manifests = new ManifestSet();
|
|
1018
|
+
const matched = /* @__PURE__ */ new Set();
|
|
1019
|
+
const pageCount = files.length + (usingStdin ? 1 : 0);
|
|
1020
|
+
const planned = [];
|
|
1021
|
+
const removeOne = async (label, content, path) => {
|
|
1022
|
+
const setup = await prepared.setupFor(label, content);
|
|
1023
|
+
if (setup.sidecar !== void 0) hits.record(setup.sidecar, label);
|
|
1024
|
+
const owner = setup.sidecar?.owner;
|
|
1025
|
+
const page = readPage(label, content, {
|
|
1026
|
+
...forced === void 0 ? {} : { format: forced.name },
|
|
1027
|
+
...setup.options.citations === void 0 ? {} : { citations: setup.options.citations },
|
|
1028
|
+
...owner === void 0 ? {} : { owned: { file: owner.file, collection: owner.collection } }
|
|
1029
|
+
});
|
|
1030
|
+
const { format } = page;
|
|
1031
|
+
const extractor = pickExtractor(label, forced?.name);
|
|
1032
|
+
const inputs = citationInputs(
|
|
1033
|
+
label,
|
|
1034
|
+
extractor.extract(content, label),
|
|
1035
|
+
setup.sidecar?.citations
|
|
1036
|
+
);
|
|
1037
|
+
const lines = splitLines(content);
|
|
1038
|
+
const entriesFor = (value) => {
|
|
1039
|
+
const [, digits] = POINTER.exec(value) ?? [];
|
|
1040
|
+
if (digits === void 0) {
|
|
1041
|
+
return inputs.flatMap((input, index2) => idOf(input.entry) === value ? [index2] : []);
|
|
1042
|
+
}
|
|
1043
|
+
const index = Number(digits);
|
|
1044
|
+
if (index < inputs.length) return [index];
|
|
1045
|
+
if (pageCount === 1) {
|
|
1046
|
+
throw new CiteError(
|
|
1047
|
+
`"${value}" is past the last entry of ${label} (${entryCount(inputs.length)}).`
|
|
1048
|
+
);
|
|
1049
|
+
}
|
|
1050
|
+
return [];
|
|
1051
|
+
};
|
|
1052
|
+
const markersFor = (id) => id === void 0 ? [] : page.statements.filter(
|
|
1053
|
+
(s) => s.payload.kind === "ref" && s.payload.ids.includes(id)
|
|
1054
|
+
);
|
|
1055
|
+
const dropped = /* @__PURE__ */ new Map();
|
|
1056
|
+
const drop = (statement, id) => {
|
|
1057
|
+
const held = dropped.get(statement.line) ?? { statement, ids: /* @__PURE__ */ new Set() };
|
|
1058
|
+
held.ids.add(id);
|
|
1059
|
+
dropped.set(statement.line, held);
|
|
1060
|
+
};
|
|
1061
|
+
const removed = [];
|
|
1062
|
+
const indices = /* @__PURE__ */ new Set();
|
|
1063
|
+
const markerLines = [];
|
|
1064
|
+
for (const value of only) {
|
|
1065
|
+
const found = entriesFor(value);
|
|
1066
|
+
for (const index of found) {
|
|
1067
|
+
const input = inputs[index];
|
|
1068
|
+
const id = idOf(input?.entry);
|
|
1069
|
+
const markers2 = markersFor(id);
|
|
1070
|
+
indices.add(index);
|
|
1071
|
+
markerLines.push(...markers2.map((s) => s.line));
|
|
1072
|
+
if (id !== void 0) for (const statement of markers2) drop(statement, id);
|
|
1073
|
+
removed.push({
|
|
1074
|
+
...id === void 0 ? {} : { id },
|
|
1075
|
+
index,
|
|
1076
|
+
...input === void 0 ? {} : { origin: input.origin },
|
|
1077
|
+
markerLines: markers2.map((s) => s.line)
|
|
1078
|
+
});
|
|
1079
|
+
matched.add(value);
|
|
1080
|
+
}
|
|
1081
|
+
if (found.length > 0) continue;
|
|
1082
|
+
const markers = markersFor(value);
|
|
1083
|
+
if (markers.length === 0) continue;
|
|
1084
|
+
markerLines.push(...markers.map((s) => s.line));
|
|
1085
|
+
for (const statement of markers) drop(statement, value);
|
|
1086
|
+
removed.push({ id: value, markerLines: markers.map((s) => s.line) });
|
|
1087
|
+
matched.add(value);
|
|
1088
|
+
}
|
|
1089
|
+
const nothing = { file: label, removed: [], diff: "", written: false };
|
|
1090
|
+
if (removed.length === 0) {
|
|
1091
|
+
if (path === void 0) nothing.content = content;
|
|
1092
|
+
planned.push({ page: nothing, ...path === void 0 ? {} : { path } });
|
|
1093
|
+
return;
|
|
1094
|
+
}
|
|
1095
|
+
const touched = [...new Set(markerLines)].sort((a, b) => a - b);
|
|
1096
|
+
for (const line of touched) {
|
|
1097
|
+
if (!isMarkerLine(lines[line - 1] ?? "", format)) {
|
|
1098
|
+
const id = page.statements.find((s) => s.line === line)?.payload;
|
|
1099
|
+
const named2 = id?.kind === "ref" ? ` ${id.ids.join(" ")}` : "";
|
|
1100
|
+
throw new CiteError(
|
|
1101
|
+
`the marker${named2} at ${label}:${String(line)} shares its line with text; remove it by hand.`
|
|
1102
|
+
);
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
const kept = /* @__PURE__ */ new Map();
|
|
1106
|
+
const ordered = [];
|
|
1107
|
+
for (const line of touched) {
|
|
1108
|
+
const held = dropped.get(line);
|
|
1109
|
+
const [head, ...rest] = held === void 0 || held.statement.payload.kind !== "ref" ? [] : held.statement.payload.ids.filter((id) => !held.ids.has(id));
|
|
1110
|
+
if (head === void 0) ordered.push(line);
|
|
1111
|
+
else kept.set(line, [head, ...rest]);
|
|
1112
|
+
}
|
|
1113
|
+
const survivors = page.citations.filter((c) => !indices.has(c.origin.index));
|
|
1114
|
+
const shifted = shiftedEntries({
|
|
1115
|
+
citations: survivors,
|
|
1116
|
+
at: ordered,
|
|
1117
|
+
delta: -1,
|
|
1118
|
+
bodyLine: page.bodyLine,
|
|
1119
|
+
label
|
|
1120
|
+
});
|
|
1121
|
+
const edits = [];
|
|
1122
|
+
for (const [line, ids] of kept) {
|
|
1123
|
+
const held = dropped.get(line);
|
|
1124
|
+
if (held === void 0) continue;
|
|
1125
|
+
const edit = respellEdit(content, held.statement, ids);
|
|
1126
|
+
if (edit === void 0) {
|
|
1127
|
+
throw new CiteError(
|
|
1128
|
+
`manni cannot read the marker at ${label}:${String(line)} to rewrite it; remove it by hand.`
|
|
1129
|
+
);
|
|
1130
|
+
}
|
|
1131
|
+
edits.push(edit);
|
|
1132
|
+
}
|
|
1133
|
+
edits.push(...lineRemovals(content, ordered));
|
|
1134
|
+
let after = applyEdits(content, edits);
|
|
1135
|
+
for (const { index, lines: moved } of shifted.frontmatter) {
|
|
1136
|
+
after = spliceEntryField(after, format, index, ["claim", "lines"], moved);
|
|
1137
|
+
}
|
|
1138
|
+
if (owner === void 0) {
|
|
1139
|
+
after = removeFrontmatterCitations(after, format, indices, label);
|
|
1140
|
+
} else {
|
|
1141
|
+
if (setup.sidecar?.entry === void 0) {
|
|
1142
|
+
throw new CiteError(
|
|
1143
|
+
`${label} carries no ${owner.join}: value, so its citations cannot be keyed in ${owner.file}.`
|
|
1144
|
+
);
|
|
1145
|
+
}
|
|
1146
|
+
const kept2 = inputs.map((input, index) => {
|
|
1147
|
+
const moved = shifted.manifest.get(index);
|
|
1148
|
+
return moved === void 0 ? input.entry : withClaimLines(input.entry, moved);
|
|
1149
|
+
}).filter((_entry, index) => !indices.has(index));
|
|
1150
|
+
if (kept2.length === 0) await manifests.remove(owner, setup.sidecar.entry);
|
|
1151
|
+
else void await manifests.write(owner, setup.sidecar.entry, kept2, 0);
|
|
1152
|
+
}
|
|
1153
|
+
const out = {
|
|
1154
|
+
file: label,
|
|
1155
|
+
removed,
|
|
1156
|
+
diff: after === content ? "" : unifiedDiff(label, content, after),
|
|
1157
|
+
written: false
|
|
1158
|
+
};
|
|
1159
|
+
if (path === void 0) out.content = after;
|
|
1160
|
+
planned.push({ page: out, ...after === content ? {} : { content: after }, ...path === void 0 ? {} : { path } });
|
|
1161
|
+
};
|
|
1162
|
+
if (usingStdin) await removeOne(STDIN_LABEL, opts.stdinContent ?? "");
|
|
1163
|
+
for (const file of files) {
|
|
1164
|
+
await removeOne(file, await readTarget(run, file), resolve2(run.base, file));
|
|
1165
|
+
}
|
|
1166
|
+
assertNoOrphanJoins(prepared, hits);
|
|
1167
|
+
const missing = only.find((value) => !matched.has(value));
|
|
1168
|
+
if (missing !== void 0) {
|
|
1169
|
+
const where = pageCount === 1 ? `${planned[0]?.page.file ?? "the page"} has no` : `none of ${String(pageCount)} pages has an`;
|
|
1170
|
+
const what = POINTER.test(missing) ? `entry ${missing}` : `entry or marker ${missing}`;
|
|
1171
|
+
throw new CiteError(`${where} ${what}.`);
|
|
1172
|
+
}
|
|
1173
|
+
const write = opts.dryRun !== true;
|
|
1174
|
+
for (const item of planned) {
|
|
1175
|
+
if (item.content === void 0 || item.path === void 0) continue;
|
|
1176
|
+
if (write) await writeFileAtomic(item.path, item.content);
|
|
1177
|
+
item.page.written = write;
|
|
1178
|
+
}
|
|
1179
|
+
const rewritten = [];
|
|
1180
|
+
const record = (changed) => {
|
|
1181
|
+
rewritten.push({ file: changed.file, diff: changed.diff, written: write });
|
|
1182
|
+
};
|
|
1183
|
+
if (write) await manifests.commit({ after: record });
|
|
1184
|
+
else for (const changed of manifests.changed()) record(changed);
|
|
1185
|
+
const pages = planned.map((item) => item.page);
|
|
1186
|
+
return {
|
|
1187
|
+
pages,
|
|
1188
|
+
removed: pages.reduce((n, page) => n + page.removed.length, 0),
|
|
1189
|
+
...rewritten.length > 0 ? { manifests: rewritten } : {}
|
|
1190
|
+
};
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
// src/cite/commands/update.ts
|
|
1194
|
+
import { resolve as resolve3 } from "node:path";
|
|
1195
|
+
function sayNoUnit(reason) {
|
|
1196
|
+
switch (reason) {
|
|
1197
|
+
case "outside":
|
|
1198
|
+
return "the line is outside the page body";
|
|
1199
|
+
case "blank":
|
|
1200
|
+
return "the line is blank";
|
|
1201
|
+
case "fence-crossed":
|
|
1202
|
+
return "the claim's lines are not all inside one fenced block";
|
|
1203
|
+
case "marker":
|
|
1204
|
+
return "that line now holds a cite marker, not claim text";
|
|
1205
|
+
case "not-a-paragraph":
|
|
1206
|
+
return "the line does not start a paragraph";
|
|
1207
|
+
case "table-short":
|
|
1208
|
+
return "the table no longer holds every row the claim covers";
|
|
1209
|
+
case "table-rule":
|
|
1210
|
+
return "that line is a table rule, not claim text";
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
function unitNoun(kind) {
|
|
1214
|
+
return kind === "fenced-lines" ? "fenced text" : kind;
|
|
1215
|
+
}
|
|
1216
|
+
function settles(plan) {
|
|
1217
|
+
switch (plan.kind) {
|
|
1218
|
+
case "claim-moved":
|
|
1219
|
+
return "claim-moved";
|
|
1220
|
+
case "source-moved":
|
|
1221
|
+
return "source-moved";
|
|
1222
|
+
case "claim-accepted":
|
|
1223
|
+
return "claim-changed";
|
|
1224
|
+
case "source-accepted":
|
|
1225
|
+
return plan.result.source.status === "never-true" ? "source-never-true" : "source-changed";
|
|
1226
|
+
case "marker-moved":
|
|
1227
|
+
return "marker-misplaced";
|
|
1228
|
+
case "claim-reanchored":
|
|
1229
|
+
return "claim-moved";
|
|
1230
|
+
case "claim-words-held":
|
|
1231
|
+
return "claim-reanchored";
|
|
1232
|
+
// A refused claim leaves nothing written, but its finding is the row the
|
|
1233
|
+
// refusal prints, so it is not also reported as skipped.
|
|
1234
|
+
case "claim-replaced":
|
|
1235
|
+
return "claim-changed";
|
|
1236
|
+
// A shifted entry was `current`, so it had no finding to settle. A
|
|
1237
|
+
// re-committed one was `current` too: `check` never reported it, which is
|
|
1238
|
+
// why the repair is asked for rather than offered.
|
|
1239
|
+
case "claim-shifted":
|
|
1240
|
+
case "source-recommitted":
|
|
1241
|
+
return "";
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
function apply2(content, format, plan) {
|
|
1245
|
+
const index = plan.result.origin.index;
|
|
1246
|
+
switch (plan.kind) {
|
|
1247
|
+
case "claim-moved":
|
|
1248
|
+
case "claim-shifted":
|
|
1249
|
+
return spliceEntryField(content, format, index, ["claim", "lines"], plan.lines);
|
|
1250
|
+
case "claim-reanchored":
|
|
1251
|
+
return spliceEntryField(content, format, index, ["claim", "integrity"], plan.pin);
|
|
1252
|
+
case "claim-words-held": {
|
|
1253
|
+
let out = spliceEntryField(content, format, index, ["claim", "integrity"], plan.pin);
|
|
1254
|
+
if (plan.lines !== void 0) {
|
|
1255
|
+
out = spliceEntryField(out, format, index, ["claim", "lines"], plan.lines);
|
|
1256
|
+
}
|
|
1257
|
+
return out;
|
|
1258
|
+
}
|
|
1259
|
+
// Nothing is written for a refused claim; the report is the whole of it.
|
|
1260
|
+
case "claim-replaced":
|
|
1261
|
+
// The move is a page rewrite, applied to the whole page before any splice.
|
|
1262
|
+
case "marker-moved":
|
|
1263
|
+
return content;
|
|
1264
|
+
case "source-moved": {
|
|
1265
|
+
let out = content;
|
|
1266
|
+
if (plan.file !== void 0) {
|
|
1267
|
+
out = spliceEntryField(out, format, index, ["source", "file"], plan.file);
|
|
1268
|
+
}
|
|
1269
|
+
if (plan.lines !== void 0) {
|
|
1270
|
+
out = spliceEntryField(out, format, index, ["source", "lines"], plan.lines);
|
|
1271
|
+
}
|
|
1272
|
+
out = spliceEntryField(out, format, index, ["source", "integrity"], plan.pin);
|
|
1273
|
+
if (plan.commitSha !== void 0) {
|
|
1274
|
+
out = spliceEntryField(out, format, index, ["source", "commit-sha"], plan.commitSha);
|
|
1275
|
+
}
|
|
1276
|
+
return out;
|
|
1277
|
+
}
|
|
1278
|
+
case "source-recommitted":
|
|
1279
|
+
return spliceEntryField(content, format, index, ["source", "commit-sha"], plan.commitSha);
|
|
1280
|
+
case "claim-accepted": {
|
|
1281
|
+
let out = spliceEntryField(content, format, index, ["claim", "integrity"], plan.pin);
|
|
1282
|
+
if (plan.lines !== void 0) {
|
|
1283
|
+
out = spliceEntryField(out, format, index, ["claim", "lines"], plan.lines);
|
|
1284
|
+
}
|
|
1285
|
+
return out;
|
|
1286
|
+
}
|
|
1287
|
+
case "source-accepted": {
|
|
1288
|
+
let out = content;
|
|
1289
|
+
if (plan.span !== void 0) {
|
|
1290
|
+
out = spliceEntryField(out, format, index, ["source", "lines"], plan.span.lines);
|
|
1291
|
+
}
|
|
1292
|
+
out = spliceEntryField(
|
|
1293
|
+
out,
|
|
1294
|
+
format,
|
|
1295
|
+
index,
|
|
1296
|
+
["source", "integrity"],
|
|
1297
|
+
plan.minted.source.integrity
|
|
1298
|
+
);
|
|
1299
|
+
const commit = plan.minted.source["commit-sha"];
|
|
1300
|
+
if (commit !== void 0) {
|
|
1301
|
+
out = spliceEntryField(out, format, index, ["source", "commit-sha"], commit);
|
|
1302
|
+
}
|
|
1303
|
+
return out;
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
function isRecord2(value) {
|
|
1308
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1309
|
+
}
|
|
1310
|
+
function applyToEntry(entry, plan) {
|
|
1311
|
+
if (!isRecord2(entry)) return false;
|
|
1312
|
+
const end = (name) => isRecord2(entry[name]) ? entry[name] : void 0;
|
|
1313
|
+
switch (plan.kind) {
|
|
1314
|
+
case "claim-moved":
|
|
1315
|
+
case "claim-shifted": {
|
|
1316
|
+
const claim = end("claim");
|
|
1317
|
+
if (claim === void 0) return false;
|
|
1318
|
+
claim.lines = plan.lines;
|
|
1319
|
+
return true;
|
|
1320
|
+
}
|
|
1321
|
+
case "claim-reanchored": {
|
|
1322
|
+
const claim = end("claim");
|
|
1323
|
+
if (claim === void 0) return false;
|
|
1324
|
+
claim.integrity = plan.pin;
|
|
1325
|
+
return true;
|
|
1326
|
+
}
|
|
1327
|
+
case "claim-words-held": {
|
|
1328
|
+
const claim = end("claim");
|
|
1329
|
+
if (claim === void 0) return false;
|
|
1330
|
+
claim.integrity = plan.pin;
|
|
1331
|
+
if (plan.lines !== void 0) claim.lines = plan.lines;
|
|
1332
|
+
return true;
|
|
1333
|
+
}
|
|
1334
|
+
// Nothing is written for a refused claim.
|
|
1335
|
+
case "claim-replaced":
|
|
1336
|
+
// The page holds the marker, so the manifest has nothing to change.
|
|
1337
|
+
case "marker-moved":
|
|
1338
|
+
return true;
|
|
1339
|
+
case "source-moved": {
|
|
1340
|
+
const source = end("source");
|
|
1341
|
+
if (source === void 0) return false;
|
|
1342
|
+
if (plan.file !== void 0) source.file = plan.file;
|
|
1343
|
+
if (plan.lines !== void 0) source.lines = plan.lines;
|
|
1344
|
+
source.integrity = plan.pin;
|
|
1345
|
+
if (plan.commitSha !== void 0) source["commit-sha"] = plan.commitSha;
|
|
1346
|
+
return true;
|
|
1347
|
+
}
|
|
1348
|
+
case "source-recommitted": {
|
|
1349
|
+
const source = end("source");
|
|
1350
|
+
if (source === void 0) return false;
|
|
1351
|
+
source["commit-sha"] = plan.commitSha;
|
|
1352
|
+
return true;
|
|
1353
|
+
}
|
|
1354
|
+
case "claim-accepted": {
|
|
1355
|
+
const claim = end("claim");
|
|
1356
|
+
if (claim === void 0) return false;
|
|
1357
|
+
claim.integrity = plan.pin;
|
|
1358
|
+
if (plan.lines !== void 0) claim.lines = plan.lines;
|
|
1359
|
+
return true;
|
|
1360
|
+
}
|
|
1361
|
+
case "source-accepted": {
|
|
1362
|
+
const source = end("source");
|
|
1363
|
+
if (source === void 0) return false;
|
|
1364
|
+
if (plan.span !== void 0) source.lines = plan.span.lines;
|
|
1365
|
+
source.integrity = plan.minted.source.integrity;
|
|
1366
|
+
const commit = plan.minted.source["commit-sha"];
|
|
1367
|
+
if (commit !== void 0) source["commit-sha"] = commit;
|
|
1368
|
+
return true;
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
function rewriteOf(plan) {
|
|
1373
|
+
const { citation, origin, source } = plan.result;
|
|
1374
|
+
const base = { index: origin.index };
|
|
1375
|
+
if (citation.id !== void 0) base.id = citation.id;
|
|
1376
|
+
if (origin.line !== void 0) base.line = origin.line;
|
|
1377
|
+
switch (plan.kind) {
|
|
1378
|
+
case "claim-moved":
|
|
1379
|
+
return {
|
|
1380
|
+
...base,
|
|
1381
|
+
end: "claim",
|
|
1382
|
+
reason: "moved",
|
|
1383
|
+
status: "moved",
|
|
1384
|
+
from: plan.from,
|
|
1385
|
+
to: plan.to,
|
|
1386
|
+
fromLines: plan.from,
|
|
1387
|
+
toLines: plan.to
|
|
1388
|
+
};
|
|
1389
|
+
case "marker-moved": {
|
|
1390
|
+
const from = String(plan.from);
|
|
1391
|
+
const to = String(plan.to);
|
|
1392
|
+
return {
|
|
1393
|
+
...base,
|
|
1394
|
+
end: "marker",
|
|
1395
|
+
reason: "re-anchored",
|
|
1396
|
+
status: "misplaced",
|
|
1397
|
+
from,
|
|
1398
|
+
to,
|
|
1399
|
+
fromLines: from,
|
|
1400
|
+
toLines: to
|
|
1401
|
+
};
|
|
1402
|
+
}
|
|
1403
|
+
case "claim-reanchored": {
|
|
1404
|
+
const was = citation.claim?.integrity ?? "";
|
|
1405
|
+
return {
|
|
1406
|
+
...base,
|
|
1407
|
+
end: "claim",
|
|
1408
|
+
reason: "re-anchored",
|
|
1409
|
+
status: "moved",
|
|
1410
|
+
from: was,
|
|
1411
|
+
to: plan.pin,
|
|
1412
|
+
fromPin: was,
|
|
1413
|
+
toPin: plan.pin,
|
|
1414
|
+
lines: plan.held,
|
|
1415
|
+
newLines: plan.now
|
|
1416
|
+
};
|
|
1417
|
+
}
|
|
1418
|
+
case "claim-words-held": {
|
|
1419
|
+
const was = citation.claim?.integrity ?? "";
|
|
1420
|
+
const out = {
|
|
1421
|
+
...base,
|
|
1422
|
+
end: "claim",
|
|
1423
|
+
reason: "re-anchored",
|
|
1424
|
+
status: "reanchored",
|
|
1425
|
+
from: was,
|
|
1426
|
+
to: plan.pin,
|
|
1427
|
+
fromPin: was,
|
|
1428
|
+
toPin: plan.pin,
|
|
1429
|
+
fromLines: plan.fromLines,
|
|
1430
|
+
toLines: plan.toLines,
|
|
1431
|
+
at: plan.at
|
|
1432
|
+
};
|
|
1433
|
+
if (plan.commitSha !== void 0) out.commitSha = plan.commitSha;
|
|
1434
|
+
return out;
|
|
1435
|
+
}
|
|
1436
|
+
case "claim-replaced":
|
|
1437
|
+
return {
|
|
1438
|
+
...base,
|
|
1439
|
+
end: "claim",
|
|
1440
|
+
reason: "replaced",
|
|
1441
|
+
status: "changed",
|
|
1442
|
+
from: plan.pin,
|
|
1443
|
+
to: plan.would,
|
|
1444
|
+
fromPin: plan.pin,
|
|
1445
|
+
toPin: plan.would,
|
|
1446
|
+
at: plan.at,
|
|
1447
|
+
commitSha: plan.commitSha,
|
|
1448
|
+
...plan.wordShare === void 0 ? {} : { wordShare: plan.wordShare }
|
|
1449
|
+
};
|
|
1450
|
+
case "claim-shifted":
|
|
1451
|
+
return {
|
|
1452
|
+
...base,
|
|
1453
|
+
end: "claim",
|
|
1454
|
+
reason: "shifted",
|
|
1455
|
+
status: "current",
|
|
1456
|
+
from: plan.from,
|
|
1457
|
+
to: plan.to,
|
|
1458
|
+
fromLines: plan.from,
|
|
1459
|
+
toLines: plan.to
|
|
1460
|
+
};
|
|
1461
|
+
case "source-moved":
|
|
1462
|
+
return {
|
|
1463
|
+
...base,
|
|
1464
|
+
end: "source",
|
|
1465
|
+
reason: "moved",
|
|
1466
|
+
status: "moved",
|
|
1467
|
+
from: source.src,
|
|
1468
|
+
to: plan.to
|
|
1469
|
+
};
|
|
1470
|
+
case "source-recommitted":
|
|
1471
|
+
return {
|
|
1472
|
+
...base,
|
|
1473
|
+
end: "source",
|
|
1474
|
+
reason: "recommitted",
|
|
1475
|
+
// The source itself held. Only its date was wrong.
|
|
1476
|
+
status: "current",
|
|
1477
|
+
because: plan.because,
|
|
1478
|
+
from: plan.from,
|
|
1479
|
+
to: plan.commitSha,
|
|
1480
|
+
fromCommit: plan.from,
|
|
1481
|
+
toCommit: plan.commitSha,
|
|
1482
|
+
src: source.src
|
|
1483
|
+
};
|
|
1484
|
+
case "claim-accepted": {
|
|
1485
|
+
const was = citation.claim?.integrity ?? "";
|
|
1486
|
+
const out = {
|
|
1487
|
+
...base,
|
|
1488
|
+
end: "claim",
|
|
1489
|
+
reason: "accepted",
|
|
1490
|
+
// A bypassed guard reads `replaced`, so the log shows that the line
|
|
1491
|
+
// held other text and a named accept took it anyway.
|
|
1492
|
+
status: plan.replaced === true ? "replaced" : "changed",
|
|
1493
|
+
from: was,
|
|
1494
|
+
to: plan.pin,
|
|
1495
|
+
fromPin: was,
|
|
1496
|
+
toPin: plan.pin,
|
|
1497
|
+
at: plan.unit.lines.start,
|
|
1498
|
+
text: quoted(plan.unit.text)
|
|
1499
|
+
};
|
|
1500
|
+
const stored = plan.result.claim?.fileLines;
|
|
1501
|
+
const now = spellLines(plan.unit.lines);
|
|
1502
|
+
if (stored !== void 0 && stored !== now) {
|
|
1503
|
+
out.fromLines = stored;
|
|
1504
|
+
out.toLines = now;
|
|
1505
|
+
}
|
|
1506
|
+
const markerLine = plan.markerLine ?? (plan.result.anchor === "marker" ? plan.result.markerLine : void 0);
|
|
1507
|
+
if (markerLine !== void 0) out.markerLine = markerLine;
|
|
1508
|
+
return out;
|
|
1509
|
+
}
|
|
1510
|
+
case "source-accepted": {
|
|
1511
|
+
const out = {
|
|
1512
|
+
...base,
|
|
1513
|
+
end: "source",
|
|
1514
|
+
reason: "accepted",
|
|
1515
|
+
status: source.status === "never-true" ? "never-true" : "changed",
|
|
1516
|
+
from: citation.source.integrity,
|
|
1517
|
+
to: plan.minted.source.integrity,
|
|
1518
|
+
fromPin: citation.source.integrity,
|
|
1519
|
+
toPin: plan.minted.source.integrity,
|
|
1520
|
+
src: source.src
|
|
1521
|
+
};
|
|
1522
|
+
if (plan.span !== void 0) {
|
|
1523
|
+
out.fromLines = plan.span.from;
|
|
1524
|
+
out.toLines = plan.span.to;
|
|
1525
|
+
}
|
|
1526
|
+
const commit = plan.minted.source["commit-sha"];
|
|
1527
|
+
if (commit !== void 0) out.commitSha = commit;
|
|
1528
|
+
return out;
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
var CLAIM_WORD_SHARE = 0.5;
|
|
1533
|
+
function replacementAt(claim, text, format) {
|
|
1534
|
+
const was = claim.baselineText;
|
|
1535
|
+
const commit = claim.commitSha;
|
|
1536
|
+
if (was === void 0 || commit === void 0) return void 0;
|
|
1537
|
+
const before = claimWords(was, format);
|
|
1538
|
+
const now = claimWords(text, format);
|
|
1539
|
+
if (!sharesSentence(before, now)) return { commit };
|
|
1540
|
+
const share = wordShare(before, now);
|
|
1541
|
+
if (share < CLAIM_WORD_SHARE) return { commit, share };
|
|
1542
|
+
return void 0;
|
|
1543
|
+
}
|
|
1544
|
+
var QUOTE_CAP = 200;
|
|
1545
|
+
function quoted(text) {
|
|
1546
|
+
const all = normalizeWhitespace(text.join("\n"));
|
|
1547
|
+
return all.length <= QUOTE_CAP ? all : `${all.slice(0, QUOTE_CAP)}\u2026`;
|
|
1548
|
+
}
|
|
1549
|
+
function named(id, text) {
|
|
1550
|
+
return id === void 0 ? text : `${id}: ${text}`;
|
|
1551
|
+
}
|
|
1552
|
+
function crossing(page, misplaced) {
|
|
1553
|
+
for (const other of page.citations) {
|
|
1554
|
+
const spec = other.citation.claim?.lines;
|
|
1555
|
+
const recorded = spec === void 0 ? void 0 : parseLines(spec);
|
|
1556
|
+
if (recorded === void 0) continue;
|
|
1557
|
+
const file = toFileLines(recorded, page.bodyLine);
|
|
1558
|
+
const holdsMarker = file.start <= misplaced.line && misplaced.line <= file.end;
|
|
1559
|
+
const holdsPlace = file.start < misplaced.place && misplaced.place <= file.end;
|
|
1560
|
+
if (!holdsMarker && !holdsPlace) continue;
|
|
1561
|
+
return other.citation.id === void 0 ? `the claim at ${spellAt(file)}` : `the claim of ${other.citation.id} (${spellAt(file)})`;
|
|
1562
|
+
}
|
|
1563
|
+
return void 0;
|
|
1564
|
+
}
|
|
1565
|
+
function decideMarkers(input) {
|
|
1566
|
+
const { page, report, lines, accept, only } = input;
|
|
1567
|
+
const out = { moves: [], stays: [] };
|
|
1568
|
+
for (const misplaced of misplacedMarkers(page, lines)) {
|
|
1569
|
+
const entry = misplaced.entry;
|
|
1570
|
+
const line = misplaced.line;
|
|
1571
|
+
const stay = (rule, message) => {
|
|
1572
|
+
out.stays.push({ line, rule, message });
|
|
1573
|
+
};
|
|
1574
|
+
if (entry === void 0) {
|
|
1575
|
+
if (only !== void 0) continue;
|
|
1576
|
+
const own = report.findings.find(
|
|
1577
|
+
(finding) => finding.line === line && finding.rule.startsWith("marker-") && finding.rule !== "marker-misplaced"
|
|
1578
|
+
);
|
|
1579
|
+
stay(
|
|
1580
|
+
own?.rule ?? "marker-orphan",
|
|
1581
|
+
`the marker at line ${String(line)} stays, because it names no entry update can check.`
|
|
1582
|
+
);
|
|
1583
|
+
continue;
|
|
1584
|
+
}
|
|
1585
|
+
const id = entry.citation.id;
|
|
1586
|
+
if (only !== void 0 && (id === void 0 || !only.has(id))) continue;
|
|
1587
|
+
const result = report.citations.find((r) => r.origin.index === entry.origin.index);
|
|
1588
|
+
if (result === void 0) continue;
|
|
1589
|
+
if (entry.citation.claim?.lines !== void 0) {
|
|
1590
|
+
stay(
|
|
1591
|
+
"anchor-invalid",
|
|
1592
|
+
named(id, `the marker at line ${String(line)} stays, because the entry also has claim lines. Keep one.`)
|
|
1593
|
+
);
|
|
1594
|
+
continue;
|
|
1595
|
+
}
|
|
1596
|
+
const across = crossing(page, misplaced);
|
|
1597
|
+
if (across !== void 0) {
|
|
1598
|
+
stay(
|
|
1599
|
+
"marker-misplaced",
|
|
1600
|
+
named(id, `the marker at line ${String(line)} stays, because moving it would change ${across}.`)
|
|
1601
|
+
);
|
|
1602
|
+
continue;
|
|
1603
|
+
}
|
|
1604
|
+
if (result.claim !== null && result.claim.status === "changed" && !accept) {
|
|
1605
|
+
stay(
|
|
1606
|
+
"marker-misplaced",
|
|
1607
|
+
named(
|
|
1608
|
+
id,
|
|
1609
|
+
`the marker at line ${String(line)} stays, because its claim changed since it was pinned. update --accept moves it and re-pins.`
|
|
1610
|
+
)
|
|
1611
|
+
);
|
|
1612
|
+
continue;
|
|
1613
|
+
}
|
|
1614
|
+
out.moves.push({ result, entry, misplaced });
|
|
1615
|
+
}
|
|
1616
|
+
return out;
|
|
1617
|
+
}
|
|
1618
|
+
function anchoredNow(content, format, lines, markerLine, quote) {
|
|
1619
|
+
const at = offsetOfLine(content, markerLine) + (lines[markerLine - 1]?.length ?? 0);
|
|
1620
|
+
const span = anchoredLines(content, at, format, quote);
|
|
1621
|
+
if (span === void 0) return void 0;
|
|
1622
|
+
const pin = pinOfLines(lines, span);
|
|
1623
|
+
return pin === void 0 ? void 0 : { span, pin };
|
|
1624
|
+
}
|
|
1625
|
+
function codeOf(error) {
|
|
1626
|
+
if (typeof error !== "object" || error === null || !("code" in error)) return void 0;
|
|
1627
|
+
const code = error.code;
|
|
1628
|
+
return typeof code === "string" ? code : void 0;
|
|
1629
|
+
}
|
|
1630
|
+
var WrittenFiles = class {
|
|
1631
|
+
before = /* @__PURE__ */ new Map();
|
|
1632
|
+
labels = /* @__PURE__ */ new Map();
|
|
1633
|
+
/** Remember what `path` held before this run wrote it. */
|
|
1634
|
+
record(path, label, content) {
|
|
1635
|
+
if (!this.before.has(path)) {
|
|
1636
|
+
this.before.set(path, content);
|
|
1637
|
+
this.labels.set(path, label);
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
/**
|
|
1641
|
+
* Put every file back, and say what happened: `<file> could not be written
|
|
1642
|
+
* (<code>). <pages> was restored.`
|
|
1643
|
+
*/
|
|
1644
|
+
async restore(file, error) {
|
|
1645
|
+
const restored = [];
|
|
1646
|
+
const failed = [];
|
|
1647
|
+
for (const [path, content] of this.before) {
|
|
1648
|
+
const label = this.labels.get(path) ?? path;
|
|
1649
|
+
try {
|
|
1650
|
+
await writeFileAtomic(path, content);
|
|
1651
|
+
restored.push(label);
|
|
1652
|
+
} catch {
|
|
1653
|
+
failed.push(label);
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
const code = codeOf(error);
|
|
1657
|
+
const why = code === void 0 ? "" : ` (${code})`;
|
|
1658
|
+
const parts = [`${file} could not be written${why}.`];
|
|
1659
|
+
if (restored.length > 0) {
|
|
1660
|
+
parts.push(`${listOf(restored)} ${restored.length === 1 ? "was" : "were"} restored.`);
|
|
1661
|
+
}
|
|
1662
|
+
if (failed.length > 0) {
|
|
1663
|
+
parts.push(
|
|
1664
|
+
`${listOf(failed)} could not be restored, and ${failed.length === 1 ? "holds" : "hold"} an intermediate state.`
|
|
1665
|
+
);
|
|
1666
|
+
}
|
|
1667
|
+
return new CiteError(parts.join(" "));
|
|
1668
|
+
}
|
|
1669
|
+
};
|
|
1670
|
+
function citationsIn(rows) {
|
|
1671
|
+
const withMarker = new Set(rows.filter((row) => row.end === "marker").map((row) => row.index));
|
|
1672
|
+
return withMarker.size + rows.filter((row) => !withMarker.has(row.index)).length;
|
|
1673
|
+
}
|
|
1674
|
+
async function runUpdate(opts) {
|
|
1675
|
+
const prepared = await prepareRun(opts, "updated", "update", { require: true });
|
|
1676
|
+
const { run, files, usingStdin, forced, pageOptions, git } = prepared;
|
|
1677
|
+
await assertNoOrphans(prepared);
|
|
1678
|
+
const hits = joinHits();
|
|
1679
|
+
const manifests = new ManifestSet();
|
|
1680
|
+
const writes = new WrittenFiles();
|
|
1681
|
+
const only = opts.only !== void 0 && opts.only.length > 0 ? new Set(opts.only) : void 0;
|
|
1682
|
+
const accept = opts.accept === true;
|
|
1683
|
+
const recommit = opts.recommit === true;
|
|
1684
|
+
if (recommit && !await git.available()) {
|
|
1685
|
+
throw new CiteError(
|
|
1686
|
+
"--recommit needs git to verify recorded commits, and git is not available here."
|
|
1687
|
+
);
|
|
1688
|
+
}
|
|
1689
|
+
const outsideHistory = async (commit, head) => {
|
|
1690
|
+
if (git.shallow === void 0 || git.isAncestor === void 0) return false;
|
|
1691
|
+
if (await git.shallow()) return false;
|
|
1692
|
+
return !await git.isAncestor(commit, head);
|
|
1693
|
+
};
|
|
1694
|
+
const recommitFor = async (result) => {
|
|
1695
|
+
const recorded = result.citation.source["commit-sha"];
|
|
1696
|
+
const path = result.source.resolvedPath;
|
|
1697
|
+
if (recorded === void 0 || path === void 0) return void 0;
|
|
1698
|
+
const head = await git.head();
|
|
1699
|
+
if (head === null || head === recorded) return void 0;
|
|
1700
|
+
const range = sourceRange(result.citation.source);
|
|
1701
|
+
const pin = result.citation.source.integrity;
|
|
1702
|
+
const key = range.encrypted ? run.key : void 0;
|
|
1703
|
+
let because;
|
|
1704
|
+
if (await outsideHistory(recorded, head)) because = "not-in-history";
|
|
1705
|
+
else {
|
|
1706
|
+
const was = await historyOf(git, recorded, path, range, pin, key, void 0);
|
|
1707
|
+
if (was.kind !== "never-true") return void 0;
|
|
1708
|
+
because = "not-contained";
|
|
1709
|
+
}
|
|
1710
|
+
const now = await historyOf(git, head, path, range, pin, key, void 0);
|
|
1711
|
+
return now.kind === "original" ? { commit: head, because } : void 0;
|
|
1712
|
+
};
|
|
1713
|
+
const plansFor = async (result, entry, page, lines, lineNow, declined) => {
|
|
1714
|
+
const out = [];
|
|
1715
|
+
const claim = result.claim;
|
|
1716
|
+
if (result.anchor !== "marker" && claim !== null && claim.status === "moved" && claim.newLines !== void 0) {
|
|
1717
|
+
const at = parseLines(claim.newLines);
|
|
1718
|
+
const shifted = at === void 0 ? void 0 : (() => {
|
|
1719
|
+
const file = toFileLines(at, page.bodyLine);
|
|
1720
|
+
const to = { start: lineNow(file.start), end: lineNow(file.end) };
|
|
1721
|
+
return to.end - to.start === file.end - file.start ? toBodyLines(to, page.bodyLine) : void 0;
|
|
1722
|
+
})();
|
|
1723
|
+
if (at !== void 0 && shifted !== void 0) {
|
|
1724
|
+
out.push({
|
|
1725
|
+
kind: "claim-moved",
|
|
1726
|
+
result,
|
|
1727
|
+
lines: lineSpec(shifted),
|
|
1728
|
+
from: claim.fileLines ?? "",
|
|
1729
|
+
to: spellLines(toFileLines(shifted, page.bodyLine))
|
|
1730
|
+
});
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
if (result.anchor !== "marker" && claim !== null && claim.status === "reanchored" && claim.newLines !== void 0) {
|
|
1734
|
+
const at = parseLines(claim.newLines);
|
|
1735
|
+
const file = at === void 0 ? void 0 : toFileLines(at, page.bodyLine);
|
|
1736
|
+
const pin = file === void 0 ? void 0 : pinOfLines(lines, file);
|
|
1737
|
+
const to = file === void 0 ? void 0 : { start: lineNow(file.start), end: lineNow(file.end) };
|
|
1738
|
+
if (file !== void 0 && to !== void 0 && pin !== void 0 && to.end - to.start === file.end - file.start) {
|
|
1739
|
+
const plan = {
|
|
1740
|
+
kind: "claim-words-held",
|
|
1741
|
+
result,
|
|
1742
|
+
pin,
|
|
1743
|
+
at: to.start,
|
|
1744
|
+
fromLines: claim.fileLines ?? "",
|
|
1745
|
+
toLines: spellLines(to),
|
|
1746
|
+
lines: lineSpec(toBodyLines(to, page.bodyLine))
|
|
1747
|
+
};
|
|
1748
|
+
if (claim.commitSha !== void 0) plan.commitSha = claim.commitSha;
|
|
1749
|
+
out.push(plan);
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
if (result.source.status === "moved" && result.source.newSrc !== void 0) {
|
|
1753
|
+
const to = result.source.newSrc;
|
|
1754
|
+
const recorded = result.citation.source["commit-sha"];
|
|
1755
|
+
const at = result.source.newLines === void 0 ? void 0 : parseLines(result.source.newLines);
|
|
1756
|
+
const went = parseSrc(to).path;
|
|
1757
|
+
const crossFile = went !== result.citation.source.file;
|
|
1758
|
+
if (at !== void 0 || result.citation.source.lines === void 0) {
|
|
1759
|
+
try {
|
|
1760
|
+
if (crossFile) {
|
|
1761
|
+
out.push({
|
|
1762
|
+
kind: "source-moved",
|
|
1763
|
+
result,
|
|
1764
|
+
to,
|
|
1765
|
+
file: went,
|
|
1766
|
+
pin: result.citation.source.integrity,
|
|
1767
|
+
...at === void 0 ? {} : { lines: lineSpec(at) }
|
|
1768
|
+
});
|
|
1769
|
+
} else if (at !== void 0) {
|
|
1770
|
+
const minted = await mintCitation({
|
|
1771
|
+
root: run.root,
|
|
1772
|
+
src: to,
|
|
1773
|
+
...run.key === void 0 ? {} : { key: run.key },
|
|
1774
|
+
...recorded === void 0 ? { commitSha: false } : {},
|
|
1775
|
+
gitClient: git,
|
|
1776
|
+
...pageOptions.sourceIndex === void 0 ? {} : { sourceIndex: pageOptions.sourceIndex }
|
|
1777
|
+
});
|
|
1778
|
+
const commit = minted.source["commit-sha"];
|
|
1779
|
+
if (recorded !== void 0 && commit === void 0) {
|
|
1780
|
+
declined.set(result.origin.index, {
|
|
1781
|
+
rule: "source-moved",
|
|
1782
|
+
why: "Not rewritten: git is not available here, so the entry's commit-sha cannot advance with its lines."
|
|
1783
|
+
});
|
|
1784
|
+
} else {
|
|
1785
|
+
out.push({
|
|
1786
|
+
kind: "source-moved",
|
|
1787
|
+
result,
|
|
1788
|
+
lines: lineSpec(at),
|
|
1789
|
+
to,
|
|
1790
|
+
pin: minted.source.integrity,
|
|
1791
|
+
...commit === void 0 ? {} : { commitSha: commit }
|
|
1792
|
+
});
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
} catch (error) {
|
|
1796
|
+
if (!(error instanceof CiteError)) throw error;
|
|
1797
|
+
declined.set(result.origin.index, {
|
|
1798
|
+
rule: "source-moved",
|
|
1799
|
+
why: `Not rewritten: ${error.message}`
|
|
1800
|
+
});
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
if (recommit && result.source.status === "current") {
|
|
1805
|
+
const to = await recommitFor(result);
|
|
1806
|
+
if (to !== void 0) {
|
|
1807
|
+
out.push({
|
|
1808
|
+
kind: "source-recommitted",
|
|
1809
|
+
result,
|
|
1810
|
+
from: result.citation.source["commit-sha"] ?? "",
|
|
1811
|
+
commitSha: to.commit,
|
|
1812
|
+
because: to.because
|
|
1813
|
+
});
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
if (!accept) return out;
|
|
1817
|
+
if (claim !== null && claim.status === "changed" && entry !== void 0 && result.anchor !== "marker") {
|
|
1818
|
+
const held = claim.fileLines === void 0 ? void 0 : parseLines(claim.fileLines);
|
|
1819
|
+
const at = held?.start;
|
|
1820
|
+
const named2 = only !== void 0;
|
|
1821
|
+
const unit = at === void 0 ? void 0 : unitAt(page, at, lines, held, named2);
|
|
1822
|
+
const wantsBlock = entry.citation.quote === true;
|
|
1823
|
+
const pin = unit === void 0 ? void 0 : pinOfLines(lines, unit.lines);
|
|
1824
|
+
const wide = unit === void 0 ? void 0 : tooWide(unit.lines);
|
|
1825
|
+
if (unit !== void 0 && wide !== void 0) {
|
|
1826
|
+
declined.set(result.origin.index, {
|
|
1827
|
+
rule: "claim-changed",
|
|
1828
|
+
why: `Not re-pinned: the ${unitNoun(unit.kind)} ${wide}.`
|
|
1829
|
+
});
|
|
1830
|
+
} else if (unit === void 0) {
|
|
1831
|
+
const why = at === void 0 ? void 0 : noUnitAt(page, at, lines, held, named2);
|
|
1832
|
+
if (why !== void 0) {
|
|
1833
|
+
declined.set(result.origin.index, {
|
|
1834
|
+
rule: "claim-changed",
|
|
1835
|
+
why: `Not re-pinned: ${sayNoUnit(why)}.`
|
|
1836
|
+
});
|
|
1837
|
+
}
|
|
1838
|
+
} else if (wantsBlock && unit.kind !== "block") {
|
|
1839
|
+
declined.set(result.origin.index, {
|
|
1840
|
+
rule: "claim-changed",
|
|
1841
|
+
why: "Not re-pinned: quote: true, and the line no longer opens a fenced block."
|
|
1842
|
+
});
|
|
1843
|
+
} else if (pin !== void 0) {
|
|
1844
|
+
const elsewhere = named2 ? [] : otherClaimSpans(lines, unit.lines, pin, page.bodyLine);
|
|
1845
|
+
const replaced = replacementAt(claim, unit.text, page.format);
|
|
1846
|
+
if (replaced !== void 0 && only === void 0) {
|
|
1847
|
+
out.push({
|
|
1848
|
+
kind: "claim-replaced",
|
|
1849
|
+
result,
|
|
1850
|
+
at: unit.lines.start,
|
|
1851
|
+
commitSha: replaced.commit,
|
|
1852
|
+
...replaced.share === void 0 ? {} : { wordShare: replaced.share },
|
|
1853
|
+
pin: entry.citation.claim?.integrity ?? "",
|
|
1854
|
+
would: pin
|
|
1855
|
+
});
|
|
1856
|
+
} else if (elsewhere.length > 0) {
|
|
1857
|
+
const id = result.citation.id;
|
|
1858
|
+
const anyway = id === void 0 ? "" : ` Re-run with --only ${id} to accept it anyway.`;
|
|
1859
|
+
declined.set(result.origin.index, {
|
|
1860
|
+
rule: "claim-changed",
|
|
1861
|
+
why: `Not re-pinned: the text there also appears at ${spellElsewhere(elsewhere)}, so a pin cannot identify it.${anyway}`
|
|
1862
|
+
});
|
|
1863
|
+
} else {
|
|
1864
|
+
const plan = { kind: "claim-accepted", result, unit, pin };
|
|
1865
|
+
if (replaced !== void 0) plan.replaced = true;
|
|
1866
|
+
if (entry.citation.claim?.lines !== void 0) {
|
|
1867
|
+
const body = toBodyLines(unit.lines, page.bodyLine);
|
|
1868
|
+
const recorded = parseLines(entry.citation.claim.lines);
|
|
1869
|
+
if (recorded === void 0 || recorded.start !== body.start || recorded.end !== body.end) {
|
|
1870
|
+
plan.lines = lineSpec(body);
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
out.push(plan);
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
if (result.source.status === "changed" || result.source.status === "never-true") {
|
|
1878
|
+
const stored = parseSrc(result.source.src);
|
|
1879
|
+
const span = result.source.newLines === void 0 ? void 0 : parseLines(result.source.newLines);
|
|
1880
|
+
const at = span === void 0 || span.start === stored.start && span.end === stored.end ? void 0 : { ...stored, start: span.start, end: span.end };
|
|
1881
|
+
try {
|
|
1882
|
+
const minted = await mintCitation({
|
|
1883
|
+
root: run.root,
|
|
1884
|
+
src: at === void 0 ? result.source.src : formatSrc(at),
|
|
1885
|
+
...run.key === void 0 ? {} : { key: run.key },
|
|
1886
|
+
...result.citation.source["commit-sha"] === void 0 ? { commitSha: false } : {},
|
|
1887
|
+
gitClient: git,
|
|
1888
|
+
...pageOptions.sourceIndex === void 0 ? {} : { sourceIndex: pageOptions.sourceIndex }
|
|
1889
|
+
});
|
|
1890
|
+
out.push({
|
|
1891
|
+
kind: "source-accepted",
|
|
1892
|
+
result,
|
|
1893
|
+
minted,
|
|
1894
|
+
...at === void 0 || span === void 0 ? {} : {
|
|
1895
|
+
span: {
|
|
1896
|
+
lines: lineSpec(span),
|
|
1897
|
+
from: spellLines({ start: stored.start ?? 1, end: stored.end ?? stored.start ?? 1 }),
|
|
1898
|
+
to: spellLines(span)
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
});
|
|
1902
|
+
} catch (error) {
|
|
1903
|
+
if (!(error instanceof CiteError)) throw error;
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
return out;
|
|
1907
|
+
};
|
|
1908
|
+
const pages = [];
|
|
1909
|
+
const updateOne = async (label, content, path) => {
|
|
1910
|
+
const setup = await prepared.setupFor(label, content);
|
|
1911
|
+
if (setup.sidecar !== void 0) hits.record(setup.sidecar, label);
|
|
1912
|
+
const report = await checkCitations({ file: label, content, format: forced?.name }, setup.options);
|
|
1913
|
+
const { format } = report;
|
|
1914
|
+
const severity = resolveSeverity(setup.options.severity);
|
|
1915
|
+
const page = readPage(label, content, {
|
|
1916
|
+
...forced === void 0 ? {} : { format: forced.name },
|
|
1917
|
+
...setup.options.citations === void 0 ? {} : { citations: setup.options.citations },
|
|
1918
|
+
...setup.options.owned === void 0 ? {} : { owned: setup.options.owned }
|
|
1919
|
+
});
|
|
1920
|
+
const lines = splitLines(content);
|
|
1921
|
+
const rewritten2 = [];
|
|
1922
|
+
const refused = [];
|
|
1923
|
+
const settled = /* @__PURE__ */ new Set();
|
|
1924
|
+
const declined = /* @__PURE__ */ new Map();
|
|
1925
|
+
const markers = decideMarkers({ page, report, lines, accept, only });
|
|
1926
|
+
const movedPage = markers.moves.length === 0 ? void 0 : applyMarkerMoves(
|
|
1927
|
+
content,
|
|
1928
|
+
markers.moves.map(({ misplaced }) => ({
|
|
1929
|
+
from: misplaced.line,
|
|
1930
|
+
place: misplaced.place
|
|
1931
|
+
})),
|
|
1932
|
+
(line) => markerIndent(content, line, format, page.bodyLine)
|
|
1933
|
+
);
|
|
1934
|
+
let after = movedPage?.content ?? content;
|
|
1935
|
+
const afterLines = movedPage === void 0 ? lines : splitLines(after);
|
|
1936
|
+
const movedIndexes = new Set(markers.moves.map(({ entry }) => entry.origin.index));
|
|
1937
|
+
const lineNow = (line) => movedPage?.line(line) ?? line;
|
|
1938
|
+
const owner = setup.sidecar?.owner;
|
|
1939
|
+
const entries = setup.sidecar?.citations === void 0 ? void 0 : JSON.parse(JSON.stringify(setup.sidecar.citations.map((c) => c.entry)));
|
|
1940
|
+
let manifestDirty = false;
|
|
1941
|
+
for (const result of report.citations) {
|
|
1942
|
+
const selected = only === void 0 || result.citation.id !== void 0 && only.has(result.citation.id);
|
|
1943
|
+
const entry = page.citations.find((c) => c.origin.index === result.origin.index);
|
|
1944
|
+
const plans = [];
|
|
1945
|
+
const move = selected ? markers.moves.find((m) => m.entry.origin.index === result.origin.index) : void 0;
|
|
1946
|
+
if (move !== void 0) {
|
|
1947
|
+
plans.push({
|
|
1948
|
+
kind: "marker-moved",
|
|
1949
|
+
result,
|
|
1950
|
+
from: move.misplaced.line,
|
|
1951
|
+
to: lineNow(move.misplaced.line)
|
|
1952
|
+
});
|
|
1953
|
+
}
|
|
1954
|
+
const marker = entry?.marker;
|
|
1955
|
+
const recorded = entry?.citation.claim?.integrity;
|
|
1956
|
+
if (selected && result.anchor === "marker" && marker !== void 0 && recorded !== void 0 && result.claim !== null) {
|
|
1957
|
+
const quote = entry?.citation.quote === true;
|
|
1958
|
+
const now = anchoredNow(after, format, afterLines, lineNow(marker.line), quote);
|
|
1959
|
+
const status = result.claim.status;
|
|
1960
|
+
if (now !== void 0 && now.pin !== recorded) {
|
|
1961
|
+
const unitText = afterLines.slice(now.span.start - 1, now.span.end);
|
|
1962
|
+
if (status === "moved" || status === "current" && move !== void 0) {
|
|
1963
|
+
plans.push({
|
|
1964
|
+
kind: "claim-reanchored",
|
|
1965
|
+
result,
|
|
1966
|
+
pin: now.pin,
|
|
1967
|
+
held: result.claim.fileLines ?? "",
|
|
1968
|
+
now: spellLines(now.span)
|
|
1969
|
+
});
|
|
1970
|
+
} else if (status === "reanchored") {
|
|
1971
|
+
const held = {
|
|
1972
|
+
kind: "claim-words-held",
|
|
1973
|
+
result,
|
|
1974
|
+
pin: now.pin,
|
|
1975
|
+
at: now.span.start,
|
|
1976
|
+
fromLines: result.claim.fileLines ?? "",
|
|
1977
|
+
toLines: spellLines(now.span)
|
|
1978
|
+
};
|
|
1979
|
+
if (result.claim.commitSha !== void 0) held.commitSha = result.claim.commitSha;
|
|
1980
|
+
plans.push(held);
|
|
1981
|
+
} else if (status === "changed" && accept) {
|
|
1982
|
+
const kind = quote ? "block" : "paragraph";
|
|
1983
|
+
const wide = tooWide(now.span);
|
|
1984
|
+
if (wide !== void 0) {
|
|
1985
|
+
declined.set(result.origin.index, {
|
|
1986
|
+
rule: "claim-changed",
|
|
1987
|
+
why: `Not re-pinned: the ${kind} ${wide}.`
|
|
1988
|
+
});
|
|
1989
|
+
} else {
|
|
1990
|
+
const replaced = replacementAt(result.claim, unitText, format);
|
|
1991
|
+
plans.push(
|
|
1992
|
+
replaced === void 0 || only !== void 0 ? {
|
|
1993
|
+
kind: "claim-accepted",
|
|
1994
|
+
result,
|
|
1995
|
+
pin: now.pin,
|
|
1996
|
+
markerLine: lineNow(marker.line),
|
|
1997
|
+
unit: { lines: now.span, kind, text: unitText },
|
|
1998
|
+
...replaced === void 0 ? {} : { replaced: true }
|
|
1999
|
+
} : {
|
|
2000
|
+
kind: "claim-replaced",
|
|
2001
|
+
result,
|
|
2002
|
+
at: now.span.start,
|
|
2003
|
+
commitSha: replaced.commit,
|
|
2004
|
+
...replaced.share === void 0 ? {} : { wordShare: replaced.share },
|
|
2005
|
+
pin: recorded,
|
|
2006
|
+
would: now.pin
|
|
2007
|
+
}
|
|
2008
|
+
);
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
}
|
|
2013
|
+
if (selected) {
|
|
2014
|
+
plans.push(...await plansFor(result, entry, page, lines, lineNow, declined));
|
|
2015
|
+
}
|
|
2016
|
+
const spec = entry?.citation.claim?.lines;
|
|
2017
|
+
const wentElsewhere = result.claim?.status === "moved";
|
|
2018
|
+
if (movedPage !== void 0 && spec !== void 0 && !wentElsewhere && !plans.some((p) => p.kind === "claim-moved")) {
|
|
2019
|
+
const at = parseLines(spec);
|
|
2020
|
+
const file = at === void 0 ? void 0 : toFileLines(at, page.bodyLine);
|
|
2021
|
+
const to = file === void 0 ? void 0 : { start: lineNow(file.start), end: lineNow(file.end) };
|
|
2022
|
+
if (file !== void 0 && to !== void 0 && to.end - to.start === file.end - file.start && (to.start !== file.start || to.end !== file.end)) {
|
|
2023
|
+
plans.push({
|
|
2024
|
+
kind: "claim-shifted",
|
|
2025
|
+
result,
|
|
2026
|
+
lines: lineSpec(toBodyLines(to, page.bodyLine)),
|
|
2027
|
+
from: spellLines(file),
|
|
2028
|
+
to: spellLines(to)
|
|
2029
|
+
});
|
|
2030
|
+
}
|
|
2031
|
+
}
|
|
2032
|
+
for (const plan of plans) {
|
|
2033
|
+
if (plan.kind === "claim-replaced") {
|
|
2034
|
+
refused.push(rewriteOf(plan));
|
|
2035
|
+
settled.add(`${String(result.origin.index)}\0${settles(plan)}`);
|
|
2036
|
+
continue;
|
|
2037
|
+
}
|
|
2038
|
+
if (plan.kind === "marker-moved") {
|
|
2039
|
+
} else if (result.origin.kind === "manifest") {
|
|
2040
|
+
const held = entries?.[result.origin.index];
|
|
2041
|
+
if (held === void 0) {
|
|
2042
|
+
declined.set(result.origin.index, {
|
|
2043
|
+
rule: settles(plan),
|
|
2044
|
+
why: `Not rewritten: ${owner?.file ?? "the manifest"} has no entry at index ${String(result.origin.index)}.`
|
|
2045
|
+
});
|
|
2046
|
+
continue;
|
|
2047
|
+
}
|
|
2048
|
+
if (!applyToEntry(held, plan)) continue;
|
|
2049
|
+
manifestDirty = true;
|
|
2050
|
+
} else {
|
|
2051
|
+
after = apply2(after, format, plan);
|
|
2052
|
+
}
|
|
2053
|
+
rewritten2.push(rewriteOf(plan));
|
|
2054
|
+
settled.add(`${String(result.origin.index)}\0${settles(plan)}`);
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
for (const index of movedIndexes) settled.add(`${String(index)}\0marker-misplaced`);
|
|
2058
|
+
if (manifestDirty && owner !== void 0 && entries !== void 0) {
|
|
2059
|
+
if (setup.sidecar?.entry === void 0) {
|
|
2060
|
+
throw new CiteError(
|
|
2061
|
+
`${label} carries no ${owner.join}: value, so its citations cannot be keyed in ${owner.file}.`
|
|
2062
|
+
);
|
|
2063
|
+
}
|
|
2064
|
+
await manifests.write(owner, setup.sidecar.entry, entries, 0);
|
|
2065
|
+
}
|
|
2066
|
+
const stayAt = new Map(markers.stays.map((stay) => [stay.line, stay]));
|
|
2067
|
+
const skipped2 = report.findings.filter(
|
|
2068
|
+
(finding) => (only === void 0 || finding.id !== void 0 && only.has(finding.id)) && !settled.has(`${String(finding.index ?? -1)}\0${finding.rule}`)
|
|
2069
|
+
).map((finding) => {
|
|
2070
|
+
if (finding.rule === "marker-misplaced") {
|
|
2071
|
+
const stay = stayAt.get(finding.line ?? -1);
|
|
2072
|
+
if (stay === void 0) return finding;
|
|
2073
|
+
const level = severity[stay.rule];
|
|
2074
|
+
return {
|
|
2075
|
+
...finding,
|
|
2076
|
+
message: stay.message,
|
|
2077
|
+
...level === "off" ? {} : { severity: level }
|
|
2078
|
+
};
|
|
2079
|
+
}
|
|
2080
|
+
const turned = finding.index === void 0 ? void 0 : declined.get(finding.index);
|
|
2081
|
+
const why = turned?.rule === finding.rule ? turned.why : void 0;
|
|
2082
|
+
return why === void 0 ? finding : { ...finding, message: `${finding.message} ${why}` };
|
|
2083
|
+
});
|
|
2084
|
+
const diff = after === content ? "" : unifiedDiff(label, content, after);
|
|
2085
|
+
const written = after !== content && path !== void 0 && opts.dryRun !== true;
|
|
2086
|
+
if (written) {
|
|
2087
|
+
try {
|
|
2088
|
+
await writeFileAtomic(path, after);
|
|
2089
|
+
} catch (error) {
|
|
2090
|
+
throw await writes.restore(label, error);
|
|
2091
|
+
}
|
|
2092
|
+
writes.record(path, label, content);
|
|
2093
|
+
}
|
|
2094
|
+
const out = { file: label, rewritten: rewritten2, refused, skipped: skipped2, diff, written };
|
|
2095
|
+
if (path === void 0) out.content = after;
|
|
2096
|
+
pages.push(out);
|
|
2097
|
+
return report;
|
|
2098
|
+
};
|
|
2099
|
+
const reports = [];
|
|
2100
|
+
if (usingStdin) reports.push(await updateOne(STDIN_LABEL, opts.stdinContent ?? ""));
|
|
2101
|
+
for (const file of files) {
|
|
2102
|
+
reports.push(await updateOne(file, await readTarget(run, file), resolve3(run.base, file)));
|
|
2103
|
+
}
|
|
2104
|
+
assertNoOrphanJoins(prepared, hits);
|
|
2105
|
+
const write = opts.dryRun !== true;
|
|
2106
|
+
const rewrittenManifests = [];
|
|
2107
|
+
const record = (changed) => {
|
|
2108
|
+
rewrittenManifests.push({ file: changed.file, diff: changed.diff, written: write });
|
|
2109
|
+
};
|
|
2110
|
+
if (write) {
|
|
2111
|
+
await manifests.commit({
|
|
2112
|
+
// A manifest joins the run's written files the moment it lands, so a
|
|
2113
|
+
// later manifest's failure puts this one back with the pages. Without
|
|
2114
|
+
// it the tree would keep pins no page content matches.
|
|
2115
|
+
after: (changed) => {
|
|
2116
|
+
writes.record(changed.path, changed.file, changed.before);
|
|
2117
|
+
record(changed);
|
|
2118
|
+
},
|
|
2119
|
+
onError: async (changed, error) => {
|
|
2120
|
+
throw await writes.restore(changed.file, error);
|
|
2121
|
+
}
|
|
2122
|
+
});
|
|
2123
|
+
} else for (const changed of manifests.changed()) record(changed);
|
|
2124
|
+
const reminted = pages.some(
|
|
2125
|
+
(page) => page.rewritten.some((r) => r.reason === "accepted" && r.end === "source")
|
|
2126
|
+
);
|
|
2127
|
+
const also = [];
|
|
2128
|
+
if (reminted && !await git.available()) also.push(GIT_UNAVAILABLE_COMMIT);
|
|
2129
|
+
if (recommit && git.shallow !== void 0 && git.isAncestor !== void 0 && await git.shallow()) {
|
|
2130
|
+
also.push(SHALLOW_RECOMMIT);
|
|
2131
|
+
}
|
|
2132
|
+
sayNotices(reports, opts.onNotice, also);
|
|
2133
|
+
const rewritten = pages.reduce((n, page) => n + citationsIn(page.rewritten), 0);
|
|
2134
|
+
const skipped = pages.reduce((n, page) => n + page.skipped.length + page.refused.length, 0);
|
|
2135
|
+
const undone = pages.some(
|
|
2136
|
+
(page) => page.refused.length > 0 || page.skipped.some((finding) => finding.severity === "error")
|
|
2137
|
+
);
|
|
2138
|
+
return {
|
|
2139
|
+
pages,
|
|
2140
|
+
rewritten,
|
|
2141
|
+
skipped,
|
|
2142
|
+
...rewrittenManifests.length > 0 ? { manifests: rewrittenManifests } : {},
|
|
2143
|
+
exitCode: undone ? 1 : 0
|
|
2144
|
+
};
|
|
2145
|
+
}
|
|
2146
|
+
|
|
2147
|
+
export {
|
|
2148
|
+
mintCitation,
|
|
2149
|
+
shiftedEntries,
|
|
2150
|
+
withClaimLines,
|
|
2151
|
+
appendFrontmatterCitation,
|
|
2152
|
+
removeFrontmatterCitations,
|
|
2153
|
+
removeLine,
|
|
2154
|
+
entryObject,
|
|
2155
|
+
spliceEntryField,
|
|
2156
|
+
insertStatementBefore,
|
|
2157
|
+
unifiedDiff,
|
|
2158
|
+
ManifestSet,
|
|
2159
|
+
splice,
|
|
2160
|
+
itemLine,
|
|
2161
|
+
runAdd,
|
|
2162
|
+
runRemove,
|
|
2163
|
+
runUpdate
|
|
2164
|
+
};
|
|
2165
|
+
//# sourceMappingURL=chunk-5YXBLLAC.js.map
|