Coverage for src / pyTRLCConverter / base_converter.py: 75%

73 statements  

« prev     ^ index     » next       coverage.py v7.12.0, created at 2025-11-21 12:06 +0000

1""" Converter base class which does the argparser handling and provides helper functions. 

2 

3 Author: Norbert Schulz (norbert.schulz@newtec.de) 

4""" 

5 

6# pyTRLCConverter - A tool to convert TRLC files to specific formats. 

7# Copyright (c) 2024 - 2025 NewTec GmbH 

8# 

9# This file is part of pyTRLCConverter program. 

10# 

11# The pyTRLCConverter program is free software: you can redistribute it and/or modify it under 

12# the terms of the GNU General Public License as published by the Free Software Foundation, 

13# either version 3 of the License, or (at your option) any later version. 

14# 

15# The pyTRLCConverter program is distributed in the hope that it will be useful, but 

16# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 

17# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 

18# 

19# You should have received a copy of the GNU General Public License along with pyTRLCConverter. 

20# If not, see <https://www.gnu.org/licenses/>. 

21 

22# Imports ********************************************************************** 

23from enum import Enum 

24from typing import Optional, Any, Callable 

25from pyTRLCConverter.abstract_converter import AbstractConverter 

26from pyTRLCConverter.ret import Ret 

27from pyTRLCConverter.trlc_helper import Record_Object 

28from pyTRLCConverter.translator import Translator 

29from pyTRLCConverter.logger import log_error 

30from pyTRLCConverter.render_config import RenderConfig 

31 

32# Variables ******************************************************************** 

33 

34# Classes ********************************************************************** 

35 

36 

37class RecordsPolicy(Enum): 

38 """ 

39 Enum class to define policies for handling records during conversion. 

40 

41 Attributes: 

42 RECORD_CONVERT_ALL (int): Convert all records, including undefined ones 

43 using convert_record_object_generic(). 

44 RECORD_SKIP_UNDEFINED (int): Skip records types that are not linked to a handler. 

45 """ 

46 RECORD_CONVERT_ALL= 1 

47 RECORD_SKIP_UNDEFINED = 2 

48 

49 

50class BaseConverter(AbstractConverter): 

51 # lobster-trace: SwRequirements.sw_req_destination_format 

52 # lobster-trace: SwRequirements.sw_req_translation 

53 """ 

54 Base converter with empty method implementations and helper functions  

55 for subclassing converters. 

56 """ 

57 # Converter specific sub parser 

58 _parser = None 

59 

60 # Default value used to replace empty attribute values. 

61 EMPTY_ATTRIBUTE_DEFAULT = "N/A" 

62 

63 def __init__(self, args: Any) -> None: 

64 """ 

65 Initializes the converter with the given arguments. 

66 

67 Args: 

68 args (Any): The parsed program arguments. 

69 """ 

70 

71 # Store the command line arguments. 

72 self._args = args 

73 

74 # Record handler dictionary for project specific record handlers. 

75 self._record_handler_dict = {} # type: dict[str, Callable] 

76 

77 # Set the default policy for handling records. 

78 self._record_policy = RecordsPolicy.RECORD_CONVERT_ALL 

79 

80 # Set the default value for empty attributes. 

81 self._empty_attribute_value = BaseConverter.EMPTY_ATTRIBUTE_DEFAULT 

82 

83 # Requirement type attribute translator. 

84 self._translator = Translator() 

85 

86 # Render configuration used to know how to interprete the attribute value. 

87 self._render_cfg = RenderConfig() 

88 

89 @classmethod 

90 def register(cls, args_parser: Any) -> None: 

91 """Register converter specific argument parser. 

92 

93 Args: 

94 args_parser (Any): Argument parser 

95 """ 

96 BaseConverter._parser = args_parser.add_parser( 

97 cls.get_subcommand(), 

98 help=cls.get_description() 

99 ) 

100 BaseConverter._parser.set_defaults(converter_class=cls) 

101 

102 def set_render_cfg(self, render_cfg: RenderConfig) -> None: 

103 """Set the render configuration. 

104 

105 Args: 

106 render_cfg (RenderConfig): Render configuration 

107 """ 

108 self._render_cfg = render_cfg 

109 

110 def begin(self) -> Ret: 

111 """ Begin the conversion process. 

112 

113 Returns: 

114 Ret: Status 

115 """ 

116 result = Ret.OK 

117 

118 if isinstance(self._args.translation, str): 

119 if self._translator.load(self._args.translation) is False: 

120 result = Ret.ERROR 

121 

122 return result 

123 

124 def enter_file(self, file_name: str) -> Ret: 

125 """Enter a file. 

126 

127 Args: 

128 file_name (str): File name 

129 

130 Returns: 

131 Ret: Status 

132 """ 

133 return Ret.OK 

134 

135 def leave_file(self, file_name: str) -> Ret: 

136 """Leave a file. 

137 

138 Args: 

139 file_name (str): File name 

140 

141 Returns: 

142 Ret: Status 

143 """ 

144 return Ret.OK 

145 

146 def convert_section(self, section: str, level: int) -> Ret: 

147 """Process the given section item. 

148 

149 Args: 

150 section (str): The section name 

151 level (int): The section indentation level 

152 

153 Returns: 

154 Ret: Status 

155 """ 

156 return Ret.OK 

157 

158 def convert_record_object(self, record: Record_Object, level: int) -> Ret: 

159 """Process the given record object. 

160 

161 Args: 

162 record (Record_Object): The record object 

163 level (int): The record level 

164 

165 Returns: 

166 Ret: Status 

167 """ 

168 # Get the record attribute translation dictionary. 

169 translation = self._translator.get_translation(record.n_typ.name) 

170 

171 # Check for a specific record handler. 

172 record_handler = self._record_handler_dict.get(record.n_typ.name) 

173 if callable(record_handler): 

174 result = record_handler(record, level, translation) 

175 

176 # Don't trust project specific handlers to return a valid status. 

177 if not isinstance(result, Ret): 

178 log_error(f"Invalid return value from record handler for record {record.n_typ.name}.", True) 

179 result = Ret.ERROR 

180 

181 elif self._record_policy == RecordsPolicy.RECORD_CONVERT_ALL: 

182 result = self.convert_record_object_generic(record, level, translation) 

183 

184 else: 

185 result = Ret.OK 

186 

187 return result 

188 

189 def finish(self): 

190 """Finish the conversion process. 

191 """ 

192 return Ret.OK 

193 

194 # helpers ************************************************************** 

195 

196 def convert_record_object_generic(self, record: Record_Object, level: int, translation: Optional[dict]) -> Ret: 

197 """Convert a record object generically. 

198 

199 Args: 

200 record (Record_Object): The record object. 

201 level (int): The record level. 

202 translation (Optional[dict]): Translation dictionary for the record object. 

203 If None, no translation is applied. 

204 

205 Returns: 

206 Ret: Status 

207 """ 

208 

209 raise NotImplementedError 

210 

211 def _set_project_record_handler(self, record_type: str, handler: Callable) -> None: 

212 """Set a project specific record handler. 

213 

214 Args: 

215 record_type (str): The record type 

216 handler (Callable): The handler function 

217 """ 

218 self._record_handler_dict[record_type] = handler 

219 

220 def _set_project_record_handlers(self, handlers: dict[str, Callable]) -> None: 

221 """Set project specific record handlers. 

222 

223 Args: 

224 handler (dict[str, Callable]): List of record type and handler function tuples 

225 """ 

226 for record_type, handler in handlers.items(): 

227 self._set_project_record_handler(record_type, handler) 

228 

229 def _get_attribute(self, record: Record_Object, attribute_name: str) -> str: 

230 """Get the attribute value from the record object. 

231 If the attribute is not found or empty, return the default value. 

232 

233 Args: 

234 record (Record_Object): The record object 

235 attribute_name (str): The attribute name to get the value from. 

236 

237 Returns: 

238 str: The attribute value. 

239 """ 

240 record_dict = record.to_python_dict() 

241 attribute_value = record_dict[attribute_name] 

242 

243 if attribute_value is None: 

244 attribute_value = self._empty_attribute_value 

245 elif attribute_value == "": 

246 attribute_value = self._empty_attribute_value 

247 

248 return attribute_value 

249 

250 def _translate_attribute_name(self, translation: Optional[dict], attribute_name: str) -> str: 

251 """Translate attribute name on demand. 

252 If no translation is provided, the attribute name will be returned as is. 

253 

254 Args: 

255 translation (Optional[dict]): The translation dictionary. 

256 attribute_name (str): The attribute name to translate. 

257 

258 Returns: 

259 str: The translated attribute name. 

260 """ 

261 

262 if translation is not None: 

263 if attribute_name in translation: 

264 attribute_name = translation[attribute_name] 

265 

266 return attribute_name 

267 

268# Functions ******************************************************************** 

269 

270# Main *************************************************************************