ominfra 0.0.0.dev268__py3-none-any.whl → 0.0.0.dev270__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.
- ominfra/scripts/journald2aws.py +582 -583
- ominfra/scripts/manage.py +576 -577
- ominfra/scripts/supervisor.py +578 -579
- {ominfra-0.0.0.dev268.dist-info → ominfra-0.0.0.dev270.dist-info}/METADATA +3 -3
- {ominfra-0.0.0.dev268.dist-info → ominfra-0.0.0.dev270.dist-info}/RECORD +9 -9
- {ominfra-0.0.0.dev268.dist-info → ominfra-0.0.0.dev270.dist-info}/WHEEL +0 -0
- {ominfra-0.0.0.dev268.dist-info → ominfra-0.0.0.dev270.dist-info}/entry_points.txt +0 -0
- {ominfra-0.0.0.dev268.dist-info → ominfra-0.0.0.dev270.dist-info}/licenses/LICENSE +0 -0
- {ominfra-0.0.0.dev268.dist-info → ominfra-0.0.0.dev270.dist-info}/top_level.txt +0 -0
ominfra/scripts/manage.py
CHANGED
@@ -1433,75 +1433,6 @@ def render_ini_sections(
|
|
1433
1433
|
##
|
1434
1434
|
|
1435
1435
|
|
1436
|
-
_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]*)?'
|
1437
|
-
|
1438
|
-
TOML_RE_NUMBER = re.compile(
|
1439
|
-
r"""
|
1440
|
-
0
|
1441
|
-
(?:
|
1442
|
-
x[0-9A-Fa-f](?:_?[0-9A-Fa-f])* # hex
|
1443
|
-
|
|
1444
|
-
b[01](?:_?[01])* # bin
|
1445
|
-
|
|
1446
|
-
o[0-7](?:_?[0-7])* # oct
|
1447
|
-
)
|
1448
|
-
|
|
1449
|
-
[+-]?(?:0|[1-9](?:_?[0-9])*) # dec, integer part
|
1450
|
-
(?P<floatpart>
|
1451
|
-
(?:\.[0-9](?:_?[0-9])*)? # optional fractional part
|
1452
|
-
(?:[eE][+-]?[0-9](?:_?[0-9])*)? # optional exponent part
|
1453
|
-
)
|
1454
|
-
""",
|
1455
|
-
flags=re.VERBOSE,
|
1456
|
-
)
|
1457
|
-
TOML_RE_LOCALTIME = re.compile(_TOML_TIME_RE_STR)
|
1458
|
-
TOML_RE_DATETIME = re.compile(
|
1459
|
-
rf"""
|
1460
|
-
([0-9]{{4}})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01]) # date, e.g. 1988-10-27
|
1461
|
-
(?:
|
1462
|
-
[Tt ]
|
1463
|
-
{_TOML_TIME_RE_STR}
|
1464
|
-
(?:([Zz])|([+-])([01][0-9]|2[0-3]):([0-5][0-9]))? # optional time offset
|
1465
|
-
)?
|
1466
|
-
""",
|
1467
|
-
flags=re.VERBOSE,
|
1468
|
-
)
|
1469
|
-
|
1470
|
-
|
1471
|
-
def toml_match_to_datetime(match: re.Match) -> ta.Union[datetime.datetime, datetime.date]:
|
1472
|
-
"""Convert a `RE_DATETIME` match to `datetime.datetime` or `datetime.date`.
|
1473
|
-
|
1474
|
-
Raises ValueError if the match does not correspond to a valid date or datetime.
|
1475
|
-
"""
|
1476
|
-
(
|
1477
|
-
year_str,
|
1478
|
-
month_str,
|
1479
|
-
day_str,
|
1480
|
-
hour_str,
|
1481
|
-
minute_str,
|
1482
|
-
sec_str,
|
1483
|
-
micros_str,
|
1484
|
-
zulu_time,
|
1485
|
-
offset_sign_str,
|
1486
|
-
offset_hour_str,
|
1487
|
-
offset_minute_str,
|
1488
|
-
) = match.groups()
|
1489
|
-
year, month, day = int(year_str), int(month_str), int(day_str)
|
1490
|
-
if hour_str is None:
|
1491
|
-
return datetime.date(year, month, day)
|
1492
|
-
hour, minute, sec = int(hour_str), int(minute_str), int(sec_str)
|
1493
|
-
micros = int(micros_str.ljust(6, '0')) if micros_str else 0
|
1494
|
-
if offset_sign_str:
|
1495
|
-
tz: ta.Optional[datetime.tzinfo] = toml_cached_tz(
|
1496
|
-
offset_hour_str, offset_minute_str, offset_sign_str,
|
1497
|
-
)
|
1498
|
-
elif zulu_time:
|
1499
|
-
tz = datetime.UTC
|
1500
|
-
else: # local date-time
|
1501
|
-
tz = None
|
1502
|
-
return datetime.datetime(year, month, day, hour, minute, sec, micros, tzinfo=tz)
|
1503
|
-
|
1504
|
-
|
1505
1436
|
@functools.lru_cache() # noqa
|
1506
1437
|
def toml_cached_tz(hour_str: str, minute_str: str, sign_str: str) -> datetime.timezone:
|
1507
1438
|
sign = 1 if sign_str == '+' else -1
|
@@ -1513,47 +1444,25 @@ def toml_cached_tz(hour_str: str, minute_str: str, sign_str: str) -> datetime.ti
|
|
1513
1444
|
)
|
1514
1445
|
|
1515
1446
|
|
1516
|
-
def
|
1517
|
-
|
1518
|
-
|
1519
|
-
return datetime.time(int(hour_str), int(minute_str), int(sec_str), micros)
|
1520
|
-
|
1521
|
-
|
1522
|
-
def toml_match_to_number(match: re.Match, parse_float: TomlParseFloat) -> ta.Any:
|
1523
|
-
if match.group('floatpart'):
|
1524
|
-
return parse_float(match.group())
|
1525
|
-
return int(match.group(), 0)
|
1526
|
-
|
1527
|
-
|
1528
|
-
TOML_ASCII_CTRL = frozenset(chr(i) for i in range(32)) | frozenset(chr(127))
|
1529
|
-
|
1530
|
-
# Neither of these sets include quotation mark or backslash. They are currently handled as separate cases in the parser
|
1531
|
-
# functions.
|
1532
|
-
TOML_ILLEGAL_BASIC_STR_CHARS = TOML_ASCII_CTRL - frozenset('\t')
|
1533
|
-
TOML_ILLEGAL_MULTILINE_BASIC_STR_CHARS = TOML_ASCII_CTRL - frozenset('\t\n')
|
1447
|
+
def toml_make_safe_parse_float(parse_float: TomlParseFloat) -> TomlParseFloat:
|
1448
|
+
"""
|
1449
|
+
A decorator to make `parse_float` safe.
|
1534
1450
|
|
1535
|
-
|
1536
|
-
|
1451
|
+
`parse_float` must not return dicts or lists, because these types would be mixed with parsed TOML tables and arrays,
|
1452
|
+
thus confusing the parser. The returned decorated callable raises `ValueError` instead of returning illegal types.
|
1453
|
+
"""
|
1537
1454
|
|
1538
|
-
|
1455
|
+
# The default `float` callable never returns illegal types. Optimize it.
|
1456
|
+
if parse_float is float:
|
1457
|
+
return float
|
1539
1458
|
|
1540
|
-
|
1541
|
-
|
1542
|
-
|
1543
|
-
|
1544
|
-
|
1459
|
+
def safe_parse_float(float_str: str) -> ta.Any:
|
1460
|
+
float_value = parse_float(float_str)
|
1461
|
+
if isinstance(float_value, (dict, list)):
|
1462
|
+
raise ValueError('parse_float must not return dicts or lists') # noqa
|
1463
|
+
return float_value
|
1545
1464
|
|
1546
|
-
|
1547
|
-
{
|
1548
|
-
'\\b': '\u0008', # backspace
|
1549
|
-
'\\t': '\u0009', # tab
|
1550
|
-
'\\n': '\u000A', # linefeed
|
1551
|
-
'\\f': '\u000C', # form feed
|
1552
|
-
'\\r': '\u000D', # carriage return
|
1553
|
-
'\\"': '\u0022', # quote
|
1554
|
-
'\\\\': '\u005C', # backslash
|
1555
|
-
},
|
1556
|
-
)
|
1465
|
+
return safe_parse_float
|
1557
1466
|
|
1558
1467
|
|
1559
1468
|
class TomlDecodeError(ValueError):
|
@@ -1578,63 +1487,15 @@ def toml_loads(s: str, /, *, parse_float: TomlParseFloat = float) -> ta.Dict[str
|
|
1578
1487
|
src = s.replace('\r\n', '\n')
|
1579
1488
|
except (AttributeError, TypeError):
|
1580
1489
|
raise TypeError(f"Expected str object, not '{type(s).__qualname__}'") from None
|
1581
|
-
pos = 0
|
1582
|
-
out = TomlOutput(TomlNestedDict(), TomlFlags())
|
1583
|
-
header: TomlKey = ()
|
1584
|
-
parse_float = toml_make_safe_parse_float(parse_float)
|
1585
1490
|
|
1586
|
-
|
1587
|
-
while True:
|
1588
|
-
# 1. Skip line leading whitespace
|
1589
|
-
pos = toml_skip_chars(src, pos, TOML_WS)
|
1590
|
-
|
1591
|
-
# 2. Parse rules. Expect one of the following:
|
1592
|
-
# - end of file
|
1593
|
-
# - end of line
|
1594
|
-
# - comment
|
1595
|
-
# - key/value pair
|
1596
|
-
# - append dict to list (and move to its namespace)
|
1597
|
-
# - create dict (and move to its namespace)
|
1598
|
-
# Skip trailing whitespace when applicable.
|
1599
|
-
try:
|
1600
|
-
char = src[pos]
|
1601
|
-
except IndexError:
|
1602
|
-
break
|
1603
|
-
if char == '\n':
|
1604
|
-
pos += 1
|
1605
|
-
continue
|
1606
|
-
if char in TOML_KEY_INITIAL_CHARS:
|
1607
|
-
pos = toml_key_value_rule(src, pos, out, header, parse_float)
|
1608
|
-
pos = toml_skip_chars(src, pos, TOML_WS)
|
1609
|
-
elif char == '[':
|
1610
|
-
try:
|
1611
|
-
second_char: ta.Optional[str] = src[pos + 1]
|
1612
|
-
except IndexError:
|
1613
|
-
second_char = None
|
1614
|
-
out.flags.finalize_pending()
|
1615
|
-
if second_char == '[':
|
1616
|
-
pos, header = toml_create_list_rule(src, pos, out)
|
1617
|
-
else:
|
1618
|
-
pos, header = toml_create_dict_rule(src, pos, out)
|
1619
|
-
pos = toml_skip_chars(src, pos, TOML_WS)
|
1620
|
-
elif char != '#':
|
1621
|
-
raise toml_suffixed_err(src, pos, 'Invalid statement')
|
1622
|
-
|
1623
|
-
# 3. Skip comment
|
1624
|
-
pos = toml_skip_comment(src, pos)
|
1491
|
+
parse_float = toml_make_safe_parse_float(parse_float)
|
1625
1492
|
|
1626
|
-
|
1627
|
-
|
1628
|
-
|
1629
|
-
|
1630
|
-
break
|
1631
|
-
if char != '\n':
|
1632
|
-
raise toml_suffixed_err(
|
1633
|
-
src, pos, 'Expected newline or end of document after a statement',
|
1634
|
-
)
|
1635
|
-
pos += 1
|
1493
|
+
parser = TomlParser(
|
1494
|
+
src,
|
1495
|
+
parse_float=parse_float,
|
1496
|
+
)
|
1636
1497
|
|
1637
|
-
return
|
1498
|
+
return parser.parse()
|
1638
1499
|
|
1639
1500
|
|
1640
1501
|
class TomlFlags:
|
@@ -1646,6 +1507,8 @@ class TomlFlags:
|
|
1646
1507
|
EXPLICIT_NEST = 1
|
1647
1508
|
|
1648
1509
|
def __init__(self) -> None:
|
1510
|
+
super().__init__()
|
1511
|
+
|
1649
1512
|
self._flags: ta.Dict[str, dict] = {}
|
1650
1513
|
self._pending_flags: ta.Set[ta.Tuple[TomlKey, int]] = set()
|
1651
1514
|
|
@@ -1696,6 +1559,8 @@ class TomlFlags:
|
|
1696
1559
|
|
1697
1560
|
class TomlNestedDict:
|
1698
1561
|
def __init__(self) -> None:
|
1562
|
+
super().__init__()
|
1563
|
+
|
1699
1564
|
# The parsed content of the TOML document
|
1700
1565
|
self.dict: ta.Dict[str, ta.Any] = {}
|
1701
1566
|
|
@@ -1728,479 +1593,613 @@ class TomlNestedDict:
|
|
1728
1593
|
cont[last_key] = [{}]
|
1729
1594
|
|
1730
1595
|
|
1731
|
-
class
|
1732
|
-
|
1733
|
-
|
1734
|
-
|
1596
|
+
class TomlParser:
|
1597
|
+
def __init__(
|
1598
|
+
self,
|
1599
|
+
src: str,
|
1600
|
+
*,
|
1601
|
+
parse_float: TomlParseFloat = float,
|
1602
|
+
) -> None:
|
1603
|
+
super().__init__()
|
1735
1604
|
|
1736
|
-
|
1737
|
-
try:
|
1738
|
-
while src[pos] in chars:
|
1739
|
-
pos += 1
|
1740
|
-
except IndexError:
|
1741
|
-
pass
|
1742
|
-
return pos
|
1605
|
+
self.src = src
|
1743
1606
|
|
1607
|
+
self.parse_float = parse_float
|
1744
1608
|
|
1745
|
-
|
1746
|
-
|
1747
|
-
pos
|
1748
|
-
expect: str,
|
1749
|
-
*,
|
1750
|
-
error_on: ta.FrozenSet[str],
|
1751
|
-
error_on_eof: bool,
|
1752
|
-
) -> TomlPos:
|
1753
|
-
try:
|
1754
|
-
new_pos = src.index(expect, pos)
|
1755
|
-
except ValueError:
|
1756
|
-
new_pos = len(src)
|
1757
|
-
if error_on_eof:
|
1758
|
-
raise toml_suffixed_err(src, new_pos, f'Expected {expect!r}') from None
|
1609
|
+
self.data = TomlNestedDict()
|
1610
|
+
self.flags = TomlFlags()
|
1611
|
+
self.pos = 0
|
1759
1612
|
|
1760
|
-
|
1761
|
-
while src[pos] not in error_on:
|
1762
|
-
pos += 1
|
1763
|
-
raise toml_suffixed_err(src, pos, f'Found invalid character {src[pos]!r}')
|
1764
|
-
return new_pos
|
1613
|
+
ASCII_CTRL = frozenset(chr(i) for i in range(32)) | frozenset(chr(127))
|
1765
1614
|
|
1615
|
+
# Neither of these sets include quotation mark or backslash. They are currently handled as separate cases in the
|
1616
|
+
# parser functions.
|
1617
|
+
ILLEGAL_BASIC_STR_CHARS = ASCII_CTRL - frozenset('\t')
|
1618
|
+
ILLEGAL_MULTILINE_BASIC_STR_CHARS = ASCII_CTRL - frozenset('\t\n')
|
1766
1619
|
|
1767
|
-
|
1768
|
-
|
1769
|
-
char: ta.Optional[str] = src[pos]
|
1770
|
-
except IndexError:
|
1771
|
-
char = None
|
1772
|
-
if char == '#':
|
1773
|
-
return toml_skip_until(
|
1774
|
-
src, pos + 1, '\n', error_on=TOML_ILLEGAL_COMMENT_CHARS, error_on_eof=False,
|
1775
|
-
)
|
1776
|
-
return pos
|
1620
|
+
ILLEGAL_LITERAL_STR_CHARS = ILLEGAL_BASIC_STR_CHARS
|
1621
|
+
ILLEGAL_MULTILINE_LITERAL_STR_CHARS = ILLEGAL_MULTILINE_BASIC_STR_CHARS
|
1777
1622
|
|
1623
|
+
ILLEGAL_COMMENT_CHARS = ILLEGAL_BASIC_STR_CHARS
|
1778
1624
|
|
1779
|
-
|
1780
|
-
|
1781
|
-
|
1782
|
-
|
1783
|
-
|
1784
|
-
if pos == pos_before_skip:
|
1785
|
-
return pos
|
1625
|
+
WS = frozenset(' \t')
|
1626
|
+
WS_AND_NEWLINE = WS | frozenset('\n')
|
1627
|
+
BARE_KEY_CHARS = frozenset(string.ascii_letters + string.digits + '-_')
|
1628
|
+
KEY_INITIAL_CHARS = BARE_KEY_CHARS | frozenset("\"'")
|
1629
|
+
HEXDIGIT_CHARS = frozenset(string.hexdigits)
|
1786
1630
|
|
1631
|
+
BASIC_STR_ESCAPE_REPLACEMENTS = types.MappingProxyType({
|
1632
|
+
'\\b': '\u0008', # backspace
|
1633
|
+
'\\t': '\u0009', # tab
|
1634
|
+
'\\n': '\u000A', # linefeed
|
1635
|
+
'\\f': '\u000C', # form feed
|
1636
|
+
'\\r': '\u000D', # carriage return
|
1637
|
+
'\\"': '\u0022', # quote
|
1638
|
+
'\\\\': '\u005C', # backslash
|
1639
|
+
})
|
1787
1640
|
|
1788
|
-
def
|
1789
|
-
|
1790
|
-
pos = toml_skip_chars(src, pos, TOML_WS)
|
1791
|
-
pos, key = toml_parse_key(src, pos)
|
1641
|
+
def parse(self) -> ta.Dict[str, ta.Any]: # noqa: C901
|
1642
|
+
header: TomlKey = ()
|
1792
1643
|
|
1793
|
-
|
1794
|
-
|
1795
|
-
|
1796
|
-
|
1797
|
-
|
1798
|
-
|
1799
|
-
|
1644
|
+
# Parse one statement at a time (typically means one line in TOML source)
|
1645
|
+
while True:
|
1646
|
+
# 1. Skip line leading whitespace
|
1647
|
+
self.skip_chars(self.WS)
|
1648
|
+
|
1649
|
+
# 2. Parse rules. Expect one of the following:
|
1650
|
+
# - end of file
|
1651
|
+
# - end of line
|
1652
|
+
# - comment
|
1653
|
+
# - key/value pair
|
1654
|
+
# - append dict to list (and move to its namespace)
|
1655
|
+
# - create dict (and move to its namespace)
|
1656
|
+
# Skip trailing whitespace when applicable.
|
1657
|
+
try:
|
1658
|
+
char = self.src[self.pos]
|
1659
|
+
except IndexError:
|
1660
|
+
break
|
1661
|
+
if char == '\n':
|
1662
|
+
self.pos += 1
|
1663
|
+
continue
|
1664
|
+
if char in self.KEY_INITIAL_CHARS:
|
1665
|
+
self.key_value_rule(header)
|
1666
|
+
self.skip_chars(self.WS)
|
1667
|
+
elif char == '[':
|
1668
|
+
try:
|
1669
|
+
second_char: ta.Optional[str] = self.src[self.pos + 1]
|
1670
|
+
except IndexError:
|
1671
|
+
second_char = None
|
1672
|
+
self.flags.finalize_pending()
|
1673
|
+
if second_char == '[':
|
1674
|
+
header = self.create_list_rule()
|
1675
|
+
else:
|
1676
|
+
header = self.create_dict_rule()
|
1677
|
+
self.skip_chars(self.WS)
|
1678
|
+
elif char != '#':
|
1679
|
+
raise self.suffixed_err('Invalid statement')
|
1800
1680
|
|
1801
|
-
|
1802
|
-
|
1803
|
-
return pos + 1, key
|
1681
|
+
# 3. Skip comment
|
1682
|
+
self.skip_comment()
|
1804
1683
|
|
1684
|
+
# 4. Expect end of line or end of file
|
1685
|
+
try:
|
1686
|
+
char = self.src[self.pos]
|
1687
|
+
except IndexError:
|
1688
|
+
break
|
1689
|
+
if char != '\n':
|
1690
|
+
raise self.suffixed_err('Expected newline or end of document after a statement')
|
1691
|
+
self.pos += 1
|
1805
1692
|
|
1806
|
-
|
1807
|
-
pos += 2 # Skip "[["
|
1808
|
-
pos = toml_skip_chars(src, pos, TOML_WS)
|
1809
|
-
pos, key = toml_parse_key(src, pos)
|
1693
|
+
return self.data.dict
|
1810
1694
|
|
1811
|
-
|
1812
|
-
|
1813
|
-
|
1814
|
-
|
1815
|
-
|
1816
|
-
|
1817
|
-
try:
|
1818
|
-
out.data.append_nest_to_list(key)
|
1819
|
-
except KeyError:
|
1820
|
-
raise toml_suffixed_err(src, pos, 'Cannot overwrite a value') from None
|
1695
|
+
def skip_chars(self, chars: ta.Iterable[str]) -> None:
|
1696
|
+
try:
|
1697
|
+
while self.src[self.pos] in chars:
|
1698
|
+
self.pos += 1
|
1699
|
+
except IndexError:
|
1700
|
+
pass
|
1821
1701
|
|
1822
|
-
|
1823
|
-
|
1824
|
-
|
1702
|
+
def skip_until(
|
1703
|
+
self,
|
1704
|
+
expect: str,
|
1705
|
+
*,
|
1706
|
+
error_on: ta.FrozenSet[str],
|
1707
|
+
error_on_eof: bool,
|
1708
|
+
) -> None:
|
1709
|
+
try:
|
1710
|
+
new_pos = self.src.index(expect, self.pos)
|
1711
|
+
except ValueError:
|
1712
|
+
new_pos = len(self.src)
|
1713
|
+
if error_on_eof:
|
1714
|
+
raise self.suffixed_err(f'Expected {expect!r}', pos=new_pos) from None
|
1825
1715
|
|
1716
|
+
if not error_on.isdisjoint(self.src[self.pos:new_pos]):
|
1717
|
+
while self.src[self.pos] not in error_on:
|
1718
|
+
self.pos += 1
|
1719
|
+
raise self.suffixed_err(f'Found invalid character {self.src[self.pos]!r}')
|
1720
|
+
self.pos = new_pos
|
1826
1721
|
|
1827
|
-
def
|
1828
|
-
|
1829
|
-
|
1830
|
-
|
1831
|
-
|
1832
|
-
|
1833
|
-
|
1834
|
-
|
1835
|
-
|
1836
|
-
|
1837
|
-
|
1838
|
-
|
1839
|
-
for cont_key in relative_path_cont_keys:
|
1840
|
-
# Check that dotted key syntax does not redefine an existing table
|
1841
|
-
if out.flags.is_(cont_key, TomlFlags.EXPLICIT_NEST):
|
1842
|
-
raise toml_suffixed_err(src, pos, f'Cannot redefine namespace {cont_key}')
|
1843
|
-
# Containers in the relative path can't be opened with the table syntax or dotted key/value syntax in following
|
1844
|
-
# table sections.
|
1845
|
-
out.flags.add_pending(cont_key, TomlFlags.EXPLICIT_NEST)
|
1846
|
-
|
1847
|
-
if out.flags.is_(abs_key_parent, TomlFlags.FROZEN):
|
1848
|
-
raise toml_suffixed_err(
|
1849
|
-
src,
|
1850
|
-
pos,
|
1851
|
-
f'Cannot mutate immutable namespace {abs_key_parent}',
|
1852
|
-
)
|
1722
|
+
def skip_comment(self) -> None:
|
1723
|
+
try:
|
1724
|
+
char: ta.Optional[str] = self.src[self.pos]
|
1725
|
+
except IndexError:
|
1726
|
+
char = None
|
1727
|
+
if char == '#':
|
1728
|
+
self.pos += 1
|
1729
|
+
self.skip_until(
|
1730
|
+
'\n',
|
1731
|
+
error_on=self.ILLEGAL_COMMENT_CHARS,
|
1732
|
+
error_on_eof=False,
|
1733
|
+
)
|
1853
1734
|
|
1854
|
-
|
1855
|
-
|
1856
|
-
|
1857
|
-
|
1858
|
-
|
1859
|
-
|
1860
|
-
|
1861
|
-
if isinstance(value, (dict, list)):
|
1862
|
-
out.flags.set(header + key, TomlFlags.FROZEN, recursive=True)
|
1863
|
-
nest[key_stem] = value
|
1864
|
-
return pos
|
1735
|
+
def skip_comments_and_array_ws(self) -> None:
|
1736
|
+
while True:
|
1737
|
+
pos_before_skip = self.pos
|
1738
|
+
self.skip_chars(self.WS_AND_NEWLINE)
|
1739
|
+
self.skip_comment()
|
1740
|
+
if self.pos == pos_before_skip:
|
1741
|
+
return
|
1865
1742
|
|
1743
|
+
def create_dict_rule(self) -> TomlKey:
|
1744
|
+
self.pos += 1 # Skip "["
|
1745
|
+
self.skip_chars(self.WS)
|
1746
|
+
key = self.parse_key()
|
1866
1747
|
|
1867
|
-
|
1868
|
-
|
1869
|
-
|
1870
|
-
parse_float: TomlParseFloat,
|
1871
|
-
) -> ta.Tuple[TomlPos, TomlKey, ta.Any]:
|
1872
|
-
pos, key = toml_parse_key(src, pos)
|
1873
|
-
try:
|
1874
|
-
char: ta.Optional[str] = src[pos]
|
1875
|
-
except IndexError:
|
1876
|
-
char = None
|
1877
|
-
if char != '=':
|
1878
|
-
raise toml_suffixed_err(src, pos, "Expected '=' after a key in a key/value pair")
|
1879
|
-
pos += 1
|
1880
|
-
pos = toml_skip_chars(src, pos, TOML_WS)
|
1881
|
-
pos, value = toml_parse_value(src, pos, parse_float)
|
1882
|
-
return pos, key, value
|
1883
|
-
|
1884
|
-
|
1885
|
-
def toml_parse_key(src: str, pos: TomlPos) -> ta.Tuple[TomlPos, TomlKey]:
|
1886
|
-
pos, key_part = toml_parse_key_part(src, pos)
|
1887
|
-
key: TomlKey = (key_part,)
|
1888
|
-
pos = toml_skip_chars(src, pos, TOML_WS)
|
1889
|
-
while True:
|
1748
|
+
if self.flags.is_(key, TomlFlags.EXPLICIT_NEST) or self.flags.is_(key, TomlFlags.FROZEN):
|
1749
|
+
raise self.suffixed_err(f'Cannot declare {key} twice')
|
1750
|
+
self.flags.set(key, TomlFlags.EXPLICIT_NEST, recursive=False)
|
1890
1751
|
try:
|
1891
|
-
|
1892
|
-
except
|
1893
|
-
|
1894
|
-
|
1895
|
-
|
1896
|
-
|
1897
|
-
pos
|
1898
|
-
|
1899
|
-
|
1900
|
-
|
1752
|
+
self.data.get_or_create_nest(key)
|
1753
|
+
except KeyError:
|
1754
|
+
raise self.suffixed_err('Cannot overwrite a value') from None
|
1755
|
+
|
1756
|
+
if not self.src.startswith(']', self.pos):
|
1757
|
+
raise self.suffixed_err("Expected ']' at the end of a table declaration")
|
1758
|
+
self.pos += 1
|
1759
|
+
return key
|
1760
|
+
|
1761
|
+
def create_list_rule(self) -> TomlKey:
|
1762
|
+
self.pos += 2 # Skip "[["
|
1763
|
+
self.skip_chars(self.WS)
|
1764
|
+
key = self.parse_key()
|
1765
|
+
|
1766
|
+
if self.flags.is_(key, TomlFlags.FROZEN):
|
1767
|
+
raise self.suffixed_err(f'Cannot mutate immutable namespace {key}')
|
1768
|
+
# Free the namespace now that it points to another empty list item...
|
1769
|
+
self.flags.unset_all(key)
|
1770
|
+
# ...but this key precisely is still prohibited from table declaration
|
1771
|
+
self.flags.set(key, TomlFlags.EXPLICIT_NEST, recursive=False)
|
1772
|
+
try:
|
1773
|
+
self.data.append_nest_to_list(key)
|
1774
|
+
except KeyError:
|
1775
|
+
raise self.suffixed_err('Cannot overwrite a value') from None
|
1901
1776
|
|
1777
|
+
if not self.src.startswith(']]', self.pos):
|
1778
|
+
raise self.suffixed_err("Expected ']]' at the end of an array declaration")
|
1779
|
+
self.pos += 2
|
1780
|
+
return key
|
1902
1781
|
|
1903
|
-
def
|
1904
|
-
|
1905
|
-
char: ta.Optional[str] = src[pos]
|
1906
|
-
except IndexError:
|
1907
|
-
char = None
|
1908
|
-
if char in TOML_BARE_KEY_CHARS:
|
1909
|
-
start_pos = pos
|
1910
|
-
pos = toml_skip_chars(src, pos, TOML_BARE_KEY_CHARS)
|
1911
|
-
return pos, src[start_pos:pos]
|
1912
|
-
if char == "'":
|
1913
|
-
return toml_parse_literal_str(src, pos)
|
1914
|
-
if char == '"':
|
1915
|
-
return toml_parse_one_line_basic_str(src, pos)
|
1916
|
-
raise toml_suffixed_err(src, pos, 'Invalid initial character for a key part')
|
1917
|
-
|
1918
|
-
|
1919
|
-
def toml_parse_one_line_basic_str(src: str, pos: TomlPos) -> ta.Tuple[TomlPos, str]:
|
1920
|
-
pos += 1
|
1921
|
-
return toml_parse_basic_str(src, pos, multiline=False)
|
1922
|
-
|
1923
|
-
|
1924
|
-
def toml_parse_array(src: str, pos: TomlPos, parse_float: TomlParseFloat) -> ta.Tuple[TomlPos, list]:
|
1925
|
-
pos += 1
|
1926
|
-
array: list = []
|
1927
|
-
|
1928
|
-
pos = toml_skip_comments_and_array_ws(src, pos)
|
1929
|
-
if src.startswith(']', pos):
|
1930
|
-
return pos + 1, array
|
1931
|
-
while True:
|
1932
|
-
pos, val = toml_parse_value(src, pos, parse_float)
|
1933
|
-
array.append(val)
|
1934
|
-
pos = toml_skip_comments_and_array_ws(src, pos)
|
1935
|
-
|
1936
|
-
c = src[pos:pos + 1]
|
1937
|
-
if c == ']':
|
1938
|
-
return pos + 1, array
|
1939
|
-
if c != ',':
|
1940
|
-
raise toml_suffixed_err(src, pos, 'Unclosed array')
|
1941
|
-
pos += 1
|
1942
|
-
|
1943
|
-
pos = toml_skip_comments_and_array_ws(src, pos)
|
1944
|
-
if src.startswith(']', pos):
|
1945
|
-
return pos + 1, array
|
1946
|
-
|
1947
|
-
|
1948
|
-
def toml_parse_inline_table(src: str, pos: TomlPos, parse_float: TomlParseFloat) -> ta.Tuple[TomlPos, dict]:
|
1949
|
-
pos += 1
|
1950
|
-
nested_dict = TomlNestedDict()
|
1951
|
-
flags = TomlFlags()
|
1952
|
-
|
1953
|
-
pos = toml_skip_chars(src, pos, TOML_WS)
|
1954
|
-
if src.startswith('}', pos):
|
1955
|
-
return pos + 1, nested_dict.dict
|
1956
|
-
while True:
|
1957
|
-
pos, key, value = toml_parse_key_value_pair(src, pos, parse_float)
|
1782
|
+
def key_value_rule(self, header: TomlKey) -> None:
|
1783
|
+
key, value = self.parse_key_value_pair()
|
1958
1784
|
key_parent, key_stem = key[:-1], key[-1]
|
1959
|
-
|
1960
|
-
|
1785
|
+
abs_key_parent = header + key_parent
|
1786
|
+
|
1787
|
+
relative_path_cont_keys = (header + key[:i] for i in range(1, len(key)))
|
1788
|
+
for cont_key in relative_path_cont_keys:
|
1789
|
+
# Check that dotted key syntax does not redefine an existing table
|
1790
|
+
if self.flags.is_(cont_key, TomlFlags.EXPLICIT_NEST):
|
1791
|
+
raise self.suffixed_err(f'Cannot redefine namespace {cont_key}')
|
1792
|
+
# Containers in the relative path can't be opened with the table syntax or dotted key/value syntax in
|
1793
|
+
# following table sections.
|
1794
|
+
self.flags.add_pending(cont_key, TomlFlags.EXPLICIT_NEST)
|
1795
|
+
|
1796
|
+
if self.flags.is_(abs_key_parent, TomlFlags.FROZEN):
|
1797
|
+
raise self.suffixed_err(f'Cannot mutate immutable namespace {abs_key_parent}')
|
1798
|
+
|
1961
1799
|
try:
|
1962
|
-
nest =
|
1800
|
+
nest = self.data.get_or_create_nest(abs_key_parent)
|
1963
1801
|
except KeyError:
|
1964
|
-
raise
|
1802
|
+
raise self.suffixed_err('Cannot overwrite a value') from None
|
1965
1803
|
if key_stem in nest:
|
1966
|
-
raise
|
1967
|
-
|
1968
|
-
pos = toml_skip_chars(src, pos, TOML_WS)
|
1969
|
-
c = src[pos:pos + 1]
|
1970
|
-
if c == '}':
|
1971
|
-
return pos + 1, nested_dict.dict
|
1972
|
-
if c != ',':
|
1973
|
-
raise toml_suffixed_err(src, pos, 'Unclosed inline table')
|
1804
|
+
raise self.suffixed_err('Cannot overwrite a value')
|
1805
|
+
# Mark inline table and array namespaces recursively immutable
|
1974
1806
|
if isinstance(value, (dict, list)):
|
1975
|
-
flags.set(key, TomlFlags.FROZEN, recursive=True)
|
1976
|
-
|
1977
|
-
pos = toml_skip_chars(src, pos, TOML_WS)
|
1978
|
-
|
1807
|
+
self.flags.set(header + key, TomlFlags.FROZEN, recursive=True)
|
1808
|
+
nest[key_stem] = value
|
1979
1809
|
|
1980
|
-
def
|
1981
|
-
|
1982
|
-
|
1983
|
-
|
1984
|
-
|
1985
|
-
|
1986
|
-
|
1987
|
-
|
1988
|
-
|
1989
|
-
|
1990
|
-
|
1991
|
-
|
1992
|
-
|
1810
|
+
def parse_key_value_pair(self) -> ta.Tuple[TomlKey, ta.Any]:
|
1811
|
+
key = self.parse_key()
|
1812
|
+
try:
|
1813
|
+
char: ta.Optional[str] = self.src[self.pos]
|
1814
|
+
except IndexError:
|
1815
|
+
char = None
|
1816
|
+
if char != '=':
|
1817
|
+
raise self.suffixed_err("Expected '=' after a key in a key/value pair")
|
1818
|
+
self.pos += 1
|
1819
|
+
self.skip_chars(self.WS)
|
1820
|
+
value = self.parse_value()
|
1821
|
+
return key, value
|
1822
|
+
|
1823
|
+
def parse_key(self) -> TomlKey:
|
1824
|
+
key_part = self.parse_key_part()
|
1825
|
+
key: TomlKey = (key_part,)
|
1826
|
+
self.skip_chars(self.WS)
|
1827
|
+
while True:
|
1993
1828
|
try:
|
1994
|
-
char = src[pos]
|
1829
|
+
char: ta.Optional[str] = self.src[self.pos]
|
1995
1830
|
except IndexError:
|
1996
|
-
|
1997
|
-
if char != '
|
1998
|
-
|
1999
|
-
pos += 1
|
2000
|
-
|
2001
|
-
|
2002
|
-
|
2003
|
-
|
2004
|
-
|
2005
|
-
|
2006
|
-
|
2007
|
-
|
2008
|
-
|
2009
|
-
|
1831
|
+
char = None
|
1832
|
+
if char != '.':
|
1833
|
+
return key
|
1834
|
+
self.pos += 1
|
1835
|
+
self.skip_chars(self.WS)
|
1836
|
+
key_part = self.parse_key_part()
|
1837
|
+
key += (key_part,)
|
1838
|
+
self.skip_chars(self.WS)
|
1839
|
+
|
1840
|
+
def parse_key_part(self) -> str:
|
1841
|
+
try:
|
1842
|
+
char: ta.Optional[str] = self.src[self.pos]
|
1843
|
+
except IndexError:
|
1844
|
+
char = None
|
1845
|
+
if char in self.BARE_KEY_CHARS:
|
1846
|
+
start_pos = self.pos
|
1847
|
+
self.skip_chars(self.BARE_KEY_CHARS)
|
1848
|
+
return self.src[start_pos:self.pos]
|
1849
|
+
if char == "'":
|
1850
|
+
return self.parse_literal_str()
|
1851
|
+
if char == '"':
|
1852
|
+
return self.parse_one_line_basic_str()
|
1853
|
+
raise self.suffixed_err('Invalid initial character for a key part')
|
2010
1854
|
|
1855
|
+
def parse_one_line_basic_str(self) -> str:
|
1856
|
+
self.pos += 1
|
1857
|
+
return self.parse_basic_str(multiline=False)
|
2011
1858
|
|
2012
|
-
def
|
2013
|
-
|
1859
|
+
def parse_array(self) -> list:
|
1860
|
+
self.pos += 1
|
1861
|
+
array: list = []
|
2014
1862
|
|
1863
|
+
self.skip_comments_and_array_ws()
|
1864
|
+
if self.src.startswith(']', self.pos):
|
1865
|
+
self.pos += 1
|
1866
|
+
return array
|
1867
|
+
while True:
|
1868
|
+
val = self.parse_value()
|
1869
|
+
array.append(val)
|
1870
|
+
self.skip_comments_and_array_ws()
|
1871
|
+
|
1872
|
+
c = self.src[self.pos:self.pos + 1]
|
1873
|
+
if c == ']':
|
1874
|
+
self.pos += 1
|
1875
|
+
return array
|
1876
|
+
if c != ',':
|
1877
|
+
raise self.suffixed_err('Unclosed array')
|
1878
|
+
self.pos += 1
|
1879
|
+
|
1880
|
+
self.skip_comments_and_array_ws()
|
1881
|
+
if self.src.startswith(']', self.pos):
|
1882
|
+
self.pos += 1
|
1883
|
+
return array
|
1884
|
+
|
1885
|
+
def parse_inline_table(self) -> dict:
|
1886
|
+
self.pos += 1
|
1887
|
+
nested_dict = TomlNestedDict()
|
1888
|
+
flags = TomlFlags()
|
1889
|
+
|
1890
|
+
self.skip_chars(self.WS)
|
1891
|
+
if self.src.startswith('}', self.pos):
|
1892
|
+
self.pos += 1
|
1893
|
+
return nested_dict.dict
|
1894
|
+
while True:
|
1895
|
+
key, value = self.parse_key_value_pair()
|
1896
|
+
key_parent, key_stem = key[:-1], key[-1]
|
1897
|
+
if flags.is_(key, TomlFlags.FROZEN):
|
1898
|
+
raise self.suffixed_err(f'Cannot mutate immutable namespace {key}')
|
1899
|
+
try:
|
1900
|
+
nest = nested_dict.get_or_create_nest(key_parent, access_lists=False)
|
1901
|
+
except KeyError:
|
1902
|
+
raise self.suffixed_err('Cannot overwrite a value') from None
|
1903
|
+
if key_stem in nest:
|
1904
|
+
raise self.suffixed_err(f'Duplicate inline table key {key_stem!r}')
|
1905
|
+
nest[key_stem] = value
|
1906
|
+
self.skip_chars(self.WS)
|
1907
|
+
c = self.src[self.pos:self.pos + 1]
|
1908
|
+
if c == '}':
|
1909
|
+
self.pos += 1
|
1910
|
+
return nested_dict.dict
|
1911
|
+
if c != ',':
|
1912
|
+
raise self.suffixed_err('Unclosed inline table')
|
1913
|
+
if isinstance(value, (dict, list)):
|
1914
|
+
flags.set(key, TomlFlags.FROZEN, recursive=True)
|
1915
|
+
self.pos += 1
|
1916
|
+
self.skip_chars(self.WS)
|
1917
|
+
|
1918
|
+
def parse_basic_str_escape(self, multiline: bool = False) -> str:
|
1919
|
+
escape_id = self.src[self.pos:self.pos + 2]
|
1920
|
+
self.pos += 2
|
1921
|
+
if multiline and escape_id in {'\\ ', '\\\t', '\\\n'}:
|
1922
|
+
# Skip whitespace until next non-whitespace character or end of the doc. Error if non-whitespace is found
|
1923
|
+
# before newline.
|
1924
|
+
if escape_id != '\\\n':
|
1925
|
+
self.skip_chars(self.WS)
|
1926
|
+
try:
|
1927
|
+
char = self.src[self.pos]
|
1928
|
+
except IndexError:
|
1929
|
+
return ''
|
1930
|
+
if char != '\n':
|
1931
|
+
raise self.suffixed_err("Unescaped '\\' in a string")
|
1932
|
+
self.pos += 1
|
1933
|
+
self.skip_chars(self.WS_AND_NEWLINE)
|
1934
|
+
return ''
|
1935
|
+
if escape_id == '\\u':
|
1936
|
+
return self.parse_hex_char(4)
|
1937
|
+
if escape_id == '\\U':
|
1938
|
+
return self.parse_hex_char(8)
|
1939
|
+
try:
|
1940
|
+
return self.BASIC_STR_ESCAPE_REPLACEMENTS[escape_id]
|
1941
|
+
except KeyError:
|
1942
|
+
raise self.suffixed_err("Unescaped '\\' in a string") from None
|
2015
1943
|
|
2016
|
-
def
|
2017
|
-
|
2018
|
-
if len(hex_str) != hex_len or not TOML_HEXDIGIT_CHARS.issuperset(hex_str):
|
2019
|
-
raise toml_suffixed_err(src, pos, 'Invalid hex value')
|
2020
|
-
pos += hex_len
|
2021
|
-
hex_int = int(hex_str, 16)
|
2022
|
-
if not toml_is_unicode_scalar_value(hex_int):
|
2023
|
-
raise toml_suffixed_err(src, pos, 'Escaped character is not a Unicode scalar value')
|
2024
|
-
return pos, chr(hex_int)
|
1944
|
+
def parse_basic_str_escape_multiline(self) -> str:
|
1945
|
+
return self.parse_basic_str_escape(multiline=True)
|
2025
1946
|
|
1947
|
+
@classmethod
|
1948
|
+
def is_unicode_scalar_value(cls, codepoint: int) -> bool:
|
1949
|
+
return (0 <= codepoint <= 55295) or (57344 <= codepoint <= 1114111)
|
1950
|
+
|
1951
|
+
def parse_hex_char(self, hex_len: int) -> str:
|
1952
|
+
hex_str = self.src[self.pos:self.pos + hex_len]
|
1953
|
+
if len(hex_str) != hex_len or not self.HEXDIGIT_CHARS.issuperset(hex_str):
|
1954
|
+
raise self.suffixed_err('Invalid hex value')
|
1955
|
+
self.pos += hex_len
|
1956
|
+
hex_int = int(hex_str, 16)
|
1957
|
+
if not self.is_unicode_scalar_value(hex_int):
|
1958
|
+
raise self.suffixed_err('Escaped character is not a Unicode scalar value')
|
1959
|
+
return chr(hex_int)
|
1960
|
+
|
1961
|
+
def parse_literal_str(self) -> str:
|
1962
|
+
self.pos += 1 # Skip starting apostrophe
|
1963
|
+
start_pos = self.pos
|
1964
|
+
self.skip_until("'", error_on=self.ILLEGAL_LITERAL_STR_CHARS, error_on_eof=True)
|
1965
|
+
end_pos = self.pos
|
1966
|
+
self.pos += 1
|
1967
|
+
return self.src[start_pos:end_pos] # Skip ending apostrophe
|
1968
|
+
|
1969
|
+
def parse_multiline_str(self, *, literal: bool) -> str:
|
1970
|
+
self.pos += 3
|
1971
|
+
if self.src.startswith('\n', self.pos):
|
1972
|
+
self.pos += 1
|
1973
|
+
|
1974
|
+
if literal:
|
1975
|
+
delim = "'"
|
1976
|
+
start_pos = self.pos
|
1977
|
+
self.skip_until(
|
1978
|
+
"'''",
|
1979
|
+
error_on=self.ILLEGAL_MULTILINE_LITERAL_STR_CHARS,
|
1980
|
+
error_on_eof=True,
|
1981
|
+
)
|
1982
|
+
result = self.src[start_pos:self.pos]
|
1983
|
+
self.pos += 3
|
1984
|
+
else:
|
1985
|
+
delim = '"'
|
1986
|
+
result = self.parse_basic_str(multiline=True)
|
1987
|
+
|
1988
|
+
# Add at maximum two extra apostrophes/quotes if the end sequence is 4 or 5 chars long instead of just 3.
|
1989
|
+
if not self.src.startswith(delim, self.pos):
|
1990
|
+
return result
|
1991
|
+
self.pos += 1
|
1992
|
+
if not self.src.startswith(delim, self.pos):
|
1993
|
+
return result + delim
|
1994
|
+
self.pos += 1
|
1995
|
+
return result + (delim * 2)
|
1996
|
+
|
1997
|
+
def parse_basic_str(self, *, multiline: bool) -> str:
|
1998
|
+
if multiline:
|
1999
|
+
error_on = self.ILLEGAL_MULTILINE_BASIC_STR_CHARS
|
2000
|
+
parse_escapes = self.parse_basic_str_escape_multiline
|
2001
|
+
else:
|
2002
|
+
error_on = self.ILLEGAL_BASIC_STR_CHARS
|
2003
|
+
parse_escapes = self.parse_basic_str_escape
|
2004
|
+
result = ''
|
2005
|
+
start_pos = self.pos
|
2006
|
+
while True:
|
2007
|
+
try:
|
2008
|
+
char = self.src[self.pos]
|
2009
|
+
except IndexError:
|
2010
|
+
raise self.suffixed_err('Unterminated string') from None
|
2011
|
+
if char == '"':
|
2012
|
+
if not multiline:
|
2013
|
+
end_pos = self.pos
|
2014
|
+
self.pos += 1
|
2015
|
+
return result + self.src[start_pos:end_pos]
|
2016
|
+
if self.src.startswith('"""', self.pos):
|
2017
|
+
end_pos = self.pos
|
2018
|
+
self.pos += 3
|
2019
|
+
return result + self.src[start_pos:end_pos]
|
2020
|
+
self.pos += 1
|
2021
|
+
continue
|
2022
|
+
if char == '\\':
|
2023
|
+
result += self.src[start_pos:self.pos]
|
2024
|
+
parsed_escape = parse_escapes()
|
2025
|
+
result += parsed_escape
|
2026
|
+
start_pos = self.pos
|
2027
|
+
continue
|
2028
|
+
if char in error_on:
|
2029
|
+
raise self.suffixed_err(f'Illegal character {char!r}')
|
2030
|
+
self.pos += 1
|
2026
2031
|
|
2027
|
-
def
|
2028
|
-
pos += 1 # Skip starting apostrophe
|
2029
|
-
start_pos = pos
|
2030
|
-
pos = toml_skip_until(
|
2031
|
-
src, pos, "'", error_on=TOML_ILLEGAL_LITERAL_STR_CHARS, error_on_eof=True,
|
2032
|
-
)
|
2033
|
-
return pos + 1, src[start_pos:pos] # Skip ending apostrophe
|
2034
|
-
|
2035
|
-
|
2036
|
-
def toml_parse_multiline_str(src: str, pos: TomlPos, *, literal: bool) -> ta.Tuple[TomlPos, str]:
|
2037
|
-
pos += 3
|
2038
|
-
if src.startswith('\n', pos):
|
2039
|
-
pos += 1
|
2040
|
-
|
2041
|
-
if literal:
|
2042
|
-
delim = "'"
|
2043
|
-
end_pos = toml_skip_until(
|
2044
|
-
src,
|
2045
|
-
pos,
|
2046
|
-
"'''",
|
2047
|
-
error_on=TOML_ILLEGAL_MULTILINE_LITERAL_STR_CHARS,
|
2048
|
-
error_on_eof=True,
|
2049
|
-
)
|
2050
|
-
result = src[pos:end_pos]
|
2051
|
-
pos = end_pos + 3
|
2052
|
-
else:
|
2053
|
-
delim = '"'
|
2054
|
-
pos, result = toml_parse_basic_str(src, pos, multiline=True)
|
2055
|
-
|
2056
|
-
# Add at maximum two extra apostrophes/quotes if the end sequence is 4 or 5 chars long instead of just 3.
|
2057
|
-
if not src.startswith(delim, pos):
|
2058
|
-
return pos, result
|
2059
|
-
pos += 1
|
2060
|
-
if not src.startswith(delim, pos):
|
2061
|
-
return pos, result + delim
|
2062
|
-
pos += 1
|
2063
|
-
return pos, result + (delim * 2)
|
2064
|
-
|
2065
|
-
|
2066
|
-
def toml_parse_basic_str(src: str, pos: TomlPos, *, multiline: bool) -> ta.Tuple[TomlPos, str]:
|
2067
|
-
if multiline:
|
2068
|
-
error_on = TOML_ILLEGAL_MULTILINE_BASIC_STR_CHARS
|
2069
|
-
parse_escapes = toml_parse_basic_str_escape_multiline
|
2070
|
-
else:
|
2071
|
-
error_on = TOML_ILLEGAL_BASIC_STR_CHARS
|
2072
|
-
parse_escapes = toml_parse_basic_str_escape
|
2073
|
-
result = ''
|
2074
|
-
start_pos = pos
|
2075
|
-
while True:
|
2032
|
+
def parse_value(self) -> ta.Any: # noqa: C901
|
2076
2033
|
try:
|
2077
|
-
char = src[pos]
|
2034
|
+
char: ta.Optional[str] = self.src[self.pos]
|
2078
2035
|
except IndexError:
|
2079
|
-
|
2036
|
+
char = None
|
2037
|
+
|
2038
|
+
# IMPORTANT: order conditions based on speed of checking and likelihood
|
2039
|
+
|
2040
|
+
# Basic strings
|
2080
2041
|
if char == '"':
|
2081
|
-
if
|
2082
|
-
return
|
2083
|
-
|
2084
|
-
|
2085
|
-
|
2086
|
-
|
2087
|
-
|
2088
|
-
|
2089
|
-
|
2090
|
-
|
2091
|
-
|
2092
|
-
|
2093
|
-
|
2094
|
-
|
2095
|
-
|
2042
|
+
if self.src.startswith('"""', self.pos):
|
2043
|
+
return self.parse_multiline_str(literal=False)
|
2044
|
+
return self.parse_one_line_basic_str()
|
2045
|
+
|
2046
|
+
# Literal strings
|
2047
|
+
if char == "'":
|
2048
|
+
if self.src.startswith("'''", self.pos):
|
2049
|
+
return self.parse_multiline_str(literal=True)
|
2050
|
+
return self.parse_literal_str()
|
2051
|
+
|
2052
|
+
# Booleans
|
2053
|
+
if char == 't':
|
2054
|
+
if self.src.startswith('true', self.pos):
|
2055
|
+
self.pos += 4
|
2056
|
+
return True
|
2057
|
+
if char == 'f':
|
2058
|
+
if self.src.startswith('false', self.pos):
|
2059
|
+
self.pos += 5
|
2060
|
+
return False
|
2096
2061
|
|
2062
|
+
# Arrays
|
2063
|
+
if char == '[':
|
2064
|
+
return self.parse_array()
|
2097
2065
|
|
2098
|
-
|
2099
|
-
|
2100
|
-
|
2101
|
-
|
2102
|
-
|
2103
|
-
|
2104
|
-
|
2105
|
-
|
2106
|
-
|
2107
|
-
|
2108
|
-
|
2109
|
-
|
2110
|
-
|
2111
|
-
|
2112
|
-
if
|
2113
|
-
|
2114
|
-
|
2115
|
-
|
2116
|
-
|
2117
|
-
|
2118
|
-
|
2119
|
-
|
2120
|
-
|
2121
|
-
|
2122
|
-
|
2123
|
-
|
2124
|
-
|
2125
|
-
|
2126
|
-
|
2127
|
-
|
2128
|
-
|
2129
|
-
|
2130
|
-
|
2131
|
-
|
2132
|
-
|
2133
|
-
|
2134
|
-
|
2135
|
-
|
2136
|
-
|
2137
|
-
|
2138
|
-
# Dates and times
|
2139
|
-
datetime_match = TOML_RE_DATETIME.match(src, pos)
|
2140
|
-
if datetime_match:
|
2141
|
-
try:
|
2142
|
-
datetime_obj = toml_match_to_datetime(datetime_match)
|
2143
|
-
except ValueError as e:
|
2144
|
-
raise toml_suffixed_err(src, pos, 'Invalid date or datetime') from e
|
2145
|
-
return datetime_match.end(), datetime_obj
|
2146
|
-
localtime_match = TOML_RE_LOCALTIME.match(src, pos)
|
2147
|
-
if localtime_match:
|
2148
|
-
return localtime_match.end(), toml_match_to_localtime(localtime_match)
|
2149
|
-
|
2150
|
-
# Integers and "normal" floats. The regex will greedily match any type starting with a decimal char, so needs to be
|
2151
|
-
# located after handling of dates and times.
|
2152
|
-
number_match = TOML_RE_NUMBER.match(src, pos)
|
2153
|
-
if number_match:
|
2154
|
-
return number_match.end(), toml_match_to_number(number_match, parse_float)
|
2155
|
-
|
2156
|
-
# Special floats
|
2157
|
-
first_three = src[pos:pos + 3]
|
2158
|
-
if first_three in {'inf', 'nan'}:
|
2159
|
-
return pos + 3, parse_float(first_three)
|
2160
|
-
first_four = src[pos:pos + 4]
|
2161
|
-
if first_four in {'-inf', '+inf', '-nan', '+nan'}:
|
2162
|
-
return pos + 4, parse_float(first_four)
|
2163
|
-
|
2164
|
-
raise toml_suffixed_err(src, pos, 'Invalid value')
|
2165
|
-
|
2166
|
-
|
2167
|
-
def toml_suffixed_err(src: str, pos: TomlPos, msg: str) -> TomlDecodeError:
|
2168
|
-
"""Return a `TomlDecodeError` where error message is suffixed with coordinates in source."""
|
2169
|
-
|
2170
|
-
def coord_repr(src: str, pos: TomlPos) -> str:
|
2171
|
-
if pos >= len(src):
|
2066
|
+
# Inline tables
|
2067
|
+
if char == '{':
|
2068
|
+
return self.parse_inline_table()
|
2069
|
+
|
2070
|
+
# Dates and times
|
2071
|
+
datetime_match = self.RE_DATETIME.match(self.src, self.pos)
|
2072
|
+
if datetime_match:
|
2073
|
+
try:
|
2074
|
+
datetime_obj = self.match_to_datetime(datetime_match)
|
2075
|
+
except ValueError as e:
|
2076
|
+
raise self.suffixed_err('Invalid date or datetime') from e
|
2077
|
+
self.pos = datetime_match.end()
|
2078
|
+
return datetime_obj
|
2079
|
+
localtime_match = self.RE_LOCALTIME.match(self.src, self.pos)
|
2080
|
+
if localtime_match:
|
2081
|
+
self.pos = localtime_match.end()
|
2082
|
+
return self.match_to_localtime(localtime_match)
|
2083
|
+
|
2084
|
+
# Integers and "normal" floats. The regex will greedily match any type starting with a decimal char, so needs to
|
2085
|
+
# be located after handling of dates and times.
|
2086
|
+
number_match = self.RE_NUMBER.match(self.src, self.pos)
|
2087
|
+
if number_match:
|
2088
|
+
self.pos = number_match.end()
|
2089
|
+
return self.match_to_number(number_match, self.parse_float)
|
2090
|
+
|
2091
|
+
# Special floats
|
2092
|
+
first_three = self.src[self.pos:self.pos + 3]
|
2093
|
+
if first_three in {'inf', 'nan'}:
|
2094
|
+
self.pos += 3
|
2095
|
+
return self.parse_float(first_three)
|
2096
|
+
first_four = self.src[self.pos:self.pos + 4]
|
2097
|
+
if first_four in {'-inf', '+inf', '-nan', '+nan'}:
|
2098
|
+
self.pos += 4
|
2099
|
+
return self.parse_float(first_four)
|
2100
|
+
|
2101
|
+
raise self.suffixed_err('Invalid value')
|
2102
|
+
|
2103
|
+
def coord_repr(self, pos: TomlPos) -> str:
|
2104
|
+
if pos >= len(self.src):
|
2172
2105
|
return 'end of document'
|
2173
|
-
line = src.count('\n', 0, pos) + 1
|
2106
|
+
line = self.src.count('\n', 0, pos) + 1
|
2174
2107
|
if line == 1:
|
2175
2108
|
column = pos + 1
|
2176
2109
|
else:
|
2177
|
-
column = pos - src.rindex('\n', 0, pos)
|
2110
|
+
column = pos - self.src.rindex('\n', 0, pos)
|
2178
2111
|
return f'line {line}, column {column}'
|
2179
2112
|
|
2180
|
-
|
2113
|
+
def suffixed_err(self, msg: str, *, pos: ta.Optional[TomlPos] = None) -> TomlDecodeError:
|
2114
|
+
"""Return a `TomlDecodeError` where error message is suffixed with coordinates in source."""
|
2181
2115
|
|
2116
|
+
if pos is None:
|
2117
|
+
pos = self.pos
|
2118
|
+
return TomlDecodeError(f'{msg} (at {self.coord_repr(pos)})')
|
2182
2119
|
|
2183
|
-
|
2184
|
-
return (0 <= codepoint <= 55295) or (57344 <= codepoint <= 1114111)
|
2120
|
+
_TIME_RE_STR = r'([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9])(?:\.([0-9]{1,6})[0-9]*)?'
|
2185
2121
|
|
2122
|
+
RE_NUMBER = re.compile(
|
2123
|
+
r"""
|
2124
|
+
0
|
2125
|
+
(?:
|
2126
|
+
x[0-9A-Fa-f](?:_?[0-9A-Fa-f])* # hex
|
2127
|
+
|
|
2128
|
+
b[01](?:_?[01])* # bin
|
2129
|
+
|
|
2130
|
+
o[0-7](?:_?[0-7])* # oct
|
2131
|
+
)
|
2132
|
+
|
|
2133
|
+
[+-]?(?:0|[1-9](?:_?[0-9])*) # dec, integer part
|
2134
|
+
(?P<floatpart>
|
2135
|
+
(?:\.[0-9](?:_?[0-9])*)? # optional fractional part
|
2136
|
+
(?:[eE][+-]?[0-9](?:_?[0-9])*)? # optional exponent part
|
2137
|
+
)
|
2138
|
+
""",
|
2139
|
+
flags=re.VERBOSE,
|
2140
|
+
)
|
2186
2141
|
|
2187
|
-
|
2188
|
-
"""A decorator to make `parse_float` safe.
|
2142
|
+
RE_LOCALTIME = re.compile(_TIME_RE_STR)
|
2189
2143
|
|
2190
|
-
|
2191
|
-
|
2192
|
-
|
2193
|
-
|
2194
|
-
|
2195
|
-
|
2144
|
+
RE_DATETIME = re.compile(
|
2145
|
+
rf"""
|
2146
|
+
([0-9]{{4}})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01]) # date, e.g. 1988-10-27
|
2147
|
+
(?:
|
2148
|
+
[Tt ]
|
2149
|
+
{_TIME_RE_STR}
|
2150
|
+
(?:([Zz])|([+-])([01][0-9]|2[0-3]):([0-5][0-9]))? # optional time offset
|
2151
|
+
)?
|
2152
|
+
""",
|
2153
|
+
flags=re.VERBOSE,
|
2154
|
+
)
|
2196
2155
|
|
2197
|
-
|
2198
|
-
|
2199
|
-
|
2200
|
-
|
2201
|
-
return float_value
|
2156
|
+
@classmethod
|
2157
|
+
def match_to_datetime(cls, match: re.Match) -> ta.Union[datetime.datetime, datetime.date]:
|
2158
|
+
"""
|
2159
|
+
Convert a `RE_DATETIME` match to `datetime.datetime` or `datetime.date`.
|
2202
2160
|
|
2203
|
-
|
2161
|
+
Raises ValueError if the match does not correspond to a valid date or datetime.
|
2162
|
+
"""
|
2163
|
+
|
2164
|
+
(
|
2165
|
+
year_str,
|
2166
|
+
month_str,
|
2167
|
+
day_str,
|
2168
|
+
hour_str,
|
2169
|
+
minute_str,
|
2170
|
+
sec_str,
|
2171
|
+
micros_str,
|
2172
|
+
zulu_time,
|
2173
|
+
offset_sign_str,
|
2174
|
+
offset_hour_str,
|
2175
|
+
offset_minute_str,
|
2176
|
+
) = match.groups()
|
2177
|
+
year, month, day = int(year_str), int(month_str), int(day_str)
|
2178
|
+
if hour_str is None:
|
2179
|
+
return datetime.date(year, month, day)
|
2180
|
+
hour, minute, sec = int(hour_str), int(minute_str), int(sec_str)
|
2181
|
+
micros = int(micros_str.ljust(6, '0')) if micros_str else 0
|
2182
|
+
if offset_sign_str:
|
2183
|
+
tz: ta.Optional[datetime.tzinfo] = toml_cached_tz(
|
2184
|
+
offset_hour_str, offset_minute_str, offset_sign_str,
|
2185
|
+
)
|
2186
|
+
elif zulu_time:
|
2187
|
+
tz = datetime.UTC
|
2188
|
+
else: # local date-time
|
2189
|
+
tz = None
|
2190
|
+
return datetime.datetime(year, month, day, hour, minute, sec, micros, tzinfo=tz)
|
2191
|
+
|
2192
|
+
@classmethod
|
2193
|
+
def match_to_localtime(cls, match: re.Match) -> datetime.time:
|
2194
|
+
hour_str, minute_str, sec_str, micros_str = match.groups()
|
2195
|
+
micros = int(micros_str.ljust(6, '0')) if micros_str else 0
|
2196
|
+
return datetime.time(int(hour_str), int(minute_str), int(sec_str), micros)
|
2197
|
+
|
2198
|
+
@classmethod
|
2199
|
+
def match_to_number(cls, match: re.Match, parse_float: TomlParseFloat) -> ta.Any:
|
2200
|
+
if match.group('floatpart'):
|
2201
|
+
return parse_float(match.group())
|
2202
|
+
return int(match.group(), 0)
|
2204
2203
|
|
2205
2204
|
|
2206
2205
|
########################################
|