# Copyright 2018 Open Source Robotics Foundation, Inc.
#
# 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.
"""Module for the LaunchDescriptionSource class."""
import traceback
from typing import Optional
from typing import Text
import launch.logging
from .launch_context import LaunchContext
from .launch_description import LaunchDescription
from .some_substitutions_type import SomeSubstitutionsType
from .utilities import normalize_to_list_of_substitutions
from .utilities import perform_substitutions
[docs]
class LaunchDescriptionSource:
"""Encapsulation of a launch description, where it comes from, and how it was generated."""
[docs]
def __init__(
self,
launch_description: Optional[LaunchDescription] = None,
location: SomeSubstitutionsType = '<string>',
method: str = 'unspecified mechanism from a script',
) -> None:
"""
Create a LaunchDescriptionSource.
For example, loading a file called ``example.launch.py`` for inclusion
might end up setting `location` to ``/path/to/example.launch.py`` and
the ``method`` to be ``interpreted python launch file``.
:param launch_description: the launch description that this source represents
:param location: the location from where this launch description was loaded if applicable
:param method: the method by which the launch description was generated
"""
self.__launch_description: Optional[LaunchDescription] = launch_description
self.__expanded_location: Optional[Text] = None
self.__location = normalize_to_list_of_substitutions(location)
self.__method: str = method
self.__logger = launch.logging.get_logger(__name__)
[docs]
def try_get_launch_description_without_context(self) -> Optional[LaunchDescription]:
"""
Attempt to load the LaunchDescription without a context, return None if unsuccessful.
This method is useful for trying to introspect the included launch
description without visiting the user of this source.
"""
if self.__launch_description is None:
# Try to expand the launch file path and load the launch file with a local context.
try:
context = LaunchContext()
expanded_location = \
perform_substitutions(context, self.__location)
return self._get_launch_description(expanded_location)
except Exception as exc:
self.__logger.debug(traceback.format_exc())
self.__logger.debug(
'Failed to load the launch file without a context: ' + str(exc),
)
return self.__launch_description
[docs]
def get_launch_description(self, context: LaunchContext) -> LaunchDescription:
"""Get the LaunchDescription, loading it if necessary."""
if self.__expanded_location is None:
self.__expanded_location = \
perform_substitutions(context, self.__location)
if self.__launch_description is None:
self.__launch_description = \
self._get_launch_description(self.__expanded_location)
return self.__launch_description
def _get_launch_description(self, location):
"""Get the LaunchDescription from location."""
if self.__launch_description is None:
raise RuntimeError(
'LaunchDescriptionSource.get_launch_description(): '
'called without launch description being set'
)
@property
def location(self) -> str:
"""
Get the location of the launch description source as a string.
The string is either a list of Substitution instances converted to
strings or the expanded path if :py:meth:`get_launch_description` has
been called.
"""
if self.__expanded_location is None:
# get_launch_description() has not been called yet
return ' + '.join([str(sub) for sub in self.__location])
return self.__expanded_location
@property
def method(self) -> str:
"""Getter for self.__method."""
return self.__method