Blogging with Emacs Org-mode and Jekyll
Published • 17 Jul 2012
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:
- The excellent Ian Barton's Org-Jekyll site.
- The excellent Juan Reyero's Org-Jekyll site. [Not yet implemented here.]
- The convenient Metajack's Emacs-Jekyll hack.
- 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:
- Jekyll only requires the directory structure inside the
org-jekyll/org/subdirectory. By putting this structure insideorg-jekyll/org/we have all the sources of our website as one org project. - Org will publish this
org-jekyll/org/project toorg-jekyll/rootdir. In the process, all*.orgfiles will be exported to.html; the rest will simply be copied over. We will then run jekyll onorg-jekyll/rootdir, which will create aorg-jekyll/rootdir/_sitedirectory ready to be copied to our webserver. org-jekyll/org/_draftswill store our post drafts. When sufficiently polished, we will move them toorg-jekyll/org/_posts. This workflow will be automatised. Note that_draftsfiles won't be processed by jekyll since_draftsis 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).org-jekyll/org-jekyll.elis 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 <body> </body> 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:
- 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 runM-x org-publish-project RET mvm RETfrom within Org-mode (shortcut:C-c C-e X m(vm)). - In both
org-mvmandorg-static-mvmcomponents 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. org-mvmusesorg-publish-org-to-htmlto publish allfiles.org(:base-extension: "org") in its base-directory to its publishing directory. On the other hand,org-static-mvmusesorg-publish-attachmentto effectively copy all files with any of the extensions listed in:base-extensionto the publishing directory. Be sure to set the right extensions of all your files. [With this current setup,.gitigngore,.htaccess,CNAMEdon't yet get published.]- Change the options as needed (particularly the base and publishing directories, author and email fields).
- 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 & 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 & 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:
- 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
_draftsfolder); C-c j P- post current draft (i.e., move file from the
_draftsfolder to the_postsfolder changing the file name to include the correct date prefix as required by jekyll); C-c j d- list all drafts (files in the
_draftsdirectory); C-c j p- list all posts (files in the
_postsdirectory).
- Be sure to change the paths to suit your needs (search for the string "org-jekyll" to find the paths in the code).
- Customize the
jekyll-post-templatevariable 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.elcode. - Hack
org-static-mvmso that.gitigngore,.htaccess,CNAMEalso get published. (Links). Wouldn't it be convenient to have a ":base-extension: any-except-.org" thing? - Hack
jekyll-draft-postso 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\"> (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 "<h%d>%s</h%d>\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:
- Go to the
rootdirand launchjekyll --server --auto. (With these options, jekyll runs a webserver on http://localhost:4000, and keeps track of changing files underrootdir.) - Launch
emacs:C-c j dto open new draft.- Write draft.
C-c j pWhen ready, move draft to post.C-c C-e X mvmPublish project.
- Copy
org-jekyll/rootdir/_siteto live webserver.
/
