Pyjoyment 0.1.0__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.
Files changed (200) hide show
  1. Pyjo/App/ASGI.py +735 -0
  2. Pyjo/App/CGI.py +773 -0
  3. Pyjo/App/FastCGI.py +631 -0
  4. Pyjo/App/Lambda.py +987 -0
  5. Pyjo/App/WSGI.py +427 -0
  6. Pyjo/App/__init__.py +409 -0
  7. Pyjo/Asset/File.py +377 -0
  8. Pyjo/Asset/Memory.py +235 -0
  9. Pyjo/Asset/__init__.py +200 -0
  10. Pyjo/Base.py +215 -0
  11. Pyjo/Cache.py +119 -0
  12. Pyjo/Collection.py +570 -0
  13. Pyjo/Content/MultiPart.py +325 -0
  14. Pyjo/Content/Single.py +193 -0
  15. Pyjo/Content/__init__.py +806 -0
  16. Pyjo/Cookie/Request.py +90 -0
  17. Pyjo/Cookie/Response.py +294 -0
  18. Pyjo/Cookie/__init__.py +143 -0
  19. Pyjo/DOM/CSS.py +1019 -0
  20. Pyjo/DOM/HTML.py +550 -0
  21. Pyjo/DOM/__init__.py +1329 -0
  22. Pyjo/Date.py +294 -0
  23. Pyjo/EventEmitter.py +522 -0
  24. Pyjo/Exception.py +494 -0
  25. Pyjo/File.py +959 -0
  26. Pyjo/GetOpt.py +605 -0
  27. Pyjo/Headers.py +760 -0
  28. Pyjo/HelloWorld.py +77 -0
  29. Pyjo/Home.py +109 -0
  30. Pyjo/IOLoop/ChildProcess.py +591 -0
  31. Pyjo/IOLoop/Client.py +736 -0
  32. Pyjo/IOLoop/Datagram.py +982 -0
  33. Pyjo/IOLoop/Messages.py +664 -0
  34. Pyjo/IOLoop/Server.py +458 -0
  35. Pyjo/IOLoop/Stdio.py +446 -0
  36. Pyjo/IOLoop/Stream.py +644 -0
  37. Pyjo/IOLoop/Subprocess.py +419 -0
  38. Pyjo/IOLoop/TLS.py +360 -0
  39. Pyjo/IOLoop/__init__.py +1036 -0
  40. Pyjo/IOLoop/certs/README.md +41 -0
  41. Pyjo/IOLoop/certs/ca.crt +20 -0
  42. Pyjo/IOLoop/certs/server.crt +21 -0
  43. Pyjo/IOLoop/certs/server.key +28 -0
  44. Pyjo/JSON/Pointer.py +135 -0
  45. Pyjo/JSON/RPC.py +772 -0
  46. Pyjo/JSON/Schema.py +2506 -0
  47. Pyjo/JSON/__init__.py +573 -0
  48. Pyjo/Loader.py +342 -0
  49. Pyjo/Log.py +723 -0
  50. Pyjo/Message/Request.py +725 -0
  51. Pyjo/Message/Response.py +476 -0
  52. Pyjo/Message/__init__.py +830 -0
  53. Pyjo/Parameters.py +472 -0
  54. Pyjo/Path.py +419 -0
  55. Pyjo/Promise.py +894 -0
  56. Pyjo/Prove.py +1305 -0
  57. Pyjo/Reactor/Asyncio.py +854 -0
  58. Pyjo/Reactor/Poll.py +439 -0
  59. Pyjo/Reactor/__init__.py +525 -0
  60. Pyjo/SSE.py +119 -0
  61. Pyjo/Server/ASGI.py +486 -0
  62. Pyjo/Server/CGI.py +259 -0
  63. Pyjo/Server/Daemon.py +1007 -0
  64. Pyjo/Server/FastCGI.py +517 -0
  65. Pyjo/Server/Hot/__init__.py +687 -0
  66. Pyjo/Server/Hot/__main__.py +5 -0
  67. Pyjo/Server/Lambda.py +641 -0
  68. Pyjo/Server/Prefork.py +917 -0
  69. Pyjo/Server/Prethread.py +394 -0
  70. Pyjo/Server/Reload/Backend/Poll.py +114 -0
  71. Pyjo/Server/Reload/Backend/__init__.py +126 -0
  72. Pyjo/Server/Reload/__init__.py +482 -0
  73. Pyjo/Server/Reload/__main__.py +5 -0
  74. Pyjo/Server/Stdio.py +228 -0
  75. Pyjo/Server/WSGI.py +349 -0
  76. Pyjo/Server/__init__.py +396 -0
  77. Pyjo/String/Bytes.py +532 -0
  78. Pyjo/String/HTML.py +210 -0
  79. Pyjo/String/Mixin.py +333 -0
  80. Pyjo/String/Template.py +470 -0
  81. Pyjo/String/Unicode.py +553 -0
  82. Pyjo/String/__init__.py +0 -0
  83. Pyjo/TOML.py +232 -0
  84. Pyjo/Template.py +1290 -0
  85. Pyjo/Test/Pyjo.py +1454 -0
  86. Pyjo/Test/__init__.py +1233 -0
  87. Pyjo/Test/pytest_plugin.py +305 -0
  88. Pyjo/Transaction/HTTP.py +374 -0
  89. Pyjo/Transaction/WebSocket.py +709 -0
  90. Pyjo/Transaction/__init__.py +429 -0
  91. Pyjo/URL/Template.py +459 -0
  92. Pyjo/URL/__init__.py +651 -0
  93. Pyjo/Upload.py +139 -0
  94. Pyjo/UserAgent/CookieJar.py +391 -0
  95. Pyjo/UserAgent/Proxy.py +138 -0
  96. Pyjo/UserAgent/Server.py +239 -0
  97. Pyjo/UserAgent/Transactor.py +766 -0
  98. Pyjo/UserAgent/__init__.py +1243 -0
  99. Pyjo/Util.py +3567 -0
  100. Pyjo/WebSocket.py +332 -0
  101. Pyjo/YAML.py +1167 -0
  102. Pyjo/__init__.py +441 -0
  103. Pyjo/__main__.py +35 -0
  104. Pyjo/py.typed +0 -0
  105. Pyjoyment/Command/Author/__init__.py +20 -0
  106. Pyjoyment/Command/Author/generate/__init__.py +106 -0
  107. Pyjoyment/Command/Author/generate/app.py +299 -0
  108. Pyjoyment/Command/Author/generate/dockerfile.py +281 -0
  109. Pyjoyment/Command/Author/generate/kubernetes.py +514 -0
  110. Pyjoyment/Command/Author/generate/lambda_zip.py +372 -0
  111. Pyjoyment/Command/Author/generate/lite_app.py +113 -0
  112. Pyjoyment/Command/Author/generate/plugin.py +267 -0
  113. Pyjoyment/Command/Author/generate/pyproject.py +123 -0
  114. Pyjoyment/Command/Author/inflate.py +101 -0
  115. Pyjoyment/Command/__init__.py +332 -0
  116. Pyjoyment/Command/asgi.py +92 -0
  117. Pyjoyment/Command/cgi.py +90 -0
  118. Pyjoyment/Command/daemon.py +160 -0
  119. Pyjoyment/Command/eval.py +128 -0
  120. Pyjoyment/Command/fastcgi.py +99 -0
  121. Pyjoyment/Command/get.py +305 -0
  122. Pyjoyment/Command/lambda.py +117 -0
  123. Pyjoyment/Command/prefork.py +181 -0
  124. Pyjoyment/Command/prethread.py +126 -0
  125. Pyjoyment/Command/routes.py +133 -0
  126. Pyjoyment/Command/stdio.py +92 -0
  127. Pyjoyment/Command/version.py +141 -0
  128. Pyjoyment/Command/wsgi.py +93 -0
  129. Pyjoyment/Commands.py +546 -0
  130. Pyjoyment/Controller.py +1307 -0
  131. Pyjoyment/HelloWorld.py +56 -0
  132. Pyjoyment/Lite.py +494 -0
  133. Pyjoyment/Plugin/AllowedHosts.py +205 -0
  134. Pyjoyment/Plugin/Config.py +248 -0
  135. Pyjoyment/Plugin/DefaultHelpers.py +1202 -0
  136. Pyjoyment/Plugin/EPLRenderer.py +134 -0
  137. Pyjoyment/Plugin/EPRenderer.py +212 -0
  138. Pyjoyment/Plugin/HeaderCondition.py +98 -0
  139. Pyjoyment/Plugin/Health.py +325 -0
  140. Pyjoyment/Plugin/JSONConfig.py +172 -0
  141. Pyjoyment/Plugin/Mount.py +223 -0
  142. Pyjoyment/Plugin/PyRenderer.py +209 -0
  143. Pyjoyment/Plugin/TOMLConfig.py +141 -0
  144. Pyjoyment/Plugin/TagHelpers.py +959 -0
  145. Pyjoyment/Plugin/YAMLConfig.py +178 -0
  146. Pyjoyment/Plugin/__init__.py +80 -0
  147. Pyjoyment/Plugins.py +215 -0
  148. Pyjoyment/Renderer.py +673 -0
  149. Pyjoyment/Routes/Match.py +251 -0
  150. Pyjoyment/Routes/Pattern.py +443 -0
  151. Pyjoyment/Routes/Route.py +827 -0
  152. Pyjoyment/Routes/__init__.py +419 -0
  153. Pyjoyment/Sessions.py +255 -0
  154. Pyjoyment/Static.py +421 -0
  155. Pyjoyment/Types.py +238 -0
  156. Pyjoyment/Validator/Validation.py +395 -0
  157. Pyjoyment/Validator/__init__.py +304 -0
  158. Pyjoyment/__init__.py +1083 -0
  159. Pyjoyment/__main__.py +5 -0
  160. Pyjoyment/py.typed +0 -0
  161. Pyjoyment/resources/public/favicon.ico +0 -0
  162. Pyjoyment/resources/public/favicon.svg +345 -0
  163. Pyjoyment/resources/public/pyjo/bootstrap/LICENSE +21 -0
  164. Pyjoyment/resources/public/pyjo/bootstrap/bootstrap.bundle.min.js.map +1 -0
  165. Pyjoyment/resources/public/pyjo/bootstrap/bootstrap.css +6 -0
  166. Pyjoyment/resources/public/pyjo/bootstrap/bootstrap.js +7 -0
  167. Pyjoyment/resources/public/pyjo/bootstrap/bootstrap.min.css.map +1 -0
  168. Pyjoyment/resources/public/pyjo/failunicat.png +0 -0
  169. Pyjoyment/resources/public/pyjo/failunicat.svg +50 -0
  170. Pyjoyment/resources/public/pyjo/highlight.js/LICENSE +29 -0
  171. Pyjoyment/resources/public/pyjo/highlight.js/highlight-mojo-dark.css +56 -0
  172. Pyjoyment/resources/public/pyjo/highlight.js/highlight.min.js +1262 -0
  173. Pyjoyment/resources/public/pyjo/highlight.js/pyjoyment.min.js +1 -0
  174. Pyjoyment/resources/public/pyjo/logo-black-4x.png +0 -0
  175. Pyjoyment/resources/public/pyjo/logo-black-4x.svg +346 -0
  176. Pyjoyment/resources/public/pyjo/logo-white-2x.png +0 -0
  177. Pyjoyment/resources/public/pyjo/logo-white-2x.svg +346 -0
  178. Pyjoyment/resources/public/pyjo/logo-white-4x.png +0 -0
  179. Pyjoyment/resources/public/pyjo/logo-white-4x.svg +346 -0
  180. Pyjoyment/resources/public/pyjo/logo-white.png +0 -0
  181. Pyjoyment/resources/public/pyjo/logo-white.svg +346 -0
  182. Pyjoyment/resources/public/pyjo/logo.png +0 -0
  183. Pyjoyment/resources/public/pyjo/logo.svg +346 -0
  184. Pyjoyment/resources/public/pyjo/notfound.png +0 -0
  185. Pyjoyment/resources/public/pyjo/notfound.svg +3 -0
  186. Pyjoyment/resources/public/pyjo/nounicat.png +0 -0
  187. Pyjoyment/resources/public/pyjo/nounicat.svg +5 -0
  188. Pyjoyment/resources/public/pyjo/pinstripe-dark.png +0 -0
  189. Pyjoyment/resources/public/pyjo/pinstripe-dark.svg +4 -0
  190. Pyjoyment/resources/public/pyjo/pinstripe-light.png +0 -0
  191. Pyjoyment/resources/public/pyjo/pinstripe-light.svg +4 -0
  192. Pyjoyment/resources/public/pyjo/pyjo.css +204 -0
  193. Pyjoyment/resources/templates/pyjo/debug.html.ep +346 -0
  194. Pyjoyment/resources/templates/pyjo/exception.html.ep +32 -0
  195. Pyjoyment/resources/templates/pyjo/not_found.html.ep +45 -0
  196. pyjoyment-0.1.0.dist-info/METADATA +376 -0
  197. pyjoyment-0.1.0.dist-info/RECORD +200 -0
  198. pyjoyment-0.1.0.dist-info/WHEEL +4 -0
  199. pyjoyment-0.1.0.dist-info/entry_points.txt +8 -0
  200. pyjoyment-0.1.0.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,470 @@
1
+ r"""
2
+ Pyjo.String.Template - Template string with escaping
3
+ ====================================================
4
+ ::
5
+
6
+ from Pyjo.String.Template import t
7
+
8
+ name = '<script>'
9
+
10
+ # A t-string of Python 3.14
11
+ print(t(t'<p>{name}</p>'))
12
+
13
+ # The same on any Python
14
+ print(t('<p>{name}</p>'))
15
+ print(t('<p>{name}</p>', name=name))
16
+
17
+ # A template in a template is markup
18
+ def link_to(text, url):
19
+ return t(t'<a href="{url}">{text}</a>')
20
+
21
+ print(t(t'<li>{link_to(name, "/user")}</li>'))
22
+
23
+ :mod:`Pyjo.String.Template` is a template string, a t-string of
24
+ `PEP 750 <https://peps.python.org/pep-0750/>`_, which renders into
25
+ :mod:`Pyjo.String.HTML`, escaping the values that it interpolates, unless they
26
+ are markup. :func:`t` wraps a t-string of Python 3.14, and on any Python it
27
+ takes a string with the fields of :meth:`str.format`, whose values it finds as
28
+ a t-string would.
29
+
30
+ The template has the interface of :class:`string.templatelib.Template`, its
31
+ :attr:`~Pyjo_String_Template.strings`,
32
+ :attr:`~Pyjo_String_Template.interpolations` and
33
+ :attr:`~Pyjo_String_Template.values`, and it is a string, the rendered one,
34
+ as :mod:`Pyjo.String.Mixin` makes it: ``str(template)``, ``print`` and
35
+ comparisons render it. ``Interpolation`` of this module is
36
+ :class:`string.templatelib.Interpolation` on Python 3.14, and a class with
37
+ the same interface on an older Python, so that a t-string and :func:`t` have
38
+ the same interpolations. ::
39
+
40
+ import Pyjo.String.Template
41
+
42
+ from Pyjo.String.Template import Interpolation
43
+
44
+ # "<p>&lt;script&gt;</p>"
45
+ Pyjo.String.Template.new('<p>', Interpolation(name, 'name'), '</p>')
46
+
47
+ A value is rendered by its type:
48
+
49
+ * a template, of :mod:`Pyjo.String.Template` or of Python 3.14: it is rendered
50
+ as it is, not escaped;
51
+ * markup, an object with the method ``__html__``, as :mod:`Pyjo.String.HTML`,
52
+ ``Markup`` of markupsafe or a template: it is inserted as it is;
53
+ * ``None``: nothing;
54
+ * an iterable, as a list, a :mod:`Pyjo.Collection` or a generator, which is
55
+ not a string, bytes or a mapping: each of its items by these rules;
56
+ * anything else: it is formatted with :func:`format` and escaped.
57
+
58
+ A conversion (``!r``, ``!s``, ``!a``) or a format specification (``:>10``)
59
+ of the field makes a string of the value, which is escaped. ::
60
+
61
+ # "<ul><li>&lt;b&gt;</li><li>&lt;i&gt;</li></ul>"
62
+ tags = ['<b>', '<i>']
63
+ t(t'<ul>{[t"<li>{tag}</li>" for tag in tags]}</ul>')
64
+
65
+ A string of :func:`t` has no code, so that a string of a user cannot run any:
66
+ a field is a name, followed by ``.attribute`` and ``[key]``, as in
67
+ :meth:`str.format`, but not by an attribute that starts with ``_``, which
68
+ would reach the internals of Python. The value of the name is the keyword
69
+ argument of the name, or the variable of the caller: a local one, a global
70
+ one or a builtin, as a t-string sees them. A function sees the variables of
71
+ the functions around it only where it uses them itself, so a variable that
72
+ only the template uses is passed as a keyword argument. ::
73
+
74
+ # "<p>&lt;b&gt;</p>"
75
+ t('<p>{user.name}</p>', user=user)
76
+
77
+ # "<p>&lt;b&gt;</p>"
78
+ t('<p>{user[name]}</p>', user={'name': '<b>'})
79
+
80
+ See also
81
+ --------
82
+
83
+ :mod:`Pyjoyment`, https://mojolicious.org.
84
+
85
+ Inspired by `PEP 750 <https://peps.python.org/pep-0750/>`_.
86
+
87
+ Classes
88
+ -------
89
+ """
90
+
91
+ import Pyjo.Base
92
+ import Pyjo.String.HTML
93
+ import Pyjo.String.Mixin
94
+ import Pyjo.Util
95
+
96
+ import builtins
97
+ import inspect
98
+ import re
99
+ import string
100
+ import sys
101
+
102
+ from collections.abc import Iterable, Iterator, Mapping
103
+ from dataclasses import dataclass
104
+ from types import FrameType
105
+ from typing import Any, Literal, TypeGuard
106
+
107
+
108
+ type Anything = builtins.object
109
+ type Conversion = Literal['a', 'r', 's']
110
+
111
+ if sys.version_info >= (3, 14):
112
+ from string.templatelib import Interpolation, Template
113
+
114
+ else:
115
+
116
+ @dataclass(frozen=True, eq=False, repr=False, slots=True)
117
+ class Interpolation[T]:
118
+ """::
119
+
120
+ interpolation = Interpolation(value, 'name', 'r', '>10')
121
+
122
+ An interpolation of a template: the value, the text of the field, the
123
+ conversion and the format specification. It is
124
+ :class:`string.templatelib.Interpolation` on Python 3.14.
125
+ """
126
+
127
+ value: T
128
+ expression: str = ''
129
+ conversion: Conversion | None = None
130
+ format_spec: str = ''
131
+
132
+ def __post_init__(self) -> None:
133
+ if self.conversion not in (None, 'a', 'r', 's'):
134
+ raise ValueError("Interpolation() argument 'conversion' must be one of 's', 'a' or 'r'")
135
+
136
+ def __repr__(self) -> str:
137
+ return f'Interpolation({self.value!r}, {self.expression!r}, {self.conversion!r}, {self.format_spec!r})'
138
+
139
+
140
+ class Pyjo_String_Template(Pyjo.Base.object, Pyjo.String.Mixin.object):
141
+ """::
142
+
143
+ template = Pyjo.String.Template.new('<p>', Interpolation(name, 'name'), '</p>')
144
+
145
+ A template of strings and interpolations, as
146
+ :class:`string.templatelib.Template` makes it.
147
+ :mod:`Pyjo.String.Template` inherits all methods from
148
+ :mod:`Pyjo.String.Mixin` and implements the following new ones.
149
+ """
150
+
151
+ def __init__(self, *args: str | Interpolation[Any]) -> None:
152
+ strings = ['']
153
+ interpolations: list[Interpolation[Any]] = []
154
+ for arg in args:
155
+ match arg:
156
+ case str():
157
+ strings[-1] += str(arg)
158
+ case Interpolation():
159
+ interpolations.append(arg)
160
+ strings.append('')
161
+ case _:
162
+ raise TypeError(
163
+ f"Template.__init__ *args need to be of type 'str' or 'Interpolation', got {type(arg).__name__}"
164
+ )
165
+ self._strings = tuple(strings)
166
+ self._interpolations = tuple(interpolations)
167
+
168
+ def __add__(self, other: Anything) -> Any:
169
+ """::
170
+
171
+ template = template + other
172
+
173
+ Concatenate two templates into a template, or the rendered template
174
+ with a string into :mod:`Pyjo.String.HTML`, escaping the string unless
175
+ it is markup.
176
+ """
177
+ if is_template(other):
178
+ return self.new(*self, *other)
179
+ if isinstance(other, str) or hasattr(other, '__html__'):
180
+ return self.render() + other
181
+ return NotImplemented
182
+
183
+ def __html__(self) -> str:
184
+ """::
185
+
186
+ markup = template.__html__()
187
+
188
+ The rendered template, as the ``__html__`` protocol gives it.
189
+ """
190
+ return str(self.render())
191
+
192
+ def __iter__(self) -> Iterator[str | Interpolation[Any]]:
193
+ """::
194
+
195
+ for item in template:
196
+ ...
197
+
198
+ The strings that are not empty and the interpolations, in their
199
+ order.
200
+ """
201
+ for s, interpolation in zip(self._strings, self._interpolations, strict=False):
202
+ if s:
203
+ yield s
204
+ yield interpolation
205
+ if self._strings[-1]:
206
+ yield self._strings[-1]
207
+
208
+ def __radd__(self, other: Anything) -> Any:
209
+ """::
210
+
211
+ template = other + template
212
+
213
+ Concatenate two templates into a template, or a string with the
214
+ rendered template into :mod:`Pyjo.String.HTML`, escaping the string
215
+ unless it is markup.
216
+ """
217
+ if is_template(other):
218
+ return self.new(*other, *self)
219
+ if isinstance(other, str) or hasattr(other, '__html__'):
220
+ return self.render().__radd__(other)
221
+ return NotImplemented
222
+
223
+ def __repr__(self) -> str:
224
+ """::
225
+
226
+ string = repr(template)
227
+
228
+ The template as the code that makes it.
229
+ """
230
+ return f'{self.__module__}.new({", ".join(map(repr, self))})'
231
+
232
+ @property
233
+ def interpolations(self) -> tuple[Interpolation[Any], ...]:
234
+ """::
235
+
236
+ interpolations = template.interpolations
237
+
238
+ The interpolations of the template.
239
+ """
240
+ return self._interpolations
241
+
242
+ def render(self) -> Pyjo.String.HTML.object:
243
+ """::
244
+
245
+ string = template.render()
246
+
247
+ Render the template into :mod:`Pyjo.String.HTML`.
248
+ """
249
+ return Pyjo.String.HTML.new(_render(self._strings, self._interpolations))
250
+
251
+ @property
252
+ def strings(self) -> tuple[str, ...]:
253
+ """::
254
+
255
+ strings = template.strings
256
+
257
+ The strings of the template, one more than the interpolations: the
258
+ one before each of them and the one at the end, which can be empty.
259
+ """
260
+ return self._strings
261
+
262
+ def to_str(self) -> Pyjo.String.HTML.object:
263
+ """::
264
+
265
+ string = template.to_str()
266
+
267
+ The rendered template, as :meth:`render` gives it.
268
+ """
269
+ return self.render()
270
+
271
+ @property
272
+ def values(self) -> tuple[Any, ...]:
273
+ """::
274
+
275
+ values = template.values
276
+
277
+ The values of the interpolations.
278
+ """
279
+ return tuple(i.value for i in self._interpolations)
280
+
281
+
282
+ def escape(value: Anything) -> str:
283
+ """::
284
+
285
+ string = escape(value)
286
+
287
+ Render a value as an interpolation without a conversion and a format
288
+ specification, escaping it unless it is markup. ::
289
+
290
+ # "&lt;b&gt;<i>"
291
+ escape(['<b>', html('<i>')])
292
+ """
293
+ return _value(value, None, '')
294
+
295
+
296
+ def is_template(value: Anything) -> TypeGuard[Iterable[str | Interpolation[Any]]]:
297
+ """::
298
+
299
+ boolean = is_template(value)
300
+
301
+ Check if a value is a template, of :mod:`Pyjo.String.Template` or a
302
+ t-string of Python 3.14. ::
303
+
304
+ # True
305
+ is_template(t('<p>{name}</p>'))
306
+
307
+ # False
308
+ is_template('<p>{name}</p>')
309
+ """
310
+ if isinstance(value, Pyjo_String_Template):
311
+ return True
312
+ if sys.version_info >= (3, 14):
313
+ return isinstance(value, Template)
314
+ return False
315
+
316
+
317
+ def raw(value: Anything) -> str:
318
+ """::
319
+
320
+ string = raw(value)
321
+
322
+ Render a value as :func:`escape` does, but without escaping it. ::
323
+
324
+ # "<b><i>"
325
+ raw(['<b>', html('<i>')])
326
+ """
327
+ return _value(value, None, '', False)
328
+
329
+
330
+ def t(template: Anything, /, **values: Anything) -> Pyjo_String_Template:
331
+ """::
332
+
333
+ template = t(t'<p>{name}</p>')
334
+ template = t('<p>{name}</p>')
335
+ template = t('<p>{name}</p>', name=name)
336
+
337
+ Construct a new :mod:`Pyjo.String.Template` object from a t-string of
338
+ Python 3.14, or from a string with the fields of :meth:`str.format`, whose
339
+ values are the keyword arguments, or the variables of the caller.
340
+ """
341
+ if isinstance(template, str):
342
+ frame = inspect.currentframe()
343
+ try:
344
+ return Pyjo_String_Template(*_parse(str(template), values, frame and frame.f_back))
345
+ finally:
346
+ del frame
347
+ if values:
348
+ raise TypeError('t() takes keyword arguments only with a string')
349
+ if isinstance(template, Pyjo_String_Template):
350
+ return template
351
+ if is_template(template):
352
+ return Pyjo_String_Template(*template)
353
+ raise TypeError(f't() takes a string or a template, not {type(template).__name__}')
354
+
355
+
356
+ RE_FIELD = re.compile(r'\s*([^\W\d]\w*)((?:\.[^\W\d]\w*|\[[^\]]+\])*)\s*')
357
+ RE_ITEM = re.compile(r'\.(\w+)|\[([^\]]+)\]')
358
+
359
+ _FORMATTER = string.Formatter()
360
+
361
+
362
+ def _convert(value: Anything, conversion: str | None) -> Anything:
363
+ if conversion == 'r':
364
+ return repr(value)
365
+ if conversion == 's':
366
+ return str(value)
367
+ if conversion == 'a':
368
+ return ascii(value)
369
+ return value
370
+
371
+
372
+ def _iterable(value: Anything) -> bool:
373
+ return isinstance(value, Iterable) and not isinstance(
374
+ value, (str, bytes, bytearray, Mapping, Pyjo.String.Mixin.object)
375
+ )
376
+
377
+
378
+ def _lookup(field: str, values: Mapping[str, Anything], frame: FrameType | None) -> Any:
379
+ if (m := RE_FIELD.fullmatch(field)) is None:
380
+ if not field.strip() or field.strip().isdigit():
381
+ raise ValueError(f'Field {field!r} is positional, t() takes names only')
382
+ raise ValueError(
383
+ f'Field {field!r} is not a name with attributes and keys, pass its value as a keyword argument instead'
384
+ )
385
+ name, rest = m.groups()
386
+ value: Any
387
+ if name in values:
388
+ value = values[name]
389
+ elif frame is None:
390
+ raise NameError(f'name {name!r} is not defined', name=name)
391
+ elif name in frame.f_locals:
392
+ value = frame.f_locals[name]
393
+ elif name in frame.f_globals:
394
+ value = frame.f_globals[name]
395
+ elif name in frame.f_builtins:
396
+ value = frame.f_builtins[name]
397
+ else:
398
+ raise NameError(f'name {name!r} is not defined', name=name)
399
+ for item in RE_ITEM.finditer(rest):
400
+ attr, key = item.groups()
401
+ if attr is not None:
402
+ if attr.startswith('_'):
403
+ raise ValueError(f'Field {field!r} has an attribute that starts with "_"')
404
+ value = getattr(value, attr)
405
+ else:
406
+ value = value[int(key) if key.isdigit() else key]
407
+ return value
408
+
409
+
410
+ def _parse(source: str, values: Mapping[str, Anything], frame: FrameType | None) -> Iterator[str | Interpolation[Any]]:
411
+ try:
412
+ for text, field, spec, conversion in _FORMATTER.parse(source):
413
+ yield text
414
+ if field is None:
415
+ continue
416
+ format_spec = spec or ''
417
+ expression = field
418
+ if field.rstrip().endswith('='):
419
+ # The text of the field is kept and its value is the repr, as with "=" of a t-string
420
+ expression = field.rstrip()[:-1]
421
+ yield field
422
+ if conversion is None and not format_spec:
423
+ conversion = 'r'
424
+ if conversion not in (None, 'a', 'r', 's'):
425
+ raise ValueError(f'Field {field!r} has an unknown conversion {conversion!r}')
426
+ value = _lookup(expression, values, frame)
427
+ if '{' in format_spec:
428
+ format_spec = ''.join(_spec(format_spec, values, frame))
429
+ yield Interpolation(value, expression, conversion, format_spec)
430
+ finally:
431
+ del frame
432
+
433
+
434
+ def _render(strings: tuple[str, ...], interpolations: tuple[Interpolation[Any], ...]) -> str:
435
+ parts = [strings[0]]
436
+ for interpolation, s in zip(interpolations, strings[1:], strict=True):
437
+ parts.append(_value(interpolation.value, interpolation.conversion, interpolation.format_spec))
438
+ parts.append(s)
439
+ return ''.join(parts)
440
+
441
+
442
+ # A format specification with fields, which give strings as they are, as in a t-string
443
+ def _spec(format_spec: str, values: Mapping[str, Anything], frame: FrameType | None) -> Iterator[str]:
444
+ for text, field, spec, conversion in _FORMATTER.parse(format_spec):
445
+ yield text
446
+ if field is not None:
447
+ if '{' in (spec := spec or ''):
448
+ raise ValueError('Max string recursion exceeded')
449
+ yield format(_convert(_lookup(field, values, frame), conversion), spec)
450
+
451
+
452
+ def _value(value: Any, conversion: str | None, format_spec: str, escaped: bool = True) -> str:
453
+ if conversion or format_spec:
454
+ string = format(_convert(value, conversion), format_spec)
455
+ return Pyjo.Util.xml_escape(string) if escaped else string
456
+ if isinstance(value, Pyjo_String_Template):
457
+ return _render(value.strings, value.interpolations)
458
+ if sys.version_info >= (3, 14) and isinstance(value, Template):
459
+ return _render(value.strings, value.interpolations)
460
+ if (markup := getattr(value, '__html__', None)) is not None:
461
+ return str(markup())
462
+ if value is None:
463
+ return ''
464
+ if _iterable(value):
465
+ return ''.join(_value(v, None, '', escaped) for v in value)
466
+ return Pyjo.Util.xml_escape(format(value, '')) if escaped else format(value, '')
467
+
468
+
469
+ new = Pyjo_String_Template.new
470
+ object = Pyjo_String_Template