check_sphinx_api_rst.py

プログラムの動作

check_sphinx_api_rst.py は、SphinxプロジェクトのReStructuredText (RST) ファイル、特に *_api.rst という命名規則を持つファイルを走査し、Sphinxの autodoc 拡張機能で問題を引き起こしやすい記述を検出・修正するためのユーティリティです。

このプログラムの主な機能は以下の通りです。

  1. 問題のあるモジュール名の検出:

    • .. automodule:: ディレクティブに指定されたモジュール名に、Pythonのモジュールインポート規則では許可されないハイフン (-) や、パス区切り文字 (\ または /) が含まれていないかをチェックします。これらの文字は通常、ドット (.) またはアンダースコア (_) に置換される必要があります。

  2. __main__ ガードなしの argv 使用の検出:

    • automodule で参照されているPythonソースコード (.py ファイル) 内で、sys.argv または argv が使用されているにもかかわらず、if __name__ == "__main__": という慣用的なガード句が存在しないケースを検出します。これにより、autodoc がモジュールをインポートした際に、意図せずスクリプトのメイン処理が実行されてしまう可能性を回避します。

  3. 自動修正機能 (--fix):

    • --fix オプションが指定された場合、検出された問題を自動的に修正します。

      • automodule ディレクティブ内のモジュール名に含まれる \/. に、-_ に置換します。

      • 対応するPythonファイル (.py) のファイル名に含まれる -_ にリネームします。

      • *_api.rst ファイル名に含まれる -_ にリネームします。

      • 同じディレクトリ内の関連するドキュメントファイル (usage, examples, index を含む *.rst*.md ファイル) における、修正前のファイル名・モジュール名への参照も自動的に修正します。

      • 関連するドキュメントファイルのリネームも行います。

  4. ドライランモード (--dry-run):

    • --fix オプションと組み合わせて --dry-run を指定すると、実際のファイル変更を行わずに、計画されている修正内容をレポートとして出力します。

このプログラムは、Sphinxドキュメントのビルドエラーや意図しない動作を防ぎ、Pythonモジュールとドキュメントの整合性を保つことを目的としています。

原理

check_sphinx_api_rst.py は、主に正規表現とファイルシステム操作を組み合わせて動作します。

  1. ファイル検索と読み込み:

    • find_files 関数が指定されたルートディレクトリ以下から、ワイルドカードパターン (*_api.rst など) に一致するファイルを再帰的に検索します。

    • read_text 関数が、ファイルの文字コードを自動推定して内容を読み込みます。一般的なエンコーディング (utf-8, cp932, shift_jis, latin-1) を試行し、デコードできない場合は errors="replace" で読み込みます。

  2. RSTファイル解析:

    • extract_automodule_names 関数が、RSTファイルの内容に対して AUTOMODULE_RE 正規表現を適用し、.. automodule:: ディレクティブからモジュール名を抽出します。

      • AUTOMODULE_RE = r"(^\s*\.\.\s+automodule::\s+)([^\s]+)(\s*$)"

      • これは行頭の .. automodule:: の後に続く空白を含まない文字列(モジュール名)をキャプチャします。

  3. モジュール名の正規化とパス解決:

    • safe_module_name 関数が、抽出されたモジュール名に対して以下の変換を行います。

      • パス区切り文字 \ および /. に置換。

      • ハイフン -_ に置換。

      • これにより、Pythonの import 文で有効なモジュール名形式に変換されます。

    • module_name_to_py_path 関数が、正規化されたモジュール名から対応するPythonファイルへのパスを推測します (例: regression.adaptive_gaussian_ridgeroot/regression/adaptive_gaussian_ridge.py)。

    • fallback_py_path_from_rst 関数は、*_api.rst ファイル名から同じディレクトリ内の対応するPythonファイルを推定するフォールバックメカニズムを提供します (例: regression/foo_api.rstregression/foo.py)。

  4. Pythonファイル解析と argv チェック:

    • has_unprotected_argv 関数が、対応するPythonファイルのコンテンツに対して以下の正規表現チェックを行います。

      • MAIN_GUARD_RE = r"if\s+__name__\s*==\s*['\"]__main__['\"]\s*:"

        • if __name__ == "__main__": または if __name__ == '__main__': のパターンを検出します。

      • ARGV_RE = r"\b(?:sys\s*\.\s*)?argv\b"

        • 単語境界にある argv または sys.argv のパターンを検出します。

    • この関数は、__main__ ガードが存在しない (MAIN_GUARD_RE がマッチしない) かつ argv が使用されている (ARGV_RE がマッチする) 場合に True を返します。

  5. 自動修正のロジック (--fix):

    • fix_module_name 関数が、safe_module_name で得られた正しいモジュール名に基づいて、関連するファイルシステムやファイル内容の修正を行います。

    • rename_file 関数は、ファイルが存在し、かつ移動先が存在しない場合にファイルのリネームを実行します。

    • replace_automodule_name 関数は、AUTOMODULE_RE を用いて、.. automodule:: ディレクティブ内のモジュール名のみを安全に置換します。これは、str.replace() では意図しない箇所まで置換してしまう可能性があるためです。

    • replace_in_file 関数は、指定された文字列をファイル内で置換します。これは、RSTファイル内の他の参照(例: _usage.rst から _api.rst への参照)を修正するために使用されます。

    • 同じディレクトリにある _usage, _examples, _index を含むファイル名やその中の参照も、ハイフンからアンダースコアへの変更が行われます。

これらの原理により、プログラムはSphinx autodoc の厳密な要件を満たすように、RSTドキュメントとPythonソースコードの間の整合性を確保します。

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

check_sphinx_api_rst.py は、Pythonの標準ライブラリのみを使用しており、追加でインストールが必要な非標準ライブラリはありません

必要な入力ファイル

このプログラムは、以下の種類のファイルを読み込み、解析します。

  1. Sphinx RSTソースファイル:

    • デフォルトでは、--root オプションで指定されたディレクトリ (デフォルトは ./source) 以下にある *_api.rst というパターンに一致するファイル。

    • --files オプションを使用して、異なるワイルドカードパターン (例: *.rst) を指定することも可能です。

    • これらのファイルには、.. automodule:: ディレクティブが含まれていることが期待されます。

  2. Pythonソースファイル:

    • *_api.rst ファイル内の .. automodule:: ディレクティブで指定されたモジュール名に対応する .py ファイル。

    • これらの .py ファイルは、--root ディレクトリを基準としたパスに存在することが期待されます。

    • 例: .. automodule:: my_package.my_module であれば、--root/my_package/my_module.py が検索されます。

    • 対応する .py ファイルが見つからない場合でも、プログラムは *_api.rst ファイル名から同名の .py ファイルをフォールバックとして探します (例: foo_api.rstfoo.py)。

  3. 関連するRST/Markdownファイル:

    • --fix オプションが指定された場合、*_api.rst ファイルと同じディレクトリ内にある *_usage.rst, *_examples.rst, *_index.rst, *.rst, *.md などのファイルも、修正されたモジュール名やファイル名への参照を更新するために読み込まれることがあります。

生成される出力ファイル

プログラムは、以下の出力ファイルを生成します。

  1. failed_api_rst.txt (デフォルト):

    • --outfile オプションでパスを指定できます。

    • このファイルには、チェックに失敗した *_api.rst ファイルの相対パスと、それぞれ失敗した理由が一覧で記述されます。

    • ファイル例:

      path/to/my-module_api.rst
        - invalid module name contains '-': my-module
        - corresponding py file not found for module: my-module
      another/path/script_api.rst
        - argv is used but __main__ guard was not found: another/path/script.py
      
      [ACTIONS]
      - RENAME path/to/my-module.py -> path/to/my_module.py
      - REPLACE automodule in path/to/my-module_api.rst: my-module -> my_module
      - RENAME path/to/my-module_api.rst -> path/to/my_module_api.rst
      
    • --fix オプションが使用された場合、ファイルへの変更が実際に実行されたか、または --dry-run で計画された修正アクションのログもこのファイルに追記されます。

  2. 修正されたソースファイル (オプション):

    • --fix オプションが指定され、かつ --dry-run が指定されていない場合、プログラムは検出された問題に基づいて以下のファイルを直接変更します。

      • *_api.rst ファイルのコンテンツ (.. automodule:: ディレクティブのモジュール名)。

      • 対応するPythonソースファイル (.py) のファイル名。

      • *_api.rst ファイルのファイル名。

      • *_usage.rst, *_examples.rst, *_index.rst, *.rst, *.md などの関連ファイルのファイル名とそのコンテンツ。

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

check_sphinx_api_rst.py は、以下のコマンドライン引数を受け付けます。

python check_sphinx_api_rst.py [-h] [--root PATH] [--files PATTERN] [--outfile PATH] [--exclude KEYWORD] [--show-ok {0,1}] [--fix] [--dry-run]
  • -h, --help: ヘルプメッセージを表示して終了します。

  • --root PATH: Sphinxソースディレクトリのルートパスを指定します。デフォルトは "./source" です。

  • --files PATTERN: ターゲットとなるRSTファイルのワイルドカードパターンを指定します。デフォルトは "*_api.rst" です。

  • --outfile PATH: 出力ファイルパスを指定します。デフォルトは "failed_api_rst.txt" です。

  • --exclude KEYWORD: チェックから除外するパスキーワードを指定します。複数回指定可能です。

  • --show-ok {0,1}: 問題がなかったファイルも表示するかどうかを指定します。デフォルトは 0 (表示しない) です。

  • --fix: automodule 名のハイフンやパス区切り文字を自動的に修正します。

  • --dry-run: --fix オプションと組み合わせて使用し、ファイルを変更せずに計画された修正内容を表示します。

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

1. 基本的なチェック実行

デフォルトの ./source ディレクトリ内にある *_api.rst ファイルをチェックし、問題があれば標準出力に表示し、failed_api_rst.txt にログを書き込みます。

python check_sphinx_api_rst.py

実行結果の例:

root    : /path/to/project/source
pattern : *_api.rst
found   : 5 files
fix     : False
dry-run : False

[FAILED] my-module_api.rst
         - invalid module name contains '-': my-module
         - corresponding py file not found for module: my-module
[FAILED] another_module_api.rst
         - argv is used but __main__ guard was not found: another_module.py

checked : 5
skipped : 0
failed  : 2 / 5
actions : 0
output  : /path/to/project/failed_api_rst.txt

2. 特定のルートディレクトリを指定して実行

./docs ディレクトリをSphinxのソースルートとして指定してチェックします。

python check_sphinx_api_rst.py --root ./docs

3. 自動修正 (ドライランモード)

検出された問題を自動修正しますが、--dry-run が指定されているため、実際のファイル変更は行われません。どのような修正が提案されるかを確認できます。

python check_sphinx_api_rst.py --fix --dry-run

実行結果の例:

root    : /path/to/project/source
pattern : *_api.rst
found   : 5 files
fix     : True
dry-run : True

[FIX]    my-module_api.rst
         - RENAME /path/to/project/source/my-module.py -> /path/to/project/source/my_module.py
         - REPLACE automodule in /path/to/project/source/my-module_api.rst: my-module -> my_module
         - RENAME /path/to/project/source/my-module_api.rst -> /path/to/project/source/my_module_api.rst
         - REPLACE in /path/to/project/source/index.rst: my-module_api -> my_module_api
[FAILED] my-module_api.rst
         - invalid module name still contains invalid character: my-module (Note: This might still show if not fully fixed in the dry run, or if other issues remain)
[FAILED] another_module_api.rst
         - argv is used but __main__ guard was not found: another_module.py

checked : 5
skipped : 0
failed  : 2 / 5
actions : 4
output  : /path/to/project/failed_api_rst.txt

この出力では、[FIX] のセクションでどのような変更が実行される予定かが詳細に表示されます。ファイル名は my-module_api.rst と表示されていますが、これは dry-run 時の表示のため、実際にはリネームされると my_module_api.rst になります。

4. 実際にファイルを修正

--fix オプションのみを指定すると、検出された問題がファイルシステム上で実際に修正されます。 この操作は元に戻せないので、実行前に必ずバックアップを取るか、--dry-run で内容を十分に確認してください。

python check_sphinx_api_rst.py --fix

実行結果の例:

root    : /path/to/project/source
pattern : *_api.rst
found   : 5 files
fix     : True
dry-run : False

[FIX]    my-module_api.rst
         - RENAME /path/to/project/source/my-module.py -> /path/to/project/source/my_module.py
         - REPLACE automodule in /path/to/project/source/my-module_api.rst: my-module -> my_module
         - RENAME /path/to/project/source/my-module_api.rst -> /path/to/project/source/my_module_api.rst
         - REPLACE in /path/to/project/source/index.rst: my-module_api -> my_module_api
[FAILED] another_module_api.rst
         - argv is used but __main__ guard was not found: another_module.py

checked : 5
skipped : 0
failed  : 1 / 5
actions : 4
output  : /path/to/project/failed_api_rst.txt

この例では、my-module_api.rst の関連する問題は修正され、残る問題は another_module_api.rst__main__ ガードに関するものだけとなっています。__main__ ガードの不足は自動修正の対象外です。