Source code for sphinx_autodoc_toml.extension

"""Sphinx extension for autodoc-toml directive.

.. spec:: The extension MUST provide an autodoc-toml directive for Sphinx.
   :id: S_EXT_001
   :status: implemented
   :tags: sphinx, directive
   :links: R_SPHINX_001

.. spec:: The autodoc-toml directive MUST accept a file path argument.
   :id: S_EXT_002
   :status: implemented
   :tags: sphinx, directive
   :links: R_SPHINX_002

.. spec:: The extension MUST resolve both relative and absolute file paths.
   :id: S_EXT_003
   :status: implemented
   :tags: sphinx, paths
   :links: R_SPHINX_003

.. spec:: The extension MUST generate proper docutils nodes from doc-comments.
   :id: S_EXT_004
   :status: implemented
   :tags: sphinx, generation
   :links: R_SPHINX_004

.. spec:: The extension MUST use nested_parse to support embedded Sphinx directives.
   :id: S_EXT_005
   :status: implemented
   :tags: sphinx, directives
   :links: R_SPHINX_005

.. spec:: The extension MUST log clear warnings and errors for invalid files.
   :id: S_EXT_006
   :status: implemented
   :tags: sphinx, error-handling
   :links: R_SPHINX_006

.. spec:: The extension MUST display TOML content in collapsible admonitions.
   :id: S_EXT_007
   :status: implemented
   :tags: sphinx, ui
   :links: R_SPHINX_007
"""

from pathlib import Path
from typing import Any, Dict, List

from docutils import nodes
from docutils.parsers.rst import Directive, directives
from docutils.statemachine import ViewList
from sphinx.application import Sphinx
from sphinx.util import logging

from sphinx_autodoc_toml.parser import DocComment, parse_toml_file

logger = logging.getLogger(__name__)


[docs] class AutodocTomlDirective(Directive): """ Directive to automatically document TOML files with embedded doc-comments. Usage: .. autodoc-toml:: path/to/file.toml :show-all: :recursive: """ # Directive configuration has_content = False required_arguments = 1 # Path to the TOML file optional_arguments = 0 final_argument_whitespace = True option_spec = { "show-all": directives.flag, "recursive": directives.flag, }
[docs] def run(self) -> List[nodes.Node]: """ Execute the directive. Returns: List of docutils nodes to insert into the document """ # Get the TOML file path from the argument toml_path_str = self.arguments[0] # Resolve the path relative to the source directory env = self.state.document.settings.env source_dir = Path(env.srcdir) # Handle both absolute and relative paths toml_path = Path(toml_path_str) if not toml_path.is_absolute(): toml_path = source_dir / toml_path # Verify the file exists if not toml_path.exists(): logger.warning( f"TOML file not found: {toml_path}", location=(env.docname, self.lineno), ) return [] # Parse the TOML file and extract doc-comments try: doc_comments = parse_toml_file(toml_path) except Exception as e: logger.error( f"Failed to parse TOML file {toml_path}: {e}", location=(env.docname, self.lineno), ) return [] if not doc_comments: logger.info( f"No doc-comments found in {toml_path}", location=(env.docname, self.lineno), ) return [] # Generate the documentation nodes result_nodes = [] for doc_comment in doc_comments: # Create a section for this TOML item section_nodes = self._create_section_for_doc_comment(doc_comment, toml_path) result_nodes.extend(section_nodes) return result_nodes
def _create_section_for_doc_comment( self, doc_comment: DocComment, toml_path: Path ) -> List[nodes.Node]: """ Create documentation nodes for a single doc-comment. Args: doc_comment: The DocComment object Returns: List of docutils nodes """ result: List[nodes.Node] = [] # Create a section header for the TOML path if doc_comment.path: # Create a title node for the TOML path title_text = doc_comment.toml_path section_id = f"toml-{doc_comment.full_path.replace('.', '-')}" # Create a rubric (subheading) for the TOML item rubric = nodes.rubric(text=title_text) rubric["ids"].append(section_id) result.append(rubric) # Parse the doc-comment content and insert it into the document if doc_comment.content or doc_comment.toml_content: # Build the combined RST content using ViewList for proper source tracking rst_content: ViewList = ViewList() source_name = str(toml_path) # Add the doc-comment content (split into individual lines) if doc_comment.content: for i, line in enumerate(doc_comment.content.split("\n")): rst_content.append(line, source_name, doc_comment.line_number + i) # Add the collapsible admonition with TOML code if doc_comment.toml_content: # Calculate the line offset for the admonition content current_line = doc_comment.line_number if doc_comment.content: current_line += len(doc_comment.content.split("\n")) # Add a blank line before the admonition if there's doc-comment content rst_content.append("", source_name, current_line) current_line += 1 # Create the admonition block rst_content.append(".. admonition:: View Configuration", source_name, current_line) current_line += 1 rst_content.append(" :class: dropdown", source_name, current_line) current_line += 1 rst_content.append("", source_name, current_line) current_line += 1 rst_content.append(" .. code-block:: toml", source_name, current_line) current_line += 1 rst_content.append(" :linenos:", source_name, current_line) current_line += 1 rst_content.append("", source_name, current_line) current_line += 1 # Add the TOML content with proper indentation for line in doc_comment.toml_content.split("\n"): rst_content.append(f" {line}", source_name, current_line) current_line += 1 # Use the ViewList (no need to create a separate StringList) content_string_list = rst_content # Create a container node to hold the parsed content container = nodes.container() container["classes"].append("toml-doc-comment") # Use nested_parse to parse the content as reStructuredText # This allows Sphinx directives (like .. req:: or .. spec::) to work # Note: We pass 0 as the offset since this is generated content self.state.nested_parse(content_string_list, 0, container) # type: ignore[arg-type] result.append(container) return result
[docs] def setup(app: Sphinx) -> Dict[str, Any]: """ Sphinx extension setup function. Args: app: The Sphinx application instance Returns: Extension metadata """ # Register the autodoc-toml directive app.add_directive("autodoc-toml", AutodocTomlDirective) return { "version": "0.1.0", "parallel_read_safe": True, "parallel_write_safe": True, }