omdev 0.0.0.dev7__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.
Files changed (67) hide show
  1. omdev/__about__.py +35 -0
  2. omdev/__init__.py +0 -0
  3. omdev/amalg/__init__.py +0 -0
  4. omdev/amalg/__main__.py +4 -0
  5. omdev/amalg/amalg.py +513 -0
  6. omdev/classdot.py +61 -0
  7. omdev/cmake.py +164 -0
  8. omdev/exts/__init__.py +0 -0
  9. omdev/exts/_distutils/__init__.py +10 -0
  10. omdev/exts/_distutils/build_ext.py +367 -0
  11. omdev/exts/_distutils/compilers/__init__.py +3 -0
  12. omdev/exts/_distutils/compilers/ccompiler.py +1032 -0
  13. omdev/exts/_distutils/compilers/options.py +80 -0
  14. omdev/exts/_distutils/compilers/unixccompiler.py +385 -0
  15. omdev/exts/_distutils/dir_util.py +76 -0
  16. omdev/exts/_distutils/errors.py +62 -0
  17. omdev/exts/_distutils/extension.py +107 -0
  18. omdev/exts/_distutils/file_util.py +216 -0
  19. omdev/exts/_distutils/modified.py +47 -0
  20. omdev/exts/_distutils/spawn.py +103 -0
  21. omdev/exts/_distutils/sysconfig.py +349 -0
  22. omdev/exts/_distutils/util.py +201 -0
  23. omdev/exts/_distutils/version.py +308 -0
  24. omdev/exts/build.py +43 -0
  25. omdev/exts/cmake.py +195 -0
  26. omdev/exts/importhook.py +88 -0
  27. omdev/exts/scan.py +74 -0
  28. omdev/interp/__init__.py +1 -0
  29. omdev/interp/__main__.py +4 -0
  30. omdev/interp/cli.py +63 -0
  31. omdev/interp/inspect.py +105 -0
  32. omdev/interp/providers.py +67 -0
  33. omdev/interp/pyenv.py +353 -0
  34. omdev/interp/resolvers.py +76 -0
  35. omdev/interp/standalone.py +187 -0
  36. omdev/interp/system.py +125 -0
  37. omdev/interp/types.py +92 -0
  38. omdev/mypy/__init__.py +0 -0
  39. omdev/mypy/debug.py +86 -0
  40. omdev/pyproject/__init__.py +1 -0
  41. omdev/pyproject/__main__.py +4 -0
  42. omdev/pyproject/cli.py +319 -0
  43. omdev/pyproject/configs.py +97 -0
  44. omdev/pyproject/ext.py +107 -0
  45. omdev/pyproject/pkg.py +196 -0
  46. omdev/scripts/__init__.py +0 -0
  47. omdev/scripts/execrss.py +19 -0
  48. omdev/scripts/findimports.py +62 -0
  49. omdev/scripts/findmagic.py +70 -0
  50. omdev/scripts/interp.py +2118 -0
  51. omdev/scripts/pyproject.py +3584 -0
  52. omdev/scripts/traceimport.py +502 -0
  53. omdev/tokens.py +42 -0
  54. omdev/toml/__init__.py +1 -0
  55. omdev/toml/parser.py +823 -0
  56. omdev/toml/writer.py +104 -0
  57. omdev/tools/__init__.py +0 -0
  58. omdev/tools/dockertools.py +81 -0
  59. omdev/tools/sqlrepl.py +193 -0
  60. omdev/versioning/__init__.py +1 -0
  61. omdev/versioning/specifiers.py +531 -0
  62. omdev/versioning/versions.py +416 -0
  63. omdev-0.0.0.dev7.dist-info/LICENSE +21 -0
  64. omdev-0.0.0.dev7.dist-info/METADATA +24 -0
  65. omdev-0.0.0.dev7.dist-info/RECORD +67 -0
  66. omdev-0.0.0.dev7.dist-info/WHEEL +5 -0
  67. omdev-0.0.0.dev7.dist-info/top_level.txt +1 -0
omdev/toml/parser.py ADDED
@@ -0,0 +1,823 @@
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/f5009b69e0cd94b990270e04e65b9d4d2b365844/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
+ src = s.replace('\r\n', '\n')
201
+ pos = 0
202
+ out = TomlOutput(TomlNestedDict(), TomlFlags())
203
+ header: TomlKey = ()
204
+ parse_float = toml_make_safe_parse_float(parse_float)
205
+
206
+ # Parse one statement at a time (typically means one line in TOML source)
207
+ while True:
208
+ # 1. Skip line leading whitespace
209
+ pos = toml_skip_chars(src, pos, TOML_WS)
210
+
211
+ # 2. Parse rules. Expect one of the following:
212
+ # - end of file
213
+ # - end of line
214
+ # - comment
215
+ # - key/value pair
216
+ # - append dict to list (and move to its namespace)
217
+ # - create dict (and move to its namespace)
218
+ # Skip trailing whitespace when applicable.
219
+ try:
220
+ char = src[pos]
221
+ except IndexError:
222
+ break
223
+ if char == '\n':
224
+ pos += 1
225
+ continue
226
+ if char in TOML_KEY_INITIAL_CHARS:
227
+ pos = toml_key_value_rule(src, pos, out, header, parse_float)
228
+ pos = toml_skip_chars(src, pos, TOML_WS)
229
+ elif char == '[':
230
+ try:
231
+ second_char: ta.Optional[str] = src[pos + 1]
232
+ except IndexError:
233
+ second_char = None
234
+ out.flags.finalize_pending()
235
+ if second_char == '[':
236
+ pos, header = toml_create_list_rule(src, pos, out)
237
+ else:
238
+ pos, header = toml_create_dict_rule(src, pos, out)
239
+ pos = toml_skip_chars(src, pos, TOML_WS)
240
+ elif char != '#':
241
+ raise toml_suffixed_err(src, pos, 'Invalid statement')
242
+
243
+ # 3. Skip comment
244
+ pos = toml_skip_comment(src, pos)
245
+
246
+ # 4. Expect end of line or end of file
247
+ try:
248
+ char = src[pos]
249
+ except IndexError:
250
+ break
251
+ if char != '\n':
252
+ raise toml_suffixed_err(
253
+ src, pos, 'Expected newline or end of document after a statement',
254
+ )
255
+ pos += 1
256
+
257
+ return out.data.dict
258
+
259
+
260
+ class TomlFlags:
261
+ """Flags that map to parsed keys/namespaces."""
262
+
263
+ # Marks an immutable namespace (inline array or inline table).
264
+ FROZEN = 0
265
+ # Marks a nest that has been explicitly created and can no longer be opened using the "[table]" syntax.
266
+ EXPLICIT_NEST = 1
267
+
268
+ def __init__(self) -> None:
269
+ self._flags: ta.Dict[str, dict] = {}
270
+ self._pending_flags: ta.Set[ta.Tuple[TomlKey, int]] = set()
271
+
272
+ def add_pending(self, key: TomlKey, flag: int) -> None:
273
+ self._pending_flags.add((key, flag))
274
+
275
+ def finalize_pending(self) -> None:
276
+ for key, flag in self._pending_flags:
277
+ self.set(key, flag, recursive=False)
278
+ self._pending_flags.clear()
279
+
280
+ def unset_all(self, key: TomlKey) -> None:
281
+ cont = self._flags
282
+ for k in key[:-1]:
283
+ if k not in cont:
284
+ return
285
+ cont = cont[k]['nested']
286
+ cont.pop(key[-1], None)
287
+
288
+ def set(self, key: TomlKey, flag: int, *, recursive: bool) -> None: # noqa: A003
289
+ cont = self._flags
290
+ key_parent, key_stem = key[:-1], key[-1]
291
+ for k in key_parent:
292
+ if k not in cont:
293
+ cont[k] = {'flags': set(), 'recursive_flags': set(), 'nested': {}}
294
+ cont = cont[k]['nested']
295
+ if key_stem not in cont:
296
+ cont[key_stem] = {'flags': set(), 'recursive_flags': set(), 'nested': {}}
297
+ cont[key_stem]['recursive_flags' if recursive else 'flags'].add(flag)
298
+
299
+ def is_(self, key: TomlKey, flag: int) -> bool:
300
+ if not key:
301
+ return False # document root has no flags
302
+ cont = self._flags
303
+ for k in key[:-1]:
304
+ if k not in cont:
305
+ return False
306
+ inner_cont = cont[k]
307
+ if flag in inner_cont['recursive_flags']:
308
+ return True
309
+ cont = inner_cont['nested']
310
+ key_stem = key[-1]
311
+ if key_stem in cont:
312
+ cont = cont[key_stem]
313
+ return flag in cont['flags'] or flag in cont['recursive_flags']
314
+ return False
315
+
316
+
317
+ class TomlNestedDict:
318
+ def __init__(self) -> None:
319
+ # The parsed content of the TOML document
320
+ self.dict: ta.Dict[str, ta.Any] = {}
321
+
322
+ def get_or_create_nest(
323
+ self,
324
+ key: TomlKey,
325
+ *,
326
+ access_lists: bool = True,
327
+ ) -> dict:
328
+ cont: ta.Any = self.dict
329
+ for k in key:
330
+ if k not in cont:
331
+ cont[k] = {}
332
+ cont = cont[k]
333
+ if access_lists and isinstance(cont, list):
334
+ cont = cont[-1]
335
+ if not isinstance(cont, dict):
336
+ raise KeyError('There is no nest behind this key')
337
+ return cont
338
+
339
+ def append_nest_to_list(self, key: TomlKey) -> None:
340
+ cont = self.get_or_create_nest(key[:-1])
341
+ last_key = key[-1]
342
+ if last_key in cont:
343
+ list_ = cont[last_key]
344
+ if not isinstance(list_, list):
345
+ raise KeyError('An object other than list found behind this key')
346
+ list_.append({})
347
+ else:
348
+ cont[last_key] = [{}]
349
+
350
+
351
+ class TomlOutput(ta.NamedTuple):
352
+ data: TomlNestedDict
353
+ flags: TomlFlags
354
+
355
+
356
+ def toml_skip_chars(src: str, pos: TomlPos, chars: ta.Iterable[str]) -> TomlPos:
357
+ try:
358
+ while src[pos] in chars:
359
+ pos += 1
360
+ except IndexError:
361
+ pass
362
+ return pos
363
+
364
+
365
+ def toml_skip_until(
366
+ src: str,
367
+ pos: TomlPos,
368
+ expect: str,
369
+ *,
370
+ error_on: ta.FrozenSet[str],
371
+ error_on_eof: bool,
372
+ ) -> TomlPos:
373
+ try:
374
+ new_pos = src.index(expect, pos)
375
+ except ValueError:
376
+ new_pos = len(src)
377
+ if error_on_eof:
378
+ raise toml_suffixed_err(src, new_pos, f'Expected {expect!r}') from None
379
+
380
+ if not error_on.isdisjoint(src[pos:new_pos]):
381
+ while src[pos] not in error_on:
382
+ pos += 1
383
+ raise toml_suffixed_err(src, pos, f'Found invalid character {src[pos]!r}')
384
+ return new_pos
385
+
386
+
387
+ def toml_skip_comment(src: str, pos: TomlPos) -> TomlPos:
388
+ try:
389
+ char: ta.Optional[str] = src[pos]
390
+ except IndexError:
391
+ char = None
392
+ if char == '#':
393
+ return toml_skip_until(
394
+ src, pos + 1, '\n', error_on=TOML_ILLEGAL_COMMENT_CHARS, error_on_eof=False,
395
+ )
396
+ return pos
397
+
398
+
399
+ def toml_skip_comments_and_array_ws(src: str, pos: TomlPos) -> TomlPos:
400
+ while True:
401
+ pos_before_skip = pos
402
+ pos = toml_skip_chars(src, pos, TOML_WS_AND_NEWLINE)
403
+ pos = toml_skip_comment(src, pos)
404
+ if pos == pos_before_skip:
405
+ return pos
406
+
407
+
408
+ def toml_create_dict_rule(src: str, pos: TomlPos, out: TomlOutput) -> ta.Tuple[TomlPos, TomlKey]:
409
+ pos += 1 # Skip "["
410
+ pos = toml_skip_chars(src, pos, TOML_WS)
411
+ pos, key = toml_parse_key(src, pos)
412
+
413
+ if out.flags.is_(key, TomlFlags.EXPLICIT_NEST) or out.flags.is_(key, TomlFlags.FROZEN):
414
+ raise toml_suffixed_err(src, pos, f'Cannot declare {key} twice')
415
+ out.flags.set(key, TomlFlags.EXPLICIT_NEST, recursive=False)
416
+ try:
417
+ out.data.get_or_create_nest(key)
418
+ except KeyError:
419
+ raise toml_suffixed_err(src, pos, 'Cannot overwrite a value') from None
420
+
421
+ if not src.startswith(']', pos):
422
+ raise toml_suffixed_err(src, pos, "Expected ']' at the end of a table declaration")
423
+ return pos + 1, key
424
+
425
+
426
+ def toml_create_list_rule(src: str, pos: TomlPos, out: TomlOutput) -> ta.Tuple[TomlPos, TomlKey]:
427
+ pos += 2 # Skip "[["
428
+ pos = toml_skip_chars(src, pos, TOML_WS)
429
+ pos, key = toml_parse_key(src, pos)
430
+
431
+ if out.flags.is_(key, TomlFlags.FROZEN):
432
+ raise toml_suffixed_err(src, pos, f'Cannot mutate immutable namespace {key}')
433
+ # Free the namespace now that it points to another empty list item...
434
+ out.flags.unset_all(key)
435
+ # ...but this key precisely is still prohibited from table declaration
436
+ out.flags.set(key, TomlFlags.EXPLICIT_NEST, recursive=False)
437
+ try:
438
+ out.data.append_nest_to_list(key)
439
+ except KeyError:
440
+ raise toml_suffixed_err(src, pos, 'Cannot overwrite a value') from None
441
+
442
+ if not src.startswith(']]', pos):
443
+ raise toml_suffixed_err(src, pos, "Expected ']]' at the end of an array declaration")
444
+ return pos + 2, key
445
+
446
+
447
+ def toml_key_value_rule(
448
+ src: str,
449
+ pos: TomlPos,
450
+ out: TomlOutput,
451
+ header: TomlKey,
452
+ parse_float: TomlParseFloat,
453
+ ) -> TomlPos:
454
+ pos, key, value = toml_parse_key_value_pair(src, pos, parse_float)
455
+ key_parent, key_stem = key[:-1], key[-1]
456
+ abs_key_parent = header + key_parent
457
+
458
+ relative_path_cont_keys = (header + key[:i] for i in range(1, len(key)))
459
+ for cont_key in relative_path_cont_keys:
460
+ # Check that dotted key syntax does not redefine an existing table
461
+ if out.flags.is_(cont_key, TomlFlags.EXPLICIT_NEST):
462
+ raise toml_suffixed_err(src, pos, f'Cannot redefine namespace {cont_key}')
463
+ # Containers in the relative path can't be opened with the table syntax or dotted key/value syntax in following
464
+ # table sections.
465
+ out.flags.add_pending(cont_key, TomlFlags.EXPLICIT_NEST)
466
+
467
+ if out.flags.is_(abs_key_parent, TomlFlags.FROZEN):
468
+ raise toml_suffixed_err(
469
+ src,
470
+ pos,
471
+ f'Cannot mutate immutable namespace {abs_key_parent}',
472
+ )
473
+
474
+ try:
475
+ nest = out.data.get_or_create_nest(abs_key_parent)
476
+ except KeyError:
477
+ raise toml_suffixed_err(src, pos, 'Cannot overwrite a value') from None
478
+ if key_stem in nest:
479
+ raise toml_suffixed_err(src, pos, 'Cannot overwrite a value')
480
+ # Mark inline table and array namespaces recursively immutable
481
+ if isinstance(value, (dict, list)):
482
+ out.flags.set(header + key, TomlFlags.FROZEN, recursive=True)
483
+ nest[key_stem] = value
484
+ return pos
485
+
486
+
487
+ def toml_parse_key_value_pair(
488
+ src: str,
489
+ pos: TomlPos,
490
+ parse_float: TomlParseFloat,
491
+ ) -> ta.Tuple[TomlPos, TomlKey, ta.Any]:
492
+ pos, key = toml_parse_key(src, pos)
493
+ try:
494
+ char: ta.Optional[str] = src[pos]
495
+ except IndexError:
496
+ char = None
497
+ if char != '=':
498
+ raise toml_suffixed_err(src, pos, "Expected '=' after a key in a key/value pair")
499
+ pos += 1
500
+ pos = toml_skip_chars(src, pos, TOML_WS)
501
+ pos, value = toml_parse_value(src, pos, parse_float)
502
+ return pos, key, value
503
+
504
+
505
+ def toml_parse_key(src: str, pos: TomlPos) -> ta.Tuple[TomlPos, TomlKey]:
506
+ pos, key_part = toml_parse_key_part(src, pos)
507
+ key: TomlKey = (key_part,)
508
+ pos = toml_skip_chars(src, pos, TOML_WS)
509
+ while True:
510
+ try:
511
+ char: ta.Optional[str] = src[pos]
512
+ except IndexError:
513
+ char = None
514
+ if char != '.':
515
+ return pos, key
516
+ pos += 1
517
+ pos = toml_skip_chars(src, pos, TOML_WS)
518
+ pos, key_part = toml_parse_key_part(src, pos)
519
+ key += (key_part,)
520
+ pos = toml_skip_chars(src, pos, TOML_WS)
521
+
522
+
523
+ def toml_parse_key_part(src: str, pos: TomlPos) -> ta.Tuple[TomlPos, str]:
524
+ try:
525
+ char: ta.Optional[str] = src[pos]
526
+ except IndexError:
527
+ char = None
528
+ if char in TOML_BARE_KEY_CHARS:
529
+ start_pos = pos
530
+ pos = toml_skip_chars(src, pos, TOML_BARE_KEY_CHARS)
531
+ return pos, src[start_pos:pos]
532
+ if char == "'":
533
+ return toml_parse_literal_str(src, pos)
534
+ if char == '"':
535
+ return toml_parse_one_line_basic_str(src, pos)
536
+ raise toml_suffixed_err(src, pos, 'Invalid initial character for a key part')
537
+
538
+
539
+ def toml_parse_one_line_basic_str(src: str, pos: TomlPos) -> ta.Tuple[TomlPos, str]:
540
+ pos += 1
541
+ return toml_parse_basic_str(src, pos, multiline=False)
542
+
543
+
544
+ def toml_parse_array(src: str, pos: TomlPos, parse_float: TomlParseFloat) -> ta.Tuple[TomlPos, list]:
545
+ pos += 1
546
+ array: list = []
547
+
548
+ pos = toml_skip_comments_and_array_ws(src, pos)
549
+ if src.startswith(']', pos):
550
+ return pos + 1, array
551
+ while True:
552
+ pos, val = toml_parse_value(src, pos, parse_float)
553
+ array.append(val)
554
+ pos = toml_skip_comments_and_array_ws(src, pos)
555
+
556
+ c = src[pos:pos + 1]
557
+ if c == ']':
558
+ return pos + 1, array
559
+ if c != ',':
560
+ raise toml_suffixed_err(src, pos, 'Unclosed array')
561
+ pos += 1
562
+
563
+ pos = toml_skip_comments_and_array_ws(src, pos)
564
+ if src.startswith(']', pos):
565
+ return pos + 1, array
566
+
567
+
568
+ def toml_parse_inline_table(src: str, pos: TomlPos, parse_float: TomlParseFloat) -> ta.Tuple[TomlPos, dict]:
569
+ pos += 1
570
+ nested_dict = TomlNestedDict()
571
+ flags = TomlFlags()
572
+
573
+ pos = toml_skip_chars(src, pos, TOML_WS)
574
+ if src.startswith('}', pos):
575
+ return pos + 1, nested_dict.dict
576
+ while True:
577
+ pos, key, value = toml_parse_key_value_pair(src, pos, parse_float)
578
+ key_parent, key_stem = key[:-1], key[-1]
579
+ if flags.is_(key, TomlFlags.FROZEN):
580
+ raise toml_suffixed_err(src, pos, f'Cannot mutate immutable namespace {key}')
581
+ try:
582
+ nest = nested_dict.get_or_create_nest(key_parent, access_lists=False)
583
+ except KeyError:
584
+ raise toml_suffixed_err(src, pos, 'Cannot overwrite a value') from None
585
+ if key_stem in nest:
586
+ raise toml_suffixed_err(src, pos, f'Duplicate inline table key {key_stem!r}')
587
+ nest[key_stem] = value
588
+ pos = toml_skip_chars(src, pos, TOML_WS)
589
+ c = src[pos:pos + 1]
590
+ if c == '}':
591
+ return pos + 1, nested_dict.dict
592
+ if c != ',':
593
+ raise toml_suffixed_err(src, pos, 'Unclosed inline table')
594
+ if isinstance(value, (dict, list)):
595
+ flags.set(key, TomlFlags.FROZEN, recursive=True)
596
+ pos += 1
597
+ pos = toml_skip_chars(src, pos, TOML_WS)
598
+
599
+
600
+ def toml_parse_basic_str_escape(
601
+ src: str,
602
+ pos: TomlPos,
603
+ *,
604
+ multiline: bool = False,
605
+ ) -> ta.Tuple[TomlPos, str]:
606
+ escape_id = src[pos:pos + 2]
607
+ pos += 2
608
+ if multiline and escape_id in {'\\ ', '\\\t', '\\\n'}:
609
+ # Skip whitespace until next non-whitespace character or end of the doc. Error if non-whitespace is found before
610
+ # newline.
611
+ if escape_id != '\\\n':
612
+ pos = toml_skip_chars(src, pos, TOML_WS)
613
+ try:
614
+ char = src[pos]
615
+ except IndexError:
616
+ return pos, ''
617
+ if char != '\n':
618
+ raise toml_suffixed_err(src, pos, "Unescaped '\\' in a string")
619
+ pos += 1
620
+ pos = toml_skip_chars(src, pos, TOML_WS_AND_NEWLINE)
621
+ return pos, ''
622
+ if escape_id == '\\u':
623
+ return toml_parse_hex_char(src, pos, 4)
624
+ if escape_id == '\\U':
625
+ return toml_parse_hex_char(src, pos, 8)
626
+ try:
627
+ return pos, TOML_BASIC_STR_ESCAPE_REPLACEMENTS[escape_id]
628
+ except KeyError:
629
+ raise toml_suffixed_err(src, pos, "Unescaped '\\' in a string") from None
630
+
631
+
632
+ def toml_parse_basic_str_escape_multiline(src: str, pos: TomlPos) -> ta.Tuple[TomlPos, str]:
633
+ return toml_parse_basic_str_escape(src, pos, multiline=True)
634
+
635
+
636
+ def toml_parse_hex_char(src: str, pos: TomlPos, hex_len: int) -> ta.Tuple[TomlPos, str]:
637
+ hex_str = src[pos:pos + hex_len]
638
+ if len(hex_str) != hex_len or not TOML_HEXDIGIT_CHARS.issuperset(hex_str):
639
+ raise toml_suffixed_err(src, pos, 'Invalid hex value')
640
+ pos += hex_len
641
+ hex_int = int(hex_str, 16)
642
+ if not toml_is_unicode_scalar_value(hex_int):
643
+ raise toml_suffixed_err(src, pos, 'Escaped character is not a Unicode scalar value')
644
+ return pos, chr(hex_int)
645
+
646
+
647
+ def toml_parse_literal_str(src: str, pos: TomlPos) -> ta.Tuple[TomlPos, str]:
648
+ pos += 1 # Skip starting apostrophe
649
+ start_pos = pos
650
+ pos = toml_skip_until(
651
+ src, pos, "'", error_on=TOML_ILLEGAL_LITERAL_STR_CHARS, error_on_eof=True,
652
+ )
653
+ return pos + 1, src[start_pos:pos] # Skip ending apostrophe
654
+
655
+
656
+ def toml_parse_multiline_str(src: str, pos: TomlPos, *, literal: bool) -> ta.Tuple[TomlPos, str]:
657
+ pos += 3
658
+ if src.startswith('\n', pos):
659
+ pos += 1
660
+
661
+ if literal:
662
+ delim = "'"
663
+ end_pos = toml_skip_until(
664
+ src,
665
+ pos,
666
+ "'''",
667
+ error_on=TOML_ILLEGAL_MULTILINE_LITERAL_STR_CHARS,
668
+ error_on_eof=True,
669
+ )
670
+ result = src[pos:end_pos]
671
+ pos = end_pos + 3
672
+ else:
673
+ delim = '"'
674
+ pos, result = toml_parse_basic_str(src, pos, multiline=True)
675
+
676
+ # Add at maximum two extra apostrophes/quotes if the end sequence is 4 or 5 chars long instead of just 3.
677
+ if not src.startswith(delim, pos):
678
+ return pos, result
679
+ pos += 1
680
+ if not src.startswith(delim, pos):
681
+ return pos, result + delim
682
+ pos += 1
683
+ return pos, result + (delim * 2)
684
+
685
+
686
+ def toml_parse_basic_str(src: str, pos: TomlPos, *, multiline: bool) -> ta.Tuple[TomlPos, str]:
687
+ if multiline:
688
+ error_on = TOML_ILLEGAL_MULTILINE_BASIC_STR_CHARS
689
+ parse_escapes = toml_parse_basic_str_escape_multiline
690
+ else:
691
+ error_on = TOML_ILLEGAL_BASIC_STR_CHARS
692
+ parse_escapes = toml_parse_basic_str_escape
693
+ result = ''
694
+ start_pos = pos
695
+ while True:
696
+ try:
697
+ char = src[pos]
698
+ except IndexError:
699
+ raise toml_suffixed_err(src, pos, 'Unterminated string') from None
700
+ if char == '"':
701
+ if not multiline:
702
+ return pos + 1, result + src[start_pos:pos]
703
+ if src.startswith('"""', pos):
704
+ return pos + 3, result + src[start_pos:pos]
705
+ pos += 1
706
+ continue
707
+ if char == '\\':
708
+ result += src[start_pos:pos]
709
+ pos, parsed_escape = parse_escapes(src, pos)
710
+ result += parsed_escape
711
+ start_pos = pos
712
+ continue
713
+ if char in error_on:
714
+ raise toml_suffixed_err(src, pos, f'Illegal character {char!r}')
715
+ pos += 1
716
+
717
+
718
+ def toml_parse_value( # noqa: C901
719
+ src: str,
720
+ pos: TomlPos,
721
+ parse_float: TomlParseFloat,
722
+ ) -> ta.Tuple[TomlPos, ta.Any]:
723
+ try:
724
+ char: ta.Optional[str] = src[pos]
725
+ except IndexError:
726
+ char = None
727
+
728
+ # IMPORTANT: order conditions based on speed of checking and likelihood
729
+
730
+ # Basic strings
731
+ if char == '"':
732
+ if src.startswith('"""', pos):
733
+ return toml_parse_multiline_str(src, pos, literal=False)
734
+ return toml_parse_one_line_basic_str(src, pos)
735
+
736
+ # Literal strings
737
+ if char == "'":
738
+ if src.startswith("'''", pos):
739
+ return toml_parse_multiline_str(src, pos, literal=True)
740
+ return toml_parse_literal_str(src, pos)
741
+
742
+ # Booleans
743
+ if char == 't':
744
+ if src.startswith('true', pos):
745
+ return pos + 4, True
746
+ if char == 'f':
747
+ if src.startswith('false', pos):
748
+ return pos + 5, False
749
+
750
+ # Arrays
751
+ if char == '[':
752
+ return toml_parse_array(src, pos, parse_float)
753
+
754
+ # Inline tables
755
+ if char == '{':
756
+ return toml_parse_inline_table(src, pos, parse_float)
757
+
758
+ # Dates and times
759
+ datetime_match = TOML_RE_DATETIME.match(src, pos)
760
+ if datetime_match:
761
+ try:
762
+ datetime_obj = toml_match_to_datetime(datetime_match)
763
+ except ValueError as e:
764
+ raise toml_suffixed_err(src, pos, 'Invalid date or datetime') from e
765
+ return datetime_match.end(), datetime_obj
766
+ localtime_match = TOML_RE_LOCALTIME.match(src, pos)
767
+ if localtime_match:
768
+ return localtime_match.end(), toml_match_to_localtime(localtime_match)
769
+
770
+ # Integers and "normal" floats. The regex will greedily match any type starting with a decimal char, so needs to be
771
+ # located after handling of dates and times.
772
+ number_match = TOML_RE_NUMBER.match(src, pos)
773
+ if number_match:
774
+ return number_match.end(), toml_match_to_number(number_match, parse_float)
775
+
776
+ # Special floats
777
+ first_three = src[pos:pos + 3]
778
+ if first_three in {'inf', 'nan'}:
779
+ return pos + 3, parse_float(first_three)
780
+ first_four = src[pos:pos + 4]
781
+ if first_four in {'-inf', '+inf', '-nan', '+nan'}:
782
+ return pos + 4, parse_float(first_four)
783
+
784
+ raise toml_suffixed_err(src, pos, 'Invalid value')
785
+
786
+
787
+ def toml_suffixed_err(src: str, pos: TomlPos, msg: str) -> TomlDecodeError:
788
+ """Return a `TomlDecodeError` where error message is suffixed with coordinates in source."""
789
+
790
+ def coord_repr(src: str, pos: TomlPos) -> str:
791
+ if pos >= len(src):
792
+ return 'end of document'
793
+ line = src.count('\n', 0, pos) + 1
794
+ if line == 1:
795
+ column = pos + 1
796
+ else:
797
+ column = pos - src.rindex('\n', 0, pos)
798
+ return f'line {line}, column {column}'
799
+
800
+ return TomlDecodeError(f'{msg} (at {coord_repr(src, pos)})')
801
+
802
+
803
+ def toml_is_unicode_scalar_value(codepoint: int) -> bool:
804
+ return (0 <= codepoint <= 55295) or (57344 <= codepoint <= 1114111)
805
+
806
+
807
+ def toml_make_safe_parse_float(parse_float: TomlParseFloat) -> TomlParseFloat:
808
+ """A decorator to make `parse_float` safe.
809
+
810
+ `parse_float` must not return dicts or lists, because these types would be mixed with parsed TOML tables and arrays,
811
+ thus confusing the parser. The returned decorated callable raises `ValueError` instead of returning illegal types.
812
+ """
813
+ # The default `float` callable never returns illegal types. Optimize it.
814
+ if parse_float is float:
815
+ return float
816
+
817
+ def safe_parse_float(float_str: str) -> ta.Any:
818
+ float_value = parse_float(float_str)
819
+ if isinstance(float_value, (dict, list)):
820
+ raise ValueError('parse_float must not return dicts or lists') # noqa
821
+ return float_value
822
+
823
+ return safe_parse_float