Skip to content

Commit 2c20f0f

Browse files
committed
Expose an API for each rosidl CLI command.
Signed-off-by: Michel Hidalgo <[email protected]>
1 parent c54b80a commit 2c20f0f

File tree

6 files changed

+231
-79
lines changed

6 files changed

+231
-79
lines changed

rosidl_cli/rosidl_cli/command/generate/__init__.py

Lines changed: 18 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@
1616

1717
from rosidl_cli.command import Command
1818

19-
from .extensions import load_type_extensions
20-
from .extensions import load_typesupport_extensions
19+
from .api import generate
2120

2221

2322
class GenerateCommand(Command):
@@ -27,17 +26,17 @@ class GenerateCommand(Command):
2726

2827
def add_arguments(self, parser):
2928
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 '.'."))
29+
'-o', '--output-path', metavar='PATH',
30+
type=pathlib.Path, default=None,
31+
help=('Path to directory to hold generated '
32+
"source code files. Defaults to '.'."))
3433
parser.add_argument(
35-
'-t', '--type', metavar='TYPE_SPEC',
36-
dest='type_specs', action='append', default=[],
34+
'-t', '--type', metavar='TYPE',
35+
dest='types', action='append', default=[],
3736
help='Target type representations for generation.')
3837
parser.add_argument(
39-
'-ts', '--type-support', metavar='TYPESUPPORT_SPEC',
40-
dest='typesupport_specs', action='append', default=[],
38+
'-ts', '--type-support', metavar='TYPESUPPORT',
39+
dest='typesupports', action='append', default=[],
4140
help='Target type supports for generation.')
4241
parser.add_argument(
4342
'-I', '--include-path', type=pathlib.Path, metavar='PATH',
@@ -47,36 +46,16 @@ def add_arguments(self, parser):
4746
'package_name', help='Name of the package to generate code for')
4847
parser.add_argument(
4948
'interface_files', metavar='interface_file', nargs='+',
50-
help=('Normalized relative path to interface definition file. '
49+
help=('Relative path to an interface definition file. '
5150
"If prefixed by another path followed by a colon ':', "
5251
'path resolution is performed against such path.'))
5352

5453
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-
)
54+
generate(
55+
package_name=args.package_name,
56+
interface_files=args.interface_files,
57+
include_paths=args.include_paths,
58+
output_path=args.output_path,
59+
types=args.types,
60+
typesupports=args.typesupports
61+
)
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
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+
from .extensions import load_type_extensions
16+
from .extensions import load_typesupport_extensions
17+
18+
19+
def generate(
20+
*,
21+
package_name,
22+
interface_files,
23+
include_paths=None,
24+
output_path=None,
25+
types=None,
26+
typesupports=None
27+
):
28+
"""
29+
Generate source code from interface definition files.
30+
31+
To do so, this function leverages type representation and type
32+
support generation support as provided by third-party package
33+
extensions.
34+
35+
Each path to an interface definition file is a relative path optionally
36+
prefixed by another path followed by a colon ':', against which the first
37+
relative path is to be resolved.
38+
39+
The directory structure that these relative paths exhibit will be replicated
40+
on output (as opposed to the prefix path, which will be ignored).
41+
42+
If no type representation nor type support is specified, all available ones
43+
will be generated.
44+
45+
If more than one type representation or type support is generated, the
46+
name of each will be appended to the given `output_path` to preclude
47+
name clashes upon writing source code files.
48+
49+
:param package_name: name of the package to generate source code for
50+
:param interface_files: list of paths to interface definition files
51+
:param include_paths: optional list of paths to include dependency
52+
interface definition files from
53+
:param output_path: optional path to directory to hold generated
54+
source code files, defaults to the current working directory
55+
:param types: optional list of type representations to generate
56+
:param typesupports: optional list of type supports to generate
57+
:returns: list of lists of paths to generated source code files,
58+
one group per type or type support extension invoked
59+
"""
60+
extensions = []
61+
62+
unspecific_generation = not types and not typesupports
63+
64+
if types or unspecific_generation:
65+
extensions.extend(load_type_extensions(
66+
specs=types,
67+
strict=not unspecific_generation))
68+
69+
if typesupports or unspecific_generation:
70+
extensions.extend(load_typesupport_extensions(
71+
specs=typesupports,
72+
strict=not unspecific_generation))
73+
74+
if unspecific_generation and not extensions:
75+
raise RuntimeError('No type nor typesupport extensions were found')
76+
77+
if include_paths is None:
78+
include_paths = []
79+
80+
if output_path is None:
81+
output_path = pathlib.Path.cwd()
82+
83+
if len(extensions) > 1:
84+
return [
85+
extension.generate(
86+
package_name, interface_files, include_paths,
87+
output_path=output_path / extension.name)
88+
for extension in extensions
89+
]
90+
91+
return [extensions[0].generate(
92+
package_name, interface_files,
93+
include_paths, output_path
94+
)]
95+
96+

rosidl_cli/rosidl_cli/command/generate/extensions.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ def generate(
4343
:param include_paths: list of paths to include dependency interface
4444
definition files from.
4545
:param output_path: path to directory to hold generated source code files
46+
:returns: list of paths to generated source files
4647
"""
4748
raise NotImplementedError()
4849

rosidl_cli/rosidl_cli/command/translate/__init__.py

Lines changed: 18 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,11 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
import collections
16-
import os
1715
import pathlib
1816

1917
from rosidl_cli.command import Command
2018

21-
from .extensions import load_translate_extensions
19+
from .api import translate
2220

2321

2422
class TranslateCommand(Command):
@@ -29,14 +27,15 @@ class TranslateCommand(Command):
2927
def add_arguments(self, parser):
3028
parser.add_argument(
3129
'-o', '--output-path', metavar='PATH',
32-
type=pathlib.Path, default=pathlib.Path.cwd(),
33-
help=('Path to directory to hold translated interface definition'
34-
"files. Defaults to '.'."))
30+
type=pathlib.Path, default=None,
31+
help=('Path to directory to hold translated interface '
32+
"definition files. Defaults to '.'.")
33+
)
3534
parser.add_argument(
36-
'--use', '--translator', metavar='TRANSLATOR_SPEC',
37-
dest='translator_specs', action='append', default=[],
38-
help=('Translators to be used. If none is given, '
39-
'suitable available ones will be used.')
35+
'--use', '--translator', metavar='TRANSLATOR',
36+
dest='translators', action='append', default=[],
37+
help=('Translator to be used. If none is specified, '
38+
'all available ones will be considered.')
4039
)
4140
parser.add_argument(
4241
'--to', '--output-format', required=True,
@@ -60,39 +59,18 @@ def add_arguments(self, parser):
6059
help='Name of the package all interface files belong to')
6160
parser.add_argument(
6261
'interface_files', metavar='interface_file', nargs='+',
63-
help=('Normalized relative path to an interface definition file. '
62+
help=('Relative path to an interface definition file. '
6463
"If prefixed by another path followed by a colon ':', "
6564
'path resolution is performed against such path.')
6665
)
6766

6867
def main(self, *, args):
69-
extensions = load_translate_extensions(
70-
specs=args.translator_specs,
71-
strict=any(args.translator_specs)
68+
translate(
69+
package_name=args.package_name,
70+
interface_files=args.interface_files,
71+
output_format=args.output_format,
72+
input_format=args.input_format,
73+
include_paths=args.include_paths,
74+
output_path=args.output_path,
75+
translators=args.translators
7276
)
73-
if not extensions:
74-
return 'No translate extensions found'
75-
76-
if not args.input_format:
77-
interface_files_per_format = collections.defaultdict(list)
78-
for interface_file in args.interface_files:
79-
input_format = os.path.splitext(interface_file)[-1][1:]
80-
interface_files_per_format[input_format].append(interface_file)
81-
else:
82-
interface_files_per_format = {
83-
args.input_format: args.interface_files}
84-
85-
for input_format, interface_files in interface_files_per_format.items():
86-
extension = next((
87-
extension for extension in extensions
88-
if extension.input_format == input_format and \
89-
extension.output_format == args.output_format
90-
), None)
91-
92-
if not extension:
93-
return (f"Translation from '{input_format}' to "
94-
f"'{args.output_format}' is not supported")
95-
96-
extension.translate(
97-
args.package_name, interface_files,
98-
args.include_paths, args.output_path)
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
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 collections
16+
import os
17+
import pathlib
18+
19+
from .extensions import load_translate_extensions
20+
21+
22+
def translate(
23+
*,
24+
package_name,
25+
interface_files,
26+
output_format,
27+
input_format=None,
28+
include_paths=None,
29+
output_path=None,
30+
translators=None
31+
):
32+
"""
33+
Translate interface definition files from one format to another.
34+
35+
To do so, this function leverages translation support as provided
36+
by third-party package extensions.
37+
38+
Each path to an interface definition file is a relative path optionally
39+
prefixed by another path followed by a colon ':', against which the first
40+
relative path is to be resolved.
41+
42+
The directory structure that these relative paths exhibit will be
43+
replicated on output (as opposed to the prefix path, which will be
44+
ignored).
45+
46+
If no translators are specified, all available ones will be considered.
47+
48+
:param package_name: name of the package all interface files belong to
49+
:param interface_files: list of paths to interface definition files
50+
:param output_format: format to translate interface definition files to
51+
:param input_format: optional format to assume for all interface
52+
definition files, deduced from file extensions if not given
53+
:param include_paths: optional list of paths to include dependency
54+
interface definition files from
55+
:param output_path: optional path to directory to hold translated
56+
interface definition files, defaults to the current working directory
57+
:param translators: optional list of translators to use
58+
:returns: list of paths to translated interface definition files
59+
"""
60+
extensions = load_translate_extensions(
61+
specs=translators, strict=bool(translators)
62+
)
63+
if not extensions:
64+
raise RuntimeError('No translate extensions found')
65+
66+
if not input_format:
67+
interface_files_per_format = collections.defaultdict(list)
68+
for interface_file in interface_files:
69+
input_format = os.path.splitext(interface_file)[-1][1:]
70+
interface_files_per_format[input_format].append(interface_file)
71+
else:
72+
interface_files_per_format = {
73+
args.input_format: args.interface_files
74+
}
75+
76+
if include_paths is None:
77+
include_paths = []
78+
79+
if output_path is None:
80+
output_path = pathlib.Path.cwd()
81+
82+
translated_interface_files = []
83+
for input_format, interface_files in interface_files_per_format.items():
84+
extension = next((
85+
extension for extension in extensions
86+
if extension.input_format == input_format and \
87+
extension.output_format == output_format
88+
), None)
89+
90+
if not extension:
91+
raise RuntimeError(f"Translation from '{input_format}' to "
92+
f"'{output_format}' is not supported")
93+
94+
translated_interface_files.extend(extension.translate(
95+
package_name, interface_files, include_paths, output_path))
96+
97+
return translated_interface_files

rosidl_cli/rosidl_cli/command/translate/extensions.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ def translate(
5252
definition files from
5353
:param output_path: path to directory to hold translated interface
5454
definition files
55+
:returns: list of paths to translated interface definition files
5556
"""
5657
raise NotImplementedError()
5758

0 commit comments

Comments
 (0)