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.
Files changed (42) hide show
  1. {kisstdlib-0.0.10 → kisstdlib-0.0.11}/PKG-INFO +2 -2
  2. {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib/argparse_ext.py +0 -34
  3. {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib/fs.py +17 -7
  4. kisstdlib-0.0.11/kisstdlib/io/adapter.py +140 -0
  5. {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib.egg-info/PKG-INFO +2 -2
  6. {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib.egg-info/SOURCES.txt +1 -0
  7. {kisstdlib-0.0.10 → kisstdlib-0.0.11}/pyproject.toml +1 -1
  8. {kisstdlib-0.0.10 → kisstdlib-0.0.11}/LICENSE.txt +0 -0
  9. {kisstdlib-0.0.10 → kisstdlib-0.0.11}/README.md +0 -0
  10. {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib/__init__.py +0 -0
  11. {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib/base.py +0 -0
  12. {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib/compression.py +0 -0
  13. {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib/failure.py +0 -0
  14. {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib/getpass_ext.py +0 -0
  15. {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib/io/__init__.py +0 -0
  16. {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib/io/base.py +0 -0
  17. {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib/io/encoder.py +0 -0
  18. {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib/io/stdio.py +0 -0
  19. {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib/io/wrapper.py +0 -0
  20. {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib/itertools_ext.py +0 -0
  21. {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib/logging_ext.py +0 -0
  22. {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib/parsing.py +0 -0
  23. {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib/py.typed +0 -0
  24. {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib/pyrepr.py +0 -0
  25. {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib/signal_ext.py +0 -0
  26. {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib/sorted.py +0 -0
  27. {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib/sqlite3_ext.py +0 -0
  28. {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib/string_ext.py +0 -0
  29. {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib/tariter.py +0 -0
  30. {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib/time.py +0 -0
  31. {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib.egg-info/dependency_links.txt +0 -0
  32. {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib.egg-info/entry_points.txt +0 -0
  33. {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib.egg-info/requires.txt +0 -0
  34. {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib.egg-info/top_level.txt +0 -0
  35. {kisstdlib-0.0.10 → kisstdlib-0.0.11}/kisstdlib_bin/describe_forest.py +0 -0
  36. {kisstdlib-0.0.10 → kisstdlib-0.0.11}/setup.cfg +0 -0
  37. {kisstdlib-0.0.10 → kisstdlib-0.0.11}/setup.py +0 -0
  38. {kisstdlib-0.0.10 → kisstdlib-0.0.11}/test/test_basics.py +0 -0
  39. {kisstdlib-0.0.10 → kisstdlib-0.0.11}/test/test_fs.py +0 -0
  40. {kisstdlib-0.0.10 → kisstdlib-0.0.11}/test/test_io.py +0 -0
  41. {kisstdlib-0.0.10 → kisstdlib-0.0.11}/test/test_sorted.py +0 -0
  42. {kisstdlib-0.0.10 → kisstdlib-0.0.11}/test/test_time.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: kisstdlib
3
- Version: 0.0.10
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 # type: ignore
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"_{_os.getpid()}" if add_pid else ""
1104
- _dot_part = f".{prog}{pid}{ext}"
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
1
+ Metadata-Version: 2.2
2
2
  Name: kisstdlib
3
- Version: 0.0.10
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/
@@ -27,6 +27,7 @@ kisstdlib.egg-info/entry_points.txt
27
27
  kisstdlib.egg-info/requires.txt
28
28
  kisstdlib.egg-info/top_level.txt
29
29
  kisstdlib/io/__init__.py
30
+ kisstdlib/io/adapter.py
30
31
  kisstdlib/io/base.py
31
32
  kisstdlib/io/encoder.py
32
33
  kisstdlib/io/stdio.py
@@ -9,7 +9,7 @@ namespaces = true
9
9
  "kisstdlib" = ["py.typed"]
10
10
  [project]
11
11
  name = "kisstdlib"
12
- version = "0.0.10"
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