Writing Emacs-Lisp, or elisp


In the much-vaunted style of literate programming, here’s an emacs-lisp function I wrote recently.

The motivation was to convert a shell alias I added this morning, which counts the number of Zettelkasten notes I’ve written that day. (“Jesus, him, too?” Yes, dear reader, I’m afraid that this is one trend I haven’t managed to dodge forever, but my adoption has been cautious and deliberate.)

alias today_count="fd $(date -u +"%Y-%m-%d") ~/org/zd | wc -l"

Using the lovely fd utility, I find all files in my Zetteldeft directory, which start with today’s date. Then, I pipe that output to wc -l to get the count. Easy peasy, chicken breezy; took me about two minutes to write. Turns out this doesn’t work as an alias; the date isn’t recalculated every time the alias is called.

But, in the spirit of staying focused inside of one tool (where I’m already writing my notes), I wanted to convert the alias to an emacs-lisp function I could call from anywhere in Emacs. And in the spirit of learning in public, here’s an annotated walk-through, sharing the lessons I learned the hard way.

We start with importing a sequence library to use later for filtering entries.

(require 'seq)

Next, we declare our function, with no variables. It’s not a stretch, though, to imagine specifying which date (or dates!) I would want a count from.

(defun zd/today-count ()

After that, gasp, a document string, which the elisp linter was very insistent on me adding. Does the culture influence the tooling, or does the tooling influence the culture?

"Return the number of notes created today. Depends on notes saved with the format YEAR-MONTH-DAY."

Then, we use the interactive special form. This is necessary for the requirement to be able to call the function from anywhere using M-x.


Next up, there’s a function for returning today’s date in a string, formatted in such a way as to match up with how our filenames are constructed. This started out as a variable, but it turns out that defvar will assign a value to a variable only once. I suspect there’s a more semantically-correct way to do this, but this is what we have for now.

(defun get-today ()
  (format-time-string "%Y-%m-%d"))

Here, I’m making use of that seq library that was imported at the very start. An anonymous function takes each filename (as a string), and returns a non-nil value if the filename matches a regular expression built from today’s date.

(defun get-today-files (day)
    (lambda (f) (string-match-p (regexp-quote day) f))
    (directory-files deft-directory)))

Lastly, in a convoluted line, we print a string to the Messages buffer, which includes the date and number of notes written so far!

(message "It's %s; you've written %d notes so far." (get-today) (length (get-today-files (get-today)))))

And to be able to use this from anywhere in Emacs, I run (load "~/.doom.d/zd") in my Doom Emacs config.el (though there may be a better way to do this). That zd file contains my function, and ends with (provide 'zd).

There it is — the thing works, and even on different days. I look forward to revisiting my approach in six months, chortling at my naivete, and improving my code and process.