minecraft-wiki-mdifier 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- minecraft_wiki_mdifier-0.1.0/.github/workflows/ci.yml +37 -0
- minecraft_wiki_mdifier-0.1.0/.gitignore +58 -0
- minecraft_wiki_mdifier-0.1.0/.pre-commit-config.yaml +12 -0
- minecraft_wiki_mdifier-0.1.0/LICENSE +21 -0
- minecraft_wiki_mdifier-0.1.0/PKG-INFO +579 -0
- minecraft_wiki_mdifier-0.1.0/README-en.md +404 -0
- minecraft_wiki_mdifier-0.1.0/README-ja.md +411 -0
- minecraft_wiki_mdifier-0.1.0/README.md +554 -0
- minecraft_wiki_mdifier-0.1.0/pyproject.toml +70 -0
- minecraft_wiki_mdifier-0.1.0/src/minecraft_wiki_mdifier/__init__.py +26 -0
- minecraft_wiki_mdifier-0.1.0/src/minecraft_wiki_mdifier/_session.py +37 -0
- minecraft_wiki_mdifier-0.1.0/src/minecraft_wiki_mdifier/_validators.py +26 -0
- minecraft_wiki_mdifier-0.1.0/src/minecraft_wiki_mdifier/cache.py +135 -0
- minecraft_wiki_mdifier-0.1.0/src/minecraft_wiki_mdifier/cli.py +469 -0
- minecraft_wiki_mdifier-0.1.0/src/minecraft_wiki_mdifier/converter.py +464 -0
- minecraft_wiki_mdifier-0.1.0/src/minecraft_wiki_mdifier/exceptions.py +46 -0
- minecraft_wiki_mdifier-0.1.0/src/minecraft_wiki_mdifier/formatters.py +81 -0
- minecraft_wiki_mdifier-0.1.0/src/minecraft_wiki_mdifier/lib.py +273 -0
- minecraft_wiki_mdifier-0.1.0/src/minecraft_wiki_mdifier/parser.py +354 -0
- minecraft_wiki_mdifier-0.1.0/src/minecraft_wiki_mdifier/template_expander.py +787 -0
- minecraft_wiki_mdifier-0.1.0/src/minecraft_wiki_mdifier/wiki.py +306 -0
- minecraft_wiki_mdifier-0.1.0/templates_list.txt +1748 -0
- minecraft_wiki_mdifier-0.1.0/tests/__init__.py +0 -0
- minecraft_wiki_mdifier-0.1.0/tests/conftest.py +43 -0
- minecraft_wiki_mdifier-0.1.0/tests/test_cache.py +97 -0
- minecraft_wiki_mdifier-0.1.0/tests/test_cli.py +226 -0
- minecraft_wiki_mdifier-0.1.0/tests/test_converter.py +241 -0
- minecraft_wiki_mdifier-0.1.0/tests/test_formatter.py +46 -0
- minecraft_wiki_mdifier-0.1.0/tests/test_lib_batch.py +132 -0
- minecraft_wiki_mdifier-0.1.0/tests/test_parser.py +90 -0
- minecraft_wiki_mdifier-0.1.0/tests/test_template_expander.py +307 -0
- minecraft_wiki_mdifier-0.1.0/tests/test_wiki.py +285 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
matrix:
|
|
14
|
+
python-version: ["3.13"]
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
20
|
+
uses: actions/setup-python@v5
|
|
21
|
+
with:
|
|
22
|
+
python-version: ${{ matrix.python-version }}
|
|
23
|
+
cache: pip
|
|
24
|
+
|
|
25
|
+
- name: Install dependencies
|
|
26
|
+
run: |
|
|
27
|
+
python -m pip install --upgrade pip
|
|
28
|
+
pip install -e ".[dev]"
|
|
29
|
+
|
|
30
|
+
- name: Lint (ruff)
|
|
31
|
+
run: ruff check .
|
|
32
|
+
|
|
33
|
+
- name: Format check
|
|
34
|
+
run: ruff format --check .
|
|
35
|
+
|
|
36
|
+
- name: Test
|
|
37
|
+
run: pytest tests/ -v
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
.Python
|
|
7
|
+
build/
|
|
8
|
+
develop-eggs/
|
|
9
|
+
dist/
|
|
10
|
+
downloads/
|
|
11
|
+
eggs/
|
|
12
|
+
.eggs/
|
|
13
|
+
lib/
|
|
14
|
+
lib64/
|
|
15
|
+
parts/
|
|
16
|
+
sdist/
|
|
17
|
+
var/
|
|
18
|
+
wheels/
|
|
19
|
+
*.egg-info/
|
|
20
|
+
.installed.cfg
|
|
21
|
+
*.egg
|
|
22
|
+
|
|
23
|
+
# Virtual environments
|
|
24
|
+
.venv/
|
|
25
|
+
venv/
|
|
26
|
+
ENV/
|
|
27
|
+
env/
|
|
28
|
+
|
|
29
|
+
# IDE
|
|
30
|
+
.idea/
|
|
31
|
+
.vscode/
|
|
32
|
+
*.swp
|
|
33
|
+
*.swo
|
|
34
|
+
*~
|
|
35
|
+
.DS_Store
|
|
36
|
+
|
|
37
|
+
# pytest
|
|
38
|
+
.pytest_cache/
|
|
39
|
+
htmlcov/
|
|
40
|
+
.coverage
|
|
41
|
+
.coverage.*
|
|
42
|
+
.cache
|
|
43
|
+
nosetests.xml
|
|
44
|
+
coverage.xml
|
|
45
|
+
*.cover
|
|
46
|
+
|
|
47
|
+
# mypy
|
|
48
|
+
.mypy_cache/
|
|
49
|
+
|
|
50
|
+
# 本地测试产物
|
|
51
|
+
*.test
|
|
52
|
+
*.out
|
|
53
|
+
|
|
54
|
+
# 测试 Python 缓存
|
|
55
|
+
tests/__pycache__/
|
|
56
|
+
|
|
57
|
+
# Claude Code 本地配置
|
|
58
|
+
.claude/
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# pre-commit 配置:https://pre-commit.com/
|
|
2
|
+
# 安装:pip install pre-commit
|
|
3
|
+
# 启用:pre-commit install
|
|
4
|
+
# 手动运行:pre-commit run --all-files
|
|
5
|
+
|
|
6
|
+
repos:
|
|
7
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
8
|
+
rev: v0.15.16
|
|
9
|
+
hooks:
|
|
10
|
+
- id: ruff
|
|
11
|
+
args: [--fix]
|
|
12
|
+
- id: ruff-format
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 stone-brick
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,579 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: minecraft-wiki-mdifier
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: 将Minecraft Wiki页面转换为AI助手易读的Markdown格式
|
|
5
|
+
Author: stone-brick
|
|
6
|
+
License: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Keywords: converter,markdown,minecraft,wiki
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Classifier: Topic :: Text Processing :: Markup
|
|
14
|
+
Requires-Python: >=3.13
|
|
15
|
+
Requires-Dist: beautifulsoup4>=4.12.0
|
|
16
|
+
Requires-Dist: click>=8.1.0
|
|
17
|
+
Requires-Dist: markdownify>=1.2.0
|
|
18
|
+
Requires-Dist: requests>=2.31.0
|
|
19
|
+
Provides-Extra: dev
|
|
20
|
+
Requires-Dist: pre-commit>=3.5.0; extra == 'dev'
|
|
21
|
+
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
22
|
+
Requires-Dist: ruff>=0.15.0; extra == 'dev'
|
|
23
|
+
Requires-Dist: tqdm>=4.66.0; extra == 'dev'
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
<div align="center">
|
|
27
|
+
|
|
28
|
+
# ⚡ Minecraft Wiki MDifier
|
|
29
|
+
|
|
30
|
+
将 Minecraft Wiki 页面转换为 AI 助手易读的 Markdown 格式
|
|
31
|
+
|
|
32
|
+
[](LICENSE)
|
|
33
|
+
[](https://python.org)
|
|
34
|
+
|
|
35
|
+
**[English](./README-en.md)** · **[日本語](./README-ja.md)**
|
|
36
|
+
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
## 安装
|
|
40
|
+
|
|
41
|
+
**需要 Python >= 3.13**,依赖:`requests`, `beautifulsoup4`, `click`, `markdownify`。
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install -e .
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### PATH 设置
|
|
48
|
+
|
|
49
|
+
`mdifier` 命令装在 Python 的 Scripts 目录。如果终端找不到命令:
|
|
50
|
+
|
|
51
|
+
**Windows (Git Bash / PowerShell)**:
|
|
52
|
+
```bash
|
|
53
|
+
# 找到 Scripts 路径(一般输出形如 C:\Program Files\Python\Python313\Scripts)
|
|
54
|
+
python -c "import sysconfig; print(sysconfig.get_paths()['scripts'])"
|
|
55
|
+
# 临时加 PATH(替换上面输出的实际路径,Git Bash 用正斜杠)
|
|
56
|
+
export PATH="$PATH:/d/Program\ Files/Python/Python313/Scripts"
|
|
57
|
+
# 永久加:把上述加到 ~/.bashrc
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**macOS / Linux**:
|
|
61
|
+
```bash
|
|
62
|
+
# 通常 pip install 会自动装到 ~/.local/bin
|
|
63
|
+
export PATH="$PATH:$HOME/.local/bin"
|
|
64
|
+
# 或:python -m mdifier.cli(跨平台等价)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
验证:
|
|
68
|
+
```bash
|
|
69
|
+
mdifier --version
|
|
70
|
+
# 如不行:python -m mdifier.cli --version
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### 路径最佳实践(AI 助手必看)
|
|
74
|
+
|
|
75
|
+
`-o` 和 `-i` 参数的路径处理**因 shell 而异**。为避免混乱,**推荐使用相对路径**:
|
|
76
|
+
|
|
77
|
+
| 路径形式 | PowerShell | Git Bash | 推荐度 |
|
|
78
|
+
|----------|------------|----------|--------|
|
|
79
|
+
| `output/x.md`(相对) | cwd | cwd | 🥇 跨 shell 一致 |
|
|
80
|
+
| `D:/tests/x.md`(Windows 绝对) | D:\tests\x.md | D:\tests\x.md | 🥈 两 shell 都认 |
|
|
81
|
+
| `/tests/x.md`(Unix 绝对) | **D:\tests\x.md**(字面)| D:\Program Files\Git\tests(MSYS 翻译) | ❌ 行为不一致 |
|
|
82
|
+
|
|
83
|
+
**为什么 PowerShell 把 `/tests/x.md` 写到 D:\tests\?** PowerShell 不会做 MSYS 翻译,按字面理解路径,相当于 `D:\tests\x.md`(D: 是当前盘符)。
|
|
84
|
+
|
|
85
|
+
**为什么 Git Bash 把 `/tests/x.md` 翻译到 D:\Program Files\Git\tests\?** Git Bash 启动时把 `/` 映射到 Git 安装目录(MSYS 机制),这跟系统根目录不是一回事。
|
|
86
|
+
|
|
87
|
+
**推荐写法(AI 助手)**:
|
|
88
|
+
```bash
|
|
89
|
+
# 用相对路径(自动基于当前工作目录)
|
|
90
|
+
cd ~/wiki && mdifier convert "铁锭" -o output/iron.md
|
|
91
|
+
|
|
92
|
+
# 批量时先建子目录
|
|
93
|
+
mkdir -p output && mdifier batch -t 钻石 -t 铁锭 -o output/
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
保存路径**会显示为绝对路径**(避免你猜测它在哪)。
|
|
97
|
+
|
|
98
|
+
### CLI 退出码(BSD sysexits)
|
|
99
|
+
|
|
100
|
+
| 退出码 | 名称 | 含义 |
|
|
101
|
+
|--------|------|------|
|
|
102
|
+
| 0 | 成功 | 全部 OK |
|
|
103
|
+
| 64 (`EX_USAGE`) | 命令行参数错 | lang、--marker-format 格式 |
|
|
104
|
+
| 65 (`EX_DATAERR`) | 数据错 | 页面不存在、批量部分失败 |
|
|
105
|
+
| 70 (`EX_SOFTWARE`) | 内部软件错 | 未预期异常 |
|
|
106
|
+
| 74 (`EX_IOERR`) | 本地 I/O 错 | 写入文件失败、目录创建失败 |
|
|
107
|
+
| 75 (`EX_TEMPFAIL`) | 网络临时失败 | Wiki API 连不上(可重试)|
|
|
108
|
+
| 77 (`EX_NOPERM`) | 权限错 | 无写权限 |
|
|
109
|
+
|
|
110
|
+
错误消息用 `click.secho(..., fg='red')` 染色(管道/非 tty 自动失效)。
|
|
111
|
+
|
|
112
|
+
## 使用方式
|
|
113
|
+
|
|
114
|
+
### CLI
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
# 转换页面(中文 wiki 默认)
|
|
118
|
+
mdifier convert "铁锭"
|
|
119
|
+
|
|
120
|
+
# 英文 wiki
|
|
121
|
+
mdifier convert "Iron Ingot" --lang en -o iron.md
|
|
122
|
+
|
|
123
|
+
# 输出到文件
|
|
124
|
+
mdifier convert "铁锭" -o iron_ingot.md
|
|
125
|
+
|
|
126
|
+
# 完整 JSON 输出(含 templates)
|
|
127
|
+
mdifier convert "铁锭" --detail
|
|
128
|
+
|
|
129
|
+
# 使用 URL(自动识别语言)
|
|
130
|
+
mdifier convert "https://zh.minecraft.wiki/铁锭"
|
|
131
|
+
mdifier convert "https://minecraft.wiki/wiki/Iron_Ingot"
|
|
132
|
+
|
|
133
|
+
# 搜索页面
|
|
134
|
+
mdifier search "钻石"
|
|
135
|
+
mdifier search "diamond" --lang en
|
|
136
|
+
mdifier search "钻石" -n 20 # 返回结果数(默认 10)
|
|
137
|
+
|
|
138
|
+
# 批量转换:多个标题 → 独立 .md 文件
|
|
139
|
+
mdifier batch -t 钻石 -t 铁锭 -t 附魔台 -o ./out
|
|
140
|
+
mdifier batch -t Iron_Ingot -t Diamond --lang en -o ./en_out
|
|
141
|
+
|
|
142
|
+
# 批量转换:从文件读取标题列表(每行一个,# 开头为注释,空行跳过)
|
|
143
|
+
mdifier batch -i pages.txt -o ./out --workers 8
|
|
144
|
+
|
|
145
|
+
# 批量转换:从搜索结果中取前 N 个(--search-limit 默认 20)
|
|
146
|
+
mdifier batch --from-search "红石" --search-limit 30 -o ./out
|
|
147
|
+
|
|
148
|
+
# 批量:禁用进度条
|
|
149
|
+
mdifier batch -i pages.txt -o ./out --no-progress
|
|
150
|
+
|
|
151
|
+
# 缓存管理
|
|
152
|
+
mdifier cache info # 查看缓存状态
|
|
153
|
+
mdifier cache clear # 强制清空缓存(默认会交互确认;加 -y 跳过)
|
|
154
|
+
mdifier cache prune # 仅清理过期条目(保留未过期)
|
|
155
|
+
|
|
156
|
+
# 自定义模板标记(喂给不同 LLM prompt 风格)
|
|
157
|
+
# 格式:open/close,用单个 / 分隔,{name} 是模板类名占位符
|
|
158
|
+
mdifier batch -t 钻石 --marker-format '<details><summary>{name}</summary>/</details>'
|
|
159
|
+
mdifier batch -t Iron_Ingot --marker-format '<template:{name} start>/<template:{name} end>'
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
没装 pip 或找不到 `mdifier` 命令时,直接用 `python -m` 运行模块:
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
python -m mdifier.cli convert "铁锭"
|
|
166
|
+
python -m mdifier.cli search "钻石"
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Python 库
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
from mdifier import convert, convert_detailed, convert_many, search, BatchConvertResult, ConvertResult
|
|
173
|
+
|
|
174
|
+
# 简单转换(中文默认)
|
|
175
|
+
md = convert("铁锭")
|
|
176
|
+
print(md)
|
|
177
|
+
|
|
178
|
+
# 英文 wiki
|
|
179
|
+
md_en = convert("Iron Ingot", lang="en")
|
|
180
|
+
|
|
181
|
+
# 详细模式返回 ConvertResult(带 title、source、templates)
|
|
182
|
+
# CLI 可用 --detail 等价:mdifier convert "铁锭" --detail
|
|
183
|
+
result: ConvertResult = convert_detailed("铁锭")
|
|
184
|
+
print(f"标题: {result.title}")
|
|
185
|
+
print(f"来源: {result.source}") # "api" 或 "html"
|
|
186
|
+
print(f"模板: {result.templates}") # 非空 dict,含所有展开后的模板数据
|
|
187
|
+
print(f"Markdown 长度: {len(result.markdown)}")
|
|
188
|
+
|
|
189
|
+
# 跨调用共享缓存(同一进程内多次 convert 不重复请求模板)
|
|
190
|
+
shared_cache = {}
|
|
191
|
+
convert("钻石", template_cache=shared_cache)
|
|
192
|
+
convert("铁锭", template_cache=shared_cache) # 共享已展开的模板
|
|
193
|
+
|
|
194
|
+
# 批量转换
|
|
195
|
+
result = convert_many(["钻石", "铁锭", "附魔台"], max_workers=4)
|
|
196
|
+
for r in result.results:
|
|
197
|
+
print(f"=== {r.title} ===")
|
|
198
|
+
print(r.markdown)
|
|
199
|
+
if result.failed:
|
|
200
|
+
print(f"失败: {result.failed}")
|
|
201
|
+
if result.unresolved:
|
|
202
|
+
print(f"未展开模板: {result.unresolved}")
|
|
203
|
+
|
|
204
|
+
# 跨语言批量:混合 URL + 纯标题,内部按 lang 分组
|
|
205
|
+
items = [
|
|
206
|
+
"钻石", # zh
|
|
207
|
+
"https://minecraft.wiki/wiki/Diamond", # en(URL 识别)
|
|
208
|
+
"Iron Ingot", # 使用 --lang 默认值
|
|
209
|
+
"https://zh.minecraft.wiki/wiki/工作台", # zh
|
|
210
|
+
]
|
|
211
|
+
result = convert_many(items, lang="zh")
|
|
212
|
+
|
|
213
|
+
# 进度回调
|
|
214
|
+
def on_progress(done, total, title):
|
|
215
|
+
print(f"[{done}/{total}] {title}")
|
|
216
|
+
result = convert_many(["钻石", "铁锭", "附魔台"], on_progress=on_progress)
|
|
217
|
+
|
|
218
|
+
# 搜索
|
|
219
|
+
results = search("diamond", lang="en")
|
|
220
|
+
for r in results[:5]:
|
|
221
|
+
print(f"{r['title']}: {r['description']} ({r['url']})")
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### URL 自动识别
|
|
225
|
+
|
|
226
|
+
| URL 模式 | 识别为 |
|
|
227
|
+
|----------|--------|
|
|
228
|
+
| `https://zh.minecraft.wiki/wiki/铁锭` | zh |
|
|
229
|
+
| `https://zh.minecraft.wiki/铁锭`(省略 `/wiki/`) | zh |
|
|
230
|
+
| `https://minecraft.wiki/wiki/Iron_Ingot` | en |
|
|
231
|
+
| `https://en.minecraft.wiki/wiki/Diamond` | en |
|
|
232
|
+
| `钻石`(纯标题) | 使用 `--lang` 默认值 |
|
|
233
|
+
|
|
234
|
+
### 错误处理
|
|
235
|
+
|
|
236
|
+
```python
|
|
237
|
+
# 单页 convert 抛 InvalidInputError(继承自 ValueError)
|
|
238
|
+
try:
|
|
239
|
+
md = convert("nonexistent_xyz_123")
|
|
240
|
+
except InvalidInputError as e:
|
|
241
|
+
print(f"失败: {e}") # "无法获取页面: nonexistent_xyz_123"
|
|
242
|
+
|
|
243
|
+
# 批量 convert_many 不抛,仅聚合到 result.failed
|
|
244
|
+
result = convert_many(["钻石", "nonexistent_xyz_123"])
|
|
245
|
+
for t, err in result.failed:
|
|
246
|
+
print(f" 失败: {t}: {err}")
|
|
247
|
+
# CLI 模式下 result.failed 非空时 exit code = 65 (EX_DATAERR)
|
|
248
|
+
|
|
249
|
+
# 语言不支持抛 InvalidInputError
|
|
250
|
+
try:
|
|
251
|
+
convert("X", lang="xx")
|
|
252
|
+
except InvalidInputError as e:
|
|
253
|
+
print(e) # "Unsupported language: xx. Available: ['zh', 'en']"
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
**自定义异常层级**:
|
|
257
|
+
|
|
258
|
+
| 异常 | 父类 | 含义 |
|
|
259
|
+
|------|------|------|
|
|
260
|
+
| `MdifierError` | `Exception` | 基类 |
|
|
261
|
+
| `InvalidInputError` | `MdifierError`, `ValueError` | 用户输入错误 |
|
|
262
|
+
| `FetchError` | `MdifierError`, `requests.RequestException` | 网络错误基类 |
|
|
263
|
+
| `NetworkError` | `FetchError` | 连接失败/超时 |
|
|
264
|
+
| `WikiAPIError` | `FetchError` | API 异常结构 |
|
|
265
|
+
| `PageNotFoundError` | `FetchError` | 页面不存在 |
|
|
266
|
+
| `CacheError` | `MdifierError`, `OSError` | 缓存读写失败 |
|
|
267
|
+
|
|
268
|
+
## 功能特点
|
|
269
|
+
|
|
270
|
+
- **双模式**:CLI(`mdifier`)+ Python 库
|
|
271
|
+
- **多语言支持**:内置 `zh`(zh.minecraft.wiki)、`en`(minecraft.wiki)和 `ja`(ja.minecraft.wiki)
|
|
272
|
+
- **批量转换**:`mdifier batch` 子命令支持 -t / -i / --from-search
|
|
273
|
+
- **跨语言批量**:标题列表可混合 zh/en/ja 页面,内部自动按语言分组
|
|
274
|
+
- **持久化模板缓存**:相同模板只请求一次,跨运行共享(**5.4x 加速**)
|
|
275
|
+
- **缓存管理**:`mdifier cache info/clear/prune` 子命令组
|
|
276
|
+
- **自动 PascalCase**:仅对全小写、无空格/连字符的纯字母名生效(如 `for` → `For`、`id table` → `Id Table`)
|
|
277
|
+
- **未展开报告**:批量结束时报告缺失的模板名
|
|
278
|
+
- **模板标记可配置**:可自定义 `<template:xxx>` 标记格式
|
|
279
|
+
- **批量可取消**:`MarkdownConverter.cancel()` 中断大批量任务
|
|
280
|
+
- **智能获取**:优先 MediaWiki API,HTML 降级抓取
|
|
281
|
+
- **网络重试**:HTTP GET 请求自动重试 3 次(指数退避 0.5s/1s/2s),应对瞬时 5xx/429
|
|
282
|
+
- **模板适配**:合成表、物品信息框、战利品表等 30+ 常见模板自动展开
|
|
283
|
+
- **mcui 解析**:合成台、熔炉、织布机、锻造台的图片化 UI 转语义化文本
|
|
284
|
+
- **颜色代码**:Minecraft `&e` `&r` 等格式代码转为 `[yellow]` `[reset]` 等语义标签
|
|
285
|
+
- **并发优化**:模板展开使用线程池,单页 4.6x 加速
|
|
286
|
+
|
|
287
|
+
## 模板处理
|
|
288
|
+
|
|
289
|
+
模板被包裹在 `<template:xxx>` 标记中,内容按格式分类型处理:
|
|
290
|
+
|
|
291
|
+
| 模板 | 输出 |
|
|
292
|
+
|------|------|
|
|
293
|
+
| `Infobox`(物品信息框) | 两列 Markdown 表格 |
|
|
294
|
+
| `Crafting`(合成表) | 三列:材料 / 配方 / 描述 |
|
|
295
|
+
| `LootChest`(战利品表) | 六列:物品 / 来源 / 数量 / 概率等 |
|
|
296
|
+
| `mcui`(合成台/熔炉/织布机/锻造台) | 3x3 网格文本 + 物品描述 |
|
|
297
|
+
| `Hatnote`、`Quote` | 用 markdownify 转为 Markdown 格式 |
|
|
298
|
+
| 其他未识别模板 | 通用 markdownify 转换 |
|
|
299
|
+
| 展开失败(API 异常) | 回退文本 `[模板名: k=v]`,标记为 `class="error"` |
|
|
300
|
+
|
|
301
|
+
**输出示例**:
|
|
302
|
+
|
|
303
|
+
```markdown
|
|
304
|
+
### 合成
|
|
305
|
+
|
|
306
|
+
<template:wikitable start>
|
|
307
|
+
| 材料 | 合成 配方 |
|
|
308
|
+
| --- | --- |
|
|
309
|
+
| 钻石块 | [_|_|_ / _|_|钻石块|_ / _|_|_] -> 钻石x9 |
|
|
310
|
+
<template:wikitable end>
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
## 性能与缓存
|
|
314
|
+
|
|
315
|
+
### 缓存机制
|
|
316
|
+
|
|
317
|
+
模板展开结果自动持久化到磁盘:
|
|
318
|
+
|
|
319
|
+
- **位置**:`~/.cache/mdifier/templates.json`(Windows: `C:\Users\<user>\.cache\mdifier\`)
|
|
320
|
+
- **大小**:~1 MB / 1000 模板
|
|
321
|
+
- **TTL**:7 天(过期自动失效)
|
|
322
|
+
- **共享**:跨进程、跨运行、跨项目
|
|
323
|
+
|
|
324
|
+
### 性能数据
|
|
325
|
+
|
|
326
|
+
| 场景 | 耗时 |
|
|
327
|
+
|------|------|
|
|
328
|
+
| 首次运行(建立缓存) | ~6s |
|
|
329
|
+
| 二次运行(命中缓存) | ~1s |
|
|
330
|
+
| **加速比** | **5.4x** |
|
|
331
|
+
|
|
332
|
+
### 自定义模板标记
|
|
333
|
+
|
|
334
|
+
可改为 HTML `details` 等风格。`open` 和 `close` 各自可独立配置:
|
|
335
|
+
|
|
336
|
+
```python
|
|
337
|
+
from mdifier.converter import MarkdownConverter
|
|
338
|
+
|
|
339
|
+
c = MarkdownConverter()
|
|
340
|
+
c.template_marker_open = ":::{name}"
|
|
341
|
+
c.template_marker_close = ":::"
|
|
342
|
+
|
|
343
|
+
# 输出示例:
|
|
344
|
+
# :::infobox
|
|
345
|
+
# ...内容...
|
|
346
|
+
# :::
|
|
347
|
+
|
|
348
|
+
# 也可以 HTML 风格
|
|
349
|
+
c.template_marker_open = "<details><summary>{name}</summary>"
|
|
350
|
+
c.template_marker_close = "</details>"
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
CLI 端用 `--marker-format`(格式:`open/close`,用单个 `/` 分隔,`{name}` 是模板类名占位符):
|
|
354
|
+
|
|
355
|
+
```bash
|
|
356
|
+
mdifier batch -t 钻石 --marker-format '<details><summary>{name}</summary>/</details>'
|
|
357
|
+
mdifier batch -i pages.txt --marker-format '<template:{name} start>/<template:{name} end>'
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### 批量取消(API 用户)
|
|
361
|
+
|
|
362
|
+
通过 `converter_factory` 参数获得 converter 引用,从其他线程调用 `cancel()`:
|
|
363
|
+
|
|
364
|
+
```python
|
|
365
|
+
import threading
|
|
366
|
+
from mdifier import convert_many
|
|
367
|
+
from mdifier.converter import MarkdownConverter
|
|
368
|
+
|
|
369
|
+
c = MarkdownConverter(lang='zh')
|
|
370
|
+
threading.Timer(0.5, c.cancel).start()
|
|
371
|
+
# 0.5 秒后自动取消批量任务
|
|
372
|
+
convert_many(['钻石', '铁锭', '附魔台'],
|
|
373
|
+
converter_factory=lambda l, cache: c)
|
|
374
|
+
|
|
375
|
+
# 取消后可检查状态和未展开模板
|
|
376
|
+
print(c.is_cancelled()) # True
|
|
377
|
+
print(c.unresolved_templates) # frozenset({'HistoryTable', ...})
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### 批量输出文件命名
|
|
381
|
+
|
|
382
|
+
批量输出文件(`-o out_dir/`)按以下规则生成文件名:
|
|
383
|
+
|
|
384
|
+
- 页面标题经 `_slug()` 处理:非法文件名字符(`\ / : * ? " < > |`)→ `_`;空格→`_`;emoji 等高位 Unicode → 移除
|
|
385
|
+
- 标题为空时回退为 `untitled`
|
|
386
|
+
- 同名冲突:自动加 `-2`、`-3` 后缀,超过 999 次冲突则用 uuid 前 6 位
|
|
387
|
+
|
|
388
|
+
### 输入文件格式(`-i`)
|
|
389
|
+
|
|
390
|
+
```
|
|
391
|
+
# 注释行(# 开头)
|
|
392
|
+
钻石
|
|
393
|
+
铁锭
|
|
394
|
+
# 空行自动跳过
|
|
395
|
+
Iron Ingot
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
### 跨调用共享缓存(不持久化)
|
|
399
|
+
|
|
400
|
+
单页 `convert` 也支持传入 `template_cache`,同一进程内多次转换共享模板展开结果:
|
|
401
|
+
|
|
402
|
+
```python
|
|
403
|
+
from mdifier import convert
|
|
404
|
+
|
|
405
|
+
shared = {}
|
|
406
|
+
convert("钻石", template_cache=shared) # 24 条模板展开
|
|
407
|
+
convert("铁锭", template_cache=shared) # 增量 17 条,24 条共享
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
**与磁盘缓存的区别**:
|
|
411
|
+
- `template_cache` 参数:进程内共享,不写盘
|
|
412
|
+
- 磁盘缓存(`~/.cache/mdifier/`):跨进程、跨运行共享
|
|
413
|
+
- `convert_many()` 内部使用磁盘缓存;不写单页 `convert` 的中间缓存
|
|
414
|
+
|
|
415
|
+
### 缓存管理命令对比
|
|
416
|
+
|
|
417
|
+
| 命令 | 行为 | 适用场景 |
|
|
418
|
+
|------|------|----------|
|
|
419
|
+
| `mdifier cache info` | 显示统计(只读) | 查看缓存状态 |
|
|
420
|
+
| `mdifier cache clear` | 删除整个缓存文件 | wiki 大改,强制重建 |
|
|
421
|
+
| `mdifier cache prune` | 保留未过期(< 7 天),仅删除过期 | 日常维护 |
|
|
422
|
+
|
|
423
|
+
注:`cache clear` 默认会交互确认(除非 `-y`)。
|
|
424
|
+
|
|
425
|
+
**Python API 等效**:
|
|
426
|
+
|
|
427
|
+
```python
|
|
428
|
+
from mdifier.cache import cache_info, clear_cache, save_cache
|
|
429
|
+
|
|
430
|
+
# 手动将进程内缓存写入磁盘(convert_many 自动调用)
|
|
431
|
+
save_cache(my_cache_dict)
|
|
432
|
+
|
|
433
|
+
# 等价于 cache clear
|
|
434
|
+
clear_cache()
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
### `cache_info()` 返回字段
|
|
438
|
+
|
|
439
|
+
| 字段 | 类型 | 含义 |
|
|
440
|
+
|------|------|------|
|
|
441
|
+
| `path` | str | 缓存文件路径 |
|
|
442
|
+
| `exists` | bool | 是否存在 |
|
|
443
|
+
| `size_bytes` | int | 字节数 |
|
|
444
|
+
| `size_mb` | float | MB(保留 2 位小数)|
|
|
445
|
+
| `entries` | int | 总条目数 |
|
|
446
|
+
| `fresh_entries` | int | 未过期条目数 |
|
|
447
|
+
| `expired_entries` | int | 已过期条目数 |
|
|
448
|
+
| `oldest_ts` | str | 最早时间戳(ISO 格式)|
|
|
449
|
+
| `newest_ts` | str | 最新时间戳(ISO 格式)|
|
|
450
|
+
|
|
451
|
+
Python 等价:
|
|
452
|
+
|
|
453
|
+
```python
|
|
454
|
+
from mdifier.cache import cache_info, clear_cache
|
|
455
|
+
|
|
456
|
+
info = cache_info()
|
|
457
|
+
if info["exists"] and info["size_mb"] > 100:
|
|
458
|
+
clear_cache() # 清理
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
## 项目结构
|
|
462
|
+
|
|
463
|
+
```
|
|
464
|
+
src/mdifier/
|
|
465
|
+
├── __init__.py # 包初始化,导出 convert/convert_detailed/convert_many/search
|
|
466
|
+
├── lib.py # 库模式 API(含 convert_many)
|
|
467
|
+
├── cli.py # CLI 入口(click,含 batch/cache 子命令)
|
|
468
|
+
├── convert.py # 独立转换脚本(命令行直接运行)
|
|
469
|
+
├── search.py # 独立搜索脚本
|
|
470
|
+
├── wiki.py # MediaWiki API 获取 + HTML 降级
|
|
471
|
+
├── parser.py # Wikitext 解析器(模板/链接/标题)
|
|
472
|
+
├── template_expander.py # 模板展开:action=bucket 或 action=parse + 格式检测
|
|
473
|
+
├── formatters.py # Minecraft 颜色代码 → 语义化标签
|
|
474
|
+
├── converter.py # Markdown 生成:dict dispatch 渲染
|
|
475
|
+
└── cache.py # 模板展开缓存持久化
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
### 数据流
|
|
479
|
+
|
|
480
|
+
1. `WikiFetcher` → MediaWiki API 获取 wikitext
|
|
481
|
+
2. `WikiParser` → 解析 AST,提取模板到 `templates` 字典
|
|
482
|
+
3. `TemplateExpander` → 对 Lua 数据查询模板(Trade uses 等)优先尝试 `action=bucket`,失败则降级到 `action=parse`
|
|
483
|
+
4. `MarkdownConverter` → 按格式分发到对应渲染器,生成最终 Markdown
|
|
484
|
+
5. 跨运行:`get_or_load_persistent_cache()` 模块级单例懒加载磁盘缓存;批量结束仅 `save_cache()` 一次
|
|
485
|
+
|
|
486
|
+
## 开发
|
|
487
|
+
|
|
488
|
+
### 安装开发依赖
|
|
489
|
+
|
|
490
|
+
```bash
|
|
491
|
+
pip install -e ".[dev]"
|
|
492
|
+
pre-commit install
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
### 运行 ruff 检查
|
|
496
|
+
|
|
497
|
+
```bash
|
|
498
|
+
ruff check .
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
或通过 pre-commit 自动触发(提交时自动运行):
|
|
502
|
+
|
|
503
|
+
```bash
|
|
504
|
+
pre-commit run --all-files
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
### 手动测试
|
|
508
|
+
|
|
509
|
+
```bash
|
|
510
|
+
# 转换单页
|
|
511
|
+
mdifier convert "钻石" -o diamond.md
|
|
512
|
+
|
|
513
|
+
# 多页批量
|
|
514
|
+
for page in 钻石 铁锭 附魔台; do
|
|
515
|
+
mdifier convert "$page" -o "${page}.md"
|
|
516
|
+
done
|
|
517
|
+
|
|
518
|
+
# 缓存管理
|
|
519
|
+
mdifier cache info
|
|
520
|
+
mdifier cache clear -y
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
## 高级选项
|
|
524
|
+
|
|
525
|
+
### `MarkdownConverter` 构造参数
|
|
526
|
+
|
|
527
|
+
```python
|
|
528
|
+
from mdifier.converter import MarkdownConverter
|
|
529
|
+
|
|
530
|
+
c = MarkdownConverter(
|
|
531
|
+
lang="zh", # 语言:zh / en / ja
|
|
532
|
+
max_workers=10, # 模板展开线程池大小
|
|
533
|
+
template_cache={}, # 跨调用共享缓存(None 则新建)
|
|
534
|
+
use_persistent_cache=True, # 是否加载磁盘缓存(默认 True)
|
|
535
|
+
)
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
### `BatchConvertResult.results` 顺序说明
|
|
539
|
+
|
|
540
|
+
`result.results` **仅含成功项**,顺序与输入**不严格一致**(`convert_many` 按 lang 分组、按 future 完成顺序填充)。如需保持输入顺序,可自行构建 `dict[title, result]`:
|
|
541
|
+
|
|
542
|
+
```python
|
|
543
|
+
result = convert_many(["钻石", "铁锭", "附魔台"], lang="zh")
|
|
544
|
+
by_title = {r.title: r for r in result.results}
|
|
545
|
+
md_diamond = by_title.get("钻石")
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
### `MinecraftColorFormatter` 独立 API
|
|
549
|
+
|
|
550
|
+
低层颜色规范类,可独立使用或子类化:
|
|
551
|
+
|
|
552
|
+
```python
|
|
553
|
+
from mdifier.formatters import MinecraftColorFormatter
|
|
554
|
+
|
|
555
|
+
f = MinecraftColorFormatter()
|
|
556
|
+
md_text = f.clean("&e黄色&r重置") # '[yellow]黄色[reset]重置'
|
|
557
|
+
|
|
558
|
+
# 自定义颜色规范(未来扩展)
|
|
559
|
+
class HtmlColorFormatter(MinecraftColorFormatter):
|
|
560
|
+
COLORS = {"red": "#ff0000", "blue": "#0000ff"}
|
|
561
|
+
# ...
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
## 依赖
|
|
565
|
+
|
|
566
|
+
### 必需
|
|
567
|
+
|
|
568
|
+
- `requests` — MediaWiki API HTTP 客户端
|
|
569
|
+
- `beautifulsoup4` — HTML 解析
|
|
570
|
+
- `click` — CLI 框架
|
|
571
|
+
- `markdownify` — 通用 HTML → Markdown 转换
|
|
572
|
+
|
|
573
|
+
### 可选
|
|
574
|
+
|
|
575
|
+
- `tqdm` — `mdifier batch` 进度条;缺则降级为 stderr 文本
|
|
576
|
+
|
|
577
|
+
## License
|
|
578
|
+
|
|
579
|
+
MIT
|