
Org blog with RSS
23/06/2024
Why do you even need RSS?
RSS might seem like an outdated, marginal thing. But it still has at least one benefit-you can use an RSS feed as a sitemap for search engines. Plus, it's pretty geeky.
Add RSS feed
So, what's happening here? Let's start by integrating our templating functions into the build.
Use sitemap backend in the build
(setq org-publish-project-alist
(list
(list "blog-rss"
:author "Alex M"
:email "iam@fidonode.me"
:base-directory my/blog-src-path
:base-extension "org"
:recursive t
:exclude (regexp-opt '("rss.org" "index.org" "404.org" "posts.org"))
:publishing-function 'my/publish-to-rss
:publishing-directory my/web-export-path
:rss-extension "xml"
:html-link-home my/url
:html-link-use-abs-url t
:html-link-org-files-as-html t
:auto-sitemap t
:sitemap-filename "rss.org"
:sitemap-title "rss"
:sitemap-style 'list
:sitemap-sort-files 'anti-chronologically
:sitemap-function 'my/format-rss-feed
:sitemap-format-entry 'my/format-rss-feed-entry)
))
How does it work? As you can see, we use the default sitemap generator
from Org Export with some additional steps. By default, the sitemap
generator collects all org files from :base-directory
. It then places
links to these files as separate entries into an intermediate org file
and publishes this org file. We use custom functions for collecting
entries, formatting entries, and publishing the org file.
Publishing and formatting functions
We need a mandatory dependency because we don't want to mess with forming correct XML by ourselves.
(require 'ox-rss)
Here's the core of the process. We need to prepare entries before
feeding them to the org-rss-publish-to-rss
function. Since we're doing
it our own way, we need to start by creating a bullet with a link to the
post, and then add RSS_PERMALINK
, RSS_TITLE
, and PUBDATE
. I also
add the first two lines of the blog post instead of a description. This
is the native way to do a preview in the RSS world.
(defun my/format-rss-feed-entry (entry style project)
"Format ENTRY for the RSS feed.
ENTRY is a file name. STYLE is either 'list' or 'tree'.
PROJECT is the current project."
(cond ((not (directory-name-p entry))
(let* ((file (org-publish--expand-file-name entry project))
(title (org-publish-find-title entry project))
(date (format-time-string "%Y-%m-%d" (org-publish-find-date entry project)))
(link (concat (file-name-sans-extension entry) ".html")))
(with-temp-buffer
(org-mode)
(insert (format "* [[file:%s][%s]]\n" file title))
(org-set-property "RSS_PERMALINK" link)
(org-set-property "RSS_TITLE" title)
(org-set-property "PUBDATE" date)
(let ((first-two-lines (with-temp-buffer
(insert-file-contents file)
(buffer-substring-no-properties
(point-min)
(progn (forward-line 2) (point))))))
(if (string-suffix-p "\n" first-two-lines)
(setq first-two-lines (substring first-two-lines 0 -1)))
(insert first-two-lines))
(goto-char (point-max))
(insert "...")
(buffer-string))))
((eq style 'tree)
;; Return only last subdir.
(file-name-nondirectory (directory-file-name entry)))
(t entry)))
This function creates the content of the intermediate org file. Mostly a
rudimentary title, and then we ask Org to unwind the list of collected
files with the my/format-rss-feed-entry
function.
(defun my/format-rss-feed (title list)
"Generate RSS feed, as a string.
TITLE is the title of the RSS feed. LIST is an internal
representation for the files to include, as returned by
`org-list-to-lisp'. PROJECT is the current project."
(concat "#+TITLE: " title "\n"
"#+STARTUP: showall \n\n"
(org-list-to-subtree list 1 '(:icount "" :istart ""))))
This function replaces the default publishing function to filter everything except the intermediate file before publishing.
(defun my/publish-to-rss (plist filename pub-dir)
"Publish RSS with PLIST, only when FILENAME is 'rss.org'.
PUB-DIR is when the output will be placed."
(if (equal "rss.org" (file-name-nondirectory filename))
(org-rss-publish-to-rss plist filename pub-dir)))
Voila! Now you have an rss.xml
in your export path.