AIとのコード共有を劇的に簡単に！複数ファイルを1つにまとめるPythonスクリプト

# AIとの共有を簡単にする！複数のファイルを1つにまとめるスクリプト（v03.01）

## はじめに
AIに相談する際、複数のPythonファイルを共有するのが面倒だと感じたことはありませんか？ZIPファイルのアップロードができない場合、一つ一つファイルを共有するのは大変です。そこで、複数のファイルを1つのテキストファイルにまとめるスクリプトを作成しました。

このスクリプトを使えば、プロジェクトフォルダ内の全てのPythonファイル（および他の指定した拡張子のファイル）を1つのテキストファイルにまとめることができます。これにより、AIとのコード共有がより簡単になります。

## 更新情報
- **v03.01**: 
  - ファイル名を「10_データ収集.py」に変更
  - 出力ファイル名を「11_開発状況.txt」に変更
  - 100MB以上のファイルを自動的に省略する機能を追加
  - テキスト概要セクションを追加（AIへの指示を含む）
  - 保存場所を明示的に表示するように改善

- **v02.01**:
  - ディレクトリ構造の表示機能を追加
  - 除外ディレクトリの設定機能を追加
  - ファイル収集と内容出力を統合

## スクリプトの機能
- 指定した拡張子（デフォルトは.py、.js）のファイルを収集
- サブディレクトリも含めて再帰的にファイルを探索
- 特定のディレクトリ（.git, venv など）を除外
- 各ファイルの絶対パスと中身を出力
- ディレクトリ構造を視覚的に表示
- 100MB以上の大きなファイルを自動省略
- 日本語のファイル対応

## スクリプトのコード
```python
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# v03.02
import os
import codecs
import datetime

# 収集するファイルの拡張子リスト
TARGET_EXTENSIONS = ['.py', '.html', '.css', '.js']
# TARGET_EXTENSIONS = ['.py', '.js','.html']
# TARGET_EXTENSIONS = ['.py', '.js']
# TARGET_EXTENSIONS = ['.py', '.html']
# TARGET_EXTENSIONS = ['.php', '.html']
# TARGET_EXTENSIONS = ['.js']
# TARGET_EXTENSIONS = ['.py']

# 除外するディレクトリのリスト
EXCLUDE_DIRS = ['.git','logs','old','venv','__pycache__', 'venv', 'migrations', 'static']
# EXCLUDE_DIRS = ['.git','logs','old','venv','__pycache__', 'venv', 'migrations'] #staticフォルダを除外しない
EXCLUDE_DIRS += ['mailform']

# ファイル階層を表示するファイルの拡張子リスト
TARGET_EXTENSIONS2 = ['.py', '.html', '.css', '.js']

# ファイル階層を表示する際に除外するディレクトリのリスト
EXCLUDE_DIRS2 = ['.git','logs','old','venv','__pycache__', 'venv', 'migrations', 'static']
EXCLUDE_DIRS2 += ['mailform']


# 出力ファイル名
OUTPUT_FILE = "./11_開発状況.txt"

# 大きすぎるファイルの閾値（バイト単位）
MAX_FILE_SIZE = 0.01
MAX_FILE_SIZE_BYTE = MAX_FILE_SIZE * 1024 * 1024  # 100MB
print(f"最大サイズ：{MAX_FILE_SIZE}MB")

# 設定情報の出力
print(f"収集するファイルの拡張子リスト: {TARGET_EXTENSIONS}")
print(f"除外するディレクトリのリスト: {EXCLUDE_DIRS}")
print(f"ファイル階層を表示するファイルの拡張子リスト: {TARGET_EXTENSIONS2}")
print(f"ファイル階層を表示する際に除外するディレクトリのリスト: {EXCLUDE_DIRS2}")

print(f"{TARGET_EXTENSIONS}のファイルを収集します。（{EXCLUDE_DIRS}を除く）")

# 最大サイズを超えたファイルを格納するリスト
large_files = []

def collect_files(root_dir):
    collected_files = []
    for root, dirs, files in os.walk(root_dir):
        # 除外するディレクトリを dirs から削除して走査対象から外す
        dirs[:] = [d for d in dirs if d not in EXCLUDE_DIRS]
        
        for file in files:
            if any(file.endswith(ext) for ext in TARGET_EXTENSIONS):
                file_path = os.path.join(root, file)
                collected_files.append(file_path)
                
                # ファイルサイズをチェック
                file_size = os.path.getsize(file_path)
                if file_size > MAX_FILE_SIZE_BYTE:
                    large_files.append((file_path, file_size / (1024 * 1024)))
    
    return collected_files

def read_file_content(file_path):
    file_size = os.path.getsize(file_path)
    if file_size > MAX_FILE_SIZE_BYTE:
        # ファイルサイズが大きすぎる場合はメッセージを返す
        file_extension = os.path.splitext(file_path)[1]
        return f"この{file_extension}ファイルは{MAX_FILE_SIZE}MB以上なので省略。機能追加、修正にあたり中身を見る必要があれば<テキスト概要>に記載の方法で追加の情報提供を依頼してください。。"
    
    try:
        with codecs.open(file_path, 'r', encoding='utf-8') as file:
            return file.read()
    except UnicodeDecodeError:
        return "このファイルはUTF-8でエンコードされていないため読み込めません。"

def write_output(collected_files, output_file):
    with codecs.open(output_file, 'w', encoding='utf-8') as out_file:
        for file_path in collected_files:
            out_file.write(f"<ファイルパス>{file_path}</ファイルパス>\n")
            out_file.write("<ファイル中身>\n")
            out_file.write(read_file_content(file_path))
            out_file.write("\n</ファイル中身>\n\n")

def display_directory_tree(directory, prefix="", output_file=None):
    """ディレクトリ構造をツリー形式で表示/ファイル出力する"""
    # ディレクトリ内のファイルとフォルダを取得
    entries = os.listdir(directory)
    entries = sorted([e for e in entries if e not in EXCLUDE_DIRS2])
    
    for i, entry in enumerate(entries):
        path = os.path.join(directory, entry)
        is_last = i == len(entries) - 1
        
        # 現在の項目のプレフィックスを作成
        current_prefix = "└── " if is_last else "├── "
        # 次の階層のプレフィックスを作成
        next_prefix = "    " if is_last else "│   "
        
        # ファイル/ディレクトリ行を作成
        line = f"{prefix}{current_prefix}{entry}"
        
        # 出力先に応じて処理
        if output_file:
            output_file.write(line + "\n")
        else:
            print(line)
        
        # ディレクトリの場合は再帰的に処理
        if os.path.isdir(path):
            display_directory_tree(path, prefix + next_prefix, output_file)

def write_overview(output_file):
    """テキストの概要部分を書き込む"""
    overview = f"""<テキスト概要>
このテキストはAIに対して開発状況を共有するものです。この開発状況を見て修正や機能追加、提案などを行ってください。
修正を提案する場合、修正がしやすいようにできるかぎり関数毎、もしくはクラス、小さいファイルなら全文を教えて下さい。全文ではない場合もインデントを元のインデントに合わせてください。
修正がファイル全文の場合は「全文修正」と一言伝えてください。
修正を提案する際、修正のファイルパス提示するようにお願いします。
なるべくデータ量を小さくするため、{MAX_FILE_SIZE}MB以上のデータは省略されています。そのため、もし、さらなる情報が必要な場合は、以下のように伝えてください。その後追加情報を提供します。〇〇.xxは追加情報が必要なファイル名です。複数必要であれば必要に応じてコマンドを修正してください。
「echo "<〇〇.xx>" > 12_追加提供情報データ.txt && cat 〇〇.xx >> 12_追加提供情報データ.txt && echo "</〇〇.xx>" >> 12_追加提供情報データ.txt」

収集するファイルの拡張子リスト: {TARGET_EXTENSIONS}
除外するディレクトリのリスト: {EXCLUDE_DIRS}
ファイル階層を表示するファイルの拡張子リスト: {TARGET_EXTENSIONS2}
ファイル階層を表示する際に除外するディレクトリのリスト: {EXCLUDE_DIRS2}
</テキスト概要>

"""
    output_file.write(overview)

def main():
    root_directory = os.getcwd()  # 現在のディレクトリを使用
    
    # ファイル収集と内容出力
    collected_files = collect_files(root_directory)
    with codecs.open(OUTPUT_FILE, 'w', encoding='utf-8') as out_file:
        # テキストの概要を出力
        write_overview(out_file)
        
        # ディレクトリ構造を出力
        out_file.write("<ディレクトリ構造>\n")
        display_directory_tree(root_directory, output_file=out_file)
        out_file.write("</ディレクトリ構造>\n\n")
        
        # ファイル内容を出力
        for file_path in collected_files:
            out_file.write(f"<ファイルパス>{file_path}</ファイルパス>\n")
            out_file.write("<ファイル中身>\n")
            out_file.write(read_file_content(file_path))
            out_file.write("\n</ファイル中身>\n\n")
    
    # 最大サイズを超えたファイルの一覧を表示
    if large_files:
        print(f"\n最大サイズ（{MAX_FILE_SIZE}MB）を超えたファイル一覧:")
        for file_path, size_mb in large_files:
            print(f"  - {file_path} ({size_mb:.2f}MB)")
    
    # 出力ファイルのサイズを取得
    output_file_size = os.path.getsize(OUTPUT_FILE)
    output_file_size_kb = output_file_size / 1024
    output_file_size_mb = output_file_size_kb / 1024
    
    print(f"{len(collected_files)}個のファイルが {OUTPUT_FILE} にまとめられました。")
    print(f"保存場所は{OUTPUT_FILE}")
    print(f"出力ファイルのサイズ: {output_file_size_kb:.2f}KB ({output_file_size_mb:.2f}MB)")

if __name__ == "__main__":
    main()
```

## スクリプトの解説

### 新機能（v03.01）
- **ファイルサイズ制限**: `MAX_FILE_SIZE`で定義された100MB以上のファイルは自動的に省略され、代わりにメッセージが表示されます。
- **テキスト概要セクション**: 出力ファイルの先頭に、AIへの指示を含むテキスト概要セクションが追加されます。
- **保存場所の明示**: スクリプト実行後、ターミナルに保存場所が明示的に表示されるようになりました。

### 基本機能
- **拡張子の指定**: `TARGET_EXTENSIONS`リストで収集するファイルの拡張子を指定します。様々な組み合わせのテンプレートがコメントで用意されています。
- **除外ディレクトリ**: `EXCLUDE_DIRS`リストで指定されたディレクトリは探索から除外されます。
- **ファイルの収集**: `collect_files`関数が、指定されたディレクトリとそのサブディレクトリを再帰的に探索し、対象の拡張子を持つファイルを収集します。
- **ファイル内容の読み取り**: `read_file_content`関数が、UTF-8エンコーディングを使用してファイルの内容を読み取ります。
- **ディレクトリ構造の表示**: `display_directory_tree`関数が、プロジェクトのディレクトリ構造を視覚的に表示します。
- **出力ファイルの作成**: 収集したファイルの情報を指定されたフォーマットで出力ファイルに書き込みます。

## 使い方

1. このスクリプトを `10_データ収集.py` として保存します。
2. コマンドラインで、スクリプトを実行したいプロジェクトディレクトリに移動します。
3. 以下のコマンドを実行します：
   ```
   python 10_データ収集.py
   ```
4. スクリプトが実行され、`11_開発状況.txt` ファイルが作成されます。

## カスタマイズ

- 異なる拡張子のファイルを収集したい場合は、`TARGET_EXTENSIONS`リストを編集します。コメントアウトされたテンプレートを使用することもできます。
- 除外するディレクトリを変更したい場合は、`EXCLUDE_DIRS`リストを編集します。
- 出力ファイル名を変更したい場合は、`OUTPUT_FILE`変数を編集します。
- ファイルサイズの制限を変更したい場合は、`MAX_FILE_SIZE`変数を編集します。

## まとめ

このスクリプトを使用することで、AIとのコード共有がより簡単になります。複数のファイルを1つのテキストファイルにまとめることで、プロジェクトの全体像を簡単に共有できます。v03.01では、大きなファイルの自動省略機能やAIへの指示を含むテキスト概要セクションの追加など、さらに便利になりました。

プロジェクトの規模が大きくなっても、このスクリプトを使えば簡単にAIと共有できるので、開発の効率化に役立ててください！