diff --git a/index.rst b/index.rst index afd44ace..ff3a35d4 100644 --- a/index.rst +++ b/index.rst @@ -8,3 +8,6 @@ Cyber Master Infrastructure Solution documentation * roles/index.rst + + +.. roles-overview:: \ No newline at end of file diff --git a/sphinx/conf.py b/sphinx/conf.py index 9ae401ad..2a6aec7e 100644 --- a/sphinx/conf.py +++ b/sphinx/conf.py @@ -54,6 +54,7 @@ extensions = [ "sphinx.ext.autodoc", "myst_parser", 'local_md_files', + 'roles_overview', ] autosummary_generate = True diff --git a/sphinx/roles_overview.py b/sphinx/roles_overview.py new file mode 100644 index 00000000..78cf60b9 --- /dev/null +++ b/sphinx/roles_overview.py @@ -0,0 +1,85 @@ +import os +import glob +import yaml +from docutils import nodes +from docutils.parsers.rst import Directive +from docutils.statemachine import ViewList +from sphinx.util import logging + +logger = logging.getLogger(__name__) + +class RolesOverviewDirective(Directive): + """ + A directive to embed a roles overview as reStructuredText. + + It scans the roles directory (i.e. every folder under "roles") for a + "meta/main.yml" file, reads the category (if provided) and the role’s description + (from galaxy_info.description), and outputs a listing grouped by category. + """ + has_content = False + + def run(self): + env = self.state.document.settings.env + srcdir = env.srcdir + roles_dir = os.path.join(srcdir, 'roles') + if not os.path.isdir(roles_dir): + logger.warning(f"Roles directory not found: {roles_dir}") + error_node = self.state.document.reporter.error( + "Roles directory not found.", line=self.lineno) + return [error_node] + + # Dictionary mapping categories to role entries. + categories = {} + for role_path in glob.glob(os.path.join(roles_dir, '*')): + if os.path.isdir(role_path): + meta_path = os.path.join(role_path, 'meta', 'main.yml') + if os.path.exists(meta_path): + try: + with open(meta_path, 'r', encoding='utf-8') as f: + data = yaml.safe_load(f) + except Exception as e: + logger.warning(f"Error reading YAML file {meta_path}: {e}") + continue + + role_name = os.path.basename(role_path) + category = data.get('category', 'uncategorized') + role_description = data.get('galaxy_info', {}).get('description', '') + role_entry = { + 'name': role_name, + 'description': role_description, + 'link': f'roles/{role_name}/README.md' + } + categories.setdefault(category, []).append(role_entry) + else: + logger.warning(f"meta/main.yml not found for role {role_path}") + + # Sort categories and roles alphabetically. + sorted_categories = sorted(categories.items(), key=lambda x: x[0].lower()) + for category, roles in sorted_categories: + roles.sort(key=lambda r: r['name'].lower()) + + # Build reStructuredText content. + lines = [] + for category, roles in sorted_categories: + lines.append(f".. rubric:: {category}") + lines.append("") + for role in roles: + lines.append(f"* **[`{role['name']}`]({role['link']})**") + if role['description']: + lines.append(f" - {role['description']}") + lines.append("") + + rst_content = "\n".join(lines) + + # Use a ViewList for nested_parse. + rst_lines = ViewList() + for i, line in enumerate(rst_content.splitlines()): + rst_lines.append(line, '') + + container = nodes.container() + self.state.nested_parse(rst_lines, self.content_offset, container) + return [container] + +def setup(app): + app.add_directive("roles-overview", RolesOverviewDirective) + return {'version': '0.1', 'parallel_read_safe': True}