Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/tomlkit/_utils.py: 87%

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 Mapping 

6from datetime import date 

7from datetime import datetime 

8from datetime import time 

9from datetime import timedelta 

10from datetime import timezone 

11from typing import Collection 

12 

13from tomlkit._compat import decode 

14 

15 

16RFC_3339_LOOSE = re.compile( 

17 "^" 

18 r"(([0-9]+)-(\d{2})-(\d{2}))?" # Date 

19 "(" 

20 "([Tt ])?" # Separator 

21 r"(\d{2}):(\d{2}):(\d{2})(\.([0-9]+))?" # Time 

22 r"(([Zz])|([\+|\-]([01][0-9]|2[0-3]):([0-5][0-9])))?" # Timezone 

23 ")?" 

24 "$" 

25) 

26 

27RFC_3339_DATETIME = re.compile( 

28 "^" 

29 "([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])" # Date 

30 "[Tt ]" # Separator 

31 r"([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.([0-9]+))?" # Time 

32 r"(([Zz])|([\+|\-]([01][0-9]|2[0-3]):([0-5][0-9])))?" # Timezone 

33 "$" 

34) 

35 

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

37 

38RFC_3339_TIME = re.compile( 

39 r"^([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.([0-9]+))?$" 

40) 

41 

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

43 

44 

45def parse_rfc3339(string: str) -> datetime | date | time: 

46 m = RFC_3339_DATETIME.match(string) 

47 if m: 

48 year = int(m.group(1)) 

49 month = int(m.group(2)) 

50 day = int(m.group(3)) 

51 hour = int(m.group(4)) 

52 minute = int(m.group(5)) 

53 second = int(m.group(6)) 

54 microsecond = 0 

55 

56 if m.group(7): 

57 microsecond = int((f"{m.group(8):<06s}")[:6]) 

58 

59 if m.group(9): 

60 # Timezone 

61 tz = m.group(9) 

62 if tz.upper() == "Z": 

63 tzinfo = _utc 

64 else: 

65 sign = m.group(11)[0] 

66 hour_offset, minute_offset = int(m.group(12)), int(m.group(13)) 

67 offset = timedelta(seconds=hour_offset * 3600 + minute_offset * 60) 

68 if sign == "-": 

69 offset = -offset 

70 

71 tzinfo = timezone(offset, f"{sign}{m.group(12)}:{m.group(13)}") 

72 

73 return datetime( 

74 year, month, day, hour, minute, second, microsecond, tzinfo=tzinfo 

75 ) 

76 else: 

77 return datetime(year, month, day, hour, minute, second, microsecond) 

78 

79 m = RFC_3339_DATE.match(string) 

80 if m: 

81 year = int(m.group(1)) 

82 month = int(m.group(2)) 

83 day = int(m.group(3)) 

84 

85 return date(year, month, day) 

86 

87 m = RFC_3339_TIME.match(string) 

88 if m: 

89 hour = int(m.group(1)) 

90 minute = int(m.group(2)) 

91 second = int(m.group(3)) 

92 microsecond = 0 

93 

94 if m.group(4): 

95 microsecond = int((f"{m.group(5):<06s}")[:6]) 

96 

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

98 

99 raise ValueError("Invalid RFC 339 string") 

100 

101 

102# https://toml.io/en/v1.0.0#string 

103CONTROL_CHARS = frozenset(chr(c) for c in range(0x20)) | {chr(0x7F)} 

104_escaped = { 

105 "b": "\b", 

106 "t": "\t", 

107 "n": "\n", 

108 "f": "\f", 

109 "r": "\r", 

110 '"': '"', 

111 "\\": "\\", 

112} 

113_compact_escapes = { 

114 **{v: f"\\{k}" for k, v in _escaped.items()}, 

115 '"""': '""\\"', 

116} 

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

118 

119 

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

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

122 

123 

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

125 s = decode(s) 

126 

127 res = [] 

128 start = 0 

129 

130 def flush(inc=1): 

131 if start != i: 

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

133 

134 return i + inc 

135 

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

137 

138 i = 0 

139 while i < len(s): 

140 for seq in found_sequences: 

141 seq_len = len(seq) 

142 if s[i:].startswith(seq): 

143 start = flush(seq_len) 

144 res.append(_compact_escapes.get(seq) or _unicode_escape(seq)) 

145 i += seq_len - 1 # fast-forward escape sequence 

146 i += 1 

147 

148 flush() 

149 

150 return "".join(res) 

151 

152 

153def merge_dicts(d1: dict, d2: dict) -> dict: 

154 for k, v in d2.items(): 

155 if k in d1 and isinstance(d1[k], dict) and isinstance(v, Mapping): 

156 merge_dicts(d1[k], v) 

157 else: 

158 d1[k] = d2[k]