implement a summary property on Content

Allow the user to set a summary and make Article extract its own summary
when the Article.render is called and the summary is still missing.
Extracted summaries only work when the first item of a markdown file is
a paragraph.
This commit is contained in:
2026-08-05 04:37:16 +02:00
parent 7bf5fb5d92
commit 7688a6ab18
+22 -3
View File
@@ -11,6 +11,7 @@ if TYPE_CHECKING:
import yaml import yaml
from markupsafe import Markup from markupsafe import Markup
from markdown_it import MarkdownIt from markdown_it import MarkdownIt
from markdown_it.token import Token
from pygments import highlight as pygments_highlight from pygments import highlight as pygments_highlight
from pygments.formatters import HtmlFormatter from pygments.formatters import HtmlFormatter
from pygments.lexers import get_lexer_by_name 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) return pygments_highlight(code, lexer, html_formatter)
md = MarkdownIt(options_update={"highlight": highlight})
def read_frontmatter(path: Path) -> dict[str, Any]: def read_frontmatter(path: Path) -> dict[str, Any]:
lines = [] lines = []
with path.open("r", encoding="utf-8") as f: with path.open("r", encoding="utf-8") as f:
@@ -88,6 +92,7 @@ class Content(ABC):
created: date, created: date,
updated: date | None = None, updated: date | None = None,
tags: set[str] | None = None, tags: set[str] | None = None,
summary: str | None = None,
): ):
if tags is None: if tags is None:
tags = set() tags = set()
@@ -96,6 +101,7 @@ class Content(ABC):
self.created = created self.created = created
self.updated = updated self.updated = updated
self.tags: set[str] = tags self.tags: set[str] = tags
self.summary: str | None = summary
self.uri: str = self.build_uri(section, slug) self.uri: str = self.build_uri(section, slug)
@staticmethod @staticmethod
@@ -131,14 +137,27 @@ class Article(Content):
meta["date"], meta["date"],
meta.get("updated", None), meta.get("updated", None),
meta.get("tags", None), meta.get("tags", None),
meta.get("summary", None),
) )
self.title: str = meta["title"] self.title: str = meta["title"]
self.content: Markup = Markup("") 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: def render(self, site: Site) -> None:
md = MarkdownIt() tokens = md.parse(self.read_body())
md.options["highlight"] = highlight if self.summary is None:
self.content = Markup(md.render(self.read_body())) self.summary = Article.extract_summary(tokens)
self.content = Markup(md.renderer.render(tokens, md.options, {}))
template = site.jinja_env.get_template("article.html") template = site.jinja_env.get_template("article.html")
html = template.render(article=self, site=site) html = template.render(article=self, site=site)