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

86 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 

12 

13from tomlkit._compat import decode 

14 

15 

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) 

26 

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) 

36 

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

38 

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) 

43 

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

45 

46 

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 

57 

58 if m.group("fraction"): 

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

60 

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 

72 

73 tzinfo = timezone(offset, tz) 

74 

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) 

80 

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

86 

87 return date(year, month, day) 

88 

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 

95 

96 if m.group("fraction"): 

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

98 

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

100 

101 raise ValueError("Invalid RFC 3339 string") 

102 

103 

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

121 

122 

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

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

125 

126 

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

128 s = decode(s) 

129 

130 res = [] 

131 start = 0 

132 

133 def flush(inc=1): 

134 if start != i: 

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

136 

137 return i + inc 

138 

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

140 

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 

150 

151 flush() 

152 

153 return "".join(res) 

154 

155 

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]