Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/tomlkit/_utils.py: 95%
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
12from typing import Any
14from tomlkit._compat import decode
17RFC_3339_LOOSE = re.compile(
18 "^"
19 r"(?P<date>(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2}))?" # Date
20 "("
21 "(?P<sep>[Tt ])?" # Separator
22 r"(?P<time>(?P<hour>\d{2}):(?P<minute>\d{2})(:(?P<second>\d{2})(\.(?P<fraction>[0-9]+))?)?)" # Time
23 r"(?P<tz>([Zz])|([\+\-]([01][0-9]|2[0-3]):([0-5][0-9])))?" # Timezone
24 ")?"
25 "$"
26)
28RFC_3339_DATETIME = re.compile(
29 "^"
30 r"(?P<year>\d{4})-(?P<month>0[1-9]|1[012])-(?P<day>0[1-9]|[12][0-9]|3[01])" # Date
31 "[Tt ]" # Separator
32 r"(?P<hour>[01][0-9]|2[0-3]):(?P<minute>[0-5][0-9])" # Time
33 r"(:(?P<second>[0-5][0-9]|60)(\.(?P<fraction>[0-9]+))?)?"
34 r"(?P<tz>([Zz])|([\+\-]([01][0-9]|2[0-3]):([0-5][0-9])))?" # Timezone
35 "$"
36)
38RFC_3339_DATE = re.compile("^([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])$")
40RFC_3339_TIME = re.compile(
41 r"^(?P<hour>[01][0-9]|2[0-3]):(?P<minute>[0-5][0-9])"
42 r"(:(?P<second>[0-5][0-9]|60)(\.(?P<fraction>[0-9]+))?)?$"
43)
45_utc = timezone(timedelta(), "UTC")
48def parse_rfc3339(string: str) -> datetime | date | time:
49 m = RFC_3339_DATETIME.match(string)
50 if m:
51 year = int(m.group("year"))
52 month = int(m.group("month"))
53 day = int(m.group("day"))
54 hour = int(m.group("hour"))
55 minute = int(m.group("minute"))
56 second = int(m.group("second") or 0)
57 microsecond = 0
59 if m.group("fraction"):
60 microsecond = int((f"{m.group('fraction'):<06s}")[:6])
62 if m.group("tz"):
63 # Timezone
64 tz = m.group("tz")
65 if tz.upper() == "Z":
66 tzinfo = _utc
67 else:
68 sign = tz[0]
69 hour_offset, minute_offset = map(int, tz[1:].split(":"))
70 offset = timedelta(seconds=hour_offset * 3600 + minute_offset * 60)
71 if sign == "-":
72 offset = -offset
74 tzinfo = timezone(offset, tz)
76 return datetime(
77 year, month, day, hour, minute, second, microsecond, tzinfo=tzinfo
78 )
79 else:
80 return datetime(year, month, day, hour, minute, second, microsecond)
82 m = RFC_3339_DATE.match(string)
83 if m:
84 year = int(m.group(1))
85 month = int(m.group(2))
86 day = int(m.group(3))
88 return date(year, month, day)
90 m = RFC_3339_TIME.match(string)
91 if m:
92 hour = int(m.group("hour"))
93 minute = int(m.group("minute"))
94 second = int(m.group("second") or 0)
95 microsecond = 0
97 if m.group("fraction"):
98 microsecond = int((f"{m.group('fraction'):<06s}")[:6])
100 return time(hour, minute, second, microsecond)
102 raise ValueError("Invalid RFC 3339 string")
105# https://toml.io/en/v1.0.0#string
106CONTROL_CHARS = frozenset(chr(c) for c in range(0x20)) | {chr(0x7F)}
107_escaped = {
108 "b": "\b",
109 "t": "\t",
110 "n": "\n",
111 "f": "\f",
112 "r": "\r",
113 "e": "\x1b",
114 '"': '"',
115 "\\": "\\",
116}
117_compact_escapes = {
118 **{v: f"\\{k}" for k, v in _escaped.items()},
119 '"""': '""\\"',
120}
121_basic_escapes = CONTROL_CHARS | {'"', "\\"}
124def _unicode_escape(seq: str) -> str:
125 return "".join(f"\\u{ord(c):04x}" for c in seq)
128def escape_string(s: str, escape_sequences: Collection[str] = _basic_escapes) -> str:
129 s = decode(s)
131 res = []
132 start = 0
134 def flush(inc: int = 1) -> int:
135 if start != i:
136 res.append(s[start:i])
138 return i + inc
140 found_sequences = {seq for seq in escape_sequences if seq in s}
142 i = 0
143 while i < len(s):
144 for seq in found_sequences:
145 seq_len = len(seq)
146 if s[i:].startswith(seq):
147 start = flush(seq_len)
148 res.append(_compact_escapes.get(seq) or _unicode_escape(seq))
149 i += seq_len - 1 # fast-forward escape sequence
150 i += 1
152 flush()
154 return "".join(res)
157def merge_dicts(d1: dict[str, Any], d2: dict[str, Any]) -> None:
158 for k, v in d2.items():
159 if k in d1 and isinstance(d1[k], dict) and isinstance(v, Mapping):
160 merge_dicts(d1[k], dict(v))
161 else:
162 d1[k] = d2[k]