Plain is headed towards 1.0! Subscribe for development updates →

 1from html.parser import HTMLParser
 2
 3import mistune
 4from pygments import highlight
 5from pygments.formatters import html
 6from pygments.lexers import get_lexer_by_name
 7
 8from plain.utils.text import slugify
 9
10
11class PagesRenderer(mistune.HTMLRenderer):
12    def heading(self, text, level, **attrs):
13        """Automatically add an ID to headings if one is not provided."""
14
15        if "id" not in attrs:
16            inner_text = get_inner_text(text)
17            attrs["id"] = slugify(inner_text)
18
19        return super().heading(text, level, **attrs)
20
21    def block_code(self, code, info=None):
22        """Highlight code blocks using Pygments."""
23
24        if info:
25            lexer = get_lexer_by_name(info, stripall=True)
26            formatter = html.HtmlFormatter(wrapcode=True)
27            return highlight(code, lexer, formatter)
28
29        return "<pre><code>" + mistune.escape(code) + "</code></pre>"
30
31
32def render_markdown(content):
33    renderer = PagesRenderer(escape=False)
34    markdown = mistune.create_markdown(
35        renderer=renderer, plugins=["strikethrough", "table"]
36    )
37    return markdown(content)
38
39
40class InnerTextParser(HTMLParser):
41    def __init__(self):
42        super().__init__()
43        self.text_content = []
44
45    def handle_data(self, data):
46        # Collect all text data
47        self.text_content.append(data.strip())
48
49
50def get_inner_text(html_content):
51    parser = InnerTextParser()
52    parser.feed(html_content)
53    return " ".join([text for text in parser.text_content if text])