Skip to content

Commit f0b42cf

Browse files
hidmicsloretz
andauthored
Add rosidl generate CLI. (#567)
Signed-off-by: Michel Hidalgo <[email protected]> Co-authored-by: Shane Loretz <[email protected]>
1 parent 57befb8 commit f0b42cf

File tree

23 files changed

+896
-0
lines changed

23 files changed

+896
-0
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Copyright 2021 Open Source Robotics Foundation, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
if type register-python-argcomplete3 > /dev/null 2>&1; then
16+
eval "$(register-python-argcomplete3 rosidl)"
17+
elif type register-python-argcomplete > /dev/null 2>&1; then
18+
eval "$(register-python-argcomplete rosidl)"
19+
fi
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Copyright 2021 Open Source Robotics Foundation, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
autoload -U +X compinit && compinit
16+
autoload -U +X bashcompinit && bashcompinit
17+
18+
# Get this scripts directory
19+
__rosidl_cli_completion_dir=${0:a:h}
20+
# Just source the bash version, it works in zsh too
21+
source "$__rosidl_cli_completion_dir/rosidl-argcomplete.bash"
22+
# Cleanup
23+
unset __rosidl_cli_completion_dir

rosidl_cli/package.xml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?xml version="1.0"?>
2+
<?xml-model href="http://download.ros.org/schema/package_format2.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
3+
<package format="2">
4+
<name>rosidl_cli</name>
5+
<version>0.1.0</version>
6+
<description>
7+
Command line tools for ROS interface generation.
8+
</description>
9+
<maintainer email="[email protected]">Chris Lalancette</maintainer>
10+
<maintainer email="[email protected]">Shane Loretz</maintainer>
11+
<license>Apache License 2.0</license>
12+
13+
<author email="[email protected]">Michel Hidalgo</author>
14+
15+
<exec_depend>python3-argcomplete</exec_depend>
16+
<exec_depend>python3-importlib-metadata</exec_depend>
17+
18+
<test_depend>ament_copyright</test_depend>
19+
<test_depend>ament_flake8</test_depend>
20+
<test_depend>ament_pep257</test_depend>
21+
<test_depend>ament_xmllint</test_depend>
22+
<test_depend>python3-pytest</test_depend>
23+
24+
<export>
25+
<build_type>ament_python</build_type>
26+
</export>
27+
</package>

rosidl_cli/pytest.ini

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[pytest]
2+
junit_family=xunit2

rosidl_cli/resource/package.dsv

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
source;share/rosidl_cli/environment/rosidl-argcomplete.bash
2+
source;share/rosidl_cli/environment/rosidl-argcomplete.zsh

rosidl_cli/resource/rosidl_cli

Whitespace-only changes.

rosidl_cli/rosidl_cli/__init__.py

Whitespace-only changes.

rosidl_cli/rosidl_cli/cli.py

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# Copyright 2021 Open Source Robotics Foundation, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import argparse
16+
import signal
17+
18+
from rosidl_cli.command.generate import GenerateCommand
19+
from rosidl_cli.common import get_first_line_doc
20+
21+
22+
def add_subparsers(parser, cli_name, commands):
23+
"""
24+
Create argparse subparser for each command.
25+
26+
The ``cli_name`` is used for the title and description of the
27+
``add_subparsers`` function call.
28+
29+
For each command a subparser is created.
30+
31+
:param parser: the parent argument parser
32+
:type parser: :py:class:`argparse.ArgumentParser`
33+
:param str cli_name: name of the command line command to which the
34+
subparsers are being added
35+
:param commands: each of the commands contributing specific arguments
36+
:type commands: :py:class:`List[Command]`
37+
"""
38+
# add subparser with description of available subparsers
39+
description = ''
40+
41+
commands = sorted(commands, key=lambda command: command.name)
42+
max_length = max(len(command.name) for command in commands)
43+
for command in commands:
44+
description += '%s %s\n' % (
45+
command.name.ljust(max_length),
46+
get_first_line_doc(command))
47+
subparser = parser.add_subparsers(
48+
title='Commands', description=description,
49+
metavar=f'Call `{cli_name} <command> -h` for more detailed usage.')
50+
subparser.dest = '_command'
51+
subparser.required = True
52+
53+
# add extension specific sub-sub-parser with its arguments
54+
for command in commands:
55+
command_parser = subparser.add_parser(
56+
command.name,
57+
description=get_first_line_doc(command),
58+
formatter_class=argparse.RawDescriptionHelpFormatter)
59+
command_parser.set_defaults(_command=command)
60+
command.add_arguments(command_parser)
61+
62+
return subparser
63+
64+
65+
def main():
66+
script_name = 'rosidl'
67+
description = f'{script_name} is an extensible command-line tool ' \
68+
'for ROS interface generation.'
69+
70+
# top level parser
71+
parser = argparse.ArgumentParser(
72+
description=description,
73+
formatter_class=argparse.RawDescriptionHelpFormatter
74+
)
75+
76+
commands = [GenerateCommand()]
77+
78+
# add arguments for command extension(s)
79+
add_subparsers(
80+
parser,
81+
script_name,
82+
commands
83+
)
84+
85+
# register argcomplete hook if available
86+
try:
87+
from argcomplete import autocomplete
88+
except ImportError:
89+
pass
90+
else:
91+
autocomplete(parser, exclude=['-h', '--help'])
92+
93+
# parse the command line arguments
94+
args = parser.parse_args()
95+
96+
# call the main method of the command
97+
try:
98+
rc = args._command.main(args=args)
99+
except KeyboardInterrupt:
100+
rc = signal.SIGINT
101+
except (ValueError, RuntimeError) as e:
102+
rc = str(e)
103+
return rc
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Copyright 2021 Open Source Robotics Foundation, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
16+
class Command:
17+
"""
18+
The extension point for 'command' extensions.
19+
20+
The following methods must be defined:
21+
* `main`
22+
* `add_arguments`
23+
"""
24+
25+
def add_arguments(self, parser):
26+
pass
27+
28+
def main(self, *, parser, args):
29+
raise NotImplementedError()
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# Copyright 2021 Open Source Robotics Foundation, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import pathlib
16+
17+
from rosidl_cli.command import Command
18+
19+
from .extensions import load_type_extensions
20+
from .extensions import load_typesupport_extensions
21+
22+
23+
class GenerateCommand(Command):
24+
"""Generate source code from interface definition files."""
25+
26+
name = 'generate'
27+
28+
def add_arguments(self, parser):
29+
parser.add_argument(
30+
'-o', '--output-path', type=pathlib.Path,
31+
metavar='PATH', default=pathlib.Path.cwd(),
32+
help=('Path to directory to hold generated source code files. '
33+
"Defaults to '.'."))
34+
parser.add_argument(
35+
'-t', '--type', metavar='TYPE_SPEC',
36+
dest='type_specs', action='append', default=[],
37+
help='Target type representations for generation.')
38+
parser.add_argument(
39+
'-ts', '--type-support', metavar='TYPESUPPORT_SPEC',
40+
dest='typesupport_specs', action='append', default=[],
41+
help='Target type supports for generation.')
42+
parser.add_argument(
43+
'-I', '--include-path', type=pathlib.Path, metavar='PATH',
44+
dest='include_paths', action='append', default=[],
45+
help='Paths to include dependency interface definition files from.')
46+
parser.add_argument(
47+
'package_name', help='Name of the package to generate code for')
48+
parser.add_argument(
49+
'interface_files', metavar='interface_file', nargs='+',
50+
help=('Normalized relative path to interface definition file. '
51+
"If prefixed by another path followed by a colon ':', "
52+
'path resolution is performed against such path.'))
53+
54+
def main(self, *, args):
55+
extensions = []
56+
57+
unspecific_generation = \
58+
not args.type_specs and not args.typesupport_specs
59+
60+
if args.type_specs or unspecific_generation:
61+
extensions.extend(load_type_extensions(
62+
specs=args.type_specs,
63+
strict=not unspecific_generation))
64+
65+
if args.typesupport_specs or unspecific_generation:
66+
extensions.extend(load_typesupport_extensions(
67+
specs=args.typesupport_specs,
68+
strict=not unspecific_generation))
69+
70+
if unspecific_generation and not extensions:
71+
return 'No type nor typesupport extensions were found'
72+
73+
if len(extensions) > 1:
74+
for extension in extensions:
75+
extension.generate(
76+
args.package_name, args.interface_files, args.include_paths,
77+
output_path=args.output_path / extension.name)
78+
else:
79+
extensions[0].generate(
80+
args.package_name, args.interface_files,
81+
args.include_paths, args.output_path
82+
)

0 commit comments

Comments
 (0)