fixup! diigo-cache: RSS in diigo's export shape, from cached items
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
This commit is contained in:
1 parent
d145841a2e
commit
dfd9a3d1ef
1 file changed
+54
-24
@@ -5,8 +5,8 @@
|
|||||||
*
|
*
|
||||||
* node scripts/compare-export.ts <cache.sqlite> <export.xml> <out-dir>
|
* node scripts/compare-export.ts <cache.sqlite> <export.xml> <out-dir>
|
||||||
*/
|
*/
|
||||||
import { readFileSync, writeFileSync } from "node:fs";
|
import * as nodeFs from "node:fs";
|
||||||
import { join } from "node:path";
|
import { basename, join } from "node:path";
|
||||||
import { DatabaseSync } from "node:sqlite";
|
import { DatabaseSync } from "node:sqlite";
|
||||||
import { itemDescription, toRss } from "../src/rss.ts";
|
import { itemDescription, toRss } from "../src/rss.ts";
|
||||||
|
|
||||||
@@ -77,50 +77,80 @@ export function compare(ours: Item[], theirs: Exported[]): { complete: number; r
|
|||||||
return { complete, report };
|
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<typeof nodeFs, "writeFileSync">, 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
|
* Generates RSS from a cache's items (src/rss.ts) and compares it with a
|
||||||
* diigo RSS export: title, tags, description, highlights + sticky notes.
|
* diigo RSS export: title, tags, description, highlights + sticky notes.
|
||||||
* Prints counts only; writes <out-dir>/from-cache.rss and
|
* Prints counts to stdout; writes <out-dir>/from-cache.rss and
|
||||||
* <out-dir>/report.json (mismatched links, by category).
|
* <out-dir>/report.json (mismatched links, by category).
|
||||||
*
|
*
|
||||||
* The program's edge: every power it uses arrives as a default argument
|
* The program's edge (ocap discipline, transitional form): it takes the
|
||||||
* (ocap discipline), so a test can pass others.
|
* standard authorities as default arguments and attenuates them here —
|
||||||
*
|
* the database to {@link cacheItems}, the output directory to
|
||||||
* Ends by reporting an exit code (2: usage) through `exit`.
|
* {@link outDir} — before anything else uses them. Ends by reporting an
|
||||||
|
* exit code (2: usage) through `exit`.
|
||||||
*/
|
*/
|
||||||
export function main(
|
export function main(
|
||||||
argv = process.argv.slice(2),
|
argv = process.argv.slice(2),
|
||||||
{
|
{
|
||||||
readText = (path: string) => readFileSync(path, "utf8"),
|
fs = nodeFs,
|
||||||
writeText = (path: string, text: string) => writeFileSync(path, text),
|
connect = (path: string) => new DatabaseSync(path, { readOnly: true }),
|
||||||
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),
|
|
||||||
now = () => new Date(),
|
now = () => new Date(),
|
||||||
log = (x: unknown) => console.log(x),
|
stdout = process.stdout,
|
||||||
warn = (x: unknown) => console.error(x),
|
stderr = process.stderr,
|
||||||
exit = (code: number) => {
|
exit = (code: number) => {
|
||||||
process.exitCode = code;
|
process.exitCode = code;
|
||||||
},
|
},
|
||||||
|
}: {
|
||||||
|
fs?: Pick<typeof nodeFs, "readFileSync" | "writeFileSync">;
|
||||||
|
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 {
|
): void {
|
||||||
const [dbPath, exportPath, outDir] = argv;
|
const [dbPath, exportPath, outPath] = argv;
|
||||||
if (!dbPath || !exportPath || !outDir) {
|
if (!dbPath || !exportPath || !outPath) {
|
||||||
warn("usage: node scripts/compare-export.ts <cache.sqlite> <export.xml> <out-dir>");
|
stderr.write("usage: node scripts/compare-export.ts <cache.sqlite> <export.xml> <out-dir>\n");
|
||||||
return exit(2);
|
return exit(2);
|
||||||
}
|
}
|
||||||
const ours = readItems(dbPath);
|
const cache = cacheItems(connect(dbPath));
|
||||||
writeText(join(outDir, "from-cache.rss"), toRss(ours, { title: "from the cache", pubDate: now().toUTCString() }));
|
const out = outDir(fs, outPath);
|
||||||
const theirs = parseExport(readText(exportPath));
|
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);
|
const { complete, report } = compare(ours, theirs);
|
||||||
writeText(join(outDir, "report.json"), JSON.stringify(report, null, 1));
|
out.write("report.json", JSON.stringify(report, null, 1));
|
||||||
log({
|
const counts = {
|
||||||
export: theirs.length,
|
export: theirs.length,
|
||||||
cache: ours.length,
|
cache: ours.length,
|
||||||
complete,
|
complete,
|
||||||
...Object.fromEntries(Object.entries(report).map(([k, v]) => [k, v.length])),
|
...Object.fromEntries(Object.entries(report).map(([k, v]) => [k, v.length])),
|
||||||
});
|
};
|
||||||
|
stdout.write(`${JSON.stringify(counts, null, 1)}\n`);
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user