Path:
strictdoc/export/html/html_templates.py
Lines:
155
Non-empty lines:
131
Non-empty lines covered with requirements:
131 / 131 (100.0%)
Functions:
16
Functions covered by requirements:
16 / 16 (100.0%)
1
import datetime
2
import glob
3
import hashlib
4
import os.path
5
import shutil
6
from pathlib import Path
7
from typing import Any, List, Optional
8
9
from jinja2 import (
10
Environment,
11
FileSystemLoader,
12
ModuleLoader,
13
StrictUndefined,
14
Template,
15
)16
from markupsafe import Markup
17
18
from strictdoc import environment
19
from strictdoc.core.project_config import ProjectConfig
20
from strictdoc.export.html.jinja.assert_extension import AssertExtension
21
from strictdoc.helpers.file_modification_time import get_file_modification_time
22
from strictdoc.helpers.timing import measure_performance
23
24
25
class JinjaEnvironment:
26
environment: Environment
27
28
def __init__(self, environment: Environment):
29
self.environment = environment
30
31
def get_template(self, *args: Any, **kwargs: Any) -> Template:
32
return self.environment.get_template(*args, **kwargs)
33
34
def render_template_as_markup(
35
self, template: str, *args: Any, **kwargs: Any
36
) -> Markup:
37
return Markup(
38
self.environment.get_template(template).render(*args, **kwargs)
39
)40
41
42
class HTMLTemplates:
43
@staticmethod44
def create(
45
project_config: ProjectConfig,
46
enable_caching: bool,
47
strictdoc_last_update: datetime.datetime,
48
) -> "HTMLTemplates":
49
assert isinstance(strictdoc_last_update, datetime.datetime)
50
if enable_caching:
51
cacheable_templates = CompiledHTMLTemplates(project_config)
52
cacheable_templates.reset_jinja_environment_if_outdated(
53
strictdoc_last_update54
)55
cacheable_templates.compile_jinja_templates()
56
return CompiledHTMLTemplates(project_config)
57
58
return NormalHTMLTemplates()
59
60
def jinja_environment(self) -> JinjaEnvironment:
61
raise NotImplementedError
62
63
64
class NormalHTMLTemplates(HTMLTemplates):
65
def __init__(self) -> None:
66
self._jinja_environment: JinjaEnvironment = JinjaEnvironment(
67
Environment(
68
loader=FileSystemLoader(
69
environment.get_path_to_html_templates()
70
),71
undefined=StrictUndefined,
72
extensions=[AssertExtension],
73
autoescape=True,
74
)75
)76
77
def jinja_environment(self) -> JinjaEnvironment:
78
return self._jinja_environment
79
80
81
class CompiledHTMLTemplates(HTMLTemplates):
82
def __init__(self, project_config: ProjectConfig):
83
path_to_output_dir_hash = hashlib.md5(
84
project_config.output_dir.encode("utf-8")
85
).hexdigest()
86
self.path_to_jinja_cache_bucket_dir = os.path.join(
87
project_config.get_path_to_cache_dir(),
88
"jinja",
89
path_to_output_dir_hash,
90
)91
self._jinja_environment: Optional[JinjaEnvironment] = None
92
93
def compile_jinja_templates(self) -> None:
94
if os.path.isdir(self.path_to_jinja_cache_bucket_dir):
95
return96
jinja_environment = Environment(
97
loader=FileSystemLoader(environment.get_path_to_html_templates()),
98
undefined=StrictUndefined,
99
extensions=[AssertExtension],
100
autoescape=True,
101
)102
# TODO: Check if this line is still needed (might be some older workaround).103
jinja_environment.globals.update(isinstance=isinstance)
104
with measure_performance("Compile Jinja templates"):
105
106
def filter_function_(name: str) -> bool:
107
# On macOS, the .DS_Store files make Jinja templates compiler108
# to crash.109
# https://github.com/strictdoc-project/strictdoc/issues/1266110
if name.endswith(".DS_Store"):
111
return False
112
return True
113
114
Path(self.path_to_jinja_cache_bucket_dir).mkdir(
115
parents=True, exist_ok=True
116
)117
jinja_environment.compile_templates(
118
self.path_to_jinja_cache_bucket_dir,
119
zip=None,
120
filter_func=filter_function_,
121
ignore_errors=False,
122
)123
124
def jinja_environment(self) -> JinjaEnvironment:
125
if self._jinja_environment is not None:
126
return self._jinja_environment
127
assert os.path.isdir(self.path_to_jinja_cache_bucket_dir)
128
self._jinja_environment = JinjaEnvironment(
129
Environment(
130
loader=ModuleLoader(self.path_to_jinja_cache_bucket_dir),
131
undefined=StrictUndefined,
132
extensions=[AssertExtension],
133
autoescape=True,
134
)135
)136
return self._jinja_environment
137
138
def reset_jinja_environment_if_outdated(
139
self, strictdoc_last_update: datetime.datetime
140
) -> None:
141
assert isinstance(strictdoc_last_update, datetime.datetime)
142
143
if os.path.isdir(self.path_to_jinja_cache_bucket_dir):
144
jinja_cache_files: List[str] = list(
145
glob.iglob(
146
f"{self.path_to_jinja_cache_bucket_dir}/**/*.py",
147
recursive=True,
148
)149
)150
151
jinja_cache_mtime = get_file_modification_time(jinja_cache_files[0])
152
153
if strictdoc_last_update > jinja_cache_mtime:
154
self._jinja_environment = None
155
shutil.rmtree(self.path_to_jinja_cache_bucket_dir)