2026-09-19 14:15:01 -04:00
|
|
|
# What these programs promise
|
2026-09-19 13:09:20 -04:00
|
|
|
|
2026-09-19 14:15:01 -04:00
|
|
|
Eight things hold for every program here. Together they are what lets a person
|
|
|
|
|
put any of them in a pipeline without first reading how it works.
|
2026-09-19 13:09:20 -04:00
|
|
|
|
2026-09-19 14:15:01 -04:00
|
|
|
Part of [catena](../README.md).
|
2026-09-19 13:09:20 -04:00
|
|
|
|
2026-09-19 14:15:01 -04:00
|
|
|
## Each one does a single job
|
2026-09-19 13:09:20 -04:00
|
|
|
|
2026-09-19 14:15:01 -04:00
|
|
|
No program here has a mode switch that changes what it is. Where two jobs
|
|
|
|
|
would otherwise share a name, there are two programs — which is why
|
|
|
|
|
[`cuenorm`](../crates/cuenorm/README.md), [`cuepack`](../crates/cuepack/README.md) and [`cueseg`](../crates/cueseg/README.md) are
|
|
|
|
|
separate commands rather than one command with a flag.
|
2026-09-19 13:09:20 -04:00
|
|
|
|
2026-09-19 14:15:01 -04:00
|
|
|
## Input on stdin, results on stdout, diagnostics on stderr
|
2026-09-19 13:09:20 -04:00
|
|
|
|
2026-09-19 14:15:01 -04:00
|
|
|
Output and commentary never share a stream, so a result can always be piped
|
|
|
|
|
somewhere without filtering anything out of it.
|
2026-09-19 13:09:20 -04:00
|
|
|
|
2026-09-19 14:15:01 -04:00
|
|
|
Almost all of them read stdin or a path. Where one takes a path only, it says
|
|
|
|
|
so in `--help` and says why: [`pdftriage`](../crates/pdftriage/README.md) reads two specific
|
|
|
|
|
page ranges rather than the whole file, which a stream cannot offer.
|
2026-09-19 13:09:20 -04:00
|
|
|
|
2026-09-19 14:15:01 -04:00
|
|
|
## Anything list-shaped is NDJSON
|
2026-09-19 13:09:20 -04:00
|
|
|
|
2026-09-19 14:15:01 -04:00
|
|
|
One [record](records.md) per line, flushed as it is produced — so a slow stage
|
|
|
|
|
downstream sees each record as it appears rather than when the producer
|
|
|
|
|
finishes.
|
2026-09-19 13:09:20 -04:00
|
|
|
|
2026-09-19 14:15:01 -04:00
|
|
|
Readers are strict, and each strictness prevents a failure that would
|
|
|
|
|
otherwise be silent. A record from a version the reader does not know is
|
|
|
|
|
refused rather than parsed into whatever lines up. A bad line ends the stream
|
|
|
|
|
instead of being skipped, because it means the two ends disagree about the
|
|
|
|
|
format. Lines are bounded, so a program pointed at a binary file fails instead
|
|
|
|
|
of allocating it. A byte-order mark and blank lines are ignored, so a stream
|
|
|
|
|
that was concatenated or written on Windows still reads.
|
|
|
|
|
|
|
|
|
|
## Exit codes carry the answer
|
2026-09-19 13:09:20 -04:00
|
|
|
|
|
|
|
|
| Code | Meaning |
|
|
|
|
|
|---|---|
|
|
|
|
|
| `0` | Yes, or done |
|
|
|
|
|
| `1` | No |
|
|
|
|
|
| `2` | Could not tell |
|
|
|
|
|
| `64` | The command line was wrong |
|
2026-09-19 14:15:01 -04:00
|
|
|
| `65` | The input was not what the program can work on |
|
2026-09-19 13:09:20 -04:00
|
|
|
| `69` | A program it needs is not installed |
|
2026-09-19 14:15:01 -04:00
|
|
|
| `70` | Its own fault |
|
2026-09-19 13:09:20 -04:00
|
|
|
| `74` | Reading or writing failed |
|
|
|
|
|
|
2026-09-19 14:15:01 -04:00
|
|
|
Failures start where `sysexits.h` puts them, so "no" and "broke" are never the
|
|
|
|
|
same number. That is what makes this work:
|
2026-09-19 13:09:20 -04:00
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
for f in *.pdf; do pdftriage --quiet "$f" && ocrmypdf "$f" "ocr/$f"; done
|
|
|
|
|
```
|
|
|
|
|
|
2026-09-19 14:15:01 -04:00
|
|
|
A consumer closing the pipe is not a failure. `… | head -1` exits `0`, the
|
|
|
|
|
same as any other filter.
|
2026-09-19 13:09:20 -04:00
|
|
|
|
2026-09-19 14:15:01 -04:00
|
|
|
## Nothing reaches outside the process
|
2026-09-19 13:09:20 -04:00
|
|
|
|
2026-09-19 14:15:01 -04:00
|
|
|
No network, no database, no configuration file, no state kept between runs.
|
2026-09-19 19:19:54 -04:00
|
|
|
Five exceptions, each declared in `--help`:
|
2026-09-19 13:09:20 -04:00
|
|
|
|
2026-09-19 14:15:01 -04:00
|
|
|
- [`emb`](../crates/emb/README.md) makes an HTTP call, which is the whole of what it does.
|
2026-09-19 19:19:54 -04:00
|
|
|
- [`bibfetch`](../crates/bibfetch/README.md) asks a register about an identifier, likewise.
|
2026-09-19 15:25:13 -04:00
|
|
|
- [`pdftriage`](../crates/pdftriage/README.md) runs `pdftotext` and `pdfinfo`.
|
|
|
|
|
- [`pdfgutter`](../crates/pdfgutter/README.md) runs `pdftoppm` to look at a page.
|
|
|
|
|
- [`avplan`](../crates/avplan/README.md) runs `ffprobe` to read a file's streams, and
|
|
|
|
|
`ffmpeg` to ask which encoders the machine has.
|
|
|
|
|
|
|
|
|
|
The three that call another program exit `69` when it is missing, rather than
|
|
|
|
|
guessing at what it would have said.
|
2026-09-19 13:09:20 -04:00
|
|
|
|
2026-09-19 14:15:01 -04:00
|
|
|
## Nothing is assumed about where it is running
|
2026-09-19 13:09:20 -04:00
|
|
|
|
2026-09-19 14:15:01 -04:00
|
|
|
No hostnames, no paths, no private vocabularies are built in. General
|
|
|
|
|
reference data is — ISBN check digits, the HTML character references, the
|
|
|
|
|
names of well-known publishers — and anything specific to one collection is
|
|
|
|
|
supplied in a file rather than compiled in.
|
2026-09-19 13:09:20 -04:00
|
|
|
|
2026-09-19 14:15:01 -04:00
|
|
|
## The same input gives the same output
|
2026-09-19 13:09:20 -04:00
|
|
|
|
2026-09-19 14:15:01 -04:00
|
|
|
On every run and on every machine. No dependence on hash ordering, no reading
|
|
|
|
|
of the clock or the locale. Numbers that JSON cannot represent are refused
|
|
|
|
|
where they are produced rather than written as `null` and lost somewhere
|
|
|
|
|
downstream.
|
2026-09-19 13:09:20 -04:00
|
|
|
|
2026-09-19 14:15:01 -04:00
|
|
|
## `--json` is the stable form
|
2026-09-19 13:09:20 -04:00
|
|
|
|
2026-09-19 14:15:01 -04:00
|
|
|
Where a program prints something for a person to read, `--json` prints the
|
|
|
|
|
same answer as a record, and that record does not change shape without a major
|
|
|
|
|
version. Programs whose only sensible output is a stream of records write that
|
|
|
|
|
stream and have no `--json` to add.
|
2026-09-19 15:25:13 -04:00
|
|
|
|
|
|
|
|
## Every one writes its own manual page
|
|
|
|
|
|
|
|
|
|
`--man` writes the program's manual page, in roff, to stdout:
|
|
|
|
|
|
|
|
|
|
```sh
|
|
|
|
|
pdfgutter --man | gzip > /usr/local/share/man/man1/pdfgutter.1.gz
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
It is generated from the same definition `--help` is, so the two can never
|
|
|
|
|
come to say different things.
|
2026-09-19 17:21:07 -04:00
|
|
|
|
|
|
|
|
See also [the recipes](recipes.md), for what these rules buy when the
|
|
|
|
|
tools are put in a row.
|