Files
santifer--career-ops/patch-latex-content.mjs
T
wehub-resource-sync d083df1fdb
CodeQL Analysis / Analyze (javascript-typescript) (push) Failing after 2s
Web CI / web typecheck + build (push) Failing after 1s
Release Please / release-please (push) Failing after 1s
CodeQL Analysis / Analyze (go) (push) Failing after 16s
chore: import upstream snapshot with attribution
2026-07-13 12:02:43 +08:00

87 lines
2.5 KiB
JavaScript

#!/usr/bin/env node
/**
* patch-latex-content.mjs — Apply prose patches to a user-owned LaTeX CV in place.
*
* Usage:
* node patch-latex-content.mjs <source.tex> <patches.json> <output.tex>
*
* patches.json:
* { "patches": [ { "id": "bullet-0", "text": "Tailored bullet text" } ] }
*
* Optional manifest fields in patches.json (from extract-latex-content.mjs):
* { "slots": [...], "patches": [...] }
*/
import { readFile, writeFile, mkdir } from 'fs/promises';
import { existsSync } from 'fs';
import { resolve, dirname } from 'path';
import { pathToFileURL } from 'url';
import { applyPatches } from './lib/latex-content.mjs';
async function main() {
const args = process.argv.slice(2).filter(a => a !== '--help');
const [sourcePath, patchesPath, outputPath] = args;
if (!sourcePath || !patchesPath || !outputPath) {
console.error('Usage: node patch-latex-content.mjs <source.tex> <patches.json> <output.tex>');
process.exit(1);
}
const absSource = resolve(sourcePath);
const absPatches = resolve(patchesPath);
const absOutput = resolve(outputPath);
if (!existsSync(absSource)) {
console.error(`Source not found: ${absSource}`);
process.exit(1);
}
if (!existsSync(absPatches)) {
console.error(`Patches file not found: ${absPatches}`);
process.exit(1);
}
let tex;
let payload;
try {
tex = await readFile(absSource, 'utf-8');
payload = JSON.parse(await readFile(absPatches, 'utf-8'));
} catch (err) {
console.error(`Failed to read input: ${err.message}`);
process.exit(1);
}
const patches = Array.isArray(payload.patches) ? payload.patches : [];
const slots = Array.isArray(payload.slots) ? payload.slots : [];
if (slots.length === 0) {
console.error('patches.json must include a slots array from extract-latex-content.mjs');
process.exit(1);
}
const missing = patches.filter(p => !slots.some(s => s.id === p.id));
if (missing.length > 0) {
console.error(`Unknown patch ids: ${missing.map(p => p.id).join(', ')}`);
process.exit(1);
}
const patched = applyPatches(tex, patches, slots);
const outDir = dirname(absOutput);
if (!existsSync(outDir)) {
await mkdir(outDir, { recursive: true });
}
await writeFile(absOutput, patched, 'utf-8');
const report = {
source: absSource,
output: absOutput,
patched: patches.length,
valid: true,
};
console.log(JSON.stringify(report, null, 2));
}
if (import.meta.url === pathToFileURL(process.argv[1] || '').href) {
main();
}