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

75 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-02 12:20 +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 - 2026 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 """Enum class to define policies for handling records during conversion.""" 

39 

40 RECORD_CONVERT_ALL = 1 

41 """Convert all records, including undefined ones using convert_record_object_generic().""" 

42 

43 RECORD_SKIP_UNDEFINED = 2 

44 """Skip record types that are not linked to a handler.""" 

45 

46 

47class BaseConverter(AbstractConverter): 

48 # lobster-trace: SwRequirements.sw_req_destination_format 

49 # lobster-trace: SwRequirements.sw_req_translation 

50 """ 

51 Base converter with empty method implementations and helper functions  

52 for subclassing converters. 

53 """ 

54 # Converter specific sub parser 

55 _parser = None 

56 

57 # Default value used to replace empty attribute values. 

58 EMPTY_ATTRIBUTE_DEFAULT = "N/A" 

59 

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

61 """ 

62 Initializes the converter with the given arguments. 

63 

64 Args: 

65 args (Any): The parsed program arguments. 

66 """ 

67 

68 # Store the command line arguments. 

69 self._args = args 

70 

71 # Record handler dictionary for project specific record handlers. 

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

73 

74 # Set the default policy for handling records. 

75 self._record_policy = RecordsPolicy.RECORD_CONVERT_ALL 

76 

77 # Set the default value for empty attributes. 

78 self._empty_attribute_value = BaseConverter.EMPTY_ATTRIBUTE_DEFAULT 

79 

80 # Requirement type attribute translator. 

81 self._translator = Translator() 

82 

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

84 self._render_cfg = RenderConfig() 

85 

86 @classmethod 

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

88 """Register converter specific argument parser. 

89 

90 Args: 

91 args_parser (Any): Argument parser 

92 """ 

93 BaseConverter._parser = args_parser.add_parser( 

94 cls.get_subcommand(), 

95 help=cls.get_description() 

96 ) 

97 BaseConverter._parser.set_defaults(converter_class=cls) 

98 

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

100 """Set the render configuration. 

101 

102 Args: 

103 render_cfg (RenderConfig): Render configuration 

104 """ 

105 self._render_cfg = render_cfg 

106 

107 def begin(self) -> Ret: 

108 """ Begin the conversion process. 

109 

110 Returns: 

111 Ret: Status 

112 """ 

113 result = Ret.OK 

114 

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

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

117 result = Ret.ERROR 

118 

119 return result 

120 

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

122 """Enter a file. 

123 

124 Args: 

125 file_name (str): File name 

126 

127 Returns: 

128 Ret: Status 

129 """ 

130 return Ret.OK 

131 

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

133 """Leave a file. 

134 

135 Args: 

136 file_name (str): File name 

137 

138 Returns: 

139 Ret: Status 

140 """ 

141 return Ret.OK 

142 

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

144 """Process the given section item. 

145 

146 Args: 

147 section (str): The section name 

148 level (int): The section indentation level 

149 

150 Returns: 

151 Ret: Status 

152 """ 

153 return Ret.OK 

154 

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

156 """Process the given record object. 

157 

158 Args: 

159 record (Record_Object): The record object 

160 level (int): The record level 

161 

162 Returns: 

163 Ret: Status 

164 """ 

165 # Get the record attribute translation dictionary. 

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

167 

168 # Check for a specific record handler. 

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

170 if callable(record_handler): 

171 result = record_handler(record, level, translation) 

172 

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

174 if not isinstance(result, Ret): 

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

176 result = Ret.ERROR 

177 

178 elif self._record_policy == RecordsPolicy.RECORD_CONVERT_ALL: 

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

180 

181 else: 

182 result = Ret.OK 

183 

184 return result 

185 

186 def finish(self): 

187 """Finish the conversion process. 

188 """ 

189 return Ret.OK 

190 

191 # helpers ************************************************************** 

192 

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

194 """Convert a record object generically. 

195 

196 Args: 

197 record (Record_Object): The record object. 

198 level (int): The record level. 

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

200 If None, no translation is applied. 

201 

202 Returns: 

203 Ret: Status 

204 """ 

205 

206 raise NotImplementedError 

207 

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

209 """Set a project specific record handler. 

210 

211 Args: 

212 record_type (str): The record type 

213 handler (Callable): The handler function 

214 """ 

215 self._record_handler_dict[record_type] = handler 

216 

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

218 """Set project specific record handlers. 

219 

220 Args: 

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

222 """ 

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

224 self._set_project_record_handler(record_type, handler) 

225 

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

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

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

229 

230 Args: 

231 record (Record_Object): The record object 

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

233 

234 Returns: 

235 str: The attribute value. 

236 """ 

237 record_dict = record.to_python_dict() 

238 attribute_value = record_dict[attribute_name] 

239 

240 if attribute_value is None: 

241 attribute_value = self._empty_attribute_value 

242 elif attribute_value == "": 

243 attribute_value = self._empty_attribute_value 

244 

245 return attribute_value 

246 

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

248 """Translate attribute name on demand. 

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

250 

251 Args: 

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

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

254 

255 Returns: 

256 str: The translated attribute name. 

257 """ 

258 

259 if translation is not None: 

260 if attribute_name in translation: 

261 attribute_name = translation[attribute_name] 

262 

263 return attribute_name 

264 

265# Functions ******************************************************************** 

266 

267# Main *************************************************************************