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. The parse-metadata
function below is what
I came up with. I query the Org parse tree for the title, date and
category keywords, and the abstract
special block. I created a
little helper sb/org-kw-get
function 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)))