From d37b7acc0eb9c367e1b69eb21fb3edab9c93ca2a Mon Sep 17 00:00:00 2001 From: artivis Date: Fri, 4 Oct 2019 16:14:47 -0400 Subject: [PATCH 1/2] add ros2plugin Signed-off-by: artivis --- ros2plugin/package.xml | 26 +++++++ ros2plugin/resource/ros2plugin | 0 ros2plugin/ros2plugin/__init__.py | 0 ros2plugin/ros2plugin/api/__init__.py | 81 ++++++++++++++++++++++ ros2plugin/ros2plugin/command/__init__.py | 0 ros2plugin/ros2plugin/command/plugin.py | 39 +++++++++++ ros2plugin/ros2plugin/verb/__init__.py | 44 ++++++++++++ ros2plugin/ros2plugin/verb/list.py | 82 +++++++++++++++++++++++ ros2plugin/setup.py | 46 +++++++++++++ ros2plugin/test/test_copyright.py | 23 +++++++ ros2plugin/test/test_flake8.py | 23 +++++++ ros2plugin/test/test_pep257.py | 23 +++++++ ros2plugin/test/test_xmllint.py | 23 +++++++ 13 files changed, 410 insertions(+) create mode 100644 ros2plugin/package.xml create mode 100644 ros2plugin/resource/ros2plugin create mode 100644 ros2plugin/ros2plugin/__init__.py create mode 100644 ros2plugin/ros2plugin/api/__init__.py create mode 100644 ros2plugin/ros2plugin/command/__init__.py create mode 100644 ros2plugin/ros2plugin/command/plugin.py create mode 100644 ros2plugin/ros2plugin/verb/__init__.py create mode 100644 ros2plugin/ros2plugin/verb/list.py create mode 100644 ros2plugin/setup.py create mode 100644 ros2plugin/test/test_copyright.py create mode 100644 ros2plugin/test/test_flake8.py create mode 100644 ros2plugin/test/test_pep257.py create mode 100644 ros2plugin/test/test_xmllint.py diff --git a/ros2plugin/package.xml b/ros2plugin/package.xml new file mode 100644 index 00000000..953099f5 --- /dev/null +++ b/ros2plugin/package.xml @@ -0,0 +1,26 @@ + + + + ros2plugin + 0.7.4 + + The plugin command for ROS 2 command line tools. + + Jeremie Deray + Apache License 2.0 + + ament_index_python + rclpy + ros2cli + ros2pkg + + ament_copyright + ament_flake8 + ament_pep257 + ament_xmllint + python3-pytest + + + ament_python + + diff --git a/ros2plugin/resource/ros2plugin b/ros2plugin/resource/ros2plugin new file mode 100644 index 00000000..e69de29b diff --git a/ros2plugin/ros2plugin/__init__.py b/ros2plugin/ros2plugin/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/ros2plugin/ros2plugin/api/__init__.py b/ros2plugin/ros2plugin/api/__init__.py new file mode 100644 index 00000000..963e6616 --- /dev/null +++ b/ros2plugin/ros2plugin/api/__init__.py @@ -0,0 +1,81 @@ +# Copyright 2019 Canonical Ltd. +# +# 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_index_python.resources import get_resource +from ament_index_python.resources import get_resource_types +from ament_index_python.resources import get_resources +from ament_index_python.resources import has_resource + +PLUGIN_RESOURCE_TYPE = '__pluginlib__plugin' + + +def is_plugin_ressource_type(resource_type): + """ + Check if resource_type has the plugin extension. + + :param str resource_type: the ressource type name to be evaluated. + + :return: a boolean, True if resource_type has the plugin extension. + """ + assert resource_type, 'The resource type must not be empty' + return PLUGIN_RESOURCE_TYPE in resource_type + + +def get_registered_plugin_ressource_list(): + """ + Get all plugin ressources registered in the ament index. + + :return: a filtered list containing the plugin ressouce types. + """ + return filter(is_plugin_ressource_type, get_resource_types()) + + +def get_package_names_with_plugin_ressource_types(): + """ + Get the names of all packages that register a plugin ressource in the ament index. + + :return: a list of packages exporting plugins. + """ + packages = [] + for plugin in get_registered_plugin_ressource_list(): + packages += list(get_resources(plugin).keys()) + return packages + + +def get_package_plugin_ressource(*, package_name=None): + """ + Get all plugin ressources registered in the ament index for the given package. + + :param package_name: whose component types are to be retrieved. + :return: a list of plugin ressources relative path. + """ + plugin_ressources = get_registered_plugin_ressource_list() + package_plugins = [] + for plugin_ressource in plugin_ressources: + if has_resource(plugin_ressource, package_name): + component_registry, _ = get_resource(plugin_ressource, package_name) + package_plugins += [line.split(';')[0] for line in component_registry.splitlines()] + return package_plugins + + +def get_registered_plugin_ressources(): + """ + Get all plugin ressources registered in the ament index. + + :return: a list of (package name, plugin type names) tuples. + """ + return [ + (package_name, get_package_plugin_ressource(package_name=package_name)) + for package_name in get_package_names_with_plugin_ressource_types() + ] diff --git a/ros2plugin/ros2plugin/command/__init__.py b/ros2plugin/ros2plugin/command/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/ros2plugin/ros2plugin/command/plugin.py b/ros2plugin/ros2plugin/command/plugin.py new file mode 100644 index 00000000..66d4005d --- /dev/null +++ b/ros2plugin/ros2plugin/command/plugin.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. + +from ros2cli.command import add_subparsers +from ros2cli.command import CommandExtension +from ros2cli.verb import get_verb_extensions + + +class PluginCommand(CommandExtension): + """Various plugin related sub-commands.""" + + def add_arguments(self, parser, cli_name): + self._subparser = parser + # get verb extensions and let them add their arguments + verb_extensions = get_verb_extensions('ros2plugin.verb') + add_subparsers( + parser, cli_name, '_verb', verb_extensions, required=False) + + def main(self, *, parser, args): + if not hasattr(args, '_verb'): + # in case no verb was passed + self._subparser.print_help() + return 0 + + extension = getattr(args, '_verb') + + # call the verb's main method + return extension.main(args=args) diff --git a/ros2plugin/ros2plugin/verb/__init__.py b/ros2plugin/ros2plugin/verb/__init__.py new file mode 100644 index 00000000..532e3a9f --- /dev/null +++ b/ros2plugin/ros2plugin/verb/__init__.py @@ -0,0 +1,44 @@ +# Copyright 2019 Canonical Ltd. +# +# 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 ros2cli.plugin_system import PLUGIN_SYSTEM_VERSION +from ros2cli.plugin_system import satisfies_version + + +class VerbExtension: + """ + The extension point for 'plugin' verb extensions. + + The following properties must be defined: + * `NAME` (will be set to the entry point name) + + The following methods must be defined: + * `main` + + The following methods can be defined: + * `add_arguments` + """ + + NAME = None + EXTENSION_POINT_VERSION = '0.1' + + def __init__(self): + super(VerbExtension, self).__init__() + satisfies_version(PLUGIN_SYSTEM_VERSION, '^0.1') + + def add_arguments(self, parser, cli_name): + pass + + def main(self, *, args): + raise NotImplementedError() diff --git a/ros2plugin/ros2plugin/verb/list.py b/ros2plugin/ros2plugin/verb/list.py new file mode 100644 index 00000000..1ba20d54 --- /dev/null +++ b/ros2plugin/ros2plugin/verb/list.py @@ -0,0 +1,82 @@ +# Copyright 2019 Canonical Ltd. +# +# 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 collections import namedtuple + +import os +import xml.etree.ElementTree as ET + +from ament_index_python import get_package_prefix +from ament_index_python import PackageNotFoundError +from ros2cli.node.strategy import add_arguments + +from ros2plugin.api import get_registered_plugin_ressources +from ros2plugin.verb import VerbExtension + + +PluginInfo = namedtuple('Plugin', ('name', 'type', 'base')) + + +class ListVerb(VerbExtension): + """Output a list of plugins.""" + + def add_arguments(self, parser, cli_name): + add_arguments(parser) + parser.add_argument( + '--packages', action='store_true', + help='List the packages that register plugins') + parser.add_argument( + '--package', type=str, + help='Name of the package to list plugins from') + + def main(self, *, args): + plugin_ressources = get_registered_plugin_ressources() + + if args.package: + plugin_ressources + + if args.packages: + for package_name, package_plugin_ressources in sorted(plugin_ressources): + print(package_name + ':') + if any(package_plugin_ressources): + print(*['\t' + r for r in package_plugin_ressources], sep='\n') + return + + for package_name, package_plugin_ressources in sorted(plugin_ressources): + plugins = [] + print(package_name + ':') + for package_plugin_ressource in package_plugin_ressources: + try: + package_prefix = get_package_prefix(package_name) + except PackageNotFoundError: + print('Package ' + package_name + ' not found.') + + plugin_xml = os.path.join(package_prefix, package_plugin_ressource) + if not os.path.isfile(plugin_xml): + print('XML manifest ' + os.path.basename(plugin_xml) + ' not found.') + + tree = ET.parse(plugin_xml) + + for e in tree.iter(): + if e.tag == 'class': + try: + plugin_name = e.attrib['name'] + except KeyError: + plugin_name = e.attrib['type'] + plugins.append(PluginInfo( + plugin_name, e.attrib['type'], e.attrib['base_class_type']) + ) + + if any(plugins): + print(*['\t' + str(p) for p in plugins], sep='\n') diff --git a/ros2plugin/setup.py b/ros2plugin/setup.py new file mode 100644 index 00000000..c181cef5 --- /dev/null +++ b/ros2plugin/setup.py @@ -0,0 +1,46 @@ +from setuptools import find_packages +from setuptools import setup + +package_name = 'ros2plugin' + +setup( + name=package_name, + version='0.7.4', + packages=find_packages(exclude=['test']), + data_files=[ + ('share/' + package_name, ['package.xml']), + ('share/ament_index/resource_index/packages', + ['resource/' + package_name]), + ], + install_requires=['ros2cli'], + zip_safe=True, + author='Jeremie Deray', + author_email='jeremie.deray@canonical.com', + maintainer='Jeremie Deray', + maintainer_email='jeremie.deray@canonical.com', + url='https://github.com/ros2/ros2cli/tree/master/ros2plugin', + download_url='https://github.com/ros2/ros2cli/releases', + keywords=[], + classifiers=[ + 'Environment :: Console', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: Apache Software License', + 'Programming Language :: Python', + ], + description='The plugin command for ROS 2 command line tools.', + long_description="""\ +The package provides the plugin command for the ROS 2 command line tools.""", + license='Apache License, Version 2.0', + tests_require=['pytest'], + entry_points={ + 'ros2cli.command': [ + 'plugin = ros2plugin.command.plugin:PluginCommand', + ], + 'ros2cli.extension_point': [ + 'ros2plugin.verb = ros2plugin.verb:VerbExtension', + ], + 'ros2plugin.verb': [ + 'list = ros2plugin.verb.list:ListVerb', + ], + } +) diff --git a/ros2plugin/test/test_copyright.py b/ros2plugin/test/test_copyright.py new file mode 100644 index 00000000..cf0fae31 --- /dev/null +++ b/ros2plugin/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/ros2plugin/test/test_flake8.py b/ros2plugin/test/test_flake8.py new file mode 100644 index 00000000..eff82996 --- /dev/null +++ b/ros2plugin/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/ros2plugin/test/test_pep257.py b/ros2plugin/test/test_pep257.py new file mode 100644 index 00000000..0e38a6c6 --- /dev/null +++ b/ros2plugin/test/test_pep257.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_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' diff --git a/ros2plugin/test/test_xmllint.py b/ros2plugin/test/test_xmllint.py new file mode 100644 index 00000000..f46285e7 --- /dev/null +++ b/ros2plugin/test/test_xmllint.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. + +from ament_xmllint.main import main +import pytest + + +@pytest.mark.linter +@pytest.mark.xmllint +def test_xmllint(): + rc = main(argv=[]) + assert rc == 0, 'Found errors' From 69fb259f4f24d6d50785eb2347cdb1c8b8532d7d Mon Sep 17 00:00:00 2001 From: artivis Date: Mon, 13 Jan 2020 09:36:44 -0500 Subject: [PATCH 2/2] fix typo Signed-off-by: artivis --- ros2plugin/ros2plugin/api/__init__.py | 38 +++++++++++++-------------- ros2plugin/ros2plugin/verb/list.py | 18 ++++++------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/ros2plugin/ros2plugin/api/__init__.py b/ros2plugin/ros2plugin/api/__init__.py index 963e6616..c0dfd885 100644 --- a/ros2plugin/ros2plugin/api/__init__.py +++ b/ros2plugin/ros2plugin/api/__init__.py @@ -20,11 +20,11 @@ PLUGIN_RESOURCE_TYPE = '__pluginlib__plugin' -def is_plugin_ressource_type(resource_type): +def is_plugin_resource_type(resource_type): """ Check if resource_type has the plugin extension. - :param str resource_type: the ressource type name to be evaluated. + :param str resource_type: the resource type name to be evaluated. :return: a boolean, True if resource_type has the plugin extension. """ @@ -32,50 +32,50 @@ def is_plugin_ressource_type(resource_type): return PLUGIN_RESOURCE_TYPE in resource_type -def get_registered_plugin_ressource_list(): +def get_registered_plugin_resource_list(): """ - Get all plugin ressources registered in the ament index. + Get all plugin resources registered in the ament index. :return: a filtered list containing the plugin ressouce types. """ - return filter(is_plugin_ressource_type, get_resource_types()) + return filter(is_plugin_resource_type, get_resource_types()) -def get_package_names_with_plugin_ressource_types(): +def get_package_names_with_plugin_resource_types(): """ - Get the names of all packages that register a plugin ressource in the ament index. + Get the names of all packages that register a plugin resource in the ament index. :return: a list of packages exporting plugins. """ packages = [] - for plugin in get_registered_plugin_ressource_list(): + for plugin in get_registered_plugin_resource_list(): packages += list(get_resources(plugin).keys()) return packages -def get_package_plugin_ressource(*, package_name=None): +def get_package_plugin_resource(*, package_name=None): """ - Get all plugin ressources registered in the ament index for the given package. + Get all plugin resources registered in the ament index for the given package. :param package_name: whose component types are to be retrieved. - :return: a list of plugin ressources relative path. + :return: a list of plugin resources relative path. """ - plugin_ressources = get_registered_plugin_ressource_list() + plugin_resources = get_registered_plugin_resource_list() package_plugins = [] - for plugin_ressource in plugin_ressources: - if has_resource(plugin_ressource, package_name): - component_registry, _ = get_resource(plugin_ressource, package_name) + for plugin_resource in plugin_resources: + if has_resource(plugin_resource, package_name): + component_registry, _ = get_resource(plugin_resource, package_name) package_plugins += [line.split(';')[0] for line in component_registry.splitlines()] return package_plugins -def get_registered_plugin_ressources(): +def get_registered_plugin_resources(): """ - Get all plugin ressources registered in the ament index. + Get all plugin resources registered in the ament index. :return: a list of (package name, plugin type names) tuples. """ return [ - (package_name, get_package_plugin_ressource(package_name=package_name)) - for package_name in get_package_names_with_plugin_ressource_types() + (package_name, get_package_plugin_resource(package_name=package_name)) + for package_name in get_package_names_with_plugin_resource_types() ] diff --git a/ros2plugin/ros2plugin/verb/list.py b/ros2plugin/ros2plugin/verb/list.py index 1ba20d54..d81b7bbd 100644 --- a/ros2plugin/ros2plugin/verb/list.py +++ b/ros2plugin/ros2plugin/verb/list.py @@ -21,7 +21,7 @@ from ament_index_python import PackageNotFoundError from ros2cli.node.strategy import add_arguments -from ros2plugin.api import get_registered_plugin_ressources +from ros2plugin.api import get_registered_plugin_resources from ros2plugin.verb import VerbExtension @@ -41,28 +41,28 @@ def add_arguments(self, parser, cli_name): help='Name of the package to list plugins from') def main(self, *, args): - plugin_ressources = get_registered_plugin_ressources() + plugin_resources = get_registered_plugin_resources() if args.package: - plugin_ressources + plugin_resources if args.packages: - for package_name, package_plugin_ressources in sorted(plugin_ressources): + for package_name, package_plugin_resources in sorted(plugin_resources): print(package_name + ':') - if any(package_plugin_ressources): - print(*['\t' + r for r in package_plugin_ressources], sep='\n') + if any(package_plugin_resources): + print(*['\t' + r for r in package_plugin_resources], sep='\n') return - for package_name, package_plugin_ressources in sorted(plugin_ressources): + for package_name, package_plugin_resources in sorted(plugin_resources): plugins = [] print(package_name + ':') - for package_plugin_ressource in package_plugin_ressources: + for package_plugin_resource in package_plugin_resources: try: package_prefix = get_package_prefix(package_name) except PackageNotFoundError: print('Package ' + package_name + ' not found.') - plugin_xml = os.path.join(package_prefix, package_plugin_ressource) + plugin_xml = os.path.join(package_prefix, package_plugin_resource) if not os.path.isfile(plugin_xml): print('XML manifest ' + os.path.basename(plugin_xml) + ' not found.')