Initial upload of ai-code-audit-cli project
Some checks failed
Some checks failed
This commit is contained in:
113
src/utils/file_utils.py
Normal file
113
src/utils/file_utils.py
Normal file
@@ -0,0 +1,113 @@
|
||||
"""File utilities for AI Code Audit CLI."""
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Iterator, Optional
|
||||
|
||||
|
||||
class FileUtils:
|
||||
"""Utilities for file operations."""
|
||||
|
||||
SUPPORTED_EXTENSIONS = {
|
||||
".py",
|
||||
".js",
|
||||
".ts",
|
||||
".jsx",
|
||||
".tsx",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize file utilities."""
|
||||
self.supported_extensions = self.SUPPORTED_EXTENSIONS
|
||||
|
||||
def find_files(
|
||||
self,
|
||||
path: Path,
|
||||
max_size: int = 5 * 1024 * 1024,
|
||||
excluded_patterns: list[str] | None = None,
|
||||
) -> Iterator[Path]:
|
||||
"""Find all files in a directory recursively."""
|
||||
if excluded_patterns is None:
|
||||
excluded_patterns = []
|
||||
|
||||
if path.is_file():
|
||||
if self._should_include(path, max_size, excluded_patterns):
|
||||
yield path
|
||||
return
|
||||
|
||||
for root, dirs, files in os.walk(path):
|
||||
for pattern in excluded_patterns[:]:
|
||||
if pattern in dirs:
|
||||
dirs.remove(pattern)
|
||||
|
||||
for file in files:
|
||||
file_path = Path(root) / file
|
||||
if self._should_include(file_path, max_size, excluded_patterns):
|
||||
yield file_path
|
||||
|
||||
def _should_include(
|
||||
self, file_path: Path, max_size: int, excluded_patterns: list[str]
|
||||
) -> bool:
|
||||
"""Check if a file should be included in scanning."""
|
||||
try:
|
||||
if file_path.suffix.lower() not in self.supported_extensions:
|
||||
return False
|
||||
|
||||
if file_path.stat().st_size > max_size:
|
||||
return False
|
||||
|
||||
str_path = str(file_path)
|
||||
for pattern in excluded_patterns:
|
||||
if pattern in str_path:
|
||||
return False
|
||||
|
||||
return True
|
||||
except (OSError, PermissionError):
|
||||
return False
|
||||
|
||||
def read_file(self, file_path: Path, encoding: str = "utf-8") -> str:
|
||||
"""Read file contents with error handling."""
|
||||
try:
|
||||
return file_path.read_text(encoding=encoding, errors="replace")
|
||||
except UnicodeDecodeError:
|
||||
try:
|
||||
return file_path.read_text(encoding="latin-1", errors="replace")
|
||||
except Exception:
|
||||
raise ValueError(f"Could not decode file: {file_path}")
|
||||
|
||||
def get_file_info(self, file_path: Path) -> dict:
|
||||
"""Get information about a file."""
|
||||
try:
|
||||
stat = file_path.stat()
|
||||
return {
|
||||
"path": str(file_path.absolute()),
|
||||
"name": file_path.name,
|
||||
"extension": file_path.suffix,
|
||||
"size": stat.st_size,
|
||||
"modified": stat.st_mtime,
|
||||
"is_readable": os.access(file_path, os.R_OK),
|
||||
}
|
||||
except Exception:
|
||||
return {
|
||||
"path": str(file_path.absolute()),
|
||||
"name": file_path.name,
|
||||
"extension": file_path.suffix,
|
||||
"size": 0,
|
||||
"modified": 0,
|
||||
"is_readable": False,
|
||||
}
|
||||
|
||||
def count_lines(self, content: str) -> int:
|
||||
"""Count lines in content."""
|
||||
if not content:
|
||||
return 0
|
||||
return len(content.split('\n'))
|
||||
|
||||
def get_lines_around(
|
||||
self, content: str, line_number: int, context: int = 2
|
||||
) -> list[str]:
|
||||
"""Get lines around a specific line number."""
|
||||
lines = content.split('\n')
|
||||
start = max(0, line_number - context - 1)
|
||||
end = min(len(lines), line_number + context)
|
||||
return lines[start:end]
|
||||
Reference in New Issue
Block a user