Posts tagged with "Emacs Lisp"

blogmore.el v4.2

2 min read

Another wee update to blogmore.el, with a bump to v4.2.

After adding the webp helper command the other day, something about it has been bothering me. While the command is there as a simple helper if I want to change an individual image to webp -- so it's not intended to be a general-purpose tool -- it felt "wrong" that it did this one specific thing.

So I've changed it up and now, rather than being a command that changes an image's filename so that it has a webp extension, it now cycles through a small range of different image formats. Specifically it goes jpeg to png to gif to webp.

With this change in place I can position point on an image in the Markdown of a post and keep running the command to cycle the extension through the different options. I suppose at some point it might make sense to turn this into something that actually converts the image itself, but this is about going back and editing key posts when I change their image formats.

Another change is to the code that slugs the title of a post to make the Markdown file name. I ran into the motivating issue yesterday when posting some images on my photoblog. I had a title with an apostrophe in it, which meant that it went from something like Dave's Test (as the title) to dave-s-test (as the slug). While the slug doesn't really matter, this felt sort of messy; I would prefer that it came out as daves-test.

Given that wish, I modified blogmore-slug so that it strips ' and " before doing the conversion of non-alphanumeric characters to -. While doing this, for the sake of completeness, I did a simple attempt at removing accents from some characters too. So now the slugs come out a little tidier still.

(blogmore-slug "That's Café Ëmacs")
"thats-cafe-emacs"

The slug function has been the perfect use for an Emacs Lisp function I've never used before: thread-last. It's not like I've been avoiding it, it's just more a case of I've never quite felt it was worthwhile using until now. Thanks to it the body of blogmore-slug looks like this:

(thread-last
  title
  downcase
  ucs-normalize-NFKD-string
  (seq-filter (lambda (char) (or (< char #x300) (> char #x36F))))
  concat
  (replace-regexp-in-string (rx (+ (any "'\""))) "")
  (replace-regexp-in-string (rx (+ (not (any "0-9a-z")))) "-")
  (replace-regexp-in-string (rx (or (seq bol "-") (seq "-" eol))) ""))

rather than something like this:

(replace-regexp-in-string
 (rx (or (seq bol "-") (seq "-" eol))) ""
 (replace-regexp-in-string
  (rx (+ (not (any "0-9a-z")))) "-"
  (replace-regexp-in-string
   (rx (+ (any "'\""))) ""
   (concat
    (seq-filter
     (lambda (char)
       (or (< char #x300) (> char #x36F)))
     (ucs-normalize-NFKD-string
      (downcase title)))))))

Given that making the slug is very much a "pipeline" of functions, the former looks far more readable and feels more maintainable than the latter.

blogmore.el v4.1

1 min read

Following on from yesterday's experiment with webp I got to thinking that it might be handy to add a wee command to blogmore.el that can quickly swap an image's extension from whatever it is to webp.

So v4.1 has happened. The new command is simple enough, called blogmore-webpify-image-at-point; it just looks to see if there's a Markdown image on the current line and, if there is, replaces the file's extension with webp no matter what it was before.

If/when I decide to convert all the png files in the blog to webp I'll obviously use something very batch-oriented, but for now I'm still experimenting, so going back and quickly changing the odd image here and there is a nicely cautious approach.

I have, of course, added the command to the transient menu that is brought up by the blogmore command.

One other small change in v4.1 is that a newly created post is saved right away. This doesn't make a huge difference, but it does mean I start out with a saved post that will be seen by BlogMore when generating the site.

boxquote.el v2.4

2 min read

boxquote.el is another of my oldest Emacs Lisp packages. The original code itself was inspired by something I saw on Usenet, and writing my own version of it seemed like a great learning exercise; as noted in the thanks section in the commentary in the source:

Kai Grossjohann for inspiring the idea of boxquote. I wrote this code to mimic the "inclusion quoting" style in his Usenet posts. I could have hassled him for his code but it was far more fun to write it myself.

While I never used this package to quote text I was replying to in Usenet posts, I did use it a lot on Usenet, and in mailing lists, and similar places, to quote stuff.

The default use is to quote a body of text; often a paragraph, or a region, or perhaps even Emacs' idea of a defun.

,----
| `boxquote.el` provides a set of functions for using a text quoting style
| that partially boxes in the left hand side of an area of text, such a
| marking style might be used to show externally included text or example
| code.
`----

Where the package really turned into something fun and enduring, for me, was when I started to add the commands that grabbed information from elsewhere in Emacs and added a title to explain the content of the quote. For example, using boxquote-describe-function to quote the documentation for a function at someone, while also showing them how to get at that documentation:

,----[ C-h f boxquote-text RET ]
| boxquote-text is an autoloaded interactive native-comp-function in
| ‘boxquote.el’.
|
| (boxquote-text TEXT)
|
| Insert TEXT, boxquoted.
`----

Or perhaps getting help with a particular key combination:

,----[ C-h k C-c b ]
| C-c b runs the command boxquote (found in global-map), which is an
| interactive native-comp-function in ‘boxquote.el’.
|
| It is bound to C-c b.
|
| (boxquote)
|
| Show a transient for boxquote commands.
|
|   This function is for interactive use only.
|
| [back]
`----

Or figuring out where a particular command is and how to get at it:

,----[ C-h w fill-paragraph RET ]
| fill-paragraph is on fill-paragraph (M-q)
`----

While I seldom have use for this package these days (mainly because I don't write on Usenet or in mailing lists any more) I did keep carrying it around (always pulling it down from melpa) and had all the various commands bound to some key combination.

(use-package boxquote
  :ensure t
  :bind
  ("<f12> b i"   . boxquote-insert-file)
  ("<f12> b M-w" . boxquote-kill-ring-save)
  ("<f12> b y"   . boxquote-yank)
  ("<f12> b b"   . boxquote-region)
  ("<f12> b t"   . boxquote-title)
  ("<f12> b h f" . boxquote-describe-function)
  ("<f12> b h v" . boxquote-describe-variable)
  ("<f12> b h k" . boxquote-describe-key)
  ("<f12> b h w" . boxquote-where-is)
  ("<f12> b !"   . boxquote-shell-command))

Recently, with the creation of blogmore.el, I moved the boxquote commands off the b prefix (because I wanted that for blogging) and onto an x prefix. Even then... that's a lot of commands bound to a lot of keys that I almost never use but still can't let go of.

Then I got to thinking: I'd made good use of transient in blogmore.el, why not use it here too? So now boxquote.el has acquired a boxquote command which uses transient.

The boxquote transient in action

Now I can have:

(use-package boxquote
  :ensure t
  :bind
  ("C-c b" . boxquote))

and all the commands are still easy to get to and easy to (re)discover. I've also done my best to make them context-sensitive too, so only applicable commands should be usable at any given time.

slstats.el v1.11

1 min read

Yet another older Emacs Lisp package that has had a tidy up. This one is slstats.el, a wee package that can be used to look up various statistics about the Second Life grid. It's mainly a wrapper around the API provided by the Second Life grid survey.

When slstats is run, you get an overview of all of the information available.

An overview of the grid

There are also various commands for viewing individual details about the grid in the echo area:

  • slstats-signups - Display the Second Life sign-up count
  • slstats-exchange-rate - Display the L$ -> $ exchange rate
  • slstats-inworld - Display how many avatars are in-world in Second Life
  • slstats-concurrency - Display the latest-known concurrency stats for Second Life
  • slstats-grid-size - Display the grid size data for Second Life

There is also slstats-region-info which will show information and the object and terrain maps for a specific region.

Region information for Da Boom

As with a good few of my older packages: it's probably not that useful, but at the same time it was educational to write it to start with, and it can be an amusement from time to time.

wordcloud.el v1.4

1 min read

I think I'm mostly caught up with the collection of Emacs Lisp packages that need updating and tidying, which means yesterday evening's clean-up should be one of the last (although I would like to revisit a couple and actually improve and extend them at some point).

As for what I cleaned up yesterday: wordcloud.el. This is a package that, when run in a buffer, will count the frequency of words in that buffer and show the results in a fresh window, complete with the "word cloud" differing-font-size effect.

Word cloud in action

This package is about 10 years old at this point, and I'm struggling to remember why I wrote it now. I know I was doing something -- either writing something or reviewing it -- and the frequency of some words was important. I also remember this doing the job just fine and solving the problem I needed to solve.

Since then it's just sat around in my personal library of stuff I've written in Emacs Lisp, not really used. I imagine that's where it's going back to, but at least it's cleaned up and should be functional for a long time to come.

quiz.el v1.7

1 min read

I wondered yesterday:

...those question headers are displaying differently, with the background colour no longer spanning the width of the window. I'd like to understand why.

Turns out it was pretty straightforward:

diff --git a/quiz.el b/quiz.el
index 2dbe45d..c1ba255 100644
--- a/quiz.el
+++ b/quiz.el
@@ -40,7 +40,8 @@
 (defface quiz-question-number-face
   '((t :height 1.3
        :background "black"
-       :foreground "white"))
+       :foreground "white"
+       :extend t))
   "Face for the question number."
   :group 'quiz)

and so v1.7 has happened.

Quiz with reinstated header look

It looks like, perhaps, at some point in the past, :extend was t by default, but it no longer is? Either way, explicitly setting it to t has done the trick.

fasta.el v1.1

1 min read

Today's Emacs Lisp package tidy-up is of a package I first wrote a couple of employers ago. While working on code I often found myself viewing FASTA files in an Emacs buffer and so I thought it would be fun to use this as a reason to knock up a simple mode for highlighting them.

fasta.el was the result.

An example FASTA file

While I doubt it was or is of much use to others, it helped me better understand simple font-locking in Emacs Lisp, and also made some buffers look a little less boring when I was messing with test data.

As for this update: it's the usual stuff of cleaning up deprecated uses of setf, mostly.

If bioinformatics-related Emacs Lisp code written by a non-bioinformatician is your thing, you might also find 2bit.el of interest too. Much like fasta.el it too probably doesn't have a practical use, but it sure was fun to write and taught me a few things along the way; it also sort of goes hand-in-hand with fasta.el too.

quiz.el v1.6

1 min read

A quick little refresh of one of my old packages, this time quiz.el. This is a nice little distraction when you're working in Emacs, letting you spin up a quick trivia quiz in a buffer.

Quiz in action

It's backed by the Open Trivia Database, so there's a good few subjects, questions, and levels of difficulty to play with.

The only changes I've made to it in this release are the usual clean-ups of the deprecated uses of setf, plus I've added q as a binding to the quiz window to quickly quit the quiz.

I might have to come back and revisit it soon, as it looks like the default face choices could probably do with a rethink, and I can see at the moment that the attempt at a window-wide "header" for each question isn't working any longer. For comparison, here's how the package looked when running back when I first wrote it back in 2017:

How it originally looked

Leaving aside the fact that I was still running a very light Emacs then, those question headers are displaying differently, with the background colour no longer spanning the width of the window. I'd like to understand why.

expando.el v1.5

1 min read

While I have been doing a lot of hacking on blogmore.el, I haven't forgotten my plan to revisit and refresh some of my older personal packages. This evening I've paid some attention to expando.el.

This started life a long time ago, as part of my grab-bag of handy functions that got carried around and copied from machine to machine, until I did a big tidy-up of everything back in 2017 and turned various things into packages that I managed via a self-hosted (well, GitHub pages hosted) package index.

It's a pretty simple but very useful bit of code that lets me quickly macroexpand a sexp at point and pretty print it into a display window. I've often found it indispensable when it came to writing my own macros.

expando in action

This release simply adds a lexical-binding header to the file, and also adds a q key binding to the resulting view window so that it can be quickly and easily closed.

Also, as with all my other personal packages, I've swapped away from using delpa to simply using :vc to pull it in.

(use-package expando
  :vc (:url "https://github.com/davep/expando.el" :rev :newest)
  :bind
  ("C-c e" . expando-macro))

Or perhaps I should say...

(progn
  (use-package-vc-install
   '(expando (:url "https://github.com/davep/expando.el") nil) nil)
  (defvar use-package--warning69
    #'(lambda (keyword err)
        (let
            ((msg
              (format "%s/%s: %s" 'expando keyword
                      (error-message-string err))))
          (display-warning 'use-package msg :error))))
  (condition-case err
      (progn
        (if (fboundp 'expando-macro) nil
          (autoload #'expando-macro "expando" nil t))
        (let*
            ((name "C-c e") (key [3 101])
             (kmap
              (or (if (and nil (symbolp nil)) (symbol-value nil) nil)
                  global-map))
             (kdesc
              (cons (if (stringp name) name (key-description name))
                    (if (symbolp nil) nil 'nil)))
             (binding (lookup-key kmap key)))
          (require 'bind-key)
          (let
              ((entry (assoc kdesc personal-keybindings))
               (details
                (list #'expando-macro (if (numberp binding) nil binding))))
            (if entry (setcdr entry details)
              (add-to-list 'personal-keybindings (cons kdesc details))))
          (define-key kmap key #'expando-macro)))
    ((debug error) (funcall use-package--warning69 :catch err))))

blogmore.el v4.0

2 min read

Despite having bumped it from 2.x to 3.x yesterday, I'm calling v4.0 on blogmore.el today. There's a good reason for this though. While tinkering with some of the configuration yesterday, and also answering a configuration question last night, I realised that it made sense to make some of the internals into public utility functions.

Now, sure, Emacs Lisp doesn't really have internals in the private function sense, but I've always liked the approach that a package-- prefix communicates "internal, might go away" vs package- which tells me "this is a stable part of the API of this package". With this in mind I've always tried to write my code using this convention. I did this with blogmore.el too and a lot of the code had the blogmore-- prefix.

There's plenty of code in there that someone might want to make use of, if they wanted to add their own commands, or do fun things with the configuration. So with this in mind I've "promoted" a bunch of code to being "public" and, in that case, I feel this deserves another major version bump1.

Things that are now part of the "public" interface include:

  • blogmore-clean-time-string
  • blogmore-get-frontmatter
  • blogmore-remove-frontmatter
  • blogmore-set-frontmatter
  • blogmore-slug
  • blogmore-toggle-frontmatter
  • blogmore-with-post

Each one is documented via its docstring (just a quick Ctrl+h f function-name RET away) and hopefully is pretty self-explanatory.

blogmore-with-post is especially handy as it provides a quick and easy way of pulling information from a post file. So something like this:

(blogmore-with-post "~/write/davep.github.com/content/posts/2026/04/2026-04-05-blogmore-el-v3-1.md"
  (list
   (blogmore-get-frontmatter "title")
   (blogmore-get-frontmatter "date")))

resulting in:

("blogmore.el v3.1" "2026-04-05 20:04:44+0100")

Meaning that this snippet from yesterday's post:

(with-temp-buffer
  (insert-file-contents-literally file)
  (parse-iso8601-time-string
   (blogmore--clean-time-string (blogmore--get-frontmatter-property "date"))))

becomes:

(blogmore-with-post file
  (parse-iso8601-time-string
   (blogmore-clean-time-string (blogmore-get-frontmatter "date"))))

Not massively different, but it reads better and now all the calls are to the "public API" of the package.

Not all the changes are "promoted internals". I've also added a blogmore-remove-tag command (and also added it to the transient menu).

Removing a tag

I've also changed the way that blogmore-add-tag works so that, now, if it's called from the transient, it immediately goes back to the tag input prompt, allowing for another tag to be immediately selected (you can quit out of this with Ctrl+g). Removal of a tag works in a similar way, making things a lot quicker.

I've also added some extra tests too, which makes it even easier for me to make future changes with confidence. The more I work with it the more I appreciate that ERT is available.


  1. Ordinarily it shouldn't matter as the public interface isn't changing, but some of the "internal" functions had been mentioned as options for configuration.