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
« 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.
3 Author: Norbert Schulz (norbert.schulz@newtec.de)
4"""
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/>.
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
32# Variables ********************************************************************
34# Classes **********************************************************************
37class RecordsPolicy(Enum):
38 """
39 Enum class to define policies for handling records during conversion.
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
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
60 # Default value used to replace empty attribute values.
61 EMPTY_ATTRIBUTE_DEFAULT = "N/A"
63 def __init__(self, args: Any) -> None:
64 """
65 Initializes the converter with the given arguments.
67 Args:
68 args (Any): The parsed program arguments.
69 """
71 # Store the command line arguments.
72 self._args = args
74 # Record handler dictionary for project specific record handlers.
75 self._record_handler_dict = {} # type: dict[str, Callable]
77 # Set the default policy for handling records.
78 self._record_policy = RecordsPolicy.RECORD_CONVERT_ALL
80 # Set the default value for empty attributes.
81 self._empty_attribute_value = BaseConverter.EMPTY_ATTRIBUTE_DEFAULT
83 # Requirement type attribute translator.
84 self._translator = Translator()
86 # Render configuration used to know how to interprete the attribute value.
87 self._render_cfg = RenderConfig()
89 @classmethod
90 def register(cls, args_parser: Any) -> None:
91 """Register converter specific argument parser.
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)
102 def set_render_cfg(self, render_cfg: RenderConfig) -> None:
103 """Set the render configuration.
105 Args:
106 render_cfg (RenderConfig): Render configuration
107 """
108 self._render_cfg = render_cfg
110 def begin(self) -> Ret:
111 """ Begin the conversion process.
113 Returns:
114 Ret: Status
115 """
116 result = Ret.OK
118 if isinstance(self._args.translation, str):
119 if self._translator.load(self._args.translation) is False:
120 result = Ret.ERROR
122 return result
124 def enter_file(self, file_name: str) -> Ret:
125 """Enter a file.
127 Args:
128 file_name (str): File name
130 Returns:
131 Ret: Status
132 """
133 return Ret.OK
135 def leave_file(self, file_name: str) -> Ret:
136 """Leave a file.
138 Args:
139 file_name (str): File name
141 Returns:
142 Ret: Status
143 """
144 return Ret.OK
146 def convert_section(self, section: str, level: int) -> Ret:
147 """Process the given section item.
149 Args:
150 section (str): The section name
151 level (int): The section indentation level
153 Returns:
154 Ret: Status
155 """
156 return Ret.OK
158 def convert_record_object(self, record: Record_Object, level: int) -> Ret:
159 """Process the given record object.
161 Args:
162 record (Record_Object): The record object
163 level (int): The record level
165 Returns:
166 Ret: Status
167 """
168 # Get the record attribute translation dictionary.
169 translation = self._translator.get_translation(record.n_typ.name)
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)
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
181 elif self._record_policy == RecordsPolicy.RECORD_CONVERT_ALL:
182 result = self.convert_record_object_generic(record, level, translation)
184 else:
185 result = Ret.OK
187 return result
189 def finish(self):
190 """Finish the conversion process.
191 """
192 return Ret.OK
194 # helpers **************************************************************
196 def convert_record_object_generic(self, record: Record_Object, level: int, translation: Optional[dict]) -> Ret:
197 """Convert a record object generically.
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.
205 Returns:
206 Ret: Status
207 """
209 raise NotImplementedError
211 def _set_project_record_handler(self, record_type: str, handler: Callable) -> None:
212 """Set a project specific record handler.
214 Args:
215 record_type (str): The record type
216 handler (Callable): The handler function
217 """
218 self._record_handler_dict[record_type] = handler
220 def _set_project_record_handlers(self, handlers: dict[str, Callable]) -> None:
221 """Set project specific record handlers.
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)
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.
233 Args:
234 record (Record_Object): The record object
235 attribute_name (str): The attribute name to get the value from.
237 Returns:
238 str: The attribute value.
239 """
240 record_dict = record.to_python_dict()
241 attribute_value = record_dict[attribute_name]
243 if attribute_value is None:
244 attribute_value = self._empty_attribute_value
245 elif attribute_value == "":
246 attribute_value = self._empty_attribute_value
248 return attribute_value
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.
254 Args:
255 translation (Optional[dict]): The translation dictionary.
256 attribute_name (str): The attribute name to translate.
258 Returns:
259 str: The translated attribute name.
260 """
262 if translation is not None:
263 if attribute_name in translation:
264 attribute_name = translation[attribute_name]
266 return attribute_name
268# Functions ********************************************************************
270# Main *************************************************************************