ada-converter / src /utils /zip_utils.py
syum-af's picture
wip
feac656
Raw
History Blame Contribute Delete
7.74 kB
"""
ZIP utilities for handling Ada project ZIP files and creating Python project ZIP files.
"""
import os
import tempfile
import zipfile
import shutil
from pathlib import Path
from typing import Tuple, Optional
from loguru import logger
class ZipHandler:
"""Handles ZIP file operations for Ada to Python conversion."""
def __init__(self):
self.temp_base_dir = Path(tempfile.gettempdir()) / "ada-conversion"
# Clean up any existing sessions from previous runs
self._cleanup_previous_sessions()
self.temp_base_dir.mkdir(exist_ok=True)
logger.debug("ZIP handler initialized", temp_dir=str(self.temp_base_dir))
def _cleanup_previous_sessions(self) -> None:
"""Clean up any existing session directories from previous runs."""
if self.temp_base_dir.exists():
logger.debug("Cleaning up previous sessions", temp_base=str(self.temp_base_dir))
shutil.rmtree(self.temp_base_dir)
def is_zip_file(self, file_path: str) -> bool:
"""
Check if the given file path is a ZIP file.
Args:
file_path: Path to the file to check
Returns:
True if the file is a ZIP file, False otherwise
"""
try:
path = Path(file_path)
if not path.exists() or not path.is_file():
return False
# Check file extension
if path.suffix.lower() != '.zip':
return False
# Verify it's actually a ZIP file by trying to open it
with zipfile.ZipFile(path, 'r') as zip_file:
# Try to read the file list
zip_file.namelist()
return True
except (zipfile.BadZipFile, Exception):
return False
def extract_ada_project(self, zip_path: str, session_id: Optional[str] = None) -> Tuple[str, str]:
"""
Extract Ada project ZIP file to temporary directory.
Args:
zip_path: Path to the ZIP file containing Ada project
session_id: Optional session ID for unique directory naming
Returns:
Tuple of (extracted_project_path, temp_session_dir)
Raises:
FileNotFoundError: If ZIP file doesn't exist
zipfile.BadZipFile: If file is not a valid ZIP
ValueError: If no Ada files found in ZIP
"""
zip_path = Path(zip_path)
if not zip_path.exists():
raise FileNotFoundError(f"ZIP file does not exist: {zip_path}")
if not self.is_zip_file(str(zip_path)):
raise zipfile.BadZipFile(f"Not a valid ZIP file: {zip_path}")
# Create unique session directory
if session_id is None:
session_id = f"session_{os.getpid()}_{zip_path.stem}"
session_dir = self.temp_base_dir / session_id
session_dir.mkdir(parents=True, exist_ok=True)
logger.info("Extracting Ada project ZIP",
zip_file=str(zip_path),
extract_to=str(session_dir))
# Extract ZIP file
with zipfile.ZipFile(zip_path, 'r') as zip_file:
zip_file.extractall(session_dir)
# Find the Ada project directory within the extracted content
ada_project_path = self._find_ada_project_root(session_dir)
if ada_project_path is None:
raise ValueError(f"No Ada files found in ZIP: {zip_path}")
logger.success("Ada project extracted successfully",
ada_project=str(ada_project_path),
ada_files_found=len(list(ada_project_path.rglob("*.ad[s|b]"))) + len(list(ada_project_path.rglob("*.ada"))),
session_dir=str(session_dir))
return str(ada_project_path), str(session_dir)
def _find_ada_project_root(self, extracted_dir: Path) -> Optional[Path]:
"""
Find the root directory containing Ada files within extracted content.
Args:
extracted_dir: Directory where ZIP was extracted
Returns:
Path to Ada project root, or None if no Ada files found
"""
ada_extensions = {'.ads', '.adb', '.ada'}
# Check if extracted_dir itself contains Ada files
ada_files = []
for ext in ada_extensions:
ada_files.extend(list(extracted_dir.rglob(f"*{ext}")))
if not ada_files:
return None
# Find the common parent directory of all Ada files
if len(ada_files) == 1:
# If only one file, its parent is the project root
return ada_files[0].parent
# Find common parent of all Ada files
common_parent = Path(ada_files[0]).parent
for ada_file in ada_files[1:]:
# Find common path between current common_parent and this file's parent
try:
# Get relative path from common_parent to this file's parent
ada_file.parent.relative_to(common_parent)
except ValueError:
# Files are in different branches, need to go up
while True:
try:
ada_file.parent.relative_to(common_parent)
break
except ValueError:
common_parent = common_parent.parent
if common_parent == extracted_dir.parent:
# Gone too far up
common_parent = extracted_dir
break
return common_parent
def create_python_project_zip(self, python_project_path: str, output_zip_path: Optional[str] = None) -> str:
"""
Create a ZIP file from the generated Python project.
Args:
python_project_path: Path to the Python project directory
output_zip_path: Optional output ZIP path (defaults to project_name.zip)
Returns:
Path to the created ZIP file
Raises:
FileNotFoundError: If Python project directory doesn't exist
"""
project_path = Path(python_project_path)
if not project_path.exists():
raise FileNotFoundError(f"Python project directory does not exist: {python_project_path}")
if not project_path.is_dir():
raise ValueError(f"Path is not a directory: {python_project_path}")
# Determine output ZIP path
if output_zip_path is None:
output_zip_path = str(project_path.parent / f"{project_path.name}.zip")
output_zip_path = Path(output_zip_path)
logger.info("Creating Python project ZIP",
python_project=str(project_path),
output_zip=str(output_zip_path))
# Create ZIP file
with zipfile.ZipFile(output_zip_path, 'w', zipfile.ZIP_DEFLATED) as zip_file:
for file_path in project_path.rglob('*'):
if file_path.is_file():
# Get relative path within the project
relative_path = file_path.relative_to(project_path)
zip_file.write(file_path, relative_path)
logger.success("Python project ZIP created",
zip_file=str(output_zip_path),
zip_size=f"{output_zip_path.stat().st_size / (1024*1024):.1f} MB")
return str(output_zip_path)