Spaces:
Sleeping
Sleeping
File size: 3,743 Bytes
8d28c3c 681ede6 230b1a5 681ede6 230b1a5 560aacd 230b1a5 560aacd 681ede6 560aacd 681ede6 560aacd 681ede6 560aacd 681ede6 230b1a5 8d28c3c b212889 681ede6 cefab8e b212889 230b1a5 b212889 681ede6 8d28c3c 681ede6 230b1a5 8d28c3c 681ede6 b212889 681ede6 8d28c3c b212889 681ede6 8d28c3c b212889 681ede6 8d28c3c 1820fc3 8490a22 681ede6 1820fc3 681ede6 1820fc3 8d28c3c 1820fc3 681ede6 1820fc3 681ede6 |
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))
|