omdev 0.0.0.dev36__py3-none-any.whl → 0.0.0.dev37__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 omdev might be problematic. Click here for more details.
- omdev/.manifests.json +1 -1
- omdev/amalg/amalg.py +3 -1
- omdev/interp/inspect.py +1 -1
- omdev/interp/pyenv.py +2 -2
- omdev/interp/system.py +1 -1
- omdev/interp/types.py +3 -3
- omdev/packaging/names.py +44 -0
- omdev/packaging/requires.py +484 -0
- omdev/scripts/interp.py +9 -4
- omdev/scripts/pyproject.py +1175 -1170
- omdev/tools/piptools.py +34 -0
- {omdev-0.0.0.dev36.dist-info → omdev-0.0.0.dev37.dist-info}/METADATA +2 -2
- {omdev-0.0.0.dev36.dist-info → omdev-0.0.0.dev37.dist-info}/RECORD +20 -18
- /omdev/{versioning → packaging}/__init__.py +0 -0
- /omdev/{versioning → packaging}/specifiers.py +0 -0
- /omdev/{versioning → packaging}/versions.py +0 -0
- {omdev-0.0.0.dev36.dist-info → omdev-0.0.0.dev37.dist-info}/LICENSE +0 -0
- {omdev-0.0.0.dev36.dist-info → omdev-0.0.0.dev37.dist-info}/WHEEL +0 -0
- {omdev-0.0.0.dev36.dist-info → omdev-0.0.0.dev37.dist-info}/entry_points.txt +0 -0
- {omdev-0.0.0.dev36.dist-info → omdev-0.0.0.dev37.dist-info}/top_level.txt +0 -0
omdev/scripts/pyproject.py
CHANGED
|
@@ -77,12 +77,7 @@ if sys.version_info < (3, 8):
|
|
|
77
77
|
########################################
|
|
78
78
|
|
|
79
79
|
|
|
80
|
-
# ../../
|
|
81
|
-
TomlParseFloat = ta.Callable[[str], ta.Any]
|
|
82
|
-
TomlKey = ta.Tuple[str, ...]
|
|
83
|
-
TomlPos = int # ta.TypeAlias
|
|
84
|
-
|
|
85
|
-
# ../../versioning/versions.py
|
|
80
|
+
# ../../packaging/versions.py
|
|
86
81
|
VersionLocalType = ta.Tuple[ta.Union[int, str], ...]
|
|
87
82
|
VersionCmpPrePostDevType = ta.Union['InfinityVersionType', 'NegativeInfinityVersionType', ta.Tuple[str, int]]
|
|
88
83
|
_VersionCmpLocalType0 = ta.Tuple[ta.Union[ta.Tuple[int, str], ta.Tuple['NegativeInfinityVersionType', ta.Union[int, str]]], ...] # noqa
|
|
@@ -90,10 +85,15 @@ VersionCmpLocalType = ta.Union['NegativeInfinityVersionType', _VersionCmpLocalTy
|
|
|
90
85
|
VersionCmpKey = ta.Tuple[int, ta.Tuple[int, ...], VersionCmpPrePostDevType, VersionCmpPrePostDevType, VersionCmpPrePostDevType, VersionCmpLocalType] # noqa
|
|
91
86
|
VersionComparisonMethod = ta.Callable[[VersionCmpKey, VersionCmpKey], bool]
|
|
92
87
|
|
|
88
|
+
# ../../toml/parser.py
|
|
89
|
+
TomlParseFloat = ta.Callable[[str], ta.Any]
|
|
90
|
+
TomlKey = ta.Tuple[str, ...]
|
|
91
|
+
TomlPos = int # ta.TypeAlias
|
|
92
|
+
|
|
93
93
|
# ../../../omlish/lite/check.py
|
|
94
94
|
T = ta.TypeVar('T')
|
|
95
95
|
|
|
96
|
-
# ../../
|
|
96
|
+
# ../../packaging/specifiers.py
|
|
97
97
|
UnparsedVersion = ta.Union['Version', str]
|
|
98
98
|
UnparsedVersionVar = ta.TypeVar('UnparsedVersionVar', bound=UnparsedVersion)
|
|
99
99
|
CallableVersionOperator = ta.Callable[['Version', str], bool]
|
|
@@ -270,1340 +270,1340 @@ def get_git_revision(
|
|
|
270
270
|
|
|
271
271
|
|
|
272
272
|
########################################
|
|
273
|
-
# ../../
|
|
274
|
-
#
|
|
275
|
-
#
|
|
276
|
-
# Licensed to PSF under a Contributor Agreement.
|
|
277
|
-
#
|
|
278
|
-
# PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
|
|
279
|
-
# --------------------------------------------
|
|
280
|
-
#
|
|
281
|
-
# 1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), and the Individual or Organization
|
|
282
|
-
# ("Licensee") accessing and otherwise using this software ("Python") in source or binary form and its associated
|
|
283
|
-
# documentation.
|
|
284
|
-
#
|
|
285
|
-
# 2. Subject to the terms and conditions of this License Agreement, PSF hereby grants Licensee a nonexclusive,
|
|
286
|
-
# royalty-free, world-wide license to reproduce, analyze, test, perform and/or display publicly, prepare derivative
|
|
287
|
-
# works, distribute, and otherwise use Python alone or in any derivative version, provided, however, that PSF's License
|
|
288
|
-
# Agreement and PSF's notice of copyright, i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009,
|
|
289
|
-
# 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023 Python Software Foundation; All
|
|
290
|
-
# Rights Reserved" are retained in Python alone or in any derivative version prepared by Licensee.
|
|
291
|
-
#
|
|
292
|
-
# 3. In the event Licensee prepares a derivative work that is based on or incorporates Python or any part thereof, and
|
|
293
|
-
# wants to make the derivative work available to others as provided herein, then Licensee hereby agrees to include in
|
|
294
|
-
# any such work a brief summary of the changes made to Python.
|
|
295
|
-
#
|
|
296
|
-
# 4. PSF is making Python available to Licensee on an "AS IS" basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES,
|
|
297
|
-
# EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY
|
|
298
|
-
# OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT INFRINGE ANY THIRD PARTY
|
|
299
|
-
# RIGHTS.
|
|
300
|
-
#
|
|
301
|
-
# 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL
|
|
302
|
-
# DAMAGES OR LOSS AS A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, OR ANY DERIVATIVE THEREOF, EVEN IF
|
|
303
|
-
# ADVISED OF THE POSSIBILITY THEREOF.
|
|
273
|
+
# ../../packaging/versions.py
|
|
274
|
+
# Copyright (c) Donald Stufft and individual contributors.
|
|
275
|
+
# All rights reserved.
|
|
304
276
|
#
|
|
305
|
-
#
|
|
277
|
+
# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
|
|
278
|
+
# following conditions are met:
|
|
306
279
|
#
|
|
307
|
-
#
|
|
308
|
-
#
|
|
309
|
-
# name in a trademark sense to endorse or promote products or services of Licensee, or any third party.
|
|
280
|
+
# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the
|
|
281
|
+
# following disclaimer.
|
|
310
282
|
#
|
|
311
|
-
#
|
|
312
|
-
#
|
|
283
|
+
# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
|
|
284
|
+
# following disclaimer in the documentation and/or other materials provided with the distribution.
|
|
313
285
|
#
|
|
314
|
-
#
|
|
286
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
|
287
|
+
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
288
|
+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
289
|
+
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
290
|
+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
|
291
|
+
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
292
|
+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. This file is dual licensed under the terms of the
|
|
293
|
+
# Apache License, Version 2.0, and the BSD License. See the LICENSE file in the root of this repository for complete
|
|
294
|
+
# details.
|
|
295
|
+
# https://github.com/pypa/packaging/blob/2c885fe91a54559e2382902dce28428ad2887be5/src/packaging/version.py
|
|
315
296
|
|
|
316
297
|
|
|
317
298
|
##
|
|
318
299
|
|
|
319
300
|
|
|
320
|
-
|
|
301
|
+
class InfinityVersionType:
|
|
302
|
+
def __repr__(self) -> str:
|
|
303
|
+
return 'Infinity'
|
|
321
304
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
0
|
|
325
|
-
(?:
|
|
326
|
-
x[0-9A-Fa-f](?:_?[0-9A-Fa-f])* # hex
|
|
327
|
-
|
|
|
328
|
-
b[01](?:_?[01])* # bin
|
|
329
|
-
|
|
|
330
|
-
o[0-7](?:_?[0-7])* # oct
|
|
331
|
-
)
|
|
332
|
-
|
|
|
333
|
-
[+-]?(?:0|[1-9](?:_?[0-9])*) # dec, integer part
|
|
334
|
-
(?P<floatpart>
|
|
335
|
-
(?:\.[0-9](?:_?[0-9])*)? # optional fractional part
|
|
336
|
-
(?:[eE][+-]?[0-9](?:_?[0-9])*)? # optional exponent part
|
|
337
|
-
)
|
|
338
|
-
""",
|
|
339
|
-
flags=re.VERBOSE,
|
|
340
|
-
)
|
|
341
|
-
TOML_RE_LOCALTIME = re.compile(_TOML_TIME_RE_STR)
|
|
342
|
-
TOML_RE_DATETIME = re.compile(
|
|
343
|
-
rf"""
|
|
344
|
-
([0-9]{{4}})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01]) # date, e.g. 1988-10-27
|
|
345
|
-
(?:
|
|
346
|
-
[Tt ]
|
|
347
|
-
{_TOML_TIME_RE_STR}
|
|
348
|
-
(?:([Zz])|([+-])([01][0-9]|2[0-3]):([0-5][0-9]))? # optional time offset
|
|
349
|
-
)?
|
|
350
|
-
""",
|
|
351
|
-
flags=re.VERBOSE,
|
|
352
|
-
)
|
|
305
|
+
def __hash__(self) -> int:
|
|
306
|
+
return hash(repr(self))
|
|
353
307
|
|
|
308
|
+
def __lt__(self, other: object) -> bool:
|
|
309
|
+
return False
|
|
354
310
|
|
|
355
|
-
def
|
|
356
|
-
|
|
311
|
+
def __le__(self, other: object) -> bool:
|
|
312
|
+
return False
|
|
357
313
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
(
|
|
361
|
-
year_str,
|
|
362
|
-
month_str,
|
|
363
|
-
day_str,
|
|
364
|
-
hour_str,
|
|
365
|
-
minute_str,
|
|
366
|
-
sec_str,
|
|
367
|
-
micros_str,
|
|
368
|
-
zulu_time,
|
|
369
|
-
offset_sign_str,
|
|
370
|
-
offset_hour_str,
|
|
371
|
-
offset_minute_str,
|
|
372
|
-
) = match.groups()
|
|
373
|
-
year, month, day = int(year_str), int(month_str), int(day_str)
|
|
374
|
-
if hour_str is None:
|
|
375
|
-
return datetime.date(year, month, day)
|
|
376
|
-
hour, minute, sec = int(hour_str), int(minute_str), int(sec_str)
|
|
377
|
-
micros = int(micros_str.ljust(6, '0')) if micros_str else 0
|
|
378
|
-
if offset_sign_str:
|
|
379
|
-
tz: ta.Optional[datetime.tzinfo] = toml_cached_tz(
|
|
380
|
-
offset_hour_str, offset_minute_str, offset_sign_str,
|
|
381
|
-
)
|
|
382
|
-
elif zulu_time:
|
|
383
|
-
tz = datetime.UTC
|
|
384
|
-
else: # local date-time
|
|
385
|
-
tz = None
|
|
386
|
-
return datetime.datetime(year, month, day, hour, minute, sec, micros, tzinfo=tz)
|
|
314
|
+
def __eq__(self, other: object) -> bool:
|
|
315
|
+
return isinstance(other, self.__class__)
|
|
387
316
|
|
|
317
|
+
def __gt__(self, other: object) -> bool:
|
|
318
|
+
return True
|
|
388
319
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
sign = 1 if sign_str == '+' else -1
|
|
392
|
-
return datetime.timezone(
|
|
393
|
-
datetime.timedelta(
|
|
394
|
-
hours=sign * int(hour_str),
|
|
395
|
-
minutes=sign * int(minute_str),
|
|
396
|
-
),
|
|
397
|
-
)
|
|
320
|
+
def __ge__(self, other: object) -> bool:
|
|
321
|
+
return True
|
|
398
322
|
|
|
323
|
+
def __neg__(self: object) -> 'NegativeInfinityVersionType':
|
|
324
|
+
return NegativeInfinityVersion
|
|
399
325
|
|
|
400
|
-
def toml_match_to_localtime(match: re.Match) -> datetime.time:
|
|
401
|
-
hour_str, minute_str, sec_str, micros_str = match.groups()
|
|
402
|
-
micros = int(micros_str.ljust(6, '0')) if micros_str else 0
|
|
403
|
-
return datetime.time(int(hour_str), int(minute_str), int(sec_str), micros)
|
|
404
326
|
|
|
327
|
+
InfinityVersion = InfinityVersionType()
|
|
405
328
|
|
|
406
|
-
def toml_match_to_number(match: re.Match, parse_float: TomlParseFloat) -> ta.Any:
|
|
407
|
-
if match.group('floatpart'):
|
|
408
|
-
return parse_float(match.group())
|
|
409
|
-
return int(match.group(), 0)
|
|
410
329
|
|
|
330
|
+
class NegativeInfinityVersionType:
|
|
331
|
+
def __repr__(self) -> str:
|
|
332
|
+
return '-Infinity'
|
|
411
333
|
|
|
412
|
-
|
|
334
|
+
def __hash__(self) -> int:
|
|
335
|
+
return hash(repr(self))
|
|
413
336
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
TOML_ILLEGAL_BASIC_STR_CHARS = TOML_ASCII_CTRL - frozenset('\t')
|
|
417
|
-
TOML_ILLEGAL_MULTILINE_BASIC_STR_CHARS = TOML_ASCII_CTRL - frozenset('\t\n')
|
|
337
|
+
def __lt__(self, other: object) -> bool:
|
|
338
|
+
return True
|
|
418
339
|
|
|
419
|
-
|
|
420
|
-
|
|
340
|
+
def __le__(self, other: object) -> bool:
|
|
341
|
+
return True
|
|
421
342
|
|
|
422
|
-
|
|
343
|
+
def __eq__(self, other: object) -> bool:
|
|
344
|
+
return isinstance(other, self.__class__)
|
|
423
345
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
TOML_BARE_KEY_CHARS = frozenset(string.ascii_letters + string.digits + '-_')
|
|
427
|
-
TOML_KEY_INITIAL_CHARS = TOML_BARE_KEY_CHARS | frozenset("\"'")
|
|
428
|
-
TOML_HEXDIGIT_CHARS = frozenset(string.hexdigits)
|
|
346
|
+
def __gt__(self, other: object) -> bool:
|
|
347
|
+
return False
|
|
429
348
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
'\\b': '\u0008', # backspace
|
|
433
|
-
'\\t': '\u0009', # tab
|
|
434
|
-
'\\n': '\u000A', # linefeed
|
|
435
|
-
'\\f': '\u000C', # form feed
|
|
436
|
-
'\\r': '\u000D', # carriage return
|
|
437
|
-
'\\"': '\u0022', # quote
|
|
438
|
-
'\\\\': '\u005C', # backslash
|
|
439
|
-
},
|
|
440
|
-
)
|
|
349
|
+
def __ge__(self, other: object) -> bool:
|
|
350
|
+
return False
|
|
441
351
|
|
|
352
|
+
def __neg__(self: object) -> InfinityVersionType:
|
|
353
|
+
return InfinityVersion
|
|
442
354
|
|
|
443
|
-
class TomlDecodeError(ValueError):
|
|
444
|
-
"""An error raised if a document is not valid TOML."""
|
|
445
355
|
|
|
356
|
+
NegativeInfinityVersion = NegativeInfinityVersionType()
|
|
446
357
|
|
|
447
|
-
def toml_load(fp: ta.BinaryIO, /, *, parse_float: TomlParseFloat = float) -> ta.Dict[str, ta.Any]:
|
|
448
|
-
"""Parse TOML from a binary file object."""
|
|
449
|
-
b = fp.read()
|
|
450
|
-
try:
|
|
451
|
-
s = b.decode()
|
|
452
|
-
except AttributeError:
|
|
453
|
-
raise TypeError("File must be opened in binary mode, e.g. use `open('foo.toml', 'rb')`") from None
|
|
454
|
-
return toml_loads(s, parse_float=parse_float)
|
|
455
358
|
|
|
359
|
+
##
|
|
456
360
|
|
|
457
|
-
def toml_loads(s: str, /, *, parse_float: TomlParseFloat = float) -> ta.Dict[str, ta.Any]: # noqa: C901
|
|
458
|
-
"""Parse TOML from a string."""
|
|
459
361
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
362
|
+
class _Version(ta.NamedTuple):
|
|
363
|
+
epoch: int
|
|
364
|
+
release: ta.Tuple[int, ...]
|
|
365
|
+
dev: ta.Optional[ta.Tuple[str, int]]
|
|
366
|
+
pre: ta.Optional[ta.Tuple[str, int]]
|
|
367
|
+
post: ta.Optional[ta.Tuple[str, int]]
|
|
368
|
+
local: ta.Optional[VersionLocalType]
|
|
466
369
|
|
|
467
|
-
# Parse one statement at a time (typically means one line in TOML source)
|
|
468
|
-
while True:
|
|
469
|
-
# 1. Skip line leading whitespace
|
|
470
|
-
pos = toml_skip_chars(src, pos, TOML_WS)
|
|
471
370
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
# - end of line
|
|
475
|
-
# - comment
|
|
476
|
-
# - key/value pair
|
|
477
|
-
# - append dict to list (and move to its namespace)
|
|
478
|
-
# - create dict (and move to its namespace)
|
|
479
|
-
# Skip trailing whitespace when applicable.
|
|
480
|
-
try:
|
|
481
|
-
char = src[pos]
|
|
482
|
-
except IndexError:
|
|
483
|
-
break
|
|
484
|
-
if char == '\n':
|
|
485
|
-
pos += 1
|
|
486
|
-
continue
|
|
487
|
-
if char in TOML_KEY_INITIAL_CHARS:
|
|
488
|
-
pos = toml_key_value_rule(src, pos, out, header, parse_float)
|
|
489
|
-
pos = toml_skip_chars(src, pos, TOML_WS)
|
|
490
|
-
elif char == '[':
|
|
491
|
-
try:
|
|
492
|
-
second_char: ta.Optional[str] = src[pos + 1]
|
|
493
|
-
except IndexError:
|
|
494
|
-
second_char = None
|
|
495
|
-
out.flags.finalize_pending()
|
|
496
|
-
if second_char == '[':
|
|
497
|
-
pos, header = toml_create_list_rule(src, pos, out)
|
|
498
|
-
else:
|
|
499
|
-
pos, header = toml_create_dict_rule(src, pos, out)
|
|
500
|
-
pos = toml_skip_chars(src, pos, TOML_WS)
|
|
501
|
-
elif char != '#':
|
|
502
|
-
raise toml_suffixed_err(src, pos, 'Invalid statement')
|
|
371
|
+
class InvalidVersion(ValueError): # noqa
|
|
372
|
+
pass
|
|
503
373
|
|
|
504
|
-
# 3. Skip comment
|
|
505
|
-
pos = toml_skip_comment(src, pos)
|
|
506
374
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
char = src[pos]
|
|
510
|
-
except IndexError:
|
|
511
|
-
break
|
|
512
|
-
if char != '\n':
|
|
513
|
-
raise toml_suffixed_err(
|
|
514
|
-
src, pos, 'Expected newline or end of document after a statement',
|
|
515
|
-
)
|
|
516
|
-
pos += 1
|
|
375
|
+
class _BaseVersion:
|
|
376
|
+
_key: ta.Tuple[ta.Any, ...]
|
|
517
377
|
|
|
518
|
-
|
|
378
|
+
def __hash__(self) -> int:
|
|
379
|
+
return hash(self._key)
|
|
519
380
|
|
|
381
|
+
def __lt__(self, other: '_BaseVersion') -> bool:
|
|
382
|
+
if not isinstance(other, _BaseVersion):
|
|
383
|
+
return NotImplemented # type: ignore
|
|
384
|
+
return self._key < other._key
|
|
520
385
|
|
|
521
|
-
|
|
522
|
-
|
|
386
|
+
def __le__(self, other: '_BaseVersion') -> bool:
|
|
387
|
+
if not isinstance(other, _BaseVersion):
|
|
388
|
+
return NotImplemented # type: ignore
|
|
389
|
+
return self._key <= other._key
|
|
523
390
|
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
391
|
+
def __eq__(self, other: object) -> bool:
|
|
392
|
+
if not isinstance(other, _BaseVersion):
|
|
393
|
+
return NotImplemented
|
|
394
|
+
return self._key == other._key
|
|
528
395
|
|
|
529
|
-
def
|
|
530
|
-
|
|
531
|
-
|
|
396
|
+
def __ge__(self, other: '_BaseVersion') -> bool:
|
|
397
|
+
if not isinstance(other, _BaseVersion):
|
|
398
|
+
return NotImplemented # type: ignore
|
|
399
|
+
return self._key >= other._key
|
|
532
400
|
|
|
533
|
-
def
|
|
534
|
-
|
|
401
|
+
def __gt__(self, other: '_BaseVersion') -> bool:
|
|
402
|
+
if not isinstance(other, _BaseVersion):
|
|
403
|
+
return NotImplemented # type: ignore
|
|
404
|
+
return self._key > other._key
|
|
535
405
|
|
|
536
|
-
def
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
self.
|
|
406
|
+
def __ne__(self, other: object) -> bool:
|
|
407
|
+
if not isinstance(other, _BaseVersion):
|
|
408
|
+
return NotImplemented
|
|
409
|
+
return self._key != other._key
|
|
540
410
|
|
|
541
|
-
def unset_all(self, key: TomlKey) -> None:
|
|
542
|
-
cont = self._flags
|
|
543
|
-
for k in key[:-1]:
|
|
544
|
-
if k not in cont:
|
|
545
|
-
return
|
|
546
|
-
cont = cont[k]['nested']
|
|
547
|
-
cont.pop(key[-1], None)
|
|
548
411
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
412
|
+
_VERSION_PATTERN = r"""
|
|
413
|
+
v?
|
|
414
|
+
(?:
|
|
415
|
+
(?:(?P<epoch>[0-9]+)!)?
|
|
416
|
+
(?P<release>[0-9]+(?:\.[0-9]+)*)
|
|
417
|
+
(?P<pre>
|
|
418
|
+
[-_\.]?
|
|
419
|
+
(?P<pre_l>alpha|a|beta|b|preview|pre|c|rc)
|
|
420
|
+
[-_\.]?
|
|
421
|
+
(?P<pre_n>[0-9]+)?
|
|
422
|
+
)?
|
|
423
|
+
(?P<post>
|
|
424
|
+
(?:-(?P<post_n1>[0-9]+))
|
|
425
|
+
|
|
|
426
|
+
(?:
|
|
427
|
+
[-_\.]?
|
|
428
|
+
(?P<post_l>post|rev|r)
|
|
429
|
+
[-_\.]?
|
|
430
|
+
(?P<post_n2>[0-9]+)?
|
|
431
|
+
)
|
|
432
|
+
)?
|
|
433
|
+
(?P<dev>
|
|
434
|
+
[-_\.]?
|
|
435
|
+
(?P<dev_l>dev)
|
|
436
|
+
[-_\.]?
|
|
437
|
+
(?P<dev_n>[0-9]+)?
|
|
438
|
+
)?
|
|
439
|
+
)
|
|
440
|
+
(?:\+(?P<local>[a-z0-9]+(?:[-_\.][a-z0-9]+)*))?
|
|
441
|
+
"""
|
|
559
442
|
|
|
560
|
-
|
|
561
|
-
if not key:
|
|
562
|
-
return False # document root has no flags
|
|
563
|
-
cont = self._flags
|
|
564
|
-
for k in key[:-1]:
|
|
565
|
-
if k not in cont:
|
|
566
|
-
return False
|
|
567
|
-
inner_cont = cont[k]
|
|
568
|
-
if flag in inner_cont['recursive_flags']:
|
|
569
|
-
return True
|
|
570
|
-
cont = inner_cont['nested']
|
|
571
|
-
key_stem = key[-1]
|
|
572
|
-
if key_stem in cont:
|
|
573
|
-
cont = cont[key_stem]
|
|
574
|
-
return flag in cont['flags'] or flag in cont['recursive_flags']
|
|
575
|
-
return False
|
|
443
|
+
VERSION_PATTERN = _VERSION_PATTERN
|
|
576
444
|
|
|
577
445
|
|
|
578
|
-
class
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
self.dict: ta.Dict[str, ta.Any] = {}
|
|
446
|
+
class Version(_BaseVersion):
|
|
447
|
+
_regex = re.compile(r'^\s*' + VERSION_PATTERN + r'\s*$', re.VERBOSE | re.IGNORECASE)
|
|
448
|
+
_key: VersionCmpKey
|
|
582
449
|
|
|
583
|
-
def
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
access_lists: bool = True,
|
|
588
|
-
) -> dict:
|
|
589
|
-
cont: ta.Any = self.dict
|
|
590
|
-
for k in key:
|
|
591
|
-
if k not in cont:
|
|
592
|
-
cont[k] = {}
|
|
593
|
-
cont = cont[k]
|
|
594
|
-
if access_lists and isinstance(cont, list):
|
|
595
|
-
cont = cont[-1]
|
|
596
|
-
if not isinstance(cont, dict):
|
|
597
|
-
raise KeyError('There is no nest behind this key')
|
|
598
|
-
return cont
|
|
450
|
+
def __init__(self, version: str) -> None:
|
|
451
|
+
match = self._regex.search(version)
|
|
452
|
+
if not match:
|
|
453
|
+
raise InvalidVersion(f"Invalid version: '{version}'")
|
|
599
454
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
else:
|
|
609
|
-
cont[last_key] = [{}]
|
|
455
|
+
self._version = _Version(
|
|
456
|
+
epoch=int(match.group('epoch')) if match.group('epoch') else 0,
|
|
457
|
+
release=tuple(int(i) for i in match.group('release').split('.')),
|
|
458
|
+
pre=_parse_letter_version(match.group('pre_l'), match.group('pre_n')),
|
|
459
|
+
post=_parse_letter_version(match.group('post_l'), match.group('post_n1') or match.group('post_n2')),
|
|
460
|
+
dev=_parse_letter_version(match.group('dev_l'), match.group('dev_n')),
|
|
461
|
+
local=_parse_local_version(match.group('local')),
|
|
462
|
+
)
|
|
610
463
|
|
|
464
|
+
self._key = _version_cmpkey(
|
|
465
|
+
self._version.epoch,
|
|
466
|
+
self._version.release,
|
|
467
|
+
self._version.pre,
|
|
468
|
+
self._version.post,
|
|
469
|
+
self._version.dev,
|
|
470
|
+
self._version.local,
|
|
471
|
+
)
|
|
611
472
|
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
flags: TomlFlags
|
|
473
|
+
def __repr__(self) -> str:
|
|
474
|
+
return f"<Version('{self}')>"
|
|
615
475
|
|
|
476
|
+
def __str__(self) -> str:
|
|
477
|
+
parts = []
|
|
616
478
|
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
while src[pos] in chars:
|
|
620
|
-
pos += 1
|
|
621
|
-
except IndexError:
|
|
622
|
-
pass
|
|
623
|
-
return pos
|
|
479
|
+
if self.epoch != 0:
|
|
480
|
+
parts.append(f'{self.epoch}!')
|
|
624
481
|
|
|
482
|
+
parts.append('.'.join(str(x) for x in self.release))
|
|
625
483
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
pos: TomlPos,
|
|
629
|
-
expect: str,
|
|
630
|
-
*,
|
|
631
|
-
error_on: ta.FrozenSet[str],
|
|
632
|
-
error_on_eof: bool,
|
|
633
|
-
) -> TomlPos:
|
|
634
|
-
try:
|
|
635
|
-
new_pos = src.index(expect, pos)
|
|
636
|
-
except ValueError:
|
|
637
|
-
new_pos = len(src)
|
|
638
|
-
if error_on_eof:
|
|
639
|
-
raise toml_suffixed_err(src, new_pos, f'Expected {expect!r}') from None
|
|
484
|
+
if self.pre is not None:
|
|
485
|
+
parts.append(''.join(str(x) for x in self.pre))
|
|
640
486
|
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
pos += 1
|
|
644
|
-
raise toml_suffixed_err(src, pos, f'Found invalid character {src[pos]!r}')
|
|
645
|
-
return new_pos
|
|
487
|
+
if self.post is not None:
|
|
488
|
+
parts.append(f'.post{self.post}')
|
|
646
489
|
|
|
490
|
+
if self.dev is not None:
|
|
491
|
+
parts.append(f'.dev{self.dev}')
|
|
647
492
|
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
char: ta.Optional[str] = src[pos]
|
|
651
|
-
except IndexError:
|
|
652
|
-
char = None
|
|
653
|
-
if char == '#':
|
|
654
|
-
return toml_skip_until(
|
|
655
|
-
src, pos + 1, '\n', error_on=TOML_ILLEGAL_COMMENT_CHARS, error_on_eof=False,
|
|
656
|
-
)
|
|
657
|
-
return pos
|
|
493
|
+
if self.local is not None:
|
|
494
|
+
parts.append(f'+{self.local}')
|
|
658
495
|
|
|
496
|
+
return ''.join(parts)
|
|
659
497
|
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
pos = toml_skip_chars(src, pos, TOML_WS_AND_NEWLINE)
|
|
664
|
-
pos = toml_skip_comment(src, pos)
|
|
665
|
-
if pos == pos_before_skip:
|
|
666
|
-
return pos
|
|
498
|
+
@property
|
|
499
|
+
def epoch(self) -> int:
|
|
500
|
+
return self._version.epoch
|
|
667
501
|
|
|
502
|
+
@property
|
|
503
|
+
def release(self) -> ta.Tuple[int, ...]:
|
|
504
|
+
return self._version.release
|
|
668
505
|
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
pos, key = toml_parse_key(src, pos)
|
|
506
|
+
@property
|
|
507
|
+
def pre(self) -> ta.Optional[ta.Tuple[str, int]]:
|
|
508
|
+
return self._version.pre
|
|
673
509
|
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
try:
|
|
678
|
-
out.data.get_or_create_nest(key)
|
|
679
|
-
except KeyError:
|
|
680
|
-
raise toml_suffixed_err(src, pos, 'Cannot overwrite a value') from None
|
|
510
|
+
@property
|
|
511
|
+
def post(self) -> ta.Optional[int]:
|
|
512
|
+
return self._version.post[1] if self._version.post else None
|
|
681
513
|
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
514
|
+
@property
|
|
515
|
+
def dev(self) -> ta.Optional[int]:
|
|
516
|
+
return self._version.dev[1] if self._version.dev else None
|
|
685
517
|
|
|
518
|
+
@property
|
|
519
|
+
def local(self) -> ta.Optional[str]:
|
|
520
|
+
if self._version.local:
|
|
521
|
+
return '.'.join(str(x) for x in self._version.local)
|
|
522
|
+
else:
|
|
523
|
+
return None
|
|
686
524
|
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
pos, key = toml_parse_key(src, pos)
|
|
525
|
+
@property
|
|
526
|
+
def public(self) -> str:
|
|
527
|
+
return str(self).split('+', 1)[0]
|
|
691
528
|
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
out.flags.unset_all(key)
|
|
696
|
-
# ...but this key precisely is still prohibited from table declaration
|
|
697
|
-
out.flags.set(key, TomlFlags.EXPLICIT_NEST, recursive=False)
|
|
698
|
-
try:
|
|
699
|
-
out.data.append_nest_to_list(key)
|
|
700
|
-
except KeyError:
|
|
701
|
-
raise toml_suffixed_err(src, pos, 'Cannot overwrite a value') from None
|
|
529
|
+
@property
|
|
530
|
+
def base_version(self) -> str:
|
|
531
|
+
parts = []
|
|
702
532
|
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
return pos + 2, key
|
|
533
|
+
if self.epoch != 0:
|
|
534
|
+
parts.append(f'{self.epoch}!')
|
|
706
535
|
|
|
536
|
+
parts.append('.'.join(str(x) for x in self.release))
|
|
707
537
|
|
|
708
|
-
|
|
709
|
-
src: str,
|
|
710
|
-
pos: TomlPos,
|
|
711
|
-
out: TomlOutput,
|
|
712
|
-
header: TomlKey,
|
|
713
|
-
parse_float: TomlParseFloat,
|
|
714
|
-
) -> TomlPos:
|
|
715
|
-
pos, key, value = toml_parse_key_value_pair(src, pos, parse_float)
|
|
716
|
-
key_parent, key_stem = key[:-1], key[-1]
|
|
717
|
-
abs_key_parent = header + key_parent
|
|
538
|
+
return ''.join(parts)
|
|
718
539
|
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
if out.flags.is_(cont_key, TomlFlags.EXPLICIT_NEST):
|
|
723
|
-
raise toml_suffixed_err(src, pos, f'Cannot redefine namespace {cont_key}')
|
|
724
|
-
# Containers in the relative path can't be opened with the table syntax or dotted key/value syntax in following
|
|
725
|
-
# table sections.
|
|
726
|
-
out.flags.add_pending(cont_key, TomlFlags.EXPLICIT_NEST)
|
|
540
|
+
@property
|
|
541
|
+
def is_prerelease(self) -> bool:
|
|
542
|
+
return self.dev is not None or self.pre is not None
|
|
727
543
|
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
pos,
|
|
732
|
-
f'Cannot mutate immutable namespace {abs_key_parent}',
|
|
733
|
-
)
|
|
544
|
+
@property
|
|
545
|
+
def is_postrelease(self) -> bool:
|
|
546
|
+
return self.post is not None
|
|
734
547
|
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
raise toml_suffixed_err(src, pos, 'Cannot overwrite a value') from None
|
|
739
|
-
if key_stem in nest:
|
|
740
|
-
raise toml_suffixed_err(src, pos, 'Cannot overwrite a value')
|
|
741
|
-
# Mark inline table and array namespaces recursively immutable
|
|
742
|
-
if isinstance(value, (dict, list)):
|
|
743
|
-
out.flags.set(header + key, TomlFlags.FROZEN, recursive=True)
|
|
744
|
-
nest[key_stem] = value
|
|
745
|
-
return pos
|
|
548
|
+
@property
|
|
549
|
+
def is_devrelease(self) -> bool:
|
|
550
|
+
return self.dev is not None
|
|
746
551
|
|
|
552
|
+
@property
|
|
553
|
+
def major(self) -> int:
|
|
554
|
+
return self.release[0] if len(self.release) >= 1 else 0
|
|
747
555
|
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
parse_float: TomlParseFloat,
|
|
752
|
-
) -> ta.Tuple[TomlPos, TomlKey, ta.Any]:
|
|
753
|
-
pos, key = toml_parse_key(src, pos)
|
|
754
|
-
try:
|
|
755
|
-
char: ta.Optional[str] = src[pos]
|
|
756
|
-
except IndexError:
|
|
757
|
-
char = None
|
|
758
|
-
if char != '=':
|
|
759
|
-
raise toml_suffixed_err(src, pos, "Expected '=' after a key in a key/value pair")
|
|
760
|
-
pos += 1
|
|
761
|
-
pos = toml_skip_chars(src, pos, TOML_WS)
|
|
762
|
-
pos, value = toml_parse_value(src, pos, parse_float)
|
|
763
|
-
return pos, key, value
|
|
556
|
+
@property
|
|
557
|
+
def minor(self) -> int:
|
|
558
|
+
return self.release[1] if len(self.release) >= 2 else 0
|
|
764
559
|
|
|
560
|
+
@property
|
|
561
|
+
def micro(self) -> int:
|
|
562
|
+
return self.release[2] if len(self.release) >= 3 else 0
|
|
765
563
|
|
|
766
|
-
def toml_parse_key(src: str, pos: TomlPos) -> ta.Tuple[TomlPos, TomlKey]:
|
|
767
|
-
pos, key_part = toml_parse_key_part(src, pos)
|
|
768
|
-
key: TomlKey = (key_part,)
|
|
769
|
-
pos = toml_skip_chars(src, pos, TOML_WS)
|
|
770
|
-
while True:
|
|
771
|
-
try:
|
|
772
|
-
char: ta.Optional[str] = src[pos]
|
|
773
|
-
except IndexError:
|
|
774
|
-
char = None
|
|
775
|
-
if char != '.':
|
|
776
|
-
return pos, key
|
|
777
|
-
pos += 1
|
|
778
|
-
pos = toml_skip_chars(src, pos, TOML_WS)
|
|
779
|
-
pos, key_part = toml_parse_key_part(src, pos)
|
|
780
|
-
key += (key_part,)
|
|
781
|
-
pos = toml_skip_chars(src, pos, TOML_WS)
|
|
782
564
|
|
|
565
|
+
def _parse_letter_version(
|
|
566
|
+
letter: ta.Optional[str],
|
|
567
|
+
number: ta.Union[str, bytes, ta.SupportsInt, None],
|
|
568
|
+
) -> ta.Optional[ta.Tuple[str, int]]:
|
|
569
|
+
if letter:
|
|
570
|
+
if number is None:
|
|
571
|
+
number = 0
|
|
783
572
|
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
if char == "'":
|
|
794
|
-
return toml_parse_literal_str(src, pos)
|
|
795
|
-
if char == '"':
|
|
796
|
-
return toml_parse_one_line_basic_str(src, pos)
|
|
797
|
-
raise toml_suffixed_err(src, pos, 'Invalid initial character for a key part')
|
|
573
|
+
letter = letter.lower()
|
|
574
|
+
if letter == 'alpha':
|
|
575
|
+
letter = 'a'
|
|
576
|
+
elif letter == 'beta':
|
|
577
|
+
letter = 'b'
|
|
578
|
+
elif letter in ['c', 'pre', 'preview']:
|
|
579
|
+
letter = 'rc'
|
|
580
|
+
elif letter in ['rev', 'r']:
|
|
581
|
+
letter = 'post'
|
|
798
582
|
|
|
583
|
+
return letter, int(number)
|
|
584
|
+
if not letter and number:
|
|
585
|
+
letter = 'post'
|
|
586
|
+
return letter, int(number)
|
|
799
587
|
|
|
800
|
-
|
|
801
|
-
pos += 1
|
|
802
|
-
return toml_parse_basic_str(src, pos, multiline=False)
|
|
588
|
+
return None
|
|
803
589
|
|
|
804
590
|
|
|
805
|
-
|
|
806
|
-
pos += 1
|
|
807
|
-
array: list = []
|
|
591
|
+
_local_version_separators = re.compile(r'[\._-]')
|
|
808
592
|
|
|
809
|
-
pos = toml_skip_comments_and_array_ws(src, pos)
|
|
810
|
-
if src.startswith(']', pos):
|
|
811
|
-
return pos + 1, array
|
|
812
|
-
while True:
|
|
813
|
-
pos, val = toml_parse_value(src, pos, parse_float)
|
|
814
|
-
array.append(val)
|
|
815
|
-
pos = toml_skip_comments_and_array_ws(src, pos)
|
|
816
593
|
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
594
|
+
def _parse_local_version(local: ta.Optional[str]) -> ta.Optional[VersionLocalType]:
|
|
595
|
+
if local is not None:
|
|
596
|
+
return tuple(
|
|
597
|
+
part.lower() if not part.isdigit() else int(part)
|
|
598
|
+
for part in _local_version_separators.split(local)
|
|
599
|
+
)
|
|
600
|
+
return None
|
|
823
601
|
|
|
824
|
-
pos = toml_skip_comments_and_array_ws(src, pos)
|
|
825
|
-
if src.startswith(']', pos):
|
|
826
|
-
return pos + 1, array
|
|
827
602
|
|
|
603
|
+
def _version_cmpkey(
|
|
604
|
+
epoch: int,
|
|
605
|
+
release: ta.Tuple[int, ...],
|
|
606
|
+
pre: ta.Optional[ta.Tuple[str, int]],
|
|
607
|
+
post: ta.Optional[ta.Tuple[str, int]],
|
|
608
|
+
dev: ta.Optional[ta.Tuple[str, int]],
|
|
609
|
+
local: ta.Optional[VersionLocalType],
|
|
610
|
+
) -> VersionCmpKey:
|
|
611
|
+
_release = tuple(reversed(list(itertools.dropwhile(lambda x: x == 0, reversed(release)))))
|
|
828
612
|
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
613
|
+
if pre is None and post is None and dev is not None:
|
|
614
|
+
_pre: VersionCmpPrePostDevType = NegativeInfinityVersion
|
|
615
|
+
elif pre is None:
|
|
616
|
+
_pre = InfinityVersion
|
|
617
|
+
else:
|
|
618
|
+
_pre = pre
|
|
833
619
|
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
pos, key, value = toml_parse_key_value_pair(src, pos, parse_float)
|
|
839
|
-
key_parent, key_stem = key[:-1], key[-1]
|
|
840
|
-
if flags.is_(key, TomlFlags.FROZEN):
|
|
841
|
-
raise toml_suffixed_err(src, pos, f'Cannot mutate immutable namespace {key}')
|
|
842
|
-
try:
|
|
843
|
-
nest = nested_dict.get_or_create_nest(key_parent, access_lists=False)
|
|
844
|
-
except KeyError:
|
|
845
|
-
raise toml_suffixed_err(src, pos, 'Cannot overwrite a value') from None
|
|
846
|
-
if key_stem in nest:
|
|
847
|
-
raise toml_suffixed_err(src, pos, f'Duplicate inline table key {key_stem!r}')
|
|
848
|
-
nest[key_stem] = value
|
|
849
|
-
pos = toml_skip_chars(src, pos, TOML_WS)
|
|
850
|
-
c = src[pos:pos + 1]
|
|
851
|
-
if c == '}':
|
|
852
|
-
return pos + 1, nested_dict.dict
|
|
853
|
-
if c != ',':
|
|
854
|
-
raise toml_suffixed_err(src, pos, 'Unclosed inline table')
|
|
855
|
-
if isinstance(value, (dict, list)):
|
|
856
|
-
flags.set(key, TomlFlags.FROZEN, recursive=True)
|
|
857
|
-
pos += 1
|
|
858
|
-
pos = toml_skip_chars(src, pos, TOML_WS)
|
|
620
|
+
if post is None:
|
|
621
|
+
_post: VersionCmpPrePostDevType = NegativeInfinityVersion
|
|
622
|
+
else:
|
|
623
|
+
_post = post
|
|
859
624
|
|
|
625
|
+
if dev is None:
|
|
626
|
+
_dev: VersionCmpPrePostDevType = InfinityVersion
|
|
627
|
+
else:
|
|
628
|
+
_dev = dev
|
|
860
629
|
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
multiline: bool = False,
|
|
866
|
-
) -> ta.Tuple[TomlPos, str]:
|
|
867
|
-
escape_id = src[pos:pos + 2]
|
|
868
|
-
pos += 2
|
|
869
|
-
if multiline and escape_id in {'\\ ', '\\\t', '\\\n'}:
|
|
870
|
-
# Skip whitespace until next non-whitespace character or end of the doc. Error if non-whitespace is found before
|
|
871
|
-
# newline.
|
|
872
|
-
if escape_id != '\\\n':
|
|
873
|
-
pos = toml_skip_chars(src, pos, TOML_WS)
|
|
874
|
-
try:
|
|
875
|
-
char = src[pos]
|
|
876
|
-
except IndexError:
|
|
877
|
-
return pos, ''
|
|
878
|
-
if char != '\n':
|
|
879
|
-
raise toml_suffixed_err(src, pos, "Unescaped '\\' in a string")
|
|
880
|
-
pos += 1
|
|
881
|
-
pos = toml_skip_chars(src, pos, TOML_WS_AND_NEWLINE)
|
|
882
|
-
return pos, ''
|
|
883
|
-
if escape_id == '\\u':
|
|
884
|
-
return toml_parse_hex_char(src, pos, 4)
|
|
885
|
-
if escape_id == '\\U':
|
|
886
|
-
return toml_parse_hex_char(src, pos, 8)
|
|
887
|
-
try:
|
|
888
|
-
return pos, TOML_BASIC_STR_ESCAPE_REPLACEMENTS[escape_id]
|
|
889
|
-
except KeyError:
|
|
890
|
-
raise toml_suffixed_err(src, pos, "Unescaped '\\' in a string") from None
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
def toml_parse_basic_str_escape_multiline(src: str, pos: TomlPos) -> ta.Tuple[TomlPos, str]:
|
|
894
|
-
return toml_parse_basic_str_escape(src, pos, multiline=True)
|
|
630
|
+
if local is None:
|
|
631
|
+
_local: VersionCmpLocalType = NegativeInfinityVersion
|
|
632
|
+
else:
|
|
633
|
+
_local = tuple((i, '') if isinstance(i, int) else (NegativeInfinityVersion, i) for i in local)
|
|
895
634
|
|
|
635
|
+
return epoch, _release, _pre, _post, _dev, _local
|
|
896
636
|
|
|
897
|
-
def toml_parse_hex_char(src: str, pos: TomlPos, hex_len: int) -> ta.Tuple[TomlPos, str]:
|
|
898
|
-
hex_str = src[pos:pos + hex_len]
|
|
899
|
-
if len(hex_str) != hex_len or not TOML_HEXDIGIT_CHARS.issuperset(hex_str):
|
|
900
|
-
raise toml_suffixed_err(src, pos, 'Invalid hex value')
|
|
901
|
-
pos += hex_len
|
|
902
|
-
hex_int = int(hex_str, 16)
|
|
903
|
-
if not toml_is_unicode_scalar_value(hex_int):
|
|
904
|
-
raise toml_suffixed_err(src, pos, 'Escaped character is not a Unicode scalar value')
|
|
905
|
-
return pos, chr(hex_int)
|
|
906
637
|
|
|
638
|
+
##
|
|
907
639
|
|
|
908
|
-
def toml_parse_literal_str(src: str, pos: TomlPos) -> ta.Tuple[TomlPos, str]:
|
|
909
|
-
pos += 1 # Skip starting apostrophe
|
|
910
|
-
start_pos = pos
|
|
911
|
-
pos = toml_skip_until(
|
|
912
|
-
src, pos, "'", error_on=TOML_ILLEGAL_LITERAL_STR_CHARS, error_on_eof=True,
|
|
913
|
-
)
|
|
914
|
-
return pos + 1, src[start_pos:pos] # Skip ending apostrophe
|
|
915
640
|
|
|
641
|
+
def canonicalize_version(
|
|
642
|
+
version: ta.Union[Version, str],
|
|
643
|
+
*,
|
|
644
|
+
strip_trailing_zero: bool = True,
|
|
645
|
+
) -> str:
|
|
646
|
+
if isinstance(version, str):
|
|
647
|
+
try:
|
|
648
|
+
parsed = Version(version)
|
|
649
|
+
except InvalidVersion:
|
|
650
|
+
return version
|
|
651
|
+
else:
|
|
652
|
+
parsed = version
|
|
916
653
|
|
|
917
|
-
|
|
918
|
-
pos += 3
|
|
919
|
-
if src.startswith('\n', pos):
|
|
920
|
-
pos += 1
|
|
654
|
+
parts = []
|
|
921
655
|
|
|
922
|
-
if
|
|
923
|
-
|
|
924
|
-
end_pos = toml_skip_until(
|
|
925
|
-
src,
|
|
926
|
-
pos,
|
|
927
|
-
"'''",
|
|
928
|
-
error_on=TOML_ILLEGAL_MULTILINE_LITERAL_STR_CHARS,
|
|
929
|
-
error_on_eof=True,
|
|
930
|
-
)
|
|
931
|
-
result = src[pos:end_pos]
|
|
932
|
-
pos = end_pos + 3
|
|
933
|
-
else:
|
|
934
|
-
delim = '"'
|
|
935
|
-
pos, result = toml_parse_basic_str(src, pos, multiline=True)
|
|
656
|
+
if parsed.epoch != 0:
|
|
657
|
+
parts.append(f'{parsed.epoch}!')
|
|
936
658
|
|
|
937
|
-
|
|
938
|
-
if
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
if not src.startswith(delim, pos):
|
|
942
|
-
return pos, result + delim
|
|
943
|
-
pos += 1
|
|
944
|
-
return pos, result + (delim * 2)
|
|
659
|
+
release_segment = '.'.join(str(x) for x in parsed.release)
|
|
660
|
+
if strip_trailing_zero:
|
|
661
|
+
release_segment = re.sub(r'(\.0)+$', '', release_segment)
|
|
662
|
+
parts.append(release_segment)
|
|
945
663
|
|
|
664
|
+
if parsed.pre is not None:
|
|
665
|
+
parts.append(''.join(str(x) for x in parsed.pre))
|
|
946
666
|
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
error_on = TOML_ILLEGAL_MULTILINE_BASIC_STR_CHARS
|
|
950
|
-
parse_escapes = toml_parse_basic_str_escape_multiline
|
|
951
|
-
else:
|
|
952
|
-
error_on = TOML_ILLEGAL_BASIC_STR_CHARS
|
|
953
|
-
parse_escapes = toml_parse_basic_str_escape
|
|
954
|
-
result = ''
|
|
955
|
-
start_pos = pos
|
|
956
|
-
while True:
|
|
957
|
-
try:
|
|
958
|
-
char = src[pos]
|
|
959
|
-
except IndexError:
|
|
960
|
-
raise toml_suffixed_err(src, pos, 'Unterminated string') from None
|
|
961
|
-
if char == '"':
|
|
962
|
-
if not multiline:
|
|
963
|
-
return pos + 1, result + src[start_pos:pos]
|
|
964
|
-
if src.startswith('"""', pos):
|
|
965
|
-
return pos + 3, result + src[start_pos:pos]
|
|
966
|
-
pos += 1
|
|
967
|
-
continue
|
|
968
|
-
if char == '\\':
|
|
969
|
-
result += src[start_pos:pos]
|
|
970
|
-
pos, parsed_escape = parse_escapes(src, pos)
|
|
971
|
-
result += parsed_escape
|
|
972
|
-
start_pos = pos
|
|
973
|
-
continue
|
|
974
|
-
if char in error_on:
|
|
975
|
-
raise toml_suffixed_err(src, pos, f'Illegal character {char!r}')
|
|
976
|
-
pos += 1
|
|
667
|
+
if parsed.post is not None:
|
|
668
|
+
parts.append(f'.post{parsed.post}')
|
|
977
669
|
|
|
670
|
+
if parsed.dev is not None:
|
|
671
|
+
parts.append(f'.dev{parsed.dev}')
|
|
978
672
|
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
pos: TomlPos,
|
|
982
|
-
parse_float: TomlParseFloat,
|
|
983
|
-
) -> ta.Tuple[TomlPos, ta.Any]:
|
|
984
|
-
try:
|
|
985
|
-
char: ta.Optional[str] = src[pos]
|
|
986
|
-
except IndexError:
|
|
987
|
-
char = None
|
|
673
|
+
if parsed.local is not None:
|
|
674
|
+
parts.append(f'+{parsed.local}')
|
|
988
675
|
|
|
989
|
-
|
|
676
|
+
return ''.join(parts)
|
|
990
677
|
|
|
991
|
-
# Basic strings
|
|
992
|
-
if char == '"':
|
|
993
|
-
if src.startswith('"""', pos):
|
|
994
|
-
return toml_parse_multiline_str(src, pos, literal=False)
|
|
995
|
-
return toml_parse_one_line_basic_str(src, pos)
|
|
996
678
|
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
679
|
+
########################################
|
|
680
|
+
# ../../toml/parser.py
|
|
681
|
+
# SPDX-License-Identifier: MIT
|
|
682
|
+
# SPDX-FileCopyrightText: 2021 Taneli Hukkinen
|
|
683
|
+
# Licensed to PSF under a Contributor Agreement.
|
|
684
|
+
#
|
|
685
|
+
# PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
|
|
686
|
+
# --------------------------------------------
|
|
687
|
+
#
|
|
688
|
+
# 1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), and the Individual or Organization
|
|
689
|
+
# ("Licensee") accessing and otherwise using this software ("Python") in source or binary form and its associated
|
|
690
|
+
# documentation.
|
|
691
|
+
#
|
|
692
|
+
# 2. Subject to the terms and conditions of this License Agreement, PSF hereby grants Licensee a nonexclusive,
|
|
693
|
+
# royalty-free, world-wide license to reproduce, analyze, test, perform and/or display publicly, prepare derivative
|
|
694
|
+
# works, distribute, and otherwise use Python alone or in any derivative version, provided, however, that PSF's License
|
|
695
|
+
# Agreement and PSF's notice of copyright, i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009,
|
|
696
|
+
# 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023 Python Software Foundation; All
|
|
697
|
+
# Rights Reserved" are retained in Python alone or in any derivative version prepared by Licensee.
|
|
698
|
+
#
|
|
699
|
+
# 3. In the event Licensee prepares a derivative work that is based on or incorporates Python or any part thereof, and
|
|
700
|
+
# wants to make the derivative work available to others as provided herein, then Licensee hereby agrees to include in
|
|
701
|
+
# any such work a brief summary of the changes made to Python.
|
|
702
|
+
#
|
|
703
|
+
# 4. PSF is making Python available to Licensee on an "AS IS" basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES,
|
|
704
|
+
# EXPRESS OR IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR WARRANTY
|
|
705
|
+
# OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT INFRINGE ANY THIRD PARTY
|
|
706
|
+
# RIGHTS.
|
|
707
|
+
#
|
|
708
|
+
# 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL
|
|
709
|
+
# DAMAGES OR LOSS AS A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, OR ANY DERIVATIVE THEREOF, EVEN IF
|
|
710
|
+
# ADVISED OF THE POSSIBILITY THEREOF.
|
|
711
|
+
#
|
|
712
|
+
# 6. This License Agreement will automatically terminate upon a material breach of its terms and conditions.
|
|
713
|
+
#
|
|
714
|
+
# 7. Nothing in this License Agreement shall be deemed to create any relationship of agency, partnership, or joint
|
|
715
|
+
# venture between PSF and Licensee. This License Agreement does not grant permission to use PSF trademarks or trade
|
|
716
|
+
# name in a trademark sense to endorse or promote products or services of Licensee, or any third party.
|
|
717
|
+
#
|
|
718
|
+
# 8. By copying, installing or otherwise using Python, Licensee agrees to be bound by the terms and conditions of this
|
|
719
|
+
# License Agreement.
|
|
720
|
+
#
|
|
721
|
+
# https://github.com/python/cpython/blob/f5009b69e0cd94b990270e04e65b9d4d2b365844/Lib/tomllib/_parser.py
|
|
1002
722
|
|
|
1003
|
-
# Booleans
|
|
1004
|
-
if char == 't':
|
|
1005
|
-
if src.startswith('true', pos):
|
|
1006
|
-
return pos + 4, True
|
|
1007
|
-
if char == 'f':
|
|
1008
|
-
if src.startswith('false', pos):
|
|
1009
|
-
return pos + 5, False
|
|
1010
723
|
|
|
1011
|
-
|
|
1012
|
-
if char == '[':
|
|
1013
|
-
return toml_parse_array(src, pos, parse_float)
|
|
724
|
+
##
|
|
1014
725
|
|
|
1015
|
-
# Inline tables
|
|
1016
|
-
if char == '{':
|
|
1017
|
-
return toml_parse_inline_table(src, pos, parse_float)
|
|
1018
726
|
|
|
1019
|
-
|
|
1020
|
-
datetime_match = TOML_RE_DATETIME.match(src, pos)
|
|
1021
|
-
if datetime_match:
|
|
1022
|
-
try:
|
|
1023
|
-
datetime_obj = toml_match_to_datetime(datetime_match)
|
|
1024
|
-
except ValueError as e:
|
|
1025
|
-
raise toml_suffixed_err(src, pos, 'Invalid date or datetime') from e
|
|
1026
|
-
return datetime_match.end(), datetime_obj
|
|
1027
|
-
localtime_match = TOML_RE_LOCALTIME.match(src, pos)
|
|
1028
|
-
if localtime_match:
|
|
1029
|
-
return localtime_match.end(), toml_match_to_localtime(localtime_match)
|
|
727
|
+
_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]*)?'
|
|
1030
728
|
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
729
|
+
TOML_RE_NUMBER = re.compile(
|
|
730
|
+
r"""
|
|
731
|
+
0
|
|
732
|
+
(?:
|
|
733
|
+
x[0-9A-Fa-f](?:_?[0-9A-Fa-f])* # hex
|
|
734
|
+
|
|
|
735
|
+
b[01](?:_?[01])* # bin
|
|
736
|
+
|
|
|
737
|
+
o[0-7](?:_?[0-7])* # oct
|
|
738
|
+
)
|
|
739
|
+
|
|
|
740
|
+
[+-]?(?:0|[1-9](?:_?[0-9])*) # dec, integer part
|
|
741
|
+
(?P<floatpart>
|
|
742
|
+
(?:\.[0-9](?:_?[0-9])*)? # optional fractional part
|
|
743
|
+
(?:[eE][+-]?[0-9](?:_?[0-9])*)? # optional exponent part
|
|
744
|
+
)
|
|
745
|
+
""",
|
|
746
|
+
flags=re.VERBOSE,
|
|
747
|
+
)
|
|
748
|
+
TOML_RE_LOCALTIME = re.compile(_TOML_TIME_RE_STR)
|
|
749
|
+
TOML_RE_DATETIME = re.compile(
|
|
750
|
+
rf"""
|
|
751
|
+
([0-9]{{4}})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01]) # date, e.g. 1988-10-27
|
|
752
|
+
(?:
|
|
753
|
+
[Tt ]
|
|
754
|
+
{_TOML_TIME_RE_STR}
|
|
755
|
+
(?:([Zz])|([+-])([01][0-9]|2[0-3]):([0-5][0-9]))? # optional time offset
|
|
756
|
+
)?
|
|
757
|
+
""",
|
|
758
|
+
flags=re.VERBOSE,
|
|
759
|
+
)
|
|
1036
760
|
|
|
1037
|
-
# Special floats
|
|
1038
|
-
first_three = src[pos:pos + 3]
|
|
1039
|
-
if first_three in {'inf', 'nan'}:
|
|
1040
|
-
return pos + 3, parse_float(first_three)
|
|
1041
|
-
first_four = src[pos:pos + 4]
|
|
1042
|
-
if first_four in {'-inf', '+inf', '-nan', '+nan'}:
|
|
1043
|
-
return pos + 4, parse_float(first_four)
|
|
1044
761
|
|
|
1045
|
-
|
|
762
|
+
def toml_match_to_datetime(match: re.Match) -> ta.Union[datetime.datetime, datetime.date]:
|
|
763
|
+
"""Convert a `RE_DATETIME` match to `datetime.datetime` or `datetime.date`.
|
|
1046
764
|
|
|
765
|
+
Raises ValueError if the match does not correspond to a valid date or datetime.
|
|
766
|
+
"""
|
|
767
|
+
(
|
|
768
|
+
year_str,
|
|
769
|
+
month_str,
|
|
770
|
+
day_str,
|
|
771
|
+
hour_str,
|
|
772
|
+
minute_str,
|
|
773
|
+
sec_str,
|
|
774
|
+
micros_str,
|
|
775
|
+
zulu_time,
|
|
776
|
+
offset_sign_str,
|
|
777
|
+
offset_hour_str,
|
|
778
|
+
offset_minute_str,
|
|
779
|
+
) = match.groups()
|
|
780
|
+
year, month, day = int(year_str), int(month_str), int(day_str)
|
|
781
|
+
if hour_str is None:
|
|
782
|
+
return datetime.date(year, month, day)
|
|
783
|
+
hour, minute, sec = int(hour_str), int(minute_str), int(sec_str)
|
|
784
|
+
micros = int(micros_str.ljust(6, '0')) if micros_str else 0
|
|
785
|
+
if offset_sign_str:
|
|
786
|
+
tz: ta.Optional[datetime.tzinfo] = toml_cached_tz(
|
|
787
|
+
offset_hour_str, offset_minute_str, offset_sign_str,
|
|
788
|
+
)
|
|
789
|
+
elif zulu_time:
|
|
790
|
+
tz = datetime.UTC
|
|
791
|
+
else: # local date-time
|
|
792
|
+
tz = None
|
|
793
|
+
return datetime.datetime(year, month, day, hour, minute, sec, micros, tzinfo=tz)
|
|
1047
794
|
|
|
1048
|
-
def toml_suffixed_err(src: str, pos: TomlPos, msg: str) -> TomlDecodeError:
|
|
1049
|
-
"""Return a `TomlDecodeError` where error message is suffixed with coordinates in source."""
|
|
1050
795
|
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
796
|
+
@functools.lru_cache() # noqa
|
|
797
|
+
def toml_cached_tz(hour_str: str, minute_str: str, sign_str: str) -> datetime.timezone:
|
|
798
|
+
sign = 1 if sign_str == '+' else -1
|
|
799
|
+
return datetime.timezone(
|
|
800
|
+
datetime.timedelta(
|
|
801
|
+
hours=sign * int(hour_str),
|
|
802
|
+
minutes=sign * int(minute_str),
|
|
803
|
+
),
|
|
804
|
+
)
|
|
1060
805
|
|
|
1061
|
-
return TomlDecodeError(f'{msg} (at {coord_repr(src, pos)})')
|
|
1062
806
|
|
|
807
|
+
def toml_match_to_localtime(match: re.Match) -> datetime.time:
|
|
808
|
+
hour_str, minute_str, sec_str, micros_str = match.groups()
|
|
809
|
+
micros = int(micros_str.ljust(6, '0')) if micros_str else 0
|
|
810
|
+
return datetime.time(int(hour_str), int(minute_str), int(sec_str), micros)
|
|
1063
811
|
|
|
1064
|
-
def toml_is_unicode_scalar_value(codepoint: int) -> bool:
|
|
1065
|
-
return (0 <= codepoint <= 55295) or (57344 <= codepoint <= 1114111)
|
|
1066
812
|
|
|
813
|
+
def toml_match_to_number(match: re.Match, parse_float: TomlParseFloat) -> ta.Any:
|
|
814
|
+
if match.group('floatpart'):
|
|
815
|
+
return parse_float(match.group())
|
|
816
|
+
return int(match.group(), 0)
|
|
1067
817
|
|
|
1068
|
-
def toml_make_safe_parse_float(parse_float: TomlParseFloat) -> TomlParseFloat:
|
|
1069
|
-
"""A decorator to make `parse_float` safe.
|
|
1070
818
|
|
|
1071
|
-
|
|
1072
|
-
thus confusing the parser. The returned decorated callable raises `ValueError` instead of returning illegal types.
|
|
1073
|
-
"""
|
|
1074
|
-
# The default `float` callable never returns illegal types. Optimize it.
|
|
1075
|
-
if parse_float is float:
|
|
1076
|
-
return float
|
|
819
|
+
TOML_ASCII_CTRL = frozenset(chr(i) for i in range(32)) | frozenset(chr(127))
|
|
1077
820
|
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
return float_value
|
|
821
|
+
# Neither of these sets include quotation mark or backslash. They are currently handled as separate cases in the parser
|
|
822
|
+
# functions.
|
|
823
|
+
TOML_ILLEGAL_BASIC_STR_CHARS = TOML_ASCII_CTRL - frozenset('\t')
|
|
824
|
+
TOML_ILLEGAL_MULTILINE_BASIC_STR_CHARS = TOML_ASCII_CTRL - frozenset('\t\n')
|
|
1083
825
|
|
|
1084
|
-
|
|
826
|
+
TOML_ILLEGAL_LITERAL_STR_CHARS = TOML_ILLEGAL_BASIC_STR_CHARS
|
|
827
|
+
TOML_ILLEGAL_MULTILINE_LITERAL_STR_CHARS = TOML_ILLEGAL_MULTILINE_BASIC_STR_CHARS
|
|
1085
828
|
|
|
829
|
+
TOML_ILLEGAL_COMMENT_CHARS = TOML_ILLEGAL_BASIC_STR_CHARS
|
|
1086
830
|
|
|
1087
|
-
|
|
1088
|
-
|
|
831
|
+
TOML_WS = frozenset(' \t')
|
|
832
|
+
TOML_WS_AND_NEWLINE = TOML_WS | frozenset('\n')
|
|
833
|
+
TOML_BARE_KEY_CHARS = frozenset(string.ascii_letters + string.digits + '-_')
|
|
834
|
+
TOML_KEY_INITIAL_CHARS = TOML_BARE_KEY_CHARS | frozenset("\"'")
|
|
835
|
+
TOML_HEXDIGIT_CHARS = frozenset(string.hexdigits)
|
|
1089
836
|
|
|
837
|
+
TOML_BASIC_STR_ESCAPE_REPLACEMENTS = types.MappingProxyType(
|
|
838
|
+
{
|
|
839
|
+
'\\b': '\u0008', # backspace
|
|
840
|
+
'\\t': '\u0009', # tab
|
|
841
|
+
'\\n': '\u000A', # linefeed
|
|
842
|
+
'\\f': '\u000C', # form feed
|
|
843
|
+
'\\r': '\u000D', # carriage return
|
|
844
|
+
'\\"': '\u0022', # quote
|
|
845
|
+
'\\\\': '\u005C', # backslash
|
|
846
|
+
},
|
|
847
|
+
)
|
|
1090
848
|
|
|
1091
|
-
class TomlWriter:
|
|
1092
|
-
@dc.dataclass(frozen=True)
|
|
1093
|
-
class Literal:
|
|
1094
|
-
s: str
|
|
1095
849
|
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
self._out = out
|
|
850
|
+
class TomlDecodeError(ValueError):
|
|
851
|
+
"""An error raised if a document is not valid TOML."""
|
|
1099
852
|
|
|
1100
|
-
self._indent = 0
|
|
1101
|
-
self._wrote_indent = False
|
|
1102
853
|
|
|
1103
|
-
|
|
854
|
+
def toml_load(fp: ta.BinaryIO, /, *, parse_float: TomlParseFloat = float) -> ta.Dict[str, ta.Any]:
|
|
855
|
+
"""Parse TOML from a binary file object."""
|
|
856
|
+
b = fp.read()
|
|
857
|
+
try:
|
|
858
|
+
s = b.decode()
|
|
859
|
+
except AttributeError:
|
|
860
|
+
raise TypeError("File must be opened in binary mode, e.g. use `open('foo.toml', 'rb')`") from None
|
|
861
|
+
return toml_loads(s, parse_float=parse_float)
|
|
1104
862
|
|
|
1105
|
-
def _w(self, s: str) -> None:
|
|
1106
|
-
if not self._wrote_indent:
|
|
1107
|
-
self._out.write(' ' * self._indent)
|
|
1108
|
-
self._wrote_indent = True
|
|
1109
|
-
self._out.write(s)
|
|
1110
863
|
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
self._wrote_indent = False
|
|
864
|
+
def toml_loads(s: str, /, *, parse_float: TomlParseFloat = float) -> ta.Dict[str, ta.Any]: # noqa: C901
|
|
865
|
+
"""Parse TOML from a string."""
|
|
1114
866
|
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
867
|
+
# The spec allows converting "\r\n" to "\n", even in string literals. Let's do so to simplify parsing.
|
|
868
|
+
src = s.replace('\r\n', '\n')
|
|
869
|
+
pos = 0
|
|
870
|
+
out = TomlOutput(TomlNestedDict(), TomlFlags())
|
|
871
|
+
header: TomlKey = ()
|
|
872
|
+
parse_float = toml_make_safe_parse_float(parse_float)
|
|
1121
873
|
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
return s
|
|
874
|
+
# Parse one statement at a time (typically means one line in TOML source)
|
|
875
|
+
while True:
|
|
876
|
+
# 1. Skip line leading whitespace
|
|
877
|
+
pos = toml_skip_chars(src, pos, TOML_WS)
|
|
1127
878
|
|
|
1128
|
-
|
|
879
|
+
# 2. Parse rules. Expect one of the following:
|
|
880
|
+
# - end of file
|
|
881
|
+
# - end of line
|
|
882
|
+
# - comment
|
|
883
|
+
# - key/value pair
|
|
884
|
+
# - append dict to list (and move to its namespace)
|
|
885
|
+
# - create dict (and move to its namespace)
|
|
886
|
+
# Skip trailing whitespace when applicable.
|
|
887
|
+
try:
|
|
888
|
+
char = src[pos]
|
|
889
|
+
except IndexError:
|
|
890
|
+
break
|
|
891
|
+
if char == '\n':
|
|
892
|
+
pos += 1
|
|
893
|
+
continue
|
|
894
|
+
if char in TOML_KEY_INITIAL_CHARS:
|
|
895
|
+
pos = toml_key_value_rule(src, pos, out, header, parse_float)
|
|
896
|
+
pos = toml_skip_chars(src, pos, TOML_WS)
|
|
897
|
+
elif char == '[':
|
|
898
|
+
try:
|
|
899
|
+
second_char: ta.Optional[str] = src[pos + 1]
|
|
900
|
+
except IndexError:
|
|
901
|
+
second_char = None
|
|
902
|
+
out.flags.finalize_pending()
|
|
903
|
+
if second_char == '[':
|
|
904
|
+
pos, header = toml_create_list_rule(src, pos, out)
|
|
905
|
+
else:
|
|
906
|
+
pos, header = toml_create_dict_rule(src, pos, out)
|
|
907
|
+
pos = toml_skip_chars(src, pos, TOML_WS)
|
|
908
|
+
elif char != '#':
|
|
909
|
+
raise toml_suffixed_err(src, pos, 'Invalid statement')
|
|
1129
910
|
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
if i:
|
|
1133
|
-
self._nl()
|
|
1134
|
-
self._w('[')
|
|
1135
|
-
self._w(self._maybe_quote(k))
|
|
1136
|
-
self._w(']')
|
|
1137
|
-
self._nl()
|
|
1138
|
-
self.write_table_contents(v)
|
|
911
|
+
# 3. Skip comment
|
|
912
|
+
pos = toml_skip_comment(src, pos)
|
|
1139
913
|
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
914
|
+
# 4. Expect end of line or end of file
|
|
915
|
+
try:
|
|
916
|
+
char = src[pos]
|
|
917
|
+
except IndexError:
|
|
918
|
+
break
|
|
919
|
+
if char != '\n':
|
|
920
|
+
raise toml_suffixed_err(
|
|
921
|
+
src, pos, 'Expected newline or end of document after a statement',
|
|
922
|
+
)
|
|
923
|
+
pos += 1
|
|
1146
924
|
|
|
1147
|
-
|
|
1148
|
-
self._w('[')
|
|
1149
|
-
self._nl()
|
|
1150
|
-
self._indent += 1
|
|
1151
|
-
for e in obj:
|
|
1152
|
-
self.write_value(e)
|
|
1153
|
-
self._w(',')
|
|
1154
|
-
self._nl()
|
|
1155
|
-
self._indent -= 1
|
|
1156
|
-
self._w(']')
|
|
925
|
+
return out.data.dict
|
|
1157
926
|
|
|
1158
|
-
def write_inline_table(self, obj: ta.Mapping) -> None:
|
|
1159
|
-
self._w('{')
|
|
1160
|
-
for i, (k, v) in enumerate(obj.items()):
|
|
1161
|
-
if i:
|
|
1162
|
-
self._w(', ')
|
|
1163
|
-
self.write_key(k)
|
|
1164
|
-
self._w(' = ')
|
|
1165
|
-
self.write_value(v)
|
|
1166
|
-
self._w('}')
|
|
1167
927
|
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
for i, e in enumerate(obj):
|
|
1171
|
-
if i:
|
|
1172
|
-
self._w(', ')
|
|
1173
|
-
self.write_value(e)
|
|
1174
|
-
self._w(']')
|
|
928
|
+
class TomlFlags:
|
|
929
|
+
"""Flags that map to parsed keys/namespaces."""
|
|
1175
930
|
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
self._w(self._maybe_quote(obj.replace('_', '-')))
|
|
1181
|
-
elif isinstance(obj, int):
|
|
1182
|
-
self._w(repr(str(obj)))
|
|
1183
|
-
else:
|
|
1184
|
-
raise TypeError(obj)
|
|
931
|
+
# Marks an immutable namespace (inline array or inline table).
|
|
932
|
+
FROZEN = 0
|
|
933
|
+
# Marks a nest that has been explicitly created and can no longer be opened using the "[table]" syntax.
|
|
934
|
+
EXPLICIT_NEST = 1
|
|
1185
935
|
|
|
1186
|
-
def
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
elif isinstance(obj, (str, int, float)):
|
|
1190
|
-
self._w(repr(obj))
|
|
1191
|
-
elif isinstance(obj, ta.Mapping):
|
|
1192
|
-
self.write_inline_table(obj)
|
|
1193
|
-
elif isinstance(obj, ta.Sequence):
|
|
1194
|
-
if not obj:
|
|
1195
|
-
self.write_inline_array(obj)
|
|
1196
|
-
else:
|
|
1197
|
-
self.write_array(obj)
|
|
1198
|
-
else:
|
|
1199
|
-
raise TypeError(obj)
|
|
936
|
+
def __init__(self) -> None:
|
|
937
|
+
self._flags: ta.Dict[str, dict] = {}
|
|
938
|
+
self._pending_flags: ta.Set[ta.Tuple[TomlKey, int]] = set()
|
|
1200
939
|
|
|
940
|
+
def add_pending(self, key: TomlKey, flag: int) -> None:
|
|
941
|
+
self._pending_flags.add((key, flag))
|
|
1201
942
|
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
#
|
|
1207
|
-
# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
|
|
1208
|
-
# following conditions are met:
|
|
1209
|
-
#
|
|
1210
|
-
# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the
|
|
1211
|
-
# following disclaimer.
|
|
1212
|
-
#
|
|
1213
|
-
# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
|
|
1214
|
-
# following disclaimer in the documentation and/or other materials provided with the distribution.
|
|
1215
|
-
#
|
|
1216
|
-
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
|
1217
|
-
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
1218
|
-
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
1219
|
-
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
1220
|
-
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
|
1221
|
-
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
1222
|
-
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. This file is dual licensed under the terms of the
|
|
1223
|
-
# Apache License, Version 2.0, and the BSD License. See the LICENSE file in the root of this repository for complete
|
|
1224
|
-
# details.
|
|
1225
|
-
# https://github.com/pypa/packaging/blob/2c885fe91a54559e2382902dce28428ad2887be5/src/packaging/version.py
|
|
943
|
+
def finalize_pending(self) -> None:
|
|
944
|
+
for key, flag in self._pending_flags:
|
|
945
|
+
self.set(key, flag, recursive=False)
|
|
946
|
+
self._pending_flags.clear()
|
|
1226
947
|
|
|
948
|
+
def unset_all(self, key: TomlKey) -> None:
|
|
949
|
+
cont = self._flags
|
|
950
|
+
for k in key[:-1]:
|
|
951
|
+
if k not in cont:
|
|
952
|
+
return
|
|
953
|
+
cont = cont[k]['nested']
|
|
954
|
+
cont.pop(key[-1], None)
|
|
1227
955
|
|
|
1228
|
-
|
|
956
|
+
def set(self, key: TomlKey, flag: int, *, recursive: bool) -> None: # noqa: A003
|
|
957
|
+
cont = self._flags
|
|
958
|
+
key_parent, key_stem = key[:-1], key[-1]
|
|
959
|
+
for k in key_parent:
|
|
960
|
+
if k not in cont:
|
|
961
|
+
cont[k] = {'flags': set(), 'recursive_flags': set(), 'nested': {}}
|
|
962
|
+
cont = cont[k]['nested']
|
|
963
|
+
if key_stem not in cont:
|
|
964
|
+
cont[key_stem] = {'flags': set(), 'recursive_flags': set(), 'nested': {}}
|
|
965
|
+
cont[key_stem]['recursive_flags' if recursive else 'flags'].add(flag)
|
|
1229
966
|
|
|
967
|
+
def is_(self, key: TomlKey, flag: int) -> bool:
|
|
968
|
+
if not key:
|
|
969
|
+
return False # document root has no flags
|
|
970
|
+
cont = self._flags
|
|
971
|
+
for k in key[:-1]:
|
|
972
|
+
if k not in cont:
|
|
973
|
+
return False
|
|
974
|
+
inner_cont = cont[k]
|
|
975
|
+
if flag in inner_cont['recursive_flags']:
|
|
976
|
+
return True
|
|
977
|
+
cont = inner_cont['nested']
|
|
978
|
+
key_stem = key[-1]
|
|
979
|
+
if key_stem in cont:
|
|
980
|
+
cont = cont[key_stem]
|
|
981
|
+
return flag in cont['flags'] or flag in cont['recursive_flags']
|
|
982
|
+
return False
|
|
1230
983
|
|
|
1231
|
-
class InfinityVersionType:
|
|
1232
|
-
def __repr__(self) -> str:
|
|
1233
|
-
return 'Infinity'
|
|
1234
984
|
|
|
1235
|
-
|
|
1236
|
-
|
|
985
|
+
class TomlNestedDict:
|
|
986
|
+
def __init__(self) -> None:
|
|
987
|
+
# The parsed content of the TOML document
|
|
988
|
+
self.dict: ta.Dict[str, ta.Any] = {}
|
|
1237
989
|
|
|
1238
|
-
def
|
|
1239
|
-
|
|
990
|
+
def get_or_create_nest(
|
|
991
|
+
self,
|
|
992
|
+
key: TomlKey,
|
|
993
|
+
*,
|
|
994
|
+
access_lists: bool = True,
|
|
995
|
+
) -> dict:
|
|
996
|
+
cont: ta.Any = self.dict
|
|
997
|
+
for k in key:
|
|
998
|
+
if k not in cont:
|
|
999
|
+
cont[k] = {}
|
|
1000
|
+
cont = cont[k]
|
|
1001
|
+
if access_lists and isinstance(cont, list):
|
|
1002
|
+
cont = cont[-1]
|
|
1003
|
+
if not isinstance(cont, dict):
|
|
1004
|
+
raise KeyError('There is no nest behind this key')
|
|
1005
|
+
return cont
|
|
1240
1006
|
|
|
1241
|
-
def
|
|
1242
|
-
|
|
1007
|
+
def append_nest_to_list(self, key: TomlKey) -> None:
|
|
1008
|
+
cont = self.get_or_create_nest(key[:-1])
|
|
1009
|
+
last_key = key[-1]
|
|
1010
|
+
if last_key in cont:
|
|
1011
|
+
list_ = cont[last_key]
|
|
1012
|
+
if not isinstance(list_, list):
|
|
1013
|
+
raise KeyError('An object other than list found behind this key')
|
|
1014
|
+
list_.append({})
|
|
1015
|
+
else:
|
|
1016
|
+
cont[last_key] = [{}]
|
|
1243
1017
|
|
|
1244
|
-
def __eq__(self, other: object) -> bool:
|
|
1245
|
-
return isinstance(other, self.__class__)
|
|
1246
1018
|
|
|
1247
|
-
|
|
1248
|
-
|
|
1019
|
+
class TomlOutput(ta.NamedTuple):
|
|
1020
|
+
data: TomlNestedDict
|
|
1021
|
+
flags: TomlFlags
|
|
1249
1022
|
|
|
1250
|
-
def __ge__(self, other: object) -> bool:
|
|
1251
|
-
return True
|
|
1252
1023
|
|
|
1253
|
-
|
|
1254
|
-
|
|
1024
|
+
def toml_skip_chars(src: str, pos: TomlPos, chars: ta.Iterable[str]) -> TomlPos:
|
|
1025
|
+
try:
|
|
1026
|
+
while src[pos] in chars:
|
|
1027
|
+
pos += 1
|
|
1028
|
+
except IndexError:
|
|
1029
|
+
pass
|
|
1030
|
+
return pos
|
|
1255
1031
|
|
|
1256
1032
|
|
|
1257
|
-
|
|
1033
|
+
def toml_skip_until(
|
|
1034
|
+
src: str,
|
|
1035
|
+
pos: TomlPos,
|
|
1036
|
+
expect: str,
|
|
1037
|
+
*,
|
|
1038
|
+
error_on: ta.FrozenSet[str],
|
|
1039
|
+
error_on_eof: bool,
|
|
1040
|
+
) -> TomlPos:
|
|
1041
|
+
try:
|
|
1042
|
+
new_pos = src.index(expect, pos)
|
|
1043
|
+
except ValueError:
|
|
1044
|
+
new_pos = len(src)
|
|
1045
|
+
if error_on_eof:
|
|
1046
|
+
raise toml_suffixed_err(src, new_pos, f'Expected {expect!r}') from None
|
|
1258
1047
|
|
|
1048
|
+
if not error_on.isdisjoint(src[pos:new_pos]):
|
|
1049
|
+
while src[pos] not in error_on:
|
|
1050
|
+
pos += 1
|
|
1051
|
+
raise toml_suffixed_err(src, pos, f'Found invalid character {src[pos]!r}')
|
|
1052
|
+
return new_pos
|
|
1259
1053
|
|
|
1260
|
-
class NegativeInfinityVersionType:
|
|
1261
|
-
def __repr__(self) -> str:
|
|
1262
|
-
return '-Infinity'
|
|
1263
1054
|
|
|
1264
|
-
|
|
1265
|
-
|
|
1055
|
+
def toml_skip_comment(src: str, pos: TomlPos) -> TomlPos:
|
|
1056
|
+
try:
|
|
1057
|
+
char: ta.Optional[str] = src[pos]
|
|
1058
|
+
except IndexError:
|
|
1059
|
+
char = None
|
|
1060
|
+
if char == '#':
|
|
1061
|
+
return toml_skip_until(
|
|
1062
|
+
src, pos + 1, '\n', error_on=TOML_ILLEGAL_COMMENT_CHARS, error_on_eof=False,
|
|
1063
|
+
)
|
|
1064
|
+
return pos
|
|
1266
1065
|
|
|
1267
|
-
def __lt__(self, other: object) -> bool:
|
|
1268
|
-
return True
|
|
1269
1066
|
|
|
1270
|
-
|
|
1271
|
-
|
|
1067
|
+
def toml_skip_comments_and_array_ws(src: str, pos: TomlPos) -> TomlPos:
|
|
1068
|
+
while True:
|
|
1069
|
+
pos_before_skip = pos
|
|
1070
|
+
pos = toml_skip_chars(src, pos, TOML_WS_AND_NEWLINE)
|
|
1071
|
+
pos = toml_skip_comment(src, pos)
|
|
1072
|
+
if pos == pos_before_skip:
|
|
1073
|
+
return pos
|
|
1272
1074
|
|
|
1273
|
-
def __eq__(self, other: object) -> bool:
|
|
1274
|
-
return isinstance(other, self.__class__)
|
|
1275
1075
|
|
|
1276
|
-
|
|
1277
|
-
|
|
1076
|
+
def toml_create_dict_rule(src: str, pos: TomlPos, out: TomlOutput) -> ta.Tuple[TomlPos, TomlKey]:
|
|
1077
|
+
pos += 1 # Skip "["
|
|
1078
|
+
pos = toml_skip_chars(src, pos, TOML_WS)
|
|
1079
|
+
pos, key = toml_parse_key(src, pos)
|
|
1278
1080
|
|
|
1279
|
-
|
|
1280
|
-
|
|
1081
|
+
if out.flags.is_(key, TomlFlags.EXPLICIT_NEST) or out.flags.is_(key, TomlFlags.FROZEN):
|
|
1082
|
+
raise toml_suffixed_err(src, pos, f'Cannot declare {key} twice')
|
|
1083
|
+
out.flags.set(key, TomlFlags.EXPLICIT_NEST, recursive=False)
|
|
1084
|
+
try:
|
|
1085
|
+
out.data.get_or_create_nest(key)
|
|
1086
|
+
except KeyError:
|
|
1087
|
+
raise toml_suffixed_err(src, pos, 'Cannot overwrite a value') from None
|
|
1281
1088
|
|
|
1282
|
-
|
|
1283
|
-
|
|
1089
|
+
if not src.startswith(']', pos):
|
|
1090
|
+
raise toml_suffixed_err(src, pos, "Expected ']' at the end of a table declaration")
|
|
1091
|
+
return pos + 1, key
|
|
1284
1092
|
|
|
1285
1093
|
|
|
1286
|
-
|
|
1094
|
+
def toml_create_list_rule(src: str, pos: TomlPos, out: TomlOutput) -> ta.Tuple[TomlPos, TomlKey]:
|
|
1095
|
+
pos += 2 # Skip "[["
|
|
1096
|
+
pos = toml_skip_chars(src, pos, TOML_WS)
|
|
1097
|
+
pos, key = toml_parse_key(src, pos)
|
|
1287
1098
|
|
|
1099
|
+
if out.flags.is_(key, TomlFlags.FROZEN):
|
|
1100
|
+
raise toml_suffixed_err(src, pos, f'Cannot mutate immutable namespace {key}')
|
|
1101
|
+
# Free the namespace now that it points to another empty list item...
|
|
1102
|
+
out.flags.unset_all(key)
|
|
1103
|
+
# ...but this key precisely is still prohibited from table declaration
|
|
1104
|
+
out.flags.set(key, TomlFlags.EXPLICIT_NEST, recursive=False)
|
|
1105
|
+
try:
|
|
1106
|
+
out.data.append_nest_to_list(key)
|
|
1107
|
+
except KeyError:
|
|
1108
|
+
raise toml_suffixed_err(src, pos, 'Cannot overwrite a value') from None
|
|
1288
1109
|
|
|
1289
|
-
|
|
1110
|
+
if not src.startswith(']]', pos):
|
|
1111
|
+
raise toml_suffixed_err(src, pos, "Expected ']]' at the end of an array declaration")
|
|
1112
|
+
return pos + 2, key
|
|
1290
1113
|
|
|
1291
1114
|
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1115
|
+
def toml_key_value_rule(
|
|
1116
|
+
src: str,
|
|
1117
|
+
pos: TomlPos,
|
|
1118
|
+
out: TomlOutput,
|
|
1119
|
+
header: TomlKey,
|
|
1120
|
+
parse_float: TomlParseFloat,
|
|
1121
|
+
) -> TomlPos:
|
|
1122
|
+
pos, key, value = toml_parse_key_value_pair(src, pos, parse_float)
|
|
1123
|
+
key_parent, key_stem = key[:-1], key[-1]
|
|
1124
|
+
abs_key_parent = header + key_parent
|
|
1299
1125
|
|
|
1126
|
+
relative_path_cont_keys = (header + key[:i] for i in range(1, len(key)))
|
|
1127
|
+
for cont_key in relative_path_cont_keys:
|
|
1128
|
+
# Check that dotted key syntax does not redefine an existing table
|
|
1129
|
+
if out.flags.is_(cont_key, TomlFlags.EXPLICIT_NEST):
|
|
1130
|
+
raise toml_suffixed_err(src, pos, f'Cannot redefine namespace {cont_key}')
|
|
1131
|
+
# Containers in the relative path can't be opened with the table syntax or dotted key/value syntax in following
|
|
1132
|
+
# table sections.
|
|
1133
|
+
out.flags.add_pending(cont_key, TomlFlags.EXPLICIT_NEST)
|
|
1134
|
+
|
|
1135
|
+
if out.flags.is_(abs_key_parent, TomlFlags.FROZEN):
|
|
1136
|
+
raise toml_suffixed_err(
|
|
1137
|
+
src,
|
|
1138
|
+
pos,
|
|
1139
|
+
f'Cannot mutate immutable namespace {abs_key_parent}',
|
|
1140
|
+
)
|
|
1141
|
+
|
|
1142
|
+
try:
|
|
1143
|
+
nest = out.data.get_or_create_nest(abs_key_parent)
|
|
1144
|
+
except KeyError:
|
|
1145
|
+
raise toml_suffixed_err(src, pos, 'Cannot overwrite a value') from None
|
|
1146
|
+
if key_stem in nest:
|
|
1147
|
+
raise toml_suffixed_err(src, pos, 'Cannot overwrite a value')
|
|
1148
|
+
# Mark inline table and array namespaces recursively immutable
|
|
1149
|
+
if isinstance(value, (dict, list)):
|
|
1150
|
+
out.flags.set(header + key, TomlFlags.FROZEN, recursive=True)
|
|
1151
|
+
nest[key_stem] = value
|
|
1152
|
+
return pos
|
|
1300
1153
|
|
|
1301
|
-
class InvalidVersion(ValueError): # noqa
|
|
1302
|
-
pass
|
|
1303
1154
|
|
|
1155
|
+
def toml_parse_key_value_pair(
|
|
1156
|
+
src: str,
|
|
1157
|
+
pos: TomlPos,
|
|
1158
|
+
parse_float: TomlParseFloat,
|
|
1159
|
+
) -> ta.Tuple[TomlPos, TomlKey, ta.Any]:
|
|
1160
|
+
pos, key = toml_parse_key(src, pos)
|
|
1161
|
+
try:
|
|
1162
|
+
char: ta.Optional[str] = src[pos]
|
|
1163
|
+
except IndexError:
|
|
1164
|
+
char = None
|
|
1165
|
+
if char != '=':
|
|
1166
|
+
raise toml_suffixed_err(src, pos, "Expected '=' after a key in a key/value pair")
|
|
1167
|
+
pos += 1
|
|
1168
|
+
pos = toml_skip_chars(src, pos, TOML_WS)
|
|
1169
|
+
pos, value = toml_parse_value(src, pos, parse_float)
|
|
1170
|
+
return pos, key, value
|
|
1304
1171
|
|
|
1305
|
-
class _BaseVersion:
|
|
1306
|
-
_key: ta.Tuple[ta.Any, ...]
|
|
1307
1172
|
|
|
1308
|
-
|
|
1309
|
-
|
|
1173
|
+
def toml_parse_key(src: str, pos: TomlPos) -> ta.Tuple[TomlPos, TomlKey]:
|
|
1174
|
+
pos, key_part = toml_parse_key_part(src, pos)
|
|
1175
|
+
key: TomlKey = (key_part,)
|
|
1176
|
+
pos = toml_skip_chars(src, pos, TOML_WS)
|
|
1177
|
+
while True:
|
|
1178
|
+
try:
|
|
1179
|
+
char: ta.Optional[str] = src[pos]
|
|
1180
|
+
except IndexError:
|
|
1181
|
+
char = None
|
|
1182
|
+
if char != '.':
|
|
1183
|
+
return pos, key
|
|
1184
|
+
pos += 1
|
|
1185
|
+
pos = toml_skip_chars(src, pos, TOML_WS)
|
|
1186
|
+
pos, key_part = toml_parse_key_part(src, pos)
|
|
1187
|
+
key += (key_part,)
|
|
1188
|
+
pos = toml_skip_chars(src, pos, TOML_WS)
|
|
1310
1189
|
|
|
1311
|
-
def __lt__(self, other: '_BaseVersion') -> bool:
|
|
1312
|
-
if not isinstance(other, _BaseVersion):
|
|
1313
|
-
return NotImplemented # type: ignore
|
|
1314
|
-
return self._key < other._key
|
|
1315
1190
|
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1191
|
+
def toml_parse_key_part(src: str, pos: TomlPos) -> ta.Tuple[TomlPos, str]:
|
|
1192
|
+
try:
|
|
1193
|
+
char: ta.Optional[str] = src[pos]
|
|
1194
|
+
except IndexError:
|
|
1195
|
+
char = None
|
|
1196
|
+
if char in TOML_BARE_KEY_CHARS:
|
|
1197
|
+
start_pos = pos
|
|
1198
|
+
pos = toml_skip_chars(src, pos, TOML_BARE_KEY_CHARS)
|
|
1199
|
+
return pos, src[start_pos:pos]
|
|
1200
|
+
if char == "'":
|
|
1201
|
+
return toml_parse_literal_str(src, pos)
|
|
1202
|
+
if char == '"':
|
|
1203
|
+
return toml_parse_one_line_basic_str(src, pos)
|
|
1204
|
+
raise toml_suffixed_err(src, pos, 'Invalid initial character for a key part')
|
|
1320
1205
|
|
|
1321
|
-
def __eq__(self, other: object) -> bool:
|
|
1322
|
-
if not isinstance(other, _BaseVersion):
|
|
1323
|
-
return NotImplemented
|
|
1324
|
-
return self._key == other._key
|
|
1325
1206
|
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
return self._key >= other._key
|
|
1207
|
+
def toml_parse_one_line_basic_str(src: str, pos: TomlPos) -> ta.Tuple[TomlPos, str]:
|
|
1208
|
+
pos += 1
|
|
1209
|
+
return toml_parse_basic_str(src, pos, multiline=False)
|
|
1330
1210
|
|
|
1331
|
-
def __gt__(self, other: '_BaseVersion') -> bool:
|
|
1332
|
-
if not isinstance(other, _BaseVersion):
|
|
1333
|
-
return NotImplemented # type: ignore
|
|
1334
|
-
return self._key > other._key
|
|
1335
1211
|
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
return self._key != other._key
|
|
1212
|
+
def toml_parse_array(src: str, pos: TomlPos, parse_float: TomlParseFloat) -> ta.Tuple[TomlPos, list]:
|
|
1213
|
+
pos += 1
|
|
1214
|
+
array: list = []
|
|
1340
1215
|
|
|
1216
|
+
pos = toml_skip_comments_and_array_ws(src, pos)
|
|
1217
|
+
if src.startswith(']', pos):
|
|
1218
|
+
return pos + 1, array
|
|
1219
|
+
while True:
|
|
1220
|
+
pos, val = toml_parse_value(src, pos, parse_float)
|
|
1221
|
+
array.append(val)
|
|
1222
|
+
pos = toml_skip_comments_and_array_ws(src, pos)
|
|
1341
1223
|
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
[-_\.]?
|
|
1349
|
-
(?P<pre_l>alpha|a|beta|b|preview|pre|c|rc)
|
|
1350
|
-
[-_\.]?
|
|
1351
|
-
(?P<pre_n>[0-9]+)?
|
|
1352
|
-
)?
|
|
1353
|
-
(?P<post>
|
|
1354
|
-
(?:-(?P<post_n1>[0-9]+))
|
|
1355
|
-
|
|
|
1356
|
-
(?:
|
|
1357
|
-
[-_\.]?
|
|
1358
|
-
(?P<post_l>post|rev|r)
|
|
1359
|
-
[-_\.]?
|
|
1360
|
-
(?P<post_n2>[0-9]+)?
|
|
1361
|
-
)
|
|
1362
|
-
)?
|
|
1363
|
-
(?P<dev>
|
|
1364
|
-
[-_\.]?
|
|
1365
|
-
(?P<dev_l>dev)
|
|
1366
|
-
[-_\.]?
|
|
1367
|
-
(?P<dev_n>[0-9]+)?
|
|
1368
|
-
)?
|
|
1369
|
-
)
|
|
1370
|
-
(?:\+(?P<local>[a-z0-9]+(?:[-_\.][a-z0-9]+)*))?
|
|
1371
|
-
"""
|
|
1224
|
+
c = src[pos:pos + 1]
|
|
1225
|
+
if c == ']':
|
|
1226
|
+
return pos + 1, array
|
|
1227
|
+
if c != ',':
|
|
1228
|
+
raise toml_suffixed_err(src, pos, 'Unclosed array')
|
|
1229
|
+
pos += 1
|
|
1372
1230
|
|
|
1373
|
-
|
|
1231
|
+
pos = toml_skip_comments_and_array_ws(src, pos)
|
|
1232
|
+
if src.startswith(']', pos):
|
|
1233
|
+
return pos + 1, array
|
|
1374
1234
|
|
|
1375
1235
|
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1236
|
+
def toml_parse_inline_table(src: str, pos: TomlPos, parse_float: TomlParseFloat) -> ta.Tuple[TomlPos, dict]:
|
|
1237
|
+
pos += 1
|
|
1238
|
+
nested_dict = TomlNestedDict()
|
|
1239
|
+
flags = TomlFlags()
|
|
1379
1240
|
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1241
|
+
pos = toml_skip_chars(src, pos, TOML_WS)
|
|
1242
|
+
if src.startswith('}', pos):
|
|
1243
|
+
return pos + 1, nested_dict.dict
|
|
1244
|
+
while True:
|
|
1245
|
+
pos, key, value = toml_parse_key_value_pair(src, pos, parse_float)
|
|
1246
|
+
key_parent, key_stem = key[:-1], key[-1]
|
|
1247
|
+
if flags.is_(key, TomlFlags.FROZEN):
|
|
1248
|
+
raise toml_suffixed_err(src, pos, f'Cannot mutate immutable namespace {key}')
|
|
1249
|
+
try:
|
|
1250
|
+
nest = nested_dict.get_or_create_nest(key_parent, access_lists=False)
|
|
1251
|
+
except KeyError:
|
|
1252
|
+
raise toml_suffixed_err(src, pos, 'Cannot overwrite a value') from None
|
|
1253
|
+
if key_stem in nest:
|
|
1254
|
+
raise toml_suffixed_err(src, pos, f'Duplicate inline table key {key_stem!r}')
|
|
1255
|
+
nest[key_stem] = value
|
|
1256
|
+
pos = toml_skip_chars(src, pos, TOML_WS)
|
|
1257
|
+
c = src[pos:pos + 1]
|
|
1258
|
+
if c == '}':
|
|
1259
|
+
return pos + 1, nested_dict.dict
|
|
1260
|
+
if c != ',':
|
|
1261
|
+
raise toml_suffixed_err(src, pos, 'Unclosed inline table')
|
|
1262
|
+
if isinstance(value, (dict, list)):
|
|
1263
|
+
flags.set(key, TomlFlags.FROZEN, recursive=True)
|
|
1264
|
+
pos += 1
|
|
1265
|
+
pos = toml_skip_chars(src, pos, TOML_WS)
|
|
1384
1266
|
|
|
1385
|
-
self._version = _Version(
|
|
1386
|
-
epoch=int(match.group('epoch')) if match.group('epoch') else 0,
|
|
1387
|
-
release=tuple(int(i) for i in match.group('release').split('.')),
|
|
1388
|
-
pre=_parse_letter_version(match.group('pre_l'), match.group('pre_n')),
|
|
1389
|
-
post=_parse_letter_version(match.group('post_l'), match.group('post_n1') or match.group('post_n2')),
|
|
1390
|
-
dev=_parse_letter_version(match.group('dev_l'), match.group('dev_n')),
|
|
1391
|
-
local=_parse_local_version(match.group('local')),
|
|
1392
|
-
)
|
|
1393
1267
|
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1268
|
+
def toml_parse_basic_str_escape(
|
|
1269
|
+
src: str,
|
|
1270
|
+
pos: TomlPos,
|
|
1271
|
+
*,
|
|
1272
|
+
multiline: bool = False,
|
|
1273
|
+
) -> ta.Tuple[TomlPos, str]:
|
|
1274
|
+
escape_id = src[pos:pos + 2]
|
|
1275
|
+
pos += 2
|
|
1276
|
+
if multiline and escape_id in {'\\ ', '\\\t', '\\\n'}:
|
|
1277
|
+
# Skip whitespace until next non-whitespace character or end of the doc. Error if non-whitespace is found before
|
|
1278
|
+
# newline.
|
|
1279
|
+
if escape_id != '\\\n':
|
|
1280
|
+
pos = toml_skip_chars(src, pos, TOML_WS)
|
|
1281
|
+
try:
|
|
1282
|
+
char = src[pos]
|
|
1283
|
+
except IndexError:
|
|
1284
|
+
return pos, ''
|
|
1285
|
+
if char != '\n':
|
|
1286
|
+
raise toml_suffixed_err(src, pos, "Unescaped '\\' in a string")
|
|
1287
|
+
pos += 1
|
|
1288
|
+
pos = toml_skip_chars(src, pos, TOML_WS_AND_NEWLINE)
|
|
1289
|
+
return pos, ''
|
|
1290
|
+
if escape_id == '\\u':
|
|
1291
|
+
return toml_parse_hex_char(src, pos, 4)
|
|
1292
|
+
if escape_id == '\\U':
|
|
1293
|
+
return toml_parse_hex_char(src, pos, 8)
|
|
1294
|
+
try:
|
|
1295
|
+
return pos, TOML_BASIC_STR_ESCAPE_REPLACEMENTS[escape_id]
|
|
1296
|
+
except KeyError:
|
|
1297
|
+
raise toml_suffixed_err(src, pos, "Unescaped '\\' in a string") from None
|
|
1402
1298
|
|
|
1403
|
-
def __repr__(self) -> str:
|
|
1404
|
-
return f"<Version('{self}')>"
|
|
1405
1299
|
|
|
1406
|
-
|
|
1407
|
-
|
|
1300
|
+
def toml_parse_basic_str_escape_multiline(src: str, pos: TomlPos) -> ta.Tuple[TomlPos, str]:
|
|
1301
|
+
return toml_parse_basic_str_escape(src, pos, multiline=True)
|
|
1408
1302
|
|
|
1409
|
-
if self.epoch != 0:
|
|
1410
|
-
parts.append(f'{self.epoch}!')
|
|
1411
1303
|
|
|
1412
|
-
|
|
1304
|
+
def toml_parse_hex_char(src: str, pos: TomlPos, hex_len: int) -> ta.Tuple[TomlPos, str]:
|
|
1305
|
+
hex_str = src[pos:pos + hex_len]
|
|
1306
|
+
if len(hex_str) != hex_len or not TOML_HEXDIGIT_CHARS.issuperset(hex_str):
|
|
1307
|
+
raise toml_suffixed_err(src, pos, 'Invalid hex value')
|
|
1308
|
+
pos += hex_len
|
|
1309
|
+
hex_int = int(hex_str, 16)
|
|
1310
|
+
if not toml_is_unicode_scalar_value(hex_int):
|
|
1311
|
+
raise toml_suffixed_err(src, pos, 'Escaped character is not a Unicode scalar value')
|
|
1312
|
+
return pos, chr(hex_int)
|
|
1413
1313
|
|
|
1414
|
-
if self.pre is not None:
|
|
1415
|
-
parts.append(''.join(str(x) for x in self.pre))
|
|
1416
1314
|
|
|
1417
|
-
|
|
1418
|
-
|
|
1315
|
+
def toml_parse_literal_str(src: str, pos: TomlPos) -> ta.Tuple[TomlPos, str]:
|
|
1316
|
+
pos += 1 # Skip starting apostrophe
|
|
1317
|
+
start_pos = pos
|
|
1318
|
+
pos = toml_skip_until(
|
|
1319
|
+
src, pos, "'", error_on=TOML_ILLEGAL_LITERAL_STR_CHARS, error_on_eof=True,
|
|
1320
|
+
)
|
|
1321
|
+
return pos + 1, src[start_pos:pos] # Skip ending apostrophe
|
|
1419
1322
|
|
|
1420
|
-
if self.dev is not None:
|
|
1421
|
-
parts.append(f'.dev{self.dev}')
|
|
1422
1323
|
|
|
1423
|
-
|
|
1424
|
-
|
|
1324
|
+
def toml_parse_multiline_str(src: str, pos: TomlPos, *, literal: bool) -> ta.Tuple[TomlPos, str]:
|
|
1325
|
+
pos += 3
|
|
1326
|
+
if src.startswith('\n', pos):
|
|
1327
|
+
pos += 1
|
|
1425
1328
|
|
|
1426
|
-
|
|
1329
|
+
if literal:
|
|
1330
|
+
delim = "'"
|
|
1331
|
+
end_pos = toml_skip_until(
|
|
1332
|
+
src,
|
|
1333
|
+
pos,
|
|
1334
|
+
"'''",
|
|
1335
|
+
error_on=TOML_ILLEGAL_MULTILINE_LITERAL_STR_CHARS,
|
|
1336
|
+
error_on_eof=True,
|
|
1337
|
+
)
|
|
1338
|
+
result = src[pos:end_pos]
|
|
1339
|
+
pos = end_pos + 3
|
|
1340
|
+
else:
|
|
1341
|
+
delim = '"'
|
|
1342
|
+
pos, result = toml_parse_basic_str(src, pos, multiline=True)
|
|
1427
1343
|
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
return
|
|
1344
|
+
# Add at maximum two extra apostrophes/quotes if the end sequence is 4 or 5 chars long instead of just 3.
|
|
1345
|
+
if not src.startswith(delim, pos):
|
|
1346
|
+
return pos, result
|
|
1347
|
+
pos += 1
|
|
1348
|
+
if not src.startswith(delim, pos):
|
|
1349
|
+
return pos, result + delim
|
|
1350
|
+
pos += 1
|
|
1351
|
+
return pos, result + (delim * 2)
|
|
1431
1352
|
|
|
1432
|
-
@property
|
|
1433
|
-
def release(self) -> ta.Tuple[int, ...]:
|
|
1434
|
-
return self._version.release
|
|
1435
1353
|
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1354
|
+
def toml_parse_basic_str(src: str, pos: TomlPos, *, multiline: bool) -> ta.Tuple[TomlPos, str]:
|
|
1355
|
+
if multiline:
|
|
1356
|
+
error_on = TOML_ILLEGAL_MULTILINE_BASIC_STR_CHARS
|
|
1357
|
+
parse_escapes = toml_parse_basic_str_escape_multiline
|
|
1358
|
+
else:
|
|
1359
|
+
error_on = TOML_ILLEGAL_BASIC_STR_CHARS
|
|
1360
|
+
parse_escapes = toml_parse_basic_str_escape
|
|
1361
|
+
result = ''
|
|
1362
|
+
start_pos = pos
|
|
1363
|
+
while True:
|
|
1364
|
+
try:
|
|
1365
|
+
char = src[pos]
|
|
1366
|
+
except IndexError:
|
|
1367
|
+
raise toml_suffixed_err(src, pos, 'Unterminated string') from None
|
|
1368
|
+
if char == '"':
|
|
1369
|
+
if not multiline:
|
|
1370
|
+
return pos + 1, result + src[start_pos:pos]
|
|
1371
|
+
if src.startswith('"""', pos):
|
|
1372
|
+
return pos + 3, result + src[start_pos:pos]
|
|
1373
|
+
pos += 1
|
|
1374
|
+
continue
|
|
1375
|
+
if char == '\\':
|
|
1376
|
+
result += src[start_pos:pos]
|
|
1377
|
+
pos, parsed_escape = parse_escapes(src, pos)
|
|
1378
|
+
result += parsed_escape
|
|
1379
|
+
start_pos = pos
|
|
1380
|
+
continue
|
|
1381
|
+
if char in error_on:
|
|
1382
|
+
raise toml_suffixed_err(src, pos, f'Illegal character {char!r}')
|
|
1383
|
+
pos += 1
|
|
1439
1384
|
|
|
1440
|
-
@property
|
|
1441
|
-
def post(self) -> ta.Optional[int]:
|
|
1442
|
-
return self._version.post[1] if self._version.post else None
|
|
1443
1385
|
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1386
|
+
def toml_parse_value( # noqa: C901
|
|
1387
|
+
src: str,
|
|
1388
|
+
pos: TomlPos,
|
|
1389
|
+
parse_float: TomlParseFloat,
|
|
1390
|
+
) -> ta.Tuple[TomlPos, ta.Any]:
|
|
1391
|
+
try:
|
|
1392
|
+
char: ta.Optional[str] = src[pos]
|
|
1393
|
+
except IndexError:
|
|
1394
|
+
char = None
|
|
1447
1395
|
|
|
1448
|
-
|
|
1449
|
-
def local(self) -> ta.Optional[str]:
|
|
1450
|
-
if self._version.local:
|
|
1451
|
-
return '.'.join(str(x) for x in self._version.local)
|
|
1452
|
-
else:
|
|
1453
|
-
return None
|
|
1396
|
+
# IMPORTANT: order conditions based on speed of checking and likelihood
|
|
1454
1397
|
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1398
|
+
# Basic strings
|
|
1399
|
+
if char == '"':
|
|
1400
|
+
if src.startswith('"""', pos):
|
|
1401
|
+
return toml_parse_multiline_str(src, pos, literal=False)
|
|
1402
|
+
return toml_parse_one_line_basic_str(src, pos)
|
|
1458
1403
|
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1404
|
+
# Literal strings
|
|
1405
|
+
if char == "'":
|
|
1406
|
+
if src.startswith("'''", pos):
|
|
1407
|
+
return toml_parse_multiline_str(src, pos, literal=True)
|
|
1408
|
+
return toml_parse_literal_str(src, pos)
|
|
1462
1409
|
|
|
1463
|
-
|
|
1464
|
-
|
|
1410
|
+
# Booleans
|
|
1411
|
+
if char == 't':
|
|
1412
|
+
if src.startswith('true', pos):
|
|
1413
|
+
return pos + 4, True
|
|
1414
|
+
if char == 'f':
|
|
1415
|
+
if src.startswith('false', pos):
|
|
1416
|
+
return pos + 5, False
|
|
1465
1417
|
|
|
1466
|
-
|
|
1418
|
+
# Arrays
|
|
1419
|
+
if char == '[':
|
|
1420
|
+
return toml_parse_array(src, pos, parse_float)
|
|
1467
1421
|
|
|
1468
|
-
|
|
1422
|
+
# Inline tables
|
|
1423
|
+
if char == '{':
|
|
1424
|
+
return toml_parse_inline_table(src, pos, parse_float)
|
|
1469
1425
|
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1426
|
+
# Dates and times
|
|
1427
|
+
datetime_match = TOML_RE_DATETIME.match(src, pos)
|
|
1428
|
+
if datetime_match:
|
|
1429
|
+
try:
|
|
1430
|
+
datetime_obj = toml_match_to_datetime(datetime_match)
|
|
1431
|
+
except ValueError as e:
|
|
1432
|
+
raise toml_suffixed_err(src, pos, 'Invalid date or datetime') from e
|
|
1433
|
+
return datetime_match.end(), datetime_obj
|
|
1434
|
+
localtime_match = TOML_RE_LOCALTIME.match(src, pos)
|
|
1435
|
+
if localtime_match:
|
|
1436
|
+
return localtime_match.end(), toml_match_to_localtime(localtime_match)
|
|
1473
1437
|
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1438
|
+
# Integers and "normal" floats. The regex will greedily match any type starting with a decimal char, so needs to be
|
|
1439
|
+
# located after handling of dates and times.
|
|
1440
|
+
number_match = TOML_RE_NUMBER.match(src, pos)
|
|
1441
|
+
if number_match:
|
|
1442
|
+
return number_match.end(), toml_match_to_number(number_match, parse_float)
|
|
1477
1443
|
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1444
|
+
# Special floats
|
|
1445
|
+
first_three = src[pos:pos + 3]
|
|
1446
|
+
if first_three in {'inf', 'nan'}:
|
|
1447
|
+
return pos + 3, parse_float(first_three)
|
|
1448
|
+
first_four = src[pos:pos + 4]
|
|
1449
|
+
if first_four in {'-inf', '+inf', '-nan', '+nan'}:
|
|
1450
|
+
return pos + 4, parse_float(first_four)
|
|
1481
1451
|
|
|
1482
|
-
|
|
1483
|
-
def major(self) -> int:
|
|
1484
|
-
return self.release[0] if len(self.release) >= 1 else 0
|
|
1452
|
+
raise toml_suffixed_err(src, pos, 'Invalid value')
|
|
1485
1453
|
|
|
1486
|
-
@property
|
|
1487
|
-
def minor(self) -> int:
|
|
1488
|
-
return self.release[1] if len(self.release) >= 2 else 0
|
|
1489
1454
|
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
return self.release[2] if len(self.release) >= 3 else 0
|
|
1455
|
+
def toml_suffixed_err(src: str, pos: TomlPos, msg: str) -> TomlDecodeError:
|
|
1456
|
+
"""Return a `TomlDecodeError` where error message is suffixed with coordinates in source."""
|
|
1493
1457
|
|
|
1458
|
+
def coord_repr(src: str, pos: TomlPos) -> str:
|
|
1459
|
+
if pos >= len(src):
|
|
1460
|
+
return 'end of document'
|
|
1461
|
+
line = src.count('\n', 0, pos) + 1
|
|
1462
|
+
if line == 1:
|
|
1463
|
+
column = pos + 1
|
|
1464
|
+
else:
|
|
1465
|
+
column = pos - src.rindex('\n', 0, pos)
|
|
1466
|
+
return f'line {line}, column {column}'
|
|
1494
1467
|
|
|
1495
|
-
|
|
1496
|
-
letter: ta.Optional[str],
|
|
1497
|
-
number: ta.Union[str, bytes, ta.SupportsInt, None],
|
|
1498
|
-
) -> ta.Optional[ta.Tuple[str, int]]:
|
|
1499
|
-
if letter:
|
|
1500
|
-
if number is None:
|
|
1501
|
-
number = 0
|
|
1468
|
+
return TomlDecodeError(f'{msg} (at {coord_repr(src, pos)})')
|
|
1502
1469
|
|
|
1503
|
-
letter = letter.lower()
|
|
1504
|
-
if letter == 'alpha':
|
|
1505
|
-
letter = 'a'
|
|
1506
|
-
elif letter == 'beta':
|
|
1507
|
-
letter = 'b'
|
|
1508
|
-
elif letter in ['c', 'pre', 'preview']:
|
|
1509
|
-
letter = 'rc'
|
|
1510
|
-
elif letter in ['rev', 'r']:
|
|
1511
|
-
letter = 'post'
|
|
1512
1470
|
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
letter = 'post'
|
|
1516
|
-
return letter, int(number)
|
|
1471
|
+
def toml_is_unicode_scalar_value(codepoint: int) -> bool:
|
|
1472
|
+
return (0 <= codepoint <= 55295) or (57344 <= codepoint <= 1114111)
|
|
1517
1473
|
|
|
1518
|
-
return None
|
|
1519
1474
|
|
|
1475
|
+
def toml_make_safe_parse_float(parse_float: TomlParseFloat) -> TomlParseFloat:
|
|
1476
|
+
"""A decorator to make `parse_float` safe.
|
|
1520
1477
|
|
|
1521
|
-
|
|
1478
|
+
`parse_float` must not return dicts or lists, because these types would be mixed with parsed TOML tables and arrays,
|
|
1479
|
+
thus confusing the parser. The returned decorated callable raises `ValueError` instead of returning illegal types.
|
|
1480
|
+
"""
|
|
1481
|
+
# The default `float` callable never returns illegal types. Optimize it.
|
|
1482
|
+
if parse_float is float:
|
|
1483
|
+
return float
|
|
1522
1484
|
|
|
1485
|
+
def safe_parse_float(float_str: str) -> ta.Any:
|
|
1486
|
+
float_value = parse_float(float_str)
|
|
1487
|
+
if isinstance(float_value, (dict, list)):
|
|
1488
|
+
raise ValueError('parse_float must not return dicts or lists') # noqa
|
|
1489
|
+
return float_value
|
|
1523
1490
|
|
|
1524
|
-
|
|
1525
|
-
if local is not None:
|
|
1526
|
-
return tuple(
|
|
1527
|
-
part.lower() if not part.isdigit() else int(part)
|
|
1528
|
-
for part in _local_version_separators.split(local)
|
|
1529
|
-
)
|
|
1530
|
-
return None
|
|
1491
|
+
return safe_parse_float
|
|
1531
1492
|
|
|
1532
1493
|
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
release: ta.Tuple[int, ...],
|
|
1536
|
-
pre: ta.Optional[ta.Tuple[str, int]],
|
|
1537
|
-
post: ta.Optional[ta.Tuple[str, int]],
|
|
1538
|
-
dev: ta.Optional[ta.Tuple[str, int]],
|
|
1539
|
-
local: ta.Optional[VersionLocalType],
|
|
1540
|
-
) -> VersionCmpKey:
|
|
1541
|
-
_release = tuple(reversed(list(itertools.dropwhile(lambda x: x == 0, reversed(release)))))
|
|
1494
|
+
########################################
|
|
1495
|
+
# ../../toml/writer.py
|
|
1542
1496
|
|
|
1543
|
-
if pre is None and post is None and dev is not None:
|
|
1544
|
-
_pre: VersionCmpPrePostDevType = NegativeInfinityVersion
|
|
1545
|
-
elif pre is None:
|
|
1546
|
-
_pre = InfinityVersion
|
|
1547
|
-
else:
|
|
1548
|
-
_pre = pre
|
|
1549
1497
|
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1498
|
+
class TomlWriter:
|
|
1499
|
+
@dc.dataclass(frozen=True)
|
|
1500
|
+
class Literal:
|
|
1501
|
+
s: str
|
|
1554
1502
|
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
_dev = dev
|
|
1503
|
+
def __init__(self, out: ta.TextIO) -> None:
|
|
1504
|
+
super().__init__()
|
|
1505
|
+
self._out = out
|
|
1559
1506
|
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
else:
|
|
1563
|
-
_local = tuple((i, '') if isinstance(i, int) else (NegativeInfinityVersion, i) for i in local)
|
|
1507
|
+
self._indent = 0
|
|
1508
|
+
self._wrote_indent = False
|
|
1564
1509
|
|
|
1565
|
-
|
|
1510
|
+
#
|
|
1566
1511
|
|
|
1512
|
+
def _w(self, s: str) -> None:
|
|
1513
|
+
if not self._wrote_indent:
|
|
1514
|
+
self._out.write(' ' * self._indent)
|
|
1515
|
+
self._wrote_indent = True
|
|
1516
|
+
self._out.write(s)
|
|
1567
1517
|
|
|
1568
|
-
|
|
1518
|
+
def _nl(self) -> None:
|
|
1519
|
+
self._out.write('\n')
|
|
1520
|
+
self._wrote_indent = False
|
|
1569
1521
|
|
|
1522
|
+
def _needs_quote(self, s: str) -> bool:
|
|
1523
|
+
return (
|
|
1524
|
+
not s or
|
|
1525
|
+
any(c in s for c in '\'"\n') or
|
|
1526
|
+
s[0] not in string.ascii_letters
|
|
1527
|
+
)
|
|
1570
1528
|
|
|
1571
|
-
def
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
if isinstance(version, str):
|
|
1577
|
-
try:
|
|
1578
|
-
parsed = Version(version)
|
|
1579
|
-
except InvalidVersion:
|
|
1580
|
-
return version
|
|
1581
|
-
else:
|
|
1582
|
-
parsed = version
|
|
1529
|
+
def _maybe_quote(self, s: str) -> str:
|
|
1530
|
+
if self._needs_quote(s):
|
|
1531
|
+
return repr(s)
|
|
1532
|
+
else:
|
|
1533
|
+
return s
|
|
1583
1534
|
|
|
1584
|
-
|
|
1535
|
+
#
|
|
1585
1536
|
|
|
1586
|
-
|
|
1587
|
-
|
|
1537
|
+
def write_root(self, obj: ta.Mapping) -> None:
|
|
1538
|
+
for i, (k, v) in enumerate(obj.items()):
|
|
1539
|
+
if i:
|
|
1540
|
+
self._nl()
|
|
1541
|
+
self._w('[')
|
|
1542
|
+
self._w(self._maybe_quote(k))
|
|
1543
|
+
self._w(']')
|
|
1544
|
+
self._nl()
|
|
1545
|
+
self.write_table_contents(v)
|
|
1588
1546
|
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1547
|
+
def write_table_contents(self, obj: ta.Mapping) -> None:
|
|
1548
|
+
for k, v in obj.items():
|
|
1549
|
+
self.write_key(k)
|
|
1550
|
+
self._w(' = ')
|
|
1551
|
+
self.write_value(v)
|
|
1552
|
+
self._nl()
|
|
1593
1553
|
|
|
1594
|
-
|
|
1595
|
-
|
|
1554
|
+
def write_array(self, obj: ta.Sequence) -> None:
|
|
1555
|
+
self._w('[')
|
|
1556
|
+
self._nl()
|
|
1557
|
+
self._indent += 1
|
|
1558
|
+
for e in obj:
|
|
1559
|
+
self.write_value(e)
|
|
1560
|
+
self._w(',')
|
|
1561
|
+
self._nl()
|
|
1562
|
+
self._indent -= 1
|
|
1563
|
+
self._w(']')
|
|
1596
1564
|
|
|
1597
|
-
|
|
1598
|
-
|
|
1565
|
+
def write_inline_table(self, obj: ta.Mapping) -> None:
|
|
1566
|
+
self._w('{')
|
|
1567
|
+
for i, (k, v) in enumerate(obj.items()):
|
|
1568
|
+
if i:
|
|
1569
|
+
self._w(', ')
|
|
1570
|
+
self.write_key(k)
|
|
1571
|
+
self._w(' = ')
|
|
1572
|
+
self.write_value(v)
|
|
1573
|
+
self._w('}')
|
|
1599
1574
|
|
|
1600
|
-
|
|
1601
|
-
|
|
1575
|
+
def write_inline_array(self, obj: ta.Sequence) -> None:
|
|
1576
|
+
self._w('[')
|
|
1577
|
+
for i, e in enumerate(obj):
|
|
1578
|
+
if i:
|
|
1579
|
+
self._w(', ')
|
|
1580
|
+
self.write_value(e)
|
|
1581
|
+
self._w(']')
|
|
1602
1582
|
|
|
1603
|
-
|
|
1604
|
-
|
|
1583
|
+
def write_key(self, obj: ta.Any) -> None:
|
|
1584
|
+
if isinstance(obj, TomlWriter.Literal):
|
|
1585
|
+
self._w(obj.s)
|
|
1586
|
+
elif isinstance(obj, str):
|
|
1587
|
+
self._w(self._maybe_quote(obj.replace('_', '-')))
|
|
1588
|
+
elif isinstance(obj, int):
|
|
1589
|
+
self._w(repr(str(obj)))
|
|
1590
|
+
else:
|
|
1591
|
+
raise TypeError(obj)
|
|
1605
1592
|
|
|
1606
|
-
|
|
1593
|
+
def write_value(self, obj: ta.Any) -> None:
|
|
1594
|
+
if isinstance(obj, bool):
|
|
1595
|
+
self._w(str(obj).lower())
|
|
1596
|
+
elif isinstance(obj, (str, int, float)):
|
|
1597
|
+
self._w(repr(obj))
|
|
1598
|
+
elif isinstance(obj, ta.Mapping):
|
|
1599
|
+
self.write_inline_table(obj)
|
|
1600
|
+
elif isinstance(obj, ta.Sequence):
|
|
1601
|
+
if not obj:
|
|
1602
|
+
self.write_inline_array(obj)
|
|
1603
|
+
else:
|
|
1604
|
+
self.write_array(obj)
|
|
1605
|
+
else:
|
|
1606
|
+
raise TypeError(obj)
|
|
1607
1607
|
|
|
1608
1608
|
|
|
1609
1609
|
########################################
|
|
@@ -1899,6 +1899,11 @@ def check_non_empty_str(v: ta.Optional[str]) -> str:
|
|
|
1899
1899
|
return v
|
|
1900
1900
|
|
|
1901
1901
|
|
|
1902
|
+
def check_state(v: bool, msg: str = 'Illegal state') -> None:
|
|
1903
|
+
if not v:
|
|
1904
|
+
raise ValueError(msg)
|
|
1905
|
+
|
|
1906
|
+
|
|
1902
1907
|
########################################
|
|
1903
1908
|
# ../../../omlish/lite/json.py
|
|
1904
1909
|
|
|
@@ -2009,83 +2014,7 @@ def is_sunder(name: str) -> bool:
|
|
|
2009
2014
|
|
|
2010
2015
|
|
|
2011
2016
|
########################################
|
|
2012
|
-
#
|
|
2013
|
-
"""
|
|
2014
|
-
TODO:
|
|
2015
|
-
- embed pip._internal.req.parse_requirements, add additional env stuff? breaks compat with raw pip
|
|
2016
|
-
"""
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
class RequirementsRewriter:
|
|
2020
|
-
def __init__(
|
|
2021
|
-
self,
|
|
2022
|
-
venv: ta.Optional[str] = None,
|
|
2023
|
-
) -> None:
|
|
2024
|
-
super().__init__()
|
|
2025
|
-
self._venv = venv
|
|
2026
|
-
|
|
2027
|
-
@cached_nullary
|
|
2028
|
-
def _tmp_dir(self) -> str:
|
|
2029
|
-
return tempfile.mkdtemp('-omlish-reqs')
|
|
2030
|
-
|
|
2031
|
-
VENV_MAGIC = '# @omlish-venv'
|
|
2032
|
-
|
|
2033
|
-
def rewrite_file(self, in_file: str) -> str:
|
|
2034
|
-
with open(in_file) as f:
|
|
2035
|
-
src = f.read()
|
|
2036
|
-
|
|
2037
|
-
in_lines = src.splitlines(keepends=True)
|
|
2038
|
-
out_lines = []
|
|
2039
|
-
|
|
2040
|
-
for l in in_lines:
|
|
2041
|
-
if self.VENV_MAGIC in l:
|
|
2042
|
-
lp, _, rp = l.partition(self.VENV_MAGIC)
|
|
2043
|
-
rp = rp.partition('#')[0]
|
|
2044
|
-
omit = False
|
|
2045
|
-
for v in rp.split():
|
|
2046
|
-
if v[0] == '!':
|
|
2047
|
-
if self._venv is not None and self._venv == v[1:]:
|
|
2048
|
-
omit = True
|
|
2049
|
-
break
|
|
2050
|
-
else:
|
|
2051
|
-
raise NotImplementedError
|
|
2052
|
-
|
|
2053
|
-
if omit:
|
|
2054
|
-
out_lines.append('# OMITTED: ' + l)
|
|
2055
|
-
continue
|
|
2056
|
-
|
|
2057
|
-
out_req = self.rewrite(l.rstrip('\n'), for_file=True)
|
|
2058
|
-
out_lines.append(out_req + '\n')
|
|
2059
|
-
|
|
2060
|
-
out_file = os.path.join(self._tmp_dir(), os.path.basename(in_file))
|
|
2061
|
-
if os.path.exists(out_file):
|
|
2062
|
-
raise Exception(f'file exists: {out_file}')
|
|
2063
|
-
|
|
2064
|
-
with open(out_file, 'w') as f:
|
|
2065
|
-
f.write(''.join(out_lines))
|
|
2066
|
-
return out_file
|
|
2067
|
-
|
|
2068
|
-
def rewrite(self, in_req: str, *, for_file: bool = False) -> str:
|
|
2069
|
-
if in_req.strip().startswith('-r'):
|
|
2070
|
-
l = in_req.strip()
|
|
2071
|
-
lp, _, rp = l.partition(' ')
|
|
2072
|
-
if lp == '-r':
|
|
2073
|
-
inc_in_file, _, rest = rp.partition(' ')
|
|
2074
|
-
else:
|
|
2075
|
-
inc_in_file, rest = lp[2:], rp
|
|
2076
|
-
|
|
2077
|
-
inc_out_file = self.rewrite_file(inc_in_file)
|
|
2078
|
-
if for_file:
|
|
2079
|
-
return ' '.join(['-r ', inc_out_file, rest])
|
|
2080
|
-
else:
|
|
2081
|
-
return '-r' + inc_out_file
|
|
2082
|
-
|
|
2083
|
-
else:
|
|
2084
|
-
return in_req
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
########################################
|
|
2088
|
-
# ../../versioning/specifiers.py
|
|
2017
|
+
# ../../packaging/specifiers.py
|
|
2089
2018
|
# Copyright (c) Donald Stufft and individual contributors.
|
|
2090
2019
|
# All rights reserved.
|
|
2091
2020
|
#
|
|
@@ -2606,6 +2535,82 @@ class SpecifierSet(BaseSpecifier):
|
|
|
2606
2535
|
return iter(filtered)
|
|
2607
2536
|
|
|
2608
2537
|
|
|
2538
|
+
########################################
|
|
2539
|
+
# ../reqs.py
|
|
2540
|
+
"""
|
|
2541
|
+
TODO:
|
|
2542
|
+
- embed pip._internal.req.parse_requirements, add additional env stuff? breaks compat with raw pip
|
|
2543
|
+
"""
|
|
2544
|
+
|
|
2545
|
+
|
|
2546
|
+
class RequirementsRewriter:
|
|
2547
|
+
def __init__(
|
|
2548
|
+
self,
|
|
2549
|
+
venv: ta.Optional[str] = None,
|
|
2550
|
+
) -> None:
|
|
2551
|
+
super().__init__()
|
|
2552
|
+
self._venv = venv
|
|
2553
|
+
|
|
2554
|
+
@cached_nullary
|
|
2555
|
+
def _tmp_dir(self) -> str:
|
|
2556
|
+
return tempfile.mkdtemp('-omlish-reqs')
|
|
2557
|
+
|
|
2558
|
+
VENV_MAGIC = '# @omlish-venv'
|
|
2559
|
+
|
|
2560
|
+
def rewrite_file(self, in_file: str) -> str:
|
|
2561
|
+
with open(in_file) as f:
|
|
2562
|
+
src = f.read()
|
|
2563
|
+
|
|
2564
|
+
in_lines = src.splitlines(keepends=True)
|
|
2565
|
+
out_lines = []
|
|
2566
|
+
|
|
2567
|
+
for l in in_lines:
|
|
2568
|
+
if self.VENV_MAGIC in l:
|
|
2569
|
+
lp, _, rp = l.partition(self.VENV_MAGIC)
|
|
2570
|
+
rp = rp.partition('#')[0]
|
|
2571
|
+
omit = False
|
|
2572
|
+
for v in rp.split():
|
|
2573
|
+
if v[0] == '!':
|
|
2574
|
+
if self._venv is not None and self._venv == v[1:]:
|
|
2575
|
+
omit = True
|
|
2576
|
+
break
|
|
2577
|
+
else:
|
|
2578
|
+
raise NotImplementedError
|
|
2579
|
+
|
|
2580
|
+
if omit:
|
|
2581
|
+
out_lines.append('# OMITTED: ' + l)
|
|
2582
|
+
continue
|
|
2583
|
+
|
|
2584
|
+
out_req = self.rewrite(l.rstrip('\n'), for_file=True)
|
|
2585
|
+
out_lines.append(out_req + '\n')
|
|
2586
|
+
|
|
2587
|
+
out_file = os.path.join(self._tmp_dir(), os.path.basename(in_file))
|
|
2588
|
+
if os.path.exists(out_file):
|
|
2589
|
+
raise Exception(f'file exists: {out_file}')
|
|
2590
|
+
|
|
2591
|
+
with open(out_file, 'w') as f:
|
|
2592
|
+
f.write(''.join(out_lines))
|
|
2593
|
+
return out_file
|
|
2594
|
+
|
|
2595
|
+
def rewrite(self, in_req: str, *, for_file: bool = False) -> str:
|
|
2596
|
+
if in_req.strip().startswith('-r'):
|
|
2597
|
+
l = in_req.strip()
|
|
2598
|
+
lp, _, rp = l.partition(' ')
|
|
2599
|
+
if lp == '-r':
|
|
2600
|
+
inc_in_file, _, rest = rp.partition(' ')
|
|
2601
|
+
else:
|
|
2602
|
+
inc_in_file, rest = lp[2:], rp
|
|
2603
|
+
|
|
2604
|
+
inc_out_file = self.rewrite_file(inc_in_file)
|
|
2605
|
+
if for_file:
|
|
2606
|
+
return ' '.join(['-r ', inc_out_file, rest])
|
|
2607
|
+
else:
|
|
2608
|
+
return '-r' + inc_out_file
|
|
2609
|
+
|
|
2610
|
+
else:
|
|
2611
|
+
return in_req
|
|
2612
|
+
|
|
2613
|
+
|
|
2609
2614
|
########################################
|
|
2610
2615
|
# ../../../omlish/lite/logs.py
|
|
2611
2616
|
"""
|