Skip to content
Closed
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
83 changes: 83 additions & 0 deletions launch/launch/substitutions/path_join_substitution.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,56 @@

from ..launch_context import LaunchContext
from ..substitution import Substitution
<<<<<<< HEAD
=======
from ..utilities import normalize_to_list_of_substitutions
from ..utilities import perform_substitutions
>>>>>>> e716d1f (Add a `/` path join operator for `PathJoinSubstitution` (#868))


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

<<<<<<< HEAD
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
"""
>>>>>>> e716d1f (Add a `/` path join operator for `PathJoinSubstitution` (#868))
from ..utilities import normalize_to_list_of_substitutions
self.__substitutions = normalize_to_list_of_substitutions(substitutions)

Expand All @@ -41,6 +84,46 @@ def describe(self) -> Text:
return f"PathJoin('{' + '.join([s.describe() for s in self.substitutions])}')"

def perform(self, context: LaunchContext) -> Text:
<<<<<<< HEAD
"""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))
>>>>>>> e716d1f (Add a `/` path join operator for `PathJoinSubstitution` (#868))
20 changes: 20 additions & 0 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,30 @@

import os

<<<<<<< HEAD
from launch.substitutions import PathJoinSubstitution
=======
from launch import LaunchContext
from launch.substitutions import PathJoinSubstitution, PathSubstitution
from launch.substitutions import TextSubstitution
>>>>>>> e716d1f (Add a `/` path join operator for `PathJoinSubstitution` (#868))


def test_path_join():
path = ['asd', 'bsd', 'cds']
sub = PathJoinSubstitution(path)
<<<<<<< HEAD
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')
>>>>>>> e716d1f (Add a `/` path join operator for `PathJoinSubstitution` (#868))