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