Blogging with Emacs
Jon Fineman's Place resume blog
Yet another static site generator using Emacs Elisp with Org Mode and webfeeder to generate an Atom feed.
Being rather new to Emacs I found ox-publish easy enough to use to create blog posts and export them to html. What was still a mystery and really still is, is how to use org-rss-publish-to-rss to create an RSS/Atom feed.
Many of the examples I found used functions to create the RSS feed. They did not rely on the basic exporter.
All of the ones I found were rather old and didn't work. Again my Elisp skill is at a novice level, so I couldn't tell if they didn't work because of bugs, incompleteness, or at this point a difference in Emacs' versions.
A few didn't even include all of the code needed. The few that did work didn't create a valid RSS feed.xml!
The post "Creating an RSS Feed" by Randy Ridenour 1 however was very simple, didn't use org-rss-publish-to-rss, and could create an RSS or Atom feed using the webfeeder package. This really inspired me to continue with Org Publish to create my RSS/Atom feed but use webfeeder.
The other difficulty I had being even newer to Org Mode was visualizing how the actual web site and HTML fit into Org Mode. It wasn't clear from the posted examples if they produced the blog post I was reading!
So hopefully this won't suffer from the same problems and shortcomings.
Two negatives I haven't fully explored yet are:
- If you run too many Org Publish commands in your Emacs session, keyboard response slows down to a painful delay. It seems to be spending an inordinate amount of time in garbage collection.
- If you remove say an Org file it will still show up in the HTML export. Not sure yet of the proper way to remove it. Once it occurs it seems the only way to clean up is to delete Emacs' cache from ~/.org-timestamps/* .
Throw in browser cache and a CDN and where the stray file is can be a little confusing.
Inspired me to continue and not use Org publish to create my RSS/Atom feed but use webfeeder.
Unfortunately I lost track of who created the function sitemap-format-entry for the tag :sitemap-format-entry. Maybe from here: https://taingram.org/blog/org-mode-blog.html
My Elisp file is below. It contains a few functions. All of my files and blog posts can be found at: jjf blog at sr.ht
jjf/org-pub - builds/publishes just the changed files.
jjf/org-pub-all - builds/publishes all files.
jjf/org-blog-staging - builds any files I may be editing that I don't want to include in the site yet.
jjf/webfeeder-build-atom - generates the feed.xml for the blog.
jjf/blog-sitemap-format-entry - For the blog and rss section plugin for :sitemap-format-entry to format my sitemap.
jjf/rss-sitemap-format-entry - Same as above.
My publish.el file
(require 'ox-publish) (require 'ox-rss) (require 'webfeeder) (setq org-html-validation-link nil ;; Don't show validation link org-html-head-include-scripts nil ;; Use our own scripts org-html-head-include-default-style nil ;; Use our own styles org-html-head "<link rel=\"stylesheet\" href=\"~/org/jon.fineman.me/content/simple.min.css\" />" org-html-home/up-format "" org-html-link-up "" ) (setq user-mail-address "jjf.blog@fineman.me") (setq org-publish-project-alist `(("pages" :base-directory "~/org/jon.fineman.me/content/" :base-extension "org" :recursive nil :publishing-directory "~/org/jon.fineman.me/html/" :publishing-function org-html-publish-to-html :html-link-home "https://jon.fineman.me/" :html-link-org-files-as-html t :html-link-use-abs-url nil :html-scripts nil :html-style t :html-html5-fancy t ) ("blog" :base-directory "~/org/jon.fineman.me/content/posts/" :base-extension "org" :recursive t :publishing-directory "~/org/jon.fineman.me/html/posts/" :publishing-function org-html-publish-to-html :html-link-home "https://jon.fineman.me/posts/" :html-link-org-files-as-html t :html-link-use-abs-url n :html-scripts nil :html-style t :html-html5-fancy t :exclude "index.org\\|site.org\\|rss.org\\|sitemap.org\\|feed.org\\|header-p.org\\|blog-sitemap.org" :auto-sitemap t :sitemap-title "" :sitemap-filename "index.org" :sitemap-style list :sitemap-ignore-case t :sitemap-format-entry jjf/blog-sitemap-format-entry :sitemap-sort-files anti-chronologically ) ("media" :base-directory "~/org/jon.fineman.me/content/posts/media/" :base-extension "jpg\\|gif\\|png" :recursive t :publishing-directory "~/org/jon.fineman.me/html/posts/media/" :publishing-function org-publish-attachment) ("static" :base-directory "~/org/jon.fineman.me/content/" :base-extension "css" :recursive t :publishing-directory "~/org/jon.fineman.me/html/" :publishing-function org-publish-attachment) ("resume" :base-directory "~/org/jon.fineman.me/content/resume/" :base-extension "pdf" :recursive t :publishing-directory "~/org/jon.fineman.me/html/resume/" :publishing-function org-publish-attachment) ("pages-static" :base-directory "~/org/jon.fineman.me/content/pages-static/" :base-extension "ico\\|txt\\|html" :recursive t :publishing-directory "~/org/jon.fineman.me/html/" :publishing-function org-publish-attachment) ("editing" :base-directory "~/org/jon.fineman.me/content/editing/" :base-extension "org" :recursive t :publishing-directory "~/org/jon.fineman.me/html/staging/" :publishing-function org-html-publish-to-html :html-link-org-files-as-html t :html-link-use-abs-url n :html-scripts nil :html-style t :html-html5-fancy t :exclude "index.org\\|site.org\\|rss.org\\|sitemap.org\\|feed.org\\|header-p.org\\|blog-sitemap.org" :auto-sitemap n ) ("blog-rss" :base-directory "~/org/jon.fineman.me/content/posts/" :base-extension "org" :recursive nil :exclude "rss.org\\|sitemap.org\\|index.org\\|header-p.org" :publishing-function org-rss-publish-to-rss :publishing-directory "~/org/jon.fineman.me/html/rss/" :rss-extension "xml" :html-link-home "https://jon.fineman.me" :html-link-use-abs-url t :html-link-org-files-as-html t :auto-sitemap t :sitemap-filename "feed.org" :sitemap-title "jjf rss" :sitemap-style list :sitemap-ignore-case t :sitemap-format-entry jjf/rss-sitemap-format-entry :sitemap-sort-files anti-chronologically ) ("jon.fineman.me" :components ("pages" "pages-static" "blog" "media" "static" "resume")) ("blog-staging" :componenrs ("editing")) ("blog-rss-test" :components ("blog-rss-test")) ) ) (defun jjf/org-pub (arg) "generate html" (interactive "P") (message "Generating website for staging...") (if arg ;; force rebuild of everything (org-publish "jon.fineman.me" t nil) ;; only rebuild what has changed (org-publish "jon.fineman.me" nil nil)) (message "Done generating website") ) (define-key org-mode-map (kbd "C-c p") (lambda () "generate org html" (interactive) (jjf/org-pub t))) (defun jjf/org-pub-all () "genereate all html" (interactive) ;;(org-publish "jon.fineman.me" t nil)) (jjf/org-pub t) (message "Done generating website") (jjf/webfeeder-build-atom) (message "Done generating feed") ) (defun jjf/org-blog-staging () "genereate staging files" (interactive) (message "starting staging") (org-publish "editing" t nil) (message "Done generating feed") ) (defun jjf/webfeeder-build-atom () (interactive) (setq default-directory "~/org/jon.fineman.me/html/") (setq files (remove "posts/header-p.html" (remove "posts/blog-sitemap.html" (remove "posts/index.html" (directory-files-recursively "posts/" ".*\\.html"))))) (webfeeder-build "feed.xml" "." "https://jon.fineman.me/" files :builder 'webfeeder-make-atom :title "jjf rss" :description "jjf rss feed" :author "jjf.blog@fineman.me" ) (message "rss feed complete") ) (defun jjf/blog-sitemap-format-entry (entry style project) (cond ((not (directory-name-p entry)) (format "%s [[file:%s][%s]]" (format-time-string "%Y-%m-%d - " (org-publish-find-date entry project)) entry (org-publish-find-title entry project))) ((eq style 'tree) (file-name-nondirectory (directory-file-name entry))) (t entry))) (defun jjf/rss-sitemap-format-entry (entry style project) (cond ((not (directory-name-p entry)) (format "%s [[file:posts/%s][%s]]" (format-time-string "%Y-%m-%d - " (org-publish-find-date entry project)) entry (org-publish-find-title entry project))) ((eq style 'tree) (file-name-nondirectory (directory-file-name entry))) (t entry)))
Footnotes:
Creating an RSS Feed by Randy Ridenour https://randyridenour.net/posts/2025-01-07-creating-rss-feed.html