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.
- Pyjo/App/ASGI.py +735 -0
- Pyjo/App/CGI.py +773 -0
- Pyjo/App/FastCGI.py +631 -0
- Pyjo/App/Lambda.py +987 -0
- Pyjo/App/WSGI.py +427 -0
- Pyjo/App/__init__.py +409 -0
- Pyjo/Asset/File.py +377 -0
- Pyjo/Asset/Memory.py +235 -0
- Pyjo/Asset/__init__.py +200 -0
- Pyjo/Base.py +215 -0
- Pyjo/Cache.py +119 -0
- Pyjo/Collection.py +570 -0
- Pyjo/Content/MultiPart.py +325 -0
- Pyjo/Content/Single.py +193 -0
- Pyjo/Content/__init__.py +806 -0
- Pyjo/Cookie/Request.py +90 -0
- Pyjo/Cookie/Response.py +294 -0
- Pyjo/Cookie/__init__.py +143 -0
- Pyjo/DOM/CSS.py +1019 -0
- Pyjo/DOM/HTML.py +550 -0
- Pyjo/DOM/__init__.py +1329 -0
- Pyjo/Date.py +294 -0
- Pyjo/EventEmitter.py +522 -0
- Pyjo/Exception.py +494 -0
- Pyjo/File.py +959 -0
- Pyjo/GetOpt.py +605 -0
- Pyjo/Headers.py +760 -0
- Pyjo/HelloWorld.py +77 -0
- Pyjo/Home.py +109 -0
- Pyjo/IOLoop/ChildProcess.py +591 -0
- Pyjo/IOLoop/Client.py +736 -0
- Pyjo/IOLoop/Datagram.py +982 -0
- Pyjo/IOLoop/Messages.py +664 -0
- Pyjo/IOLoop/Server.py +458 -0
- Pyjo/IOLoop/Stdio.py +446 -0
- Pyjo/IOLoop/Stream.py +644 -0
- Pyjo/IOLoop/Subprocess.py +419 -0
- Pyjo/IOLoop/TLS.py +360 -0
- Pyjo/IOLoop/__init__.py +1036 -0
- Pyjo/IOLoop/certs/README.md +41 -0
- Pyjo/IOLoop/certs/ca.crt +20 -0
- Pyjo/IOLoop/certs/server.crt +21 -0
- Pyjo/IOLoop/certs/server.key +28 -0
- Pyjo/JSON/Pointer.py +135 -0
- Pyjo/JSON/RPC.py +772 -0
- Pyjo/JSON/Schema.py +2506 -0
- Pyjo/JSON/__init__.py +573 -0
- Pyjo/Loader.py +342 -0
- Pyjo/Log.py +723 -0
- Pyjo/Message/Request.py +725 -0
- Pyjo/Message/Response.py +476 -0
- Pyjo/Message/__init__.py +830 -0
- Pyjo/Parameters.py +472 -0
- Pyjo/Path.py +419 -0
- Pyjo/Promise.py +894 -0
- Pyjo/Prove.py +1305 -0
- Pyjo/Reactor/Asyncio.py +854 -0
- Pyjo/Reactor/Poll.py +439 -0
- Pyjo/Reactor/__init__.py +525 -0
- Pyjo/SSE.py +119 -0
- Pyjo/Server/ASGI.py +486 -0
- Pyjo/Server/CGI.py +259 -0
- Pyjo/Server/Daemon.py +1007 -0
- Pyjo/Server/FastCGI.py +517 -0
- Pyjo/Server/Hot/__init__.py +687 -0
- Pyjo/Server/Hot/__main__.py +5 -0
- Pyjo/Server/Lambda.py +641 -0
- Pyjo/Server/Prefork.py +917 -0
- Pyjo/Server/Prethread.py +394 -0
- Pyjo/Server/Reload/Backend/Poll.py +114 -0
- Pyjo/Server/Reload/Backend/__init__.py +126 -0
- Pyjo/Server/Reload/__init__.py +482 -0
- Pyjo/Server/Reload/__main__.py +5 -0
- Pyjo/Server/Stdio.py +228 -0
- Pyjo/Server/WSGI.py +349 -0
- Pyjo/Server/__init__.py +396 -0
- Pyjo/String/Bytes.py +532 -0
- Pyjo/String/HTML.py +210 -0
- Pyjo/String/Mixin.py +333 -0
- Pyjo/String/Template.py +470 -0
- Pyjo/String/Unicode.py +553 -0
- Pyjo/String/__init__.py +0 -0
- Pyjo/TOML.py +232 -0
- Pyjo/Template.py +1290 -0
- Pyjo/Test/Pyjo.py +1454 -0
- Pyjo/Test/__init__.py +1233 -0
- Pyjo/Test/pytest_plugin.py +305 -0
- Pyjo/Transaction/HTTP.py +374 -0
- Pyjo/Transaction/WebSocket.py +709 -0
- Pyjo/Transaction/__init__.py +429 -0
- Pyjo/URL/Template.py +459 -0
- Pyjo/URL/__init__.py +651 -0
- Pyjo/Upload.py +139 -0
- Pyjo/UserAgent/CookieJar.py +391 -0
- Pyjo/UserAgent/Proxy.py +138 -0
- Pyjo/UserAgent/Server.py +239 -0
- Pyjo/UserAgent/Transactor.py +766 -0
- Pyjo/UserAgent/__init__.py +1243 -0
- Pyjo/Util.py +3567 -0
- Pyjo/WebSocket.py +332 -0
- Pyjo/YAML.py +1167 -0
- Pyjo/__init__.py +441 -0
- Pyjo/__main__.py +35 -0
- Pyjo/py.typed +0 -0
- Pyjoyment/Command/Author/__init__.py +20 -0
- Pyjoyment/Command/Author/generate/__init__.py +106 -0
- Pyjoyment/Command/Author/generate/app.py +299 -0
- Pyjoyment/Command/Author/generate/dockerfile.py +281 -0
- Pyjoyment/Command/Author/generate/kubernetes.py +514 -0
- Pyjoyment/Command/Author/generate/lambda_zip.py +372 -0
- Pyjoyment/Command/Author/generate/lite_app.py +113 -0
- Pyjoyment/Command/Author/generate/plugin.py +267 -0
- Pyjoyment/Command/Author/generate/pyproject.py +123 -0
- Pyjoyment/Command/Author/inflate.py +101 -0
- Pyjoyment/Command/__init__.py +332 -0
- Pyjoyment/Command/asgi.py +92 -0
- Pyjoyment/Command/cgi.py +90 -0
- Pyjoyment/Command/daemon.py +160 -0
- Pyjoyment/Command/eval.py +128 -0
- Pyjoyment/Command/fastcgi.py +99 -0
- Pyjoyment/Command/get.py +305 -0
- Pyjoyment/Command/lambda.py +117 -0
- Pyjoyment/Command/prefork.py +181 -0
- Pyjoyment/Command/prethread.py +126 -0
- Pyjoyment/Command/routes.py +133 -0
- Pyjoyment/Command/stdio.py +92 -0
- Pyjoyment/Command/version.py +141 -0
- Pyjoyment/Command/wsgi.py +93 -0
- Pyjoyment/Commands.py +546 -0
- Pyjoyment/Controller.py +1307 -0
- Pyjoyment/HelloWorld.py +56 -0
- Pyjoyment/Lite.py +494 -0
- Pyjoyment/Plugin/AllowedHosts.py +205 -0
- Pyjoyment/Plugin/Config.py +248 -0
- Pyjoyment/Plugin/DefaultHelpers.py +1202 -0
- Pyjoyment/Plugin/EPLRenderer.py +134 -0
- Pyjoyment/Plugin/EPRenderer.py +212 -0
- Pyjoyment/Plugin/HeaderCondition.py +98 -0
- Pyjoyment/Plugin/Health.py +325 -0
- Pyjoyment/Plugin/JSONConfig.py +172 -0
- Pyjoyment/Plugin/Mount.py +223 -0
- Pyjoyment/Plugin/PyRenderer.py +209 -0
- Pyjoyment/Plugin/TOMLConfig.py +141 -0
- Pyjoyment/Plugin/TagHelpers.py +959 -0
- Pyjoyment/Plugin/YAMLConfig.py +178 -0
- Pyjoyment/Plugin/__init__.py +80 -0
- Pyjoyment/Plugins.py +215 -0
- Pyjoyment/Renderer.py +673 -0
- Pyjoyment/Routes/Match.py +251 -0
- Pyjoyment/Routes/Pattern.py +443 -0
- Pyjoyment/Routes/Route.py +827 -0
- Pyjoyment/Routes/__init__.py +419 -0
- Pyjoyment/Sessions.py +255 -0
- Pyjoyment/Static.py +421 -0
- Pyjoyment/Types.py +238 -0
- Pyjoyment/Validator/Validation.py +395 -0
- Pyjoyment/Validator/__init__.py +304 -0
- Pyjoyment/__init__.py +1083 -0
- Pyjoyment/__main__.py +5 -0
- Pyjoyment/py.typed +0 -0
- Pyjoyment/resources/public/favicon.ico +0 -0
- Pyjoyment/resources/public/favicon.svg +345 -0
- Pyjoyment/resources/public/pyjo/bootstrap/LICENSE +21 -0
- Pyjoyment/resources/public/pyjo/bootstrap/bootstrap.bundle.min.js.map +1 -0
- Pyjoyment/resources/public/pyjo/bootstrap/bootstrap.css +6 -0
- Pyjoyment/resources/public/pyjo/bootstrap/bootstrap.js +7 -0
- Pyjoyment/resources/public/pyjo/bootstrap/bootstrap.min.css.map +1 -0
- Pyjoyment/resources/public/pyjo/failunicat.png +0 -0
- Pyjoyment/resources/public/pyjo/failunicat.svg +50 -0
- Pyjoyment/resources/public/pyjo/highlight.js/LICENSE +29 -0
- Pyjoyment/resources/public/pyjo/highlight.js/highlight-mojo-dark.css +56 -0
- Pyjoyment/resources/public/pyjo/highlight.js/highlight.min.js +1262 -0
- Pyjoyment/resources/public/pyjo/highlight.js/pyjoyment.min.js +1 -0
- Pyjoyment/resources/public/pyjo/logo-black-4x.png +0 -0
- Pyjoyment/resources/public/pyjo/logo-black-4x.svg +346 -0
- Pyjoyment/resources/public/pyjo/logo-white-2x.png +0 -0
- Pyjoyment/resources/public/pyjo/logo-white-2x.svg +346 -0
- Pyjoyment/resources/public/pyjo/logo-white-4x.png +0 -0
- Pyjoyment/resources/public/pyjo/logo-white-4x.svg +346 -0
- Pyjoyment/resources/public/pyjo/logo-white.png +0 -0
- Pyjoyment/resources/public/pyjo/logo-white.svg +346 -0
- Pyjoyment/resources/public/pyjo/logo.png +0 -0
- Pyjoyment/resources/public/pyjo/logo.svg +346 -0
- Pyjoyment/resources/public/pyjo/notfound.png +0 -0
- Pyjoyment/resources/public/pyjo/notfound.svg +3 -0
- Pyjoyment/resources/public/pyjo/nounicat.png +0 -0
- Pyjoyment/resources/public/pyjo/nounicat.svg +5 -0
- Pyjoyment/resources/public/pyjo/pinstripe-dark.png +0 -0
- Pyjoyment/resources/public/pyjo/pinstripe-dark.svg +4 -0
- Pyjoyment/resources/public/pyjo/pinstripe-light.png +0 -0
- Pyjoyment/resources/public/pyjo/pinstripe-light.svg +4 -0
- Pyjoyment/resources/public/pyjo/pyjo.css +204 -0
- Pyjoyment/resources/templates/pyjo/debug.html.ep +346 -0
- Pyjoyment/resources/templates/pyjo/exception.html.ep +32 -0
- Pyjoyment/resources/templates/pyjo/not_found.html.ep +45 -0
- pyjoyment-0.1.0.dist-info/METADATA +376 -0
- pyjoyment-0.1.0.dist-info/RECORD +200 -0
- pyjoyment-0.1.0.dist-info/WHEEL +4 -0
- pyjoyment-0.1.0.dist-info/entry_points.txt +8 -0
- pyjoyment-0.1.0.dist-info/licenses/LICENSE +201 -0
Pyjo/String/Template.py
ADDED
|
@@ -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><script></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><b></li><li><i></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><b></p>"
|
|
75
|
+
t('<p>{user.name}</p>', user=user)
|
|
76
|
+
|
|
77
|
+
# "<p><b></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
|
+
# "<b><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
|