ada-assistant / src /tools /project_handler.py
syum-af's picture
refactor
503a15e
Raw
History Blame Contribute Delete
9.95 kB
"""Project handling utilities for Ada files and archives."""
import tempfile
import uuid
import os
import zipfile
import glob
import shutil
from src.agents.code_analyzer import CodeAnalyzerAgent
from src.agents.ada_converter import AdaConverterAgent
from src.agents.unit_test_generator import UnitTestGeneratorAgent
class ProjectHandler:
"""Handles Ada project operations including extraction, analysis, conversion, and download."""
def __init__(self, model):
"""Initialize ProjectHandler with model and create agents."""
self.model = model
self.analyzer = CodeAnalyzerAgent(model)
self.converter = AdaConverterAgent(model)
self.test_generator = UnitTestGeneratorAgent(model)
@staticmethod
def cleanup_old_projects(target_dir: str | None = None) -> None:
"""
Clean up old ada_project_* directories in the temp directory.
Args:
target_dir: Directory to clean up (defaults to system temp directory)
"""
if target_dir is None:
target_dir = tempfile.gettempdir()
# Find all ada_project_* directories
pattern = os.path.join(target_dir, "ada_project_*")
old_projects = glob.glob(pattern)
# Remove each directory
for project_dir in old_projects:
if os.path.isdir(project_dir):
try:
shutil.rmtree(project_dir)
except OSError:
# Ignore errors if directory can't be removed
pass
def unzip_project(self, zip_file: str, target_dir: str | None = None) -> str:
"""
Extract a zip file containing Ada project files.
Args:
zip_file: Path to the zip file
target_dir: Directory to extract to (defaults to system temp directory)
Returns:
Path to the extracted directory (unique UUID-based name)
"""
if target_dir is None:
target_dir = tempfile.gettempdir()
# Clean up old ada_project_* directories
self.cleanup_old_projects(target_dir)
# Generate unique directory name using UUID
unique_dir_name = f"ada_project_{uuid.uuid4().hex}"
extract_path = os.path.join(target_dir, unique_dir_name)
# Create the extraction directory
os.makedirs(extract_path, exist_ok=True)
# Extract zip file to extract_path
with zipfile.ZipFile(zip_file, 'r') as zip_ref:
zip_ref.extractall(extract_path)
return extract_path
@staticmethod
def build_directory_tree(directory_path: str) -> str:
"""
Build a visual directory tree representation.
Args:
directory_path: Path to the directory to build tree for
Returns:
String representation of directory tree suitable for Gradio display
"""
def _build_tree(path: str, prefix: str = "") -> str:
"""Recursively build tree structure."""
items = []
if not os.path.exists(path):
return ""
try:
# Get all items in directory
entries = sorted(os.listdir(path))
for i, entry in enumerate(entries):
entry_path = os.path.join(path, entry)
is_last_item = i == len(entries) - 1
# Choose appropriate tree symbols
current_prefix = "└── " if is_last_item else "├── "
next_prefix = prefix + (" " if is_last_item else "│ ")
if os.path.isdir(entry_path):
items.append(f"{prefix}{current_prefix}{entry}/")
# Recursively add subdirectory contents
subtree = _build_tree(entry_path, next_prefix)
if subtree:
items.append(subtree)
else:
items.append(f"{prefix}{current_prefix}{entry}")
except PermissionError:
items.append(f"{prefix}[Permission Denied]")
return "\n".join(items)
# Start with root directory name
root_name = os.path.basename(directory_path) or directory_path
tree = f"{root_name}/\n"
tree += _build_tree(directory_path)
return tree
@staticmethod
def find_ada_files(extracted_path: str) -> list[str]:
"""Find all Ada files (.ads and .adb) in the extracted project."""
ada_files = []
for root, _, files in os.walk(extracted_path):
for file in files:
if file.endswith(('.ads', '.adb')):
ada_files.append(os.path.join(root, file))
return ada_files
def analyze_business_logic(self, ada_code: str) -> str:
"""Analyze Ada code to extract business logic."""
return self.analyzer.extract_business_logic(ada_code)
def convert_to_python(self, ada_code: str) -> str:
"""Convert Ada code to Python."""
return self.converter.convert_to_python(ada_code)
def generate_unit_tests(self, python_code: str) -> str:
"""Generate unit tests for Python code."""
return self.test_generator.generate_unit_tests(python_code)
def process_ada_file(self, ada_code: str) -> tuple[str, str, str]:
"""Process Ada code: analyze, convert to Python, and generate unit tests.
Args:
ada_code: The Ada source code to process
Returns:
Tuple of (analysis_result, python_code, unit_tests)
"""
# Analyze the Ada code
analysis_result = self.analyze_business_logic(ada_code)
# Convert Ada code to Python
python_code = self.convert_to_python(ada_code)
# Generate unit tests for the converted Python code
unit_tests = self.generate_unit_tests(python_code)
return analysis_result, python_code, unit_tests
def convert_and_download_project(self, extracted_path: str) -> str | None:
"""Convert all Ada files to Python and create downloadable zip with tests"""
if not extracted_path:
return None
try:
# Create temporary directory for converted files
temp_dir = tempfile.mkdtemp()
converted_root = os.path.join(temp_dir, "converted_project")
tests_dir = os.path.join(converted_root, "tests")
os.makedirs(converted_root, exist_ok=True)
os.makedirs(tests_dir, exist_ok=True)
# Find all Ada files (.ads and .adb)
ada_files = self.find_ada_files(extracted_path)
converted_files = []
# Convert each Ada file to Python
for ada_file in ada_files:
try:
# Read Ada file content
with open(ada_file, 'r', encoding='utf-8') as f:
ada_code = f.read()
# Convert to Python
python_code = self.convert_to_python(ada_code)
# Calculate relative path to preserve directory structure
rel_path = os.path.relpath(ada_file, extracted_path)
python_filename = os.path.splitext(rel_path)[0] + '.py'
python_file_path = os.path.join(converted_root, python_filename)
# Create directories if needed
os.makedirs(os.path.dirname(python_file_path), exist_ok=True)
# Write Python file
with open(python_file_path, 'w', encoding='utf-8') as f:
f.write(python_code)
converted_files.append((python_file_path, python_filename, python_code))
except Exception as e:
print(f"Error converting {ada_file}: {str(e)}")
continue
# Generate unit tests for each converted Python file
for python_file_path, python_filename, python_code in converted_files:
try:
# Generate unit tests from Python code string
unit_tests = self.generate_unit_tests(python_code)
# Create test file name
test_filename = f"test_{os.path.basename(python_filename)}"
test_file_path = os.path.join(tests_dir, test_filename)
# Write test file
with open(test_file_path, 'w', encoding='utf-8') as f:
f.write(unit_tests)
except Exception as e:
print(f"Error generating tests for {python_filename}: {str(e)}")
continue
# Create zip file
zip_path = os.path.join(temp_dir, "converted_ada_project.zip")
with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
for root, _, files in os.walk(converted_root):
for file in files:
file_path = os.path.join(root, file)
arcname = os.path.relpath(file_path, converted_root)
zipf.write(file_path, arcname)
return zip_path
except Exception as e:
print(f"Error in convert_and_download_project: {str(e)}")
return None