Skip to content
Open
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ ros2doc/
# Downloaded at HTML build time for browser-side package lists (large).
source/_static/rosdistro_cache/*.yaml.gz
.env

# Downloaded at HTML build time for browser-side package lists (large).
source/_static/rosdistro_cache/*.yaml.gz
62 changes: 1 addition & 61 deletions conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,39 +91,8 @@
'sphinxcontrib.mermaid',
'ros_related_packages',
'ros_related_articles',
'short_description',
'pagefind_meta',
'showmeta',
]

# pagefind search index configuration.

pagefind_merge_enabled = False
pagefind_merge_package_pkgs = []
pagefind_merge_index_base = 'https://docs.ros.org'
pagefind_merge_index_overrides = {}
pagefind_merge_filter_per_pkg = None
pagefind_merge_index_weight_per_pkg = None

# Pagefind search UI (modal + /search.html): result metadata lines and facet sidebar.
# Dict keys = .. meta:: field names; values = display labels.
# Order here is facet dropdown order and result-meta line order (allowlist).
# Only listed keys are indexed as facets; keys must exist on at least one page in the build.
# Other meta (e.g. description, keywords) stays SEO-only and does not appear in the facet sidebar.

pagefind_result_meta_order = {
'product': 'Product',
'distribution': 'Distribution',
'area': 'Area',
'capability': 'Capability',
'community': 'Community',
'installation': 'Installation',
'framework': 'Framework',
'tool': 'Tools',
'contentType': 'Content type',
'experience': 'Level',
}

# Intersphinx mapping

intersphinx_mapping = {
Expand Down Expand Up @@ -195,37 +164,12 @@
'rolling': 'Rolling Ridley',
}

# Tier 1 Ubuntu platform for binary deb installs (see the release page for each distro)
distro_ubuntu_deb_platform = {
'crystal': 'Ubuntu Bionic (18.04)',
'dashing': 'Ubuntu Bionic (18.04)',
'eloquent': 'Ubuntu Bionic (18.04)',
'foxy': 'Ubuntu Focal (20.04)',
'galactic': 'Ubuntu Focal (20.04)',
'humble': 'Ubuntu Jammy (22.04)',
'iron': 'Ubuntu Jammy (22.04)',
'jazzy': 'Ubuntu Noble (24.04)',
'kilted': 'Ubuntu Noble (24.04)',
'lyrical': 'Ubuntu Resolute Raccoon (26.04)',
'rolling': 'Ubuntu Resolute Raccoon (26.04)',
}

# ARM64 Ubuntu status page suffix on repo.ros2.org (ros_{distro}_{suffix}.html)
distro_arm_status_suffix = {
'humble': 'ujv8',
'iron': 'ujv8',
'lyrical': 'armv8',
}

# These default values will be overridden when building multiversion
macros = {
'DISTRO': 'rolling',
'DISTRO_TITLE': 'Rolling',
'DISTRO_TITLE_FULL': 'Rolling Ridley',
'DISTRO_UBUNTU_DEB_PLATFORM': distro_ubuntu_deb_platform['rolling'],
'DISTRO_ARM_STATUS_SUFFIX': distro_arm_status_suffix.get('rolling', 'unv8'),
'REPOS_FILE_BRANCH': 'rolling',
'PRODUCT': 'ROS 2',
}

html_favicon = 'favicon.ico'
Expand All @@ -239,7 +183,7 @@
html_sourcelink_suffix = ''

# Relative to html_static_path
html_css_files = ['custom.css', 'adopters.css', 'pagefind-docsearch.css']
html_css_files = ['custom.css', 'adopters.css']
html_js_files = [
('vendor/pako.min.js', {'defer': ''}),
('vendor/js-yaml.min.js', {'defer': ''}),
Expand Down Expand Up @@ -422,10 +366,6 @@ def smv_rewrite_configs(app, config):
'DISTRO': distro,
'DISTRO_TITLE': distro.title(),
'DISTRO_TITLE_FULL': distro_full_names[distro],
'DISTRO_UBUNTU_DEB_PLATFORM': distro_ubuntu_deb_platform.get(
distro, 'Ubuntu Noble (24.04)'
),
'DISTRO_ARM_STATUS_SUFFIX': distro_arm_status_suffix.get(distro, 'unv8'),
'REPOS_FILE_BRANCH' : distro,
}

Expand Down
126 changes: 106 additions & 20 deletions plugins/ros_related_articles.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,20 +87,114 @@ def _normalized_value(raw: str) -> str:
return ' '.join(raw.strip().lower().split())


def _previous_sibling(node: nodes.Node) -> nodes.Node | None:
"""Return the node immediately before *node* among its parent's children."""
parent = node.parent
if parent is None:
return None
children = parent.children
idx = children.index(node)
if idx == 0:
return None
return children[idx - 1]


def _next_sibling(node: nodes.Node) -> nodes.Node | None:
"""Return the node immediately after *node* among its parent's children."""
parent = node.parent
if parent is None:
return None
children = parent.children
idx = children.index(node)
if idx + 1 >= len(children):
return None
return children[idx + 1]


def _ensure_class(node: nodes.Element, class_name: str) -> None:
"""Append *class_name* to *node* if it is not already present."""
classes = list(node.get('classes', []) or [])
if class_name not in classes:
classes.append(class_name)
node['classes'] = classes


def _append_article_items(
bullet_list: nodes.bullet_list,
matches: List[RelatedArticle],
app,
fromdocname: str,
) -> None:
"""Append related-article links as list items to *bullet_list*."""
for item in matches:
refuri = app.builder.get_relative_uri(fromdocname, item['docname'])
link = nodes.reference('', item['title'], refuri=refuri)
entry = nodes.list_item()
para = nodes.paragraph()
para += link
entry += para
bullet_list += entry


def _absorb_bullet_list(
target: nodes.bullet_list,
source: nodes.bullet_list,
) -> None:
"""Move all list items from *source* onto the end of *target*."""
for child in list(source.children):
if isinstance(child, nodes.list_item):
source.remove(child)
target.append(child)


def _resolve_related_articles_list(
node: RosRelatedArticlesNode,
matches: List[RelatedArticle],
app,
fromdocname: str,
) -> None:
"""Replace *node* with generated links, merging adjacent manual bullet lists."""
prev = _previous_sibling(node)
next_sib = _next_sibling(node)
prev_list = prev if isinstance(prev, nodes.bullet_list) else None
next_list = next_sib if isinstance(next_sib, nodes.bullet_list) else None

if prev_list is not None:
target = prev_list
_ensure_class(target, 'related-articles')
else:
target = nodes.bullet_list(classes=['related-articles'])

_append_article_items(target, matches, app, fromdocname)

if next_list is not None:
_absorb_bullet_list(target, next_list)
next_list.replace_self([])

if prev_list is not None:
node.replace_self([])
else:
node.replace_self(target)


class RosRelatedArticlesNode(nodes.General, nodes.Element):
"""Placeholder node replaced during ``doctree-resolved``."""


class RosRelatedArticlesDirective(SphinxDirective):
"""Emit a placeholder for static related-article links.
"""Emit a placeholder replaced by a bullet list of related article links.

Uses page metadata values from ``.. meta::``:
Write the section intro (e.g. ``Related articles:``) in the RST source
before this directive. Optional bullet items immediately before or after
the directive are merged into the same list as the generated links.

.. code-block:: rst

.. meta::
:area: Tutorials
:experience: Beginner

Uses page metadata values from ``.. meta::`` (see above).
"""

has_content = False
Expand Down Expand Up @@ -173,7 +267,7 @@ def build_related_articles_index(app, env) -> None:


def resolve_related_articles(app, doctree, fromdocname) -> None:
"""Replace placeholders with static paragraph + list markup."""
"""Replace placeholders with a static bullet list."""
index: List[RelatedArticle] = getattr(app.env, 'ros_related_articles_index', [])
for node in list(doctree.traverse(RosRelatedArticlesNode)):
area = _normalized_value(str(node.get('area', '')))
Expand All @@ -189,27 +283,19 @@ def resolve_related_articles(app, doctree, fromdocname) -> None:
matches.sort(key=lambda item: item['title'].lower())
matches = matches[:max_items]

prev = _previous_sibling(node)
next_sib = _next_sibling(node)
prev_list = prev if isinstance(prev, nodes.bullet_list) else None
next_list = next_sib if isinstance(next_sib, nodes.bullet_list) else None

if not matches:
if prev_list is not None and next_list is not None:
_absorb_bullet_list(prev_list, next_list)
next_list.replace_self([])
node.replace_self([])
continue

container = nodes.container(classes=['related-articles'])
intro = nodes.paragraph()
intro += nodes.Text('Related articles:')
container += intro

bullets = nodes.bullet_list()
for item in matches:
refuri = app.builder.get_relative_uri(fromdocname, item['docname'])
link = nodes.reference('', item['title'], refuri=refuri)
entry = nodes.list_item()
para = nodes.paragraph()
para += link
entry += para
bullets += entry
container += bullets

node.replace_self(container)
_resolve_related_articles_list(node, matches, app, fromdocname)


def setup(app):
Expand Down
4 changes: 4 additions & 0 deletions plugins/ros_related_packages.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@ def _positive_int_option(argument: str) -> int:
class RosRelatedPackagesDirective(SphinxDirective):
"""Emit a placeholder ``div`` filled at runtime by ``related_packages.js``.

Write the section intro (e.g. ``Packages/reference:``) in the RST source
before this directive. Optional bullet items immediately before or after
the directive are merged into the same list at runtime.

Filter criteria (currently ``build-type``) should be supplied as **HTML meta tags**
via Docutils ``.. meta::`` so values appear in ``<head>`` and not in the page body::

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
.. meta::
:area: ROS-framework
:experience: beginner, intermediate

Migrating Interfaces
====================

Expand Down Expand Up @@ -59,4 +63,6 @@ This will replace ``add_message_files`` and ``add_service_files`` listing of all
Related content
---------------

Related articles:

.. ros-related-articles::
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,13 @@ Steps
Related content
---------------

Related articles:

* some stuff here

.. ros-related-articles::

Packages/reference:

.. ros-related-packages::

Original file line number Diff line number Diff line change
Expand Up @@ -412,4 +412,6 @@ Now that you understand the details behind creating, building and sourcing your
Related content
---------------

Related articles:

.. ros-related-articles::
Original file line number Diff line number Diff line change
Expand Up @@ -545,4 +545,6 @@ You'll start with a simple publisher/subscriber system, which you can choose to
Related content
---------------

Related articles:

.. ros-related-articles::
52 changes: 42 additions & 10 deletions source/_static/related_packages.js
Original file line number Diff line number Diff line change
Expand Up @@ -308,9 +308,19 @@
return a.localeCompare(b);
});
var picked = names.slice(0, max);
var prevList = el.previousElementSibling;
prevList = prevList && prevList.tagName === 'UL' ? prevList : null;
var nextList = el.nextElementSibling;
nextList = nextList && nextList.tagName === 'UL' ? nextList : null;
var mergeList;

if (prevList) {
mergeList = prevList;
} else {
mergeList = document.createElement('ul');
mergeList.className = 'related-packages__list';
}

var ul = document.createElement('ul');
ul.className = 'related-packages__list';
var j;
for (j = 0; j < picked.length; j += 1) {
var pkg = picked[j];
Expand All @@ -322,24 +332,46 @@
a.rel = 'noopener noreferrer';
li.appendChild(a);
li.appendChild(document.createTextNode(': ' + description));
ul.appendChild(li);
mergeList.appendChild(li);
}

el.innerHTML = '';
el.classList.remove('related-packages--loading');

if (picked.length === 0) {
el.innerHTML = '';
if (prevList && nextList) {
while (nextList.firstChild) {
prevList.appendChild(nextList.firstChild);
}
nextList.remove();
prevList.classList.add('related-packages__list');
}
if (prevList || nextList) {
el.remove();
return;
}
var p = document.createElement('p');
p.className = 'related-packages__empty';
p.textContent = 'No packages matched this filter.';
el.appendChild(p);
} else {
var intro = document.createElement('p');
intro.className = 'related-packages__intro';
intro.textContent = 'Packages/reference: ';
el.appendChild(intro);
el.appendChild(ul);
return;
}

if (nextList) {
while (nextList.firstChild) {
mergeList.appendChild(nextList.firstChild);
}
nextList.remove();
}

if (prevList) {
mergeList.classList.add('related-packages__list');
el.remove();
return;
}

el.parentNode.insertBefore(mergeList, el);
el.remove();
}

function fillAll() {
Expand Down
Loading