Emacs' Builtin TemplatingSat, Jun 11, 2016
I use Hugo for blogging. It statically generates the site, which means I need to type up a markdown file for every post. This should be so easy that I can do it at a moment’s notice, without thought. But there’s a bit of formatting that goes into the headers for such a file, so let’s streamline the process using Emacs.
There are a lot of templating options for Emacs, but my philosophy with almost all software is that you should use built-in functionality if it covers your needs, resorting to external projects only when the functionality gain outweighs the ongoing cost of maintaining a dependency on the external project. Luckily, Emacs has a couple of built-in systems that make this task easy.
For this task, we’ll use the Skeleton language in conjunction with abbrevs. Skeleton allows us to define functions that automatically insert the template text, and abbrevs allow us to invoke those functions when we type short codes in the buffer.
First, the skeleton:
(define-skeleton new-blog-post-skeleton ; Description of skeleton "Create a new blog post for Hugo" ; placeholder, since we're using skeleton-read directly "" ; Remaining lines are just concatenated together "---\nlayout: post\ntitle: \"" ; Forms are evaluated and the result included (skeleton-read "Title: ") "\"\ndate: " (skeleton-read "Date: " (format-time-string "%Y-%m-%d"))) "\n---\n\n")
This uses skeleton in a bit of an unconventional way, since it inlines
skeleton-read function so we can write a template that contains
multiple values. With that caveat out of the way, the code is
actually pretty simple. The first three arguments are fixed:
- The name of the skeleton being defined.
- A description of the skeleton
- A prompt used to gather input
We disregard (3) here since we use
skeleton-read directly. Any
argument beyond the first three arguments is just concatenated
together to get the final template. Strings are handled as-is, and
forms are evaluated, with their result (a string) concatenated like a
normal string would be.
In this case, we use the form
skeleton-read, which is a function
that prompts for user input, accepting an argument for the prompt
text, and an optional second argument for the default text to be used.
We leverage the second argument to default to today’s date, which we
can fetch and format using
Now that we’ve defined the skeleton, we just have to find an easy way
to activate this function. Enter
abbrev mode. We can use the short
code =nbp= (for New Blog Post) to activate the skeleton code:
(define-abbrev global-abbrev-table "nbp" "" 'new-blog-post-skeleton) (setq default-abbrev-mode t)