Selfie
Loading...
Searching...
No Matches
SnapshotValueReader.py
Go to the documentation of this file.
1import base64
2from typing import Callable, Optional
3
4from .LineReader import LineReader
5from .ParseException import ParseException
6from .PerCharacterEscaper import PerCharacterEscaper
7from .SnapshotValue import SnapshotValue
8
9
11 KEY_FIRST_CHAR = "╔"
12 KEY_START = "╔═ "
13 KEY_END = " ═╗"
14 FLAG_BASE64 = " ═╗ base64"
15 name_esc = PerCharacterEscaper.specified_escape("\\\\[(])\nn\tt╔┌╗┐═─")
16 body_esc = PerCharacterEscaper.self_escape("\ud801\udf43\ud801\udf41")
17
18 def __init__(self, line_reader: LineReader):
19 self.line_reader = line_reader
20 self.line: Optional[str] = None
22
23 def peek_key(self) -> Optional[str]:
24 return self.__next_key()
25
26 def next_value(self) -> SnapshotValue:
27 # Validate key
28 self.__next_key()
29 nextLineCheckForBase64: Optional[str] = self.__next_line()
30 if nextLineCheckForBase64 is None:
31 raise ParseException(self.line_reader, "Expected to validate key")
32 is_base64: bool = self.FLAG_BASE64 in nextLineCheckForBase64
33 self.__reset_line()
34
35 # Read value
36 buffer: list[str] = []
37
38 def consumer(line: str) -> None:
39 # Check for special condition and append to buffer accordingly
40 if len(line) >= 2 and ord(line[0]) == 0xD801 and ord(line[1]) == 0xDF41:
41 buffer.append(self.KEY_FIRST_CHARKEY_FIRST_CHAR)
42 buffer.append(line[2:])
43 else:
44 buffer.append(line)
45 buffer.append("\n")
46
47 self.__scan_value(consumer)
48
49 raw_string: str = "" if buffer.__len__() == 0 else ("".join(buffer))[:-1]
50
51 # Decode or unescape value
52 if is_base64:
53 decoded_bytes: bytes = base64.b64decode(raw_string)
54 return SnapshotValue.of(decoded_bytes)
55 else:
56 return SnapshotValue.of(self.body_esc.unescape(raw_string))
57
58 def skip_value(self) -> None:
59 self.__next_key()
60 self.__reset_line()
61 self.__scan_value(lambda _: None)
62
63 def __scan_value(self, consumer: Callable[[str], None]) -> None:
64 nextLine: Optional[str] = self.__next_line()
65 while (
66 nextLine is not None
67 and nextLine.find(SnapshotValueReader.KEY_FIRST_CHAR) != 0
68 ):
69 self.__reset_line()
70 consumer(nextLine)
71 nextLine = self.__next_line()
72
73 def __next_key(self) -> Optional[str]:
74 line: Optional[str] = self.__next_line()
75 if line is None:
76 return None
77 start_index: int = line.find(self.KEY_START)
78 end_index: int = line.find(self.KEY_END)
79 if start_index == -1:
80 raise ParseException(
81 self.line_reader, f"Expected to start with '{self.KEY_START}'"
82 )
83 if end_index == -1:
84 raise ParseException(
85 self.line_reader, f"Expected to contain '{self.KEY_END}'"
86 )
87 key: str = line[start_index + len(self.KEY_START) : end_index]
88 if key.startswith(" ") or key.endswith(" "):
89 space_type = "Leading" if key.startswith(" ") else "Trailing"
90 raise ParseException(
91 self.line_reader, f"{space_type} spaces are disallowed: '{key}'"
92 )
93 return self.name_esc.unescape(key)
94
95 def __next_line(self) -> Optional[str]:
96 if self.line is None:
97 self.line = self.line_reader.read_line()
98 return self.line
99
100 def __reset_line(self) -> None:
101 self.line = None
102
103 @classmethod
104 def of(cls, content: str) -> "SnapshotValueReader":
105 return cls(LineReader.for_string(content))
106
107 @classmethod
108 def of_binary(cls, content: bytes) -> "SnapshotValueReader":
109 return cls(LineReader.for_binary(content))
"SnapshotValueReader" of(cls, str content)
"SnapshotValueReader" of_binary(cls, bytes content)
None __scan_value(self, Callable[[str], None] consumer)