_common.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. """
  2. .. codeauthor:: Tsuyoshi Hombashi <tsuyoshi.hombashi@gmail.com>
  3. """
  4. import ntpath
  5. import platform
  6. import re
  7. import string
  8. import sys
  9. from pathlib import PurePath
  10. from typing import Any, Final, Optional
  11. from ._const import Platform
  12. from ._types import PathType, PlatformType
  13. _re_whitespaces: Final = re.compile(r"^[\s]+$")
  14. def validate_pathtype(
  15. text: PathType, allow_whitespaces: bool = False, error_msg: Optional[str] = None
  16. ) -> None:
  17. from .error import ErrorReason, ValidationError
  18. if _is_not_null_string(text) or isinstance(text, PurePath):
  19. return
  20. if allow_whitespaces and _re_whitespaces.search(str(text)):
  21. return
  22. if is_null_string(text):
  23. raise ValidationError(reason=ErrorReason.NULL_NAME)
  24. raise TypeError(f"text must be a string: actual={type(text)}")
  25. def to_str(name: PathType) -> str:
  26. if isinstance(name, PurePath):
  27. return str(name)
  28. return name
  29. def is_nt_abspath(value: str) -> bool:
  30. ver_info = sys.version_info[:2]
  31. if ver_info <= (3, 10):
  32. if value.startswith("\\\\"):
  33. return True
  34. elif ver_info >= (3, 13):
  35. return ntpath.isabs(value)
  36. drive, _tail = ntpath.splitdrive(value)
  37. return ntpath.isabs(value) and len(drive) > 0
  38. def is_null_string(value: Any) -> bool:
  39. if value is None:
  40. return True
  41. try:
  42. return len(value.strip()) == 0
  43. except AttributeError:
  44. return False
  45. def _is_not_null_string(value: Any) -> bool:
  46. try:
  47. return len(value.strip()) > 0
  48. except AttributeError:
  49. return False
  50. def _get_unprintable_ascii_chars() -> list[str]:
  51. return [chr(c) for c in range(128) if chr(c) not in string.printable]
  52. unprintable_ascii_chars: Final = tuple(_get_unprintable_ascii_chars())
  53. def _get_ascii_symbols() -> list[str]:
  54. symbol_list: list[str] = []
  55. for i in range(128):
  56. c = chr(i)
  57. if c in unprintable_ascii_chars or c in string.digits + string.ascii_letters:
  58. continue
  59. symbol_list.append(c)
  60. return symbol_list
  61. ascii_symbols: Final = tuple(_get_ascii_symbols())
  62. __RE_UNPRINTABLE_CHARS: Final = re.compile(
  63. "[{}]".format(re.escape("".join(unprintable_ascii_chars))), re.UNICODE
  64. )
  65. __RE_ANSI_ESCAPE: Final = re.compile(
  66. r"(?:\x1B[@-Z\\-_]|[\x80-\x9A\x9C-\x9F]|(?:\x1B\[|\x9B)[0-?]*[ -/]*[@-~])"
  67. )
  68. def validate_unprintable_char(text: str) -> None:
  69. from .error import InvalidCharError
  70. match_list = __RE_UNPRINTABLE_CHARS.findall(to_str(text))
  71. if match_list:
  72. raise InvalidCharError(f"unprintable character found: {match_list}")
  73. def replace_unprintable_char(text: str, replacement_text: str = "") -> str:
  74. try:
  75. return __RE_UNPRINTABLE_CHARS.sub(replacement_text, text)
  76. except (TypeError, AttributeError):
  77. raise TypeError("text must be a string")
  78. def replace_ansi_escape(text: str, replacement_text: str = "") -> str:
  79. try:
  80. return __RE_ANSI_ESCAPE.sub(replacement_text, text)
  81. except (TypeError, AttributeError):
  82. raise TypeError("text must be a string")
  83. def normalize_platform(name: Optional[PlatformType]) -> Platform:
  84. if isinstance(name, Platform):
  85. return name
  86. if not name:
  87. return Platform.UNIVERSAL
  88. platform_str = name.strip().casefold()
  89. if platform_str == "posix":
  90. return Platform.POSIX
  91. if platform_str == "auto":
  92. platform_str = platform.system().casefold()
  93. if platform_str in ["linux"]:
  94. return Platform.LINUX
  95. if platform_str and platform_str.startswith("win"):
  96. return Platform.WINDOWS
  97. if platform_str in ["mac", "macos", "darwin"]:
  98. return Platform.MACOS
  99. return Platform.UNIVERSAL
  100. def findall_to_str(match: list[Any]) -> str:
  101. uniq_list = {repr(text) for text in match}
  102. return ", ".join(uniq_list)
  103. def truncate_str(text: str, encoding: str, max_bytes: int) -> str:
  104. str_bytes = text.encode(encoding)
  105. str_bytes = str_bytes[:max_bytes]
  106. # last char might be malformed, ignore it
  107. return str_bytes.decode(encoding, "ignore")