From b28736f767a0d7577a0c2a163a230b5e293a608d Mon Sep 17 00:00:00 2001 From: ivanpauno Date: Fri, 10 May 2019 16:42:00 -0300 Subject: [PATCH] Added yaml launch frontend implementation Signed-off-by: ivanpauno --- launch_yaml/launch_yaml/__init__.py | 23 ++++ launch_yaml/launch_yaml/entity.py | 103 ++++++++++++++++++ launch_yaml/launch_yaml/parser.py | 39 +++++++ launch_yaml/package.xml | 21 ++++ launch_yaml/setup.py | 34 ++++++ launch_yaml/test/launch_yaml/executable.yaml | 12 ++ .../test/launch_yaml/test_executable.py | 48 ++++++++ launch_yaml/test/test_copyright.py | 23 ++++ launch_yaml/test/test_flake8.py | 23 ++++ launch_yaml/test/test_pep257.py | 23 ++++ 10 files changed, 349 insertions(+) create mode 100644 launch_yaml/launch_yaml/__init__.py create mode 100644 launch_yaml/launch_yaml/entity.py create mode 100644 launch_yaml/launch_yaml/parser.py create mode 100644 launch_yaml/package.xml create mode 100644 launch_yaml/setup.py create mode 100644 launch_yaml/test/launch_yaml/executable.yaml create mode 100644 launch_yaml/test/launch_yaml/test_executable.py create mode 100644 launch_yaml/test/test_copyright.py create mode 100644 launch_yaml/test/test_flake8.py create mode 100644 launch_yaml/test/test_pep257.py diff --git a/launch_yaml/launch_yaml/__init__.py b/launch_yaml/launch_yaml/__init__.py new file mode 100644 index 000000000..f3d901609 --- /dev/null +++ b/launch_yaml/launch_yaml/__init__.py @@ -0,0 +1,23 @@ +# Copyright 2019 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. + +"""Main entry point for the `launch_xml` package.""" + +from .entity import Entity +from .parser import Parser + +__all__ = [ + 'Entity', + 'Parser', +] diff --git a/launch_yaml/launch_yaml/entity.py b/launch_yaml/launch_yaml/entity.py new file mode 100644 index 000000000..34458008c --- /dev/null +++ b/launch_yaml/launch_yaml/entity.py @@ -0,0 +1,103 @@ +# Copyright 2019 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 YAML Entity class.""" + +from typing import List +from typing import Optional +from typing import Text +from typing import Tuple +from typing import Union + +from launch_frontend import Entity as BaseEntity +from launch_frontend.type_utils import check_type + + +class Entity(BaseEntity): + """Single item in the intermediate YAML front_end representation.""" + + def __init__( + self, + element: dict, + *, + type_name: Optional[Text] = None, + parent: 'Entity' = None + ) -> Text: + """Construnctor.""" + if type_name is None: + if len(element) != 1: + raise RuntimeError('Expected a len 1 dictionary') + self.__type_name = list(element.keys())[0] + self.__element = element[self.__type_name] + else: + self.__type_name = type_name + self.__element = element + self.__parent = parent + + @property + def type_name(self) -> Text: + """Get Entity type.""" + return self.__type_name + + @property + def parent(self) -> Optional['Entity']: + """Get Entity parent.""" + return self.__parent + + @property + def children(self) -> List['Entity']: + """Get the Entity's children.""" + if not isinstance(self.__element, list): + raise TypeError('Expected a list, got {}'.format(type(self.element))) + return [Entity(child) for child in self.__element] + + def get_attr( + self, + name: Text, + *, + types: Union[Text, Tuple[Text]] = 'str', + optional: bool = False + ) -> Optional[Union[ + Text, + int, + float, + List[Text], + List[int], + List[float], + List['Entity'] + ]]: + """Access an attribute of the entity.""" + if name not in self.__element: + if not optional: + raise AttributeError( + 'Can not find attribute {} in Entity {}'.format( + name, self.type_name)) + else: + return None + data = self.__element[name] + if types == 'list[Entity]': + if isinstance(data, list) and isinstance(data[0], dict): + return [Entity(child, type_name=name) for child in data] + raise TypeError( + 'Attribute {} of Entity {} expected to be a list of dictionaries.'.format( + name, self.type_name + ) + ) + if not check_type(data, types): + raise TypeError( + 'Attribute {} of Entity {} expected to be of type {}, got {}'.format( + name, self.type_name, types, type(data) + ) + ) + return data diff --git a/launch_yaml/launch_yaml/parser.py b/launch_yaml/launch_yaml/parser.py new file mode 100644 index 000000000..b98e2315a --- /dev/null +++ b/launch_yaml/launch_yaml/parser.py @@ -0,0 +1,39 @@ +# Copyright 2019 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 YAML Parser class.""" + +import io +from typing import Union + +import launch_frontend + +import yaml + +from .entity import Entity + + +class Parser(launch_frontend.Parser): + """YAML parser implementation.""" + + @classmethod + def load( + cls, + stream: Union[str, io.TextIOBase], + ) -> (Entity, 'Parser'): + """Load a YAML launch file.""" + if isinstance(stream, str): + stream = open(stream, 'r') + """Return entity loaded with markup file.""" + return (Entity(yaml.safe_load(stream)), cls) diff --git a/launch_yaml/package.xml b/launch_yaml/package.xml new file mode 100644 index 000000000..e0d5246d0 --- /dev/null +++ b/launch_yaml/package.xml @@ -0,0 +1,21 @@ + + + + launch_yaml + 0.7.3 + The ROS launch YAML frontend. + Ivan Paunovic + Apache License 2.0 + + launch + launch_frontend + + ament_copyright + ament_flake8 + ament_pep257 + python3-pytest + + + ament_python + + diff --git a/launch_yaml/setup.py b/launch_yaml/setup.py new file mode 100644 index 000000000..9cd706167 --- /dev/null +++ b/launch_yaml/setup.py @@ -0,0 +1,34 @@ +from setuptools import find_packages +from setuptools import setup + +setup( + name='launch_yaml', + version='0.7.3', + packages=find_packages(exclude=['test']), + install_requires=['setuptools'], + zip_safe=True, + author='Ivan Paunovic', + author_email='ivanpauno@ekumenlabs.com', + maintainer='Ivan Paunovic', + maintainer_email='ivanpauno@ekumenlabs.com', + url='https://github.com/ros2/launch', + download_url='https://github.com/ros2/launch/releases', + keywords=['ROS'], + classifiers=[ + 'Intended Audience :: Developers', + 'License :: OSI Approved :: Apache Software License', + 'Programming Language :: Python', + 'Topic :: Software Development', + ], + description='YAML `launch` front-end extension.', + long_description=( + 'This package provides YAML parsing ability to `launch-frontend` package.' + ), + license='Apache License, Version 2.0', + tests_require=['pytest'], + entry_points={ + 'launch_frontend.parser': [ + 'yaml = launch_yaml:Parser', + ], + } +) diff --git a/launch_yaml/test/launch_yaml/executable.yaml b/launch_yaml/test/launch_yaml/executable.yaml new file mode 100644 index 000000000..8e205d94b --- /dev/null +++ b/launch_yaml/test/launch_yaml/executable.yaml @@ -0,0 +1,12 @@ +launch: + - executable: + cmd: ls + cwd: '/' + name: my_ls + args: -l -a -s + shell: true + output: log + launch_prefix: $(env LAUNCH_PREFIX) + env: + - name: var + value: '1' diff --git a/launch_yaml/test/launch_yaml/test_executable.py b/launch_yaml/test/launch_yaml/test_executable.py new file mode 100644 index 000000000..7898c01cb --- /dev/null +++ b/launch_yaml/test/launch_yaml/test_executable.py @@ -0,0 +1,48 @@ +# Copyright 2019 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. + +"""Test parsing an executable action.""" + +from pathlib import Path + +from launch import LaunchService + +from launch_frontend import Parser + + +def test_executable(): + """Parse executable yaml example.""" + root_entity, parser = Parser.load(str(Path(__file__).parent / 'executable.yaml')) + ld = parser.parse_description(root_entity) + executable = ld.entities[0] + cmd = [i[0].perform(None) for i in executable.cmd] + assert( + cmd == ['ls', '-l', '-a', '-s']) + assert(executable.cwd[0].perform(None) == '/') + assert(executable.name[0].perform(None) == 'my_ls') + assert(executable.shell is True) + assert(executable.output == 'log') + key, value = executable.additional_env[0] + key = key[0].perform(None) + value = value[0].perform(None) + assert(key == 'var') + assert(value == '1') + # assert(executable.prefix[0].perform(None) == 'time') + ls = LaunchService() + ls.include_launch_description(ld) + assert(0 == ls.run()) + + +if __name__ == '__main__': + test_executable() diff --git a/launch_yaml/test/test_copyright.py b/launch_yaml/test/test_copyright.py new file mode 100644 index 000000000..cf0fae31f --- /dev/null +++ b/launch_yaml/test/test_copyright.py @@ -0,0 +1,23 @@ +# Copyright 2017 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. + +from ament_copyright.main import main +import pytest + + +@pytest.mark.copyright +@pytest.mark.linter +def test_copyright(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found errors' diff --git a/launch_yaml/test/test_flake8.py b/launch_yaml/test/test_flake8.py new file mode 100644 index 000000000..eff829969 --- /dev/null +++ b/launch_yaml/test/test_flake8.py @@ -0,0 +1,23 @@ +# Copyright 2017 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. + +from ament_flake8.main import main +import pytest + + +@pytest.mark.flake8 +@pytest.mark.linter +def test_flake8(): + rc = main(argv=[]) + assert rc == 0, 'Found errors' diff --git a/launch_yaml/test/test_pep257.py b/launch_yaml/test/test_pep257.py new file mode 100644 index 000000000..3aeb4d348 --- /dev/null +++ b/launch_yaml/test/test_pep257.py @@ -0,0 +1,23 @@ +# Copyright 2015 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. + +from ament_pep257.main import main +import pytest + + +@pytest.mark.linter +@pytest.mark.pep257 +def test_pep257(): + rc = main(argv=[]) + assert rc == 0, 'Found code style errors / warnings'