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,959 @@
1
+ """
2
+ Pyjoyment.Plugin.TagHelpers - Tag helpers plugin
3
+ ================================================
4
+ ::
5
+
6
+ # Pyjoyment
7
+ app.plugin('TagHelpers')
8
+
9
+ # Pyjoyment.Lite
10
+ plugin('TagHelpers')
11
+
12
+ :mod:`Pyjoyment.Plugin.TagHelpers` is a collection of HTML tag helpers for
13
+ :mod:`Pyjoyment`, based on the
14
+ `HTML Living Standard <https://html.spec.whatwg.org>`_.
15
+
16
+ Most form helpers can automatically pick up previous input values and will
17
+ show them as default. You can also use the helper ``param`` of
18
+ :mod:`Pyjoyment.Plugin.DefaultHelpers` to set them manually and let necessary
19
+ attributes always be generated automatically. ::
20
+
21
+ % if not param('country'):
22
+ % param('country', 'germany')
23
+ % end
24
+ <%= radio_button('country', 'germany') %> Germany
25
+ <%= radio_button('country', 'france') %> France
26
+ <%= radio_button('country', 'uk') %> UK
27
+
28
+ For fields that failed validation with the helper ``validation`` of
29
+ :mod:`Pyjoyment.Plugin.DefaultHelpers` the ``field-with-error`` class will be
30
+ automatically added through ``tag_with_error``, to make styling with CSS
31
+ easier. ::
32
+
33
+ <input class="field-with-error" name="age" type="text" value="250">
34
+
35
+ The helpers take the attributes of the tag as keyword arguments. An attribute
36
+ whose name is not an identifier of Python, or is a keyword of it, as
37
+ ``class``, ``for`` or ``data-id``, is passed with ``**``, and an attribute
38
+ without a value has the value ``None``. ::
39
+
40
+ %= text_field('first_name', **{'class': 'user'}, required=None)
41
+
42
+ A block of a template, between ``begin`` and ``end``, is the content of the tag
43
+ when it is the last positional argument, and the attributes follow it. ::
44
+
45
+ %= label_for('first_name', begin
46
+ First name
47
+ %= end, **{'class': 'user'})
48
+
49
+ This is a core plugin, that means it is always enabled and its code a good
50
+ example for learning how to build new plugins, you're welcome to fork it.
51
+
52
+ See :mod:`Pyjoyment.Plugins` for a list of plugins that are available by
53
+ default.
54
+
55
+ Helpers
56
+ -------
57
+
58
+ :mod:`Pyjoyment.Plugin.TagHelpers` implements the following helpers.
59
+
60
+ asset_tag ::
61
+
62
+ %= asset_tag('/app.js')
63
+ %= asset_tag('/app.js', **{'async': 'async'})
64
+
65
+ Generate ``script``, ``link`` or ``img`` tag for static asset.
66
+
67
+ button_to ::
68
+
69
+ %= button_to('Test', 'some_get_route')
70
+ %= button_to('Test', 'some_get_route', {'id': 23}, **{'class': 'menu'})
71
+ %= button_to('Test', 'http://example.com/test', **{'class': 'menu'})
72
+ %= button_to('Remove', 'some_delete_route')
73
+
74
+ Generate portable ``form`` tag with ``form_for``, containing a single button. ::
75
+
76
+ <form action="/path/to/get/route">
77
+ <input type="submit" value="Test">
78
+ </form>
79
+ <form action="/path/to/get/route/23" class="menu">
80
+ <input type="submit" value="Test">
81
+ </form>
82
+ <form action="http://example.com/test" class="menu">
83
+ <input type="submit" value="Test">
84
+ </form>
85
+ <form action="/path/to/delete/route?_method=DELETE" method="POST">
86
+ <input type="submit" value="Remove">
87
+ </form>
88
+
89
+ check_box ::
90
+
91
+ %= check_box('employed')
92
+ %= check_box('employed', 1)
93
+ %= check_box('employed', 1, checked=None, id='foo')
94
+
95
+ Generate ``input`` tag of type ``checkbox``. Previous input values will
96
+ automatically get picked up and shown as default. ::
97
+
98
+ <input name="employed" type="checkbox">
99
+ <input name="employed" type="checkbox" value="1">
100
+ <input checked id="foo" name="employed" type="checkbox" value="1">
101
+
102
+ color_field ::
103
+
104
+ %= color_field('background')
105
+ %= color_field('background', '#ffffff')
106
+ %= color_field('background', '#ffffff', id='foo')
107
+
108
+ Generate ``input`` tag of type ``color``. Previous input values will
109
+ automatically get picked up and shown as default. ::
110
+
111
+ <input name="background" type="color">
112
+ <input name="background" type="color" value="#ffffff">
113
+ <input id="foo" name="background" type="color" value="#ffffff">
114
+
115
+ csrf_button_to ::
116
+
117
+ %= csrf_button_to('Remove', 'some_delete_route')
118
+
119
+ Same as ``button_to``, but also includes a ``csrf_field``. ::
120
+
121
+ <form action="/path/to/delete/route?_method=DELETE" method="POST">
122
+ <input name="csrf_token" type="hidden" value="fa6a08...">
123
+ <input type="submit" value="Remove">
124
+ </form>
125
+
126
+ csrf_field ::
127
+
128
+ %= csrf_field()
129
+
130
+ Generate ``input`` tag of type ``hidden`` with the helper ``csrf_token`` of
131
+ :mod:`Pyjoyment.Plugin.DefaultHelpers`. ::
132
+
133
+ <input name="csrf_token" type="hidden" value="fa6a08...">
134
+
135
+ date_field ::
136
+
137
+ %= date_field('end')
138
+ %= date_field('end', '2012-12-21')
139
+ %= date_field('end', '2012-12-21', id='foo')
140
+
141
+ Generate ``input`` tag of type ``date``. Previous input values will
142
+ automatically get picked up and shown as default. ::
143
+
144
+ <input name="end" type="date">
145
+ <input name="end" type="date" value="2012-12-21">
146
+ <input id="foo" name="end" type="date" value="2012-12-21">
147
+
148
+ datetime_field ::
149
+
150
+ %= datetime_field('end')
151
+ %= datetime_field('end', '2012-12-21T23:59:59')
152
+ %= datetime_field('end', '2012-12-21T23:59:59', id='foo')
153
+
154
+ Generate ``input`` tag of type ``datetime-local``. Previous input values will
155
+ automatically get picked up and shown as default. ::
156
+
157
+ <input name="end" type="datetime-local">
158
+ <input name="end" type="datetime-local" value="2012-12-21T23:59:59">
159
+ <input id="foo" name="end" type="datetime-local" value="2012-12-21T23:59:59">
160
+
161
+ email_field ::
162
+
163
+ %= email_field('notify')
164
+ %= email_field('notify', 'nospam@example.com')
165
+ %= email_field('notify', 'nospam@example.com', id='foo')
166
+
167
+ Generate ``input`` tag of type ``email``. Previous input values will
168
+ automatically get picked up and shown as default. ::
169
+
170
+ <input name="notify" type="email">
171
+ <input name="notify" type="email" value="nospam@example.com">
172
+ <input id="foo" name="notify" type="email" value="nospam@example.com">
173
+
174
+ favicon ::
175
+
176
+ %= favicon()
177
+ %= favicon('/favicon.ico')
178
+
179
+ Generate ``link`` tag for favicon, defaulting to the one that comes bundled
180
+ with Pyjoyment. ::
181
+
182
+ <link href="/favicon.ico" rel="icon">
183
+ <link href="/favicon.ico" rel="icon">
184
+
185
+ file_field ::
186
+
187
+ %= file_field('avatar')
188
+ %= file_field('avatar', id='foo')
189
+
190
+ Generate ``input`` tag of type ``file``. ::
191
+
192
+ <input name="avatar" type="file">
193
+ <input id="foo" name="avatar" type="file">
194
+
195
+ form_for ::
196
+
197
+ %= form_for('login', begin
198
+ %= text_field('first_name')
199
+ %= submit_button()
200
+ %= end)
201
+ %= form_for('login', {'format': 'txt'}, begin
202
+ %= text_field('first_name')
203
+ %= submit_button()
204
+ %= end, method='POST')
205
+ %= form_for('/login', begin
206
+ %= text_field('first_name', disabled='disabled')
207
+ %= submit_button()
208
+ %= end, enctype='multipart/form-data')
209
+ %= form_for('http://example.com/login', begin
210
+ %= text_field('first_name')
211
+ %= submit_button()
212
+ %= end, method='POST')
213
+ %= form_for('some_delete_route', begin
214
+ %= submit_button('Remove')
215
+ %= end)
216
+
217
+ Generate portable ``form`` tag with
218
+ :meth:`Pyjoyment.Controller.Pyjoyment_Controller.url_for`. For routes that do
219
+ not allow ``GET``, a ``method`` attribute with the value ``POST`` will be
220
+ automatically added. And for methods other than ``GET`` or ``POST``, an
221
+ ``_method`` query parameter will be added as well. ::
222
+
223
+ <form action="/path/to/login">
224
+ <input name="first_name" type="text">
225
+ <input type="submit" value="Ok">
226
+ </form>
227
+ <form action="/path/to/login.txt" method="POST">
228
+ <input name="first_name" type="text">
229
+ <input type="submit" value="Ok">
230
+ </form>
231
+ <form action="/path/to/login" enctype="multipart/form-data">
232
+ <input disabled="disabled" name="first_name" type="text">
233
+ <input type="submit" value="Ok">
234
+ </form>
235
+ <form action="http://example.com/login" method="POST">
236
+ <input name="first_name" type="text">
237
+ <input type="submit" value="Ok">
238
+ </form>
239
+ <form action="/path/to/delete/route?_method=DELETE" method="POST">
240
+ <input type="submit" value="Remove">
241
+ </form>
242
+
243
+ hidden_field ::
244
+
245
+ %= hidden_field('foo', 'bar')
246
+ %= hidden_field('foo', 'bar', id='bar')
247
+
248
+ Generate ``input`` tag of type ``hidden``. ::
249
+
250
+ <input name="foo" type="hidden" value="bar">
251
+ <input id="bar" name="foo" type="hidden" value="bar">
252
+
253
+ image ::
254
+
255
+ %= image('/images/foo.png')
256
+ %= image('/images/foo.png', alt='Foo')
257
+
258
+ Generate portable ``img`` tag. ::
259
+
260
+ <img src="/path/to/images/foo.png">
261
+ <img alt="Foo" src="/path/to/images/foo.png">
262
+
263
+ input_tag ::
264
+
265
+ %= input_tag('first_name')
266
+ %= input_tag('first_name', 'Default')
267
+ %= input_tag('employed', type='checkbox')
268
+
269
+ Generate ``input`` tag. Previous input values will automatically get picked up
270
+ and shown as default. ::
271
+
272
+ <input name="first_name">
273
+ <input name="first_name" value="Default">
274
+ <input name="employed" type="checkbox">
275
+
276
+ javascript ::
277
+
278
+ %= javascript('/script.js')
279
+ %= javascript('/script.js', defer=None)
280
+ %= javascript(begin
281
+ const a = 'b';
282
+ %= end)
283
+
284
+ Generate portable ``script`` tag for JavaScript asset. ::
285
+
286
+ <script src="/path/to/script.js"></script>
287
+ <script defer src="/path/to/script.js"></script>
288
+ <script>//<![CDATA[
289
+
290
+ const a = 'b';
291
+
292
+ //]]></script>
293
+
294
+ label_for ::
295
+
296
+ %= label_for('first_name', 'First name')
297
+ %= label_for('first_name', 'First name', **{'class': 'user'})
298
+ %= label_for('first_name', begin
299
+ First name
300
+ %= end)
301
+ %= label_for('first_name', begin
302
+ First name
303
+ %= end, **{'class': 'user'})
304
+
305
+ Generate ``label`` tag. ::
306
+
307
+ <label for="first_name">First name</label>
308
+ <label class="user" for="first_name">First name</label>
309
+ <label for="first_name">
310
+ First name
311
+ </label>
312
+ <label class="user" for="first_name">
313
+ First name
314
+ </label>
315
+
316
+ link_to ::
317
+
318
+ %= link_to('Home', 'index')
319
+ %= link_to('Home', 'index', {'format': 'txt'}, **{'class': 'menu'})
320
+ %= link_to('index', {'format': 'txt'}, begin
321
+ Home
322
+ %= end, **{'class': 'menu'})
323
+ %= link_to('Contact', 'mailto:sri@example.com')
324
+ <%= link_to('index', begin %>Home<% end) %>
325
+ <%= link_to('/file.txt', begin %>File<% end) %>
326
+ <%= link_to('https://mojolicious.org', begin %>Mojolicious<% end) %>
327
+ <%= link_to(url_for().set(query={'foo': 'bar'}).to_abs(), begin %>Retry<% end) %>
328
+
329
+ Generate portable ``a`` tag with
330
+ :meth:`Pyjoyment.Controller.Pyjoyment_Controller.url_for`, defaults to using
331
+ the capitalized link target as content. ::
332
+
333
+ <a href="/path/to/index">Home</a>
334
+ <a class="menu" href="/path/to/index.txt">Home</a>
335
+ <a class="menu" href="/path/to/index.txt">
336
+ Home
337
+ </a>
338
+ <a href="mailto:sri@example.com">Contact</a>
339
+ <a href="/path/to/index">Home</a>
340
+ <a href="/path/to/file.txt">File</a>
341
+ <a href="https://mojolicious.org">Mojolicious</a>
342
+ <a href="http://127.0.0.1:3000/current/path?foo=bar">Retry</a>
343
+
344
+ The first argument to ``link_to`` is the link content, except when the last
345
+ positional argument is a callable, such as a template block (created with the
346
+ ``begin`` and ``end`` keywords); in that case, the link content is omitted at
347
+ the start of the argument list, and the block will become the link content.
348
+
349
+ month_field ::
350
+
351
+ %= month_field('vacation')
352
+ %= month_field('vacation', '2012-12')
353
+ %= month_field('vacation', '2012-12', id='foo')
354
+
355
+ Generate ``input`` tag of type ``month``. Previous input values will
356
+ automatically get picked up and shown as default. ::
357
+
358
+ <input name="vacation" type="month">
359
+ <input name="vacation" type="month" value="2012-12">
360
+ <input id="foo" name="vacation" type="month" value="2012-12">
361
+
362
+ number_field ::
363
+
364
+ %= number_field('age')
365
+ %= number_field('age', 25)
366
+ %= number_field('age', 25, id='foo', min=0, max=200)
367
+
368
+ Generate ``input`` tag of type ``number``. Previous input values will
369
+ automatically get picked up and shown as default. ::
370
+
371
+ <input name="age" type="number">
372
+ <input name="age" type="number" value="25">
373
+ <input id="foo" max="200" min="0" name="age" type="number" value="25">
374
+
375
+ password_field ::
376
+
377
+ %= password_field('pass')
378
+ %= password_field('pass', id='foo')
379
+
380
+ Generate ``input`` tag of type ``password``. ::
381
+
382
+ <input name="pass" type="password">
383
+ <input id="foo" name="pass" type="password">
384
+
385
+ radio_button ::
386
+
387
+ %= radio_button('test')
388
+ %= radio_button('country', 'germany')
389
+ %= radio_button('country', 'germany', checked=None, id='foo')
390
+
391
+ Generate ``input`` tag of type ``radio``. Previous input values will
392
+ automatically get picked up and shown as default. ::
393
+
394
+ <input name="test" type="radio">
395
+ <input name="country" type="radio" value="germany">
396
+ <input checked id="foo" name="country" type="radio" value="germany">
397
+
398
+ range_field ::
399
+
400
+ %= range_field('age')
401
+ %= range_field('age', 25)
402
+ %= range_field('age', 25, id='foo', min=0, max=200)
403
+
404
+ Generate ``input`` tag of type ``range``. Previous input values will
405
+ automatically get picked up and shown as default. ::
406
+
407
+ <input name="age" type="range">
408
+ <input name="age" type="range" value="25">
409
+ <input id="foo" max="200" min="0" name="age" type="range" value="25">
410
+
411
+ search_field ::
412
+
413
+ %= search_field('q')
414
+ %= search_field('q', 'perl')
415
+ %= search_field('q', 'perl', id='foo')
416
+
417
+ Generate ``input`` tag of type ``search``. Previous input values will
418
+ automatically get picked up and shown as default. ::
419
+
420
+ <input name="q" type="search">
421
+ <input name="q" type="search" value="perl">
422
+ <input id="foo" name="q" type="search" value="perl">
423
+
424
+ select_field ::
425
+
426
+ %= select_field('country', ['de', 'en'])
427
+ %= select_field('country', [['Germany', 'de'], 'en'], id='eu')
428
+ %= select_field('country', [['Germany', 'de', {'selected': 'selected'}], 'en'])
429
+ %= select_field('country', [c.c('EU', [['Germany', 'de'], 'en'], {'id': 'eu'})])
430
+ %= select_field('country', [c.c('EU', ['de', 'en']), c.c('Asia', ['cn', 'jp'])])
431
+
432
+ Generate ``select`` and ``option`` tags from lists and ``optgroup`` tags from
433
+ :mod:`Pyjo.Collection` objects. An option is its value or a list of its label,
434
+ its value and a dict of its attributes; a group is a collection of its label,
435
+ its options and a dict of its attributes. Previous input values will
436
+ automatically get picked up and shown as default. ::
437
+
438
+ <select name="country">
439
+ <option value="de">de</option>
440
+ <option value="en">en</option>
441
+ </select>
442
+ <select id="eu" name="country">
443
+ <option value="de">Germany</option>
444
+ <option value="en">en</option>
445
+ </select>
446
+ <select name="country">
447
+ <option selected="selected" value="de">Germany</option>
448
+ <option value="en">en</option>
449
+ </select>
450
+ <select name="country">
451
+ <optgroup id="eu" label="EU">
452
+ <option value="de">Germany</option>
453
+ <option value="en">en</option>
454
+ </optgroup>
455
+ </select>
456
+ <select name="country">
457
+ <optgroup label="EU">
458
+ <option value="de">de</option>
459
+ <option value="en">en</option>
460
+ </optgroup>
461
+ <optgroup label="Asia">
462
+ <option value="cn">cn</option>
463
+ <option value="jp">jp</option>
464
+ </optgroup>
465
+ </select>
466
+
467
+ stylesheet ::
468
+
469
+ %= stylesheet('/foo.css')
470
+ %= stylesheet('/foo.css', title='Foo style')
471
+ %= stylesheet(begin
472
+ body {color: #000}
473
+ %= end)
474
+
475
+ Generate portable ``style`` or ``link`` tag for CSS asset. ::
476
+
477
+ <link href="/path/to/foo.css" rel="stylesheet">
478
+ <link href="/path/to/foo.css" rel="stylesheet" title="Foo style">
479
+ <style>/*<![CDATA[*/
480
+
481
+ body {color: #000}
482
+
483
+ /*]]>*/</style>
484
+
485
+ submit_button ::
486
+
487
+ %= submit_button()
488
+ %= submit_button('Ok!', id='foo')
489
+
490
+ Generate ``input`` tag of type ``submit``. ::
491
+
492
+ <input type="submit" value="Ok">
493
+ <input id="foo" type="submit" value="Ok!">
494
+
495
+ t ::
496
+
497
+ %= t('div', 'test & 123')
498
+
499
+ Alias for ``tag``. ::
500
+
501
+ <div>test &amp; 123</div>
502
+
503
+ tag ::
504
+
505
+ %= tag('br')
506
+ %= tag('div')
507
+ %= tag('div', id='foo', hidden=None)
508
+ %= tag('div', 'test & 123')
509
+ %= tag('div', 'test & 123', id='foo')
510
+ %= tag('div', 'test & 123', data={'my_id': 1, 'Name': 'test'})
511
+ %= tag('div', begin
512
+ test & 123
513
+ %= end)
514
+ <%= tag('div', begin %>test & 123<% end, id='foo') %>
515
+
516
+ Alias for :meth:`Pyjo.DOM.Pyjo_DOM.new_tag`. ::
517
+
518
+ <br>
519
+ <div></div>
520
+ <div hidden id="foo"></div>
521
+ <div>test &amp; 123</div>
522
+ <div id="foo">test &amp; 123</div>
523
+ <div data-my-id="1" data-name="test">test &amp; 123</div>
524
+ <div>
525
+ test & 123
526
+ </div>
527
+ <div id="foo">test & 123</div>
528
+
529
+ Very useful for reuse in more specific tag helpers. ::
530
+
531
+ output = c.tag('meta')
532
+ output = c.tag('meta', charset='UTF-8')
533
+ output = c.tag('div', '<p>This will be escaped</p>')
534
+ output = c.tag('div', lambda: '<p>This will not be escaped</p>')
535
+
536
+ Results are automatically wrapped in :mod:`Pyjo.String.HTML` objects to
537
+ prevent accidental double escaping in ``ep`` templates.
538
+
539
+ tag_with_error ::
540
+
541
+ %= tag_with_error('input', **{'class': 'foo'})
542
+
543
+ Same as ``tag``, but adds the class ``field-with-error``. ::
544
+
545
+ <input class="foo field-with-error">
546
+
547
+ tel_field ::
548
+
549
+ %= tel_field('work')
550
+ %= tel_field('work', '123456789')
551
+ %= tel_field('work', '123456789', id='foo')
552
+
553
+ Generate ``input`` tag of type ``tel``. Previous input values will
554
+ automatically get picked up and shown as default. ::
555
+
556
+ <input name="work" type="tel">
557
+ <input name="work" type="tel" value="123456789">
558
+ <input id="foo" name="work" type="tel" value="123456789">
559
+
560
+ text_area ::
561
+
562
+ %= text_area('story')
563
+ %= text_area('story', cols=40)
564
+ %= text_area('story', 'Default', cols=40)
565
+ %= text_area('story', begin
566
+ Default
567
+ %= end, cols=40)
568
+
569
+ Generate ``textarea`` tag. Previous input values will automatically get picked
570
+ up and shown as default. ::
571
+
572
+ <textarea name="story"></textarea>
573
+ <textarea cols="40" name="story"></textarea>
574
+ <textarea cols="40" name="story">Default</textarea>
575
+ <textarea cols="40" name="story">
576
+ Default
577
+ </textarea>
578
+
579
+ text_field ::
580
+
581
+ %= text_field('first_name')
582
+ %= text_field('first_name', 'Default')
583
+ %= text_field('first_name', 'Default', **{'class': 'user'})
584
+
585
+ Generate ``input`` tag of type ``text``. Previous input values will
586
+ automatically get picked up and shown as default. ::
587
+
588
+ <input name="first_name" type="text">
589
+ <input name="first_name" type="text" value="Default">
590
+ <input class="user" name="first_name" type="text" value="Default">
591
+
592
+ time_field ::
593
+
594
+ %= time_field('start')
595
+ %= time_field('start', '23:59:59')
596
+ %= time_field('start', '23:59:59', id='foo')
597
+
598
+ Generate ``input`` tag of type ``time``. Previous input values will
599
+ automatically get picked up and shown as default. ::
600
+
601
+ <input name="start" type="time">
602
+ <input name="start" type="time" value="23:59:59">
603
+ <input id="foo" name="start" type="time" value="23:59:59">
604
+
605
+ url_field ::
606
+
607
+ %= url_field('address')
608
+ %= url_field('address', 'https://mojolicious.org')
609
+ %= url_field('address', 'https://mojolicious.org', id='foo')
610
+
611
+ Generate ``input`` tag of type ``url``. Previous input values will
612
+ automatically get picked up and shown as default. ::
613
+
614
+ <input name="address" type="url">
615
+ <input name="address" type="url" value="https://mojolicious.org">
616
+ <input id="foo" name="address" type="url" value="https://mojolicious.org">
617
+
618
+ week_field ::
619
+
620
+ %= week_field('vacation')
621
+ %= week_field('vacation', '2012-W17')
622
+ %= week_field('vacation', '2012-W17', id='foo')
623
+
624
+ Generate ``input`` tag of type ``week``. Previous input values will
625
+ automatically get picked up and shown as default. ::
626
+
627
+ <input name="vacation" type="week">
628
+ <input name="vacation" type="week" value="2012-W17">
629
+ <input id="foo" name="vacation" type="week" value="2012-W17">
630
+
631
+ See also
632
+ --------
633
+
634
+ :mod:`Pyjoyment`, https://mojolicious.org.
635
+
636
+ Ported from `Mojolicious::Plugin::TagHelpers
637
+ <https://github.com/mojolicious/mojo/blob/v9.49/lib/Mojolicious/Plugin/TagHelpers.pm>`_ of Mojolicious 9.49.
638
+ """
639
+
640
+ import Pyjo.Collection
641
+ import Pyjo.String.HTML
642
+ import Pyjo.URL
643
+ import Pyjoyment.Plugin
644
+
645
+ from Pyjo.DOM.HTML import tag_to_html
646
+ from Pyjoyment.Controller import Helper
647
+
648
+ import functools
649
+
650
+ from collections.abc import Callable
651
+ from typing import TYPE_CHECKING, Any, cast
652
+
653
+
654
+ if TYPE_CHECKING:
655
+ import Pyjoyment
656
+ import Pyjoyment.Controller
657
+
658
+ type Controller = Pyjoyment.Controller.object
659
+
660
+
661
+ class Pyjoyment_Plugin_TagHelpers(Pyjoyment.Plugin.object):
662
+ """
663
+ :mod:`Pyjoyment.Plugin.TagHelpers` inherits all methods from
664
+ :mod:`Pyjoyment.Plugin` and implements the following new ones.
665
+ """
666
+
667
+ def register(self, app: 'Pyjoyment.object', conf: dict[str, Any] | None = None) -> None:
668
+ """::
669
+
670
+ plugin.register(Pyjoyment.new())
671
+
672
+ Register helpers in :mod:`Pyjoyment` application.
673
+ """
674
+ # Text field variations
675
+ time = ('date', 'month', 'time', 'week')
676
+ for name in (*time, 'color', 'email', 'number', 'range', 'search', 'tel', 'text', 'url'):
677
+ app.helper(f'{name}_field', functools.partial(_field, name))
678
+ app.helper('datetime_field', functools.partial(_field, 'datetime-local'))
679
+
680
+ app.helper('asset_tag', _asset_tag)
681
+ app.helper('csrf_field', _csrf_field)
682
+ app.helper('form_for', _form_for)
683
+ app.helper('hidden_field', _hidden_field)
684
+ app.helper('javascript', _javascript)
685
+ app.helper('label_for', _label_for)
686
+ app.helper('link_to', _link_to)
687
+ app.helper('select_field', _select_field)
688
+ app.helper('stylesheet', _stylesheet)
689
+ app.helper('submit_button', _submit_button)
690
+ app.helper('tag_with_error', _tag_with_error)
691
+ app.helper('text_area', _text_area)
692
+
693
+ app.helper('button_to', functools.partial(_button_to, False))
694
+ app.helper('check_box', functools.partial(_field, 'checkbox'))
695
+ app.helper('csrf_button_to', functools.partial(_button_to, True))
696
+ app.helper('favicon', _favicon)
697
+ app.helper('file_field', functools.partial(_empty_field, 'file'))
698
+ app.helper('image', _image)
699
+ app.helper('input_tag', _input)
700
+ app.helper('password_field', functools.partial(_empty_field, 'password'))
701
+ app.helper('radio_button', functools.partial(_field, 'radio'))
702
+
703
+ # "t" is just a shortcut for the "tag" helper
704
+ for name in ('t', 'tag'):
705
+ app.helper(name, _tag_helper)
706
+
707
+
708
+ def _asset_tag(c: 'Controller', target: str, /, **attrs: Any) -> Pyjo.String.HTML.object:
709
+ url = c.url_for_asset(target)
710
+
711
+ helpers: Any = c.helpers
712
+ if target.endswith('.js'):
713
+ return helpers.javascript(url, **attrs)
714
+ if target.endswith('.css'):
715
+ return helpers.stylesheet(url, **attrs)
716
+ return helpers.image(url, **attrs)
717
+
718
+
719
+ def _button_to(csrf: bool, c: 'Controller', text: Any, /, *args: Any, **attrs: Any) -> Pyjo.String.HTML.object:
720
+ prefix = _csrf_field(c) if csrf else ''
721
+ return _form_for(c, *args, lambda: prefix + _submit_button(c, text), **attrs)
722
+
723
+
724
+ def _content(content: Any) -> 'str | Callable[[], str] | None':
725
+ # A number is its string, as Perl stringifies it
726
+ if content is None or isinstance(content, str):
727
+ return content
728
+ if callable(content):
729
+ return cast(Callable[[], str], content)
730
+ return str(content)
731
+
732
+
733
+ def _csrf_field(c: 'Controller', /, **attrs: Any) -> Pyjo.String.HTML.object:
734
+ helpers: Any = c.helpers
735
+ return _hidden_field(c, 'csrf_token', helpers.csrf_token(), **attrs)
736
+
737
+
738
+ def _empty_field(type: str, c: 'Controller', name: str, /, **attrs: Any) -> Pyjo.String.HTML.object:
739
+ return _validation(c, name, 'input', None, **{'name': name, **attrs, 'type': type})
740
+
741
+
742
+ def _favicon(c: 'Controller', file: Any = None, /) -> Pyjo.String.HTML.object:
743
+ return _tag('link', rel='icon', href=_file_url(c, file if file is not None else 'favicon.ico'))
744
+
745
+
746
+ def _field(type: str, c: 'Controller', name: str, /, *args: Any, **attrs: Any) -> Pyjo.String.HTML.object:
747
+ return _input(c, name, *args, **{**attrs, 'type': type})
748
+
749
+
750
+ def _file_url(c: 'Controller', url: Any) -> Pyjo.URL.object:
751
+ return url if isinstance(url, Pyjo.URL.object) else c.url_for_file(url)
752
+
753
+
754
+ def _form_for(c: 'Controller', target: Any, /, *args: Any, **attrs: Any) -> Pyjo.String.HTML.object:
755
+ url: list[Any] = [target]
756
+ rest = list(args)
757
+ if rest and isinstance(rest[0], dict):
758
+ url.append(rest.pop(0))
759
+
760
+ # Method detection
761
+ r = c.app.routes.lookup(url[0]) if isinstance(url[0], str) else None
762
+ method = r.suggested_method if r is not None else 'GET'
763
+ post = {'method': 'POST'} if method != 'GET' else {}
764
+
765
+ action = c.url_for(*url)
766
+ if post and method != 'POST':
767
+ action.query = {'_method': method}
768
+ return _tag('form', *rest, **{'action': action, **post, **attrs})
769
+
770
+
771
+ def _hidden_field(c: 'Controller', name: str, value: Any, /, **attrs: Any) -> Pyjo.String.HTML.object:
772
+ return _tag('input', **{'name': name, 'value': value, **attrs, 'type': 'hidden'})
773
+
774
+
775
+ def _image(c: 'Controller', url: Any, /, **attrs: Any) -> Pyjo.String.HTML.object:
776
+ return _tag('img', **{'src': _file_url(c, url), **attrs})
777
+
778
+
779
+ def _input(c: 'Controller', name: str, /, *args: Any, **attrs: Any) -> Pyjo.String.HTML.object:
780
+ if args:
781
+ attrs = {'value': args[0], **attrs}
782
+
783
+ if values := [str(v) for v in c.every_param(name) if v is not None]:
784
+ # Checkbox or radiobutton
785
+ type = attrs.get('type') or ''
786
+ if type == 'checkbox' or type == 'radio':
787
+ value = attrs.get('value')
788
+ value = 'on' if value is None else str(value)
789
+ attrs.pop('checked', None)
790
+ if value in values:
791
+ attrs['checked'] = None
792
+
793
+ # Others
794
+ else:
795
+ attrs['value'] = values[-1]
796
+
797
+ return _validation(c, name, 'input', None, **{'name': name, **attrs})
798
+
799
+
800
+ def _javascript(c: 'Controller', /, *args: Any, **attrs: Any) -> Pyjo.String.HTML.object:
801
+ rest = list(args)
802
+ content = f'//<![CDATA[\n{rest.pop()()}\n//]]>' if rest and callable(rest[-1]) else ''
803
+ src = {'src': _file_url(c, rest[0])} if rest else {}
804
+ return _tag('script', lambda: content, **{**src, **attrs})
805
+
806
+
807
+ def _label_for(c: 'Controller', name: str, content: Any = None, /, **attrs: Any) -> Pyjo.String.HTML.object:
808
+ return _validation(c, name, 'label', content, **{'for': name, **attrs})
809
+
810
+
811
+ def _link_to(c: 'Controller', content: Any, /, *args: Any, **attrs: Any) -> Pyjo.String.HTML.object:
812
+ url: list[Any] = [content]
813
+ rest = list(args)
814
+
815
+ # Content
816
+ if rest and callable(rest[-1]):
817
+ content = rest.pop()
818
+ else:
819
+ url = [rest.pop(0)] if rest else [content]
820
+
821
+ # Captures
822
+ if rest and isinstance(rest[0], dict):
823
+ url.append(rest.pop(0))
824
+
825
+ return _tag('a', content, **{'href': c.url_for(*url), **attrs})
826
+
827
+
828
+ # Unlike Mojolicious: the attributes of an option or a group are a dict after its value, where Mojo takes a flat list of
829
+ # names and values, as a list of Python keeps them as one value
830
+ def _option(values: set[str], pair: Any) -> Pyjo.String.HTML.object:
831
+ if not isinstance(pair, list | tuple):
832
+ pair = [pair, pair]
833
+ pair = list(cast(list[Any] | tuple[Any, ...], pair))
834
+ attrs: dict[str, Any] = {'value': pair[1], **(pair[2] if len(pair) > 2 else {})}
835
+ if values:
836
+ attrs.pop('selected', None)
837
+ if str(pair[1]) in values:
838
+ attrs['selected'] = None
839
+
840
+ return _tag('option', pair[0], **attrs)
841
+
842
+
843
+ def _select_field(c: 'Controller', name: str, options: list[Any], /, **attrs: Any) -> Pyjo.String.HTML.object:
844
+ values = {str(v) for v in c.every_param(name) if v is not None}
845
+
846
+ groups = ''
847
+ for group in options:
848
+ # "optgroup" tag
849
+ if isinstance(group, Pyjo.Collection.object):
850
+ items = list(cast(Pyjo.Collection.object[Any], group))
851
+ label, group_values = items[0], items[1]
852
+ group_attrs: dict[str, Any] = items[2] if len(items) > 2 else {}
853
+ content = ''.join(_option(values, value) for value in group_values)
854
+ groups += _tag('optgroup', lambda content=content: content, **{'label': label, **group_attrs})
855
+
856
+ # "option" tag
857
+ else:
858
+ groups += _option(values, group)
859
+
860
+ return _validation(c, name, 'select', lambda: groups, **{'name': name, **attrs})
861
+
862
+
863
+ def _stylesheet(c: 'Controller', /, *args: Any, **attrs: Any) -> Pyjo.String.HTML.object:
864
+ rest = list(args)
865
+ content = f'/*<![CDATA[*/\n{rest.pop()()}\n/*]]>*/' if rest and callable(rest[-1]) else ''
866
+ if not rest:
867
+ return _tag('style', lambda: content, **attrs)
868
+ return _tag('link', **{'rel': 'stylesheet', 'href': _file_url(c, rest[0]), **attrs})
869
+
870
+
871
+ def _submit_button(c: 'Controller', value: Any = None, /, **attrs: Any) -> Pyjo.String.HTML.object:
872
+ return _tag('input', **{'value': value if value is not None else 'Ok', **attrs, 'type': 'submit'})
873
+
874
+
875
+ def _tag(name: str, content: Any = None, /, **attrs: Any) -> Pyjo.String.HTML.object:
876
+ return Pyjo.String.HTML.new(tag_to_html(name, _content(content), **attrs))
877
+
878
+
879
+ def _tag_helper(c: 'Controller', name: str, content: Any = None, /, **attrs: Any) -> Pyjo.String.HTML.object:
880
+ return _tag(name, content, **attrs)
881
+
882
+
883
+ def _tag_with_error(c: 'Controller', tag: str, content: Any = None, /, **attrs: Any) -> Pyjo.String.HTML.object:
884
+ attrs['class'] = f'{attrs["class"]} field-with-error' if attrs.get('class') else 'field-with-error'
885
+ return _tag(tag, content, **attrs)
886
+
887
+
888
+ def _text_area(c: 'Controller', name: str, /, *args: Any, **attrs: Any) -> Pyjo.String.HTML.object:
889
+ rest = list(args)
890
+ cb = rest.pop() if rest and callable(rest[-1]) else None
891
+ content = rest[0] if rest else None
892
+ if (value := c.param(name)) is not None:
893
+ content = value
894
+ elif content is None:
895
+ content = cb if cb is not None else ''
896
+
897
+ return _validation(c, name, 'textarea', content, **{'name': name, **attrs})
898
+
899
+
900
+ def _validation(c: 'Controller', name: str, tag: str, content: Any, /, **attrs: Any) -> Pyjo.String.HTML.object:
901
+ if not c.helpers.validation().has_error(name):
902
+ return _tag(tag, content, **attrs)
903
+ return c.helpers.tag_with_error(tag, content, **attrs)
904
+
905
+
906
+ # Not in Mojolicious: the types of the helpers, for type checkers, which Pyjoyment.Controller and Pyjoyment have
907
+ class Helpers:
908
+ """
909
+ The types of the helpers of :mod:`Pyjoyment.Plugin.TagHelpers`, for type
910
+ checkers and editors, which :mod:`Pyjoyment.Controller` and
911
+ :mod:`Pyjoyment` have, while the helpers themselves are found in the
912
+ renderer of the application at run time. See
913
+ :class:`Pyjoyment.Controller.Helper`.
914
+ """
915
+
916
+ color_field = Helper(functools.partial(_field, 'color'))
917
+ date_field = Helper(functools.partial(_field, 'date'))
918
+ datetime_field = Helper(functools.partial(_field, 'datetime-local'))
919
+ email_field = Helper(functools.partial(_field, 'email'))
920
+ month_field = Helper(functools.partial(_field, 'month'))
921
+ number_field = Helper(functools.partial(_field, 'number'))
922
+ range_field = Helper(functools.partial(_field, 'range'))
923
+ search_field = Helper(functools.partial(_field, 'search'))
924
+ tel_field = Helper(functools.partial(_field, 'tel'))
925
+ text_field = Helper(functools.partial(_field, 'text'))
926
+ time_field = Helper(functools.partial(_field, 'time'))
927
+ url_field = Helper(functools.partial(_field, 'url'))
928
+ week_field = Helper(functools.partial(_field, 'week'))
929
+
930
+ asset_tag = Helper(_asset_tag)
931
+ csrf_field = Helper(_csrf_field)
932
+ form_for = Helper(_form_for)
933
+ hidden_field = Helper(_hidden_field)
934
+ javascript = Helper(_javascript)
935
+ label_for = Helper(_label_for)
936
+ link_to = Helper(_link_to)
937
+ select_field = Helper(_select_field)
938
+ stylesheet = Helper(_stylesheet)
939
+ submit_button = Helper(_submit_button)
940
+ tag_with_error = Helper(_tag_with_error)
941
+ text_area = Helper(_text_area)
942
+
943
+ button_to = Helper(functools.partial(_button_to, False))
944
+ check_box = Helper(functools.partial(_field, 'checkbox'))
945
+ csrf_button_to = Helper(functools.partial(_button_to, True))
946
+ favicon = Helper(_favicon)
947
+ file_field = Helper(functools.partial(_empty_field, 'file'))
948
+ image = Helper(_image)
949
+ input_tag = Helper(_input)
950
+ password_field = Helper(functools.partial(_empty_field, 'password'))
951
+ radio_button = Helper(functools.partial(_field, 'radio'))
952
+
953
+ t = Helper(_tag_helper)
954
+ tag = Helper(_tag_helper)
955
+
956
+
957
+ new = Pyjoyment_Plugin_TagHelpers.new
958
+ object = Pyjoyment_Plugin_TagHelpers
959
+ TagHelpers = Pyjoyment_Plugin_TagHelpers