AI Agent:tools:file_list.py:ソースコード

#!/usr/bin/env python3
"""
ファイル: list_files_recursive.py
作成者: ChatGPT
変更修正者:robotcreation-dialy.com

説明:
指定されたフォルダに含まれるすべてのファイルパス(サブフォルダも含む)のリストを返す単一の関数 `list_files_recursive(folder_path)` を提供します。返されるパスは絶対パスで、アルファベット順に並べられています。
スクリプトを直接実行した際に、現在の作業ディレクトリで関数を実行する小さなテストハーネスが含まれています。
"""

import os
from typing import List
from langchain_core.tools import tool
from pydantic import BaseModel, Field
import json
g_file_description = {}
g_data_file_path = ""

def _list_files_recursive(folder_path: str) -> List[str]:
    """
    *folder_path* 配下のすべてのファイルパスのリストを再帰的に返します。
    
    パラメータ
    ----------
    folder_path : str
    スキャンするディレクトリへのパス。相対パスまたは絶対パスで指定します。
    パスが存在しないかディレクトリでない場合は、ValueError がスローされます。
    
    戻り値
    -------
    List[str]
    ソートされた絶対ファイルパスのリスト。ディレクトリは除外されます。
    
    注記
    -----
    * 隠しファイル(ドットで始まるもの)も含まれます。
    * ファイルを指すシンボリックリンクも含まれます。ディレクトリを指すリンクは os.walk によって辿られます。
    * この関数は、大規模なディレクトリツリーに効率的な `os.walk` を使用します。
    """
    global g_file_description
    global g_data_file_path
    folder_path = folder_path.replace("\\", "/")
    if not os.path.isdir(folder_path):
        raise ValueError(f"'{folder_path}' is not a valid directory")

    abs_root = os.path.abspath(folder_path)
    file_list: List[str] = []

    for root, dirs, files in os.walk(abs_root):
        for name in files:
            file_path = os.path.join(root, name)
            file_list.append(file_path)

    file_list.sort()
    g_file_description = {}
    for file_path in file_list:
        file_path = file_path.replace("\\", "/")
        file_path = file_path.replace(folder_path, "")
        _set_file_description(file_path)
    if os.path.exists(g_data_file_path):
        _load_file_descriptions()
    else:
        _save_file_descriptions()

    return file_list

def _create_folder(folder_path):
    """指定されたフォルダが存在しない場合、フォルダを作成します。

    Args:
      folder_path: 作成するフォルダのパス。
    """
    folder_path = folder_path.replace("\\", "/")
    if not os.path.exists(folder_path):
        os.makedirs(folder_path)
    if folder_path.endswith("/"):
        return folder_path
    else:
        return folder_path + "/"
def _get_parent_directory(file_path):
    """__file__からファイル名を除いてさらに親フォルダのパスを得る関数。

    Args:
        file_path: ファイルのパス。

    Returns:
        親フォルダのパス。ファイルが存在しない場合はNoneを返す。
    """
    if not os.path.exists(file_path):
        return None
    parent_dir = os.path.dirname(file_path)
    grandparent_dir = os.path.dirname(parent_dir)
    return grandparent_dir

def _get_directory(file_path):
    """__file__からファイル名を除いてさらに親フォルダのパスを得る関数。

    Args:
        file_path: ファイルのパス。

    Returns:
        親フォルダのパス。ファイルが存在しない場合はNoneを返す。
    """
    if not os.path.exists(file_path):
        return None
    parent_dir = os.path.dirname(file_path)
    
    return parent_dir

def _set_data_file_path(file_path):
    """データファイルのパスを設定します。

    Args:
      file_path: データファイルのパス。
    """
    global g_data_file_path
    g_data_file_path = file_path

def _print_file_descriptions():
    """現在のファイルの説明をコンソールに出力します。
    """
    global g_file_description
    for file_path, description in g_file_description.items():
        print(f"{file_path}: {description}")


def _set_file_description(file_path, purpose_of_creation = "",description=""):
    """指定されたファイルの説明を作成します。

    Args:
      file_path: 説明を作成するファイルのパス。
      purpose_of_creation: ファイル作成目的
      description: ファイル内容の説明
    """
    global g_file_description
    g_file_description[file_path] = {"purpose_of_creation":purpose_of_creation,"description":description}

def _save_file_descriptions():
    """ファイルの説明を指定されたパスに保存します。

    Args:
      output_path: 説明を保存するファイルのパス。
    """
    global g_file_description
    global g_data_file_path
    with open(g_data_file_path, "w", encoding="utf-8") as f:
        json.dump(g_file_description, f, indent=4, ensure_ascii=False)

def _load_file_descriptions():
    """指定されたパスからファイルの説明を読み込みます。

    Args:
      input_path: 説明を読み込むファイルのパス。
    """
    global g_file_description
    global g_data_file_path
    if not os.path.exists(g_data_file_path):
        return
    with open(g_data_file_path, "r", encoding="utf-8") as f:
        g_file_description = json.load(f)
    _clean_up_file_descriptions()

def _get_all_file_descriptions() -> str:
    """現在のすべてのファイルの説明を取得します。

    Returns:
      すべてのファイルの説明を含む辞書。
    """
    global g_file_description
    # g_file_descriptionをJSON文字列として返す
    result = json.dumps(g_file_description, indent=4, ensure_ascii=False)
    return result

def _clean_up_file_descriptions():
    """
    存在しないファイルを説明リストから削除します。
    """
    global g_file_description
    to_delete = []
    for file_path in g_file_description.keys():
        if not os.path.exists(file_path):
            to_delete.append(file_path)
    for file_path in to_delete:
        del g_file_description[file_path]

def _get_and_create_all_file_descriptions(folder_path: str) -> List[str]:
    """
    *folder_path* 配下のすべてのファイルパスのリストを再帰的に返します。
    
    パラメータ
    ----------
    folder_path : str
    スキャンするディレクトリへのパス。相対パスまたは絶対パスで指定します。
    パスが存在しないかディレクトリでない場合は、ValueError がスローされます。
    
    戻り値
    -------
    List[str]
    ソートされた絶対ファイルパスのリスト。ディレクトリは除外されます。
    
    注記
    -----
    * 隠しファイル(ドットで始まるもの)も含まれます。
    * ファイルを指すシンボリックリンクも含まれます。ディレクトリを指すリンクは os.walk によって辿られます。
    * この関数は、大規模なディレクトリツリーに効率的な `os.walk` を使用します。
    """
    _create_folder(folder_path)
    file_path =  os.path.join(folder_path, "progress_file_list.json")
    _set_data_file_path(file_path)
    if not os.path.exists(file_path):
        _list_files_recursive(folder_path)
        
    else:

        _load_file_descriptions()
        _clean_up_file_descriptions()    
    return _get_all_file_descriptions()

# --------------------------------------------------------------------------- #
# Test harness – runs when the script is executed directly
# --------------------------------------------------------------------------- #
class SetFileDescription(BaseModel):
    file_path: str = Field()
    description: str = Field(default="")

@tool(args_schema=SetFileDescription)
def set_file_description(file_path, purpose_of_creation = "",description=""):
    """
    指定されたファイルの説明を作成します。

    Args:
      file_path: 説明を作成するファイルのパス。
      purpose_of_creation: ファイル作成目的
      description: ファイル内容の説明
    """
    _set_file_description(file_path, purpose_of_creation,description)

@tool
def save_file_descriptions():
    """
    ファイルの説明を指定されたパスに保存します。

    Args:
      output_path: 説明を保存するファイルのパス。
    """
    _save_file_descriptions()


@tool
def load_file_descriptions():
    """
    指定されたパスからファイルの説明を読み込みます。

    Args:
      input_path: 説明を読み込むファイルのパス。
    """
    _load_file_descriptions()


def _get_file_description(file_path):
    """
    指定されたファイルの説明を取得します。

    Args:
      file_path: 説明を取得するファイルのパス。

    Returns:
      ファイルの説明。説明が存在しない場合は空文字列を返す。
    """
    global g_file_description
    return g_file_description.get(file_path, "")



@tool
def get_all_file_descriptions() -> str:
    """
    現在のすべてのファイルの説明を取得します。

    Returns:
      すべてのファイルの説明を含む辞書。
    """
    return _get_all_file_descriptions()


if __name__ == "__main__":
    import sys

    # Use the directory from which the script is run if no argument is given
    target_dir = _get_directory(__file__)

    try:
        files = _list_files_recursive(target_dir)
        print(f"Found {len(files)} file(s) under '{target_dir}':")
        target_dir = target_dir.replace("/", "\\")
        print(target_dir)
        for f in files:
            f = f.replace(target_dir, "")
            
            print(f)
        print(target_dir)
    except ValueError as e:
        print(f"Error: {e}")
        sys.exit(1)