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/File.py
ADDED
|
@@ -0,0 +1,959 @@
|
|
|
1
|
+
r"""
|
|
2
|
+
Pyjo.File - File system paths
|
|
3
|
+
=============================
|
|
4
|
+
::
|
|
5
|
+
|
|
6
|
+
from Pyjo.File import File
|
|
7
|
+
|
|
8
|
+
# Portably deal with file system paths
|
|
9
|
+
path = File('/home/sri/.vimrc')
|
|
10
|
+
print(path.slurp())
|
|
11
|
+
print(path.dirname())
|
|
12
|
+
print(path.basename())
|
|
13
|
+
print(path.extname)
|
|
14
|
+
print(path.sibling('.bashrc'))
|
|
15
|
+
|
|
16
|
+
# Or import the module, as in Google Python Style Guide
|
|
17
|
+
|
|
18
|
+
import Pyjo.File
|
|
19
|
+
|
|
20
|
+
# Portably deal with file system paths
|
|
21
|
+
path = Pyjo.File.new('/home/sri/.vimrc')
|
|
22
|
+
print(path.slurp())
|
|
23
|
+
print(path.dirname())
|
|
24
|
+
print(path.basename())
|
|
25
|
+
print(path.extname)
|
|
26
|
+
print(path.sibling('.bashrc'))
|
|
27
|
+
|
|
28
|
+
# Use the alternative constructor
|
|
29
|
+
from Pyjo.File import path
|
|
30
|
+
path = path('/tmp/foo/bar').make_path()
|
|
31
|
+
path.child('test.txt').spew('Hello Pyjo!')
|
|
32
|
+
|
|
33
|
+
:mod:`Pyjo.File` is a container for file system paths that provides a
|
|
34
|
+
friendly API for dealing with different operating systems. It keeps the path
|
|
35
|
+
as it is given, and it is an :class:`os.PathLike`, so the standard library
|
|
36
|
+
takes it where it takes a path. ::
|
|
37
|
+
|
|
38
|
+
# Use the path with the standard library
|
|
39
|
+
path = Pyjo.File.new('/home/sri/test')
|
|
40
|
+
with open(path) as f:
|
|
41
|
+
print(f.read())
|
|
42
|
+
|
|
43
|
+
See also
|
|
44
|
+
--------
|
|
45
|
+
|
|
46
|
+
:mod:`Pyjoyment`, https://mojolicious.org.
|
|
47
|
+
|
|
48
|
+
Ported from `Mojo::File <https://github.com/mojolicious/mojo/blob/v9.49/lib/Mojo/File.pm>`_
|
|
49
|
+
of Mojolicious 9.49.
|
|
50
|
+
|
|
51
|
+
Functions
|
|
52
|
+
---------
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
import Pyjo.Base
|
|
56
|
+
import Pyjo.Collection
|
|
57
|
+
import Pyjo.Exception
|
|
58
|
+
import Pyjo.String.Mixin
|
|
59
|
+
|
|
60
|
+
from Pyjo.Util import decode, encode
|
|
61
|
+
|
|
62
|
+
import builtins
|
|
63
|
+
import inspect
|
|
64
|
+
import os
|
|
65
|
+
import pathlib
|
|
66
|
+
import random
|
|
67
|
+
import re
|
|
68
|
+
import shutil
|
|
69
|
+
import string
|
|
70
|
+
import sys
|
|
71
|
+
import tempfile as _tempfile
|
|
72
|
+
import weakref
|
|
73
|
+
|
|
74
|
+
from collections.abc import Callable, Iterator
|
|
75
|
+
from types import TracebackType
|
|
76
|
+
from typing import IO, TYPE_CHECKING, Any, NoReturn, Self, cast
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
if TYPE_CHECKING:
|
|
80
|
+
import Pyjo.Transaction.HTTP
|
|
81
|
+
import Pyjo.UserAgent
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
_random = random.SystemRandom()
|
|
85
|
+
|
|
86
|
+
# File::Spec and File::Basename of Perl follow the system: on Windows a path may have a volume, and both / and \
|
|
87
|
+
# separate its levels
|
|
88
|
+
_WIN32 = sys.platform == 'win32'
|
|
89
|
+
|
|
90
|
+
# The bytes of a file as they are, as sysread and syswrite of Perl pass them, where the text mode of the C runtime of
|
|
91
|
+
# Windows, the default of os.open there, writes \n as CRLF and reads CRLF as \n
|
|
92
|
+
_BINARY: int = getattr(os, 'O_BINARY', 0)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
type Anything = builtins.object
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class Pyjo_File(Pyjo.Base.object, Pyjo.String.Mixin.object):
|
|
99
|
+
"""
|
|
100
|
+
:mod:`Pyjo.File` inherits all methods from :mod:`Pyjo.Base` and
|
|
101
|
+
:mod:`Pyjo.String.Mixin` and implements the following new ones.
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
def __init__(self, *parts: Anything) -> None:
|
|
105
|
+
"""::
|
|
106
|
+
|
|
107
|
+
path = Pyjo.File.new()
|
|
108
|
+
path = Pyjo.File.new('/home/sri/.vimrc')
|
|
109
|
+
path = Pyjo.File.new('/home', 'sri', '.vimrc')
|
|
110
|
+
path = Pyjo.File.new(pathlib.Path('/home/sri/.vimrc'))
|
|
111
|
+
path = Pyjo.File.new(tempfile.NamedTemporaryFile())
|
|
112
|
+
path = Pyjo.File.new(tempfile.TemporaryDirectory())
|
|
113
|
+
|
|
114
|
+
Construct a new :mod:`Pyjo.File` object, defaults to using the current
|
|
115
|
+
working directory. A temporary file or directory is kept as long as the
|
|
116
|
+
object is, or until the end of a ``with`` block. ::
|
|
117
|
+
|
|
118
|
+
# "foo/bar/baz.txt" (on UNIX)
|
|
119
|
+
Pyjo.File.new('foo', 'bar', 'baz.txt')
|
|
120
|
+
"""
|
|
121
|
+
if any(p is None for p in parts):
|
|
122
|
+
raise ValueError('Invalid path')
|
|
123
|
+
|
|
124
|
+
# Not in Mojolicious: a temporary file or directory of the standard
|
|
125
|
+
# library, kept as Mojo keeps the object of File::Temp
|
|
126
|
+
self._temp = parts[0] if len(parts) == 1 and _is_temp(parts[0]) else None
|
|
127
|
+
self._path = (
|
|
128
|
+
_str(parts[0])
|
|
129
|
+
if len(parts) == 1
|
|
130
|
+
else _catfile(*map(_str, parts))
|
|
131
|
+
if len(parts)
|
|
132
|
+
else _canonpath(os.getcwd())
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
# Not in Mojolicious: a context manager, which removes the temporary file or directory that the object keeps where the
|
|
136
|
+
# block ends, as PyPy frees the object only when its garbage collector runs
|
|
137
|
+
def __enter__(self) -> Self:
|
|
138
|
+
return self
|
|
139
|
+
|
|
140
|
+
def __exit__(
|
|
141
|
+
self, type: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None
|
|
142
|
+
) -> None:
|
|
143
|
+
if (temp := self._temp) is not None:
|
|
144
|
+
_release(temp)
|
|
145
|
+
|
|
146
|
+
def __bool__(self) -> bool:
|
|
147
|
+
"""::
|
|
148
|
+
|
|
149
|
+
boolean = bool(path)
|
|
150
|
+
|
|
151
|
+
Always true.
|
|
152
|
+
"""
|
|
153
|
+
return True
|
|
154
|
+
|
|
155
|
+
def __fspath__(self) -> str:
|
|
156
|
+
"""::
|
|
157
|
+
|
|
158
|
+
string = os.fspath(path)
|
|
159
|
+
|
|
160
|
+
The path for the standard library, as :meth:`to_str` gives it.
|
|
161
|
+
"""
|
|
162
|
+
return self._path
|
|
163
|
+
|
|
164
|
+
def __getitem__(self, index: int) -> str:
|
|
165
|
+
"""::
|
|
166
|
+
|
|
167
|
+
part = path[0]
|
|
168
|
+
|
|
169
|
+
A part of the path, as :meth:`to_list` gives it.
|
|
170
|
+
"""
|
|
171
|
+
return self.to_list()[index]
|
|
172
|
+
|
|
173
|
+
def __iter__(self) -> Iterator[str]:
|
|
174
|
+
"""::
|
|
175
|
+
|
|
176
|
+
for part in path:
|
|
177
|
+
...
|
|
178
|
+
|
|
179
|
+
Iterate the parts of the path, as :meth:`to_list` gives them.
|
|
180
|
+
"""
|
|
181
|
+
return iter(self.to_list())
|
|
182
|
+
|
|
183
|
+
def __len__(self) -> int:
|
|
184
|
+
"""::
|
|
185
|
+
|
|
186
|
+
size = len(path)
|
|
187
|
+
|
|
188
|
+
Number of the parts of the path.
|
|
189
|
+
"""
|
|
190
|
+
return len(self.to_list())
|
|
191
|
+
|
|
192
|
+
def basename(self, *suffixes: str) -> str:
|
|
193
|
+
"""::
|
|
194
|
+
|
|
195
|
+
name = path.basename()
|
|
196
|
+
name = path.basename('.txt')
|
|
197
|
+
|
|
198
|
+
Return the last level of the path, as ``File::Basename`` of Perl does. ::
|
|
199
|
+
|
|
200
|
+
# ".vimrc" (on UNIX)
|
|
201
|
+
path('/home/sri/.vimrc').basename()
|
|
202
|
+
|
|
203
|
+
# "test" (on UNIX)
|
|
204
|
+
path('/home/sri/test.txt').basename('.txt')
|
|
205
|
+
"""
|
|
206
|
+
return _basename(self._path, *suffixes)
|
|
207
|
+
|
|
208
|
+
def child(self, *parts: Anything) -> Self:
|
|
209
|
+
"""::
|
|
210
|
+
|
|
211
|
+
child = path.child('.vimrc')
|
|
212
|
+
|
|
213
|
+
Return a new :mod:`Pyjo.File` object relative to the path. A child of
|
|
214
|
+
a temporary directory keeps the directory as long as it is kept. ::
|
|
215
|
+
|
|
216
|
+
# "/home/sri/.vimrc" (on UNIX)
|
|
217
|
+
path('/home').child('sri', '.vimrc')
|
|
218
|
+
"""
|
|
219
|
+
child = self.new(self._path, *parts)
|
|
220
|
+
# Unlike Mojolicious: Perl keeps the temporary directory of
|
|
221
|
+
# tempdir()->child(...) until the end of the statement, Python frees it
|
|
222
|
+
# as soon as child() returns, so the child keeps it
|
|
223
|
+
child._temp = self._temp
|
|
224
|
+
return child
|
|
225
|
+
|
|
226
|
+
def chmod(self, mode: int) -> Self:
|
|
227
|
+
"""::
|
|
228
|
+
|
|
229
|
+
path = path.chmod(0o644)
|
|
230
|
+
|
|
231
|
+
Change file permissions.
|
|
232
|
+
"""
|
|
233
|
+
try:
|
|
234
|
+
os.chmod(self._path, mode)
|
|
235
|
+
except OSError as e:
|
|
236
|
+
_croak(e, f'Can\'t chmod file "{self._path}"')
|
|
237
|
+
return self
|
|
238
|
+
|
|
239
|
+
def copy_to(self, to: Anything) -> Self:
|
|
240
|
+
"""::
|
|
241
|
+
|
|
242
|
+
destination = path.copy_to('/home/sri')
|
|
243
|
+
destination = path.copy_to('/home/sri/.vimrc.backup')
|
|
244
|
+
|
|
245
|
+
Copy file with :mod:`shutil` and return the destination as a
|
|
246
|
+
:mod:`Pyjo.File` object.
|
|
247
|
+
"""
|
|
248
|
+
destination = self._destination(to)
|
|
249
|
+
try:
|
|
250
|
+
shutil.copyfile(self._path, destination)
|
|
251
|
+
except OSError as e:
|
|
252
|
+
_croak(e, f'Can\'t copy file "{self._path}" to "{_str(to)}"')
|
|
253
|
+
return self.new(destination)
|
|
254
|
+
|
|
255
|
+
def dirname(self) -> Self:
|
|
256
|
+
"""::
|
|
257
|
+
|
|
258
|
+
name = path.dirname()
|
|
259
|
+
|
|
260
|
+
Return all but the last level of the path, as ``File::Basename`` of Perl
|
|
261
|
+
does, as a :mod:`Pyjo.File` object. ::
|
|
262
|
+
|
|
263
|
+
# "/home/sri" (on UNIX)
|
|
264
|
+
path('/home/sri/.vimrc').dirname()
|
|
265
|
+
"""
|
|
266
|
+
return self.new(_dirname(self._path))
|
|
267
|
+
|
|
268
|
+
def download(
|
|
269
|
+
self, url: Anything, *, headers: dict[str, Any] | None = None, ua: 'Pyjo.UserAgent.object | None' = None
|
|
270
|
+
) -> bool:
|
|
271
|
+
"""::
|
|
272
|
+
|
|
273
|
+
boolean = path.download('https://example.com/test.tar.gz')
|
|
274
|
+
boolean = path.download('https://example.com/test.tar.gz', headers={'Accept': '*/*'})
|
|
275
|
+
boolean = path.download('https://example.com/test.tar.gz', ua=Pyjo.UserAgent.new())
|
|
276
|
+
|
|
277
|
+
Download file from URL, returns true once the file has been downloaded
|
|
278
|
+
completely. Incomplete downloads are resumed. Follows ``10`` redirects
|
|
279
|
+
by default and does not limit the size of the response, which will be
|
|
280
|
+
streamed memory efficiently. Note that this method is **EXPERIMENTAL**
|
|
281
|
+
and might change without warning!
|
|
282
|
+
"""
|
|
283
|
+
import Pyjo.UserAgent
|
|
284
|
+
|
|
285
|
+
if ua is None:
|
|
286
|
+
ua = Pyjo.UserAgent.new(max_redirects=10, max_response_size=0)
|
|
287
|
+
tx = _download_error(ua.transactor.download(ua.head(url, headers or {}), self._path))
|
|
288
|
+
return bool(_download_error(ua.start(tx))) if tx is not None else True
|
|
289
|
+
|
|
290
|
+
@property
|
|
291
|
+
def extname(self) -> str:
|
|
292
|
+
"""::
|
|
293
|
+
|
|
294
|
+
ext = path.extname
|
|
295
|
+
|
|
296
|
+
Return file extension of the path. ::
|
|
297
|
+
|
|
298
|
+
# "js"
|
|
299
|
+
path('/home/sri/test.js').extname
|
|
300
|
+
"""
|
|
301
|
+
return m[1] if (m := re.search(r'.+\.([^.]+)$', self.basename())) else ''
|
|
302
|
+
|
|
303
|
+
@property
|
|
304
|
+
def is_abs(self) -> bool:
|
|
305
|
+
"""::
|
|
306
|
+
|
|
307
|
+
boolean = path.is_abs
|
|
308
|
+
|
|
309
|
+
Check if the path is absolute. ::
|
|
310
|
+
|
|
311
|
+
# True (on UNIX)
|
|
312
|
+
path('/home/sri/.vimrc').is_abs
|
|
313
|
+
|
|
314
|
+
# False (on UNIX)
|
|
315
|
+
path('.vimrc').is_abs
|
|
316
|
+
"""
|
|
317
|
+
return _is_abs(self._path) > 0
|
|
318
|
+
|
|
319
|
+
def list(self, *, dir: bool = False, hidden: bool = False) -> 'Pyjo.Collection.object[Self]':
|
|
320
|
+
"""::
|
|
321
|
+
|
|
322
|
+
collection = path.list()
|
|
323
|
+
collection = path.list(hidden=True)
|
|
324
|
+
|
|
325
|
+
List all files in the directory and return a :mod:`Pyjo.Collection`
|
|
326
|
+
object containing the results as :mod:`Pyjo.File` objects. The list does
|
|
327
|
+
not include ``.`` and ``..``. ::
|
|
328
|
+
|
|
329
|
+
# List files
|
|
330
|
+
for file in path('/home/sri/myapp').list().each():
|
|
331
|
+
print(file)
|
|
332
|
+
|
|
333
|
+
These options are currently available:
|
|
334
|
+
|
|
335
|
+
``dir``
|
|
336
|
+
Include directories.
|
|
337
|
+
|
|
338
|
+
``hidden``
|
|
339
|
+
Include hidden files.
|
|
340
|
+
"""
|
|
341
|
+
if not os.path.isdir(self._path):
|
|
342
|
+
return Pyjo.Collection.new()
|
|
343
|
+
try:
|
|
344
|
+
names = os.listdir(self._path)
|
|
345
|
+
except OSError as e:
|
|
346
|
+
_croak(e, f'Can\'t open directory "{self._path}"')
|
|
347
|
+
if not hidden:
|
|
348
|
+
names = [n for n in names if not n.startswith('.')]
|
|
349
|
+
files = [_catfile(self._path, n) for n in names]
|
|
350
|
+
if not dir:
|
|
351
|
+
files = [f for f in files if not os.path.isdir(f)]
|
|
352
|
+
|
|
353
|
+
return Pyjo.Collection.object(self.new(f) for f in sorted(files))
|
|
354
|
+
|
|
355
|
+
def list_tree(
|
|
356
|
+
self, *, dir: bool = False, hidden: bool = False, max_depth: int | None = None
|
|
357
|
+
) -> 'Pyjo.Collection.object[Self]':
|
|
358
|
+
"""::
|
|
359
|
+
|
|
360
|
+
collection = path.list_tree()
|
|
361
|
+
collection = path.list_tree(hidden=True)
|
|
362
|
+
|
|
363
|
+
List all files recursively in the directory and return a
|
|
364
|
+
:mod:`Pyjo.Collection` object containing the results as
|
|
365
|
+
:mod:`Pyjo.File` objects. The list does not include ``.`` and ``..``,
|
|
366
|
+
and symbolic links to directories are not followed. ::
|
|
367
|
+
|
|
368
|
+
# List all templates
|
|
369
|
+
for file in path('/home/sri/myapp/templates').list_tree().each():
|
|
370
|
+
print(file)
|
|
371
|
+
|
|
372
|
+
These options are currently available:
|
|
373
|
+
|
|
374
|
+
``dir``
|
|
375
|
+
Include directories.
|
|
376
|
+
|
|
377
|
+
``hidden``
|
|
378
|
+
Include hidden files and directories.
|
|
379
|
+
|
|
380
|
+
``max_depth``
|
|
381
|
+
Maximum number of levels to descend when searching for files.
|
|
382
|
+
"""
|
|
383
|
+
if not os.path.isdir(self._path):
|
|
384
|
+
return Pyjo.Collection.new()
|
|
385
|
+
|
|
386
|
+
results: builtins.list[Self] = []
|
|
387
|
+
|
|
388
|
+
def walk(path: str, depth: int) -> None:
|
|
389
|
+
try:
|
|
390
|
+
names = sorted(os.listdir(path))
|
|
391
|
+
except OSError:
|
|
392
|
+
return
|
|
393
|
+
if not hidden:
|
|
394
|
+
names = [n for n in names if not n.startswith('.')]
|
|
395
|
+
for name in names:
|
|
396
|
+
child = self.new(path, name)
|
|
397
|
+
is_dir = os.path.isdir(child)
|
|
398
|
+
if dir or not is_dir:
|
|
399
|
+
results.append(child)
|
|
400
|
+
if is_dir and not os.path.islink(child) and (not max_depth or depth + 1 < max_depth):
|
|
401
|
+
walk(child._path, depth + 1)
|
|
402
|
+
|
|
403
|
+
walk(self._path, 0)
|
|
404
|
+
|
|
405
|
+
return Pyjo.Collection.object(results)
|
|
406
|
+
|
|
407
|
+
def lstat(self) -> os.stat_result:
|
|
408
|
+
"""::
|
|
409
|
+
|
|
410
|
+
stat = path.lstat()
|
|
411
|
+
|
|
412
|
+
Return a :class:`os.stat_result` object for the symlink. ::
|
|
413
|
+
|
|
414
|
+
# Get symlink size
|
|
415
|
+
print(path('/usr/sbin/sendmail').lstat().st_size)
|
|
416
|
+
|
|
417
|
+
# Get symlink modification time
|
|
418
|
+
print(path('/usr/sbin/sendmail').lstat().st_mtime)
|
|
419
|
+
"""
|
|
420
|
+
return os.lstat(self._path)
|
|
421
|
+
|
|
422
|
+
def make_path(self, mode: int = 0o777) -> Self:
|
|
423
|
+
"""::
|
|
424
|
+
|
|
425
|
+
path = path.make_path()
|
|
426
|
+
path = path.make_path(mode=0o711)
|
|
427
|
+
|
|
428
|
+
Create the directories if they don't already exist, with
|
|
429
|
+
:func:`os.makedirs`.
|
|
430
|
+
"""
|
|
431
|
+
os.makedirs(self._path, mode, exist_ok=True)
|
|
432
|
+
return self
|
|
433
|
+
|
|
434
|
+
def move_to(self, to: Anything) -> Self:
|
|
435
|
+
"""::
|
|
436
|
+
|
|
437
|
+
destination = path.move_to('/home/sri')
|
|
438
|
+
destination = path.move_to('/home/sri/.vimrc.backup')
|
|
439
|
+
|
|
440
|
+
Move file with :mod:`shutil` and return the destination as a
|
|
441
|
+
:mod:`Pyjo.File` object.
|
|
442
|
+
"""
|
|
443
|
+
destination = self._destination(to)
|
|
444
|
+
try:
|
|
445
|
+
shutil.move(self._path, destination)
|
|
446
|
+
except OSError as e:
|
|
447
|
+
_croak(e, f'Can\'t move file "{self._path}" to "{_str(to)}"')
|
|
448
|
+
return self.new(destination)
|
|
449
|
+
|
|
450
|
+
def open(self, mode: str = 'r', *args: Any, **kwargs: Any) -> IO[Any]:
|
|
451
|
+
"""::
|
|
452
|
+
|
|
453
|
+
handle = path.open('r+')
|
|
454
|
+
handle = path.open('rb')
|
|
455
|
+
handle = path.open('r', encoding='UTF-8')
|
|
456
|
+
|
|
457
|
+
Open file with :func:`open`.
|
|
458
|
+
"""
|
|
459
|
+
try:
|
|
460
|
+
return builtins.open(self._path, mode, *args, **kwargs)
|
|
461
|
+
except OSError as e:
|
|
462
|
+
_croak(e, f'Can\'t open file "{self._path}"')
|
|
463
|
+
|
|
464
|
+
def realpath(self) -> Self:
|
|
465
|
+
"""::
|
|
466
|
+
|
|
467
|
+
realpath = path.realpath()
|
|
468
|
+
|
|
469
|
+
Resolve the path with :func:`os.path.realpath` and return the result as
|
|
470
|
+
a :mod:`Pyjo.File` object.
|
|
471
|
+
"""
|
|
472
|
+
return self.new(os.path.realpath(self._path))
|
|
473
|
+
|
|
474
|
+
def remove(self) -> Self:
|
|
475
|
+
"""::
|
|
476
|
+
|
|
477
|
+
path = path.remove()
|
|
478
|
+
|
|
479
|
+
Delete file.
|
|
480
|
+
"""
|
|
481
|
+
if os.path.exists(self._path):
|
|
482
|
+
try:
|
|
483
|
+
os.unlink(self._path)
|
|
484
|
+
except OSError as e:
|
|
485
|
+
_croak(e, f'Can\'t remove file "{self._path}"')
|
|
486
|
+
return self
|
|
487
|
+
|
|
488
|
+
def remove_tree(self, *, keep_root: bool = False) -> Self:
|
|
489
|
+
"""::
|
|
490
|
+
|
|
491
|
+
path = path.remove_tree()
|
|
492
|
+
path = path.remove_tree(keep_root=True)
|
|
493
|
+
|
|
494
|
+
Delete this directory and any files and subdirectories it may contain.
|
|
495
|
+
"""
|
|
496
|
+
if not os.path.lexists(self._path):
|
|
497
|
+
return self
|
|
498
|
+
if not keep_root:
|
|
499
|
+
shutil.rmtree(self._path)
|
|
500
|
+
return self
|
|
501
|
+
for name in os.listdir(self._path):
|
|
502
|
+
child = os.path.join(self._path, name)
|
|
503
|
+
if os.path.isdir(child) and not os.path.islink(child):
|
|
504
|
+
shutil.rmtree(child)
|
|
505
|
+
else:
|
|
506
|
+
os.unlink(child)
|
|
507
|
+
return self
|
|
508
|
+
|
|
509
|
+
def sibling(self, *parts: Anything) -> Self:
|
|
510
|
+
"""::
|
|
511
|
+
|
|
512
|
+
sibling = path.sibling('.vimrc')
|
|
513
|
+
|
|
514
|
+
Return a new :mod:`Pyjo.File` object relative to the directory part of
|
|
515
|
+
the path. ::
|
|
516
|
+
|
|
517
|
+
# "/home/sri/.vimrc" (on UNIX)
|
|
518
|
+
path('/home/sri/.bashrc').sibling('.vimrc')
|
|
519
|
+
|
|
520
|
+
# "/home/sri/.ssh/known_hosts" (on UNIX)
|
|
521
|
+
path('/home/sri/.bashrc').sibling('.ssh', 'known_hosts')
|
|
522
|
+
"""
|
|
523
|
+
return self.new(_dirname(self._path), *parts)
|
|
524
|
+
|
|
525
|
+
def slurp(self, encoding: str | None = None) -> bytes | str | None:
|
|
526
|
+
"""::
|
|
527
|
+
|
|
528
|
+
data = path.slurp()
|
|
529
|
+
chars = path.slurp('UTF-8')
|
|
530
|
+
|
|
531
|
+
Read all data at once from the file. If an encoding is provided, an
|
|
532
|
+
attempt will be made to decode the content, and ``None`` is returned if
|
|
533
|
+
it fails.
|
|
534
|
+
"""
|
|
535
|
+
try:
|
|
536
|
+
fd = os.open(self._path, os.O_RDONLY | _BINARY)
|
|
537
|
+
except OSError as e:
|
|
538
|
+
_croak(e, f'Can\'t open file "{self._path}"')
|
|
539
|
+
try:
|
|
540
|
+
content = b''
|
|
541
|
+
while buffer := os.read(fd, 131072):
|
|
542
|
+
content += buffer
|
|
543
|
+
except OSError as e:
|
|
544
|
+
_croak(e, f'Can\'t read from file "{self._path}"')
|
|
545
|
+
finally:
|
|
546
|
+
os.close(fd)
|
|
547
|
+
|
|
548
|
+
return decode(encoding, content) if encoding else content
|
|
549
|
+
|
|
550
|
+
def spew(self, content: bytes | str, encoding: str | None = None) -> Self:
|
|
551
|
+
"""::
|
|
552
|
+
|
|
553
|
+
path = path.spew(data)
|
|
554
|
+
path = path.spew(chars, 'UTF-8')
|
|
555
|
+
|
|
556
|
+
Write all data at once to the file. If an encoding is provided, an
|
|
557
|
+
attempt to encode the content will be made prior to writing, and
|
|
558
|
+
characters without one are encoded with ``UTF-8``.
|
|
559
|
+
"""
|
|
560
|
+
if isinstance(content, str):
|
|
561
|
+
# Unlike Mojolicious: characters without an encoding are UTF-8, where
|
|
562
|
+
# Perl writes the characters 0-255 as bytes and dies of a wider one
|
|
563
|
+
content = encode(encoding, content) if encoding else content.encode('UTF-8')
|
|
564
|
+
try:
|
|
565
|
+
fd = os.open(self._path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC | _BINARY, 0o666)
|
|
566
|
+
except OSError as e:
|
|
567
|
+
_croak(e, f'Can\'t open file "{self._path}"')
|
|
568
|
+
try:
|
|
569
|
+
if os.write(fd, content) != len(content):
|
|
570
|
+
raise OSError(f'Can\'t write to file "{self._path}"')
|
|
571
|
+
except OSError as e:
|
|
572
|
+
_croak(e, f'Can\'t write to file "{self._path}"')
|
|
573
|
+
finally:
|
|
574
|
+
os.close(fd)
|
|
575
|
+
return self
|
|
576
|
+
|
|
577
|
+
def spurt(self, *chunks: bytes | str) -> Self:
|
|
578
|
+
"""::
|
|
579
|
+
|
|
580
|
+
path = path.spurt(*chunks)
|
|
581
|
+
|
|
582
|
+
Alias for :meth:`spew` that writes multiple chunks.
|
|
583
|
+
"""
|
|
584
|
+
if all(isinstance(c, str) for c in chunks):
|
|
585
|
+
return self.spew(''.join(str(c) for c in chunks))
|
|
586
|
+
return self.spew(b''.join(c.encode('UTF-8') if isinstance(c, str) else c for c in chunks))
|
|
587
|
+
|
|
588
|
+
def stat(self) -> os.stat_result:
|
|
589
|
+
"""::
|
|
590
|
+
|
|
591
|
+
stat = path.stat()
|
|
592
|
+
|
|
593
|
+
Return a :class:`os.stat_result` object for the path. ::
|
|
594
|
+
|
|
595
|
+
# Get file size
|
|
596
|
+
print(path('/home/sri/.bashrc').stat().st_size)
|
|
597
|
+
|
|
598
|
+
# Get file modification time
|
|
599
|
+
print(path('/home/sri/.bashrc').stat().st_mtime)
|
|
600
|
+
"""
|
|
601
|
+
return os.stat(self._path)
|
|
602
|
+
|
|
603
|
+
def to_abs(self) -> Self:
|
|
604
|
+
"""::
|
|
605
|
+
|
|
606
|
+
absolute = path.to_abs()
|
|
607
|
+
|
|
608
|
+
Return absolute path as a :mod:`Pyjo.File` object, the path does not
|
|
609
|
+
need to exist on the file system.
|
|
610
|
+
"""
|
|
611
|
+
path = self._path
|
|
612
|
+
if (level := _is_abs(path)) == 2 or (level and not _WIN32):
|
|
613
|
+
return self.new(_canonpath(path))
|
|
614
|
+
|
|
615
|
+
# File::Spec::Win32's rel2abs: a path from the root takes the volume of the working directory, and one with a
|
|
616
|
+
# drive and no root the working directory of that drive
|
|
617
|
+
if _WIN32:
|
|
618
|
+
if level:
|
|
619
|
+
volume = re.match(r'(?:[a-zA-Z]:|(?:\\\\|//)[^\\/]+[\\/][^\\/]+)?', os.getcwd())
|
|
620
|
+
assert volume is not None
|
|
621
|
+
return self.new(_canonpath(volume[0] + path))
|
|
622
|
+
if drive := re.match(r'[a-zA-Z]:', path):
|
|
623
|
+
return self.new(_catfile(os.path.abspath(drive[0]), path[2:]))
|
|
624
|
+
return self.new(_catfile(os.getcwd(), path))
|
|
625
|
+
|
|
626
|
+
def to_list(self) -> 'builtins.list[str]':
|
|
627
|
+
"""::
|
|
628
|
+
|
|
629
|
+
parts = path.to_list()
|
|
630
|
+
|
|
631
|
+
Split the path on directory separators. ::
|
|
632
|
+
|
|
633
|
+
# "home:sri:.vimrc" (on UNIX)
|
|
634
|
+
':'.join(path('/home/sri/.vimrc').to_list())
|
|
635
|
+
"""
|
|
636
|
+
return _splitdir(self._path)
|
|
637
|
+
|
|
638
|
+
# Not in Mojolicious: the conversion to pathlib
|
|
639
|
+
def to_path(self) -> pathlib.Path:
|
|
640
|
+
"""::
|
|
641
|
+
|
|
642
|
+
p = path.to_path()
|
|
643
|
+
|
|
644
|
+
Turn the path into :class:`pathlib.Path`.
|
|
645
|
+
"""
|
|
646
|
+
return pathlib.Path(self._path)
|
|
647
|
+
|
|
648
|
+
def to_rel(self, base: Anything) -> Self:
|
|
649
|
+
"""::
|
|
650
|
+
|
|
651
|
+
relative = path.to_rel('/some/base/path')
|
|
652
|
+
|
|
653
|
+
Return a relative path from the original path to the destination path as
|
|
654
|
+
a :mod:`Pyjo.File` object. ::
|
|
655
|
+
|
|
656
|
+
# "sri/.vimrc" (on UNIX)
|
|
657
|
+
path('/home/sri/.vimrc').to_rel('/home')
|
|
658
|
+
"""
|
|
659
|
+
return self.new(os.path.relpath(self._path, _str(base)))
|
|
660
|
+
|
|
661
|
+
# Renamed from Mojolicious: to_string
|
|
662
|
+
def to_str(self) -> str:
|
|
663
|
+
"""::
|
|
664
|
+
|
|
665
|
+
string = path.to_str()
|
|
666
|
+
|
|
667
|
+
Stringify the path.
|
|
668
|
+
"""
|
|
669
|
+
return self._path
|
|
670
|
+
|
|
671
|
+
def touch(self) -> Self:
|
|
672
|
+
"""::
|
|
673
|
+
|
|
674
|
+
path = path.touch()
|
|
675
|
+
|
|
676
|
+
Create file if it does not exist or change the modification and access
|
|
677
|
+
time to the current time. ::
|
|
678
|
+
|
|
679
|
+
# Safely read file
|
|
680
|
+
print(path('.bashrc').touch().slurp())
|
|
681
|
+
"""
|
|
682
|
+
if not os.path.exists(self._path):
|
|
683
|
+
self.open('w').close()
|
|
684
|
+
try:
|
|
685
|
+
os.utime(self._path)
|
|
686
|
+
except OSError as e:
|
|
687
|
+
_croak(e, f'Can\'t touch file "{self._path}"')
|
|
688
|
+
return self
|
|
689
|
+
|
|
690
|
+
# Left out from Mojolicious: with_roles, as Pyjoyment has no roles
|
|
691
|
+
|
|
692
|
+
def _destination(self, to: Anything) -> str:
|
|
693
|
+
to = _str(to)
|
|
694
|
+
return _catfile(to, _basename(self._path)) if os.path.isdir(to) else to
|
|
695
|
+
|
|
696
|
+
|
|
697
|
+
def curfile() -> Pyjo_File:
|
|
698
|
+
"""::
|
|
699
|
+
|
|
700
|
+
path = curfile()
|
|
701
|
+
|
|
702
|
+
Construct a new :mod:`Pyjo.File` object for the absolute path to the
|
|
703
|
+
current source file.
|
|
704
|
+
"""
|
|
705
|
+
frame = inspect.currentframe()
|
|
706
|
+
caller = frame.f_back if frame is not None else None
|
|
707
|
+
return Pyjo_File(os.path.realpath(caller.f_code.co_filename if caller is not None else ''))
|
|
708
|
+
|
|
709
|
+
|
|
710
|
+
def path(*parts: Anything) -> Pyjo_File:
|
|
711
|
+
"""::
|
|
712
|
+
|
|
713
|
+
path = path()
|
|
714
|
+
path = path('/home/sri/.vimrc')
|
|
715
|
+
path = path('/home', 'sri', '.vimrc')
|
|
716
|
+
path = path(tempfile.TemporaryDirectory())
|
|
717
|
+
|
|
718
|
+
Construct a new :mod:`Pyjo.File` object, defaults to using the current
|
|
719
|
+
working directory. ::
|
|
720
|
+
|
|
721
|
+
# "foo/bar/baz.txt" (on UNIX)
|
|
722
|
+
path('foo', 'bar', 'baz.txt')
|
|
723
|
+
"""
|
|
724
|
+
return Pyjo_File(*parts)
|
|
725
|
+
|
|
726
|
+
|
|
727
|
+
def tempdir(template: str | None = None, **kwargs: Any) -> Pyjo_File:
|
|
728
|
+
"""::
|
|
729
|
+
|
|
730
|
+
path = tempdir()
|
|
731
|
+
path = tempdir('tempXXXXX')
|
|
732
|
+
|
|
733
|
+
Construct a new :mod:`Pyjo.File` object for a temporary directory, made
|
|
734
|
+
with :func:`tempfile.mkdtemp` and its keyword arguments, which is removed
|
|
735
|
+
with the object or at the end of a ``with`` block, as Perl's
|
|
736
|
+
``File::Temp`` removes it, without the :exc:`ResourceWarning` that
|
|
737
|
+
:class:`tempfile.TemporaryDirectory` gives when it is not cleaned up
|
|
738
|
+
explicitly. The ``X`` characters at the end of a
|
|
739
|
+
template are random, and a directory with a template is made in the
|
|
740
|
+
current working directory, as ``File::Temp`` makes it. ::
|
|
741
|
+
|
|
742
|
+
# Longer version, which warns when the directory is removed with the object
|
|
743
|
+
path = path(tempfile.TemporaryDirectory())
|
|
744
|
+
"""
|
|
745
|
+
if template is not None:
|
|
746
|
+
return Pyjo_File(_Temp(_mkdir(template, **kwargs), _rmtree))
|
|
747
|
+
return Pyjo_File(_Temp(cast(str, _tempfile.mkdtemp(**kwargs)), _rmtree))
|
|
748
|
+
|
|
749
|
+
|
|
750
|
+
def tempfile(**kwargs: Any) -> Pyjo_File:
|
|
751
|
+
"""::
|
|
752
|
+
|
|
753
|
+
path = tempfile()
|
|
754
|
+
path = tempfile(dir='/tmp')
|
|
755
|
+
|
|
756
|
+
Construct a new :mod:`Pyjo.File` object for a temporary file, made with
|
|
757
|
+
:func:`tempfile.mkstemp` and its keyword arguments, which is removed with
|
|
758
|
+
the object or at the end of a ``with`` block, as Perl's ``File::Temp``
|
|
759
|
+
removes it, without the :exc:`ResourceWarning` that
|
|
760
|
+
:func:`tempfile.NamedTemporaryFile` gives when it is not closed
|
|
761
|
+
explicitly. ::
|
|
762
|
+
|
|
763
|
+
# Longer version, which warns when the file is removed with the object
|
|
764
|
+
path = path(tempfile.NamedTemporaryFile(dir='/tmp'))
|
|
765
|
+
"""
|
|
766
|
+
handle, name = cast(tuple[int, str], _tempfile.mkstemp(**kwargs))
|
|
767
|
+
os.close(handle)
|
|
768
|
+
return Pyjo_File(_Temp(name, _unlink))
|
|
769
|
+
|
|
770
|
+
|
|
771
|
+
# A temporary file or directory of File::Temp, removed with the object without a ResourceWarning
|
|
772
|
+
class _Temp:
|
|
773
|
+
def __init__(self, name: str, remove: Callable[[str], Anything]) -> None:
|
|
774
|
+
self.name = name
|
|
775
|
+
self.remove = weakref.finalize(self, remove, name)
|
|
776
|
+
|
|
777
|
+
|
|
778
|
+
# Remove a temporary file or directory now, once, where the object would remove it when it goes
|
|
779
|
+
def _release(temp: Anything) -> None:
|
|
780
|
+
if isinstance(temp, _Temp):
|
|
781
|
+
temp.remove()
|
|
782
|
+
elif isinstance(temp, _tempfile.TemporaryDirectory):
|
|
783
|
+
cast(_tempfile.TemporaryDirectory[str], temp).cleanup()
|
|
784
|
+
else:
|
|
785
|
+
cast(Any, temp).close()
|
|
786
|
+
|
|
787
|
+
|
|
788
|
+
# File::Temp's newdir with a template
|
|
789
|
+
def _mkdir(template: str, dir: str = '.') -> str:
|
|
790
|
+
prefix = template.rstrip('X')
|
|
791
|
+
size = len(template) - len(prefix)
|
|
792
|
+
chars = string.ascii_letters + string.digits
|
|
793
|
+
while True:
|
|
794
|
+
name = os.path.abspath(os.path.join(dir, prefix + ''.join(_random.choice(chars) for _ in range(size))))
|
|
795
|
+
try:
|
|
796
|
+
os.mkdir(name, 0o700)
|
|
797
|
+
return name
|
|
798
|
+
except FileExistsError:
|
|
799
|
+
continue
|
|
800
|
+
|
|
801
|
+
|
|
802
|
+
def _rmtree(name: str) -> None:
|
|
803
|
+
shutil.rmtree(name, ignore_errors=True)
|
|
804
|
+
|
|
805
|
+
|
|
806
|
+
def _unlink(name: str) -> None:
|
|
807
|
+
try:
|
|
808
|
+
os.unlink(name)
|
|
809
|
+
except OSError:
|
|
810
|
+
pass
|
|
811
|
+
|
|
812
|
+
|
|
813
|
+
# File::Basename's basename
|
|
814
|
+
def _basename(path: str, *suffixes: str) -> str:
|
|
815
|
+
path = _strip_trailing_sep(path)
|
|
816
|
+
basename, dirname = _fileparse(path)
|
|
817
|
+
tail = ''
|
|
818
|
+
for suffix in suffixes:
|
|
819
|
+
if basename.endswith(suffix):
|
|
820
|
+
basename, tail = basename[: -len(suffix)], suffix + tail
|
|
821
|
+
if len(tail) and not len(basename):
|
|
822
|
+
basename = tail
|
|
823
|
+
return basename if len(basename) else dirname
|
|
824
|
+
|
|
825
|
+
|
|
826
|
+
# File::Spec's canonpath
|
|
827
|
+
def _canonpath(path: str) -> str:
|
|
828
|
+
if _WIN32:
|
|
829
|
+
return _canon_cat(path) if len(path) else path
|
|
830
|
+
path = re.sub(r'/{2,}', '/', path)
|
|
831
|
+
path = re.sub(r'(?:/\.)+(?:/|\Z)', '/', path)
|
|
832
|
+
if path != './':
|
|
833
|
+
path = re.sub(r'^(?:\./)+', '', path, flags=re.S)
|
|
834
|
+
path = re.sub(r'^/(?:\.\./)+', '/', path)
|
|
835
|
+
path = re.sub(r'^/\.\.$', '/', path)
|
|
836
|
+
if path != '/':
|
|
837
|
+
path = re.sub(r'/\Z', '', path)
|
|
838
|
+
return path
|
|
839
|
+
|
|
840
|
+
|
|
841
|
+
# File::Spec's catfile
|
|
842
|
+
def _catfile(*parts: str) -> str:
|
|
843
|
+
if _WIN32:
|
|
844
|
+
if not len(parts) or parts[0] == '':
|
|
845
|
+
return _canon_cat('/', *parts[1:])
|
|
846
|
+
if re.match(r'[a-zA-Z]:\Z', parts[0]):
|
|
847
|
+
return _canon_cat(parts[0] + '\\', *parts[1:])
|
|
848
|
+
return _canon_cat(*parts)
|
|
849
|
+
return _canonpath('/'.join(parts))
|
|
850
|
+
|
|
851
|
+
|
|
852
|
+
# File::Spec::Win32's _canon_cat
|
|
853
|
+
def _canon_cat(first: str, *rest: str) -> str:
|
|
854
|
+
volume = ''
|
|
855
|
+
if m := re.match(r'([A-Za-z]:)([\\/]?)', first):
|
|
856
|
+
volume = m[1].upper() + ('\\' if m[2] else '')
|
|
857
|
+
elif m := re.match(r'(?:\\\\|//)([^\\/]+)(?:[\\/]([^\\/]+))?[\\/]?', first, re.S):
|
|
858
|
+
volume = f'\\\\{m[1]}' + (f'\\{m[2]}' if m[2] is not None else '') + '\\'
|
|
859
|
+
elif m := re.match(r'[\\/]', first):
|
|
860
|
+
volume = '\\'
|
|
861
|
+
if m:
|
|
862
|
+
first = first[m.end() :]
|
|
863
|
+
path = '\\'.join((first, *rest))
|
|
864
|
+
|
|
865
|
+
path = re.sub(r'[\\/]+', r'\\', path)
|
|
866
|
+
path = re.sub(r'(?:(?:\A|\\)\.(?:\\\.)*(?:\\|\Z))+', r'\\', path)
|
|
867
|
+
while True:
|
|
868
|
+
path, n = re.subn(
|
|
869
|
+
r'(?:(?:\A|\\)[^\\]+\\\.\.(?<!\A\.\.\\\.\.)(?<!\\\.\.\\\.\.)(?:\\|\Z))+', r'\\', path, count=1, flags=re.S
|
|
870
|
+
)
|
|
871
|
+
if not n:
|
|
872
|
+
break
|
|
873
|
+
path = re.sub(r'\A\\', '', path)
|
|
874
|
+
path = re.sub(r'\\\Z', '', path)
|
|
875
|
+
|
|
876
|
+
if volume.endswith('\\'):
|
|
877
|
+
path = re.sub(r'\A\.\.(?:\\\.\.)*(?:\\|\Z)', '', path)
|
|
878
|
+
if path == '' and (m := re.match(r'(\\\\.*)\\\Z', volume, re.S)):
|
|
879
|
+
return m[1]
|
|
880
|
+
return volume + path if len(path) or len(volume) else '.'
|
|
881
|
+
|
|
882
|
+
|
|
883
|
+
def _download_error(tx: 'Pyjo.Transaction.HTTP.object') -> 'Pyjo.Transaction.HTTP.object | None':
|
|
884
|
+
if not (err := tx.error):
|
|
885
|
+
return tx
|
|
886
|
+
if err['message'] in ('Download complete', 'Download incomplete'):
|
|
887
|
+
return None
|
|
888
|
+
if code := err.get('code'):
|
|
889
|
+
raise Pyjo.Exception.new(f'{code} response: {err["message"]}')
|
|
890
|
+
raise Pyjo.Exception.new(f'Download error: {err["message"]}')
|
|
891
|
+
|
|
892
|
+
|
|
893
|
+
def _croak(e: OSError, message: str) -> NoReturn:
|
|
894
|
+
error = type(e)(f'{message}: {e.strerror}' if e.strerror else message)
|
|
895
|
+
error.errno = e.errno
|
|
896
|
+
raise error from e
|
|
897
|
+
|
|
898
|
+
|
|
899
|
+
# File::Basename's dirname
|
|
900
|
+
def _dirname(path: str) -> str:
|
|
901
|
+
basename, dirname = _fileparse(path)
|
|
902
|
+
dirname = _strip_trailing_sep(dirname)
|
|
903
|
+
if not len(basename):
|
|
904
|
+
basename, dirname = _fileparse(dirname)
|
|
905
|
+
dirname = _strip_trailing_sep(dirname)
|
|
906
|
+
return dirname
|
|
907
|
+
|
|
908
|
+
|
|
909
|
+
# File::Basename's fileparse
|
|
910
|
+
def _fileparse(path: str) -> tuple[str, str]:
|
|
911
|
+
if _WIN32:
|
|
912
|
+
m = re.match(r'((?:.*[:\\/])?)(.*)', path, re.S)
|
|
913
|
+
assert m is not None
|
|
914
|
+
return m[2], m[1] if re.search(r'[\\/]\Z', m[1]) else m[1] + '.\\'
|
|
915
|
+
m = re.match(r'^(.*/)?(.*)$', path, re.S)
|
|
916
|
+
assert m is not None
|
|
917
|
+
return m[2], m[1] or './'
|
|
918
|
+
|
|
919
|
+
|
|
920
|
+
# File::Spec's file_name_is_absolute, 2 for a path with a volume on Windows
|
|
921
|
+
def _is_abs(path: str) -> int:
|
|
922
|
+
if not _WIN32:
|
|
923
|
+
return 1 if path.startswith('/') else 0
|
|
924
|
+
if m := re.match(r'[a-zA-Z]:|(?:\\\\|//)[^\\/]+[\\/][^\\/]+', path):
|
|
925
|
+
return 2 if m[0][1] != ':' or re.match(r'[a-zA-Z]:[\\/]', path) else 0
|
|
926
|
+
return 1 if re.match(r'[\\/]', path) else 0
|
|
927
|
+
|
|
928
|
+
|
|
929
|
+
def _is_temp(value: Anything) -> bool:
|
|
930
|
+
return isinstance(value, (_Temp, _tempfile.TemporaryDirectory)) or type(value).__name__ == '_TemporaryFileWrapper'
|
|
931
|
+
|
|
932
|
+
|
|
933
|
+
# File::Spec's splitdir
|
|
934
|
+
def _splitdir(path: str) -> 'builtins.list[str]':
|
|
935
|
+
if not _WIN32:
|
|
936
|
+
return path.split('/')
|
|
937
|
+
if not re.search(r'[\\/]\Z', path):
|
|
938
|
+
return re.split(r'[\\/]', path) if len(path) else []
|
|
939
|
+
return [*re.split(r'[\\/]', path)[:-1], '']
|
|
940
|
+
|
|
941
|
+
|
|
942
|
+
# File::Basename's _strip_trailing_sep
|
|
943
|
+
def _strip_trailing_sep(path: str) -> str:
|
|
944
|
+
if _WIN32:
|
|
945
|
+
return re.sub(r'([^:])[\\/]*\Z', r'\1', path)
|
|
946
|
+
return re.sub(r'(.)/*\Z', r'\1', path, flags=re.S)
|
|
947
|
+
|
|
948
|
+
|
|
949
|
+
def _str(value: Anything) -> str:
|
|
950
|
+
if _is_temp(value):
|
|
951
|
+
return str(cast(Any, value).name)
|
|
952
|
+
if isinstance(value, os.PathLike):
|
|
953
|
+
return str(os.fspath(value)) # pyright: ignore[reportUnknownArgumentType]
|
|
954
|
+
return str(value)
|
|
955
|
+
|
|
956
|
+
|
|
957
|
+
new = Pyjo_File.new
|
|
958
|
+
object = Pyjo_File
|
|
959
|
+
File = Pyjo_File
|