Skip to content

Instantly share code, notes, and snippets.

@muzimuzhi
Created July 3, 2020 17:00
Show Gist options
  • Save muzimuzhi/f6447099a732bb32d31d1f81f61e6b7b to your computer and use it in GitHub Desktop.
Save muzimuzhi/f6447099a732bb32d31d1f81f61e6b7b to your computer and use it in GitHub Desktop.
A Python script showing multiply defined and unused labels in *.tex files
#!/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