"""Platform detection for Linux, macOS, and WSL environments.""" import os import platform import re from dataclasses import dataclass from enum import Enum from pathlib import Path from typing import Optional class Platform(Enum): """Supported platforms.""" LINUX = "linux" MACOS = "macos" WSL = "wsl" UNKNOWN = "unknown" @dataclass class PlatformInfo: """Information about the current platform.""" system: str release: str version: str machine: str platform: Platform is_wsl: bool wsl_version: Optional[str] = None class PlatformNotSupported(Exception): """Raised when the platform is not supported.""" pass class PlatformDetector: """Detects and provides information about the current platform.""" WSL_PATTERN = re.compile(r'(microsoft|wsl|WSL)', re.IGNORECASE) @staticmethod def detect() -> PlatformInfo: """Detect the current platform and return detailed information.""" system = platform.system() release = platform.release() version = platform.version() machine = platform.machine() is_wsl, wsl_version = PlatformDetector._detect_wsl() platform_enum = Platform.UNKNOWN if system == "Darwin": platform_enum = Platform.MACOS elif system == "Linux": if is_wsl: platform_enum = Platform.WSL else: platform_enum = Platform.LINUX return PlatformInfo( system=system, release=release, version=version, machine=machine, platform=platform_enum, is_wsl=is_wsl, wsl_version=wsl_version, ) @staticmethod def _detect_wsl() -> tuple[bool, Optional[str]]: """Detect if running in WSL and return version info.""" try: os_release_path = Path("/proc/sys/kernel/osrelease") if os_release_path.exists(): content = os_release_path.read_text().strip() if PlatformDetector.WSL_PATTERN.search(content): wsl_version = content return True, wsl_version except (IOError, PermissionError): pass try: version_path = Path("/proc/version") if version_path.exists(): content = version_path.read_text().strip() if PlatformDetector.WSL_PATTERN.search(content): return True, content except (IOError, PermissionError): pass if "microsoft" in platform.release().lower(): return True, platform.release() return False, None @staticmethod def is_linux() -> bool: """Check if running on Linux.""" return platform.system() == "Linux" and not PlatformDetector._detect_wsl()[0] @staticmethod def is_macos() -> bool: """Check if running on macOS.""" return platform.system() == "Darwin" @staticmethod def is_wsl() -> bool: """Check if running on WSL.""" return PlatformDetector._detect_wsl()[0] @staticmethod def is_supported() -> bool: """Check if the current platform is supported.""" return PlatformDetector.detect().platform != Platform.UNKNOWN @staticmethod def get_shell_config_dir() -> Path: """Get the platform-specific shell configuration directory.""" info = PlatformDetector.detect() if info.platform == Platform.MACOS: return Path.home() / ".config" elif info.platform in (Platform.LINUX, Platform.WSL): xdg_config = os.environ.get("XDG_CONFIG_HOME") if xdg_config: return Path(xdg_config) return Path.home() / ".config" else: return Path.home() @staticmethod def get_home_dir() -> Path: """Get the user's home directory.""" return Path.home() @staticmethod def get_backup_dir() -> Path: """Get the default backup directory based on platform.""" return PlatformDetector.get_home_dir() / ".dev-env-sync-backups" @staticmethod def get_editor_config_dir(editor: str) -> Path: """Get the platform-specific config directory for an editor.""" info = PlatformDetector.detect() home = Path.home() if editor == "vscode": if info.platform == Platform.MACOS: return home / "Library/Application Support/Code/User" else: xdg_config = os.environ.get("XDG_CONFIG_HOME") if xdg_config: return Path(xdg_config) / "Code/User" return home / ".config/Code/User" elif editor == "neovim": if info.platform == Platform.MACOS: return home / ".config/nvim" else: xdg_config = os.environ.get("XDG_CONFIG_HOME") if xdg_config: return Path(xdg_config) / "nvim" return home / ".config/nvim" elif editor == "vim": if info.platform == Platform.MACOS: return home else: return home else: return PlatformDetector.get_shell_config_dir() @staticmethod def get_package_manager() -> str: """Get the appropriate package manager for the platform.""" info = PlatformDetector.detect() if info.platform == Platform.MACOS: return "brew" elif info.platform == Platform.WSL: if PlatformDetector._has_command("apt"): return "apt" elif PlatformDetector._has_command("dnf"): return "dnf" elif PlatformDetector._has_command("yum"): return "yum" elif info.platform == Platform.LINUX: if PlatformDetector._has_command("apt"): return "apt" elif PlatformDetector._has_command("dnf"): return "dnf" elif PlatformDetector._has_command("yum"): return "yum" elif PlatformDetector._has_command("pacman"): return "pacman" return "unknown" @staticmethod def _has_command(cmd: str) -> bool: """Check if a command is available.""" import shutil return shutil.which(cmd) is not None @staticmethod def normalize_path(path: str) -> Path: """Normalize a path for the current platform.""" path_obj = Path(path).expanduser() if not path_obj.is_absolute(): if path.startswith("~"): path_obj = Path.home() / path[2:] else: path_obj = Path.cwd() / path_obj return path_obj @staticmethod def get_path_separator() -> str: """Get the path separator for the current platform.""" return "/" if platform.system() != "Windows" else "\\" @staticmethod def get_line_ending() -> str: """Get the appropriate line ending for the current platform.""" info = PlatformDetector.detect() if info.platform == Platform.MACOS and platform.mac_ver()[0].startswith("10"): return "\r" return "\n"