Add extracted tools: CitrineOS, OpenOCPP, ShapeShifter
- CitrineOS core extracted (CSMS OCPP 2.0.1) - OpenOCPP extracted (firmware OCPP 1.6J/2.0.1) - ShapeShifter library installed (pip install -e) - ShapeShifter specification extracted - EVerest extracted TODO updated with progress
This commit is contained in:
3
tools/EVerest-main/applications/utils/ev-dev-tools/.gitignore
vendored
Normal file
3
tools/EVerest-main/applications/utils/ev-dev-tools/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
build
|
||||
__pycache__
|
||||
*.egg-info
|
||||
@@ -0,0 +1,31 @@
|
||||
load("//applications/utils:requirements.bzl", "requirement")
|
||||
load("@rules_python//python:defs.bzl", "py_binary", "py_library")
|
||||
load("//third-party/bazel/toolchains:defs.bzl", "CROSS_PYTHON_INCOMPATIBLE")
|
||||
|
||||
exports_files(
|
||||
["BUILD.bazel"],
|
||||
)
|
||||
|
||||
py_library(
|
||||
name = "lib",
|
||||
srcs = glob(["src/ev_cli/*.py"]),
|
||||
imports = ["src"],
|
||||
target_compatible_with = CROSS_PYTHON_INCOMPATIBLE,
|
||||
deps = [
|
||||
requirement("jinja2"),
|
||||
requirement("jsonschema"),
|
||||
requirement("stringcase"),
|
||||
requirement("pyyaml"),
|
||||
],
|
||||
)
|
||||
|
||||
py_binary(
|
||||
name = "ev-cli",
|
||||
srcs = ["src/ev_cli/ev.py"],
|
||||
data = glob(["src/ev_cli/templates/**"]),
|
||||
legacy_create_init = False,
|
||||
main = "src/ev_cli/ev.py",
|
||||
visibility = ["//visibility:public"],
|
||||
target_compatible_with = CROSS_PYTHON_INCOMPATIBLE,
|
||||
deps = [":lib"],
|
||||
)
|
||||
@@ -0,0 +1,4 @@
|
||||
ev_create_python_wheel_targets(
|
||||
PACKAGE_NAME
|
||||
"ev-dev-tools"
|
||||
)
|
||||
201
tools/EVerest-main/applications/utils/ev-dev-tools/LICENSE
Normal file
201
tools/EVerest-main/applications/utils/ev-dev-tools/LICENSE
Normal file
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with 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, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
195
tools/EVerest-main/applications/utils/ev-dev-tools/README.rst
Normal file
195
tools/EVerest-main/applications/utils/ev-dev-tools/README.rst
Normal file
@@ -0,0 +1,195 @@
|
||||
=========================
|
||||
EVerest development tools
|
||||
=========================
|
||||
|
||||
This python project currently consists of the following packages
|
||||
|
||||
- `ev_cli`: EVerest module auto generation
|
||||
|
||||
Install
|
||||
-------
|
||||
To install `ev_cli`:
|
||||
|
||||
python3 -m pip install .
|
||||
|
||||
ev_cli
|
||||
------
|
||||
|
||||
The `ev_cli` package comes with a command line tool, named ``ev-cli``.
|
||||
It has the following subcommands
|
||||
|
||||
- module:
|
||||
auto generation and update of EVerest modules from its interface and
|
||||
manifest definitions
|
||||
|
||||
- interface:
|
||||
auto generation of c++ header files for defined interfaces
|
||||
|
||||
- helpers:
|
||||
utility commands
|
||||
|
||||
There exist short forms, for all subcommands and options. Simply call:
|
||||
|
||||
ev-cli --help
|
||||
|
||||
for getting the list of short forms.
|
||||
|
||||
Both the `module` and `interface` command have the following options in
|
||||
common:
|
||||
|
||||
- `--work-dir`:
|
||||
work directory which also contains the manifest definitions (default: ``.``)
|
||||
|
||||
- `--everest-dir`:
|
||||
root directory of EVerest core or any directory containing interface
|
||||
and module definitions (default: ``.``)
|
||||
|
||||
- `--schemas-dir`:
|
||||
schemas directory of the EVerest framework, containing the schema
|
||||
definitions (default: ``EVerest/lib/everest/framework/schemas``)
|
||||
|
||||
- `--clang-format-file`:
|
||||
if c++output should be formatted, set this to the path of the
|
||||
.clang-format file
|
||||
|
||||
Generating c++ header files for defined interfaces
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Assuming that the interface definitions in json format are located at
|
||||
``./interfaces/*.json``, simply:
|
||||
|
||||
ev-cli interfaces generate-headers
|
||||
|
||||
This will generate the c++ header files for all interfaces and output them
|
||||
to ``./generated/include/generated``. To generate only a single interface, call:
|
||||
|
||||
ev-cli interfaces generate-headers InterfaceName.json
|
||||
|
||||
For each interface an ``Implementation.hpp`` and ``Interface.hpp``
|
||||
header file will be generated. The former represents the `implementers`
|
||||
view, and the latter the `users` view of the interface, when used in a
|
||||
module.
|
||||
|
||||
Creating and updating auto generated files for modules (c++ only)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Assuming the modules are located at ``./modules`` and the initial
|
||||
skeleton for a module named `Example` with its manifest in
|
||||
``./modules/Example/manifest.json`` should be created, call:
|
||||
|
||||
ev-cli module create Example
|
||||
|
||||
This will create the following files inside the ``./modules/Example``
|
||||
subdirectory
|
||||
|
||||
- ``CMakeLists.txt``:
|
||||
build instruction file for CMake
|
||||
|
||||
- ``ld-ev.hpp``/``ld-ev.cpp``:
|
||||
glue code files for this module to get hooked up by the EVerest
|
||||
framework
|
||||
|
||||
- ``Example.hpp``/``Example.cpp``:
|
||||
header and source file for the module
|
||||
|
||||
Furthermore, for each interface provided by the module a subdirectory
|
||||
with the name of the `interface id` will be created. If, for example,
|
||||
the manifest looks like::
|
||||
|
||||
{
|
||||
"description": "Example module",
|
||||
"provides": {
|
||||
"main": {
|
||||
"description": "SampleInterface implementation",
|
||||
"interface": "SampleInterface",
|
||||
...
|
||||
}
|
||||
}
|
||||
...
|
||||
}
|
||||
|
||||
a subdirectory named ``main`` will be created, including two files
|
||||
``SampleInterfaceImpl.hpp`` and ``SampleInterfaceImpl.cpp``. The header
|
||||
file declares the implementation of `SampleInterface`, which derives
|
||||
from the auto generated interface header files from the previous
|
||||
subsection.
|
||||
|
||||
Now it is up to the user to implement logic in the module and interface
|
||||
implementation `cpp` source files.
|
||||
|
||||
If the modules' ``manifest.json`` or inferface definitions, used by the
|
||||
module, change, you can update the generated files by using:
|
||||
|
||||
ev-cli module update Example
|
||||
|
||||
**Note**:
|
||||
|
||||
1.
|
||||
``cpp`` source files will never be changed or overwritten by the
|
||||
`update` subcommand. The `create` subcommand only resets / overrides
|
||||
the files when using the ``--force`` option
|
||||
|
||||
2.
|
||||
``hpp`` header files and the ``CMakeLists.txt`` file will get
|
||||
updated, if its interface dependencies definitions change and the
|
||||
`update` subcommand is used. You can force an update by using the
|
||||
``--force`` option. During an update, the sections marked like::
|
||||
|
||||
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
|
||||
.....
|
||||
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
|
||||
|
||||
will be kept. If you want to completely reset / override these
|
||||
files, you need to recreate the using `create` subcommand with the
|
||||
``--force`` option.
|
||||
|
||||
3.
|
||||
Generated files will never be deleted. So make sure, you do this if
|
||||
you, for example, change the interface ids or remove interfaces from
|
||||
the module
|
||||
|
||||
These additional options might be useful for the `create` and `update`
|
||||
subcommands:
|
||||
|
||||
1. ``--force``:
|
||||
force creation or update
|
||||
|
||||
2. ``--diff``:
|
||||
don't touch anything, only show a `diff` of what would be changed
|
||||
|
||||
3. ``--only``:
|
||||
this option takes a comma separated list of files, that should be
|
||||
touched only. This is especially helpful, if you want to recreate
|
||||
only a single interface implementation ``cpp`` file, because you
|
||||
changed the corresponding interface a lot. To get a list of possible files, you can simply call:
|
||||
|
||||
ev-cli module create Example --only which
|
||||
|
||||
this would output for the above mentioned example::
|
||||
|
||||
Available files for category "core"
|
||||
cmakelists
|
||||
ld-ev.hpp
|
||||
ld-ev.cpp
|
||||
module.hpp
|
||||
module.cpp
|
||||
Available files for category "interfaces"
|
||||
main.hpp
|
||||
main.cpp
|
||||
|
||||
So calling:
|
||||
|
||||
ev-cli module create Example --only main.cpp,cmakelists --force
|
||||
|
||||
would recreate the ``CMakeLists.txt`` and the
|
||||
``main/SampleInterfaceImpl.cpp`` files, whereas:
|
||||
|
||||
ev-clie module update Example --only module.hpp
|
||||
|
||||
would update only the module header file ``Example.hpp``
|
||||
|
||||
|
||||
Auto generating NodeJS modules
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
**tbd**
|
||||
@@ -0,0 +1,9 @@
|
||||
[build-system]
|
||||
requires = [
|
||||
"setuptools>=42",
|
||||
"wheel"
|
||||
]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[tool.autopep8]
|
||||
max_line_length = 120
|
||||
34
tools/EVerest-main/applications/utils/ev-dev-tools/setup.cfg
Normal file
34
tools/EVerest-main/applications/utils/ev-dev-tools/setup.cfg
Normal file
@@ -0,0 +1,34 @@
|
||||
[metadata]
|
||||
name = ev-dev-tools
|
||||
version = attr: ev_cli.__version__
|
||||
author = aw
|
||||
author_email = aw@pionix.de
|
||||
description = Utilities for developing with the everest framework
|
||||
long_description = file: README.rst
|
||||
long_description_content_type = text/x-rst
|
||||
url = https://github.com/EVerest/everest-utils
|
||||
classifiers =
|
||||
Programming Language :: Python :: 3
|
||||
License :: OSI Approved :: Apache Software License
|
||||
Operating System :: OS Independent
|
||||
|
||||
[options]
|
||||
install_requires =
|
||||
jinja2 ~=3.1
|
||||
jsonschema ~=4.26
|
||||
stringcase ~=1.2
|
||||
pyyaml ~=6.0
|
||||
package_dir =
|
||||
= src
|
||||
packages = ev_cli
|
||||
python_requires = >=3.7
|
||||
|
||||
[options.entry_points]
|
||||
console_scripts =
|
||||
ev-cli = ev_cli.ev:main
|
||||
|
||||
[options.package_data]
|
||||
ev_cli =
|
||||
templates/*.j2
|
||||
licenses/**
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
from setuptools import setup
|
||||
|
||||
setup()
|
||||
@@ -0,0 +1,2 @@
|
||||
"""EVerest command line utility."""
|
||||
__version__ = '0.7.3'
|
||||
@@ -0,0 +1,84 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# Copyright Pionix GmbH and Contributors to EVerest
|
||||
#
|
||||
"""
|
||||
Provide error parsing functionality.
|
||||
author: andreas.heinrich@pionix.de
|
||||
"""
|
||||
|
||||
from typing import List, NamedTuple
|
||||
from pathlib import Path
|
||||
from . import helpers
|
||||
import yaml, jsonschema
|
||||
|
||||
class ErrorDefinition(NamedTuple):
|
||||
"""Error definition class."""
|
||||
namespace: str
|
||||
name: str
|
||||
description: str
|
||||
|
||||
class ErrorParser:
|
||||
"""Error parser class."""
|
||||
validators = None
|
||||
error_definitions = {}
|
||||
|
||||
@classmethod
|
||||
def load_error_definition_file(cls, path: Path):
|
||||
try:
|
||||
error_def = yaml.safe_load(path.read_text())
|
||||
ErrorParser.validators['error_declaration_list'].validate(error_def)
|
||||
except OSError as err:
|
||||
raise Exception(f'Could not open error definition file {err.filename}: {err.strerror}') from err
|
||||
except yaml.YAMLError as err:
|
||||
raise Exception(f'Could not parse error definition file {path}: {err}') from err
|
||||
except jsonschema.ValidationError as err:
|
||||
raise Exception(f'Error definition file {path} is not valid: {err}') from err
|
||||
|
||||
namespace = path.stem
|
||||
if namespace in cls.error_definitions:
|
||||
raise Exception(f'Error definition namespace { namespace } already exists.')
|
||||
else:
|
||||
cls.error_definitions[namespace] = {}
|
||||
for entry in error_def['errors']:
|
||||
error = ErrorDefinition(namespace, entry['name'], entry['description'])
|
||||
if error.name in cls.error_definitions[error.namespace]:
|
||||
raise Exception(f'Error definition { error.namespace }/{ error.name } already exists.')
|
||||
cls.error_definitions[error.namespace][error.name] = error
|
||||
|
||||
@classmethod
|
||||
def get_error_definition(cls, namespace: str, name: str) -> ErrorDefinition:
|
||||
"""Get error definition for the provided namespace and name."""
|
||||
if namespace not in cls.error_definitions:
|
||||
path = helpers.resolve_everest_dir_path(f'errors/{ namespace }.yaml')
|
||||
cls.load_error_definition_file(path)
|
||||
if name not in cls.error_definitions[namespace]:
|
||||
raise Exception(f'Error definition { namespace }/{ name } does not exist.')
|
||||
return cls.error_definitions[namespace][name]
|
||||
|
||||
@classmethod
|
||||
def get_error_definitions(cls, namespace: str) -> List[ErrorDefinition]:
|
||||
"""Get error definitions for the provided namespace."""
|
||||
if namespace not in cls.error_definitions:
|
||||
path = helpers.resolve_everest_dir_path(f'errors/{ namespace }.yaml')
|
||||
cls.load_error_definition_file(path)
|
||||
result = []
|
||||
for error in cls.error_definitions[namespace].values():
|
||||
result.append(error)
|
||||
return result
|
||||
|
||||
@classmethod
|
||||
def resolve_error_reference(cls, error_ref: str) -> List[ErrorDefinition]:
|
||||
"""Resolve error reference."""
|
||||
ref_prefix = '/errors/'
|
||||
if not error_ref.startswith(ref_prefix):
|
||||
raise Exception(f'Error reference { error_ref } does not start with { ref_prefix }.')
|
||||
error_ref = error_ref[len(ref_prefix):]
|
||||
if '#/' in error_ref:
|
||||
namespace, name = error_ref.split('#/')
|
||||
result = []
|
||||
result.append(cls.get_error_definition(namespace, name))
|
||||
return result
|
||||
else:
|
||||
return cls.get_error_definitions(error_ref)
|
||||
902
tools/EVerest-main/applications/utils/ev-dev-tools/src/ev_cli/ev.py
Executable file
902
tools/EVerest-main/applications/utils/ev-dev-tools/src/ev_cli/ev.py
Executable file
@@ -0,0 +1,902 @@
|
||||
#!/usr/bin/env -S python3 -tt
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# Copyright Pionix GmbH and Contributors to EVerest
|
||||
#
|
||||
"""
|
||||
author: aw@pionix.de
|
||||
FIXME (aw): Module documentation.
|
||||
"""
|
||||
|
||||
from ev_cli import __version__
|
||||
from ev_cli import helpers
|
||||
from ev_cli.type_parsing import TypeParser
|
||||
from ev_cli.error_parsing import ErrorParser
|
||||
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
import jinja2 as j2
|
||||
import argparse
|
||||
import stringcase
|
||||
from typing import List
|
||||
|
||||
|
||||
# FIXME (aw): remove these global variables
|
||||
|
||||
# global variables
|
||||
everest_dirs: List[Path] = []
|
||||
work_dir: Path = None
|
||||
|
||||
# jinja template environment and global variable
|
||||
env = j2.Environment(loader=j2.FileSystemLoader(Path(__file__).parent / 'templates'),
|
||||
lstrip_blocks=True, trim_blocks=True, undefined=j2.StrictUndefined,
|
||||
keep_trailing_newline=True)
|
||||
|
||||
templates = {}
|
||||
validators = {}
|
||||
|
||||
# Function declarations
|
||||
|
||||
|
||||
def setup_jinja_env():
|
||||
env.globals['timestamp'] = datetime.utcnow()
|
||||
# FIXME (aw): which repo to use? everest or everest-framework?
|
||||
env.filters['snake_case'] = helpers.snake_case
|
||||
env.filters['create_dummy_result'] = helpers.create_dummy_result
|
||||
|
||||
templates.update({
|
||||
'interface_base': env.get_template('interface-Base.hpp.j2'),
|
||||
'interface_exports': env.get_template('interface-Exports.hpp.j2'),
|
||||
'interface_impl.hpp': env.get_template('interface-Impl.hpp.j2'),
|
||||
'interface_impl.cpp': env.get_template('interface-Impl.cpp.j2'),
|
||||
'types.hpp': env.get_template('types.hpp.j2'),
|
||||
'module.hpp': env.get_template('module.hpp.j2'),
|
||||
'module.cpp': env.get_template('module.cpp.j2'),
|
||||
'ld-ev.hpp': env.get_template('ld-ev.hpp.j2'),
|
||||
'ld-ev.cpp': env.get_template('ld-ev.cpp.j2'),
|
||||
'cmakelists': env.get_template('CMakeLists.txt.j2'),
|
||||
'index.rst': env.get_template('index.rst.j2'),
|
||||
})
|
||||
|
||||
|
||||
def generate_tmpl_data_for_if(interface, if_def, type_file):
|
||||
helpers.parsed_enums.clear()
|
||||
helpers.parsed_types.clear()
|
||||
helpers.type_headers.clear()
|
||||
types = []
|
||||
enums = []
|
||||
vars = []
|
||||
for var, var_info in if_def.get('vars', {}).items():
|
||||
(type_info, enum_info) = helpers.extended_build_type_info(var, var_info, type_file)
|
||||
if enum_info and type_file:
|
||||
enums.append(enum_info)
|
||||
|
||||
vars.append(type_info)
|
||||
|
||||
cmds = []
|
||||
for cmd, cmd_info in if_def.get('cmds', {}).items():
|
||||
args = []
|
||||
for arg, arg_info in cmd_info.get('arguments', {}).items():
|
||||
(type_info, enum_info) = helpers.extended_build_type_info(arg, arg_info, type_file)
|
||||
if enum_info and type_file:
|
||||
enums.append(enum_info)
|
||||
|
||||
args.append(type_info)
|
||||
|
||||
result_type_info = None
|
||||
if 'result' in cmd_info:
|
||||
result_info = cmd_info['result']
|
||||
|
||||
(result_type_info, enum_info) = helpers.extended_build_type_info('result', result_info, type_file)
|
||||
if enum_info and type_file:
|
||||
enums.append(enum_info)
|
||||
|
||||
cmds.append({'name': cmd, 'args': args, 'result': result_type_info})
|
||||
|
||||
if type_file:
|
||||
for parsed_enum in helpers.parsed_enums:
|
||||
enum_info = {
|
||||
'name': parsed_enum['name'],
|
||||
'description': parsed_enum['description'],
|
||||
'enum_type': stringcase.capitalcase(parsed_enum['name']),
|
||||
'enum': parsed_enum['enums']
|
||||
}
|
||||
enums.append(enum_info)
|
||||
|
||||
if type_file:
|
||||
for parsed_type in helpers.parsed_types:
|
||||
parsed_type['name'] = stringcase.capitalcase(parsed_type['name'])
|
||||
if 'properties' in parsed_type:
|
||||
for prop in parsed_type['properties']:
|
||||
if 'type_dict' in prop['info']:
|
||||
path = Path('generated/types') / \
|
||||
prop['info']['type_dict']['type_relative_path'].with_suffix('.hpp')
|
||||
helpers.type_headers.add(path.as_posix())
|
||||
|
||||
types.append(parsed_type)
|
||||
|
||||
error_lists = if_def.get('errors', [])
|
||||
# Use a dict to avoid duplicate error definitions
|
||||
errors_dict = {}
|
||||
for entry in error_lists:
|
||||
if not 'reference' in entry:
|
||||
raise Exception(f'Error definition {entry} does not have a reference.')
|
||||
for error in ErrorParser.resolve_error_reference(entry['reference']):
|
||||
if error.namespace not in errors_dict:
|
||||
errors_dict[error.namespace] = {}
|
||||
if error.name in errors_dict[error.namespace]:
|
||||
raise Exception(f'Error definition {error.namespace}/{error.name} already referenced.')
|
||||
errors_dict[error.namespace][error.name] = error
|
||||
errors = []
|
||||
for value in errors_dict.values():
|
||||
errors.extend(value.values())
|
||||
|
||||
tmpl_data = {
|
||||
'info': {
|
||||
'base_class_header': f'generated/interfaces/{interface}/Implementation.hpp',
|
||||
'interface': interface,
|
||||
'desc': if_def['description'],
|
||||
'type_headers': sorted(helpers.type_headers)
|
||||
},
|
||||
'enums': enums,
|
||||
'types': types,
|
||||
'vars': vars,
|
||||
'cmds': cmds,
|
||||
'errors': errors,
|
||||
}
|
||||
|
||||
return tmpl_data
|
||||
|
||||
|
||||
def generate_tmpl_data_for_module(module, module_def):
|
||||
provides = []
|
||||
for impl, impl_info in module_def.get('provides', {}).items():
|
||||
config = []
|
||||
for conf_id, conf_info in impl_info.get('config', {}).items():
|
||||
type_info = helpers.build_type_info(conf_id, conf_info['type'])
|
||||
config.append(type_info)
|
||||
|
||||
provides.append({
|
||||
'id': impl,
|
||||
'type': impl_info['interface'],
|
||||
'desc': impl_info['description'],
|
||||
'config': config,
|
||||
'class_name': f'{impl_info["interface"]}Impl',
|
||||
'base_class': f'{impl_info["interface"]}ImplBase',
|
||||
'base_class_header': f'generated/interfaces/{impl_info["interface"]}/Implementation.hpp'
|
||||
})
|
||||
|
||||
requires = []
|
||||
for requirement_id, req_info in module_def.get('requires', {}).items():
|
||||
# min_connections=1 and max_connections=1 is the default if not provided otherwise (see manifest meta schema)
|
||||
is_vector = not (
|
||||
('min_connections' not in req_info or req_info['min_connections'] == 1) and
|
||||
('max_connections' not in req_info or req_info['max_connections'] == 1))
|
||||
requires.append({
|
||||
'id': requirement_id,
|
||||
'is_vector': is_vector,
|
||||
'type': req_info['interface'],
|
||||
'class_name': f'{req_info["interface"]}Intf',
|
||||
'exports_header': f'generated/interfaces/{req_info["interface"]}/Interface.hpp'
|
||||
})
|
||||
|
||||
module_config = []
|
||||
for conf_id, conf_info in module_def.get('config', {}).items():
|
||||
type_info = helpers.build_type_info(conf_id, conf_info['type'])
|
||||
module_config.append(type_info)
|
||||
|
||||
tmpl_data = {
|
||||
'info': {
|
||||
'name': module,
|
||||
'class_name': module, # FIXME (aw): enforce capital case?
|
||||
'desc': module_def['description'],
|
||||
'module_header': f'{module}.hpp',
|
||||
'module_config': module_config,
|
||||
'ld_ev_header': 'ld-ev.hpp',
|
||||
'enable_external_mqtt': module_def.get('enable_external_mqtt', False),
|
||||
'enable_telemetry': module_def.get('enable_telemetry', False),
|
||||
'enable_global_errors': module_def.get('enable_global_errors', False)
|
||||
},
|
||||
'provides': provides,
|
||||
'requires': requires,
|
||||
}
|
||||
|
||||
return tmpl_data
|
||||
|
||||
|
||||
def construct_impl_file_paths(impl):
|
||||
interface = impl['type']
|
||||
common_part = f'{impl["id"]}/{interface}'
|
||||
return (f'{common_part}Impl.hpp', f'{common_part}Impl.cpp')
|
||||
|
||||
|
||||
def set_impl_specific_path_vars(tmpl_data, output_path):
|
||||
"""Set cpp_file_rel_path and class_header vars to implementation template data."""
|
||||
for impl in tmpl_data['provides']:
|
||||
(impl['class_header'], impl['cpp_file_rel_path']) = construct_impl_file_paths(impl)
|
||||
|
||||
|
||||
def generate_module_loader_files(rel_mod_dir, output_dir):
|
||||
loader_files = []
|
||||
(_, _, mod) = rel_mod_dir.rpartition('/')
|
||||
|
||||
mod_path = work_dir / f'modules/{rel_mod_dir}/manifest.yaml'
|
||||
if not mod_path.exists():
|
||||
raise Exception(f'Could not find module manifest ({mod_path}')
|
||||
|
||||
mod_def = helpers.load_validated_module_def(mod_path, validators['module'])
|
||||
tmpl_data = generate_tmpl_data_for_module(mod, mod_def)
|
||||
|
||||
set_impl_specific_path_vars(tmpl_data, mod_path.parent)
|
||||
|
||||
# ld-ev.hpp
|
||||
tmpl_data['info']['hpp_guard'] = 'LD_EV_HPP'
|
||||
|
||||
loader_files.append({
|
||||
'filename': 'ld-ev.hpp',
|
||||
'path': output_dir / mod / 'ld-ev.hpp',
|
||||
'printable_name': f'{mod}/ld-ev.hpp',
|
||||
'content': templates['ld-ev.hpp'].render(tmpl_data),
|
||||
'template_path': Path(templates['ld-ev.hpp'].filename),
|
||||
'last_mtime': mod_path.stat().st_mtime
|
||||
})
|
||||
|
||||
# ld-ev.cpp
|
||||
loader_files.append({
|
||||
'filename': 'ld-ev.cpp',
|
||||
'path': output_dir / mod / 'ld-ev.cpp',
|
||||
'printable_name': f'{mod}/ld-ev.cpp',
|
||||
'content': templates['ld-ev.cpp'].render(tmpl_data),
|
||||
'template_path': Path(templates['ld-ev.cpp'].filename),
|
||||
'last_mtime': mod_path.stat().st_mtime
|
||||
})
|
||||
|
||||
return loader_files
|
||||
|
||||
|
||||
def generate_module_files(rel_mod_dir, update_flag, licenses):
|
||||
(_, _, mod) = rel_mod_dir.rpartition('/')
|
||||
|
||||
mod_files = {'core': [], 'interfaces': [], 'docs': []}
|
||||
mod_path = work_dir / f'modules/{rel_mod_dir}/manifest.yaml'
|
||||
mod_def = helpers.load_validated_module_def(mod_path, validators['module'])
|
||||
|
||||
default_license_dir = Path(__file__).parent / 'licenses'
|
||||
current_license_dir = work_dir / 'licenses'
|
||||
additional_license_dir = Path(licenses)
|
||||
license_dirs = [default_license_dir, current_license_dir, additional_license_dir]
|
||||
license_url = mod_def['metadata']['license']
|
||||
license_header = helpers.get_license_header(license_dirs, license_url)
|
||||
|
||||
if not license_header:
|
||||
print(f'Could not find license "{license_url}" in {license_dirs}.')
|
||||
print('Consider providing a additonal custom license directory with --licenses')
|
||||
exit(1)
|
||||
|
||||
tmpl_data = generate_tmpl_data_for_module(mod, mod_def)
|
||||
output_path = mod_path.parent
|
||||
# FIXME (aw): we might move the following function into generate_tmp_data_for_module
|
||||
set_impl_specific_path_vars(tmpl_data, output_path)
|
||||
|
||||
cmakelists_blocks = {
|
||||
'version': 'v1',
|
||||
'format_str': '# ev@{uuid}:{version}',
|
||||
'regex_str': '^(?P<indent>\s*)# ev@(?P<uuid>[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}):(?P<version>.*)$',
|
||||
'definitions': {
|
||||
'add_general': {
|
||||
'id': 'bcc62523-e22b-41d7-ba2f-825b493a3c97',
|
||||
'content': '# insert your custom targets and additional config variables here'
|
||||
},
|
||||
'add_other': {
|
||||
'id': 'c55432ab-152c-45a9-9d2e-7281d50c69c3',
|
||||
'content': '# insert other things like install cmds etc here'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl_hpp_blocks = {
|
||||
'version': 'v1',
|
||||
'format_str': '// ev@{uuid}:{version}',
|
||||
'regex_str': '^(?P<indent>\s*)// ev@(?P<uuid>[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}):(?P<version>.*)$',
|
||||
'definitions': {
|
||||
'add_headers': {
|
||||
'id': '75ac1216-19eb-4182-a85c-820f1fc2c091',
|
||||
'content': '// insert your custom include headers here'
|
||||
},
|
||||
'public_defs': {
|
||||
'id': '8ea32d28-373f-4c90-ae5e-b4fcc74e2a61',
|
||||
'content': '// insert your public definitions here'
|
||||
},
|
||||
'protected_defs': {
|
||||
'id': 'd2d1847a-7b88-41dd-ad07-92785f06f5c4',
|
||||
'content': '// insert your protected definitions here'
|
||||
},
|
||||
'private_defs': {
|
||||
'id': '3370e4dd-95f4-47a9-aaec-ea76f34a66c9',
|
||||
'content': '// insert your private definitions here'
|
||||
},
|
||||
'after_class': {
|
||||
'id': '3d7da0ad-02c2-493d-9920-0bbbd56b9876',
|
||||
'content': '// insert other definitions here'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod_hpp_blocks = {
|
||||
'version': 'v1',
|
||||
'format_str': '// ev@{uuid}:{version}',
|
||||
'regex_str': '^(?P<indent>\s*)// ev@(?P<uuid>[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}):(?P<version>.*)$',
|
||||
'definitions': {
|
||||
'add_headers': {
|
||||
'id': '4bf81b14-a215-475c-a1d3-0a484ae48918',
|
||||
'content': '// insert your custom include headers here'
|
||||
},
|
||||
'public_defs': {
|
||||
'id': '1fce4c5e-0ab8-41bb-90f7-14277703d2ac',
|
||||
'content': '// insert your public definitions here'
|
||||
},
|
||||
'protected_defs': {
|
||||
'id': '4714b2ab-a24f-4b95-ab81-36439e1478de',
|
||||
'content': '// insert your protected definitions here'
|
||||
},
|
||||
'private_defs': {
|
||||
'id': '211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8',
|
||||
'content': '// insert your private definitions here'
|
||||
},
|
||||
'after_class': {
|
||||
'id': '087e516b-124c-48df-94fb-109508c7cda9',
|
||||
'content': '// insert other definitions here'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# provided interface implementations (impl cpp & hpp)
|
||||
for impl in tmpl_data['provides']:
|
||||
interface = impl['type']
|
||||
(impl_hpp_file, impl_cpp_file) = construct_impl_file_paths(impl)
|
||||
|
||||
# load template data for interface
|
||||
if_def, last_mtime = load_interface_definition(interface)
|
||||
|
||||
if_tmpl_data = generate_tmpl_data_for_if(interface, if_def, False)
|
||||
|
||||
if_tmpl_data['info'].update({
|
||||
'hpp_guard': helpers.snake_case(f'{impl["id"]}_{interface}').upper() + '_IMPL_HPP',
|
||||
'config': impl['config'],
|
||||
'class_name': interface + 'Impl',
|
||||
'class_parent': interface + 'ImplBase',
|
||||
'module_header': f'../{mod}.hpp',
|
||||
'module_class': mod,
|
||||
'interface_implementation_id': impl['id']
|
||||
})
|
||||
|
||||
if_tmpl_data['info']['blocks'] = helpers.load_tmpl_blocks(
|
||||
impl_hpp_blocks, output_path / impl_hpp_file, update_flag)
|
||||
if_tmpl_data['info']['license_header'] = license_header
|
||||
|
||||
# FIXME (aw): time stamp should include parent interfaces modification dates
|
||||
mod_files['interfaces'].append({
|
||||
'abbr': f'{impl["id"]}.hpp',
|
||||
'path': output_path / impl_hpp_file,
|
||||
'printable_name': impl_hpp_file,
|
||||
'content': templates['interface_impl.hpp'].render(if_tmpl_data),
|
||||
'template_path': Path(templates['interface_impl.hpp'].filename),
|
||||
'last_mtime': last_mtime,
|
||||
'license_header': license_header
|
||||
})
|
||||
|
||||
mod_files['interfaces'].append({
|
||||
'abbr': f'{impl["id"]}.cpp',
|
||||
'path': output_path / impl_cpp_file,
|
||||
'printable_name': impl_cpp_file,
|
||||
'content': templates['interface_impl.cpp'].render(if_tmpl_data),
|
||||
'template_path': Path(templates['interface_impl.cpp'].filename),
|
||||
'last_mtime': last_mtime,
|
||||
'license_header': license_header
|
||||
})
|
||||
|
||||
cmakelists_file = output_path / 'CMakeLists.txt'
|
||||
tmpl_data['info']['blocks'] = helpers.load_tmpl_blocks(cmakelists_blocks, cmakelists_file, update_flag)
|
||||
tmpl_data['info']['license_header'] = license_header
|
||||
mod_files['core'].append({
|
||||
'abbr': 'cmakelists',
|
||||
'path': cmakelists_file,
|
||||
'content': templates['cmakelists'].render(tmpl_data),
|
||||
'template_path': Path(templates['cmakelists'].filename),
|
||||
'last_mtime': mod_path.stat().st_mtime
|
||||
})
|
||||
|
||||
# module.hpp
|
||||
tmpl_data['info']['hpp_guard'] = helpers.snake_case(mod).upper() + '_HPP'
|
||||
mod_hpp_file = output_path / f'{mod}.hpp'
|
||||
tmpl_data['info']['blocks'] = helpers.load_tmpl_blocks(mod_hpp_blocks, mod_hpp_file, update_flag)
|
||||
mod_files['core'].append({
|
||||
'abbr': 'module.hpp',
|
||||
'path': mod_hpp_file,
|
||||
'content': templates['module.hpp'].render(tmpl_data),
|
||||
'template_path': Path(templates['module.hpp'].filename),
|
||||
'last_mtime': mod_path.stat().st_mtime,
|
||||
'license_header': license_header
|
||||
})
|
||||
|
||||
# module.cpp
|
||||
mod_cpp_file = output_path / f'{mod}.cpp'
|
||||
mod_files['core'].append({
|
||||
'abbr': 'module.cpp',
|
||||
'path': mod_cpp_file,
|
||||
'content': templates['module.cpp'].render(tmpl_data),
|
||||
'template_path': Path(templates['module.cpp'].filename),
|
||||
'last_mtime': mod_path.stat().st_mtime,
|
||||
'license_header': license_header
|
||||
})
|
||||
|
||||
# docs/index.rst
|
||||
mod_files['docs'].append({
|
||||
'abbr': 'index.rst',
|
||||
'path': output_path / 'docs' / 'index.rst',
|
||||
'content': templates['index.rst'].render(tmpl_data),
|
||||
'template_path': Path(templates['index.rst'].filename),
|
||||
'last_mtime': mod_path.stat().st_mtime
|
||||
})
|
||||
|
||||
for file_info in [*mod_files['core'], *mod_files['interfaces'], *mod_files['docs']]:
|
||||
file_info['printable_name'] = file_info['path'].relative_to(output_path)
|
||||
|
||||
return mod_files
|
||||
|
||||
|
||||
def load_interface_definition(interface):
|
||||
if_path = helpers.resolve_everest_dir_path(f'interfaces/{interface}.yaml')
|
||||
|
||||
if_def = helpers.load_validated_interface_def(if_path, validators['interface'])
|
||||
|
||||
if 'vars' not in if_def:
|
||||
if_def['vars'] = {}
|
||||
if 'cmds' not in if_def:
|
||||
if_def['cmds'] = {}
|
||||
|
||||
last_mtime = if_path.stat().st_mtime
|
||||
|
||||
return if_def, last_mtime
|
||||
|
||||
|
||||
def generate_interface_headers(interface, all_interfaces_flag, output_dir):
|
||||
if_parts = {'base': None, 'exports': None, 'types': None}
|
||||
|
||||
try:
|
||||
if_def, last_mtime = load_interface_definition(interface)
|
||||
except Exception as e:
|
||||
if not all_interfaces_flag:
|
||||
raise
|
||||
else:
|
||||
# FIXME (aw): should we really silently ignore that?
|
||||
print(f'Ignoring interface {interface} with reason: {e}')
|
||||
return
|
||||
|
||||
tmpl_data = generate_tmpl_data_for_if(interface, if_def, False)
|
||||
|
||||
output_path = output_dir / interface
|
||||
output_path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
tmpl_data['info']['interface_name'] = f'{interface}'
|
||||
tmpl_data['info']['namespace'] = [f'{interface}']
|
||||
|
||||
# generate Base file (providers view)
|
||||
tmpl_data['info']['hpp_guard'] = helpers.snake_case(interface).upper() + '_IMPLEMENTATION_HPP'
|
||||
tmpl_data['info']['class_name'] = f'{interface}ImplBase'
|
||||
|
||||
base_file = output_path / 'Implementation.hpp'
|
||||
|
||||
if_parts['base'] = {
|
||||
'path': base_file,
|
||||
'content': templates['interface_base'].render(tmpl_data),
|
||||
'template_path': Path(templates['interface_base'].filename),
|
||||
'last_mtime': last_mtime,
|
||||
'printable_name': base_file.relative_to(output_path.parent)
|
||||
}
|
||||
|
||||
# generate Exports file (users view)
|
||||
tmpl_data['info']['hpp_guard'] = helpers.snake_case(interface).upper() + '_INTERFACE_HPP'
|
||||
tmpl_data['info']['class_name'] = f'{interface}Intf'
|
||||
|
||||
exports_file = output_path / 'Interface.hpp'
|
||||
|
||||
if_parts['exports'] = {
|
||||
'path': exports_file,
|
||||
'content': templates['interface_exports'].render(tmpl_data),
|
||||
'template_path': Path(templates['interface_exports'].filename),
|
||||
'last_mtime': last_mtime,
|
||||
'printable_name': exports_file.relative_to(output_path.parent)
|
||||
}
|
||||
|
||||
# generate Types file
|
||||
tmpl_data['info']['hpp_guard'] = helpers.snake_case(interface).upper() + '_TYPES_HPP'
|
||||
|
||||
types_file = output_path / 'Types.hpp'
|
||||
|
||||
if_parts['types'] = {
|
||||
'path': types_file,
|
||||
'content': templates['types.hpp'].render(tmpl_data),
|
||||
'template_path': Path(templates['types.hpp'].filename),
|
||||
'last_mtime': last_mtime,
|
||||
'printable_name': types_file.relative_to(output_path.parent)
|
||||
}
|
||||
|
||||
return if_parts
|
||||
|
||||
|
||||
def module_create(args):
|
||||
create_strategy = 'force-create' if args.force else 'create'
|
||||
|
||||
detected_projects = helpers.detect_everest_projects(args.everest_projects, args.build_dir)
|
||||
if detected_projects:
|
||||
helpers.everest_dirs.extend(detected_projects)
|
||||
|
||||
mod_files = generate_module_files(args.module, False, args.licenses)
|
||||
|
||||
if args.only == 'which':
|
||||
helpers.print_available_mod_files(mod_files)
|
||||
return
|
||||
else:
|
||||
try:
|
||||
helpers.filter_mod_files(args.only, mod_files)
|
||||
except Exception as err:
|
||||
print(err)
|
||||
return
|
||||
|
||||
for file_info in mod_files['core'] + mod_files['interfaces'] + mod_files['docs']:
|
||||
if not args.disable_clang_format:
|
||||
helpers.clang_format(args.clang_format_file, file_info)
|
||||
|
||||
helpers.write_content_to_file(file_info, create_strategy, args.diff)
|
||||
|
||||
|
||||
def module_update(args):
|
||||
detected_projects = helpers.detect_everest_projects(args.everest_projects, args.build_dir)
|
||||
if detected_projects:
|
||||
helpers.everest_dirs.extend(detected_projects)
|
||||
|
||||
# Always generate type info before updating module
|
||||
for type_with_namespace in list_types_with_namespace():
|
||||
_tmpl_data, _last_mtime = TypeParser.generate_type_info(type_with_namespace, all_types=True)
|
||||
|
||||
primary_update_strategy = 'force-update' if args.force else 'update'
|
||||
update_strategy = {'module.cpp': 'update-if-non-existent'}
|
||||
for file_name in ['cmakelists', 'module.hpp']:
|
||||
update_strategy[file_name] = primary_update_strategy
|
||||
|
||||
# FIXME (aw): refactor out this only handling and rename it properly
|
||||
mod_files = generate_module_files(args.module, True, args.licenses)
|
||||
|
||||
if args.only == 'which':
|
||||
helpers.print_available_mod_files(mod_files)
|
||||
return
|
||||
else:
|
||||
try:
|
||||
helpers.filter_mod_files(args.only, mod_files)
|
||||
except Exception as err:
|
||||
print(err)
|
||||
return
|
||||
|
||||
if not args.disable_clang_format:
|
||||
for file_info in mod_files['core'] + mod_files['interfaces']:
|
||||
helpers.clang_format(args.clang_format_file, file_info)
|
||||
|
||||
for file_info in mod_files['core']:
|
||||
helpers.write_content_to_file(file_info, update_strategy[file_info['abbr']], args.diff, '', True)
|
||||
|
||||
for file_info in mod_files['interfaces']:
|
||||
if file_info['abbr'].endswith('.hpp'):
|
||||
helpers.write_content_to_file(file_info, primary_update_strategy, args.diff, '', True)
|
||||
else:
|
||||
helpers.write_content_to_file(file_info, 'update-if-non-existent', args.diff, '', True)
|
||||
|
||||
|
||||
def module_genld(args):
|
||||
output_dir = Path(args.output_dir).resolve() if args.output_dir else work_dir / \
|
||||
'build/generated/generated/modules'
|
||||
primary_update_strategy = 'force-update' if args.force else 'update'
|
||||
|
||||
loader_files = generate_module_loader_files(args.module, output_dir)
|
||||
|
||||
if not args.disable_clang_format:
|
||||
for file_info in loader_files:
|
||||
helpers.clang_format(args.clang_format_file, file_info)
|
||||
|
||||
for file_info in loader_files:
|
||||
helpers.write_content_to_file_and_check_template(file_info, primary_update_strategy)
|
||||
|
||||
|
||||
def module_get_templates(args):
|
||||
interface_files = args.separator.join(
|
||||
[templates['ld-ev.hpp'].filename,
|
||||
templates['ld-ev.cpp'].filename])
|
||||
|
||||
print(f'{interface_files}')
|
||||
|
||||
|
||||
def interface_genhdr(args):
|
||||
# Always generate type info before generating interfaces
|
||||
for type_with_namespace in list_types_with_namespace():
|
||||
_tmpl_data, _last_mtime = TypeParser.generate_type_info(type_with_namespace, all_types=True)
|
||||
|
||||
output_dir = Path(args.output_dir).resolve() if args.output_dir else work_dir / \
|
||||
'build/generated/include/generated/interfaces'
|
||||
primary_update_strategy = 'force-update' if args.force else 'update'
|
||||
|
||||
interfaces = args.interfaces
|
||||
all_interfaces = False
|
||||
if not interfaces:
|
||||
all_interfaces = True
|
||||
interfaces = []
|
||||
for everest_dir in everest_dirs:
|
||||
if_dir = everest_dir / 'interfaces'
|
||||
interfaces += [if_path.stem for if_path in if_dir.iterdir() if (if_path.is_file()
|
||||
and if_path.suffix == '.yaml')]
|
||||
|
||||
for interface in interfaces:
|
||||
if_parts = generate_interface_headers(interface, all_interfaces, output_dir)
|
||||
|
||||
if not args.disable_clang_format:
|
||||
# FIXME (aw): this broken, because in case all_interfaces is true, if_parts might be none for invalid interface files
|
||||
helpers.clang_format(args.clang_format_file, if_parts['base'])
|
||||
helpers.clang_format(args.clang_format_file, if_parts['exports'])
|
||||
helpers.clang_format(args.clang_format_file, if_parts['types'])
|
||||
|
||||
helpers.write_content_to_file_and_check_template(if_parts['base'], primary_update_strategy, args.diff)
|
||||
helpers.write_content_to_file_and_check_template(if_parts['exports'], primary_update_strategy, args.diff)
|
||||
helpers.write_content_to_file_and_check_template(if_parts['types'], primary_update_strategy, args.diff)
|
||||
|
||||
|
||||
def interface_get_templates(args):
|
||||
interface_files = args.separator.join(
|
||||
[templates['interface_base'].filename,
|
||||
templates['interface_exports'].filename])
|
||||
|
||||
print(f'{interface_files}')
|
||||
|
||||
|
||||
def helpers_genuuids(args):
|
||||
if (args.count <= 0):
|
||||
raise Exception(f'Invalid number ("{args.count}") of uuids to generate')
|
||||
helpers.generate_some_uuids(args.count)
|
||||
|
||||
|
||||
def helpers_yaml2json(args):
|
||||
helpers.yaml2json(Path(args.input).resolve(), Path(args.output).resolve())
|
||||
|
||||
|
||||
def helpers_json2yaml(args):
|
||||
helpers.json2yaml(Path(args.input).resolve(), Path(args.output).resolve())
|
||||
|
||||
|
||||
def list_types_with_namespace(types=None) -> List:
|
||||
if not types:
|
||||
types = []
|
||||
for everest_dir in everest_dirs:
|
||||
types_dir = everest_dir / 'types'
|
||||
types += list(types_dir.glob('**/*.yaml'))
|
||||
|
||||
types_with_namespace = []
|
||||
for type_path in types:
|
||||
relative_path = None
|
||||
for everest_dir in everest_dirs:
|
||||
types_dir = everest_dir / 'types'
|
||||
try:
|
||||
relative_path = type_path.relative_to(types_dir).with_suffix('')
|
||||
break
|
||||
except ValueError:
|
||||
pass
|
||||
uppercase_path = []
|
||||
for part in relative_path.parts:
|
||||
uppercase_path.append(stringcase.capitalcase(part))
|
||||
namespace = '::'.join(relative_path.parts)
|
||||
type_with_namespace = {
|
||||
'path': type_path,
|
||||
'relative_path': relative_path,
|
||||
'namespace': namespace,
|
||||
'uppercase_path': uppercase_path,
|
||||
}
|
||||
|
||||
types_with_namespace.append(type_with_namespace)
|
||||
|
||||
return types_with_namespace
|
||||
|
||||
|
||||
def types_genhdr(args):
|
||||
print('Generating global type headers.')
|
||||
output_dir = Path(args.output_dir).resolve() if args.output_dir else work_dir / \
|
||||
'build/generated/generated/types'
|
||||
|
||||
primary_update_strategy = 'force-update' if args.force else 'update'
|
||||
|
||||
types = None
|
||||
all_types = False
|
||||
if 'types' not in args:
|
||||
all_types = True
|
||||
else:
|
||||
types = args.types
|
||||
|
||||
types_with_namespace = list_types_with_namespace(types=types)
|
||||
|
||||
for type_with_namespace in types_with_namespace:
|
||||
type_parts = TypeParser.generate_type_headers(type_with_namespace, all_types, output_dir)
|
||||
|
||||
if not args.disable_clang_format:
|
||||
helpers.clang_format(args.clang_format_file, type_parts['types'])
|
||||
|
||||
helpers.write_content_to_file_and_check_template(type_parts['types'], primary_update_strategy, args.diff)
|
||||
|
||||
|
||||
def types_get_templates(args):
|
||||
interface_files = templates['types.hpp'].filename
|
||||
|
||||
print(f'{interface_files}')
|
||||
|
||||
|
||||
def main():
|
||||
global validators, everest_dirs, work_dir
|
||||
everest_dir = Path(__file__).parent.parent.parent.parent.parent.parent
|
||||
everest_framework_dir = everest_dir / 'lib' / 'everest' / 'framework'
|
||||
schemas_dir = everest_framework_dir / 'schemas'
|
||||
everest_dir_default = [str(everest_dir), str(Path.cwd().parent / 'EVerest')]
|
||||
|
||||
parser = argparse.ArgumentParser(description='Everest command line tool')
|
||||
parser.add_argument('--version', action='version', version=f'%(prog)s {__version__}')
|
||||
|
||||
common_parser = argparse.ArgumentParser(add_help=False)
|
||||
|
||||
common_parser.add_argument('--work-dir', '-wd', type=str,
|
||||
help='work directory containing the manifest definitions (default: .)', default=str(Path.cwd()))
|
||||
common_parser.add_argument('--everest-dir', '-ed', nargs='*',
|
||||
help=f'everest directory containing the interface definitions (default: {everest_dir_default})',
|
||||
default=everest_dir_default)
|
||||
common_parser.add_argument('--everest-projects', '-ep', nargs='*',
|
||||
help='everest project names. used in auto detection of their directories to get eg. interface defintions (default: EVerest)',
|
||||
default=['EVerest'])
|
||||
common_parser.add_argument('--schemas-dir', '-sd', type=str,
|
||||
help=f'everest framework directory containing the schema definitions (default: {schemas_dir})',
|
||||
default=str(schemas_dir))
|
||||
common_parser.add_argument('--licenses', '-lc', type=str,
|
||||
help='license directory from which ev-cli will attempt to parse custom license texts (default ../licenses)',
|
||||
default=str(Path.cwd() / '../licenses'))
|
||||
common_parser.add_argument('--build-dir', '-bd', type=str,
|
||||
help='everest build directory from which ev-cli will attempt to parse the everest framework schema definitions (default ./build)',
|
||||
default=str(Path.cwd() / 'build'))
|
||||
common_parser.add_argument('--clang-format-file', type=str, default=str(Path.cwd()),
|
||||
help='Path to the directory, containing the .clang-format file (default: .)')
|
||||
common_parser.add_argument('--disable-clang-format', action='store_true', default=False,
|
||||
help='Set this flag to disable clang-format')
|
||||
|
||||
subparsers = parser.add_subparsers(metavar='<command>', help='available commands', required=True)
|
||||
parser_mod = subparsers.add_parser('module', aliases=['mod'], help='module related actions')
|
||||
parser_if = subparsers.add_parser('interface', aliases=['if'], help='interface related actions')
|
||||
parser_hlp = subparsers.add_parser('helpers', aliases=['hlp'], help='helper actions')
|
||||
parser_types = subparsers.add_parser('types', aliases=['ty'], help='type related actions')
|
||||
|
||||
mod_actions = parser_mod.add_subparsers(metavar='<action>', help='available actions', required=True)
|
||||
mod_create_parser = mod_actions.add_parser('create', aliases=['c'], parents=[
|
||||
common_parser], help='create module(s)')
|
||||
mod_create_parser.add_argument('module', type=str, help='name of the module, that should be created')
|
||||
mod_create_parser.add_argument('-f', '--force', action='store_true', help='force overwriting - use with care!')
|
||||
mod_create_parser.add_argument('-d', '--diff', '--dry-run', action='store_true',
|
||||
help='show resulting diff on create or overwrite')
|
||||
mod_create_parser.add_argument('--only', type=str,
|
||||
help='Comma separated filter list of module files, that should be created. '
|
||||
'For a list of available files use "--only which".')
|
||||
mod_create_parser.set_defaults(action_handler=module_create)
|
||||
|
||||
mod_update_parser = mod_actions.add_parser('update', aliases=['u'], parents=[
|
||||
common_parser], help='update module(s)')
|
||||
mod_update_parser.add_argument('module', type=str, help='name of the module, that should be updated')
|
||||
mod_update_parser.add_argument('-f', '--force', action='store_true', help='force overwriting')
|
||||
mod_update_parser.add_argument('-d', '--diff', '--dry-run', action='store_true', help='show resulting diff')
|
||||
mod_update_parser.add_argument('--only', type=str,
|
||||
help='Comma separated filter list of module files, that should be updated. '
|
||||
'For a list of available files use "--only which".')
|
||||
mod_update_parser.set_defaults(action_handler=module_update)
|
||||
|
||||
mod_genld_parser = mod_actions.add_parser(
|
||||
'generate-loader', aliases=['gl'], parents=[common_parser], help='generate everest loader')
|
||||
mod_genld_parser.add_argument(
|
||||
'module', type=str, help='name of the module, for which the loader should be generated')
|
||||
mod_genld_parser.add_argument('-f', '--force', action='store_true', help='force overwriting')
|
||||
mod_genld_parser.add_argument('-o', '--output-dir', type=str, help='Output directory for generated loader '
|
||||
'files (default: {everest-dir}/build/generated/generated/modules)')
|
||||
mod_genld_parser.set_defaults(action_handler=module_genld)
|
||||
|
||||
if_actions = parser_if.add_subparsers(metavar='<action>', help='available actions', required=True)
|
||||
if_genhdr_parser = if_actions.add_parser(
|
||||
'generate-headers', aliases=['gh'], parents=[common_parser], help='generate headers')
|
||||
if_genhdr_parser.add_argument('-f', '--force', action='store_true', help='force overwriting')
|
||||
if_genhdr_parser.add_argument('-o', '--output-dir', type=str, help='Output directory for generated interface '
|
||||
'headers (default: {everest-dir}/build/generated/generated/interfaces)')
|
||||
if_genhdr_parser.add_argument('-d', '--diff', '--dry-run', action='store_true', help='show resulting diff')
|
||||
if_genhdr_parser.add_argument('interfaces', nargs='*', help='a list of interfaces, for which header files should '
|
||||
'be generated - if no interface is given, all will be processed and non-processable '
|
||||
'will be skipped')
|
||||
if_genhdr_parser.set_defaults(action_handler=interface_genhdr)
|
||||
|
||||
hlp_actions = parser_hlp.add_subparsers(metavar='<action>', help='available actions', required=True)
|
||||
hlp_genuuid_parser = hlp_actions.add_parser('generate-uuids', help='generate uuids')
|
||||
hlp_genuuid_parser.add_argument('count', type=int, default=3)
|
||||
hlp_genuuid_parser.set_defaults(action_handler=helpers_genuuids)
|
||||
|
||||
hlp_yaml2json_parser = hlp_actions.add_parser('yaml2json', help='convert yaml into json')
|
||||
hlp_yaml2json_parser.add_argument('input', type=str, help='path to yaml input file')
|
||||
hlp_yaml2json_parser.add_argument('output', type=str, help='path to json output file')
|
||||
hlp_yaml2json_parser.set_defaults(action_handler=helpers_yaml2json)
|
||||
|
||||
hlp_json2yaml_parser = hlp_actions.add_parser('json2yaml', help='convert json into yaml')
|
||||
hlp_json2yaml_parser.add_argument('input', type=str, help='path to json input file')
|
||||
hlp_json2yaml_parser.add_argument('output', type=str, help='path to yaml output file')
|
||||
hlp_json2yaml_parser.set_defaults(action_handler=helpers_json2yaml)
|
||||
|
||||
types_actions = parser_types.add_subparsers(metavar='<action>', help='available actions', required=True)
|
||||
types_genhdr_parser = types_actions.add_parser(
|
||||
'generate-headers', aliases=['gh'], parents=[common_parser], help='generate type headers')
|
||||
types_genhdr_parser.add_argument('-f', '--force', action='store_true', help='force overwriting')
|
||||
types_genhdr_parser.add_argument('-o', '--output-dir', type=str, help='Output directory for generated type '
|
||||
'headers (default: {everest-dir}/build/generated/generated/types)')
|
||||
types_genhdr_parser.add_argument('-d', '--diff', '--dry-run', action='store_true', help='show resulting diff')
|
||||
types_genhdr_parser.add_argument('types', nargs='*', help='a list of types, for which header files should '
|
||||
'be generated - if no type is given, all will be processed and non-processable '
|
||||
'will be skipped')
|
||||
types_genhdr_parser.set_defaults(action_handler=types_genhdr)
|
||||
|
||||
for sub_parser, get_template_function in [
|
||||
(mod_actions, module_get_templates),
|
||||
(if_actions, interface_get_templates),
|
||||
(types_actions, types_get_templates)
|
||||
]:
|
||||
get_templates_parser = sub_parser.add_parser(
|
||||
'get-templates', aliases=['gt'], parents=[common_parser], help='get paths to template files')
|
||||
get_templates_parser.add_argument(
|
||||
'-s', '--separator', type=str, default='\n', help='separator between template files')
|
||||
get_templates_parser.set_defaults(action_handler=get_template_function)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if 'everest_dir' in args:
|
||||
# FIXME (aw): the helper commands do not set everest_dir, work_dir and schema_dirs, but the following common
|
||||
# code has to run for all other commands - we need some better check here than just checking for
|
||||
# 'everest_dir' in args!
|
||||
for entry in args.everest_dir:
|
||||
everest_dir = Path(entry).resolve()
|
||||
everest_dirs.append(everest_dir)
|
||||
|
||||
helpers.everest_dirs = everest_dirs
|
||||
|
||||
work_dir = Path(args.work_dir).resolve()
|
||||
|
||||
setup_jinja_env()
|
||||
|
||||
schemas_dir = Path(args.schemas_dir).resolve()
|
||||
if not schemas_dir.exists():
|
||||
print(f'The default ("{schemas_dir}") xor supplied (via --schemas-dir) schemas directory'
|
||||
' doesn\'t exist.\n'
|
||||
f'dir: {schemas_dir}')
|
||||
cmake_cache_path = Path(args.build_dir) / 'CMakeCache.txt'
|
||||
found_dir = helpers.get_path_from_cmake_cache('everest-framework', cmake_cache_path, '--schemas-dir')
|
||||
if not found_dir:
|
||||
exit(1)
|
||||
schemas_dir = found_dir / 'schemas'
|
||||
if not schemas_dir.exists():
|
||||
exit(1)
|
||||
|
||||
validators = helpers.load_validators(schemas_dir)
|
||||
|
||||
TypeParser.validators = validators
|
||||
TypeParser.templates = templates
|
||||
|
||||
ErrorParser.validators = validators
|
||||
|
||||
args.action_handler(args)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
main()
|
||||
except helpers.EVerestParsingException as e:
|
||||
raise SystemExit(e)
|
||||
@@ -0,0 +1,817 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# Copyright Pionix GmbH and Contributors to EVerest
|
||||
#
|
||||
"""
|
||||
author: aw@pionix.de
|
||||
FIXME (aw): Module documentation.
|
||||
"""
|
||||
|
||||
from .type_parsing import TypeParser
|
||||
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import subprocess
|
||||
import re
|
||||
from typing import Dict, List, Tuple
|
||||
import keyword
|
||||
|
||||
import json
|
||||
import jsonschema
|
||||
|
||||
import yaml
|
||||
|
||||
from uuid import uuid4
|
||||
|
||||
import stringcase
|
||||
|
||||
|
||||
everest_dirs: List[Path] = []
|
||||
|
||||
|
||||
class EVerestParsingException(SystemExit):
|
||||
pass
|
||||
|
||||
|
||||
def snake_case(word: str) -> str:
|
||||
"""Convert capital case to snake case
|
||||
Only alphanumerical characters are allowed. Only inserts camelcase
|
||||
between a consecutive lower and upper alphabetical character and
|
||||
lowers first letter
|
||||
"""
|
||||
|
||||
out = ''
|
||||
if len(word) == 0:
|
||||
return out
|
||||
cur_char: str = ''
|
||||
for i in range(len(word)):
|
||||
if i == 0:
|
||||
cur_char = word[i]
|
||||
if not cur_char.isalnum():
|
||||
raise Exception('Non legal character in: ' + word)
|
||||
out += cur_char.lower()
|
||||
continue
|
||||
last_char: str = cur_char
|
||||
cur_char = word[i]
|
||||
if (last_char.islower() and last_char.isalpha() and cur_char.isupper() and cur_char.isalpha):
|
||||
out += '_'
|
||||
if not cur_char.isalnum():
|
||||
out += '_'
|
||||
else:
|
||||
out += cur_char.lower()
|
||||
|
||||
return out
|
||||
|
||||
|
||||
def create_dummy_result(json_type) -> str:
|
||||
def primitive_to_sample_value(type):
|
||||
if type == 'boolean':
|
||||
return 'true'
|
||||
elif type == 'integer':
|
||||
return '42'
|
||||
elif type == 'number':
|
||||
return '3.14'
|
||||
elif type == 'string':
|
||||
return '"everest"'
|
||||
elif type == 'object':
|
||||
return '{}'
|
||||
elif type == 'array':
|
||||
return '{}'
|
||||
else:
|
||||
raise Exception(f'This json type "{type}" is not known or not implemented')
|
||||
|
||||
if isinstance(json_type, list):
|
||||
return '{}' # default initialization for variant
|
||||
else:
|
||||
return primitive_to_sample_value(json_type)
|
||||
|
||||
|
||||
cpp_type_map = {
|
||||
'null': 'std::nullptr_t', # FIXME (aw): who gets the null, json? or the variant
|
||||
'integer': 'int',
|
||||
'number': 'double',
|
||||
'string': 'std::string',
|
||||
'boolean': 'bool',
|
||||
'array': 'Array',
|
||||
'object': 'Object',
|
||||
}
|
||||
|
||||
def clang_format(config_file_path, file_info):
|
||||
# check if we handle cpp and hpp files
|
||||
if not file_info['path'].suffix in ('.hpp', '.cpp'):
|
||||
return
|
||||
|
||||
clang_format_path = shutil.which('clang-format')
|
||||
if clang_format_path is None:
|
||||
raise RuntimeError('Could not find clang-format executable - needed when passing clang-format config file')
|
||||
|
||||
config_file_path = Path(config_file_path)
|
||||
if not config_file_path.is_dir():
|
||||
raise RuntimeError(f'Supplied directory for the clang-format file ({config_file_path}) does not exist')
|
||||
|
||||
if not (config_file_path / '.clang-format').exists():
|
||||
raise RuntimeError(f'Supplied directory for the clang-format file '
|
||||
f'({config_file_path}) does not contain a .clang-format file')
|
||||
|
||||
content = file_info['content']
|
||||
|
||||
run_parms = {'capture_output': True, 'cwd': config_file_path, 'encoding': 'utf-8', 'input': content}
|
||||
|
||||
format_cmd = subprocess.run([clang_format_path, '--style=file'], **run_parms)
|
||||
|
||||
if format_cmd.returncode != 0:
|
||||
raise RuntimeError(f'clang-format failed with:\n{format_cmd.stderr}')
|
||||
|
||||
file_info['content'] = format_cmd.stdout
|
||||
|
||||
|
||||
def resolve_everest_dir_path(postfix):
|
||||
resolved_path = None
|
||||
for everest_dir in everest_dirs:
|
||||
path = everest_dir / postfix
|
||||
if path.exists():
|
||||
resolved_path = path
|
||||
break
|
||||
|
||||
if not resolved_path:
|
||||
raise EVerestParsingException(
|
||||
f'Could not resolve "{postfix}" in any of the provided everest-dir ({everest_dirs}).')
|
||||
|
||||
return resolved_path
|
||||
|
||||
|
||||
def build_type_info(name, json_type):
|
||||
ti = {
|
||||
'name': name,
|
||||
'is_variant': False,
|
||||
'cpp_type': None,
|
||||
'json_type': json_type
|
||||
}
|
||||
|
||||
if isinstance(json_type, list):
|
||||
ti['is_variant'] = True
|
||||
ti['cpp_type'] = [cpp_type_map[e] for e in json_type if e != 'null']
|
||||
ti['cpp_type'].sort() # sort, so template generation might get reduced
|
||||
# prepend boost::blank if type 'null' exists, so the variant
|
||||
# gets default initialized with blank
|
||||
if 'null' in json_type:
|
||||
ti['cpp_type'].insert(0, cpp_type_map['null'])
|
||||
else:
|
||||
ti['cpp_type'] = cpp_type_map[json_type]
|
||||
|
||||
return ti
|
||||
|
||||
|
||||
type_headers = set()
|
||||
parsed_types: List = []
|
||||
parsed_enums: List = []
|
||||
current_defs: Dict = {}
|
||||
|
||||
format_types = dict()
|
||||
# format_types['date-time'] = 'DateTime'
|
||||
|
||||
|
||||
def object_exists(name: str) -> bool:
|
||||
"""Check if an object already exists."""
|
||||
for el in parsed_types:
|
||||
if el['name'] == name:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def add_enum_type(name: str, enums: Tuple[str], description: str):
|
||||
"""Add enum type to parsed_types."""
|
||||
for el in parsed_enums:
|
||||
if el['name'] == name:
|
||||
raise Exception('Warning: enum ' + name + ' already exists')
|
||||
parsed_enums.append({
|
||||
'name': name,
|
||||
'enums': enums,
|
||||
'description': description
|
||||
})
|
||||
|
||||
|
||||
def parse_ref(ref: str, prop_type, prop_info: Dict) -> Tuple[str, dict]:
|
||||
if ref not in TypeParser.all_types:
|
||||
TypeParser.all_types[ref] = TypeParser.parse_type_url(type_url=ref)
|
||||
type_dict = TypeParser.all_types[ref]
|
||||
|
||||
type_path = resolve_everest_dir_path('types' / type_dict['type_relative_path'] .with_suffix('.yaml'))
|
||||
if not type_path or not type_path.exists():
|
||||
raise EVerestParsingException('$ref: ' + ref + f' referenced type file "{type_path}" does not exist.')
|
||||
|
||||
(td, _mod) = TypeParser.load_type_definition(type_path)
|
||||
if 'types' in td and type_dict['type_name'] in td['types']:
|
||||
local_type_info = td['types'][type_dict['type_name']]
|
||||
if local_type_info['type'] == 'string' and 'enum' in local_type_info:
|
||||
prop_info['enum'] = True
|
||||
prop_type = type_dict['namespaced_type']
|
||||
prop_info['prop']['type'] = prop_type
|
||||
prop_info['type_dict'] = type_dict
|
||||
|
||||
path = Path('generated/types') / \
|
||||
type_dict['type_relative_path'].with_suffix('.hpp')
|
||||
type_headers.add(path.as_posix())
|
||||
|
||||
return (prop_type, prop_info)
|
||||
|
||||
|
||||
def parse_property(prop_name: str, prop: Dict, depends_on: List[str], type_file: bool) -> Tuple[str, dict]:
|
||||
"""Determine type of property and proceed with it.
|
||||
In case it is a $ref, look it up in the TypeParser
|
||||
Currently, the following property types are supported:
|
||||
- string (and enum as a special case)
|
||||
- integer
|
||||
- number
|
||||
- boolean
|
||||
- array
|
||||
- object (will be parsed recursivly)
|
||||
"""
|
||||
|
||||
prop_type = None
|
||||
prop_info = {
|
||||
'description': prop.get('description', 'TODO: description'),
|
||||
'prop': prop,
|
||||
'enum': False
|
||||
}
|
||||
if '$ref' in prop:
|
||||
return parse_ref(prop['$ref'], prop_type, prop_info)
|
||||
|
||||
if 'type' not in prop:
|
||||
raise EVerestParsingException(f'{prop_name} does not contain a type property')
|
||||
|
||||
if prop['type'] == 'string':
|
||||
if 'enum' in prop and type_file:
|
||||
prop_type = stringcase.capitalcase(prop_name)
|
||||
add_enum_type(prop_type, prop['enum'], prop_info['description'])
|
||||
elif 'format' in prop:
|
||||
if prop['format'] in format_types:
|
||||
prop_type = format_types[prop['format']]
|
||||
else:
|
||||
# unsupported format type
|
||||
prop_type = 'std::string'
|
||||
prop_info['unsupported_format'] = True
|
||||
else:
|
||||
prop_type = 'std::string'
|
||||
elif prop['type'] == 'integer':
|
||||
prop_type = 'int32_t'
|
||||
elif prop['type'] == 'number':
|
||||
prop_type = 'float'
|
||||
elif prop['type'] == 'boolean':
|
||||
prop_type = 'bool'
|
||||
elif prop['type'] == 'array':
|
||||
if 'items' in prop:
|
||||
prop_type = 'std::vector<' + parse_property(prop_name, prop['items'], depends_on, type_file)[0] + '>'
|
||||
else:
|
||||
raise EVerestParsingException(f'Property items of array {prop_name} does not contain a type property')
|
||||
elif prop['type'] == 'object':
|
||||
prop_type = stringcase.capitalcase(prop_name)
|
||||
depends_on.append(prop_type)
|
||||
if not object_exists(prop_type):
|
||||
parse_object(prop_type, prop, type_file)
|
||||
else:
|
||||
raise Exception('Unknown type: ' + prop['type'])
|
||||
|
||||
return (prop_type, prop_info)
|
||||
|
||||
|
||||
def parse_object(ob_name: str, json_schema: Dict, type_file: bool):
|
||||
"""Parse a JSON object.
|
||||
Iterates over the properties of this object, parses their type
|
||||
and puts these information into the global dict parsed_types.
|
||||
"""
|
||||
|
||||
ob_dict = {'name': ob_name, 'properties': [], 'depends_on': []}
|
||||
parsed_types.insert(0, ob_dict)
|
||||
|
||||
if 'properties' not in json_schema:
|
||||
# object has no properties, probably not a complex object
|
||||
if '$ref' in json_schema:
|
||||
if json_schema['$ref'] not in TypeParser.all_types:
|
||||
TypeParser.all_types[json_schema['$ref']] = TypeParser.parse_type_url(type_url=json_schema['$ref'])
|
||||
type_dict = TypeParser.all_types[json_schema['$ref']]
|
||||
|
||||
type_path = resolve_everest_dir_path('types' / type_dict['type_relative_path'].with_suffix('.yaml'))
|
||||
if not type_path or not type_path.exists():
|
||||
raise EVerestParsingException(
|
||||
'$ref: ' + json_schema['$ref'] + f' referenced type file "{type_path}" does not exist.')
|
||||
TypeParser.does_type_exist(type_url=json_schema['$ref'], json_type=json_schema['type'])
|
||||
|
||||
prop_type = type_dict['namespaced_type']
|
||||
ob_dict['name'] = prop_type
|
||||
path = Path('generated/types') / \
|
||||
type_dict['type_relative_path'].with_suffix('.hpp')
|
||||
type_headers.add(path.as_posix())
|
||||
return ob_dict
|
||||
return
|
||||
|
||||
if not type_file:
|
||||
return
|
||||
|
||||
for prop_name, prop in json_schema['properties'].items():
|
||||
if not prop_name.isidentifier() or keyword.iskeyword(prop_name):
|
||||
raise Exception(prop_name + ' can\'t be used as an identifier!')
|
||||
(prop_type, prop_info) = parse_property(prop_name, prop, ob_dict['depends_on'], type_file)
|
||||
ob_dict['properties'].append({
|
||||
'name': prop_name,
|
||||
'json_name': prop_name,
|
||||
'type': prop_type,
|
||||
'info': prop_info,
|
||||
'enum': 'enum' in prop or prop_info['enum'],
|
||||
'required': prop_name in json_schema.get('required', {}),
|
||||
})
|
||||
|
||||
ob_dict['properties'].sort(key=lambda x: x.get('required'), reverse=True)
|
||||
|
||||
return ob_dict
|
||||
|
||||
|
||||
def generate_header_for_type(type_name: str) -> Path:
|
||||
return (Path('generated/types') / type_name).with_suffix('.hpp')
|
||||
|
||||
|
||||
def extended_build_type_info(name: str, info: dict, type_file=False) -> Tuple[dict, dict]:
|
||||
"""Extend build_type_info with enum and object type handling."""
|
||||
type_info = build_type_info(name, info['type'])
|
||||
enum_info = None
|
||||
|
||||
if type_info['json_type'] == 'string':
|
||||
if 'enum' in info and type_file:
|
||||
enum_info = {
|
||||
'name': name,
|
||||
'description': info.get('description', 'TODO: description'),
|
||||
'enum_type': stringcase.capitalcase(name),
|
||||
'enum': info['enum']
|
||||
}
|
||||
|
||||
type_info['enum_type'] = enum_info['enum_type']
|
||||
elif '$ref' in info:
|
||||
if info['$ref'] not in TypeParser.all_types:
|
||||
TypeParser.all_types[info['$ref']] = TypeParser.parse_type_url(type_url=info['$ref'])
|
||||
type_dict = TypeParser.all_types[info['$ref']]
|
||||
|
||||
type_path = resolve_everest_dir_path('types' / type_dict['type_relative_path'] .with_suffix('.yaml'))
|
||||
if not type_path or not type_path.exists():
|
||||
raise EVerestParsingException('$ref: ' + info['$ref'] +
|
||||
f' referenced type file "{type_path}" does not exist.')
|
||||
|
||||
(td, _mod) = TypeParser.load_type_definition(type_path)
|
||||
if 'types' in td and type_dict['type_name'] in td['types']:
|
||||
local_type_info = td['types'][type_dict['type_name']]
|
||||
if local_type_info['type'] == 'string' and 'enum' in local_type_info:
|
||||
enum_info = {
|
||||
'name': name,
|
||||
'description': local_type_info.get('description', 'TODO: description'),
|
||||
'enum_type': type_dict['namespaced_type'],
|
||||
'enum': local_type_info['enum']
|
||||
}
|
||||
|
||||
type_info['enum_type'] = enum_info['enum_type']
|
||||
path = generate_header_for_type(type_dict['type_relative_path'])
|
||||
type_headers.add(path.as_posix())
|
||||
elif type_info['json_type'] == 'object':
|
||||
try:
|
||||
ob = parse_object(name, info, type_file)
|
||||
if ob and 'name' in ob:
|
||||
type_info['object_type'] = ob['name']
|
||||
except EVerestParsingException as e:
|
||||
raise EVerestParsingException(f'Error parsing object {name}: {e}')
|
||||
elif type_info['json_type'] == 'array':
|
||||
if '$ref' in info['items']:
|
||||
if info['items']['$ref'] not in TypeParser.all_types:
|
||||
TypeParser.all_types[info['items']['$ref']] = TypeParser.parse_type_url(type_url=info['items']['$ref'])
|
||||
type_dict = TypeParser.all_types[info['items']['$ref']]
|
||||
|
||||
type_path = resolve_everest_dir_path('types' / type_dict['type_relative_path'] .with_suffix('.yaml'))
|
||||
if not type_path or not type_path.exists():
|
||||
raise EVerestParsingException(
|
||||
'$ref: ' + info['items']['$ref'] + f' referenced type file "{type_path}" does not exist.')
|
||||
|
||||
(td, _mod) = TypeParser.load_type_definition(type_path)
|
||||
if 'types' in td and type_dict['type_name'] in td['types']:
|
||||
local_type_info = td['types'][type_dict['type_name']]
|
||||
if 'enum' in local_type_info:
|
||||
type_info['array_type_contains_enum'] = True
|
||||
type_info['array_type'] = type_dict['namespaced_type']
|
||||
path = generate_header_for_type(type_dict['type_relative_path'])
|
||||
type_headers.add(path.as_posix())
|
||||
|
||||
return (type_info, enum_info)
|
||||
|
||||
|
||||
def load_validators(schema_path: Path):
|
||||
# FIXME (aw): we should also patch the schemas like in everest-framework
|
||||
validators = {}
|
||||
for validator, filename in zip(
|
||||
['interface', 'module', 'config', 'type', 'error_declaration_list'],
|
||||
['interface', 'manifest', 'config', 'type', 'error-declaration-list']):
|
||||
try:
|
||||
schema = yaml.safe_load((schema_path / f'{filename}.yaml').read_text())
|
||||
jsonschema.Draft7Validator.check_schema(schema)
|
||||
validators[validator] = jsonschema.Draft7Validator(schema)
|
||||
except OSError as err:
|
||||
print(f'Could not open schema file {err.filename}: {err.strerror}')
|
||||
exit(1)
|
||||
except jsonschema.SchemaError as err:
|
||||
print(f'Schema error in schema file {filename}.yaml')
|
||||
raise
|
||||
except yaml.YAMLError as err:
|
||||
raise Exception(f'Could not parse interface definition file {schema_path}') from err
|
||||
|
||||
return validators
|
||||
|
||||
|
||||
def load_validated_interface_def(if_def_path: Path, validator):
|
||||
if_def = {}
|
||||
try:
|
||||
if_def = yaml.safe_load(if_def_path.read_text())
|
||||
# validating interface
|
||||
validator.validate(if_def)
|
||||
# validate var/cmd subparts
|
||||
if 'vars' in if_def:
|
||||
for _var_name, var_def in if_def['vars'].items():
|
||||
jsonschema.Draft7Validator.check_schema(var_def)
|
||||
if 'cmds' in if_def:
|
||||
for _cmd_name, cmd_def in if_def['cmds'].items():
|
||||
if 'arguments' in cmd_def:
|
||||
for _arg_name, arg_def in cmd_def['arguments'].items():
|
||||
jsonschema.Draft7Validator.check_schema(arg_def)
|
||||
if 'result' in cmd_def:
|
||||
jsonschema.Draft7Validator.check_schema(cmd_def['result'])
|
||||
except OSError as err:
|
||||
raise Exception(f'Could not open interface definition file {err.filename}: {err.strerror}') from err
|
||||
except jsonschema.ValidationError as err:
|
||||
raise Exception(f'Validation error in interface definition file {if_def_path}: {err}') from err
|
||||
except yaml.YAMLError as err:
|
||||
raise Exception(f'Could not parse interface definition file {if_def_path}') from err
|
||||
|
||||
return if_def
|
||||
|
||||
|
||||
def load_validated_type_def(type_def_path: Path, validator):
|
||||
"""Load a type definition from the provided path and validate it with the provided validator."""
|
||||
|
||||
try:
|
||||
type_def = yaml.safe_load(type_def_path.read_text())
|
||||
# validating type definition
|
||||
validator.validate(type_def)
|
||||
|
||||
return type_def
|
||||
except OSError as err:
|
||||
raise Exception(f'Could not open type definition file {err.filename}: {err.strerror}') from err
|
||||
except jsonschema.ValidationError as err:
|
||||
raise Exception(f'Validation error in type definition file {type_def_path}') from err
|
||||
except yaml.YAMLError as err:
|
||||
raise Exception(f'Could not parse interface definition file {type_def_path}') from err
|
||||
|
||||
return type_def
|
||||
|
||||
|
||||
def load_validated_module_def(module_path: Path, validator):
|
||||
try:
|
||||
module_def = yaml.safe_load(module_path.read_text())
|
||||
validator.validate(module_def)
|
||||
except OSError as err:
|
||||
raise Exception(f'Could not open type definition file {err.filename}: {err.strerror}') from err
|
||||
except jsonschema.ValidationError as err:
|
||||
raise Exception(f'Validation error in module definition file {module_path}') from err
|
||||
except yaml.YAMLError as err:
|
||||
raise Exception(f'Could not parse interface definition file {module_path}') from err
|
||||
|
||||
return module_def
|
||||
|
||||
|
||||
def generate_some_uuids(count):
|
||||
for i in range(count):
|
||||
print(uuid4())
|
||||
|
||||
|
||||
def yaml2json(yaml_file: Path, json_file: Path):
|
||||
if not yaml_file.exists():
|
||||
print(f'The input file ({yaml_file}) does not exist')
|
||||
exit(1)
|
||||
|
||||
with open(yaml_file, 'r') as yaml_content:
|
||||
content_as_dict = yaml.safe_load(yaml_content)
|
||||
|
||||
with open(json_file, 'w') as json_content:
|
||||
json.dump(content_as_dict, json_content, indent=2)
|
||||
|
||||
|
||||
def json2yaml(json_file: Path, yaml_file: Path):
|
||||
if not json_file.exists():
|
||||
print(f'The input file ({json_file}) does not exist')
|
||||
exit(1)
|
||||
|
||||
with open(json_file, 'r') as json_content:
|
||||
content_as_dict = json.load(json_content)
|
||||
|
||||
with open(yaml_file, 'w') as yaml_content:
|
||||
yaml.safe_dump(content_as_dict, yaml_content, indent=2, sort_keys=False, width=120)
|
||||
|
||||
|
||||
def __check_for_match(blocks_def, line, line_no, file_path):
|
||||
match = re.search(blocks_def['regex_str'], line)
|
||||
if not match:
|
||||
return None
|
||||
|
||||
# mb = match_block
|
||||
mb = {
|
||||
'id': match.group('uuid'),
|
||||
'version': match.group('version'),
|
||||
'tag': blocks_def['format_str'].format(
|
||||
uuid=match.group('uuid'),
|
||||
version=match.group('version')
|
||||
)
|
||||
}
|
||||
|
||||
# check if uuid and version exists
|
||||
if blocks_def['version'] != mb['version']:
|
||||
raise ValueError(
|
||||
f'Error while parsing {file_path}:\n'
|
||||
f' matched line {line_no}: {line}\n'
|
||||
f' contains version "{mb["version"]}", which is different from the blocks definition version "{blocks_def["version"]}"'
|
||||
)
|
||||
|
||||
for block, block_info in blocks_def['definitions'].items():
|
||||
if block_info['id'] != mb['id']:
|
||||
continue
|
||||
|
||||
mb['name'] = block
|
||||
mb['block'] = block_info
|
||||
|
||||
if not 'block' in mb:
|
||||
raise ValueError(
|
||||
f'Error while parsing {file_path}:\n'
|
||||
f' matched line {line_no}: {line}\n'
|
||||
f' contains uuid "{mb["id"]}", which doesn\'t exist in the block definition'
|
||||
)
|
||||
|
||||
return mb
|
||||
|
||||
|
||||
def generate_tmpl_blocks(blocks_def, file_path=None):
|
||||
|
||||
tmpl_block = {}
|
||||
for block_name, block_def in blocks_def['definitions'].items():
|
||||
tmpl_block[block_name] = {
|
||||
'tag': blocks_def['format_str'].format(
|
||||
uuid=block_def['id'],
|
||||
version=blocks_def['version']
|
||||
),
|
||||
'content': block_def['content'],
|
||||
'first_use': True
|
||||
}
|
||||
|
||||
if not file_path:
|
||||
return tmpl_block
|
||||
|
||||
try:
|
||||
file_data = file_path.read_text()
|
||||
except OSError as err:
|
||||
print(f'Could not open file {err.filename} for parsing blocks: {err.strerror}')
|
||||
exit(1)
|
||||
|
||||
line_no = 0
|
||||
matched_block = None
|
||||
content = None
|
||||
|
||||
for line in file_data.splitlines(True):
|
||||
line_no += 1
|
||||
|
||||
if not matched_block:
|
||||
matched_block = __check_for_match(blocks_def, line.rstrip(), line_no, file_path)
|
||||
content = None
|
||||
continue
|
||||
|
||||
if (line.strip() == matched_block['tag']):
|
||||
if (content):
|
||||
tmpl_block[matched_block['name']]['content'] = content.rstrip()
|
||||
tmpl_block[matched_block['name']]['first_use'] = False
|
||||
matched_block = None
|
||||
else:
|
||||
content = (content + line) if content else line
|
||||
|
||||
if matched_block:
|
||||
raise ValueError(
|
||||
f'Error while parsing {file_path}:\n'
|
||||
f' matched tag line {matched_block["tag"]}\n'
|
||||
f' could not find closing tag'
|
||||
)
|
||||
|
||||
return tmpl_block
|
||||
|
||||
|
||||
def load_tmpl_blocks(blocks_def, file_path, update):
|
||||
if update and file_path.exists():
|
||||
return generate_tmpl_blocks(blocks_def, file_path)
|
||||
else:
|
||||
return generate_tmpl_blocks(blocks_def)
|
||||
|
||||
|
||||
def __show_diff_for(file_info):
|
||||
diff_path = shutil.which('diff')
|
||||
if diff_path == None:
|
||||
raise Exception('Can\'t generate diff, because "diff" executable not found')
|
||||
|
||||
file_path = file_info['path']
|
||||
|
||||
diff_ignore = ''
|
||||
if file_path.suffix in ('.hpp', '.cpp'):
|
||||
diff_ignore = '^//.*'
|
||||
elif file_path.name == 'CMakeLists.txt':
|
||||
diff_ignore = '^#.*'
|
||||
diff_ignore_args = ['-I', diff_ignore] if diff_ignore else []
|
||||
|
||||
run_parms = {'input': file_info['content'], 'capture_output': True, 'encoding': 'utf-8'}
|
||||
|
||||
diff = subprocess.run([
|
||||
diff_path,
|
||||
'-ruN',
|
||||
*diff_ignore_args,
|
||||
'--label', file_info['printable_name'],
|
||||
'--color=always',
|
||||
file_path,
|
||||
'-'
|
||||
], **run_parms).stdout
|
||||
if diff:
|
||||
print(diff)
|
||||
|
||||
|
||||
def filter_mod_files(only, mod_files):
|
||||
if not only:
|
||||
return
|
||||
|
||||
filter_files = set([c.strip() for c in only.split(',')])
|
||||
not_filtered_files = filter_files.copy()
|
||||
|
||||
# first check if all selected file filters are valid
|
||||
for category_files in mod_files.values():
|
||||
for file_info in category_files:
|
||||
if file_info['abbr'] in not_filtered_files:
|
||||
not_filtered_files.remove(file_info['abbr'])
|
||||
|
||||
if not_filtered_files:
|
||||
raise Exception(f'Unknown file filters for --only option: {not_filtered_files}\n'
|
||||
'Use "--only which" to show available file filters')
|
||||
|
||||
# now do the filtering
|
||||
for category, category_files in mod_files.items():
|
||||
mod_files[category] = list(filter(lambda x: x['abbr'] in filter_files, category_files))
|
||||
|
||||
|
||||
def print_available_mod_files(mod_files):
|
||||
for category, category_files in mod_files.items():
|
||||
print(f'Available files for category "{category}"')
|
||||
for file_info in category_files:
|
||||
print(f' {file_info["abbr"]}')
|
||||
|
||||
def get_mtime(filename: str | Path) -> float:
|
||||
if isinstance(filename, str):
|
||||
filename = Path(filename)
|
||||
|
||||
return filename.stat().st_mtime
|
||||
|
||||
|
||||
def is_template_newer(file_info) -> Tuple[bool, str]:
|
||||
template_path = file_info['template_path']
|
||||
generated_path = file_info['path']
|
||||
|
||||
if not generated_path.exists():
|
||||
return (True, ' (Generated file did not exist)')
|
||||
|
||||
if get_mtime(template_path) > get_mtime(generated_path):
|
||||
return (True, ' (Template file has changed since last generation)')
|
||||
|
||||
return (False, '')
|
||||
|
||||
|
||||
def write_content_to_file(file_info, strategy, only_diff=False, reason = '', check_license_header=False):
|
||||
# strategy:
|
||||
# update: update only if dest older or not existent
|
||||
# force-update: update, even if dest newer
|
||||
# update-if-non-existent: update only if file does not exists
|
||||
# create: create file only if it does not exist
|
||||
# force-create: create file, even if it exists
|
||||
# FIXME (aw): we should have this as an enum
|
||||
|
||||
strategies = ['update', 'force-update', 'update-if-non-existent', 'create', 'force-create']
|
||||
|
||||
file_path = file_info['path']
|
||||
file_dir = file_path.parent
|
||||
printable_name = file_info['printable_name']
|
||||
|
||||
method = ''
|
||||
|
||||
if only_diff:
|
||||
return __show_diff_for(file_info)
|
||||
|
||||
if strategy == 'update':
|
||||
if file_path.exists() and file_path.stat().st_mtime > file_info['last_mtime']:
|
||||
print(f'Skipping {printable_name} (up-to-date)')
|
||||
return
|
||||
method = 'Updating'
|
||||
elif strategy == 'force-update':
|
||||
method = 'Force-updating' if file_path.exists() else 'Creating'
|
||||
elif strategy == 'force-create':
|
||||
method = 'Overwriting' if file_path.exists() else 'Creating'
|
||||
elif strategy == 'update-if-non-existent' or strategy == 'create':
|
||||
if file_path.exists():
|
||||
print(f'Skipping {printable_name} (use create --force to recreate)')
|
||||
return
|
||||
method = 'Creating'
|
||||
else:
|
||||
raise Exception(f'Invalid strategy "{strategy}"\nSupported strategies: {strategies}')
|
||||
|
||||
print(f'{method} file {printable_name}{reason}')
|
||||
|
||||
if not file_dir.exists():
|
||||
file_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# check if file header is different from license header
|
||||
if check_license_header:
|
||||
if 'license_header' in file_info and file_path.exists():
|
||||
original_content = file_path.read_text()
|
||||
if not original_content.startswith(file_info['license_header']):
|
||||
# determine likely end of license header
|
||||
search_terms = ['#ifndef', '#pragma once', '#include']
|
||||
original_license_header = ''
|
||||
for search in search_terms:
|
||||
index = original_content.find(search)
|
||||
if index >= 0:
|
||||
original_license_header = original_content[0:index]
|
||||
break
|
||||
print(f'Keeping the existing licence header:\n{original_license_header}')
|
||||
file_info['content'] = file_info['content'].replace(
|
||||
file_info['license_header'], original_license_header.strip())
|
||||
|
||||
file_path.write_text(file_info['content'])
|
||||
|
||||
|
||||
def write_content_to_file_and_check_template(file_info, strategy, only_diff=False):
|
||||
# check if template is newer and force-update file if it is
|
||||
update_strategy = strategy
|
||||
(newer, reason) = is_template_newer(file_info)
|
||||
if newer:
|
||||
update_strategy = 'force-update'
|
||||
write_content_to_file(file_info, update_strategy, only_diff, reason)
|
||||
|
||||
|
||||
def get_license_header(license_dirs, license_url):
|
||||
url_schemas = ['http://', 'https://']
|
||||
for url_schema in url_schemas:
|
||||
if license_url.startswith(url_schema):
|
||||
license_url = license_url.replace(url_schema, '', 1)
|
||||
license_path = None
|
||||
for license_dir in license_dirs:
|
||||
check_license_path = license_dir / license_url
|
||||
print(f'Checking if license "{check_license_path}" exists...')
|
||||
if check_license_path.exists():
|
||||
license_path = check_license_path
|
||||
|
||||
if not license_path:
|
||||
return None
|
||||
with open(license_path, 'r') as custom_license_file:
|
||||
return custom_license_file.read().strip()
|
||||
|
||||
|
||||
def get_path_from_cmake_cache(variable_prefix, cmake_cache_path, option_name):
|
||||
print(f'Searching for {variable_prefix} in: {cmake_cache_path}')
|
||||
print(f'You can either provide the {variable_prefix} directory with {option_name} or influence the'
|
||||
' automatic search path by setting --build-dir (default: ./build)')
|
||||
if not cmake_cache_path.exists():
|
||||
print(f'CMakeCache.txt does not exist: {cmake_cache_path}')
|
||||
return None
|
||||
with open(cmake_cache_path, 'r') as cmake_cache_file:
|
||||
search = f'{variable_prefix}_SOURCE_DIR:STATIC='
|
||||
for line in cmake_cache_file:
|
||||
if line.startswith(search):
|
||||
found_dir = Path(line.replace(search, '', 1).strip(' \t\n\r'))
|
||||
if found_dir.exists():
|
||||
print(f'Found {variable_prefix} directory: {found_dir}')
|
||||
user_choice = input('Do you want to use this? [Y/n] ').lower()
|
||||
if user_choice == 'y' or not user_choice:
|
||||
return found_dir
|
||||
break
|
||||
return None
|
||||
|
||||
|
||||
def detect_everest_projects(everest_projects, build_dir):
|
||||
detected_everest_project = False
|
||||
for everest_dir in everest_dirs:
|
||||
if everest_dir.exists() and everest_dir.name in everest_projects:
|
||||
detected_everest_project = True
|
||||
|
||||
found_dirs = []
|
||||
|
||||
if not detected_everest_project:
|
||||
print('Could not detect ' + ", ".join(everest_projects) + ' path in --everest-dir')
|
||||
cmake_cache_path = Path(build_dir) / 'CMakeCache.txt'
|
||||
for everest_project in everest_projects:
|
||||
found_dir = get_path_from_cmake_cache(everest_project, cmake_cache_path, '--everest-dir')
|
||||
if found_dir:
|
||||
found_dirs.append(found_dir)
|
||||
|
||||
return found_dirs
|
||||
@@ -0,0 +1,2 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
@@ -0,0 +1,2 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
@@ -0,0 +1,2 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
@@ -0,0 +1,19 @@
|
||||
{% from "helper_macros.j2" import insert_block, print_template_info %}
|
||||
{{ print_template_info('3', 'marked regions will be kept', '#') }}
|
||||
|
||||
# module setup:
|
||||
# - ${MODULE_NAME}: module name
|
||||
ev_setup_cpp_module()
|
||||
|
||||
{{ insert_block(info.blocks.add_general) }}
|
||||
|
||||
{% if provides %}
|
||||
target_sources(${MODULE_NAME}
|
||||
PRIVATE
|
||||
{% for impl in provides %}
|
||||
"{{ impl.cpp_file_rel_path }}"
|
||||
{% endfor %}
|
||||
)
|
||||
|
||||
{% endif %}
|
||||
{{ insert_block(info.blocks.add_other) }}
|
||||
@@ -0,0 +1,127 @@
|
||||
{% macro cpp_type(type, interface=none, call=false) -%}
|
||||
{% if type.is_variant -%}
|
||||
std::variant<{{ type.cpp_type|join(', ') }}>
|
||||
{%- else -%}
|
||||
{% if interface is not none and 'object_type' in type %}
|
||||
{{ interface + '::' + type.object_type }}
|
||||
{% else %}
|
||||
{% if call -%}{{'const '}}{%- endif %}
|
||||
{% if 'object_type' in type -%}
|
||||
{{ type.object_type }}
|
||||
{%- elif 'enum_type' in type -%}
|
||||
{{ type.enum_type }}
|
||||
{%- elif 'array_type' in type -%}
|
||||
std::vector<{{ type.array_type }}>
|
||||
{%- else -%}
|
||||
{{ type.cpp_type }}
|
||||
{%- endif %}
|
||||
{% if call -%}&{%- endif %}
|
||||
{%- endif %}
|
||||
{%- endif %}
|
||||
{%- endmacro %}
|
||||
|
||||
{% macro result_type(type, interface=none) -%}
|
||||
{% if type -%}
|
||||
{{ cpp_type(type) }}
|
||||
{%- else -%}
|
||||
void
|
||||
{%- endif %}
|
||||
{%- endmacro %}
|
||||
|
||||
{% macro var_to_any(var, name) -%}
|
||||
{% if var.is_variant -%}
|
||||
Everest::variant_to_json({{ name }})
|
||||
{%- else -%}
|
||||
{{ name }}
|
||||
{%- endif %}
|
||||
{%- endmacro %}
|
||||
|
||||
{% macro var_to_cpp(arg) -%}
|
||||
{% if arg.is_variant -%}
|
||||
Everest::json_to_variant<{{ arg.cpp_type|join(', ')}}>
|
||||
{%- else -%}
|
||||
static_cast<{{ arg.cpp_type }}>
|
||||
{%- endif %}
|
||||
{%- endmacro %}
|
||||
|
||||
{% macro call_cmd_signature(cmd, interface=none) -%}
|
||||
{{ result_type(cmd.result, interface) }} call_{{ cmd.name }}(
|
||||
{%- for arg in cmd.args -%}
|
||||
{{ cpp_type(arg, none, true) }} {{ arg.name }}{{ ', ' if not loop.last }}
|
||||
{%- endfor -%}
|
||||
)
|
||||
{%- endmacro %}
|
||||
|
||||
{% macro handle_cmd_signature(cmd, class_name=None, interface=none) -%}
|
||||
{% if not class_name %}virtual {% endif -%}
|
||||
{{ result_type(cmd.result, interface) }} {% if class_name %}{{ class_name }}::{% endif -%}
|
||||
handle_{{ cmd.name }}(
|
||||
{%- for arg in cmd.args -%}
|
||||
{{ cpp_type(arg) }}& {{ arg.name }}{{ ', ' if not loop.last }}
|
||||
{%- endfor -%}
|
||||
)
|
||||
{%- endmacro %}
|
||||
|
||||
{% macro publish_var_signature(var) %}
|
||||
void publish_{{ var.name }}({% if var.json_type != 'null' %}{{ cpp_type(var) }} value{% endif %}) {
|
||||
_ev->publish(_name, "{{ var.name }}", {% if var.json_type != 'null' %}{{ var_to_any(var, 'value')}}{% else %}nullptr{% endif %});
|
||||
}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro list_json_types(json_type) -%}
|
||||
{% if json_type is iterable and json_type is not string -%}
|
||||
{
|
||||
{%- for type in json_type -%}
|
||||
"{{ type }}"{{ ', ' if not loop.last}}
|
||||
{%- endfor -%}
|
||||
}
|
||||
{%- else -%}
|
||||
{"{{ json_type }}"}
|
||||
{%- endif %}
|
||||
{%- endmacro %}
|
||||
|
||||
{% macro insert_block(block, indent=0) %}
|
||||
{{ block.tag }}
|
||||
{{ " "*indent if block.first_use }}{{ block.content }}
|
||||
{{ " "*indent }}{{ block.tag }}
|
||||
{%- endmacro %}
|
||||
|
||||
{% macro print_spdx_line(license, year_tag=None) %}
|
||||
// SPDX-License-Identifier: {{ license }}
|
||||
// Copyright{% if year_tag %} {{ year_tag }}{% endif %} Pionix GmbH and Contributors to EVerest
|
||||
{%- endmacro %}
|
||||
|
||||
{% macro print_license_header(license) %}
|
||||
{{ license }}
|
||||
{%- endmacro %}
|
||||
|
||||
{% macro print_template_info(version, title='DO NOT EDIT!', comment_sep='//') %}
|
||||
{{ comment_sep }}
|
||||
{{ comment_sep }} AUTO GENERATED - {{ title|upper }}
|
||||
{{ comment_sep }} template version {{ version }}
|
||||
{{ comment_sep }}
|
||||
{%- endmacro %}
|
||||
|
||||
{% macro string_to_enum(enum_type) %}
|
||||
{% if '::' not in enum_type %}
|
||||
string_to_{{ enum_type | snake_case -}}
|
||||
{% else %}
|
||||
{{ enum_type[:enum_type.rfind('::')] }}::string_to_{{ enum_type[(enum_type.rfind('::')+2):] | snake_case -}}
|
||||
{% endif %}
|
||||
{%- endmacro %}
|
||||
|
||||
{% macro enum_to_string(enum_type) %}
|
||||
{% if '::' not in enum_type %}
|
||||
{{ enum_type | snake_case }}_to_string
|
||||
{%- else %}
|
||||
{{ enum_type[:enum_type.rfind('::')] }}::{{ enum_type[(enum_type.rfind('::')+2):] | snake_case }}_to_string
|
||||
{%- endif %}
|
||||
{%- endmacro %}
|
||||
|
||||
{% macro enum_to_string_view(enum_type) %}
|
||||
{% if '::' not in enum_type %}
|
||||
{{ enum_type | snake_case }}_to_string_view
|
||||
{%- else %}
|
||||
{{ enum_type[:enum_type.rfind('::')] }}::{{ enum_type[(enum_type.rfind('::')+2):] | snake_case }}_to_string_view
|
||||
{%- endif %}
|
||||
{%- endmacro %}
|
||||
@@ -0,0 +1,24 @@
|
||||
.. _everest_modules_handwritten_{{ info.name }}:
|
||||
|
||||
.. This file is a placeholder for optional multiple files
|
||||
handwritten documentation for the {{ info.name }} module.
|
||||
|
||||
.. This handwritten documentation is optional. In case
|
||||
you do not want to write it, you can delete the doc/ directory.
|
||||
|
||||
.. The documentation can be written in reStructuredText,
|
||||
and will be converted to HTML and PDF by Sphinx.
|
||||
This index.rst file is the entry point for the module documentation.
|
||||
|
||||
.. Use underlined-only headlines inside this document (highest-level
|
||||
sub-section headline should use "=" characters)
|
||||
|
||||
.. The content of this file will be included in the auto-generated HTML
|
||||
page for the module. You can link to it using the following
|
||||
reference: everest_modules_{{ info.name }}.
|
||||
|
||||
.. *******************************************
|
||||
.. {{ info.name }}
|
||||
.. *******************************************
|
||||
|
||||
{{ info.desc }}
|
||||
@@ -0,0 +1,194 @@
|
||||
{% from "helper_macros.j2" import handle_cmd_signature, publish_var_signature, list_json_types, var_to_cpp, var_to_any, print_template_info, print_spdx_line, string_to_enum, enum_to_string, enum_to_string_view %}
|
||||
{{ print_spdx_line('Apache-2.0') }}
|
||||
#ifndef {{ info.hpp_guard }}
|
||||
#define {{ info.hpp_guard }}
|
||||
|
||||
{{ print_template_info('6') }}
|
||||
|
||||
#include <framework/ModuleAdapter.hpp>
|
||||
#include <utils/types.hpp>
|
||||
#include <utils/error.hpp>
|
||||
#include <utils/error/error_state_monitor.hpp>
|
||||
#include <utils/error/error_manager_impl.hpp>
|
||||
#include <utils/error/error_factory.hpp>
|
||||
|
||||
#include "Types.hpp"
|
||||
|
||||
class {{ info.class_name }} : public Everest::ImplementationBase {
|
||||
public:
|
||||
{{ info.class_name }}(Everest::ModuleAdapter* ev, const std::string& name)
|
||||
: Everest::ImplementationBase(),
|
||||
_ev(ev),
|
||||
_name(name) {
|
||||
if (ev == nullptr) {
|
||||
EVLOG_error << "ev is nullptr, please check the initialization of the module";
|
||||
error_manager = nullptr;
|
||||
error_state_monitor = nullptr;
|
||||
error_factory = nullptr;
|
||||
EVLOG_error << "error_manager, error_state_monitor and error_factory are nullptr";
|
||||
} else {
|
||||
error_manager = ev->get_error_manager_impl(name);
|
||||
if (error_manager == nullptr) {
|
||||
EVLOG_error << "error_manager is nullptr";
|
||||
}
|
||||
error_state_monitor = ev->get_error_state_monitor_impl(name);
|
||||
if (error_state_monitor == nullptr) {
|
||||
EVLOG_error << "error_state_monitor is nullptr";
|
||||
}
|
||||
error_factory = ev->get_error_factory(name);
|
||||
if (error_factory == nullptr) {
|
||||
EVLOG_error << "error_factory is nullptr";
|
||||
}
|
||||
impl_mapping = Everest::get_impl_mapping(ev->get_mapping(), name);
|
||||
}
|
||||
}
|
||||
|
||||
{% if not vars %}
|
||||
// no variables defined for this interface
|
||||
{% else %}
|
||||
// publish functions for variables
|
||||
{% for var in vars %}
|
||||
void publish_{{ var.name }}(
|
||||
{%- if 'array_type' in var %}
|
||||
const std::vector<{{ var.array_type }}>&
|
||||
{%- elif 'object_type' in var %}
|
||||
{{- 'const '}} {{- var.object_type }}&
|
||||
{%- elif 'enum_type' in var %}
|
||||
{{- 'const '}} {{- var.enum_type }}&
|
||||
{%- else %}
|
||||
{{- 'const '}} {{- var.cpp_type }}&
|
||||
{%- endif -%}
|
||||
{{ ' value) {' }}
|
||||
{% if 'object_type' in var %}
|
||||
_ev->publish(_name, "{{ var.name }}", value);
|
||||
{% elif 'enum_type' in var %}
|
||||
_ev->publish(_name, "{{ var.name }}", {{ enum_to_string_view(var.enum_type) }}(value));
|
||||
{% elif 'array_type' in var %}
|
||||
{% if 'array_type_contains_enum' in var %}
|
||||
std::vector<std::string> string_array;
|
||||
string_array.reserve(value.size());
|
||||
for (const auto& entry : value) {
|
||||
string_array.push_back({{ enum_to_string(var.array_type) }}(entry));
|
||||
}
|
||||
_ev->publish(_name, "{{ var.name }}", string_array);
|
||||
{% else %}
|
||||
_ev->publish(_name, "{{ var.name }}", value);
|
||||
{% endif %}
|
||||
{% else %}
|
||||
_ev->publish(_name, "{{ var.name }}", value);
|
||||
{% endif %}
|
||||
}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
void raise_error(const Everest::error::Error& error) {
|
||||
error_manager->raise_error(error);
|
||||
}
|
||||
|
||||
void clear_error(const Everest::error::ErrorType& type) {
|
||||
error_manager->clear_error(type);
|
||||
}
|
||||
|
||||
void clear_error(const Everest::error::ErrorType& type, const Everest::error::ErrorSubType& sub_type) {
|
||||
error_manager->clear_error(type, sub_type);
|
||||
}
|
||||
|
||||
void clear_all_errors_of_impl() {
|
||||
error_manager->clear_all_errors();
|
||||
}
|
||||
|
||||
void clear_all_errors_of_impl(const Everest::error::ErrorType& type) {
|
||||
error_manager->clear_all_errors(type);
|
||||
}
|
||||
|
||||
std::shared_ptr<Everest::error::ErrorStateMonitor> error_state_monitor;
|
||||
std::shared_ptr<Everest::error::ErrorFactory> error_factory;
|
||||
std::shared_ptr<Everest::error::ErrorManagerImpl> error_manager;
|
||||
|
||||
std::optional<Mapping> get_mapping() {
|
||||
return impl_mapping;
|
||||
}
|
||||
|
||||
protected:
|
||||
{% if not cmds %}
|
||||
// no commands defined for this interface
|
||||
{% else %}
|
||||
// command handler functions (virtual)
|
||||
{% for cmd in cmds %}
|
||||
{{ handle_cmd_signature(cmd, none, info.interface_name) }} = 0;
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
private:
|
||||
Everest::ModuleAdapter* const _ev;
|
||||
const std::string _name;
|
||||
std::optional<Mapping> impl_mapping;
|
||||
|
||||
// helper function for getting all commands
|
||||
void _gather_cmds([[maybe_unused]] std::vector<Everest::cmd>& cmds) override {
|
||||
{% if not cmds %}
|
||||
// this interface does not offer any commands
|
||||
{% else %}
|
||||
{% for cmd in cmds %}
|
||||
// {{ cmd.name }} command
|
||||
Everest::cmd {{ cmd.name }}_cmd;
|
||||
{{ cmd.name }}_cmd.impl_id = _name;
|
||||
{{ cmd.name }}_cmd.cmd_name = "{{ cmd.name }}";
|
||||
{% if not cmd.args %}
|
||||
// cmd {{ cmd.name }} has no arguments
|
||||
{% else %}
|
||||
{{ cmd.name }}_cmd.arg_types = {
|
||||
{% for arg in cmd.args %}
|
||||
{"{{ arg.name }}", {{ list_json_types(arg.json_type) + '}' }}{{ ',' if not loop.last }}
|
||||
{% endfor %}
|
||||
};
|
||||
{% endif %}
|
||||
{{ cmd.name }}_cmd.cmd = [this](const Parameters& args) -> Result {
|
||||
{% for arg in cmd.args %}
|
||||
{% if 'object_type' in arg %}
|
||||
auto {{ arg.name }} = args.at("{{ arg.name }}").get<{{ arg.object_type }}>();
|
||||
{% elif 'enum_type' in arg %}
|
||||
auto {{ arg.name }} = {{ string_to_enum(arg.enum_type) }}(args.at("{{ arg.name }}").get<std::string>());
|
||||
{% elif 'array_type' in arg %}
|
||||
json {{ arg.name }}_json_array = args["{{ arg.name }}"];
|
||||
{% if 'array_type_contains_enum' in arg %}
|
||||
std::vector<{{ arg.array_type }}> {{ arg.name }};
|
||||
for (auto entry : {{ arg.name }}_json_array) {
|
||||
{{ arg.name }}.push_back({{ string_to_enum(arg.array_type) }}(entry));
|
||||
}
|
||||
{% else %}
|
||||
auto {{ arg.name }} = args.at("{{ arg.name }}").get<std::vector<{{ arg.array_type }}>>();
|
||||
{% endif %}
|
||||
{% else %}
|
||||
auto {{ arg.name }} = {{ var_to_cpp(arg) }}(args.at("{{ arg.name }}"));
|
||||
{% endif %}
|
||||
{% else %}
|
||||
(void) args; // no arguments used for this callback
|
||||
{% endfor %}
|
||||
|
||||
{{ 'auto result = ' if cmd.result }}this->handle_{{ cmd.name }}(
|
||||
{%- for arg in cmd.args -%}
|
||||
{{ arg.name }}{{ ', ' if not loop.last }}
|
||||
{%- endfor -%}
|
||||
);
|
||||
{% if cmd.result and 'object_type' in cmd.result %}
|
||||
return result;
|
||||
{% elif cmd.result and 'enum_type' in cmd.result %}
|
||||
return {{ enum_to_string(cmd.result.enum_type) }}(result);
|
||||
{% else %}
|
||||
return {{ var_to_any(cmd.result, 'result') if cmd.result else 'nullptr'}};
|
||||
{% endif %}
|
||||
};
|
||||
{% if cmd.result %}
|
||||
{{ cmd.name }}_cmd.return_type = {{ list_json_types(cmd.result.json_type) }};
|
||||
{% endif %}
|
||||
cmds.emplace_back(std::move({{ cmd.name }}_cmd));
|
||||
{% if not loop.last %}
|
||||
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
};
|
||||
};
|
||||
|
||||
#endif // {{ info.hpp_guard }}
|
||||
@@ -0,0 +1,163 @@
|
||||
{% from "helper_macros.j2" import call_cmd_signature, var_to_any, var_to_cpp, print_template_info, cpp_type, result_type, print_spdx_line, string_to_enum, enum_to_string %}
|
||||
{{ print_spdx_line('Apache-2.0') }}
|
||||
#ifndef {{ info.hpp_guard }}
|
||||
#define {{ info.hpp_guard }}
|
||||
|
||||
{{ print_template_info('5') }}
|
||||
|
||||
#include <framework/ModuleAdapter.hpp>
|
||||
#include <utils/types.hpp>
|
||||
#include <utils/error.hpp>
|
||||
#include <utils/error/error_state_monitor.hpp>
|
||||
#include <utils/error/error_manager_req.hpp>
|
||||
|
||||
#include "Types.hpp"
|
||||
|
||||
class {{ info.class_name }} {
|
||||
public:
|
||||
{{ info.class_name }}(Everest::ModuleAdapter* adapter, Requirement req, const std::string& module_id, std::optional<Mapping> mapping)
|
||||
: module_id(module_id),
|
||||
_adapter(adapter),
|
||||
_req(req),
|
||||
_mapping(mapping) {
|
||||
if (adapter == nullptr) {
|
||||
EVLOG_error << "adapter is nullptr, please check the initialization of the module";
|
||||
error_manager = nullptr;
|
||||
error_state_monitor = nullptr;
|
||||
EVLOG_error << "error_manager and error_state_monitor are nullptr";
|
||||
} else {
|
||||
error_manager = adapter->get_error_manager_req(req);
|
||||
if (error_manager == nullptr) {
|
||||
EVLOG_error << "error_manager is nullptr";
|
||||
}
|
||||
error_state_monitor = adapter->get_error_state_monitor_req(req);
|
||||
if (error_state_monitor == nullptr) {
|
||||
EVLOG_error << "error_state_monitor is nullptr";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const std::string module_id;
|
||||
|
||||
{% if not vars %}
|
||||
// this interface does not export any variables to subscribe to
|
||||
{% else %}
|
||||
// variables available for subscription
|
||||
{% for var in vars %}
|
||||
void subscribe_{{ var.name }}(const std::function<void({% if var.json_type != 'null' %}const {{ cpp_type(var) }}&{% endif %})>& listener) {
|
||||
_adapter->subscribe(_req, "{{ var.name }}", [func = std::move(listener)](const Value& value) {
|
||||
{% if 'object_type' in var %}
|
||||
func(value);
|
||||
{% elif 'enum_type' in var %}
|
||||
func({{ string_to_enum(var.enum_type) }}({{ var_to_cpp(var) }}(value)));
|
||||
{% elif 'array_type' in var %}
|
||||
{% if 'array_type_contains_enum' in var %}
|
||||
std::vector<{{ var.array_type }}> typed_value;
|
||||
for (auto& entry : value) {
|
||||
typed_value.push_back({{ string_to_enum(var.array_type) }}(entry));
|
||||
}
|
||||
func(typed_value);
|
||||
{% else %}
|
||||
func(value);
|
||||
{% endif %}
|
||||
{% elif var.json_type != 'null' %}
|
||||
func({{ var_to_cpp(var) }}(value));
|
||||
{% else %}
|
||||
if (not Everest::detail::is_type_compatible<{{ cpp_type(var) }}>(value.type())) {
|
||||
EVLOG_error << "Callback for variable '{{ var.name }}' in interface '{{ info.interface }}' has wrong type!";
|
||||
}
|
||||
func();
|
||||
{% endif %}
|
||||
});
|
||||
}
|
||||
{% if not loop.last %}
|
||||
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
void subscribe_error(
|
||||
const Everest::error::ErrorType& type,
|
||||
const Everest::error::ErrorCallback& callback,
|
||||
const Everest::error::ErrorCallback& clear_callback
|
||||
) {
|
||||
error_manager->subscribe_error(type, callback, clear_callback);
|
||||
}
|
||||
|
||||
void subscribe_all_errors(
|
||||
const Everest::error::ErrorCallback& callback,
|
||||
const Everest::error::ErrorCallback& clear_callback
|
||||
) {
|
||||
error_manager->subscribe_all_errors(callback, clear_callback);
|
||||
}
|
||||
|
||||
{% if not cmds %}
|
||||
// this interface does not export any commands to call
|
||||
{% else %}
|
||||
// commands available to call
|
||||
{% for cmd in cmds %}
|
||||
{{ call_cmd_signature(cmd, info.interface_name) }} {
|
||||
{% for arg in cmd.args %}
|
||||
{% if 'array_type' in arg %}
|
||||
{% if 'array_type_contains_enum' in arg %}
|
||||
Array {{ arg.name }}_array;
|
||||
for (const auto& {{ arg.name }}_entry : {{ arg.name }}) {
|
||||
{{ arg.name }}_array.push_back({{ enum_to_string(arg.array_type) }}({{ arg.name }}_entry));
|
||||
}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{{ '' }}{% if cmd.result %}Result result = {% endif %}_adapter->call(_req, "{{ cmd.name }}",
|
||||
{{ 'Parameters{' }}
|
||||
{% for arg in cmd.args %}
|
||||
{% if 'enum_type' in arg %}
|
||||
{"{{ arg.name }}", {{ enum_to_string(arg.enum_type) }}({{ arg.name }})}
|
||||
{% elif 'object_type' in arg %}
|
||||
{"{{ arg.name }}", {{ arg.name }}}
|
||||
{% elif 'array_type' in arg %}
|
||||
{% if 'array_type_contains_enum' in arg %}
|
||||
{"{{ arg.name }}", {{ var_to_any(arg, arg.name + '_array') }}}
|
||||
{% else %}
|
||||
{"{{ arg.name }}", {{ var_to_any(arg, arg.name) }}}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{"{{ arg.name }}", {{ var_to_any(arg, arg.name) }}}
|
||||
{% endif%}
|
||||
{% if not loop.last %},{% endif %}
|
||||
{% endfor %}
|
||||
{{ '}' }}
|
||||
);
|
||||
{% if cmd.result %}
|
||||
{% if 'enum_type' in cmd.result %}
|
||||
auto retval = {{ string_to_enum(cmd.result.enum_type) }}({{ var_to_cpp(cmd.result) }}(result.value()));
|
||||
{% elif 'object_type' in cmd.result %}
|
||||
json retval_json = result.value();
|
||||
{{ result_type(cmd.result) }} retval = retval_json;
|
||||
{% elif 'array_type' in cmd.result %}
|
||||
{{ result_type(cmd.result) }} retval (result.value().begin(), result.value().end());
|
||||
{% else %}
|
||||
auto retval = {{ var_to_cpp(cmd.result) }}(result.value());
|
||||
{% endif %}
|
||||
return retval;
|
||||
{% endif %}
|
||||
}
|
||||
{% if not loop.last %}
|
||||
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
std::shared_ptr<Everest::error::ErrorStateMonitor> error_state_monitor;
|
||||
|
||||
std::optional<Mapping> get_mapping() {
|
||||
return _mapping;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<Everest::error::ErrorManagerReq> error_manager;
|
||||
Everest::ModuleAdapter* const _adapter;
|
||||
Requirement _req;
|
||||
std::optional<Mapping> _mapping;
|
||||
};
|
||||
|
||||
#endif // {{ info.hpp_guard }}
|
||||
@@ -0,0 +1,27 @@
|
||||
{% from "helper_macros.j2" import handle_cmd_signature, print_license_header %}
|
||||
{{ print_license_header(info.license_header) }}
|
||||
|
||||
#include "{{ info.class_name}}.hpp"
|
||||
|
||||
namespace module {
|
||||
namespace {{ info.interface_implementation_id }} {
|
||||
|
||||
void {{ info.class_name }}::init() {
|
||||
|
||||
}
|
||||
|
||||
void {{ info.class_name }}::ready() {
|
||||
|
||||
}
|
||||
{% for cmd in cmds %}
|
||||
|
||||
{{ handle_cmd_signature(cmd, info.class_name, '::'+info.interface) }}{
|
||||
// your code for cmd {{ cmd.name }} goes here
|
||||
{% if cmd.result %}
|
||||
return {{ cmd.result.json_type|create_dummy_result }};
|
||||
{% endif %}
|
||||
}
|
||||
{% endfor %}
|
||||
|
||||
} // namespace {{ info.interface_implementation_id }}
|
||||
} // namespace module
|
||||
@@ -0,0 +1,61 @@
|
||||
{% from "helper_macros.j2" import handle_cmd_signature, print_template_info, insert_block, cpp_type, print_license_header %}
|
||||
{{ print_license_header(info.license_header) }}
|
||||
#ifndef {{ info.hpp_guard }}
|
||||
#define {{ info.hpp_guard }}
|
||||
|
||||
{{ print_template_info('3', 'marked regions will be kept') }}
|
||||
|
||||
#include <{{ info.base_class_header }}>
|
||||
|
||||
#include "{{ info.module_header }}"
|
||||
|
||||
{{ insert_block(info.blocks.add_headers) }}
|
||||
|
||||
namespace module {
|
||||
namespace {{ info.interface_implementation_id }} {
|
||||
|
||||
struct Conf {
|
||||
{% for item in info.config %}
|
||||
{{ cpp_type(item) }} {{ item.name }};
|
||||
{% endfor %}
|
||||
};
|
||||
|
||||
class {{ info.class_name }} : public {{ info.class_parent}} {
|
||||
public:
|
||||
{{ info.class_name }}() = delete;
|
||||
{{ info.class_name }}(Everest::ModuleAdapter* ev, const Everest::PtrContainer<{{ info.module_class }}> &mod, Conf& config) :
|
||||
{{ info.class_parent }}(ev, "{{ info.interface_implementation_id }}"),
|
||||
mod(mod),
|
||||
config(config)
|
||||
{};
|
||||
|
||||
{{ insert_block(info.blocks.public_defs, indent=4) }}
|
||||
|
||||
protected:
|
||||
{% if not cmds %}
|
||||
// no commands defined for this interface
|
||||
{% else %}
|
||||
// command handler functions (virtual)
|
||||
{% for cmd in cmds %}
|
||||
{{ handle_cmd_signature(cmd, none, '::'+info.interface) }} override;
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{{ insert_block(info.blocks.protected_defs, indent=4) }}
|
||||
|
||||
private:
|
||||
const Everest::PtrContainer<{{ info.module_class }}>& mod;
|
||||
const Conf& config;
|
||||
|
||||
virtual void init() override;
|
||||
virtual void ready() override;
|
||||
|
||||
{{ insert_block(info.blocks.private_defs, indent=4) }}
|
||||
};
|
||||
|
||||
{{ insert_block(info.blocks.after_class) }}
|
||||
|
||||
} // namespace {{ info.interface_implementation_id }}
|
||||
} // namespace module
|
||||
|
||||
#endif // {{ info.hpp_guard }}
|
||||
@@ -0,0 +1,172 @@
|
||||
{% from "helper_macros.j2" import print_template_info, var_to_cpp, print_spdx_line %}
|
||||
{{ print_spdx_line('Apache-2.0') }}
|
||||
{{ print_template_info('5') }}
|
||||
|
||||
#include "{{ info.ld_ev_header }}"
|
||||
|
||||
#ifdef EVEREST_COVERAGE_ENABLED
|
||||
#include <everest/helpers/coverage.hpp>
|
||||
#endif
|
||||
|
||||
#include "{{ info.module_header }}"
|
||||
{% for impl in provides %}
|
||||
#include "{{ impl.class_header }}"
|
||||
{% endfor %}
|
||||
|
||||
#include <framework/runtime.hpp>
|
||||
#include <utils/types.hpp>
|
||||
#include <generated/version_information.hpp>
|
||||
#ifndef PROJECT_NAME
|
||||
#define PROJECT_NAME "undefined project"
|
||||
#endif
|
||||
#ifndef PROJECT_VERSION
|
||||
#define PROJECT_VERSION "undefined version"
|
||||
#endif
|
||||
#ifndef GIT_VERSION
|
||||
#define GIT_VERSION "undefined git version"
|
||||
#endif
|
||||
|
||||
{% if info.enable_global_errors %}
|
||||
#include <utils/error/error_manager_req_global.hpp>
|
||||
|
||||
{% endif %}
|
||||
namespace module {
|
||||
|
||||
// FIXME (aw): could this way of keeping static variables be changed somehow?
|
||||
static Everest::ModuleAdapter adapter {};
|
||||
static Everest::PtrContainer<{{info.class_name }}> mod_ptr {};
|
||||
|
||||
// per module configs
|
||||
{# FIXME (aw): instead of being static, this could also be inside a map #}
|
||||
{% for impl in provides %}
|
||||
static {{ impl.id }}::Conf {{ impl.id }}_config;
|
||||
{% endfor %}
|
||||
static Conf module_conf;
|
||||
static ModuleInfo module_info;
|
||||
|
||||
{% if info.enable_global_errors %}
|
||||
void subscribe_global_all_errors(
|
||||
const Everest::error::ErrorCallback& callback,
|
||||
const Everest::error::ErrorCallback& clear_callback
|
||||
) {
|
||||
adapter.get_global_error_manager()->subscribe_global_all_errors(callback, clear_callback);
|
||||
}
|
||||
|
||||
std::shared_ptr<Everest::error::ErrorStateMonitor> get_global_error_state_monitor() {
|
||||
return adapter.get_global_error_state_monitor();
|
||||
}
|
||||
|
||||
{% endif %}
|
||||
|
||||
std::shared_ptr<Everest::config::ConfigServiceClient> get_config_service_client() {
|
||||
return adapter.get_config_service_client();
|
||||
}
|
||||
|
||||
void LdEverest::init(ModuleConfigs module_configs, const ModuleInfo& mod_info) {
|
||||
EVLOG_debug << "init() called on module {{ info.name }}";
|
||||
|
||||
// populate config for provided implementations
|
||||
{% for impl in provides %}
|
||||
auto {{ impl.id }}_config_input = std::move(module_configs["{{ impl.id }}"]);
|
||||
{% for item in impl.config %}
|
||||
{{ impl.id }}_config.{{ item.name }} = std::get<{{ item.cpp_type }}>({{ impl.id }}_config_input["{{ item.name }}"]);
|
||||
{% endfor %}
|
||||
|
||||
{% endfor %}
|
||||
{% if info.module_config|length %}
|
||||
|
||||
{% for item in info.module_config %}
|
||||
module_conf.{{ item.name }} = std::get<{{ item.cpp_type }}>(module_configs["!module"]["{{ item.name }}"]);
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
module_info = mod_info;
|
||||
|
||||
mod_ptr->init();
|
||||
}
|
||||
|
||||
void LdEverest::ready() {
|
||||
EVLOG_debug << "ready() called on module {{ info.name }}";
|
||||
mod_ptr->ready();
|
||||
}
|
||||
|
||||
void register_module_adapter(Everest::ModuleAdapter module_adapter) {
|
||||
adapter = std::move(module_adapter);
|
||||
}
|
||||
|
||||
std::vector<Everest::cmd> everest_register(const RequirementInitialization& requirement_init) {
|
||||
EVLOG_debug << "everest_register() called on module {{ info.name }}";
|
||||
|
||||
adapter.check_complete();
|
||||
|
||||
{% for impl in provides %}
|
||||
auto p_{{ impl.id }} = std::make_unique<{{ impl.id }}::{{ impl.class_name }}>(&adapter, mod_ptr, {{ impl.id }}_config);
|
||||
adapter.gather_cmds(*p_{{ impl.id }});
|
||||
|
||||
{% endfor %}
|
||||
{% for requirement in requires %}
|
||||
{# FIXME: needs refactoring #}
|
||||
{% if requirement.is_vector %}
|
||||
auto r_{{ requirement.id }} = std::vector<std::unique_ptr<{{ requirement.class_name }}>>();
|
||||
if (auto it = requirement_init.find("{{ requirement.id }}"); it != requirement_init.end()) {
|
||||
for (const auto& requirement_initializer : (*it).second) {
|
||||
auto requirement_module_id = requirement_initializer.fulfillment.module_id;
|
||||
auto requirement = requirement_initializer.requirement;
|
||||
auto mapping = requirement_initializer.mapping;
|
||||
r_{{ requirement.id }}.emplace_back(std::make_unique<{{ requirement.class_name }}>(&adapter, requirement, requirement_module_id, mapping));
|
||||
}
|
||||
}
|
||||
{% else %}
|
||||
std::string r_{{ requirement.id }}_requirement_module_id;
|
||||
Requirement r_{{ requirement.id }}_requirement;
|
||||
std::optional<Mapping> r_{{ requirement.id }}_mapping;
|
||||
if (auto it = requirement_init.find("{{ requirement.id }}"); it != requirement_init.end()) {
|
||||
auto requirement_initializer = (*it).second;
|
||||
if (requirement_initializer.size() > 0) {
|
||||
r_{{ requirement.id }}_requirement_module_id = requirement_initializer.at(0).fulfillment.module_id;
|
||||
r_{{ requirement.id }}_requirement = requirement_initializer.at(0).requirement;
|
||||
r_{{ requirement.id }}_mapping = requirement_initializer.at(0).mapping;
|
||||
}
|
||||
}
|
||||
auto r_{{ requirement.id }} = std::make_unique<{{ requirement.class_name }}>(&adapter, r_{{ requirement.id }}_requirement, r_{{ requirement.id }}_requirement_module_id, r_{{ requirement.id }}_mapping);
|
||||
{% endif %}
|
||||
{% else %}
|
||||
(void) requirement_init; // no requirements -> unused requirement initialization
|
||||
{% endfor %}
|
||||
|
||||
{% if info.enable_external_mqtt %}
|
||||
static Everest::MqttProvider mqtt_provider(adapter);
|
||||
{% endif %}
|
||||
static Everest::TelemetryProvider telemetry_provider(adapter);
|
||||
|
||||
static {{ info.class_name }} module(
|
||||
module_info,
|
||||
{%- if info.enable_external_mqtt %}mqtt_provider, {% endif -%}
|
||||
{%- if info.enable_telemetry %}telemetry_provider, {% endif -%}
|
||||
{%- for impl in provides -%}
|
||||
std::move(p_{{ impl.id }}){{ ', ' }}
|
||||
{%- endfor -%}
|
||||
{%- for requirement in requires -%}
|
||||
std::move(r_{{ requirement.id }}){{ ', ' }}
|
||||
{%- endfor -%}
|
||||
module_conf);
|
||||
|
||||
mod_ptr.set(&module);
|
||||
|
||||
return adapter.registered_commands;
|
||||
}
|
||||
|
||||
} // namespace module
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
#ifdef EVEREST_COVERAGE_ENABLED
|
||||
everest::helpers::install_signal_handlers_for_gcov();
|
||||
#endif
|
||||
|
||||
auto module_loader = Everest::ModuleLoader(argc, argv, Everest::ModuleCallbacks(
|
||||
module::register_module_adapter, module::everest_register,
|
||||
module::LdEverest::init, module::LdEverest::ready),
|
||||
{PROJECT_NAME, PROJECT_VERSION, GIT_VERSION});
|
||||
|
||||
return module_loader.initialize();
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
{% from "helper_macros.j2" import print_template_info, print_spdx_line %}
|
||||
{{ print_spdx_line('Apache-2.0') }}
|
||||
#ifndef {{ info.hpp_guard }}
|
||||
#define {{ info.hpp_guard }}
|
||||
|
||||
{{ print_template_info('3') }}
|
||||
|
||||
#include <framework/ModuleAdapter.hpp>
|
||||
#include <framework/everest.hpp>
|
||||
|
||||
#include <everest/logging.hpp>
|
||||
|
||||
namespace module {
|
||||
|
||||
// helper class for invoking private functions on module
|
||||
struct LdEverest {
|
||||
static void init(ModuleConfigs module_configs, const ModuleInfo& info);
|
||||
static void ready();
|
||||
};
|
||||
|
||||
void register_module_adapter(Everest::ModuleAdapter module_adapter);
|
||||
|
||||
std::vector<Everest::cmd> everest_register(const RequirementInitialization& requirement_init);
|
||||
|
||||
{% if info.enable_global_errors %}
|
||||
void subscribe_global_all_errors(
|
||||
const Everest::error::ErrorCallback& callback,
|
||||
const Everest::error::ErrorCallback& clear_callback
|
||||
);
|
||||
std::shared_ptr<Everest::error::ErrorStateMonitor> get_global_error_state_monitor();
|
||||
{% endif %}
|
||||
|
||||
std::shared_ptr<Everest::config::ConfigServiceClient> get_config_service_client();
|
||||
|
||||
} // namespace module
|
||||
|
||||
#endif // {{ info.hpp_guard }}
|
||||
@@ -0,0 +1,19 @@
|
||||
{% from "helper_macros.j2" import print_license_header %}
|
||||
{{ print_license_header(info.license_header) }}
|
||||
#include "{{ info.module_header }}"
|
||||
|
||||
namespace module {
|
||||
|
||||
void {{ info.class_name }}::init() {
|
||||
{% for impl in provides %}
|
||||
invoke_init(*p_{{ impl.id }});
|
||||
{% endfor %}
|
||||
}
|
||||
|
||||
void {{ info.class_name }}::ready() {
|
||||
{% for impl in provides %}
|
||||
invoke_ready(*p_{{ impl.id }});
|
||||
{% endfor %}
|
||||
}
|
||||
|
||||
} // namespace module
|
||||
@@ -0,0 +1,109 @@
|
||||
{% from "helper_macros.j2" import print_template_info, insert_block, cpp_type, print_license_header %}
|
||||
{{ print_license_header(info.license_header) }}
|
||||
#ifndef {{ info.hpp_guard }}
|
||||
#define {{ info.hpp_guard }}
|
||||
|
||||
{{ print_template_info('2', 'marked regions will be kept') }}
|
||||
|
||||
#include "{{ info.ld_ev_header }}"
|
||||
|
||||
{% for impl in provides %}
|
||||
{% if loop.first %}
|
||||
// headers for provided interface implementations
|
||||
{% endif %}
|
||||
#include <{{ impl.base_class_header }}>
|
||||
{% endfor %}
|
||||
|
||||
{% for interface in requires %}
|
||||
{% if loop.first %}
|
||||
// headers for required interface implementations
|
||||
{% endif %}
|
||||
#include <{{ interface.exports_header }}>
|
||||
{% endfor %}
|
||||
|
||||
{{ insert_block(info.blocks.add_headers) }}
|
||||
|
||||
namespace module {
|
||||
|
||||
struct Conf {
|
||||
{% for item in info.module_config %}
|
||||
{{ cpp_type(item) }} {{ item.name }};
|
||||
{% endfor %}
|
||||
};
|
||||
|
||||
class {{ info.class_name }} : public Everest::ModuleBase {
|
||||
public:
|
||||
{{ info.class_name }}() = delete;
|
||||
{{ info.class_name }}(
|
||||
const ModuleInfo& info,
|
||||
{% if info.enable_external_mqtt %}
|
||||
Everest::MqttProvider& mqtt_provider,
|
||||
{% endif %}
|
||||
{% if info.enable_telemetry %}
|
||||
Everest::TelemetryProvider& telemetry,
|
||||
{% endif %}
|
||||
{% for impl in provides %}
|
||||
std::unique_ptr<{{ impl.base_class }}> p_{{ impl.id }},
|
||||
{% endfor %}
|
||||
{% for requirement in requires %}
|
||||
{% if requirement.is_vector %}
|
||||
std::vector<std::unique_ptr<{{ requirement.class_name }}>> r_{{ requirement.id }},
|
||||
{% else %}
|
||||
std::unique_ptr<{{ requirement.class_name }}> r_{{ requirement.id }},
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
Conf& config
|
||||
) :
|
||||
ModuleBase(info),
|
||||
{% if info.enable_external_mqtt %}
|
||||
mqtt(mqtt_provider),
|
||||
{% endif %}
|
||||
{% if info.enable_telemetry %}
|
||||
telemetry(telemetry),
|
||||
{% endif %}
|
||||
{% for impl in provides %}
|
||||
p_{{ impl.id }}(std::move(p_{{ impl.id }})),
|
||||
{% endfor %}
|
||||
{% for requirement in requires %}
|
||||
r_{{ requirement.id }}(std::move(r_{{ requirement.id }})),
|
||||
{% endfor %}
|
||||
config(config)
|
||||
{};
|
||||
|
||||
{% if info.enable_external_mqtt %}
|
||||
Everest::MqttProvider& mqtt;
|
||||
{% endif %}
|
||||
{% if info.enable_telemetry %}
|
||||
Everest::TelemetryProvider& telemetry;
|
||||
{% endif %}
|
||||
{% for impl in provides %}
|
||||
const std::unique_ptr<{{ impl.base_class }}> p_{{ impl.id }};
|
||||
{% endfor %}
|
||||
{% for requirement in requires %}
|
||||
{% if requirement.is_vector %}
|
||||
const std::vector<std::unique_ptr<{{ requirement.class_name }}>> r_{{ requirement.id }};
|
||||
{% else %}
|
||||
const std::unique_ptr<{{ requirement.class_name }}> r_{{ requirement.id }};
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
const Conf& config;
|
||||
|
||||
{{ insert_block(info.blocks.public_defs, indent=4) }}
|
||||
|
||||
protected:
|
||||
{{ insert_block(info.blocks.protected_defs, indent=4) }}
|
||||
|
||||
private:
|
||||
friend class LdEverest;
|
||||
void init();
|
||||
void ready();
|
||||
|
||||
{{ insert_block(info.blocks.private_defs, indent=4) }}
|
||||
|
||||
};
|
||||
|
||||
{{ insert_block(info.blocks.after_class) }}
|
||||
|
||||
} // namespace module
|
||||
|
||||
#endif // {{ info.hpp_guard }}
|
||||
@@ -0,0 +1,243 @@
|
||||
{% from "helper_macros.j2" import print_template_info, print_spdx_line, string_to_enum, enum_to_string, enum_to_string_view %}
|
||||
{{ print_spdx_line('Apache-2.0') }}
|
||||
#ifndef {{ info.hpp_guard }}
|
||||
#define {{ info.hpp_guard }}
|
||||
|
||||
{{ print_template_info('5') }}
|
||||
|
||||
#include <optional>
|
||||
#include <ostream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
{% if 'type_headers' in info %}
|
||||
{% for type_header in info.type_headers %}
|
||||
#include <{{type_header}}>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
using nlohmann::json;
|
||||
|
||||
{% if not enums %}
|
||||
// no enums defined for this interface
|
||||
{% else %}
|
||||
|
||||
// enums of {{ info.interface_name }}
|
||||
|
||||
{% for enum in enums %}
|
||||
{% for namespace in info.namespace %}
|
||||
namespace {{ namespace }} {
|
||||
{% endfor %}
|
||||
enum class {{ enum.enum_type}}
|
||||
{
|
||||
{% for e in enum.enum %}
|
||||
{{e}},
|
||||
{% endfor %}
|
||||
};
|
||||
|
||||
/// \brief Converts the given {{ enum.enum_type }} \p e to human readable string
|
||||
/// \returns a string representation of the {{ enum.enum_type }}
|
||||
inline std::string {{ enum.enum_type | snake_case }}_to_string(const {{ enum.enum_type }}& e) {
|
||||
switch (e) {
|
||||
{% for e in enum.enum %}
|
||||
case {{ enum.enum_type }}::{{ e }}: return "{{e}}";
|
||||
{% endfor %}
|
||||
}
|
||||
|
||||
throw std::out_of_range("No known string conversion for provided enum of type {{ enum.enum_type }}");
|
||||
}
|
||||
|
||||
/// \brief Converts the given {{ enum.enum_type }} \p e to human readable string
|
||||
/// \returns a string_view representation of the {{ enum.enum_type }}
|
||||
inline constexpr std::string_view {{ enum.enum_type | snake_case }}_to_string_view(const {{ enum.enum_type }}& e) {
|
||||
switch (e) {
|
||||
{% for e in enum.enum %}
|
||||
case {{ enum.enum_type }}::{{ e }}: return "{{e}}";
|
||||
{% endfor %}
|
||||
}
|
||||
|
||||
throw std::out_of_range("No known string conversion for provided enum of type {{ enum.enum_type }}");
|
||||
}
|
||||
|
||||
/// \brief Converts the given std::string \p s to {{ enum.enum_type }}
|
||||
/// \returns a {{ enum.enum_type }} from a string representation
|
||||
inline {{ enum.enum_type }} string_to_{{ enum.enum_type | snake_case }}(const std::string& s) {
|
||||
{% for e in enum.enum %}
|
||||
if (s == "{{e}}") {
|
||||
return {{ enum.enum_type }}::{{ e }};
|
||||
}
|
||||
{% endfor %}
|
||||
|
||||
throw std::out_of_range("Provided string " + s + " could not be converted to enum of type {{ enum.enum_type }}");
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given {{ enum.enum_type }} \p {{ enum.enum_type | snake_case }} to the given output stream \p os
|
||||
/// \returns an output stream with the {{ enum.enum_type }} written to
|
||||
inline std::ostream& operator<<(std::ostream& os, const types::{{ info.interface_name }}::{{ enum.enum_type }}& {{ enum.enum_type | snake_case }}) {
|
||||
os << types::{{info.interface_name}}::{{ enum.enum_type | snake_case }}_to_string({{ enum.enum_type | snake_case }});
|
||||
return os;
|
||||
}
|
||||
|
||||
{% for namespace in info.namespace|reverse %}
|
||||
} // namespace {{namespace}}
|
||||
{% endfor %}
|
||||
|
||||
{% endfor %}
|
||||
{% endif%}
|
||||
|
||||
{% if not types %}
|
||||
// no types defined for this interface
|
||||
{% else %}
|
||||
// types of {{ info.interface_name }}
|
||||
{% for namespace in info.namespace %}
|
||||
namespace {{ namespace }} {
|
||||
{% endfor %}
|
||||
|
||||
{% for parsed_type in types %}
|
||||
{% if parsed_type.properties|length > 0 %}
|
||||
struct {{ parsed_type.name }} {
|
||||
{% for property in parsed_type.properties %}
|
||||
{# Constraints will be checked by the framework #}
|
||||
{{ 'std::optional<' if not property.required -}}
|
||||
{{ property.type -}}
|
||||
{{ '>' if not property.required -}}
|
||||
{{ ' ' + property.name + ';' }} ///< {{ property.info.description }}
|
||||
{% endfor %}
|
||||
|
||||
/// \brief Conversion from a given {{ parsed_type.name }} \p k to a given json object \p j
|
||||
friend void to_json(json& j, const {{ parsed_type.name }}& k) {
|
||||
// the required parts of the type
|
||||
{% if parsed_type.properties|selectattr('required')|list|length %}
|
||||
j = json{
|
||||
{%- endif %}
|
||||
{%- for property in parsed_type.properties %}
|
||||
{%- if property.required +%}
|
||||
{"{{property.name}}",
|
||||
{%- if property.enum %} {{ enum_to_string_view(property.type) }}(k.{{ property.name }})
|
||||
{%- else %}
|
||||
{%- if property.type == 'DateTime' %} k.{{property.name}}.to_rfc3339()
|
||||
{%- else %} k.{{property.name}}
|
||||
{%- endif %}
|
||||
{%- endif %}},
|
||||
{%- endif %}
|
||||
{%- endfor %}
|
||||
{% if not parsed_type.properties|selectattr('required')|list|length %}
|
||||
j = json ({});
|
||||
{%- else +%}
|
||||
};
|
||||
{%- endif %}
|
||||
|
||||
// the optional parts of the type
|
||||
{% for property in parsed_type.properties %}
|
||||
{% if not property.required %}
|
||||
if (k.{{property.name}}) {
|
||||
{% if property.type.startswith('std::vector<') %}
|
||||
{%- if parsed_type.properties|selectattr('required')|list|length %}
|
||||
j.emplace("{{property.name}}", *k.{{property.name}});
|
||||
{%- else %}
|
||||
{#only optional keys in json#}
|
||||
{#TODO: add key to json when there are no required keys but multiple optional keys#}
|
||||
{# FIXME: this is never generated?#}
|
||||
if (j.size() == 0) {
|
||||
j = json{{'{{"'+property.name+'", json::array()}};'}}
|
||||
} else {
|
||||
j.emplace("{{property.name}}", *k.{{property.name}});
|
||||
}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{%- if property.enum %}
|
||||
j.emplace("{{property.name}}", {{ enum_to_string_view(property.type) }}(*k.{{ property.name }}));
|
||||
{%- else %}
|
||||
{%- if property.type == 'DateTime' %}
|
||||
j.emplace("{{property.name}}", k.{{property.name}}->to_rfc3339());
|
||||
{%- else %}
|
||||
j.emplace("{{property.name}}", *k.{{property.name}});
|
||||
{%- endif %}
|
||||
{%- endif %}
|
||||
{% endif %}
|
||||
|
||||
}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
}
|
||||
|
||||
/// \brief Conversion from a given json object \p j to a given {{ parsed_type.name }} \p k
|
||||
friend void from_json(const json& j, {{ parsed_type.name }}& k) {
|
||||
// the required parts of the type
|
||||
{% for property in parsed_type.properties %}
|
||||
{% if property.required %}
|
||||
{% if property.type.startswith('std::vector<') %}
|
||||
k.{{property.name}} = std::move(j.at("{{property.name}}").get<{{property.type}}>());
|
||||
{% else %}
|
||||
k.{{property.name}} =
|
||||
{%- if property.enum %} {{ string_to_enum(property.type) }}(j.at("{{property.name}}"))
|
||||
{%- else %}
|
||||
{%- if property.type == 'DateTime' %} DateTime(std::string(j.at("{{property.name}}")));
|
||||
{%- else %} j.at("{{property.name}}")
|
||||
{%- endif %}
|
||||
{%- endif %};
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{%- endfor %}
|
||||
|
||||
// the optional parts of the type
|
||||
auto it = j.end();
|
||||
{% for property in parsed_type.properties %}
|
||||
{% if not property.required %}
|
||||
it = it = j.find("{{property.name}}");
|
||||
if (it != j.end()) {
|
||||
{% if property.type.startswith('std::vector<') %}
|
||||
k.{{property.name}} = j.at("{{property.name}}").get<{{property.type}}>();
|
||||
{% else %}
|
||||
{%- if property.enum %}
|
||||
k.{{property.name}} = {{ string_to_enum(property.type) }}(it->get<std::string>());
|
||||
{%- else %}
|
||||
k.{{property.name}} = it->get<{{property.type}}>();
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
}
|
||||
|
||||
/// \brief Compares objects of type {{ parsed_type.name }} for equality
|
||||
friend constexpr bool operator==(const {{ parsed_type.name }}& k, const {{ parsed_type.name }}& l) {
|
||||
{%- for var, tuple in [('k.', 'lhs'), ('l.', 'rhs')] +%}
|
||||
const auto& {{ tuple }}_tuple = std::tie(
|
||||
{%- for property in parsed_type.properties +%}
|
||||
{{ var }}{{ property.name }}
|
||||
{%- if not loop.last %},
|
||||
{%- endif %}
|
||||
{%- endfor +%}
|
||||
);
|
||||
{%- endfor +%}
|
||||
return lhs_tuple == rhs_tuple;
|
||||
}
|
||||
|
||||
/// \brief Compares objects of type {{ parsed_type.name }} for inequality
|
||||
friend constexpr bool operator!=(const {{ parsed_type.name }}& k, const {{ parsed_type.name }}& l) {
|
||||
return not operator==(k, l);
|
||||
}
|
||||
|
||||
/// \brief Writes the string representation of the given {{ parsed_type.name }} \p k to the given output stream \p os
|
||||
/// \returns an output stream with the {{ parsed_type.name }} written to
|
||||
friend std::ostream& operator<<(std::ostream& os, const {{ parsed_type.name }}& k) {
|
||||
os << json(k).dump(4);
|
||||
return os;
|
||||
}
|
||||
|
||||
};
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% for namespace in info.namespace|reverse %}
|
||||
} // namespace {{namespace}}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
|
||||
#endif // {{ info.hpp_guard }}
|
||||
@@ -0,0 +1,196 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# Copyright Pionix GmbH and Contributors to EVerest
|
||||
#
|
||||
"""
|
||||
Provide type parsing functionality.
|
||||
author: kai-uwe.hermann@pionix.de
|
||||
"""
|
||||
|
||||
from . import helpers
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Tuple
|
||||
|
||||
|
||||
import stringcase
|
||||
|
||||
|
||||
class TypeParser:
|
||||
"""Provide generation of type headers from type definitions."""
|
||||
validators = None
|
||||
templates = None
|
||||
all_types = {}
|
||||
validated_type_defs = {}
|
||||
|
||||
@classmethod
|
||||
def parse_type_url(cls, type_url: str) -> Dict:
|
||||
"""Parse a global type URL in the following format /filename#/typename."""
|
||||
|
||||
type_dict = {
|
||||
'type_relative_path': None,
|
||||
'namespaced_type': None,
|
||||
'header_file': None,
|
||||
'type_name': None
|
||||
}
|
||||
if not type_url.startswith('/'):
|
||||
raise Exception('type_url: ' + type_url + ' needs to start with a "/".')
|
||||
if '#/' not in type_url:
|
||||
raise Exception('type_url: ' + type_url + ' needs to refer to a specific type with "#/TypeName".')
|
||||
type_relative_path, prop_type = type_url.split('#/')
|
||||
type_relative_path = Path(type_relative_path[1:])
|
||||
|
||||
namespaced_type = 'types::' + '::'.join(type_relative_path.parts) + f'::{prop_type}'
|
||||
type_dict['type_relative_path'] = type_relative_path
|
||||
type_dict['namespaced_type'] = namespaced_type
|
||||
type_dict['type_name'] = prop_type
|
||||
|
||||
return type_dict
|
||||
|
||||
@classmethod
|
||||
def does_type_exist(cls, type_url: str, json_type: str):
|
||||
"""Checks if the referenced type exists"""
|
||||
if type_url not in TypeParser.all_types:
|
||||
TypeParser.all_types[type_url] = TypeParser.parse_type_url(type_url=type_url)
|
||||
type_dict = TypeParser.all_types[type_url]
|
||||
type_path = helpers.resolve_everest_dir_path('types' / type_dict['type_relative_path'].with_suffix('.yaml'))
|
||||
if not type_path or not type_path.exists():
|
||||
raise helpers.EVerestParsingException(
|
||||
'$ref: ' + type_url + f' referenced type file "{type_path}" does not exist.')
|
||||
if type_path not in TypeParser.validated_type_defs:
|
||||
TypeParser.validated_type_defs[type_path] = helpers.load_validated_type_def(
|
||||
type_path, TypeParser.validators['type'])
|
||||
|
||||
if type_dict['type_name'] not in TypeParser.validated_type_defs[type_path]['types']:
|
||||
raise helpers.EVerestParsingException('$ref: ' + type_url + ' referenced type "' +
|
||||
type_dict['type_name'] + f'" does not exist in type file "{type_path}".')
|
||||
|
||||
type_schema = TypeParser.validated_type_defs[type_path]['types'][type_dict['type_name']]
|
||||
|
||||
if json_type != type_schema['type']:
|
||||
raise helpers.EVerestParsingException('$ref: ' + type_url + ' referenced type "' +
|
||||
type_dict['type_name'] + f'" in type file "{type_path}"' +
|
||||
f' should be of type "{json_type}" but is of type: "' +
|
||||
type_schema['type'] + '".')
|
||||
|
||||
@classmethod
|
||||
def generate_tmpl_data_for_type(cls, type_with_namespace, type_def):
|
||||
"""Generate template data based on the provided type and type definition."""
|
||||
helpers.parsed_enums.clear()
|
||||
helpers.parsed_types.clear()
|
||||
helpers.type_headers.clear()
|
||||
types = []
|
||||
enums = []
|
||||
|
||||
for type_name, type_properties in type_def.get('types', {}).items():
|
||||
type_url = f'/{type_with_namespace["relative_path"]}#/{type_name}'
|
||||
TypeParser.all_types[type_url] = TypeParser.parse_type_url(type_url=type_url)
|
||||
try:
|
||||
(_type_info, enum_info) = helpers.extended_build_type_info(type_name, type_properties, type_file=True)
|
||||
if enum_info:
|
||||
enums.append(enum_info)
|
||||
except helpers.EVerestParsingException as e:
|
||||
raise helpers.EVerestParsingException(f'Error parsing type {type_name}: {e}')
|
||||
|
||||
for parsed_enum in helpers.parsed_enums:
|
||||
enum_info = {
|
||||
'name': parsed_enum['name'],
|
||||
'description': parsed_enum['description'],
|
||||
'enum_type': stringcase.capitalcase(parsed_enum['name']),
|
||||
'enum': parsed_enum['enums']
|
||||
}
|
||||
enums.append(enum_info)
|
||||
|
||||
for parsed_type in helpers.parsed_types:
|
||||
parsed_type['name'] = stringcase.capitalcase(parsed_type['name'])
|
||||
types.append(parsed_type)
|
||||
|
||||
type_headers = sorted(helpers.type_headers)
|
||||
|
||||
# Remove the header itself from the includes.
|
||||
own_header = helpers.generate_header_for_type(type_with_namespace["relative_path"])
|
||||
type_headers = [header for header in type_headers if str(header) != str(own_header)]
|
||||
|
||||
# sort types, so no forward declaration is necessary
|
||||
sorted_types: List = []
|
||||
for struct_type in types:
|
||||
insert_at: int = 0
|
||||
for dep_struct_type in struct_type['depends_on']:
|
||||
|
||||
for i, _entry in enumerate(sorted_types):
|
||||
# the new one depends on the current
|
||||
if sorted_types[i]['name'] == dep_struct_type:
|
||||
insert_at = max(insert_at, i + 1)
|
||||
break
|
||||
|
||||
sorted_types.insert(insert_at, struct_type)
|
||||
|
||||
tmpl_data = {
|
||||
'info': {
|
||||
'type': type_with_namespace['namespace'],
|
||||
'desc': type_def['description'],
|
||||
'type_headers': type_headers,
|
||||
},
|
||||
'enums': enums,
|
||||
'types': sorted_types,
|
||||
}
|
||||
|
||||
return tmpl_data
|
||||
|
||||
@classmethod
|
||||
def load_type_definition(cls, type_path: Path):
|
||||
"""Load a type definition from the provided path and check its last modification time."""
|
||||
if type_path not in TypeParser.validated_type_defs:
|
||||
TypeParser.validated_type_defs[type_path] = helpers.load_validated_type_def(
|
||||
type_path, TypeParser.validators['type'])
|
||||
|
||||
last_mtime = type_path.stat().st_mtime
|
||||
|
||||
return TypeParser.validated_type_defs[type_path], last_mtime
|
||||
|
||||
@classmethod
|
||||
def generate_type_info(cls, type_with_namespace, all_types) -> Tuple:
|
||||
"""Generate type template data."""
|
||||
try:
|
||||
type_def, last_mtime = TypeParser.load_type_definition(type_with_namespace['path'])
|
||||
except Exception as e:
|
||||
if not all_types:
|
||||
raise
|
||||
else:
|
||||
print(f'Ignoring type {type_with_namespace["namespace"]} with reason: {e}')
|
||||
return
|
||||
|
||||
tmpl_data = TypeParser.generate_tmpl_data_for_type(type_with_namespace, type_def)
|
||||
|
||||
return (tmpl_data, last_mtime)
|
||||
|
||||
@classmethod
|
||||
def generate_type_headers(cls, type_with_namespace, all_types, output_dir):
|
||||
"""Render template data to generate type headers."""
|
||||
tmpl_data, last_mtime = TypeParser.generate_type_info(type_with_namespace, all_types)
|
||||
|
||||
types_parts = {'types': None}
|
||||
|
||||
output_path = output_dir / type_with_namespace['relative_path']
|
||||
types_file = output_path.with_suffix('.hpp')
|
||||
output_path = output_path.parent
|
||||
output_path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
namespaces = ['types']
|
||||
namespaces.extend(type_with_namespace['relative_path'].parts)
|
||||
|
||||
tmpl_data['info']['interface_name'] = f'{type_with_namespace["namespace"]}'
|
||||
tmpl_data['info']['namespace'] = namespaces
|
||||
tmpl_data['info']['hpp_guard'] = 'TYPES_' + helpers.snake_case(
|
||||
''.join(type_with_namespace['uppercase_path'])).upper() + '_TYPES_HPP'
|
||||
|
||||
types_parts['types'] = {
|
||||
'path': types_file,
|
||||
'content': TypeParser.templates['types.hpp'].render(tmpl_data),
|
||||
'last_mtime': last_mtime,
|
||||
'template_path': Path(TypeParser.templates['types.hpp'].filename),
|
||||
'printable_name': types_file.relative_to(output_path.parent)
|
||||
}
|
||||
|
||||
return types_parts
|
||||
Reference in New Issue
Block a user