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
Pyjo/Collection.py ADDED
@@ -0,0 +1,570 @@
1
+ r"""
2
+ Pyjo.Collection - Collection
3
+ ============================
4
+ ::
5
+
6
+ from Pyjo.Collection import Collection
7
+
8
+ # Manipulate collection
9
+ collection = Collection(['just', 'works'])
10
+ collection.insert(0, 'it')
11
+ print(collection.join("\n"))
12
+
13
+ # Or import the module, as in Google Python Style Guide
14
+
15
+ import Pyjo.Collection
16
+
17
+ # Manipulate collection
18
+ collection = Pyjo.Collection.new('just', 'works')
19
+ collection.insert(0, 'it')
20
+ print(collection.join("\n"))
21
+
22
+ # Chain methods
23
+ collection.map(lambda e: e.capitalize()).shuffle().each(lambda word, num: print(f'{num}: {word}'))
24
+
25
+ # Use the alternative constructor
26
+ from Pyjo.Collection import c
27
+ c('a', 'b', 'c').join('/').url_escape().say()
28
+
29
+ :mod:`Pyjo.Collection` is a :class:`list` for collections. The class itself
30
+ takes an iterable, as :class:`list` does. ::
31
+
32
+ # Access list directly to manipulate collection
33
+ collection = Pyjo.Collection.new(*range(1, 26))
34
+ collection[23] += 100
35
+ for e in collection:
36
+ print(e)
37
+
38
+ # Make a collection of an iterable
39
+ collection = Pyjo.Collection.object(range(1, 26))
40
+
41
+ See also
42
+ --------
43
+
44
+ :mod:`Pyjoyment`, https://mojolicious.org.
45
+
46
+ Ported from `Mojo::Collection <https://github.com/mojolicious/mojo/blob/v9.49/lib/Mojo/Collection.pm>`_
47
+ of Mojolicious 9.49.
48
+
49
+ Functions
50
+ ---------
51
+ """
52
+
53
+ import Pyjo.Base
54
+ import Pyjo.String.Unicode
55
+
56
+ import builtins
57
+ import functools
58
+ import random
59
+ import re
60
+
61
+ from collections.abc import Callable, Iterable, Iterator
62
+ from typing import Any, Self, cast, overload
63
+
64
+
65
+ type Anything = builtins.object
66
+
67
+
68
+ class Pyjo_Collection[T](list[T]):
69
+ """
70
+ :mod:`Pyjo.Collection` inherits all methods from :class:`list` and
71
+ implements the following new ones.
72
+ """
73
+
74
+ def __repr__(self) -> str:
75
+ return f'{self.__module__}.new({", ".join(map(repr, self))})'
76
+
77
+ def __json__(self) -> list[T]:
78
+ """::
79
+
80
+ array = collection.__json__()
81
+
82
+ Alias for :meth:`to_list`.
83
+ """
84
+ return self.to_list()
85
+
86
+ def compact(self) -> Self:
87
+ """::
88
+
89
+ new = collection.compact()
90
+
91
+ Create a new collection with all elements that are not ``None`` and not
92
+ an empty string. ::
93
+
94
+ # "0, 1, 2, 3"
95
+ c(0, 1, None, 2, '', 3).compact().join(', ')
96
+ """
97
+ return type(self)(e for e in self if e is not None and (not isinstance(e, (str, bytes)) or len(e)))
98
+
99
+ @overload
100
+ def each(self) -> list[T]: ...
101
+
102
+ @overload
103
+ def each(self, cb: Callable[[T, int], Any]) -> Self: ...
104
+
105
+ def each(self, cb: Callable[[T, int], Any] | None = None) -> list[T] | Self:
106
+ """::
107
+
108
+ elements = collection.each()
109
+ collection = collection.each(lambda e, num: ...)
110
+
111
+ Evaluate callback for each element in collection, or return all
112
+ elements as a list if none has been provided. The element will be the
113
+ first argument passed to the callback, and its number the second. ::
114
+
115
+ # Make a numbered list
116
+ collection.each(lambda e, num: print(f'{num}: {e}'))
117
+ """
118
+ if cb is None:
119
+ return self.to_list()
120
+ for i, e in enumerate(self, 1):
121
+ cb(e, i)
122
+ return self
123
+
124
+ @overload
125
+ def first(self, cb: Callable[[T], object] | None = None, /) -> T | None: ...
126
+
127
+ @overload
128
+ def first(self, cb: Callable[..., Any] | str | re.Pattern[str], /, *args: Any, **kwargs: Any) -> T | None: ...
129
+
130
+ def first(
131
+ self, cb: Callable[..., Any] | str | re.Pattern[str] | None = None, /, *args: Any, **kwargs: Any
132
+ ) -> T | None:
133
+ """::
134
+
135
+ first = collection.first()
136
+ first = collection.first(re.compile(r'foo'))
137
+ first = collection.first(lambda e: ...)
138
+ first = collection.first('some_method')
139
+ first = collection.first('some_method', *args)
140
+
141
+ Evaluate regular expression/callback for, or call method on, each
142
+ element in collection and return the first one that matched the
143
+ regular expression, or for which the callback/method returned true.
144
+ The element will be the first argument passed to the callback. ::
145
+
146
+ # Longer version
147
+ first = collection.first(lambda e: e.some_method(*args))
148
+
149
+ # Find first value that contains the word "pyjo"
150
+ interesting = collection.first(re.compile(r'pyjo', re.I))
151
+
152
+ # Find first value that is greater than 5
153
+ greater = collection.first(lambda e: e > 5)
154
+ """
155
+ if cb is None:
156
+ return self[0] if len(self) else None
157
+ if isinstance(cb, re.Pattern):
158
+ return next((e for e in self if cb.search(_str(e))), None)
159
+ return next((e for e in self if _call(e, cb, args, kwargs)), None)
160
+
161
+ def flatten(self) -> 'Pyjo_Collection[Any]':
162
+ """::
163
+
164
+ new = collection.flatten()
165
+
166
+ Flatten nested collections/lists/tuples recursively and create a new
167
+ collection with all elements. ::
168
+
169
+ # "1, 2, 3, 4, 5, 6, 7"
170
+ c(1, [2, [3, 4], 5, [6]], 7).flatten().join(', ')
171
+ """
172
+ return cast(Pyjo_Collection[Any], type(self)(_flatten(self)))
173
+
174
+ # Not in Mojolicious: the expression form of an index with a default, as
175
+ # dict.get() is
176
+ @overload
177
+ def get(self, index: int) -> T | None: ...
178
+
179
+ @overload
180
+ def get[D](self, index: int, default: D) -> T | D: ...
181
+
182
+ def get(self, index: int, default: Any = None) -> Any:
183
+ """::
184
+
185
+ element = collection.get(index)
186
+ element = collection.get(index, default)
187
+
188
+ Return the element at the index, or the default, which is ``None``
189
+ unless given, if there is no such element. A negative index counts
190
+ from the end. ::
191
+
192
+ import sys
193
+ from Pyjo.Util import die
194
+ address = c(*sys.argv).get(1) or die('address missing')
195
+ port = int(c(*sys.argv).get(2, '80'))
196
+ """
197
+ return self[index] if -len(self) <= index < len(self) else default
198
+
199
+ @overload
200
+ def grep(self, cb: Callable[[T], object], /) -> Self: ...
201
+
202
+ @overload
203
+ def grep(self, cb: Callable[..., Any] | str | re.Pattern[str], /, *args: Any, **kwargs: Any) -> Self: ...
204
+
205
+ def grep(self, cb: Callable[..., Any] | str | re.Pattern[str], /, *args: Any, **kwargs: Any) -> Self:
206
+ """::
207
+
208
+ new = collection.grep(re.compile(r'foo'))
209
+ new = collection.grep(lambda e: ...)
210
+ new = collection.grep('some_method')
211
+ new = collection.grep('some_method', *args)
212
+
213
+ Evaluate regular expression/callback for, or call method on, each
214
+ element in collection and create a new collection with all elements
215
+ that matched the regular expression, or for which the callback/method
216
+ returned true. The element will be the first argument passed to the
217
+ callback. ::
218
+
219
+ # Longer version
220
+ new = collection.grep(lambda e: e.some_method(*args))
221
+
222
+ # Find all values that contain the word "pyjo"
223
+ interesting = collection.grep(re.compile(r'pyjo', re.I))
224
+
225
+ # Find all values that are greater than 5
226
+ greater = collection.grep(lambda e: e > 5)
227
+ """
228
+ if isinstance(cb, re.Pattern):
229
+ return type(self)(e for e in self if cb.search(_str(e)))
230
+ return type(self)(e for e in self if _call(e, cb, args, kwargs))
231
+
232
+ def head(self, size: int) -> Self:
233
+ """::
234
+
235
+ new = collection.head(4)
236
+ new = collection.head(-2)
237
+
238
+ Create a new collection with up to the specified number of elements
239
+ from the beginning of the collection. A negative number will count
240
+ from the end. ::
241
+
242
+ # "A B C"
243
+ c('A', 'B', 'C', 'D', 'E').head(3).join(' ')
244
+
245
+ # "A B"
246
+ c('A', 'B', 'C', 'D', 'E').head(-3).join(' ')
247
+ """
248
+ return type(self)(self[:size])
249
+
250
+ def join(self, string: str | None = None) -> 'Pyjo.String.Unicode.object':
251
+ r"""::
252
+
253
+ string = collection.join()
254
+ string = collection.join("\n")
255
+
256
+ Turn collection into :mod:`Pyjo.String.Unicode`. ::
257
+
258
+ # Join all values with commas
259
+ collection.join(', ').say()
260
+ """
261
+ return Pyjo.String.Unicode.new((string or '').join(map(_str, self)))
262
+
263
+ def last(self) -> T | None:
264
+ """::
265
+
266
+ last = collection.last()
267
+
268
+ Return the last element in collection.
269
+ """
270
+ return self[-1] if len(self) else None
271
+
272
+ @overload
273
+ def map[U](self, cb: Callable[[T], U], /) -> 'Pyjo_Collection[U]': ...
274
+
275
+ @overload
276
+ def map(self, cb: Callable[..., Any] | str, /, *args: Any, **kwargs: Any) -> 'Pyjo_Collection[Any]': ...
277
+
278
+ def map(self, cb: Callable[..., Any] | str, /, *args: Any, **kwargs: Any) -> 'Pyjo_Collection[Any]':
279
+ """::
280
+
281
+ new = collection.map(lambda e: ...)
282
+ new = collection.map('some_method')
283
+ new = collection.map('some_method', *args)
284
+
285
+ Evaluate callback for, or call method on, each element in collection
286
+ and create a new collection from the results. The element will be the
287
+ first argument passed to the callback. ::
288
+
289
+ # Longer version
290
+ new = collection.map(lambda e: e.some_method(*args))
291
+
292
+ # Append the word "pyjo" to all values
293
+ pyjoified = collection.map(lambda e: e + 'pyjo')
294
+ """
295
+ return cast(Pyjo_Collection[Any], type(self)(_call(e, cb, args, kwargs) for e in self))
296
+
297
+ @classmethod
298
+ def new(cls, *elements: T) -> Self:
299
+ """::
300
+
301
+ collection = Pyjo.Collection.new(1, 2, 3)
302
+
303
+ Construct a new :mod:`Pyjo.Collection` object. The constructor of the
304
+ class takes an iterable, as the one of :class:`list`, so that a single
305
+ element that is iterable stays one element of :meth:`new`. ::
306
+
307
+ collection = Collection([1, 2, 3])
308
+ collection = Collection(n for n in range(3))
309
+ """
310
+ return cls(elements)
311
+
312
+ def reduce(self, cb: Callable[[Any, T], Any], *initial: Any) -> Any:
313
+ """::
314
+
315
+ result = collection.reduce(lambda a, b: ...)
316
+ result = collection.reduce(lambda a, b: ..., initial)
317
+
318
+ Reduce elements in collection with a callback and return its final
319
+ result, passing ``a`` and ``b`` each time the callback is executed. The
320
+ first time ``a`` will be an optional initial value or the first element
321
+ in the collection. And from then on ``a`` will be the return value of
322
+ the callback, while ``b`` will always be the next element in the
323
+ collection. ::
324
+
325
+ # Calculate the sum of all values
326
+ total = collection.reduce(lambda a, b: a + b)
327
+
328
+ # Count how often each value occurs in collection
329
+ counts = collection.reduce(lambda a, b: a | {b: a.get(b, 0) + 1}, {})
330
+ """
331
+ elements = [*initial, *self]
332
+ return functools.reduce(cb, elements) if len(elements) else None
333
+
334
+ # Mojo's reverse creates a new collection, list.reverse() reverses in place
335
+ def reverse(self) -> Self: # pyright: ignore[reportIncompatibleMethodOverride]
336
+ """::
337
+
338
+ new = collection.reverse()
339
+
340
+ Create a new collection with all elements in reverse order.
341
+ """
342
+ return type(self)(reversed(self))
343
+
344
+ def shuffle(self) -> Self:
345
+ """::
346
+
347
+ new = collection.shuffle()
348
+
349
+ Create a new collection with all elements in random order.
350
+ """
351
+ return type(self)(random.sample(self, len(self)))
352
+
353
+ @property
354
+ def size(self) -> int:
355
+ """::
356
+
357
+ size = collection.size
358
+
359
+ Number of elements in collection.
360
+ """
361
+ return len(self)
362
+
363
+ # Mojo's sort creates a new collection, list.sort() sorts in place
364
+ def sort( # pyright: ignore[reportIncompatibleMethodOverride]
365
+ self, *, key: Callable[[T], Any] | None = None, reverse: bool = False
366
+ ) -> Self:
367
+ """::
368
+
369
+ new = collection.sort()
370
+ new = collection.sort(key=lambda e: ...)
371
+ new = collection.sort(reverse=True)
372
+
373
+ Sort elements based on return value of a key function and create a new
374
+ collection from the results, as :func:`sorted` does. ::
375
+
376
+ # Sort values case-insensitive
377
+ case_insensitive = collection.sort(key=str.upper)
378
+ """
379
+ # Unlike Mojolicious: Mojo takes a function that compares $a and $b, which
380
+ # Python 3 has no use for; the key and reverse arguments are sorted()'s
381
+ return type(self)(sorted(cast(list[Any], self), key=key, reverse=reverse))
382
+
383
+ def tail(self, size: int) -> Self:
384
+ """::
385
+
386
+ new = collection.tail(4)
387
+ new = collection.tail(-2)
388
+
389
+ Create a new collection with up to the specified number of elements
390
+ from the end of the collection. A negative number will count from the
391
+ beginning. ::
392
+
393
+ # "C D E"
394
+ c('A', 'B', 'C', 'D', 'E').tail(3).join(' ')
395
+
396
+ # "D E"
397
+ c('A', 'B', 'C', 'D', 'E').tail(-3).join(' ')
398
+ """
399
+ return type(self)(self[max(len(self) - size, 0) :] if size >= 0 else self[-size:])
400
+
401
+ def tap(self, cb: Callable[..., Any] | str, *args: Any, **kwargs: Any) -> Self:
402
+ """::
403
+
404
+ collection = collection.tap(lambda c: ...)
405
+
406
+ Alias for :meth:`Pyjo.Base.tap`.
407
+ """
408
+ Pyjo.Base.tap(self, cb, *args, **kwargs)
409
+ return self
410
+
411
+ # Not in Mojolicious: a conversion to a type of the standard library
412
+ def to_dict[K, V](self: 'Pyjo_Collection[tuple[K, V]]') -> dict[K, V]:
413
+ """::
414
+
415
+ d = collection.to_dict()
416
+
417
+ Turn collection of pairs of key and value into :class:`dict`. ::
418
+
419
+ # {'a': 1, 'b': 2, 'c': 3}
420
+ c(('a', 1), ('b', 2), ('c', 3)).to_dict()
421
+ """
422
+ return dict(self)
423
+
424
+ # Not in Mojolicious: a conversion to a type of the standard library
425
+ def to_iter(self) -> Iterator[T]:
426
+ """::
427
+
428
+ i = collection.to_iter()
429
+
430
+ Turn collection into an iterator. ::
431
+
432
+ for e in c(1, 2, 3).to_iter():
433
+ print(e)
434
+ """
435
+ return iter(self)
436
+
437
+ def to_list(self) -> list[T]:
438
+ """::
439
+
440
+ array = collection.to_list()
441
+
442
+ Turn collection into :class:`list`.
443
+ """
444
+ return list(self)
445
+
446
+ # Not in Mojolicious: a conversion to a type of the standard library
447
+ def to_set(self) -> set[T]:
448
+ """::
449
+
450
+ s = collection.to_set()
451
+
452
+ Turn collection into :class:`set`. ::
453
+
454
+ # {1, 2, 3}
455
+ c(1, 2, 3, 2).to_set()
456
+ """
457
+ return set(self)
458
+
459
+ # Not in Mojolicious: a conversion to a type of the standard library
460
+ def to_tuple(self) -> tuple[T, ...]:
461
+ """::
462
+
463
+ t = collection.to_tuple()
464
+
465
+ Turn collection into :class:`tuple`. ::
466
+
467
+ # (1, 2, 3)
468
+ c(1, 2, 3).to_tuple()
469
+ """
470
+ return tuple(self)
471
+
472
+ def uniq(self, cb: Callable[..., Any] | str | None = None, *args: Any, **kwargs: Any) -> Self:
473
+ """::
474
+
475
+ new = collection.uniq()
476
+ new = collection.uniq(lambda e: ...)
477
+ new = collection.uniq('some_method')
478
+ new = collection.uniq('some_method', *args)
479
+
480
+ Create a new collection without duplicate elements, using the string
481
+ representation of either the elements or the return value of the
482
+ callback/method to decide uniqueness. Note that ``None`` and empty
483
+ string are treated the same. ::
484
+
485
+ # Longer version
486
+ new = collection.uniq(lambda e: e.some_method(*args))
487
+
488
+ # "foo bar baz"
489
+ c('foo', 'bar', 'bar', 'baz').uniq().join(' ')
490
+
491
+ # [[1, 2], [2, 1]]
492
+ c([1, 2], [2, 1], [3, 2]).uniq(lambda e: e[1]).to_list()
493
+ """
494
+ seen: set[str] = set()
495
+ new = type(self)()
496
+ for e in self:
497
+ value = e if cb is None else _call(e, cb, args, kwargs)
498
+ if (key := '' if value is None else _str(value)) not in seen:
499
+ seen.add(key)
500
+ new.append(e)
501
+ return new
502
+
503
+ # Left out from Mojolicious: with_roles, as Pyjoyment has no roles
504
+
505
+ # Not in Mojolicious: the method form of zip(), for a chain
506
+ @overload
507
+ def zip[U](self, iterable: Iterable[U], /, *, strict: bool = False) -> 'Pyjo_Collection[tuple[T, U]]': ...
508
+
509
+ @overload
510
+ def zip(self, *iterables: Iterable[Any], strict: bool = False) -> 'Pyjo_Collection[tuple[Any, ...]]': ...
511
+
512
+ def zip(self, *iterables: Iterable[Any], strict: bool = False) -> 'Pyjo_Collection[tuple[Any, ...]]':
513
+ """::
514
+
515
+ new = collection.zip(iterable)
516
+ new = collection.zip(iterable, strict=True)
517
+
518
+ Create a new collection of tuples of the elements of the collection and
519
+ of the iterables, as :func:`zip` does. ::
520
+
521
+ # [('a', 1), ('b', 2), ('c', 3)]
522
+ c('a', 'b', 'c').zip([1, 2, 3]).to_list()
523
+
524
+ # {'a': 1, 'b': 2, 'c': 3}
525
+ c('a', 'b', 'c').zip([1, 2, 3]).to_dict()
526
+ """
527
+ return cast(
528
+ Pyjo_Collection[tuple[Any, ...]], type(self)(cast(Iterable[Any], zip(self, *iterables, strict=strict)))
529
+ )
530
+
531
+
532
+ def c[T](*elements: T) -> Pyjo_Collection[T]:
533
+ """::
534
+
535
+ collection = c(1, 2, 3)
536
+
537
+ Construct a new :mod:`Pyjo.Collection` object.
538
+ """
539
+ return Pyjo_Collection(elements)
540
+
541
+
542
+ # A method of the element, or Mojo's attribute, which is a property
543
+ def _call(element: Any, cb: Callable[..., Any] | str, args: tuple[Any, ...], kwargs: dict[str, Any]) -> Any:
544
+ if callable(cb):
545
+ return cb(element, *args, **kwargs)
546
+ value = getattr(element, cb)
547
+ if callable(value):
548
+ return value(*args, **kwargs)
549
+ if len(args):
550
+ setattr(element, cb, args[0])
551
+ return element
552
+ return value
553
+
554
+
555
+ def _flatten(elements: Iterable[Any]) -> Iterable[Any]:
556
+ for e in elements:
557
+ if isinstance(e, (list, tuple)):
558
+ yield from _flatten(cast(Iterable[Any], e))
559
+ else:
560
+ yield e
561
+
562
+
563
+ def _str(element: Anything) -> str:
564
+ return Pyjo.String.Unicode.new(element).to_str()
565
+
566
+
567
+ # Any: the type of the elements is not known before they are given
568
+ new = Pyjo_Collection[Any].new
569
+ object = Pyjo_Collection
570
+ Collection = Pyjo_Collection