Created
July 3, 2020 17:00
-
-
Save muzimuzhi/f6447099a732bb32d31d1f81f61e6b7b to your computer and use it in GitHub Desktop.
A Python script showing multiply defined and unused labels in *.tex files
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
""" | |
Analysis *.tex files in current directory or given <path>, show info about | |
multiply defined and unused labels. | |
Usage: | |
python3 analysisLatexLabels.py | |
python3 analysisLatexLabels.py <path> | |
For example, | |
python3 analysisLatexLabels.py <path/to/pgfplots>/doc | |
""" | |
from os import PathLike | |
from pathlib import Path, PosixPath, WindowsPath | |
import re | |
import sys | |
from typing import Dict, List, Tuple, Union | |
# declare types | |
XPath = Union[PosixPath, WindowsPath, Path, PathLike] | |
# constant | |
if len(sys.argv) > 1: | |
DIR = sys.argv[1] | |
else: | |
DIR = '.' | |
# global variables | |
# list of tex files | |
tex_files: List[XPath] = list(Path(DIR).glob('**/*.tex')) | |
# defined labels, list of tuple (label name, tex file, line number) | |
labels: List[Tuple[str, str, int]] = [] | |
# used refs, dict of { label name : [(tex file, line number), (tex file, line number), ...] } | |
refs: Dict[str, List[Tuple[str, int]]] = {} | |
# get all labels and references | |
re_label = re.compile(r'\\label{(.*?)}') | |
re_ref = re.compile(r'\\(|page|name|auto|autopage)ref\*?{(.*?)}') | |
for tex in tex_files: | |
with open(tex) as f: | |
# For better diagnostic info, read file by lines. | |
# This assumes that every label or ref resides in single line. | |
f_lines = f.readlines() | |
curr_tex = str(tex) | |
for lineno, line in enumerate(f_lines): | |
lineno += 1 | |
# get labels | |
for label in re_label.findall(line): | |
labels.append((label, curr_tex, lineno)) | |
# get references | |
for ref in re_ref.findall(line): | |
label = ref[1] | |
if label in refs: | |
refs[label].append((curr_tex, lineno)) | |
else: | |
refs[label] = [(curr_tex, lineno)] | |
labels.sort() | |
# check multiply defined label(s) | |
multiply_defined = [] | |
for i in range(len(labels) - 1): | |
# label_curr = labels[i] | |
# label_next = labels[i+1] | |
if labels[i][0] == labels[i+1][0]: | |
error = f'Label "{labels[i][0]}" is multiply defined in' | |
# Use format "<relative path to file>:<line number>" so that clicking "<file>:<line>" opens <file> | |
# right at <line>. This is the feature of some shells, for example iTerm2.app for macOS. | |
error += f'\n\t{labels[i][1]}:{labels[i][2]} and' | |
error += f'\n\t{labels[i+1][1]}:{labels[i+1][2]}' | |
multiply_defined.append(error) | |
# check unused label(s) | |
unused = [] | |
for label in labels: | |
if label[0] not in refs: | |
error = f'Label "{label[0]}" unused:' | |
error += f'\n\t{label[1]}:{label[2]}' | |
unused.append(error) | |
# print results | |
print(*multiply_defined, sep='\n') | |
print(*unused, sep='\n') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment