Spaces:
Running
Running
File size: 3,743 Bytes
e50fc98 227e75d e50fc98 227e75d 5a2169d 227e75d 5a2169d e50fc98 5a2169d e50fc98 5a2169d e50fc98 5a2169d e50fc98 227e75d e50fc98 227e75d e50fc98 227e75d e50fc98 227e75d e50fc98 227e75d e50fc98 227e75d e50fc98 227e75d e50fc98 227e75d e50fc98 227e75d e50fc98 227e75d e50fc98 227e75d e50fc98 227e75d e50fc98 227e75d e50fc98 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
# core/file_scanner.py
import chardet
from pathlib import Path
from typing import List, Optional, Set
from dataclasses import dataclass
@dataclass
class FileInfo:
path: Path
size: int
extension: str
content: Optional[str] = None
encoding: Optional[str] = None
@property
def formatted_size(self) -> str:
"""ファイルサイズを見やすい単位で表示"""
if self.size < 1024:
return f"{self.size} B"
elif self.size < 1024 * 1024:
return f"{self.size / 1024:.1f} KB"
else:
return f"{self.size / (1024 * 1024):.1f} MB"
class FileScanner:
"""
指定された拡張子のファイルだけを再帰的に検索し、ファイル内容を読み込むクラス。
"""
EXCLUDED_DIRS = {
'.git', '__pycache__', 'node_modules', 'venv',
'.env', 'build', 'dist', 'target', 'bin', 'obj'
}
def __init__(self, base_dir: Path, target_extensions: Set[str]):
"""
base_dir: 解析を開始するディレクトリ(Path)
target_extensions: 対象とする拡張子の集合 (例: {'.py', '.js', '.md'})
"""
self.base_dir = base_dir
# 大文字・小文字のブレを吸収するために小文字化して保持
self.target_extensions = {ext.lower() for ext in target_extensions}
def _should_scan_file(self, path: Path) -> bool:
"""対象外フォルダ・拡張子を除外"""
# 除外フォルダ判定
if any(excluded in path.parts for excluded in self.EXCLUDED_DIRS):
return False
# 拡張子チェック
if path.suffix.lower() in self.target_extensions:
return True
return False
def _read_file_content(self, file_path: Path) -> (Optional[str], Optional[str]):
"""
ファイル内容を読み込み、エンコーディングを判定して返す。
先頭4096バイトをchardetで解析し、失敗時はcp932も試す。
"""
try:
with file_path.open('rb') as rb:
raw_data = rb.read(4096)
detect_result = chardet.detect(raw_data)
encoding = detect_result['encoding'] if detect_result['confidence'] > 0.7 else 'utf-8'
# 推定エンコーディングで読み込み
try:
with file_path.open('r', encoding=encoding) as f:
return f.read(), encoding
except UnicodeDecodeError:
# cp932 を再試行 (Windows向け)
with file_path.open('r', encoding='cp932') as f:
return f.read(), 'cp932'
except Exception:
return None, None
def scan_files(self) -> List[FileInfo]:
"""
再帰的にファイルを探して、指定拡張子だけをFileInfoオブジェクトのリストとして返す。
"""
if not self.base_dir.exists():
raise FileNotFoundError(f"指定ディレクトリが見つかりません: {self.base_dir}")
collected_files = []
for entry in self.base_dir.glob("**/*"):
if entry.is_file() and self._should_scan_file(entry):
content, encoding = self._read_file_content(entry)
file_info = FileInfo(
path=entry.resolve(),
size=entry.stat().st_size,
extension=entry.suffix.lower(),
content=content,
encoding=encoding
)
collected_files.append(file_info)
# path の文字列表現でソート
return sorted(collected_files, key=lambda x: str(x.path))
|