Hide keyboard shortcuts

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