Posts tagged with "BlogMore"

BlogMore v2.9.0

1 min read

After releasing blogmore.el v2.6 this morning, I noticed something about the post: the text that was marked up with <kbd> wasn't really standing out as keys. In blog posts, as in documentation, if I mention the name of a key, I like to mark it up with <kbd>. Ideally, with such markup, the styling of the page it's being used on will make it clear that it's supposed to be read as a key.

I've never put any such styling into the default styles made available in BlogMore.

So here we are with BlogMore v2.9.0, now with a bit of markup, and theme support, for keys marked up with <kbd>. So now, hopefully, if I say you should press Ctrl+F4 to make this blog look better, those keys should stand out a little better than they used to.

blogmore.el v2.7

2 min read

There's no question that the experiment that is BlogMore has resulted in me blogging more. Although my previous setup wasn't exactly all friction, there's something about "owning" most of the tools and really knowing how they work, and being able to quickly modify them so they work "just so", that makes me more inclined to quickly write something up.

I can see this if I look at the numbers in the archive for this blog. In March alone I wrote 43 posts; that's more than I wrote in any whole year, other than 2023. While I suspect this will start to calm down as work on BlogMore and blogmore.el settles down, I sense I'll be writing a bit more often for some time to come.

Because of this I decided to do a little bit of housekeeping on the posts directory in the blog's source repository. Originally I had the Markdown source for every post all in one directory. Then last month I broke those down by year. Then earlier today, seeing how this year is going, I decided to break 2026 down by month.

Then I realised I had a problem in blogmore.el. It assumed that the Markdown file for a new post (blogmore-new) would always be created in a subdirectory named after the year, underneath the defined posts directory. Until today that was the case1, but now I wanted it to work differently.

So this is why I'm making a second release in one day: I added the ability to configure the subdirectory where a new post is created. I've changed the default now so that it assumes the user wants the subdirectory to be YYYY/MM/DD (because more granular feels like the right default). In my case I don't want that, I just want YYYY/MM, but now I can configure that. The value that is set is a function that returns the name of the subdirectory, so in the case of my blog I have it as:

(lambda () (format-time-string "%Y/%m/"))

On the other hand, for my photoblog I want the full date as a subdirectory so I can leave it as the default. The whole use-package for blogmore now looks like:

(use-package blogmore
  :ensure t
  :defer t
  :vc (:url "https://github.com/davep/blogmore.el" :rev :newest)
  :init
  (add-hook 'blogmore-new-post-hook #'end-it)
  (blogmore-work-on "blog.davep.org")
  :custom
  (blogmore-blogs
   '(("blog.davep.org"
      ;; Root directory for posts.
      "~/write/davep.github.com/content/posts/"
      ;; Subdirectory for new posts, relative to the root.
      (lambda () (format-time-string "%Y/%m/")))
     ("seen-by.davep.dev"
      ;; Root directory for posts.
      "~/write/seen-by/content/posts/")))
  :bind
  ("<f12> b" . blogmore))

Technically this is a breaking change because it bumps the meaning of each "position" in the values within blogmore-blogs. However, in my case, because I was only ever defining the blog name and the top-level directory for the posts (both mandatory), this didn't break anything; I also strongly suspect nobody else is using this so I very much doubt I'm messing with someone else's setup2. If I have I apologise; do let me know.

Anyway, all of this goes to explain why the heck I made two releases of the same package back to back in the same day. This is what happens when my namesake is having fun outside and so I just want to sit on the sofa, hack on some code, and watch the chaos in the garden.


  1. For my blog, which again shows that blogmore.el started as a quick hack for getting work done on my blog, but I also want to make it as configurable as possible. 

  2. Even if someone else is using this I would suspect they hadn't configured anything more than I have. 

blogmore.el v2.6

2 min read

Like most people, I imagine, I first ran into transient when first using magit. I took to it pretty quickly and it's always made sense to me as a user interface. But... I've never used it for any code I've ever written.

I think, incorrectly, I'd half assumed it was going to be some faff to set up, and of course for a good while it wasn't part of Emacs anyway. Given this, I'd always had it filed under the heading "that's so neat I'll give it a go one day but not at the moment".

Meanwhile... ever since I did the last big revamp of my Emacs configuration, I found myself leaning into a command binding approach that does the whole prefix-letter-letter thing. For reasons I can't actually remember I fell into the habit of using F121 as my chosen prefix key. As such, over the past 10 or so years (since I greatly overhauled my Emacs setup), I've got into setting up bindings for commands that follow this prefix convention.

So when I created blogmore.el I set up the commands following this pattern.

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

It works well, it makes it nice and easy to remember the bindings, etc. Nobody needs me to sell them on the merits of this approach.

Then I got to thinking last night: why am I setting up all those bindings when I could probably do it all via a transient? So that was the moment to actually RTFM and get it going. The first version was incredibly quick to get up and running and I was kicking myself that I'd taken so long to actually look at the package properly.

This morning I've worked on it a little more and the final form is still pretty straightforward.

(transient-define-prefix blogmore ()
  "Show a transient for BlogMore commands."
  [:description
   (lambda ()
     (format "BlogMore: %s\n"
             (if (blogmore--chosen-blog-sans-error)
                 (blogmore--blog-title)
               "No blog selected")))
   ["Blog"
    ("b"  "Select blog" blogmore-work-on)]
   ["Post"
    ("n" "New post" blogmore-new :inapt-if-not blogmore--chosen-blog-sans-error)
    ("e" "Edit post" blogmore-edit :inapt-if-not blogmore--chosen-blog-sans-error)
    ("d" "Toggle draft status" blogmore-toggle-draft :inapt-if-not blogmore--blog-post-p)
    ("c" "Set post category" blogmore-set-category :inapt-if-not blogmore--blog-post-p)
    ("t" "Add tag" blogmore-add-tag :inapt-if-not blogmore--blog-post-p)
    ("u d" "Update date" blogmore-update-date :inapt-if-not blogmore--blog-post-p)
    ("u m" "Update modified date" blogmore-update-modified :inapt-if-not blogmore--blog-post-p)]
   ["Links"
    ("l c" "Link to a category" blogmore-link-category :inapt-if-not blogmore--blog-post-p)
    ("l p" "Link to a post" blogmore-link-post :inapt-if-not blogmore--blog-post-p)
    ("l t" "Link to a tag" blogmore-link-tag :inapt-if-not blogmore--blog-post-p)]])

With this in place I can simplify my use-package quite a bit, just binding a single key to run blogmore.

(use-package blogmore
  :ensure t
  :defer t
  :vc (:url "https://github.com/davep/blogmore.el" :rev :newest)
  :init
  (add-hook 'blogmore-new-post-hook #'end-it)
  (blogmore-work-on "blog.davep.org")
  :custom
  (blogmore-blogs
   '(("blog.davep.org" "~/write/davep.github.com/content/posts/")
     ("seen-by.davep.dev" "~/write/seen-by/content/posts/")))
  :bind
  ("<f12> b" . blogmore))

Now, when I'm working on a blog post, I can just hit F12 b and I get a neat menu:

BlogMore with all commands available

Better still, because of how transient works, I can ensure that only applicable commands are available, while still showing them all. So if I've not even got a blog selected yet:

With no commands available

Or with a blog selected but not actually working on a post yet:

With some commands available

So far I'm really liking this approach, and I'm tempted to lean into transient more with some of my packages now. While on the surface it does seem that it has the downside of the binding choices being dictated by me, the fact is that the commands are all still there and anyone can use their own bindings, or I guess override the transient itself and do their own thing.


  1. Yes, it is a bit out of the way on the keyboard, but so is Esc. I find my muscle memory has no problem with it. 

blogmore.el v2.5

2 min read

Following on from yesterday's release, I've bumped blogmore.el to v2.5. The main change to the package is the thing I mentioned yesterday about the toggle of the draft status. The draft toggle yesterday was pretty simple, with it working like:

  • If there is no draft frontmatter, draft: true is added
  • If there is any draft frontmatter, it is removed

This meant that if you had draft: false set and you went to toggle, it would be removed, which is the same as setting it to draft: false.

Unlikely to generally be an issue, but I also couldn't let that stay like that. It bothered me.

So now it works as you'd expect:

  • If there is no draft frontmatter, draft: true is added
  • If draft: true is there, it is removed
  • If draft: false is there, it is set to draft: true

Much better.

Another change is that I fixed a problem with older supported versions of Emacs. I didn't know this was a problem because I'm running 30.2 everywhere. Meanwhile, thanks to package-lint-current-buffer from package-lint.el, I have:

Package-Requires: ((emacs "29.1"))

in the metadata for the package. Turns out though that sort used to require two parameters (the sequence and the predicate), whereas now it's fine with just one (it will accept just the sequence and will default the predicate). So of course blogmore.el was working fine for me, but would have crashed for someone with an earlier Emacs.

As for how I found this out... well I finally, for the first time ever, dived into using ERT to write some tests. While I've used testing frameworks in other languages, I'd never looked at this with Emacs Lisp. It works a treat and is great to work with; I think I'll be using this a lot more from now on.

Having got tests going I realised I should run them with GitHub actions, which then meant I managed to discover setup-emacs. Having found this the next logical step was to set up a matrix test for all the versions of Emacs I expect blogmore.el to work on. This worked fine, except... it didn't. While the tests worked locally, they were failing for some Emacsen over on GitHub.

And that's how I discovered the issue with sort on versions earlier than the one I'm using locally.

All in all, that was a good little period of hacking. New things discovered, the package improved, and a wider range of support for different versions of Emacs.

BlogMore v2.8.0

1 min read

I've just published v2.8.0 of BlogMore to PyPI. This is a small update which addresses a bug that Andy reported.

The fix was simple enough, and is another little interesting thing to keep in mind given that BlogMore is an ongoing Copilot experiment. When I first kicked off BlogMore I let it decide which library to use to handle Markdown (I'm more used to markdown-it-py via Textual and so via Hike), and so also let it decide which extensions made most sense given the request. I've honestly never run into the idea of metadata before, only ever dealing with or caring about frontmatter1.

On the other hand, I will say this: I was cooking dinner when the report came in; I pointed Copilot at the issue and let it figure it out. After eating, clearing things away, and general post-dinner chilling, I dropped into the repo to see what it had made of it and... it had figured the issue out and fixed it.


  1. I guess technically they're the same thing, but here I mean I'm more used to the delimited YAML of frontmatter than whatever it is the meta plugin was dealing with. 

blogmore.el v2.4

1 min read

I've just released a little update to blogmore.el, adding blogmore-toggle-draft as a command. This came about in connection with the feature request that resulted in BlogMore v2.7.0 being released.

While I don't personally use draft for my posts, I can see the utility of it and if someone were to happen to use blogmore.el, it could be useful to have this bound to a handy key combination.

As for how it works: that's simple. When run, if there is no draft: frontmatter property, then draft: true is added. If draft: is there it is removed. Yes, it does mean that it will also remove draft: false too but... eh. Okay, fine, I might handle that case as a followup but I couldn't really imagine someone wanting to keep draft: false in the frontmatter.

If a post is ready to go, why bother with a header that means the same thing when it's not there?

BlogMore v2.7.0

1 min read

Given I've been on a little bit of an Emacs Lisp side quest, it's been a couple or so days since I made a release of BlogMore. Today's release comes after a feature request about draft posts.

While support for marking posts as drafts, and including or excluding them from a build, is something that's been in BlogMore from the start, it's not something I've ever used. These days, when I'm writing a post, especially if it's one that's taking a while to write, I'll do it in a branch and eventually PR into main before publishing. Given this it was useful to get a request relating to the feature as it helps me understand how someone else might use it.

So as of v2.7.0, if a post is marked as a draft, and if drafts are included in the build, it will be pretty obvious:

A draft post

When the post's title appears in the archive it will also appear obvious that it's still a draft too:

A draft post in the archive

All of this is, of course, modifiable via the template API and via styling, so if the choice of colour or icon doesn't suit it can be modified to taste.

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!

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.