Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/tomlkit/source.py: 98%
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
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
1from __future__ import annotations
3from copy import copy
4from typing import Any
6from tomlkit.exceptions import ParseError
7from tomlkit.exceptions import UnexpectedCharError
8from tomlkit.toml_char import TOMLChar
11class _State:
12 def __init__(
13 self,
14 source: Source,
15 save_marker: bool | None = False,
16 restore: bool | None = False,
17 ) -> None:
18 self._source = source
19 self._save_marker = save_marker
20 self.restore = restore
22 def __enter__(self) -> _State:
23 # Entering this context manager - save the state
24 self._chars = copy(self._source._chars)
25 self._idx = self._source._idx
26 self._current = self._source._current
27 self._marker = self._source._marker
29 return self
31 def __exit__(self, exception_type, exception_val, trace):
32 # Exiting this context manager - restore the prior state
33 if self.restore or exception_type:
34 self._source._chars = self._chars
35 self._source._idx = self._idx
36 self._source._current = self._current
37 if self._save_marker:
38 self._source._marker = self._marker
41class _StateHandler:
42 """
43 State preserver for the Parser.
44 """
46 def __init__(self, source: Source) -> None:
47 self._source = source
48 self._states = []
50 def __call__(self, *args, **kwargs):
51 return _State(self._source, *args, **kwargs)
53 def __enter__(self) -> _State:
54 state = self()
55 self._states.append(state)
56 return state.__enter__()
58 def __exit__(self, exception_type, exception_val, trace):
59 state = self._states.pop()
60 return state.__exit__(exception_type, exception_val, trace)
63class Source(str):
64 EOF = TOMLChar("\0")
66 def __init__(self, _: str) -> None:
67 super().__init__()
69 # Collection of TOMLChars
70 self._chars = iter([(i, TOMLChar(c)) for i, c in enumerate(self)])
72 self._idx = 0
73 self._marker = 0
74 self._current = TOMLChar("")
76 self._state = _StateHandler(self)
78 self.inc()
80 def reset(self):
81 # initialize both idx and current
82 self.inc()
84 # reset marker
85 self.mark()
87 @property
88 def state(self) -> _StateHandler:
89 return self._state
91 @property
92 def idx(self) -> int:
93 return self._idx
95 @property
96 def current(self) -> TOMLChar:
97 return self._current
99 @property
100 def marker(self) -> int:
101 return self._marker
103 def extract(self) -> str:
104 """
105 Extracts the value between marker and index
106 """
107 return self[self._marker : self._idx]
109 def inc(self, exception: type[ParseError] | None = None) -> bool:
110 """
111 Increments the parser if the end of the input has not been reached.
112 Returns whether or not it was able to advance.
113 """
114 try:
115 self._idx, self._current = next(self._chars)
117 return True
118 except StopIteration:
119 self._idx = len(self)
120 self._current = self.EOF
121 if exception:
122 raise self.parse_error(exception) from None
124 return False
126 def inc_n(self, n: int, exception: type[ParseError] | None = None) -> bool:
127 """
128 Increments the parser by n characters
129 if the end of the input has not been reached.
130 """
131 return all(self.inc(exception=exception) for _ in range(n))
133 def consume(self, chars, min=0, max=-1):
134 """
135 Consume chars until min/max is satisfied is valid.
136 """
137 while self.current in chars and max != 0:
138 min -= 1
139 max -= 1
140 if not self.inc():
141 break
143 # failed to consume minimum number of characters
144 if min > 0:
145 raise self.parse_error(UnexpectedCharError, self.current)
147 def end(self) -> bool:
148 """
149 Returns True if the parser has reached the end of the input.
150 """
151 return self._current is self.EOF
153 def mark(self) -> None:
154 """
155 Sets the marker to the index's current position
156 """
157 self._marker = self._idx
159 def parse_error(
160 self,
161 exception: type[ParseError] = ParseError,
162 *args: Any,
163 **kwargs: Any,
164 ) -> ParseError:
165 """
166 Creates a generic "parse error" at the current position.
167 """
168 line, col = self._to_linecol()
170 return exception(line, col, *args, **kwargs)
172 def _to_linecol(self) -> tuple[int, int]:
173 cur = 0
174 for i, line in enumerate(self.splitlines()):
175 if cur + len(line) + 1 > self.idx:
176 return (i + 1, self.idx - cur)
178 cur += len(line) + 1
180 return len(self.splitlines()), 0