Repository Public Research preview
1// Entry point of the audit engine: loads a manuscript from a confined path or2// from inline text, attaches its bibliography, and exposes every audit.3import { dirname, join } from "node:path";4import { AuditError, MAX_FILE_BYTES, readInside } from "./io.mjs";5import { detectFormat, parseBib, parseManuscript } from "./parse.mjs";67export { AuditError } from "./io.mjs";8export { parseManuscript, parseBib, detectFormat, countColumns } from "./parse.mjs";9export { outline, auditCitations, auditCrossReferences, auditNumbers, auditStatistics, auditTables, auditClaims, auditStyle, auditReplication, extractNumbers, ROLES, DISCLAIMER } from "./audits.mjs";10export { fullAudit, reviewScaffold, compareVersions, ENGINE_VERSION } from "./report.mjs";11export { CODES } from "./codes.mjs";1213/**14 * Loads a manuscript. Exactly one of `path` (inside `root`) or `text` is required.15 * With a path, \input / \include files and declared bibliography files are read16 * from the same root. With inline text nothing is read from disk.17 */18export function loadDocument({ root = process.cwd(), path, text, format, bib_path, bib_text } = {}) {19 const hasPath = typeof path === "string" && path.length > 0;20 const hasText = typeof text === "string";21 if (hasPath === hasText) throw new AuditError("input_invalid", "Provide exactly one of `path` (a file inside the allowed root) or `text` (the manuscript source).");22 if (format && !["auto", "latex", "markdown"].includes(format)) throw new AuditError("input_invalid", "`format` must be auto, latex or markdown.");23 if (bib_path && bib_text) throw new AuditError("input_invalid", "Provide `bib_path` or `bib_text`, not both.");24 let doc;25 const bibFiles = [], missing = [];26 let entries = [], problems = [];27 const addBib = (file, content) => { const r = parseBib(content, file); bibFiles.push(file); entries = entries.concat(r.entries); problems = problems.concat(r.problems); };28 if (hasText) {29 if (Buffer.byteLength(text) > MAX_FILE_BYTES) throw new AuditError("file_too_large", `Inline text is larger than ${MAX_FILE_BYTES} bytes.`);30 doc = parseManuscript({ file: "<inline>", text, format: format && format !== "auto" ? format : detectFormat("", text) });31 if (bib_path) throw new AuditError("input_invalid", "`bib_path` needs `path`. With inline text, pass the bibliography as `bib_text`.");32 } else {33 const main = readInside(root, path);34 const base = dirname(main.rel);35 const reader = (name) => {36 for (const cand of /\.[A-Za-z0-9]+$/.test(name) ? [name] : [`${name}.tex`, name]) {37 try {38 const r = readInside(root, join(base, cand));39 return { file: r.rel, text: r.text };40 } catch (e) {41 if (e.code === "path_forbidden") return { reason: "outside the allowed root" };42 if (e.code !== "not_found" && e.code !== "not_a_file") return { reason: e.code ?? "unreadable" };43 }44 }45 return { reason: "not found" };46 };47 doc = parseManuscript({ file: main.rel, text: main.text, format: format && format !== "auto" ? format : detectFormat(main.rel, main.text), reader });48 if (bib_path) {49 const b = readInside(root, bib_path);50 addBib(b.rel, b.text);51 } else if (!bib_text) {52 for (const name of [...new Set(doc.bibSources)]) {53 const cand = /\.(bib|json)$/i.test(name) ? name : `${name}.bib`;54 try {55 const b = readInside(root, join(base, cand));56 addBib(b.rel, b.text);57 } catch {58 missing.push(cand);59 }60 }61 }62 }63 if (bib_text) addBib("<inline-bib>", bib_text);64 if (bibFiles.length || missing.length) doc.bib = { files: bibFiles, entries, problems, missingFiles: missing };65 return doc;66}67