Coverage for pyEDAA/OutputFilter/__init__.py: 35%
158 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-20 22:02 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-20 22:02 +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.16.0"
38__keywords__ = ["cli", "abstraction layer", "eda", "filter", "classification"]
39__project_url__ = "https://github.com/edaa-org/pyEDAA.OutputFilter"
40__documentation_url__ = "https://edaa-org.github.io/pyEDAA.OutputFilter"
41__issue_tracker_url__ = "https://GitHub.com/edaa-org/pyEDAA.OutputFilter/issues"
43from datetime import datetime
44from enum import Flag
45from typing import Any, Generator, Callable, Tuple, Union, Optional as Nullable, Generic, TypeVar
47from pyTooling.Common import getFullyQualifiedName
48from pyTooling.Decorators import export, readonly
49from pyTooling.Exceptions import ExceptionBase
50from pyTooling.MetaClasses import ExtendedType
53@export
54class OutputFilterException(ExceptionBase):
55 """Base-class for all pyEDAA.OutputFilter specific exceptions."""
58LineClassification = TypeVar("LineClassification", bound=Flag)
59LineProcessingAction = TypeVar("LineProcessingAction", bound=Flag)
61@export
62class Line(Generic[LineClassification, LineProcessingAction], metaclass=ExtendedType, slots=True):
63 """
64 This class represents any line in a log file.
66 A line has a line number (:attr:`_lineNumber`), a message (:attr:`__message`) and a message kind (:attr:`__kind`). In
67 addition, all line objects in a log file form a doubly
68 linked list.
69 """
70 _lineNumber: int
71 _timestamp: Nullable[datetime]
72 _document: "Document"
73 _kind: LineClassification
74 _action: LineProcessingAction
75 _message: str
76 _previousLine: Nullable["Line"]
77 _nextLine: Nullable["Line"]
79 def __init__(
80 self,
81 lineNumber: int,
82 kind: LineClassification,
83 action: LineProcessingAction,
84 message: str,
85 previousLine: Nullable["Line"] = None
86 ) -> None:
87 self._lineNumber = lineNumber
88 self._kind = kind
89 self._action = action
90 self._message = message
91 self._previousLine = previousLine
92 self._nextLine = None
94 if previousLine is not None:
95 previousLine._nextLine = self
97 if not isinstance(message, str): 97 ↛ 98line 97 didn't jump to line 98 because the condition on line 97 was never true
98 pass
100 @readonly
101 def LineNumber(self) -> int:
102 return self._lineNumber
104 @readonly
105 def Kind(self) -> LineClassification:
106 return self._kind
108 @readonly
109 def Action(self) -> LineProcessingAction:
110 return self._action
112 @readonly
113 def Message(self) -> str:
114 return self._message
116 @property
117 def PreviousLine(self) -> Nullable["Line"]:
118 return self._previousLine
120 @PreviousLine.setter
121 def PreviousLine(self, line: "Line") -> None:
122 self._previousLine = line
123 if line is not None:
124 line._nextLine = self
126 @readonly
127 def NextLine(self) -> Nullable["Line"]:
128 return self._nextLine
130 def StartsWith(self, prefix: Union[str, Tuple[str, ...]]):
131 return self._message.startswith(prefix)
133 def Partition(self, separator: str) -> Tuple[str, str, str]:
134 return self._message.partition(separator)
136 def GetIterator(
137 self,
138 stopPredicate: Nullable[Callable[["Line"], bool]] = None,
139 *,
140 reverse: bool = False,
141 inclusive: bool = True,
142 maxLines: Nullable[int] = None,
143 ) -> Generator["Line", None, None]:
144 """
145 Iterate consecutive lines starting from next line towards the end of the log.
147 If the order is reversed, iterate starting at the previous line towards the beginning of the log. The iteration ends
148 either at the bounds of the log, by specifying a stop predicate or a maximum number of lines to return. When stopped
149 this line is usually included in the iteration, but can be excluded.
151 :param stopPredicate: Optional, a callable receiving a :class:`Line` and returning ``True`` when iteration should
152 stop at that line.
153 :param reverse: Optional, reverse the iteration from previous line to the beginning of the log.
154 :param inclusive: Optional, when ``True`` the line where ``stopPredicate`` or ``maxLines`` triggers, is
155 included in the iteration, otherwise it's excluded.
156 :param maxLines: Optional, maximum number of lines to yield.
157 :returns: A generator yielding :class:`Line` in the requested direction, stopping at the log boundary,
158 the predicate match, or the line limit — whichever comes first.
159 :raises TypeError: When ``stopPredicate`` is not callable.
160 :raises ValueError: When ``maxLines`` is not a positive integer.
161 """
162 if stopPredicate is not None and not callable(stopPredicate):
163 ex = TypeError("Parameter 'stopPredicate' is not a callable.")
164 ex.add_note(f"Got type '{getFullyQualifiedName(stopPredicate)}'.")
165 raise ex
166 if not isinstance(reverse, bool):
167 ex = TypeError("Parameter 'reverse' is not a boolean.")
168 ex.add_note(f"Got type '{getFullyQualifiedName(reverse)}'.")
169 raise ex
170 if not isinstance(inclusive, bool):
171 ex = TypeError("Parameter 'inclusive' is not a boolean.")
172 ex.add_note(f"Got type '{getFullyQualifiedName(inclusive)}'.")
173 raise ex
174 if maxLines is not None:
175 if not isinstance(maxLines, int):
176 ex = TypeError("Parameter 'maxLines' is not a integer.")
177 ex.add_note(f"Got type '{getFullyQualifiedName(maxLines)}'.")
178 raise ex
179 elif maxLines <= 0:
180 ex = ValueError("Parameter 'maxLines' must be a positive integer.")
181 ex.add_note(f"Got {maxLines!r}.")
182 raise ex
184 current = self._previousLine if reverse else self._nextLine
186 if maxLines is None:
187 if stopPredicate is None:
188 if reverse:
189 while current is not None:
190 yield current
191 current = current._previousLine
192 else:
193 while current is not None:
194 yield current
195 current = current._nextLine
196 else:
197 if reverse:
198 while current is not None:
199 if stopPredicate(current):
200 if inclusive:
201 yield current
202 return
203 yield current
204 current = current._previousLine
205 else:
206 while current is not None:
207 if stopPredicate(current):
208 if inclusive:
209 yield current
210 return
211 yield current
212 current = current._nextLine
214 elif stopPredicate is None:
215 remaining = maxLines
216 if reverse:
217 while current is not None and remaining > 0:
218 yield current
219 current = current._previousLine
220 remaining -= 1
221 else:
222 while current is not None and remaining > 0:
223 yield current
224 current = current._nextLine
225 remaining -= 1
227 else:
228 remaining = maxLines
229 if reverse:
230 while current is not None and remaining > 0:
231 if stopPredicate(current):
232 if inclusive:
233 yield current
234 return
235 yield current
236 current = current._previousLine
237 remaining -= 1
238 else:
239 while current is not None and remaining > 0:
240 if stopPredicate(current):
241 if inclusive:
242 yield current
243 return
244 yield current
245 current = current._nextLine
246 remaining -= 1
248 def __getitem__(self, item: slice) -> str:
249 return self._message[item]
251 def __eq__(self, other: Any):
252 return self._message == other
254 def __ne__(self, other: Any):
255 return self._message != other
257 def __str__(self) -> str:
258 return self._message
260 def __repr__(self) -> str:
261 return f"{self._lineNumber}: {self._message}"
264@export
265class InfoMessage(metaclass=ExtendedType, mixin=True):
266 pass
269@export
270class WarningMessage(metaclass=ExtendedType, mixin=True):
271 pass
274@export
275class CriticalWarningMessage(metaclass=ExtendedType, mixin=True):
276 pass
279@export
280class ErrorMessage(metaclass=ExtendedType, mixin=True):
281 pass