CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-mkdocstrings

Automatic documentation from sources, for MkDocs.

Pending
Overview
Eval results
Files

rendering-system.mddocs/

Rendering System

HTML rendering components including syntax highlighting, heading management, and output formatting processors. The rendering system transforms collected documentation data into properly formatted HTML with consistent styling and structure.

Capabilities

Syntax Highlighting

Code syntax highlighter that matches Markdown configuration and provides consistent code formatting.

class Highlighter(Highlight):
    """Code highlighter that matches Markdown configuration."""
    
    _highlight_config_keys: frozenset[str]
    """Configuration keys supported by the highlighter."""
    
    def __init__(self, md: Markdown) -> None:
        """
        Configure to match a Markdown instance.
        
        Args:
            md: The Markdown instance to read configs from
        """
    
    def highlight(
        self,
        src: str,
        language: str | None = None,
        *,
        inline: bool = False,
        dedent: bool = True,
        linenums: bool | None = None,
        **kwargs: Any
    ) -> str:
        """
        Highlight a code-snippet.
        
        Args:
            src: The code to highlight
            language: Explicitly tell what language to use for highlighting
            inline: Whether to highlight as inline
            dedent: Whether to dedent the code before highlighting it or not
            linenums: Whether to add line numbers in the result
            **kwargs: Pass on to pymdownx.highlight.Highlight.highlight
            
        Returns:
            The highlighted code as HTML text, marked safe (not escaped for HTML)
        """

Usage Examples:

Basic syntax highlighting:

from mkdocstrings import Highlighter
from markdown import Markdown

md = Markdown(extensions=['codehilite'])
highlighter = Highlighter(md)

# Highlight Python code
python_code = """
def hello_world():
    print("Hello, World!")
    return True
"""

highlighted = highlighter.highlight(python_code, language="python")
print(highlighted)  # Returns HTML with syntax highlighting

Inline code highlighting:

# Highlight inline code
inline_code = "print('hello')"
highlighted_inline = highlighter.highlight(
    inline_code, 
    language="python", 
    inline=True
)

With line numbers:

# Highlight with line numbers
highlighted_with_lines = highlighter.highlight(
    python_code,
    language="python",
    linenums=True
)

Heading Management

Tree processor that shifts heading levels in rendered HTML content to maintain proper document hierarchy.

class HeadingShiftingTreeprocessor(Treeprocessor):
    """Shift levels of all Markdown headings according to the configured base level."""
    
    name: str = "mkdocstrings_headings"
    """The name of the treeprocessor."""
    
    regex: re.Pattern = re.compile(r"([Hh])([1-6])")
    """The regex to match heading tags."""
    
    shift_by: int
    """The number of heading 'levels' to add to every heading. <h2> with shift_by = 3 becomes <h5>."""
    
    def __init__(self, md: Markdown, shift_by: int) -> None:
        """
        Initialize the object.
        
        Args:
            md: A markdown.Markdown instance
            shift_by: The number of heading 'levels' to add to every heading
        """
    
    def run(self, root: Element) -> None:
        """
        Shift the levels of all headings in the document.
        
        Args:
            root: Root element of the tree to process
        """

Usage Examples:

Shift headings in handler rendering:

from mkdocstrings import HeadingShiftingTreeprocessor
from markdown import Markdown
from xml.etree.ElementTree import Element, fromstring

# Create processor that shifts headings down by 2 levels
md = Markdown()
processor = HeadingShiftingTreeprocessor(md, shift_by=2)

# Process HTML content
html_content = """
<h1>Main Title</h1>
<h2>Subtitle</h2>
<h3>Section</h3>
"""

root = fromstring(f"<div>{html_content}</div>")
processor.run(root)

# Now h1 becomes h3, h2 becomes h4, h3 becomes h5

Integration in handlers:

class MyHandler(BaseHandler):
    def render(self, data: CollectorItem, options: HandlerOptions, *, locale: str | None = None) -> str:
        # Get heading level from options
        heading_level = options.get("heading_level", 1)
        
        # Create inner Markdown with heading shifter
        inner_md = Markdown()
        if heading_level > 1:
            shifter = HeadingShiftingTreeprocessor(inner_md, shift_by=heading_level - 1)
            inner_md.treeprocessors.register(shifter, "heading_shift", 5)
        
        # Process docstring with proper heading levels
        docstring_html = inner_md.convert(data.docstring)
        
        return template.render(data=data, docstring_html=docstring_html)

ID Management

Tree processor that prepends prefixes to HTML element IDs to avoid conflicts in the generated documentation.

class IdPrependingTreeprocessor(Treeprocessor):
    """Prepend the configured prefix to IDs of all HTML elements."""
    
    name: str = "mkdocstrings_ids"
    """The name of the treeprocessor."""
    
    id_prefix: str
    """The prefix to add to every ID. It is prepended without any separator; specify your own separator if needed."""
    
    def __init__(self, md: Markdown, id_prefix: str) -> None:
        """
        Initialize the object.
        
        Args:
            md: A markdown.Markdown instance
            id_prefix: The prefix to add to every ID. It is prepended without any separator
        """
    
    def run(self, root: Element) -> None:
        """
        Prepend the configured prefix to all IDs in the document.
        
        Args:
            root: Root element of the tree to process
        """

Usage Examples:

Prevent ID conflicts between multiple autodoc blocks:

from mkdocstrings import IdPrependingTreeprocessor

# Create processor with prefix
processor = IdPrependingTreeprocessor(md, id_prefix="module1-")

# Process HTML with IDs
html_with_ids = """
<h2 id="function">My Function</h2>
<div id="example">Example</div>
"""

root = fromstring(f"<div>{html_with_ids}</div>")
processor.run(root)

# IDs become: "module1-function", "module1-example"

Handler integration:

class MyHandler(BaseHandler):
    def render(self, data: CollectorItem, options: HandlerOptions, *, locale: str | None = None) -> str:
        # Create unique ID prefix for this object
        id_prefix = data.name.replace(".", "-") + "-"
        
        # Process with ID prefixing
        inner_md = Markdown()
        id_processor = IdPrependingTreeprocessor(inner_md, id_prefix)
        inner_md.treeprocessors.register(id_processor, "id_prefix", 10)
        
        # Convert content with prefixed IDs
        content_html = inner_md.convert(data.content)
        
        return template.render(data=data, content_html=content_html)

Paragraph Stripping

Tree processor that unwraps single paragraph elements from output for cleaner formatting.

class ParagraphStrippingTreeprocessor(Treeprocessor):
    """Unwraps the <p> element around the whole output."""
    
    name: str = "mkdocstrings_strip_paragraph"
    """The name of the treeprocessor."""
    
    strip: bool = False
    """Whether to strip <p> elements or not."""
    
    def run(self, root: Element) -> Element | None:
        """
        Unwrap the root element if it's a single <p> element.
        
        Args:
            root: Root element of the tree to process
            
        Returns:
            Modified root element or None
        """

Usage Examples:

Strip single paragraphs from docstring content:

from mkdocstrings import ParagraphStrippingTreeprocessor

# Create processor
processor = ParagraphStrippingTreeprocessor()
processor.strip = True

# Process content that has unnecessary paragraph wrapper
html_content = "<p>Simple text content</p>"
root = fromstring(html_content)
result = processor.run(root)

# Result: "Simple text content" (paragraph wrapper removed)

Handler usage for inline content:

def render_inline_docstring(self, docstring: str) -> str:
    """Render docstring as inline content without paragraph wrapper."""
    inner_md = Markdown()
    
    # Add paragraph stripper for inline rendering
    stripper = ParagraphStrippingTreeprocessor()
    stripper.strip = True
    inner_md.treeprocessors.register(stripper, "strip_p", 15)
    
    return inner_md.convert(docstring)

Integration with Handlers

Template Integration

Rendering components integrate with Jinja2 templates:

def render(self, data: CollectorItem, options: HandlerOptions, *, locale: str | None = None) -> str:
    # Set up rendering environment
    highlighter = Highlighter(self.md)
    
    # Configure template globals
    template_globals = {
        "highlight": highlighter.highlight,
        "shift_headings": self.shift_headings,
        "config": options
    }
    
    # Render template with rendering utilities
    template = self.env.get_template("handler.html")
    return template.render(
        data=data,
        **template_globals
    )

Markdown Configuration

Rendering components respect Markdown configuration:

# Handler initialization
def __init__(self, *args, **kwargs):
    super().__init__(*args, **kwargs)
    
    # Create highlighter that matches main Markdown config
    self.highlighter = Highlighter(self.md)
    
    # Configure processors based on options
    self.configure_processors()

def configure_processors(self):
    """Configure tree processors based on handler options."""
    # Add heading shifter if needed
    if self.config.get("heading_level", 1) > 1:
        shift_by = self.config["heading_level"] - 1
        shifter = HeadingShiftingTreeprocessor(self.inner_md, shift_by)
        self.inner_md.treeprocessors.register(shifter, "shift_headings", 5)
    
    # Add ID prefixer for unique IDs
    if self.config.get("unique_ids", True):
        id_prefix = self.get_id_prefix()
        prefixer = IdPrependingTreeprocessor(self.inner_md, id_prefix)
        self.inner_md.treeprocessors.register(prefixer, "prefix_ids", 10)

Custom Rendering

Extend rendering for custom needs:

class CustomHandler(BaseHandler):
    def setup_rendering(self):
        """Set up custom rendering pipeline."""
        # Create custom highlighter with additional languages
        self.highlighter = Highlighter(self.md)
        
        # Add custom tree processors
        self.add_custom_processors()
    
    def add_custom_processors(self):
        """Add custom tree processors."""
        # Custom processor for special formatting
        custom_processor = CustomTreeProcessor(self.inner_md)
        self.inner_md.treeprocessors.register(custom_processor, "custom", 20)
        
    def render_with_custom_formatting(self, content: str, language: str) -> str:
        """Render content with custom formatting."""
        # Highlight code
        highlighted = self.highlighter.highlight(content, language=language)
        
        # Apply custom processing
        processed = self.apply_custom_processing(highlighted)
        
        return processed

Markdown Inner Extension

Extension that should always be added to Markdown sub-documents that handlers request.

class MkdocstringsInnerExtension(Extension):
    """Extension that should always be added to Markdown sub-documents that handlers request (and only them)."""
    
    headings: list[Element]
    """The list that will be populated with all HTML heading elements encountered in the document."""
    
    def __init__(self, headings: list[Element]) -> None:
        """
        Initialize the object.
        
        Args:
            headings: A list that will be populated with all HTML heading elements encountered in the document
        """
    
    def extendMarkdown(self, md: Markdown) -> None:
        """
        Register the extension.
        
        Args:
            md: A markdown.Markdown instance
        """

Usage Examples:

Using the inner extension for handler sub-documents:

from mkdocstrings import MkdocstringsInnerExtension
from markdown import Markdown
from xml.etree.ElementTree import Element

# Create list to collect headings
headings: list[Element] = []

# Create inner extension
inner_extension = MkdocstringsInnerExtension(headings)

# Create Markdown instance for sub-document processing
inner_md = Markdown(extensions=[inner_extension])

# Process handler content
content = """
# Function Documentation

## Parameters

### param1
Description of parameter 1.

### param2  
Description of parameter 2.
"""

result = inner_md.convert(content)

# headings list now contains all heading elements found
print(f"Found {len(headings)} headings")
for heading in headings:
    print(f"- {heading.tag}: {heading.text}")

Handler integration:

class MyHandler(BaseHandler):
    def render(self, data: CollectorItem, options: HandlerOptions, *, locale: str | None = None) -> str:
        # Create headings collector
        headings: list[Element] = []
        
        # Set up inner Markdown with extension
        inner_extension = MkdocstringsInnerExtension(headings)
        inner_md = Markdown(extensions=[
            inner_extension,
            'toc',
            'codehilite'
        ])
        
        # Process docstring content
        docstring_html = inner_md.convert(data.docstring)
        
        # Use collected headings for navigation or TOC
        toc_data = [(h.tag, h.text, h.get('id')) for h in headings]
        
        # Render template with processed content
        template = self.env.get_template('function.html')
        return template.render(
            data=data,
            docstring_html=docstring_html,
            toc_data=toc_data
        )

This rendering system provides consistent, high-quality HTML output while maintaining flexibility for customization and integration with different themes and styling approaches.

Install with Tessl CLI

npx tessl i tessl/pypi-mkdocstrings

docs

handler-system.md

index.md

inventory-system.md

logging-system.md

markdown-processing.md

plugin-integration.md

rendering-system.md

tile.json