Files
omicron-ssg/omicron/ssg/output/feed.py
T
2026-08-26 05:07:15 +02:00

84 lines
2.8 KiB
Python

import logging
from datetime import date
from pathlib import Path
from typing import TYPE_CHECKING, Literal
from xml.etree.ElementTree import Element, ElementTree, SubElement
from omicron.ssg.output.output import Output
from omicron.ssg.output.content import Content
log = logging.getLogger(__name__)
if TYPE_CHECKING:
from omicron.ssg.site import Site # for static checking with mypy
else:
Site = "omicron.ssg.site.Site" # for runtime checking with beartype
ITEMS_PER_FEED = 20
def atom_date(d: date) -> str:
return f"{d.isoformat()}T00:00:00Z"
class Feed(Output):
NAMESPACE = "http://www.w3.org/2005/Atom"
def __init__(
self,
site: Site,
kind: Literal["root", "section"],
items: list[Content],
label: str | None,
):
super().__init__(site, Feed.build_path(kind, label))
self.kind = kind
self.label = label
self.items = sorted(items, key=lambda c: c.created, reverse=True)[
:ITEMS_PER_FEED
]
@staticmethod
def build_path(kind: Literal["root", "section"], label: str | None) -> Path:
file = Path("atom.xml")
if kind == "root":
return file
assert label, "label can't be None if kind is not root"
return Path(label) / file
def write(self) -> None:
feed_url = f"{self.site.origin}{self.url}"
title = f"{self.site.name}{self.label}" if self.label else self.site.name
updated = max(
(c.updated or c.created for c in self.items), default=date.today()
)
feed = Element("feed", xmlns=Feed.NAMESPACE)
SubElement(feed, "title").text = title
SubElement(feed, "id").text = feed_url
SubElement(feed, "link", rel="self", href=feed_url)
SubElement(feed, "updated").text = atom_date(updated)
for item in self.items:
entry = SubElement(feed, "entry")
item_url = f"{self.site.origin}{item.url}"
SubElement(entry, "title").text = item.title
SubElement(entry, "id").text = item_url
SubElement(entry, "link", href=item_url)
SubElement(entry, "published").text = atom_date(item.created)
SubElement(entry, "updated").text = atom_date(item.updated or item.created)
if item.summary:
summary = SubElement(entry, "summary", type="html")
summary.text = str(item.summary)
dest = self.site.output_path / self.destination
dest.parent.mkdir(parents=True, exist_ok=True)
ElementTree(feed).write(dest, encoding="utf-8", xml_declaration=True)
log.debug("Wrote %s", dest)
def discover_feeds(site: Site) -> list[Feed]:
outputs = [Feed(site, "root", site.content, None)]
for section, items in site.by_section.items():
outputs.append(Feed(site, "section", items, section))
return outputs