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
« 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.
3 Author: Norbert Schulz (norbert.schulz@newtec.de)
4"""
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/>.
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 """Enum class to define policies for handling records during conversion."""
40 RECORD_CONVERT_ALL = 1
41 """Convert all records, including undefined ones using convert_record_object_generic()."""
43 RECORD_SKIP_UNDEFINED = 2
44 """Skip record types that are not linked to a handler."""
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
57 # Default value used to replace empty attribute values.
58 EMPTY_ATTRIBUTE_DEFAULT = "N/A"
60 def __init__(self, args: Any) -> None:
61 """
62 Initializes the converter with the given arguments.
64 Args:
65 args (Any): The parsed program arguments.
66 """
68 # Store the command line arguments.
69 self._args = args
71 # Record handler dictionary for project specific record handlers.
72 self._record_handler_dict = {} # type: dict[str, Callable]
74 # Set the default policy for handling records.
75 self._record_policy = RecordsPolicy.RECORD_CONVERT_ALL
77 # Set the default value for empty attributes.
78 self._empty_attribute_value = BaseConverter.EMPTY_ATTRIBUTE_DEFAULT
80 # Requirement type attribute translator.
81 self._translator = Translator()
83 # Render configuration used to know how to interprete the attribute value.
84 self._render_cfg = RenderConfig()
86 @classmethod
87 def register(cls, args_parser: Any) -> None:
88 """Register converter specific argument parser.
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)
99 def set_render_cfg(self, render_cfg: RenderConfig) -> None:
100 """Set the render configuration.
102 Args:
103 render_cfg (RenderConfig): Render configuration
104 """
105 self._render_cfg = render_cfg
107 def begin(self) -> Ret:
108 """ Begin the conversion process.
110 Returns:
111 Ret: Status
112 """
113 result = Ret.OK
115 if isinstance(self._args.translation, str):
116 if self._translator.load(self._args.translation) is False:
117 result = Ret.ERROR
119 return result
121 def enter_file(self, file_name: str) -> Ret:
122 """Enter a file.
124 Args:
125 file_name (str): File name
127 Returns:
128 Ret: Status
129 """
130 return Ret.OK
132 def leave_file(self, file_name: str) -> Ret:
133 """Leave a file.
135 Args:
136 file_name (str): File name
138 Returns:
139 Ret: Status
140 """
141 return Ret.OK
143 def convert_section(self, section: str, level: int) -> Ret:
144 """Process the given section item.
146 Args:
147 section (str): The section name
148 level (int): The section indentation level
150 Returns:
151 Ret: Status
152 """
153 return Ret.OK
155 def convert_record_object(self, record: Record_Object, level: int) -> Ret:
156 """Process the given record object.
158 Args:
159 record (Record_Object): The record object
160 level (int): The record level
162 Returns:
163 Ret: Status
164 """
165 # Get the record attribute translation dictionary.
166 translation = self._translator.get_translation(record.n_typ.name)
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)
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
178 elif self._record_policy == RecordsPolicy.RECORD_CONVERT_ALL:
179 result = self.convert_record_object_generic(record, level, translation)
181 else:
182 result = Ret.OK
184 return result
186 def finish(self):
187 """Finish the conversion process.
188 """
189 return Ret.OK
191 # helpers **************************************************************
193 def convert_record_object_generic(self, record: Record_Object, level: int, translation: Optional[dict]) -> Ret:
194 """Convert a record object generically.
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.
202 Returns:
203 Ret: Status
204 """
206 raise NotImplementedError
208 def _set_project_record_handler(self, record_type: str, handler: Callable) -> None:
209 """Set a project specific record handler.
211 Args:
212 record_type (str): The record type
213 handler (Callable): The handler function
214 """
215 self._record_handler_dict[record_type] = handler
217 def _set_project_record_handlers(self, handlers: dict[str, Callable]) -> None:
218 """Set project specific record handlers.
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)
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.
230 Args:
231 record (Record_Object): The record object
232 attribute_name (str): The attribute name to get the value from.
234 Returns:
235 str: The attribute value.
236 """
237 record_dict = record.to_python_dict()
238 attribute_value = record_dict[attribute_name]
240 if attribute_value is None:
241 attribute_value = self._empty_attribute_value
242 elif attribute_value == "":
243 attribute_value = self._empty_attribute_value
245 return attribute_value
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.
251 Args:
252 translation (Optional[dict]): The translation dictionary.
253 attribute_name (str): The attribute name to translate.
255 Returns:
256 str: The translated attribute name.
257 """
259 if translation is not None:
260 if attribute_name in translation:
261 attribute_name = translation[attribute_name]
263 return attribute_name
265# Functions ********************************************************************
267# Main *************************************************************************