Skip to content

Commit

Permalink
Support msgs, services, and actions (#76)
Browse files Browse the repository at this point in the history
* Support msg and srv  and action entries
  • Loading branch information
rkent authored Mar 27, 2024
1 parent 7b364ef commit bccae77
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 5 deletions.
33 changes: 28 additions & 5 deletions rosdoc2/verbs/build/builders/sphinx_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')

Expand All @@ -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
Expand All @@ -42,13 +43,22 @@ def generate_package_toc_entry(*, build_context) -> str:
# inside the string to fall under the `:toctree:` directive
toc_entry_cpp = ' C++ API <generated/index>\n'
toc_entry_py = ' Python API <modules>\n'
toc_entry_msg = ' Message Definitions <generated/message_definitions>\n'
toc_entry_srv = ' Service Definitions <generated/service_definitions>\n'
toc_entry_action = ' Action Definitions <generated/action_definitions>\n'

toc_entry = '\n'

if build_type == 'ament_python' or always_run_sphinx_apidoc or ament_cmake_python:
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
if interface_counts['action'] > 0:
toc_entry += toc_entry_action
return toc_entry


Expand Down Expand Up @@ -451,7 +461,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 = \
Expand Down Expand Up @@ -568,7 +588,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)

Expand All @@ -590,7 +611,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:
Expand Down
100 changes: 100 additions & 0 deletions rosdoc2/verbs/build/generate_interface_docs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# 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, services, and actions."""

import fnmatch
import os

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'), ('action', 'action')):
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
11 changes: 11 additions & 0 deletions test/packages/full_package/action/Fibonacci.action
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# This action is based on the action tutorial
# https://docs.ros.org/en/rolling/Tutorials/Intermediate/Creating-an-Action.html

# order of the Fibonacci sequence we want to compute
int32 order
---
# the final result
int32[] sequence
---
# feedback of what is completed so far
int32[] partial_sequence
3 changes: 3 additions & 0 deletions test/test_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,9 @@ def test_full_package(session_dir):
PKG_NAME,
'python api',
'c++ api',
'message definitions',
'service definitions',
'action definitions'
]
file_includes = [
'generated/index.html'
Expand Down

0 comments on commit bccae77

Please sign in to comment.