Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 44 additions & 4 deletions specs/001-pathway-pdfs/contracts/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -228,10 +228,50 @@ is a PDF, or a folder of today's PDFs, as today's book is one file per top-level
pathway, and the new side likewise; a folder is compared file by file, by top-level
pathway, and a file on one side only is reported as a difference.

- **Output:** a JSON report of the differences by part and kind, with counts. Names
appear only as counts and hashes (FR-013).
- **`--expected <file>`:** the recorded differences. With it, the exit is 1 only for a
difference not recorded.
- **Output:** a JSON report of the differences by part and kind, with counts.
- Each difference has its `file`, its `part` (`file`, `cover`, `introduction`,
`analysis`, `sections`, `section`, `figure`, `contents` or `short-entries`), a `field`
where the part has several (a section's `summation`, the cover's `names`), its `kind`
(`value`, `count`, `text`, `order`, `missing` from the new side, `extra` on it, `kind`,
`target`, `missing-target` or `unreadable`), a `count`, and the sections it is in by
stable identifier.
- **What may be printed:** no text read from a PDF is printed unless it has one of these
shapes: a count, yes or no, a release number, one of a fixed list (the analysis's
types and resources, the parts, fields and kinds), a stable identifier
(`R-[A-Z]{3}-\d+`), an analysis token of the form the analysis service issues (printed
in its one canonical form, so copies encoded otherwise are one token), or one
of Reactome's species. Anything else is printed as a hash and a length, and the
cover's names as a count and a hash (FR-013). The hashes are salted once a run, so
they say which values are equal within a report and cannot be checked against a
guessed name. The cover's title is never printed: a side's title is said only to be
its file's name or not (`same` or `differs`). A section's text is never printed.
- A list in another order is a difference of its own kind, `order`. The sections' order
is read by their longest common subsequence, so only the sections out of it are
reported; a section written twice is compared occurrence by occurrence.
- A contents entry whose target is not read on either side is `missing-target`.
- A file that cannot be read is named on standard error and reported as `unreadable`;
the other files are still compared.
- `unmatched`: the records of `--expected` that matched no difference, by their place in
its list.
- **`--expected <file>`:** the recorded differences, as `{ "differences": [...] }`, each
with a `part`, a `kind` and a `reason`, and optionally a `field`, a `file` and `events`;
any other key is refused (exit 2). A record naming events covers only those events, and
never a difference that names none. Each difference it covers is reported with its
reason. With it, the exit is 1 only for a difference not recorded.
- **`--reference-margin <pt>`:** the depth of the bands at the top and bottom of the
reference's pages read as margins (running headers, page numbers), 50 by default, as
pdf-export's own; T040 sets it for `event-pdf`'s files from their real margins.
- **Standard error:** paths, and an error's kind and code, never its message. pdf.js's
warnings, which quote a file's content, are off, and while it compares every method of
the console is held: what a library writes there is counted and said in one line, with
the kinds of the errors it passed, never its words. `check-book` likewise says an error
that stops it by its kind and code alone.
- **Exit:** 0 with no difference (or none not recorded), 1 with one, 2 on bad arguments, a
path that does not exist, or a reference and a new side not both PDFs or both folders.
- **What it does not know yet:** the reducer reads parts by their labels, pdf-export's
and other words for them; `event-pdf`'s own labels, margins, contents form and names'
separator are not known here, and are for T040 to add from its real files. A label not
known leaves its part unread, which shows as a difference.

## `pdf-export serve`

Expand Down
49 changes: 48 additions & 1 deletion specs/001-pathway-pdfs/tasks.md

Large diffs are not rendered by default.

15 changes: 12 additions & 3 deletions src/check/book.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
import { readdirSync, readFileSync, statSync } from 'node:fs';
import { join } from 'node:path';

import { getDocument } from 'pdfjs-dist/legacy/build/pdf.mjs';
import { getDocument, VerbosityLevel } from 'pdfjs-dist/legacy/build/pdf.mjs';

import { printedDocument } from '../book/book.ts';
import {
Expand All @@ -45,6 +45,7 @@ import { checkFiles, neededFiles, readSummary, SUMMARY } from '../preflight/file
import { figureRecord } from '../preflight/folder.ts';
import { fileName } from '../preflight/names.ts';
import { bookPathways } from '../preflight/preflight.ts';
import { errorKind, UnreadPdf } from '../print/unread.ts';

/** FR-011's checks, in the order their faults are listed (contracts/cli.md). */
export const CHECKS = [
Expand Down Expand Up @@ -177,7 +178,11 @@ const isRecord = (value: unknown): value is Record<string, unknown> =>

/** A PDF's text, links and outline, read with pdf.js. */
async function readPdf(path: string): Promise<Read> {
const task = getDocument({ data: new Uint8Array(readFileSync(path)) });
// pdf.js's warnings off: they quote the file's content, and the book holds names
const task = getDocument({
data: new Uint8Array(readFileSync(path)),
verbosity: VerbosityLevel.ERRORS,
});
try {
const doc = await task.promise;
const pieces: Piece[] = [];
Expand Down Expand Up @@ -228,6 +233,9 @@ async function readPdf(path: string): Promise<Read> {
const top = (await doc.getOutline()) as Parameters<typeof walk>[0] | null;
await walk(top ?? []);
return { pieces: pieces.sort(byReading), links: links.sort(byReading), outline };
} catch (error) {
// pdf.js's errors by their kind alone: their messages quote the file
throw new UnreadPdf(error);
} finally {
await task.destroy();
}
Expand Down Expand Up @@ -639,7 +647,8 @@ export async function checkBookCommand(
graph = undefined;
checked = await checkBook(settings.folder, docs, settings.files);
} catch (error) {
err(`The check stopped: ${message(error)}\n`);
// the error's kind and code alone: it may come from reading a PDF, which holds names
err(`The check stopped: ${errorKind(error)}\n`);
return 2;
} finally {
await closing(graph, err);
Expand Down
9 changes: 8 additions & 1 deletion src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
// pdf-export's command line (contracts/cli.md). Its commands come with spec 001's tasks:
// serve (T022) runs the single-event service; preflight (T024) runs the book's preflight
// alone; figures (T046) draws the book's figures into a folder; book (T029) writes the
// book; check-book (T030) checks a written book for FR-011's faults; compare is to come.
// book; check-book (T030) checks a written book for FR-011's faults; compare (T035)
// compares with event-pdf's output by FR-012.
import { constants as osConstants } from 'node:os';

import { privateTemporary } from './print/temporary.ts';
Expand Down Expand Up @@ -93,6 +94,12 @@ if (import.meta.main) {
out: (text) => process.stdout.write(text),
err: (text) => process.stderr.write(text),
});
} else if (args[0] === 'compare') {
const { compareCommand } = await import('./compare/compare.ts');
process.exitCode = await compareCommand(args.slice(1), {
out: (text) => process.stdout.write(text),
err: (text) => process.stderr.write(text),
});
} else if (args[0] === 'figures') {
const { figuresCommand } = await import('./figures/step.ts');
process.exitCode = await figuresCommand(args.slice(1), process.env, {
Expand Down
Loading
Loading