Optimized role categories

This commit is contained in:
Kevin Veen-Birkenbach 2025-03-17 00:28:22 +01:00
parent 281413d571
commit 4697e71c54
No known key found for this signature in database
GPG Key ID: 44D8F11FD62F878E
2 changed files with 49 additions and 33 deletions

View File

@ -1,3 +1,4 @@
myst-parser myst-parser
sphinx sphinx
sphinxawesome-theme sphinxawesome-theme
docutils

View File

@ -1,21 +1,22 @@
import os import os
import glob import glob
import re
import yaml import yaml
from docutils import nodes from docutils import nodes
from docutils.parsers.rst import Directive
from docutils.statemachine import ViewList
from sphinx.util import logging from sphinx.util import logging
from docutils.parsers.rst import Directive
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class RolesOverviewDirective(Directive): class RolesOverviewDirective(Directive):
""" """
A directive to embed a roles overview as reStructuredText. A directive to embed a roles overview as reStructuredText.
It scans the roles directory (i.e. every folder under "roles") for a It scans the roles directory (each folder under "roles") for a "meta/main.yml" file,
"meta/main.yml" file, reads the roles galaxy tags (from galaxy_info.galaxy_tags) reads the roles galaxy tags and description, and outputs an overview grouped by tag.
and description (from galaxy_info.description), and outputs a listing grouped For each role, it attempts to extract a level1 heading from its README.md as the title.
by each tag. Roles without galaxy tags are grouped under "uncategorized". If no title is found, the role folder name is used.
The title is rendered as a clickable link to the role's README.md.
""" """
has_content = False has_content = False
@ -29,7 +30,7 @@ class RolesOverviewDirective(Directive):
"Roles directory not found.", line=self.lineno) "Roles directory not found.", line=self.lineno)
return [error_node] return [error_node]
# Dictionary mapping tags to role entries. # Gather role entries grouped by tag.
categories = {} categories = {}
for role_path in glob.glob(os.path.join(roles_dir, '*')): for role_path in glob.glob(os.path.join(roles_dir, '*')):
if os.path.isdir(role_path): if os.path.isdir(role_path):
@ -43,7 +44,20 @@ class RolesOverviewDirective(Directive):
continue continue
role_name = os.path.basename(role_path) role_name = os.path.basename(role_path)
# Try to get galaxy_tags from galaxy_info. If none, use "uncategorized". # Determine title from README.md if available.
readme_path = os.path.join(role_path, 'README.md')
title = role_name
if os.path.exists(readme_path):
try:
with open(readme_path, 'r', encoding='utf-8') as f:
for line in f:
match = re.match(r'^#\s+(.*)$', line)
if match:
title = match.group(1).strip()
break
except Exception as e:
logger.warning(f"Error reading README.md for {role_name}: {e}")
galaxy_info = data.get('galaxy_info', {}) galaxy_info = data.get('galaxy_info', {})
tags = galaxy_info.get('galaxy_tags', []) tags = galaxy_info.get('galaxy_tags', [])
if not tags: if not tags:
@ -51,11 +65,11 @@ class RolesOverviewDirective(Directive):
role_description = galaxy_info.get('description', '') role_description = galaxy_info.get('description', '')
role_entry = { role_entry = {
'name': role_name, 'name': role_name,
'title': title,
'description': role_description, 'description': role_description,
'link': f'roles/{role_name}/README.md', 'link': f'roles/{role_name}/README.md',
'tags': tags, 'tags': tags,
} }
# Add this role to every tag it belongs to.
for tag in tags: for tag in tags:
categories.setdefault(tag, []).append(role_entry) categories.setdefault(tag, []).append(role_entry)
else: else:
@ -66,30 +80,31 @@ class RolesOverviewDirective(Directive):
for tag, roles in sorted_categories: for tag, roles in sorted_categories:
roles.sort(key=lambda r: r['name'].lower()) roles.sort(key=lambda r: r['name'].lower())
# Build reStructuredText content. # Build the document structure.
lines = []
for tag, roles in sorted_categories:
lines.append(f".. rubric:: {tag}")
lines.append("")
for role in roles:
# Render the role name as a hyperlink in reStructuredText.
lines.append(f"* `{role['name']} <{role['link']}>`_")
# Insert a line break before the description.
if role['description']:
lines.append("")
lines.append(f" {role['description']}")
lines.append("")
lines.append("")
rst_content = "\n".join(lines)
# Use a ViewList for nested_parse.
rst_lines = ViewList()
for line in rst_content.splitlines():
rst_lines.append(line, '<roles-overview>')
container = nodes.container() container = nodes.container()
self.state.nested_parse(rst_lines, self.content_offset, container)
# For each category add a rubric heading.
for tag, roles in sorted_categories:
rubric = nodes.rubric(text=tag)
container += rubric
# For each role create a separate section.
for role in roles:
# Create a section with an explicit ID.
section_id = nodes.make_id(role['title'])
section = nodes.section(ids=[section_id])
# Create a title node that contains a reference.
title_node = nodes.title()
reference = nodes.reference(text=role['title'], refuri=role['link'])
title_node += reference
section += title_node
if role['description']:
para = nodes.paragraph(text=role['description'])
section += para
container += section
return [container] return [container]
def setup(app): def setup(app):