omlish 0.0.0.dev4__py3-none-any.whl → 0.0.0.dev5__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.
Potentially problematic release.
This version of omlish might be problematic. Click here for more details.
- omlish/__about__.py +1 -1
- omlish/__init__.py +1 -1
- omlish/asyncs/__init__.py +1 -4
- omlish/asyncs/anyio.py +66 -0
- omlish/asyncs/flavors.py +27 -1
- omlish/asyncs/trio_asyncio.py +24 -18
- omlish/c3.py +1 -1
- omlish/cached.py +1 -2
- omlish/collections/__init__.py +4 -1
- omlish/collections/cache/impl.py +1 -1
- omlish/collections/indexed.py +1 -1
- omlish/collections/utils.py +38 -6
- omlish/configs/__init__.py +5 -0
- omlish/configs/classes.py +53 -0
- omlish/configs/dotenv.py +586 -0
- omlish/configs/props.py +589 -49
- omlish/dataclasses/impl/api.py +1 -1
- omlish/dataclasses/impl/as_.py +1 -1
- omlish/dataclasses/impl/fields.py +1 -0
- omlish/dataclasses/impl/init.py +1 -1
- omlish/dataclasses/impl/main.py +1 -0
- omlish/dataclasses/impl/metaclass.py +6 -1
- omlish/dataclasses/impl/order.py +1 -1
- omlish/dataclasses/impl/reflect.py +15 -2
- omlish/defs.py +1 -1
- omlish/diag/procfs.py +29 -1
- omlish/diag/procstats.py +32 -0
- omlish/diag/replserver/console.py +3 -3
- omlish/diag/replserver/server.py +6 -5
- omlish/diag/threads.py +86 -0
- omlish/docker.py +19 -0
- omlish/fnpairs.py +26 -18
- omlish/graphs/dags.py +113 -0
- omlish/graphs/domination.py +268 -0
- omlish/graphs/trees.py +2 -2
- omlish/http/__init__.py +25 -0
- omlish/http/asgi.py +131 -0
- omlish/http/consts.py +31 -4
- omlish/http/cookies.py +194 -0
- omlish/http/dates.py +70 -0
- omlish/http/encodings.py +6 -0
- omlish/http/json.py +273 -0
- omlish/http/sessions.py +197 -0
- omlish/inject/__init__.py +8 -2
- omlish/inject/bindings.py +3 -3
- omlish/inject/exceptions.py +3 -3
- omlish/inject/impl/elements.py +33 -24
- omlish/inject/impl/injector.py +1 -0
- omlish/inject/impl/multis.py +74 -0
- omlish/inject/impl/providers.py +19 -39
- omlish/inject/{proxy.py → impl/proxy.py} +2 -2
- omlish/inject/impl/scopes.py +1 -0
- omlish/inject/injector.py +1 -0
- omlish/inject/keys.py +3 -9
- omlish/inject/multis.py +70 -0
- omlish/inject/providers.py +23 -23
- omlish/inject/scopes.py +7 -3
- omlish/inject/types.py +0 -8
- omlish/iterators.py +13 -0
- omlish/json.py +2 -1
- omlish/lang/__init__.py +4 -0
- omlish/lang/classes/restrict.py +1 -1
- omlish/lang/classes/virtual.py +2 -2
- omlish/lang/contextmanagers.py +64 -0
- omlish/lang/datetimes.py +6 -5
- omlish/lang/functions.py +10 -0
- omlish/lang/imports.py +11 -2
- omlish/lang/typing.py +1 -0
- omlish/logs/utils.py +1 -1
- omlish/marshal/datetimes.py +1 -1
- omlish/reflect.py +8 -2
- omlish/sync.py +70 -0
- omlish/term.py +6 -1
- omlish/testing/pytest/__init__.py +5 -0
- omlish/testing/pytest/helpers.py +0 -24
- omlish/testing/pytest/inject/harness.py +1 -1
- omlish/testing/pytest/marks.py +48 -0
- omlish/testing/pytest/plugins/__init__.py +2 -0
- omlish/testing/pytest/plugins/managermarks.py +60 -0
- omlish/testing/testing.py +10 -0
- omlish/text/delimit.py +4 -0
- {omlish-0.0.0.dev4.dist-info → omlish-0.0.0.dev5.dist-info}/METADATA +1 -1
- {omlish-0.0.0.dev4.dist-info → omlish-0.0.0.dev5.dist-info}/RECORD +86 -69
- {omlish-0.0.0.dev4.dist-info → omlish-0.0.0.dev5.dist-info}/WHEEL +1 -1
- {omlish-0.0.0.dev4.dist-info → omlish-0.0.0.dev5.dist-info}/LICENSE +0 -0
- {omlish-0.0.0.dev4.dist-info → omlish-0.0.0.dev5.dist-info}/top_level.txt +0 -0
omlish/configs/props.py
CHANGED
|
@@ -1,64 +1,604 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
# jProperties - Java Property file parser and writer for Python
|
|
2
|
+
#
|
|
3
|
+
# Copyright (c) 2015, Tilman Blumenbach
|
|
4
|
+
# All rights reserved.
|
|
5
|
+
#
|
|
6
|
+
# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
|
|
7
|
+
# following conditions are met:
|
|
8
|
+
#
|
|
9
|
+
# * Redistributions of source code must retain the above copyright notice, this list of conditions and the following
|
|
10
|
+
# disclaimer.
|
|
11
|
+
#
|
|
12
|
+
# * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
|
|
13
|
+
# disclaimer in the documentation and/or other materials provided with the distribution.
|
|
14
|
+
#
|
|
15
|
+
# * Neither the name of jProperties nor the names of its contributors may be used to endorse or promote products derived
|
|
16
|
+
# from this software without specific prior written permission.
|
|
17
|
+
#
|
|
18
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
|
19
|
+
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
20
|
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
21
|
+
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
22
|
+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
|
23
|
+
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
24
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
25
|
+
import codecs
|
|
26
|
+
import collections.abc
|
|
27
|
+
import contextlib
|
|
28
|
+
import functools
|
|
29
|
+
import io
|
|
30
|
+
import itertools
|
|
5
31
|
import re
|
|
32
|
+
import struct
|
|
33
|
+
import sys
|
|
34
|
+
import time
|
|
6
35
|
import typing as ta
|
|
7
36
|
|
|
8
37
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
38
|
+
class PropertyTuple(ta.NamedTuple):
|
|
39
|
+
data: ta.Any
|
|
40
|
+
meta: ta.Any
|
|
12
41
|
|
|
13
42
|
|
|
14
|
-
def
|
|
15
|
-
return
|
|
43
|
+
def _is_runtime_meta(key: str | bytes) -> bool:
|
|
44
|
+
return (
|
|
45
|
+
(isinstance(key, str) and key.startswith('__')) or
|
|
46
|
+
(isinstance(key, bytes) and key.startswith(b'__'))
|
|
47
|
+
)
|
|
16
48
|
|
|
17
49
|
|
|
18
|
-
def
|
|
19
|
-
|
|
50
|
+
def _escape_non_ascii(unicode_obj: str | bytes) -> str:
|
|
51
|
+
def replace(match):
|
|
52
|
+
s = match.group(0)
|
|
53
|
+
n = ord(s)
|
|
54
|
+
if n < 0x10000:
|
|
55
|
+
return f'\\u{n:04x}'
|
|
56
|
+
else:
|
|
57
|
+
n -= 0x10000
|
|
58
|
+
s1 = 0xd800 | ((n >> 10) & 0x3ff)
|
|
59
|
+
s2 = 0xdc00 | (n & 0x3ff)
|
|
60
|
+
return f'\\u{s1:04x}\\u{s2:04x}'
|
|
20
61
|
|
|
62
|
+
if isinstance(unicode_obj, bytes):
|
|
63
|
+
unicode_obj = unicode_obj.decode('utf-8')
|
|
21
64
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
return
|
|
65
|
+
return re.sub(r'[^ -~]', replace, unicode_obj)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
_jbackslash_replace_codec_name = __name__ + '.jbackslashreplace'
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@functools.partial(codecs.register_error, _jbackslash_replace_codec_name)
|
|
72
|
+
def _jbackslashreplace_error_handler(err):
|
|
73
|
+
if not isinstance(err, UnicodeEncodeError):
|
|
74
|
+
raise err
|
|
75
|
+
|
|
76
|
+
return _escape_non_ascii(err.object[err.start:err.end]), err.end
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _escape_str(
|
|
80
|
+
raw_str: ta.Any,
|
|
81
|
+
*,
|
|
82
|
+
only_leading_spaces: bool = False,
|
|
83
|
+
escape_non_printing: bool = False,
|
|
84
|
+
line_breaks_only: bool = False,
|
|
85
|
+
) -> str:
|
|
86
|
+
if isinstance(raw_str, bytes):
|
|
87
|
+
raw_str = raw_str.decode('utf-8')
|
|
88
|
+
elif not isinstance(raw_str, str):
|
|
89
|
+
raw_str = str(raw_str)
|
|
90
|
+
|
|
91
|
+
trans_dict = {
|
|
92
|
+
ord('\r'): '\\r',
|
|
93
|
+
ord('\n'): '\\n',
|
|
94
|
+
ord('\f'): '\\f',
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if not line_breaks_only:
|
|
98
|
+
trans_dict.update(
|
|
99
|
+
{
|
|
100
|
+
ord('#'): '\\#',
|
|
101
|
+
ord('!'): '\\!',
|
|
102
|
+
ord('='): '\\=',
|
|
103
|
+
ord(':'): '\\:',
|
|
104
|
+
ord('\\'): '\\\\',
|
|
105
|
+
ord('\t'): '\\t',
|
|
106
|
+
},
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
escaped_str = raw_str.translate(trans_dict)
|
|
110
|
+
|
|
111
|
+
if not only_leading_spaces:
|
|
112
|
+
escaped_str = escaped_str.replace(' ', '\\ ')
|
|
113
|
+
else:
|
|
114
|
+
escaped_str = re.sub('^ ', '\\\\ ', escaped_str)
|
|
115
|
+
|
|
116
|
+
if escape_non_printing:
|
|
117
|
+
escaped_str = _escape_non_ascii(escaped_str)
|
|
118
|
+
|
|
119
|
+
return escaped_str
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class PropertyError(Exception):
|
|
123
|
+
"""Base exception class for all exceptions raised by this module."""
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class ParseError(PropertyError):
|
|
127
|
+
def __init__(self, message: str, line_number: int, file_obj: ta.Any = None) -> None:
|
|
128
|
+
super().__init__()
|
|
129
|
+
self.message = message
|
|
130
|
+
self.line_number = line_number
|
|
131
|
+
self.file_obj = file_obj
|
|
132
|
+
|
|
133
|
+
def __str__(self) -> str:
|
|
134
|
+
filename = '<unknown>' if not hasattr(self.file_obj, 'filename') else self.file_obj.filename
|
|
135
|
+
return f'Parse error in {filename}:{self.line_number}: {self.message}'
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class Properties(collections.abc.MutableMapping):
|
|
139
|
+
"""
|
|
140
|
+
A parser for Java property files.
|
|
141
|
+
|
|
142
|
+
This class implements parsing Java property files as defined here:
|
|
143
|
+
http://docs.oracle.com/javase/7/docs/api/java/util/Properties.html#load(java.io.Reader)
|
|
144
|
+
"""
|
|
145
|
+
|
|
146
|
+
_EOL = '\r\n'
|
|
147
|
+
_WHITESPACE = ' \t\f'
|
|
148
|
+
_ALLWHITESPACE = _EOL + _WHITESPACE
|
|
149
|
+
|
|
150
|
+
def __init__(
|
|
151
|
+
self,
|
|
152
|
+
*,
|
|
153
|
+
process_escapes_in_values: bool = True,
|
|
154
|
+
) -> None:
|
|
155
|
+
super().__init__()
|
|
156
|
+
|
|
157
|
+
self._process_escapes_in_values = process_escapes_in_values
|
|
158
|
+
|
|
159
|
+
self.reset()
|
|
160
|
+
self.clear()
|
|
161
|
+
|
|
162
|
+
_source_file: ta.IO[str] | None
|
|
163
|
+
_next_metadata: dict[str, str]
|
|
164
|
+
_lookahead: str | None = None
|
|
165
|
+
_prev_key: str | None
|
|
166
|
+
_metadata: dict[str, ta.Any]
|
|
167
|
+
_key_order: list[str]
|
|
168
|
+
_properties: dict[str, ta.Any]
|
|
169
|
+
_line_number: int
|
|
170
|
+
_metadoc: bool
|
|
171
|
+
|
|
172
|
+
def __len__(self) -> int:
|
|
173
|
+
return len(self._properties)
|
|
34
174
|
|
|
175
|
+
def __getitem__(self, item: str) -> PropertyTuple:
|
|
176
|
+
if not isinstance(item, str):
|
|
177
|
+
raise TypeError('Property keys must be of type str')
|
|
35
178
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
179
|
+
if item not in self._properties:
|
|
180
|
+
raise KeyError('Key not found')
|
|
181
|
+
|
|
182
|
+
return PropertyTuple(
|
|
183
|
+
self._properties[item],
|
|
184
|
+
self._metadata.get(item, {}),
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
def __setitem__(self, key: str, value) -> None:
|
|
188
|
+
if not isinstance(key, str):
|
|
189
|
+
raise TypeError('Property keys must be of type str')
|
|
190
|
+
|
|
191
|
+
metadata = None
|
|
192
|
+
if isinstance(value, tuple):
|
|
193
|
+
value, metadata = value
|
|
194
|
+
|
|
195
|
+
if not isinstance(value, str):
|
|
196
|
+
raise TypeError('Property values must be of type str')
|
|
197
|
+
|
|
198
|
+
if metadata is not None and not isinstance(metadata, dict):
|
|
199
|
+
raise TypeError('Metadata needs to be a dictionary')
|
|
200
|
+
|
|
201
|
+
self._properties[key] = value
|
|
202
|
+
if metadata is not None:
|
|
203
|
+
self._metadata[key] = metadata
|
|
204
|
+
|
|
205
|
+
def __delitem__(self, key: str) -> None:
|
|
206
|
+
if not isinstance(key, str):
|
|
207
|
+
raise TypeError('Property keys must be of type str')
|
|
208
|
+
|
|
209
|
+
if key not in self._properties:
|
|
210
|
+
raise KeyError('Key not found')
|
|
211
|
+
|
|
212
|
+
# Remove the property itself.
|
|
213
|
+
del self._properties[key]
|
|
214
|
+
|
|
215
|
+
# Remove its metadata as well.
|
|
216
|
+
if key in self._metadata:
|
|
217
|
+
del self._metadata[key]
|
|
218
|
+
|
|
219
|
+
# We also no longer need to remember its key order since the property does not exist anymore.
|
|
220
|
+
with contextlib.suppress(ValueError):
|
|
221
|
+
self._key_order.remove(key)
|
|
222
|
+
|
|
223
|
+
def __iter__(self) -> ta.Iterator:
|
|
224
|
+
return self._properties.__iter__()
|
|
225
|
+
|
|
226
|
+
@property
|
|
227
|
+
def properties(self):
|
|
228
|
+
return self._properties
|
|
229
|
+
|
|
230
|
+
@properties.setter
|
|
231
|
+
def properties(self, value):
|
|
232
|
+
self._properties = value
|
|
233
|
+
|
|
234
|
+
@properties.deleter
|
|
235
|
+
def properties(self):
|
|
236
|
+
self._properties = {}
|
|
237
|
+
|
|
238
|
+
def getmeta(self, key: str) -> dict:
|
|
239
|
+
return self._metadata.get(key, {})
|
|
240
|
+
|
|
241
|
+
def setmeta(self, key: str, metadata: dict):
|
|
242
|
+
if not isinstance(metadata, dict):
|
|
243
|
+
raise TypeError('Metadata needs to be a dictionary')
|
|
244
|
+
|
|
245
|
+
self._metadata[key] = metadata
|
|
246
|
+
|
|
247
|
+
def _peek(self) -> str:
|
|
248
|
+
if self._lookahead is None:
|
|
249
|
+
c = self._source_file.read(1) # type: ignore
|
|
250
|
+
if c == '':
|
|
251
|
+
raise EOFError
|
|
252
|
+
self._lookahead = c
|
|
253
|
+
return self._lookahead
|
|
254
|
+
|
|
255
|
+
def _getc(self) -> str:
|
|
256
|
+
c = self._peek()
|
|
257
|
+
self._lookahead = None
|
|
258
|
+
return c
|
|
259
|
+
|
|
260
|
+
def _handle_eol(self) -> None:
|
|
261
|
+
c = self._peek()
|
|
262
|
+
|
|
263
|
+
if c == '\r':
|
|
264
|
+
self._line_number += 1
|
|
265
|
+
self._getc()
|
|
266
|
+
try:
|
|
267
|
+
if self._peek() == '\n':
|
|
268
|
+
# DOS line ending. Skip it.
|
|
269
|
+
self._getc()
|
|
270
|
+
except EOFError:
|
|
271
|
+
pass
|
|
272
|
+
|
|
273
|
+
elif c == '\n':
|
|
274
|
+
self._line_number += 1
|
|
275
|
+
self._getc()
|
|
276
|
+
|
|
277
|
+
def _skip_whitespace(self, stop_at_eol: bool = False) -> None:
|
|
40
278
|
while True:
|
|
41
|
-
|
|
42
|
-
if
|
|
43
|
-
|
|
279
|
+
c = self._peek()
|
|
280
|
+
if c not in self._ALLWHITESPACE:
|
|
281
|
+
return
|
|
282
|
+
|
|
283
|
+
if c in self._EOL:
|
|
284
|
+
if stop_at_eol:
|
|
285
|
+
return
|
|
286
|
+
self._handle_eol()
|
|
287
|
+
|
|
44
288
|
else:
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
289
|
+
self._getc()
|
|
290
|
+
|
|
291
|
+
def _skip_natural_line(self) -> str:
|
|
292
|
+
line = ''
|
|
293
|
+
try:
|
|
294
|
+
while self._peek() not in self._EOL:
|
|
295
|
+
line += self._getc()
|
|
296
|
+
self._handle_eol()
|
|
297
|
+
except EOFError:
|
|
298
|
+
pass
|
|
299
|
+
return line
|
|
300
|
+
|
|
301
|
+
def _parse_comment(self) -> None:
|
|
302
|
+
self._getc()
|
|
303
|
+
|
|
304
|
+
if self._peek() != ':':
|
|
305
|
+
docstr = self._skip_natural_line()
|
|
306
|
+
if self._metadoc and self._prev_key:
|
|
307
|
+
prev_metadata = self._metadata.setdefault(self._prev_key, {})
|
|
308
|
+
prev_metadata.setdefault('_doc', '')
|
|
309
|
+
if docstr.startswith(' '):
|
|
310
|
+
docstr = docstr[1:]
|
|
311
|
+
prev_metadata['_doc'] += docstr + '\n'
|
|
312
|
+
return
|
|
313
|
+
|
|
314
|
+
self._getc()
|
|
315
|
+
|
|
316
|
+
key = self._parse_key(True)
|
|
317
|
+
value = self._parse_value(True)
|
|
318
|
+
|
|
319
|
+
if not key:
|
|
320
|
+
raise ParseError('Empty key in metadata key-value pair', self._line_number, self._source_file)
|
|
321
|
+
|
|
322
|
+
self._next_metadata[key] = value
|
|
323
|
+
|
|
324
|
+
# \r, \n, \t or \f.
|
|
325
|
+
_ESCAPED_CHARS: ta.ClassVar[ta.Mapping[str, str]] = {
|
|
326
|
+
ec: eval(r"u'\%s'" % (ec,)) # noqa
|
|
327
|
+
for ec in 'rntf'
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
def _handle_escape(self, allow_line_continuation: bool = True) -> str:
|
|
331
|
+
if self._peek() == '\\':
|
|
332
|
+
self._getc()
|
|
333
|
+
|
|
334
|
+
try:
|
|
335
|
+
escaped_char = self._peek()
|
|
336
|
+
except EOFError:
|
|
337
|
+
return ''
|
|
338
|
+
|
|
339
|
+
if escaped_char in self._EOL:
|
|
340
|
+
if allow_line_continuation:
|
|
49
341
|
try:
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
342
|
+
self._handle_eol()
|
|
343
|
+
self._skip_whitespace(True)
|
|
344
|
+
except EOFError:
|
|
345
|
+
pass
|
|
346
|
+
|
|
347
|
+
return ''
|
|
348
|
+
|
|
349
|
+
self._getc()
|
|
350
|
+
|
|
351
|
+
try:
|
|
352
|
+
return self._ESCAPED_CHARS[escaped_char]
|
|
353
|
+
except KeyError:
|
|
354
|
+
pass
|
|
355
|
+
|
|
356
|
+
if escaped_char == 'u':
|
|
357
|
+
start_linenumber = self._line_number
|
|
358
|
+
|
|
359
|
+
try:
|
|
360
|
+
codepoint_hex = ''
|
|
361
|
+
for _ in range(4):
|
|
362
|
+
codepoint_hex += self._getc()
|
|
363
|
+
|
|
364
|
+
codepoint = int(codepoint_hex, base=16)
|
|
365
|
+
|
|
366
|
+
# See: http://unicodebook.readthedocs.io/unicode_encodings.html#utf-16-surrogate-pairs
|
|
367
|
+
if 0xD800 <= codepoint <= 0xDBFF:
|
|
368
|
+
codepoint2_hex = ''
|
|
369
|
+
try:
|
|
370
|
+
for _ in range(6):
|
|
371
|
+
codepoint2_hex += self._getc()
|
|
372
|
+
except EOFError:
|
|
373
|
+
pass
|
|
374
|
+
|
|
375
|
+
if codepoint2_hex[:2] != r'\u' or len(codepoint2_hex) != 6:
|
|
376
|
+
raise ParseError(
|
|
377
|
+
'High surrogate unicode escape sequence not followed by another '
|
|
378
|
+
'(low surrogate) unicode escape sequence.',
|
|
379
|
+
start_linenumber,
|
|
380
|
+
self._source_file,
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
codepoint2 = int(codepoint2_hex[2:], base=16)
|
|
384
|
+
if not (0xDC00 <= codepoint2 <= 0xDFFF):
|
|
385
|
+
raise ParseError(
|
|
386
|
+
'Low surrogate unicode escape sequence expected after high surrogate '
|
|
387
|
+
'escape sequence, but got a non-low-surrogate unicode escape sequence.',
|
|
388
|
+
start_linenumber,
|
|
389
|
+
self._source_file,
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
final_codepoint = 0x10000
|
|
393
|
+
final_codepoint += (codepoint & 0x03FF) << 10
|
|
394
|
+
final_codepoint += codepoint2 & 0x03FF
|
|
395
|
+
|
|
396
|
+
codepoint = final_codepoint
|
|
397
|
+
|
|
398
|
+
return struct.pack('=I', codepoint).decode('utf-32')
|
|
399
|
+
except (EOFError, ValueError) as e:
|
|
400
|
+
raise ParseError('Parse error', start_linenumber, self._source_file) from e
|
|
401
|
+
|
|
402
|
+
return escaped_char
|
|
403
|
+
|
|
404
|
+
def _parse_key(self, single_line_only: bool = False) -> str:
|
|
405
|
+
self._skip_whitespace(single_line_only)
|
|
406
|
+
|
|
407
|
+
key = ''
|
|
408
|
+
while True:
|
|
409
|
+
try:
|
|
410
|
+
c = self._peek()
|
|
411
|
+
except EOFError:
|
|
412
|
+
break
|
|
413
|
+
|
|
414
|
+
if c == '\\':
|
|
415
|
+
key += self._handle_escape(not single_line_only)
|
|
416
|
+
continue
|
|
417
|
+
|
|
418
|
+
if c in self._ALLWHITESPACE or c in ':=':
|
|
419
|
+
break
|
|
420
|
+
|
|
421
|
+
key += self._getc()
|
|
422
|
+
|
|
423
|
+
return key
|
|
424
|
+
|
|
425
|
+
def _parse_value(self, single_line_only: bool = False) -> str:
|
|
426
|
+
try:
|
|
427
|
+
self._skip_whitespace(True)
|
|
428
|
+
if self._peek() in ':=':
|
|
429
|
+
self._getc()
|
|
430
|
+
self._skip_whitespace(True)
|
|
431
|
+
except EOFError:
|
|
432
|
+
return ''
|
|
433
|
+
|
|
434
|
+
value = ''
|
|
435
|
+
while True:
|
|
436
|
+
try:
|
|
437
|
+
c = self._peek()
|
|
438
|
+
except EOFError:
|
|
439
|
+
break
|
|
440
|
+
|
|
441
|
+
if c == '\\' and self._process_escapes_in_values:
|
|
442
|
+
value += self._handle_escape(not single_line_only)
|
|
443
|
+
continue
|
|
444
|
+
|
|
445
|
+
if c in self._EOL:
|
|
446
|
+
self._handle_eol()
|
|
447
|
+
break
|
|
448
|
+
|
|
449
|
+
value += self._getc()
|
|
450
|
+
|
|
451
|
+
return value
|
|
452
|
+
|
|
453
|
+
def _parse_logical_line(self) -> bool:
|
|
454
|
+
try:
|
|
455
|
+
self._skip_whitespace()
|
|
456
|
+
c = self._peek()
|
|
457
|
+
except EOFError:
|
|
458
|
+
return False
|
|
459
|
+
|
|
460
|
+
if c in '!#':
|
|
461
|
+
try:
|
|
462
|
+
self._parse_comment()
|
|
463
|
+
except EOFError:
|
|
464
|
+
# Nothing more to parse.
|
|
465
|
+
return False
|
|
466
|
+
|
|
467
|
+
return True
|
|
468
|
+
|
|
469
|
+
try:
|
|
470
|
+
key = self._parse_key()
|
|
471
|
+
value = self._parse_value()
|
|
472
|
+
except EOFError:
|
|
473
|
+
return False
|
|
474
|
+
|
|
475
|
+
if key not in self._properties:
|
|
476
|
+
self._key_order.append(key)
|
|
477
|
+
|
|
478
|
+
self._properties[key] = value
|
|
479
|
+
|
|
480
|
+
if self._next_metadata:
|
|
481
|
+
self._metadata[key] = self._next_metadata
|
|
482
|
+
self._next_metadata = {}
|
|
483
|
+
self._prev_key = key
|
|
484
|
+
|
|
485
|
+
return True
|
|
486
|
+
|
|
487
|
+
def _parse(self) -> None:
|
|
488
|
+
while self._parse_logical_line():
|
|
489
|
+
pass
|
|
490
|
+
|
|
491
|
+
def reset(self, metadoc: bool = False) -> None:
|
|
492
|
+
self._source_file = None
|
|
493
|
+
self._line_number = 1
|
|
494
|
+
self._lookahead = None
|
|
495
|
+
|
|
496
|
+
self._next_metadata = {}
|
|
497
|
+
|
|
498
|
+
self._prev_key = None
|
|
499
|
+
self._metadoc = metadoc
|
|
500
|
+
|
|
501
|
+
def clear(self) -> None:
|
|
502
|
+
self._properties = {}
|
|
503
|
+
self._metadata = {}
|
|
504
|
+
self._key_order = []
|
|
505
|
+
|
|
506
|
+
def load(
|
|
507
|
+
self,
|
|
508
|
+
source_data,
|
|
509
|
+
encoding: str | None = 'iso-8859-1',
|
|
510
|
+
metadoc: bool = False,
|
|
511
|
+
) -> None:
|
|
512
|
+
self.reset(metadoc)
|
|
513
|
+
|
|
514
|
+
if isinstance(source_data, bytes):
|
|
515
|
+
self._source_file = io.StringIO(source_data.decode(encoding or 'iso-8859-1'))
|
|
516
|
+
elif isinstance(source_data, str):
|
|
517
|
+
self._source_file = io.StringIO(source_data)
|
|
518
|
+
elif encoding is not None:
|
|
519
|
+
self._source_file = codecs.getreader(encoding)(source_data) # type: ignore
|
|
520
|
+
else:
|
|
521
|
+
self._source_file = source_data
|
|
522
|
+
|
|
523
|
+
self._parse()
|
|
524
|
+
|
|
525
|
+
def store(
|
|
526
|
+
self,
|
|
527
|
+
out_stream,
|
|
528
|
+
initial_comments: str | None = None,
|
|
529
|
+
encoding: str = 'iso-8859-1',
|
|
530
|
+
strict: bool = True,
|
|
531
|
+
strip_meta: bool = True,
|
|
532
|
+
timestamp: bool = True,
|
|
533
|
+
) -> None:
|
|
534
|
+
out_codec_info = codecs.lookup(encoding)
|
|
535
|
+
wrapped_out_stream = out_codec_info.streamwriter(out_stream, _jbackslash_replace_codec_name)
|
|
536
|
+
properties_escape_nonprinting = strict and out_codec_info == codecs.lookup('latin_1')
|
|
537
|
+
|
|
538
|
+
if initial_comments is not None:
|
|
539
|
+
initial_comments = re.sub(r'(\r\n|\r)', '\n', initial_comments)
|
|
540
|
+
initial_comments = re.sub(r'\n(?![#!])', '\n#', initial_comments)
|
|
541
|
+
initial_comments = re.sub(r'(\n[#!]):', r'\g<1>\:', initial_comments)
|
|
542
|
+
print('#' + initial_comments, file=wrapped_out_stream)
|
|
543
|
+
|
|
544
|
+
if timestamp:
|
|
545
|
+
day_of_week = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
|
|
546
|
+
month = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
|
|
547
|
+
now = time.gmtime()
|
|
548
|
+
print(
|
|
549
|
+
'#%s %s %02d %02d:%02d:%02d UTC %04d' % (
|
|
550
|
+
day_of_week[now.tm_wday],
|
|
551
|
+
month[now.tm_mon - 1],
|
|
552
|
+
now.tm_mday,
|
|
553
|
+
now.tm_hour,
|
|
554
|
+
now.tm_min,
|
|
555
|
+
now.tm_sec,
|
|
556
|
+
now.tm_year,
|
|
557
|
+
),
|
|
558
|
+
file=wrapped_out_stream,
|
|
559
|
+
)
|
|
560
|
+
|
|
561
|
+
unordered_keys = set(self._properties)
|
|
562
|
+
ordered_keys = set(self._key_order)
|
|
563
|
+
unordered_keys -= ordered_keys
|
|
564
|
+
|
|
565
|
+
unordered_keys_xs = list(unordered_keys)
|
|
566
|
+
unordered_keys_xs.sort()
|
|
567
|
+
|
|
568
|
+
for key in itertools.chain(self._key_order, unordered_keys_xs):
|
|
569
|
+
if key in self._properties:
|
|
570
|
+
metadata = self.getmeta(key)
|
|
571
|
+
if not strip_meta and len(metadata):
|
|
572
|
+
for mkey in sorted(metadata):
|
|
573
|
+
if _is_runtime_meta(mkey):
|
|
574
|
+
continue
|
|
575
|
+
|
|
576
|
+
print(
|
|
577
|
+
'#: {}={}'.format( # noqa
|
|
578
|
+
_escape_str(mkey),
|
|
579
|
+
_escape_str(metadata[mkey], only_leading_spaces=True),
|
|
580
|
+
),
|
|
581
|
+
file=wrapped_out_stream,
|
|
582
|
+
)
|
|
583
|
+
|
|
584
|
+
print(
|
|
585
|
+
'='.join([
|
|
586
|
+
_escape_str(
|
|
587
|
+
key,
|
|
588
|
+
escape_non_printing=properties_escape_nonprinting,
|
|
589
|
+
),
|
|
590
|
+
_escape_str(
|
|
591
|
+
self._properties[key],
|
|
592
|
+
only_leading_spaces=True,
|
|
593
|
+
escape_non_printing=properties_escape_nonprinting,
|
|
594
|
+
line_breaks_only=not self._process_escapes_in_values,
|
|
595
|
+
),
|
|
596
|
+
]),
|
|
597
|
+
file=wrapped_out_stream,
|
|
598
|
+
)
|
|
599
|
+
|
|
600
|
+
def list(self, out_stream=sys.stderr) -> None:
|
|
601
|
+
print('-- listing properties --', file=out_stream)
|
|
602
|
+
for key in self._properties:
|
|
603
|
+
msg = f'{key}={self._properties[key]}'
|
|
604
|
+
print(msg, file=out_stream)
|
omlish/dataclasses/impl/api.py
CHANGED
|
@@ -34,7 +34,7 @@ def field( # noqa
|
|
|
34
34
|
check: ta.Callable[[ta.Any], bool] | None = None,
|
|
35
35
|
check_type: bool | None = None,
|
|
36
36
|
override: bool = False,
|
|
37
|
-
repr_fn: ta.Callable[[ta.Any], str | None] | None = None
|
|
37
|
+
repr_fn: ta.Callable[[ta.Any], str | None] | None = None,
|
|
38
38
|
): # -> dc.Field
|
|
39
39
|
if default is not MISSING and default_factory is not MISSING:
|
|
40
40
|
raise ValueError('cannot specify both default and default_factory')
|
omlish/dataclasses/impl/as_.py
CHANGED
omlish/dataclasses/impl/init.py
CHANGED
|
@@ -7,9 +7,9 @@ from .exceptions import CheckError
|
|
|
7
7
|
from .fields import field_init
|
|
8
8
|
from .fields import field_type
|
|
9
9
|
from .fields import has_default
|
|
10
|
-
from .internals import FieldType
|
|
11
10
|
from .internals import HAS_DEFAULT_FACTORY
|
|
12
11
|
from .internals import POST_INIT_NAME
|
|
12
|
+
from .internals import FieldType
|
|
13
13
|
from .metadata import Check
|
|
14
14
|
from .metadata import Init
|
|
15
15
|
from .processing import Processor
|