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

推荐订阅源

IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
C
CXSECURITY Database RSS Feed - CXSecurity.com
博客园_首页
H
Hackread – Cybersecurity News, Data Breaches, AI and More
T
ThreatConnect
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - 聂微东
H
Help Net Security
T
Threat Research - Cisco Blogs
Blog — PlanetScale
Blog — PlanetScale
A
Arctic Wolf
G
Google Developers Blog
量子位
U
Unit 42
I
InfoQ
V
V2EX
F
Fox-IT International blog
P
Privacy & Cybersecurity Law Blog
V
Visual Studio Blog
J
Java Code Geeks
大猫的无限游戏
大猫的无限游戏
C
CERT Recently Published Vulnerability Notes
博客园 - 三生石上(FineUI控件)
T
The Exploit Database - CXSecurity.com
T
Tailwind CSS Blog
SecWiki News
SecWiki News
Know Your Adversary
Know Your Adversary
MyScale Blog
MyScale Blog
宝玉的分享
宝玉的分享
The Hacker News
The Hacker News
Project Zero
Project Zero
Application and Cybersecurity Blog
Application and Cybersecurity Blog
月光博客
月光博客
Recent Commits to openclaw:main
Recent Commits to openclaw:main
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
G
GRAHAM CLULEY
C
Cisco Blogs
I
Intezer
Simon Willison's Weblog
Simon Willison's Weblog
O
OpenAI News
Recorded Future
Recorded Future
T
Tenable Blog
W
WeLiveSecurity
腾讯CDC
Stack Overflow Blog
Stack Overflow Blog
T
The Blog of Author Tim Ferriss
www.infosecurity-magazine.com
www.infosecurity-magazine.com
D
Docker
C
Cybersecurity and Infrastructure Security Agency CISA
PCI Perspectives
PCI Perspectives

追梦人物的博客

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 自动生成接口文档 单元测试 限制接口访问频率 API 版本管理 如何在 Windows 下搭建高效的 django 开发环境 加缓存为接口提速 基于 drf-haystack 实现文章搜索接口 评论接口 实现分类、标签、归档日期接口 在接口返回Markdown解析后的内容 文章详情接口 分页 使用视图集简化代码 用类视图实现首页 API 实现博客首页文章列表 API - HelloDjango - django REST framework 教程 初始化 RESTful API 风格的博客系统 django-rest-framework 是什么鬼? - HelloDjango - django REST framework 教程 结束 or 开始? Coverage.py 统计测试覆盖率 单元测试:测试评论应用 单元测试:测试 blog 应用 Django Haystack 全文检索与关键词高亮 Django 博客实现简单的全文搜索 开启 Django 博客的 RSS 功能 统计各个分类和标签下的文章数 稳定易用的 Django 分页库,完善分页功能 通过 Django Pagination 实现简单分页 在脚本中使用 ORM:Faker 批量生成测试数据 Django 官方推荐的姿势:类视图 Django 使用 union 合并不同模型(Model) 的查询集(QuerySet) 开发博客文章阅读量统计功能 使用 Docker 让部署 Django 项目更加轻松 使用 Certbot 向 Let's Encrypt 免费申请 HTTPS 证书 使用 Fabric 自动化部署 博客代码开源啦 (赠书)推荐一本django书籍:Django企业开发实战 Nginx+Gunicorn+Supervisor 部署 Django 博客应用 优化博客功能细节,提升使用体验 交流的桥梁:评论功能 分类、归档和标签页 页面侧边栏:使用自定义模板标签 自动生成文章摘要 Markdown 文章自动生成目录,提升阅读体验 让博客支持 Markdown 语法和代码高亮 开发博客文章详情页 创作后台开启,请开始你的表演 博客从“裸奔”到“有皮肤” Django 的接客之道 Django 迁移、操作数据库 创建 Django 博客的数据库模型 开始进入 django 开发之旅 "空空如也"的博客应用 一种自顶而下的Python装饰器设计方法 Python提取支付宝和微信支付二维码 2018 - 我的学生生涯最后一年回顾 批量清除todo练习参考答案 筛选练习参考答案 删除todo练习参考答案 编辑todo练习参考答案 添加todo练习参考答案 标为完成练习参考答案 入门仪式_Hello_Vue练习参考答案 组件化todo应用 批量清除todo 本地存储 筛选 还剩多少todo未完成 全部标为完成 自定义指令实现自动聚焦 删除todo 编辑todo 添加todo 标为完成 UI 显示todo列表 入门仪式:Hello Vue 在学习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 的内容:

!!! note "注意"
    请注意这段内容!

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

$ export ENV_VAR=test

...\> set ENV_VAR=test

致谢

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

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

-- EOF --