<address id="tl19n"></address>
      <address id="tl19n"><nobr id="tl19n"></nobr></address>

        <address id="tl19n"></address>

          <address id="tl19n"></address>
          <form id="tl19n"></form>

              首頁 > 上網技巧 > 拓展Python Markdown

              拓展Python Markdown

              時間:2022-01-14 12:43 作者:QQ地帶 我要評論

              最近閱讀 django 的官方文檔,發現一些很細節的文檔內容展現形式,能夠極大地提高文檔的閱讀體驗。閱讀其他技術文檔時也會經常發現類似的內容展現形式。我的博客主要也是發布一些技術類文章,于是決定實現類似的功能以增強讀者閱讀博客文章的體驗。
               
              確定需求后,簡單地研究了一下實現方式,然后花了一個晚上的時間把功能上線了,在這里分享記錄一下整個功能的實現過程。
               
              確定需求
              閱讀技術類文檔經常會看到這么幾種內容:Code block、Admonition、Command 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 選項卡,完美滿足需求。
               
              至此,實現方案基本就可以確定了:
               
              admonition 功能,直接使用 markdown 庫的官方 admonition 拓展就可以;
              Code block 在 pymdown-extensions 中有一個更好的拓展實現,叫做 SuperFences,但是還是無法滿足生成代碼塊 header 的需求,因此我們考慮對 SuperFences 再做進一步拓展;
               
              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 文檔中那樣的展示效果了。
               
              參考資料
               
              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 內容的功能,這樣就無法展示代碼塊所在的文件路徑等信息了;瞬簧贂r間讀了一下 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 實現以上拓展并不容易,難點主要在以下兩處:
               
              SuperFences 在解析內容時會校驗拓展選項,默認的校驗器(validator)只接受 linenums、hl_lines 兩個拓展選項,任何多余的選項都無法通過校驗,所以我們添加的 filename 拓展選項就無法通過校驗,而 SuperFences 并未暴露任何接口可以替換掉默認的校驗器。
              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。
               
              效果演示
              來看看最終的實現效果。

              標簽: Python
              頂一下
              (0)
              0%
              踩一下
              (0)
              0%

              Google提供的廣告

              国产古代一级a毛片,国产成人午夜在线直播,write.as 当众老师
                <address id="tl19n"></address>
                  <address id="tl19n"><nobr id="tl19n"></nobr></address>

                    <address id="tl19n"></address>

                      <address id="tl19n"></address>
                      <form id="tl19n"></form>