codekingpro/portable-devtools
114k
1from __future__ import annotations2 3import logging4import os5import shutil6import sys7from stat import S_IWUSR8 9 10def ensure_dir(path):11 if not path.exists():12 logging.debug("create folder %s", str(path))13 os.makedirs(str(path))14 15 16def ensure_safe_to_do(src, dest):17 if src == dest:18 msg = f"source and destination is the same {src}"19 raise ValueError(msg)20 if not dest.exists():21 return22 if dest.is_dir() and not dest.is_symlink():23 logging.debug("remove directory %s", dest)24 safe_delete(dest)25 else:26 logging.debug("remove file %s", dest)27 dest.unlink()28 29 30def symlink(src, dest):31 ensure_safe_to_do(src, dest)32 logging.debug("symlink %s", _Debug(src, dest))33 dest.symlink_to(src, target_is_directory=src.is_dir())34 35 36def copy(src, dest):37 ensure_safe_to_do(src, dest)38 is_dir = src.is_dir()39 method = copytree if is_dir else shutil.copy40 logging.debug("copy %s", _Debug(src, dest))41 method(str(src), str(dest))42 43 44def copytree(src, dest):45 for root, _, files in os.walk(src):46 dest_dir = os.path.join(dest, os.path.relpath(root, src))47 if not os.path.isdir(dest_dir):48 os.makedirs(dest_dir)49 for name in files:50 src_f = os.path.join(root, name)51 dest_f = os.path.join(dest_dir, name)52 shutil.copy(src_f, dest_f)53 54 55def safe_delete(dest):56 def onerror(func, path, exc_info): # noqa: ARG00157 if not os.access(path, os.W_OK):58 os.chmod(path, S_IWUSR)59 func(path)60 else:61 raise # noqa: PLE070462 63 kwargs = {"onexc" if sys.version_info >= (3, 12) else "onerror": onerror}64 shutil.rmtree(str(dest), ignore_errors=True, **kwargs)65 66 67class _Debug:68 def __init__(self, src, dest) -> None:69 self.src = src70 self.dest = dest71 72 def __str__(self) -> str:73 return f"{'directory ' if self.src.is_dir() else ''}{self.src!s} to {self.dest!s}"74 75 76__all__ = [77 "copy",78 "copytree",79 "ensure_dir",80 "safe_delete",81 "symlink",82 "symlink",83]84 