↓ Skip to Content
Start of content

Creating animated gifs using emacs-director

My last post about sorting lines had a couple of animated gifs to show how the feature worked. They were created using a combination of emacs-director, asciinema, and asciicast2gif. Here's how I made them.

The process

Creating a gif using this setup works like this:

  1. Create an elisp file containing instructions for emacs-director to run.
  2. Run emacs with this file and the emacs-director bootstrap file.
  3. Capture the output with asciinema.
  4. Convert the asciinema file to an animated gif using asciicast2gif.

I created a small Makefile to handle these steps, so all I need to do is write the instruction file and then use make to create the actual gif.

Let's create a really simple gif that says "Hello, World!".

Step 1 - Creating the instruction file

This is divided into two parts: a director-bootstrap command that tells emacs-director some basic information about the environment, and the director-run section that contains a full list of instructions.

For the bootstrap we can either use an existing user directory - useful if things need to be setup a certain way - or use the tmp directory.

The fun part is all contained in director-run, which tells Emacs exactly which steps to follow. Let's say hello!

(director-bootstrap
 :user-dir "/tmp/director-demo")

(director-run
 :version 1
 :before-start (lambda ()
		 (switch-to-buffer (get-buffer-create "*say-hello*"))
		 (menu-bar-mode -1))
 :steps '((:type "Hello, World!"))
 :typing-style 'human
 :delay-between-steps 1
 :after-end (lambda () (kill-emacs 0))
 :on-error  (lambda () (kill-emacs 1)))

The :before-start section can be used to set up buffers, switch the major mode, and run any other code that we don't want to look like it is being typed.

Step 2 - Turning it into a gif

Here's the Makefile I use:

%.gif: %.cast
    asciicast2gif $< $@

%.cast: %.el
    asciinema rec $@ -c 'emacs -nw -Q -l util/director-bootstrap.el -l $<'

Calling make hello-world.gif will convert a file called hello-world.el into a .cast file, which will then be turned into an animated gif.

The finished image looks like this:

Saying hello with emacs

There are a few little gotchas:

  • The finished gif will be based on the size of terminal where make was executed. If you create the gif from a fullscreen terminal you'll end up with a pretty big image.
  • This process uses the terminal version of Emacs, so showing things involving the gui is not possible.
  • When running code in :before-start there will be a frame or two before it is executed which can look a little weird.

None of these detract from the overall package, and it's a really easy way to create animated gifs of Emacs behaviour.

We'll finish with a slightly more complex gif:

Saying hello with emacs lisp

This uses the same director-bootstrap as before, but has a few more steps:

(director-run
 :version 1
 :before-start (lambda ()
		 (menu-bar-mode -1)
		 (switch-to-buffer (get-buffer-create "*say-hello*"))
		 (emacs-lisp-mode))
 :steps '((:type "(defun say-hello ()\r")
	  (:type "\"Say hello to the world.\"\r")
	  (:type "(interactive)\r")
	  (:type "(message \"Hello, World!\"))\r")
	  (:type "\C-x\C-e")
	  (:wait 2)
	  (:type "\M-x")
	  (:type "say-hello")
	  (:type [return]))
 :typing-style 'human
 :delay-between-steps 1
 :after-end (lambda () (sleep-for 5) (kill-emacs 0))
 :on-error  (lambda () (kill-emacs 1)))

Normally to create a gif like this I would type everything by hand and record the screen with licecap or recordMyDesktop, so being able to automate things is a huge time saver.


How to sort lines of text with Emacs

Alphabetically sorting a bunch of text is something I need to do fairly regularly. Thankfully Emacs has a built-in function to help with that: sort-lines.

It looks like this:

Sorting some lines

Calling M-x sort-lines will sort all lines in the current region. Lines are sorted numerically, then alphabetically, and capital letters are sorted before lower-case ones (e.g. A will come before a).

Emacs will only sort text in the highlighted region, so it's important to mark everything that needs sorting. Otherwise it can end up like this:

Incorrectly sorting some lines in a region

sort-lines is one of those handy little functions that I never knew I wanted, until I was sat with a pile of unsorted text and wondering "I wonder if Emacs can help".


Per-project TODO.org files with org-projectile

A TODO.org file is one of the first things I create when starting a new project. I use this file to organize all of the project's milestones and tasks, as well as to track time spent on different items.

One disadvantage with this approach is that scheduled tasks will not appear in the org-agenda without adding the TODO.org's path to org-agenda-files. Doing this by hand can be tedious, especially with a lot of projects.

Thankfully there is a package to help with this: org-projectile. I already use projectile to navigate my projects; org-projectile sits on top of that and adds some useful features:

  • I can jump to the TODO list for a project quickly.
  • I can capture tasks for the current project with a few key presses.
  • All of my project TODO.org files can be added to my agenda with a few lines of code. This makes scheduling project tasks much easier.

I made a couple of changes to integrate it better with my setup. By default org-projectile adds every registered projectile project, but not everything indexed by projectile has a TODO.org file.

This small function filter the list to only projects that exist:

(defun sodaware/org-projectile-todo-files ()
  "Fetch a list of org TODO files for projects that actually exist."
  (seq-filter #'file-exists-p (org-projectile-todo-files)))

;; Add org-projectile files to org.
(setq org-agenda-files
      (append org-agenda-files (sodaware/org-projectile-todo-files)))

I also created a helper function to open the TODO.org file for the current project:

(defun sodaware/org-projectile-goto-project-file ()
  "Open the TODO.org file for the current project."
  (interactive)
  (org-projectile-goto-location-for-project (projectile-project-name)))

My org-projectile configuration looks like this:

(use-package org-projectile
  :after (org)
  :config
  (org-projectile-per-project)
  :custom
  (org-projectile-per-project-filepath "TODO.org"))

(use-package org-projectile-helm
  :after org-projectile
  :bind (("C-c n p" . org-projectile-helm-template-or-project)
	 ("C-c p O" . sodaware/org-projectile-goto-project-file)))

I suck at self-promotion

The Finally Finish Something 2021 game jam finished on Sunday, and Splodey Boats 2000 received a whopping two ratings. Even though they were overwhelmingly positive, I'm still disappointed with how little feedback it received.

I think a big part of this is that even though I reviewed plenty of games, I never directly asked people to review mine.

This pattern is repeated in other areas. I like creating stuff, but I'm always hesitant to share what I've made outside of my own spaces. I think a big part of that is a fear of negative feedback; constructive feedback is fine, but online comments tend to be quite blunt and not all that useful.

I've been spending time on improving how I get things done, but I haven't spent any time on promoting what I've done. As much as I dislike self-promotion, I think it's an important step in improving other areas that I'm working on.

It takes courage to put yourself out there.


Importing CSV files into ledger using reckon

I've been using ledger to manage my accounts for a couple of years, but I've never been 100% happy with the importing process. The process usually goes like this:

  1. Login to my bank and export transactions to a CSV file.
  2. Modify it so that ledger can read it.
  3. Run the convert command to change it to ledger syntax.
  4. Go through the converted file and add categories as needed.

It's not too painful, but there's enough friction to put me off from doing it regularly. As part of my improve my processes goal I wanted to revisit this process and smooth off some rough edges.

There are a number of CSV conversion tools for ledger, and after some experimentation I settled on reckon.

With reckon the import process looks like this:

  1. Login to my bank and export transactions to a CSV file.
  2. Run the reckon command and categorize transactions one-by-one.

It's a much more streamlined process, and categorizing transactions on the command line is nice and easy. One feature I really like about reckon is that it can learn account names from existing files. This makes the data-entry part much quicker, and for some accounts it can be run unattended without making mistakes.

reckon can also use a custom token file when categorizing transactions, so with a little work I could eliminate the manual entry part entirely.