Linking Org mode blog posts into the index page

I wanted it to be easy to create a new entry on the index page. I call on Emacs Lisp to help.

As mentioned when I first moved to use Org mode for blogging I haven't had a way to automatically create an index of posts. It's been a rather tedious manual task, and now that I've added TAGs and the RSS_PERMALINK property to the things I need to address I didn't want to do it manually any more. I took the opportunity to learn a little more Emacs lisp, and experiment with the Org APIs.

First and foremost I wanted a function that I could execute when visiting a blog post that generated the index entry for me, ready to paste into the index page. parse-metadata below is what I came up with. I query the Org parsetree for the title, date and category keywords, and the abstract special block. I created a little helper macro to avoid repetition, since I expected to extract three different keywords in the same way. (I've since decided not to extract DATE keywords). Figuring out how to extract the abstract was much harder, but I managed something that works well enough.

For generating the entry (in index.org) for my blog post I use Org mode programmatically. One caveat was that some of the APIs (I think most notably the org-set-tags-to call) would not work until I turned on Org mode in my temporary buffer. Instead of returning this as a string, I copy it to my kill ring so it's ready for pasting into the index page.

 1: (defun sb/org-kw-get (key)
 2:   "Return a lambda that takes an Org keyword
 3: element and returns its :value property if its :key
 4: property matches `key'."
 5:   `(lambda (kw)
 6:      (if (equal ,key (org-element-property :key kw))
 7:          (org-element-property :value kw))))
 8: 
 9: (defun sb/parse-metadata ()
10:   "Call in a blog post to get an entry suitable for
11: linking to this post from the index page."
12:   (interactive)
13:   (let* ((path (s-chop-prefix
14:                 (expand-file-name "~/blog/")
15:                 (buffer-file-name)))
16:          (tree (org-element-parse-buffer))
17: 
18:          (title
19:           (org-element-map tree 'keyword
20:             (sb/org-kw-get "TITLE") nil t))
21: 
22:          (categories
23:           (org-element-map tree 'keyword
24:             (sb/org-kw-get "CATEGORY")))
25: 
26:          (abstract
27:           (org-element-interpret-data
28:            (org-element-map tree 'special-block
29:              (lambda (sb)
30:                (if (equal "abstract"
31:                           (org-element-property :type sb))
32:                    (org-element-contents sb)))))))))
33: 
34:     (with-temp-buffer
35:       (org-mode)
36:       (org-insert-heading)
37: 
38:       ;; Would have loved to use `org-insert-link' here but
39:       ;; I can't stop it from presenting a prompt :-(
40:       (insert "[[file:" path "][" title "]]")
41: 
42:       (insert "\n\n")
43:       (insert abstract)
44: 
45:       (org-set-property "PUBDATE" date)
46:       (org-set-property "RSS_PERMALINK"
47:                         (format "%s.html"
48:                                 (s-chop-suffix ".org" path)))
49: 
50:       ;; Need to go back to the first line to set tags
51:       (goto-char (point-min))
52:       (org-set-tags-to categories)
53:       (org-set-tags nil t) ;; adjust tags in the source
54: 
55:       ;; Copy the contents of the temporary buffer as a string
56:       ;; *without properties* to my kill ring for pasting into
57:       ;; my index.org file
58:       (copy-region-as-kill
59:        (point-min) (point-max)))))

Author: Stig Brautaset

Validate