针对 CPHOS 物理竞赛联考 LaTeX 模板的自动化格式检查工具。
- Python >= 3.10
- 无第三方依赖
# 检查单个文件
python main.py your-problem.tex
# 检查多个文件
python main.py problem1.tex problem2.tex输出示例:
============================================================
检查文件: your-problem.tex
============================================================
#1 [MATH-003] ⚠️ 警告 第134行 应使用 \varepsilon 而非 \epsilon
↳ 建议: 替换为 \varepsilon
#2 [TEXT-004] 💡 信息 第37行 存在未处理的 TODO 标记: 修改分值、标题
↳ 建议: 完成 TODO 内容后移除标记
--- 汇总: 0 个错误, 1 个警告, 1 个信息 ---
| 参数 | 说明 |
|---|---|
<files> |
要检查的 .tex 文件路径(支持多个) |
--list-rules |
列出所有已注册的检查规则 |
--min-severity <level> |
最低报告级别:info(默认)/ warning / error |
--exclude <ID ...> |
排除指定规则,如 --exclude MATH-001 FIG-003 |
--include <ID ...> |
只运行指定规则 |
--no-color |
禁用终端彩色输出 |
--no-cache |
禁用缓存,强制重新检查 |
--ignore <序号> |
忽略指定序号的问题(逗号分隔),如 --ignore 1,3 |
--list-ignores |
列出某文件已忽略的问题 |
--clear-ignores |
清除某文件的全部忽略项 |
--remove-ignore <指纹> |
移除单个忽略项(按指纹) |
每条检查结果分为三个严重级别:
| 级别 | 标记 | 含义 | 退出码 |
|---|---|---|---|
| ERROR | ❌ 错误 | 格式严重错误,可能导致编译失败 | 1 |
| WARNING | 不规范但不影响编译 | 0 | |
| INFO | 💡 信息 | 建议性提示 | 0 |
使用 --min-severity 可过滤低级别问题:
# 只看警告和错误
python main.py --min-severity warning your-problem.tex
# 只看错误
python main.py --min-severity error your-problem.tex| 规则 ID | 说明 |
|---|---|
| STRUCT-001 | 检查 \documentclass 是否使用 cphos 文档类 |
| STRUCT-002 | 检查必需的元数据命令(\cphostitle、\cphossubtitle)是否存在 |
| STRUCT-003 | 检查必需的环境(document、problem、problemstatement、solution)是否存在 |
| STRUCT-004 | 检查环境的 \begin/\end 配对与嵌套是否正确 |
| STRUCT-005 | 检查 problem 环境是否声明了总分值 |
| 规则 ID | 说明 |
|---|---|
| MATH-001 | 检查微分符号 d 是否使用正体 \mathrm{d} |
| MATH-002 | 检查特殊常数(e, π, i)是否使用正体 |
| MATH-003 | 检查介电常量是否使用 \varepsilon 而非 \epsilon |
| MATH-004 | 检查矢量是否使用 \vec{} 而非加粗 |
| MATH-005 | 检查数值与单位之间的格式(\text{~ 单位}) |
| MATH-006 | 检查大写希腊字母是否使用斜体(\var- 形式) |
| MATH-007 | 检查微分号 \mathrm{d} 前是否缺失\,间隔 |
| MATH-008 | 检查约化普朗克常量是否使用 \hslash 而非 \hbar |
| 规则 ID | 说明 |
|---|---|
| FIG-001 | 检查 figure/wrapfigure/subfigure 环境中是否包含 \caption |
| FIG-002 | 检查 figure/wrapfigure/subfigure 环境中是否包含 \label |
| FIG-003 | 检查 figure 环境是否指定了浮动位置 [H](不含 wrapfigure/subfigure) |
| FIG-004 | 检查 figure 环境中是否使用 \centering(不含 wrapfigure/subfigure) |
| 规则 ID | 说明 |
|---|---|
| SCORE-001 | 检查 solution 环境末尾是否有 \scoring 命令 |
| SCORE-002 | 检查解答中的公式是否通过 \eqtagscore 标记分值 |
| SCORE-003 | 检查 \solsubq 命令格式是否有两个参数 {编号}{分值} |
| SCORE-004 | 检查小问分值之和(\eqtagscore + \addtext)是否等于题目总分(multisol 感知,仅计首个解法) |
| 规则 ID | 说明 |
|---|---|
| TEXT-001 | 检查题干中的公式是否使用 \eqtag(而非 \eqtagscore) |
| TEXT-002 | 检查中文文本中的括号是否使用中文标点 |
| TEXT-003 | 检查英文逗号、句号后是否有空格 |
| TEXT-004 | 检查文档中是否有未处理的 TODO 注释 |
| TEXT-005 | 检查是否存在可用 \label/\ref 替代的硬编码公式引用(如"(1)式") |
| 规则 ID | 说明 |
|---|---|
| MSOL-001 | 检查 multisol 环境中是否包含至少两个 \item |
| MSOL-002 | 检查 multisol 中第二条及后续解法的 \eqtagscore 编号是否带 * 后缀 |
| MSOL-003 | 检查 multisol 中各解法的分值总和是否一致 |
首次检查时,工具会在 .tex 文件同目录下创建 .format_cache/ 文件夹,以 JSON 格式存储检查结果和文件 SHA-256 哈希。再次检查同一文件时,若文件未修改则直接读取缓存(输出末尾显示 (来自缓存))。
设计要点:缓存始终存储全量检查结果(运行所有规则、不过滤级别),--min-severity、--exclude 等筛选仅在展示时应用,确保缓存不会因筛选条件不同而遗漏问题。
# 强制跳过缓存
python main.py --no-cache your-problem.tex检查输出中每条问题前有 #序号,可通过 --ignore 将特定问题加入忽略列表:
# 第一步:正常检查,查看序号
python main.py your-problem.tex
# 第二步:忽略 #2 和 #3
python main.py --ignore 2,3 your-problem.tex
# 查看已忽略的问题
python main.py --list-ignores your-problem.tex
# 清除所有忽略
python main.py --clear-ignores your-problem.tex
# 按指纹移除单条忽略
python main.py --remove-ignore <fingerprint> your-problem.tex忽略机制基于 rule_id + 源码行内容 生成稳定指纹,因此:
- 插入/删除其他行导致行号变化 → 忽略仍有效
- 被忽略的行内容被修改 → 忽略自动失效
在 src/rules/ 下新建 .py 文件,继承 BaseRule 并设置 rule_id 即可自动注册,无需修改其他文件:
from src.rules.base import BaseRule
from src.models import Issue, Severity
class MyCustomRule(BaseRule):
rule_id = "CUSTOM-001"
description = "我的自定义检查"
def check(self, content: str, lines: list[str]) -> list[Issue]:
issues = []
for i, line in enumerate(lines):
if "某个不规范写法" in line:
issues.append(Issue(
rule_id=self.rule_id,
severity=Severity.WARNING,
message="检测到不规范写法",
line=i + 1,
suggestion="建议的修改方式",
))
return issues然后在 src/checker.py 中添加一行导入:
import src.rules.your_module # noqa: F401FormatChecker/
├── main.py # CLI 入口
├── README.md
├── LICENSE
├── src/
│ ├── __init__.py
│ ├── models.py # 数据模型(Severity, Issue, CheckResult)
│ ├── checker.py # 检查引擎(规则调度、缓存集成、过滤)
│ ├── cache.py # 缓存与忽略管理(JSON 存储、哈希比对)
│ └── rules/
│ ├── __init__.py
│ ├── base.py # BaseRule 基类 + RuleRegistry 自动注册
│ ├── structure.py # 文档结构规则(5 条)
│ ├── math_format.py # 数学排版规则(8 条)
│ ├── figure.py # 图片规范规则(4 条)
│ ├── scoring.py # 评分系统规则(4 条)
│ ├── text_format.py # 文本格式规则(5 条)
│ └── multisol.py # 多解法规则(3 条)
└── tests/
└── example-problem.tex # CPHOS 模板示例文件