1# -*- coding: utf-8 -*-
2"""
3Utility functions for RT-Thread build system.
4
5This module provides common utility functions used throughout the build system.
6"""
7
8import os
9import sys
10import platform
11from typing import List, Tuple, Optional
12
13
14class PathService:
15    """Service for path manipulation and normalization."""
16
17    def __init__(self, base_path: str = None):
18        self.base_path = base_path or os.getcwd()
19
20    def normalize_path(self, path: str) -> str:
21        """
22        Normalize path for cross-platform compatibility.
23
24        Args:
25            path: Path to normalize
26
27        Returns:
28            Normalized path
29        """
30        # Convert to absolute path if relative
31        if not os.path.isabs(path):
32            path = os.path.abspath(os.path.join(self.base_path, path))
33
34        # Normalize separators
35        path = os.path.normpath(path)
36
37        # Convert to forward slashes for consistency
38        if platform.system() == 'Windows':
39            path = path.replace('\\', '/')
40
41        return path
42
43    def make_relative(self, path: str, base: str = None) -> str:
44        """
45        Make path relative to base.
46
47        Args:
48            path: Path to make relative
49            base: Base path (defaults to self.base_path)
50
51        Returns:
52            Relative path
53        """
54        if base is None:
55            base = self.base_path
56
57        path = self.normalize_path(path)
58        base = self.normalize_path(base)
59
60        try:
61            rel_path = os.path.relpath(path, base)
62            # Convert to forward slashes
63            if platform.system() == 'Windows':
64                rel_path = rel_path.replace('\\', '/')
65            return rel_path
66        except ValueError:
67            # Different drives on Windows
68            return path
69
70    def split_path(self, path: str) -> List[str]:
71        """
72        Split path into components.
73
74        Args:
75            path: Path to split
76
77        Returns:
78            List of path components
79        """
80        path = self.normalize_path(path)
81        parts = []
82
83        while True:
84            head, tail = os.path.split(path)
85            if tail:
86                parts.insert(0, tail)
87            if head == path:  # Reached root
88                if head:
89                    parts.insert(0, head)
90                break
91            path = head
92
93        return parts
94
95    def common_prefix(self, paths: List[str]) -> str:
96        """
97        Find common prefix of multiple paths.
98
99        Args:
100            paths: List of paths
101
102        Returns:
103            Common prefix path
104        """
105        if not paths:
106            return ""
107
108        # Normalize all paths
109        normalized = [self.normalize_path(p) for p in paths]
110
111        # Find common prefix
112        prefix = os.path.commonpath(normalized)
113
114        return self.normalize_path(prefix)
115
116
117class PlatformInfo:
118    """Platform and system information."""
119
120    @staticmethod
121    def get_platform() -> str:
122        """Get platform name (Windows, Linux, Darwin)."""
123        return platform.system()
124
125    @staticmethod
126    def get_architecture() -> str:
127        """Get system architecture."""
128        return platform.machine()
129
130    @staticmethod
131    def is_windows() -> bool:
132        """Check if running on Windows."""
133        return platform.system() == 'Windows'
134
135    @staticmethod
136    def is_linux() -> bool:
137        """Check if running on Linux."""
138        return platform.system() == 'Linux'
139
140    @staticmethod
141    def is_macos() -> bool:
142        """Check if running on macOS."""
143        return platform.system() == 'Darwin'
144
145    @staticmethod
146    def get_python_version() -> Tuple[int, int, int]:
147        """Get Python version tuple."""
148        return sys.version_info[:3]
149
150    @staticmethod
151    def check_python_version(min_version: Tuple[int, int]) -> bool:
152        """
153        Check if Python version meets minimum requirement.
154
155        Args:
156            min_version: Minimum version tuple (major, minor)
157
158        Returns:
159            True if version is sufficient
160        """
161        current = sys.version_info[:2]
162        return current >= min_version
163
164
165class FileUtils:
166    """File operation utilities."""
167
168    @staticmethod
169    def read_file(filepath: str, encoding: str = 'utf-8') -> str:
170        """
171        Read file content.
172
173        Args:
174            filepath: File path
175            encoding: File encoding
176
177        Returns:
178            File content
179        """
180        with open(filepath, 'r', encoding=encoding) as f:
181            return f.read()
182
183    @staticmethod
184    def write_file(filepath: str, content: str, encoding: str = 'utf-8') -> None:
185        """
186        Write content to file.
187
188        Args:
189            filepath: File path
190            content: Content to write
191            encoding: File encoding
192        """
193        # Ensure directory exists
194        directory = os.path.dirname(filepath)
195        if directory:
196            os.makedirs(directory, exist_ok=True)
197
198        with open(filepath, 'w', encoding=encoding) as f:
199            f.write(content)
200
201    @staticmethod
202    def copy_file(src: str, dst: str) -> None:
203        """
204        Copy file from src to dst.
205
206        Args:
207            src: Source file path
208            dst: Destination file path
209        """
210        import shutil
211
212        # Ensure destination directory exists
213        dst_dir = os.path.dirname(dst)
214        if dst_dir:
215            os.makedirs(dst_dir, exist_ok=True)
216
217        shutil.copy2(src, dst)
218
219    @staticmethod
220    def find_files(directory: str, pattern: str, recursive: bool = True) -> List[str]:
221        """
222        Find files matching pattern.
223
224        Args:
225            directory: Directory to search
226            pattern: File pattern (supports wildcards)
227            recursive: Search recursively
228
229        Returns:
230            List of matching file paths
231        """
232        import fnmatch
233
234        matches = []
235
236        if recursive:
237            for root, dirnames, filenames in os.walk(directory):
238                for filename in filenames:
239                    if fnmatch.fnmatch(filename, pattern):
240                        matches.append(os.path.join(root, filename))
241        else:
242            try:
243                filenames = os.listdir(directory)
244                for filename in filenames:
245                    if fnmatch.fnmatch(filename, pattern):
246                        filepath = os.path.join(directory, filename)
247                        if os.path.isfile(filepath):
248                            matches.append(filepath)
249            except OSError:
250                pass
251
252        return sorted(matches)
253
254
255class VersionUtils:
256    """Version comparison utilities."""
257
258    @staticmethod
259    def parse_version(version_str: str) -> Tuple[int, ...]:
260        """
261        Parse version string to tuple.
262
263        Args:
264            version_str: Version string (e.g., "1.2.3")
265
266        Returns:
267            Version tuple
268        """
269        try:
270            parts = version_str.split('.')
271            return tuple(int(p) for p in parts if p.isdigit())
272        except (ValueError, AttributeError):
273            return (0,)
274
275    @staticmethod
276    def compare_versions(v1: str, v2: str) -> int:
277        """
278        Compare two version strings.
279
280        Args:
281            v1: First version
282            v2: Second version
283
284        Returns:
285            -1 if v1 < v2, 0 if equal, 1 if v1 > v2
286        """
287        t1 = VersionUtils.parse_version(v1)
288        t2 = VersionUtils.parse_version(v2)
289
290        # Pad shorter version with zeros
291        if len(t1) < len(t2):
292            t1 = t1 + (0,) * (len(t2) - len(t1))
293        elif len(t2) < len(t1):
294            t2 = t2 + (0,) * (len(t1) - len(t2))
295
296        if t1 < t2:
297            return -1
298        elif t1 > t2:
299            return 1
300        else:
301            return 0
302
303    @staticmethod
304    def version_satisfies(version: str, requirement: str) -> bool:
305        """
306        Check if version satisfies requirement.
307
308        Args:
309            version: Version string
310            requirement: Requirement string (e.g., ">=1.2.0")
311
312        Returns:
313            True if satisfied
314        """
315        import re
316
317        # Parse requirement
318        match = re.match(r'([<>=]+)\s*(.+)', requirement)
319        if not match:
320            # Exact match required
321            return version == requirement
322
323        op, req_version = match.groups()
324        cmp = VersionUtils.compare_versions(version, req_version)
325
326        if op == '>=':
327            return cmp >= 0
328        elif op == '<=':
329            return cmp <= 0
330        elif op == '>':
331            return cmp > 0
332        elif op == '<':
333            return cmp < 0
334        elif op == '==':
335            return cmp == 0
336        elif op == '!=':
337            return cmp != 0
338        else:
339            return False