nikkie-ftnextの日記

イベントレポートや読書メモを発信

sphinx-designの:octicon:ロールは、どのように実装されているのか

はじめに

未来ちゃ、お誕生日おめでとうございます!🍵 nikkieです

先日取り上げたsphinx-designの:octicon:ロール、どのように実装されているのか覗きました。

目次

sphinx-designの:octicon:ロール

Sphinxで作るHTMLのドキュメントに、ボタンやカード、グリッド、アイコン(など)を追加できるのがsphinx-design1

最近ではGitHub octiconもサポートしていたことを知りました

この:octicon:ロールについて実装を見ていきます。

OcticonRoleクラス

実体はOcticonRoleクラスです。

ロール設定箇所
https://github.com/executablebooks/sphinx-design/blob/v0.6.0/sphinx_design/icons.py#L30

app.add_role("octicon", OcticonRole())

https://www.sphinx-doc.org/ja/master/extdev/appapi.html#sphinx.application.Sphinx.add_role

SphinxSphinxRoleクラスを継承しており、run()メソッドがポイントです。
https://github.com/executablebooks/sphinx-design/blob/v0.6.0/sphinx_design/icons.py#L124

https://www.sphinx-doc.org/ja/master/development/tutorials/extending_syntax.html#the-role-class

This class extends the SphinxRole class.
The class contains a run method, which is a requirement for every role.

run()メソッドは以下のような記法を受け入れる実装となっており

:octicon:`report`
:octicon:`report;1em;sd-text-info`

docutilsのrawノードを返します。
https://github.com/executablebooks/sphinx-design/blob/v0.6.0/sphinx_design/icons.py#L140

node = nodes.raw("", nodes.Text(svg), format="html")

このsvgget_octicon()関数の返り値です。
https://github.com/executablebooks/sphinx-design/blob/v0.6.0/sphinx_design/icons.py#L132

get_octicon()関数

https://github.com/executablebooks/sphinx-design/blob/v0.6.0/sphinx_design/icons.py#L64

Return the HTML for an GitHub octicon SVG icon.

svgタグを返す関数です。

GitHub octiconのSVGJSONファイルに格納されています。
https://github.com/executablebooks/sphinx-design/blob/v0.6.0/sphinx_design/compiled/octicons.json
octiconの名前でSVGのデータを取得します。

データのイメージ(reportを例に)
https://github.com/executablebooks/sphinx-design/blob/v0.6.0/sphinx_design/compiled/octicons.json#L14811-L14872

  "report": {
    "name": "report",
    "heights": {
      "16": {
        "path": "<path> 詳細は https://primer.style/foundations/icons/report-16 参照</path>",
      },
      "24": {
        "path": "<path> 詳細は https://primer.style/foundations/icons/report-24 参照</path>",
      }
    }
  },

ビルドされたHTML中のsvgタグ

<svg version="1.1" width="1.0em" height="1.0em" class="sd-octicon sd-octicon-report" viewBox="0 0 16 16" aria-hidden="true"><path d="..."></path></svg>

reportには16pxと24pxのデータがありますが、16pxが使われています(viewBox)。
このあたりの分岐:https://github.com/executablebooks/sphinx-design/blob/v0.6.0/sphinx_design/icons.py#L87-L96

widthとheightを引数から指定できることで、1emのようにして16pxのデータでも本文のフォントサイズになじませられるのですね。

get_octicon_data()関数

octiconのJSONからSVGデータ全件を取得する関数です。
https://github.com/executablebooks/sphinx-design/blob/v0.6.0/sphinx_design/icons.py#L50

Load all octicon data.

ファイルの読み込みが絡むと遅くなりそうですが、functools.lru_cacheで対処しています!

@lru_cache(1)
def get_octicon_data() -> dict[str, Any]:

https://docs.python.org/ja/3/library/functools.html#functools.lru_cache

関数をメモ化用の呼び出し可能オブジェクトでラップし、最近の呼び出し最大 maxsize 回まで保存するするデコレータです。
高価な関数や I/O に束縛されている関数を定期的に同じ引数で呼び出すときに、時間を節約できます。

1回呼んだらそれ以降はキャッシュが効くので速くできるという理解です。
実際octiconロールの実装では、get_octicon()関数が呼ばれるたびにget_octicon_data()が呼ばれています。

自分でスクリプトを書くときは最初に読み込んで変数に代入して読み込みは1回だけにするのですが、lru_cacheを使うことで変数を用意しなくても同様のことができるというのは興味深いです。

終わりに

sphinx-designの:octicon:ロールの実装を以下のように理解しました

  • SVGデータは1つのJSONにまとめて保持し、名前で引ける状態(ファイル読み込みにはLRUキャッシュも!)
  • SVGデータをもとに、viewBoxやwidth, heightを指定してHTMLのsvgタグを返す

自作Sphinx拡張のfeature request対応の中で、sphinx-designの実装を眺めました。
非常に参考になったのでパクります!


  1. 今月のPython Monthly Topicsでも取り上げられています