diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 0ecb36e..3c7b67a 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -25,6 +25,7 @@ repos:
- id: check-toml
- id: check-xml
- id: check-yaml
+ exclude: test/data/parameters_template.yaml
- id: debug-statements
- id: destroyed-symlinks
- id: detect-private-key
diff --git a/README.md b/README.md
index cc2cfb2..2e7fef1 100644
--- a/README.md
+++ b/README.md
@@ -13,6 +13,12 @@ colcon test --packages-select launch_param_builder --event-handlers console_dire
colcon test-result
```
+To run pre-commit, use the following command.
+
+```bash
+pre-commit run --all-files
+```
+
To add a copyright for a new file
```bash
diff --git a/example/example_node.py b/example/example_node.py
index 586d14b..caa510a 100755
--- a/example/example_node.py
+++ b/example/example_node.py
@@ -64,7 +64,28 @@ def __init__(self):
"my_robot",
descriptor=ParameterDescriptor(type=ParameterType.PARAMETER_STRING),
)
+ self.declare_parameter(
+ "env_0.names.ur",
+ descriptor=ParameterDescriptor(type=ParameterType.PARAMETER_STRING),
+ )
+ self.declare_parameter(
+ "env_0.names.panda",
+ descriptor=ParameterDescriptor(type=ParameterType.PARAMETER_STRING),
+ )
+ self.declare_parameter(
+ "radians",
+ descriptor=ParameterDescriptor(type=ParameterType.PARAMETER_DOUBLE),
+ )
+ self.declare_parameter(
+ "degrees",
+ descriptor=ParameterDescriptor(type=ParameterType.PARAMETER_DOUBLE),
+ )
+ self.declare_parameter(
+ "names",
+ descriptor=ParameterDescriptor(type=ParameterType.PARAMETER_STRING_ARRAY),
+ )
+ self.get_logger().info(f"Parameters: {self.get_parameters_by_prefix('')}")
self.get_logger().info(
f"my_parameter: {self.get_parameter('my_parameter').value}"
)
@@ -81,6 +102,13 @@ def __init__(self):
self.get_logger().info(
f"package_name: {self.get_parameter('package_name').value}"
)
+ self.get_logger().info(f"ur ip: {self.get_parameter('env_0.names.ur').value}")
+ self.get_logger().info(
+ f"panda ip: {self.get_parameter('env_0.names.panda').value}"
+ )
+ self.get_logger().info(f"degrees: {self.get_parameter('degrees').value}")
+ self.get_logger().info(f"radians: {self.get_parameter('radians').value}")
+ self.get_logger().info(f"names: {self.get_parameter('names').value}")
def main(args=None):
diff --git a/example/launch_param_builder_example.launch.py b/example/launch_param_builder_example.launch.py
index 5113457..823df20 100644
--- a/example/launch_param_builder_example.launch.py
+++ b/example/launch_param_builder_example.launch.py
@@ -45,6 +45,17 @@ def generate_launch_description():
.file_parameter(
"parameter_file", "config/parameter_file"
) # Or /absolute/path/to/file
+ .yaml(
+ file_path="config/parameters_template.yaml",
+ mappings={
+ "namespace": "env_0",
+ "robots": [
+ {"name": "ur", "ip": "127.0.0.1"},
+ {"name": "panda", "ip": "127.0.0.2"},
+ ],
+ "names": ["name1", "name2", "name3"],
+ },
+ )
.yaml(
file_path="config/parameters.yaml"
) # Or /absolute/path/to/file
diff --git a/launch_param_builder/launch_param_builder.py b/launch_param_builder/launch_param_builder.py
index d63a993..e40ce19 100644
--- a/launch_param_builder/launch_param_builder.py
+++ b/launch_param_builder/launch_param_builder.py
@@ -32,6 +32,7 @@
from .utils import load_file, load_yaml, load_xacro, ParameterValueType
from ament_index_python.packages import get_package_share_directory
+from typing import Optional
class ParameterBuilder(object):
@@ -42,22 +43,33 @@ def __init__(self, package_name: str):
self._package_path = Path(get_package_share_directory(package_name))
self._parameters = {}
- def yaml(self, file_path: str, parameter_namespace: str = None):
+ def yaml(
+ self,
+ file_path: str,
+ parameter_namespace: str = None,
+ mappings: Optional[dict] = None,
+ ):
if parameter_namespace:
if parameter_namespace in self._parameters:
self._parameters[parameter_namespace].update(
- load_yaml(self._package_path / file_path)
+ load_yaml(self._package_path / file_path, mappings=mappings)
)
else:
self._parameters[parameter_namespace] = load_yaml(
- self._package_path / file_path
+ self._package_path / file_path, mappings=mappings
)
else:
- self._parameters.update(load_yaml(self._package_path / file_path))
+ self._parameters.update(
+ load_yaml(self._package_path / file_path, mappings=mappings)
+ )
return self
- def file_parameter(self, parameter_name: str, file_path: str):
- self._parameters[parameter_name] = load_file(self._package_path / file_path)
+ def file_parameter(
+ self, parameter_name: str, file_path: str, mappings: Optional[dict] = None
+ ):
+ self._parameters[parameter_name] = load_file(
+ self._package_path / file_path, mappings=mappings
+ )
return self
def xacro_parameter(
diff --git a/launch_param_builder/utils.py b/launch_param_builder/utils.py
index b2a517e..4e8d1bb 100644
--- a/launch_param_builder/utils.py
+++ b/launch_param_builder/utils.py
@@ -29,9 +29,11 @@
import yaml
from pathlib import Path
-from typing import List, Union
+from typing import List, Union, Optional
import xacro
from ament_index_python.packages import get_package_share_directory
+import jinja2
+import math
class ParameterBuilderFileNotFoundError(KeyError):
@@ -52,31 +54,46 @@ class ParameterBuilderFileNotFoundError(KeyError):
]
+def render_template(template: Path, mappings: dict):
+ with template.open("r") as file:
+ jinja2_template = jinja2.Template(file.read())
+ jinja2_template.globals["radians"] = math.radians
+ jinja2_template.globals["degrees"] = math.degrees
+ return jinja2_template.render(mappings)
+
+
def raise_if_file_not_found(file_path: Path):
if not file_path.exists():
raise ParameterBuilderFileNotFoundError(f"File {file_path} doesn't exist")
-def load_file(file_path: Path):
+def load_file(file_path: Path, mappings: Optional[dict] = None):
raise_if_file_not_found(file_path)
+ if mappings is not None:
+ return render_template(file_path, mappings)
try:
with open(file_path, "r") as file:
return file.read()
- except EnvironmentError: # parent of IOError, OSError *and* WindowsError where available
+ except (
+ EnvironmentError
+ ): # parent of IOError, OSError *and* WindowsError where available
return None
-def load_yaml(file_path: Path):
+def load_yaml(file_path: Path, mappings: Optional[dict] = None):
raise_if_file_not_found(file_path)
try:
- with open(file_path, "r") as file:
- return yaml.load(file, Loader=yaml.FullLoader)
- except EnvironmentError: # parent of IOError, OSError *and* WindowsError where available
+ return yaml.load(
+ render_template(file_path, mappings or {}), Loader=yaml.FullLoader
+ )
+ except (
+ EnvironmentError
+ ): # parent of IOError, OSError *and* WindowsError where available
return None
-def load_xacro(file_path: Path, mappings: dict = None):
+def load_xacro(file_path: Path, mappings: Optional[dict] = None):
raise_if_file_not_found(file_path)
file = xacro.process_file(file_path, mappings=mappings)
diff --git a/package.xml b/package.xml
index 9b28ce1..40509d3 100644
--- a/package.xml
+++ b/package.xml
@@ -11,9 +11,10 @@
python3-pytest
ament_index_python
+ python3-jinja2
python3-yaml
- xacro
rclpy
+ xacro
ament_python
diff --git a/test/data/parameter_file_template b/test/data/parameter_file_template
new file mode 100644
index 0000000..49dba44
--- /dev/null
+++ b/test/data/parameter_file_template
@@ -0,0 +1 @@
+This's a template parameter file {{ test_name }}
diff --git a/test/data/parameters_template.yaml b/test/data/parameters_template.yaml
new file mode 100644
index 0000000..c8217c5
--- /dev/null
+++ b/test/data/parameters_template.yaml
@@ -0,0 +1,8 @@
+{{ namespace }}:
+ names:
+ {% for robot in robots %}
+ {{ robot.name }}: "{{ robot.ip }}"
+ {% endfor %}
+radians: {{ radians(120) }}
+degrees: {{ degrees(1.0) }}
+names: {{ names }}
diff --git a/test/test_launch_param_builder.py b/test/test_launch_param_builder.py
index c629f5f..4a62a93 100644
--- a/test/test_launch_param_builder.py
+++ b/test/test_launch_param_builder.py
@@ -28,6 +28,7 @@
from launch_param_builder import ParameterBuilder
+import math
def test_builder():
@@ -35,9 +36,26 @@ def test_builder():
ParameterBuilder("launch_param_builder")
.parameter("my_parameter", 20.0)
.file_parameter(
- "parameter_file", "config/parameter_file"
+ "parameter_file",
+ "config/parameter_file",
) # Or /absolute/path/to/file
+ .file_parameter(
+ "parameter_file_template",
+ "config/parameter_file_template",
+ mappings={"test_name": "testing"},
+ )
.yaml(file_path="config/parameters.yaml") # Or /absolute/path/to/file
+ .yaml(
+ file_path="config/parameters_template.yaml",
+ mappings={
+ "namespace": "env_0",
+ "robots": [
+ {"name": "ur", "ip": "127.0.0.1"},
+ {"name": "panda", "ip": "127.0.0.2"},
+ ],
+ "names": ["name1", "name2", "name3"],
+ },
+ )
.xacro_parameter(
parameter_name="my_robot",
file_path="config/parameter.xacro", # Or /absolute/path/to/file
@@ -52,3 +70,16 @@ def test_builder():
assert parameters["the_answer_to_life"] == 42
assert parameters["package_name"] == "launch_param_builder"
assert parameters.get("my_robot") is not None, "Parameter xacro not loaded"
+ assert (
+ parameters["parameter_file_template"]
+ == "This's a template parameter file testing"
+ )
+ assert math.isclose(parameters["radians"], 2.0943951, rel_tol=1e-6)
+ assert math.isclose(parameters["degrees"], 57.2958, rel_tol=1e-6)
+ assert (
+ parameters.get("env_0").get("names") is not None
+ ), "Parameter yaml file not loaded"
+ names = parameters["env_0"]["names"]
+ assert names["ur"] == "127.0.0.1"
+ assert names["panda"] == "127.0.0.2"
+ assert len(parameters["names"]) == 3