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
|
from collections.abc import Iterable, Mapping
import os
import os.path
from string import Template
from ._error import CruException
class CruTemplateError(CruException):
pass
class TemplateFile:
def __init__(self, source: str, destination_path: str | None):
self._source = source
self._destination = destination_path
self._template: Template | None = None
@property
def source(self) -> str:
return self._source
@property
def destination(self) -> str | None:
return self._destination
@destination.setter
def destination(self, value: str | None) -> None:
self._destination = value
@property
def template(self) -> Template:
if self._template is None:
return self.reload_template()
return self._template
def reload_template(self) -> Template:
with open(self._source, "r") as f:
self._template = Template(f.read())
return self._template
@property
def variables(self) -> set[str]:
return set(self.template.get_identifiers())
def generate(self, variables: Mapping[str, str]) -> str:
return self.template.substitute(variables)
def generate_to_destination(self, variables: Mapping[str, str]) -> None:
if self._destination is None:
raise CruTemplateError("No destination specified for this template.")
with open(self._destination, "w") as f:
f.write(self.generate(variables))
class TemplateDirectory:
def __init__(
self,
source: str,
destination: str,
exclude: Iterable[str],
file_suffix: str = ".template",
):
self._files: list[TemplateFile] | None = None
self._source = source
self._destination = destination
self._exclude = [os.path.normpath(p) for p in exclude]
self._file_suffix = file_suffix
@property
def files(self) -> list[TemplateFile]:
if self._files is None:
return self.reload()
else:
return self._files
@property
def source(self) -> str:
return self._source
@property
def destination(self) -> str:
return self._destination
@property
def exclude(self) -> list[str]:
return self._exclude
@property
def file_suffix(self) -> str:
return self._file_suffix
@staticmethod
def _scan_files(
root_path: str, exclude: list[str], suffix: str | None
) -> Iterable[str]:
for root, _dirs, files in os.walk(root_path):
for file in files:
if suffix is None or file.endswith(suffix):
path = os.path.join(root, file)
path = os.path.relpath(path, root_path)
if suffix is not None:
path = path[: -len(suffix)]
is_exclude = False
for exclude_path in exclude:
if path.startswith(exclude_path):
is_exclude = True
break
if not is_exclude:
yield path
def reload(self) -> list[TemplateFile]:
if not os.path.isdir(self.source):
raise CruTemplateError(
f"Source directory {self.source} does not exist or is not a directory."
)
files = self._scan_files(self.source, self.exclude, self.file_suffix)
self._files = [
TemplateFile(
os.path.join(self._source, file + self.file_suffix),
os.path.join(self._destination, file),
)
for file in files
]
return self._files
@property
def variables(self) -> set[str]:
s = set()
for file in self.files:
s.update(file.variables)
return s
def generate_to_destination(self, variables: Mapping[str, str]) -> None:
for file in self.files:
file.generate_to_destination(variables)
def extra_files_in_destination(self) -> Iterable[str]:
source_files = set(os.path.relpath(f.source, self.source) for f in self.files)
for file in self._scan_files(self.destination, self.exclude, None):
if file not in source_files:
yield file
|