From afc446bc9e454b9707ace1d6d22336f1613adb7f Mon Sep 17 00:00:00 2001 From: Jean-Marie 'Histausse' Mineau Date: Sat, 2 May 2026 00:31:23 +0200 Subject: [PATCH] generate rss channel as xml str --- lib/main.typ | 1 + lib/rss.typ | 81 ++++++++++++++++++++++++++++++++++++++++++ lib/summary.typ | 14 ++++---- test_template/main.typ | 44 ++++++++++++++--------- 4 files changed, 117 insertions(+), 23 deletions(-) create mode 100644 lib/rss.typ diff --git a/lib/main.typ b/lib/main.typ index 0f985f3..4004770 100644 --- a/lib/main.typ +++ b/lib/main.typ @@ -2,6 +2,7 @@ #import "./html_body.typ": html_body #import "./html_utils.typ": html_show #import "./summary.typ": summary, card-list +#import "./rss.typ": rss /// Mail template function diff --git a/lib/rss.typ b/lib/rss.typ new file mode 100644 index 0000000..1f8175b --- /dev/null +++ b/lib/rss.typ @@ -0,0 +1,81 @@ +#import "./summary.typ": summary-sort-key + +// TODO DOCUMENT + +#let xml-tag( + name, + attrs: (), + children: none, + body: none, +) = { + assert(not ((children != none) and (body != none)), message: "children and body cannot be set at the same time") + + ("<" + name + + if attrs.len() != 0 { " " } else { "" } + + attrs.map(attr => attr.at(0) + "=\"" + attr.at(1) + "\"").join(" ") + + if children == none and body == none { "/>" } else if children != none { + ( + ">\n " + children.join("\n").replace("\n", "\n ") + + "\n" + ) + } else if body != none { + ">" + body + "" + } else { + panic("children and body cannot be set at the same time") + }) +} + +// We don't care about the hour, but RFC 822 requires it, so use 0 and GMT +#let date-format-rfc822 = "[day padding:zero] [month repr:short] [year repr:full] 00:00:01 +0000" + +#let rss( + title, + link, + description, + rss-url, + summaries, + language: "en-us", + managing-editor: none, + webmaster: none, +) = { + let channel-element = ( + xml-tag("title", body: title), + xml-tag("link", body: link), + xml-tag("description", body: description), + xml-tag("language", body: language), + xml-tag("lastBuildDate", body: datetime.today().display(date-format-rfc822)), + xml-tag("docs", body: "https://www.rssboard.org/rss-specification"), + xml-tag("generator", body: "Some random Typst scrypt"), + xml-tag("atom:link", attrs: ( + ("href", rss-url), + ("rel", "self"), + ("type", "application/rss+xml") + )), + ) + if managing-editor != none { + channel-element.push(xml-tag("managingEditor", body: managing-editor)) + } + if webmaster != none { + channel-element.push(xml-tag("webMaster", body: webmaster)) + } + let items = summaries.sorted( + key: summary-sort-key + ).map(it => xml-tag( + "item", + children: ( + xml-tag("title", body: it.document-args.title), + xml-tag("link", body: it.template-args.url), + xml-tag("guid", body: it.template-args.url), + xml-tag("description", body: it.document-args.description), + xml-tag("pubDate", body: it.document-args.date.display(date-format-rfc822)), + ) + it.template-args.tags.map(tag => xml-tag("category", body: tag)) + )) + xml-tag( + "rss", + attrs: ( + ("xmlns:atom", "http://www.w3.org/2005/Atom"), + ("version","2.0") + ), + children: (xml-tag("channel", children: channel-element + items),) + ) +} diff --git a/lib/summary.typ b/lib/summary.typ index b149597..a0bc01a 100644 --- a/lib/summary.typ +++ b/lib/summary.typ @@ -146,6 +146,13 @@ return summ } +#let summary-sort-key(summ) = -( + summ.document-args.date.day()-1 + 31 * ( + (summ.document-args.date.month()-1) + + 12 * summ.document-args.date.year() + ) +) + /// Display a list of summary cards. /// /// min-width is the minimum width of the cards, in px. @@ -166,12 +173,7 @@ lang: "raw-css", ) chtml.summary-card-list(attrs: (class: class-name), { - for summ in summs.sorted(key: it => - -(it.document-args.date.day()-1 + 31 * ( - (it.document-args.date.month()-1) + - 12 * it.document-args.date.year() - )) - ) { + for summ in summs.sorted(key: summary-sort-key) { summ.card } }) diff --git a/test_template/main.typ b/test_template/main.typ index 01354de..89ac7b3 100644 --- a/test_template/main.typ +++ b/test_template/main.typ @@ -166,22 +166,32 @@ thead { ) #let perm = (17, 6, 20, 19, 15, 5, 13, 11, 14, 12, 16, 10, 2, 3, 1, 9, 7, 4, 18, 8) -#card-list( - min-width: 200, - range(20).map(i => - summary( - url: "http://test.example.com", - title: "Card " + str(perm.at(i)), - tags: if perm.at(i) == 20 { - ("tag2", "tag5","loooooonnnnnnnnnnnnnng-tag","some-tag","some-other-tag") - } else { - (2, 3, 5, 7).filter(j => calc.rem(perm.at(i), j) == 0).map(j => "tag" + str(j)) - }, - preview-image: summ.preview-image, - img-copyright: if calc.rem(i, 3) == 0 { [Histausse ] } else { none }, - author: "Me!", - description: lorem(10 * calc.rem(i * 123, 10)), - date: datetime(year: 2000, month: 12, day: perm.at(i)), - ) +#let summaries = range(20).map(i => + summary( + url: "http://test.example.com/tst/" + str(perm.at(i)), + title: "Card " + str(perm.at(i)), + tags: if perm.at(i) == 20 { + ("tag2", "tag5","loooooonnnnnnnnnnnnnng-tag","some-tag","some-other-tag") + } else { + (2, 3, 5, 7).filter(j => calc.rem(perm.at(i), j) == 0).map(j => "tag" + str(j)) + }, + preview-image: summ.preview-image, + img-copyright: if calc.rem(i, 3) == 0 { [Histausse ] } else { none }, + author: "Me!", + description: lorem(10 * calc.rem(i * 123, 10)), + date: datetime(year: 2000, month: 12, day: perm.at(i)), ) ) +#card-list( + min-width: 200, + summaries, +) + +#raw(lang: "xml", block: true, rss( + "TeTyTe test typst template", + "https://test.example.com", + "Test of the TTT template", + "https://test.example.com/rss.xml", + summaries, + webmaster: "me@example.com (Me)", +))