From dfd9a3d1efd073594e9f42772dfa505341bf91a0 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Wed, 7 Oct 2026 08:36:57 -0500 Subject: [PATCH] fixup! diigo-cache: RSS in diigo's export shape, from cached items MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit compare-export.ts per the ocap style guide (dckc/awesome-ocap, style-guide/): main takes standard authorities under standard names as default arguments — argv, fs, connect, now, stdout, stderr, exit (the transitional form of _script_io) — and attenuates them itself: connect(dbPath) to a read-only cacheItems, the output directory to an outDir that writes only plain file names inside it. Counts go to stdout (program output), usage to stderr. Generated-by: Claude Opus 5.5 --- diigo-cache/scripts/compare-export.ts | 78 ++++++++++++++++++--------- 1 file changed, 54 insertions(+), 24 deletions(-) diff --git a/diigo-cache/scripts/compare-export.ts b/diigo-cache/scripts/compare-export.ts index ad50748..533e47a 100644 --- a/diigo-cache/scripts/compare-export.ts +++ b/diigo-cache/scripts/compare-export.ts @@ -5,8 +5,8 @@ * * node scripts/compare-export.ts */ -import { readFileSync, writeFileSync } from "node:fs"; -import { join } from "node:path"; +import * as nodeFs from "node:fs"; +import { basename, join } from "node:path"; import { DatabaseSync } from "node:sqlite"; import { itemDescription, toRss } from "../src/rss.ts"; @@ -77,50 +77,80 @@ export function compare(ours: Item[], theirs: Exported[]): { complete: number; r return { complete, report }; } +/** read-only access to one cache's items: attenuates a database */ +export function cacheItems(db: { prepare(sql: string): { all(): unknown[] } }) { + return { + /** every item, newest update first */ + all: (): Item[] => + (db.prepare("select json from items order by updated desc").all() as { json: string }[]).map( + (r) => JSON.parse(r.json) as Item, + ), + }; +} + +/** write access to files directly inside one directory: attenuates fs */ +export function outDir(fs: Pick, dir: string) { + return { + write(name: string, text: string): void { + if (basename(name) !== name) throw new Error(`not a plain file name: ${name}`); + fs.writeFileSync(join(dir, name), text); + }, + }; +} + /** * Generates RSS from a cache's items (src/rss.ts) and compares it with a * diigo RSS export: title, tags, description, highlights + sticky notes. - * Prints counts only; writes /from-cache.rss and + * Prints counts to stdout; writes /from-cache.rss and * /report.json (mismatched links, by category). * - * The program's edge: every power it uses arrives as a default argument - * (ocap discipline), so a test can pass others. - * - * Ends by reporting an exit code (2: usage) through `exit`. + * The program's edge (ocap discipline, transitional form): it takes the + * standard authorities as default arguments and attenuates them here — + * the database to {@link cacheItems}, the output directory to + * {@link outDir} — before anything else uses them. Ends by reporting an + * exit code (2: usage) through `exit`. */ export function main( argv = process.argv.slice(2), { - readText = (path: string) => readFileSync(path, "utf8"), - writeText = (path: string, text: string) => writeFileSync(path, text), - readItems = (dbPath: string): Item[] => - (new DatabaseSync(dbPath, { readOnly: true }).prepare("select json from items order by updated desc").all() as { - json: string; - }[]).map((r) => JSON.parse(r.json) as Item), + fs = nodeFs, + connect = (path: string) => new DatabaseSync(path, { readOnly: true }), now = () => new Date(), - log = (x: unknown) => console.log(x), - warn = (x: unknown) => console.error(x), + stdout = process.stdout, + stderr = process.stderr, exit = (code: number) => { process.exitCode = code; }, + }: { + fs?: Pick; + connect?: (path: string) => { prepare(sql: string): { all(): unknown[] } }; + now?: () => Date; + stdout?: { write(text: string): unknown }; + stderr?: { write(text: string): unknown }; + exit?: (code: number) => void; } = {}, ): void { - const [dbPath, exportPath, outDir] = argv; - if (!dbPath || !exportPath || !outDir) { - warn("usage: node scripts/compare-export.ts "); + const [dbPath, exportPath, outPath] = argv; + if (!dbPath || !exportPath || !outPath) { + stderr.write("usage: node scripts/compare-export.ts \n"); return exit(2); } - const ours = readItems(dbPath); - writeText(join(outDir, "from-cache.rss"), toRss(ours, { title: "from the cache", pubDate: now().toUTCString() })); - const theirs = parseExport(readText(exportPath)); + const cache = cacheItems(connect(dbPath)); + const out = outDir(fs, outPath); + const exportXml = fs.readFileSync(exportPath, "utf8"); + + const ours = cache.all(); + out.write("from-cache.rss", toRss(ours, { title: "from the cache", pubDate: now().toUTCString() })); + const theirs = parseExport(exportXml); const { complete, report } = compare(ours, theirs); - writeText(join(outDir, "report.json"), JSON.stringify(report, null, 1)); - log({ + out.write("report.json", JSON.stringify(report, null, 1)); + const counts = { export: theirs.length, cache: ours.length, complete, ...Object.fromEntries(Object.entries(report).map(([k, v]) => [k, v.length])), - }); + }; + stdout.write(`${JSON.stringify(counts, null, 1)}\n`); exit(0); }