Tagging blog posts from Emacs

First, I apologise in advance (to all 3 of you subscribed) if you got an avalanche of old new posts in your RSS feed. I’ve just gone through and tagged a lot of posts.

What’s most interesting, at least for me, was that I did it en-masse via Emacs. This blog is a set of static files generated by Jekyll Bootstrap, as I have written about previously. A post is simply a file with a bit of YAML meta-data up the top, optionally including tags applying to that post:

layout: post
title: "Sample yaml front-matter"
tags: [SomeTag,AnotherTag]
Body of the post...

To simplify the repetitive work involved I wrote some elisp to take a bunch of files, query for the relevant tags, and add them to the front matter:

(defun mh/tag-posts (tags)
  "Apply tags (specified interactively) to the files marked in
dired, merging with existing tags.  Assumes the use of
jekyll-bootstrap, ie with the `---' delimited yaml front-matter
at the beginning."
  (interactive (list (read-from-minibuffer "Tag(s), comma-separated: ")))
  (let ((tags (split-string tags "[, ]+")))
        ((do-tags (f)
                  (with-current-buffer (find-file-noselect f)
                      (goto-char 0)
                      (if (re-search-forward "^tags: \\[\\([[:alnum:], ]*\\)\\]" nil t)
                          (let* ((existing-tags (split-string (match-string 1) "[, ]+"))
                                 (combined (union existing-tags tags))
                                 (new-tags (mapconcat 'identity combined ",")))
                            ;; replace-match wasn't working for some reason:
                            (goto-char (match-beginning 1))
                            (delete-region (match-beginning 1) (match-end 1))
                            (insert new-tags))
                          (goto-char 0)
                          (forward-line 1)
                          (re-search-forward "^---" nil t) ; find second occurence, assuming first is on line 1
                          (goto-char (match-beginning 0))
                          (insert (format "tags: [%s]\n" (mapconcat 'identity tags ","))))))
      (mapcar #'do-tags (dired-get-marked-files)))))

The guts are in the last line: I mark files using dired in Emacs, then invoke mh/tag-posts, which applies a function to every marked file. This function then just has to take care of a few corner cases such as the tag already being included (by using union from the cl common lisp emulation library to avoid repetition), and when there’s no tags: [] entry at all, in which case it must add one.

Mark Hepburn 24 March 2013
blog comments powered by Disqus