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!

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.