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
       |                          :ensure t
       |                          :custom
       |                          (org-roam-directory "~/workbench/second-brain")
       |                          (org-roam-dailies-directory "journals/")
       |                          (org-roam-complete-everywhere t)
       |                          :init
       |                          (setq org-roam-v2-ack t)
       |                          (org-roam-db-autosync-mode t))
       | (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.