burst_sphinx_indexes.py 技術ドキュメント

プログラムの動作

burst_sphinx_indexes.py は、Sphinxプロジェクトで使用されるreStructuredTextファイル内の .. toctree:: ディレクティブにおけるglobパターンを自動的に展開し、対応するファイル群をtoctreeのエントリとして挿入するPythonスクリプトです。

主な機能は以下の通りです。

  • globパターンの展開: 親のreStructuredTextファイル内の .. toctree:: ディレクティブに *** を含むglobパターン(例: docs/*, chapters/**/index)が指定された場合、そのパターンに合致するファイルを検索します。

  • 複数ファイル形式対応: .rst (reStructuredText) および .md (Markdown) 拡張子を持つファイルを対象とします。パターンに拡張子が明示されていない場合、自動的に両方の拡張子を試行します。

  • 重複エントリの除外: 同じファイルが複数回マッチしたり、手動で既に記述されている場合でも、toctreeブロック内で重複するエントリは自動的に除外されます。

  • エントリのソート (オプション): 展開されたファイルエントリをファイル名に基づいてアルファベット順にソートする機能を提供します。

  • セクションタイトルをラベルとして使用 (オプション): 展開される各子ファイルの先頭のセクションタイトル(reStructuredTextの下線見出しまたはMarkdownのH1見出し)を抽出し、toctreeエントリのラベルとして使用できます。

  • ファイルの上書きとバックアップ: 処理結果は元のreStructuredTextファイルを上書きします。変更前のファイルは、オプションでバックアップとして保存できます。

  • 詳細ログ出力 (オプション): 処理の進行状況や検出された問題に関する詳細なメッセージを標準エラー出力にログとして出力できます。

このスクリプトは、Sphinxプロジェクトでドキュメント構造が頻繁に更新される場合や、大量のファイルを管理する際に、手動でのtoctreeエントリの更新作業を自動化し、メンテナンスの手間を削減することを目的としています。

原理

burst_sphinx_indexes.py は、reStructuredText (RST) ファイルの解析、ファイルシステムのglobパターンマッチング、およびセクションタイトルの抽出という主要な原理に基づいて動作します。

  1. RSTファイル解析とtoctreeブロックの特定:

    • スクリプトはまず、指定された親RSTファイルを読み込み、各行を順に処理します。

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

    • toctree ディレクティブの開始行のインデント幅を indent_width 関数で計算し、このインデントを基準にtoctreeブロックの範囲を特定します。indent_width 関数はタブ文字を4スペースとして展開してインデントを正確に計測します。

    • toctree ブロック内では、オプション行(:maxdepth: 2 など)や空行、そしてglobパターンやファイルパスが記述されたエントリ行を区別して処理します。

  2. globパターンの展開:

    • toctree ブロック内でglobパターン(例: docs/*)が検出された場合、glob_with_extensions 関数が呼び出されます。

    • この関数は pathlib.Path.glob() メソッドを利用して、親RSTファイルのあるディレクトリを基準にファイルシステムを検索します。

    • もしパターンにファイル拡張子が含まれていない場合(例: docs/*)、スクリプトは自動的に .rst.md の両方の拡張子を付加して検索します(例: docs/*.rstdocs/*.md)。これにより、異なる形式のドキュメントが混在するディレクトリでも適切にマッチングが行われます。

    • 検索結果は pathlib.Path オブジェクトのリストとして返されます。

  3. セクションタイトルの抽出 (ラベルオプション有効時):

    • --label 1 オプションが指定された場合、extract_first_section_title 関数が展開された各子ファイルに対して呼び出されます。

    • この関数はファイルの内容を読み込み、最初のセクションタイトルを以下のいずれかの形式で抽出します。

      • Markdown H1: 正規表現 MD_H1_RE = re.compile(r'^\s*#\s+(.+?)\s*$') を使用して、行頭の # で始まるH1見出しを検索します。

      • reStructuredText 下線見出し: 正規表現 RST_UNDERLINE_RE = re.compile(r'^[=\-~^"'#*+]+$')` を使用して、タイトル行の直後に同じ文字で構成される下線があるパターンを検索します。

    • 抽出されたタイトルは、toctreeエントリのラベルとして使用されます (Title <path/to/file>)。

  4. エントリの整形、重複排除、ソート:

    • glob_with_extensions から返されたパスリストは、まず .rst または .md 拡張子を持つもののみにフィルタリングされます。

    • 同じtoctreeブロック内で既に展開されたファイルパス(Path.resolve() で正規化されたパス)が seen_paths_in_block セットに記録され、重複するエントリの追加が防止されます。

    • --sort 1 オプションが指定されている場合、sorted() 関数と lambda 式を使ってファイル名 (p.name) をキーとしてエントリがソートされます。

  5. 出力の生成とファイル更新:

    • 元のRSTファイルの各行と、展開・整形されたtoctreeエントリを結合し、新しいRSTファイルの内容を文字列として構築します。

    • 構築された内容が元のファイル内容と異なる場合のみ、Path.write_text() を使用して元のファイルを更新します。

    • --backup 1 オプションが指定されている場合、shutil.copy2() を使用して更新前に元のファイルのバックアップを作成します。

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

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

burst_sphinx_indexes.py は、Pythonの標準ライブラリのみを使用しており、非標準ライブラリは一切必要ありません。 したがって、追加のインストール作業は不要です。

標準ライブラリとは、Pythonをインストールした際にデフォルトで含まれているモジュールのことで、以下のものが使用されています。

  • argparse: コマンドライン引数を解析するために使用されます。

  • re: 正規表現を用いたテキストのパターンマッチングに使用されます。

  • pathlib: オブジェクト指向のアプローチでファイルシステムパスを操作するために使用されます。

  • shutil: ファイル操作(バックアップファイルの作成など)に使用されます。

  • sys: システム固有のパラメータや関数(標準エラー出力への書き込みなど)にアクセスするために使用されます。

  • typing: 型ヒント(型アノテーション)のために使用されます。

必要な入力ファイル

burst_sphinx_indexes.py を実行するためには、以下の入力ファイルが必要です。

  1. 親reStructuredTextファイル:

    • コマンドライン引数 parent で指定される主要な入力ファイルです。通常、.rst 拡張子を持ちます(例: index.rst, contents.rst)。

    • このファイルは、少なくとも1つ以上の .. toctree:: ディレクティブを含んでいることが期待されます。

    • toctree ディレクティブの本文には、globパターン(例: docs/*, chapters/**/intro)または個別のファイルパス(例: introduction, reference/api)が記述されている必要があります。

    例: index.rst の内容

    .. toctree::
       :maxdepth: 2
       :caption: ドキュメントの目次:
    
       getting_started
       user_guide/*
       api_docs/module*
       notes/release_notes
    
  2. 子ドキュメントファイル群:

    • 親reStructuredTextファイルの toctree ディレクティブ内に記述されたglobパターンやファイルパスによって参照される、実際のドキュメントファイル群です。

    • これらのファイルは、.rst (reStructuredText) または .md (Markdown) のいずれかの拡張子を持つ必要があります。他の拡張子のファイルは無視されます。

    • --label 1 オプションが有効な場合、これらの子ファイルの先頭には有効なセクションタイトル(RSTの下線見出しまたはMarkdownのH1見出し)が含まれていることが期待されます。

    例:

    • user_guide/setup.rst

    • user_guide/basic_usage.md

    • api_docs/module_a.rst

    • api_docs/module_b.md

    • notes/release_notes.rst

スクリプトは、指定された親RSTファイルの存在するディレクトリを基準として、globパターンの展開を行います。そのため、子ファイル群は親RSTファイルからの相対パスでアクセス可能な場所に配置されている必要があります。

生成される出力ファイル

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

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

    • コマンドライン引数 parent で指定された入力ファイルが、展開されたtoctreeエントリを含む新しい内容で上書きされます。

    • このファイルの内容には、.. toctree:: ディレクティブ内のglobパターンが、実際に検出されたファイルパスのリストに置き換えられた結果が反映されます。

    • --sort 1 オプションが有効であれば、展開されたエントリはファイル名順にソートされます。

    • --label 1 オプションが有効であれば、各エントリは タイトル <ファイルパス> の形式で記述されます。

    例: index.rst (更新後)

    .. toctree::
       :maxdepth: 2
       :caption: ドキュメントの目次:
    
       getting_started
       ユーザーガイド: 基本的な使い方 <user_guide/basic_usage>
       ユーザーガイド: セットアップ <user_guide/setup>
       APIリファレンス: Module A <api_docs/module_a>
       APIリファレンス: Module B <api_docs/module_b>
       notes/release_notes
    
  2. バックアップファイル (オプション):

    • --backup 1 オプションが指定された場合、元の親reStructuredTextファイルが更新される前に、その内容がバックアップファイルとして保存されます。

    • バックアップファイルの名前は、元のファイル名に .backup という拡張子が追加されたものになります(例: index.rst.backup)。

    例: index.rst.backup これは、index.rst が更新される前の元の内容を保持します。

  3. 標準出力:

    • スクリプトの実行が完了すると、更新された親reStructuredTextファイルの内容全体が標準出力に表示されます。

    • これは、ファイルへの書き込みとは別に、変更内容をすぐに確認するためのものです。

  4. 標準エラー出力 (詳細ログオプション有効時):

    • --verbose 1 オプションが指定された場合、処理の過程で検出されたファイルやパターンマッチングの結果、警告メッセージなどが標準エラー出力にログとして出力されます。

    例: 標準エラー出力のログ

    [INFO] backup created: index.rst.backup
    [INFO] glob pattern='user_guide/*.rst' matched 1 path(s)
    [INFO] glob pattern='user_guide/*.md' matched 1 path(s)
    [INFO] added labeled entry: ユーザーガイド: 基本的な使い方 <user_guide/basic_usage>
    [INFO] added labeled entry: ユーザーガイド: セットアップ <user_guide/setup>
    [INFO] glob pattern='api_docs/module*.rst' matched 1 path(s)
    [INFO] glob pattern='api_docs/module*.md' matched 1 path(s)
    [INFO] added labeled entry: APIリファレンス: Module A <api_docs/module_a>
    [INFO] added labeled entry: APIリファレンス: Module B <api_docs/module_b>
    [INFO] updated file: index.rst
    

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

burst_sphinx_indexes.py は以下の形式でコマンドラインから実行します。

python burst_sphinx_indexes.py parent [--sort {0,1}] [--label {0,1}] [--backup {0,1}] [--verbose {0,1}]

引数の説明

  • parent

    • 必須。親となるreStructuredTextファイルのパスを指定します。このファイル内の .. toctree:: ディレクティブが処理の対象となります。

    • 例: index.rst, docs/contents.rst

  • --sort {0,1}

    • 展開されたtoctreeエントリをソートするかどうかを制御します。

    • 1 (デフォルト): 展開されたエントリをファイル名(相対パス)でアルファベット順にソートします。

    • 0: globパターンによって検出された元の順序を維持します。

    • 例: --sort 1

  • --label {0,1}

    • 展開されたエントリに子ファイルの最初のセクションタイトルをラベルとして使用するかどうかを制御します。

    • 1: 子ファイルの最初のセクションタイトルを抽出し、タイトル <ファイルパス> の形式でtoctreeエントリを追加します。

    • 0 (デフォルト): ファイルパスのみをエントリとして追加します(例: ファイルパス)。

    • 例: --label 1

  • --backup {0,1}

    • 元の親reStructuredTextファイルのバックアップを作成するかどうかを制御します。

    • 1 (デフォルト): 更新前に元のファイルのバックアップ(.backup 拡張子付き)を作成します。

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

    • 例: --backup 0

  • --verbose {0,1}

    • 詳細なログメッセージを標準エラー出力に表示するかどうかを制御します。

    • 1: 処理の進行状況、ファイルマッチング、警告などの詳細情報を出力します。

    • 0 (デフォルト): 警告メッセージのみを出力し、通常は静かです。

    • 例: --verbose 1

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

ここでは、具体的なファイル構成と、それに対する burst_sphinx_indexes.py の実行例を示します。

前提となるファイル構成

以下のファイルとディレクトリが存在すると仮定します。

my_project/
├── index.rst
└── chapters/
    ├── chapter_a.rst
    ├── chapter_b.md
    ├── chapter_c.rst
    └── appendix.txt

my_project/index.rst の初期内容

.. toctree::
   :maxdepth: 2
   :caption: ドキュメントの構成:

   introduction
   chapters/*
   conclusion

chapters/chapter_a.rst の内容

===================
最初の章: はじめに
===================

ここに chapter_a の内容が続きます。

chapters/chapter_b.md の内容

# 第2章: 基本概念

ここに chapter_b の内容が続きます。

chapters/chapter_c.rst の内容

第三章
~~~~~~

ここに chapter_c の内容が続きます。

実行コマンド例

index.rst 内の chapters/* globパターンを展開し、ソートしてセクションタイトルをラベルとして使用し、バックアップを作成して詳細ログを表示します。

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

実行結果の説明

  1. 標準エラー出力 (verboseログ):

    [INFO] backup created: my_project/index.rst.backup
    [INFO] entered toctree at line 4, indent=0
    [INFO] glob pattern='chapters/*.rst' matched 2 path(s)
    [INFO] glob pattern='chapters/*.md' matched 1 path(s)
    [INFO] added labeled entry: 最初の章: はじめに <chapters/chapter_a>
    [INFO] added labeled entry: 第2章: 基本概念 <chapters/chapter_b>
    [INFO] added labeled entry: 第三章 <chapters/chapter_c>
    [INFO] updated file: my_project/index.rst
    
    • index.rst.backup が作成されたことが示されます。

    • chapters/*.rstchapters/*.md の両方のパターンが試行され、それぞれ2つと1つのファイルがマッチしたことがログされます。

    • appendix.txt.rst または .md 拡張子ではないため、無視されます。

    • --label 1 が有効なため、各ファイルから抽出されたタイトルとともにエントリが追加されたことが示されます。

  2. my_project/index.rst の変更内容: ファイルは以下の内容に更新されます。

    .. toctree::
       :maxdepth: 2
       :caption: ドキュメントの構成:
    
       introduction
       最初の章: はじめに <chapters/chapter_a>
       第2章: 基本概念 <chapters/chapter_b>
       第三章 <chapters/chapter_c>
       conclusion
    
    • chapters/* の行が、実際に検出された chapter_a.rst, chapter_b.md, chapter_c.rst のエントリに展開されています。

    • --sort 1 オプションにより、展開されたエントリはファイル名のアルファベット順(chapter_a, chapter_b, chapter_c の順)にソートされています。

    • --label 1 オプションにより、各エントリは子ファイルの最初のセクションタイトルをラベルとして表示しています。

  3. my_project/index.rst.backup の生成: 元の index.rst の内容がそのまま保持された my_project/index.rst.backup ファイルが作成されます。

  4. 標準出力: コマンド実行後、標準出力には更新された my_project/index.rst の上記内容が表示されます。