course-publisher/md2html.py(260 行)支持一个独特扩展——把列表项后面的引用折叠成"看答案"按钮。
而引入 markdown 库会带来依赖和不可控的输出。
渲染器是一个手写状态机,用"层栈"管理列表嵌套(第 112-137 行):
def render(md: str) -> str:
"""把 Markdown 正文渲染成 HTML 片段(不含 <html>/<body>)。
列表用一个"层栈"来管:每层记 {indent, kind, li_open}。
`<li>` 故意**不立刻闭合**,因为「课堂提问」的折叠答案要落在它里面;
遇到下一个块级元素或列表层级变化时才补上 `</li>`。
"""
stack: list[dict] = [] # [{indent, kind, li_open}]
can_attach = False # 刚输出过 <li>,下一条引用可以折叠进去
答案折叠的关键分支(第 197-213 行):
if s.startswith(">"): # 引用
...
if can_attach and stack:
# 「课堂提问」:答案折叠进上一个列表项,先自己想再看答案
out.append('<details class="answer"><summary>看答案</summary>'
f'<div class="answer-body">{_loose(body)}</div></details>')
close_li(stack[-1]) # 现在才补 </li>
can_attach = False
else:
out.append(f"<blockquote>{_loose(body)}</blockquote>")
还有一个容易被忽略但很真实的坑(第 143-156 行):
松散列表(列表项之间有空行)不能断开,否则会另起一个 <ol>、编号从 1 重来。
<li>:为了实现"答案挂在列表项里"这个扩展,故意不立刻写 </li>re + html,任何环境都能跑<li> 内""松散列表不能断成两个 <ol>")tests/test_md2html.py(197 行);作者还用"模板/参考解"双实现验证mdcourse 工具:md2html.py + build.py(课程结构校验 + 静态站生成 + zip)markdown、markdown-it-py、mistune 功能完整、性能好——普通场景应该用它们第 19 课(零依赖构建与交付验收)