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 # ``\e`` is TOML 1.1 only; write ESC as ``\u001b`` so TOML 1.0 parsers
119 # (e.g. ``tomllib``) can read the output.
120 **{v: f"\\{k}" for k, v in _escaped.items() if k != "e"},
121 '"""': '""\\"',
122}
123_basic_escapes = CONTROL_CHARS | {'"', "\\"}
126def _unicode_escape(seq: str) -> str:
127 return "".join(f"\\u{ord(c):04x}" for c in seq)
130def escape_string(s: str, escape_sequences: Collection[str] = _basic_escapes) -> str:
131 s = decode(s)
133 res = []
134 start = 0
136 def flush(inc: int = 1) -> int:
137 if start != i:
138 res.append(s[start:i])
140 return i + inc
142 found_sequences = {seq for seq in escape_sequences if seq in s}
144 i = 0
145 while i < len(s):
146 for seq in found_sequences:
147 seq_len = len(seq)
148 if s[i:].startswith(seq):
149 start = flush(seq_len)
150 res.append(_compact_escapes.get(seq) or _unicode_escape(seq))
151 i += seq_len - 1 # fast-forward escape sequence
152 i += 1
154 flush()
156 return "".join(res)
159def merge_dicts(d1: dict[str, Any], d2: dict[str, Any]) -> None:
160 for k, v in d2.items():
161 if k in d1 and isinstance(d1[k], dict) and isinstance(v, Mapping):
162 merge_dicts(d1[k], dict(v))
163 else:
164 d1[k] = d2[k]