runtime_compat.py 2.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """
  4. Runtime compatibility helpers.
  5. """
  6. from __future__ import annotations
  7. import os
  8. import re
  9. import sys
  10. from pathlib import Path
  11. from typing import Union
  12. def enable_windows_utf8_stdio(*, skip_in_pytest: bool = False) -> bool:
  13. """Enable UTF-8 stdio wrappers on Windows.
  14. Returns:
  15. True if wrapping was applied, False otherwise.
  16. """
  17. if sys.platform != "win32":
  18. return False
  19. if skip_in_pytest and os.environ.get("PYTEST_CURRENT_TEST"):
  20. return False
  21. stdout_encoding = str(getattr(sys.stdout, "encoding", "") or "").lower()
  22. stderr_encoding = str(getattr(sys.stderr, "encoding", "") or "").lower()
  23. if stdout_encoding == "utf-8" and stderr_encoding == "utf-8":
  24. return False
  25. try:
  26. import io
  27. if hasattr(sys.stdout, "buffer"):
  28. sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
  29. if hasattr(sys.stderr, "buffer"):
  30. sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8")
  31. return True
  32. except Exception:
  33. return False
  34. _WIN_POSIX_DRIVE_RE = re.compile(r"^/(?P<drive>[a-zA-Z])/(?P<rest>.*)$")
  35. _WIN_WSL_MNT_DRIVE_RE = re.compile(r"^/mnt/(?P<drive>[a-zA-Z])/(?P<rest>.*)$")
  36. def normalize_windows_path(value: Union[str, Path]) -> Path:
  37. """
  38. 将 Windows 上常见的 POSIX 风格路径规范化为 Windows 盘符路径。
  39. 典型来源:
  40. - Git Bash / MSYS: /d/desktop/... => D:/desktop/...
  41. - WSL: /mnt/d/desktop/... => D:/desktop/...
  42. 非 Windows 平台直接返回 Path(value)。
  43. """
  44. if sys.platform != "win32":
  45. return Path(value)
  46. raw = str(value).strip()
  47. if not raw:
  48. return Path(raw)
  49. m = _WIN_WSL_MNT_DRIVE_RE.match(raw)
  50. if m:
  51. drive = m.group("drive").upper()
  52. rest = m.group("rest")
  53. return Path(f"{drive}:/{rest}")
  54. m = _WIN_POSIX_DRIVE_RE.match(raw)
  55. if m:
  56. drive = m.group("drive").upper()
  57. rest = m.group("rest")
  58. return Path(f"{drive}:/{rest}")
  59. return Path(value)