About & Contact About and Contact
Site Map Site Map
RSS feed / RSS feed
Follow our
          tweets: @gorgnegre Follow our tweets: @gorgnegre
Our sources Our sources

Blogging with Emacs Org-mode and Jekyll

Published • 17 Jul 2012 • emacs • org-mode • jekyll • web

This article shows how to set up the combo Emacs Org-mode & Jekyll in order to publish a static website or blog. Our purpose is to edit simple filename.org files, let Org-mode export to html, and have Jekyll publish them (or just fragments via tags) to a static website (maybe hosted at github). Essentially, we are using org to produce everything between the <body> tags on the page and Jekyll to produce the rest —note that you can easily embed html content in your org pages using the #+BEGIN_HTML tag.

This setup encourages an efficient workflow. Org-mode is very convenient for all-around editing, from simple note taking (with the excellent org-capture) to managing one's tasks (it's easy to set up a getting-things-done system) to writing fully-fledged articles (which can later be exported to LaTeX, for instance). Hence it feels just natural to write one's ramblings in org-mode. Jekyll, on the other hand, is a lightweight static website generator. By feeding it with org's output, we achieve a simple way of deploying a website which can be served by almost any web server without requiring additional components such as php. With some customisation, the workflow becomes highly automated.

After several years using the excellent Foswiki twiki based system and Drupal CMS, I have moved to using a static site generated with Jekyll. Since the site is maintained just by myself, I didn't really need the collaborative features of a wiki, nor the hassle of a mysql database (and certainly, not my web logs filling up with bots trying to post stuff to my webs —even though all edits required authorisation). Properly organised text files is what I'm after. Also Jekyll makes it easy for me to write using Emacs Org-mode while maintaining my site under version control using Git.

You can clone a repository with the setup outlined in this article from https://github.com/gorgnegre/org-jekyll.

References

This document blends content found in these other sites:

  1. The excellent Ian Barton's Org-Jekyll site.
  2. The excellent Juan Reyero's Org-Jekyll site. [Not yet implemented here.]
  3. The convenient Metajack's Emacs-Jekyll hack.
  4. Justin Lilly's succint org publishing page.

From Org-mode to Jekyll

Our starting point assumes that you have your org-mode properly setup and that you know how to use it (particularly, org publishing). Also, that Jekyll is installed and that you have a fair idea of its workings (particularly the use of YAML Front Matter data and the Liquid templating system).

Basic Directory Structure

As you may then know from its usage page, Jekyll expects a certain directory structure, so we will use the following hierarchy:

[astro@gorgnegre]% tree org-jekyll/
org-jekyll/
├── org/
│   ├── _config.yml
│   ├── _drafts/
│   │   ├── horse-riding+explore-the-cosmos.org
│   │   └── spend-some-holidays-at-gorgnegre.org
│   ├── files/
│   │   ├── css/
│   │   │   └── screen.css
│   │   └── images/
│   │       └── gorgnegre-logo.png
│   ├── _includes/
│   │   └── header.html
│   ├── index.org
│   ├── _layouts/
│   │   ├── base.html
│   │   └── post.html
│   └── _posts/
│       └── 2012-07-17-using-emacs-orgmode-to-blog-with-jekyll.org
├── org-jekyll.el
├── README.org
└── rootdir/

Things to note:

  1. Jekyll only requires the directory structure inside the org-jekyll/org/ subdirectory. By putting this structure inside org-jekyll/org/ we have all the sources of our website as one org project.
  2. Org will publish this org-jekyll/org/ project to org-jekyll/rootdir. In the process, all *.org files will be exported to .html; the rest will simply be copied over. We will then run jekyll on org-jekyll/rootdir, which will create a org-jekyll/rootdir/_site directory ready to be copied to our webserver.
  3. org-jekyll/org/_drafts will store our post drafts. When sufficiently polished, we will move them to org-jekyll/org/_posts. This workflow will be automatised. Note that _drafts files won't be processed by jekyll since _drafts is not a special jekyll directory (as opposed to _posts, which it is) and then jekyll ignores directory/file names starting with an underscore (or a dot).
  4. org-jekyll/org-jekyll.el is the file we will be discussing in the next section.

Org-mode Publishing Configuration

So next, let's tell org-mode how we want the export. By default org produces complete web pages. However, as Jekyll will parse files with YAML Front Matter data (which allows you to specify some predefined variables in your page) using the Liquid templating system (which allows you to create default templates to be used site wide), we are only interested in the section of the page between the <body> tags, as Jekyll produces the rest. Most things in org are configurable and it's possible to tell org to export only the bits of the page between the <body> tags. So we will open (or download) a org-jekyll.el configuration file, place it somewhere where emacs can read it at start-up, and add the following code (you could also put the code in your init.el file, if you prefer):

 1 ;;; Emacs org-mode support for blogging with Jekyll.
 2 ;;;
 3 ;;; To use, just put this file somewhere in your emacs load path and
 4 ;;; (require 'org-jekyll)
 5 ;;;
 6 ;;; An article showing its use can be found at:
 7 ;;;    - http://www.gorgnegre.com/linux/using-emacs-orgmode-to-blog-with-jekyll.html
 8 ;;;
 9 ;;; Adapted from
10 ;;;    - http://orgmode.org/worg/org-tutorials/org-jekyll.html
11 ;;;    - https://github.com/metajack/jekyll/blob/master/emacs/jekyll.el
12 ;;;
13 ;;; Gorg Negre 2012-07-05
14 
15 (provide 'org-jekyll)
16 
17 ;; Define our org project to be exported. Run "M-x org-export X mvm" to
18 ;; export.
19 (setq org-publish-project-alist
20       '(
21 
22    ("org-mvm"
23           :base-directory "~/org-jekyll/org/" ;; Path to your org files.
24           :base-extension "org"
25           :publishing-directory "~/org-jekyll/rootdir/" ;; Path to your Jekyll project.
26           :recursive t
27           :publishing-function org-publish-org-to-html
28           :headline-levels 6
29           :html-extension "html"
30           :body-only t ;; Only export section between &lt;body&gt; &lt;/body&gt; tags
31           :section-numbers nil
32           :table-of-contents nil
33 
34           :author "Your Name"
35           :email "user@example.cat"
36     )
37 
38     ("org-static-mvm"
39           :base-directory "~/org-jekyll/org/"
40           :base-extension "css\\|js\\|png\\|jpg\\|ico\\|gif\\|pdf\\|mp3\\|flac\\|ogg\\|swf\\|php\\|markdown\\|md\\|html\\|htm\\|sh\\|xml\\|gz\\|bz2\\|vcf\\|zip\\|txt\\|tex\\|otf\\|ttf\\|eot\\|rb\\|yml\\|htaccess\\|gitignore"
41           :publishing-directory "~/org-jekyll/rootdir/"
42           :recursive t
43           :publishing-function org-publish-attachment)
44 
45     ("mvm" :components ("org-mvm" "org-static-mvm"))
46 
47 ))

Things to note:

  1. We define a project called mvm (last line) which includes two publishing function components: one for the org files (org-mvm) and one for the non-org files (org-static-mvm). Then, to export our site we simply run M-x org-publish-project RET mvm RET from within Org-mode (shortcut: C-c C-e X m(vm)).
  2. In both org-mvm and org-static-mvm components we set the base directory where all our source files reside —in our case, ~/org-jekyll/org/ —; the publishing directory where org will export to — i.e., our jekyll project root directory ~/org-jekyll/rootdir/ —; we tell org to recursively parse the base directory. Target directories are created, if they don't yet exist.
  3. org-mvm uses org-publish-org-to-html to publish all files.org (:base-extension: "org") in its base-directory to its publishing directory. On the other hand, org-static-mvm uses org-publish-attachment to effectively copy all files with any of the extensions listed in :base-extension to the publishing directory. Be sure to set the right extensions of all your files. [With this current setup, .gitigngore, .htaccess, CNAME don't yet get published.]
  4. Change the options as needed (particularly the base and publishing directories, author and email fields).
  5. Org uses timestamps to track when a file has changed. By default, publishing functions normally only publish changed files. You can override this and force publishing of all files by giving a prefix argument to any publishing commands (i.e., C-u C-c C-e X mvm).

YAML Headers in Org Files

#yaml-headers

As we mentioned, Jekyll parses files with YAML headers and uses the variables therein specified through the templates held in the _layouts directory to produce the final static web site. If we want our org files to be parsed by jekyll, we need to include YAML headers in them. A YAML header might look like this:

---
layout: post
title: Blogging with Emacs Org-mode and Jekyll
excerpt: How to set up the combo emacs org-mode &amp; jekyll to publish a static website/blog.
categories:
  - linux
tags:
  - emacs
  - org-mode
  - jekyll
  - web
published: true
---

However, since we want this header to be seen as it is above by jekyll, we need to escape it from the org exportation to html. We do this by enclosing the fragment between a pair of #+BEGIN_HTML / #+END_HTML tags. So the first lines in our org file will now look like:

 #+BEGIN_HTML
 ---
 layout: post
 title: Blogging with Emacs Org-mode and Jekyll
 excerpt: How to set up the combo emacs org-mode &amp; jekyll to publish a static website/blog.
 categories:
  - linux
 tags:
  - emacs
  - org-mode
  - jekyll
  - web
 published: true
 ---
 ,#+END_HTML

We could decide we want some extra org-mode header tags before that lot (such as #+AUTHOR: or #+EMAIL:). That's ok and encouraged.

It seems that Jekyll is smart enough to admit files with YAML headers which are not placed really first thing on the file. If it finds a YAML header midway through the file, it takes it fine while it ignores any text above it.

As a side note, general Jekyll configuration is done throuh YAML tags in the _config.yml file.

Blogging with Emacs Org-mode and Jekyll

We now have all the required ingredients to have a pleasant experience working on our Jekyll website from within Emacs' Org-mode. We write org files in the ~/org-jekyll/org/ project directory, let Org-mode publish our project by running M-x org-export X mvm, and finally have Jekyll parse the files throuh the templates and build our static website under ~/org-jekyll/rootdir/_site.

However, Jekyll is blog aware and understands that the files under the _posts directory which are named yyyy-mm-dd-post-title.ext are blog posts. We can use this to our advantage, and one last bit of code appended to our previous org-jekyll.el file will make our blogging experience somewhat more comfortable:

 47 ;; Improve our blogging experience with Org-Jekyll. This code sets four
 48 ;; functions with corresponding key bindings:
 49 ;;
 50 ;; C-c j n - Create new draft
 51 ;; C-c j P - Post current draft
 52 ;; C-c j d - Show all drafts
 53 ;; C-c j p - Show all posts
 54 ;;
 55 ;; Once a draft has been posted (i.e., moved from the _drafts
 56 ;; directory to _post with the required date prefix in the filename), we
 57 ;; then need to html-export it to the jekyll rootdir (with org-publish).
 58 
 59  (global-set-key (kbd "C-c j n") 'jekyll-draft-post)
 60  (global-set-key (kbd "C-c j P") 'jekyll-publish-post)
 61  (global-set-key (kbd "C-c j p") (lambda ()
 62     (interactive)
 63     (find-file "~/org-jekyll/org/_posts/")))
 64  (global-set-key (kbd "C-c j d") (lambda ()
 65     (interactive)
 66     (find-file "~/org-jekyll/org/_drafts/")))
 67 
 68 (defvar jekyll-directory "~/org-jekyll/org/"
 69   "Path to Jekyll blog.")
 70 (defvar jekyll-drafts-dir "_drafts/"
 71   "Relative path to drafts directory.")
 72 (defvar jekyll-posts-dir "_posts/"
 73   "Relative path to posts directory.")
 74 (defvar jekyll-post-ext ".org"
 75   "File extension of Jekyll posts.")
 76 (defvar jekyll-post-template
 77   "#+STARTUP: showall\n#+STARTUP: hidestars\n#+OPTIONS: H:2 num:nil tags:nil toc:1 timestamps:t\n#+BEGIN_HTML\n---\nlayout: post\ntitle: %s\nexcerpt: \ncategories:\n  -  \ntags:\n  -  \npublished: false\n---\n#+END_HTML\n\n** "
 78   "Default template for Jekyll posts. %s will be replace by the post title.")
 79 
 80 (defun jekyll-make-slug (s)
 81   "Turn a string into a slug."
 82   (replace-regexp-in-string
 83    " " "-" (downcase
 84             (replace-regexp-in-string
 85              "[^A-Za-z0-9 ]" "" s))))
 86 
 87 (defun jekyll-yaml-escape (s)
 88   "Escape a string for YAML."
 89   (if (or (string-match ":" s)
 90           (string-match "\"" s))
 91       (concat "\"" (replace-regexp-in-string "\"" "\\\\\"" s) "\"")
 92     s))
 93 
 94 (defun jekyll-draft-post (title)
 95   "Create a new Jekyll blog post."
 96   (interactive "sPost Title: ")
 97   (let ((draft-file (concat jekyll-directory jekyll-drafts-dir
 98                             (jekyll-make-slug title)
 99                             jekyll-post-ext)))
100     (if (file-exists-p draft-file)
101         (find-file draft-file)
102       (find-file draft-file)
103       (insert (format jekyll-post-template (jekyll-yaml-escape title))))))
104 
105 (defun jekyll-publish-post ()
106   "Move a draft post to the posts directory, and rename it so that it
107 contains the date."
108   (interactive)
109   (cond
110    ((not (equal
111           (file-name-directory (buffer-file-name (current-buffer)))
112           (concat jekyll-directory jekyll-drafts-dir)))
113     (message "This is not a draft post.")
114     (insert (file-name-directory (buffer-file-name (current-buffer))) "\n"
115             (concat jekyll-directory jekyll-drafts-dir)))
116    ((buffer-modified-p)
117     (message "Can't publish post; buffer has modifications."))
118    (t
119     (let ((filename
120            (concat jekyll-directory jekyll-posts-dir
121                    (format-time-string "%Y-%m-%d-")
122                    (file-name-nondirectory
123                     (buffer-file-name (current-buffer)))))
124           (old-point (point)))
125       (rename-file (buffer-file-name (current-buffer))
126                    filename)
127       (kill-buffer nil)
128       (find-file filename)
129       (set-window-point (selected-window) old-point)))))

Things to note:

  1. Read the comments at the top. Basically this code sets four functions with corresponding key bindings:
    C-c j n
    create new draft (i.e., a new file in the _drafts folder);
    C-c j P
    post current draft (i.e., move file from the _drafts folder to the _posts folder changing the file name to include the correct date prefix as required by jekyll);
    C-c j d
    list all drafts (files in the _drafts directory);
    C-c j p
    list all posts (files in the _posts directory).
  2. Be sure to change the paths to suit your needs (search for the string "org-jekyll" to find the paths in the code).
  3. Customize the jekyll-post-template variable to suit your needs.

This code could be further hacked so that after the post title, the mode-line also asks you about the post excerpt, categories and tags, for instance.

Links to Posts

To link to another post in my site while writing in org-mode, I use the following syntax:

[[file:{% post_url 2007-12-15-utthita-trikonasana %}][Utthita Trikonasana]]

Improvements

  • Include Reyero's hack which I have so far left out.
  • Tell Ian Barton about typos in his jekyll-org.el code.
  • Hack org-static-mvm so that .gitigngore, .htaccess, CNAME also get published. (Links). Wouldn't it be convenient to have a ":base-extension: any-except-.org" thing?
  • Hack jekyll-draft-post so that after the post title, the mode-line also asks about the post excerpt, categories and tags.

Including a Table of Contents

As it happens, org-export ignores creating a table of contents when only the body is exported (as is the case for our org-mvm publishing component with its :body-only t setting) due to the fact that the prospected exporter to atom uses body only to create html markup for atom feed entries. In a discussion on Org-mode's mailing list, David Maus proposes to create a body without toc when body-only is set to t and create a body with toc when body-only is the symbol 'include-toc. In the meanwhile, Jan Böcker provides a one-line patch to change this baddish behaviour:

---
 lisp/org-html.el |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/lisp/org-html.el b/lisp/org-html.el
index 74f3a55..ab1aac2 100644
--- a/lisp/org-html.el
+++ b/lisp/org-html.el
@@ -807,7 +807,7 @@ lang=\"%s\" xml:lang=\"%s\"&gt;
      (if title (insert (format org-export-html-title-format
                    (org-html-expand title))))))

-      (if (and org-export-with-toc (not body-only))
+      (if org-export-with-toc
      (progn
        (push (format "&lt;h%d&gt;%s&lt;/h%d&gt;\n"
              org-export-html-toplevel-hlevel
-- 
1.7.0.3

Possibly, a better solution can be achieved with a Javascript snipped such as the jQuery table of contents plugin. Check out Clapper's blog for an implementation.

Summary

We have seen how to configure Emacs Org-mode so that we can easily maintain a static website or blog with Jekyll. To ease the deployment, we complement this article with an accompanying github repository —with the directory structure, required configuration files and instructions. Feel free to clone it.

With this setup, the workflow to post a blog or article can be summarised as:

  1. Go to the rootdir and launch jekyll --server --auto. (With these options, jekyll runs a webserver on http://localhost:4000, and keeps track of changing files under rootdir.)
  2. Launch emacs:
    1. C-c j d to open new draft.
    2. Write draft.
    3. C-c j p When ready, move draft to post.
    4. C-c C-e X mvm Publish project.
  3. Copy org-jekyll/rootdir/_site to live webserver.



For referencing: permalink to this article.
comments powered by Disqus