summaryrefslogtreecommitdiffstats
path: root/contrib/python/fonttools/fontTools/misc/filesystem/_osfs.py
diff options
context:
space:
mode:
authorrobot-piglet <[email protected]>2025-07-31 11:14:11 +0300
committerrobot-piglet <[email protected]>2025-07-31 12:10:37 +0300
commite177928be72df9669dbb830824b4233a33c8723f (patch)
treea91d4ec6bbe7dc221c049475a91255c2996fd84e /contrib/python/fonttools/fontTools/misc/filesystem/_osfs.py
parenta1700abf3c749b43117e757deb259d2a7bcdf46a (diff)
Intermediate changes
commit_hash:60aaacde4a6a0fb68b6435d7f100365d0c77d64d
Diffstat (limited to 'contrib/python/fonttools/fontTools/misc/filesystem/_osfs.py')
-rw-r--r--contrib/python/fonttools/fontTools/misc/filesystem/_osfs.py164
1 files changed, 164 insertions, 0 deletions
diff --git a/contrib/python/fonttools/fontTools/misc/filesystem/_osfs.py b/contrib/python/fonttools/fontTools/misc/filesystem/_osfs.py
new file mode 100644
index 00000000000..3f533bb8da4
--- /dev/null
+++ b/contrib/python/fonttools/fontTools/misc/filesystem/_osfs.py
@@ -0,0 +1,164 @@
+from __future__ import annotations
+
+import errno
+import platform
+import shutil
+import stat
+import typing
+from os import PathLike
+from pathlib import Path
+
+from ._base import FS
+from ._errors import (
+ CreateFailed,
+ DirectoryExpected,
+ DirectoryNotEmpty,
+ FileExpected,
+ IllegalDestination,
+ ResourceError,
+ ResourceNotFound,
+)
+from ._info import Info
+from ._path import isbase
+
+if typing.TYPE_CHECKING:
+ from collections.abc import Collection
+ from typing import IO, Any
+
+ from ._subfs import SubFS
+
+
+_WINDOWS_PLATFORM = platform.system() == "Windows"
+
+
+class OSFS(FS):
+ """Filesystem for a directory on the local disk.
+
+ A thin layer on top of `pathlib.Path`.
+ """
+
+ def __init__(self, root: str | PathLike, create: bool = False):
+ super().__init__()
+ self._root = Path(root).resolve()
+ if create:
+ self._root.mkdir(parents=True, exist_ok=True)
+ else:
+ if not self._root.is_dir():
+ raise CreateFailed(
+ f"unable to create OSFS: {root!r} does not exist or is not a directory"
+ )
+
+ def _abs(self, rel_path: str) -> Path:
+ self.check()
+ return (self._root / rel_path.strip("/")).resolve()
+
+ def open(self, path: str, mode: str = "rb", **kwargs) -> IO[Any]:
+ try:
+ return self._abs(path).open(mode, **kwargs)
+ except FileNotFoundError:
+ raise ResourceNotFound(f"No such file or directory: {path!r}")
+
+ def exists(self, path: str) -> bool:
+ return self._abs(path).exists()
+
+ def isdir(self, path: str) -> bool:
+ return self._abs(path).is_dir()
+
+ def isfile(self, path: str) -> bool:
+ return self._abs(path).is_file()
+
+ def listdir(self, path: str) -> list[str]:
+ return [p.name for p in self._abs(path).iterdir()]
+
+ def _mkdir(self, path: str, parents: bool = False, exist_ok: bool = False) -> SubFS:
+ self._abs(path).mkdir(parents=parents, exist_ok=exist_ok)
+ return self.opendir(path)
+
+ def makedir(self, path: str, recreate: bool = False) -> SubFS:
+ return self._mkdir(path, parents=False, exist_ok=recreate)
+
+ def makedirs(self, path: str, recreate: bool = False) -> SubFS:
+ return self._mkdir(path, parents=True, exist_ok=recreate)
+
+ def getinfo(self, path: str, namespaces: Collection[str] | None = None) -> Info:
+ path = self._abs(path)
+ if not path.exists():
+ raise ResourceNotFound(f"No such file or directory: {str(path)!r}")
+ info = {
+ "basic": {
+ "name": path.name,
+ "is_dir": path.is_dir(),
+ }
+ }
+ namespaces = namespaces or ()
+ if "details" in namespaces:
+ stat_result = path.stat()
+ details = info["details"] = {
+ "accessed": stat_result.st_atime,
+ "modified": stat_result.st_mtime,
+ "size": stat_result.st_size,
+ "type": stat.S_IFMT(stat_result.st_mode),
+ "created": getattr(stat_result, "st_birthtime", None),
+ }
+ ctime_key = "created" if _WINDOWS_PLATFORM else "metadata_changed"
+ details[ctime_key] = stat_result.st_ctime
+ return Info(info)
+
+ def remove(self, path: str):
+ path = self._abs(path)
+ try:
+ path.unlink()
+ except FileNotFoundError:
+ raise ResourceNotFound(f"No such file or directory: {str(path)!r}")
+ except OSError as e:
+ if path.is_dir():
+ raise FileExpected(f"path {str(path)!r} should be a file")
+ else:
+ raise ResourceError(f"unable to remove {str(path)!r}: {e}")
+
+ def removedir(self, path: str):
+ try:
+ self._abs(path).rmdir()
+ except NotADirectoryError:
+ raise DirectoryExpected(f"path {path!r} should be a directory")
+ except OSError as e:
+ if e.errno == errno.ENOTEMPTY:
+ raise DirectoryNotEmpty(f"Directory not empty: {path!r}")
+ else:
+ raise ResourceError(f"unable to remove {path!r}: {e}")
+
+ def removetree(self, path: str):
+ shutil.rmtree(self._abs(path))
+
+ def movedir(self, src_dir: str, dst_dir: str, create: bool = False):
+ if isbase(src_dir, dst_dir):
+ raise IllegalDestination(f"cannot move {src_dir!r} to {dst_dir!r}")
+ src_path = self._abs(src_dir)
+ if not src_path.exists():
+ raise ResourceNotFound(f"Source {src_dir!r} does not exist")
+ elif not src_path.is_dir():
+ raise DirectoryExpected(f"Source {src_dir!r} should be a directory")
+ dst_path = self._abs(dst_dir)
+ if not create and not dst_path.exists():
+ raise ResourceNotFound(f"Destination {dst_dir!r} does not exist")
+ if dst_path.is_file():
+ raise DirectoryExpected(f"Destination {dst_dir!r} should be a directory")
+ if create:
+ dst_path.parent.mkdir(parents=True, exist_ok=True)
+ if dst_path.exists():
+ if list(dst_path.iterdir()):
+ raise DirectoryNotEmpty(f"Destination {dst_dir!r} is not empty")
+ elif _WINDOWS_PLATFORM:
+ # on Unix os.rename silently replaces an empty dst_dir whereas on
+ # Windows it always raises FileExistsError, empty or not.
+ dst_path.rmdir()
+ src_path.rename(dst_path)
+
+ def getsyspath(self, path: str) -> str:
+ return str(self._abs(path))
+
+ def __repr__(self) -> str:
+ return f"{self.__class__.__name__}({str(self._root)!r})"
+
+ def __str__(self) -> str:
+ return f"<{self.__class__.__name__.lower()} '{self._root}'>"