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

87 statements  

1from __future__ import annotations 

2 

3import re 

4 

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 

13 

14from tomlkit._compat import decode 

15 

16 

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) 

27 

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) 

37 

38RFC_3339_DATE = re.compile("^([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])$") 

39 

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) 

44 

45_utc = timezone(timedelta(), "UTC") 

46 

47 

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 

58 

59 if m.group("fraction"): 

60 microsecond = int((f"{m.group('fraction'):<06s}")[:6]) 

61 

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 

73 

74 tzinfo = timezone(offset, tz) 

75 

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) 

81 

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)) 

87 

88 return date(year, month, day) 

89 

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 

96 

97 if m.group("fraction"): 

98 microsecond = int((f"{m.group('fraction'):<06s}")[:6]) 

99 

100 return time(hour, minute, second, microsecond) 

101 

102 raise ValueError("Invalid RFC 3339 string") 

103 

104 

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 | {'"', "\\"} 

124 

125 

126def _unicode_escape(seq: str) -> str: 

127 return "".join(f"\\u{ord(c):04x}" for c in seq) 

128 

129 

130def escape_string(s: str, escape_sequences: Collection[str] = _basic_escapes) -> str: 

131 s = decode(s) 

132 

133 res = [] 

134 start = 0 

135 

136 def flush(inc: int = 1) -> int: 

137 if start != i: 

138 res.append(s[start:i]) 

139 

140 return i + inc 

141 

142 found_sequences = {seq for seq in escape_sequences if seq in s} 

143 

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 

153 

154 flush() 

155 

156 return "".join(res) 

157 

158 

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]