kisstdlib 0.0.10__tar.gz → 0.0.11__tar.gz
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.
- {kisstdlib-0.0.10 → kisstdlib-0.0.11}/PKG-INFO +2 -2
- {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib/argparse_ext.py +0 -34
- {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib/fs.py +17 -7
- kisstdlib-0.0.11/kisstdlib/io/adapter.py +140 -0
- {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib.egg-info/PKG-INFO +2 -2
- {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib.egg-info/SOURCES.txt +1 -0
- {kisstdlib-0.0.10 → kisstdlib-0.0.11}/pyproject.toml +1 -1
- {kisstdlib-0.0.10 → kisstdlib-0.0.11}/LICENSE.txt +0 -0
- {kisstdlib-0.0.10 → kisstdlib-0.0.11}/README.md +0 -0
- {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib/__init__.py +0 -0
- {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib/base.py +0 -0
- {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib/compression.py +0 -0
- {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib/failure.py +0 -0
- {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib/getpass_ext.py +0 -0
- {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib/io/__init__.py +0 -0
- {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib/io/base.py +0 -0
- {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib/io/encoder.py +0 -0
- {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib/io/stdio.py +0 -0
- {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib/io/wrapper.py +0 -0
- {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib/itertools_ext.py +0 -0
- {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib/logging_ext.py +0 -0
- {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib/parsing.py +0 -0
- {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib/py.typed +0 -0
- {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib/pyrepr.py +0 -0
- {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib/signal_ext.py +0 -0
- {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib/sorted.py +0 -0
- {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib/sqlite3_ext.py +0 -0
- {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib/string_ext.py +0 -0
- {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib/tariter.py +0 -0
- {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib/time.py +0 -0
- {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib.egg-info/dependency_links.txt +0 -0
- {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib.egg-info/entry_points.txt +0 -0
- {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib.egg-info/requires.txt +0 -0
- {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib.egg-info/top_level.txt +0 -0
- {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib_bin/describe_forest.py +0 -0
- {kisstdlib-0.0.10 → kisstdlib-0.0.11}/setup.cfg +0 -0
- {kisstdlib-0.0.10 → kisstdlib-0.0.11}/setup.py +0 -0
- {kisstdlib-0.0.10 → kisstdlib-0.0.11}/test/test_basics.py +0 -0
- {kisstdlib-0.0.10 → kisstdlib-0.0.11}/test/test_fs.py +0 -0
- {kisstdlib-0.0.10 → kisstdlib-0.0.11}/test/test_io.py +0 -0
- {kisstdlib-0.0.10 → kisstdlib-0.0.11}/test/test_sorted.py +0 -0
- {kisstdlib-0.0.10 → kisstdlib-0.0.11}/test/test_time.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: kisstdlib
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.11
|
|
4
4
|
Summary: System programming with conceptually and algebraically simple primitives.
|
|
5
5
|
Author-email: Jan Malakhovski <oxij@oxij.org>
|
|
6
6
|
Project-URL: Homepage, https://oxij.org/software/kisstdlib/
|
|
@@ -194,38 +194,6 @@ class _OptionallyMarkdownHelpAction(Action):
|
|
|
194
194
|
parser.post_parse_args = self.show_help
|
|
195
195
|
|
|
196
196
|
|
|
197
|
-
class _StoreMapAction(Action):
|
|
198
|
-
def __init__(self, func: _t.Callable[[_t.Any], _t.Any] = _identity, **kwargs: _t.Any) -> None:
|
|
199
|
-
super().__init__(**kwargs)
|
|
200
|
-
self.func = func
|
|
201
|
-
|
|
202
|
-
def __call__(
|
|
203
|
-
self,
|
|
204
|
-
parser: _t.Any,
|
|
205
|
-
namespace: Namespace,
|
|
206
|
-
values: _t.Any,
|
|
207
|
-
option_string: str | None = None,
|
|
208
|
-
) -> None:
|
|
209
|
-
setattr(namespace, self.dest, self.func(values))
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
class _AppendMapAction(Action):
|
|
213
|
-
def __init__(self, func: _t.Callable[[_t.Any], _t.Any] = _identity, **kwargs: _t.Any) -> None:
|
|
214
|
-
super().__init__(default=[], **kwargs)
|
|
215
|
-
self.func = func
|
|
216
|
-
|
|
217
|
-
def __call__(
|
|
218
|
-
self,
|
|
219
|
-
parser: _t.Any,
|
|
220
|
-
namespace: Namespace,
|
|
221
|
-
values: _t.Any,
|
|
222
|
-
option_string: str | None = None,
|
|
223
|
-
) -> None:
|
|
224
|
-
v = getattr(namespace, self.dest)
|
|
225
|
-
v.append(self.func(values))
|
|
226
|
-
setattr(namespace, self.dest, v)
|
|
227
|
-
|
|
228
|
-
|
|
229
197
|
class BetterArgumentParser(ArgumentParser):
|
|
230
198
|
"""Like `argparse.ArgumentParser`, but uses `BetterHelpFormatter` by default,
|
|
231
199
|
and implements `format_help` that recurses into subcommands and appends
|
|
@@ -257,8 +225,6 @@ class BetterArgumentParser(ArgumentParser):
|
|
|
257
225
|
**kwargs,
|
|
258
226
|
)
|
|
259
227
|
|
|
260
|
-
self.register("action", "store_map", _StoreMapAction)
|
|
261
|
-
self.register("action", "append_map", _AppendMapAction)
|
|
262
228
|
self.register("action", "help_omd", _OptionallyMarkdownHelpAction)
|
|
263
229
|
|
|
264
230
|
self.add_version = add_version
|
|
@@ -61,6 +61,16 @@ def dirname_dot(path: _t.AnyStr) -> tuple[_t.AnyStr, bool]:
|
|
|
61
61
|
return path, True
|
|
62
62
|
|
|
63
63
|
|
|
64
|
+
def realdir(path: _t.AnyStr, strict: bool = False) -> _t.AnyStr:
|
|
65
|
+
"""Apply `os.path.realpath` to the `dirname` part of `path`.
|
|
66
|
+
|
|
67
|
+
Essentially, this returns the canonical path of the inode the `path` points to, even when that
|
|
68
|
+
inode is a symlink.
|
|
69
|
+
"""
|
|
70
|
+
dn, bn = _op.split(path)
|
|
71
|
+
return _op.join(_op.realpath(dn, strict=strict), bn)
|
|
72
|
+
|
|
73
|
+
|
|
64
74
|
def read_file_maybe(path: str | bytes) -> bytes | None:
|
|
65
75
|
"""Return contents of the given file path, or `None` if it does not exist."""
|
|
66
76
|
try:
|
|
@@ -192,7 +202,7 @@ def iter_subtree(
|
|
|
192
202
|
fstat = _os.stat(path, follow_symlinks=follow_symlinks)
|
|
193
203
|
except OSError as exc:
|
|
194
204
|
if handle_error is not None:
|
|
195
|
-
eno = exc.errno
|
|
205
|
+
eno = exc.errno or 0
|
|
196
206
|
handle_error(
|
|
197
207
|
"failed to `stat`: [Errno %d, %s] %s: %s",
|
|
198
208
|
eno,
|
|
@@ -218,7 +228,7 @@ def iter_subtree(
|
|
|
218
228
|
scandir_it = _os.scandir(path)
|
|
219
229
|
except OSError as exc:
|
|
220
230
|
if handle_error is not None:
|
|
221
|
-
eno = exc.errno
|
|
231
|
+
eno = exc.errno or 0
|
|
222
232
|
handle_error(
|
|
223
233
|
"failed to `scandir`: [Errno %d, %s] %s: %s",
|
|
224
234
|
eno,
|
|
@@ -240,7 +250,7 @@ def iter_subtree(
|
|
|
240
250
|
break
|
|
241
251
|
except OSError as exc:
|
|
242
252
|
if handle_error is not None:
|
|
243
|
-
eno = exc.errno
|
|
253
|
+
eno = exc.errno or 0
|
|
244
254
|
handle_error(
|
|
245
255
|
"failed in `scandir`: [Errno %d, %s] %s: %s",
|
|
246
256
|
eno,
|
|
@@ -255,7 +265,7 @@ def iter_subtree(
|
|
|
255
265
|
entry_is_dir = entry.is_dir(follow_symlinks=follow_symlinks)
|
|
256
266
|
except OSError as exc:
|
|
257
267
|
if handle_error is not None:
|
|
258
|
-
eno = exc.errno
|
|
268
|
+
eno = exc.errno or 0
|
|
259
269
|
handle_error(
|
|
260
270
|
"failed to `stat`: [Errno %d, %s] %s: %s",
|
|
261
271
|
eno,
|
|
@@ -398,7 +408,7 @@ def describe_forest(
|
|
|
398
408
|
"""Produce a simple `find .`+`stat`-like description of walks of given `paths`.
|
|
399
409
|
See `describe-forest` script for more info.
|
|
400
410
|
"""
|
|
401
|
-
escape: _t.Callable[[str], str] = _identity if literal else _escape_path
|
|
411
|
+
escape: _t.Callable[[str], str] = _identity if literal else _escape_path
|
|
402
412
|
seen: dict[tuple[int, int], tuple[_t.AnyStr, int, str]] = {}
|
|
403
413
|
for i, dirpath in enumerate(paths):
|
|
404
414
|
for fpath, _eps, _edir in iter_subtree(dirpath, follow_symlinks=follow_symlinks):
|
|
@@ -1100,6 +1110,6 @@ def setup_fs(prog: str | None = None, ext: str = ".part", add_pid: bool = True)
|
|
|
1100
1110
|
prog = _op.basename(_sys.argv[0])
|
|
1101
1111
|
|
|
1102
1112
|
global _dot_part, _dot_partb
|
|
1103
|
-
pid = f"
|
|
1104
|
-
_dot_part = f".{
|
|
1113
|
+
pid = f"{_os.getpid()}." if add_pid else ""
|
|
1114
|
+
_dot_part = f".{pid}{prog}{ext}"
|
|
1105
1115
|
_dot_partb = _dot_part.encode("ascii")
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# Copyright (c) 2018-2025 Jan Malakhovski <oxij@oxij.org>
|
|
2
|
+
#
|
|
3
|
+
# This file is a part of `kisstdlib` project.
|
|
4
|
+
#
|
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
# furnished to do so, subject to the following conditions:
|
|
11
|
+
#
|
|
12
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
# copies or substantial portions of the Software.
|
|
14
|
+
#
|
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
# SOFTWARE.
|
|
22
|
+
|
|
23
|
+
"""Adapt almost any folding function into `IOBase` interface."""
|
|
24
|
+
|
|
25
|
+
import typing as _t
|
|
26
|
+
from zlib import compressobj as _compressobj, decompressobj as _decompressobj
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class IOAdapter:
|
|
30
|
+
def __init__(self, fobj: _t.Any) -> None:
|
|
31
|
+
self._fobj = fobj
|
|
32
|
+
|
|
33
|
+
def close(self) -> None:
|
|
34
|
+
self._fobj.close()
|
|
35
|
+
|
|
36
|
+
def flush(self) -> None:
|
|
37
|
+
self._fobj.flush()
|
|
38
|
+
|
|
39
|
+
def __enter__(self) -> _t.Any:
|
|
40
|
+
return self
|
|
41
|
+
|
|
42
|
+
def __exit__(self, exc_type: _t.Any, exc_value: _t.Any, exc_tb: _t.Any) -> None:
|
|
43
|
+
self.close()
|
|
44
|
+
|
|
45
|
+
def fileno(self) -> int:
|
|
46
|
+
return self._fobj.fileno() # type: ignore
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def closed(self) -> bool:
|
|
50
|
+
return self._fobj.closed # type: ignore
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class ReadAdapter(IOAdapter):
|
|
54
|
+
def __init__(self, fobj: _t.Any, block_size: int) -> None:
|
|
55
|
+
super().__init__(fobj)
|
|
56
|
+
self._block_size = block_size
|
|
57
|
+
self._buffer = b""
|
|
58
|
+
self._eof = False
|
|
59
|
+
|
|
60
|
+
def _handle_eof(self) -> bytes:
|
|
61
|
+
raise NotImplementedError()
|
|
62
|
+
|
|
63
|
+
def _handle_data(self, data: bytes) -> bytes:
|
|
64
|
+
raise NotImplementedError()
|
|
65
|
+
|
|
66
|
+
def read(self, size: int = -1) -> bytes:
|
|
67
|
+
while not self._eof and (size == -1 or len(self._buffer) < size):
|
|
68
|
+
data = self._fobj.read(self._block_size)
|
|
69
|
+
if len(data) == 0:
|
|
70
|
+
self._buffer += self._handle_eof()
|
|
71
|
+
self._eof = True
|
|
72
|
+
else:
|
|
73
|
+
self._buffer += self._handle_data(data)
|
|
74
|
+
|
|
75
|
+
if len(self._buffer) == 0:
|
|
76
|
+
return b""
|
|
77
|
+
|
|
78
|
+
if size == -1 or len(self._buffer) == size:
|
|
79
|
+
res = self._buffer
|
|
80
|
+
self._buffer = b""
|
|
81
|
+
return res
|
|
82
|
+
|
|
83
|
+
res = self._buffer[:size]
|
|
84
|
+
self._buffer = self._buffer[len(res) :]
|
|
85
|
+
return res
|
|
86
|
+
|
|
87
|
+
def tell(self) -> int:
|
|
88
|
+
return self._fobj.tell() # type: ignore
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class UpdateFinalizeReader(ReadAdapter):
|
|
92
|
+
def __init__(self, preprocessor: _t.Any, fobj: _t.Any, block_size: int) -> None:
|
|
93
|
+
super().__init__(fobj, block_size)
|
|
94
|
+
self._preprocessor = preprocessor
|
|
95
|
+
|
|
96
|
+
def _handle_eof(self) -> bytes:
|
|
97
|
+
return self._preprocessor.finalize() # type: ignore
|
|
98
|
+
|
|
99
|
+
def _handle_data(self, data: bytes) -> bytes:
|
|
100
|
+
return self._preprocessor.update(data) # type: ignore
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class UpdateFinalizeWriter(IOAdapter):
|
|
104
|
+
def __init__(self, fobj: _t.Any, preprocessor: _t.Any) -> None:
|
|
105
|
+
super().__init__(fobj)
|
|
106
|
+
self._preprocessor = preprocessor
|
|
107
|
+
|
|
108
|
+
def write(self, data: bytes) -> None:
|
|
109
|
+
self._fobj.write(self._preprocessor.update(data))
|
|
110
|
+
|
|
111
|
+
def close(self) -> None:
|
|
112
|
+
if not self.closed:
|
|
113
|
+
self._fobj.write(self._preprocessor.finalize())
|
|
114
|
+
self._fobj.close()
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class ZlibDecompressor(ReadAdapter):
|
|
118
|
+
def __init__(self, fobj: _t.Any, block_size: int, **kwargs: _t.Any) -> None:
|
|
119
|
+
super().__init__(fobj, block_size)
|
|
120
|
+
self._decompressor = _decompressobj(**kwargs)
|
|
121
|
+
|
|
122
|
+
def _handle_eof(self) -> bytes:
|
|
123
|
+
return self._decompressor.flush()
|
|
124
|
+
|
|
125
|
+
def _handle_data(self, data: bytes) -> bytes:
|
|
126
|
+
return self._decompressor.decompress(data)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class ZlibCompressor(IOAdapter):
|
|
130
|
+
def __init__(self, fobj: _t.Any, **kwargs: _t.Any) -> None:
|
|
131
|
+
super().__init__(fobj)
|
|
132
|
+
self._compressor = _compressobj(**kwargs)
|
|
133
|
+
|
|
134
|
+
def write(self, data: bytes) -> None:
|
|
135
|
+
self._fobj.write(self._compressor.compress(data))
|
|
136
|
+
|
|
137
|
+
def close(self) -> None:
|
|
138
|
+
if not self.closed:
|
|
139
|
+
self._fobj.write(self._compressor.flush())
|
|
140
|
+
self._fobj.close()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: kisstdlib
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.11
|
|
4
4
|
Summary: System programming with conceptually and algebraically simple primitives.
|
|
5
5
|
Author-email: Jan Malakhovski <oxij@oxij.org>
|
|
6
6
|
Project-URL: Homepage, https://oxij.org/software/kisstdlib/
|
|
@@ -9,7 +9,7 @@ namespaces = true
|
|
|
9
9
|
"kisstdlib" = ["py.typed"]
|
|
10
10
|
[project]
|
|
11
11
|
name = "kisstdlib"
|
|
12
|
-
version = "0.0.
|
|
12
|
+
version = "0.0.11"
|
|
13
13
|
authors = [{ name = "Jan Malakhovski", email = "oxij@oxij.org" }]
|
|
14
14
|
description = "System programming with conceptually and algebraically simple primitives."
|
|
15
15
|
readme = "README.md"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|