TL;DR The following script will export a given org file to HTML, using emacsclient if an Emacs daemon is running and fall back to using Emacs if no Emacs daemon is running. For more options including a one-liner, as alias or usage in GNU make keep on reading.

#!/bin/sh
if emacsclient -a false -e 't'; then
  emacsclient \
    -e '(progn (find-file "$1") (org-html-export-to-html))'
else
  emacs --batch \
    -l ~/.emacs.d/init.el \
    -f org-html-export-to-html \
    --kill "$1"
fi

At 200ok we use Org mode a lot. And by "a lot", I mean basically for everything from writing (documentation, concepts, quotes, and blog posts like this one), to organizing work (budgeting, capacity planning, time tracking, ticketing, meeting minutes), even our ledgers are generated in org. Some of our processes are highly automated, and we built quite some tooling to facilitate automation. Most of it is not fit for open-sourcing, but you might have come across organice (a FLOSS implementation of Org mode without the dependency of Emacs, built for mobile and desktop browsers) or ukko (a versatile static site generator), which uses the pattern described in this article to render HTML pages from org sources.

Naturally, when sharing documents, like meeting minutes, quotes, and invoices with our business partners, using org internally boils down to exporting to HTML or PDF. From within Emacs' org-mode, this is done with C-c C-e h h or C-c C-e l p, respectively. When we want to automate this, we have to be able to trigger the export from the command line. Using HTML as an example, this can be done with the following command:

emacs --batch \
  --load ~/.emacs.d/init.el \
  --funcall org-html-export-to-html \
  --kill "${ORG_FILE}"

Let's break the command down. --batch will run Emacs in batch mode; this means we have to provide the file to load with --load, as well as the function to call with --funcall. We will load the user's standard Emacs configuration to ensure we have the same setup as if we exported interactively. In theory, the file loaded could be limited to the required packages and configuration to save loading time, but this optimization pales in comparison to the time saver I will soon lay out. --kill will ensure that Emacs exits after the call to our function has returned.

This command takes some time, as it not only will have to start Emacs but also load the users config, which might load a lot of packages. The actual export is rather quick, even for larger org files. The noticeable wait stems from starting Emacs and loading packages. Therefore Emacs users who close and open Emacs regularly usually have Emacs daemon in place. An instance of Emacs that runs as a daemon, also known as Emacs server. Starting an Emacs daemon is as easy as running emacs --daemon. With Emacs running as daemon emacsclient is used to connect. Emacsclient doesn't provide the same "batch" functionality. But we can still use it to export org files through Emacs daemon. We'll simply need to pass it some elisp code to do the same.

emacsclient \
  --eval "(progn (find-file \"${ORG_FILE}\") (org-html-export-to-html))"

With --eval we can pass elisp code to the running instance of Emacs daemon. progn is an elisp special form that allows us to put multiple function calls in a sequence. find-file will find and, more importantly, load the given file into a buffer. Finally org-html-export-to-html will export the file to HTML. This is much faster as it happens in the daemon, an already running instance of Emacs.

Now you'll want to ensure that your automation always works (because what good are automations if they fail). If you run the command on your machine, it might be safe to assume you have an Emacs daemon running. But there are plenty of good reasons or environments which don't have an Emacs daemon running. Think of continuous integration systems or other developer's systems. This means we should check if an Emacs daemon is available and resort to just using Emacs otherwise. Fortunately, checking is easy:

emacsclient --alternate-editor false --eval 't'

This will run emacsclient with the option --alternate-editor which allows us to define an executable that should be run unless an Emacs daemon is found to connect to. We specify false as alternate editor because in order to check if Emacs daemon is running, we don't want another editor to open, instead we want the command to fail, i.e. return a non-zero exit code, which false does. --eval 't' will just evaluate to true, which essential is a noop, which will make emacsclient return with a zero exit code, meaning success. This can conveniently be used as a condition to if. Hence the final script will look like this:

#!/bin/sh
if emacsclient --alternate-editor false --eval 't'; then
  emacsclient \
    --eval "(progn (find-file \"$1\") (org-html-export-to-html))"
else
  emacs --batch \
    --load ~/.emacs.d/init.el \
    --funcall org-html-export-to-html \
    --kill "$1"
fi

So far so good. This solution follows the principle of ceteris paribus. This means the file will be exported and everything else is unchanged, including the fact that Emacs daemon was either running or not. If you are willing to sacrifice the principle of ceteris paribus the solution can even be reduced to:

#!/bin/sh
emacsclient --alternate-editor "" \
  --eval "(progn (find-file \"$1\") (org-html-export-to-html))"

From the manpage of emacs client:

-a, –alternate-editor=EDITOR

if the Emacs server is not running, run the specified editor instead. This can also be specified via the 'ALTERNATE\EDITOR' environment variable. If the value of EDITOR is the empty string, then Emacs is started in daemon mode and emacsclient will try to connect to it.

This will start Emacs daemon if it is not running, which not only makes the check for a running Emacs daemon unnescessary it will also automatically speed up consecutive exports.

Additionally, this is so short, instead of a shell script this could be an alias when wrapped in an immediate function.

alias org2html='f() { emacsclient -a "" -e "(progn (find-file \"$1\") (org-html-export-to-html))" };f'

As a bonus for the reader who made it to this point. Here is the snippet of how we use the pattern above in our Makefiles. "Wait, what?" you say. Why Makefiles? Much like org, we are using GNU make a lot. And by "a lot", I mean basically for everything – you see where this is going. I cannot stress enough how useful GNU make is! It's exactly like the late Joe Armstrong said:

Make is the only build tool you'll ever need to learn. […] I cannot understand why people use specialized build tools for their different languages. […] Once you've learned how make works, you use it for everything, and then you don't need to learn a new build tool when you change your programming language. It's very easy to learn as well.

(Word of warning when copying this code: GNU make is very picky about tabs and space, and those are easy to mess up when copying and pasting code from a website.)

F?=default.org

.PHONY: export
export: ## Exports a given org file F to HTML
      if emacsclient -a false -e 't'; then \
	emacsclient -e '(progn (find-file "$(F)") (org-html-export-to-html))'; \
      else \
	emacs --batch -l ~/.emacs.d/init.el $(F) -f org-html-export-to-html --kill; \
      fi

.PHONY: watch
watch: ## watches subdirectories and exports and syncs on change
      filewatcher -l *.org 'make export F=$$FILENAME; make sync'

Having this in your Makefile will export default.org to a HTML file with make export. This can be applied to any other org file by providing its path as F to make.

make export F=path/to/your.org

Additionally, the make target watch uses Filewatcher CLI to observe changes to org files in any subdirectories and run make export and make sync for a fully automated deployment process on save.


If you liked this post, please consider supporting our Free and Open Source software work – you can sponsor us on Github and Patreon or star our FLOSS repositories.