File size: 7,744 Bytes
feac656
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
"""
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)