Automatically incrementing JD numbers in filenames
Recently I have really gotten into literate programming after reading about it online and in Donald Knuth’s paper1, which I recommend giving a look. It has provided some much needed motivation around mundane tasks while also serving to document my scripts at work.
I store all my literate programs using a folder structure called
Johnny Decimal where each file is numbered according to its parent
folders. Here’s an example: 16.01 coolCode.org
. This is only a part
of the JD system, you can read more about it following the link. The
problem I faced was one of repetition. Each time I wanted to write a
new literate program I had to manually create the file while making
sure to increment the JD number. Repetition gets tedious and I am
using Emacs with org mode to do my text editing after all. To avoid
the tedium I decided to create a set of functions ending in a capture
template that will prompt for a title and create the filename with the
next JD number automatically. This article will explain my process.
Incrementing the JD number
The first task I set out to solve was to automatically get all the JD
numbers in a directory for a given file type so that I could increment
it. I wanted to be able to specify the file type as literate programs
will always be .org
files and it is likely that there will be other
file types with the same filename in the directory.
Since I am not at all proficient in Emacs Lisp I relied heavily on the
built-in documentation (C-h o
) and searching online.
(defun my/get-next-jd-number-in-jd-dir (dir filetype)
"Returns the next JD number for a given filetype in a JD directory."
(setq-local jd-number
(car
(last
(mapcar (lambda (f)
(substring f 0 5))
(directory-files dir nil
(concat "\." filetype "$"))))))
(concat
(substring jd-number 0 3)
(format "%02d" (1+ (string-to-number
(substring jd-number 3 nil))))))
Going through the function
First I set a local variable using setq-local
to avoid polluting the
global space. I am unsure whether this is considered best practice in
Emacs Lisp, but it works so I am fine with it. mapcar
takes a list and
applies some function to it before returning the modified list. Here I
am using it to call (substring f 0 5
) on the outputs of
directory-files
which lists the contents of a given directory. In this
case I am interested in .org
files as I am leveraging org mode for my
literary programming, but the function could be used for any other
file type.
Getting the last element of the list
After some searching I found the function last
which returns the last
link of a list, meaning you will get a list containing exactly one
element. In the docstring for last
, as you can see below, it is stated
that the car
of the return value is the last element. So I figured I
would wrap the call to last
in car
, which worked just fine.
Return the last link of LIST. Its car is the last element.
A note on the use of substring
The use of substring
in this code is really a consequence of my
limited knowledge of Emacs Lisp and not necessarily the best way to do
it. In the case of a default JD system we know that the number we are
after will be on index 0 through 5, so the hard coded version will
work. However, I would very much appreciate feedback or suggestions
for improving the function.
Checking the results against the directory
Calling the function my/get-next-jd-number-in-jd-dir
returns the
number 16.06
, but I am not sure it is correct. After all it is just a
number, there are no file names and I can’t be bothered to manually
check the directory. So I’ll include this boon of literate
programming, free of charge!
Org mode in Emacs is great because it lets me evaluate source code
blocks in the prose. Here I wrote a quick little script to return to
me the contents of my literate programming directory, automatically,
just by placing my cursor in the code block and entering the following
keyboard shortcut C-c C-c
.
(mapcar (lambda (f)
(format "%s\n" f))
(directory-files "~/Documents/JD/10-19 Data Collection/16 Literate Programming/" nil "\.org$"))
- 16.01 coolCode.org
- 16.02 uplinkingTheDownload.org
- 16.03 mainframePhasingAndHowItsDone.org
- 16.04 elementSubjectToChange.org
- 16.05 genericEventsAsContentInteractions.org
As you can see the function did indeed return the incremented number. Great success, it will save me a lot of typing.
Creating the file path and capturing to it
The last step was to find out how to create a capture template that would prompt me for the title, set the title in the org file, and save the file with a camel cased version of the title. I had to scour the worldwide web for answers before I finally came upon a solution that worked. A global variable dedicated to hold the title string entered in the prompt.
Getting the file path
The following function utilizes my/get-next-jd-number-in-jd-dir
to
return the incremented JD number and concatenates the result with a
camel cased version of the prompted title.
(defun my/jd-prompt-for-new-lit-filename--capture ()
(setq jd--lit-title (read-string "Title: "))
(setq-local jd--lit-dir "~/Documents/JD/10-19 Data Collection/16 Literate Programming/"
jd--lit-filename (expand-file-name
(concat (my/get-next-jd-number-in-jd-dir jd--lit-dir "org")
" "
(s-lower-camel-case jd--lit-title)
".org") jd--lit-dir)))
Here are the results of the previous code block. As you can see from
the second line I entered the string This is a Test Title
and from the
first line you can see the incremented, camel cased result.
/Users/eanilsen/Documents/JD/10-19 Data Collection/16 Literate Programming/16.06 thisIsATestTitle.org
This is a Test Title
Defining the capture template
Here I used the variable, which I named jd--lit-title
to be sure it would
not interfere with any other piece of code, and as you will see below I
used the %(sexp)
syntax to include it in the capture template.
(setq org-capture-templates
'(("l" "Literate Program" plain
(file my/jd-prompt-for-new-lit-filename--capture)
"%(format \"#+TITLE: %s\" jd--lit-title)\n\n%?"
:jump-to-captured t)))
Conclusion
I hope this little snippet can assist others in their quest to improve their workflow and also help me by sharing my solution so that others can make it better.
References
-
Knuth, D. E. (1984). Literate Programming. The Computer Journal, _27_(2), 97–111. https://doi.org/10.1093/comjnl/27.2.97 ↩︎