On Literate Configs
Literate Emacs Configurations
I'd like to present some thoughts on literate configurations. This is one of those interesting bits in the Emacs configuration space that shows up from time to time. I'm in the “I don't do it this way” camp and I'll present some thoughts on why that is. For those of you who choose to be in the “I do it!” camp, well, that's fantastic! The great thing about Emacs is doing it “your” way, whatever “way” that is.
What is it anyway?
Donald Knuth wrote, “Literate programming” is a methodology that combines a programming language with a documentation language, thereby making programs more robust, more portable, more easily maintained, and arguably more fun to write than programs that are written only in a high-level language. The main idea is to treat a program as a piece of literature, addressed to human beings rather than to a computer. The program is also viewed as a hypertext document, rather like the World Wide Web.”1
In the sense of your Emacs configuration, it is basically writing everything in some markup language with executable code snippets woven through the prose documenting your decisions. The most common markup language in this context is the venerable Org mode. It is well supported, has a huge list of language integrations, including Emacs Lisp (of course), and is arguably one of the “killer features” available to Emacs users. It's also a very simple markup language with very little in the way of actual syntax and it exports to many different presentation formats. Forges like GitHub and GitLab will render Org files natively in the same way they render Markdown files. This make it very useful for influencers like Protesilaos Stavrou2 (also known as “Prot”) among others to host their Emacs configuration where people can read them, get some context on why or how the configuration works, and still see the code. David Wilson in a recent livestream3 converted his configuration back to a literate style and mentioned some of the advantages being:
- Having things nicely organized in a single large Org file.
- Easy to generate a website from the configuration.
- Easy to describe why you do certain things in your configuration.
Why its a challenge
In the same video, David goes on to talk about why he stopped using it for a while:
- He got tired of having to re-tangle his configuration files every time he synced his dot files between machines.
- If you tangle (ie. generate) all the code files in your “dot files” folder and then symlink those to your home folder, you either have to ignore all the generated files or check them in with your Org file, which means lots of diffs when you re-tangle your Org file after each change.
- There is the possibility you might change one of the generated files and forget to update the Org file, which would then overwrite your changes when you re-tangle your configuration.
Another challenge, it does take time to tangle your configuration, especially if yours is on the longish side. Do that every time you save your configuration and Emacs will seem to lag a few seconds as it tangles things.
Why I'm a “don't” person
Keeping in mind, these are my opinions, here are some reasons why I prefer not to use a literate configuration:
- It's a hassle. Keeping things synced, both on the filesystem as well as in version control is just more than I prefer to manage.
- It's easier to not use a literate configuration. No extra configuration, no slow down tangling things, no extra files to either ignore or version frequently, etc.
- Documenting things can be done just as easily with code comments.
- Organizing your configuration is a matter of discipline not markup. Using Org mode does not improve how well organized your code is if you don't discipline yourself to keep things organized. It's just extra syntax. The same is true of keeping your configuration in “module” files. Its only more organized if you maintain the discipline of keeping it organized.
- I don't need to generate a website from my configuration. Anyone who wants to read it can do so on GitLab, which will render the Emacs Lisp code and comments just fine.
Outline modes – outline-minor-mode
and allout-mode
There was a suggestion on David's stream regarding using allout-mode
, one of the outline modes built-in to Emacs. I've used this mode in the past, but I usually end up back in Org mode because of all the features that project brings to the table.
allout-mode
is a fairly simple outliner though and pretty easy to get into if you need a nice outlining tool. And by simple I mean, really simple. No frills. It outlines and that's about it. The default keyboard prefix is C-c SPACE
which can be a bit to type, but once you get used to it, its not bad. That said, changing it to something like s-SPACE
might make it more approachable.
Using the built-in outline modes (outline-minor-mode
or allout-mode
) seem to be a nice alternative. In fact, reading through the documentation on Comment Tips4, using three ;;;
should start on the left column and are already considered headers in Outline mode, thus using them and turning on outline-minor-mode
and you instantly get navigation keyboarding, the ability to collapse sections, etc. and you can document your code in the same way you would with Org mode using only the Emacs Lisp syntax, and no tangling required.
allout-mode
adds additional flavor to those ;;;
comments, so you only write ;;;
plus the header level needed and you also get code navigation keyboarding, the ability to collapse sections, etc and you still only document your code with Emacs Lisp syntax and no tangling required. allout-mode
also should allow generating your configuration as LaTeX, but there is a bug in my case which I'll try tracking down at some point. I don't need it as I don't need to generate my configuration in any other language than the default Emacs Lisp.
Getting started with allout-mode
I'll show an example here of how to get started using allout-mode
. Using outline-minor-mode
doesn't require anything, per se, but you might like to use something like this to turn it on in your configuration:
M-x add-file-local-variable-prop-line RET eval RET (outline-minor-mode 1) RET
This would assume you are just using multiple semi-colon's as described in the Emacs Lisp Comment Tips4. To use allout-mode
requires just a little more setup and then some updates to those lines beginning with ;;;
.
So, lets start with your init.el
file.
M-x add-file-local-variable-prop-line RET mode RET emacs-lisp RET
M-x add-file-local-variable-prop-line RET eval RET (allout-mode 1) RET
M-x add-file-local-variable RET eval RET (allout-expose-topic '(0 :)) RET
M-x revert-buffer RET
The list item in that list sets the variables and executes the code, you'll get the standard Emacs warning about this, so you can choose to say 'y' to run it once. Other options exist to persist that decision as well. That's all you need to get things started. You should also customize the allout-auto-activation
variable and set it to t
, at least you should according to the various docstrings in allout.el
.
To proceed from there, you add ;;;_*
to major headings. A sub-heading can be added with ;;;_ *
(Note the extra space). See the allout.el
source to see a more detailed example. In my case, I generally use just these, with a couple of exceptions.
allout-mode
allows you to set your bullets to something that means something semantically. For example ;;;_ ?
might indicate a question (like, “do I need this section?”), ;;;_ ^
might indicate this section refers to the section above, for example if you are setting up IDE rules, you might have generic IDE configuration first, then sub-topics for each programming language:
;;;_* IDE
;;; This section configures eglot as my language server
(require 'crafted-ide-config) ; Crafted Emacs module
(with-eval-after-load 'crafted-ide-config
(crafted-ide-configure-tree-sitter '(latex python)))
;;;_ ^ Prog mode hooks
(with-eval-after-load "prog-mode"
(keymap-set prog-mode-map "C-c e n" #'flymake-goto-next-error)
(keymap-set prog-mode-map "C-c e p" #'flymake-goto-prev-error))
;;;_ ^ Python
(require 'my-ide-python) ; My custom Python module
Example init.el file
To complete this blog post, here is the Crafted Emacs example init.el
5 using allout-mode
. To complete the requirement as mentioned by Mr. Knuth1, you should be able to export this to LaTeX, although, for me there seems to be a bug.
;;; init.el --- Crafted Emacs Base Example -*- mode: emacs-lisp; lexical-binding: t; eval: (allout-mode 1); -*-
;; Copyright (C) 2023
;; SPDX-License-Identifier: MIT
;; Author: System Crafters Community
;;; Commentary:
;; Base example init.el (extended from the info file).
;; Basic example of loading a module.
;;; Code:
;;;_* Initial phase
;;;_ ^ Custom File
;;; Load the custom file if it exists. Among other settings, this will
;;; have the list `package-selected-packages', so we need to load that
;;; before adding more packages. The value of the `custom-file'
;;; variable must be set appropriately, by default the value is nil.
;;; This can be done here, or in the early-init.el file.
(setq custom-file (expand-file-name "custom.el" user-emacs-directory))
(when (and custom-file
(file-exists-p custom-file))
(load custom-file nil :nomessage))
;;;_ ^ Bootstrap crafted-emacs
;;; Bootstrap crafted-emacs in init.el
;;; Adds crafted-emacs modules to the `load-path', sets up a module
;;; writing template, sets the `crafted-emacs-home' variable.
(load (expand-file-name "../../modules/crafted-init-config"
user-emacs-directory))
;;; Adjust the path (e.g. to an absolute one)
;;; depending where you cloned Crafted Emacs.
;;; (load "/path/to/crafted-emacs/modules/crafted-init-config")
;;;_* Packages phase
;;; Collect list of packages to install. Do not just blindly copy this
;;; list, instead think about what you need and see if there is a
;;; module which provides the list of packages needed. This phase is
;;; not needed if manage the installed packages with Guix or Nix. It
;;; is also not needed if you do not need Crafted Emacs to install
;;; packages for a module, for example,
;;; `crafted-speedbar-config' does not require any packages to
;;; be installed.
;;; Add package definitions for completion packages
;;; to `package-selected-packages'.
(require 'crafted-completion-packages)
;;;_ ^ Manually select "ef-themes" package
(add-to-list 'package-selected-packages 'ef-themes)
;;;_ ^ Install the packages listed in the `package-selected-packages' list.
(package-install-selected-packages :noconfirm)
;;;_* Configuration phase
;;; Some example modules to configure Emacs. Don't blindly copy these,
;;; they are here for example purposes. Find the modules which work
;;; for you.
;;;_ ^ Completions
;;; Load configuration for the completion module
(require 'crafted-completion-config)
;;;_ ^ Crafted Emacs configuration
;;; Some more configurations that don't require packages to be installed
(require 'crafted-defaults-config)
(require 'crafted-startup-config)
;;;_* Optional configuration
;;;_ ^ Profile emacs startup
(defun ce-base-example/display-startup-time ()
"Display the startup time after Emacs is fully initialized."
(message "Crafted Emacs loaded in %s."
(emacs-init-time)))
(add-hook 'emacs-startup-hook #'ce-base-example/display-startup-time)
;;;_ ^ Set default coding system (especially for Windows)
(set-default-coding-systems 'utf-8)
(provide 'init)
;;;_* Local emacs vars.
;;Local variables:
;;allout-layout: (0 :)
;;eval: (allout-expose-topic allout-layout)
;;End:
;;; init.el ends here
Final thoughts
I'm generally not a fan of literate configuration. The idea isn't a bad one, it's just not for me. I don't see the point in writing in two different languages for this particular use-case, and there are simpler ways to get the same effect. I do think literate programming has it's place, for example, see this video from a recent meetup on the subject. I encourage you to try it both ways though, and to find the one which makes sense to you in your configuration.
Happy Crafting!
Footnotes
1 https://www-cs-faculty.stanford.edu/~knuth/lp.html
3 https://www.youtube.com/live/Ex9zI4Fcirs?si=Izqdqu88BSmsI-c6
4 https://www.gnu.org/software/emacs/manual/html_node/elisp/Comment-Tips.html
5 Yep, shameless plug. https://github.com/SystemCrafters/crafted-emacs/blob/master/examples/base/init.el