Index: makefile ================================================================== --- makefile +++ makefile @@ -19,11 +19,11 @@ # ~~~ Rules ~~ # # The order of these dependencies is important. They will be processed left to right. web: vitreous.sqlite $(articles-html) $(series-html) -web: index.html blog-pg1.html keyword-index.html web-extra/martin.css +web: index.html blog-pg1.html keyword-index.html web-extra/martin.css feed.xml web: ## Rebuild all web content (not PDFs) # The file vitreous.sqlite is a cache of the rendered HTML and metadata. If it is older than any of # its dependencies (or missing) all of the articles will be rebuilt. Its dependencies are also on # the Pollen cache watchlist (see pollen.rkt) @@ -62,10 +62,13 @@ racket -tm keyword-index.rkt # tidy -quiet -modify -indent --wrap 100 --wrap-attributes no --tidy-mark no $@ || true web-extra/martin.css: web-extra/martin.css.pp raco pollen render $@ + +feed.xml: vitreous.sqlite rss-feed.rkt + racket -tm rss-feed.rkt zap: ## Clear Pollen and Scribble cache, and remove all HTML output raco pollen reset rm -f *.html articles/*.html series/*.html vitreous.sqlite ADDED rss-feed.rkt Index: rss-feed.rkt ================================================================== --- rss-feed.rkt +++ rss-feed.rkt @@ -0,0 +1,105 @@ +#lang racket/base + +; SPDX-License-Identifier: BlueOak-1.0.0 +; This file is licensed under the Blue Oak Model License 1.0.0. + +;; Generates an Atom feed from the SQLite cache + +(require txexpr + racket/match + racket/file + racket/date + racket/string + db/base + "dust.rkt" + "crystalize.rkt") + +(provide main) + +(define feed-author default-authorname) +(define feed-author-email "joel@jdueck.net") +(define feed-title "The Local Yarn (Beta)") +(define feed-site-url "https://thelocalyarn.com") +(define feed-item-limit 50) + +(define (as-cdata string) + (cdata #f #f (format "" string))) ; cdata from xml package via txexpr + +(define (email-encode str) + (map char->integer (string->list str))) + +;; Atom feeds require dates to be in RFC 3339 format +;; Our published/updated dates only give year-month-day. No need to bother about time zones or DST. +;; So, arbitrarily, everything happens at a quarter of noon UTC. +(define (ymd->rfc3339 datestr) + (format "~aT11:45:00Z" datestr)) + +;; For the feed "updated" value, we do want a complete timestamp. +(define (current-rfc3339) + ;; #f argument to seconds->date forces a UTC timestamp + (define now (seconds->date (* 0.001 (current-inexact-milliseconds)) #f)) + (define timestamp + (parameterize [(date-display-format 'iso-8601)] + (date->string now #t))) + (string-append timestamp "Z")) + +;; Get the data out of the SQLite cache as vectors +(define (fetch-rows) + (define fields '(pagenode title_plain published updated author doc_html)) + (define select #<<--- + SELECT `path`, `title`, `published`, `updated`, `author`, `entry-contents` FROM + (SELECT `pagenode` AS `path`, + `title_plain` AS `title`, + `published`, + `updated`, + `author`, + `doc_html` AS `entry-contents` + FROM `articles` + UNION + SELECT `pagenode` || '#' || `note_id` AS `path`, + `title_plain` AS `title`, + `date` AS `published`, + "" AS `updated`, + `author`, + `content_html` as `entry-contents` + FROM `notes`) + ORDER BY `published` DESC LIMIT ~a +--- + ) + (query-rows (sqltools:dbc) (format select feed-item-limit))) + +(define (vector->rss-item vec) + (match-define + (vector path title published updated author contents) vec) + (define entry-url (string-append feed-site-url web-root path)) + (define update-ts + (cond [(non-empty-string? updated) updated] + [else published])) + + `(entry (author (name ,author)) + (published ,(ymd->rfc3339 published)) + (updated ,(ymd->rfc3339 update-ts)) + (title ,title) + (link [[rel "alternate"] [href ,entry-url]]) + (id ,entry-url) + (summary [[type "html"]] + ,(as-cdata contents)))) + +(define (rss-feed) + (define feed-xpr + `(feed [[xml:lang "en-us"] [xmlns "http://www.w3.org/2005/Atom"]] + (title ,feed-title) + (link [[rel "self"] [href ,(string-append feed-site-url web-root "feed.xml")]]) + (generator [[uri "http://pollenpub.com/"]] "Pollen") + (id ,(string-append feed-site-url web-root)) + (updated ,(current-rfc3339)) + (author + (name ,feed-author) + (email ,@(email-encode feed-author-email))) + ,@(map vector->rss-item (fetch-rows)))) + (string-append "\n" + (xexpr->string feed-xpr))) + +(define (main) + (spell-of-summoning!) ; Turn on the cache DB connection + (display-to-file (rss-feed) "feed.xml" #:mode 'text #:exists 'replace))