当前位置: 首页 > news >正文

python粘性拓展_拓展Python Markdown

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

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

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

确定需求

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

Code block

django-docs-code-block.png

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

Admonition

django-docs-admonition.png

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

Command tab

django-docs-command-tab.png

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

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

方案研究

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

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

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

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

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

admonition 功能,直接使用 markdown 库的官方 admonition 拓展就可以;

Code block 在 pymdown-extensions 中有一个更好的拓展实现,叫做 SuperFences,但是还是无法满足生成代码块 header 的需求,因此我们考虑对 SuperFences 再做进一步拓展;

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

具体实现

Admonition

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

!!! note "注意"

请注意这段内容!

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

注意

请注意这段内容!

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

参考资料

markdown.extensions.admonition 拓展的使用可参考官方文档 Admonition。

拓展的引入方式可参考博客项目的源码 blogproject/core/utils.py#L57。

admonition 的 CSS 样式可参考博客中的源码 frontend/src/style/_admonition.scss。

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 文档大致如下:

...

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

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

print("hello world")

```

预期的解析效果:

pyproject/hello_world.py

...

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

SuperFences 在解析内容时会校验拓展选项,默认的校验器(validator)只接受 linenums、hl_lines 两个拓展选项,任何多余的选项都无法通过校验,所以我们添加的 filename 拓展选项就无法通过校验,而 SuperFences 并未暴露任何接口可以替换掉默认的校验器。

SuperFences 最终会调用 SuperFencesBlockPreprocessor.highlight 实例方法对代码块做代码高亮处理,然后返回

...
预排版内容,这是我们期望的。理想的拓展方法是对 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

python filename="core/utils.py" linenums="1"

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 '

{}
{}
'.format(caption, code)

Command Tab

Linux/macOS

bash

$ export ENV_VAR=test

Windows

shell

...\> set ENV_VAR=test

致谢

感谢老婆大人在前端方面给予的指点。

-- EOF --

微信

支付宝

alipay0.jpg

alipay99.jpg

alipay199.jpg

alipay299.jpg

alipay599.jpg

wechatpay0.png

wechatpay99.png

wechatpay199.png

wechatpay299.png

wechatpay599.png

9.9

19.9

29.9

59.9

任意

赞赏

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • ping不通服务器_服务器远程桌面不上不要急,这就告诉你原因
  • 钢笔墨水能否代替打印机墨水_喷墨打印机该用染料墨水还是颜料墨水,区别在哪里?...
  • 零基础学python靠谱吗_零基础学python的我(开篇扯淡)
  • linux安装ssh_如何为Ubuntu服务器建立无密码SSH认证
  • responseentity 返回异常_Spring Boot异常处理
  • sklearn逻辑回归 极大似然 损失_算法 | 一个硬币与逻辑回归的故事
  • restful api和普通api有什么特点_Django REST Framework教程(1): 为什么要学习DRF, 什么是序列化和RESTful的API...
  • sql语言编程学习_在读大学生是否该重点学习一门编程语言,以及该如何选择编程语言...
  • .net 反编译_.net反编译的相关问题
  • class括号里的object_Class文件结构全面解析(下)
  • 标签打印模板_海鸟贴纸打印机:工作和生活中的得力助手!专治强迫症
  • gitlab run成功 但无法访问_用 GitLab 做 CI/CD 是什么感觉,太强了!!
  • 当前操作系统缺少黑体等字体_OpenBSD6.6正式版发布:多平台,类Unix的最安全操作系统...
  • wps 模拟分析 规划求解_基于长时间尺度的园区综合能源系统随机规划
  • 纬地8.0支持的cad版本_智慧社区弱电工程设计图纸,可编辑(CAD版本)弱电新人学习!...
  • __proto__ 和 prototype的关系
  • HTML中设置input等文本框为不可操作
  • leetcode378. Kth Smallest Element in a Sorted Matrix
  • Making An Indicator With Pure CSS
  • MySQL-事务管理(基础)
  • underscore源码剖析之整体架构
  • Xmanager 远程桌面 CentOS 7
  • 后端_MYSQL
  • 后端_ThinkPHP5
  • 如何合理的规划jvm性能调优
  • 深入浅出Node.js
  • 微信开源mars源码分析1—上层samples分析
  • 源码之下无秘密 ── 做最好的 Netty 源码分析教程
  • ​​​【收录 Hello 算法】10.4 哈希优化策略
  • ​Kaggle X光肺炎检测比赛第二名方案解析 | CVPR 2020 Workshop
  • #### go map 底层结构 ####
  • #13 yum、编译安装与sed命令的使用
  • #每日一题合集#牛客JZ23-JZ33
  • $ git push -u origin master 推送到远程库出错
  • (1)(1.8) MSP(MultiWii 串行协议)(4.1 版)
  • (1)无线电失控保护(二)
  • (八)光盘的挂载与解挂、挂载CentOS镜像、rpm安装软件详细学习笔记
  • (办公)springboot配置aop处理请求.
  • (附源码)ssm捐赠救助系统 毕业设计 060945
  • (解决办法)ASP.NET导出Excel,打开时提示“您尝试打开文件'XXX.xls'的格式与文件扩展名指定文件不一致
  • ***汇编语言 实验16 编写包含多个功能子程序的中断例程
  • *Algs4-1.5.25随机网格的倍率测试-(未读懂题)
  • .Net Core webapi RestFul 统一接口数据返回格式
  • .NET IoC 容器(三)Autofac
  • .net Stream篇(六)
  • .NET 中什么样的类是可使用 await 异步等待的?
  • .net6+aspose.words导出word并转pdf
  • .net生成的类,跨工程调用显示注释
  • .net用HTML开发怎么调试,如何使用ASP.NET MVC在调试中查看控制器生成的html?
  • /dev/sda2 is mounted; will not make a filesystem here!
  • /proc/vmstat 详解
  • ?php echo $logosrc[0];?,如何在一行中显示logo和标题?
  • @LoadBalanced 和 @RefreshScope 同时使用,负载均衡失效分析
  • @PreAuthorize注解
  • [.NET]桃源网络硬盘 v7.4