Efficient Emacs .org ➞ .el tangling

Many people now use org-mode for their config. So they usually have a very small ~/.emacs.d/init.el which more or less just load the elisp source code blocks of some org-file, e.g. with (org-babel-load-file "~/.emacs.d/config.org"). The org-mode file is used to write and document the sections in one go.

That works, but it is slow. Somehow the org-babel-load-file function opens and close the destination file for every source code block. If you still have a rotating hard disk, you can actually hear this.

I present something that is much faster. And more flexible.

BTW, if you don’t have any clue of what I mean, Sache Chua emacs configuration is a well-known example.

Faster

I make a faster version that searches the source-code blocks and appends them to some list structure. Only at the end will this list be written to the specified file.

That’s much faster.

More flexible

With the original approach, you need to annotate every source code block with :tangle no if you want to disable this. But this won’t show up in the org-mode hierarchy. Remember, org-mode has todo-states like TODO, WAIT, DONE. I added a CANCELLED state. And inside my org-document, any source code block that is in an entry with such a tudo state will simple be ignored.

I selected CANCELLED as my method to disable exports.

Implementation

(defun my-tangle-config-org ()
  "This function will write all source blocks from =config.org= into
=config.el= that are ...

- not marked as =tangle: no=
- doesn't have the TODO state =CANCELLED=
- have a source-code of =emacs-lisp="
  (require 'org)
  (let* ((body-list ())
         (output-file "config.el")
         (org-babel-default-header-args (org-babel-merge-params org-babel-default-header-args
                                                                (list (cons :tangle output-file)))))
    (message "Writing %s ..." output-file)
    (save-restriction
      (save-excursion
        (org-babel-map-src-blocks "config.org"
                                  (let* ((info (org-babel-get-src-block-info 'light))
                                         (tfile (cdr (assq :tangle (nth 2 info))))
                                         (match))
                                    (save-excursion
                                      (catch 'exit
                                        (org-back-to-heading t)
                                        (when (looking-at org-outline-regexp)
                                          (goto-char (1- (match-end 0))))
                                        (when (looking-at (concat " +" org-todo-regexp "\\( +\\|[ \t]*$\\)"))
                                          (setq match (match-string 1)))))
                                    (unless (or (string= "no" tfile)
                                                (string= "CANCELED" match)
                                                (not (string= "emacs-lisp" lang)))
                                      (add-to-list 'body-list body)))))
      (with-temp-file output-file
        (insert ";; Don't edit this file, edit config.org' instead ...\n\n")
        (insert (apply 'concat (reverse body-list))))
      (message "Wrote %s ..." output-file))))

See also

You can see more of this in my public dotemacs repository.