Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions launch/launch/substitutions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from .local_substitution import LocalSubstitution
from .not_equals_substitution import NotEqualsSubstitution
from .path_join_substitution import PathJoinSubstitution
from .path_join_substitution import PathSubstitution
from .python_expression import PythonExpression
from .substitution_failure import SubstitutionFailure
from .text_substitution import TextSubstitution
Expand All @@ -55,6 +56,7 @@
'NotEqualsSubstitution',
'OrSubstitution',
'PathJoinSubstitution',
'PathSubstitution',
'PythonExpression',
'SubstitutionFailure',
'TextSubstitution',
Expand Down
100 changes: 89 additions & 11 deletions launch/launch/substitutions/path_join_substitution.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,31 +16,109 @@

import os
from typing import Iterable
from typing import List
from typing import Text
from typing import Union

from ..launch_context import LaunchContext
from ..some_substitutions_type import SomeSubstitutionsType
from ..substitution import Substitution
from ..utilities import normalize_to_list_of_substitutions
from ..utilities import perform_substitutions


class PathJoinSubstitution(Substitution):
"""Substitution that join paths, in a platform independent way."""
"""
Substitution that join paths, in a platform independent way.

def __init__(self, substitutions: Iterable[Union[Text, Substitution]]) -> None:
"""Create a PathJoinSubstitution."""
This takes in a list of path components as substitutions. The substitutions for each path
component are performed and concatenated, and then all path components are joined.

For example:

.. code-block:: python

PathJoinSubstitution([
EnvironmentVariable('SOME_DIR'),
'cfg',
['config_', LaunchConfiguration('map'), '.yml']
])

Or:

.. code-block:: python

cfg_dir = PathJoinSubstitution([EnvironmentVariable('SOME_DIR'), 'cfg'])
cfg_file = cfg_dir / ['config_', LaunchConfiguration('map'), '.yml']

If the ``SOME_DIR`` environment variable was set to ``/home/user/dir`` and the ``map`` launch
configuration was set to ``my_map``, this would result in a path equal equivalent to (depending
on the platform):

.. code-block:: python

'/home/user/dir/cfg/config_my_map.yml'
"""

def __init__(self, substitutions: Iterable[SomeSubstitutionsType]) -> None:
"""
Create a PathJoinSubstitution.

:param substitutions: the list of path component substitutions to join
"""
from ..utilities import normalize_to_list_of_substitutions
self.__substitutions = normalize_to_list_of_substitutions(substitutions)
self.__substitutions = [
normalize_to_list_of_substitutions(path_component_substitutions)
for path_component_substitutions in substitutions
]

@property
def substitutions(self) -> Iterable[Substitution]:
def substitutions(self) -> List[List[Substitution]]:
"""Getter for variable_name."""
return self.__substitutions

def describe(self) -> Text:
def __repr__(self) -> Text:
"""Return a description of this substitution as a string."""
return f"PathJoin('{' + '.join([s.describe() for s in self.substitutions])}')"
path_components = [
' + '.join([s.describe() for s in component_substitutions])
for component_substitutions in self.substitutions
]
return f"PathJoinSubstitution('{', '.join(path_components)}')"

def perform(self, context: LaunchContext) -> Text:
"""Perform the substitution by retrieving the local variable."""
performed_substitutions = [sub.perform(context) for sub in self.__substitutions]
return os.path.join(*performed_substitutions)
"""Perform the substitutions and join into a path."""
path_components = [
perform_substitutions(context, component_substitutions)
for component_substitutions in self.substitutions
]
return os.path.join(*path_components)

def __truediv__(self, additional_path: SomeSubstitutionsType) -> 'PathJoinSubstitution':
"""Join path substitutions using the / operator, mimicking pathlib.Path operation."""
return PathJoinSubstitution(
self.substitutions + [normalize_to_list_of_substitutions(additional_path)])


class PathSubstitution(PathJoinSubstitution):
"""
Thin wrapper on PathJoinSubstitution for more pathlib.Path-like construction.

.. code-block:: python

PathSubstitution(LaunchConfiguration('base_dir')) / 'sub_dir' / 'file_name'

Which, for ``base_dir:=/my_dir``, results in (depending on the platform):

.. code-block:: python

/my_dir/sub_dir/file_name

"""

def __init__(self, path: SomeSubstitutionsType):
"""
Create a PathSubstitution.

:param path: May be a single text or Substitution element,
or an Iterable of them which are then joined
"""
super().__init__(normalize_to_list_of_substitutions(path))
18 changes: 16 additions & 2 deletions launch/test/launch/substitutions/test_path_join_substitution.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,24 @@

import os

from launch.substitutions import PathJoinSubstitution
from launch import LaunchContext
from launch.substitutions import PathJoinSubstitution, PathSubstitution
from launch.substitutions import TextSubstitution


def test_path_join():
context = LaunchContext()

path = ['asd', 'bsd', 'cds']
sub = PathJoinSubstitution(path)
assert sub.perform(None) == os.path.join(*path)
assert sub.perform(context) == os.path.join(*path)

path = ['path', ['to'], ['my_', TextSubstitution(text='file'), '.yaml']]
sub = PathJoinSubstitution(path)
assert sub.perform(context) == os.path.join('path', 'to', 'my_file.yaml')

sub = PathSubstitution('some') / 'path'
sub = sub / PathJoinSubstitution(['to', 'some', 'dir'])
sub = sub / (TextSubstitution(text='my_model'), '.xacro')
assert sub.perform(context) == os.path.join(
'some', 'path', 'to', 'some', 'dir', 'my_model.xacro')