novel-system/scripts/pre-commit-check.py

209 lines
6.8 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
"""
pre-commit-check.py · 冰朔小说创作系统 · 提交前自动检查
放在 scripts/ 目录git commit 前手动或通过 pre-commit hook 自动调用
检查范围chapters/ 下所有 .txt 文件正文
检查项
1. 字数非空白字符 2000-2800 区间仅单章文件
2. 违禁词AI 化套话
3. 半角标点引号省略号破折号
4. 代词一致性许怀钰=何采霏=按书检测
5. Markdown 泄露** ## 等
6. 元叙述正文中不出现"第X章"标题行除外多章文件跳过
用法
python3 scripts/pre-commit-check.py
退出码
0 = 全部通过
1 = 有不达标项
"""
import re
import os
import sys
import glob
REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
CHAPTERS_DIR = os.path.join(REPO_ROOT, "chapters")
# --- 检查规则 ---
# 违禁词AI 化套话
# 注意:"最后"单独检查,只匹配段首/句首的"最后,"模式AI 三段式标志)
FORBIDDEN_WORDS = [
"值得注意的是", "不得不说", "总的来说",
"作为一个", "说道",
"心中充满", "不禁", "不由得",
"与此同时",
]
# "最后"只检查句首用法AI 三段式 "最后,"
FORBIDDEN_PATTERNS = [
(r'\n\s*最后[,。]', '"最后"句首AI 三段式)'),
]
HALF_WIDTH_CHECKS = [
('"', "半角引号(应使用中文双引号)"),
("...", "半角省略号应使用……6点"),
("--", "半角破折号(应使用——中文破折号)"),
]
MARKDOWN_LEAKS = ["**", "##"]
# 每本书的主角代词规则:主角名 -> 正确代词
PROTAGONIST_RULES = {
"许怀钰": "",
"何采霏": "",
}
def count_chars(text):
"""非空白字符统计D169 铁律)"""
return len(re.findall(r'[^\s]', text))
def is_multi_chapter_file(filename):
"""判断是否为多章合并文件(之之侧旧格式)"""
# 匹配 "肖轩447-450章" / "沈婉凝278-282章" / "女频续写263章" 等
if re.search(r'\d+-\d+章', filename):
return True
# 匹配 "肖轩447-450章【8684字】" 这种
if '' in filename and '' in filename:
return True
# 匹配 "女频续写263章沈婉凝" 这种单文件多章
if re.search(r'\d+章.*', filename) and '' not in filename:
return True
return False
def check_chapter(filepath):
"""检查单个章节文件,返回 (passed, issues, skipped_reason)"""
issues = []
skipped_reason = None
with open(filepath, 'r', encoding='utf-8') as f:
text = f.read()
filename = os.path.basename(filepath)
# 0. 空文件检查
if not text.strip():
issues.append(f" ❌ 空文件")
return False, issues, None
# 判断是否为多章合并文件
multi_chapter = is_multi_chapter_file(filename)
if multi_chapter:
skipped_reason = "多章合并文件(旧格式,跳过字数和元叙述检查)"
# 1. 字数检查(仅单章文件)
char_count = count_chars(text)
if not multi_chapter:
if char_count < 2000:
issues.append(f" ❌ 字数不足: {char_count} < 2000{2000 - char_count} 字)")
elif char_count > 2800:
issues.append(f" ⚠️ 字数超标: {char_count} > 2800")
else:
issues.append(f" ✓ 字数: {char_count}")
else:
issues.append(f" 字数: {char_count}(多章合并,不判定)")
# 2. 违禁词检查(所有文件都查)
for word in FORBIDDEN_WORDS:
if word in text:
count = text.count(word)
issues.append(f" ❌ 违禁词 \"{word}\": {count}")
# 2b. 违禁模式检查(句首"最后"等)
for pattern, desc in FORBIDDEN_PATTERNS:
matches = re.findall(pattern, text)
if matches:
issues.append(f"{desc}: {len(matches)}")
# 3. 半角标点检查(所有文件都查)
for pattern_str, desc in HALF_WIDTH_CHECKS:
if pattern_str in text:
count = text.count(pattern_str)
issues.append(f" ⚠️ {desc}: {count}")
# 4. Markdown 泄露(所有文件都查)
for md in MARKDOWN_LEAKS:
if md in text:
issues.append(f" ❌ Markdown 泄露: \"{md}\"")
# 5. 元叙述检查(仅单章文件)
if not multi_chapter:
lines = text.split('\n')
for i, line in enumerate(lines):
if i == 0: # 标题行,跳过
continue
if re.search(r'\s*\d+\s*章', line):
issues.append(f" ❌ 元叙述: 第{i+1}行出现\"第X章\"")
# 6. 代词一致性
dirname = os.path.basename(os.path.dirname(filepath))
for name, correct_pronoun in PROTAGONIST_RULES.items():
if name in dirname or name in filename:
wrong_pronoun = "" if correct_pronoun == "" else ""
wrong_pattern = f"{name}{wrong_pronoun}"
if wrong_pattern in text:
issues.append(f" ❌ 代词错误: \"{wrong_pattern}\"{name}应为{correct_pronoun}")
# 判断是否通过
# ⚠️ 标记为警告(不阻断提交),❌ 标记为错误(阻断提交)
has_errors = any("" in issue for issue in issues)
passed = not has_errors
return passed, issues, skipped_reason
def main():
print("=" * 60)
print("冰朔小说创作系统 · 提交前自动检查")
print("=" * 60)
print()
# 找到所有 .txt 文件
txt_files = glob.glob(os.path.join(CHAPTERS_DIR, "**", "*.txt"), recursive=True)
if not txt_files:
print("未找到章节文件,跳过检查。")
sys.exit(0)
all_passed = True
total_files = 0
failed_files = 0
skipped_files = 0
for filepath in sorted(txt_files):
rel_path = os.path.relpath(filepath, REPO_ROOT)
total_files += 1
passed, issues, skipped_reason = check_chapter(filepath)
if skipped_reason:
skipped_files += 1
if not passed:
all_passed = False
failed_files += 1
print(f"📋 {rel_path}")
if skipped_reason:
print(f" {skipped_reason}")
for issue in issues:
print(issue)
print()
# 汇总
print("=" * 60)
print(f"检查完成: {total_files} 个文件, {total_files - failed_files} 通过, {failed_files} 不达标, {skipped_files} 跳过")
if all_passed:
print("✅ 全部通过,可以提交。")
sys.exit(0)
else:
print("❌ 有不达标项(❌ 为阻断项,⚠️ 为提醒项)。")
print(" 阻断项请修复后再提交。")
sys.exit(1)
if __name__ == "__main__":
main()