kisstdlib 0.0.1__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.1/PKG-INFO +18 -0
- kisstdlib-0.0.1/README.md +5 -0
- kisstdlib-0.0.1/kisstdlib/__init__.py +25 -0
- kisstdlib-0.0.1/kisstdlib/argparse/__init__.py +27 -0
- kisstdlib-0.0.1/kisstdlib/argparse/better.py +208 -0
- kisstdlib-0.0.1/kisstdlib/exceptions.py +44 -0
- kisstdlib-0.0.1/kisstdlib/io/__init__.py +57 -0
- kisstdlib-0.0.1/kisstdlib/io/base.py +109 -0
- kisstdlib-0.0.1/kisstdlib/io/encoder/__init__.py +28 -0
- kisstdlib-0.0.1/kisstdlib/io/encoder/base.py +80 -0
- kisstdlib-0.0.1/kisstdlib/io/encoder/pyrepr.py +152 -0
- kisstdlib-0.0.1/kisstdlib/io/wrapper.py +123 -0
- kisstdlib-0.0.1/kisstdlib/logging.py +51 -0
- kisstdlib-0.0.1/kisstdlib/path.py +100 -0
- kisstdlib-0.0.1/kisstdlib/py.typed +0 -0
- kisstdlib-0.0.1/kisstdlib.egg-info/PKG-INFO +18 -0
- kisstdlib-0.0.1/kisstdlib.egg-info/SOURCES.txt +20 -0
- kisstdlib-0.0.1/kisstdlib.egg-info/dependency_links.txt +1 -0
- kisstdlib-0.0.1/kisstdlib.egg-info/top_level.txt +1 -0
- kisstdlib-0.0.1/pyproject.toml +26 -0
- kisstdlib-0.0.1/setup.cfg +4 -0
- kisstdlib-0.0.1/setup.py +3 -0
kisstdlib-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: kisstdlib
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Keep It Simple STandarD LIBraries set for Python
|
|
5
|
+
Author-email: Jan Malakhovski <oxij@oxij.org>
|
|
6
|
+
Keywords: sys,stdlib
|
|
7
|
+
Classifier: Development Status :: 4 - Beta
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: License :: OSI Approved :: Python Software Foundation License
|
|
11
|
+
Requires-Python: >=3.10
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# What?
|
|
15
|
+
|
|
16
|
+
Keep It Simple STandarD LIBraries set for Python.
|
|
17
|
+
|
|
18
|
+
A very alpha Work In Progress kind of thing ATM.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Copyright (c) 2018-2023 Jan Malakhovski <oxij@oxij.org>
|
|
2
|
+
#
|
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
# furnished to do so, subject to the following conditions:
|
|
9
|
+
#
|
|
10
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
# copies or substantial portions of the Software.
|
|
12
|
+
#
|
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
# SOFTWARE.
|
|
20
|
+
#
|
|
21
|
+
# This file is a part of kisstdlib project.
|
|
22
|
+
#
|
|
23
|
+
# This file can be distributed under the terms of the above MIT license or
|
|
24
|
+
# Python Software Foundation License version 2 (PSF-2.0) as published by Python
|
|
25
|
+
# Software Foundation.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Copyright (c) 2023 Jan Malakhovski <oxij@oxij.org>
|
|
2
|
+
#
|
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
# furnished to do so, subject to the following conditions:
|
|
9
|
+
#
|
|
10
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
# copies or substantial portions of the Software.
|
|
12
|
+
#
|
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
# SOFTWARE.
|
|
20
|
+
#
|
|
21
|
+
# This file is a part of kisstdlib project.
|
|
22
|
+
#
|
|
23
|
+
# This file can be distributed under the terms of the above MIT license or
|
|
24
|
+
# Python Software Foundation License version 2 (PSF-2.0) as published by Python
|
|
25
|
+
# Software Foundation.
|
|
26
|
+
|
|
27
|
+
from .better import *
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# Copyright (c) 2022-2023 Jan Malakhovski <oxij@oxij.org>
|
|
2
|
+
#
|
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
# furnished to do so, subject to the following conditions:
|
|
9
|
+
#
|
|
10
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
# copies or substantial portions of the Software.
|
|
12
|
+
#
|
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
# SOFTWARE.
|
|
20
|
+
#
|
|
21
|
+
# This file is a part of kisstdlib project.
|
|
22
|
+
#
|
|
23
|
+
# This file can be distributed under the terms of the above MIT license or
|
|
24
|
+
# Python Software Foundation License version 2 (PSF-2.0) as published by Python
|
|
25
|
+
# Software Foundation.
|
|
26
|
+
|
|
27
|
+
import typing as _t
|
|
28
|
+
from argparse import *
|
|
29
|
+
from gettext import gettext as _
|
|
30
|
+
|
|
31
|
+
class BetterHelpFormatter(HelpFormatter):
|
|
32
|
+
"""Like argparse.HelpFormatter, but with better formatting.
|
|
33
|
+
Also, it adds `add_code` function.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def _fill_text(self, text : str, width : int, indent : str) -> str:
|
|
37
|
+
import textwrap
|
|
38
|
+
res = []
|
|
39
|
+
for line in text.splitlines():
|
|
40
|
+
if line == "":
|
|
41
|
+
res.append(line)
|
|
42
|
+
continue
|
|
43
|
+
|
|
44
|
+
for sub in textwrap.wrap(line, width - len(indent)):
|
|
45
|
+
sub = indent + sub
|
|
46
|
+
res.append(sub)
|
|
47
|
+
return "\n".join(res)
|
|
48
|
+
|
|
49
|
+
def _split_lines(self, text : str, width : int) -> list[str]:
|
|
50
|
+
import textwrap
|
|
51
|
+
res = []
|
|
52
|
+
for line in text.splitlines():
|
|
53
|
+
res += textwrap.wrap(line, width)
|
|
54
|
+
return res
|
|
55
|
+
|
|
56
|
+
def add_code(self, text : str) -> None:
|
|
57
|
+
self.add_text(text.strip())
|
|
58
|
+
|
|
59
|
+
class MarkdownBetterHelpFormatter(BetterHelpFormatter):
|
|
60
|
+
"""BetterHelpFormatter that outputs stuff formatted in Markdown"""
|
|
61
|
+
|
|
62
|
+
def add_code(self, text : str) -> None:
|
|
63
|
+
self.add_text("```\n" + text.strip() + "\n```")
|
|
64
|
+
|
|
65
|
+
def _format_usage(self, usage : str | None, actions : _t.Any, groups : _t.Any, prefix : str | None) -> str:
|
|
66
|
+
return super()._format_usage(usage, actions, groups, "")
|
|
67
|
+
|
|
68
|
+
def _format_action(self, action : _t.Any) -> str:
|
|
69
|
+
# determine the required width and the entry label
|
|
70
|
+
action_header = self._format_action_invocation(action)
|
|
71
|
+
|
|
72
|
+
tup = self._current_indent, '', "`" + action_header + "`"
|
|
73
|
+
action_header = '%*s- %s\n' % tup
|
|
74
|
+
|
|
75
|
+
# collect the pieces of the action help
|
|
76
|
+
parts = [action_header]
|
|
77
|
+
|
|
78
|
+
# if there was help for the action, add it
|
|
79
|
+
if action.help and action.help.strip():
|
|
80
|
+
first = True
|
|
81
|
+
for line in self._expand_help(action).splitlines():
|
|
82
|
+
if first:
|
|
83
|
+
parts.append('%*s: %s\n' % (self._current_indent, '', line))
|
|
84
|
+
else:
|
|
85
|
+
parts.append('%*s %s\n' % (self._current_indent, '', line))
|
|
86
|
+
first = False
|
|
87
|
+
|
|
88
|
+
# or add a newline if the description doesn't end with one
|
|
89
|
+
elif not action_header.endswith('\n'):
|
|
90
|
+
parts.append('\n')
|
|
91
|
+
|
|
92
|
+
# if there are any sub-actions, add their help as well
|
|
93
|
+
for subaction in self._iter_indented_subactions(action):
|
|
94
|
+
parts.append(self._format_action(subaction))
|
|
95
|
+
|
|
96
|
+
# return a single string
|
|
97
|
+
return self._join_parts(parts)
|
|
98
|
+
|
|
99
|
+
class _Section(HelpFormatter._Section):
|
|
100
|
+
def format_help(self) -> str:
|
|
101
|
+
if self.parent is not None:
|
|
102
|
+
self.formatter._indent()
|
|
103
|
+
join = self.formatter._join_parts
|
|
104
|
+
item_help = join([func(*args) for func, args in self.items])
|
|
105
|
+
if self.parent is not None:
|
|
106
|
+
self.formatter._dedent()
|
|
107
|
+
|
|
108
|
+
# return nothing if the section was empty
|
|
109
|
+
if not item_help:
|
|
110
|
+
return ''
|
|
111
|
+
|
|
112
|
+
# add the heading if the section was non-empty
|
|
113
|
+
if self.heading is not SUPPRESS and self.heading is not None:
|
|
114
|
+
heading = '%*s- %s:\n' % (self.formatter._current_indent, '', self.heading)
|
|
115
|
+
else:
|
|
116
|
+
heading = ''
|
|
117
|
+
|
|
118
|
+
# join the section-initial newline, the heading and the help
|
|
119
|
+
return join(['\n', heading, item_help, '\n'])
|
|
120
|
+
|
|
121
|
+
class BetterArgumentParser(ArgumentParser):
|
|
122
|
+
"""Like argparse.ArgumentParser but uses BetterHelpFormatter by default,
|
|
123
|
+
adds `--help` only to the root node, and
|
|
124
|
+
that `--help` prints the help for all the subcommands at once.
|
|
125
|
+
Also, provides `add_version` option.
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
formatter_class : type[BetterHelpFormatter]
|
|
129
|
+
|
|
130
|
+
def __init__(self,
|
|
131
|
+
prog : str | None = None,
|
|
132
|
+
version : str | None = None,
|
|
133
|
+
add_version : bool = False, # we set these two to False by default
|
|
134
|
+
add_help : bool = False, # so that subparsers don't get them enabled by default
|
|
135
|
+
additional_sections : list[_t.Callable[[BetterHelpFormatter], None]] = [],
|
|
136
|
+
formatter_class : type[BetterHelpFormatter] = BetterHelpFormatter,
|
|
137
|
+
*args : _t.Any, **kwargs : _t.Any) -> None:
|
|
138
|
+
super().__init__(prog, *args, formatter_class = formatter_class, add_help = False, **kwargs) # type: ignore
|
|
139
|
+
|
|
140
|
+
if version is None:
|
|
141
|
+
version = "dev"
|
|
142
|
+
if prog is not None:
|
|
143
|
+
try:
|
|
144
|
+
import importlib.metadata as meta
|
|
145
|
+
try:
|
|
146
|
+
version = meta.version(prog)
|
|
147
|
+
except meta.PackageNotFoundError:
|
|
148
|
+
pass
|
|
149
|
+
except ImportError:
|
|
150
|
+
pass
|
|
151
|
+
|
|
152
|
+
self.version = version
|
|
153
|
+
self.add_version = add_version
|
|
154
|
+
self.add_help = add_help
|
|
155
|
+
self.additional_sections = additional_sections
|
|
156
|
+
|
|
157
|
+
default_prefix = '-' if '-' in self.prefix_chars else self.prefix_chars[0]
|
|
158
|
+
if self.add_version:
|
|
159
|
+
self.add_argument(default_prefix*2 + "version", action="version", version="%(prog)s " + version)
|
|
160
|
+
|
|
161
|
+
if self.add_help:
|
|
162
|
+
self.add_argument(
|
|
163
|
+
default_prefix + "h", default_prefix*2 + "help", action='help',
|
|
164
|
+
default=SUPPRESS, help=_('show this help message and exit'))
|
|
165
|
+
|
|
166
|
+
def set_formatter_class(self, formatter_class : type[BetterHelpFormatter]) -> None:
|
|
167
|
+
self.formatter_class = formatter_class
|
|
168
|
+
if hasattr(self._subparsers, "_group_actions"):
|
|
169
|
+
for grp in self._subparsers._group_actions: # type: ignore
|
|
170
|
+
for choice, e in grp.choices.items(): # type: ignore
|
|
171
|
+
if e.formatter_class != formatter_class:
|
|
172
|
+
e.formatter_class = formatter_class
|
|
173
|
+
|
|
174
|
+
def format_help(self, width : int | None = None) -> str:
|
|
175
|
+
if width is None:
|
|
176
|
+
import shutil
|
|
177
|
+
width = shutil.get_terminal_size().columns - 2
|
|
178
|
+
formatter = self.formatter_class(prog=self.prog, width=width)
|
|
179
|
+
|
|
180
|
+
formatter.add_usage(self.usage, self._actions, self._mutually_exclusive_groups)
|
|
181
|
+
formatter.add_text(self.description)
|
|
182
|
+
|
|
183
|
+
if hasattr(self, "_action_groups"):
|
|
184
|
+
for action_group in self._action_groups:
|
|
185
|
+
formatter.start_section(action_group.title)
|
|
186
|
+
formatter.add_text(action_group.description)
|
|
187
|
+
formatter.add_arguments(action_group._group_actions)
|
|
188
|
+
formatter.end_section()
|
|
189
|
+
|
|
190
|
+
res : str = "# " + formatter.format_help()
|
|
191
|
+
|
|
192
|
+
if hasattr(self._subparsers, "_group_actions"):
|
|
193
|
+
seen = set()
|
|
194
|
+
for grp in self._subparsers._group_actions: # type: ignore
|
|
195
|
+
for choice, e in grp.choices.items(): # type: ignore
|
|
196
|
+
if e in seen: continue
|
|
197
|
+
seen.add(e)
|
|
198
|
+
e.formatter_class = self.formatter_class
|
|
199
|
+
res += "\n#" + e.format_help(width=width)
|
|
200
|
+
|
|
201
|
+
for gen in self.additional_sections:
|
|
202
|
+
formatter = self.formatter_class(prog=self.prog, width=width)
|
|
203
|
+
gen(formatter)
|
|
204
|
+
res += "\n" + formatter.format_help()
|
|
205
|
+
|
|
206
|
+
formatter.add_text(self.epilog)
|
|
207
|
+
|
|
208
|
+
return res
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Copyright (c) 2023 Jan Malakhovski <oxij@oxij.org>
|
|
2
|
+
#
|
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
# furnished to do so, subject to the following conditions:
|
|
9
|
+
#
|
|
10
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
# copies or substantial portions of the Software.
|
|
12
|
+
#
|
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
# SOFTWARE.
|
|
20
|
+
#
|
|
21
|
+
# This file is a part of kisstdlib project.
|
|
22
|
+
#
|
|
23
|
+
# This file can be distributed under the terms of the above MIT license or
|
|
24
|
+
# Python Software Foundation License version 2 (PSF-2.0) as published by Python
|
|
25
|
+
# Software Foundation.
|
|
26
|
+
|
|
27
|
+
"""Exceptions with printable descriptions.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
import typing as _t
|
|
31
|
+
|
|
32
|
+
class CatastrophicFailure(Exception):
|
|
33
|
+
def __init__(self, what : str, *args : _t.Any) -> None:
|
|
34
|
+
super().__init__()
|
|
35
|
+
self.description = what % args
|
|
36
|
+
|
|
37
|
+
def __str__(self) -> str:
|
|
38
|
+
return self.description
|
|
39
|
+
|
|
40
|
+
def elaborate(self, what : str, *args : _t.Any) -> None:
|
|
41
|
+
self.description = what % args + ": " + self.description
|
|
42
|
+
|
|
43
|
+
class Failure(CatastrophicFailure):
|
|
44
|
+
pass
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Copyright (c) 2018-2023 Jan Malakhovski <oxij@oxij.org>
|
|
2
|
+
#
|
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
# furnished to do so, subject to the following conditions:
|
|
9
|
+
#
|
|
10
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
# copies or substantial portions of the Software.
|
|
12
|
+
#
|
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
# SOFTWARE.
|
|
20
|
+
#
|
|
21
|
+
# This file is a part of kisstdlib project.
|
|
22
|
+
#
|
|
23
|
+
# This file can be distributed under the terms of the above MIT license or
|
|
24
|
+
# Python Software Foundation License version 2 (PSF-2.0) as published by Python
|
|
25
|
+
# Software Foundation.
|
|
26
|
+
|
|
27
|
+
"""KISS IO File Object hierarchy.
|
|
28
|
+
|
|
29
|
+
What?
|
|
30
|
+
|
|
31
|
+
An alternative to Python's `io` module.
|
|
32
|
+
|
|
33
|
+
Why?
|
|
34
|
+
|
|
35
|
+
APIs of Python's `io` module are way too complex. It only really works well
|
|
36
|
+
when you use either buffered raw or buffered textual IO and never try to mix
|
|
37
|
+
them.
|
|
38
|
+
|
|
39
|
+
The problem stems from the fact that Python's `io.RawIOBase` and
|
|
40
|
+
`io.TextIOBase` are trying to give the same APIs, but they do different
|
|
41
|
+
things. Which, after being multiplied with the differences of buffered and
|
|
42
|
+
unbuffered `read`s and `write`s, essentially gives FOUR different file
|
|
43
|
+
object semantics under the same file object API.
|
|
44
|
+
|
|
45
|
+
Here, instead we have separate calls for `bytes` and `str` versions of
|
|
46
|
+
`read` and `write` calls, buffering is completely transparent, reading and
|
|
47
|
+
writing parts are split into different interfaces, and text encoding
|
|
48
|
+
handling is split between them (because it has to be handled differently).
|
|
49
|
+
|
|
50
|
+
This also provides `TIOEncoder` and (WIP `TIODecoder`) classes that can do
|
|
51
|
+
simple serialization/de-serialization a-la Pascal.
|
|
52
|
+
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
from .base import *
|
|
56
|
+
from .wrapper import *
|
|
57
|
+
from .encoder import *
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Copyright (c) 2018-2023 Jan Malakhovski <oxij@oxij.org>
|
|
2
|
+
#
|
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
# furnished to do so, subject to the following conditions:
|
|
9
|
+
#
|
|
10
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
# copies or substantial portions of the Software.
|
|
12
|
+
#
|
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
# SOFTWARE.
|
|
20
|
+
#
|
|
21
|
+
# This file is a part of kisstdlib project.
|
|
22
|
+
#
|
|
23
|
+
# This file can be distributed under the terms of the above MIT license or
|
|
24
|
+
# Python Software Foundation License version 2 (PSF-2.0) as published by Python
|
|
25
|
+
# Software Foundation.
|
|
26
|
+
|
|
27
|
+
import abc as _abc
|
|
28
|
+
import sys as _sys
|
|
29
|
+
import typing as _t
|
|
30
|
+
|
|
31
|
+
MEGABYTE = 1024 * 1024
|
|
32
|
+
|
|
33
|
+
# anything that can be `write`n
|
|
34
|
+
ByteString = _t.Union[bytes, bytearray, memoryview]
|
|
35
|
+
|
|
36
|
+
ShutdownState = int # _t.NewType("ShutdownState", int), ideally, but then ~ and | operations won't work
|
|
37
|
+
SHUT_NONE = ShutdownState(0)
|
|
38
|
+
SHUT_READ = ShutdownState(1)
|
|
39
|
+
SHUT_WRITE = ShutdownState(2)
|
|
40
|
+
SHUT_BOTH = ShutdownState(3)
|
|
41
|
+
|
|
42
|
+
class MinimalIO(metaclass=_abc.ABCMeta):
|
|
43
|
+
def __repr__(self) -> str:
|
|
44
|
+
return "<%s %s>" % (self.__class__.__name__, hex(id(self)))
|
|
45
|
+
|
|
46
|
+
@_abc.abstractmethod
|
|
47
|
+
def close(self) -> None:
|
|
48
|
+
raise NotImplementedError()
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
@_abc.abstractmethod
|
|
52
|
+
def closed(self) -> bool:
|
|
53
|
+
raise NotImplementedError()
|
|
54
|
+
|
|
55
|
+
@_abc.abstractmethod
|
|
56
|
+
def shutdown(self, what : ShutdownState) -> None:
|
|
57
|
+
raise NotImplementedError()
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
@_abc.abstractmethod
|
|
61
|
+
def shutdown_state(self) -> ShutdownState:
|
|
62
|
+
raise NotImplementedError()
|
|
63
|
+
|
|
64
|
+
class MinimalIOReader(MinimalIO):
|
|
65
|
+
@_abc.abstractmethod
|
|
66
|
+
def read_some_bytes(self, size : int) -> bytes:
|
|
67
|
+
raise NotImplementedError()
|
|
68
|
+
|
|
69
|
+
def read_all_bytes(self, chunk_size : int = MEGABYTE) -> bytes:
|
|
70
|
+
data : list[bytes] = []
|
|
71
|
+
while True:
|
|
72
|
+
res = self.read_some_bytes(chunk_size)
|
|
73
|
+
rlen = len(res)
|
|
74
|
+
if rlen == 0: break
|
|
75
|
+
data.append(res)
|
|
76
|
+
return b"".join(data)
|
|
77
|
+
|
|
78
|
+
def read_exactly_bytes(self, size : int) -> bytes:
|
|
79
|
+
data : list[bytes] = []
|
|
80
|
+
total = 0
|
|
81
|
+
while total < size:
|
|
82
|
+
res = self.read_some_bytes(size - total)
|
|
83
|
+
rlen = len(res)
|
|
84
|
+
if rlen == 0: break
|
|
85
|
+
data.append(res)
|
|
86
|
+
total += rlen
|
|
87
|
+
return b"".join(data)
|
|
88
|
+
|
|
89
|
+
def read_bytes(self, size : int | None = None) -> bytes:
|
|
90
|
+
if size is None:
|
|
91
|
+
return self.read_all_bytes()
|
|
92
|
+
else:
|
|
93
|
+
return self.read_exactly_bytes(size)
|
|
94
|
+
|
|
95
|
+
class MinimalIOWriter(MinimalIO):
|
|
96
|
+
@_abc.abstractmethod
|
|
97
|
+
def write_some_bytes(self, data : ByteString) -> int:
|
|
98
|
+
raise NotImplementedError()
|
|
99
|
+
|
|
100
|
+
def write_bytes(self, data : ByteString) -> None:
|
|
101
|
+
done = 0
|
|
102
|
+
view = memoryview(data)
|
|
103
|
+
datalen = len(data)
|
|
104
|
+
while done < datalen:
|
|
105
|
+
done += self.write_some_bytes(view[done:])
|
|
106
|
+
|
|
107
|
+
@_abc.abstractmethod
|
|
108
|
+
def flush(self) -> None:
|
|
109
|
+
raise NotImplementedError()
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Copyright (c) 2023 Jan Malakhovski <oxij@oxij.org>
|
|
2
|
+
#
|
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
# furnished to do so, subject to the following conditions:
|
|
9
|
+
#
|
|
10
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
# copies or substantial portions of the Software.
|
|
12
|
+
#
|
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
# SOFTWARE.
|
|
20
|
+
#
|
|
21
|
+
# This file is a part of kisstdlib project.
|
|
22
|
+
#
|
|
23
|
+
# This file can be distributed under the terms of the above MIT license or
|
|
24
|
+
# Python Software Foundation License version 2 (PSF-2.0) as published by Python
|
|
25
|
+
# Software Foundation.
|
|
26
|
+
|
|
27
|
+
from .base import *
|
|
28
|
+
from .pyrepr import *
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Copyright (c) 2023 Jan Malakhovski <oxij@oxij.org>
|
|
2
|
+
#
|
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
# furnished to do so, subject to the following conditions:
|
|
9
|
+
#
|
|
10
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
# copies or substantial portions of the Software.
|
|
12
|
+
#
|
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
# SOFTWARE.
|
|
20
|
+
#
|
|
21
|
+
# This file is a part of kisstdlib project.
|
|
22
|
+
#
|
|
23
|
+
# This file can be distributed under the terms of the above MIT license or
|
|
24
|
+
# Python Software Foundation License version 2 (PSF-2.0) as published by Python
|
|
25
|
+
# Software Foundation.
|
|
26
|
+
|
|
27
|
+
import sys as _sys
|
|
28
|
+
import typing as _t
|
|
29
|
+
|
|
30
|
+
from ..wrapper import *
|
|
31
|
+
|
|
32
|
+
class TIOEncoder(TIOWrappedWriter):
|
|
33
|
+
default : _t.Callable[[_t.Any, _t.Any], None]
|
|
34
|
+
|
|
35
|
+
def __init__(self,
|
|
36
|
+
fobj : _t.Any,
|
|
37
|
+
eol : bytes = b"\n",
|
|
38
|
+
encoding : str = _sys.getdefaultencoding(),
|
|
39
|
+
encoders : dict[type[_t.Any], _t.Callable[[_t.Any, _t.Any], None]] = {},
|
|
40
|
+
default : _t.Callable[[_t.Any, _t.Any], None] | None = None) -> None:
|
|
41
|
+
super().__init__(fobj, eol, encoding)
|
|
42
|
+
self.encoders = encoders.copy()
|
|
43
|
+
if default is None:
|
|
44
|
+
self.default = TIOEncoder.not_implemented
|
|
45
|
+
else:
|
|
46
|
+
self.default = default
|
|
47
|
+
|
|
48
|
+
def not_implemented(self, obj : _t.Any) -> None:
|
|
49
|
+
raise NotImplementedError("don't know how to encode %s", type(obj))
|
|
50
|
+
|
|
51
|
+
def _find_encoder(self, obj_type : _t.Any) -> _t.Any:
|
|
52
|
+
for typ, enc in self.encoders.items():
|
|
53
|
+
if issubclass(obj_type, typ):
|
|
54
|
+
return enc
|
|
55
|
+
return self.default
|
|
56
|
+
|
|
57
|
+
def encode(self, obj : _t.Any) -> None:
|
|
58
|
+
typ = type(obj)
|
|
59
|
+
try:
|
|
60
|
+
enc = self.encoders[typ]
|
|
61
|
+
except KeyError:
|
|
62
|
+
enc = self._find_encoder(typ)
|
|
63
|
+
self.encoders[typ] = enc
|
|
64
|
+
enc(self, obj)
|
|
65
|
+
|
|
66
|
+
tio_default_encoders : dict[type[_t.Any], _t.Callable[[TIOWrappedWriter, _t.Any], None]] = {
|
|
67
|
+
bool: TIOEncoder.write_strable,
|
|
68
|
+
int: TIOEncoder.write_strable,
|
|
69
|
+
float: TIOEncoder.write_strable,
|
|
70
|
+
str: TIOEncoder.write_str,
|
|
71
|
+
bytes: TIOEncoder.write_bytes,
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
tio_default_encoders_ln : dict[type[_t.Any], _t.Callable[[TIOWrappedWriter, _t.Any], None]] = {
|
|
75
|
+
bool: TIOEncoder.write_strable_ln,
|
|
76
|
+
int: TIOEncoder.write_strable_ln,
|
|
77
|
+
float: TIOEncoder.write_strable_ln,
|
|
78
|
+
str: TIOEncoder.write_str_ln,
|
|
79
|
+
bytes: TIOEncoder.write_bytes_ln,
|
|
80
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# Copyright (c) 2023 Jan Malakhovski <oxij@oxij.org>
|
|
2
|
+
#
|
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
# furnished to do so, subject to the following conditions:
|
|
9
|
+
#
|
|
10
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
# copies or substantial portions of the Software.
|
|
12
|
+
#
|
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
# SOFTWARE.
|
|
20
|
+
#
|
|
21
|
+
# This file is a part of kisstdlib project.
|
|
22
|
+
#
|
|
23
|
+
# This file can be distributed under the terms of the above MIT license or
|
|
24
|
+
# Python Software Foundation License version 2 (PSF-2.0) as published by Python
|
|
25
|
+
# Software Foundation.
|
|
26
|
+
|
|
27
|
+
"""cbor2-style encoder for Pythonic Object Representation (repr)
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
import io as _io
|
|
31
|
+
import typing as _t
|
|
32
|
+
import sys as _sys
|
|
33
|
+
|
|
34
|
+
from .base import TIOEncoder
|
|
35
|
+
|
|
36
|
+
class PyReprEncoder(TIOEncoder):
|
|
37
|
+
def __init__(self,
|
|
38
|
+
fobj : _io.BufferedIOBase,
|
|
39
|
+
indent : int = 2,
|
|
40
|
+
starting_indent : int = 0,
|
|
41
|
+
width : int | None = None,
|
|
42
|
+
encoding : str = _sys.getdefaultencoding(),
|
|
43
|
+
encoders : dict[type[_t.Any], _t.Callable[[_t.Any, _t.Any], None]] | None = None,
|
|
44
|
+
default : _t.Callable[[_t.Any, _t.Any], None] | None = None) -> None:
|
|
45
|
+
super().__init__(fobj, b"\n", encoding,
|
|
46
|
+
pyrepr_default_encoders if encoders is None else encoders, default)
|
|
47
|
+
self.indent = indent
|
|
48
|
+
self.current = starting_indent
|
|
49
|
+
self.width = width
|
|
50
|
+
self.linelen = 0
|
|
51
|
+
self.want_space = False
|
|
52
|
+
self.want_ln = False
|
|
53
|
+
|
|
54
|
+
def flush_line(self, force : bool = False) -> None:
|
|
55
|
+
if self.linelen != 0:
|
|
56
|
+
if force or self.want_ln or (self.width is not None and self.linelen > self.width):
|
|
57
|
+
self.write_str_ln("")
|
|
58
|
+
self.linelen = 0
|
|
59
|
+
self.want_ln = False
|
|
60
|
+
self.want_space = False
|
|
61
|
+
|
|
62
|
+
def lnlexeme(self, token : str) -> None:
|
|
63
|
+
token = " " * self.current + token
|
|
64
|
+
self.write_str(token)
|
|
65
|
+
self.linelen = len(token)
|
|
66
|
+
|
|
67
|
+
def lexeme(self, token : str) -> None:
|
|
68
|
+
self.flush_line()
|
|
69
|
+
|
|
70
|
+
if self.linelen == 0:
|
|
71
|
+
self.lnlexeme(token)
|
|
72
|
+
elif self.want_space:
|
|
73
|
+
if self.width is not None and self.linelen + len(token) + 1 > self.width:
|
|
74
|
+
self.flush_line(True)
|
|
75
|
+
self.lnlexeme(token)
|
|
76
|
+
else:
|
|
77
|
+
token = " " + token
|
|
78
|
+
self.write_str(token)
|
|
79
|
+
self.linelen += len(token)
|
|
80
|
+
else:
|
|
81
|
+
self.write_str(token)
|
|
82
|
+
self.linelen += len(token)
|
|
83
|
+
self.want_space = True
|
|
84
|
+
|
|
85
|
+
def comment(self, token : str) -> None:
|
|
86
|
+
self.lexeme("# " + token)
|
|
87
|
+
self.flush_line(True)
|
|
88
|
+
|
|
89
|
+
def start(self, token : str) -> None:
|
|
90
|
+
self.lexeme(token)
|
|
91
|
+
self.current += self.indent
|
|
92
|
+
|
|
93
|
+
def delim(self, token : str) -> None:
|
|
94
|
+
self.want_space = False
|
|
95
|
+
self.lexeme(token)
|
|
96
|
+
|
|
97
|
+
def stop(self, token : str) -> None:
|
|
98
|
+
self.current -= self.indent
|
|
99
|
+
self.lexeme(token)
|
|
100
|
+
|
|
101
|
+
def encode_plain(self, obj : _t.Any) -> None:
|
|
102
|
+
self.lexeme(str(obj))
|
|
103
|
+
|
|
104
|
+
def encode_repr(self, obj : _t.Any) -> None:
|
|
105
|
+
self.lexeme(repr(obj))
|
|
106
|
+
|
|
107
|
+
def encode_list(self, obj : list[_t.Any]) -> None:
|
|
108
|
+
large = len(obj) > 2
|
|
109
|
+
self.start("[")
|
|
110
|
+
self.want_ln = large
|
|
111
|
+
first = True
|
|
112
|
+
for i in range(0, len(obj)):
|
|
113
|
+
if not first:
|
|
114
|
+
self.delim(",")
|
|
115
|
+
self.want_ln = large
|
|
116
|
+
first = False
|
|
117
|
+
self.encode(obj[i])
|
|
118
|
+
self.want_ln = large
|
|
119
|
+
self.stop("]")
|
|
120
|
+
|
|
121
|
+
def encode_dict(self, obj : dict[_t.Any, _t.Any]) -> None:
|
|
122
|
+
large = len(obj) > 1
|
|
123
|
+
self.start("{")
|
|
124
|
+
self.want_ln = large
|
|
125
|
+
first = True
|
|
126
|
+
for k in obj:
|
|
127
|
+
if not first:
|
|
128
|
+
self.delim(",")
|
|
129
|
+
self.want_ln = large
|
|
130
|
+
first = False
|
|
131
|
+
|
|
132
|
+
self.lexeme(repr(k))
|
|
133
|
+
self.delim(":")
|
|
134
|
+
self.encode(obj[k])
|
|
135
|
+
self.want_ln = large
|
|
136
|
+
self.stop("}")
|
|
137
|
+
|
|
138
|
+
pyrepr_default_encoders : dict[type[_t.Any], _t.Callable[[PyReprEncoder, _t.Any], None]] = {
|
|
139
|
+
bool: PyReprEncoder.encode_plain,
|
|
140
|
+
int: PyReprEncoder.encode_plain,
|
|
141
|
+
float: PyReprEncoder.encode_plain,
|
|
142
|
+
str: PyReprEncoder.encode_repr,
|
|
143
|
+
bytes: PyReprEncoder.encode_repr,
|
|
144
|
+
type(None): PyReprEncoder.encode_repr,
|
|
145
|
+
list: PyReprEncoder.encode_list,
|
|
146
|
+
dict: PyReprEncoder.encode_dict,
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
def pyrepr_dumps(obj : _t.Any, *args : _t.Any, **kwargs : _t.Any) -> bytes:
|
|
150
|
+
encoder = PyReprEncoder(_io.BytesIO(), *args, **kwargs)
|
|
151
|
+
encoder.encode(obj)
|
|
152
|
+
return encoder.fobj.getvalue() # type: ignore
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Copyright (c) 2018-2023 Jan Malakhovski <oxij@oxij.org>
|
|
2
|
+
#
|
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
# furnished to do so, subject to the following conditions:
|
|
9
|
+
#
|
|
10
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
# copies or substantial portions of the Software.
|
|
12
|
+
#
|
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
# SOFTWARE.
|
|
20
|
+
#
|
|
21
|
+
# This file is a part of kisstdlib project.
|
|
22
|
+
#
|
|
23
|
+
# This file can be distributed under the terms of the above MIT license or
|
|
24
|
+
# Python Software Foundation License version 2 (PSF-2.0) as published by Python
|
|
25
|
+
# Software Foundation.
|
|
26
|
+
|
|
27
|
+
import logging as _logging
|
|
28
|
+
import os as _os
|
|
29
|
+
import sys as _sys
|
|
30
|
+
import typing as _t
|
|
31
|
+
|
|
32
|
+
from .base import *
|
|
33
|
+
|
|
34
|
+
class MinimalIOWrapper(MinimalIO):
|
|
35
|
+
def __init__(self, fobj : _t.Any) -> None:
|
|
36
|
+
self.fobj = fobj
|
|
37
|
+
# _socket.socket's fileno() will return -1 after
|
|
38
|
+
# .close() and poll needs an actual value to unsubscribe
|
|
39
|
+
# properly so we have to keep a copy here
|
|
40
|
+
try:
|
|
41
|
+
self._fileno = fobj.fileno()
|
|
42
|
+
except OSError:
|
|
43
|
+
self._fileno = None
|
|
44
|
+
_logging.getLogger("kisstd").debug("init %s", self)
|
|
45
|
+
|
|
46
|
+
def __del__(self) -> None:
|
|
47
|
+
_logging.getLogger("kisstd").debug("del %s", self)
|
|
48
|
+
|
|
49
|
+
def __repr__(self) -> str:
|
|
50
|
+
if self._fileno is not None:
|
|
51
|
+
desc = "fd=" + str(self._fileno)
|
|
52
|
+
else:
|
|
53
|
+
desc = "obj=" + str(id(self.fobj))
|
|
54
|
+
return "<%s %s %s closed=%s>" % (self.__class__.__name__, hex(id(self)), desc, self.closed)
|
|
55
|
+
|
|
56
|
+
def close(self) -> None:
|
|
57
|
+
self.fobj.close()
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def closed(self) -> bool:
|
|
61
|
+
return self.fobj.closed # type: ignore
|
|
62
|
+
|
|
63
|
+
def shutdown(self, what : ShutdownState) -> None:
|
|
64
|
+
raise NotImplementedError("MinimalIOWrapper can't be shutdown")
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def shutdown_state(self) -> ShutdownState:
|
|
68
|
+
if self.closed:
|
|
69
|
+
return SHUT_BOTH
|
|
70
|
+
else:
|
|
71
|
+
return SHUT_NONE
|
|
72
|
+
|
|
73
|
+
def __enter__(self) -> _t.Any:
|
|
74
|
+
return self
|
|
75
|
+
|
|
76
|
+
def __exit__(self, exc_type : _t.Any, exc_value : _t.Any, exc_tb : _t.Any) -> None:
|
|
77
|
+
self.close()
|
|
78
|
+
|
|
79
|
+
class TIOWrapper(MinimalIOWrapper):
|
|
80
|
+
def __init__(self, fobj : _t.Any, eol : bytes = b"\n", encoding : str = _sys.getdefaultencoding()) -> None:
|
|
81
|
+
super().__init__(fobj)
|
|
82
|
+
self.encoding = encoding
|
|
83
|
+
self.eol = eol
|
|
84
|
+
|
|
85
|
+
class TIOWrappedReader(TIOWrapper, MinimalIOReader):
|
|
86
|
+
def read_some_bytes(self, size : int) -> bytes:
|
|
87
|
+
return self.fobj.read(size) # type: ignore
|
|
88
|
+
|
|
89
|
+
class TIOWrappedWriter(TIOWrapper, MinimalIOWriter):
|
|
90
|
+
def write_some_bytes(self, data : ByteString) -> int:
|
|
91
|
+
return self.fobj.write(data) # type: ignore
|
|
92
|
+
|
|
93
|
+
def flush(self) -> None:
|
|
94
|
+
self.fobj.flush()
|
|
95
|
+
|
|
96
|
+
def write_bytes_ln(self, data : ByteString) -> None:
|
|
97
|
+
self.write_bytes(data)
|
|
98
|
+
self.write_bytes(self.eol)
|
|
99
|
+
|
|
100
|
+
def write_str(self, data : str) -> None:
|
|
101
|
+
assert self.encoding is not None
|
|
102
|
+
self.write_bytes(data.encode(self.encoding))
|
|
103
|
+
|
|
104
|
+
def write_str_ln(self, data : str) -> None:
|
|
105
|
+
self.write_str(data)
|
|
106
|
+
self.write_bytes(self.eol)
|
|
107
|
+
|
|
108
|
+
def write_strable(self, data : _t.Any) -> None:
|
|
109
|
+
self.write_str(str(data))
|
|
110
|
+
|
|
111
|
+
def write_strable_ln(self, data : _t.Any) -> None:
|
|
112
|
+
self.write_strable(data)
|
|
113
|
+
self.write_bytes(self.eol)
|
|
114
|
+
|
|
115
|
+
def write(self, data : str | ByteString) -> None:
|
|
116
|
+
if isinstance(data, str):
|
|
117
|
+
self.write_str(data)
|
|
118
|
+
else:
|
|
119
|
+
self.write_bytes(data)
|
|
120
|
+
|
|
121
|
+
stdin = TIOWrappedReader(_os.fdopen(_sys.stdin.fileno(), "rb"))
|
|
122
|
+
stdout = TIOWrappedWriter(_os.fdopen(_sys.stdout.fileno(), "wb"))
|
|
123
|
+
stderr = TIOWrappedWriter(_os.fdopen(_sys.stderr.fileno(), "wb"))
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Copyright (c) 2023 Jan Malakhovski <oxij@oxij.org>
|
|
2
|
+
#
|
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
# furnished to do so, subject to the following conditions:
|
|
9
|
+
#
|
|
10
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
# copies or substantial portions of the Software.
|
|
12
|
+
#
|
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
# SOFTWARE.
|
|
20
|
+
#
|
|
21
|
+
# This file is a part of kisstdlib project.
|
|
22
|
+
#
|
|
23
|
+
# This file can be distributed under the terms of the above MIT license or
|
|
24
|
+
# Python Software Foundation License version 2 (PSF-2.0) as published by Python
|
|
25
|
+
# Software Foundation.
|
|
26
|
+
|
|
27
|
+
import logging as _logging
|
|
28
|
+
|
|
29
|
+
class CounterHandler(_logging.NullHandler):
|
|
30
|
+
errors : int
|
|
31
|
+
warnings : int
|
|
32
|
+
infos : int
|
|
33
|
+
debugs : int
|
|
34
|
+
|
|
35
|
+
def __init__(self, level : int = _logging.DEBUG) -> None:
|
|
36
|
+
super().__init__(level)
|
|
37
|
+
self.errors = 0
|
|
38
|
+
self.warnings = 0
|
|
39
|
+
self.infos = 0
|
|
40
|
+
self.debugs = 0
|
|
41
|
+
|
|
42
|
+
def handle(self, record : _logging.LogRecord) -> bool:
|
|
43
|
+
if record.levelno >= _logging.ERROR:
|
|
44
|
+
self.errors += 1
|
|
45
|
+
elif record.levelno >= _logging.WARNING:
|
|
46
|
+
self.warnings += 1
|
|
47
|
+
elif record.levelno >= _logging.INFO:
|
|
48
|
+
self.infos += 1
|
|
49
|
+
elif record.levelno >= _logging.DEBUG:
|
|
50
|
+
self.debugs += 1
|
|
51
|
+
return True
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Copyright (c) 2018-2023 Jan Malakhovski <oxij@oxij.org>
|
|
2
|
+
#
|
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
# furnished to do so, subject to the following conditions:
|
|
9
|
+
#
|
|
10
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
# copies or substantial portions of the Software.
|
|
12
|
+
#
|
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
# SOFTWARE.
|
|
20
|
+
#
|
|
21
|
+
# This file is a part of kisstdlib project.
|
|
22
|
+
#
|
|
23
|
+
# This file can be distributed under the terms of the above MIT license or
|
|
24
|
+
# Python Software Foundation License version 2 (PSF-2.0) as published by Python
|
|
25
|
+
# Software Foundation.
|
|
26
|
+
|
|
27
|
+
import logging as _logging
|
|
28
|
+
import os as _os
|
|
29
|
+
import stat as _stat
|
|
30
|
+
import typing as _t
|
|
31
|
+
|
|
32
|
+
def walk_orderly(path : _t.AnyStr,
|
|
33
|
+
include_directories : bool = True,
|
|
34
|
+
follow_symlinks : bool = True,
|
|
35
|
+
handle_error : _t.Callable[..., None] | None = _logging.error) -> _t.Iterable[_t.AnyStr]:
|
|
36
|
+
"""Similar to os.walk, but produces an iterator over plain file paths, allows
|
|
37
|
+
non-directories as input (which will just output a single element), and
|
|
38
|
+
the output is guaranteed to be ordered.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
try:
|
|
42
|
+
fstat = _os.stat(path, follow_symlinks = follow_symlinks)
|
|
43
|
+
except OSError:
|
|
44
|
+
if handle_error is not None:
|
|
45
|
+
handle_error("failed to stat: %s", path)
|
|
46
|
+
return
|
|
47
|
+
raise
|
|
48
|
+
|
|
49
|
+
if not _stat.S_ISDIR(fstat.st_mode):
|
|
50
|
+
yield path
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
scandir_it = _os.scandir(path)
|
|
55
|
+
except OSError:
|
|
56
|
+
if handle_error is not None:
|
|
57
|
+
handle_error("failed to scandir: %s", path)
|
|
58
|
+
return
|
|
59
|
+
raise
|
|
60
|
+
|
|
61
|
+
elements : list[_t.AnyStr] = []
|
|
62
|
+
|
|
63
|
+
with scandir_it:
|
|
64
|
+
while True:
|
|
65
|
+
try:
|
|
66
|
+
entry : _t.Any = next(scandir_it)
|
|
67
|
+
except StopIteration:
|
|
68
|
+
break
|
|
69
|
+
except OSError:
|
|
70
|
+
if handle_error is not None:
|
|
71
|
+
handle_error("failed in scandir: %s", path)
|
|
72
|
+
return
|
|
73
|
+
raise
|
|
74
|
+
else:
|
|
75
|
+
try:
|
|
76
|
+
is_dir = entry.is_dir(follow_symlinks = follow_symlinks)
|
|
77
|
+
except OSError:
|
|
78
|
+
if handle_error is not None:
|
|
79
|
+
handle_error("failed to stat: %s", entry.path)
|
|
80
|
+
return
|
|
81
|
+
raise
|
|
82
|
+
|
|
83
|
+
if is_dir:
|
|
84
|
+
if isinstance(entry.path, bytes):
|
|
85
|
+
elements.append(entry.path + b"/") # type: ignore
|
|
86
|
+
else:
|
|
87
|
+
elements.append(entry.path + "/")
|
|
88
|
+
else:
|
|
89
|
+
elements.append(entry.path)
|
|
90
|
+
|
|
91
|
+
elements.sort()
|
|
92
|
+
|
|
93
|
+
for path in elements:
|
|
94
|
+
lchar : _t.AnyStr = path[-1:]
|
|
95
|
+
if lchar == "/" or lchar == b"/":
|
|
96
|
+
if include_directories:
|
|
97
|
+
yield path
|
|
98
|
+
yield from walk_orderly(path, include_directories, follow_symlinks, handle_error)
|
|
99
|
+
else:
|
|
100
|
+
yield path
|
|
File without changes
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: kisstdlib
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Keep It Simple STandarD LIBraries set for Python
|
|
5
|
+
Author-email: Jan Malakhovski <oxij@oxij.org>
|
|
6
|
+
Keywords: sys,stdlib
|
|
7
|
+
Classifier: Development Status :: 4 - Beta
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: License :: OSI Approved :: Python Software Foundation License
|
|
11
|
+
Requires-Python: >=3.10
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# What?
|
|
15
|
+
|
|
16
|
+
Keep It Simple STandarD LIBraries set for Python.
|
|
17
|
+
|
|
18
|
+
A very alpha Work In Progress kind of thing ATM.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
setup.py
|
|
4
|
+
kisstdlib/__init__.py
|
|
5
|
+
kisstdlib/exceptions.py
|
|
6
|
+
kisstdlib/logging.py
|
|
7
|
+
kisstdlib/path.py
|
|
8
|
+
kisstdlib/py.typed
|
|
9
|
+
kisstdlib.egg-info/PKG-INFO
|
|
10
|
+
kisstdlib.egg-info/SOURCES.txt
|
|
11
|
+
kisstdlib.egg-info/dependency_links.txt
|
|
12
|
+
kisstdlib.egg-info/top_level.txt
|
|
13
|
+
kisstdlib/argparse/__init__.py
|
|
14
|
+
kisstdlib/argparse/better.py
|
|
15
|
+
kisstdlib/io/__init__.py
|
|
16
|
+
kisstdlib/io/base.py
|
|
17
|
+
kisstdlib/io/wrapper.py
|
|
18
|
+
kisstdlib/io/encoder/__init__.py
|
|
19
|
+
kisstdlib/io/encoder/base.py
|
|
20
|
+
kisstdlib/io/encoder/pyrepr.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
kisstdlib
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
[project]
|
|
5
|
+
name = "kisstdlib"
|
|
6
|
+
version = "0.0.1"
|
|
7
|
+
authors = [{ name = "Jan Malakhovski", email = "oxij@oxij.org" }]
|
|
8
|
+
description = "Keep It Simple STandarD LIBraries set for Python"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Development Status :: 4 - Beta",
|
|
12
|
+
"Programming Language :: Python :: 3",
|
|
13
|
+
"License :: OSI Approved :: MIT License",
|
|
14
|
+
"License :: OSI Approved :: Python Software Foundation License",
|
|
15
|
+
]
|
|
16
|
+
keywords = [
|
|
17
|
+
"sys",
|
|
18
|
+
"stdlib",
|
|
19
|
+
]
|
|
20
|
+
requires-python = ">=3.10"
|
|
21
|
+
[tool.setuptools.packages.find]
|
|
22
|
+
where = ["."]
|
|
23
|
+
include = ["kisstdlib*"]
|
|
24
|
+
namespaces = true
|
|
25
|
+
[tool.setuptools.package-data]
|
|
26
|
+
"kisstdlib" = ["py.typed"]
|
kisstdlib-0.0.1/setup.py
ADDED