sphinx-doclang 26.7.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sphinx_doclang/__init__.py +31 -0
- sphinx_doclang/_version.py +32 -0
- sphinx_doclang/error.py +62 -0
- sphinx_doclang/manager.py +392 -0
- sphinx_doclang/processor.py +478 -0
- sphinx_doclang/registry.py +440 -0
- sphinx_doclang-26.7.8.dist-info/METADATA +180 -0
- sphinx_doclang-26.7.8.dist-info/RECORD +10 -0
- sphinx_doclang-26.7.8.dist-info/WHEEL +5 -0
- sphinx_doclang-26.7.8.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#//|>-----------------------------------------------------------------------------------------------------------------<|
|
|
2
|
+
#//| Copyright (c) 15 Feb 2026. All rights are reserved by ASI
|
|
3
|
+
#//|>-----------------------------------------------------------------------------------------------------------------<|
|
|
4
|
+
|
|
5
|
+
#// IMPORT
|
|
6
|
+
# from importlib.resources import files
|
|
7
|
+
from sphinx.application import Sphinx
|
|
8
|
+
from sphinx.util.typing import ExtensionMetadata
|
|
9
|
+
|
|
10
|
+
from ._version import VERSION_STRING
|
|
11
|
+
from .processor import setup_processor, validate_command_configuration_values
|
|
12
|
+
from .manager import TemplateManager
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
#// RUN
|
|
16
|
+
def setup(app: Sphinx) -> ExtensionMetadata:
|
|
17
|
+
# root = files(__name__)
|
|
18
|
+
# app.config.html_static_path.append(str(root / "_static"))
|
|
19
|
+
# app.add_css_file("styles/localtoc.css")
|
|
20
|
+
|
|
21
|
+
setup_processor(app)
|
|
22
|
+
|
|
23
|
+
validate_command_configuration_values(app)
|
|
24
|
+
|
|
25
|
+
TemplateManager._init_app(app)
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
"version": VERSION_STRING,
|
|
29
|
+
"parallel_read_safe": True,
|
|
30
|
+
"parallel_write_safe": False,
|
|
31
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#//|>-----------------------------------------------------------------------------------------------------------------<|
|
|
2
|
+
#//| Copyright (c) 15 Feb 2026. All rights are reserved by ASI
|
|
3
|
+
#//|>-----------------------------------------------------------------------------------------------------------------<|
|
|
4
|
+
__all__ = (
|
|
5
|
+
"MAJOR", "MINOR", "MICRO", "REVISION",
|
|
6
|
+
"S_STABLE", "VERSION_STRING"
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
#//| Version variables
|
|
10
|
+
#//|>--------------------------------------------------------<|
|
|
11
|
+
MAJOR: int = 26
|
|
12
|
+
MINOR: int = 7
|
|
13
|
+
MICRO: int = 8
|
|
14
|
+
VERSION_STRING: str = f"{MAJOR}.{MINOR}.{MICRO}"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
#//| Development | Revision
|
|
18
|
+
#//|>--------------------------------------------------------<|
|
|
19
|
+
REVISION: int = 0
|
|
20
|
+
S_STABLE: bool = True
|
|
21
|
+
|
|
22
|
+
# If is not a stable state, update `VERSION_STRING` to reflect that
|
|
23
|
+
if not S_STABLE:
|
|
24
|
+
VERSION_STRING = f"{VERSION_STRING}.dev{max(1, REVISION)}"
|
|
25
|
+
elif REVISION > 0:
|
|
26
|
+
VERSION_STRING = f"{VERSION_STRING}.rev{REVISION}"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
#//| exec'd
|
|
30
|
+
#//|>--------------------------------------------------------<|
|
|
31
|
+
__version__: str = VERSION_STRING
|
|
32
|
+
|
sphinx_doclang/error.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#//|>-----------------------------------------------------------------------------------------------------------------<|
|
|
2
|
+
#//| Copyright (c) 18 Feb 2026. All rights are reserved by ASI
|
|
3
|
+
#//|>-----------------------------------------------------------------------------------------------------------------<|
|
|
4
|
+
__all__ = ("DocLangError","InvalidCommandError","InvalidConfigError")
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
#// LOGIC
|
|
8
|
+
class DocLangError(Exception):
|
|
9
|
+
"""Base exception for all DocLang‑related errors."""
|
|
10
|
+
|
|
11
|
+
def __init__(self, message: str, *args) -> None:
|
|
12
|
+
super().__init__(f"[DocLang Error] {message}", *args)
|
|
13
|
+
|
|
14
|
+
def note(self, title: str, message: str|list[str], indent: int = 0) -> None:
|
|
15
|
+
"""
|
|
16
|
+
Attach a formatted note to the current error.
|
|
17
|
+
|
|
18
|
+
Notes provide additional context, suggestions or multi‑line guidance
|
|
19
|
+
that appears alongside the main error message in Sphinx output.
|
|
20
|
+
|
|
21
|
+
:param title: A short label describing the purpose of the note.
|
|
22
|
+
:param message: Either a single string or a list of strings.
|
|
23
|
+
:param indent: Number of spaces to indent multi‑line messages.
|
|
24
|
+
|
|
25
|
+
:return: Nothing.
|
|
26
|
+
"""
|
|
27
|
+
# Local variables
|
|
28
|
+
formatted: str = ""
|
|
29
|
+
|
|
30
|
+
# Single-line message
|
|
31
|
+
if isinstance(message, str):
|
|
32
|
+
formatted = message
|
|
33
|
+
|
|
34
|
+
else:
|
|
35
|
+
# One-line list ➜ treat as a normal string
|
|
36
|
+
if len(message) == 1:
|
|
37
|
+
formatted = message[0]
|
|
38
|
+
|
|
39
|
+
# Multi-line list ➜ format with indentation
|
|
40
|
+
else:
|
|
41
|
+
prefix: str = f"\n\t{' ' * (len(title) + 3 + indent)}"
|
|
42
|
+
|
|
43
|
+
for message_index in range(len(message)):
|
|
44
|
+
formatted = f"{formatted}{prefix if message_index else ''}{message[message_index]}"
|
|
45
|
+
|
|
46
|
+
# Only add the note if the message contains meaningful content
|
|
47
|
+
if formatted.strip():
|
|
48
|
+
self.add_note(f"\t[{title}] {formatted}")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class InvalidCommandError(DocLangError):
|
|
52
|
+
"""Raised when a DSL command is invalid or cannot be registered."""
|
|
53
|
+
|
|
54
|
+
def __init__(self, message: str) -> None:
|
|
55
|
+
super().__init__(message)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class InvalidConfigError(DocLangError):
|
|
59
|
+
"""Raised when attempting to use a configuration value with an invalid signature."""
|
|
60
|
+
|
|
61
|
+
def __init__(self, message: str) -> None:
|
|
62
|
+
super().__init__(message)
|
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
#//|>-----------------------------------------------------------------------------------------------------------------<|
|
|
2
|
+
#//| Copyright (c) 15 Feb 2026. All rights are reserved by ASI
|
|
3
|
+
#//|>-----------------------------------------------------------------------------------------------------------------<|
|
|
4
|
+
__all__ = ("CommandManager","TemplateManager")
|
|
5
|
+
|
|
6
|
+
#// IMPORT
|
|
7
|
+
from inspect import Parameter
|
|
8
|
+
from inspect import signature as get_signature
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any, Callable, get_type_hints, get_origin, get_args
|
|
11
|
+
|
|
12
|
+
from sphinx.application import Sphinx
|
|
13
|
+
|
|
14
|
+
from .error import InvalidCommandError
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
#// LOGIC
|
|
18
|
+
class BaseCommandManager:
|
|
19
|
+
""" Manager responsible for registering, validating, and executing doc-string commands. """
|
|
20
|
+
|
|
21
|
+
def __init__(self) -> None:
|
|
22
|
+
# Private variables
|
|
23
|
+
self.__registry: dict[str, Callable] = {}
|
|
24
|
+
|
|
25
|
+
def new(self, name: str) -> Callable:
|
|
26
|
+
"""
|
|
27
|
+
Decorator used to register a new DSL command.
|
|
28
|
+
|
|
29
|
+
This method is normalizing ``name`` to lowercase.
|
|
30
|
+
|
|
31
|
+
:param name: The name under which the command will be registered.
|
|
32
|
+
:return: The original function, after registration.
|
|
33
|
+
|
|
34
|
+
:raises InvalidCommandError:
|
|
35
|
+
If a command with the same name already exists.
|
|
36
|
+
"""
|
|
37
|
+
return self._make_decorator(name, must_exist=False)
|
|
38
|
+
|
|
39
|
+
def overwrite(self, name: str) -> Callable:
|
|
40
|
+
"""
|
|
41
|
+
Decorator used to replace an existing DSL command.
|
|
42
|
+
|
|
43
|
+
This method is normalizing ``name`` to lowercase.
|
|
44
|
+
|
|
45
|
+
:param name: The name of the command to overwrite.
|
|
46
|
+
:return: The original function, after replacement.
|
|
47
|
+
|
|
48
|
+
:raises InvalidCommandError:
|
|
49
|
+
If the command does not already exist.
|
|
50
|
+
"""
|
|
51
|
+
return self._make_decorator(name, must_exist=True)
|
|
52
|
+
|
|
53
|
+
def _is_registered(self, name: str) -> bool:
|
|
54
|
+
"""
|
|
55
|
+
Check whether a command with the given name is registered.
|
|
56
|
+
|
|
57
|
+
:param name: The command name to check.
|
|
58
|
+
|
|
59
|
+
:return: ``True`` if the command exists, otherwise ``False``.
|
|
60
|
+
"""
|
|
61
|
+
return name.lower() in self.__registry
|
|
62
|
+
|
|
63
|
+
def _execute(self, name: str, *args, **kwargs) -> Any:
|
|
64
|
+
"""
|
|
65
|
+
Execute a registered command by name.
|
|
66
|
+
|
|
67
|
+
:param name: The name of the command to execute.
|
|
68
|
+
:param args: Positional arguments passed to the command.
|
|
69
|
+
:param kwargs: Keyword arguments passed to the command.
|
|
70
|
+
|
|
71
|
+
:return: The result returned by the command function.
|
|
72
|
+
|
|
73
|
+
:raises KeyError:
|
|
74
|
+
If the command name is not registered.
|
|
75
|
+
Use ``_is_registered()`` to check for existence before calling this method.
|
|
76
|
+
"""
|
|
77
|
+
return self.__registry[name.lower()](*args, **kwargs)
|
|
78
|
+
|
|
79
|
+
def _make_decorator(self, name: str, must_exist: bool) -> Callable:
|
|
80
|
+
"""
|
|
81
|
+
Internal helper that builds decorator's.
|
|
82
|
+
|
|
83
|
+
This method is normalizing ``name`` to lowercase.
|
|
84
|
+
|
|
85
|
+
:param name: The command name to register or overwrite. The name is normalized to lowercase.
|
|
86
|
+
:param must_exist: If True, the command must already be present in the registry.
|
|
87
|
+
|
|
88
|
+
:return: A decorator that registers the provided function under the given command name.
|
|
89
|
+
|
|
90
|
+
:raises InvalidCommandError:
|
|
91
|
+
If the existence condition is violated or if the subclass hook raises an error.
|
|
92
|
+
"""
|
|
93
|
+
def decorator(func: Callable) -> Callable:
|
|
94
|
+
# Local variables
|
|
95
|
+
key: str = name.lower()
|
|
96
|
+
error: InvalidCommandError = InvalidCommandError(
|
|
97
|
+
"Cannot {trying} command {command_name!r} because {reason}.".format(
|
|
98
|
+
command_name=key,
|
|
99
|
+
trying="overwrite" if must_exist else "register",
|
|
100
|
+
reason="it does not exist" if must_exist else "is already registered"
|
|
101
|
+
)
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
# Existence checks
|
|
105
|
+
if must_exist and key not in self.__registry:
|
|
106
|
+
error.note(
|
|
107
|
+
"Suggestion",
|
|
108
|
+
"Make shore the command name is correct or register a new command under this name."
|
|
109
|
+
)
|
|
110
|
+
raise error
|
|
111
|
+
|
|
112
|
+
if not must_exist and key in self.__registry:
|
|
113
|
+
error.note(
|
|
114
|
+
"Suggestion",
|
|
115
|
+
"Use a different name or overwrite the existing command if it does not satisfy your needs."
|
|
116
|
+
)
|
|
117
|
+
raise error
|
|
118
|
+
|
|
119
|
+
# Validate the command before registration
|
|
120
|
+
self._validate_command(key, func)
|
|
121
|
+
|
|
122
|
+
# Register the command
|
|
123
|
+
self.__registry[key] = func
|
|
124
|
+
return func
|
|
125
|
+
|
|
126
|
+
return decorator
|
|
127
|
+
|
|
128
|
+
@staticmethod
|
|
129
|
+
def _validate_command(name: str, func: Callable) -> None:
|
|
130
|
+
"""
|
|
131
|
+
Validate that a command function can safely receive arguments from the DSL processor.
|
|
132
|
+
|
|
133
|
+
A valid command must accept both:
|
|
134
|
+
- ``*args`` (var‑positional arguments)
|
|
135
|
+
- ``**kwargs`` (var‑keyword arguments)
|
|
136
|
+
|
|
137
|
+
This ensures that the DSL can pass any combination of positional and keyword arguments
|
|
138
|
+
without causing runtime errors.
|
|
139
|
+
|
|
140
|
+
:param name: The command name being validated.
|
|
141
|
+
:param func: The function associated with the command.
|
|
142
|
+
|
|
143
|
+
:raises InvalidCommandError:
|
|
144
|
+
If the function does not accept ``*args`` or ``**kwargs``.
|
|
145
|
+
"""
|
|
146
|
+
# Local variables
|
|
147
|
+
missing_args: str = ""
|
|
148
|
+
suggested_args: list[list[str]] = [["*args"], ["**kwargs"]]
|
|
149
|
+
|
|
150
|
+
# Validate the required arguments
|
|
151
|
+
params: list[Parameter] = list(get_signature(func).parameters.values())
|
|
152
|
+
accepts_args: bool = any(p.kind == Parameter.VAR_POSITIONAL for p in params)
|
|
153
|
+
accepts_kwargs: bool = any(p.kind == Parameter.VAR_KEYWORD for p in params)
|
|
154
|
+
|
|
155
|
+
# If var‑positional arguments is missing
|
|
156
|
+
if not accepts_args:
|
|
157
|
+
missing_args = missing_args[0][0]
|
|
158
|
+
|
|
159
|
+
# If var‑keyword arguments is missing
|
|
160
|
+
if not accepts_kwargs:
|
|
161
|
+
missing_args = f"{missing_args}{"' and '" if missing_args else ""}{suggested_args[1][0]}"
|
|
162
|
+
|
|
163
|
+
# If arguments are missing, generate a helpful error message
|
|
164
|
+
if missing_args:
|
|
165
|
+
# Build a corrected signature suggestion
|
|
166
|
+
for p in params:
|
|
167
|
+
# If one of required arguments is provided, replace the suggested name with the existing one
|
|
168
|
+
if p.kind == Parameter.VAR_POSITIONAL:
|
|
169
|
+
suggested_args[0][0] = f"*{p.name}"
|
|
170
|
+
|
|
171
|
+
elif p.kind == Parameter.VAR_KEYWORD:
|
|
172
|
+
suggested_args[1][0] = f"**{p.name}"
|
|
173
|
+
|
|
174
|
+
# Group the existing arguments
|
|
175
|
+
elif p.kind in (Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD):
|
|
176
|
+
# If a default value is provided, collect it as keyword argument along with the default value
|
|
177
|
+
if p.default is not Parameter.empty:
|
|
178
|
+
suggested_args[1].append("=".join([p.name, str(p.default)]))
|
|
179
|
+
|
|
180
|
+
# Otherwise, collect it as positional argument
|
|
181
|
+
else:
|
|
182
|
+
suggested_args[0].append(p.name)
|
|
183
|
+
|
|
184
|
+
# Move *args / **kwargs to the end of each group
|
|
185
|
+
for group in suggested_args:
|
|
186
|
+
first: str = group.pop(0)
|
|
187
|
+
group.append(first)
|
|
188
|
+
|
|
189
|
+
# Raise the error with a suggestion ready for copy‑paste
|
|
190
|
+
error: InvalidCommandError = InvalidCommandError(
|
|
191
|
+
f"Command {name!r} must accept {missing_args!r} to handle all provided arguments."
|
|
192
|
+
)
|
|
193
|
+
error.note(
|
|
194
|
+
"Suggestion",
|
|
195
|
+
[
|
|
196
|
+
f"Update the function signature for command {name!r} to:",
|
|
197
|
+
f"def {func.__name__}({', '.join([arg for group in suggested_args for arg in group])}): ..."
|
|
198
|
+
],
|
|
199
|
+
4
|
|
200
|
+
)
|
|
201
|
+
raise error
|
|
202
|
+
|
|
203
|
+
# [Case 1] The annotation exists
|
|
204
|
+
hints: dict[str, Any] = get_type_hints(func)
|
|
205
|
+
msg_err_return: str = f"Command {name!r} must return a string or a list of strings."
|
|
206
|
+
error_return: InvalidCommandError = InvalidCommandError(f"{msg_err_return}")
|
|
207
|
+
need_simulation: bool = False
|
|
208
|
+
|
|
209
|
+
if "return" in hints:
|
|
210
|
+
return_type = hints["return"]
|
|
211
|
+
return_origin = get_origin(return_type)
|
|
212
|
+
return_args = get_args(return_type)
|
|
213
|
+
|
|
214
|
+
# Accept ➜ str
|
|
215
|
+
if return_origin is str: return
|
|
216
|
+
|
|
217
|
+
# Accept ➜ list[str]
|
|
218
|
+
if return_origin is list:
|
|
219
|
+
# List with no type args ➜ simulate
|
|
220
|
+
if not return_args:
|
|
221
|
+
need_simulation = True
|
|
222
|
+
|
|
223
|
+
# Accept only list with all type args as str
|
|
224
|
+
elif all(arg_type is str for arg_type in return_args): return
|
|
225
|
+
|
|
226
|
+
# Raise an error if list have other type args then accepted one
|
|
227
|
+
else:
|
|
228
|
+
error_return.note("Detected", f"Annotation {return_type}", 4)
|
|
229
|
+
raise error_return
|
|
230
|
+
|
|
231
|
+
# Anything else ➜ simulate
|
|
232
|
+
else:
|
|
233
|
+
need_simulation = True
|
|
234
|
+
|
|
235
|
+
if not need_simulation: return
|
|
236
|
+
|
|
237
|
+
# [Case 2] No annotation OR annotation incomplete
|
|
238
|
+
simulate: Any = func(*[f"debug arg {index}" for index in range(100)])
|
|
239
|
+
simulate_type = type(simulate)
|
|
240
|
+
|
|
241
|
+
if simulate_type is str: return
|
|
242
|
+
elif simulate_type is list and all(isinstance(arg, str) for arg in simulate_type): return
|
|
243
|
+
|
|
244
|
+
else:
|
|
245
|
+
error_return.note("Simulated", f"Annotation {simulate_type}", 4)
|
|
246
|
+
raise error_return
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
class BaseTemplateManager:
|
|
250
|
+
""" Manager responsible for locating, loading and rendering template files. """
|
|
251
|
+
|
|
252
|
+
def __init__(self) -> None:
|
|
253
|
+
super().__init__()
|
|
254
|
+
|
|
255
|
+
# Private variables
|
|
256
|
+
self.__template_paths: list[Path] = []
|
|
257
|
+
self.__map: dict[str, str] = {}
|
|
258
|
+
|
|
259
|
+
@property
|
|
260
|
+
def names(self) -> list[str]:
|
|
261
|
+
""" A list-like string providing a view on the mapped names. """
|
|
262
|
+
return [name for name in self.__map.keys()]
|
|
263
|
+
|
|
264
|
+
@property
|
|
265
|
+
def values(self) -> list[str]:
|
|
266
|
+
""" A list-like string providing a view on the mapped values. """
|
|
267
|
+
return [value for value in self.__map.values()]
|
|
268
|
+
|
|
269
|
+
@property
|
|
270
|
+
def items(self) -> list[tuple[str, str]]:
|
|
271
|
+
""" A list-like tuple providing a view on the mapped names and values. """
|
|
272
|
+
return [(name, value) for name, value in self.__map.items()]
|
|
273
|
+
|
|
274
|
+
def set_multiple_items(self, **kwargs: str) -> None:
|
|
275
|
+
"""
|
|
276
|
+
Set multiple mapping items at once.
|
|
277
|
+
|
|
278
|
+
Equivalent to calling ``self[name] = value`` for each provided keyword.
|
|
279
|
+
"""
|
|
280
|
+
for key, value in kwargs.items():
|
|
281
|
+
self[key] = value
|
|
282
|
+
|
|
283
|
+
def __contains__(self, name: str) -> bool:
|
|
284
|
+
""" Return True if the ``name`` is mapped, otherwise False. """
|
|
285
|
+
return name.upper() in self.__map
|
|
286
|
+
|
|
287
|
+
def __delitem__(self, name: str) -> None:
|
|
288
|
+
""" Delete the mapped entry for the given name. """
|
|
289
|
+
key: str = name.upper()
|
|
290
|
+
|
|
291
|
+
if key in self.__map:
|
|
292
|
+
del self.__map[key]
|
|
293
|
+
|
|
294
|
+
def __getitem__(self, name: str) -> str:
|
|
295
|
+
"""
|
|
296
|
+
Retrieve the value associated with the given name.
|
|
297
|
+
|
|
298
|
+
Returns an empty string if the key is not present.
|
|
299
|
+
"""
|
|
300
|
+
return self.__map.get(name.upper(), "")
|
|
301
|
+
|
|
302
|
+
def __setitem__(self, name: str, value: str) -> None:
|
|
303
|
+
""" Mapping the given name to the provided value. """
|
|
304
|
+
self.__map[name.upper()] = value.strip()
|
|
305
|
+
|
|
306
|
+
def _clear_map(self) -> None:
|
|
307
|
+
""" Clear the map. """
|
|
308
|
+
self.__map.clear()
|
|
309
|
+
|
|
310
|
+
def _init_app(self, app: Sphinx) -> None:
|
|
311
|
+
"""
|
|
312
|
+
Initialize template search paths based on the active Sphinx application.
|
|
313
|
+
|
|
314
|
+
This method scans all directories listed in ``templates_path`` and collects those that contain a
|
|
315
|
+
``doclang`` subdirectory. Only these directories are considered valid template roots.
|
|
316
|
+
|
|
317
|
+
:param app: The active Sphinx application instance.
|
|
318
|
+
"""
|
|
319
|
+
self.__template_paths.clear()
|
|
320
|
+
|
|
321
|
+
for path in app.config["templates_path"]:
|
|
322
|
+
directory: Path = Path(app.confdir) / path / "doclang"
|
|
323
|
+
|
|
324
|
+
if directory.exists():
|
|
325
|
+
self.__template_paths.append(directory)
|
|
326
|
+
|
|
327
|
+
def _get_file_content(self, *file_path: str, extension: str = "dlt") -> str:
|
|
328
|
+
"""
|
|
329
|
+
Retrieve the raw text content of a template by name.
|
|
330
|
+
|
|
331
|
+
The method searches all registered template directories for a file named ``<name>.dlt``.
|
|
332
|
+
If found, the file is read and returned as a UTF‑8 string.
|
|
333
|
+
|
|
334
|
+
:param file_path: The file path without extension.
|
|
335
|
+
:param extension: The file extension.
|
|
336
|
+
|
|
337
|
+
:return: The file content, or an empty string if the file does not exist.
|
|
338
|
+
"""
|
|
339
|
+
if not file_path:
|
|
340
|
+
return ""
|
|
341
|
+
|
|
342
|
+
file_dir: list[str] = [*file_path]
|
|
343
|
+
file_name: str = f"{file_dir.pop()}.{extension}"
|
|
344
|
+
|
|
345
|
+
for root in self.__template_paths:
|
|
346
|
+
file: Path = Path(root, *file_dir, file_name)
|
|
347
|
+
|
|
348
|
+
if file.is_file():
|
|
349
|
+
return file.read_text(encoding="UTF-8")
|
|
350
|
+
|
|
351
|
+
return ""
|
|
352
|
+
|
|
353
|
+
def _render_content(self, content: str) -> str:
|
|
354
|
+
"""
|
|
355
|
+
Render a template content by performing simple variable substitution.
|
|
356
|
+
|
|
357
|
+
This method replaces all occurrences of ``{{ NAME }}`` with the corresponding stored mapped values.
|
|
358
|
+
|
|
359
|
+
:param content: The template content to render.
|
|
360
|
+
|
|
361
|
+
:return: The rendered content, or an empty string if the template could not be found.
|
|
362
|
+
"""
|
|
363
|
+
for name, value in self.items:
|
|
364
|
+
content = content.replace(f"{{{{ {name} }}}}", value)
|
|
365
|
+
|
|
366
|
+
return content
|
|
367
|
+
|
|
368
|
+
def _render_template(self) -> str:
|
|
369
|
+
"""
|
|
370
|
+
Render the current template by performing simple variable substitution.
|
|
371
|
+
|
|
372
|
+
This method loads the template content using ``_get_file_content`` and render it using ``_render_content``.
|
|
373
|
+
|
|
374
|
+
:return: The rendered template content, or an empty string if the template could not be found.
|
|
375
|
+
"""
|
|
376
|
+
template: str = TemplateManager._get_file_content(self["type"], self["name"])
|
|
377
|
+
|
|
378
|
+
# If the template content is empty try the fallback name
|
|
379
|
+
if template == "":
|
|
380
|
+
template = TemplateManager._get_file_content(self["type"])
|
|
381
|
+
|
|
382
|
+
return self._render_content(template)
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
#//|>-----------------------------------------------------------------------------------------------------------------<|
|
|
386
|
+
#//| Global instances
|
|
387
|
+
#//|>-----------------------------------------------------------------------------------------------------------------<|
|
|
388
|
+
#: Main command manager
|
|
389
|
+
CommandManager: BaseCommandManager = BaseCommandManager()
|
|
390
|
+
|
|
391
|
+
#: Main template manager
|
|
392
|
+
TemplateManager: BaseTemplateManager = BaseTemplateManager()
|