Coverage for pyEDAA/OutputFilter/__init__.py: 34%
155 statements
« prev ^ index » next coverage.py v7.14.3, created at 2026-06-26 23:00 +0000
« prev ^ index » next coverage.py v7.14.3, created at 2026-06-26 23:00 +0000
1# ==================================================================================================================== #
2# _____ ____ _ _ ___ _ _ _____ _ _ _ #
3# _ __ _ _| ____| _ \ / \ / \ / _ \ _ _| |_ _ __ _ _| |_| ___(_) | |_ ___ _ __ #
4# | '_ \| | | | _| | | | |/ _ \ / _ \ | | | | | | | __| '_ \| | | | __| |_ | | | __/ _ \ '__| #
5# | |_) | |_| | |___| |_| / ___ \ / ___ \ | |_| | |_| | |_| |_) | |_| | |_| _| | | | || __/ | #
6# | .__/ \__, |_____|____/_/ \_\/_/ \_(_)___/ \__,_|\__| .__/ \__,_|\__|_| |_|_|\__\___|_| #
7# |_| |___/ |_| #
8# ==================================================================================================================== #
9# Authors: #
10# Patrick Lehmann #
11# #
12# License: #
13# ==================================================================================================================== #
14# Copyright 2017-2026 Patrick Lehmann - Boetzingen, Germany #
15# Copyright 2014-2016 Technische Universitaet Dresden - Germany, Chair of VLSI-Design, Diagnostics and Architecture #
16# #
17# Licensed under the Apache License, Version 2.0 (the "License"); #
18# you may not use this file except in compliance with the License. #
19# You may obtain a copy of the License at #
20# #
21# http://www.apache.org/licenses/LICENSE-2.0 #
22# #
23# Unless required by applicable law or agreed to in writing, software #
24# distributed under the License is distributed on an "AS IS" BASIS, #
25# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
26# See the License for the specific language governing permissions and #
27# limitations under the License. #
28# #
29# SPDX-License-Identifier: Apache-2.0 #
30# ==================================================================================================================== #
31#
32"""An abstraction layer of EDA tool output filters."""
33__author__ = "Patrick Lehmann"
34__email__ = "Paebbels@gmail.com"
35__copyright__ = "2014-2026, Patrick Lehmann"
36__license__ = "Apache License, Version 2.0"
37__version__ = "0.11.0"
38__keywords__ = ["cli", "abstraction layer", "eda", "filter", "classification"]
40from datetime import datetime
41from enum import Flag
42from typing import Any, Generator, Callable, Tuple, Union, Optional as Nullable, Generic, TypeVar
44from pyTooling.Common import getFullyQualifiedName
45from pyTooling.Decorators import export, readonly
46from pyTooling.Exceptions import ExceptionBase
47from pyTooling.MetaClasses import ExtendedType
50@export
51class OutputFilterException(ExceptionBase):
52 """Base-class for all pyEDAA.OutputFilter specific exceptions."""
55LineClassification = TypeVar("LineClassification", bound=Flag)
56LineProcessingAction = TypeVar("LineProcessingAction", bound=Flag)
58@export
59class Line(Generic[LineClassification, LineProcessingAction], metaclass=ExtendedType, slots=True):
60 """
61 This class represents any line in a log file.
63 A line has a line number (:attr:`_lineNumber`), a message (:attr:`__message`) and a message kind (:attr:`__kind`). In
64 addition, all line objects in a log file form a doubly
65 linked list.
66 """
67 _lineNumber: int
68 _timestamp: Nullable[datetime]
69 _document: "Document"
70 _kind: LineClassification
71 _action: LineProcessingAction
72 _message: str
73 _previousLine: Nullable["Line"]
74 _nextLine: Nullable["Line"]
76 def __init__(
77 self,
78 lineNumber: int,
79 kind: LineClassification,
80 action: LineProcessingAction,
81 message: str,
82 previousLine: Nullable["Line"] = None
83 ) -> None:
84 self._lineNumber = lineNumber
85 self._kind = kind
86 self._action = action
87 self._message = message
88 self._previousLine = previousLine
89 self._nextLine = None
91 if previousLine is not None:
92 previousLine._nextLine = self
94 if not isinstance(message, str): 94 ↛ 95line 94 didn't jump to line 95 because the condition on line 94 was never true
95 pass
97 @readonly
98 def LineNumber(self) -> int:
99 return self._lineNumber
101 @readonly
102 def Kind(self) -> LineClassification:
103 return self._kind
105 @readonly
106 def Action(self) -> LineProcessingAction:
107 return self._action
109 @readonly
110 def Message(self) -> str:
111 return self._message
113 @property
114 def PreviousLine(self) -> Nullable["Line"]:
115 return self._previousLine
117 @PreviousLine.setter
118 def PreviousLine(self, line: "Line") -> None:
119 self._previousLine = line
120 if line is not None:
121 line._nextLine = self
123 @readonly
124 def NextLine(self) -> Nullable["Line"]:
125 return self._nextLine
127 def StartsWith(self, prefix: Union[str, Tuple[str, ...]]):
128 return self._message.startswith(prefix)
130 def Partition(self, separator: str) -> Tuple[str, str, str]:
131 return self._message.partition(separator)
133 def GetIterator(
134 self,
135 stopPredicate: Nullable[Callable[["Line"], bool]] = None,
136 *,
137 reverse: bool = False,
138 inclusive: bool = True,
139 maxLines: Nullable[int] = None,
140 ) -> Generator["Line", None, None]:
141 """
142 Iterate consecutive lines starting from next line towards the end of the log.
144 If the order is reversed, iterate starting at the previous line towards the beginning of the log. The iteration ends
145 either at the bounds of the log, by specifying a stop predicate or a maximum number of lines to return. When stopped
146 this line is usually included in the iteration, but can be excluded.
148 :param stopPredicate: Optional, a callable receiving a :class:`Line` and returning ``True`` when iteration should
149 stop at that line.
150 :param reverse: Optional, reverse the iteration from previous line to the beginning of the log.
151 :param inclusive: Optional, when ``True`` the line where ``stopPredicate`` or ``maxLines`` triggers, is
152 included in the iteration, otherwise it's excluded.
153 :param maxLines: Optional, maximum number of lines to yield.
154 :returns: A generator yielding :class:`Line` in the requested direction, stopping at the log boundary,
155 the predicate match, or the line limit — whichever comes first.
156 :raises TypeError: When ``stopPredicate`` is not callable.
157 :raises ValueError: When ``maxLines`` is not a positive integer.
158 """
159 if stopPredicate is not None and not callable(stopPredicate):
160 ex = TypeError("Parameter 'stopPredicate' is not a callable.")
161 ex.add_note(f"Got type '{getFullyQualifiedName(stopPredicate)}'.")
162 raise ex
163 if not isinstance(reverse, bool):
164 ex = TypeError("Parameter 'reverse' is not a boolean.")
165 ex.add_note(f"Got type '{getFullyQualifiedName(reverse)}'.")
166 raise ex
167 if not isinstance(inclusive, bool):
168 ex = TypeError("Parameter 'inclusive' is not a boolean.")
169 ex.add_note(f"Got type '{getFullyQualifiedName(inclusive)}'.")
170 raise ex
171 if maxLines is not None:
172 if not isinstance(maxLines, int):
173 ex = TypeError("Parameter 'maxLines' is not a integer.")
174 ex.add_note(f"Got type '{getFullyQualifiedName(maxLines)}'.")
175 raise ex
176 elif maxLines <= 0:
177 ex = ValueError("Parameter 'maxLines' must be a positive integer.")
178 ex.add_note(f"Got {maxLines!r}.")
179 raise ex
181 current = self._previousLine if reverse else self._nextLine
183 if maxLines is None:
184 if stopPredicate is None:
185 if reverse:
186 while current is not None:
187 yield current
188 current = current._previousLine
189 else:
190 while current is not None:
191 yield current
192 current = current._nextLine
193 else:
194 if reverse:
195 while current is not None:
196 if stopPredicate(current):
197 if inclusive:
198 yield current
199 return
200 yield current
201 current = current._previousLine
202 else:
203 while current is not None:
204 if stopPredicate(current):
205 if inclusive:
206 yield current
207 return
208 yield current
209 current = current._nextLine
211 elif stopPredicate is None:
212 remaining = maxLines
213 if reverse:
214 while current is not None and remaining > 0:
215 yield current
216 current = current._previousLine
217 remaining -= 1
218 else:
219 while current is not None and remaining > 0:
220 yield current
221 current = current._nextLine
222 remaining -= 1
224 else:
225 remaining = maxLines
226 if reverse:
227 while current is not None and remaining > 0:
228 if stopPredicate(current):
229 if inclusive:
230 yield current
231 return
232 yield current
233 current = current._previousLine
234 remaining -= 1
235 else:
236 while current is not None and remaining > 0:
237 if stopPredicate(current):
238 if inclusive:
239 yield current
240 return
241 yield current
242 current = current._nextLine
243 remaining -= 1
245 def __getitem__(self, item: slice) -> str:
246 return self._message[item]
248 def __eq__(self, other: Any):
249 return self._message == other
251 def __ne__(self, other: Any):
252 return self._message != other
254 def __str__(self) -> str:
255 return self._message
257 def __repr__(self) -> str:
258 return f"{self._lineNumber}: {self._message}"
261@export
262class InfoMessage(metaclass=ExtendedType, mixin=True):
263 pass
266@export
267class WarningMessage(metaclass=ExtendedType, mixin=True):
268 pass
271@export
272class CriticalWarningMessage(metaclass=ExtendedType, mixin=True):
273 pass
276@export
277class ErrorMessage(metaclass=ExtendedType, mixin=True):
278 pass