diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml
new file mode 100644
index 0000000..8b4cda9
--- /dev/null
+++ b/.github/workflows/workflow.yml
@@ -0,0 +1,38 @@
+# This workflow will install Python dependencies, run tests and lint with a single version of Python
+# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python
+
+name: Workflow
+
+on: [pull_request]
+
+permissions:
+ contents: read
+
+jobs:
+ check:
+
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+ - name: Set up Python 3.10
+ uses: actions/setup-python@v3
+ with:
+ python-version: "3.10"
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ pip install flake8 pytest
+ if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
+ - name: Lint with flake8
+ run: |
+ # stop the build if there are Python syntax errors or undefined names
+ flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
+ # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
+ flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
+ - name: Functional check
+ run: make check
+
+ - name: Style check
+ run: make check-format
+
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..8bf7333
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,251 @@
+European Space Agency Public License (ESA-PL) Permissive – v2.3
+
+ 1 Definitions
+ 1.1 "Contributor" means
+ (a) the individual or legal entity that originally creates
+ or later modifies the Software and
+ (b) each subsequent individual or legal entity that creates
+ or contributes to the creation of Modifications.
+ 1.2 "Contributor Version" means the version of the Software on which
+ the Contributor based its Modifications.
+ 1.3 "Distribution" and "Distribute" means any act of selling, giving,
+ lending, renting, distributing, communicating, transmitting,
+ or otherwise making available, physically or electronically
+ or by any other means, copies of the Software or Modifications.
+ 1.4 "ESA" means the European Space Agency.
+ 1.5 "License" means this document.
+ 1.6 "Licensor" means the individual or legal entity
+ that Distributes the Software under the License to You.
+ 1.7 "Modification" means any work or software created that is based
+ upon or derived from the Software (or portions thereof) or
+ a modification of the Software (or portions thereof).
+ For the avoidance of doubt, linking a library to the Software
+ results in a Modification.
+ 1.8 "Object Code" means any non-Source Code form of the Software
+ and/or Modifications.
+ 1.9 "Patent Claims" (of a Contributor) means any patent claim(s),
+ owned at the time of the Distribution or subsequently acquired,
+ including without limitation, method, process and apparatus claims,
+ in any patent licensable by a Contributor which would be
+ infringed by making use of the rights granted under Sec. 2.1,
+ including but not limited to make, have made, use, sell,
+ offer for sale or import of the Contributor Version
+ and/or such Contributor's Modifications (if any), either alone
+ or in combination with the Contributor Version.
+ "Licensable" means having the right to grant,
+ whether at the time of the Distribution or subsequently acquired,
+ the rights conveyed herein.
+ 1.10 "Software" means the software Distributed under this License
+ by the Licensor, in Source Code and/or Object Code form.
+ 1.11 "Source Code" means the preferred, usually human readable form of
+ the Software and/or Modifications in which modifications are made
+ and the associated documentation included in or with such code.
+ 1.12 "You" means an individual or legal entity exercising rights under
+ this License (the licensee).
+
+
+ 2 Grant of Rights
+ 2.1 Copyright. The Licensor, and each Contributor in respect of such
+ Contributor's Modifications, hereby grants You a world-wide,
+ royalty-free, non-exclusive license under Copyright, subject to
+ the terms and conditions of this License, to:
+ * use the Software;
+ * reproduce the Software by any or all means and in any
+ or all form;
+ * Modify the Software and create works based on the Software;
+ * communicate to the public, including making available, display
+ or perform the Software or copies thereof to the public;
+ * Distribute, sublicense, lend and rent the Software.
+ The license grant is perpetual and irrevocable, unless terminated
+ pursuant to Sec. 7.
+ 2.2 Patents. Each Contributor in respect of such
+ Contributor's Modifications, hereby grants You a world-wide,
+ royalty-free, non-exclusive, sub-licensable license under
+ Patent Claims to the extent necessary to make use of the rights
+ granted under Sec. 2.1, including but not limited to make,
+ have made, use, sell, offer for sale, import, export and Distribute
+ such Contributor's Modifications and the combination of such
+ Contributor's Modifications with the Contributor Version
+ (collectively called the "Patent Licensed Version" of the Software).
+ No patent license is granted for claims that are infringed:
+ * only as a consequence of further modification of
+ the Patent Licensed Version; or
+ * by the combination of the Patent Licensed Version with other
+ software or other devices or hardware, unless such combination
+ was an intended use case of the Patent Licensed Version
+ (e.g. a general purpose library is intended to be used with
+ other software, a satellite navigation software is intended to
+ be used with appropriate hardware); or
+ * by a Modification under Patent Claims in the absence of
+ the Contributor's Modifications or by a combination of
+ the Contributor's Modifications with software other than
+ the Patent Licensed Version or Modifications thereof.
+ 2.3 Trademark. This License does not grant permission to use
+ trade names, trademarks, services marks, logos or names of
+ the Licensor, except as required for reasonable and customary use in
+ describing the origin of the Software and as reasonable necessary
+ to comply with the obligations of this License (e.g. by reproducing
+ the content of the notices). For the avoidance of doubt,
+ upon Distribution of Modifications You must not use the Licensor's
+ or ESA's trademarks, names or logos in any way that states or
+ implies, or can be interpreted as stating or implying, that
+ the final product is endorsed or created by the Licensor or ESA.
+
+
+ 3 Distribution
+ 3.1 No Copyleft.
+ You may Distribute the Software and/or Modifications, as Source Code
+ or Object Code, under any license terms, provided that
+ (a) notice is given of the use of the Software and the applicability
+ of this License to the Software; and
+ (b) You make best efforts to ensure that further Distribution of
+ the Software and/or Modifications (including further
+ Modifications) is subject to the obligations set forth in this
+ Sec. 3.1 (a) and (b).
+
+
+ 4 Notices
+ The following obligations apply in the event of any Distribution of
+ the Software and/or Modifications as Source Code and/or Object Code:
+ 4.1 You must include a copy of this License and all of the notices
+ set out in this Sec. 4.
+ 4.2 You may not remove or alter any copyright, patent, trademark
+ and attribution notices nor any of the notices set out in this
+ Sec. 4, except as necessary for your compliance with this License
+ or otherwise permitted by this License, except for those notices
+ that do not pertain to the Modifications You Distribute.
+ 4.3 Each Contributor must cause its Modification carrying prominent
+ notices stating that the Software has been modified and
+ the date of modification and identify itself as the originator
+ of its Modifications in a manner that reasonably allows
+ identification and contact with the Contributor.
+ The aforementioned notices must at a minimum be in a text file
+ included with the Distribution titled "CHANGELOG".
+ 4.4 The Software may include a "NOTICE" text file containing general
+ notices. Any Contributor can create such a NOTICE file or add
+ notices to it, alongside or as an addendum to the original text,
+ provided that such notices cannot be construed as modifying
+ the License.
+ 4.5 Each Contributor must identify all of its Patent Claims by providing
+ at a minimum the patent number and identification and contact
+ information in a text file included with the Distribution
+ titled "LEGAL".
+
+ 5 Warranty and Liability
+ 5.1 Each Contributor warrants and represents that it has sufficient
+ rights to grant the rights to its Modifications conveyed
+ by this License.
+ 5.2 Except as expressly set forth in this License,
+ the Software is provided to You on an "as is" basis
+ and without warranties of any kind, including without limitation
+ merchantability, fitness for a particular purpose, absence of
+ defects or errors, accuracy or non-infringement of intellectual
+ property rights. Mandatory statutory warranty claims,
+ e.g. in the event of wilful deception or fraudulent
+ misrepresentation, shall remain unaffected.
+ 5.3 Except as expressly set forth in this License,
+ neither Licensor nor any Contributor shall be liable, including,
+ without limitation, for direct, indirect, incidental,
+ or consequential damages (including without limitation
+ loss of profit), however caused and on any theory of liability,
+ arising in any way out of the use or Distribution of the Software
+ or the exercise of any rights under this License, even if You have
+ been advised of the possibility of such damages. Mandatory statutory
+ liability claims, e.g. in the event of wilful misconduct, wilful
+ deception or fraudulent misrepresentation, shall remain unaffected.
+
+ 6 Additional Agreements
+ While Distributing the Software or Modifications, You may choose
+ to conclude additional agreements, for free or for charge, regarding
+ for example support, warranty, indemnity, liability or liability
+ obligations and/or rights, provided such additional agreements are
+ consistent with this License and do not effectively restrict
+ the recipient's rights under this License. However, in accepting such
+ obligations, You may act only on Your own behalf and on Your sole
+ responsibility, not on behalf of any other Contributor or Licensor,
+ and only if You agree to indemnify, defend, and hold each Contributor
+ or Licensor harmless for any liability incurred by, or claims asserted
+ against, such Contributor or Licensor by reason of your accepting
+ any such warranty or additional liability.
+
+ 7 Infringements
+ 7.1 If You have knowledge that exercising rights granted by this License
+ infringes third party's intellectual property rights, including
+ without limitation copyright and patent rights, You must take
+ reasonable steps (such as notifying appropriate mailing lists
+ or newsgroups) to inform ESA and those who received the Software
+ about the infringement.
+ 7.2 You acknowledge that continuing to use the Software knowing
+ that such use infringes third party rights (e.g. after receiving
+ a third party notification of infringement) would expose you to the
+ risk of being considered as intentionally infringing third party
+ rights. In such event You should acquire the respective rights
+ or modify the Software so that the Modification is non-infringing.
+
+
+ 8 Termination
+ 8.1 This License and the rights granted hereunder will terminate
+ automatically upon any breach by You with the terms of this License
+ if you fail to cure such breach within 30 days of becoming aware of
+ the breach.
+ 8.2 If You institute patent litigation against any entity (including
+ a cross-claim or counterclaim in a lawsuit) alleging that
+ the Software constitutes direct or contributory patent infringement,
+ then any patent and copyright licenses granted to You under this
+ License for the Software shall terminate as of the date such
+ litigation is filed.
+ 8.3 Any licenses validly granted by You under the License prior
+ to termination shall continue and survive termination.
+
+
+ 9 Applicable Law, Arbitration and Compliance
+ 9.1 This License is governed by the laws of the ESA Member State where
+ the Licensor resides or has his registered office. "Member States"
+ are the members of the European Space Agency pursuant to Art. 1 of
+ the ESA Convention*. This licence shall be governed by German law if
+ a dispute arises with the ESA as a Licensor or if the Licensor has
+ no residence or registered office inside a Member State.
+ 9.2 Any dispute arising out of this License shall be finally settled in
+ accordance with the Rules of Arbitration of the International
+ Chamber of Commerce by one or more arbitrators designated in
+ conformity with those rules. Arbitration proceedings shall take
+ place in Cologne, Germany. The award shall be final and binding on
+ the parties, no appeal shall lie against it. The enforcement of the
+ award shall be governed by the rules of procedure in force in the
+ state/country in which it is to be executed.
+ 9.3 For the avoidance of doubt, You are solely responsible for
+ compliance with current applicable requirements of national laws.
+ The Software can be subject to export control laws. If You export
+ the Software it is your responsibility to comply with all export
+ control laws. This may include different requirements,
+ as e.g. registering the Software with the local authorities.
+ 9.4 If it is impossible for You to comply with any of the terms of this
+ License due to statute, judicial order or regulation You must:
+ (a) comply with the terms of this License to the maximum extent
+ possible; and
+ (b) describe the limitations and the Object Code and/or Source Code
+ they affect. Such description must be included in the LEGAL
+ notice described in Section 4. Except to the extent prohibited
+ by statute or regulation, such description must be sufficiently
+ detailed for an average recipient to be able to understand it.
+
+
+ 10 Miscellaneous
+ 10.1 Only ESA has the right to modify or publish new versions of this
+ License. ESA may assign this right to other individuals or
+ legal entities. Each version will be given a distinguishing
+ version number.
+ 10.2 This License represents the complete agreement concerning subject
+ matter hereof.
+ 10.3 If any provision of this License is held invalid or unenforceable,
+ the remaining provisions of this License shall not be affected.
+ The invalid or unenforceable provision shall be construed and/or
+ reformed to the extent necessary to make it enforceable and valid.
+
+
+______________________________
+
+*: As of August 2017 the Member States are Austria, Belgium, Czech Republic,
+ Denmark, Estonia, Finland, France, Germany, Greece, Hungary, Ireland, Italy,
+ Luxembourg, The Netherlands, Norway, Poland, Portugal, Romania, Spain,
+ Sweden, Switzerland and the United Kingdom.
\ No newline at end of file
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..2f7edb8
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,7 @@
+include README.md
+include LICENSE
+include requirements.txt
+recursive-include templateprocessor *.py
+recursive-exclude tests *
+recursive-exclude * __pycache__
+recursive-exclude * *.py[co]
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..640171b
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,30 @@
+BLACK=black
+PYTHON ?= python3
+
+.PHONY : \
+ check \
+ all \
+ install \
+ clean \
+ check-format \
+ format
+
+all: check-format check
+
+install:
+ pipx install .
+
+check:
+ $(MAKE) -C tests check
+
+check-format:
+ $(BLACK) --version
+ $(BLACK) --check templateprocessor
+ $(BLACK) --check tests
+
+format:
+ $(BLACK) templateprocessor
+ $(BLACK) tests
+
+clean:
+ rm -r -f build
\ No newline at end of file
diff --git a/README.md b/README.md
index 7c57fd5..37c4e42 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,29 @@
-# template-processor
-Template processing engine developed for TASTE Document Generator
+# Template Processor
+
+## General
+
+Template Processor (TP), created as a part of "Model-Based Execution Platform for Space Applications" project (contract 4000146882/24/NL/KK) financed by the European Space Agency.
+
+TP is a template processing engine developed for TASTE Document Generator. Its main function is to consume the provided inputs (e.g., TASTE Interface View data), instantiate templates and translate them into format that can be integrated deliverable documents. Base requirements are provided in MBEP-N7S-EP-SRS, while the overall design is documented in MBEP-N7S-EP-SDD.
+
+## Installation
+
+TODO
+
+## Configuration
+
+None
+
+## Running
+
+The assumed use case is for the Template Processor to be invoked by TASTE Document Generator. However, if TP is to be used manually, the following command line interface, as documented in the built-in help, is available:
+
+TODO
+
+## Frequently Asked Questions (FAQ)
+
+None
+
+## Troubleshooting
+
+None
diff --git a/data/simple.iv.png b/data/simple.iv.png
new file mode 100644
index 0000000..34396b6
Binary files /dev/null and b/data/simple.iv.png differ
diff --git a/data/simple.iv.xml b/data/simple.iv.xml
new file mode 100644
index 0000000..cc2f58c
--- /dev/null
+++ b/data/simple.iv.xml
@@ -0,0 +1,195 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..0c82c2b
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,6 @@
+# Core dependencies for Template Processor
+# Template processing engine for TASTE Document Generator
+pytest==7.4.2
+black==24.3.0
+mako==1.3.10
+
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..ec90987
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,55 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Setup script for Template Processor
+
+Template Processor (TP), created as a part of "Model-Based Execution Platform
+for Space Applications" project (contract 4000146882/24/NL/KK) financed by
+the European Space Agency.
+"""
+
+from setuptools import setup, find_packages
+from pathlib import Path
+
+# Read the contents of README file
+this_directory = Path(__file__).parent
+long_description = (this_directory / "README.md").read_text(encoding='utf-8')
+
+setup(
+ name='template-processor',
+ version='0.0.1',
+ description='Template processing engine for TASTE Document Generator',
+ long_description=long_description,
+ long_description_content_type='text/markdown',
+ author='N7 Space',
+ author_email='mkurowski@n7space.com',
+ url='https://github.com/n7space/template-processor',
+ license='European Space Agency Public License (ESA-PL) Permissive – v2.3',
+ packages=find_packages(exclude=['tests', 'tests.*']),
+ include_package_data=True,
+ python_requires='>=3.8',
+ install_requires=[
+ # Add project dependencies here
+ ],
+ extras_require={
+ 'dev': [
+ 'pytest>=7.0.0',
+ 'pytest-cov>=4.0.0',
+ 'flake8>=6.0.0',
+ 'black>=23.0.0',
+ 'mypy>=1.0.0',
+ ],
+ },
+ entry_points={
+ 'console_scripts': [
+ 'template-processor=templateprocessor.cli:main',
+ ],
+ },
+ classifiers=[
+ 'Programming Language :: Python',
+ 'License :: ESA-PL Permissive v2.3',
+ 'Operating System :: Linux'
+ ],
+ zip_safe=False,
+)
diff --git a/templateprocessor/__init__.py b/templateprocessor/__init__.py
new file mode 100644
index 0000000..27d27ac
--- /dev/null
+++ b/templateprocessor/__init__.py
@@ -0,0 +1,15 @@
+"""
+Template Processor
+
+Template processing engine for TASTE Document Generator.
+Created as a part of "Model-Based Execution Platform for Space Applications"
+project (contract 4000146882/24/NL/KK) financed by the European Space Agency.
+"""
+
+__version__ = "0.0.1"
+__author__ = "N7 Space"
+
+from templateprocessor.ivreader import IVReader
+from templateprocessor.iv import InterfaceView
+
+__all__ = ["IVReader", "InterfaceView"]
diff --git a/templateprocessor/cli.py b/templateprocessor/cli.py
new file mode 100644
index 0000000..6884b35
--- /dev/null
+++ b/templateprocessor/cli.py
@@ -0,0 +1,45 @@
+"""
+Command Line Interface for Template Processor
+"""
+
+import argparse
+import sys
+from templateprocessor import __version__
+
+
+def main():
+ """Main entry point for the Template Processor CLI."""
+ parser = argparse.ArgumentParser(
+ description="Template Processor - Template processing engine for TASTE Document Generator",
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ )
+
+ parser.add_argument(
+ "--version", action="version", version=f"template-processor {__version__}"
+ )
+
+ parser.add_argument(
+ "-i",
+ "--input",
+ help="Input data file (e.g., TASTE Interface View data)",
+ metavar="FILE",
+ )
+
+ parser.add_argument(
+ "-t", "--template", help="Template file to process", metavar="FILE"
+ )
+
+ parser.add_argument(
+ "-o", "--output", help="Output file for processed template", metavar="FILE"
+ )
+
+ args = parser.parse_args()
+
+ print("Template Processor - Not yet implemented")
+ print(f"Version: {__version__}")
+
+ return 0
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/templateprocessor/iv.py b/templateprocessor/iv.py
new file mode 100644
index 0000000..734e9a1
--- /dev/null
+++ b/templateprocessor/iv.py
@@ -0,0 +1,210 @@
+"""
+TASTE Interface View (IV) data model classes.
+
+This module provides Python classes that reflect the schema/structure of
+TASTE Interface View XML files, allowing for parsing, manipulation, and
+generation of IV data.
+"""
+
+from dataclasses import dataclass, field
+from typing import List, Optional
+from enum import Enum
+
+
+class InterfaceKind(str, Enum):
+ """Types of interfaces in TASTE."""
+
+ SPORADIC = "Sporadic"
+ CYCLIC = "Cyclic"
+ PROTECTED = "Protected"
+ UNPROTECTED = "Unprotected"
+
+
+class Encoding(str, Enum):
+ """Parameter encoding types."""
+
+ NATIVE = "NATIVE"
+ UPER = "UPER"
+ ACN = "ACN"
+
+
+class Language(str, Enum):
+ """Programming languages supported in TASTE."""
+
+ SDL = "SDL"
+ C = "C"
+ ADA = "Ada"
+ CPP = "C++"
+ SIMULINK = "Simulink"
+ QGenc = "QGenc"
+
+
+@dataclass
+class Property:
+ """Generic property/attribute with name-value pair."""
+
+ name: str
+ value: str
+
+
+@dataclass
+class InterfaceParameter:
+ """Parameter for an interface."""
+
+ name: str
+ type: str
+ encoding: Encoding = Encoding.NATIVE
+
+
+@dataclass
+class InputParameter(InterfaceParameter):
+ """Input parameter for an interface."""
+
+ pass
+
+
+@dataclass
+class OutputParameter(InterfaceParameter):
+ """Output parameter for an interface."""
+
+ pass
+
+
+@dataclass
+class FunctionInterface:
+ """Function interface."""
+
+ id: str
+ name: str
+ kind: InterfaceKind
+ enable_multicast: bool = True
+ layer: str = "default"
+ required_system_element: bool = False
+ is_simulink_interface: bool = False
+ simulink_full_interface_ref: str = ""
+ wcet: int = 0
+ stack_size: Optional[int] = None
+ queue_size: Optional[int] = None
+ miat: Optional[int] = None
+ period: Optional[int] = None
+ dispatch_offset: Optional[int] = None
+ priority: Optional[int] = None
+ input_parameters: List[InputParameter] = field(default_factory=list)
+ output_parameters: List[OutputParameter] = field(default_factory=list)
+ properties: List[Property] = field(default_factory=list)
+
+
+@dataclass
+class ProvidedInterface(FunctionInterface):
+ """Provided interface - implemented by a function"""
+
+ pass
+
+
+@dataclass
+class RequiredInterface(FunctionInterface):
+ """Required interface - required by a function"""
+
+ pass
+
+
+@dataclass
+class Implementation:
+ """Function implementation details."""
+
+ name: str
+ language: Optional[Language] = None
+
+
+@dataclass
+class Function:
+ """TASTE Function - a software component in the system."""
+
+ id: str
+ name: str
+ is_type: bool
+ language: Optional[Language] = None
+ default_implementation: str = "default"
+ fixed_system_element: bool = False
+ required_system_element: bool = False
+ instances_min: int = 1
+ instances_max: int = 1
+ startup_priority: int = 1
+ instance_of: Optional[str] = None # For instances of function types
+ type_language: Optional[Language] = None # For function types
+ provided_interfaces: List[ProvidedInterface] = field(default_factory=list)
+ required_interfaces: List[RequiredInterface] = field(default_factory=list)
+ implementations: List[Implementation] = field(default_factory=list)
+ properties: List[Property] = field(default_factory=list)
+ nested_functions: List["Function"] = field(default_factory=list)
+ nested_connections: List["Connection"] = field(default_factory=list)
+
+
+@dataclass
+class ConnectionEndpoint:
+ """Connection endpoint."""
+
+ iface_id: str
+ function_name: str
+ iface_name: str
+
+
+@dataclass
+class ConnectionSource(ConnectionEndpoint):
+ """Source endpoint of a connection."""
+
+ pass
+
+
+@dataclass
+class ConnectionTarget(ConnectionEndpoint):
+ """Target endpoint of a connection."""
+
+ pass
+
+
+@dataclass
+class Connection:
+ """Connection between two interfaces."""
+
+ id: str
+ required_system_element: bool = False
+ name: Optional[str] = None
+ source: Optional[ConnectionSource] = None
+ target: Optional[ConnectionTarget] = None
+
+
+@dataclass
+class Comment:
+ """Comment/annotation in the interface view."""
+
+ id: str
+ name: str
+ required_system_element: bool = False
+
+
+@dataclass
+class Layer:
+ """Visual layer for organizing the interface view."""
+
+ name: str
+ is_visible: bool = True
+
+
+@dataclass
+class InterfaceView:
+ """
+ Root element representing a TASTE Interface View.
+
+ This is the main data structure that contains all functions, connections,
+ and other elements that define a TASTE system's interface architecture.
+ """
+
+ version: str
+ asn1file: str
+ uiFile: str
+ modifierHash: str
+ functions: List[Function] = field(default_factory=list)
+ connections: List[Connection] = field(default_factory=list)
+ comments: List[Comment] = field(default_factory=list)
+ layers: List[Layer] = field(default_factory=list)
diff --git a/templateprocessor/ivreader.py b/templateprocessor/ivreader.py
new file mode 100644
index 0000000..493cda7
--- /dev/null
+++ b/templateprocessor/ivreader.py
@@ -0,0 +1,299 @@
+"""
+TASTE Interface View XML Reader.
+
+This module provides functionality to parse TASTE Interface View XML files
+and construct InterfaceView data model instances.
+"""
+
+import xml.etree.ElementTree as ET
+from pathlib import Path
+from typing import Union
+
+from templateprocessor.iv import (
+ InterfaceView,
+ Function,
+ FunctionInterface,
+ ProvidedInterface,
+ RequiredInterface,
+ InterfaceParameter,
+ InputParameter,
+ OutputParameter,
+ Implementation,
+ Connection,
+ ConnectionSource,
+ ConnectionTarget,
+ Comment,
+ Layer,
+ Property,
+ Language,
+ Encoding,
+ InterfaceKind,
+)
+
+
+class IVReader:
+ """
+ Reader for TASTE Interface View XML files.
+
+ Parses XML files conforming to the TASTE Interface View schema and
+ constructs corresponding InterfaceView objects.
+
+ Example:
+ reader = IVReader()
+ interface_view = reader.read("simple.iv.xml")
+ """
+
+ def read(self, file_path: Union[str, Path]) -> InterfaceView:
+ """
+ Read and parse a TASTE Interface View XML file.
+
+ Args:
+ file_path: Path to the IV XML file
+
+ Returns:
+ InterfaceView object populated with parsed data
+
+ Raises:
+ FileNotFoundError: If the file does not exist
+ xml.etree.ElementTree.ParseError: If XML is malformed
+ """
+ file_path = Path(file_path)
+ if not file_path.exists():
+ raise FileNotFoundError(f"Interface View file not found: {file_path}")
+
+ tree = ET.parse(file_path)
+ root = tree.getroot()
+
+ return self._parse_interface_view(root)
+
+ def read_string(self, xml_content: str) -> InterfaceView:
+ """
+ Read and parse TASTE Interface View XML from a string.
+
+ Args:
+ xml_content: XML content as string
+
+ Returns:
+ InterfaceView object populated with parsed data
+
+ Raises:
+ xml.etree.ElementTree.ParseError: If XML is malformed
+ """
+ root = ET.fromstring(xml_content)
+ return self._parse_interface_view(root)
+
+ def _parse_interface_view(self, root: ET.Element) -> InterfaceView:
+ """Parse the root InterfaceView element."""
+ iv = InterfaceView(
+ version=root.get("version", ""),
+ asn1file=root.get("asn1file", ""),
+ uiFile=root.get("UiFile", ""),
+ modifierHash=root.get("modifierHash", ""),
+ )
+
+ # Parse all Function elements
+ for func_elem in root.findall("Function"):
+ function = self._parse_function(func_elem)
+ iv.functions.append(function)
+
+ # Parse all Connection elements
+ for conn_elem in root.findall("Connection"):
+ connection = self._parse_connection(conn_elem)
+ iv.connections.append(connection)
+
+ # Parse all Comment elements
+ for comment_elem in root.findall("Comment"):
+ comment = self._parse_comment(comment_elem)
+ iv.comments.append(comment)
+
+ # Parse all Layer elements
+ for layer_elem in root.findall("Layer"):
+ layer = self._parse_layer(layer_elem)
+ iv.layers.append(layer)
+
+ return iv
+
+ def _parse_function(self, elem: ET.Element) -> Function:
+ """Parse a Function element."""
+ function = Function(
+ id=elem.get("id", ""),
+ name=elem.get("name", ""),
+ is_type=elem.get("is_type", "NO") == "YES",
+ language=(
+ Language(elem.get("language", "")) if elem.get("language") else None
+ ),
+ default_implementation=elem.get("default_implementation", "default"),
+ fixed_system_element=elem.get("fixed_system_element", "NO") == "YES",
+ required_system_element=elem.get("required_system_element", "NO") == "YES",
+ instances_min=int(elem.get("instances_min", "1")),
+ instances_max=int(elem.get("instances_max", "1")),
+ startup_priority=int(elem.get("startup_priority", "1")),
+ instance_of=elem.get("instance_of"),
+ type_language=(
+ Language(elem.get("type_language"))
+ if elem.get("type_language")
+ else None
+ ),
+ )
+
+ # Parse properties
+ for prop_elem in elem.findall("Property"):
+ prop = self._parse_property(prop_elem)
+ function.properties.append(prop)
+
+ # Parse provided interfaces
+ for pi_elem in elem.findall("Provided_Interface"):
+ pi = self._parse_provided_interface(pi_elem)
+ function.provided_interfaces.append(pi)
+
+ # Parse required interfaces
+ for ri_elem in elem.findall("Required_Interface"):
+ ri = self._parse_required_interface(ri_elem)
+ function.required_interfaces.append(ri)
+
+ # Parse implementations
+ implementations_elem = elem.find("Implementations")
+ if implementations_elem is not None:
+ for impl_elem in implementations_elem.findall("Implementation"):
+ impl = self._parse_implementation(impl_elem)
+ function.implementations.append(impl)
+
+ # Parse nested functions
+ for nested_elem in elem.findall("Function"):
+ nested_function = self._parse_function(nested_elem)
+ function.nested_functions.append(nested_function)
+
+ # Parse nested connections
+ for nested_conn_elem in elem.findall("Connection"):
+ nested_connection = self._parse_connection(nested_conn_elem)
+ function.nested_connections.append(nested_connection)
+
+ return function
+
+ def _parse_interface(self, elem: ET.Element) -> FunctionInterface:
+ """Parse an interface element."""
+ iface = FunctionInterface(
+ id=elem.get("id", ""),
+ name=elem.get("name", ""),
+ kind=InterfaceKind(elem.get("kind", "")),
+ enable_multicast=elem.get("enable_multicast", "true") == "true",
+ layer=elem.get("layer", "default"),
+ required_system_element=elem.get("required_system_element", "NO") == "YES",
+ is_simulink_interface=elem.get("is_simulink_interface", "false") == "true",
+ simulink_full_interface_ref=elem.get("simulink_full_interface_ref", ""),
+ wcet=int(elem.get("wcet", "0")),
+ stack_size=int(elem.get("stack_size")) if elem.get("stack_size") else None,
+ queue_size=int(elem.get("queue_size")) if elem.get("queue_size") else None,
+ miat=int(elem.get("miat")) if elem.get("miat") else None,
+ period=int(elem.get("period")) if elem.get("period") else None,
+ dispatch_offset=(
+ int(elem.get("dispatch_offset"))
+ if elem.get("dispatch_offset")
+ else None
+ ),
+ priority=int(elem.get("priority")) if elem.get("priority") else None,
+ )
+
+ # Parse input parameters
+ for param_elem in elem.findall("Input_Parameter"):
+ param = self._parse_input_parameter(param_elem)
+ iface.input_parameters.append(param)
+
+ # Parse output parameters
+ for param_elem in elem.findall("Output_Parameter"):
+ param = self._parse_output_parameter(param_elem)
+ iface.output_parameters.append(param)
+
+ # Parse properties
+ for prop_elem in elem.findall("Property"):
+ prop = self._parse_property(prop_elem)
+ iface.properties.append(prop)
+
+ return iface
+
+ def _parse_provided_interface(self, elem: ET.Element) -> ProvidedInterface:
+ """Parse a Provided_Interface element."""
+ pi = ProvidedInterface(**vars(self._parse_interface(elem)))
+ return pi
+
+ def _parse_required_interface(self, elem: ET.Element) -> RequiredInterface:
+ """Parse a Required_Interface element."""
+ ri = RequiredInterface(**vars(self._parse_interface(elem)))
+ return ri
+
+ def _parse_parameter(self, elem: ET.Element) -> InterfaceParameter:
+ """Parse an InterfaceParameter element."""
+ return InterfaceParameter(
+ name=elem.get("name", ""),
+ type=elem.get("type", ""),
+ encoding=Encoding(elem.get("encoding", "NATIVE")),
+ )
+
+ def _parse_input_parameter(self, elem: ET.Element) -> InputParameter:
+ """Parse an Input_Parameter element."""
+ return InputParameter(**vars(self._parse_parameter(elem)))
+
+ def _parse_output_parameter(self, elem: ET.Element) -> OutputParameter:
+ """Parse an Output_Parameter element."""
+ return OutputParameter(**vars(self._parse_parameter(elem)))
+
+ def _parse_implementation(self, elem: ET.Element) -> Implementation:
+ """Parse an Implementation element."""
+ return Implementation(
+ name=elem.get("name", ""),
+ language=Language(elem.get("language", "")),
+ )
+
+ def _parse_connection(self, elem: ET.Element) -> Connection:
+ """Parse a Connection element."""
+ connection = Connection(
+ id=elem.get("id", ""),
+ required_system_element=elem.get("required_system_element", "NO") == "YES",
+ name=elem.get("name"),
+ )
+
+ # Parse source
+ source_elem = elem.find("Source")
+ if source_elem is not None:
+ pi_name = source_elem.get("pi_name")
+ ri_name = source_elem.get("ri_name")
+ connection.source = ConnectionSource(
+ iface_id=source_elem.get("iface_id", ""),
+ function_name=source_elem.get("func_name", ""),
+ iface_name=pi_name if pi_name is not None else ri_name,
+ )
+
+ # Parse target
+ target_elem = elem.find("Target")
+ if target_elem is not None:
+ pi_name = target_elem.get("pi_name")
+ ri_name = target_elem.get("ri_name")
+ connection.target = ConnectionTarget(
+ iface_id=target_elem.get("iface_id", ""),
+ function_name=target_elem.get("func_name", ""),
+ iface_name=pi_name if pi_name is not None else ri_name,
+ )
+
+ return connection
+
+ def _parse_comment(self, elem: ET.Element) -> Comment:
+ """Parse a Comment element."""
+ return Comment(
+ id=elem.get("id", ""),
+ name=elem.get("name", ""),
+ required_system_element=elem.get("required_system_element", "NO") == "YES",
+ )
+
+ def _parse_layer(self, elem: ET.Element) -> Layer:
+ """Parse a Layer element."""
+ return Layer(
+ name=elem.get("name", ""),
+ is_visible=elem.get("is_visible", "true") == "true",
+ )
+
+ def _parse_property(self, elem: ET.Element) -> Property:
+ """Parse a Property element."""
+ return Property(
+ name=elem.get("name", ""),
+ value=elem.get("value", ""),
+ )
diff --git a/tests/Makefile b/tests/Makefile
new file mode 100644
index 0000000..8581d80
--- /dev/null
+++ b/tests/Makefile
@@ -0,0 +1,11 @@
+SRC_DIR = ../../
+PYTHON ?= python3
+
+TESTS = \
+ test_ivreader.py
+
+.PHONY: \
+ check
+
+check: ${TESTS}
+ PYTHONPATH=${SRC_DIR} ${PYTHON} -m pytest ${TESTS} -vv
\ No newline at end of file
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..6673ced
--- /dev/null
+++ b/tests/__init__.py
@@ -0,0 +1,3 @@
+"""
+Tests for Template Processor
+"""
diff --git a/tests/test_ivreader.py b/tests/test_ivreader.py
new file mode 100644
index 0000000..4377a16
--- /dev/null
+++ b/tests/test_ivreader.py
@@ -0,0 +1,251 @@
+"""
+Tests for IVReader class
+"""
+
+import pytest
+from pathlib import Path
+from templateprocessor.ivreader import IVReader
+from templateprocessor.iv import (
+ InterfaceView,
+ Function,
+ Language,
+ Encoding,
+ InterfaceKind,
+)
+
+
+class TestIVReader:
+ """Test cases for IVReader class."""
+
+ # Assuming the data directory is at the workspace root
+ @staticmethod
+ def get_test_data_file(filename: str) -> Path:
+ """Get the path to a test data file."""
+ return Path(__file__).parent.parent / "data" / filename
+
+ def test_read_simple_iv_xml(self):
+ """Test reading the simple.iv.xml file."""
+ # Prepare
+ reader = IVReader()
+ iv_file = self.get_test_data_file("simple.iv.xml")
+ assert iv_file.exists()
+
+ # Read
+ iv = reader.read(iv_file)
+
+ # Verify basic attributes
+ assert isinstance(iv, InterfaceView)
+ assert iv.version == "1.3"
+ assert iv.asn1file == "simple.acn"
+ assert iv.uiFile == "interfaceview.ui.xml"
+
+ # Verify functions were parsed
+ assert len(iv.functions) > 0
+
+ # Check specific function names
+ function_names = [f.name for f in iv.functions]
+ assert "host" in function_names
+ assert "master" in function_names
+ assert "slave" in function_names
+ assert "Worker" in function_names
+ assert "WorkerType" in function_names
+
+ def test_read_nested_functions(self):
+ """Test reading nested functions in the simple.iv.xml file."""
+ # Prepare
+ reader = IVReader()
+ iv_file = self.get_test_data_file("simple.iv.xml")
+ assert iv_file.exists()
+
+ # Read
+ iv = reader.read(iv_file)
+
+ # Verify basic attributes
+ assert isinstance(iv, InterfaceView)
+ assert iv.version == "1.3"
+ assert iv.asn1file == "simple.acn"
+ assert iv.uiFile == "interfaceview.ui.xml"
+
+ # Verify functions were parsed
+ assert len(iv.functions) > 0
+
+ # Check specific function names
+ host = next((f for f in iv.functions if f.name == "host"), None)
+ function_names = [f.name for f in host.nested_functions]
+ assert "child1" in function_names
+ assert "child2" in function_names
+
+ def test_read_functions_details(self):
+ """Test that function details are correctly parsed."""
+ # Prepare
+ reader = IVReader()
+ iv_file = self.get_test_data_file("simple.iv.xml")
+ assert iv_file.exists()
+
+ # Read
+ iv = reader.read(iv_file)
+
+ # Find the 'host' function
+ host = next((f for f in iv.functions if f.name == "host"), None)
+ assert host is not None
+ assert host.language == Language.SDL
+ assert host.is_type == False
+
+ # Check interfaces
+ assert len(host.provided_interfaces) > 0
+ assert len(host.required_interfaces) > 0
+
+ # Check specific provided interface
+ child1_pi = next(
+ (pi for pi in host.provided_interfaces if pi.name == "child1_if"), None
+ )
+ assert child1_pi is not None
+ assert child1_pi.kind == InterfaceKind.SPORADIC
+ assert len(child1_pi.input_parameters) == 1
+ assert child1_pi.input_parameters[0].name == "p1"
+ assert child1_pi.input_parameters[0].type == "T-Int32"
+ assert child1_pi.input_parameters[0].encoding == Encoding.NATIVE
+
+ def test_read_connections(self):
+ """Test that connections are correctly parsed."""
+ # Prepare
+ reader = IVReader()
+ iv_file = self.get_test_data_file("simple.iv.xml")
+ assert iv_file.exists()
+
+ # Read
+ iv = reader.read(iv_file)
+
+ # Verify connections were parsed
+ assert len(iv.connections) > 0
+
+ # Check a specific connection
+ connection = next(
+ (
+ c
+ for c in iv.connections
+ if c.id == "{0ed63357-7a9b-40f0-8392-27844d53ef6b}"
+ ),
+ None,
+ )
+ assert connection.source is not None
+ assert connection.target is not None
+ assert connection.source.function_name == "master"
+ assert connection.target.function_name == "host"
+
+ def test_read_worker_type_function(self):
+ """Test parsing function type and instance."""
+ # Prepare
+ reader = IVReader()
+ iv_file = self.get_test_data_file("simple.iv.xml")
+ assert iv_file.exists()
+
+ # Read
+ iv = reader.read(iv_file)
+
+ # Find WorkerType (function type)
+ worker_type = next((f for f in iv.functions if f.name == "WorkerType"), None)
+ assert worker_type is not None
+ assert worker_type.is_type == True
+ assert worker_type.type_language == Language.SDL
+
+ # Find Worker (instance of WorkerType)
+ worker = next((f for f in iv.functions if f.name == "Worker"), None)
+ assert worker is not None
+ assert worker.instance_of == "WorkerType"
+ assert worker.is_type == False
+
+ def test_read_interface_with_multiple_parameters(self):
+ """Test parsing interface with multiple input parameters."""
+ # Prepare
+ reader = IVReader()
+ iv_file = self.get_test_data_file("simple.iv.xml")
+ assert iv_file.exists()
+
+ # Read
+ iv = reader.read(iv_file)
+
+ # Find master function
+ master = next((f for f in iv.functions if f.name == "master"), None)
+ assert master is not None
+
+ # Find unprotected RI with multiple parameters
+ unprotected_ri = next(
+ (ri for ri in master.required_interfaces if ri.name == "unprotected"), None
+ )
+ assert unprotected_ri is not None
+ assert len(unprotected_ri.input_parameters) == 3
+
+ # Verify different encodings
+ assert unprotected_ri.input_parameters[0].name == "p1"
+ assert unprotected_ri.input_parameters[0].type == "T-Int32"
+ assert unprotected_ri.input_parameters[0].encoding == Encoding.NATIVE
+ assert unprotected_ri.input_parameters[1].name == "p2"
+ assert unprotected_ri.input_parameters[1].type == "T-Int32"
+ assert unprotected_ri.input_parameters[1].encoding == Encoding.UPER
+ assert unprotected_ri.input_parameters[2].name == "p3"
+ assert unprotected_ri.input_parameters[2].type == "T-Int32"
+ assert unprotected_ri.input_parameters[2].encoding == Encoding.ACN
+
+ def test_read_output_parameters(self):
+ """Test parsing interface with output parameters."""
+ # Prepare
+ reader = IVReader()
+ iv_file = self.get_test_data_file("simple.iv.xml")
+ assert iv_file.exists()
+
+ # Read
+ iv = reader.read(iv_file)
+
+ # Find slave function
+ slave = next((f for f in iv.functions if f.name == "slave"), None)
+ assert slave is not None
+
+ # Find protected_if PI with output parameter
+ protected_pi = next(
+ (pi for pi in slave.provided_interfaces if pi.name == "protected_if"), None
+ )
+ assert protected_pi is not None
+ assert len(protected_pi.input_parameters) == 1
+ assert len(protected_pi.output_parameters) == 1
+ assert protected_pi.output_parameters[0].name == "p2"
+ assert protected_pi.output_parameters[0].encoding == Encoding.UPER
+
+ def test_read_layers_and_comments(self):
+ """Test parsing layers and comments."""
+ # Prepare
+ reader = IVReader()
+ iv_file = self.get_test_data_file("simple.iv.xml")
+ assert iv_file.exists()
+
+ # Read
+ iv = reader.read(iv_file)
+
+ # Verify layers
+ assert len(iv.layers) > 0
+ default_layer = next((l for l in iv.layers if l.name == "default"), None)
+ assert default_layer is not None
+ assert default_layer.is_visible == True
+
+ # Verify comments
+ assert len(iv.comments) > 0
+
+ def test_read_string(self):
+ """Test reading from XML string."""
+ reader = IVReader()
+
+ xml_content = """
+
+
+
+
+"""
+
+ iv = reader.read_string(xml_content)
+
+ assert iv.version == "1.0"
+ assert iv.asn1file == "test.acn"
+ assert len(iv.functions) == 1
+ assert iv.functions[0].name == "test_func"
+ assert len(iv.layers) == 1