diff --git a/rosdoc2/verbs/build/builders/sphinx_builder.py b/rosdoc2/verbs/build/builders/sphinx_builder.py index 3737d16..54784c2 100644 --- a/rosdoc2/verbs/build/builders/sphinx_builder.py +++ b/rosdoc2/verbs/build/builders/sphinx_builder.py @@ -23,6 +23,7 @@ from ..builder import Builder from ..collect_inventory_files import collect_inventory_files from ..create_format_map_from_package import create_format_map_from_package +from ..generate_interface_docs import generate_interface_docs logger = logging.getLogger('rosdoc2') @@ -32,7 +33,7 @@ def esc_backslash(path): return path.replace('\\', '\\\\') if path else path -def generate_package_toc_entry(*, build_context) -> str: +def generate_package_toc_entry(*, build_context, interface_counts) -> str: """Construct a table of content (toc) entry for the package being processed.""" build_type = build_context.build_type always_run_doxygen = build_context.always_run_doxygen @@ -48,7 +49,10 @@ def generate_package_toc_entry(*, build_context) -> str: toc_entry += toc_entry_py if build_type in ['ament_cmake', 'cmake'] or always_run_doxygen: toc_entry += toc_entry_cpp - + if interface_counts['msg'] > 0: + toc_entry += toc_entry_msg + if interface_counts['srv'] > 0: + toc_entry += toc_entry_srv return toc_entry @@ -451,7 +455,17 @@ def build(self, *, doc_build_folder, output_staging_directory): 'Note: no sourcedir provided by the user and no Sphinx sourcedir was found ' 'in the standard locations, therefore using a default Sphinx configuration.') sourcedir = os.path.join(doc_build_folder, 'default_sphinx_project') - self.generate_default_project_into_directory(sourcedir, package_src_directory) + + # Generate rst documents for interfaces + interface_counts = generate_interface_docs( + package_xml_directory, + self.build_context.package.name, + os.path.join(sourcedir, 'generated') + ) + logger.info(f'interface_counts: {interface_counts}') + + self.generate_default_project_into_directory( + sourcedir, package_src_directory, interface_counts) # Collect intersphinx mapping extensions from discovered inventory files. inventory_files = \ @@ -568,7 +582,8 @@ def locate_sphinx_sourcedir_from_standard_locations(self): return option return None - def generate_default_project_into_directory(self, directory, package_src_directory): + def generate_default_project_into_directory( + self, directory, package_src_directory, interface_counts): """Generate the default project configuration files.""" os.makedirs(directory, exist_ok=True) @@ -590,7 +605,9 @@ def generate_default_project_into_directory(self, directory, package_src_directo template_variables.update({ 'root_title': root_title, 'root_title_underline': '=' * len(root_title), - 'package_toc_entry': generate_package_toc_entry(build_context=self.build_context) + 'package_toc_entry': generate_package_toc_entry( + build_context=self.build_context, + interface_counts=interface_counts) }) with open(os.path.join(directory, 'index.rst'), 'w+') as f: diff --git a/rosdoc2/verbs/build/generate_interface_docs.py b/rosdoc2/verbs/build/generate_interface_docs.py new file mode 100644 index 0000000..242e263 --- /dev/null +++ b/rosdoc2/verbs/build/generate_interface_docs.py @@ -0,0 +1,102 @@ +# Copyright 2022 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. + + +"""Generate rst files for messages and services.""" + +import fnmatch +import os + +# from jinja2 import Template + +iface_fm_rst = """\ +{iface_name} +{name_underline} +This is a ROS {type_name} definition. + +**Source** + +.. literalinclude:: {relative_path} + +""" + +toc_fm_rst = """\ +{title} +{title_underline} + +.. toctree:: + :maxdepth: 1 + :glob: + + {type_ext}/* + +""" + + +def _find_files_with_extension(path, ext): + # Partly adapted from https://github.com/ros-infrastructure/rosdoc_lite + matches = [] + for root, _, filenames in os.walk(path): + for filename in fnmatch.filter(filenames, f'*.{ext}'): + matches.append((os.path.splitext(filename)[0], os.path.join(root, filename))) + return matches + + +def generate_interface_docs(path: str, package: str, output_dir: str): + """ + Generate rst files from messages and services. + + :param str path: Directory path to start search for files + :param str package: Name of containing package + :param str output_dir: Directory path to write output + :return: {'msg':msg_count, 'srv':srv_count} count of files written + :rtype: dict(str, int) + """ + counts = {} + for type_info in (('msg', 'message'), ('srv', 'service')): + count = 0 + (type_ext, type_name) = type_info + interfaces = _find_files_with_extension(path, type_ext) + output_dir_ex = os.path.join(output_dir, type_ext) + title = type_name.capitalize() + ' Definitions' + for interface in interfaces: + (iface_name, iface_path) = interface + relative_path = os.path.relpath(iface_path, start=output_dir_ex) + template_vars = { + 'iface_name': iface_name, + 'name_underline': '=' * len(iface_name), + 'type_name': type_name, + 'package': package, + 'type_ext': type_ext, + 'relative_path': relative_path, + 'title': title, + 'title_underline': '=' * len(title) + } + iface_rst = iface_fm_rst.format_map(template_vars) + + if not os.path.exists(output_dir_ex): + os.makedirs(output_dir_ex) + output_path = os.path.join(output_dir_ex, f'{iface_name}.rst') + with open(output_path, 'w') as f: + f.write(iface_rst) + count += 1 + if count > 0: + # generate a toc entry rst file for this type + toc_rst = toc_fm_rst.format_map(template_vars) + toc_name = type_name + '_definitions.rst' + toc_path = os.path.join(output_dir, toc_name) + with open(toc_path, 'w') as f: + f.write(toc_rst) + counts[type_ext] = count + return counts