HALFWAY TOWARDS RSS ARTICLE EXCERPTS WITH OX-RSS.EL Overview ---------------------------------------------------------------------- In this article I detail how to automatically add excerpts to an org-publishing project's RSS feed. If you're seeing this text in an RSS feed reader, then that means it worked! IMG iillustration-10.png Gathering distance ---------------------------------------------------------------------- Even if your code doesn't work it's still a good idea to share the progress you've made. That's what I told myself, anyways, as I set out to start this article. At the time, my small modifications to `ox-rss.el', a package that adds a RSS publishing backend to org-mode, were broken. Well, not broken. But not working with my existing org-publish project. I had the code working from a GUI Emacs session. But when I published my website using the shell (`emacs -q --batch --load publish-roygbyte_com.el --funcall org-publish-all'), it "broke". So this article was to be a documentation of my progress and a shy cry for help. I tore myself away from the screen and enjoyed a little bit of "snack and movement." Turns out, that was enough distance to let me gather perspective on the problem at hand! Like I said, the code was working from within a GUI Emacs session. The code, by the way, was a series of functions that returned the first paragraph (or "excerpt") from an org file. The entry point was `(org-file-first-paragraph id)', where `id' is an org-roam id. It /just worked/ when testing in my GUI Emacs session. But not in the shell--the function returned `nil' because it couldn't find a file from the given id. Turns out, my publishing file, `publish-roygbyte_com.el', had incorrectly set the location of my org-roam directory. It couldn't find a file from the given id because it was looking in the wrong place! Anyways, to cut to the chase: I figured it out. Probably this article should be more accurately called "Fully arrived at RSS article excerpts with ox-rss.el". But I'm sentimental, and I want to keep the initial context. Because: getting halfway is still distance travelled. Next time I end up at a deadend, I'll want to reminder /that/. Certainly I'm not the only programmer who gets physically stuck in mental problems. But for my own growth, I'd like to get stuck a /little/ less by taking breaks /more/. Adding excerpts to ox-rss.el ---------------------------------------------------------------------- Below is the code at the time of writing. It's also available on my , where I'll post updates and bug fixes. rss.org ...................................................................... The apptly named property `:EXCERPT_FROM_ID:' is passed an id to an org file. The id doesn't have to be the same as the file referenced in the `:RSS_PERMALINK': property. Imagine you wanted to have the RSS entry like to some random article on the internet, and you wanted the excerpt to be your thoughts on the matter. ,---- | * On comics and code | :PROPERTIES: | :PUBDATE: <2022-08-23 Tue> | :EXCERPT_FROM_ID: e7d501c8-91af-4811-8e24-ec7fe614d3b5 | :RSS_PERMALINK: comics_and_code.html | :END: `---- org-rss-headline / ox-rss.el ...................................................................... A small modification to `org-rss-headline's' function: add a conditional assignment to the contents variable. If the `:EXCERPT_FROM_ID:' property is set in the headline, and the property is a string, `org-file-first-paragraph' will return the excerpt from the given org file. Otherwise, contents remains unchanged. ,---- | ;; Excerpt from ox-rss.el | (defun org-rss-headline (headline contents info) | "Transcode HEADLINE element into RSS format. | CONTENTS is the headline contents. INFO is a plist used as a | communication channel." | (unless (or (org-element-property :footnote-section-p headline) | (> (org-export-get-relative-level headline info) 1)) | (let* ((author (and (plist-get info :with-author) | (let ((auth (plist-get info :author))) | (and auth (org-export-data auth info))))) | ;; Roygbyte's excerpt code. | (contents (let ((id (org-element-property :EXCERPT_FROM_ID headline))) | (if (stringp id) | (org-file-first-paragraph id) | contents))) | ;; Excerpt ends ... `---- org-file-first-pragraph (and other functions) / ox-rss.el ...................................................................... The rest of the code is contained below. Rather than explain what's going on (the functions include documentations which should be enough te get a grip on what's going on), I'll explain how I got it to work! The recipe, if you will, for hacking Lisp and org-mode. Here's what kept me afloat: - `C-h f', for learning what functions existed, and how they worked. - `M-x' shortdoc-display-group, for learning about buffers. - `*scratch*', for testing code. - `ox-publish.el' and `org-element.el', for learning about parsing org files. - /ANSI Common Lisp/ by Paul Graham, for learning about cons, car, cdr, and lists. - DuckDuckGo, obviously. ,---- | | (defun file-contents (filename) | "Given a filename, return the corresponding file's contents." | (with-temp-buffer | (insert-file-contents filename) | (buffer-string))) | | (defun org-file-to-element-tree (file) | "Given the path to an org file, return the file's element tree" | (with-temp-buffer | (insert-file-contents file) | (org-element-parse-buffer 'greater-element))) | | (defun extract-region-from-file (filename position) | "Given a path to a file, extract the content between the start and end position" | (with-temp-buffer | (insert-file-contents filename) | (let ((region (buffer-substring (car position) (cdr position)))) | (with-temp-buffer | (insert region) | (buffer-string))))) | | (defun org-tree-to-paragraph-positions (org-file-tree) | "Given an org-file tree, get the first paragraph positions. Returns a cons." | (org-element-map org-file-tree | 'paragraph (lambda (p) | (cons (org-element-property :begin p) | (org-element-property :end p)) | ))) | | (defun org-roam-id-to-file-path (id) | "Given an org roam id, return the file's path" | (let ((file-path (org-roam-id-find id))) | (if (arrayp file-path) | (file-truename (car (org-roam-id-find id))) | nil))) | | (defun org-file-first-paragraph (id) | "Given an org roam file by ID, find the first paragraph of the file" | (if (stringp id) | (let ((file-path (org-roam-id-to-file-path id))) | (if (stringp file-path) | (let ((file-contents (file-contents file-path)) | (file-tree (org-file-to-element-tree file-path))) | (extract-region-from-file file-path (nth 0 (org-tree-to-paragraph-positions file-tree)))) | nil | )) | nil)) | `---- publish.el ...................................................................... ,---- | (require 'org-roam) | (require 'ox-rss) | (use-package org-roam Happy helping ☃ here: You tried to output a spurious TAB character. This will break gopher. Please review your scripts. Have a nice day! Happy helping ☃ here: You tried to output a spurious TAB character. This will break gopher. Please review your scripts. Have a nice day! Happy helping ☃ here: You tried to output a spurious TAB character. This will break gopher. Please review your scripts. Have a nice day! Happy helping ☃ here: You tried to output a spurious TAB character. This will break gopher. Please review your scripts. Have a nice day! Happy helping ☃ here: You tried to output a spurious TAB character. This will break gopher. Please review your scripts. Have a nice day! Happy helping ☃ here: You tried to output a spurious TAB character. This will break gopher. Please review your scripts. Have a nice day! Happy helping ☃ here: You tried to output a spurious TAB character. This will break gopher. Please review your scripts. Have a nice day! Happy helping ☃ here: You tried to output a spurious TAB character. This will break gopher. Please review your scripts. Have a nice day! | (setq org-id-extra-files (org-roam-list-files)) `---- Final thoughts ---------------------------------------------------------------------- Lisp is a beautifully simple language, although it may not seem that way at first glance. I'm grateful I've been able to muster the patience to learn Lisp. Already, even with a very noviced understanding of its principles, I'm able to put it to use.