#!/usr/bin/env python3 # -*- coding: utf-8 -*- from datetime import datetime, timezone from typing import Mapping, MutableMapping import csv import sys import xmltodict RSS_DATE_FORMAT = '%a, %d %b %Y %T %z' ISO_DATE_FORMAT = '%Y-%m-%dT%H:%M:%S%z' def parse_book(book: MutableMapping[str, str]) -> Mapping: item = { "title": book["title_sort"], "pubDate": datetime.strptime(book.pop("timestamp"), ISO_DATE_FORMAT) .strftime(RSS_DATE_FORMAT), "guid": { "@isPermaLink": "false", "#text": book.pop("uuid"), }, "description": book.pop("comments"), # The CSV's first character is a non-breaking space for some reason, # which breaks the author column "author": book.get("author_sort") or book["\ufeffauthor_sort"], } # Prepend metadata to the item description item["description"] = "
{}
{}".format( "".join( "
{}
{}
".format( key.replace('_sort', '').replace('_', ' ').replace('\ufeff', '').capitalize(), value, ) for key, value in book.items() # Ignore empty columns if value ), item['description'] ) if book.get("tags"): item["category"] = [ { "@domain": "https://git.tilde.town/dozens/books", "#text": tag } for tag in book["tags"].split(", ") ] return item def main(): sys.stdout.write(xmltodict.unparse({ "rss": { "@version": "2.0", "@xmlns:atom": "http://www.w3.org/2005/Atom", "@xmlns:sy": "http://purl.org/rss/1.0/modules/syndication/", "channel": { "title": "dozens books", "description": "the cool calibre library of dozens", "link": "https://git.tilde.town/dozens/books", "atom:link": { "@rel": "self", "@type": "application/rss+xml", "@href": "https://git.tilde.town/dozens/books/raw/branch/main/feed.xml", }, "language": "en-US", "pubDate": datetime.now(timezone.utc) .strftime(RSS_DATE_FORMAT), "docs": "https://www.rssboard.org/rss-specification", "webMaster": "dozens@tilde.town (~dozens)", "generator": "Python " + ".".join(map(str, sys.version_info[:3])), # Update on the first of every month, at midnight UTC "sy:updatePeriod": "monthly", "sy:updateFrequency": "1", "sy:updateBase": "1971-01-01T00:00+00:00", # One month, roughly, for clients that do not support mod_syndication "ttl": 60 * 24 * 30, "item": list(map(parse_book, csv.DictReader(sys.stdin))), } } }, pretty=True, short_empty_elements=True)) if __name__ == '__main__': main()