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

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"] 

39 

40from datetime import datetime 

41from enum import Flag 

42from typing import Any, Generator, Callable, Tuple, Union, Optional as Nullable, Generic, TypeVar 

43 

44from pyTooling.Common import getFullyQualifiedName 

45from pyTooling.Decorators import export, readonly 

46from pyTooling.Exceptions import ExceptionBase 

47from pyTooling.MetaClasses import ExtendedType 

48 

49 

50@export 

51class OutputFilterException(ExceptionBase): 

52 """Base-class for all pyEDAA.OutputFilter specific exceptions.""" 

53 

54 

55LineClassification = TypeVar("LineClassification", bound=Flag) 

56LineProcessingAction = TypeVar("LineProcessingAction", bound=Flag) 

57 

58@export 

59class Line(Generic[LineClassification, LineProcessingAction], metaclass=ExtendedType, slots=True): 

60 """ 

61 This class represents any line in a log file. 

62 

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"] 

75 

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 

90 

91 if previousLine is not None: 

92 previousLine._nextLine = self 

93 

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 

96 

97 @readonly 

98 def LineNumber(self) -> int: 

99 return self._lineNumber 

100 

101 @readonly 

102 def Kind(self) -> LineClassification: 

103 return self._kind 

104 

105 @readonly 

106 def Action(self) -> LineProcessingAction: 

107 return self._action 

108 

109 @readonly 

110 def Message(self) -> str: 

111 return self._message 

112 

113 @property 

114 def PreviousLine(self) -> Nullable["Line"]: 

115 return self._previousLine 

116 

117 @PreviousLine.setter 

118 def PreviousLine(self, line: "Line") -> None: 

119 self._previousLine = line 

120 if line is not None: 

121 line._nextLine = self 

122 

123 @readonly 

124 def NextLine(self) -> Nullable["Line"]: 

125 return self._nextLine 

126 

127 def StartsWith(self, prefix: Union[str, Tuple[str, ...]]): 

128 return self._message.startswith(prefix) 

129 

130 def Partition(self, separator: str) -> Tuple[str, str, str]: 

131 return self._message.partition(separator) 

132 

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. 

143 

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. 

147 

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 

180 

181 current = self._previousLine if reverse else self._nextLine 

182 

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 

210 

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 

223 

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 

244 

245 def __getitem__(self, item: slice) -> str: 

246 return self._message[item] 

247 

248 def __eq__(self, other: Any): 

249 return self._message == other 

250 

251 def __ne__(self, other: Any): 

252 return self._message != other 

253 

254 def __str__(self) -> str: 

255 return self._message 

256 

257 def __repr__(self) -> str: 

258 return f"{self._lineNumber}: {self._message}" 

259 

260 

261@export 

262class InfoMessage(metaclass=ExtendedType, mixin=True): 

263 pass 

264 

265 

266@export 

267class WarningMessage(metaclass=ExtendedType, mixin=True): 

268 pass 

269 

270 

271@export 

272class CriticalWarningMessage(metaclass=ExtendedType, mixin=True): 

273 pass 

274 

275 

276@export 

277class ErrorMessage(metaclass=ExtendedType, mixin=True): 

278 pass