File size: 3,400 Bytes
681ede6 230b1a5 681ede6 230b1a5 560aacd 230b1a5 560aacd 681ede6 560aacd 681ede6 560aacd 681ede6 560aacd 681ede6 230b1a5 b212889 681ede6 cefab8e b212889 230b1a5 b212889 681ede6 230b1a5 681ede6 b212889 681ede6 b212889 681ede6 b212889 681ede6 1820fc3 681ede6 1820fc3 681ede6 1820fc3 681ede6 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 |
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: 解析を開始するディレクトリ
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]):
"""ファイル内容を読み込み、エンコーディングを判定"""
try:
# 先頭数KBを読み込み、エンコーディングを推定
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 を試す
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))
|