# -*- coding: utf-8 -*-
"""
lories.core.configs.directories
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
"""
from __future__ import annotations
import os
from collections.abc import Mapping
from pathlib import Path, PosixPath, WindowsPath
from typing import Dict, Optional
# FIXME: Remove this once Python >= 3.9 is a requirement
try:
from typing import LiteralString
except ImportError:
from typing_extensions import LiteralString
class Directories:
TYPE = "directories"
LIB = "lib_dir"
LOG = "log_dir"
TMP = "tmp_dir"
DATA = "data_dir"
CONF = "conf_dir"
KEYS = [LIB, LOG, TMP, DATA, CONF]
def __init__(
self,
lib_dir: str = None,
log_dir: str = None,
tmp_dir: str = None,
data_dir: str = None,
conf_dir: str = None,
):
self.lib = lib_dir
self.log = log_dir
self.tmp = tmp_dir
self.data = data_dir
self.conf = conf_dir
# noinspection PyProtectedMember
def __repr__(self) -> str:
attrs = ["conf", "data", "tmp", "log", "lib"]
return f"{type(self).__name__}({', '.join(f'{attr}={getattr(self, attr)._dir}' for attr in attrs)})"
def __str__(self) -> str:
attrs = ["conf", "data", "tmp", "log", "lib"]
return f"[{self.TYPE}]\n" + "\n".join(f'{attr} = "{str(getattr(self, attr))}"' for attr in attrs)
def __copy__(self) -> Directories:
return self.copy()
def __deepcopy__(self, memo) -> Directories:
return self.__copy__()
# noinspection PyProtectedMember
def to_dict(self) -> Dict[str, Optional[str]]:
dirs = {
self.LIB: self._lib._dir,
self.LOG: self._log._dir,
self.TMP: self._tmp._dir,
self.DATA: self._data._dir,
self.CONF: self._conf._dir,
}
return dirs
@property
def lib(self) -> Directory:
return self._lib
# noinspection PyShadowingBuiltins
@lib.setter
def lib(self, dir: str | Directory) -> None:
if isinstance(dir, Directory):
dir = str(dir)
self._lib = Directory(dir, default="lib")
@property
def log(self) -> Directory:
return self._log
# noinspection PyShadowingBuiltins
@log.setter
def log(self, dir: str | Directory) -> None:
if isinstance(dir, Directory):
dir = str(dir)
self._log = Directory(dir, default="log")
@property
def tmp(self) -> Directory:
return self._tmp
# noinspection PyShadowingBuiltins
@tmp.setter
def tmp(self, dir: str | Directory) -> None:
if isinstance(dir, Directory):
dir = str(dir)
self._tmp = Directory(dir, default="tmp")
@property
def data(self) -> Directory:
return self._data
# noinspection PyShadowingBuiltins
@data.setter
def data(self, dir: str | Directory) -> None:
if isinstance(dir, Directory):
dir = str(dir)
self._data = Directory(dir, default="data")
@property
def conf(self) -> Directory:
return self._conf
# noinspection PyShadowingBuiltins
@conf.setter
def conf(self, dir: str | Path) -> None:
if isinstance(dir, Path):
dir = str(dir)
self._conf = Directory(dir, default="conf", base=self.data)
# noinspection PyProtectedMember
def copy(self) -> Directories:
return Directories(
self._lib._dir,
self._log._dir,
self._tmp._dir,
self._data._dir,
self._conf._dir,
)
# noinspection PyProtectedMember, PyShadowingBuiltins
def update(self, configs: Mapping[str, str]) -> None:
for key in ["lib", "log", "tmp", "data"]:
dir = configs.get(f"{key}_dir", None)
if dir is not None:
setattr(self, f"{key}", dir)
conf_dir = configs.get("conf_dir", None)
if conf_dir is not None:
self.conf = conf_dir
[docs]
class Directory(Path):
_dir: Optional[Path] = None
_base: Path
default: str
# noinspection PyShadowingBuiltins, PyTypeChecker
def __new__(cls, *dirs: str, base: Optional[str | Directory] = None, default: Optional[str] = None):
cls = WindowsDirectory if os.name == "nt" else PosixDirectory
base = Directory.__parse_base(base)
dir = Directory.__parse_dir(base, *dirs, default=default)
return super().__new__(cls, Directory.__parse(base, dir, default), base=base, default=default)
def __init__(self, *dirs: str, base: Optional[str | Directory] = None, default: Optional[str] = None):
self.default = default
self._base = Directory.__parse_base(base)
self._dir = Directory.__parse_dir(self._base, *dirs, default=default)
try:
super().__init__(Directory.__parse(self._base, self._dir, default=default))
except TypeError:
# FixMe: The mro appears to be called incorrectly for python Versions < 3.12.
# ToDo: Remove this catch, as older versions proceed to be deprecated.
super().__init__()
@staticmethod
def __parse_base(base: Optional[LiteralString | str | Path]) -> Path:
if base is None or (isinstance(base, Directory) and base.is_default()):
base = Path.cwd()
elif isinstance(base, Directory) or not isinstance(base, Path):
base = Path(base)
# FIXME: Remove this once Python >= 3.9 is a requirement
# if base.is_relative_to("~"):
if str(base).startswith("~"):
base = base.expanduser()
if not base.is_absolute():
base = base.absolute()
return base
# noinspection PyShadowingBuiltins
@staticmethod
def __parse_dir(base: Path, *dirs: Optional[str], default: Optional[str] = None) -> Path:
dir = Path(*dirs) if not any(d is None for d in dirs) else None
if dir is not None:
# FIXME: Remove this once Python >= 3.9 is a requirement
# if dir.is_relative_to(base):
if str(dir).startswith(str(base)):
dir = dir.relative_to(base)
# if dir.is_relative_to("~"):
if str(dir).startswith("~"):
dir = dir.expanduser()
if str(dir) == default:
dir = None
return dir
@staticmethod
def __parse(base: Path, path: Optional[Path], default: Optional[str] = None) -> Path:
if path is None:
path = Path(default)
if path is not None and not os.path.isabs(path):
path = base.joinpath(path)
return path
def is_default(self) -> bool:
if self.default is None:
return False
return self._dir is None or str(self) == os.path.join(self._base, self.default)
# noinspection SpellCheckingInspection
[docs]
def joinpath(self, *paths: LiteralString | str | Path) -> Directory:
_dir = self.__parse(self._base, self._dir, self.default)
return Directory(_dir.joinpath(*paths), default=self.default, base=self._base)
[docs]
def relative_to(self, path: LiteralString | str | Path, *_, walk_up=False) -> Path:
return self.__parse(self._base, self._dir, self.default).relative_to(path)
[docs]
class PosixDirectory(Directory, PosixPath):
pass
[docs]
class WindowsDirectory(Directory, WindowsPath):
pass