Coverage for src/hatch_ci/fileos.py: 89%

# Coverage for src/hatch_ci/fileos.py : 89%

## 102 statements 91 run 11 missing 0 excluded

Hot-keys on this page

r m x p toggle line displays

j k next/prev highlighted chunk

0 (zero) top of page

1 (one) first highlighted chunk

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 | """various file/dir related functions""" from __future__ import annotations import os import types from pathlib import Path from typing import Any, overload class FileOSError(Exception): pass class FileOSModuleNotFoundError(FileOSError): pass class FileOSMInvalidModuleError(FileOSError): pass def rmtree(path: Path): """universal (win|*nix) rmtree""" from os import name from shutil import rmtree from stat import S_IWUSR if name == "nt": for p in path.rglob("*"): p.chmod(S_IWUSR) rmtree(path, ignore_errors=True) if path.exists(): raise RuntimeError(f"cannot remove {path=}") def mkdir(path: Path) -> Path: """make a path directory and returns if it has been created""" path.mkdir(exist_ok=True, parents=True) return path def touch(path: Path) -> Path: """touch a new empty file""" mkdir(path.parent) path.write_text("") return path @overload def which(exe: Path | str, kind: type[list], abort: bool = True) -> list[Path]: ... @overload def which(exe: Path | str, kind: None, abort: bool = True) -> Path | None: ... def which( exe: Path | str, kind: type[list] | None = None, abort: bool = True ) -> list[Path] | Path | None: candidates: list[Path] = [] for srcdir in os.environ.get("PATH", "").split(os.pathsep): for ext in os.environ.get("PATHEXT", "").split(os.pathsep): path = srcdir / Path(exe).with_suffix(ext) if not path.exists(): continue if kind is None: return path candidates.append(path) return candidates def loadmod(path: Path | str, suffix: str | None = "") -> types.ModuleType: import inspect from importlib import machinery, util if isinstance(path, str): if not Path(path).is_absolute(): path = Path(inspect.stack()[1].filename).parent / path path = Path(path) if suffix is not None: machinery.SOURCE_SUFFIXES.append(suffix) try: spec = util.spec_from_file_location(Path(path).name, Path(path)) if not spec: raise FileOSModuleNotFoundError(f"cannot find module for {path=}") module = util.module_from_spec(spec) if not spec.loader: raise FileOSMInvalidModuleError(f"invalid module in {path=}") spec.loader.exec_module(module) finally: if suffix is not None: machinery.SOURCE_SUFFIXES.pop() return module ### FILE UTILITIES def zextract(path: Path | str, items: list[str] | None = None) -> dict[str, Any]: """extracts from path (a zipfile/tarball) all data in a dictionary""" from tarfile import TarFile, is_tarfile from zipfile import ZipFile, is_zipfile path = Path(path) result = {} if is_tarfile(path): with TarFile.open(path) as tfp: for member in tfp.getmembers(): fp = tfp.extractfile(member) if not fp: continue result[member.name] = str(fp.read(), encoding="utf-8") elif is_zipfile(path): with ZipFile(path) as tfp: for zinfo in tfp.infolist(): if items and zinfo.filename not in items: continue with tfp.open(zinfo.filename) as fp: result[zinfo.filename] = str(fp.read(), encoding="utf-8").replace( "\r", "" ) return result def backup(path: Path, ext: str, overwrite: bool = False, abort: bool = True) -> Path: """creates a backup of path""" from shutil import copyfile, copymode path2 = path.parent / f"{path.name}{ext}" if path2.exists() and not overwrite: if abort: raise FileOSError(f"backup file present {path2}") return path2 copyfile(path, path2) copymode(path, path2) return path2 def unbackup(path: Path, ext: str, abort: bool = True) -> Path: """restores from a backup of path""" from shutil import move path2 = path.parent / f"{path.name}{ext}" if abort and not path2.exists(): raise FileOSError(f"cannot find backup file {path2} for {path=}") if path2.exists(): move(str(path2), str(path)) return path2