| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158 |
- from __future__ import annotations
- import re
- from collections.abc import Mapping
- from datetime import date
- from datetime import datetime
- from datetime import time
- from datetime import timedelta
- from datetime import timezone
- from typing import Collection
- from tomlkit._compat import decode
- RFC_3339_LOOSE = re.compile(
- "^"
- r"(([0-9]+)-(\d{2})-(\d{2}))?" # Date
- "("
- "([Tt ])?" # Separator
- r"(\d{2}):(\d{2}):(\d{2})(\.([0-9]+))?" # Time
- r"(([Zz])|([\+|\-]([01][0-9]|2[0-3]):([0-5][0-9])))?" # Timezone
- ")?"
- "$"
- )
- RFC_3339_DATETIME = re.compile(
- "^"
- "([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])" # Date
- "[Tt ]" # Separator
- r"([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.([0-9]+))?" # Time
- r"(([Zz])|([\+|\-]([01][0-9]|2[0-3]):([0-5][0-9])))?" # Timezone
- "$"
- )
- RFC_3339_DATE = re.compile("^([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])$")
- RFC_3339_TIME = re.compile(
- r"^([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.([0-9]+))?$"
- )
- _utc = timezone(timedelta(), "UTC")
- def parse_rfc3339(string: str) -> datetime | date | time:
- m = RFC_3339_DATETIME.match(string)
- if m:
- year = int(m.group(1))
- month = int(m.group(2))
- day = int(m.group(3))
- hour = int(m.group(4))
- minute = int(m.group(5))
- second = int(m.group(6))
- microsecond = 0
- if m.group(7):
- microsecond = int((f"{m.group(8):<06s}")[:6])
- if m.group(9):
- # Timezone
- tz = m.group(9)
- if tz.upper() == "Z":
- tzinfo = _utc
- else:
- sign = m.group(11)[0]
- hour_offset, minute_offset = int(m.group(12)), int(m.group(13))
- offset = timedelta(seconds=hour_offset * 3600 + minute_offset * 60)
- if sign == "-":
- offset = -offset
- tzinfo = timezone(offset, f"{sign}{m.group(12)}:{m.group(13)}")
- return datetime(
- year, month, day, hour, minute, second, microsecond, tzinfo=tzinfo
- )
- else:
- return datetime(year, month, day, hour, minute, second, microsecond)
- m = RFC_3339_DATE.match(string)
- if m:
- year = int(m.group(1))
- month = int(m.group(2))
- day = int(m.group(3))
- return date(year, month, day)
- m = RFC_3339_TIME.match(string)
- if m:
- hour = int(m.group(1))
- minute = int(m.group(2))
- second = int(m.group(3))
- microsecond = 0
- if m.group(4):
- microsecond = int((f"{m.group(5):<06s}")[:6])
- return time(hour, minute, second, microsecond)
- raise ValueError("Invalid RFC 339 string")
- # https://toml.io/en/v1.0.0#string
- CONTROL_CHARS = frozenset(chr(c) for c in range(0x20)) | {chr(0x7F)}
- _escaped = {
- "b": "\b",
- "t": "\t",
- "n": "\n",
- "f": "\f",
- "r": "\r",
- '"': '"',
- "\\": "\\",
- }
- _compact_escapes = {
- **{v: f"\\{k}" for k, v in _escaped.items()},
- '"""': '""\\"',
- }
- _basic_escapes = CONTROL_CHARS | {'"', "\\"}
- def _unicode_escape(seq: str) -> str:
- return "".join(f"\\u{ord(c):04x}" for c in seq)
- def escape_string(s: str, escape_sequences: Collection[str] = _basic_escapes) -> str:
- s = decode(s)
- res = []
- start = 0
- def flush(inc=1):
- if start != i:
- res.append(s[start:i])
- return i + inc
- found_sequences = {seq for seq in escape_sequences if seq in s}
- i = 0
- while i < len(s):
- for seq in found_sequences:
- seq_len = len(seq)
- if s[i:].startswith(seq):
- start = flush(seq_len)
- res.append(_compact_escapes.get(seq) or _unicode_escape(seq))
- i += seq_len - 1 # fast-forward escape sequence
- i += 1
- flush()
- return "".join(res)
- def merge_dicts(d1: dict, d2: dict) -> dict:
- for k, v in d2.items():
- if k in d1 and isinstance(d1[k], dict) and isinstance(v, Mapping):
- merge_dicts(d1[k], v)
- else:
- d1[k] = d2[k]
|