__init__.py

Summary

Lines:

84 of 167 covered (50.3%)

Branches:

3 of 64 taken (4.7%), 1 lines partially

Coverage:

37.7%

Directory:

OutputFilter

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