aboutsummaryrefslogtreecommitdiff
path: root/tools/cru-py/cru/template.py
blob: 8e06418be67fb823db876e1135d11c0674b225a5 (plain)
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