diff --git a/omicron/ssg/content.py b/omicron/ssg/content.py index e69b772..cd85eff 100644 --- a/omicron/ssg/content.py +++ b/omicron/ssg/content.py @@ -11,6 +11,7 @@ if TYPE_CHECKING: import yaml from markupsafe import Markup from markdown_it import MarkdownIt +from markdown_it.token import Token from pygments import highlight as pygments_highlight from pygments.formatters import HtmlFormatter from pygments.lexers import get_lexer_by_name @@ -44,6 +45,9 @@ def highlight(code: str, lang: str, attrs: str) -> str: return pygments_highlight(code, lexer, html_formatter) +md = MarkdownIt(options_update={"highlight": highlight}) + + def read_frontmatter(path: Path) -> dict[str, Any]: lines = [] with path.open("r", encoding="utf-8") as f: @@ -88,6 +92,7 @@ class Content(ABC): created: date, updated: date | None = None, tags: set[str] | None = None, + summary: str | None = None, ): if tags is None: tags = set() @@ -96,6 +101,7 @@ class Content(ABC): self.created = created self.updated = updated self.tags: set[str] = tags + self.summary: str | None = summary self.uri: str = self.build_uri(section, slug) @staticmethod @@ -131,14 +137,27 @@ class Article(Content): meta["date"], meta.get("updated", None), meta.get("tags", None), + meta.get("summary", None), ) self.title: str = meta["title"] self.content: Markup = Markup("") + @staticmethod + def extract_summary(tokens: list[Token]) -> str: + if not tokens or tokens[0].type != "paragraph_open" or tokens[0].hidden: + raise ContentError( + "Body does not start with a paragraph; set 'summary' in frontmatter" + ) + for j in range(1, len(tokens)): + if tokens[j].type == "paragraph_close": + return cast(str, md.renderer.render(tokens[1:j], md.options, {})) + raise ContentError("Content has no paragraph to use as summary") + def render(self, site: Site) -> None: - md = MarkdownIt() - md.options["highlight"] = highlight - self.content = Markup(md.render(self.read_body())) + tokens = md.parse(self.read_body()) + if self.summary is None: + self.summary = Article.extract_summary(tokens) + self.content = Markup(md.renderer.render(tokens, md.options, {})) template = site.jinja_env.get_template("article.html") html = template.render(article=self, site=site)