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

64 statements  

« prev     ^ index     » next       coverage.py v7.10.3, created at 2025-08-14 10:59 +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 

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 

30 

31# Variables ******************************************************************** 

32 

33# Classes ********************************************************************** 

34 

35 

36class RecordsPolicy(Enum): 

37 """ 

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

39 

40 Attributes: 

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

42 using convert_record_object_generic(). 

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

44 """ 

45 RECORD_CONVERT_ALL= 1 

46 RECORD_SKIP_UNDEFINED = 2 

47 

48 

49class BaseConverter(AbstractConverter): 

50 # lobster-trace: SwRequirements.sw_req_destination_format 

51 # lobster-trace: SwRequirements.sw_req_translation 

52 """ 

53 Base converter with empty method implementations and helper functions  

54 for subclassing converters. 

55 """ 

56 # Converter specific sub parser 

57 _parser = None 

58 

59 # Default value used to replace empty attribute values. 

60 EMPTY_ATTRIBUTE_DEFAULT = "N/A" 

61 

62 def __init__(self, args: any) -> None: 

63 """ 

64 Initializes the converter with the given arguments. 

65 

66 Args: 

67 args (any): The parsed program arguments. 

68 """ 

69 

70 # Store the command line arguments. 

71 self._args = args 

72 

73 # Record handler dictionary for project specific record handlers. 

74 self._record_handler_dict = {} # type: dict[str, callable] 

75 

76 # Set the default policy for handling records. 

77 self._record_policy = RecordsPolicy.RECORD_CONVERT_ALL 

78 

79 # Set the default value for empty attributes. 

80 self._empty_attribute_value = BaseConverter.EMPTY_ATTRIBUTE_DEFAULT 

81 

82 # Requirement type attribute translator. 

83 self._translator = Translator() 

84 

85 @classmethod 

86 def register(cls, args_parser: any) -> None: 

87 """Register converter specific argument parser. 

88 

89 Args: 

90 args_parser (any): Argument parser 

91 """ 

92 BaseConverter._parser = args_parser.add_parser( 

93 cls.get_subcommand(), 

94 help=cls.get_description() 

95 ) 

96 BaseConverter._parser.set_defaults(converter_class=cls) 

97 

98 def begin(self) -> Ret: 

99 """ Begin the conversion process. 

100 

101 Returns: 

102 Ret: Status 

103 """ 

104 result = Ret.OK 

105 

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

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

108 result = Ret.ERROR 

109 

110 return result 

111 

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

113 """Enter a file. 

114 

115 Args: 

116 file_name (str): File name 

117 

118 Returns: 

119 Ret: Status 

120 """ 

121 return Ret.OK 

122 

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

124 """Leave a file. 

125 

126 Args: 

127 file_name (str): File name 

128 

129 Returns: 

130 Ret: Status 

131 """ 

132 return Ret.OK 

133 

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

135 """Process the given section item. 

136 

137 Args: 

138 section (str): The section name 

139 level (int): The section indentation level 

140 

141 Returns: 

142 Ret: Status 

143 """ 

144 return Ret.OK 

145 

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

147 """Process the given record object. 

148 

149 Args: 

150 record (Record_Object): The record object 

151 level (int): The record level 

152 

153 Returns: 

154 Ret: Status 

155 """ 

156 # Get the record attribute translation dictionary. 

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

158 

159 # Check for a specific record handler. 

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

161 if callable(record_handler): 

162 result = record_handler(record, level, translation) 

163 

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

165 if not isinstance(result, Ret): 

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

167 result = Ret.ERROR 

168 

169 elif self._record_policy == RecordsPolicy.RECORD_CONVERT_ALL: 

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

171 

172 else: 

173 result = Ret.OK 

174 

175 return result 

176 

177 def finish(self): 

178 """Finish the conversion process. 

179 """ 

180 return Ret.OK 

181 

182 # helpers ************************************************************** 

183 

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

185 """Convert a record object generically. 

186 

187 Args: 

188 record (Record_Object): The record object. 

189 level (int): The record level. 

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

191 If None, no translation is applied. 

192 

193 Returns: 

194 Ret: Status 

195 """ 

196 

197 raise NotImplementedError 

198 

199 def _set_project_record_handler(self, record_type: str, handler: callable) -> None: 

200 """Set a project specific record handler. 

201 

202 Args: 

203 record_type (str): The record type 

204 handler (callable): The handler function 

205 """ 

206 self._record_handler_dict[record_type] = handler 

207 

208 def _set_project_record_handlers(self, handlers: dict[str, callable]) -> None: 

209 """Set project specific record handlers. 

210 

211 Args: 

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

213 """ 

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

215 self._set_project_record_handler(record_type, handler) 

216 

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

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

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

220 

221 Args: 

222 record (Record_Object): The record object 

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

224 

225 Returns: 

226 str: The attribute value. 

227 """ 

228 record_dict = record.to_python_dict() 

229 attribute_value = record_dict[attribute_name] 

230 

231 if attribute_value is None: 

232 attribute_value = self._empty_attribute_value 

233 elif attribute_value == "": 

234 attribute_value = self._empty_attribute_value 

235 

236 return attribute_value 

237 

238# Functions ******************************************************************** 

239 

240# Main *************************************************************************