From 01278e4299528b907ccebb0f524f29e1e20f3b6c Mon Sep 17 00:00:00 2001 From: Daniel Fichtinger Date: Sat, 12 Jul 2025 01:06:31 -0400 Subject: [PATCH 1/4] wip: generate RSS feed --- pyproject.toml | 3 +- src/zona/builder.py | 44 ++++++++++++++++++++-- src/zona/config.py | 44 +++++++++++++++++++++- src/zona/data/templates/page.html | 6 +-- src/zona/data/templates/post_list.html | 4 +- src/zona/metadata.py | 34 ++++++++++++----- uv.lock | 52 ++++++++++++++++++++++++++ 7 files changed, 167 insertions(+), 20 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d852304..b6d7de5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,6 +11,7 @@ authors = [ requires-python = ">=3.12" dependencies = [ "dacite>=1.9.2", + "feedgen>=1.0.0", "jinja2>=3.1.6", "l2m4m>=1.0.4", "markdown>=3.8.2", @@ -57,7 +58,7 @@ reportUnusedCallResult = false reportCallInDefaultInitializer = false enableTypeIgnoreComments = true reportIgnoreCommentWithoutRule = false -allowedUntypedLibraries = ["frontmatter", "pygments", "pymdownx", "l2m4m"] +allowedUntypedLibraries = ["frontmatter", "pygments", "pymdownx", "l2m4m", "feedgen", "feedgen.feed"] [tool.ruff] line-length = 70 diff --git a/src/zona/builder.py b/src/zona/builder.py index 416edc8..703bc2d 100644 --- a/src/zona/builder.py +++ b/src/zona/builder.py @@ -1,7 +1,9 @@ import shutil -from datetime import date +from datetime import date, datetime from pathlib import Path +from feedgen.feed import FeedGenerator + from zona import markdown as zmd from zona import util from zona.config import ZonaConfig @@ -55,7 +57,9 @@ class ZonaBuilder: layout.root / "content" / "static" ): logger.debug(f"Parsing {path.name}.") - item.metadata, item.content = parse_metadata(path) + item.metadata, item.content = parse_metadata( + path, config=self.config + ) if item.metadata.ignore or ( item.metadata.draft and not self.config.build.include_drafts @@ -97,7 +101,35 @@ class ZonaBuilder: items.append(item) self.items = items - def _build(self): + def generate_feed(self) -> str: + post_list = self._get_post_list() + config = self.config.feed + fg = FeedGenerator() + fg.id(config.link) + fg.title(config.title) + author = { + "name": config.author.name, + "email": config.author.email, + } + fg.author(author) + fg.link(href=config.link, rel="self") + fg.language(config.language) + fg.description(config.description) + + for post in post_list: + assert post.metadata + fe = fg.add_entry() # pyright: ignore[reportUnknownVariableType] + fe.id(f"{config.link}{util.normalize_url(post.url)}") # pyright: ignore[reportUnknownMemberType] + fe.title(post.metadata.title) # pyright: ignore[reportUnknownMemberType] + fe.author(author) # pyright: ignore[reportUnknownMemberType] + fe.description(post.metadata.description) # pyright: ignore[reportUnknownMemberType] + date = post.metadata.date + # ISSUE: lack of timezone causes an error, need to add TZ config option + dt = datetime.combine(date, datetime.min.time()) + fe.pubDate(dt) # pyright: ignore[reportUnknownMemberType] + return fg.rss_str(pretty=True) # pyright: ignore[reportUnknownVariableType] + + def _get_post_list(self) -> list[Item]: assert self.items # sort according to date # descending order @@ -108,7 +140,12 @@ class ZonaBuilder: else date.min, reverse=True, ) + return post_list + + def _build(self): + post_list = self._get_post_list() # number of posts + # generate RSS here posts = len(post_list) # link post chronology for i, item in enumerate(post_list): @@ -181,3 +218,4 @@ class ZonaBuilder: logger.debug("Building...") self._build() self.fresh = False + # print(self.generate_feed()) diff --git a/src/zona/config.py b/src/zona/config.py index 2a50fd4..712158d 100644 --- a/src/zona/config.py +++ b/src/zona/config.py @@ -1,9 +1,16 @@ +from __future__ import annotations + from dataclasses import dataclass, field +from datetime import tzinfo from pathlib import Path +from typing import Any +from zoneinfo import ZoneInfo import yaml +from dacite import Config as DaciteConfig from dacite import from_dict +from zona import util from zona.log import get_logger logger = get_logger() @@ -69,12 +76,40 @@ class ServerConfig: reload: ReloadConfig = field(default_factory=ReloadConfig) +@dataclass +class AuthorConfig: + name: str = "John Doe" + email: str = "john@doe.net" + + +@dataclass +class FeedConfig: + enabled: bool = True + timezone: tzinfo = field(default_factory=lambda: ZoneInfo("UTC")) + path: str = "feed.xml" + link: str = "https://example.com" + title: str = "Zona Website" + description: str = "My zona website." + language: str = "en" + author: AuthorConfig = field(default_factory=AuthorConfig) + + IGNORELIST = [".marksman.toml"] +def parse_timezone(s: Any) -> tzinfo: + if isinstance(s, str): + return ZoneInfo(s) + else: + raise TypeError( + f"Expected {str}, got {type(s)} for config key timezone" + ) + + @dataclass class ZonaConfig: base_url: str = "/" + feed: FeedConfig = field(default_factory=FeedConfig) # dictionary where key is name, value is url sitemap: SitemapConfig = field( default_factory=lambda: {"Home": "/"} @@ -87,7 +122,12 @@ class ZonaConfig: server: ServerConfig = field(default_factory=ServerConfig) @classmethod - def from_file(cls, path: Path) -> "ZonaConfig": + def from_file(cls, path: Path) -> ZonaConfig: with open(path, "r") as f: raw = yaml.safe_load(f) - return from_dict(data_class=cls, data=raw) + config: ZonaConfig = from_dict( + data_class=cls, + data=raw, + config=DaciteConfig(type_hooks={tzinfo: parse_timezone}), + ) + return config diff --git a/src/zona/data/templates/page.html b/src/zona/data/templates/page.html index 0c22f8b..0ff499b 100644 --- a/src/zona/data/templates/page.html +++ b/src/zona/data/templates/page.html @@ -1,8 +1,8 @@ {% extends "base.html" %} {% block content %} {% if metadata.show_title %} {% -include "title.html" %} {% if metadata.date %} +include "title.html" %} {% if metadata.date.date() %}
- +
{% endif %} {% endif %} {% if is_post %} {% include "post_nav.html" %} {% endif %} diff --git a/src/zona/data/templates/post_list.html b/src/zona/data/templates/post_list.html index 6b0bdca..30ea74f 100644 --- a/src/zona/data/templates/post_list.html +++ b/src/zona/data/templates/post_list.html @@ -8,8 +8,8 @@ include "title.html" %} {% endif %}