如何为 doc2dash 编写自定义解析器:从 Parser 协议到 Patcher 的完整插件开发指南
【免费下载链接】doc2dashCreate docsets for Dash.app-compatible API browsers.项目地址: https://gitcode.com/gh_mirrors/do/doc2dash
doc2dash 是一款把离线 HTML 文档转换成 Dash、Zeal 等 API 浏览器可直接检索的 docset 的开源工具,而**编写自定义解析器(Parser 插件)**正是它最核心的扩展能力:只要你的文档格式内置解析器不认,就可以用一个几十行的 Python 类教 doc2dash 读懂它。本文带你从 Parser Protocol 到 Patcher,完整走一遍插件开发的每一步 🧩
一、解析器插件的三大职责
在动手之前,先理解 doc2dash 对解析器插件的期望。官方扩展文档 docs/extending.md 明确列出三个任务:
- 检测(detect):判断一个目录是不是自己能解析的文档,并猜出 docset 的合适名称;
- 解析(parse):遍历文档目录,把每一个需要被索引的条目(函数、类、章节……)报告给 doc2dash;
- 修补(patch):往 HTML 文件里插入锚点标记,让 Dash 打开文档时能自动生成目录(TOC)。
这三件事分别对应接口里的detect()、parse()和make_patcher_for_file(),协议定义位于src/doc2dash/parsers/types.py。
二、Parser Protocol:四个接口逐一拆解
Parser 是一个 Python Protocol(结构化协议),你不需要继承任何基类,只要类满足以下形状即可:
| 接口 | 形式 | 作用 |
|---|---|---|
name | 类变量 | 解析器名称,出现在命令行输出里 |
__init__(source) | 实例方法 | 接收文档目录路径,doc2dash 会自动实例化 |
detect(path) | 静态方法 | 返回文档名称;不是自己的文档则返回None |
parse() | 生成器方法 | 逐个yield一个ParserEntry条目 |
make_patcher_for_file(path) | 上下文管理器 | 产出一个可执行的Patcher函数 |
第一步:用 detect() 快速识别文档类型
detect()是最轻量的入口——它只读文件头或标志性文件来判断"这份文档是不是我的"。最经典的技巧是找一个机器可读的标志文件。内置的 intersphinx 解析器(src/doc2dash/parsers/intersphinx.py)只检查文档根目录是否存在objects.inv并校验其前两行格式,命中就从文件里直接读出项目名作为 docset 名称:
@staticmethod def detect(path: Path) -> str | None: try: with (path / "objects.inv").open("rb") as f: if f.readline() != b"# Sphinx inventory version 2\n": return None return f.readline().split(b": ", 1)[1].strip().decode() except FileNotFoundError: return None💡 小贴士:
detect()遇到不属于自己的目录必须安静地返回None,绝不能抛异常——自动检测流程get_doctype()(位于src/doc2dash/parsers/__init__.py)会依次询问每个解析器,第一个命中者胜出。
第二步:用 parse() 生成索引条目
parse()是一个生成器,每发现一个值得索引的符号就yield一个ParserEntry。ParserEntry是src/doc2dash/parsers/types.py里的一个不可变数据类,只有三个字段:
- name:条目的完整显示名(如
parse); - type:条目类型,取自
EntryType枚举,涵盖Class、Function、Method、Module、Guide、Section等二十余种(对应 Dash 支持的条目类型); - path:文档内相对路径加
#锚点,例如api.html#module-parse。
这些条目会被逐条写入 docset 内部的 SQLite 索引(流程见src/doc2dash/convert.py中的convert_docs()),最终决定你在 Dash 里能搜到什么。
第三步:make_patcher_for_file() 准备打补丁
这个方法必须是一个上下文管理器:进入时打开目标文件、产出一个Patcher可调用对象,退出时把修改写回磁盘。内置实现用 BeautifulSoup 读取 HTML、修改、再编码回写,正是这个"进—改—出"的三段式结构。
三、Patcher:让 TOC 自动生成的关键函数
Patcher本质上是一个签名固定为patch(name, type, anchor, ref) -> bool的函数:doc2dash 会在anchor指定的位置之前插入一段ref引用,返回值表示是否成功找到锚点。
具体流程在src/doc2dash/parsers/patcher.py的patch_anchors()中:它先按文件分组收集所有带锚点的条目,然后对每个文件调用你的make_patcher_for_file(),批量执行修补。插入的引用格式是固定约定,例如:
//apple_ref/cpp/Method/foo所以你的 Patcher 要做的,就是在 HTML 里定位到id="anchor"的元素,在其前面插入一个<a class="dashAnchor" name="...">标签。定位不到就返回False,doc2dash 只会记一条调试日志并继续,不会中断整个转换。
四、用 --parser 参数加载你的自定义解析器
写好解析器后,无需修改 doc2dash 源码。只要你的模块可以被导入,直接通过--parser传入"模块路径.类名"即可:
$ doc2dash --parser my_pkg.my_parser.MyParser path/to/docs该选项的导入机制在src/doc2dash/__main__.py的ImportableType中实现:它按最后一个.拆分模块名与类名,动态导入后取出类对象。如果--parser省略,doc2dash 才会走get_doctype()自动检测流程。
五、最佳起点:研读内置 intersphinx 解析器
官方给出的最实用建议是——直接照着现成解析器抄。项目内置的完整参考实现是src/doc2dash/parsers/intersphinx.py,它展示了全部技巧:
detect()校验标志文件格式并顺带提取项目名;parse()委托给intersphinx_inventory.py读取机器可读清单,再用一张INV_TO_TYPE映射表把源格式类型翻译成EntryType;make_patcher_for_file()内用 BeautifulSoup 做多路回退定位(dt[id=...]、headerlink、span[id=...]依次尝试),兼容 Sphinx、MkDocs、pydoctor 等不同生成的页面结构。
六、验证你的解析器:参考现有测试写法
项目自带了可直接模仿的测试范例:
tests/parsers/test_detectors.py:验证每个解析器的detect()对不存在的目录都能优雅返回None,且能识别对应样例文档;tests/parsers/test_patcher.py:用了一个极简的FakeParser驱动patch_anchors(),验证"只有带#锚点的条目会被修补""失败只记日志不崩溃"等关键行为。
你的插件开发完成后,建议用同样的思路写两个小测试:一个测detect()的正反用例,一个测parse()产出的ParserEntry三元组是否正确。
七、总结:开发清单速查
✅ 1. 在detect()中找到你文档格式的"指纹"(标志文件或文件头) ✅ 2.parse()里把每个符号映射为ParserEntry(name, EntryType, "path#anchor")✅ 3.make_patcher_for_file()按"打开 → yield patch 函数 → 写回"三段式实现 ✅ 4. 用--parser 模块.类名运行,观察索引条目数量与 TOC 修补日志 ✅ 5. 参照tests/parsers/补齐正反用例
从 Parser Protocol 的四个接口到 Patcher 的上下文管理器,整套协议设计得非常克制:协议在src/doc2dash/parsers/types.py中总共只有百余行,却足以支撑任意文档格式接入 Dash 生态。现在,去把那些 Dash 官方文档集里没有的 API 文档,变成你指尖一按即达的检索体验吧 🚀
【免费下载链接】doc2dashCreate docsets for Dash.app-compatible API browsers.项目地址: https://gitcode.com/gh_mirrors/do/doc2dash
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考