forked from reactive-python/reactpy-django
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathutils.py
147 lines (128 loc) · 5.18 KB
/
utils.py
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
from __future__ import annotations
import contextlib
import logging
import os
import re
from fnmatch import fnmatch
from importlib import import_module
from django.template import engines
from django.utils.encoding import smart_str
from django_idom.config import IDOM_REGISTERED_COMPONENTS
_logger = logging.getLogger(__name__)
_component_tag = r"component"
_component_path = r"((\"[^\"']*\")|('[^\"']*'))"
_component_kwargs = r"((.*?|\s*?)*)"
COMPONENT_REGEX = re.compile(
r"{%\s*"
+ _component_tag
+ r"\s*"
+ _component_path
+ r"\s*"
+ _component_kwargs
+ r"\s*%}"
)
def _register_component(full_component_name: str) -> None:
if full_component_name in IDOM_REGISTERED_COMPONENTS:
return
module_name, component_name = full_component_name.rsplit(".", 1)
try:
module = import_module(module_name)
except ImportError as error:
raise RuntimeError(
f"Failed to import {module_name!r} while loading {component_name!r}"
) from error
try:
component = getattr(module, component_name)
except AttributeError as error:
raise RuntimeError(
f"Module {module_name!r} has no component named {component_name!r}"
) from error
IDOM_REGISTERED_COMPONENTS[full_component_name] = component
_logger.debug("IDOM has registered component %s", full_component_name)
class ComponentPreloader:
def register_all(self):
"""Registers all IDOM components found within Django templates."""
# Get all template folder paths
paths = self._get_paths()
# Get all HTML template files
templates = self._get_templates(paths)
# Get all components
components = self._get_components(templates)
# Register all components
self._register_components(components)
def _get_loaders(self):
"""Obtains currently configured template loaders."""
template_source_loaders = []
for e in engines.all():
if hasattr(e, "engine"):
template_source_loaders.extend(
e.engine.get_template_loaders(e.engine.loaders)
)
loaders = []
for loader in template_source_loaders:
if hasattr(loader, "loaders"):
loaders.extend(loader.loaders)
else:
loaders.append(loader)
return loaders
def _get_paths(self) -> set[str]:
"""Obtains a set of all template directories."""
paths: set[str] = set()
for loader in self._get_loaders():
with contextlib.suppress(ImportError, AttributeError, TypeError):
module = import_module(loader.__module__)
get_template_sources = getattr(module, "get_template_sources", None)
if get_template_sources is None:
get_template_sources = loader.get_template_sources
paths.update(smart_str(origin) for origin in get_template_sources(""))
return paths
def _get_templates(self, paths: set[str]) -> set[str]:
"""Obtains a set of all HTML template paths."""
extensions = [".html"]
templates: set[str] = set()
for path in paths:
for root, _, files in os.walk(path, followlinks=False):
templates.update(
os.path.join(root, name)
for name in files
if not name.startswith(".")
and any(fnmatch(name, f"*{glob}") for glob in extensions)
)
return templates
def _get_components(self, templates: set[str]) -> set[str]:
"""Obtains a set of all IDOM components by parsing HTML templates."""
components: set[str] = set()
for template in templates:
with contextlib.suppress(Exception):
with open(template, "r", encoding="utf-8") as template_file:
match = COMPONENT_REGEX.findall(template_file.read())
if not match:
continue
components.update(
[group[0].replace('"', "").replace("'", "") for group in match]
)
if not components:
_logger.warning(
"\033[93m"
"IDOM did not find any components! "
"You are either not using any IDOM components, "
"using the template tag incorrectly, "
"or your HTML templates are not registered with Django."
"\033[0m"
)
return components
def _register_components(self, components: set[str]) -> None:
"""Registers all IDOM components in an iterable."""
for component in components:
try:
_logger.info("IDOM preloader has detected component %s", component)
_register_component(component)
except Exception:
_logger.error(
"\033[91m"
"IDOM failed to register component '%s'! "
"This component path may not be valid, "
"or an exception may have occurred while importing."
"\033[0m",
component,
)