| """Mechanical sanity check on report.tex.
|
|
|
| There is no LaTeX engine on this machine, so this catches the errors a
|
| compile would have caught: dangling \\ref, missing \\input or figure files,
|
| unbalanced braces or environments, and unclosed inline math.
|
|
|
| python report/check_tex.py
|
| """
|
|
|
| import re
|
| import sys
|
| from collections import Counter
|
| from pathlib import Path
|
|
|
| HERE = Path(__file__).resolve().parent
|
|
|
|
|
| def main():
|
| src = (HERE / 'report.tex').read_text(encoding='utf-8')
|
|
|
| body = '\n'.join(re.sub(r'(?<!\\)%.*$', '', ln) for ln in src.splitlines())
|
|
|
| problems = 0
|
|
|
| labels = set(re.findall(r'\\label\{([^}]+)\}', body))
|
| refs = set(re.findall(r'\\(?:eq)?ref\{([^}]+)\}', body))
|
| dangling = sorted(refs - labels)
|
| print('undefined refs :', dangling or 'none')
|
| problems += len(dangling)
|
| print('unused labels :', sorted(labels - refs) or 'none')
|
|
|
| missing = [f for f in re.findall(r'\\input\{([^}]+)\}', body)
|
| if not (HERE / (f + '.tex')).exists()]
|
| print('missing inputs :', missing or 'none')
|
| problems += len(missing)
|
|
|
| figs = re.findall(r'\\includegraphics(?:\[[^\]]*\])?\{([^}]+)\}', body)
|
| missing = [f for f in figs if not (HERE / 'figures' / f).exists()]
|
| print('missing figures:', missing or 'none')
|
| problems += len(missing)
|
|
|
| balance = body.count('{') - body.count('}')
|
| print('brace balance :', balance)
|
| problems += abs(balance)
|
|
|
| counts = Counter()
|
| for kind, name in re.findall(r'\\(begin|end)\{([^}]+)\}', body):
|
| counts[name] += 1 if kind == 'begin' else -1
|
| bad_envs = {k: v for k, v in counts.items() if v}
|
| print('unbalanced envs:', bad_envs or 'none')
|
| problems += len(bad_envs)
|
|
|
| odd = [i + 1 for i, ln in enumerate(body.splitlines())
|
| if len(re.findall(r'(?<!\\)\$', ln)) % 2]
|
| print('odd $ lines :', odd or 'none')
|
| problems += len(odd)
|
|
|
| print()
|
| print('OK' if problems == 0 else f'{problems} problem(s)')
|
| return 0 if problems == 0 else 1
|
|
|
|
|
| if __name__ == '__main__':
|
| sys.exit(main())
|
|
|