Posts from March 2026

eg.el v1.2

1 min read

Okay, that's it then; this is turning into a thing I think. Second in an occasional series of posts where I tidy up some of my old Emacs packages. This time I dug out eg.el and cleaned up some of the frowned-upon behaviour.

For anyone who doesn't know it: eg.el is one of many Norton Guide readers I've written over the years. This particular one was possibly the most fun as it was the most unlikely one to write and also, I think it's fair to say, my most ambitious one as well. It also holds a special place for me in that the bulk of it was written on trains during multiple trips up to Scotland, before I finally moved here (on the MacBook Air I mentioned the other day).

As I look at it now, I sort of want to give it a proper revisit. I've written more Norton Guide code since (see AgiNG for example) and have learnt better ways to handle certain things, and I also have an even bigger Norton Guide collection to test against. All of this could make for an even better Emacs implementation.

I also suspect that, this time around, I can do a better job of handling the colour and retaining the original Norton Guide reader look and feel. But, for now, the code has been tidied up and should keep working for some time yet.

thinks.el v1.13

1 min read

Given the recent spate of hacking on some Emacs Lisp, I've got a real taste for hacking on some more. Or, more to the point, revisiting some of the packages I have in Melpa and tidying them up where necessary.

The main thing I'll need to address is cutting back on all my old setf ways. I liked that approach to doing things, it made lots of sense and felt elegant; sadly the Emacs maintainers didn't seem to agree.

So... kicking this off I've released v1.13 of thinks.el. This is a bit of nonsense I wrote back in the days when Usenet was still pretty busy and the place to be (well, okay, back in 2000 when I was still hanging out on Usenet). The package itself lets you quickly and easily...

. o O ( ...write some text and then mark it all and then run a command and )
      ( have it turned into something that looks a little like a thought   )
      ( bubble.                                                            )

It has some variations on how the bubble looks, and also lets you use customize to tweak the characters to use, and also has an "extra silly" mode too.

Updating this wasn't too bad. Mostly just a case of turning some instances of (setf (point) ...) into (goto-char ...), and also modifying one instance of incf to be cl-incf.

Honestly, I don't know how useful this package is to anyone anymore. Most folk don't even know what Usenet is these days, and all the "social" places seem to favour non-monospaced fonts, meaning the bubbles would look pretty terrible anyway.

On the other hand, it seems a shame to not update it, and perhaps someone somewhere still uses it to make some pithy parenthetical remark, possibly about September never ending.

blogmore.el v2.3

1 min read

I've bumped blogmore.el to v2.3. The main change in this release, which I've had on my mental to-do list for a couple of days now, revolves around categories, tags and case.

I've got BlogMore set up so that it's pretty relaxed about case when it comes to categories and tags; so when it comes to generated files and URLs everything collapses to lowercase. However, blogmore.el wasn't taking this into account.

While it wasn't a serious issue, it did have the side-effect that, if you had a tag of lisp and a tag of Lisp, both would appear in the list of tags you could complete from. Also, when you went to add a tag to the tags frontmatter (via the blogmore-add-tag command), if you selected Lisp and lisp was already there, you'd end up with both versions after the command finished.

ℹ️ Note

As mentioned earlier: BlogMore itself would collapse Lisp and lisp to the same tag; the downside here is you'd see both tags shown in the post itself. Not a real problem, just not very tidy.

So earlier I changed things up a little; first cleaning up when loading pre-existing values and then ensuring the newly-set tags are deduplicated.

This now means I can edit and update a post even faster, without needing to worry about accidentally duplicating tags. This in turn reduces the friction to writing a post for my blog. That is, after all, the whole point of the name of the package and the blogging tool it's connected to!

OldNews v1.3.0

1 min read

OldNews

Yesterday evening I released v1.3.0 of OldNews, my terminal-based client for TheOldReader.

The main reason for this release is that html_to_markdown had a major release and the one function I use from it fundamentally changed the return type, causing OldNews to crash any time you tried to read an article.

It was a quick enough fix, although it's one I want to go back and review and perhaps see if there's a better approach, or see if this new return type offers something I could be making better use of.

The one other change, which I made a wee while ago but hadn't got round to releasing yet (I've been kind of distracted recently), is that OldNews now only makes a call out to the API to mark an article as read when you read it, but only if it was previously unread. A small internal change nobody should really notice, but it saves on work.

If you're a user of TheOldReader and fancy interacting with it from the terminal too then it's out there to try out. It's licensed GPL-3.0 and available via GitHub and also via PyPI. If you have an environment that has pipx installed you should be able to get up and running with:

pipx install oldnews

It can also be installed using uv:

uv tool install oldnews

If you don't have uv installed you can use uvx.sh to perform the installation. For GNU/Linux or macOS or similar:

curl -LsSf uvx.sh/oldnews/install.sh | sh

or on Windows:

powershell -ExecutionPolicy ByPass -c "irm https://uvx.sh/oldnews/install.ps1 | iex"

Once installed, run the oldnews command.

blogmore.el v2.2

2 min read

It really feels like BlogMore has kicked off a whole new thing when it comes to personal hacking. During the past few years I've developed a lot of Python applications and libraries, and have had a ton of fun doing so, but during that time I've not really done anything with writing stuff for Emacs.

To a large degree I think this says something about how stable Emacs is for me (I've been using it for a touch over 30 years at this point, you'd think I'd be kind of settled with it), but it's still always fun to have a reason to hack on some Lisp code. There's little doubt my Lisp -- especially Emacs Lisp -- has got a wee bit rusty.

So I'm having a lot of fun at the moment falling into the rabbit hole of expanding on and tinkering with blogmore.el. The reason I've just made v2.2 is a good example of exactly this. There are no real user-facing changes in the code, it was all things I just wanted to tidy up.

The main thing that has been bugging me for the past day is the repeating boilerplate that resulted from adding all the different current-blog-aware setting getter functions. There were 7 different functions, all looking like this:

(defun blogmore--post-maker-function ()
  "Get the post maker function for the current blog."
  (or
   (blogmore--blog-post-maker-function (blogmore--chosen-blog))
   blogmore-default-post-maker-function))

Exact same pattern, the only thing different being the name of the getter function being called on, and the name of the variable that contained the global default value.

So just a little earlier I cleaned this up using one of my favourite things about Lisp: defmacro. There's something about macros that makes me really like coding in Lisp, and which I cite as a really good thing when asked why I like Lisp, but which I always seem to utterly fail to explain well. Macros feel like one of those things you just have to experience for yourself to really get1.

Now, thanks to this:

(defmacro blogmore--setting (setting)
  "Generate a function to get the value of SETTING for the current blog."
  `(defun ,(intern (format "blogmore--%s" setting)) ()
     ,(format "Get the %s for the current blog." setting)
     (or (,(intern (format "blogmore--blog-%s" setting)) (blogmore--chosen-blog))
         ,(intern (format "blogmore-default-%s" setting)))))

all those 7 functions can collapse to this2:

(blogmore--setting post-template)
(blogmore--setting post-maker-function)
(blogmore--setting category-maker-function)
(blogmore--setting tag-maker-function)
(blogmore--setting post-link-format)
(blogmore--setting category-link-format)
(blogmore--setting tag-link-format)

Now the code is shorter, cleaner, and if I need to change anything I only need to change it in one place. Sure, the latter part especially is one of those "you could do that with a function too" things (have the work in one place), but here I can get the language to write me a whole load of functions, all of which refer to different functions and variables, each one based off just the one symbol.

The point of all of this being: v2.2 of blogmore.el is now out, it adds nothing for the user (who I suspect is only me anyway), but I had an absolute blast dusting off more of my Emacs Lisp knowledge and getting back the urge to code even more Emacs Lisp.

All of this has even got me tidying up my ~/.emacs.d/ and has me thinking I should go back through some of my older code and clean up all that legacy nonsense.


  1. There was a time I would have said "grok" here but... well that's spoiled now. 

  2. I suppose I could reduce that to one "call" with a loop over a list of symbols, but that feels unnecessary here. 

blogmore.el v2.1

1 min read

I've given blogmore.el a wee bump to v2.1. This release fixes a small problem I noticed today when I tried to use it to edit the tags for a post on my photoblog: the code to find and gather properties from posts didn't handle deeply-nested directory hierarchies for the post markdown files. I should have noticed this when I first wrote the code, but of course I was so busy testing against my primary blog, which only goes one sub-level deep, that I never noticed it wasn't going to work deeper.

So rather than using grep to look for things like foo/**/*.md I swapped to a combination of find and grep. Which works, but is slightly (but noticeably) slower.

Then I got to thinking that if I was doing this by hand, on the command line, I'd be using ripgrep anyway. Given this I might as well use it here. Of course, not everyone who might use blogmore.el will have rg installed so it makes sense to look for that and use it if it's available, otherwise fall back on find/grep.

There's still some low-priority cleaning up I want to do around this; an obvious change I want to make being one where I want to collapse all cases of the same word (Tree vs tree, etc) into one "hit"1. For now though, as always, it's working well enough for my needs and this change fixed an obvious issue I ran into.


  1. BlogMore itself takes care of this, but it would be nice to have the prompt in blogmore.el also take this into account. 

blogmore.el v2.0

2 min read

After kicking off blogmore.el, and then tinkering with it more and more, I've found it really quite helpful while writing posts. One thing I have noticed though -- given I use BlogMore for this blog and my photoblog -- is that I wanted to be able to use the package for working with more than one blog.

So today I found myself with some time to kill and the result is that blogmore.el v2.0 has now been released. This version allows for setting up multiple blogs, each with their own settings for where posts live, how their paths are formatted, and so on.

To handle this I've also added the blogmore-work-on command so that the active blog can be quickly changed.

All of this can be configured using Emacs' customize feature.

Customize blogmore

This has all changed since v1.x, where most of the customize options have now been renamed to include -default- in their name. The idea here is that what was the value for a setting previously is now the default value if a given blog hasn't had that setting defined.

For any given blog you wish to work with, you configure a name (for your own reference) and the path to the posts. Optionally you can also set lots of other values too.

Customize the blog

If a value is left on Default, then the corresponding default setting will be used; if it's set, then that value is used for that specific blog.

The defaults out of the box match how I do things with my blogs, of course, so the configuration is pretty straightforward. As of the time of writing my use-package for blogmore.el looks like this:

(use-package blogmore
  :vc (:url "https://github.com/davep/blogmore.el" :rev :newest)
  :init (add-hook 'blogmore-new-post-hook #'end-it)
  :custom
  (blogmore-blogs
   '(("blog.davep.org" "~/write/davep.github.com/content/posts/")
     ("seen-by.davep.dev" "~/write/seen-by/content/posts/")))
  :bind
  ("<f12> m b" . blogmore-work-on)
  ("<f12> m p n" . blogmore-new)
  ("<f12> m p e" . blogmore-edit)
  ("<f12> m s c" . blogmore-set-category)
  ("<f12> m a t" . blogmore-add-tag)
  ("<f12> m u d" . blogmore-update-date)
  ("<f12> m u m" . blogmore-update-modified)
  ("<f12> m l p" . blogmore-link-post)
  ("<f12> m l c" . blogmore-link-category)
  ("<f12> m l t" . blogmore-link-tag))

In the above you can see that I've set only the blog title and posts path for each blog in blogmore-blogs; the remaining values are all implied nil and so will be defaulted. The full list of values for any given blog are:

(BLOG-NAME
 POSTS-DIRECTORY
 POST-TEMPLATE
 POST-MAKER-FUNCTION
 CATEGORY-MAKER-FUNCTION
 TAG-MAKER-FUNCTION
 POST-LINK-FORMAT
 CATEGORY-LINK-FORMAT
 TAG-LINK-FORMAT)

where:

  • BLOG-NAME is the descriptive name to use for the blog.
  • POSTS-DIRECTORY is the directory where the blog's posts are stored.
  • POST-TEMPLATE is a template for new posts. If nil, blogmore-default-template is used.
  • POST-MAKER-FUNCTION is a function that takes a filename and returns a string to be used in the post's URL. If nil, blogmore-default-post-maker-function is used.
  • CATEGORY-MAKER-FUNCTION is a function that takes a category name and returns a string to be used in the category's URL. If nil, blogmore-default-category-maker-function is used.
  • TAG-MAKER-FUNCTION is a function that takes a tag name and returns a string to be used in the tag's URL. If nil, blogmore-default-tag-maker-function is used.
  • POST-LINK-FORMAT is a format string for the post's URL, where %s is replaced with the value returned by the post maker function. If nil, blogmore-default-post-link-format is used.
  • CATEGORY-LINK-FORMAT is a format string for the category's URL, where %s is replaced with the value returned by the category maker function. If nil, blogmore-default-category-link-format is used.
  • TAG-LINK-FORMAT is a format string for the tag's URL, where %s is replaced with the value returned by the tag maker function. If nil, blogmore-default-tag-link-format is used.

While I very much doubt any of this is useful to anyone else, it's at least flexible for my purposes and can probably be configured to someone else's purpose should they happen to be using BlogMore and Emacs.

BlogMore v2.6.0

1 min read

Yesterday I read a rather positive post about BlogMore, which was lovely to see. But... when I saw the link for it over on Mastodon I noticed something wasn't quite right about the description in the preview:

The preview of the post

See, when BlogMore makes a post, if the author of the post hasn't provided a description in the frontmatter, the first paragraph of text will be used instead. When doing this the code should strip out any markup (and also skip any initial images, that sort of thing).

But, as you can see, there are things like [Dave][davep] in that description. So I checked in with Andy and that was something that came from the underlying Markdown. After a bit of checking, it became obvious that the code in BlogMore was only looking for and removing inline links, but wasn't doing anything about reference links.

So, as usual, one prompt later and the issue was handled.

As it stands, I don't think I'll keep up with the current approach. It doesn't feel quite right to me. The whole point is that the Markdown should be rendered down to pure text and then the first actual paragraph of text is used. The code I have there now is doing some regexp-based mucking about as an approximate approach. It works, more or less, but it feels like it's implementing a poor Markdown parser when there's a Markdown parser already built in.

Given this, at some point soon, I might have a play and look at the idea of "let's have a Markdown to pure text parser" and then use that. I could see it being useful for other purposes too.

Anyway, the upshot of all of this is that BlogMore v2.6.0 is now available and it handles the stripping of reference links from the description, plus the recently-added strikethrough markup too.

Hello MacBook Air (again)

2 min read

As I mentioned yesterday I decided it was time to update my portable/sofa hacking setup and treat myself to a nice new MacBook Air. It's here (well, I picked it up yesterday evening after dinner).

MacBook Air M5

So far I'm very pleased with the choice. It's light but feels sturdy. The screen is very pleasing to read. The keyboard is really nice to type on (albeit I do prefer the old MacBook Pro, but on the other hand this is a bit more quiet, which matters if you're sharing a living room with someone else). It's fast. So fast! It's also so quiet! So very quiet! And cool too. The Intel-based MacBook Pro would get very warm as I worked; this just stays cold.

The really great part though is the battery life. Depending on what I was doing, with the Intel Pro, I'd get a couple of hours off the cable. On the other hand, last night, I spent a few hours setting things up on the Air and I barely noticed the battery drop at all. This, more than anything, is what I wanted.

Well, okay, I wanted the speed, the quiet, the lack of heat, and the long battery life.

Oh, and the rather lovely "Midnight" colour. It's not black, but it's close enough.

The setup itself went pretty well, although for some odd reason I ran into problems when setting up Emacs. These days I always use Emacs Plus via Homebrew and have never had issues. Weirdly though, this time, if I did the installation method that builds locally all sorts of things went wrong. I don't know if I missed a step or something but I did what I normally do when dropping Emacs on a Mac. So I started again with the pre-built approach and that worked better.

Even then though, I ran into problems with my setup downloading everything. Things mostly worked but I kept seeing all sorts of issues relating to git-gutter and git-gutter-fringe not being able to load (despite the fact they'd downloaded fine, from what I could see).

In the end I gave up trying to get it to all work from scratch and hand-removed and then hand-installed via package-list-packages instead. Not the most scientific of approaches, and one I'm sure I'll regret at some point in the future, but at least I got to a point where I could get other stuff done on the machine.

All of which is to say: if you're reading this blog post I got my Emacs and git environment to the point I can write things and push them out to the world. At which point that's the really important stuff up and going and I can call this "set up".

Once I'm happy that's working, I think it's time to revisit my Emacs setup. While I don't think it needs another complete restart, I think it might be time to at least look through what I have loading in and perhaps remove some things I don't use any more (for example, I always carry around vterm from the days when I was testing every possible terminal I could get my hands on -- that's less important to me these days.)

BlogMore v2.5.0

1 min read

I've released BlogMore v2.5.0 out into the world. This release is the result of an observation Andy made about the Markdown library used in BlogMore (it might apply to MarkdownIT too, which would of course affect Hike): it doesn't support strikethrough markup out of the box.

I'm not sure I've ever used that markup anywhere on my blog, but I've used it often enough on GitHub (for example) that I just assume it's going to be there (and now I think about it Hike might be okay 'cos it uses Textual's Markdown widget which I know for certain uses GitHub-flavoured Markdown). But, for sure, the Markdown library doesn't implement it because it's not part of the original approach to Markdown.

On the other hand: implementing a form of strikethrough markup is one of the samples in the documentation on writing extensions, so it seemed like a very reasonable thing to add. Given this, one quick prompt to the agents later and strikethrough was added.

Now I'm going to totally have to find a reason to use this markup from time to time to time.