Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/tomlkit/_utils.py: 92%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1from __future__ import annotations
3import re
5from collections.abc import Collection
6from collections.abc import Mapping
7from datetime import date
8from datetime import datetime
9from datetime import time
10from datetime import timedelta
11from datetime import timezone
13from tomlkit._compat import decode
16RFC_3339_LOOSE = re.compile(
17 "^"
18 r"(?P<date>(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2}))?" # Date
19 "("
20 "(?P<sep>[Tt ])?" # Separator
21 r"(?P<time>(?P<hour>\d{2}):(?P<minute>\d{2})(:(?P<second>\d{2})(\.(?P<fraction>[0-9]+))?)?)" # Time
22 r"(?P<tz>([Zz])|([\+\-]([01][0-9]|2[0-3]):([0-5][0-9])))?" # Timezone
23 ")?"
24 "$"
25)
27RFC_3339_DATETIME = re.compile(
28 "^"
29 r"(?P<year>\d{4})-(?P<month>0[1-9]|1[012])-(?P<day>0[1-9]|[12][0-9]|3[01])" # Date
30 "[Tt ]" # Separator
31 r"(?P<hour>[01][0-9]|2[0-3]):(?P<minute>[0-5][0-9])" # Time
32 r"(:(?P<second>[0-5][0-9]|60)(\.(?P<fraction>[0-9]+))?)?"
33 r"(?P<tz>([Zz])|([\+\-]([01][0-9]|2[0-3]):([0-5][0-9])))?" # Timezone
34 "$"
35)
37RFC_3339_DATE = re.compile("^([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])$")
39RFC_3339_TIME = re.compile(
40 r"^(?P<hour>[01][0-9]|2[0-3]):(?P<minute>[0-5][0-9])"
41 r"(:(?P<second>[0-5][0-9]|60)(\.(?P<fraction>[0-9]+))?)?$"
42)
44_utc = timezone(timedelta(), "UTC")
47def parse_rfc3339(string: str) -> datetime | date | time:
48 m = RFC_3339_DATETIME.match(string)
49 if m:
50 year = int(m.group("year"))
51 month = int(m.group("month"))
52 day = int(m.group("day"))
53 hour = int(m.group("hour"))
54 minute = int(m.group("minute"))
55 second = int(m.group("second") or 0)
56 microsecond = 0
58 if m.group("fraction"):
59 microsecond = int((f"{m.group('fraction'):<06s}")[:6])
61 if m.group("tz"):
62 # Timezone
63 tz = m.group("tz")
64 if tz.upper() == "Z":
65 tzinfo = _utc
66 else:
67 sign = tz[0]
68 hour_offset, minute_offset = map(int, tz[1:].split(":"))
69 offset = timedelta(seconds=hour_offset * 3600 + minute_offset * 60)
70 if sign == "-":
71 offset = -offset
73 tzinfo = timezone(offset, tz)
75 return datetime(
76 year, month, day, hour, minute, second, microsecond, tzinfo=tzinfo
77 )
78 else:
79 return datetime(year, month, day, hour, minute, second, microsecond)
81 m = RFC_3339_DATE.match(string)
82 if m:
83 year = int(m.group(1))
84 month = int(m.group(2))
85 day = int(m.group(3))
87 return date(year, month, day)
89 m = RFC_3339_TIME.match(string)
90 if m:
91 hour = int(m.group("hour"))
92 minute = int(m.group("minute"))
93 second = int(m.group("second") or 0)
94 microsecond = 0
96 if m.group("fraction"):
97 microsecond = int((f"{m.group('fraction'):<06s}")[:6])
99 return time(hour, minute, second, microsecond)
101 raise ValueError("Invalid RFC 3339 string")
104# https://toml.io/en/v1.0.0#string
105CONTROL_CHARS = frozenset(chr(c) for c in range(0x20)) | {chr(0x7F)}
106_escaped = {
107 "b": "\b",
108 "t": "\t",
109 "n": "\n",
110 "f": "\f",
111 "r": "\r",
112 "e": "\x1b",
113 '"': '"',
114 "\\": "\\",
115}
116_compact_escapes = {
117 **{v: f"\\{k}" for k, v in _escaped.items()},
118 '"""': '""\\"',
119}
120_basic_escapes = CONTROL_CHARS | {'"', "\\"}
123def _unicode_escape(seq: str) -> str:
124 return "".join(f"\\u{ord(c):04x}" for c in seq)
127def escape_string(s: str, escape_sequences: Collection[str] = _basic_escapes) -> str:
128 s = decode(s)
130 res = []
131 start = 0
133 def flush(inc=1):
134 if start != i:
135 res.append(s[start:i])
137 return i + inc
139 found_sequences = {seq for seq in escape_sequences if seq in s}
141 i = 0
142 while i < len(s):
143 for seq in found_sequences:
144 seq_len = len(seq)
145 if s[i:].startswith(seq):
146 start = flush(seq_len)
147 res.append(_compact_escapes.get(seq) or _unicode_escape(seq))
148 i += seq_len - 1 # fast-forward escape sequence
149 i += 1
151 flush()
153 return "".join(res)
156def merge_dicts(d1: dict, d2: dict) -> dict:
157 for k, v in d2.items():
158 if k in d1 and isinstance(d1[k], dict) and isinstance(v, Mapping):
159 merge_dicts(d1[k], v)
160 else:
161 d1[k] = d2[k]