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/Path.py
ADDED
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pyjo.Path - Path
|
|
3
|
+
================
|
|
4
|
+
::
|
|
5
|
+
|
|
6
|
+
from Pyjo.Path import Path
|
|
7
|
+
|
|
8
|
+
# Parse
|
|
9
|
+
path = Path('/foo%2Fbar%3B/baz.html')
|
|
10
|
+
print(path[0])
|
|
11
|
+
|
|
12
|
+
# Or import the module, as in Google Python Style Guide
|
|
13
|
+
|
|
14
|
+
import Pyjo.Path
|
|
15
|
+
|
|
16
|
+
# Parse
|
|
17
|
+
path = Pyjo.Path.new('/foo%2Fbar%3B/baz.html')
|
|
18
|
+
print(path[0])
|
|
19
|
+
|
|
20
|
+
# Build
|
|
21
|
+
path = Pyjo.Path.new('/i/♥')
|
|
22
|
+
path.parts.append('pyjoyment')
|
|
23
|
+
print(path)
|
|
24
|
+
|
|
25
|
+
:mod:`Pyjo.Path` is a container for paths used by :mod:`Pyjo.URL`, based on
|
|
26
|
+
:rfc:`3986`.
|
|
27
|
+
|
|
28
|
+
See also
|
|
29
|
+
--------
|
|
30
|
+
|
|
31
|
+
:mod:`Pyjoyment`, https://mojolicious.org.
|
|
32
|
+
|
|
33
|
+
Ported from `Mojo::Path <https://github.com/mojolicious/mojo/blob/v9.49/lib/Mojo/Path.pm>`_
|
|
34
|
+
of Mojolicious 9.49.
|
|
35
|
+
|
|
36
|
+
Classes
|
|
37
|
+
-------
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
import Pyjo.Base
|
|
41
|
+
import Pyjo.String.Mixin
|
|
42
|
+
|
|
43
|
+
from Pyjo.Util import decode, url_escape, url_unescape
|
|
44
|
+
|
|
45
|
+
import re
|
|
46
|
+
|
|
47
|
+
from collections.abc import Iterator
|
|
48
|
+
from typing import Self
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class Pyjo_Path(Pyjo.Base.object, Pyjo.String.Mixin.object):
|
|
52
|
+
"""
|
|
53
|
+
:mod:`Pyjo.Path` inherits all methods from :mod:`Pyjo.Base` and
|
|
54
|
+
:mod:`Pyjo.String.Mixin` and implements the following new ones.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
def __init__(self, path: 'str | bytes | Pyjo_Path | None' = None) -> None:
|
|
58
|
+
"""::
|
|
59
|
+
|
|
60
|
+
path = Pyjo.Path.new()
|
|
61
|
+
path = Pyjo.Path.new('/foo%2Fbar%3B/baz.html')
|
|
62
|
+
|
|
63
|
+
Construct a new :mod:`Pyjo.Path` object and :meth:`parse` path if necessary.
|
|
64
|
+
"""
|
|
65
|
+
self.charset: str | None = 'UTF-8'
|
|
66
|
+
"""::
|
|
67
|
+
|
|
68
|
+
charset = path.charset
|
|
69
|
+
path.charset = 'UTF-8'
|
|
70
|
+
|
|
71
|
+
Charset used for encoding and decoding, defaults to ``UTF-8``. ::
|
|
72
|
+
|
|
73
|
+
# Disable encoding and decoding
|
|
74
|
+
path.charset = None
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
self._path: str | bytes | None = None
|
|
78
|
+
self._parts: list[str] | None = None
|
|
79
|
+
self._leading_slash = False
|
|
80
|
+
self._trailing_slash = False
|
|
81
|
+
|
|
82
|
+
if path is not None:
|
|
83
|
+
self.parse(path)
|
|
84
|
+
|
|
85
|
+
def __bool__(self) -> bool:
|
|
86
|
+
"""::
|
|
87
|
+
|
|
88
|
+
boolean = bool(path)
|
|
89
|
+
|
|
90
|
+
Always true.
|
|
91
|
+
"""
|
|
92
|
+
return True
|
|
93
|
+
|
|
94
|
+
def __getitem__(self, index: int) -> str:
|
|
95
|
+
"""::
|
|
96
|
+
|
|
97
|
+
part = path[0]
|
|
98
|
+
|
|
99
|
+
Alias for :attr:`parts`. Note that this will normalize the path and that
|
|
100
|
+
``%2F`` will be treated as ``/`` for security reasons. ::
|
|
101
|
+
|
|
102
|
+
print(path[0])
|
|
103
|
+
"""
|
|
104
|
+
return self.parts[index]
|
|
105
|
+
|
|
106
|
+
def __iter__(self) -> Iterator[str]:
|
|
107
|
+
"""::
|
|
108
|
+
|
|
109
|
+
parts = list(path)
|
|
110
|
+
|
|
111
|
+
Alias for :attr:`parts`. Note that this will normalize the path and that
|
|
112
|
+
``%2F`` will be treated as ``/`` for security reasons. ::
|
|
113
|
+
|
|
114
|
+
for part in path:
|
|
115
|
+
print(part)
|
|
116
|
+
"""
|
|
117
|
+
return iter(self.parts)
|
|
118
|
+
|
|
119
|
+
def __len__(self) -> int:
|
|
120
|
+
"""::
|
|
121
|
+
|
|
122
|
+
size = len(path)
|
|
123
|
+
|
|
124
|
+
Alias for :attr:`parts`. Note that this will normalize the path and that
|
|
125
|
+
``%2F`` will be treated as ``/`` for security reasons.
|
|
126
|
+
"""
|
|
127
|
+
return len(self.parts)
|
|
128
|
+
|
|
129
|
+
def __str__(self) -> str:
|
|
130
|
+
"""::
|
|
131
|
+
|
|
132
|
+
string = str(path)
|
|
133
|
+
|
|
134
|
+
Alias for :meth:`to_str`.
|
|
135
|
+
"""
|
|
136
|
+
return self.to_str()
|
|
137
|
+
|
|
138
|
+
def canonicalize(self) -> Self:
|
|
139
|
+
"""::
|
|
140
|
+
|
|
141
|
+
path = path.canonicalize()
|
|
142
|
+
|
|
143
|
+
Canonicalize path by resolving ``.`` and ``..``, in addition ``...`` will be
|
|
144
|
+
treated as ``.`` to protect from path traversal attacks. ::
|
|
145
|
+
|
|
146
|
+
# "/foo/baz"
|
|
147
|
+
Pyjo.Path.new('/foo/./bar/../baz').canonicalize()
|
|
148
|
+
|
|
149
|
+
# "/../baz"
|
|
150
|
+
Pyjo.Path.new('/foo/../bar/../../baz').canonicalize()
|
|
151
|
+
|
|
152
|
+
# "/foo/bar"
|
|
153
|
+
Pyjo.Path.new('/foo/.../bar').canonicalize()
|
|
154
|
+
"""
|
|
155
|
+
parts = self.parts
|
|
156
|
+
i = 0
|
|
157
|
+
while i < len(parts):
|
|
158
|
+
if parts[i] in ('', '.', '...'):
|
|
159
|
+
del parts[i]
|
|
160
|
+
elif i < 1 or parts[i] != '..' or parts[i - 1] == '..':
|
|
161
|
+
i += 1
|
|
162
|
+
else:
|
|
163
|
+
i -= 1
|
|
164
|
+
del parts[i : i + 2]
|
|
165
|
+
|
|
166
|
+
if not parts:
|
|
167
|
+
self.trailing_slash = False
|
|
168
|
+
return self
|
|
169
|
+
|
|
170
|
+
def clone(self) -> Self:
|
|
171
|
+
"""::
|
|
172
|
+
|
|
173
|
+
clone = path.clone()
|
|
174
|
+
|
|
175
|
+
Return a new :mod:`Pyjo.Path` object cloned from this path.
|
|
176
|
+
"""
|
|
177
|
+
clone = self.new()
|
|
178
|
+
clone.charset = self.charset
|
|
179
|
+
if self._parts is not None:
|
|
180
|
+
clone._leading_slash = self._leading_slash
|
|
181
|
+
clone._trailing_slash = self._trailing_slash
|
|
182
|
+
clone._parts = list(self._parts)
|
|
183
|
+
else:
|
|
184
|
+
clone._path = self._path
|
|
185
|
+
return clone
|
|
186
|
+
|
|
187
|
+
def contains(self, prefix: str) -> bool:
|
|
188
|
+
"""::
|
|
189
|
+
|
|
190
|
+
boolean = path.contains('/i/♥/pyjoyment')
|
|
191
|
+
|
|
192
|
+
Check if path contains given prefix. ::
|
|
193
|
+
|
|
194
|
+
# True
|
|
195
|
+
Pyjo.Path.new('/foo/bar').contains('/')
|
|
196
|
+
Pyjo.Path.new('/foo/bar').contains('/foo')
|
|
197
|
+
Pyjo.Path.new('/foo/bar').contains('/foo/bar')
|
|
198
|
+
|
|
199
|
+
# False
|
|
200
|
+
Pyjo.Path.new('/foo/bar').contains('/f')
|
|
201
|
+
Pyjo.Path.new('/foo/bar').contains('/bar')
|
|
202
|
+
Pyjo.Path.new('/foo/bar').contains('/whatever')
|
|
203
|
+
"""
|
|
204
|
+
return prefix == '/' or re.match(re.escape(prefix) + r'(?:/|$)', self.to_route()) is not None
|
|
205
|
+
|
|
206
|
+
@property
|
|
207
|
+
def leading_slash(self) -> bool:
|
|
208
|
+
"""::
|
|
209
|
+
|
|
210
|
+
boolean = path.leading_slash
|
|
211
|
+
path.leading_slash = boolean
|
|
212
|
+
|
|
213
|
+
Path has a leading slash. Note that this method will normalize the path and
|
|
214
|
+
that ``%2F`` will be treated as ``/`` for security reasons. ::
|
|
215
|
+
|
|
216
|
+
# "/foo/bar"
|
|
217
|
+
Pyjo.Path.new('foo/bar').set(leading_slash=True)
|
|
218
|
+
|
|
219
|
+
# "foo/bar"
|
|
220
|
+
Pyjo.Path.new('/foo/bar').set(leading_slash=False)
|
|
221
|
+
"""
|
|
222
|
+
self._parse()
|
|
223
|
+
return self._leading_slash
|
|
224
|
+
|
|
225
|
+
@leading_slash.setter
|
|
226
|
+
def leading_slash(self, value: bool) -> None:
|
|
227
|
+
self._parse()
|
|
228
|
+
self._leading_slash = value
|
|
229
|
+
|
|
230
|
+
def merge(self, path: 'str | bytes | Pyjo_Path') -> Self:
|
|
231
|
+
"""::
|
|
232
|
+
|
|
233
|
+
path = path.merge('/foo/bar')
|
|
234
|
+
path = path.merge('foo/bar')
|
|
235
|
+
path = path.merge(Pyjo.Path.new())
|
|
236
|
+
|
|
237
|
+
Merge paths. Note that this method will normalize both paths if necessary and
|
|
238
|
+
that ``%2F`` will be treated as ``/`` for security reasons. ::
|
|
239
|
+
|
|
240
|
+
# "/baz/yada"
|
|
241
|
+
Pyjo.Path.new('/foo/bar').merge('/baz/yada')
|
|
242
|
+
|
|
243
|
+
# "/foo/baz/yada"
|
|
244
|
+
Pyjo.Path.new('/foo/bar').merge('baz/yada')
|
|
245
|
+
|
|
246
|
+
# "/foo/bar/baz/yada"
|
|
247
|
+
Pyjo.Path.new('/foo/bar/').merge('baz/yada')
|
|
248
|
+
"""
|
|
249
|
+
# Replace
|
|
250
|
+
# Not in Mojolicious: bytes, encoded with the charset
|
|
251
|
+
if path.startswith(b'/') if isinstance(path, bytes) else str(path).startswith('/'):
|
|
252
|
+
return self.parse(path)
|
|
253
|
+
|
|
254
|
+
# Merge
|
|
255
|
+
if not self.trailing_slash and self.parts:
|
|
256
|
+
self.parts.pop()
|
|
257
|
+
other = self.new(path)
|
|
258
|
+
self.parts.extend(other.parts)
|
|
259
|
+
self.trailing_slash = other.trailing_slash
|
|
260
|
+
return self
|
|
261
|
+
|
|
262
|
+
def parse(self, path: 'str | bytes | Pyjo_Path') -> Self:
|
|
263
|
+
"""::
|
|
264
|
+
|
|
265
|
+
path = path.parse('/foo%2Fbar%3B/baz.html')
|
|
266
|
+
|
|
267
|
+
Parse path, given as characters or as bytes encoded with :attr:`charset`.
|
|
268
|
+
"""
|
|
269
|
+
# Not in Mojolicious: bytes, encoded with the charset
|
|
270
|
+
self._path = path if isinstance(path, bytes) else str(path)
|
|
271
|
+
self._parts = None
|
|
272
|
+
self._leading_slash = self._trailing_slash = False
|
|
273
|
+
return self
|
|
274
|
+
|
|
275
|
+
@property
|
|
276
|
+
def parts(self) -> list[str]:
|
|
277
|
+
"""::
|
|
278
|
+
|
|
279
|
+
parts = path.parts
|
|
280
|
+
path.parts = ['foo', 'bar', 'baz']
|
|
281
|
+
|
|
282
|
+
The path parts. Note that this method will normalize the path and that
|
|
283
|
+
``%2F`` will be treated as ``/`` for security reasons. ::
|
|
284
|
+
|
|
285
|
+
# Part with slash
|
|
286
|
+
path.parts.append('foo/bar')
|
|
287
|
+
"""
|
|
288
|
+
return self._parse()
|
|
289
|
+
|
|
290
|
+
@parts.setter
|
|
291
|
+
def parts(self, value: list[str]) -> None:
|
|
292
|
+
self._parse()
|
|
293
|
+
self._parts = value
|
|
294
|
+
|
|
295
|
+
def to_abs_str(self) -> str:
|
|
296
|
+
"""::
|
|
297
|
+
|
|
298
|
+
string = path.to_abs_str()
|
|
299
|
+
|
|
300
|
+
Turn path into an absolute string. ::
|
|
301
|
+
|
|
302
|
+
# "/i/%E2%99%A5/pyjoyment"
|
|
303
|
+
Pyjo.Path.new('/i/%E2%99%A5/pyjoyment').to_abs_str()
|
|
304
|
+
Pyjo.Path.new('i/%E2%99%A5/pyjoyment').to_abs_str()
|
|
305
|
+
"""
|
|
306
|
+
path = self.to_str()
|
|
307
|
+
return path if path.startswith('/') else f'/{path}'
|
|
308
|
+
|
|
309
|
+
def to_dir(self) -> Self:
|
|
310
|
+
"""::
|
|
311
|
+
|
|
312
|
+
dir = route.to_dir()
|
|
313
|
+
|
|
314
|
+
Clone path and remove everything after the right-most slash. ::
|
|
315
|
+
|
|
316
|
+
# "/i/%E2%99%A5/"
|
|
317
|
+
Pyjo.Path.new('/i/%E2%99%A5/pyjoyment').to_dir().to_abs_str()
|
|
318
|
+
|
|
319
|
+
# "i/%E2%99%A5/"
|
|
320
|
+
Pyjo.Path.new('i/%E2%99%A5/pyjoyment').to_dir().to_abs_str()
|
|
321
|
+
"""
|
|
322
|
+
clone = self.clone()
|
|
323
|
+
if not clone.trailing_slash and clone.parts:
|
|
324
|
+
clone.parts.pop()
|
|
325
|
+
clone.trailing_slash = bool(clone.parts)
|
|
326
|
+
return clone
|
|
327
|
+
|
|
328
|
+
def to_route(self) -> str:
|
|
329
|
+
"""::
|
|
330
|
+
|
|
331
|
+
route = path.to_route()
|
|
332
|
+
|
|
333
|
+
Turn path into a route. ::
|
|
334
|
+
|
|
335
|
+
# "/i/♥/pyjoyment"
|
|
336
|
+
Pyjo.Path.new('/i/%E2%99%A5/pyjoyment').to_route()
|
|
337
|
+
Pyjo.Path.new('i/%E2%99%A5/pyjoyment').to_route()
|
|
338
|
+
"""
|
|
339
|
+
clone = self.clone()
|
|
340
|
+
return '/' + '/'.join([*clone.parts, ''] if clone.trailing_slash else clone.parts)
|
|
341
|
+
|
|
342
|
+
def to_str(self) -> str:
|
|
343
|
+
"""::
|
|
344
|
+
|
|
345
|
+
string = path.to_str()
|
|
346
|
+
|
|
347
|
+
Turn path into a string. ::
|
|
348
|
+
|
|
349
|
+
# "/i/%E2%99%A5/pyjoyment"
|
|
350
|
+
Pyjo.Path.new('/i/%E2%99%A5/pyjoyment').to_str()
|
|
351
|
+
|
|
352
|
+
# "i/%E2%99%A5/pyjoyment"
|
|
353
|
+
Pyjo.Path.new('i/%E2%99%A5/pyjoyment').to_str()
|
|
354
|
+
"""
|
|
355
|
+
# Path
|
|
356
|
+
if self._path is not None:
|
|
357
|
+
return url_escape(self._encode(self._path), rb"^A-Za-z0-9\-._~!$&'()*+,;=%:@/").decode('ascii')
|
|
358
|
+
|
|
359
|
+
# Build path
|
|
360
|
+
path = '/'.join(
|
|
361
|
+
url_escape(self._encode(p), rb"^A-Za-z0-9\-._~!$&'()*+,;=:@").decode('ascii') for p in self.parts
|
|
362
|
+
)
|
|
363
|
+
if self.leading_slash:
|
|
364
|
+
path = f'/{path}'
|
|
365
|
+
if self.trailing_slash:
|
|
366
|
+
path = f'{path}/'
|
|
367
|
+
return path
|
|
368
|
+
|
|
369
|
+
@property
|
|
370
|
+
def trailing_slash(self) -> bool:
|
|
371
|
+
"""::
|
|
372
|
+
|
|
373
|
+
boolean = path.trailing_slash
|
|
374
|
+
path.trailing_slash = boolean
|
|
375
|
+
|
|
376
|
+
Path has a trailing slash. Note that this method will normalize the path and
|
|
377
|
+
that ``%2F`` will be treated as ``/`` for security reasons. ::
|
|
378
|
+
|
|
379
|
+
# "/foo/bar/"
|
|
380
|
+
Pyjo.Path.new('/foo/bar').set(trailing_slash=True)
|
|
381
|
+
|
|
382
|
+
# "/foo/bar"
|
|
383
|
+
Pyjo.Path.new('/foo/bar/').set(trailing_slash=False)
|
|
384
|
+
"""
|
|
385
|
+
self._parse()
|
|
386
|
+
return self._trailing_slash
|
|
387
|
+
|
|
388
|
+
@trailing_slash.setter
|
|
389
|
+
def trailing_slash(self, value: bool) -> None:
|
|
390
|
+
self._parse()
|
|
391
|
+
self._trailing_slash = value
|
|
392
|
+
|
|
393
|
+
def _encode(self, string: str | bytes) -> bytes:
|
|
394
|
+
# Without a charset, a character is a byte, as in a Perl byte string
|
|
395
|
+
return string if isinstance(string, bytes) else string.encode(self.charset or 'latin-1')
|
|
396
|
+
|
|
397
|
+
def _decode(self, path: str | bytes) -> str:
|
|
398
|
+
# What does not decode stays characters 0-255, as a Perl byte string
|
|
399
|
+
if (decoded := decode(self.charset or 'latin-1', path)) is not None:
|
|
400
|
+
return decoded
|
|
401
|
+
return path if isinstance(path, str) else path.decode('latin-1')
|
|
402
|
+
|
|
403
|
+
def _parse(self) -> list[str]:
|
|
404
|
+
if self._parts is None:
|
|
405
|
+
path = self._decode(url_unescape(self._path or ''))
|
|
406
|
+
self._path = None
|
|
407
|
+
|
|
408
|
+
self._leading_slash = path.startswith('/')
|
|
409
|
+
path = path.removeprefix('/')
|
|
410
|
+
self._trailing_slash = path.endswith('/')
|
|
411
|
+
path = path.removesuffix('/')
|
|
412
|
+
self._parts = path.split('/') if path else []
|
|
413
|
+
|
|
414
|
+
return self._parts
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
new = Pyjo_Path.new
|
|
418
|
+
object = Pyjo_Path
|
|
419
|
+
Path = Pyjo_Path
|