forked from forks/microblog.pub
166 lines
5.1 KiB
Python
166 lines
5.1 KiB
Python
import re
|
|
import shutil
|
|
import typing
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
from jinja2 import Environment
|
|
from jinja2 import FileSystemLoader
|
|
from jinja2 import select_autoescape
|
|
from mistletoe import Document # type: ignore
|
|
from mistletoe import HTMLRenderer # type: ignore
|
|
from mistletoe import block_token # type: ignore
|
|
from pygments import highlight # type: ignore
|
|
from pygments.formatters import HtmlFormatter # type: ignore
|
|
from pygments.lexers import get_lexer_by_name as get_lexer # type: ignore
|
|
from pygments.lexers import guess_lexer # type: ignore
|
|
|
|
from app.config import VERSION
|
|
from app.source import CustomRenderer
|
|
from app.utils.datetime import now
|
|
|
|
_FORMATTER = HtmlFormatter()
|
|
_FORMATTER.noclasses = True
|
|
|
|
|
|
class DocRenderer(CustomRenderer):
|
|
def __init__(
|
|
self,
|
|
depth=5,
|
|
omit_title=True,
|
|
filter_conds=[],
|
|
) -> None:
|
|
super().__init__(
|
|
enable_mentionify=False,
|
|
enable_hashtagify=False,
|
|
)
|
|
self._headings: list[tuple[int, str, str]] = []
|
|
self._ids: set[str] = set()
|
|
self.depth = depth
|
|
self.omit_title = omit_title
|
|
self.filter_conds = filter_conds
|
|
|
|
@property
|
|
def toc(self):
|
|
"""
|
|
Returns table of contents as a block_token.List instance.
|
|
"""
|
|
|
|
def get_indent(level):
|
|
if self.omit_title:
|
|
level -= 1
|
|
return " " * 4 * (level - 1)
|
|
|
|
def build_list_item(heading):
|
|
level, content, title_id = heading
|
|
template = '{indent}- <a href="#{id}" rel="nofollow">{content}</a>\n'
|
|
return template.format(
|
|
indent=get_indent(level), content=content, id=title_id
|
|
)
|
|
|
|
lines = [build_list_item(heading) for heading in self._headings]
|
|
items = block_token.tokenize(lines)
|
|
return items[0]
|
|
|
|
def render_heading(self, token):
|
|
"""
|
|
Overrides super().render_heading; stores rendered heading first,
|
|
then returns it.
|
|
"""
|
|
template = '<h{level} id="{id}">{inner}</h{level}>'
|
|
inner = self.render_inner(token)
|
|
title_id = inner.lower().replace(" ", "-")
|
|
if title_id in self._ids:
|
|
i = 1
|
|
while 1:
|
|
title_id = f"{title_id}_{i}"
|
|
if title_id not in self._ids:
|
|
break
|
|
self._ids.add(title_id)
|
|
rendered = template.format(level=token.level, inner=inner, id=title_id)
|
|
content = self.parse_rendered_heading(rendered)
|
|
|
|
if not (
|
|
self.omit_title
|
|
and token.level == 1
|
|
or token.level > self.depth
|
|
or any(cond(content) for cond in self.filter_conds)
|
|
):
|
|
self._headings.append((token.level, content, title_id))
|
|
return rendered
|
|
|
|
@staticmethod
|
|
def parse_rendered_heading(rendered):
|
|
"""
|
|
Helper method; converts rendered heading to plain text.
|
|
"""
|
|
return re.sub(r"<.+?>", "", rendered)
|
|
|
|
def render_block_code(self, token: typing.Any) -> str:
|
|
code = token.children[0].content
|
|
lexer = get_lexer(token.language) if token.language else guess_lexer(code)
|
|
return highlight(code, lexer, _FORMATTER)
|
|
|
|
|
|
def markdownify(content: str) -> tuple[str, Any]:
|
|
with DocRenderer() as renderer:
|
|
rendered_content = renderer.render(Document(content))
|
|
|
|
with HTMLRenderer() as html_renderer:
|
|
toc = html_renderer.render(renderer.toc)
|
|
|
|
return rendered_content, toc
|
|
|
|
|
|
def main() -> None:
|
|
# Setup Jinja
|
|
loader = FileSystemLoader("docs/templates")
|
|
env = Environment(loader=loader, autoescape=select_autoescape())
|
|
template = env.get_template("layout.html")
|
|
|
|
shutil.rmtree("docs/dist", ignore_errors=True)
|
|
Path("docs/dist").mkdir(exist_ok=True)
|
|
shutil.rmtree("docs/dist/static", ignore_errors=True)
|
|
shutil.copytree("docs/static", "docs/dist/static")
|
|
|
|
last_updated = now().replace(second=0, microsecond=0).isoformat()
|
|
|
|
readme = Path("README.md")
|
|
content, toc = markdownify(readme.read_text().removeprefix("# microblog.pub"))
|
|
template.stream(
|
|
content=content,
|
|
version=VERSION,
|
|
path="/",
|
|
last_updated=last_updated,
|
|
).dump("docs/dist/index.html")
|
|
|
|
install = Path("docs/install.md")
|
|
content, toc = markdownify(install.read_text())
|
|
template.stream(
|
|
content=content.replace("[TOC]", toc),
|
|
version=VERSION,
|
|
path="/installing.html",
|
|
last_updated=last_updated,
|
|
).dump("docs/dist/installing.html")
|
|
|
|
user_guide = Path("docs/user_guide.md")
|
|
content, toc = markdownify(user_guide.read_text())
|
|
template.stream(
|
|
content=content.replace("[TOC]", toc),
|
|
version=VERSION,
|
|
path="/user_guide.html",
|
|
last_updated=last_updated,
|
|
).dump("docs/dist/user_guide.html")
|
|
|
|
developer_guide = Path("docs/developer_guide.md")
|
|
content, toc = markdownify(developer_guide.read_text())
|
|
template.stream(
|
|
content=content.replace("[TOC]", toc),
|
|
version=VERSION,
|
|
path="/developer_guide.html",
|
|
last_updated=last_updated,
|
|
).dump("docs/dist/developer_guide.html")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|