"""Base detector class for project detection.""" from abc import ABC, abstractmethod from pathlib import Path from typing import Dict, List, Optional class BaseDetector(ABC): """Abstract base class for all detectors.""" @abstractmethod def detect(self, project_path: Path) -> List[str]: """Detect items from the project path. Args: project_path: Path to the project directory. Returns: List of detected item names (e.g., language names, framework names). """ pass @abstractmethod def get_priority(self) -> int: """Get the priority of this detector. Higher priority detectors are run first. Returns: Integer priority value. """ pass def _find_files(self, project_path: Path, patterns: List[str]) -> List[Path]: """Find files matching given patterns. Args: project_path: Root directory to search. patterns: List of filename patterns to match. Returns: List of matching file paths. """ if not project_path.exists() or not project_path.is_dir(): return [] found_files = [] try: for pattern in patterns: matches = list(project_path.rglob(pattern)) found_files.extend(matches) except PermissionError: pass except OSError: pass return found_files def _read_file_content(self, file_path: Path, max_size: int = 10240) -> Optional[str]: """Read file content safely. Args: file_path: Path to the file. max_size: Maximum file size to read in bytes. Returns: File content as string, or None if reading fails. """ try: if file_path.stat().st_size > max_size: return None with open(file_path, 'r', encoding='utf-8', errors='ignore') as f: return f.read() except (PermissionError, OSError, UnicodeDecodeError): return None def _check_file_exists(self, project_path: Path, filenames: List[str]) -> List[str]: """Check if any of the given filenames exist. Args: project_path: Project root directory. filenames: List of filenames to check. Returns: List of existing filenames. """ existing = [] for filename in filenames: if (project_path / filename).exists(): existing.append(filename) return existing def _check_content_markers(self, project_path: Path, markers: Dict[str, str]) -> List[str]: """Check for content markers in source files. Args: project_path: Project root directory. markers: Dict mapping marker names to search patterns. Returns: List of found marker names. """ found = [] try: for file_path in project_path.rglob("*"): if file_path.is_file() and file_path.suffix in ['.py', '.js', '.ts', '.go', '.java', '.rb', '.php', '.rs']: content = self._read_file_content(file_path) if content: for name, pattern in markers.items(): if name not in found and pattern.lower() in content.lower(): found.append(name) except (PermissionError, OSError): pass return found