""" .. codeauthor:: Tsuyoshi Hombashi """ import abc import os from typing import Optional, Tuple, cast from ._common import PathType, Platform, PlatformType, normalize_platform, unprintable_ascii_chars from .error import ReservedNameError, ValidationError class BaseFile: _INVALID_PATH_CHARS = "".join(unprintable_ascii_chars) _INVALID_FILENAME_CHARS = _INVALID_PATH_CHARS + "/" _INVALID_WIN_PATH_CHARS = _INVALID_PATH_CHARS + ':*?"<>|\t\n\r\x0b\x0c' _INVALID_WIN_FILENAME_CHARS = _INVALID_FILENAME_CHARS + _INVALID_WIN_PATH_CHARS + "\\" _ERROR_MSG_TEMPLATE = "invalid char found: invalids=({invalid}), value={value}" @property def platform(self) -> Platform: return self.__platform @property def reserved_keywords(self) -> Tuple[str, ...]: return tuple() @property def min_len(self) -> int: return self._min_len @property def max_len(self) -> int: return self._max_len def __init__( self, min_len: Optional[int], max_len: Optional[int], check_reserved: bool, platform_max_len: Optional[int] = None, platform: PlatformType = None, ) -> None: self.__platform = normalize_platform(platform) self._check_reserved = check_reserved if min_len is None: min_len = 1 self._min_len = max(min_len, 1) if platform_max_len is None: platform_max_len = self._get_default_max_path_len() if max_len in [None, -1]: self._max_len = platform_max_len else: self._max_len = cast(int, max_len) self._max_len = min(self._max_len, platform_max_len) self._validate_max_len() def _is_posix(self) -> bool: return self.platform == Platform.POSIX def _is_universal(self) -> bool: return self.platform == Platform.UNIVERSAL def _is_linux(self) -> bool: return self.platform == Platform.LINUX def _is_windows(self) -> bool: return self.platform == Platform.WINDOWS def _is_macos(self) -> bool: return self.platform == Platform.MACOS def _validate_max_len(self) -> None: if self.max_len < 1: raise ValueError("max_len must be greater or equals to one") if self.min_len > self.max_len: raise ValueError("min_len must be lower than max_len") def _get_default_max_path_len(self) -> int: if self._is_linux(): return 4096 if self._is_windows(): return 260 if self._is_posix() or self._is_macos(): return 1024 return 260 # universal class AbstractValidator(BaseFile, metaclass=abc.ABCMeta): @abc.abstractmethod def validate(self, value: PathType) -> None: # pragma: no cover pass def is_valid(self, value: PathType) -> bool: try: self.validate(value) except (TypeError, ValidationError): return False return True def _is_reserved_keyword(self, value: str) -> bool: return value in self.reserved_keywords class AbstractSanitizer(BaseFile, metaclass=abc.ABCMeta): @abc.abstractmethod def sanitize(self, value: PathType, replacement_text: str = "") -> PathType: # pragma: no cover pass class BaseValidator(AbstractValidator): def _validate_reserved_keywords(self, name: str) -> None: if not self._check_reserved: return root_name = self.__extract_root_name(name) if self._is_reserved_keyword(root_name.upper()): raise ReservedNameError( "'{}' is a reserved name".format(root_name), reusable_name=False, reserved_name=root_name, platform=self.platform, ) @staticmethod def __extract_root_name(path: str) -> str: return os.path.splitext(os.path.basename(path))[0]