generate RSS feed
Some checks failed
/ test-build (push) Failing after 23s

This commit is contained in:
Daniel Fichtinger 2025-07-12 01:06:31 -04:00
parent 4f8979ae9b
commit 3e68d7a159
7 changed files with 189 additions and 20 deletions

View file

@ -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

View file

@ -2,6 +2,8 @@ import shutil
from datetime import date
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,45 @@ class ZonaBuilder:
items.append(item)
self.items = items
def _build(self):
def generate_feed(self) -> bytes:
post_list = self._get_post_list()
config = self.config.feed
if config.link.endswith("/"):
config.link = config.link[:-2]
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=f"{config.link}/{config.path}",
rel="self",
type="application/rss+xml",
)
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.link( # pyright: ignore[reportUnknownMemberType]
href=f"{config.link}{util.normalize_url(post.url)}"
)
fe.title(post.metadata.title) # pyright: ignore[reportUnknownMemberType]
fe.author(author) # pyright: ignore[reportUnknownMemberType]
desc = post.metadata.description
fe.description(desc) # pyright: ignore[reportUnknownMemberType]
date = post.metadata.date
fe.pubDate(date) # pyright: ignore[reportUnknownMemberType]
out: bytes = fg.rss_str(pretty=True) # pyright: ignore[reportUnknownVariableType]
assert isinstance(out, bytes)
return out
def _get_post_list(self) -> list[Item]:
assert self.items
# sort according to date
# descending order
@ -108,7 +150,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):
@ -180,4 +227,9 @@ class ZonaBuilder:
self._discover()
logger.debug("Building...")
self._build()
if self.config.feed.enabled:
rss = self.generate_feed()
path = self.layout.content / self.config.feed.path
util.ensure_parents(path)
path.write_bytes(rss)
self.fresh = False

View file

@ -1,7 +1,13 @@
from __future__ import annotations
from dataclasses import dataclass, field
from datetime import datetime, 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.log import get_logger
@ -25,9 +31,17 @@ def find_config(start: Path | None = None) -> Path | None:
SitemapConfig = dict[str, str]
@dataclass
class PostDefaultsConfig:
description: str = "A blog post"
@dataclass
class BlogConfig:
dir: str = "blog"
defaults: PostDefaultsConfig = field(
default_factory=PostDefaultsConfig
)
@dataclass
@ -69,12 +83,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 = "rss.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 +129,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

View file

@ -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() %}
<center>
<time class="post-date" datetime="{{ metadata.date | safe }}"
>{{ metadata.date | safe}}</time>
<time class="post-date" datetime="{{ metadata.date.date() | safe }}"
>{{ metadata.date.date() | safe}}</time>
</center>
{% endif %} {% endif %} {% if is_post %} {% include "post_nav.html" %} {% endif
%}

View file

@ -8,8 +8,8 @@ include "title.html" %} {% endif %}
<ul>
{% for item in post_list %}
<li>
<time class="post-list-date" datetime="{{ item.metadata.date | safe }}"
>{{ item.metadata.date | safe}}</time>: <a href="/{{ item.url }}"
<time class="post-list-date" datetime="{{ item.metadata.date.date() | safe }}"
>{{ item.metadata.date.date() | safe}}</time>: <a href="/{{ item.url }}"
>{{ item.metadata.title }}</a>
</li>
{% endfor %}

View file

@ -1,5 +1,5 @@
from dataclasses import dataclass
from datetime import date
from datetime import date, datetime, time, tzinfo
from pathlib import Path
import frontmatter
@ -10,13 +10,14 @@ from dateutil import parser as date_parser
from yaml import YAMLError
import zona.util
from zona.config import ZonaConfig
@dataclass
class Metadata:
title: str
date: date
description: str | None
date: datetime
description: str
show_title: bool = True
show_date: bool = True
show_nav: bool = True
@ -30,14 +31,29 @@ class Metadata:
math: bool = True
def parse_date(raw_date: str | date | object) -> date:
if isinstance(raw_date, date):
return raw_date
def ensure_timezone(dt: datetime, tz: tzinfo) -> datetime:
if dt.tzinfo is None or dt.utcoffset() is None:
dt = dt.replace(tzinfo=tz)
return dt
# TODO: migrate to using datetime, where user can optionall specify
# a time as well. if only date is given, default to time.min
def parse_date(
raw_date: str | datetime | date | object, tz: tzinfo
) -> datetime:
if isinstance(raw_date, datetime):
return ensure_timezone(raw_date, tz)
elif isinstance(raw_date, date):
return datetime.combine(raw_date, time.min, tzinfo=tz)
assert isinstance(raw_date, str)
return date_parser.parse(raw_date).date()
dt = date_parser.parse(raw_date)
return ensure_timezone(dt, tz)
def parse_metadata(path: Path) -> tuple[Metadata, str]:
def parse_metadata(
path: Path, config: ZonaConfig
) -> tuple[Metadata, str]:
"""
Parses a file and returns parsed Metadata and its content. Defaults
are applied for missing fields. If there is no metadata, a Metadata
@ -53,10 +69,11 @@ def parse_metadata(path: Path) -> tuple[Metadata, str]:
raw_meta = post.metadata or {}
defaults = {
"title": zona.util.filename_to_title(path),
"date": date.fromtimestamp(path.stat().st_ctime),
"date": datetime.fromtimestamp(path.stat().st_ctime),
"description": config.blog.defaults.description,
}
meta = {**defaults, **raw_meta}
meta["date"] = parse_date(meta.get("date"))
meta["date"] = parse_date(meta.get("date"), config.feed.timezone)
try:
metadata = from_dict(
data_class=Metadata,

52
uv.lock generated
View file

@ -44,6 +44,16 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/94/35/386550fd60316d1e37eccdda609b074113298f23cef5bddb2049823fe666/dacite-1.9.2-py3-none-any.whl", hash = "sha256:053f7c3f5128ca2e9aceb66892b1a3c8936d02c686e707bee96e19deef4bc4a0", size = 16600, upload-time = "2025-02-05T09:27:24.345Z" },
]
[[package]]
name = "feedgen"
version = "1.0.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "lxml" },
{ name = "python-dateutil" },
]
sdist = { url = "https://files.pythonhosted.org/packages/6b/59/be0a6f852b5dfbf19e6c8e962c8f41407697f9f52a7902250ed98683ae89/feedgen-1.0.0.tar.gz", hash = "sha256:d9bd51c3b5e956a2a52998c3708c4d2c729f2fcc311188e1e5d3b9726393546a", size = 258496, upload-time = "2023-12-25T18:04:08.421Z" }
[[package]]
name = "iniconfig"
version = "2.1.0"
@ -87,6 +97,46 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/1e/fd/aba08bb9e527168efad57985d7db9a853eb2384b1efa5ca5f3a3794c9cef/latex2mathml-3.78.0-py3-none-any.whl", hash = "sha256:1aeca3dc027b3006ad7b301b7f4a15ffbb4c1451e3dc8c3389e97b37b497e1d6", size = 73673, upload-time = "2025-05-03T16:51:51.991Z" },
]
[[package]]
name = "lxml"
version = "6.0.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/c5/ed/60eb6fa2923602fba988d9ca7c5cdbd7cf25faa795162ed538b527a35411/lxml-6.0.0.tar.gz", hash = "sha256:032e65120339d44cdc3efc326c9f660f5f7205f3a535c1fdbf898b29ea01fb72", size = 4096938, upload-time = "2025-06-26T16:28:19.373Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/89/c3/d01d735c298d7e0ddcedf6f028bf556577e5ab4f4da45175ecd909c79378/lxml-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78718d8454a6e928470d511bf8ac93f469283a45c354995f7d19e77292f26108", size = 8429515, upload-time = "2025-06-26T16:26:06.776Z" },
{ url = "https://files.pythonhosted.org/packages/06/37/0e3eae3043d366b73da55a86274a590bae76dc45aa004b7042e6f97803b1/lxml-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:84ef591495ffd3f9dcabffd6391db7bb70d7230b5c35ef5148354a134f56f2be", size = 4601387, upload-time = "2025-06-26T16:26:09.511Z" },
{ url = "https://files.pythonhosted.org/packages/a3/28/e1a9a881e6d6e29dda13d633885d13acb0058f65e95da67841c8dd02b4a8/lxml-6.0.0-cp312-cp312-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:2930aa001a3776c3e2601cb8e0a15d21b8270528d89cc308be4843ade546b9ab", size = 5228928, upload-time = "2025-06-26T16:26:12.337Z" },
{ url = "https://files.pythonhosted.org/packages/9a/55/2cb24ea48aa30c99f805921c1c7860c1f45c0e811e44ee4e6a155668de06/lxml-6.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:219e0431ea8006e15005767f0351e3f7f9143e793e58519dc97fe9e07fae5563", size = 4952289, upload-time = "2025-06-28T18:47:25.602Z" },
{ url = "https://files.pythonhosted.org/packages/31/c0/b25d9528df296b9a3306ba21ff982fc5b698c45ab78b94d18c2d6ae71fd9/lxml-6.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bd5913b4972681ffc9718bc2d4c53cde39ef81415e1671ff93e9aa30b46595e7", size = 5111310, upload-time = "2025-06-28T18:47:28.136Z" },
{ url = "https://files.pythonhosted.org/packages/e9/af/681a8b3e4f668bea6e6514cbcb297beb6de2b641e70f09d3d78655f4f44c/lxml-6.0.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:390240baeb9f415a82eefc2e13285016f9c8b5ad71ec80574ae8fa9605093cd7", size = 5025457, upload-time = "2025-06-26T16:26:15.068Z" },
{ url = "https://files.pythonhosted.org/packages/99/b6/3a7971aa05b7be7dfebc7ab57262ec527775c2c3c5b2f43675cac0458cad/lxml-6.0.0-cp312-cp312-manylinux_2_27_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d6e200909a119626744dd81bae409fc44134389e03fbf1d68ed2a55a2fb10991", size = 5657016, upload-time = "2025-07-03T19:19:06.008Z" },
{ url = "https://files.pythonhosted.org/packages/69/f8/693b1a10a891197143c0673fcce5b75fc69132afa81a36e4568c12c8faba/lxml-6.0.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ca50bd612438258a91b5b3788c6621c1f05c8c478e7951899f492be42defc0da", size = 5257565, upload-time = "2025-06-26T16:26:17.906Z" },
{ url = "https://files.pythonhosted.org/packages/a8/96/e08ff98f2c6426c98c8964513c5dab8d6eb81dadcd0af6f0c538ada78d33/lxml-6.0.0-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:c24b8efd9c0f62bad0439283c2c795ef916c5a6b75f03c17799775c7ae3c0c9e", size = 4713390, upload-time = "2025-06-26T16:26:20.292Z" },
{ url = "https://files.pythonhosted.org/packages/a8/83/6184aba6cc94d7413959f6f8f54807dc318fdcd4985c347fe3ea6937f772/lxml-6.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:afd27d8629ae94c5d863e32ab0e1d5590371d296b87dae0a751fb22bf3685741", size = 5066103, upload-time = "2025-06-26T16:26:22.765Z" },
{ url = "https://files.pythonhosted.org/packages/ee/01/8bf1f4035852d0ff2e36a4d9aacdbcc57e93a6cd35a54e05fa984cdf73ab/lxml-6.0.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:54c4855eabd9fc29707d30141be99e5cd1102e7d2258d2892314cf4c110726c3", size = 4791428, upload-time = "2025-06-26T16:26:26.461Z" },
{ url = "https://files.pythonhosted.org/packages/29/31/c0267d03b16954a85ed6b065116b621d37f559553d9339c7dcc4943a76f1/lxml-6.0.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c907516d49f77f6cd8ead1322198bdfd902003c3c330c77a1c5f3cc32a0e4d16", size = 5678523, upload-time = "2025-07-03T19:19:09.837Z" },
{ url = "https://files.pythonhosted.org/packages/5c/f7/5495829a864bc5f8b0798d2b52a807c89966523140f3d6fa3a58ab6720ea/lxml-6.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:36531f81c8214e293097cd2b7873f178997dae33d3667caaae8bdfb9666b76c0", size = 5281290, upload-time = "2025-06-26T16:26:29.406Z" },
{ url = "https://files.pythonhosted.org/packages/79/56/6b8edb79d9ed294ccc4e881f4db1023af56ba451909b9ce79f2a2cd7c532/lxml-6.0.0-cp312-cp312-win32.whl", hash = "sha256:690b20e3388a7ec98e899fd54c924e50ba6693874aa65ef9cb53de7f7de9d64a", size = 3613495, upload-time = "2025-06-26T16:26:31.588Z" },
{ url = "https://files.pythonhosted.org/packages/0b/1e/cc32034b40ad6af80b6fd9b66301fc0f180f300002e5c3eb5a6110a93317/lxml-6.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:310b719b695b3dd442cdfbbe64936b2f2e231bb91d998e99e6f0daf991a3eba3", size = 4014711, upload-time = "2025-06-26T16:26:33.723Z" },
{ url = "https://files.pythonhosted.org/packages/55/10/dc8e5290ae4c94bdc1a4c55865be7e1f31dfd857a88b21cbba68b5fea61b/lxml-6.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:8cb26f51c82d77483cdcd2b4a53cda55bbee29b3c2f3ddeb47182a2a9064e4eb", size = 3674431, upload-time = "2025-06-26T16:26:35.959Z" },
{ url = "https://files.pythonhosted.org/packages/79/21/6e7c060822a3c954ff085e5e1b94b4a25757c06529eac91e550f3f5cd8b8/lxml-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6da7cd4f405fd7db56e51e96bff0865b9853ae70df0e6720624049da76bde2da", size = 8414372, upload-time = "2025-06-26T16:26:39.079Z" },
{ url = "https://files.pythonhosted.org/packages/a4/f6/051b1607a459db670fc3a244fa4f06f101a8adf86cda263d1a56b3a4f9d5/lxml-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b34339898bb556a2351a1830f88f751679f343eabf9cf05841c95b165152c9e7", size = 4593940, upload-time = "2025-06-26T16:26:41.891Z" },
{ url = "https://files.pythonhosted.org/packages/8e/74/dd595d92a40bda3c687d70d4487b2c7eff93fd63b568acd64fedd2ba00fe/lxml-6.0.0-cp313-cp313-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:51a5e4c61a4541bd1cd3ba74766d0c9b6c12d6a1a4964ef60026832aac8e79b3", size = 5214329, upload-time = "2025-06-26T16:26:44.669Z" },
{ url = "https://files.pythonhosted.org/packages/52/46/3572761efc1bd45fcafb44a63b3b0feeb5b3f0066886821e94b0254f9253/lxml-6.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d18a25b19ca7307045581b18b3ec9ead2b1db5ccd8719c291f0cd0a5cec6cb81", size = 4947559, upload-time = "2025-06-28T18:47:31.091Z" },
{ url = "https://files.pythonhosted.org/packages/94/8a/5e40de920e67c4f2eef9151097deb9b52d86c95762d8ee238134aff2125d/lxml-6.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d4f0c66df4386b75d2ab1e20a489f30dc7fd9a06a896d64980541506086be1f1", size = 5102143, upload-time = "2025-06-28T18:47:33.612Z" },
{ url = "https://files.pythonhosted.org/packages/7c/4b/20555bdd75d57945bdabfbc45fdb1a36a1a0ff9eae4653e951b2b79c9209/lxml-6.0.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9f4b481b6cc3a897adb4279216695150bbe7a44c03daba3c894f49d2037e0a24", size = 5021931, upload-time = "2025-06-26T16:26:47.503Z" },
{ url = "https://files.pythonhosted.org/packages/b6/6e/cf03b412f3763d4ca23b25e70c96a74cfece64cec3addf1c4ec639586b13/lxml-6.0.0-cp313-cp313-manylinux_2_27_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8a78d6c9168f5bcb20971bf3329c2b83078611fbe1f807baadc64afc70523b3a", size = 5645469, upload-time = "2025-07-03T19:19:13.32Z" },
{ url = "https://files.pythonhosted.org/packages/d4/dd/39c8507c16db6031f8c1ddf70ed95dbb0a6d466a40002a3522c128aba472/lxml-6.0.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ae06fbab4f1bb7db4f7c8ca9897dc8db4447d1a2b9bee78474ad403437bcc29", size = 5247467, upload-time = "2025-06-26T16:26:49.998Z" },
{ url = "https://files.pythonhosted.org/packages/4d/56/732d49def0631ad633844cfb2664563c830173a98d5efd9b172e89a4800d/lxml-6.0.0-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:1fa377b827ca2023244a06554c6e7dc6828a10aaf74ca41965c5d8a4925aebb4", size = 4720601, upload-time = "2025-06-26T16:26:52.564Z" },
{ url = "https://files.pythonhosted.org/packages/8f/7f/6b956fab95fa73462bca25d1ea7fc8274ddf68fb8e60b78d56c03b65278e/lxml-6.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1676b56d48048a62ef77a250428d1f31f610763636e0784ba67a9740823988ca", size = 5060227, upload-time = "2025-06-26T16:26:55.054Z" },
{ url = "https://files.pythonhosted.org/packages/97/06/e851ac2924447e8b15a294855caf3d543424364a143c001014d22c8ca94c/lxml-6.0.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:0e32698462aacc5c1cf6bdfebc9c781821b7e74c79f13e5ffc8bfe27c42b1abf", size = 4790637, upload-time = "2025-06-26T16:26:57.384Z" },
{ url = "https://files.pythonhosted.org/packages/06/d4/fd216f3cd6625022c25b336c7570d11f4a43adbaf0a56106d3d496f727a7/lxml-6.0.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4d6036c3a296707357efb375cfc24bb64cd955b9ec731abf11ebb1e40063949f", size = 5662049, upload-time = "2025-07-03T19:19:16.409Z" },
{ url = "https://files.pythonhosted.org/packages/52/03/0e764ce00b95e008d76b99d432f1807f3574fb2945b496a17807a1645dbd/lxml-6.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7488a43033c958637b1a08cddc9188eb06d3ad36582cebc7d4815980b47e27ef", size = 5272430, upload-time = "2025-06-26T16:27:00.031Z" },
{ url = "https://files.pythonhosted.org/packages/5f/01/d48cc141bc47bc1644d20fe97bbd5e8afb30415ec94f146f2f76d0d9d098/lxml-6.0.0-cp313-cp313-win32.whl", hash = "sha256:5fcd7d3b1d8ecb91445bd71b9c88bdbeae528fefee4f379895becfc72298d181", size = 3612896, upload-time = "2025-06-26T16:27:04.251Z" },
{ url = "https://files.pythonhosted.org/packages/f4/87/6456b9541d186ee7d4cb53bf1b9a0d7f3b1068532676940fdd594ac90865/lxml-6.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:2f34687222b78fff795feeb799a7d44eca2477c3d9d3a46ce17d51a4f383e32e", size = 4013132, upload-time = "2025-06-26T16:27:06.415Z" },
{ url = "https://files.pythonhosted.org/packages/b7/42/85b3aa8f06ca0d24962f8100f001828e1f1f1a38c954c16e71154ed7d53a/lxml-6.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:21db1ec5525780fd07251636eb5f7acb84003e9382c72c18c542a87c416ade03", size = 3672642, upload-time = "2025-06-26T16:27:09.888Z" },
]
[[package]]
name = "markdown"
version = "3.8.2"
@ -463,6 +513,7 @@ version = "1.2.1"
source = { editable = "." }
dependencies = [
{ name = "dacite" },
{ name = "feedgen" },
{ name = "jinja2" },
{ name = "l2m4m" },
{ name = "markdown" },
@ -489,6 +540,7 @@ dev = [
[package.metadata]
requires-dist = [
{ name = "dacite", specifier = ">=1.9.2" },
{ name = "feedgen", specifier = ">=1.0.0" },
{ name = "jinja2", specifier = ">=3.1.6" },
{ name = "l2m4m", specifier = ">=1.0.4" },
{ name = "markdown", specifier = ">=3.8.2" },