compare_dir_files.py ダウンロード/コピー

compare_dir_files.py をダウンロード

compare_dir_files.py
compare_dir_files.py
  1"""
  2ディレクトリ間のファイル構成を比較するスクリプト。
  3
  4このスクリプトは、特定の除外パターンに合致するファイルを無視し、
  52つのディレクトリツリー間でどちらか一方にのみ存在するファイルを報告します。
  6主にソースコードの同期状態や、予期せぬファイルの有無を確認する際に役立ちます。
  7
  8:doc:`compare_dir_files_usage`
  9"""
 10import argparse
 11import os
 12import re
 13from pathlib import Path
 14
 15
 16# 除外パターンの定義 (パス全体に対して判定)
 17exclude_patterns = [
 18        r"/__init__\.py$",
 19        r"/.*_docstring\.",
 20        r"/.*_\d{8}\.",
 21        r"/tklib/",
 22        r"/cms/",
 23        r"/jsap_crystal/",
 24        r"/stastical_physics/",
 25    ]
 26
 27def compare_directories(source_dir, target_dir, extension="*.py"):
 28    """
 29    2つのディレクトリの内容を比較し、一方にのみ存在するファイルを報告する。
 30
 31    この関数は、ネストされた ``is_excluded`` 関数と ``get_filtered_files`` 関数を使用して、
 32    除外パターンにマッチしないファイルを収集します。収集したファイルセットの差分を計算し、
 33    結果を標準出力に表示します。パスのセパレータはPOSIX形式 ('/') に統一されます。
 34
 35    :param source_dir: str 比較元のディレクトリパス。
 36    :param target_dir: str 比較先のディレクトリパス。
 37    :param extension: str 検索するファイルの拡張子パターン (例: "*.py")。
 38    :returns: None 結果は標準出力に表示されるため、明示的な戻り値はない。
 39    """
 40    source_path = Path(source_dir)
 41    target_path = Path(target_dir)
 42
 43    def is_excluded(rel_path_str):
 44        """
 45        相対パスが定義済み除外パターンにマッチするかを判定する。
 46
 47        相対パスの先頭に '/' を付与し、``exclude_patterns`` の各正規表現と照合します。
 48        いずれかのパターンにマッチすれば除外対象と判断します。
 49
 50        :param rel_path_str: str 比較する相対パス文字列 (例: "path/to/file.py")。
 51        :returns: bool 除外パターンにマッチした場合はTrue、そうでなければFalse。
 52        """
 53        # マッチング用に先頭に / をつける
 54        test_path = "/" + rel_path_str
 55        return any(re.search(pattern, test_path) for pattern in exclude_patterns)
 56
 57    def get_filtered_files(base_path):
 58        """
 59        指定されたベースパス以下のファイルをフィルタリングして取得する。
 60
 61        ``Path.rglob()`` を使用して指定された拡張子パターンにマッチするファイルを再帰的に検索します。
 62        各ファイルの相対パスを ``is_excluded`` 関数でチェックし、
 63        除外対象でないファイルのみをセットとして収集します。
 64        相対パスはPOSIX形式 ('/') に統一されます。
 65
 66        :param base_path: Path 検索を開始するベースディレクトリの `Path` オブジェクト。
 67        :returns: set[str] フィルタリングされたファイルの相対パスのセット。
 68        """
 69        file_set = set()
 70        for p in base_path.rglob(extension):
 71            # ベースディレクトリからの相対パスを取得し '/' 区切りに変換
 72            rel_path = p.relative_to(base_path).as_posix()
 73            if not is_excluded(rel_path):
 74                file_set.add(rel_path)
 75        return file_set
 76
 77    source_files = get_filtered_files(source_path)
 78    target_files = get_filtered_files(target_path)
 79
 80    print(f"{'='*60}")
 81    print(f" 比較レポート (Separator: '/')")
 82    print(f"{'='*60}")
 83    print(f"・[A] Source: {source_path.absolute()}")
 84    print(f"・[B] Target: {target_path.absolute()}")
 85    print(f"{'-'*60}\n")
 86
 87    # AにあってBにないもの
 88    only_in_source = sorted(source_files - target_files)
 89    print(f"【 状態: Source [A] にのみ存在 / Target [B] から欠落 】")
 90    if only_in_source:
 91        for f in only_in_source:
 92            print(f"  [MISSING in B] -> {f}")
 93    else:
 94        print("  該当なし")
 95
 96    print("\n" + "-"*60 + "\n")
 97
 98    # BにあってAにないもの
 99    only_in_target = sorted(target_files - source_files)
100    print(f"【 状態: Target [B] にのみ存在 / Source [A] から欠落 】")
101    if only_in_target:
102        for f in only_in_target:
103            print(f"  [MISSING in A] -> {f}")
104    else:
105        print("  該当なし")
106
107def main():
108    """
109    コマンドライン引数を解析し、ディレクトリ比較処理を実行する。
110
111    この関数は ``argparse`` を使用して、比較元ディレクトリ、比較先ディレクトリ、
112    検索拡張子パターンを引数として受け取ります。
113    デフォルト値も設定されており、指定されたパスが有効なディレクトリであるかを確認し、
114    ``compare_directories`` 関数を呼び出します。
115
116    :param None: コマンドライン引数は ``argparse`` によって処理されるため、明示的な引数はない。
117    :returns: None 処理結果は標準出力に表示される。
118    """
119    parser = argparse.ArgumentParser(description="特定のディレクトリやファイルパターンを除外して2つのツリーを比較します。")
120    
121    parser.add_argument("source", 
122                        nargs="?",
123                        default=r"D:\git\tkProg\tkprog_COE",
124                        help="比較元ディレクトリ(A)")
125    parser.add_argument("target", 
126                        nargs="?",
127                        default=r"D:\git\sphinx\tkProg\source",
128                        help="比較先ディレクトリ(B)")
129    parser.add_argument("-e", "--ext", 
130                        default="*.py", 
131                        help="検索パターン (デフォルト: *.py)")
132
133    args = parser.parse_args()
134
135    if not os.path.isdir(args.source) or not os.path.isdir(args.target):
136        print("エラー: 指定されたパスが存在しないか、ディレクトリではありません。")
137        return
138
139    compare_directories(args.source, args.target, args.ext)
140
141if __name__ == "__main__":
142    main()