Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Sphinxを通して考える、「拡張」の仕方 / First approach for development sphinx extension

attakei
October 15, 2022

Sphinxを通して考える、「拡張」の仕方 / First approach for development sphinx extension

PyCon JP 2022のDay 1にて登壇した際の発表時点をPDFファイルにしたものです。

なお、発表資料のオリジナルはHTML版となっています。
HTML版は表記ミスの修正などにより更新される場合があります。
これを踏まえて、PDF版は「発表当時の資料を保全」「PDFで資料閲覧したい人への共有」を目的としており、原則として更新する予定はありません。

attakei

October 15, 2022
Tweet

More Decks by attakei

Other Decks in Programming

Transcript

  1. お前誰よ Kazuya Takei attakei (Twitter, GitHub, etc) 株式会社ニジボックス 趣味系Pythonista <=

    こっち ライブラリ・拡張系を作りがち Sphinxでプレゼンテーションしたがる人
  2. sphinxcontrib-oembed oEmbedを使ったコンテンツ埋め 込みをサポート。 URLの指定だけでツイートや動画 の埋め込みが可能になる。 kAZUYA tAKEI @attakei · フォローする

    sphinx-revealjs v2.2.0 is released. Thank you for usings, feedbacks and collaborations! See PyPI: pypi.org/project/sphinx… See GitHub: github.com/attakei/sphinx… pypi.org sphinx-revealjs Sphinx extension with theme to generate Reveal.js presentation 午前1:36 · 2022 年10 月1 日 1 返信 リンクをコピー Twitter でもっと読む
  3. sphinxcontrib-oembed ↓ .. raw:: html <blockquote class="twitter-tweet"> <p lang="en" dir="ltr">

    sphinx-revealjs v2.2.0 is released.<br>Thank you for usings, feedbacks and collaborations!<b </p> &mdash; kAZUYA tAKEI (@attakei)<a href="https://twitter.com/attakei/status/1575887211962290176 </blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> .. oembed:: https://twitter.com/attakei/status/1575887211962290176
  4. setup を制するものはSphinxを制す? setup() の役割 設定項目の宣言 => app.add_config_value ディレクティブ/ビルダー等の登録 => app.add_builder

    他 イベントハンドラの登録 => app.connect ...これらは、いずれも「Sphinxのメイン処理開始までに完遂しないと困るこ と」
  5. setup を制するものはSphinxを制す? setup() の役割 Sphinx 本体 ディレクティブ イベント ハンドラ集 Sphinx

    拡張 setup() ディレク ティブ イベント ハンドラ Sphinxから呼ばれるのはsetupのみ
  6. ディレクティブ等の登録 from sphinx.directives import SphinxDirective class OembedDirective(SphinxDirective): ... def run(self):

    # 略 node = oembed() ... # 略 - node の属性に各種データを引き渡す ... return [node] # docutils のノードを持つリストを返す def setup(app): app.add_directive("oembed", OembedDirective)
  7. ディレクティブ等の登録 from docutils import nodes class oembed(nodes.General): # 大抵の場合は、ディレクティブ側で処理をするので #

    何もしないことが多い pass class visit_oembed_node(self, node): if "content" in node and "html" in node["content"]: self.body.append(node["content"]["html"]) class depart_oembed_node(self, node): pass
  8. Sphinxコアイベントとハンドラ Sphinx拡張からは、 app.connect() で関数を登録するだけで良い。 def some_func(app, config): ... def some_func2(app):

    ... def setup(app): # 本体のイベントに接続 app.connect("config-inited", some_func) # イベントを独自定義した上で、接続 app.add_event("event-for-my-extension") app.connect("event-for-my-extension", some_func2)
  9. イベントハンドラの中身を実装する sphinxcontrib-budoux の場合。 def apply_budoux(app, page_name, template_name, context, doctree): #

    body ... ドキュメントHTML の中身 # update_body 内で加工する context["body"] = update_body(context["body"]) def setup(app): app.ocnnect("html-page-context", apply_budoux)
  10. イベントハンドラの中身を実装する sphinxcontrib-budoux の場合。 def apply_budoux(app, page_name, template_name, context, doctree): #

    body ... ドキュメントHTML の中身 # update_body 内で加工する context["body"] = update_body(context["body"]) def setup(app): app.ocnnect("html-page-context", apply_budoux) ページごとの出力HTMLを加工したい = body をいじりたい
  11. イベントハンドラの中身を実装する sphinxcontrib-budoux の場合。 def apply_budoux(app, page_name, template_name, context, doctree): #

    body ... ドキュメントHTML の中身 # update_body 内で加工する context["body"] = update_body(context["body"]) def setup(app): app.ocnnect("html-page-context", apply_budoux) ページごとの出力HTMLを加工したい = body をいじりたい html-page-context イベントで処理する
  12. イベントハンドラの中身を実装する sphinxcontrib-budoux の場合。 def apply_budoux(app, page_name, template_name, context, doctree): #

    body ... ドキュメントHTML の中身 # update_body 内で加工する context["body"] = update_body(context["body"]) def setup(app): app.ocnnect("html-page-context", apply_budoux) ページごとの出力HTMLを加工したい = body をいじりたい html-page-context イベントで処理する 引数を調べて、実装する
  13. ロギング sphinx.util.logging を利用することで、 Sphinxのビルド時に本体の出力 と統一感があるロギングが出来る。 import sys from sphinx.util import

    logging logger = logging.getLogger(__name__) def setup(app): if sys.version_info.minor < 7: logger.info("NOTICE: 動きはするけど、今後サポートから外れます")
  14. ビルド想定のe2eテスト sphinx.testing を利用できる。 import pytest from bs4 import BeautifulSoup from

    bs4.element import NavigableString, Tag from sphinx.testing.util import SphinxTestApp @pytest.mark.sphinx("html") def test_default(app: SphinxTestApp, status: StringIO, warning: StringIO): app.build() out_html = app.outdir / "index.html" soup = BeautifulSoup(out_html.read_text(), "html.parser") contents = list(soup.h1.children) assert len(contents) > 1 assert isinstance(contents[0], NavigableString) assert isinstance(contents[1], Tag) assert contents[1].name == "wbr"