compare_dir_files.py テクニカルドキュメント

プログラムの動作

compare_dir_files.py は、二つの指定されたディレクトリツリーを比較し、特定のファイルパターンに基づいて、一方のディレクトリにのみ存在するファイルを検出するPythonスクリプトです。このプログラムの主な目的は、異なる場所に存在するコードベースやファイル群の同期状態を確認し、差分を特定することにあります。

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

  • ディレクトリ比較: 比較元 (Source) と比較先 (Target) の二つのディレクトリを指定し、その内容を再帰的に比較します。

  • ファイルパターンフィルタリング: *.py のように、特定のファイル拡張子やパターンにマッチするファイルのみを比較対象とします。デフォルトはPythonファイル (*.py) です。

  • 除外パターンの適用: コード内に定義された正規表現パターンリスト exclude_patterns に基づき、特定のファイルやディレクトリを比較対象から除外します。これにより、バージョン管理システムが生成する一時ファイルや、特定のライブラリディレクトリなど、比較から除外したい要素を無視できます。

  • 差分レポート出力: Source にのみ存在するファイルと、Target にのみ存在するファイルをリスト形式で標準出力に出力します。

このプログラムは、例えば、開発中のプロジェクトディレクトリと、Sphinxドキュメントのソースディレクトリ間で、Pythonファイルの追加や削除が正しく反映されているかを確認するようなシナリオで役立ちます。

原理

このプログラムは、主にPythonの pathlib モジュールと set 型の機能を活用して、効率的にディレクトリ間のファイル差分を検出します。

  1. ファイルリストの収集:

    • pathlib.Path オブジェクトを使用して、指定されたベースディレクトリ配下を再帰的に走査します (.rglob(extension))。

    • 見つかった各ファイルに対し、ベースディレクトリからの相対パスを取得し、OSに依存しない / 区切り形式 (.as_posix()) に変換します。

  2. 除外処理:

    • 収集された各相対パスは、グローバルに定義された exclude_patterns リスト内の正規表現パターンと照合されます (re.search())。

    • いずれかのパターンにマッチした場合、そのファイルは比較対象から除外されます。これは、特に自動生成ファイルやテストデータ、特定のライブラリフォルダなど、差分としては認識したくないファイルを無視するために重要です。

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

    • フィルタリングされたファイルパスは、それぞれ set (集合) として格納されます。

    • Source ディレクトリのファイル集合を \(S\)、Target ディレクトリのファイル集合を \(T\) とすると、以下の集合演算によって差分が検出されます。

      • Source にのみ存在し Target に欠落しているファイル: \(S \setminus T\) (集合 \(S\) から集合 \(T\) の要素を取り除いたもの)

      • Target にのみ存在し Source に欠落しているファイル: \(T \setminus S\) (集合 \(T\) から集合 \(S\) の要素を取り除いたもの)

    • Pythonの set 型では、これらの操作は単純な減算演算子 (-) で実現されます (source_files - target_files)。この方法により、要素の重複を気にすることなく、高速に差分を特定できます。

  4. 結果の出力:

    • 検出された差分ファイルリストはアルファベット順にソートされ、分かりやすい形式で標準出力に表示されます。

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

compare_dir_files.py は、Pythonの標準ライブラリである argparse, os, re, pathlib のみを使用しています。

そのため、追加でインストールすべき非標準ライブラリはありません。Pythonがインストールされていれば、そのまま実行可能です。

必要な入力ファイル

このプログラムは、ファイルそのものを入力として受け取るのではなく、比較対象とする二つのディレクトリのパスをコマンドライン引数として要求します。また、比較対象とするファイルのパターン (例: *.py) を指定できます。

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

  • 比較先ディレクトリ (Target): 比較される側のディレクトリのパスです。

  • 検索パターン: 比較対象とするファイルのワイルドカードパターン (例: *.py, *.txt) です。デフォルトは *.py です。

プログラム内部でハードコードされている exclude_patterns リストは、特定のファイルやディレクトリを比較から除外するために利用されます。これらのパターンは、相対パス全体に対して正規表現マッチングを行います。

生成される出力ファイル

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

比較結果はすべて標準出力(コンソール)にテキスト形式で直接表示されます。出力されるレポートには以下の情報が含まれます。

  • 比較元 (Source) ディレクトリの絶対パス

  • 比較先 (Target) ディレクトリの絶対パス

  • Source にのみ存在し Target に欠落しているファイル のリスト ([MISSING in B] で示されます)

  • Target にのみ存在し Source に欠落しているファイル のリスト ([MISSING in A] で示されます)

各ファイルパスは、比較元ディレクトリからの相対パスとして / 区切りで表示されます。

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

基本的な実行コマンドと引数の説明は以下の通りです。

python compare_dir_files.py [source_dir] [target_dir] [-e EXTENSION_PATTERN]
  • source_dir: (オプション) 比較元となるディレクトリのパスです。指定しない場合、プログラム内にハードコードされたデフォルトパス (D:\git\tkProg\tkprog_COE) が使用されます。

  • target_dir: (オプション) 比較先となるディレクトリのパスです。指定しない場合、プログラム内にハードコードされたデフォルトパス (D:\git\sphinx\tkProg\source) が使用されます。

  • -e EXTENSION_PATTERN, --ext EXTENSION_PATTERN: (オプション) 比較対象とするファイルの検索パターンを指定します。デフォルトは *.py です。

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

1. デフォルト引数を使用する場合

プログラムに引数を指定せず実行すると、コード内に設定されたデフォルトのディレクトリパス (D:\git\tkProg\tkprog_COED:\git\sphinx\tkProg\source) とデフォルトの拡張子 (*.py) で比較が実行されます。

python compare_dir_files.py

実行結果の説明: この例では、架空のファイル構成に基づいた出力を示します。 D:\git\tkProg\tkprog_COE には module_a.py があるが D:\git\sphinx\tkProg\source には無く、 逆に D:\git\sphinx\tkProg\source には old_script.py があるが D:\git\tkProg\tkprog_COE には無い、といった状況を想定しています。 また、__init__.pytklib/ ディレクトリ内のファイルは、除外パターンによって比較対象から除外されています。

============================================================
 比較レポート (Separator: '/')
============================================================
・[A] Source: D:\git\tkProg\tkprog_COE
・[B] Target: D:\git\sphinx\tkProg\source
------------------------------------------------------------

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

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

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

2. 特定のディレクトリと拡張子を指定する場合

仮に /home/user/my_project/src/var/www/html/app の2つのディレクトリ間で .js ファイルの差分を比較する例です。

python compare_dir_files.py /home/user/my_project/src /var/www/html/app -e "*.js"

実行結果の説明: この例では、/home/user/my_project/src には main.js があるが /var/www/html/app には無く、 /var/www/html/app には unused_lib.js があるが /home/user/my_project/src には無い、といった状況を想定しています。

============================================================
 比較レポート (Separator: '/')
============================================================
・[A] Source: /home/user/my_project/src
・[B] Target: /var/www/html/app
------------------------------------------------------------

【 状態: Source [A] にのみ存在 / Target [B] から欠落 】
  [MISSING in B] -> components/button.js
  [MISSING in B] -> pages/index.js

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

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