ディレクトリファイル比較ツール

このドキュメントは、Pythonスクリプト compare_dir_files.py の技術的な側面を詳細に解説します。


プログラムの動作

compare_dir_files.py は、2つの指定されたディレクトリツリーを比較し、一方にのみ存在するファイルを報告するスクリプトです。特定のパターンに合致するファイルやディレクトリは比較対象から除外されます。主な目的は、開発環境やデプロイ環境におけるソースコードの同期状態を確認したり、予期せぬファイルの有無を検出したりすることです。

具体的には、以下の機能を提供します。

  1. ディレクトリ比較: 比較元ディレクトリ (Source) と比較先ディレクトリ (Target) の内容を再帰的に走査します。

  2. ファイルフィルタリング: 指定された拡張子パターン(例: *.py)に合致するファイルのみを比較対象とします。

  3. 除外ルール: 定義された正規表現パターンリストに基づいて、特定のパスやファイルを除外します。これにより、バージョン管理システムファイル、一時ファイル、ビルド成果物など、比較に不要な要素を無視できます。

  4. 差分報告: Source にのみ存在するファイルと、Target にのみ存在するファイルをリストアップし、標準出力に表示します。

このツールは、特に大規模なプロジェクトで複数の環境間でのファイル構成の整合性を保つ必要がある場合に役立ちます。


原理

compare_dir_files.py は、主に以下のアルゴリズムと技術要素に基づいて動作します。

1. ファイル収集とフィルタリング

プログラムは、pathlib.Path モジュールの rglob() メソッドを使用して、指定されたベースディレクトリ以下にある、指定された拡張子パターンに合致するファイルを再帰的に検索します。 例として、base_path.rglob("*.py")base_path 以下のすべての .py ファイルを検索します。

検索された各ファイルに対して、以下の処理を行います。

  • ベースディレクトリからの相対パスを取得します。

  • パスセパレータをOS依存のものからPOSIX形式 (/) に統一します。これは、異なるOS環境間でも一貫した比較を可能にするためです。

2. 除外パターンの適用

プログラムには exclude_patterns というグローバルなリストがあり、正規表現の文字列が格納されています。このリストは、除外すべきファイルの相対パスを定義します。

is_excluded 関数は、与えられた相対パスがこれらの除外パターンにマッチするかどうかを判定します。この際、相対パスの先頭に明示的に / を付与 (test_path = "/" + rel_path_str) することで、正規表現がパスのルートからのマッチングを正しく行えるようにします。

例えば、exclude_patternsr"/__init__\.py$" というパターンがある場合、/path/to/__init__.py のようなパスがマッチし、/ の後に __init__.py が続くファイルが除外対象となります。同様に、r"/tklib/" のパターンは、パス中に /tklib/ を含むすべてのファイルを除外します。

除外パターンにマッチしないファイルのみが、後続の比較のためにセットとして収集されます。

3. 集合演算による差分検出

2つのディレクトリ (source_dirtarget_dir) からそれぞれ収集されたフィルタリング済みファイルの相対パスは、Pythonの set オブジェクトに格納されます。set は重複する要素を持たない順序なしコレクションであり、高速な集合演算をサポートします。

  • source_files: Source ディレクトリに存在する、除外されなかったファイルの相対パスのセット。

  • target_files: Target ディレクトリに存在する、除外されなかったファイルの相対パスのセット。

これらのセットに対して、集合の差演算を行います。

Source にのみ存在するファイル (only_in_source) は、source_files から target_files を引いた差集合として計算されます。

\[S_{only} = S - T\]

Target にのみ存在するファイル (only_in_target) は、target_files から source_files を引いた差集合として計算されます。

\[T_{only} = T - S\]

これらの差集合は、比較対象のディレクトリツリー間でどちらか一方にのみ存在するファイルを正確に特定します。結果は可読性を高めるためにソートされてから標準出力に表示されます。


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

compare_dir_files.py は、Pythonの標準ライブラリのみを使用しており、argparse, os, re, pathlib が含まれます。

したがって、特別な非標準ライブラリをインストールする必要はありません。Pythonがインストールされていれば、そのまま実行できます。


必要な入力ファイル

プログラムを実行するためには、以下の情報が必要です。

  • 比較元ディレクトリ (Source): 比較の基準となるディレクトリのパス。

  • 比較先ディレクトリ (Target): 比較対象となるディレクトリのパス。

  • 検索拡張子パターン (Optional): 比較対象とするファイルの拡張子パターン。デフォルトは *.py です。

これらの情報はコマンドライン引数として指定します。ディレクトリ内に存在するファイルの形式は、拡張子パターンに合致する限り、特に限定されません。プログラムはファイルの内容を読み取るのではなく、ファイルパスの存在のみを比較します。


生成される出力ファイル

compare_dir_files.py は、いかなるファイルも生成しません

比較結果はすべて標準出力 (stdout) に表示されます。出力内容は以下の要素で構成されます。

  • 比較レポートのヘッダーと区切り線。

  • 比較元 (Source) および比較先 (Target) ディレクトリの絶対パス。

  • Source にのみ存在するファイルのリスト (見出し: 状態: Source [A] にのみ存在 / Target [B] から欠落 )。

    • 各ファイルは [MISSING in B] -> <相対パス> の形式で表示されます。

  • Target にのみ存在するファイルのリスト (見出し: 状態: Target [B] にのみ存在 / Source [A] から欠落 )。

    • 各ファイルは [MISSING in A] -> <相対パス> の形式で表示されます。

  • どちらかのリストが空の場合、「該当なし」と表示されます。

  • すべてのファイルパスはPOSIX形式 (/ 区切り) で表示されます。


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

compare_dir_files.py をコマンドラインから実行する際の基本的な書式は以下の通りです。

python compare_dir_files.py [SOURCE_DIR] [TARGET_DIR] [-e EXTENSION_PATTERN]

引数:

  • SOURCE_DIR: 比較元ディレクトリ (A)。指定しない場合、デフォルト値 D:\git\tkProg\tkprog_COE が使用されます。

  • TARGET_DIR: 比較先ディレクトリ (B)。指定しない場合、デフォルト値 D:\git\sphinx\tkProg\source が使用されます。

  • -e, --ext EXTENSION_PATTERN: 検索するファイルの拡張子パターン。デフォルトは *.py です。


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

ここでは、compare_dir_files.py の具体的な使用例とその実行結果を説明します。 以下の仮想的なディレクトリ構成を仮定します。

D:\projects\my_source\
├── module_a.py
├── sub_dir\
│   └── helper.py
├── common.py
└── data.json

D:\projects\my_target\
├── module_a.py
├── sub_dir\
│   └── another_helper.py
├── new_feature.py
└── temp_file.py

例1: デフォルトのPythonファイル (.py) を比較する

sourcetarget ディレクトリを指定して、デフォルトの拡張子 (*.py) で比較します。

python compare_dir_files.py D:\projects\my_source D:\projects\my_target

実行結果の説明:

上記のコマンドを実行すると、以下のような出力が得られます。

============================================================
 比較レポート (Separator: '/')
============================================================
・[A] Source: D:\projects\my_source
・[B] Target: D:\projects\my_target
------------------------------------------------------------

【 状態: Source [A] にのみ存在 / Target [B] から欠落 】
  [MISSING in B] -> common.py
  [MISSING in B] -> sub_dir/helper.py

------------------------------------------------------------

【 状態: Target [B] にのみ存在 / Source [A] から欠落 】
  [MISSING in A] -> new_feature.py
  [MISSING in A] -> sub_dir/another_helper.py
  [MISSING in A] -> temp_file.py
  • module_a.py は両方のディレクトリに存在するため、リストには表示されません。

  • data.json*.py フィルタリングにより比較対象外となるため、リストには表示されません。

  • common.pysub_dir/helper.pymy_source にのみ存在するため、[MISSING in B] として報告されます。

  • new_feature.py, sub_dir/another_helper.py, temp_file.pymy_target にのみ存在するため、[MISSING in A] として報告されます。

例2: 拡張子を .json に変更して比較する

今度は、.json ファイルのみを比較対象とします。

python compare_dir_files.py D:\projects\my_source D:\projects\my_target -e "*.json"

実行結果の説明:

============================================================
 比較レポート (Separator: '/')
============================================================
・[A] Source: D:\projects\my_source
・[B] Target: D:\projects\my_target
------------------------------------------------------------

【 状態: Source [A] にのみ存在 / Target [B] から欠落 】
  [MISSING in B] -> data.json

------------------------------------------------------------

【 状態: Target [B] にのみ存在 / Source [A] から欠落 】
  該当なし
  • この場合、my_source には data.json がありますが、my_target には *.json ファイルがないため、data.json[MISSING in B] として報告されます。

  • my_target には .json ファイルが存在しないため、Target [B] にのみ存在 のセクションは「該当なし」となります。

これらの例からわかるように、compare_dir_files.py は指定された条件に基づいてディレクトリ間のファイル構成の差異を明確に示し、同期状態の確認や不要なファイルの検出に役立ちます。