qyro 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- qyro/__init__.py +17 -0
- qyro/adapters/__init__.py +4 -0
- qyro/adapters/language_adapters/__init__.py +4 -0
- qyro/adapters/language_adapters/c/__init__.py +4 -0
- qyro/adapters/language_adapters/python/__init__.py +4 -0
- qyro/adapters/language_adapters/python/python_adapter.py +584 -0
- qyro/cli/__init__.py +8 -0
- qyro/cli/__main__.py +5 -0
- qyro/cli/cli.py +392 -0
- qyro/cli/interactive.py +297 -0
- qyro/common/__init__.py +37 -0
- qyro/common/animation.py +82 -0
- qyro/common/builder.py +434 -0
- qyro/common/compiler.py +895 -0
- qyro/common/config.py +93 -0
- qyro/common/constants.py +99 -0
- qyro/common/errors.py +176 -0
- qyro/common/frontend.py +74 -0
- qyro/common/health.py +358 -0
- qyro/common/kafka_manager.py +192 -0
- qyro/common/logging.py +149 -0
- qyro/common/memory.py +147 -0
- qyro/common/metrics.py +301 -0
- qyro/common/monitoring.py +468 -0
- qyro/common/parser.py +91 -0
- qyro/common/platform.py +609 -0
- qyro/common/redis_memory.py +1108 -0
- qyro/common/rpc.py +287 -0
- qyro/common/sandbox.py +191 -0
- qyro/common/schema_loader.py +33 -0
- qyro/common/secure_sandbox.py +490 -0
- qyro/common/toolchain_validator.py +617 -0
- qyro/common/type_generator.py +176 -0
- qyro/common/validation.py +401 -0
- qyro/common/validator.py +204 -0
- qyro/gateway/__init__.py +8 -0
- qyro/gateway/gateway.py +303 -0
- qyro/orchestrator/__init__.py +8 -0
- qyro/orchestrator/orchestrator.py +1223 -0
- qyro-2.0.0.dist-info/METADATA +244 -0
- qyro-2.0.0.dist-info/RECORD +45 -0
- qyro-2.0.0.dist-info/WHEEL +5 -0
- qyro-2.0.0.dist-info/entry_points.txt +2 -0
- qyro-2.0.0.dist-info/licenses/LICENSE +21 -0
- qyro-2.0.0.dist-info/top_level.txt +1 -0
qyro/common/platform.py
ADDED
|
@@ -0,0 +1,609 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Nexus Platform Abstraction Layer
|
|
3
|
+
Provides cross-platform utilities for path handling, executable extensions,
|
|
4
|
+
and command execution. Abstracts away Windows vs Unix differences.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
import subprocess
|
|
10
|
+
import shutil
|
|
11
|
+
import platform
|
|
12
|
+
from typing import List, Optional, Tuple, Union
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from enum import Enum
|
|
15
|
+
|
|
16
|
+
from .logging import get_logger
|
|
17
|
+
|
|
18
|
+
logger = get_logger("nexus.platform")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class PlatformType(Enum):
|
|
22
|
+
"""Supported platform types."""
|
|
23
|
+
WINDOWS = "windows"
|
|
24
|
+
LINUX = "linux"
|
|
25
|
+
MACOS = "macos"
|
|
26
|
+
UNKNOWN = "unknown"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class Platform:
|
|
30
|
+
"""
|
|
31
|
+
Platform abstraction layer for cross-platform compatibility.
|
|
32
|
+
|
|
33
|
+
Provides utilities for:
|
|
34
|
+
- Path handling (Windows vs Unix paths)
|
|
35
|
+
- Executable extensions (.exe on Windows, none on Unix)
|
|
36
|
+
- Command execution and subprocess handling
|
|
37
|
+
- Platform-specific operations
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
_instance = None
|
|
41
|
+
|
|
42
|
+
def __new__(cls):
|
|
43
|
+
"""Singleton pattern to ensure consistent platform detection."""
|
|
44
|
+
if cls._instance is None:
|
|
45
|
+
cls._instance = super().__new__(cls)
|
|
46
|
+
cls._instance._initialized = False
|
|
47
|
+
return cls._instance
|
|
48
|
+
|
|
49
|
+
def __init__(self):
|
|
50
|
+
"""Initialize platform detection."""
|
|
51
|
+
if self._initialized:
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
self._initialized = True
|
|
55
|
+
self._detect_platform()
|
|
56
|
+
self._setup_platform_specifics()
|
|
57
|
+
|
|
58
|
+
def _detect_platform(self):
|
|
59
|
+
"""Detect the current platform."""
|
|
60
|
+
if sys.platform == 'win32':
|
|
61
|
+
self.type = PlatformType.WINDOWS
|
|
62
|
+
self.name = 'windows'
|
|
63
|
+
elif sys.platform == 'darwin':
|
|
64
|
+
self.type = PlatformType.MACOS
|
|
65
|
+
self.name = 'macos'
|
|
66
|
+
elif sys.platform.startswith('linux'):
|
|
67
|
+
self.type = PlatformType.LINUX
|
|
68
|
+
self.name = 'linux'
|
|
69
|
+
else:
|
|
70
|
+
self.type = PlatformType.UNKNOWN
|
|
71
|
+
self.name = 'unknown'
|
|
72
|
+
|
|
73
|
+
logger.info("platform_detected", platform=self.name, sys_platform=sys.platform)
|
|
74
|
+
|
|
75
|
+
def _setup_platform_specifics(self):
|
|
76
|
+
"""Setup platform-specific settings."""
|
|
77
|
+
# Path separator
|
|
78
|
+
self.path_sep = os.sep
|
|
79
|
+
self.path_list_sep = os.pathsep
|
|
80
|
+
|
|
81
|
+
# Executable extension
|
|
82
|
+
self.exe_extension = '.exe' if self.type == PlatformType.WINDOWS else ''
|
|
83
|
+
|
|
84
|
+
# Shell settings
|
|
85
|
+
self.use_shell = self.type == PlatformType.WINDOWS
|
|
86
|
+
|
|
87
|
+
# Default shell
|
|
88
|
+
if self.type == PlatformType.WINDOWS:
|
|
89
|
+
self.default_shell = os.environ.get('COMSPEC', 'cmd.exe')
|
|
90
|
+
else:
|
|
91
|
+
self.default_shell = os.environ.get('SHELL', '/bin/sh')
|
|
92
|
+
|
|
93
|
+
# Newline
|
|
94
|
+
self.newline = os.linesep
|
|
95
|
+
|
|
96
|
+
# Null device
|
|
97
|
+
self.null_device = 'NUL' if self.type == PlatformType.WINDOWS else '/dev/null'
|
|
98
|
+
|
|
99
|
+
# Temporary directory
|
|
100
|
+
self.temp_dir = os.environ.get('TEMP', '/tmp') if self.type == PlatformType.WINDOWS else '/tmp'
|
|
101
|
+
|
|
102
|
+
# Home directory
|
|
103
|
+
self.home_dir = os.path.expanduser('~')
|
|
104
|
+
|
|
105
|
+
# Path handling methods
|
|
106
|
+
|
|
107
|
+
def normalize_path(self, path: str) -> str:
|
|
108
|
+
"""
|
|
109
|
+
Normalize a path for the current platform.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
path: Path to normalize
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
Normalized path
|
|
116
|
+
"""
|
|
117
|
+
# Convert to absolute path
|
|
118
|
+
path = os.path.abspath(path)
|
|
119
|
+
|
|
120
|
+
# Normalize path separators
|
|
121
|
+
path = os.path.normpath(path)
|
|
122
|
+
|
|
123
|
+
return path
|
|
124
|
+
|
|
125
|
+
def join_path(self, *parts: str) -> str:
|
|
126
|
+
"""
|
|
127
|
+
Join path parts using the platform-specific separator.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
*parts: Path parts to join
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
Joined path
|
|
134
|
+
"""
|
|
135
|
+
return os.path.join(*parts)
|
|
136
|
+
|
|
137
|
+
def split_path(self, path: str) -> List[str]:
|
|
138
|
+
"""
|
|
139
|
+
Split a path into components.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
path: Path to split
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
List of path components
|
|
146
|
+
"""
|
|
147
|
+
return list(Path(path).parts)
|
|
148
|
+
|
|
149
|
+
def get_basename(self, path: str) -> str:
|
|
150
|
+
"""
|
|
151
|
+
Get the basename (filename) from a path.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
path: Path to extract basename from
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
Basename
|
|
158
|
+
"""
|
|
159
|
+
return os.path.basename(path)
|
|
160
|
+
|
|
161
|
+
def get_dirname(self, path: str) -> str:
|
|
162
|
+
"""
|
|
163
|
+
Get the directory name from a path.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
path: Path to extract directory from
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
Directory name
|
|
170
|
+
"""
|
|
171
|
+
return os.path.dirname(path)
|
|
172
|
+
|
|
173
|
+
def get_extension(self, path: str) -> str:
|
|
174
|
+
"""
|
|
175
|
+
Get the file extension from a path.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
path: Path to extract extension from
|
|
179
|
+
|
|
180
|
+
Returns:
|
|
181
|
+
File extension (including the dot)
|
|
182
|
+
"""
|
|
183
|
+
return os.path.splitext(path)[1]
|
|
184
|
+
|
|
185
|
+
def remove_extension(self, path: str) -> str:
|
|
186
|
+
"""
|
|
187
|
+
Remove the file extension from a path.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
path: Path to remove extension from
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
Path without extension
|
|
194
|
+
"""
|
|
195
|
+
return os.path.splitext(path)[0]
|
|
196
|
+
|
|
197
|
+
# Executable handling methods
|
|
198
|
+
|
|
199
|
+
def get_executable_name(self, name: str) -> str:
|
|
200
|
+
"""
|
|
201
|
+
Add platform-specific executable extension to a name.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
name: Base name of the executable
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
Name with appropriate extension
|
|
208
|
+
"""
|
|
209
|
+
if not name.endswith(self.exe_extension):
|
|
210
|
+
return name + self.exe_extension
|
|
211
|
+
return name
|
|
212
|
+
|
|
213
|
+
def is_executable(self, path: str) -> bool:
|
|
214
|
+
"""
|
|
215
|
+
Check if a file is executable.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
path: Path to check
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
True if executable, False otherwise
|
|
222
|
+
"""
|
|
223
|
+
if not os.path.isfile(path):
|
|
224
|
+
return False
|
|
225
|
+
|
|
226
|
+
if self.type == PlatformType.WINDOWS:
|
|
227
|
+
# On Windows, check for executable extensions
|
|
228
|
+
ext = self.get_extension(path).lower()
|
|
229
|
+
return ext in ['.exe', '.bat', '.cmd', '.ps1']
|
|
230
|
+
else:
|
|
231
|
+
# On Unix, check execute permission
|
|
232
|
+
return os.access(path, os.X_OK)
|
|
233
|
+
|
|
234
|
+
def which(self, command: str) -> Optional[str]:
|
|
235
|
+
"""
|
|
236
|
+
Find the full path to an executable command.
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
command: Command to find
|
|
240
|
+
|
|
241
|
+
Returns:
|
|
242
|
+
Full path if found, None otherwise
|
|
243
|
+
"""
|
|
244
|
+
return shutil.which(command)
|
|
245
|
+
|
|
246
|
+
# Command execution methods
|
|
247
|
+
|
|
248
|
+
def run_command(
|
|
249
|
+
self,
|
|
250
|
+
cmd: Union[str, List[str]],
|
|
251
|
+
cwd: Optional[str] = None,
|
|
252
|
+
env: Optional[dict] = None,
|
|
253
|
+
timeout: Optional[int] = None,
|
|
254
|
+
capture_output: bool = True,
|
|
255
|
+
shell: Optional[bool] = None,
|
|
256
|
+
text: bool = True,
|
|
257
|
+
) -> Tuple[int, str, str]:
|
|
258
|
+
"""
|
|
259
|
+
Run a command with cross-platform compatibility.
|
|
260
|
+
|
|
261
|
+
Args:
|
|
262
|
+
cmd: Command to run (string or list)
|
|
263
|
+
cwd: Working directory
|
|
264
|
+
env: Environment variables
|
|
265
|
+
timeout: Timeout in seconds
|
|
266
|
+
capture_output: Whether to capture stdout/stderr
|
|
267
|
+
shell: Whether to use shell (default: platform-specific)
|
|
268
|
+
text: Whether to return text instead of bytes
|
|
269
|
+
|
|
270
|
+
Returns:
|
|
271
|
+
Tuple of (returncode, stdout, stderr)
|
|
272
|
+
"""
|
|
273
|
+
if shell is None:
|
|
274
|
+
shell = self.use_shell
|
|
275
|
+
|
|
276
|
+
# Convert string command to list if needed
|
|
277
|
+
if isinstance(cmd, str) and not shell:
|
|
278
|
+
# On Unix, we need to split the command
|
|
279
|
+
if self.type != PlatformType.WINDOWS:
|
|
280
|
+
import shlex
|
|
281
|
+
cmd = shlex.split(cmd)
|
|
282
|
+
|
|
283
|
+
try:
|
|
284
|
+
result = subprocess.run(
|
|
285
|
+
cmd,
|
|
286
|
+
cwd=cwd,
|
|
287
|
+
env=env,
|
|
288
|
+
timeout=timeout,
|
|
289
|
+
capture_output=capture_output,
|
|
290
|
+
shell=shell,
|
|
291
|
+
text=text,
|
|
292
|
+
encoding='utf-8',
|
|
293
|
+
errors='replace'
|
|
294
|
+
)
|
|
295
|
+
return result.returncode, result.stdout, result.stderr
|
|
296
|
+
except FileNotFoundError:
|
|
297
|
+
return 1, "", f"Command not found: {cmd}"
|
|
298
|
+
except subprocess.TimeoutExpired:
|
|
299
|
+
return 1, "", f"Command timed out after {timeout} seconds"
|
|
300
|
+
except Exception as e:
|
|
301
|
+
return 1, "", str(e)
|
|
302
|
+
|
|
303
|
+
def kill_process(self, pid: int, force: bool = False) -> bool:
|
|
304
|
+
"""
|
|
305
|
+
Kill a process by PID.
|
|
306
|
+
|
|
307
|
+
Args:
|
|
308
|
+
pid: Process ID
|
|
309
|
+
force: Whether to force kill
|
|
310
|
+
|
|
311
|
+
Returns:
|
|
312
|
+
True if successful, False otherwise
|
|
313
|
+
"""
|
|
314
|
+
try:
|
|
315
|
+
if self.type == PlatformType.WINDOWS:
|
|
316
|
+
import signal
|
|
317
|
+
sig = signal.SIGTERM if not force else signal.SIGKILL
|
|
318
|
+
os.kill(pid, sig)
|
|
319
|
+
else:
|
|
320
|
+
import signal
|
|
321
|
+
sig = signal.SIGTERM if not force else signal.SIGKILL
|
|
322
|
+
os.kill(pid, sig)
|
|
323
|
+
return True
|
|
324
|
+
except Exception as e:
|
|
325
|
+
logger.warning("kill_process_failed", pid=pid, error=str(e))
|
|
326
|
+
return False
|
|
327
|
+
|
|
328
|
+
def kill_process_by_name(self, name: str, force: bool = False) -> bool:
|
|
329
|
+
"""
|
|
330
|
+
Kill processes by name.
|
|
331
|
+
|
|
332
|
+
Args:
|
|
333
|
+
name: Process name (exact match, no wildcards)
|
|
334
|
+
force: Whether to force kill
|
|
335
|
+
|
|
336
|
+
Returns:
|
|
337
|
+
True if successful, False otherwise
|
|
338
|
+
"""
|
|
339
|
+
try:
|
|
340
|
+
if self.type == PlatformType.WINDOWS:
|
|
341
|
+
# Use wmic to get exact process names and then kill by PID
|
|
342
|
+
# This prevents killing processes that start with the same name
|
|
343
|
+
cmd = ['wmic', 'process', 'where', f'name="{name}"', 'get', 'processid', '/value']
|
|
344
|
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
345
|
+
|
|
346
|
+
if result.returncode == 0 and result.stdout.strip():
|
|
347
|
+
# Extract PIDs from wmic output
|
|
348
|
+
lines = result.stdout.strip().split('\n')
|
|
349
|
+
for line in lines:
|
|
350
|
+
if line.strip().startswith('ProcessId='):
|
|
351
|
+
try:
|
|
352
|
+
pid = int(line.split('=')[1].strip())
|
|
353
|
+
if pid > 0:
|
|
354
|
+
self.kill_process(pid, force)
|
|
355
|
+
except (ValueError, IndexError):
|
|
356
|
+
continue
|
|
357
|
+
return True
|
|
358
|
+
else:
|
|
359
|
+
# Fallback to tasklist to find exact matches
|
|
360
|
+
cmd = ['tasklist', '/FI', f'IMAGENAME eq {name}', '/FO', 'CSV']
|
|
361
|
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
362
|
+
|
|
363
|
+
if result.returncode == 0 and result.stdout.strip():
|
|
364
|
+
lines = result.stdout.strip().split('\n')
|
|
365
|
+
for line in lines[1:]: # Skip header
|
|
366
|
+
if line:
|
|
367
|
+
try:
|
|
368
|
+
# CSV format: "process_name.exe",PID,...
|
|
369
|
+
process_name = line.split(',')[0].strip().strip('"')
|
|
370
|
+
if process_name.lower() == name.lower():
|
|
371
|
+
pid = int(line.split(',')[1].strip().strip('"'))
|
|
372
|
+
self.kill_process(pid, force)
|
|
373
|
+
except (ValueError, IndexError):
|
|
374
|
+
continue
|
|
375
|
+
return True
|
|
376
|
+
else:
|
|
377
|
+
# Use pgrep to get exact process IDs on Unix systems
|
|
378
|
+
cmd = ['pgrep', '-f', f'^{name}$|^./{name}$']
|
|
379
|
+
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
380
|
+
|
|
381
|
+
if result.returncode == 0 and result.stdout.strip():
|
|
382
|
+
pids = result.stdout.strip().split('\n')
|
|
383
|
+
for pid_str in pids:
|
|
384
|
+
try:
|
|
385
|
+
pid = int(pid_str.strip())
|
|
386
|
+
self.kill_process(pid, force)
|
|
387
|
+
except ValueError:
|
|
388
|
+
continue
|
|
389
|
+
else:
|
|
390
|
+
# Fallback: use pkill with exact match
|
|
391
|
+
cmd = ['pkill', '-x', name] # -x for exact match
|
|
392
|
+
if force:
|
|
393
|
+
cmd.insert(1, '-9')
|
|
394
|
+
result = subprocess.run(cmd, capture_output=True)
|
|
395
|
+
return result.returncode == 0
|
|
396
|
+
return True
|
|
397
|
+
except Exception as e:
|
|
398
|
+
logger.warning("kill_process_by_name_failed", name=name, error=str(e))
|
|
399
|
+
return False
|
|
400
|
+
|
|
401
|
+
# File system methods
|
|
402
|
+
|
|
403
|
+
def ensure_dir(self, path: str) -> str:
|
|
404
|
+
"""
|
|
405
|
+
Ensure a directory exists, creating it if necessary.
|
|
406
|
+
|
|
407
|
+
Args:
|
|
408
|
+
path: Directory path
|
|
409
|
+
|
|
410
|
+
Returns:
|
|
411
|
+
Path to the directory
|
|
412
|
+
"""
|
|
413
|
+
os.makedirs(path, exist_ok=True)
|
|
414
|
+
return path
|
|
415
|
+
|
|
416
|
+
def remove_file(self, path: str) -> bool:
|
|
417
|
+
"""
|
|
418
|
+
Remove a file if it exists.
|
|
419
|
+
|
|
420
|
+
Args:
|
|
421
|
+
path: Path to file
|
|
422
|
+
|
|
423
|
+
Returns:
|
|
424
|
+
True if removed or didn't exist, False on error
|
|
425
|
+
"""
|
|
426
|
+
try:
|
|
427
|
+
if os.path.exists(path):
|
|
428
|
+
os.remove(path)
|
|
429
|
+
return True
|
|
430
|
+
except Exception as e:
|
|
431
|
+
logger.warning("remove_file_failed", path=path, error=str(e))
|
|
432
|
+
return False
|
|
433
|
+
|
|
434
|
+
def remove_dir(self, path: str) -> bool:
|
|
435
|
+
"""
|
|
436
|
+
Remove a directory if it exists.
|
|
437
|
+
|
|
438
|
+
Args:
|
|
439
|
+
path: Path to directory
|
|
440
|
+
|
|
441
|
+
Returns:
|
|
442
|
+
True if removed or didn't exist, False on error
|
|
443
|
+
"""
|
|
444
|
+
try:
|
|
445
|
+
if os.path.exists(path):
|
|
446
|
+
shutil.rmtree(path)
|
|
447
|
+
return True
|
|
448
|
+
except Exception as e:
|
|
449
|
+
logger.warning("remove_dir_failed", path=path, error=str(e))
|
|
450
|
+
return False
|
|
451
|
+
|
|
452
|
+
# Environment methods
|
|
453
|
+
|
|
454
|
+
def get_env_var(self, name: str, default: Optional[str] = None) -> Optional[str]:
|
|
455
|
+
"""
|
|
456
|
+
Get an environment variable.
|
|
457
|
+
|
|
458
|
+
Args:
|
|
459
|
+
name: Variable name
|
|
460
|
+
default: Default value if not found
|
|
461
|
+
|
|
462
|
+
Returns:
|
|
463
|
+
Variable value or default
|
|
464
|
+
"""
|
|
465
|
+
return os.environ.get(name, default)
|
|
466
|
+
|
|
467
|
+
def set_env_var(self, name: str, value: str):
|
|
468
|
+
"""
|
|
469
|
+
Set an environment variable.
|
|
470
|
+
|
|
471
|
+
Args:
|
|
472
|
+
name: Variable name
|
|
473
|
+
value: Variable value
|
|
474
|
+
"""
|
|
475
|
+
os.environ[name] = value
|
|
476
|
+
|
|
477
|
+
def get_path_env(self) -> List[str]:
|
|
478
|
+
"""
|
|
479
|
+
Get the PATH environment variable as a list.
|
|
480
|
+
|
|
481
|
+
Returns:
|
|
482
|
+
List of paths
|
|
483
|
+
"""
|
|
484
|
+
path_str = self.get_env_var('PATH', '')
|
|
485
|
+
return path_str.split(self.path_list_sep) if path_str else []
|
|
486
|
+
|
|
487
|
+
def add_to_path(self, path: str, prepend: bool = True):
|
|
488
|
+
"""
|
|
489
|
+
Add a path to the PATH environment variable.
|
|
490
|
+
|
|
491
|
+
Args:
|
|
492
|
+
path: Path to add
|
|
493
|
+
prepend: Whether to add to the beginning
|
|
494
|
+
"""
|
|
495
|
+
current_path = self.get_path_env()
|
|
496
|
+
|
|
497
|
+
if path in current_path:
|
|
498
|
+
return # Already in PATH
|
|
499
|
+
|
|
500
|
+
if prepend:
|
|
501
|
+
current_path.insert(0, path)
|
|
502
|
+
else:
|
|
503
|
+
current_path.append(path)
|
|
504
|
+
|
|
505
|
+
self.set_env_var('PATH', self.path_list_sep.join(current_path))
|
|
506
|
+
|
|
507
|
+
# Utility methods
|
|
508
|
+
|
|
509
|
+
def is_windows(self) -> bool:
|
|
510
|
+
"""Check if running on Windows."""
|
|
511
|
+
return self.type == PlatformType.WINDOWS
|
|
512
|
+
|
|
513
|
+
def is_linux(self) -> bool:
|
|
514
|
+
"""Check if running on Linux."""
|
|
515
|
+
return self.type == PlatformType.LINUX
|
|
516
|
+
|
|
517
|
+
def is_macos(self) -> bool:
|
|
518
|
+
"""Check if running on macOS."""
|
|
519
|
+
return self.type == PlatformType.MACOS
|
|
520
|
+
|
|
521
|
+
def get_platform_info(self) -> dict:
|
|
522
|
+
"""
|
|
523
|
+
Get platform information.
|
|
524
|
+
|
|
525
|
+
Returns:
|
|
526
|
+
Dictionary with platform details
|
|
527
|
+
"""
|
|
528
|
+
return {
|
|
529
|
+
'type': self.type.value,
|
|
530
|
+
'name': self.name,
|
|
531
|
+
'system': platform.system(),
|
|
532
|
+
'release': platform.release(),
|
|
533
|
+
'version': platform.version(),
|
|
534
|
+
'machine': platform.machine(),
|
|
535
|
+
'processor': platform.processor(),
|
|
536
|
+
'python_version': platform.python_version(),
|
|
537
|
+
'executable_extension': self.exe_extension,
|
|
538
|
+
'path_separator': self.path_sep,
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
def print_platform_info(self):
|
|
542
|
+
"""Print platform information."""
|
|
543
|
+
info = self.get_platform_info()
|
|
544
|
+
print("\n" + "=" * 60)
|
|
545
|
+
print("PLATFORM INFORMATION")
|
|
546
|
+
print("=" * 60)
|
|
547
|
+
for key, value in info.items():
|
|
548
|
+
print(f"{key:20}: {value}")
|
|
549
|
+
print("=" * 60 + "\n")
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
# Global platform instance
|
|
553
|
+
_platform = None
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
def get_platform() -> Platform:
|
|
557
|
+
"""
|
|
558
|
+
Get the global platform instance.
|
|
559
|
+
|
|
560
|
+
Returns:
|
|
561
|
+
Platform instance
|
|
562
|
+
"""
|
|
563
|
+
global _platform
|
|
564
|
+
if _platform is None:
|
|
565
|
+
_platform = Platform()
|
|
566
|
+
return _platform
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
# Convenience functions for common operations
|
|
570
|
+
|
|
571
|
+
def normalize_path(path: str) -> str:
|
|
572
|
+
"""Normalize a path for the current platform."""
|
|
573
|
+
return get_platform().normalize_path(path)
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
def join_path(*parts: str) -> str:
|
|
577
|
+
"""Join path parts using the platform-specific separator."""
|
|
578
|
+
return get_platform().join_path(*parts)
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
def get_executable_name(name: str) -> str:
|
|
582
|
+
"""Add platform-specific executable extension to a name."""
|
|
583
|
+
return get_platform().get_executable_name(name)
|
|
584
|
+
|
|
585
|
+
|
|
586
|
+
def run_command(cmd: Union[str, List[str]], **kwargs) -> Tuple[int, str, str]:
|
|
587
|
+
"""Run a command with cross-platform compatibility."""
|
|
588
|
+
return get_platform().run_command(cmd, **kwargs)
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
def is_windows() -> bool:
|
|
592
|
+
"""Check if running on Windows."""
|
|
593
|
+
return get_platform().is_windows()
|
|
594
|
+
|
|
595
|
+
|
|
596
|
+
def is_linux() -> bool:
|
|
597
|
+
"""Check if running on Linux."""
|
|
598
|
+
return get_platform().is_linux()
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
def is_macos() -> bool:
|
|
602
|
+
"""Check if running on macOS."""
|
|
603
|
+
return get_platform().is_macos()
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
if __name__ == "__main__":
|
|
607
|
+
# Print platform information when executed directly
|
|
608
|
+
platform = get_platform()
|
|
609
|
+
platform.print_platform_info()
|