burst_sphinx_indexes.py 技術ドキュメント

プログラムの動作

burst_sphinx_indexes.py は、Sphinxプロジェクトで使用されるreStructuredText (RST) ファイル内の .. toctree:: ディレクティブにおけるglobパターンを自動的に展開するPythonスクリプトです。このプログラムの主な目的は、ドキュメントの構造管理を簡素化し、新しいファイルが追加されたり既存のファイルが削除されたりした際に、手動で toctree を更新する手間を削減することにあります。

主な機能:

  • Globパターン展開: documents/* のようなglobパターンを、実際に存在するファイル (documents/intro.rst, documents/chapter1.md など) のリストに展開します。

  • 複数ファイル形式対応: reStructuredText (.rst) と Markdown (.md) の両方のファイルを認識し、toctreeエントリとして追加します。

  • 重複エントリの自動除外: 展開されたリスト内で同じファイルが複数回出現するのを防ぎます。

  • ソート機能: 展開されたファイルリストをファイル名でソートするオプションを提供し、toctreeの表示順序を管理しやすくします。

  • ラベル付け機能: 展開される各エントリに対し、対応する子ファイルから最初のセクションタイトル(H1見出しまたはRST下線見出し)を抽出し、それをラベルとして使用するオプションを提供します。これにより、toctreeの可読性が向上します(例: Chapter One <path/to/chapter_one.rst>)。

  • バックアップ機能: 元のファイルを上書きする前に、自動的にバックアップファイルを作成します。

解決する課題:

Sphinxの toctree に多数のファイルを手動で追加・削除する作業は、プロジェクトの規模が大きくなるにつれて時間とエラーが発生しやすくなります。このスクリプトは、ファイルシステムの構造に基づいて toctree を自動的に生成・更新することで、この課題を解決し、ドキュメント作成のワークフローを効率化します。

原理

burst_sphinx_indexes.py は、以下のアルゴリズムと仕組みに基づいて動作します。数式や物理式は使用されていません。

  1. 親reStructuredTextファイルの読み込みと解析:

    • 指定された親RSTファイル (parent_rst) を行ごとに読み込みます。

    • ファイルの内容を走査し、.. toctree:: ディレクティブの開始を正規表現 re.match(r"\s*\.\.\s+toctree::", line) で検出します。

  2. toctree ブロックの識別:

    • toctree ディレクティブが検出されると、プログラムは「toctree ブロック内」の状態に遷移します。

    • 各行のインデント幅 (indent_width 関数: タブは4スペースとして展開して計算) を監視し、toctree ディレクティブ自体のインデントレベル (toctree_indent) を基準として、そのインデントレベル以下に戻った行が検出されると、toctree ブロックが終了したと判断します。

    • toctree ブロック内の空行や、オプション行(:option: で始まる行)はそのまま出力に追加します。

  3. Globパターンの展開:

    • toctree ブロック内で、オプション行でも空行でもない行をglobパターンと解釈します。

    • glob_with_extensions 関数を使用して、親RSTファイルのあるディレクトリ (parent_dir) を基準にglobパターンに合致するファイルを検索します。

      • パターンに拡張子(例: *.rst)が明示されている場合は、その拡張子のみで検索します。

      • パターンに拡張子がない場合(例: documents/*)は、.rst.md の両方の拡張子を補完して検索を実行します。

      • pathlib.Path.glob() メソッドを利用してファイルシステムを走査します。

  4. エントリの処理と追加:

    • globパターンによって見つかったファイルパスのリストに対して、以下の処理を適用します。

      • フィルタリング: .rst または .md の拡張子を持つファイルのみを対象とします。

      • 重複除去: 絶対パスで比較し、同じファイルが複数回リストに追加されないようにします。これは、seen_paths_in_block セットを使用して追跡されます。

      • ソート: --sort 1 オプションが指定されている場合、ファイル名をキーとして昇順でソートします。

      • ラベル付け: --label 1 オプションが指定されている場合、extract_first_section_title 関数を呼び出して、各子ファイルから最初のセクションタイトルを抽出します。

        • タイトル抽出の仕組み:

          • Markdown形式のH1見出し (# Title) を正規表現 MD_H1_RE で検索します。

          • reStructuredText形式の下線見出し (Title\n=====) を正規表現 RST_UNDERLINE_RE で検索します。

          • どちらも見つからない場合は、ファイルパスを直接使用します。

      • 出力フォーマット: 抽出されたファイルパスは、親RSTファイルに対する相対パスに変換されます (p.relative_to(parent_dir).as_posix())。ラベルが使用される場合は ラベル <相対パス> の形式で出力されます。

    • 元のglobパターン行は出力には追加せず、展開されたファイルリストのエントリに置き換えます。

  5. ファイル書き込み:

    • expand_glob_in_toctree 関数は、展開と置換が行われた最終的なRSTファイルの内容を文字列として返します。

    • main 関数で、この結果が元のファイル内容と異なる場合、以下の処理が行われます。

      • --backup 1 オプションが指定されている場合、元のRSTファイルを .backup 拡張子の付いたファイルとしてコピーします。

      • 新しい内容で元のRSTファイルを上書き保存します。

    • 最終的な内容は標準出力にも出力されます。

このプロセスにより、burst_sphinx_indexes.py は、Sphinxの toctree を動的に管理し、ドキュメントの整合性とメンテナンス性を向上させます。

必要な非標準ライブラリとインストール方法

burst_sphinx_indexes.py はPythonの標準ライブラリのみを使用しており、argparse, re, pathlib, shutil, sys, typing が含まれます。これらのライブラリはPythonのインストールに付属しているため、追加でインストールする必要がある非標準ライブラリはありません。

必要な入力ファイル

プログラムの実行には、以下のファイルが必要です。

  1. 親reStructuredTextファイル:

    • 形式: .rst 拡張子を持つテキストファイル。

    • 内容: Sphinxの .. toctree:: ディレクティブを含んでいる必要があります。このディレクティブの配下に、ファイル名を直接指定するエントリや、globパターン(例: chapter/*, documents/*.md)が記述されていることが期待されます。

    • :

      .. toctree::
         :maxdepth: 2
         :caption: Contents
      
         introduction
         getting_started/*
         modules/api_reference.rst
         notes/*.md
      
    • 役割: globパターンを展開し、自動的にtoctreeのエントリを生成・更新する対象となります。

  2. 子ファイル:

    • 形式: .rst または .md 拡張子を持つテキストファイル。

    • 内容: 親reStructuredTextファイル内のglobパターンに合致するファイル群です。--label 1 オプションを使用する場合、これらのファイルには最初のセクションタイトル(reStructuredTextのアンダーライン見出し、またはMarkdownのH1見出し)が含まれていることが望ましいです。

    • :

      • getting_started/setup.rst:

        Setup Guide
        ===========
        
      • notes/changelog.md:

        # Changelog
        
    • 役割: toctree エントリとして追加される候補となるファイルです。

これらのファイルは、プログラム実行時に指定する親reStructuredTextファイルのパスを基準とした相対パス、または絶対パスで存在している必要があります。

生成される出力ファイル

burst_sphinx_indexes.py を実行すると、以下のファイルが生成または更新されます。

  1. 更新された親reStructuredTextファイル:

    • ファイル名: プログラムに引数として与えられた元の親reStructuredTextファイル (parent.rst など) そのものが更新されます。

    • 内容: .. toctree:: ディレクティブ内のglobパターンが、検出された子ファイルのリストに置き換えられます。他の行は変更されません。

    • 例 (一部): 元の parent.rst:

      .. toctree::
         :maxdepth: 2
      
         chapters/*
      

      更新後の parent.rst (例):

      .. toctree::
         :maxdepth: 2
      
         Chapter A <chapters/chapter_a.rst>
         Chapter B <chapters/chapter_b.md>
      
    • 備考: ファイルの内容に変更がない場合、ファイルは上書きされません。

  2. バックアップファイル ( --backup 1 オプションが有効な場合):

    • ファイル名: 更新される元の親reStructuredTextファイルの名前の末尾に .backup 拡張子が追加されます (例: parent.rst.backup)。

    • 内容: プログラムによる更新前の元の親reStructuredTextファイルの内容が保存されます。

    • 備考: このファイルは、更新プロセス中に問題が発生した場合に元の状態に戻すための安全策として機能します。

  3. 標準出力:

    • 内容: 最終的に生成された、更新後の親reStructuredTextファイルの内容全体が標準出力に表示されます。

    • 備考: これにより、ファイルが実際にどのように変更されたかをすぐに確認できます。特に、変更をファイルに書き戻さずに結果を確認したい場合に便利です(現在の実装では常に標準出力されますが、ファイル書き戻しと同期しています)。

コマンドラインでの使用例 (Usage)

burst_sphinx_indexes.py は、以下の基本形式でコマンドラインから実行されます。

python burst_sphinx_indexes.py <parent_rst_file> [options]

引数:

  • <parent_rst_file> (必須):

    • 対象となる親reStructuredTextファイルのパスを指定します。このファイル内の .. toctree:: ディレクティブが解析・更新されます。

オプション:

  • --sort {0,1} (デフォルト: 1):

    • 1: toctree エントリを展開する際に、ファイル名を基準にソートします。

    • 0: globパターンでファイルが見つかった順序を保持します。

  • --label {0,1} (デフォルト: 0):

    • 1: 展開された toctree エントリに、子ファイルの最初のセクションタイトルをラベルとして追加します(例: Title <path/to/file.rst>)。

    • 0: 単純なファイルパス (path/to/file.rst) を使用します。

  • --backup {0,1} (デフォルト: 1):

    • 1: 元のファイルを上書きする前に、.backup 拡張子を付けてバックアップファイルを作成します。

    • 0: バックアップファイルを作成しません。

  • --verbose {0,1} (デフォルト: 0):

    • 1: 詳細なログメッセージを標準エラー出力 (stderr) に出力します。処理の進行状況やデバッグに役立ちます。

    • 0: 警告メッセージのみを出力し、通常は静かに実行されます。

コマンドラインでの具体的な使用例

ここでは、具体的なファイル構造とコマンド例を示し、その結果を説明します。

ファイル構造の準備:

まず、以下のディレクトリとファイルを作成します。

docs/
├── index.rst
└── chapters/
    ├── chapter_b.md
    ├── chapter_a.rst
    └── chapter_c.rst

docs/index.rst の内容 (更新前):

.. My Awesome Project

.. toctree::
   :maxdepth: 2
   :caption: Documentation

   chapters/*
   external_link.rst

docs/chapters/chapter_a.rst の内容:

Chapter Alpha
=============

This is the content of chapter A.

docs/chapters/chapter_b.md の内容:

# Chapter Beta

This is the content of chapter B.

docs/chapters/chapter_c.rst の内容:

Chapter Gamma
~~~~~~~~~~~~~

This is the content of chapter C.

docs/external_link.rst の内容: (これはglobパターンにマッチしませんが、既存のエントリとして処理されます)

External Link
=============

This is an external link.

実行例 1: 基本的な展開とソート、ラベル付け

ソートを有効にし、子ファイルからセクションタイトルを抽出してラベルとして使用する例です。詳細ログも表示します。

python burst_sphinx_indexes.py docs/index.rst --sort 1 --label 1 --verbose 1

実行結果の説明:

  1. docs/index.rst.backup という名前で、元の docs/index.rst の内容がバックアップされます。

  2. docs/index.rst が更新され、chapters/* の部分が展開されたエントリに置き換わります。

  3. 標準エラー出力に詳細なログが表示されます。

  4. 標準出力に、更新後の docs/index.rst の内容全体が表示されます。

更新後の docs/index.rst の内容:

.. My Awesome Project

.. toctree::
   :maxdepth: 2
   :caption: Documentation

   Chapter Alpha <chapters/chapter_a.rst>
   Chapter Beta <chapters/chapter_b.md>
   Chapter Gamma <chapters/chapter_c.rst>
   external_link.rst

標準エラー出力 (例):

[INFO] backup created: docs/index.rst.backup
[INFO] entered toctree at line 3, indent=0
[INFO] glob pattern='chapters/*.rst' matched 2 path(s)
[INFO] glob pattern='chapters/*.md' matched 1 path(s)
[INFO] added labeled entry: Chapter Alpha <chapters/chapter_a.rst>
[INFO] added labeled entry: Chapter Beta <chapters/chapter_b.md>
[INFO] added labeled entry: Chapter Gamma <chapters/chapter_c.rst>
[INFO] updated file: docs/index.rst

実行例 2: ソートなし、ラベルなし

ソートもラベル付けも行わず、globパターンで見つかった順序でファイルパスのみを展開する例です。バックアップは作成し、詳細ログは表示しません。

python burst_sphinx_indexes.py docs/index.rst --sort 0 --label 0 --backup 1 --verbose 0

実行結果の説明:

  1. docs/index.rst.backup が再度作成され、実行例1で更新された内容がバックアップされます。

  2. docs/index.rst が更新されます。chapters/* の部分がファイルシステムの検出順(OSやファイルシステムの実装に依存)で、ラベルなしのパスに置き換わります。

  3. 標準エラー出力には警告メッセージのみが表示され、詳細ログは出力されません。

  4. 標準出力に、更新後の docs/index.rst の内容全体が表示されます。

更新後の docs/index.rst の内容 (検出順が chapter_b.md, chapter_a.rst, chapter_c.rst の場合):

.. My Awesome Project

.. toctree::
   :maxdepth: 2
   :caption: Documentation

   chapters/chapter_b.md
   chapters/chapter_a.rst
   chapters/chapter_c.rst
   external_link.rst