Skip to content

Commit

Permalink
Show standard docs (#98)
Browse files Browse the repository at this point in the history
* Add standard documents

* Include readme on home page
  • Loading branch information
rkent authored Apr 13, 2024
1 parent 2b8a379 commit cf95ff3
Show file tree
Hide file tree
Showing 8 changed files with 164 additions and 10 deletions.
26 changes: 22 additions & 4 deletions rosdoc2/verbs/build/builders/sphinx_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from ..create_format_map_from_package import create_format_map_from_package
from ..generate_interface_docs import generate_interface_docs
from ..include_user_docs import include_user_docs
from ..standard_documents import generate_standard_document_files, locate_standard_documents

logger = logging.getLogger('rosdoc2')

Expand All @@ -34,7 +35,10 @@ def esc_backslash(path):
return path.replace('\\', '\\\\') if path else path


def generate_package_toc_entry(*, build_context, interface_counts, doc_directories) -> str:
def generate_package_toc_entry(*, build_context,
interface_counts,
doc_directories,
standard_docs) -> 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 @@ -47,6 +51,8 @@ def generate_package_toc_entry(*, build_context, interface_counts, doc_directori
toc_entry_msg = ' Message Definitions <interfaces/message_definitions>\n'
toc_entry_srv = ' Service Definitions <interfaces/service_definitions>\n'
toc_entry_action = ' Action Definitions <interfaces/action_definitions>\n'
toc_entry_standard = ' Standard Documents <standards>\n'
toc_entry_readme = '.. include:: readme_include.rst'
toc_doc_entry = """\
.. toctree::
:titlesonly:
Expand All @@ -68,10 +74,13 @@ def generate_package_toc_entry(*, build_context, interface_counts, doc_directori
if interface_counts['action'] > 0:
toc_entry += toc_entry_action

if standard_docs:
toc_entry += toc_entry_standard
# User documentation
if doc_directories:
toc_entry += toc_doc_entry

if 'readme' in standard_docs:
toc_entry += toc_entry_readme
return toc_entry


Expand Down Expand Up @@ -467,6 +476,12 @@ def build(self, *, doc_build_folder, output_staging_directory):
)
logger.info(f'interface_counts: {interface_counts}')

# locate standard documents
standard_docs = locate_standard_documents(package_xml_directory)
if standard_docs:
generate_standard_document_files(standard_docs, wrapped_sphinx_directory)
logger.info(f'standard_docs: {standard_docs}')

# include user documentation
doc_directories = include_user_docs(package_xml_directory, wrapped_sphinx_directory)
logger.info(f'doc_directories: {doc_directories}')
Expand Down Expand Up @@ -519,7 +534,8 @@ def build(self, *, doc_build_folder, output_staging_directory):
python_src_directory,
intersphinx_mapping_extensions,
interface_counts,
doc_directories)
doc_directories,
standard_docs)

# If the package has python code, then invoke `sphinx-apidoc` before building
has_python = self.build_context.build_type == 'ament_python' or \
Expand Down Expand Up @@ -642,6 +658,7 @@ def generate_wrapping_rosdoc2_sphinx_project_into_directory(
intersphinx_mapping_extensions,
interface_counts,
doc_directories,
standard_docs,
):
"""Generate the rosdoc2 sphinx project configuration files."""
# Generate a default index.rst
Expand All @@ -653,7 +670,8 @@ def generate_wrapping_rosdoc2_sphinx_project_into_directory(
'package_toc_entry': generate_package_toc_entry(
build_context=self.build_context,
interface_counts=interface_counts,
doc_directories=doc_directories)
doc_directories=doc_directories,
standard_docs=standard_docs)
})

with open(os.path.join(wrapped_sphinx_directory, 'index.rst'), 'w+') as f:
Expand Down
112 changes: 112 additions & 0 deletions rosdoc2/verbs/build/standard_documents.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# Copyright 2024 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 shutil


standard_documents_rst = """\
Standard Documents
==================
.. toctree::
:maxdepth: 1
:glob:
standard_docs/*
"""

readme_include_rst = """\
README
======
"""


def locate_standard_documents(package_xml_directory):
"""Locate standard documents."""
names = ['readme', 'license', 'contributing', 'changelog', 'quality_declaration']
found_paths = {}
package_directory_items = os.scandir(package_xml_directory)
for item in package_directory_items:
if not item.is_file():
continue
(basename, ext) = os.path.splitext(item.name)
for name in names:
if name in found_paths:
continue
if basename.lower() == name:
filetype = None
if ext.lower() in ['.md', '.markdown']:
filetype = 'md'
elif ext.lower() == '.rst':
filetype = 'rst'
else:
filetype = 'other'
found_paths[name] = {
'path': item.path,
'filename': item.name,
'type': filetype
}
return found_paths


def generate_standard_document_files(standard_docs, wrapped_sphinx_directory):
"""Generate rst documents to link to standard documents."""
wrapped_sphinx_directory = os.path.abspath(wrapped_sphinx_directory)
standards_sphinx_directory = os.path.join(wrapped_sphinx_directory, 'standard_docs')
standards_original_directory = os.path.join(standards_sphinx_directory, 'original')
if len(standard_docs):
# Create the standards.rst document that will link to the actual documents
os.makedirs(standards_sphinx_directory, exist_ok=True)
os.makedirs(standards_original_directory, exist_ok=True)
standard_documents_rst_path = os.path.join(
wrapped_sphinx_directory, 'standards.rst')
with open(standard_documents_rst_path, 'w+') as f:
f.write(standard_documents_rst)

for key, standard_doc in standard_docs.items():
# Copy the original document to the sphinx project
shutil.copy(standard_doc['path'], standards_original_directory)
# generate the file according to type
file_contents = f'{key.upper()}\n'
# using ')' as a header marker to assure the name is the title
file_contents += ')' * len(key) + '\n\n'
file_type = standard_doc['type']
file_path = f"original/{standard_doc['filename']}"
if file_type == 'rst':
file_contents += f'.. include:: {file_path}\n'
elif file_type == 'md':
file_contents += f'.. include:: {file_path}\n'
file_contents += ' :parser: myst_parser.sphinx_\n'
else:
file_contents += f'.. literalinclude:: {file_path}\n'
file_contents += ' :language: none\n'
with open(os.path.join(standards_sphinx_directory, f'{key.upper()}.rst'), 'w+') as f:
f.write(file_contents)
if key == 'readme':
# We create a second README to use with include
file_contents = readme_include_rst
file_path = f"standard_docs/original/{standard_doc['filename']}"
if file_type == 'rst':
file_contents += f'.. include:: {file_path}\n'
elif file_type == 'md':
file_contents += f'.. include:: {file_path}\n'
file_contents += ' :parser: myst_parser.sphinx_\n'
else:
file_contents += f'.. literalinclude:: {file_path}\n'
file_contents += ' :language: none\n'
with open(os.path.join(wrapped_sphinx_directory, 'readme_include.rst'), 'w+') as f:
f.write(file_contents)
2 changes: 2 additions & 0 deletions test/packages/full_package/doc/somethingElse.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Alternate Markdown type
.markdown should also be recognized as markdown.
1 change: 1 addition & 0 deletions test/packages/full_package/quality_declaration.markdown
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This document is a declaration of software quality based on the guidelines in [REP-2004](https://www.ros.org/reps/rep-2004.html).
3 changes: 0 additions & 3 deletions test/packages/only_messages/README.md

This file was deleted.

5 changes: 5 additions & 0 deletions test/packages/only_messages/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
This is the readme
==================

This file is included to make sure that the standard documents appear when there is
no other documentation. Also, this is an rst readme.
1 change: 1 addition & 0 deletions test/packages/only_python/README.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
This is a README as simple text.
24 changes: 21 additions & 3 deletions test/test_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,18 +84,20 @@ def do_test_package(
file_includes=[],
file_excludes=[],
links_exist=[],
fragments=[],
) -> None:
"""Test that package documentation exists and includes/excludes certain text.
:param pathlib.Path work_path: path where generated files were placed
:param list[str] includes: lower case text found in index.html data
:param list[str] includes: lower case text found exactly in index.html data
:param list[str] excludes: lower case text not found in index.html data
:param list[str] file_includes: path to files
(relative to root index.html directory) of files that should exist
:param list[str] file_excludes: path to files
(relative to root index.html directory) of files that should not exist
:param list[str] links_exist: Confirm that 1) a link exists containing this text, and
2) the link is a valid file
:param list[str] fragments: lower case text found partially in index.html data
"""
logger.info(f'*** Testing package {name} work_path {work_path}')
output_dir = work_path / 'output'
Expand Down Expand Up @@ -157,6 +159,16 @@ def do_test_package(
assert link_path.is_file(), \
f'file represented by <{found_item}> should exist at <{link_path}>'

# look for fragments of text
for item in fragments:
found_fragment = False
for text in parser.content:
if item in text:
found_fragment = True
break
assert found_fragment, \
f'html should have text fragment <{item}>'


def test_minimum_package(session_dir):
"""Tests of a package containing as little as possible."""
Expand Down Expand Up @@ -199,23 +211,29 @@ def test_full_package(session_dir):
'service definitions',
'action definitions',
'instructions', # has documentation
'changelog',
]
file_includes = [
'generated/index.html'
]
links_exist = [
'full_package.dummy.html',
'modules.html',
'user_docs/morestuff/more_of_more/subsub.html' # a deep documentation file
'user_docs/morestuff/more_of_more/subsub.html', # a deep documentation file
'standards.html',
]
excludes = [
'dontshowme'
]
fragments = [
'this is the package readme.',
]
do_test_package(PKG_NAME, session_dir,
includes=includes,
file_includes=file_includes,
excludes=excludes,
links_exist=links_exist)
links_exist=links_exist,
fragments=fragments)


def test_only_python(session_dir):
Expand Down

0 comments on commit cf95ff3

Please sign in to comment.