omdev 0.0.0.dev191__py3-none-any.whl → 0.0.0.dev193__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- omdev/pyproject/cli.py +1 -1
- omdev/pyproject/pkg.py +1 -1
- omdev/scripts/pyproject.py +257 -257
- {omdev-0.0.0.dev191.dist-info → omdev-0.0.0.dev193.dist-info}/METADATA +2 -2
- {omdev-0.0.0.dev191.dist-info → omdev-0.0.0.dev193.dist-info}/RECORD +9 -12
- omdev/toml/__init__.py +0 -1
- omdev/toml/parser.py +0 -826
- omdev/toml/writer.py +0 -114
- {omdev-0.0.0.dev191.dist-info → omdev-0.0.0.dev193.dist-info}/LICENSE +0 -0
- {omdev-0.0.0.dev191.dist-info → omdev-0.0.0.dev193.dist-info}/WHEEL +0 -0
- {omdev-0.0.0.dev191.dist-info → omdev-0.0.0.dev193.dist-info}/entry_points.txt +0 -0
- {omdev-0.0.0.dev191.dist-info → omdev-0.0.0.dev193.dist-info}/top_level.txt +0 -0
omdev/toml/parser.py
DELETED
@@ -1,826 +0,0 @@
|
|
1
|
-
# SPDX-License-Identifier: MIT
|
2
|
-
# SPDX-FileCopyrightText: 2021 Taneli Hukkinen
|
3
|
-
# Licensed to PSF under a Contributor Agreement.
|
4
|
-
#
|
5
|
-
# PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
|
6
|
-
# --------------------------------------------
|
7
|
-
#
|
8
|
-
# 1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), and the Individual or Organization
|
9
|
-
# ("Licensee") accessing and otherwise using this software ("Python") in source or binary form and its associated
|
10
|
-
# documentation.
|
11
|
-
#
|
12
|
-
# 2. Subject to the terms and conditions of this License Agreement, PSF hereby grants Licensee a nonexclusive,
|
13
|
-
# royalty-free, world-wide license to reproduce, analyze, test, perform and/or display publicly, prepare derivative
|
14
|
-
# works, distribute, and otherwise use Python alone or in any derivative version, provided, however, that PSF's License
|
15
|
-
# Agreement and PSF's notice of copyright, i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009,
|
16
|
-
# 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023 Python Software Foundation; All
|
17
|
-
# Rights Reserved" are retained in Python alone or in any derivative version prepared by Licensee.
|
18
|
-
#
|
19
|
-
# 3. In the event Licensee prepares a derivative work that is based on or incorporates Python or any part thereof, and
|
20
|
-
# wants to make the derivative work available to others as provided herein, then Licensee hereby agrees to include in
|
21
|
-
# any such work a brief summary of the changes made to Python.
|
22
|
-
#
|
23
|
-
# 4. PSF is making Python available to Licensee on an "AS IS" basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES,
|
24
|
-
# EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY
|
25
|
-
# OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT INFRINGE ANY THIRD PARTY
|
26
|
-
# RIGHTS.
|
27
|
-
#
|
28
|
-
# 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL
|
29
|
-
# DAMAGES OR LOSS AS A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, OR ANY DERIVATIVE THEREOF, EVEN IF
|
30
|
-
# ADVISED OF THE POSSIBILITY THEREOF.
|
31
|
-
#
|
32
|
-
# 6. This License Agreement will automatically terminate upon a material breach of its terms and conditions.
|
33
|
-
#
|
34
|
-
# 7. Nothing in this License Agreement shall be deemed to create any relationship of agency, partnership, or joint
|
35
|
-
# venture between PSF and Licensee. This License Agreement does not grant permission to use PSF trademarks or trade
|
36
|
-
# name in a trademark sense to endorse or promote products or services of Licensee, or any third party.
|
37
|
-
#
|
38
|
-
# 8. By copying, installing or otherwise using Python, Licensee agrees to be bound by the terms and conditions of this
|
39
|
-
# License Agreement.
|
40
|
-
#
|
41
|
-
# https://github.com/python/cpython/blob/9ce90206b7a4649600218cf0bd4826db79c9a312/Lib/tomllib/_parser.py
|
42
|
-
# ruff: noqa: UP006 UP007
|
43
|
-
import datetime
|
44
|
-
import functools
|
45
|
-
import re
|
46
|
-
import string
|
47
|
-
import types
|
48
|
-
import typing as ta
|
49
|
-
|
50
|
-
|
51
|
-
TomlParseFloat = ta.Callable[[str], ta.Any]
|
52
|
-
TomlKey = ta.Tuple[str, ...]
|
53
|
-
TomlPos = int # ta.TypeAlias
|
54
|
-
|
55
|
-
|
56
|
-
##
|
57
|
-
|
58
|
-
|
59
|
-
_TOML_TIME_RE_STR = r'([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])(?:\.([0-9]{1,6})[0-9]*)?'
|
60
|
-
|
61
|
-
TOML_RE_NUMBER = re.compile(
|
62
|
-
r"""
|
63
|
-
0
|
64
|
-
(?:
|
65
|
-
x[0-9A-Fa-f](?:_?[0-9A-Fa-f])* # hex
|
66
|
-
|
|
67
|
-
b[01](?:_?[01])* # bin
|
68
|
-
|
|
69
|
-
o[0-7](?:_?[0-7])* # oct
|
70
|
-
)
|
71
|
-
|
|
72
|
-
[+-]?(?:0|[1-9](?:_?[0-9])*) # dec, integer part
|
73
|
-
(?P<floatpart>
|
74
|
-
(?:\.[0-9](?:_?[0-9])*)? # optional fractional part
|
75
|
-
(?:[eE][+-]?[0-9](?:_?[0-9])*)? # optional exponent part
|
76
|
-
)
|
77
|
-
""",
|
78
|
-
flags=re.VERBOSE,
|
79
|
-
)
|
80
|
-
TOML_RE_LOCALTIME = re.compile(_TOML_TIME_RE_STR)
|
81
|
-
TOML_RE_DATETIME = re.compile(
|
82
|
-
rf"""
|
83
|
-
([0-9]{{4}})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01]) # date, e.g. 1988-10-27
|
84
|
-
(?:
|
85
|
-
[Tt ]
|
86
|
-
{_TOML_TIME_RE_STR}
|
87
|
-
(?:([Zz])|([+-])([01][0-9]|2[0-3]):([0-5][0-9]))? # optional time offset
|
88
|
-
)?
|
89
|
-
""",
|
90
|
-
flags=re.VERBOSE,
|
91
|
-
)
|
92
|
-
|
93
|
-
|
94
|
-
def toml_match_to_datetime(match: re.Match) -> ta.Union[datetime.datetime, datetime.date]:
|
95
|
-
"""Convert a `RE_DATETIME` match to `datetime.datetime` or `datetime.date`.
|
96
|
-
|
97
|
-
Raises ValueError if the match does not correspond to a valid date or datetime.
|
98
|
-
"""
|
99
|
-
(
|
100
|
-
year_str,
|
101
|
-
month_str,
|
102
|
-
day_str,
|
103
|
-
hour_str,
|
104
|
-
minute_str,
|
105
|
-
sec_str,
|
106
|
-
micros_str,
|
107
|
-
zulu_time,
|
108
|
-
offset_sign_str,
|
109
|
-
offset_hour_str,
|
110
|
-
offset_minute_str,
|
111
|
-
) = match.groups()
|
112
|
-
year, month, day = int(year_str), int(month_str), int(day_str)
|
113
|
-
if hour_str is None:
|
114
|
-
return datetime.date(year, month, day)
|
115
|
-
hour, minute, sec = int(hour_str), int(minute_str), int(sec_str)
|
116
|
-
micros = int(micros_str.ljust(6, '0')) if micros_str else 0
|
117
|
-
if offset_sign_str:
|
118
|
-
tz: ta.Optional[datetime.tzinfo] = toml_cached_tz(
|
119
|
-
offset_hour_str, offset_minute_str, offset_sign_str,
|
120
|
-
)
|
121
|
-
elif zulu_time:
|
122
|
-
tz = datetime.UTC
|
123
|
-
else: # local date-time
|
124
|
-
tz = None
|
125
|
-
return datetime.datetime(year, month, day, hour, minute, sec, micros, tzinfo=tz)
|
126
|
-
|
127
|
-
|
128
|
-
@functools.lru_cache() # noqa
|
129
|
-
def toml_cached_tz(hour_str: str, minute_str: str, sign_str: str) -> datetime.timezone:
|
130
|
-
sign = 1 if sign_str == '+' else -1
|
131
|
-
return datetime.timezone(
|
132
|
-
datetime.timedelta(
|
133
|
-
hours=sign * int(hour_str),
|
134
|
-
minutes=sign * int(minute_str),
|
135
|
-
),
|
136
|
-
)
|
137
|
-
|
138
|
-
|
139
|
-
def toml_match_to_localtime(match: re.Match) -> datetime.time:
|
140
|
-
hour_str, minute_str, sec_str, micros_str = match.groups()
|
141
|
-
micros = int(micros_str.ljust(6, '0')) if micros_str else 0
|
142
|
-
return datetime.time(int(hour_str), int(minute_str), int(sec_str), micros)
|
143
|
-
|
144
|
-
|
145
|
-
def toml_match_to_number(match: re.Match, parse_float: TomlParseFloat) -> ta.Any:
|
146
|
-
if match.group('floatpart'):
|
147
|
-
return parse_float(match.group())
|
148
|
-
return int(match.group(), 0)
|
149
|
-
|
150
|
-
|
151
|
-
TOML_ASCII_CTRL = frozenset(chr(i) for i in range(32)) | frozenset(chr(127))
|
152
|
-
|
153
|
-
# Neither of these sets include quotation mark or backslash. They are currently handled as separate cases in the parser
|
154
|
-
# functions.
|
155
|
-
TOML_ILLEGAL_BASIC_STR_CHARS = TOML_ASCII_CTRL - frozenset('\t')
|
156
|
-
TOML_ILLEGAL_MULTILINE_BASIC_STR_CHARS = TOML_ASCII_CTRL - frozenset('\t\n')
|
157
|
-
|
158
|
-
TOML_ILLEGAL_LITERAL_STR_CHARS = TOML_ILLEGAL_BASIC_STR_CHARS
|
159
|
-
TOML_ILLEGAL_MULTILINE_LITERAL_STR_CHARS = TOML_ILLEGAL_MULTILINE_BASIC_STR_CHARS
|
160
|
-
|
161
|
-
TOML_ILLEGAL_COMMENT_CHARS = TOML_ILLEGAL_BASIC_STR_CHARS
|
162
|
-
|
163
|
-
TOML_WS = frozenset(' \t')
|
164
|
-
TOML_WS_AND_NEWLINE = TOML_WS | frozenset('\n')
|
165
|
-
TOML_BARE_KEY_CHARS = frozenset(string.ascii_letters + string.digits + '-_')
|
166
|
-
TOML_KEY_INITIAL_CHARS = TOML_BARE_KEY_CHARS | frozenset("\"'")
|
167
|
-
TOML_HEXDIGIT_CHARS = frozenset(string.hexdigits)
|
168
|
-
|
169
|
-
TOML_BASIC_STR_ESCAPE_REPLACEMENTS = types.MappingProxyType(
|
170
|
-
{
|
171
|
-
'\\b': '\u0008', # backspace
|
172
|
-
'\\t': '\u0009', # tab
|
173
|
-
'\\n': '\u000A', # linefeed
|
174
|
-
'\\f': '\u000C', # form feed
|
175
|
-
'\\r': '\u000D', # carriage return
|
176
|
-
'\\"': '\u0022', # quote
|
177
|
-
'\\\\': '\u005C', # backslash
|
178
|
-
},
|
179
|
-
)
|
180
|
-
|
181
|
-
|
182
|
-
class TomlDecodeError(ValueError):
|
183
|
-
"""An error raised if a document is not valid TOML."""
|
184
|
-
|
185
|
-
|
186
|
-
def toml_load(fp: ta.BinaryIO, /, *, parse_float: TomlParseFloat = float) -> ta.Dict[str, ta.Any]:
|
187
|
-
"""Parse TOML from a binary file object."""
|
188
|
-
b = fp.read()
|
189
|
-
try:
|
190
|
-
s = b.decode()
|
191
|
-
except AttributeError:
|
192
|
-
raise TypeError("File must be opened in binary mode, e.g. use `open('foo.toml', 'rb')`") from None
|
193
|
-
return toml_loads(s, parse_float=parse_float)
|
194
|
-
|
195
|
-
|
196
|
-
def toml_loads(s: str, /, *, parse_float: TomlParseFloat = float) -> ta.Dict[str, ta.Any]: # noqa: C901
|
197
|
-
"""Parse TOML from a string."""
|
198
|
-
|
199
|
-
# The spec allows converting "\r\n" to "\n", even in string literals. Let's do so to simplify parsing.
|
200
|
-
try:
|
201
|
-
src = s.replace('\r\n', '\n')
|
202
|
-
except (AttributeError, TypeError):
|
203
|
-
raise TypeError(f"Expected str object, not '{type(s).__qualname__}'") from None
|
204
|
-
pos = 0
|
205
|
-
out = TomlOutput(TomlNestedDict(), TomlFlags())
|
206
|
-
header: TomlKey = ()
|
207
|
-
parse_float = toml_make_safe_parse_float(parse_float)
|
208
|
-
|
209
|
-
# Parse one statement at a time (typically means one line in TOML source)
|
210
|
-
while True:
|
211
|
-
# 1. Skip line leading whitespace
|
212
|
-
pos = toml_skip_chars(src, pos, TOML_WS)
|
213
|
-
|
214
|
-
# 2. Parse rules. Expect one of the following:
|
215
|
-
# - end of file
|
216
|
-
# - end of line
|
217
|
-
# - comment
|
218
|
-
# - key/value pair
|
219
|
-
# - append dict to list (and move to its namespace)
|
220
|
-
# - create dict (and move to its namespace)
|
221
|
-
# Skip trailing whitespace when applicable.
|
222
|
-
try:
|
223
|
-
char = src[pos]
|
224
|
-
except IndexError:
|
225
|
-
break
|
226
|
-
if char == '\n':
|
227
|
-
pos += 1
|
228
|
-
continue
|
229
|
-
if char in TOML_KEY_INITIAL_CHARS:
|
230
|
-
pos = toml_key_value_rule(src, pos, out, header, parse_float)
|
231
|
-
pos = toml_skip_chars(src, pos, TOML_WS)
|
232
|
-
elif char == '[':
|
233
|
-
try:
|
234
|
-
second_char: ta.Optional[str] = src[pos + 1]
|
235
|
-
except IndexError:
|
236
|
-
second_char = None
|
237
|
-
out.flags.finalize_pending()
|
238
|
-
if second_char == '[':
|
239
|
-
pos, header = toml_create_list_rule(src, pos, out)
|
240
|
-
else:
|
241
|
-
pos, header = toml_create_dict_rule(src, pos, out)
|
242
|
-
pos = toml_skip_chars(src, pos, TOML_WS)
|
243
|
-
elif char != '#':
|
244
|
-
raise toml_suffixed_err(src, pos, 'Invalid statement')
|
245
|
-
|
246
|
-
# 3. Skip comment
|
247
|
-
pos = toml_skip_comment(src, pos)
|
248
|
-
|
249
|
-
# 4. Expect end of line or end of file
|
250
|
-
try:
|
251
|
-
char = src[pos]
|
252
|
-
except IndexError:
|
253
|
-
break
|
254
|
-
if char != '\n':
|
255
|
-
raise toml_suffixed_err(
|
256
|
-
src, pos, 'Expected newline or end of document after a statement',
|
257
|
-
)
|
258
|
-
pos += 1
|
259
|
-
|
260
|
-
return out.data.dict
|
261
|
-
|
262
|
-
|
263
|
-
class TomlFlags:
|
264
|
-
"""Flags that map to parsed keys/namespaces."""
|
265
|
-
|
266
|
-
# Marks an immutable namespace (inline array or inline table).
|
267
|
-
FROZEN = 0
|
268
|
-
# Marks a nest that has been explicitly created and can no longer be opened using the "[table]" syntax.
|
269
|
-
EXPLICIT_NEST = 1
|
270
|
-
|
271
|
-
def __init__(self) -> None:
|
272
|
-
self._flags: ta.Dict[str, dict] = {}
|
273
|
-
self._pending_flags: ta.Set[ta.Tuple[TomlKey, int]] = set()
|
274
|
-
|
275
|
-
def add_pending(self, key: TomlKey, flag: int) -> None:
|
276
|
-
self._pending_flags.add((key, flag))
|
277
|
-
|
278
|
-
def finalize_pending(self) -> None:
|
279
|
-
for key, flag in self._pending_flags:
|
280
|
-
self.set(key, flag, recursive=False)
|
281
|
-
self._pending_flags.clear()
|
282
|
-
|
283
|
-
def unset_all(self, key: TomlKey) -> None:
|
284
|
-
cont = self._flags
|
285
|
-
for k in key[:-1]:
|
286
|
-
if k not in cont:
|
287
|
-
return
|
288
|
-
cont = cont[k]['nested']
|
289
|
-
cont.pop(key[-1], None)
|
290
|
-
|
291
|
-
def set(self, key: TomlKey, flag: int, *, recursive: bool) -> None: # noqa: A003
|
292
|
-
cont = self._flags
|
293
|
-
key_parent, key_stem = key[:-1], key[-1]
|
294
|
-
for k in key_parent:
|
295
|
-
if k not in cont:
|
296
|
-
cont[k] = {'flags': set(), 'recursive_flags': set(), 'nested': {}}
|
297
|
-
cont = cont[k]['nested']
|
298
|
-
if key_stem not in cont:
|
299
|
-
cont[key_stem] = {'flags': set(), 'recursive_flags': set(), 'nested': {}}
|
300
|
-
cont[key_stem]['recursive_flags' if recursive else 'flags'].add(flag)
|
301
|
-
|
302
|
-
def is_(self, key: TomlKey, flag: int) -> bool:
|
303
|
-
if not key:
|
304
|
-
return False # document root has no flags
|
305
|
-
cont = self._flags
|
306
|
-
for k in key[:-1]:
|
307
|
-
if k not in cont:
|
308
|
-
return False
|
309
|
-
inner_cont = cont[k]
|
310
|
-
if flag in inner_cont['recursive_flags']:
|
311
|
-
return True
|
312
|
-
cont = inner_cont['nested']
|
313
|
-
key_stem = key[-1]
|
314
|
-
if key_stem in cont:
|
315
|
-
cont = cont[key_stem]
|
316
|
-
return flag in cont['flags'] or flag in cont['recursive_flags']
|
317
|
-
return False
|
318
|
-
|
319
|
-
|
320
|
-
class TomlNestedDict:
|
321
|
-
def __init__(self) -> None:
|
322
|
-
# The parsed content of the TOML document
|
323
|
-
self.dict: ta.Dict[str, ta.Any] = {}
|
324
|
-
|
325
|
-
def get_or_create_nest(
|
326
|
-
self,
|
327
|
-
key: TomlKey,
|
328
|
-
*,
|
329
|
-
access_lists: bool = True,
|
330
|
-
) -> dict:
|
331
|
-
cont: ta.Any = self.dict
|
332
|
-
for k in key:
|
333
|
-
if k not in cont:
|
334
|
-
cont[k] = {}
|
335
|
-
cont = cont[k]
|
336
|
-
if access_lists and isinstance(cont, list):
|
337
|
-
cont = cont[-1]
|
338
|
-
if not isinstance(cont, dict):
|
339
|
-
raise KeyError('There is no nest behind this key')
|
340
|
-
return cont
|
341
|
-
|
342
|
-
def append_nest_to_list(self, key: TomlKey) -> None:
|
343
|
-
cont = self.get_or_create_nest(key[:-1])
|
344
|
-
last_key = key[-1]
|
345
|
-
if last_key in cont:
|
346
|
-
list_ = cont[last_key]
|
347
|
-
if not isinstance(list_, list):
|
348
|
-
raise KeyError('An object other than list found behind this key')
|
349
|
-
list_.append({})
|
350
|
-
else:
|
351
|
-
cont[last_key] = [{}]
|
352
|
-
|
353
|
-
|
354
|
-
class TomlOutput(ta.NamedTuple):
|
355
|
-
data: TomlNestedDict
|
356
|
-
flags: TomlFlags
|
357
|
-
|
358
|
-
|
359
|
-
def toml_skip_chars(src: str, pos: TomlPos, chars: ta.Iterable[str]) -> TomlPos:
|
360
|
-
try:
|
361
|
-
while src[pos] in chars:
|
362
|
-
pos += 1
|
363
|
-
except IndexError:
|
364
|
-
pass
|
365
|
-
return pos
|
366
|
-
|
367
|
-
|
368
|
-
def toml_skip_until(
|
369
|
-
src: str,
|
370
|
-
pos: TomlPos,
|
371
|
-
expect: str,
|
372
|
-
*,
|
373
|
-
error_on: ta.FrozenSet[str],
|
374
|
-
error_on_eof: bool,
|
375
|
-
) -> TomlPos:
|
376
|
-
try:
|
377
|
-
new_pos = src.index(expect, pos)
|
378
|
-
except ValueError:
|
379
|
-
new_pos = len(src)
|
380
|
-
if error_on_eof:
|
381
|
-
raise toml_suffixed_err(src, new_pos, f'Expected {expect!r}') from None
|
382
|
-
|
383
|
-
if not error_on.isdisjoint(src[pos:new_pos]):
|
384
|
-
while src[pos] not in error_on:
|
385
|
-
pos += 1
|
386
|
-
raise toml_suffixed_err(src, pos, f'Found invalid character {src[pos]!r}')
|
387
|
-
return new_pos
|
388
|
-
|
389
|
-
|
390
|
-
def toml_skip_comment(src: str, pos: TomlPos) -> TomlPos:
|
391
|
-
try:
|
392
|
-
char: ta.Optional[str] = src[pos]
|
393
|
-
except IndexError:
|
394
|
-
char = None
|
395
|
-
if char == '#':
|
396
|
-
return toml_skip_until(
|
397
|
-
src, pos + 1, '\n', error_on=TOML_ILLEGAL_COMMENT_CHARS, error_on_eof=False,
|
398
|
-
)
|
399
|
-
return pos
|
400
|
-
|
401
|
-
|
402
|
-
def toml_skip_comments_and_array_ws(src: str, pos: TomlPos) -> TomlPos:
|
403
|
-
while True:
|
404
|
-
pos_before_skip = pos
|
405
|
-
pos = toml_skip_chars(src, pos, TOML_WS_AND_NEWLINE)
|
406
|
-
pos = toml_skip_comment(src, pos)
|
407
|
-
if pos == pos_before_skip:
|
408
|
-
return pos
|
409
|
-
|
410
|
-
|
411
|
-
def toml_create_dict_rule(src: str, pos: TomlPos, out: TomlOutput) -> ta.Tuple[TomlPos, TomlKey]:
|
412
|
-
pos += 1 # Skip "["
|
413
|
-
pos = toml_skip_chars(src, pos, TOML_WS)
|
414
|
-
pos, key = toml_parse_key(src, pos)
|
415
|
-
|
416
|
-
if out.flags.is_(key, TomlFlags.EXPLICIT_NEST) or out.flags.is_(key, TomlFlags.FROZEN):
|
417
|
-
raise toml_suffixed_err(src, pos, f'Cannot declare {key} twice')
|
418
|
-
out.flags.set(key, TomlFlags.EXPLICIT_NEST, recursive=False)
|
419
|
-
try:
|
420
|
-
out.data.get_or_create_nest(key)
|
421
|
-
except KeyError:
|
422
|
-
raise toml_suffixed_err(src, pos, 'Cannot overwrite a value') from None
|
423
|
-
|
424
|
-
if not src.startswith(']', pos):
|
425
|
-
raise toml_suffixed_err(src, pos, "Expected ']' at the end of a table declaration")
|
426
|
-
return pos + 1, key
|
427
|
-
|
428
|
-
|
429
|
-
def toml_create_list_rule(src: str, pos: TomlPos, out: TomlOutput) -> ta.Tuple[TomlPos, TomlKey]:
|
430
|
-
pos += 2 # Skip "[["
|
431
|
-
pos = toml_skip_chars(src, pos, TOML_WS)
|
432
|
-
pos, key = toml_parse_key(src, pos)
|
433
|
-
|
434
|
-
if out.flags.is_(key, TomlFlags.FROZEN):
|
435
|
-
raise toml_suffixed_err(src, pos, f'Cannot mutate immutable namespace {key}')
|
436
|
-
# Free the namespace now that it points to another empty list item...
|
437
|
-
out.flags.unset_all(key)
|
438
|
-
# ...but this key precisely is still prohibited from table declaration
|
439
|
-
out.flags.set(key, TomlFlags.EXPLICIT_NEST, recursive=False)
|
440
|
-
try:
|
441
|
-
out.data.append_nest_to_list(key)
|
442
|
-
except KeyError:
|
443
|
-
raise toml_suffixed_err(src, pos, 'Cannot overwrite a value') from None
|
444
|
-
|
445
|
-
if not src.startswith(']]', pos):
|
446
|
-
raise toml_suffixed_err(src, pos, "Expected ']]' at the end of an array declaration")
|
447
|
-
return pos + 2, key
|
448
|
-
|
449
|
-
|
450
|
-
def toml_key_value_rule(
|
451
|
-
src: str,
|
452
|
-
pos: TomlPos,
|
453
|
-
out: TomlOutput,
|
454
|
-
header: TomlKey,
|
455
|
-
parse_float: TomlParseFloat,
|
456
|
-
) -> TomlPos:
|
457
|
-
pos, key, value = toml_parse_key_value_pair(src, pos, parse_float)
|
458
|
-
key_parent, key_stem = key[:-1], key[-1]
|
459
|
-
abs_key_parent = header + key_parent
|
460
|
-
|
461
|
-
relative_path_cont_keys = (header + key[:i] for i in range(1, len(key)))
|
462
|
-
for cont_key in relative_path_cont_keys:
|
463
|
-
# Check that dotted key syntax does not redefine an existing table
|
464
|
-
if out.flags.is_(cont_key, TomlFlags.EXPLICIT_NEST):
|
465
|
-
raise toml_suffixed_err(src, pos, f'Cannot redefine namespace {cont_key}')
|
466
|
-
# Containers in the relative path can't be opened with the table syntax or dotted key/value syntax in following
|
467
|
-
# table sections.
|
468
|
-
out.flags.add_pending(cont_key, TomlFlags.EXPLICIT_NEST)
|
469
|
-
|
470
|
-
if out.flags.is_(abs_key_parent, TomlFlags.FROZEN):
|
471
|
-
raise toml_suffixed_err(
|
472
|
-
src,
|
473
|
-
pos,
|
474
|
-
f'Cannot mutate immutable namespace {abs_key_parent}',
|
475
|
-
)
|
476
|
-
|
477
|
-
try:
|
478
|
-
nest = out.data.get_or_create_nest(abs_key_parent)
|
479
|
-
except KeyError:
|
480
|
-
raise toml_suffixed_err(src, pos, 'Cannot overwrite a value') from None
|
481
|
-
if key_stem in nest:
|
482
|
-
raise toml_suffixed_err(src, pos, 'Cannot overwrite a value')
|
483
|
-
# Mark inline table and array namespaces recursively immutable
|
484
|
-
if isinstance(value, (dict, list)):
|
485
|
-
out.flags.set(header + key, TomlFlags.FROZEN, recursive=True)
|
486
|
-
nest[key_stem] = value
|
487
|
-
return pos
|
488
|
-
|
489
|
-
|
490
|
-
def toml_parse_key_value_pair(
|
491
|
-
src: str,
|
492
|
-
pos: TomlPos,
|
493
|
-
parse_float: TomlParseFloat,
|
494
|
-
) -> ta.Tuple[TomlPos, TomlKey, ta.Any]:
|
495
|
-
pos, key = toml_parse_key(src, pos)
|
496
|
-
try:
|
497
|
-
char: ta.Optional[str] = src[pos]
|
498
|
-
except IndexError:
|
499
|
-
char = None
|
500
|
-
if char != '=':
|
501
|
-
raise toml_suffixed_err(src, pos, "Expected '=' after a key in a key/value pair")
|
502
|
-
pos += 1
|
503
|
-
pos = toml_skip_chars(src, pos, TOML_WS)
|
504
|
-
pos, value = toml_parse_value(src, pos, parse_float)
|
505
|
-
return pos, key, value
|
506
|
-
|
507
|
-
|
508
|
-
def toml_parse_key(src: str, pos: TomlPos) -> ta.Tuple[TomlPos, TomlKey]:
|
509
|
-
pos, key_part = toml_parse_key_part(src, pos)
|
510
|
-
key: TomlKey = (key_part,)
|
511
|
-
pos = toml_skip_chars(src, pos, TOML_WS)
|
512
|
-
while True:
|
513
|
-
try:
|
514
|
-
char: ta.Optional[str] = src[pos]
|
515
|
-
except IndexError:
|
516
|
-
char = None
|
517
|
-
if char != '.':
|
518
|
-
return pos, key
|
519
|
-
pos += 1
|
520
|
-
pos = toml_skip_chars(src, pos, TOML_WS)
|
521
|
-
pos, key_part = toml_parse_key_part(src, pos)
|
522
|
-
key += (key_part,)
|
523
|
-
pos = toml_skip_chars(src, pos, TOML_WS)
|
524
|
-
|
525
|
-
|
526
|
-
def toml_parse_key_part(src: str, pos: TomlPos) -> ta.Tuple[TomlPos, str]:
|
527
|
-
try:
|
528
|
-
char: ta.Optional[str] = src[pos]
|
529
|
-
except IndexError:
|
530
|
-
char = None
|
531
|
-
if char in TOML_BARE_KEY_CHARS:
|
532
|
-
start_pos = pos
|
533
|
-
pos = toml_skip_chars(src, pos, TOML_BARE_KEY_CHARS)
|
534
|
-
return pos, src[start_pos:pos]
|
535
|
-
if char == "'":
|
536
|
-
return toml_parse_literal_str(src, pos)
|
537
|
-
if char == '"':
|
538
|
-
return toml_parse_one_line_basic_str(src, pos)
|
539
|
-
raise toml_suffixed_err(src, pos, 'Invalid initial character for a key part')
|
540
|
-
|
541
|
-
|
542
|
-
def toml_parse_one_line_basic_str(src: str, pos: TomlPos) -> ta.Tuple[TomlPos, str]:
|
543
|
-
pos += 1
|
544
|
-
return toml_parse_basic_str(src, pos, multiline=False)
|
545
|
-
|
546
|
-
|
547
|
-
def toml_parse_array(src: str, pos: TomlPos, parse_float: TomlParseFloat) -> ta.Tuple[TomlPos, list]:
|
548
|
-
pos += 1
|
549
|
-
array: list = []
|
550
|
-
|
551
|
-
pos = toml_skip_comments_and_array_ws(src, pos)
|
552
|
-
if src.startswith(']', pos):
|
553
|
-
return pos + 1, array
|
554
|
-
while True:
|
555
|
-
pos, val = toml_parse_value(src, pos, parse_float)
|
556
|
-
array.append(val)
|
557
|
-
pos = toml_skip_comments_and_array_ws(src, pos)
|
558
|
-
|
559
|
-
c = src[pos:pos + 1]
|
560
|
-
if c == ']':
|
561
|
-
return pos + 1, array
|
562
|
-
if c != ',':
|
563
|
-
raise toml_suffixed_err(src, pos, 'Unclosed array')
|
564
|
-
pos += 1
|
565
|
-
|
566
|
-
pos = toml_skip_comments_and_array_ws(src, pos)
|
567
|
-
if src.startswith(']', pos):
|
568
|
-
return pos + 1, array
|
569
|
-
|
570
|
-
|
571
|
-
def toml_parse_inline_table(src: str, pos: TomlPos, parse_float: TomlParseFloat) -> ta.Tuple[TomlPos, dict]:
|
572
|
-
pos += 1
|
573
|
-
nested_dict = TomlNestedDict()
|
574
|
-
flags = TomlFlags()
|
575
|
-
|
576
|
-
pos = toml_skip_chars(src, pos, TOML_WS)
|
577
|
-
if src.startswith('}', pos):
|
578
|
-
return pos + 1, nested_dict.dict
|
579
|
-
while True:
|
580
|
-
pos, key, value = toml_parse_key_value_pair(src, pos, parse_float)
|
581
|
-
key_parent, key_stem = key[:-1], key[-1]
|
582
|
-
if flags.is_(key, TomlFlags.FROZEN):
|
583
|
-
raise toml_suffixed_err(src, pos, f'Cannot mutate immutable namespace {key}')
|
584
|
-
try:
|
585
|
-
nest = nested_dict.get_or_create_nest(key_parent, access_lists=False)
|
586
|
-
except KeyError:
|
587
|
-
raise toml_suffixed_err(src, pos, 'Cannot overwrite a value') from None
|
588
|
-
if key_stem in nest:
|
589
|
-
raise toml_suffixed_err(src, pos, f'Duplicate inline table key {key_stem!r}')
|
590
|
-
nest[key_stem] = value
|
591
|
-
pos = toml_skip_chars(src, pos, TOML_WS)
|
592
|
-
c = src[pos:pos + 1]
|
593
|
-
if c == '}':
|
594
|
-
return pos + 1, nested_dict.dict
|
595
|
-
if c != ',':
|
596
|
-
raise toml_suffixed_err(src, pos, 'Unclosed inline table')
|
597
|
-
if isinstance(value, (dict, list)):
|
598
|
-
flags.set(key, TomlFlags.FROZEN, recursive=True)
|
599
|
-
pos += 1
|
600
|
-
pos = toml_skip_chars(src, pos, TOML_WS)
|
601
|
-
|
602
|
-
|
603
|
-
def toml_parse_basic_str_escape(
|
604
|
-
src: str,
|
605
|
-
pos: TomlPos,
|
606
|
-
*,
|
607
|
-
multiline: bool = False,
|
608
|
-
) -> ta.Tuple[TomlPos, str]:
|
609
|
-
escape_id = src[pos:pos + 2]
|
610
|
-
pos += 2
|
611
|
-
if multiline and escape_id in {'\\ ', '\\\t', '\\\n'}:
|
612
|
-
# Skip whitespace until next non-whitespace character or end of the doc. Error if non-whitespace is found before
|
613
|
-
# newline.
|
614
|
-
if escape_id != '\\\n':
|
615
|
-
pos = toml_skip_chars(src, pos, TOML_WS)
|
616
|
-
try:
|
617
|
-
char = src[pos]
|
618
|
-
except IndexError:
|
619
|
-
return pos, ''
|
620
|
-
if char != '\n':
|
621
|
-
raise toml_suffixed_err(src, pos, "Unescaped '\\' in a string")
|
622
|
-
pos += 1
|
623
|
-
pos = toml_skip_chars(src, pos, TOML_WS_AND_NEWLINE)
|
624
|
-
return pos, ''
|
625
|
-
if escape_id == '\\u':
|
626
|
-
return toml_parse_hex_char(src, pos, 4)
|
627
|
-
if escape_id == '\\U':
|
628
|
-
return toml_parse_hex_char(src, pos, 8)
|
629
|
-
try:
|
630
|
-
return pos, TOML_BASIC_STR_ESCAPE_REPLACEMENTS[escape_id]
|
631
|
-
except KeyError:
|
632
|
-
raise toml_suffixed_err(src, pos, "Unescaped '\\' in a string") from None
|
633
|
-
|
634
|
-
|
635
|
-
def toml_parse_basic_str_escape_multiline(src: str, pos: TomlPos) -> ta.Tuple[TomlPos, str]:
|
636
|
-
return toml_parse_basic_str_escape(src, pos, multiline=True)
|
637
|
-
|
638
|
-
|
639
|
-
def toml_parse_hex_char(src: str, pos: TomlPos, hex_len: int) -> ta.Tuple[TomlPos, str]:
|
640
|
-
hex_str = src[pos:pos + hex_len]
|
641
|
-
if len(hex_str) != hex_len or not TOML_HEXDIGIT_CHARS.issuperset(hex_str):
|
642
|
-
raise toml_suffixed_err(src, pos, 'Invalid hex value')
|
643
|
-
pos += hex_len
|
644
|
-
hex_int = int(hex_str, 16)
|
645
|
-
if not toml_is_unicode_scalar_value(hex_int):
|
646
|
-
raise toml_suffixed_err(src, pos, 'Escaped character is not a Unicode scalar value')
|
647
|
-
return pos, chr(hex_int)
|
648
|
-
|
649
|
-
|
650
|
-
def toml_parse_literal_str(src: str, pos: TomlPos) -> ta.Tuple[TomlPos, str]:
|
651
|
-
pos += 1 # Skip starting apostrophe
|
652
|
-
start_pos = pos
|
653
|
-
pos = toml_skip_until(
|
654
|
-
src, pos, "'", error_on=TOML_ILLEGAL_LITERAL_STR_CHARS, error_on_eof=True,
|
655
|
-
)
|
656
|
-
return pos + 1, src[start_pos:pos] # Skip ending apostrophe
|
657
|
-
|
658
|
-
|
659
|
-
def toml_parse_multiline_str(src: str, pos: TomlPos, *, literal: bool) -> ta.Tuple[TomlPos, str]:
|
660
|
-
pos += 3
|
661
|
-
if src.startswith('\n', pos):
|
662
|
-
pos += 1
|
663
|
-
|
664
|
-
if literal:
|
665
|
-
delim = "'"
|
666
|
-
end_pos = toml_skip_until(
|
667
|
-
src,
|
668
|
-
pos,
|
669
|
-
"'''",
|
670
|
-
error_on=TOML_ILLEGAL_MULTILINE_LITERAL_STR_CHARS,
|
671
|
-
error_on_eof=True,
|
672
|
-
)
|
673
|
-
result = src[pos:end_pos]
|
674
|
-
pos = end_pos + 3
|
675
|
-
else:
|
676
|
-
delim = '"'
|
677
|
-
pos, result = toml_parse_basic_str(src, pos, multiline=True)
|
678
|
-
|
679
|
-
# Add at maximum two extra apostrophes/quotes if the end sequence is 4 or 5 chars long instead of just 3.
|
680
|
-
if not src.startswith(delim, pos):
|
681
|
-
return pos, result
|
682
|
-
pos += 1
|
683
|
-
if not src.startswith(delim, pos):
|
684
|
-
return pos, result + delim
|
685
|
-
pos += 1
|
686
|
-
return pos, result + (delim * 2)
|
687
|
-
|
688
|
-
|
689
|
-
def toml_parse_basic_str(src: str, pos: TomlPos, *, multiline: bool) -> ta.Tuple[TomlPos, str]:
|
690
|
-
if multiline:
|
691
|
-
error_on = TOML_ILLEGAL_MULTILINE_BASIC_STR_CHARS
|
692
|
-
parse_escapes = toml_parse_basic_str_escape_multiline
|
693
|
-
else:
|
694
|
-
error_on = TOML_ILLEGAL_BASIC_STR_CHARS
|
695
|
-
parse_escapes = toml_parse_basic_str_escape
|
696
|
-
result = ''
|
697
|
-
start_pos = pos
|
698
|
-
while True:
|
699
|
-
try:
|
700
|
-
char = src[pos]
|
701
|
-
except IndexError:
|
702
|
-
raise toml_suffixed_err(src, pos, 'Unterminated string') from None
|
703
|
-
if char == '"':
|
704
|
-
if not multiline:
|
705
|
-
return pos + 1, result + src[start_pos:pos]
|
706
|
-
if src.startswith('"""', pos):
|
707
|
-
return pos + 3, result + src[start_pos:pos]
|
708
|
-
pos += 1
|
709
|
-
continue
|
710
|
-
if char == '\\':
|
711
|
-
result += src[start_pos:pos]
|
712
|
-
pos, parsed_escape = parse_escapes(src, pos)
|
713
|
-
result += parsed_escape
|
714
|
-
start_pos = pos
|
715
|
-
continue
|
716
|
-
if char in error_on:
|
717
|
-
raise toml_suffixed_err(src, pos, f'Illegal character {char!r}')
|
718
|
-
pos += 1
|
719
|
-
|
720
|
-
|
721
|
-
def toml_parse_value( # noqa: C901
|
722
|
-
src: str,
|
723
|
-
pos: TomlPos,
|
724
|
-
parse_float: TomlParseFloat,
|
725
|
-
) -> ta.Tuple[TomlPos, ta.Any]:
|
726
|
-
try:
|
727
|
-
char: ta.Optional[str] = src[pos]
|
728
|
-
except IndexError:
|
729
|
-
char = None
|
730
|
-
|
731
|
-
# IMPORTANT: order conditions based on speed of checking and likelihood
|
732
|
-
|
733
|
-
# Basic strings
|
734
|
-
if char == '"':
|
735
|
-
if src.startswith('"""', pos):
|
736
|
-
return toml_parse_multiline_str(src, pos, literal=False)
|
737
|
-
return toml_parse_one_line_basic_str(src, pos)
|
738
|
-
|
739
|
-
# Literal strings
|
740
|
-
if char == "'":
|
741
|
-
if src.startswith("'''", pos):
|
742
|
-
return toml_parse_multiline_str(src, pos, literal=True)
|
743
|
-
return toml_parse_literal_str(src, pos)
|
744
|
-
|
745
|
-
# Booleans
|
746
|
-
if char == 't':
|
747
|
-
if src.startswith('true', pos):
|
748
|
-
return pos + 4, True
|
749
|
-
if char == 'f':
|
750
|
-
if src.startswith('false', pos):
|
751
|
-
return pos + 5, False
|
752
|
-
|
753
|
-
# Arrays
|
754
|
-
if char == '[':
|
755
|
-
return toml_parse_array(src, pos, parse_float)
|
756
|
-
|
757
|
-
# Inline tables
|
758
|
-
if char == '{':
|
759
|
-
return toml_parse_inline_table(src, pos, parse_float)
|
760
|
-
|
761
|
-
# Dates and times
|
762
|
-
datetime_match = TOML_RE_DATETIME.match(src, pos)
|
763
|
-
if datetime_match:
|
764
|
-
try:
|
765
|
-
datetime_obj = toml_match_to_datetime(datetime_match)
|
766
|
-
except ValueError as e:
|
767
|
-
raise toml_suffixed_err(src, pos, 'Invalid date or datetime') from e
|
768
|
-
return datetime_match.end(), datetime_obj
|
769
|
-
localtime_match = TOML_RE_LOCALTIME.match(src, pos)
|
770
|
-
if localtime_match:
|
771
|
-
return localtime_match.end(), toml_match_to_localtime(localtime_match)
|
772
|
-
|
773
|
-
# Integers and "normal" floats. The regex will greedily match any type starting with a decimal char, so needs to be
|
774
|
-
# located after handling of dates and times.
|
775
|
-
number_match = TOML_RE_NUMBER.match(src, pos)
|
776
|
-
if number_match:
|
777
|
-
return number_match.end(), toml_match_to_number(number_match, parse_float)
|
778
|
-
|
779
|
-
# Special floats
|
780
|
-
first_three = src[pos:pos + 3]
|
781
|
-
if first_three in {'inf', 'nan'}:
|
782
|
-
return pos + 3, parse_float(first_three)
|
783
|
-
first_four = src[pos:pos + 4]
|
784
|
-
if first_four in {'-inf', '+inf', '-nan', '+nan'}:
|
785
|
-
return pos + 4, parse_float(first_four)
|
786
|
-
|
787
|
-
raise toml_suffixed_err(src, pos, 'Invalid value')
|
788
|
-
|
789
|
-
|
790
|
-
def toml_suffixed_err(src: str, pos: TomlPos, msg: str) -> TomlDecodeError:
|
791
|
-
"""Return a `TomlDecodeError` where error message is suffixed with coordinates in source."""
|
792
|
-
|
793
|
-
def coord_repr(src: str, pos: TomlPos) -> str:
|
794
|
-
if pos >= len(src):
|
795
|
-
return 'end of document'
|
796
|
-
line = src.count('\n', 0, pos) + 1
|
797
|
-
if line == 1:
|
798
|
-
column = pos + 1
|
799
|
-
else:
|
800
|
-
column = pos - src.rindex('\n', 0, pos)
|
801
|
-
return f'line {line}, column {column}'
|
802
|
-
|
803
|
-
return TomlDecodeError(f'{msg} (at {coord_repr(src, pos)})')
|
804
|
-
|
805
|
-
|
806
|
-
def toml_is_unicode_scalar_value(codepoint: int) -> bool:
|
807
|
-
return (0 <= codepoint <= 55295) or (57344 <= codepoint <= 1114111)
|
808
|
-
|
809
|
-
|
810
|
-
def toml_make_safe_parse_float(parse_float: TomlParseFloat) -> TomlParseFloat:
|
811
|
-
"""A decorator to make `parse_float` safe.
|
812
|
-
|
813
|
-
`parse_float` must not return dicts or lists, because these types would be mixed with parsed TOML tables and arrays,
|
814
|
-
thus confusing the parser. The returned decorated callable raises `ValueError` instead of returning illegal types.
|
815
|
-
"""
|
816
|
-
# The default `float` callable never returns illegal types. Optimize it.
|
817
|
-
if parse_float is float:
|
818
|
-
return float
|
819
|
-
|
820
|
-
def safe_parse_float(float_str: str) -> ta.Any:
|
821
|
-
float_value = parse_float(float_str)
|
822
|
-
if isinstance(float_value, (dict, list)):
|
823
|
-
raise ValueError('parse_float must not return dicts or lists') # noqa
|
824
|
-
return float_value
|
825
|
-
|
826
|
-
return safe_parse_float
|