Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 18 additions & 39 deletions rosidl_cli/rosidl_cli/command/generate/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@

from rosidl_cli.command import Command

from .extensions import load_type_extensions
from .extensions import load_typesupport_extensions
from .api import generate


class GenerateCommand(Command):
Expand All @@ -27,17 +26,17 @@ class GenerateCommand(Command):

def add_arguments(self, parser):
parser.add_argument(
'-o', '--output-path', type=pathlib.Path,
metavar='PATH', default=pathlib.Path.cwd(),
help=('Path to directory to hold generated source code files. '
"Defaults to '.'."))
'-o', '--output-path', metavar='PATH',
type=pathlib.Path, default=None,
help=('Path to directory to hold generated '
"source code files. Defaults to '.'."))
parser.add_argument(
'-t', '--type', metavar='TYPE_SPEC',
dest='type_specs', action='append', default=[],
'-t', '--type', metavar='TYPE',
dest='types', action='append', default=[],
help='Target type representations for generation.')
parser.add_argument(
'-ts', '--type-support', metavar='TYPESUPPORT_SPEC',
dest='typesupport_specs', action='append', default=[],
'-ts', '--type-support', metavar='TYPESUPPORT',
dest='typesupports', action='append', default=[],
help='Target type supports for generation.')
parser.add_argument(
'-I', '--include-path', type=pathlib.Path, metavar='PATH',
Expand All @@ -47,36 +46,16 @@ def add_arguments(self, parser):
'package_name', help='Name of the package to generate code for')
parser.add_argument(
'interface_files', metavar='interface_file', nargs='+',
help=('Normalized relative path to interface definition file. '
help=('Relative path to an interface definition file. '
"If prefixed by another path followed by a colon ':', "
'path resolution is performed against such path.'))

def main(self, *, args):
extensions = []

unspecific_generation = \
not args.type_specs and not args.typesupport_specs

if args.type_specs or unspecific_generation:
extensions.extend(load_type_extensions(
specs=args.type_specs,
strict=not unspecific_generation))

if args.typesupport_specs or unspecific_generation:
extensions.extend(load_typesupport_extensions(
specs=args.typesupport_specs,
strict=not unspecific_generation))

if unspecific_generation and not extensions:
return 'No type nor typesupport extensions were found'

if len(extensions) > 1:
for extension in extensions:
extension.generate(
args.package_name, args.interface_files, args.include_paths,
output_path=args.output_path / extension.name)
else:
extensions[0].generate(
args.package_name, args.interface_files,
args.include_paths, args.output_path
)
generate(
package_name=args.package_name,
interface_files=args.interface_files,
include_paths=args.include_paths,
output_path=args.output_path,
types=args.types,
typesupports=args.typesupports
)
99 changes: 99 additions & 0 deletions rosidl_cli/rosidl_cli/command/generate/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Copyright 2021 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.

import os
import pathlib

from .extensions import load_type_extensions
from .extensions import load_typesupport_extensions


def generate(
*,
package_name,
interface_files,
include_paths=None,
output_path=None,
types=None,
typesupports=None
):
"""
Generate source code from interface definition files.

To do so, this function leverages type representation and type
support generation support as provided by third-party package
extensions.

Each path to an interface definition file is a relative path optionally
prefixed by another path followed by a colon ':', against which the first
relative path is to be resolved.

The directory structure that these relative paths exhibit will be replicated
on output (as opposed to the prefix path, which will be ignored).

If no type representation nor type support is specified, all available ones
will be generated.

If more than one type representation or type support is generated, the
name of each will be appended to the given `output_path` to preclude
name clashes upon writing source code files.

:param package_name: name of the package to generate source code for
:param interface_files: list of paths to interface definition files
:param include_paths: optional list of paths to include dependency
interface definition files from
:param output_path: optional path to directory to hold generated
source code files, defaults to the current working directory
:param types: optional list of type representations to generate
:param typesupports: optional list of type supports to generate
:returns: list of lists of paths to generated source code files,
one group per type or type support extension invoked
"""
extensions = []

unspecific_generation = not types and not typesupports

if types or unspecific_generation:
extensions.extend(load_type_extensions(
specs=types,
strict=not unspecific_generation))

if typesupports or unspecific_generation:
extensions.extend(load_typesupport_extensions(
specs=typesupports,
strict=not unspecific_generation))

if unspecific_generation and not extensions:
raise RuntimeError('No type nor typesupport extensions were found')

if include_paths is None:
include_paths = []

if output_path is None:
output_path = pathlib.Path.cwd()
else:
os.makedirs(output_path, exist_ok=True)

if len(extensions) > 1:
return [
extension.generate(
package_name, interface_files, include_paths,
output_path=output_path / extension.name)
for extension in extensions
]

return [extensions[0].generate(
package_name, interface_files,
include_paths, output_path
)]
1 change: 1 addition & 0 deletions rosidl_cli/rosidl_cli/command/generate/extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ def generate(
:param include_paths: list of paths to include dependency interface
definition files from.
:param output_path: path to directory to hold generated source code files
:returns: list of paths to generated source files
"""
raise NotImplementedError()

Expand Down
58 changes: 18 additions & 40 deletions rosidl_cli/rosidl_cli/command/translate/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import collections
import os
import pathlib

from rosidl_cli.command import Command

from .extensions import load_translate_extensions
from .api import translate


class TranslateCommand(Command):
Expand All @@ -29,14 +27,15 @@ class TranslateCommand(Command):
def add_arguments(self, parser):
parser.add_argument(
'-o', '--output-path', metavar='PATH',
type=pathlib.Path, default=pathlib.Path.cwd(),
help=('Path to directory to hold translated interface definition'
"files. Defaults to '.'."))
type=pathlib.Path, default=None,
help=('Path to directory to hold translated interface '
"definition files. Defaults to '.'.")
)
parser.add_argument(
'--use', '--translator', metavar='TRANSLATOR_SPEC',
dest='translator_specs', action='append', default=[],
help=('Translators to be used. If none is given, '
'suitable available ones will be used.')
'--use', '--translator', metavar='TRANSLATOR',
dest='translators', action='append', default=[],
help=('Translator to be used. If none is specified, '
'all available ones will be considered.')
)
parser.add_argument(
'--to', '--output-format', required=True,
Expand All @@ -60,39 +59,18 @@ def add_arguments(self, parser):
help='Name of the package all interface files belong to')
parser.add_argument(
'interface_files', metavar='interface_file', nargs='+',
help=('Normalized relative path to an interface definition file. '
help=('Relative path to an interface definition file. '
"If prefixed by another path followed by a colon ':', "
'path resolution is performed against such path.')
)

def main(self, *, args):
extensions = load_translate_extensions(
specs=args.translator_specs,
strict=any(args.translator_specs)
translate(
package_name=args.package_name,
interface_files=args.interface_files,
output_format=args.output_format,
input_format=args.input_format,
include_paths=args.include_paths,
output_path=args.output_path,
translators=args.translators
)
if not extensions:
return 'No translate extensions found'

if not args.input_format:
interface_files_per_format = collections.defaultdict(list)
for interface_file in args.interface_files:
input_format = os.path.splitext(interface_file)[-1][1:]
interface_files_per_format[input_format].append(interface_file)
else:
interface_files_per_format = {
args.input_format: args.interface_files}

for input_format, interface_files in interface_files_per_format.items():
extension = next((
extension for extension in extensions
if extension.input_format == input_format and
extension.output_format == args.output_format
), None)

if not extension:
return (f"Translation from '{input_format}' to "
f"'{args.output_format}' is not supported")

extension.translate(
args.package_name, interface_files,
args.include_paths, args.output_path)
100 changes: 100 additions & 0 deletions rosidl_cli/rosidl_cli/command/translate/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Copyright 2021 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.

import collections
import os
import pathlib

from .extensions import load_translate_extensions


def translate(
*,
package_name,
interface_files,
output_format,
input_format=None,
include_paths=None,
output_path=None,
translators=None
):
"""
Translate interface definition files from one format to another.

To do so, this function leverages translation support as provided
by third-party package extensions.

Each path to an interface definition file is a relative path optionally
prefixed by another path followed by a colon ':', against which the first
relative path is to be resolved.

The directory structure that these relative paths exhibit will be
replicated on output (as opposed to the prefix path, which will be
ignored).

If no translators are specified, all available ones will be considered.

:param package_name: name of the package all interface files belong to
:param interface_files: list of paths to interface definition files
:param output_format: format to translate interface definition files to
:param input_format: optional format to assume for all interface
definition files, deduced from file extensions if not given
:param include_paths: optional list of paths to include dependency
interface definition files from
:param output_path: optional path to directory to hold translated
interface definition files, defaults to the current working directory
:param translators: optional list of translators to use
:returns: list of paths to translated interface definition files
"""
extensions = load_translate_extensions(
specs=translators, strict=bool(translators)
)
if not extensions:
raise RuntimeError('No translate extensions found')

if not input_format:
interface_files_per_format = collections.defaultdict(list)
for interface_file in interface_files:
input_format = os.path.splitext(interface_file)[-1][1:]
interface_files_per_format[input_format].append(interface_file)
else:
interface_files_per_format = {input_format: interface_files}

if include_paths is None:
include_paths = []

if output_path is None:
output_path = pathlib.Path.cwd()
else:
os.makedirs(output_path, exist_ok=True)

translated_interface_files = []
for input_format, interface_files in interface_files_per_format.items():
extension = next((
extension for extension in extensions
if extension.input_format == input_format and
extension.output_format == output_format
), None)

if not extension:
raise RuntimeError('\n'.join([
f"Cannot translate the following files to '{output_format}' format:",
*[f'- {path}' for path in interface_files],
'No translator found'
]))

translated_interface_files.extend(extension.translate(
package_name, interface_files, include_paths, output_path))

return translated_interface_files
1 change: 1 addition & 0 deletions rosidl_cli/rosidl_cli/command/translate/extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ def translate(
definition files from
:param output_path: path to directory to hold translated interface
definition files
:returns: list of paths to translated interface definition files
"""
raise NotImplementedError()

Expand Down