python (3.12.0)
1 # from jaraco.path 3.5
2
3 import functools
4 import pathlib
5 from typing import Dict, Union
6
7 try:
8 from typing import Protocol, runtime_checkable
9 except ImportError: # pragma: no cover
10 # Python 3.7
11 from typing_extensions import Protocol, runtime_checkable # type: ignore
12
13
14 FilesSpec = Dict[str, Union[str, bytes, 'FilesSpec']] # type: ignore
15
16
17 @runtime_checkable
18 class ESC[4;38;5;81mTreeMaker(ESC[4;38;5;149mProtocol):
19 def __truediv__(self, *args, **kwargs):
20 ... # pragma: no cover
21
22 def mkdir(self, **kwargs):
23 ... # pragma: no cover
24
25 def write_text(self, content, **kwargs):
26 ... # pragma: no cover
27
28 def write_bytes(self, content):
29 ... # pragma: no cover
30
31
32 def _ensure_tree_maker(obj: Union[str, TreeMaker]) -> TreeMaker:
33 return obj if isinstance(obj, TreeMaker) else pathlib.Path(obj) # type: ignore
34
35
36 def build(
37 spec: FilesSpec,
38 prefix: Union[str, TreeMaker] = pathlib.Path(), # type: ignore
39 ):
40 """
41 Build a set of files/directories, as described by the spec.
42
43 Each key represents a pathname, and the value represents
44 the content. Content may be a nested directory.
45
46 >>> spec = {
47 ... 'README.txt': "A README file",
48 ... "foo": {
49 ... "__init__.py": "",
50 ... "bar": {
51 ... "__init__.py": "",
52 ... },
53 ... "baz.py": "# Some code",
54 ... }
55 ... }
56 >>> target = getfixture('tmp_path')
57 >>> build(spec, target)
58 >>> target.joinpath('foo/baz.py').read_text(encoding='utf-8')
59 '# Some code'
60 """
61 for name, contents in spec.items():
62 create(contents, _ensure_tree_maker(prefix) / name)
63
64
65 @functools.singledispatch
66 def create(content: Union[str, bytes, FilesSpec], path):
67 path.mkdir(exist_ok=True)
68 build(content, prefix=path) # type: ignore
69
70
71 @create.register
72 def _(content: bytes, path):
73 path.write_bytes(content)
74
75
76 @create.register
77 def _(content: str, path):
78 path.write_text(content, encoding='utf-8')
79
80
81 @create.register
82 def _(content: str, path):
83 path.write_text(content, encoding='utf-8')
84
85
86 class ESC[4;38;5;81mRecording:
87 """
88 A TreeMaker object that records everything that would be written.
89
90 >>> r = Recording()
91 >>> build({'foo': {'foo1.txt': 'yes'}, 'bar.txt': 'abc'}, r)
92 >>> r.record
93 ['foo/foo1.txt', 'bar.txt']
94 """
95
96 def __init__(self, loc=pathlib.PurePosixPath(), record=None):
97 self.loc = loc
98 self.record = record if record is not None else []
99
100 def __truediv__(self, other):
101 return Recording(self.loc / other, self.record)
102
103 def write_text(self, content, **kwargs):
104 self.record.append(str(self.loc))
105
106 write_bytes = write_text
107
108 def mkdir(self, **kwargs):
109 return