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 **{v: f"\\{k}" for k, v in _escaped.items()}, 

119 '"""': '""\\"', 

120} 

121_basic_escapes = CONTROL_CHARS | {'"', "\\"} 

122 

123 

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

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

126 

127 

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

129 s = decode(s) 

130 

131 res = [] 

132 start = 0 

133 

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

135 if start != i: 

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

137 

138 return i + inc 

139 

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

141 

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 

151 

152 flush() 

153 

154 return "".join(res) 

155 

156 

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]