Files
christianandClaude Opus 5 0db5e9b07d Bible App 1.4.34 — full source
First commit on the local Gitea server, made after moving the tree off the
Windows machine. Build outputs, node_modules and the AI models stay out
(see .gitignore); everything the installer build needs is tracked.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-24 13:05:20 -04:00

98 lines
3.9 KiB
JavaScript

/*
* merge.cjs — add the missing OT verses to interlinear/<Book>.json.
*
* ADDITIVE ONLY. Every verse already present keeps its existing value byte-for-byte; this only
* fills keys that are absent or empty. That matters because 36 shipped verses disagree with a
* fresh conversion (Qere/Ketiv variant picks, empty-word placeholders, and the verse-boundary
* cases TAHOT's own header lists) — those are left exactly as they are rather than "corrected"
* into a change nobody asked for.
*
* Keys are written in canonical chapter|verse order. The app looks verses up by key, but
* server.js /api/interlinear iterates Object.keys() for whole-chapter requests, so a predictable
* order is worth having. Output is minified with no trailing newline, matching the shipped files
* (verified: JSON.stringify round-trips them byte-identically).
*/
'use strict';
const fs = require('fs');
const path = require('path');
const { parseFile } = require('./tahot-convert.cjs');
const REPO = process.argv[2] || 'C:/Users/media/Downloads/bible-app';
const BACKUP = process.argv[3];
const DRY = process.argv.includes('--dry');
const DIR = __dirname + '/tahot';
if (!BACKUP && !DRY) { console.error('usage: merge.cjs <repo> <backupDir> [--dry]'); process.exit(1); }
// ---- collect every TAHOT verse ----
const all = new Map(); // "Book|ch|v" -> words
for (const f of fs.readdirSync(DIR).filter((x) => x.endsWith('.txt'))) {
const { verses } = parseFile(path.join(DIR, f));
for (const [k, v] of verses) all.set(k, v);
}
console.log('TAHOT verses parsed:', all.size);
const byBook = new Map();
for (const [k, v] of all) {
const i = k.indexOf('|');
const book = k.slice(0, i);
if (!byBook.has(book)) byBook.set(book, new Map());
byBook.get(book).set(k.slice(i + 1), v); // "ch|v" -> words
}
// ---- back up before touching anything ----
if (!DRY) {
fs.mkdirSync(BACKUP, { recursive: true });
for (const f of fs.readdirSync(path.join(REPO, 'interlinear'))) {
fs.copyFileSync(path.join(REPO, 'interlinear', f), path.join(BACKUP, f));
}
console.log('backed up interlinear/ ->', BACKUP);
}
const sortKey = (k) => { const [c, v] = k.split('|').map(Number); return c * 10000 + v; };
let totalAdded = 0, totalWords = 0, booksChanged = 0;
const report = [];
for (const [book, tahotVerses] of byBook) {
const p = path.join(REPO, 'interlinear', book + '.json');
if (!fs.existsSync(p)) { report.push([book, 'NO SHIPPED FILE', 0]); continue; }
const original = fs.readFileSync(p, 'utf-8');
const shipped = JSON.parse(original);
const merged = {};
let added = 0, words = 0;
// Union of shipped keys and TAHOT keys, canonical order.
const keys = new Set([...Object.keys(shipped), ...tahotVerses.keys()]);
for (const k of [...keys].sort((a, b) => sortKey(a) - sortKey(b))) {
const have = shipped[k];
if (have && have.length) { merged[k] = have; continue; } // untouched
const fresh = tahotVerses.get(k);
if (fresh && fresh.length) { merged[k] = fresh; added++; words += fresh.length; }
else if (have) { merged[k] = have; } // preserve an empty array as-is
}
// Safety: no shipped verse may change.
for (const k of Object.keys(shipped)) {
if (shipped[k] && shipped[k].length &&
JSON.stringify(merged[k]) !== JSON.stringify(shipped[k])) {
throw new Error('REFUSING TO WRITE: ' + book + ' ' + k + ' would change');
}
}
if (added) {
booksChanged++;
totalAdded += added;
totalWords += words;
report.push([book, added + ' verses', words + ' words']);
if (!DRY) fs.writeFileSync(p, JSON.stringify(merged));
}
}
console.log('\nbooks changed:', booksChanged, '| verses added:', totalAdded, '| words added:', totalWords);
report.sort((a, b) => parseInt(b[1]) - parseInt(a[1]));
report.forEach((r) => console.log(' ' + String(r[0]).padEnd(18) + String(r[1]).padStart(12) + ' ' + r[2]));
if (DRY) console.log('\n(dry run — nothing written)');