新增学习资料生成器模块(learner),从权威库生成多维日语学习资料: - 拼音/假名/汉字/熟字训四类索引,带拼音↔假名↔汉字交叉跳转 - 逐字音训分类(KANJIDIC2 精确查表 + 启发式回退 + 排序键) - 音变标注体系:浊化(連濁)、半浊化、促音变(促音便)、连声(れんじょう) 独立配色 + 合并逻辑 + 音变规律说明 - 显式标注表:rendaku_marks(连用形连浊)、renjou_marks(连声) - 每索引独立例词数配置(jlearn.toml + --config) - HTML 单页应用 + 静态 HTML + PDF(playwright) 清洗工具增强: - 拼音校验器(pinyin_checker)集成到 jclean - 多音字拼音校正、ます形サ変動詞转原型 数据: - 权威库补充连声词(反応/天皇/陰陽/観音/因縁/三位/輪廻/安穏) - KANJIDIC2 音训分类表、拼音校正字典 整理 .gitignore:忽略生成产物(output/study_materials)、词典数据库、 任务运行日志、备份文件
91 lines
2.6 KiB
Python
91 lines
2.6 KiB
Python
"""learner 子包测试:StudyDataBuilder 与各生成器。"""
|
||
import pytest
|
||
|
||
from pl_japanese.learner.builder import StudyDataBuilder
|
||
from pl_japanese.learner import generators
|
||
|
||
|
||
# 小型样本词表:覆盖单字多音、熟字训、空拼音等情况
|
||
SAMPLE = """\
|
||
中|国:ちゅう|ごく:zhong|guo
|
||
中|国|人:ちゅう|ごく|じん:zhong|guo|ren
|
||
生:せい:sheng
|
||
生:なま:sheng
|
||
学|生:がく|せい:xue|sheng
|
||
今日:きょう:jinri
|
||
一|人:ひと|り:yi|ren
|
||
"""
|
||
|
||
|
||
@pytest.fixture
|
||
def sample_vocab(tmp_path):
|
||
p = tmp_path / "vocab.txt"
|
||
p.write_text(SAMPLE, encoding="utf-8", newline="\n")
|
||
return p
|
||
|
||
|
||
@pytest.fixture
|
||
def builder(sample_vocab):
|
||
return StudyDataBuilder(str(sample_vocab))
|
||
|
||
|
||
def test_builder_loads(builder):
|
||
st = builder.stats()
|
||
assert st["loaded"] > 0
|
||
assert st["skipped"] == 0
|
||
|
||
|
||
def test_pinyin_reverse_lookup(builder):
|
||
# 单字 中 读 ちゅう,拼音应为 zhong
|
||
assert builder.pinyin_for("中", "ちゅう") == "zhong"
|
||
assert builder.pinyin_for("生", "せい") == "sheng"
|
||
assert builder.pinyin_for("生", "なま") == "sheng"
|
||
|
||
|
||
def test_single_only_excludes_multichar(builder):
|
||
single = builder.kanji_readings(single_only=True)
|
||
# 熟字训 今日 不应出现在单字里
|
||
assert "今日" not in single
|
||
assert "中" in single
|
||
assert "生" in single
|
||
|
||
|
||
def test_jukujikun_captures_multichar(builder):
|
||
juku = builder.jukujikun()
|
||
assert "今日" in juku
|
||
assert "きょう" in juku["今日"]
|
||
# 单字不应出现在熟字训里
|
||
assert "中" not in juku
|
||
|
||
|
||
def test_multi_reading_kanji(builder):
|
||
single = builder.kanji_readings(single_only=True)
|
||
# 生 有两种读音 せい / なま
|
||
assert set(single["生"].keys()) == {"せい", "なま"}
|
||
|
||
|
||
def test_all_generators_produce_files(builder, tmp_path):
|
||
out = tmp_path / "out"
|
||
out.mkdir()
|
||
for name, (func, _desc) in generators.GENERATORS.items():
|
||
path = func(builder, out)
|
||
assert path.exists(), f"{name} 未生成文件"
|
||
assert path.stat().st_size > 0, f"{name} 生成了空文件"
|
||
|
||
|
||
def test_anki_deck_is_tsv(builder, tmp_path):
|
||
out = tmp_path / "out"
|
||
out.mkdir()
|
||
path = generators.gen_anki_deck(builder, out)
|
||
lines = path.read_text(encoding="utf-8").strip().split("\n")
|
||
assert len(lines) > 0
|
||
for line in lines:
|
||
assert "\t" in line, "Anki 卡片应为 TAB 分隔"
|
||
front, back = line.split("\t", 1)
|
||
assert front # 正面非空
|
||
|
||
|
||
def test_missing_vocab_raises(tmp_path):
|
||
with pytest.raises(FileNotFoundError):
|
||
StudyDataBuilder(str(tmp_path / "nonexistent.txt"))
|