dotfiles

My PC Dotfiles & Website

site/git/stagit/render.py (4125B)


  1#!/usr/bin/env python3
  2"""Render file contents"""
  3
  4import html
  5import sys
  6
  7import nh3
  8from pygments import highlight
  9from pygments.formatters import HtmlFormatter
 10from pygments.lexers import get_lexer_for_filename, TextLexer
 11from pygments.util import ClassNotFound
 12
 13# Preview Limits
 14MAX_HIGHLIGHT = 512 * 1024
 15MAX_OUTPUT = 8 * 1024 * 1024
 16FORMAT = HtmlFormatter(
 17    cssclass="highlight",
 18    linenos="inline",
 19    lineanchors="loc",
 20    anchorlinenos=True,
 21    linespans="line",
 22)
 23
 24
 25def lexer_for(filename):
 26    try:
 27        return get_lexer_for_filename(filename)
 28    except ClassNotFound:
 29        return TextLexer()
 30
 31
 32def markdown_parser():
 33    from markdown import Markdown
 34    from markdown_callouts.github_callouts import GitHubCalloutsExtension
 35
 36    return Markdown(
 37        extensions=[
 38            "markdown.extensions.tables",
 39            "markdown.extensions.toc",
 40            "pymdownx.arithmatex",
 41            "pymdownx.betterem",
 42            "pymdownx.magiclink",
 43            "pymdownx.superfences",
 44            "pymdownx.tasklist",
 45            "pymdownx.tilde",
 46            GitHubCalloutsExtension(),
 47        ],
 48        extension_configs={
 49            "pymdownx.arithmatex": {"generic": True},
 50            "pymdownx.magiclink": {
 51                "repo_url_shortener": True,
 52                "repo_url_shorthand": True,
 53            },
 54        },
 55    )
 56
 57
 58def safe_attribute(tag, attr, value):
 59    # Keep Markdown separated
 60    if attr == "id" and value in {
 61        "main-content",
 62        "theme-select",
 63        "mobile-menu",
 64        "more-menu",
 65    }:
 66        return None
 67    if attr == "class":
 68        return " ".join(
 69            name
 70            for name in value.split()
 71            if name
 72            not in {
 73                "header",
 74                "header-logo",
 75                "logo",
 76                "loading",
 77                "footer",
 78                "container",
 79                "hidden",
 80                "js-only",
 81            }
 82        )
 83    return value
 84
 85
 86def render(filename, contents):
 87    lexer = TextLexer() if len(contents) > MAX_HIGHLIGHT else lexer_for(filename)
 88    print(f"Filename: {filename!r}; Lexer: {lexer.name}.", file=sys.stderr, flush=True)
 89    rendered = ""
 90    if len(contents) <= MAX_HIGHLIGHT and filename.lower().endswith(
 91        (".md", ".markdown", ".mdown")
 92    ):
 93        markup = markdown_parser().reset().convert(contents)
 94        attrs = {tag: set(values) for tag, values in nh3.ALLOWED_ATTRIBUTES.items()}
 95        attrs["*"] = {"class", "id", "title"}
 96        attrs["input"] = {"type", "checked", "disabled"}
 97        markup = nh3.clean(
 98            markup,
 99            tags=nh3.ALLOWED_TAGS | {"input"},
100            attributes=attrs,
101            attribute_filter=safe_attribute,
102            url_schemes={"http", "https", "mailto"},
103            set_tag_attribute_values={"input": {"type": "checkbox", "disabled": ""}},
104        )
105        rendered = (
106            '<h3>Rendered</h3><article class="post-content markdown">'
107            + markup
108            + "</article><h3>Code</h3>"
109        )
110        print("Markdown was rendered in addition.", file=sys.stderr, flush=True)
111    if len(contents) > MAX_HIGHLIGHT:
112        # Plain Text
113        code = (
114            "<pre>"
115            + "".join(
116                f'<span id="loc-{number}"><a class="linenos" href="#loc-{number}">{number}</a> {html.escape(line)}</span>'
117                for number, line in enumerate(contents.splitlines(keepends=True), 1)
118            )
119            + "</pre>"
120        )
121    else:
122        code = highlight(contents, lexer, FORMAT)
123    result = rendered + '<div id="blob">' + code + "</div>"
124    if len(result.encode("utf-8")) > MAX_OUTPUT:
125        print(
126            f"Skipping {filename!r}: rendered HTML exceeds 8 MiB.",
127            file=sys.stderr,
128            flush=True,
129        )
130        return "<p>File is too large to display.</p>"
131    return result
132
133
134if __name__ == "__main__":
135    if len(sys.argv) != 2:
136        sys.exit("usage: render filename")
137    contents = sys.stdin.buffer.read().decode("utf-8", errors="replace")
138    sys.stdout.write(render(sys.argv[1], contents))