Skip to content

Commit ec13830

Browse files
JihaoXinclaude
andcommitted
Parse LaTeX/BibTeX errors from log files instead of silently ignoring
- compile_latex() now parses main.log for "!" errors and main.blg for BibTeX errors (missing entries, syntax errors) - Stores errors in _last_compile_errors, logs them as warnings - compile_latex_with_errors() returns errors even when PDF exists (previously returned (True, "") masking undefined refs, bibtex issues) - Still uses nonstopmode (to avoid hanging), but errors are exposed Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 1ec169c commit ec13830

File tree

1 file changed

+49
-9
lines changed

1 file changed

+49
-9
lines changed

ark/compiler.py

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,33 @@ class CompilerMixin:
1717
def compile_latex_with_errors(self) -> tuple:
1818
"""Compile LaTeX and return (success, error_string).
1919
20-
On success: returns (True, "").
21-
On failure: returns (False, structured_errors) where structured_errors
22-
contains up to 5 error blocks parsed from main.log, with stderr fallback.
20+
On success with no errors: returns (True, "").
21+
On success with errors (PDF exists but has issues): returns (True, error_string).
22+
On failure: returns (False, structured_errors).
2323
"""
2424
success = self.compile_latex()
25-
if success:
25+
compile_errors = getattr(self, '_last_compile_errors', [])
26+
27+
if success and not compile_errors:
2628
return True, ""
2729

30+
if success and compile_errors:
31+
# PDF exists but has issues (undefined refs, bibtex errors, etc.)
32+
error_str = "PDF was generated but has the following issues:\n\n"
33+
error_str += "\n".join(f"- {e[:150]}" for e in compile_errors[:10])
34+
return True, error_str
35+
36+
# PDF not generated — real failure
2837
log_path = self.latex_dir / "main.log"
2938
errors = self._extract_latex_errors(log_path)
3039

31-
# If log parsing found nothing, use captured stderr as fallback
3240
stderr = getattr(self, '_last_compile_stderr', '')
3341
if "no specific errors found" in errors and stderr:
3442
errors += f"\n\nStderr from last pdflatex run:\n{stderr[:1000]}"
3543

44+
if compile_errors:
45+
errors += "\n\nAdditional issues:\n" + "\n".join(f"- {e[:150]}" for e in compile_errors[:10])
46+
3647
return False, errors
3748

3849
def _extract_latex_errors(self, log_path: Path) -> str:
@@ -182,12 +193,15 @@ def _auto_fix_latex(self):
182193
def compile_latex(self) -> bool:
183194
"""Compile the LaTeX paper.
184195
196+
Uses nonstopmode to avoid interactive prompts, but parses log/blg
197+
files for actual errors (undefined citations, bibtex syntax errors, etc.).
198+
185199
On success, stores the PDF path in self._latest_pdf.
186-
Returns True/False for backward compat; callers that need the
187-
path should read self._latest_pdf.
200+
Returns True/False for backward compat.
188201
"""
189202
self.log("Compiling LaTeX...")
190203
self._last_compile_stderr = ""
204+
self._last_compile_errors = []
191205
try:
192206
for cmd in [
193207
["pdflatex", "-interaction=nonstopmode", "main.tex"],
@@ -205,7 +219,26 @@ def compile_latex(self) -> bool:
205219
stdout = result.stdout.decode("utf-8", errors="replace") if isinstance(result.stdout, bytes) else (result.stdout or "")
206220
if result.returncode != 0 and "main.tex" in cmd:
207221
self._last_compile_stderr = stderr[:1000] or stdout[-1000:]
208-
self.log(f"LaTeX compilation warning: {stderr[:500]}")
222+
223+
# Parse log for real errors (not just warnings)
224+
log_path = self.latex_dir / "main.log"
225+
blg_path = self.latex_dir / "main.blg"
226+
227+
if log_path.exists():
228+
log_text = log_path.read_text(errors="replace")
229+
# LaTeX errors start with "!"
230+
latex_errors = [l.strip() for l in log_text.split("\n") if l.startswith("!")]
231+
if latex_errors:
232+
self._last_compile_errors.extend(latex_errors[:10])
233+
234+
if blg_path.exists():
235+
blg_text = blg_path.read_text(errors="replace")
236+
# BibTeX errors
237+
import re as _re
238+
bib_errors = _re.findall(r"^.*error message.*$|^I was expecting.*$|^Warning--I didn't find a database entry.*$",
239+
blg_text, _re.MULTILINE)
240+
if bib_errors:
241+
self._last_compile_errors.extend(bib_errors[:10])
209242

210243
pdf_path = self.latex_dir / "main.pdf"
211244
if pdf_path.exists() and pdf_path.stat().st_size > 0:
@@ -215,10 +248,17 @@ def compile_latex(self) -> bool:
215248
self._overfull_warnings = self._parse_overfull_warnings()
216249
page_info = f", {self._body_page_count:.1f} body pages ({self._pdf_page_count} total)" if self._body_page_count else ""
217250
overfull_info = f", {len(self._overfull_warnings)} overfull warnings" if self._overfull_warnings else ""
218-
self.log(f"LaTeX compiled successfully: {pdf_path} ({pdf_path.stat().st_size} bytes{page_info}{overfull_info})")
251+
error_info = f", {len(self._last_compile_errors)} errors" if self._last_compile_errors else ""
252+
self.log(f"LaTeX compiled successfully: {pdf_path} ({pdf_path.stat().st_size} bytes{page_info}{overfull_info}{error_info})")
253+
if self._last_compile_errors:
254+
for err in self._last_compile_errors[:5]:
255+
self.log(f" Compile issue: {err[:120]}", "WARN")
219256
return True
220257
else:
221258
self.log("LaTeX compilation failed: PDF not generated")
259+
if self._last_compile_errors:
260+
for err in self._last_compile_errors[:5]:
261+
self.log(f" Error: {err[:120]}", "ERROR")
222262
return False
223263
except Exception as e:
224264
self.log(f"LaTeX compilation error: {e}")

0 commit comments

Comments
 (0)