fix(verify-layout): escape the docstring's LaTeX macro so the tool stops emitting SyntaxWarning (#480)

`tools/verify_layout.py`'s module docstring explains which documents trigger
the Poppler `-bbox` crash, and names the macro whose absence causes it:
`\hypersetup{pdftitle=...}`. In a non-raw docstring `\h` is an invalid escape
sequence, so compiling the module prints

    SyntaxWarning: "\h" is an invalid escape sequence.
    Such sequences will not work in the future. Did you mean "\\h"?

on Python 3.12+ (CPython gh-98401 promoted the long-standing
DeprecationWarning to a SyntaxWarning). The 3.15 language reference still
documents the sequence as a SyntaxWarning, with a SyntaxError only "in a
future Python version".

This is on the real path, not a constructed one: /apply Step 5b runs
`python3 tools/verify_layout.py <pdf>` once per document, so the first
invocation after any source change prints the warning into the middle of the
layout report a user is reading to judge their CV. It is also visible in this
repo's own CI log, where `python -m unittest discover -s tests -t . -v` prints
it above the test results.

Reproduce on master:

    python3 -W error::SyntaxWarning -c "compile(open('tools/verify_layout.py',
      encoding='utf-8').read(), 'tools/verify_layout.py', 'exec')"

raises on master; after this change it is silent.

Escaping (rather than making the docstring raw) keeps the surrounding prose
unchanged, and the single-backtick markup matches the other inline literals in
the same docstring. The rendered text still reads `\hypersetup{...}`.

The new `ToolsCompileWithoutWarnings` case is a pure `compile()` of the source
text of every `tools/*.py` inside `warnings.catch_warnings(record=True)` and
asserts no `SyntaxWarning` or `DeprecationWarning` (3.10 and 3.11 report the
same escape as a DeprecationWarning) - no cache file, no temp file, so it runs
the same on Windows (py_compile's atomic rename over an open
NamedTemporaryFile is what the first version tripped on). It fails on master
and passes here on every Python in the CI matrix, and it guards the next
docstring that quotes a macro.

Verification on Python 3.10, 3.11, 3.12, 3.13 and 3.14: `python3 -m unittest discover -s
tests -t .` (488 tests OK; master is 487), `python3 -W error::SyntaxWarning`
compile of every `tools/*.py`, `tests/test_changelog_structure.py`.
This commit is contained in:
Yang authored and GitHub committed 2026-09-27 19:28:00 +02:00
1 parent 120f476a08
commit a62af6a068
3 files changed
+49 -1

No files matched your search

+11
View File
@@ -132,6 +132,17 @@ per-file diff commands.
The standard `date`-first header already worked; regression coverage checks
all three column orders with and without a BOM.
- **`verify_layout.py` no longer emits a `SyntaxWarning` on every run** (`tools/verify_layout.py`,
`tests/test_verify_layout.py`) - the module docstring names the macro whose absence triggers the
Poppler `-bbox` crash, `\hypersetup{pdftitle=...}`, and a bare `\h` in a non-raw docstring is an
invalid escape sequence. Python 3.12+ prints `SyntaxWarning: "\h" is an invalid escape sequence`
the first time the module is compiled (CPython gh-98401) - it shows up in this repo's own CI log,
and in the middle of `/apply` Step 5b's layout report. The 3.15 language reference still
documents the sequence as a `SyntaxWarning`, with a `SyntaxError` only in a future Python
version. Escaped in the docstring; the new `ToolsCompileWithoutWarnings` case compiles the
source of every `tools/*.py` with warnings captured and fails on the unescaped version, so the
next docstring that quotes a LaTeX macro is caught too.
- **`/apply` Step 5b now actually runs the page-count check it claimed Step 5d ran**
(`.claude/commands/apply.md`, `tests/test_apply_page_count.py`) - the 5b prose said
"Page count is not checked here - that is `verify_pdf.py --pages`'s job, and Step 5d already
+37
View File
@@ -16,6 +16,7 @@ from contextlib import redirect_stdout
from pathlib import Path
from unittest.mock import patch
from tools import verify_layout
from tools.verify_layout import Line, Page, find_orphans, main, parse_pdf, report
A4_HEIGHT = 842.0
@@ -164,5 +165,41 @@ class TestExtractorFailure(unittest.TestCase):
self.assertIn("skipped:", err.getvalue())
class ToolsCompileWithoutWarnings(unittest.TestCase):
"""Every tool's source must compile clean.
`verify_layout.py`'s docstring documents LaTeX macros, and a bare `\\h` in a
non-raw docstring is an invalid escape sequence: Python 3.12+ emits a
SyntaxWarning when the module is compiled (CPython gh-98401), 3.10 and 3.11
a DeprecationWarning, so the test records both; the language
reference still documents it as a warning in 3.15, with a SyntaxError only
in a future Python version. The tool is run per-document from `/apply`, so
the warning lands in the middle of a verification report. Compiling is a
pure `compile()` over the source text - no cache file, no temp file - so the
check costs nothing and covers every `tools/*.py`, guarding the next
docstring that quotes a macro too.
"""
def test_sources_have_no_invalid_escape_sequences(self):
import warnings
tools_dir = Path(verify_layout.__file__).resolve().parent
sources = sorted(tools_dir.glob("*.py"))
self.assertIn(Path(verify_layout.__file__).resolve(), sources)
offenders: dict[str, list[str]] = {}
for source in sources:
with warnings.catch_warnings(record=True) as caught:
warnings.simplefilter("always")
compile(source.read_text(encoding="utf-8"), str(source), "exec")
syntax = [
str(w.message)
for w in caught
if issubclass(w.category, (SyntaxWarning, DeprecationWarning))
]
if syntax:
offenders[source.name] = syntax
self.assertEqual({}, offenders, "tools/*.py warn about invalid escapes when compiled")
if __name__ == "__main__":
unittest.main()
+1 -1
View File
@@ -32,7 +32,7 @@ than inventing a layout failure. A broken extractor has two distinct causes: an
all, and Poppler 26.0x before 26.05 aborts `-bbox`/`-bbox-layout`/`-htmlmeta` on a PDF
whose Info dictionary carries an empty string in any field - which `hyperref` writes for
every field it does not set, so any `lualatex`/`pdflatex` document built with `hyperref`
and no `\hypersetup{pdftitle=...}` triggers a real Poppler crashing on a legal PDF (#451).
and no `\\hypersetup{pdftitle=...}` triggers a real Poppler crashing on a legal PDF (#451).
Line height serves
as a font-size proxy to spot section headings; left edge (xMin) separates bullet lines
from entry headers.