Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
3d437e4
OPENR-89: Simple test for adding an enhance job and run script on cha…
keithkirkwood-3di May 1, 2026
7351701
OPENR-89: Return the test workflow to original state
keithkirkwood-3di May 4, 2026
20efaf8
OPENR-89: Use new workflow based on push. List out files which remain…
keithkirkwood-3di May 4, 2026
7377972
OPENR-89: Fix to fetch history so that SHAs exist
keithkirkwood-3di May 4, 2026
39eb9ac
OPENR-89: Test script in wrong location
keithkirkwood-3di May 4, 2026
292e455
OPENR-89: Creat RST-specific parsing and updates in new module, and u…
keithkirkwood-3di May 4, 2026
6fd04f8
OPENR-89: Fix bug using base filename for enhance results data
keithkirkwood-3di May 4, 2026
93734f4
OPENR-89: Install python dependencies in workflow
keithkirkwood-3di May 5, 2026
88bf36a
OPENR-89: Prompt adjustments
keithkirkwood-3di May 5, 2026
2b8021e
OPENR-89: Only make API calls for fields which do not exist
keithkirkwood-3di May 5, 2026
9badeb9
OPENR-89: Enhance only RST files
keithkirkwood-3di May 5, 2026
0ededa3
OPENR-89: Small tweaks to logging and constants
keithkirkwood-3di May 5, 2026
e6389f8
OPENR-89: Adding some sanity check validation for generated values - …
keithkirkwood-3di May 6, 2026
b0513cd
OPENR-89: Add some unit tests for central enhance topics module
keithkirkwood-3di May 6, 2026
f506799
add pagefind from prototype
May 18, 2026
25a2cff
Revert "add pagefind from prototype"
May 18, 2026
795f1a0
update RT Corporation url to fix build
3di-techx May 19, 2026
5886634
add page find
3di-techx May 20, 2026
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
30 changes: 30 additions & 0 deletions .github/workflows/enhance.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Enhance content

on: push

jobs:
enhance:
# Runs only on forks when contributor pushes to their fork
if: github.event.repository.fork == true
runs-on: ubuntu-24.04
steps:
- name: Checkout
# Using checkout v5, as v4 was warning that it will soon be deprecated (Node 20)
uses: actions/checkout@v5
with:
fetch-depth: 0

- name: Setup Python
# Using setup-python v6, as v5 has same warning as above
uses: actions/setup-python@v6
with:
python-version: '3.12'

- name: Install dependencies
run: pip install --no-warn-script-location --user -r requirements.txt -c constraints.txt

- name: Enhance topics
env:
BASE_SHA: ${{ github.event.before }}
HEAD_SHA: ${{ github.event.after }}
run: make enhance-topics
18 changes: 18 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,15 @@ jobs:
- name: Build the docs
run: make html


- name: Setup Node.js (Pagefind)
uses: actions/setup-node@v4
with:
node-version: '20'

- name: Index HTML with Pagefind
run: make pagefind

- name: Upload document artifacts
uses: actions/upload-artifact@v4
id: artifact-upload-step
Expand Down Expand Up @@ -147,3 +156,12 @@ jobs:

- name: Build the docs
run: make multiversion


- name: Setup Node.js (Pagefind)
uses: actions/setup-node@v4
with:
node-version: '20'

- name: Index HTML with Pagefind
run: make pagefind
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ _build/
__pycache__
ros2doc/
.DS_Store
.env
19 changes: 18 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,26 @@ multiversion: Makefile
@echo "<html><head><meta http-equiv=\"refresh\" content=\"0; url=kilted/index.html\" /></head></html>" > build/html/index.html
$(PYTHON) make_sitemapindex.py

# Pagefind static search index (requires Node.js / npx). Run after html or multiversion.
PAGEFIND_VERSION ?= 1.5.2
pagefind:
npx -y pagefind@$(PAGEFIND_VERSION) --site "$(OUT)/html"


# Convenience: Sphinx build + Pagefind index (does not replace plain html / multiversion).
html-search:
$(MAKE) html
$(MAKE) pagefind

multiversion-search: multiversion
$(MAKE) pagefind

%: Makefile
@$(BUILD) -M $@ "$(SOURCE)" "$(OUT)" $(OPTS)

enhance-topics:
git diff --name-only --diff-filter=d $(BASE_SHA) $(HEAD_SHA) | xargs -r $(PYTHON) scripts/enhance_topics.py

lint:
./sphinx-lint-with-ros source

Expand Down Expand Up @@ -61,4 +78,4 @@ linkcheck:
@echo
@echo "Check finished. Report is in $(LINKCHECKDIR)."

.PHONY: help Makefile multiversion test test-tools linkcheck lint spellcheck check-dictionaries sort-dictionaries
.PHONY: help Makefile multiversion pagefind test test-tools linkcheck lint spellcheck check-dictionaries sort-dictionaries
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,41 @@ To test building the multisite version deployed to the website use:

**NB:** This will ignore local workspace changes and build from the branches.

### Pagefind search index

After `make html` or `make multiversion`, run [Pagefind](https://pagefind.app/) so the built HTML under `build/html` is indexed and `build/html/pagefind/` is written (search bundle and Component UI assets). From the repo root:

`make pagefind`

Or use convenience targets that run Sphinx and Pagefind in one step:

- `make html-search` — `make html` then `make pagefind`
- `make multiversion-search` — `make multiversion` then `make pagefind`

Plain `make html` and `make multiversion` do **not** run Pagefind (Node.js is only required when you index search).

This requires **Node.js** (for `npx`). Pin the CLI with `PAGEFIND_VERSION` in the Makefile if needed.

To preview search locally, serve the site over HTTP (Pagefind may not load from `file://`), for example from the repo root:

`python -m http.server 8000 --directory build/html`

Then open `http://localhost:8000/` in a browser.

#### Search results page verification

After `make html` and `make pagefind`, serve `build/html` over HTTP and check:

1. **Direct URL** — Open `http://localhost:8000/search.html?q=tutorial` (or the same path under a distro prefix for multiversion builds). The input should show the query and results should load (not stay empty or skeleton-only).
2. **Modal redirect** — From a nested page (e.g. a tutorial), open the sidebar search modal (Ctrl/Cmd+K), type a term, press Enter. You should land on the search page with `?q=` set and matching results visible.
3. **Empty query** — Open `search.html` with no `q` parameter. The page should load without errors; no search is run until you type in the input.
4. **Result metadata** — Search for `Ubuntu deb` and open a result card. Metadata labels (e.g. Area, Content Type, Experience) should match that page’s `<head>` `<meta name="..." content="...">` tags from its `.. meta::` block (e.g. `area: installation` on the Ubuntu deb install page), not URL-path guesses.

In DevTools Network, confirm `pagefind/` bundle requests return 200 (not 404).

The production [Jenkins doc job](https://build.ros.org/job/doc_ros2doc) should run the same `pagefind` step on `build/html` after Sphinx so deployed pages include the search bundle.


### Note for Windows (WSL) Users

When building the documentation on windows using WSL, it is recommended to clone and work with this repository inside the Linux filesystem (for example, under `/home/<user>/`) rather than under `/mnt/c`.
Expand Down
26 changes: 25 additions & 1 deletion conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,29 @@
'sphinx_adopters',
'sphinxcontrib.googleanalytics',
'sphinxcontrib.mermaid',
'pagefind_meta',
'showmeta',
]

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_filter_labels = {
'contentType': 'Content type',
}

pagefind_result_meta_order = [
'product',
'distro',
'area',
'capability',
'contentType',
'experience',

]

# Intersphinx mapping
Expand Down Expand Up @@ -168,6 +191,7 @@
'DISTRO_TITLE': 'Rolling',
'DISTRO_TITLE_FULL': 'Rolling Ridley',
'REPOS_FILE_BRANCH': 'rolling',
'PRODUCT': 'ROS 2',
}

html_favicon = 'favicon.ico'
Expand All @@ -181,7 +205,7 @@
html_sourcelink_suffix = ''

# Relative to html_static_path
html_css_files = ['custom.css', 'adopters.css']
html_css_files = ['custom.css', 'adopters.css', 'pagefind-docsearch.css']
html_js_files = ['adopters.js']

# -- Options for HTMLHelp output ------------------------------------------
Expand Down
4 changes: 4 additions & 0 deletions constraints.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ imagesize==1.4.1
iniconfig==2.1.0
Jinja2==3.1.6
MarkupSafe==3.0.3
openai==2.33.0
packaging==25.0
pluggy==1.6.0
polib==1.2.0
Pygments==2.19.2
pytest==8.4.2
python-dotenv==1.1.0
PyYAML==6.0.3
regex==2025.9.18
requests==2.32.5
Expand All @@ -39,4 +41,6 @@ sphinxcontrib-mermaid==1.0.0
sphinxcontrib-qthelp==2.0.0
sphinxcontrib-serializinghtml==2.0.0
stevedore==5.5.0
tenacity==9.1.4
timeout-decorator==0.5.0
urllib3==2.5.0
70 changes: 70 additions & 0 deletions plugins/meta_util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Copyright 2026 Open Robotics — shared helpers for ``.. meta::`` / Pagefind
"""
Collect every ``.. meta::`` field from the doctree, sanitize keys, and expand
``{MACRO}`` placeholders using the Sphinx ``macros`` config (longest keys first).

Sphinx / the HTML theme may also emit plain ``<meta>`` tags for the same fields.
The Pagefind extension emits additional tags with ``data-pagefind-filter`` and may
split comma-separated values into multiple tags for faceted search.
"""

from __future__ import annotations

import re
from typing import Dict, List, Optional

from docutils import nodes

# HTML ``<meta name="...">`` names should be conservative; allow common patterns.
_META_NAME_RE = re.compile(r'^[A-Za-z0-9_.:-]+$')


def sanitize_meta_key(raw: str) -> Optional[str]:
s = str(raw).strip()
if not s or not _META_NAME_RE.match(s):
return None
return s


def all_doctree_meta(doctree: Optional[nodes.document]) -> Dict[str, str]:
"""Return last-wins mapping of every ``nodes.meta`` ``name``/``property`` → ``content``."""
if doctree is None:
return {}

out: Dict[str, str] = {}
for meta in doctree.findall(nodes.meta):
if meta.get('http-equiv'):
continue
content = meta.get('content')
if not content:
continue
key: Optional[str] = None
name = meta.get('name')
if name:
key = sanitize_meta_key(str(name))
else:
prop = meta.get('property')
if prop:
key = sanitize_meta_key(str(prop))
if not key:
continue
out[key] = str(content).strip()
return out


def expand_meta_macros(text: str, macros: Dict[str, str]) -> str:
"""Expand ``{KEY}`` placeholders; longer macro names first to avoid partial matches."""
result = text
for key, value in sorted(macros.items(), key=lambda kv: len(kv[0]), reverse=True):
result = result.replace(f'{{{key}}}', value)
return result


def expand_all_meta_values(meta: Dict[str, str], macros: Dict[str, str]) -> Dict[str, str]:
"""Apply ``expand_meta_macros`` to every meta value."""
return {k: expand_meta_macros(v, macros) for k, v in meta.items()}


def split_meta_values(value: str) -> List[str]:
"""Return comma-separated metadata values as individual Pagefind values."""
return [part.strip() for part in value.split(',') if part.strip()]
Loading
Loading