paperlint 8.0.0 → 9.0.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/CLAUDE.md +9 -25
- package/CONTRIBUTING.md +5 -4
- package/README.md +4 -3
- package/dist/adapters/bibtex/index.d.ts +3 -0
- package/dist/adapters/bibtex/index.d.ts.map +1 -0
- package/dist/adapters/bibtex/index.js +103 -0
- package/dist/adapters/bibtex/index.js.map +1 -0
- package/dist/adapters/latex/filecontents.d.ts +33 -0
- package/dist/adapters/latex/filecontents.d.ts.map +1 -0
- package/dist/adapters/latex/filecontents.js +65 -0
- package/dist/adapters/latex/filecontents.js.map +1 -0
- package/dist/adapters/latex/includes.d.ts +3 -3
- package/dist/adapters/latex/includes.d.ts.map +1 -1
- package/dist/adapters/latex/includes.js +18 -10
- package/dist/adapters/latex/includes.js.map +1 -1
- package/dist/adapters/latex/index.d.ts.map +1 -1
- package/dist/adapters/latex/index.js +2 -0
- package/dist/adapters/latex/index.js.map +1 -1
- package/dist/adapters/latex/nodes.d.ts +12 -0
- package/dist/adapters/latex/nodes.d.ts.map +1 -1
- package/dist/adapters/latex/nodes.js +39 -0
- package/dist/adapters/latex/nodes.js.map +1 -1
- package/dist/adapters/latex/parse.d.ts +7 -1
- package/dist/adapters/latex/parse.d.ts.map +1 -1
- package/dist/adapters/latex/parse.js +55 -6
- package/dist/adapters/latex/parse.js.map +1 -1
- package/dist/adapters/memory/index.d.ts +3 -0
- package/dist/adapters/memory/index.d.ts.map +1 -1
- package/dist/adapters/memory/index.js +9 -0
- package/dist/adapters/memory/index.js.map +1 -1
- package/dist/adapters/references/index.d.ts.map +1 -1
- package/dist/adapters/references/index.js +27 -24
- package/dist/adapters/references/index.js.map +1 -1
- package/dist/adapters/sources-record/index.d.ts +9 -0
- package/dist/adapters/sources-record/index.d.ts.map +1 -0
- package/dist/adapters/sources-record/index.js +68 -0
- package/dist/adapters/sources-record/index.js.map +1 -0
- package/dist/adapters/tex-output/blg.d.ts +9 -0
- package/dist/adapters/tex-output/blg.d.ts.map +1 -0
- package/dist/adapters/tex-output/blg.js +20 -0
- package/dist/adapters/tex-output/blg.js.map +1 -0
- package/dist/adapters/tex-output/fls.d.ts +8 -0
- package/dist/adapters/tex-output/fls.d.ts.map +1 -0
- package/dist/adapters/tex-output/fls.js +16 -0
- package/dist/adapters/tex-output/fls.js.map +1 -0
- package/dist/adapters/tex-output/index.d.ts +7 -0
- package/dist/adapters/tex-output/index.d.ts.map +1 -0
- package/dist/adapters/tex-output/index.js +7 -0
- package/dist/adapters/tex-output/index.js.map +1 -0
- package/dist/bib-rules.d.ts +47 -0
- package/dist/bib-rules.d.ts.map +1 -0
- package/dist/bib-rules.js +127 -0
- package/dist/bib-rules.js.map +1 -0
- package/dist/build.d.ts +29 -1
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +40 -24
- package/dist/build.js.map +1 -1
- package/dist/claim-provenance.d.ts +2 -2
- package/dist/cli.d.ts +6 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +30 -26
- package/dist/cli.js.map +1 -1
- package/dist/domain/paper-source.d.ts +1 -10
- package/dist/domain/paper-source.d.ts.map +1 -1
- package/dist/domain/paper-source.js +34 -24
- package/dist/domain/paper-source.js.map +1 -1
- package/dist/domain/paper-sources.d.ts +72 -0
- package/dist/domain/paper-sources.d.ts.map +1 -0
- package/dist/domain/paper-sources.js +18 -0
- package/dist/domain/paper-sources.js.map +1 -0
- package/dist/domain/sources-record.d.ts +102 -0
- package/dist/domain/sources-record.d.ts.map +1 -0
- package/dist/domain/sources-record.js +38 -0
- package/dist/domain/sources-record.js.map +1 -0
- package/dist/domain/tex-run.d.ts +25 -0
- package/dist/domain/tex-run.d.ts.map +1 -0
- package/dist/domain/tex-run.js +79 -0
- package/dist/domain/tex-run.js.map +1 -0
- package/dist/eslint-rules/paper-context.d.ts.map +1 -1
- package/dist/eslint-rules/paper-context.js +4 -3
- package/dist/eslint-rules/paper-context.js.map +1 -1
- package/dist/eslint-rules/paper-typography.d.ts +0 -9
- package/dist/eslint-rules/paper-typography.d.ts.map +1 -1
- package/dist/eslint-rules/paper-typography.js +12 -5
- package/dist/eslint-rules/paper-typography.js.map +1 -1
- package/dist/latex-log.d.ts +10 -0
- package/dist/latex-log.d.ts.map +1 -1
- package/dist/latex-log.js +10 -0
- package/dist/latex-log.js.map +1 -1
- package/dist/latex-loop.d.ts +2 -0
- package/dist/latex-loop.d.ts.map +1 -1
- package/dist/latex-loop.js.map +1 -1
- package/dist/package-dirs.d.ts.map +1 -1
- package/dist/package-dirs.js +3 -2
- package/dist/package-dirs.js.map +1 -1
- package/dist/paper-includes.d.ts +4 -7
- package/dist/paper-includes.d.ts.map +1 -1
- package/dist/paper-includes.js +29 -21
- package/dist/paper-includes.js.map +1 -1
- package/dist/paper-prose.d.ts +2 -2
- package/dist/paper-record.d.ts +25 -0
- package/dist/paper-record.d.ts.map +1 -0
- package/dist/paper-record.js +39 -0
- package/dist/paper-record.js.map +1 -0
- package/dist/paper-sources.d.ts +66 -0
- package/dist/paper-sources.d.ts.map +1 -0
- package/dist/paper-sources.js +92 -0
- package/dist/paper-sources.js.map +1 -0
- package/dist/ports/bib-reader.d.ts +18 -0
- package/dist/ports/bib-reader.d.ts.map +1 -0
- package/dist/ports/bib-reader.js +2 -0
- package/dist/ports/bib-reader.js.map +1 -0
- package/dist/ports/check-references.d.ts +9 -4
- package/dist/ports/check-references.d.ts.map +1 -1
- package/dist/ports/latex.d.ts +15 -0
- package/dist/ports/latex.d.ts.map +1 -1
- package/dist/ports/sources-codec.d.ts +11 -0
- package/dist/ports/sources-codec.d.ts.map +1 -0
- package/dist/ports/sources-codec.js +2 -0
- package/dist/ports/sources-codec.js.map +1 -0
- package/dist/ports/tex-output.d.ts +17 -0
- package/dist/ports/tex-output.d.ts.map +1 -0
- package/dist/ports/tex-output.js +2 -0
- package/dist/ports/tex-output.js.map +1 -0
- package/dist/recorded-bibliography.d.ts +89 -0
- package/dist/recorded-bibliography.d.ts.map +1 -0
- package/dist/recorded-bibliography.js +136 -0
- package/dist/recorded-bibliography.js.map +1 -0
- package/dist/reference-rules.d.ts +5 -5
- package/dist/reference-rules.d.ts.map +1 -1
- package/dist/reference-rules.js +150 -77
- package/dist/reference-rules.js.map +1 -1
- package/dist/references.d.ts +71 -16
- package/dist/references.d.ts.map +1 -1
- package/dist/references.js +110 -26
- package/dist/references.js.map +1 -1
- package/dist/register-bands.d.ts +3 -3
- package/dist/register-bands.d.ts.map +1 -1
- package/dist/register-bands.js.map +1 -1
- package/dist/register.d.ts +2 -2
- package/dist/sources-record.d.ts +38 -0
- package/dist/sources-record.d.ts.map +1 -0
- package/dist/sources-record.js +123 -0
- package/dist/sources-record.js.map +1 -0
- package/dist/sources-rules.d.ts +44 -0
- package/dist/sources-rules.d.ts.map +1 -0
- package/dist/sources-rules.js +76 -0
- package/dist/sources-rules.js.map +1 -0
- package/dist/tex-paper.d.ts +10 -15
- package/dist/tex-paper.d.ts.map +1 -1
- package/dist/tex-paper.js +24 -40
- package/dist/tex-paper.js.map +1 -1
- package/dist/tex-venue-rules.d.ts +3 -3
- package/dist/tex-venue-rules.d.ts.map +1 -1
- package/dist/tex-venue-rules.js.map +1 -1
- package/docs/configuration.md +12 -4
- package/docs/design/paper-sources.md +197 -0
- package/docs/package-shape-options.md +1 -1
- package/docs/references.md +34 -8
- package/docs/rules/bib/reachable-entry.md +64 -0
- package/docs/rules/paper/sources-fresh.md +81 -0
- package/docs/rules.md +62 -57
- package/eslint-rules/paper-context.ts +4 -3
- package/eslint-rules/paper-typography.test.mjs +12 -72
- package/eslint-rules/paper-typography.ts +13 -6
- package/eslint.config.mjs +22 -0
- package/fixtures/build-e2e/aidc/paper.tex +1 -1
- package/fixtures/build-e2e/anon-ack/paper.tex +1 -1
- package/fixtures/build-e2e/anon-author/paper.tex +1 -1
- package/fixtures/build-e2e/anon-bib/paper.tex +1 -1
- package/fixtures/build-e2e/anon-href/paper.tex +1 -1
- package/fixtures/build-e2e/anon-macro/paper.tex +1 -1
- package/fixtures/build-e2e/anon-pdfauthor/paper.tex +1 -1
- package/fixtures/paper-sources/p1/paper.tex +30 -0
- package/fixtures/paper-sources/p1/refs.bib +6 -0
- package/fixtures/paper-sources/p1/sections/dead.tex +6 -0
- package/fixtures/paper-sources/p1/sections/intro.tex +7 -0
- package/fixtures/paper-sources/p1/sections/parked.tex +6 -0
- package/fixtures/paper-sources/p1/tex-truth.json +37 -0
- package/fixtures/paper-sources/p1/versions/2026-01-01-review.tex +4 -0
- package/fixtures/paper-sources/v1-stale/paper.tex +14 -0
- package/fixtures/paper-sources/v1-stale/refs.bib +6 -0
- package/fixtures/paper-sources/v1-stale/tex-truth.json +32 -0
- package/fixtures/paper-sources/v10-ifthenelse/anon.bib +1 -0
- package/fixtures/paper-sources/v10-ifthenelse/paper.tex +9 -0
- package/fixtures/paper-sources/v10-ifthenelse/real.bib +1 -0
- package/fixtures/paper-sources/v10-ifthenelse/tex-truth.json +28 -0
- package/fixtures/paper-sources/v11-two-blocks/paper.tex +12 -0
- package/fixtures/paper-sources/v11-two-blocks/tex-truth.json +30 -0
- package/fixtures/paper-sources/v13-percent-text/paper.tex +6 -0
- package/fixtures/paper-sources/v13-percent-text/refs.bib +3 -0
- package/fixtures/paper-sources/v13-percent-text/tex-truth.json +30 -0
- package/fixtures/paper-sources/v15-after-end/paper.tex +6 -0
- package/fixtures/paper-sources/v15-after-end/refs.bib +1 -0
- package/fixtures/paper-sources/v15-after-end/tex-truth.json +17 -0
- package/fixtures/paper-sources/v16-included-block/bibblock.tex +3 -0
- package/fixtures/paper-sources/v16-included-block/paper.tex +7 -0
- package/fixtures/paper-sources/v16-included-block/refs.bib +1 -0
- package/fixtures/paper-sources/v16-included-block/tex-truth.json +35 -0
- package/fixtures/paper-sources/v17-include-order/bibsetup.tex +1 -0
- package/fixtures/paper-sources/v17-include-order/first.bib +1 -0
- package/fixtures/paper-sources/v17-include-order/later.bib +1 -0
- package/fixtures/paper-sources/v17-include-order/paper.tex +7 -0
- package/fixtures/paper-sources/v17-include-order/tex-truth.json +40 -0
- package/fixtures/paper-sources/v2-overwrite/paper.tex +14 -0
- package/fixtures/paper-sources/v2-overwrite/refs.bib +6 -0
- package/fixtures/paper-sources/v2-overwrite/tex-truth.json +30 -0
- package/fixtures/paper-sources/v22-parked-thebibliography/paper.tex +7 -0
- package/fixtures/paper-sources/v22-parked-thebibliography/tex-truth.json +17 -0
- package/fixtures/paper-sources/v23-parked-include/old.bib +1 -0
- package/fixtures/paper-sources/v23-parked-include/paper.tex +7 -0
- package/fixtures/paper-sources/v23-parked-include/parked.tex +1 -0
- package/fixtures/paper-sources/v23-parked-include/refs.bib +1 -0
- package/fixtures/paper-sources/v23-parked-include/tex-truth.json +28 -0
- package/fixtures/paper-sources/v24-quoted-fields/paper.tex +6 -0
- package/fixtures/paper-sources/v24-quoted-fields/refs.bib +5 -0
- package/fixtures/paper-sources/v24-quoted-fields/tex-truth.json +32 -0
- package/fixtures/paper-sources/v25-string-differs/paper.tex +10 -0
- package/fixtures/paper-sources/v25-string-differs/refs.bib +2 -0
- package/fixtures/paper-sources/v25-string-differs/tex-truth.json +32 -0
- package/fixtures/paper-sources/v26-comment-in-names/one.bib +1 -0
- package/fixtures/paper-sources/v26-comment-in-names/paper.tex +7 -0
- package/fixtures/paper-sources/v26-comment-in-names/tex-truth.json +32 -0
- package/fixtures/paper-sources/v26-comment-in-names/two.bib +1 -0
- package/fixtures/paper-sources/v27-shared-string/abbreviations.bib +2 -0
- package/fixtures/paper-sources/v27-shared-string/paper.tex +6 -0
- package/fixtures/paper-sources/v27-shared-string/references.bib +6 -0
- package/fixtures/paper-sources/v27-shared-string/tex-truth.json +31 -0
- package/fixtures/paper-sources/v28-shared-string-block/abbreviations.bib +2 -0
- package/fixtures/paper-sources/v28-shared-string-block/paper.tex +14 -0
- package/fixtures/paper-sources/v28-shared-string-block/tex-truth.json +33 -0
- package/fixtures/paper-sources/v29-generated-input/paper.tex +9 -0
- package/fixtures/paper-sources/v29-generated-input/tex-truth.json +24 -0
- package/fixtures/paper-sources/v3-declared/paper.bib +6 -0
- package/fixtures/paper-sources/v3-declared/paper.tex +6 -0
- package/fixtures/paper-sources/v3-declared/tex-truth.json +28 -0
- package/fixtures/paper-sources/v30-name-next-line/paper.tex +14 -0
- package/fixtures/paper-sources/v30-name-next-line/tex-truth.json +30 -0
- package/fixtures/paper-sources/v4-commented/paper.tex +14 -0
- package/fixtures/paper-sources/v4-commented/refs.bib +6 -0
- package/fixtures/paper-sources/v4-commented/tex-truth.json +28 -0
- package/fixtures/paper-sources/v5-percent-entry/paper.tex +20 -0
- package/fixtures/paper-sources/v5-percent-entry/tex-truth.json +37 -0
- package/fixtures/paper-sources/v6-unclosed/paper.tex +6 -0
- package/fixtures/paper-sources/v6-unclosed/refs.bib +5 -0
- package/fixtures/paper-sources/v6-unclosed/tex-truth.json +37 -0
- package/fixtures/paper-sources/v8-jobname/paper.tex +9 -0
- package/fixtures/paper-sources/v8-jobname/tex-truth.json +30 -0
- package/fixtures/paper-sources/v9-two-bibliographies/one.bib +1 -0
- package/fixtures/paper-sources/v9-two-bibliographies/paper.tex +7 -0
- package/fixtures/paper-sources/v9-two-bibliographies/tex-truth.json +35 -0
- package/fixtures/paper-sources/v9-two-bibliographies/two.bib +1 -0
- package/fixtures/paper-typography/clean-paper/paper.tex +1 -1
- package/fixtures/paper-typography/messy-paper/paper.tex +1 -1
- package/package.json +2 -10
- package/scripts/check.test.ts +23 -1
- package/scripts/check.ts +7 -3
- package/scripts/coverage-config.test.ts +19 -10
- package/scripts/layer-legacy-frozen.test.ts +41 -2
- package/scripts/layer-legacy-frozen.ts +31 -2
- package/skills/grade-paper-writing/SKILL.md +4 -3
- package/skills/grade-paper-writing/SKILL.md.spec.ts +3 -2
- package/skills/grade-paper-writing/prose-lint.mjs +11 -2
- package/skills/grade-paper-writing/prose-lint.test.ts +52 -4
- package/skills/paper-pipeline/scripts/extract-ref-facts.harness.mjs +83 -33
- package/skills/paper-pipeline/scripts/extract-ref-facts.mjs +67 -115
- package/skills/paper-pipeline/scripts/extract-ref-facts.test.mjs +128 -121
- package/skills/verify-citations/SKILL.md +7 -4
- package/skills/verify-citations/SKILL.md.spec.ts +6 -3
- package/skills/verify-citations/scripts/bib-authors.check.test.mjs +17 -19
- package/skills/verify-citations/scripts/bib-authors.cli.test.ts +129 -46
- package/skills/verify-citations/scripts/bib-authors.d.mts +22 -2
- package/skills/verify-citations/scripts/bib-authors.mjs +57 -111
- package/skills/verify-citations/scripts/verify-cites.cli.test.ts +16 -0
- package/skills/verify-citations/scripts/verify-cites.d.mts +19 -3
- package/skills/verify-citations/scripts/verify-cites.mjs +42 -108
- package/skills/verify-citations/scripts/verify-cites.test.mjs +23 -36
- package/src/adapters/bibtex/index.test.ts +163 -0
- package/src/adapters/bibtex/index.ts +155 -0
- package/src/adapters/latex/filecontents.ts +114 -0
- package/src/adapters/latex/includes.ts +18 -10
- package/src/adapters/latex/index.test.ts +73 -0
- package/src/adapters/latex/index.ts +2 -0
- package/src/adapters/latex/nodes.ts +64 -0
- package/src/adapters/latex/parse.test.ts +38 -0
- package/src/adapters/latex/parse.ts +67 -6
- package/src/adapters/memory/index.ts +17 -0
- package/src/adapters/references/index.test.ts +75 -21
- package/src/adapters/references/index.ts +37 -32
- package/src/adapters/sources-record/index.test.ts +87 -0
- package/src/adapters/sources-record/index.ts +82 -0
- package/src/adapters/tex-output/blg.ts +27 -0
- package/src/adapters/tex-output/fls.ts +24 -0
- package/src/adapters/tex-output/index.test.ts +171 -0
- package/src/adapters/tex-output/index.ts +12 -0
- package/src/bib-rules.test.ts +356 -0
- package/src/bib-rules.ts +228 -0
- package/src/bibliography-at.test.ts +286 -0
- package/src/build.test.ts +210 -0
- package/src/build.ts +100 -35
- package/src/claim-provenance.test.ts +33 -7
- package/src/claim-provenance.ts +2 -2
- package/src/cli.harness.mjs +20 -10
- package/src/cli.test.ts +14 -10
- package/src/cli.ts +37 -28
- package/src/domain/paper-source.test.ts +58 -20
- package/src/domain/paper-source.ts +38 -42
- package/src/domain/paper-sources.ts +94 -0
- package/src/domain/sources-record.test.ts +71 -0
- package/src/domain/sources-record.ts +136 -0
- package/src/domain/tex-run.test.ts +228 -0
- package/src/domain/tex-run.ts +139 -0
- package/src/heading-case.test.ts +24 -10
- package/src/latex-log.ts +20 -0
- package/src/latex-loop.harness.ts +1 -0
- package/src/latex-loop.ts +2 -0
- package/src/linted-set.test.ts +9 -1
- package/src/new-paper.harness.mjs +3 -2
- package/src/new-paper.test.ts +9 -5
- package/src/package-dirs.ts +3 -2
- package/src/paper-includes.test.ts +157 -38
- package/src/paper-includes.ts +39 -35
- package/src/paper-prose.test.ts +34 -5
- package/src/paper-prose.ts +2 -2
- package/src/paper-record.test.ts +173 -0
- package/src/paper-record.ts +66 -0
- package/src/paper-settings-commands.test.ts +5 -2
- package/src/paper-sources.ts +151 -0
- package/src/ports/bib-reader.ts +27 -0
- package/src/ports/check-references.ts +7 -3
- package/src/ports/latex.ts +19 -0
- package/src/ports/sources-codec.ts +11 -0
- package/src/ports/tex-output.ts +17 -0
- package/src/recorded-bibliography.ts +297 -0
- package/src/reference-rules.ts +217 -104
- package/src/references.test.ts +520 -177
- package/src/references.ts +185 -37
- package/src/register-bands.test.ts +5 -1
- package/src/register-bands.ts +3 -5
- package/src/register.test.ts +6 -1
- package/src/register.ts +2 -2
- package/src/rule-cases.test.ts +68 -12
- package/src/rule-docs.test.ts +0 -1
- package/src/sources-record.test.ts +308 -0
- package/src/sources-record.ts +231 -0
- package/src/sources-rules.test.ts +152 -0
- package/src/sources-rules.ts +117 -0
- package/src/tex-paper.test.ts +148 -78
- package/src/tex-paper.ts +31 -61
- package/src/tex-venue-rules.test.ts +36 -8
- package/src/tex-venue-rules.ts +3 -5
- package/dist/eslint-rules/bib-reachable-entry.d.ts +0 -22
- package/dist/eslint-rules/bib-reachable-entry.d.ts.map +0 -1
- package/dist/eslint-rules/bib-reachable-entry.js +0 -75
- package/dist/eslint-rules/bib-reachable-entry.js.map +0 -1
- package/eslint-rules/bib-reachable-entry.ts +0 -76
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE `filecontents` BLOCKS OF A SOURCE — where a `.bib` TeX wrote stands in the text that wrote it.
|
|
3
|
+
* Read from the parse tree, never decided: which file a block writes, and whether TeX ran it, is
|
|
4
|
+
* TeX's answer (`written` in `_build/sources.json`); this reader only says where each block's text
|
|
5
|
+
* is, so a finding about an entry TeX read can be shown at the block that holds it
|
|
6
|
+
* (docs/design/paper-sources.md §1).
|
|
7
|
+
*
|
|
8
|
+
* unified-latex reads a `filecontents` environment as verbatim; its option and file name are the
|
|
9
|
+
* first things in that node, white space between them — a line break too: `\begin{filecontents*}` with
|
|
10
|
+
* `{refs.bib}` on the next line writes `refs.bib` (measured with pdflatex) — and its body starts on the
|
|
11
|
+
* line after the name, as LaTeX's own reader starts it.
|
|
12
|
+
* A block is live when the tree keeps it: a comment is a node of its own, `\iffalse … \fi` is cut at
|
|
13
|
+
* parse (`conditionals.ts`), and nothing after `\end{document}` is read (`liveRoot`).
|
|
14
|
+
*/
|
|
15
|
+
import type * as Ast from "@unified-latex/unified-latex-types";
|
|
16
|
+
import type { Filecontents } from "../../ports/latex.ts";
|
|
17
|
+
import {
|
|
18
|
+
argumentPieces,
|
|
19
|
+
inPlace,
|
|
20
|
+
isNode,
|
|
21
|
+
placeOf,
|
|
22
|
+
visited,
|
|
23
|
+
type ArgumentPiece,
|
|
24
|
+
type Node,
|
|
25
|
+
} from "./nodes.ts";
|
|
26
|
+
import { liveRoot, parseLatex, type ParsedTex } from "./parse.ts";
|
|
27
|
+
|
|
28
|
+
const FILECONTENTS: ReadonlySet<string> = new Set([
|
|
29
|
+
"filecontents",
|
|
30
|
+
"filecontents*",
|
|
31
|
+
]);
|
|
32
|
+
|
|
33
|
+
/** Names and options as TeX reads them: off the tree, a comma between two names. */
|
|
34
|
+
type Name = readonly ArgumentPiece[];
|
|
35
|
+
|
|
36
|
+
/** What a name or an option is written as: its pieces joined, white space trimmed off. */
|
|
37
|
+
export const written = (name: Name): string =>
|
|
38
|
+
name
|
|
39
|
+
.map((p) => p.text)
|
|
40
|
+
.join("")
|
|
41
|
+
.trim();
|
|
42
|
+
|
|
43
|
+
/** What may stand before the name: nothing, or one `[options]` — anything else is a block with no name. */
|
|
44
|
+
const OPTION = /^(?:\[[^\]]*\])?$/;
|
|
45
|
+
|
|
46
|
+
/** A block's head: its name's group, and its options. */
|
|
47
|
+
interface Head {
|
|
48
|
+
readonly name: Node;
|
|
49
|
+
readonly options: readonly string[];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** The head of a block whose text is `text`: the first group, with only white space and an option before it. */
|
|
53
|
+
function headOf(text: string): Head | null {
|
|
54
|
+
const nodes = parseLatex(text).root.content;
|
|
55
|
+
const at = nodes.findIndex((x) => x.type === "group");
|
|
56
|
+
const option = written(argumentPieces(nodes.slice(0, Math.max(at, 0)), null));
|
|
57
|
+
const name = nodes[at];
|
|
58
|
+
if (name === undefined || !OPTION.test(option)) return null;
|
|
59
|
+
return {
|
|
60
|
+
name,
|
|
61
|
+
options: option
|
|
62
|
+
.replace(/^\[|\]$/g, "")
|
|
63
|
+
.split(",")
|
|
64
|
+
.map((o) => o.trim()),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* One verbatim `filecontents` node as a block: its name, option, and body. unified-latex reads the
|
|
70
|
+
* whole environment as verbatim, so its text is parsed on its own and the name read off that tree like
|
|
71
|
+
* a declaration's (`headOf`). The body starts on the line after the one the name ends on; what TeX
|
|
72
|
+
* ignores after the name on that line is no body.
|
|
73
|
+
*/
|
|
74
|
+
export function blockOf(
|
|
75
|
+
src: string,
|
|
76
|
+
n: Readonly<Ast.VerbatimEnvironment>,
|
|
77
|
+
jobname: string,
|
|
78
|
+
): readonly Filecontents[] {
|
|
79
|
+
return inPlace(placeOf(n), (span) => {
|
|
80
|
+
// The node runs from `\begin{env}` to past `\end{env}`.
|
|
81
|
+
const headAt = span.start + `\\begin{${n.env}}`.length;
|
|
82
|
+
const end = span.end - `\\end{${n.env}}`.length;
|
|
83
|
+
const head = headOf(src.slice(headAt, end));
|
|
84
|
+
if (head === null) return [];
|
|
85
|
+
return inPlace(placeOf(head.name), (named) => {
|
|
86
|
+
const after = headAt + named.end;
|
|
87
|
+
// What TeX ignores after the name, up to the end of its line.
|
|
88
|
+
const rest = src.slice(after, end).split("\n", 1).join("");
|
|
89
|
+
return [
|
|
90
|
+
{
|
|
91
|
+
writes: written(argumentPieces([head.name], jobname)),
|
|
92
|
+
overwrite: head.options.some(
|
|
93
|
+
(o) => o === "overwrite" || o === "force",
|
|
94
|
+
),
|
|
95
|
+
span,
|
|
96
|
+
body: { start: Math.min(after + rest.length + 1, end), end },
|
|
97
|
+
},
|
|
98
|
+
];
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const isBlock = (n: Node): n is Readonly<Ast.VerbatimEnvironment> =>
|
|
104
|
+
n.type === "verbatim" && FILECONTENTS.has(n.env);
|
|
105
|
+
|
|
106
|
+
/** Every live `filecontents` block of a source whose main file's name is `jobname`, in source order. */
|
|
107
|
+
export const filecontentsOf = (
|
|
108
|
+
t: ParsedTex,
|
|
109
|
+
jobname: string,
|
|
110
|
+
): readonly Filecontents[] =>
|
|
111
|
+
visited(liveRoot(t), () => false)
|
|
112
|
+
.filter(isNode)
|
|
113
|
+
.filter(isBlock)
|
|
114
|
+
.flatMap((n) => blockOf(t.src, n, jobname));
|
|
@@ -4,6 +4,7 @@ import type { Include } from "../../domain/paper-source.ts";
|
|
|
4
4
|
import type { Span } from "../../domain/tex-document.ts";
|
|
5
5
|
import { isDefinition } from "./hidden.ts";
|
|
6
6
|
import {
|
|
7
|
+
argumentPieces,
|
|
7
8
|
inPlace,
|
|
8
9
|
isNode,
|
|
9
10
|
macroPlace,
|
|
@@ -13,7 +14,7 @@ import {
|
|
|
13
14
|
type Macro,
|
|
14
15
|
type Node,
|
|
15
16
|
} from "./nodes.ts";
|
|
16
|
-
import type
|
|
17
|
+
import { liveRoot, type ParsedTex } from "./parse.ts";
|
|
17
18
|
|
|
18
19
|
const INCLUDE_MACROS: ReadonlyMap<string, Include["macro"]> = new Map([
|
|
19
20
|
["input", "input"],
|
|
@@ -22,15 +23,22 @@ const INCLUDE_MACROS: ReadonlyMap<string, Include["macro"]> = new Map([
|
|
|
22
23
|
]);
|
|
23
24
|
|
|
24
25
|
/**
|
|
25
|
-
* The path an include names
|
|
26
|
-
*
|
|
27
|
-
*
|
|
26
|
+
* The path an include names: its braced argument read off the tree as TeX reads it (a comment is no
|
|
27
|
+
* part of it; a macro stays as written: nothing here expands one), or — TeX's own form, `\\input name`
|
|
28
|
+
* — the name up to the next space. unified-latex gives the unbraced form an argument of the first
|
|
29
|
+
* token only (`sections` of `sections/a`), so the source is read for that one.
|
|
28
30
|
*/
|
|
29
31
|
function argumentText(src: string, m: Macro): string | null {
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
+
const arg = mandatory(m)[0];
|
|
33
|
+
const start = arg?.content[0]?.position?.start.offset;
|
|
34
|
+
if (arg === undefined || start === undefined) return null;
|
|
35
|
+
// The parser gives both forms `{` as their open mark; the source says which one was written.
|
|
36
|
+
if (src[start - 1] === "{")
|
|
37
|
+
return argumentPieces(arg.content, null)
|
|
38
|
+
.map((p) => p.text)
|
|
39
|
+
.join("");
|
|
32
40
|
const rest = src.slice(start);
|
|
33
|
-
const stop = rest.search(
|
|
41
|
+
const stop = rest.search(/[\s{}%]/u);
|
|
34
42
|
return stop < 0 ? rest : rest.slice(0, stop);
|
|
35
43
|
}
|
|
36
44
|
|
|
@@ -44,11 +52,11 @@ function includeOf(src: string, m: Macro): readonly Include[] {
|
|
|
44
52
|
|
|
45
53
|
/**
|
|
46
54
|
* Every `\input`, `\include` and `\subfile` of the source, in source order — not in a comment (a
|
|
47
|
-
* comment is a node of its own)
|
|
48
|
-
* is used, and nothing here expands macros.
|
|
55
|
+
* comment is a node of its own), not in a macro definition's body, which is read where the macro
|
|
56
|
+
* is used, and nothing here expands macros; not after `\end{document}`, which TeX never reads.
|
|
49
57
|
*/
|
|
50
58
|
export const includesOf = (t: ParsedTex): readonly Include[] =>
|
|
51
|
-
visited(t
|
|
59
|
+
visited(liveRoot(t), isDefinition)
|
|
52
60
|
.filter(isNode)
|
|
53
61
|
.flatMap((n) => (n.type === "macro" ? includeOf(t.src, n) : []));
|
|
54
62
|
|
|
@@ -697,3 +697,76 @@ describe("bodyEmphasis — each style, and edge cases", () => {
|
|
|
697
697
|
expect(e && src.slice(e.span.start, e.span.end)).toBe("\\textbf{see it}");
|
|
698
698
|
});
|
|
699
699
|
});
|
|
700
|
+
|
|
701
|
+
describe("latexReader.filecontents — the blocks that write a file, where they stand", () => {
|
|
702
|
+
const SRC =
|
|
703
|
+
"\\documentclass{x}\n" +
|
|
704
|
+
"\\begin{filecontents*}[overwrite]{refs.bib}\n@misc{a, title={A}}\n\\end{filecontents*}\n" +
|
|
705
|
+
"\\begin{document}\n" +
|
|
706
|
+
"\\begin{filecontents}{\\jobname.bib}\n@misc{b, title={B}}\n\\end{filecontents}\n" +
|
|
707
|
+
"\\end{document}\n" +
|
|
708
|
+
"\\begin{filecontents}{late.bib}\n@misc{c}\n\\end{filecontents}\n";
|
|
709
|
+
|
|
710
|
+
it("each live block, in order: the file it writes (\\jobname expanded), [overwrite], its span and body", () => {
|
|
711
|
+
const blocks = latexReader.filecontents(SRC, "paper");
|
|
712
|
+
expect(
|
|
713
|
+
blocks.map((b) => [
|
|
714
|
+
b.writes,
|
|
715
|
+
b.overwrite,
|
|
716
|
+
SRC.slice(b.body.start, b.body.end),
|
|
717
|
+
]),
|
|
718
|
+
).toEqual([
|
|
719
|
+
["refs.bib", true, "@misc{a, title={A}}\n"],
|
|
720
|
+
["paper.bib", false, "@misc{b, title={B}}\n"],
|
|
721
|
+
]);
|
|
722
|
+
expect(SRC.slice(blocks[0]?.span.start, blocks[0]?.span.end)).toMatch(
|
|
723
|
+
/^\\begin\{filecontents\*\}.*\\end\{filecontents\*\}$/s,
|
|
724
|
+
);
|
|
725
|
+
});
|
|
726
|
+
|
|
727
|
+
it("a block inside a comment, or after \\end{document}, is not one TeX runs", () => {
|
|
728
|
+
expect(
|
|
729
|
+
latexReader.filecontents(
|
|
730
|
+
"% \\begin{filecontents}{x.bib}\n% @misc{z}\n% \\end{filecontents}\n\\begin{document}\n\\end{document}\n",
|
|
731
|
+
"paper",
|
|
732
|
+
),
|
|
733
|
+
).toEqual([]);
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
it("a block that names no file writes none: a group after its body's first text is no name, and a body may hold none", () => {
|
|
737
|
+
expect(
|
|
738
|
+
[
|
|
739
|
+
"\\begin{filecontents}\n@misc{z}\n\\end{filecontents}\n",
|
|
740
|
+
"\\begin{filecontents}\nplain text\n\\end{filecontents}\n",
|
|
741
|
+
].flatMap((block) =>
|
|
742
|
+
latexReader.filecontents(
|
|
743
|
+
`${block}\\begin{document}\n\\end{document}\n`,
|
|
744
|
+
"paper",
|
|
745
|
+
),
|
|
746
|
+
),
|
|
747
|
+
).toEqual([]);
|
|
748
|
+
});
|
|
749
|
+
});
|
|
750
|
+
|
|
751
|
+
describe("latexReader.filecontents — a file name on a line of its own", () => {
|
|
752
|
+
it("🔴 a name on the line after \\begin, the option before it or not: TeX takes it, and the body starts on the line after the name", () => {
|
|
753
|
+
// Measured with pdflatex: `\\begin{filecontents*}` then `{refs.bib}` on the next line writes
|
|
754
|
+
// refs.bib from the lines after the name (fixtures/paper-sources/v30-name-next-line).
|
|
755
|
+
const src =
|
|
756
|
+
"\\begin{filecontents*}\n{refs.bib}\n@misc{a, title={A}}\n\\end{filecontents*}\n" +
|
|
757
|
+
"\\begin{filecontents}[overwrite]\n {two.bib}\n@misc{b}\n\\end{filecontents}\n" +
|
|
758
|
+
"\\begin{document}\n\\end{document}\n";
|
|
759
|
+
expect(
|
|
760
|
+
latexReader
|
|
761
|
+
.filecontents(src, "paper")
|
|
762
|
+
.map((b) => [
|
|
763
|
+
b.writes,
|
|
764
|
+
b.overwrite,
|
|
765
|
+
src.slice(b.body.start, b.body.end),
|
|
766
|
+
]),
|
|
767
|
+
).toEqual([
|
|
768
|
+
["refs.bib", false, "@misc{a, title={A}}\n"],
|
|
769
|
+
["two.bib", true, "@misc{b}\n"],
|
|
770
|
+
]);
|
|
771
|
+
});
|
|
772
|
+
});
|
|
@@ -33,6 +33,7 @@ import { parseLatex } from "./parse.ts";
|
|
|
33
33
|
import { bodyEmphasis, bodyProse } from "./prose.ts";
|
|
34
34
|
import { renderedRuns } from "./rendered.ts";
|
|
35
35
|
import { layoutOverridesOf } from "./layout.ts";
|
|
36
|
+
import { filecontentsOf } from "./filecontents.ts";
|
|
36
37
|
|
|
37
38
|
export {
|
|
38
39
|
documentClassOf,
|
|
@@ -69,4 +70,5 @@ export const latexReader: LatexReader = {
|
|
|
69
70
|
template: parseTemplate,
|
|
70
71
|
withDocumentClass: (src, want) => replaceDocumentClass(parseLatex(src), want),
|
|
71
72
|
layoutOverrides: (src) => layoutOverridesOf(parseLatex(src)),
|
|
73
|
+
filecontents: (src, jobname) => filecontentsOf(parseLatex(src), jobname),
|
|
72
74
|
};
|
|
@@ -156,3 +156,67 @@ export const isArgument = (v: Visited): v is Argument =>
|
|
|
156
156
|
/** A node, as opposed to a node list or an argument (which visit also reports). */
|
|
157
157
|
export const isNode = (v: Visited): v is Node =>
|
|
158
158
|
!isList(v) && v.type !== "argument" && v.type !== "root";
|
|
159
|
+
|
|
160
|
+
/** A piece of an argument as TeX reads it: text, or a macro this reader leaves as written (`\x`). */
|
|
161
|
+
export interface ArgumentPiece {
|
|
162
|
+
readonly text: string;
|
|
163
|
+
readonly macro: boolean;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* An argument's nodes as TeX reads them, off the tree: a comment is no part of it, a group's braces
|
|
168
|
+
* are none, and white space is one space. `\jobname` is the main file's name when `jobname` is given;
|
|
169
|
+
* every other macro — `\jobnamebib` included, a control sequence of its own — is a piece of its own,
|
|
170
|
+
* written as it stands, and so is anything else (math, verbatim) a name cannot hold.
|
|
171
|
+
*/
|
|
172
|
+
export function argumentPieces(
|
|
173
|
+
nodes: readonly Node[],
|
|
174
|
+
jobname: string | null,
|
|
175
|
+
): readonly ArgumentPiece[] {
|
|
176
|
+
return nodes.flatMap((n) => pieceOf(n, jobname));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** The nodes a name cannot hold as text: an environment, math, verbatim. */
|
|
180
|
+
type Nameless = Extract<
|
|
181
|
+
Node,
|
|
182
|
+
{
|
|
183
|
+
readonly type:
|
|
184
|
+
| "root"
|
|
185
|
+
| "environment"
|
|
186
|
+
| "mathenv"
|
|
187
|
+
| "inlinemath"
|
|
188
|
+
| "displaymath"
|
|
189
|
+
| "verb"
|
|
190
|
+
| "verbatim";
|
|
191
|
+
}
|
|
192
|
+
>;
|
|
193
|
+
const NAMELESS: ReadonlySet<string> = new Set([
|
|
194
|
+
"root",
|
|
195
|
+
"environment",
|
|
196
|
+
"mathenv",
|
|
197
|
+
"inlinemath",
|
|
198
|
+
"displaymath",
|
|
199
|
+
"verb",
|
|
200
|
+
"verbatim",
|
|
201
|
+
]);
|
|
202
|
+
const isNameless = (n: Node): n is Nameless => NAMELESS.has(n.type);
|
|
203
|
+
|
|
204
|
+
/** One node of an argument as `argumentPieces` reads it. */
|
|
205
|
+
function pieceOf(n: Node, jobname: string | null): readonly ArgumentPiece[] {
|
|
206
|
+
if (isNameless(n)) return [{ text: `\\${n.type}`, macro: true }];
|
|
207
|
+
switch (n.type) {
|
|
208
|
+
case "comment":
|
|
209
|
+
return [];
|
|
210
|
+
case "string":
|
|
211
|
+
return [{ text: n.content, macro: false }];
|
|
212
|
+
case "whitespace":
|
|
213
|
+
case "parbreak":
|
|
214
|
+
return [{ text: " ", macro: false }];
|
|
215
|
+
case "group":
|
|
216
|
+
return argumentPieces(n.content, jobname);
|
|
217
|
+
case "macro":
|
|
218
|
+
return n.content === "jobname" && jobname !== null
|
|
219
|
+
? [{ text: jobname, macro: false }]
|
|
220
|
+
: [{ text: `\\${n.content}`, macro: true }];
|
|
221
|
+
}
|
|
222
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `parseLatex` keeps the trees of a bounded amount of source text: an editor session must not hold a
|
|
3
|
+
* tree for every version of the paper it has seen.
|
|
4
|
+
*/
|
|
5
|
+
import { describe, expect, it } from "vitest";
|
|
6
|
+
import { parseLatex } from "./index.ts";
|
|
7
|
+
|
|
8
|
+
describe("parseLatex's memo", () => {
|
|
9
|
+
/** A distinct source of `kib` KiB that parses quickly: one long comment. */
|
|
10
|
+
const source = (tag: string, kib: number) =>
|
|
11
|
+
`${tag}\n%${"x".repeat(kib * 1024)}`;
|
|
12
|
+
|
|
13
|
+
it("returns the same tree for the same text while it is kept", () => {
|
|
14
|
+
const s = source("same", 1);
|
|
15
|
+
expect(parseLatex(s)).toBe(parseLatex(s));
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("keeps up to 512 KiB of source: a paper's ten files stay, read in a cycle", () => {
|
|
19
|
+
const files = Array.from({ length: 11 }, (_, i) =>
|
|
20
|
+
source(`f${String(i)}`, 30),
|
|
21
|
+
);
|
|
22
|
+
const first = files.map((f) => parseLatex(f));
|
|
23
|
+
expect(files.map((f) => parseLatex(f))).toEqual(first);
|
|
24
|
+
expect(files.every((f, i) => parseLatex(f) === first[i])).toBe(true);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("past 512 KiB the text used least recently goes first, a recently used one stays", () => {
|
|
28
|
+
const big = Array.from({ length: 6 }, (_, i) =>
|
|
29
|
+
source(`b${String(i)}`, 100),
|
|
30
|
+
);
|
|
31
|
+
const trees = big.slice(0, 5).map((f) => parseLatex(f));
|
|
32
|
+
// Touch the oldest: it becomes the most recent, so the second is the one evicted.
|
|
33
|
+
expect(parseLatex(big[0] ?? "")).toBe(trees[0]);
|
|
34
|
+
parseLatex(big[5] ?? "");
|
|
35
|
+
expect(parseLatex(big[0] ?? "")).toBe(trees[0]);
|
|
36
|
+
expect(parseLatex(big[1] ?? "")).not.toBe(trees[1]);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -59,10 +59,13 @@ export const KEY_SIGNATURES: Readonly<
|
|
|
59
59
|
"subfile",
|
|
60
60
|
"bibliography",
|
|
61
61
|
"bibliographystyle",
|
|
62
|
-
"addbibresource",
|
|
63
62
|
],
|
|
64
63
|
"m",
|
|
65
64
|
),
|
|
65
|
+
// biblatex's resources take options (`[location=remote]`) before the name.
|
|
66
|
+
...signed(["addbibresource", "addglobalbib", "addsectionbib"], "o m"),
|
|
67
|
+
// bibentry's: writes `\bibdata` as `\bibliography` does, and typesets nothing.
|
|
68
|
+
...signed(["nobibliography"], "m"),
|
|
66
69
|
...signed(["includegraphics"], "s o o m"),
|
|
67
70
|
...signed(["usepackage", "RequirePackage", "documentclass"], "o m o"),
|
|
68
71
|
]);
|
|
@@ -134,9 +137,67 @@ const parser = once(() =>
|
|
|
134
137
|
}),
|
|
135
138
|
);
|
|
136
139
|
|
|
140
|
+
/**
|
|
141
|
+
* The trees parsed last, by their source: a tree is a pure function of its text, and one lint of a
|
|
142
|
+
* paper asks for the same texts many times — every rule that reads the paper (`readPaper`)
|
|
143
|
+
* parses `paper.tex`, its includes and the assembled whole again. Measured 2026-10-06 on the
|
|
144
|
+
* accepted ACM paper of the corpus: 0.9 s per reading of the paper's sources, seven of them per lint,
|
|
145
|
+
* 50–76 ms with the memo.
|
|
146
|
+
*
|
|
147
|
+
* Bounded by the SOURCE TEXT it holds (UTF-16 units, about bytes for TeX), least recently used out.
|
|
148
|
+
* A count bound is wrong both ways: 64 trees held 64 versions of one file in an editor (186 MiB for a
|
|
149
|
+
* 48 KiB paper), and 8 trees thrashed on a paper with ten includes — the rules read them in a cycle,
|
|
150
|
+
* so every read missed and the corpus lint timed out. `MEMO_BYTES` holds the largest paper of the corpus
|
|
151
|
+
* (150 KB of sources, and the assembled whole) and about ten versions of a 48 KiB file.
|
|
152
|
+
*/
|
|
153
|
+
const MEMO_BYTES = 512 * 1024;
|
|
154
|
+
const memo = new Map<string, ParsedTex>();
|
|
155
|
+
|
|
156
|
+
/** The texts to evict, least recent first, so that `adding` more fits under `MEMO_BYTES`. */
|
|
157
|
+
const evicted = (adding: number): readonly string[] => {
|
|
158
|
+
const keys = [...memo.keys()];
|
|
159
|
+
const total = keys.reduce((n, k) => n + k.length, adding);
|
|
160
|
+
const { out } = keys.reduce<{
|
|
161
|
+
readonly left: number;
|
|
162
|
+
readonly out: readonly string[];
|
|
163
|
+
}>(
|
|
164
|
+
(acc, k) =>
|
|
165
|
+
acc.left > MEMO_BYTES
|
|
166
|
+
? { left: acc.left - k.length, out: [...acc.out, k] }
|
|
167
|
+
: acc,
|
|
168
|
+
{ left: total, out: [] },
|
|
169
|
+
);
|
|
170
|
+
return out;
|
|
171
|
+
};
|
|
172
|
+
|
|
137
173
|
/** A LaTeX source → its tree, without the branches `\iffalse … \fi` hides (`conditionals.ts`). */
|
|
138
|
-
export
|
|
139
|
-
src
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
174
|
+
export function parseLatex(src: string): ParsedTex {
|
|
175
|
+
const hit = memo.get(src);
|
|
176
|
+
const parsed: ParsedTex = hit ?? {
|
|
177
|
+
src,
|
|
178
|
+
root: withoutFalseBranches(src, parser().parse(src)),
|
|
179
|
+
[PARSED]: true,
|
|
180
|
+
};
|
|
181
|
+
// Most recent last: a hit moves to the end, and the first keys are the ones used least recently.
|
|
182
|
+
// eslint-disable-next-line functional/immutable-data -- the bounded memo, see above
|
|
183
|
+
memo.delete(src);
|
|
184
|
+
// eslint-disable-next-line functional/immutable-data -- the bounded memo, see above
|
|
185
|
+
evicted(src.length).forEach((k) => memo.delete(k));
|
|
186
|
+
// eslint-disable-next-line functional/immutable-data -- the bounded memo, see above
|
|
187
|
+
memo.set(src, parsed);
|
|
188
|
+
return parsed;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* The part of a source's tree TeX reads: everything up to and with the `document` environment.
|
|
193
|
+
* TeX stops at `\end{document}`, so a `\bibliography`, a `thebibliography`, a block or an `\input`
|
|
194
|
+
* parked after it is never read. A source with no `document` environment is read whole.
|
|
195
|
+
*/
|
|
196
|
+
export function liveRoot(t: ParsedTex): Readonly<Ast.Root> {
|
|
197
|
+
const end = t.root.content.findIndex(
|
|
198
|
+
(n) => n.type === "environment" && n.env === "document",
|
|
199
|
+
);
|
|
200
|
+
return end < 0
|
|
201
|
+
? t.root
|
|
202
|
+
: { ...t.root, content: t.root.content.slice(0, end + 1) };
|
|
203
|
+
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { absolutePath } from "../../domain/paths.ts";
|
|
6
6
|
import type { Download } from "../../ports/download.ts";
|
|
7
7
|
import type { Files } from "../../ports/files.ts";
|
|
8
|
+
import type { ListDir } from "../../ports/talk-media.ts";
|
|
8
9
|
import type { Command, ProcessExit, RunProcess } from "../../ports/process.ts";
|
|
9
10
|
import type { NotAPromise, Scratch, Workspace } from "../../ports/workspace.ts";
|
|
10
11
|
import { err, ok } from "../../domain/result.ts";
|
|
@@ -55,6 +56,22 @@ export function memoryFiles(
|
|
|
55
56
|
};
|
|
56
57
|
}
|
|
57
58
|
|
|
59
|
+
/** `ListDir` over `memoryFiles`: the names directly under a directory, none when it holds no file. */
|
|
60
|
+
export const memoryListDir =
|
|
61
|
+
(files: MemoryFiles): ListDir =>
|
|
62
|
+
(dir) => [
|
|
63
|
+
...new Set(
|
|
64
|
+
[...files.map.keys()]
|
|
65
|
+
.filter((k) => k.startsWith(`${dir}/`))
|
|
66
|
+
.map((k) =>
|
|
67
|
+
k
|
|
68
|
+
.slice(dir.length + 1)
|
|
69
|
+
.split("/", 1)
|
|
70
|
+
.join(""),
|
|
71
|
+
),
|
|
72
|
+
),
|
|
73
|
+
];
|
|
74
|
+
|
|
58
75
|
export interface MemoryWorkspace extends Workspace {
|
|
59
76
|
/** Every file written into a scratch directory, in order. */
|
|
60
77
|
readonly written: {
|