Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show standard docs #98

Merged
merged 2 commits into from
Apr 13, 2024
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
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