Files
revernomad17 06f0c6eb92 fix: guard against None stdout in fix_console_encoding for pythonw.exe
When launched via pythonw.exe (no console), sys.stdout/stderr are None.
Accessing .encoding on None raised AttributeError, caught by the GUI's
pipeline import guard and shown as 'Failed to load pipeline'. Added
None check before the encoding check in fix_console_encoding(), added
a test, and documented the pitfall in CLAUDE.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 10:10:13 +07:00

111 lines
3.4 KiB
Python

"""
arma_modlist_tools.compat
~~~~~~~~~~~~~~~~~~~~~~~~~
OS detection and cross-platform utilities shared by all CLI scripts.
Supported platforms:
- Windows / Windows Server (sys.platform == "win32")
- Ubuntu / Ubuntu Server (sys.platform == "linux")
Typical usage::
from arma_modlist_tools.compat import is_windows, get_os_label, fix_console_encoding
fix_console_encoding() # call once at script start on Windows
print(get_os_label()) # "Windows Server", "Ubuntu", etc.
"""
from __future__ import annotations
import io
import platform
import sys
# ---------------------------------------------------------------------------
# Platform detection
# ---------------------------------------------------------------------------
def is_windows() -> bool:
"""Return ``True`` on Windows and Windows Server."""
return sys.platform == "win32"
def is_linux() -> bool:
"""Return ``True`` on Linux (Ubuntu, Ubuntu Server, and other distros)."""
return sys.platform == "linux"
def get_os_label() -> str:
"""
Return a human-readable OS label.
Possible values: ``"Windows"``, ``"Windows Server"``, ``"Ubuntu"``,
``"Ubuntu Server"``, ``"Linux"``, ``"Unknown"``.
"""
if is_windows():
ver = platform.version()
# Windows Server versions contain "Server" in the version string
# e.g. "10.0.17763 ... Windows Server 2019 ..."
if "Server" in platform.version() or "Server" in platform.uname().version:
return "Windows Server"
return "Windows"
if is_linux():
# Read /etc/os-release for distro name
os_release = _read_os_release()
name = os_release.get("NAME", "").lower()
if "ubuntu" in name:
# Distinguish desktop vs server: server images have no display server
if _is_headless():
return "Ubuntu Server"
return "Ubuntu"
return "Linux"
return "Unknown"
def _read_os_release() -> dict[str, str]:
"""Parse /etc/os-release into a dict (Linux only)."""
result: dict[str, str] = {}
try:
with open("/etc/os-release", encoding="utf-8") as f:
for line in f:
line = line.strip()
if "=" in line and not line.startswith("#"):
k, _, v = line.partition("=")
result[k] = v.strip('"')
except OSError:
pass
return result
def _is_headless() -> bool:
"""Return True if no graphical display server is detected (headless/server)."""
import os
# Check for common display environment variables
return not (os.environ.get("DISPLAY") or os.environ.get("WAYLAND_DISPLAY"))
# ---------------------------------------------------------------------------
# Console encoding
# ---------------------------------------------------------------------------
def fix_console_encoding() -> None:
"""
Force UTF-8 output on Windows terminals that default to cp1252.
Call once at the top of any CLI script that uses Unicode characters
(checkmarks, arrows, etc.). No-op on Linux.
"""
if not is_windows():
return
if sys.stdout is None or sys.stderr is None:
return
if sys.stdout.encoding and sys.stdout.encoding.lower() == "utf-8":
return
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8", errors="replace")