Files
dev-env-sync/dev_env_sync/core/platform.py
7000pctAUTO 36d92c28fe
Some checks failed
CI / test (push) Failing after 12s
Add core modules: config and platform detection
2026-01-30 04:07:50 +00:00

234 lines
7.3 KiB
Python

"""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"