惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - 【当耐特】
Engineering at Meta
Engineering at Meta
Microsoft Azure Blog
Microsoft Azure Blog
The Cloudflare Blog
小众软件
小众软件
有赞技术团队
有赞技术团队
MyScale Blog
MyScale Blog
A
About on SuperTechFans
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Jina AI
Jina AI
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
云风的 BLOG
云风的 BLOG
Vercel News
Vercel News
博客园_首页
T
Troy Hunt's Blog
I
InfoQ
M
MIT News - Artificial intelligence
aimingoo的专栏
aimingoo的专栏
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
P
Proofpoint News Feed
博客园 - 司徒正美
Cloudbric
Cloudbric
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Forbes - Security
Forbes - Security
D
Docker
Attack and Defense Labs
Attack and Defense Labs
Google DeepMind News
Google DeepMind News
N
News and Events Feed by Topic
博客园 - 聂微东
S
Security Affairs
Security Archives - TechRepublic
Security Archives - TechRepublic
WordPress大学
WordPress大学
N
News and Events Feed by Topic
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
H
Heimdal Security Blog
大猫的无限游戏
大猫的无限游戏
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
雷峰网
雷峰网
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
T
Threat Research - Cisco Blogs
Blog — PlanetScale
Blog — PlanetScale
A
Arctic Wolf
J
Java Code Geeks
F
Full Disclosure
L
Lohrmann on Cybersecurity
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Project Zero
Project Zero
GbyAI
GbyAI
B
Blog
爱范儿
爱范儿

追梦人物的博客

0x05:Merkle Tree & Patricia Trie 0x04:ECDSA - 以太坊设计与实现 LeetCode 105 从前序与中序遍历构造二叉树 迭代算法原理 + 完整证明 0x03:Address - 以太坊设计与实现 0x02:Secp256k1 - 以太坊设计与实现 0x00:专栏开篇 - 以太坊设计与实现 0x01:RLP 编码 - 以太坊设计与实现 Bellman-Ford 算法原理及其在 DeFi 套利中的应用 - 追梦人物的博客 迪杰斯特拉(Dijkstra)最短路径算法原理、实现与证明 - 追梦人物的博客 实战:CEX-DEX 稳定币套利监控程序开发 - 追梦人物的博客 CEX-DEX 稳定币套利模型 - 追梦人物的博客 Uniswap 手续费和协议费机制剖析 - 追梦人物的博客 Uniswap 流动性机制及相关数学原理分析 - 追梦人物的博客 uv 替代 pyenv + pipx + poetry 环境管理实践 Rust 项目从创建到发布 - 追梦人物的博客 VS Code 调试 Python - 追梦人物的博客 比特币跨市场套利的数学模型 - 追梦人物的博客 ERC-20 相关知识点总结 - 追梦人物的博客 Django 老项目如何从 SQLite 迁到 PostgreSQL 自动生成接口文档 - HelloDjango - django REST framework 教程 单元测试 - HelloDjango - django REST framework 教程 限制接口访问频率 - HelloDjango - django REST framework 教程 API 版本管理 - HelloDjango - django REST framework 教程 如何在 Windows 下搭建高效的 django 开发环境 加缓存为接口提速 - HelloDjango - django REST framework 教程 基于 drf-haystack 实现文章搜索接口 - HelloDjango 评论接口 - HelloDjango - django REST framework 教程 实现分类、标签、归档日期接口 - HelloDjango - django REST framework 教程 在接口返回Markdown解析后的内容 - HelloDjango - django REST framework 教程 文章详情接口 - HelloDjango - django REST framework 教程 分页 - HelloDjango - django REST framework 教程 使用视图集简化代码 - HelloDjango - django REST framework 教程 用类视图实现首页 API - HelloDjango - django REST framework 教程 实现博客首页文章列表 API - HelloDjango - django REST framework 教程 初始化 RESTful API 风格的博客系统 - HelloDjango django-rest-framework 是什么鬼? - HelloDjango - django REST framework 教程 结束 or 开始? - HelloDjango Coverage.py 统计测试覆盖率 - HelloDjango - Django博客教程(第二版) 单元测试:测试评论应用 - HelloDjango - Django博客教程(第二版) 单元测试:测试 blog 应用 - HelloDjango Django Haystack 全文检索与关键词高亮 - HelloDjango Django 博客实现简单的全文搜索 - HelloDjango - Django博客教程(第二版) 开启 Django 博客的 RSS 功能 - HelloDjango 统计各个分类和标签下的文章数 - HelloDjango - Django博客教程(第二版) 稳定易用的 Django 分页库,完善分页功能 - HelloDjango 通过 Django Pagination 实现简单分页 - HelloDjango 在脚本中使用 ORM:Faker 批量生成测试数据 - HelloDjango Django 官方推荐的姿势:类视图 - HelloDjango - Django博客教程(第二版) Django 使用 union 合并不同模型(Model) 的查询集(QuerySet) 开发博客文章阅读量统计功能 - HelloDjango - Django博客教程(第二版) 使用 Docker 让部署 Django 项目更加轻松 - HelloDjango 使用 Certbot 向 Let's Encrypt 免费申请 HTTPS 证书 - HelloDjango 使用 Fabric 自动化部署 - HelloDjango 博客代码开源啦 - 追梦人物的博客 (赠书)推荐一本django书籍:Django企业开发实战 - 追梦人物的博客 Nginx+Gunicorn+Supervisor 部署 Django 博客应用 - HelloDjango 优化博客功能细节,提升使用体验 - HelloDjango - Django博客教程(第二版) 交流的桥梁:评论功能 - HelloDjango - Django博客教程(第二版) 分类、归档和标签页 - HelloDjango - Django博客教程(第二版) 页面侧边栏:使用自定义模板标签 - HelloDjango - Django博客教程(第二版) 自动生成文章摘要 - HelloDjango - Django博客教程(第二版) Markdown 文章自动生成目录,提升阅读体验 - HelloDjango - Django博客教程(第二版) 让博客支持 Markdown 语法和代码高亮 - HelloDjango 开发博客文章详情页 - HelloDjango - Django博客教程(第二版) 创作后台开启,请开始你的表演 - HelloDjango - Django博客教程(第二版) 博客从“裸奔”到“有皮肤” - HelloDjango - Django博客教程(第二版) Django 的接客之道 - HelloDjango - Django博客教程(第二版) Django 迁移、操作数据库 - HelloDjango - Django博客教程(第二版) 创建 Django 博客的数据库模型 - HelloDjango 开始进入 django 开发之旅 - HelloDjango "空空如也"的博客应用 - HelloDjango - Django博客教程(第二版) 一种自顶而下的Python装饰器设计方法 - 追梦人物的博客 Python提取支付宝和微信支付二维码 - 追梦人物的博客 2018 - 我的学生生涯最后一年回顾 - 追梦人物的博客 批量清除todo练习参考答案 - Vue 2.x Todo 教程练习参考答案 筛选练习参考答案 - Vue 2.x Todo 教程练习参考答案 删除todo练习参考答案 - Vue 2.x Todo 教程练习参考答案 编辑todo练习参考答案 - Vue 2.x Todo 教程练习参考答案 添加todo练习参考答案 - Vue 2.x Todo 教程练习参考答案 标为完成练习参考答案 - Vue 2.x Todo 教程练习参考答案 入门仪式_Hello_Vue练习参考答案 - Vue 2.x Todo 教程练习参考答案 组件化todo应用 - Vue 2.x Todo 教程 批量清除todo - Vue 2.x Todo 教程 本地存储 - Vue 2.x Todo 教程 筛选 - Vue 2.x Todo 教程 还剩多少todo未完成 - Vue 2.x Todo 教程 自定义指令实现自动聚焦 - Vue 2.x Todo 教程 全部标为完成 - Vue 2.x Todo 教程 删除todo - Vue 2.x Todo 教程 编辑todo - Vue 2.x Todo 教程 添加todo - Vue 2.x Todo 教程 标为完成 - Vue 2.x Todo 教程 UI - Vue 2.x Todo 教程 显示todo列表 - Vue 2.x Todo 教程 入门仪式:Hello Vue - Vue 2.x Todo 教程 在学习django-rest-framework时收集的学习资料推荐 - 追梦人物的博客 区块链理论与应用研究小组成员招募书 - 追梦人物的博客 Python界网红,豆瓣工程师董伟明加了我的QQ后 - 追梦人物的博客 招募Django学习小组项目组核心成员 - 追梦人物的博客
拓展Python Markdown - 追梦人物的博客
2020-07-25 · via 追梦人物的博客

通过拓展 Python Markdown 来获得类似 django 官方文档的阅读体验。

最近阅读 django 的官方文档,发现一些很细节的文档内容展现形式,能够极大地提高文档的阅读体验。阅读其他技术文档时也会经常发现类似的内容展现形式。我的博客主要也是发布一些技术类文章,于是决定实现类似的功能以增强读者阅读博客文章的体验。

确定需求后,简单地研究了一下实现方式,然后花了一个晚上的时间把功能上线了,在这里分享记录一下整个功能的实现过程。

确定需求

阅读技术类文档经常会看到这么几种内容:Code blockAdmonitionCommand tab。中文不太好翻译,来看一下实际的效果就知道了,下面是 django 中这几种内容的展现形式。

Code block

django documentation code block

代码块的上方有一个 header,左边显示代码块所在文件路径,这样示例代码应该放在哪个文件就一目了然;右边是一个按钮,点击即可复制整个代码块中的内容。

Admonition

django documentation admonition

admonition 用来展现一些提示、警告等内容,文档中经常见到的有危险(danger)、警告(warning)、注意(attention)、重要(important)、提示(hint)等内容,不同类型的内容通常会以不同的背景和字体颜色区分。

Command tab

django documentation command tab

技术类文档中少不了系统命令,很多相同效果的命令在不同操作系统中的字符内容是有一定差异的。写的不太好的文档通常只给出 Linux 下的执行命令;好点的文档则将执行命令分别列出;而 django 文档的处理就非常细节,以 tab 切换的形式给出不同系统下的命令执行方式,这样既能够列出不同系统下的执行命令,又不会重复占用文档的内容空间,提高了文档的紧凑感和阅读时的流畅性。

我的需求就是要在自己博客文章中实现以上三种内容展现效果。

方案研究

博客文章的标记语言采用的是 Markdown,具体的实现采用的是 Python-Markdown/markdown 这个开源库。这个库不仅实现了 Markdown 标准语法的解析,还提供了很多丰富的拓展语法。

例如需求中提到的 admonition 功能,通过添加 markdown.extensions.admonition 拓展就可以直接实现(具体的实现原理和使用方式下面会介绍)。

Code block 的功能也有相应的拓展来实现的,但是调研发现官方自带拓展的功能弱了一点,无法通过拓展的语法在代码块的上方添加 header,只能部分满足需求。开源的第三方拓展中也没有找到可满足需求的实现,所以这里可能需要自己拓展实现。

Command tab 功能的实现在 markdown 的第三方拓展库 facelessuser/pymdown-extensions 中找到了一个 tabbed 拓展,提供的标记语法可被解析生成一个 tab 选项卡,完美满足需求。

至此,实现方案基本就可以确定了:

  1. admonition 功能,直接使用 markdown 库的官方 admonition 拓展就可以;
  2. Code blockpymdown-extensions 中有一个更好的拓展实现,叫做 SuperFences,但是还是无法满足生成代码块 header 的需求,因此我们考虑对 SuperFences 再做进一步拓展;

  3. Command tab 使用 pymdown-extensions 的 tabbed 拓展可完美满足需求。

具体实现

Admonition

admonition 的实现最为简单,只需引入官方 markdown.extensions.admonition 拓展就可以了。它的实现原理是通过下面的语法标记 admonition 的内容:

markdown 会把标记内容解析为下面的 HTML 文本:

<div class="admonition note">
<p class="admonition-title">注意</p>
<p>请注意这段内容!</p>
</div>

编写适当的 CSS 样式,就可以达到类似 django 文档中那样的展示效果了。

Code Block

code block 的实现使用 pymdown-extensions 中 SuperFences 拓展,不过遗憾的是,SuperFences 没有在代码块头部添加 header 内容的功能,这样就无法展示代码块所在的文件路径等信息了。花了不少时间读了一下 SuperFences 的源码,遗憾地发现 SuperFences 并没有暴露什么便捷的接口用于对已解析后的内容做进一步加工,如果通过继承等方式进行拓展的话可能需要覆盖重写大量方法,最后决定用一种 monkey patch 的方式进行拓展,以便使需要改动的代码量最小。

首先来看看 SuperFences 提供的代码块标记语法:

```python linenums="1"
def print_hello_world():
    print("hello world")
```

注意到高亮的第一行代码,python 指定代码块中代码属于何种编程语言,其后紧跟的 key=value 形式的键值对是拓展选项(linenums 是代码行号拓展,指定后解析的代码块中的代码将包含代码行号)。

解析后的 HTML 文档大致如下:

<pre class="highlight"><code>...</code></pre>

可惜 SuperFences 原生只提供 linenums、hl_lines 两个拓展选项,我们希望能够添加一个拓展选项 filename,用于指定代码块所属文件路径,并将其值添加到解析后的代码块头部。标记语法如下:

```python linenums="1" filename="pyproject/hello_world.py"
def print_hello_world():
    print("hello world")
```

预期的解析效果:

<div class="literal-block">
  <div class="code-block-caption">pyproject/hello_world.py</div>
  <pre class="highlight"><code>...</code></pre>
</div>

不过想基于 SuperFences 实现以上拓展并不容易,难点主要在以下两处:

  1. SuperFences 在解析内容时会校验拓展选项,默认的校验器(validator)只接受 linenums、hl_lines 两个拓展选项,任何多余的选项都无法通过校验,所以我们添加的 filename 拓展选项就无法通过校验,而 SuperFences 并未暴露任何接口可以替换掉默认的校验器。
  2. SuperFences 最终会调用 SuperFencesBlockPreprocessor.highlight 实例方法对代码块做代码高亮处理,然后返回 <pre>...</pre> 预排版内容,这是我们期望的。理想的拓展方法是对 highlight 方法返回的内容再进行包装,即在外层再包上 filename 选项的内容,但是 SuperFences 并未暴露任何接口可以替换 SuperFencesBlockPreprocessor 类,这样就无法通过继承覆盖重写 highlight 方法的方式增强 SuperFencesBlockPreprocessor

好在 Python 语言足够灵活,我们可以通过 monkey patch 的方式以最小代码 kill 掉上述两个难点。

对于难点 1,SuperFences 使用的默认校验器 highlight_validator 是定义在 pymdownx.superfences 模块中的顶层函数,因此这里采用的方式就是在 SuperFences 调用这个函数之前,将 highlight_validator 替换为我们自定义的函数,这在 Python 中实现非常简单:

import pymdownx.superfences

pymdownx.superfences.highlight_validator = _highlight_validator

_highlight_validator 是我们自定义的函数,放宽了原校验函数的校验逻辑,具体的实现代码可参考本博客的源码 blogproject/core/utils.py#L18

对于难点 2,想要对一个类方法返回的结果进一步包装,自然想到类方法装饰器。首先实现一个装饰器,对 highlight 方法返回的结果进行进一步的处理,然后再用 monkey patch 的方式将 SuperFencesBlockPreprocessor.highlight 方法替换为装饰后的方法。具体的实现代码请参考博客的源码 blogproject/core/utils.py#L26

最后编写适当的 CSS 样式,就可以达到类似 django 文档中代码块那样的展示效果了。相关的样式代码可参考博客的源码 frontend/src/style/_literal.scss

参考资料

SuperFences 拓展还提供了很多丰富的功能,具体使用方式可参考其官方文档 SuperFences

Command Tab

Command tab 借助 pymdown-extensions 的 tabbed 拓展实现,标记语法如下:

=== "Linux/macOS"
    ```bash
    $ pipenv install django
    ```

=== "Windows"
    ```shell
    ...\> pipenv install django
    ```

这段内容将被解析为一段具有 tab 选项卡结构的 HTML 代码段,编写相应的 CSS 样式就可以实现类似 django 文档中那样的命令切换选项卡效果,相关的样式代码可参考博客的源码 frontend/src/style/_tabbed.scss

效果演示

来看看最终的实现效果。

Admonition

危险

千万不要进行这样的操作:sudo rm -rf /*。

警告

如果执行了 sudo rm -rf /* 导致系统无法恢复,后果自负。

当心

千万当心在搜索历史命令时不经意间导致 sudo rm -rf /* 命令的执行。

注意

千万注意你的猫在键盘上乱踩时敲出 sudo rm -rf /* 命令。

重要

最好不要在系统中留下 sudo rm -rf /* 的历史记录。

提示

注意 sudo rm -rf /* 后也是可能被恢复的,所以如果你是删库跑路,一定要采取其他措施掩盖你的行径。

Code Block

core/utils.py

def caption_fence_code_format(source, language, css_class, options, md):
    code = fence_code_format(source, language, css_class, options, md)
    caption = options.get("filename", "")
    if caption == "":
        return code
    return '<div><div class="code-caption">{}</div>{}</div>'.format(caption, code)

Command Tab

致谢

感谢 Python-Markdown/markdownpymdown-extensions 开发者们的辛勤付出。

感谢 djangoproject.com 提供的参考实现。

-- EOF --