Skip to content

Commit f31e8ea

Browse files
committed
Merge commit 'eae2795226f33774528d51dbb58fa9f9604fe946' into wb/t40
2 parents 9f944e5 + eae2795 commit f31e8ea

5 files changed

Lines changed: 86 additions & 27 deletions

File tree

‎in2lambda/validation/pdf/__init__.py‎

Lines changed: 55 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
The trick for tracing is a marker: the document handed to pandoc carries a raw-LaTeX
99
comment naming the field before each field's markdown, and pandoc copies raw blocks
1010
through untouched. ``xelatex -file-line-error`` then reports every error as
11-
``set.tex:<line>: <message>``, and the last marker above that line names the field.
11+
``set.tex:<line>: <message>``, and the last marker above that line names the field. Not
12+
every error, though: a file LaTeX cannot find is announced with no location at all, and
13+
is traced instead through the ``Emergency stop.`` that follows it, which has one.
1214
1315
pandoc and xelatex are both optional, as they are everywhere else in in2lambda: without
1416
them this reports what to install rather than raising.
@@ -48,8 +50,17 @@
4850
)
4951
"""One ``-file-line-error`` line. The file is only ``set.tex`` for the set's own text."""
5052

51-
_ABORTED = re.compile(r"^! (.+)$", re.MULTILINE)
52-
r"""An error xelatex printed with no file and line to it - see :func:`_aborted`."""
53+
_BARE = re.compile(r"^! (.+)$", re.MULTILINE)
54+
r"""An error xelatex printed with no file and line to it.
55+
56+
``-file-line-error`` only rewrites an error raised at a line of a file. A file that
57+
cannot be found is announced through ``\typeout`` rather than raised, and an error
58+
raised once the input has run out - ``File ended while scanning use of \frac`` - has no
59+
line left to name, so both are only ever in the log behind a ``!``.
60+
"""
61+
62+
_STOP = "Emergency stop."
63+
"""TeX's last line, which says where it gave up rather than what was wrong."""
5364

5465
_IMAGE = re.compile(r"(!\[[^\]]*\]\()([^)]*)(\))")
5566
"""A markdown image with its path apart, so that the path can be rewritten or dropped."""
@@ -133,14 +144,13 @@ def render(
133144
latex, log = _compile(fields, images, work)
134145
problems = _reported(log, _locations(latex))
135146
if not (compiled := work / "set.pdf").is_file():
136-
# Nothing traced back to a field is the emergency-stop case: xelatex gave up
137-
# where it could name no line, so the log's own `!` lines are the only
138-
# account there is of why nothing was typeset, and saying none would leave
139-
# the refusal naming no cause at all.
140-
said = [str(problem) for problem in problems] or _aborted(log)
147+
# Why nothing was typeset and not merely that nothing was: whatever stopped
148+
# xelatex is among the problems like any other error, traced to the field
149+
# the stop happened in where there is one.
150+
said = "; ".join(str(problem) for problem in problems)
141151
raise CompileFailed(
142152
f"xelatex produced no PDF of {output.name}"
143-
+ (": " + "; ".join(said) if said else ".")
153+
+ (f": {said}" if said else ".")
144154
)
145155
output.parent.mkdir(parents=True, exist_ok=True)
146156
shutil.copy(compiled, output)
@@ -252,17 +262,6 @@ def _marked_document(fields: list[tuple[str, str]], available: set[str]) -> str:
252262
return "\n".join(blocks)
253263

254264

255-
def _aborted(log: str) -> list[str]:
256-
r"""Everything the xelatex log says went wrong without saying where.
257-
258-
``-file-line-error`` rewrites an error that happened at a line of a file, which is
259-
what :func:`_reported` reads. An error that happened as the input ran out -
260-
``File ended while scanning use of \frac`` - has no such line, and xelatex prints it
261-
the old way, so it is only ever in the log behind a ``!``.
262-
"""
263-
return [error.strip() for error in _ABORTED.findall(log)]
264-
265-
266265
def _locations(latex: str) -> list[tuple[int, str]]:
267266
"""Each marker in the generated LaTeX as the line it is on and the field it names."""
268267
return [
@@ -272,20 +271,49 @@ def _locations(latex: str) -> list[tuple[int, str]]:
272271
]
273272

274273

274+
def _where(file: str, line: int, locations: list[tuple[int, str]]) -> str:
275+
"""The field this line of this file is in, or `_SET` if it is in none of them.
276+
277+
Only ``set.tex`` holds the set's own text, so a line of a package or a font is
278+
nothing to do with the markers however it numbers.
279+
"""
280+
where = _SET
281+
if file == "set.tex":
282+
for number, location in locations:
283+
if number <= line:
284+
where = location
285+
return where
286+
287+
275288
def _reported(log: str, locations: list[tuple[int, str]]) -> list[Problem]:
276289
"""The xelatex log's errors as problems, each against the field it happened in.
277290
278291
The same error repeated - a command used twice, say - is one problem, since the
279-
author has one thing to go and fix.
292+
author has one thing to go and fix. An error printed bare is reported where the
293+
``Emergency stop.`` after it says the reading had got to, that being the only line
294+
of an abort with a location on it, and the stop itself only when there is nothing
295+
else to say.
280296
"""
281-
found = []
297+
bare = [message.strip() for message in _BARE.findall(log)]
298+
stop = _SET if _STOP in bare else None
299+
300+
errors = []
282301
for error in _ERROR.finditer(log):
283302
file, line, message = error[1], int(error[2]), error[3].strip()
284-
where = _SET
285-
if file == "set.tex":
286-
for number, location in locations:
287-
if number <= line:
288-
where = location
303+
if message == _STOP:
304+
stop = _where(file, line, locations)
305+
else:
306+
errors.append((_where(file, line, locations), message))
307+
308+
# Only the first bare error: whatever follows it is TeX unwinding from it, and the
309+
# author has the one thing to go and fix.
310+
if causes := [message for message in bare if message != _STOP]:
311+
errors.append((stop or _SET, causes[0]))
312+
elif not errors and stop:
313+
errors.append((stop, _STOP))
314+
315+
found: list[Problem] = []
316+
for where, message in errors:
289317
problem = Problem(where, f"the PDF generator cannot compile this: {message}")
290318
if problem not in found:
291319
found.append(problem)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
The set: the PDF generator cannot compile this: File ended while scanning use of \frac .
12
Question 1 "Kinetic energy", part (a), text, characters 15-32: KaTeX rejects it: Unexpected end of input in a macro argument, expected '}' at end of input: …frac{1}{2 m v^2
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Question 1 "Missing file", part (a), text: the PDF generator cannot compile this: LaTeX Error: File `nosuchfile.tex' not found.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"displayChatbot": true,
3+
"displayFinalAnswer": true,
4+
"displayStructuredTutorial": true,
5+
"displayWorkedSolution": true,
6+
"masterContent": "",
7+
"orderNumber": 0,
8+
"parts": [
9+
{
10+
"answerContent": "",
11+
"content": "The working is in \\input{nosuchfile}.",
12+
"orderNumber": 0,
13+
"responseAreas": [],
14+
"workedSolution": {
15+
"children": [],
16+
"content": ""
17+
}
18+
}
19+
],
20+
"publish": true,
21+
"title": "Missing file"
22+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"description": "A part whose text reads in a file that is not there, which LaTeX announces without a line before stopping. \\input rather than \\usepackage, which after \\begin{document} is refused before the file is ever looked up.",
3+
"finalAnswerVisibility": "OPEN_WITH_WARNINGS",
4+
"name": "Missing file",
5+
"structuredTutorialVisibility": "OPEN",
6+
"workedSolutionVisibility": "OPEN_WITH_WARNINGS"
7+
}

0 commit comments

Comments
 (0)