Files
Marc WäckerlinandClaude Opus 5 adc58e19d7 One LaTeX design for a company, in three templates
A company that writes with LaTeX ends up with a document class, a letter class
and a presentation theme that share a design and not a line of code. Every fix
is then made three times, two of them late and the third never.

Here the page is written once. A company declares its colours, faces and logo in
one file, and the document template, the letter class and the presentation theme
read that one file. Nothing in the suite carries a colour value, a font name or
a file name of any company, which is what lets it be published while the
companies stay private.

Every measure of the page follows from a measurement or from a named definition:
the room the head and the foot need is taken from the boxes they really build,
one line of the body text stands between the head and the text and between the
text and the foot, a heading never stands alone at the foot of a page, a picture
takes the size of the family, and a Markdown file reaches the same page as the
same document written in LaTeX.

The suite ships with a company that does not exist, Nordwind AG, so that it
builds and is measured anywhere: three colours, the TeX Gyre families every
installation carries, and an icon drawn in TikZ.

574 checks over five measurements, all of them on the rendered page or on the
build log rather than on the source.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-22 15:06:07 +02:00

99 lines
4.1 KiB
Python

#!/usr/bin/env python3
"""Every build log of every document, read for what a green run hides.
A build that ends with a PDF has not said that it went well. LaTeX reports a
box that runs over the edge, a character its font does not carry and a head
that is too small for what stands in it, and then writes the page anyway. An
error that stands in every log hides the next one that means something.
"""
import os
import re
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import support
from support import Check, build
EXAMPLES = os.path.join(support.ROOT, "examples")
DOCUMENTS = ("sample.tex", "logo-example.tex", "letter.tex",
"presentation.tex")
# What no log of this suite may carry, and why each one matters.
COMPLAINTS = (
# An error stops nothing in nonstopmode: the run goes on and the page comes
# out with the defect on it.
(re.compile(r"^! ", re.MULTILINE), "an error"),
# A box that runs over the edge is text outside the paper.
(re.compile(r"^Overfull \\hbox", re.MULTILINE), "an overfull box"),
# A character the font does not carry prints NOTHING, with no gap a reader
# would notice and no error.
(re.compile(r"Missing character", re.MULTILINE), "a missing character"),
# A font shape LaTeX does not find is replaced silently, and the page then
# carries a face nobody chose.
(re.compile(r"Font shape .* undefined", re.MULTILINE),
"an undefined font shape"),
(re.compile(r"Some font shapes were not available", re.MULTILINE),
"a substituted font"),
# A head that is too small for what stands in it overprints the text under
# it, and fancyhdr says so once per page.
(re.compile(r"headheight is too small", re.MULTILINE),
"a head that is too small"),
# And a foot that is too small is the same defect at the other edge: the
# page number and whatever the document put beside it are drawn into the
# text above them. fancyhdr says so once per page.
(re.compile(r"footskip is too small", re.MULTILINE),
"a foot that is too small"),
# A title block without an author prints the block and leaves the line out,
# and the document says nothing about who wrote it.
(re.compile(r"No \\author given", re.MULTILINE),
"a title block without an author"),
(re.compile(r"Package hyperref Warning", re.MULTILINE),
"a warning of hyperref"),
)
def logs():
"""Every log THIS RUN produced, from the examples and from the fixtures.
Only the logs under the test build: a log beside the examples was written
by whatever state the working tree had when somebody last built there, and
a defect that was repaired an hour ago still stands in it. Measured on
2026-09-21, when a warning that the fresh build no longer carries was
reported from a log of the build before it.
The probe of the converter is not among them either: it is run once, on
purpose, to measure the width of a line, and it produces no page for any
reader. Its log therefore always asks for the second run that a document
gets and a measurement does not need.
"""
found = []
for directory, _, names in os.walk(support.BUILD):
for name in names:
if name.endswith(".log") and name != "metrics.log":
found.append(os.path.join(directory, name))
return sorted(found)
def main():
check = Check("logs")
for name in DOCUMENTS:
source = os.path.join(EXAMPLES, name)
if os.path.exists(source):
build(source, EXAMPLES)
read = logs()
check.that(read, "no build log was found at all")
for path in read:
with open(path, encoding="utf-8", errors="replace") as handle:
text = handle.read()
for pattern, what in COMPLAINTS:
hit = pattern.search(text)
check.that(hit is None,
f"{os.path.basename(path)} reports {what}: "
f"{text[hit.start():hit.start() + 120].splitlines()[0] if hit else ''}")
print(f" {len(read)} logs read")
return check.done()
if __name__ == "__main__":
sys.exit(main())