<?xml version='1.0' encoding='UTF-8'?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
  <id>https://blog.davep.org</id>
  <title>davep</title>
  <updated>2026-05-20T18:37:01.917120+00:00</updated>
  <link href="https://blog.davep.org/feeds/coding.atom.xml" rel="self"/>
  <link href="https://blog.davep.org" rel="alternate"/>
  <generator uri="https://lkiesow.github.io/python-feedgen" version="1.0.0">python-feedgen</generator>
  <subtitle>Posts in category "Coding" from davep</subtitle>
  <entry>
    <id>https://blog.davep.org/2026/05/20/blogmore-v2-25-0.html</id>
    <title>BlogMore v2.25.0</title>
    <updated>2026-05-20T19:34:38+01:00</updated>
    <content type="html">&lt;p&gt;Following on from the &lt;a href="https://blog.davep.org/2026/05/18/blogmore-v2-24-0.html"&gt;previous release&lt;/a&gt;,
which was all about trying to get a big &lt;a href="https://pagespeed.web.dev/" rel="noopener noreferrer" target="_blank"&gt;PageSpeed
Insights&lt;/a&gt; win through image optimisation, I'm
chasing some more validation from that site by trying to squeeze just a
little more performance out of the code that
&lt;a href="https://blogmore.davep.dev/" rel="noopener noreferrer" target="_blank"&gt;BlogMore&lt;/a&gt; generates.&lt;/p&gt;
&lt;p&gt;BlogMore &lt;a href="https://blogmore.davep.dev/changelog/#v2250" rel="noopener noreferrer" target="_blank"&gt;v2.25.0&lt;/a&gt; has the
following changes to allow tinkering in ways that might speed things up a
touch, depending on the nature of the blog:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;CSS bundling&lt;/strong&gt; -- Every page generated by BlogMore pulls in at least these
three CSS files:
&lt;a href="https://github.com/davep/blogmore/blob/60be38ed295f232fe805826d53c972b7602cd767/src/blogmore/templates/static/style.css" rel="noopener noreferrer" target="_blank"&gt;&lt;code&gt;style.css&lt;/code&gt;&lt;/a&gt;,
&lt;code&gt;code.css&lt;/code&gt; and &lt;code&gt;fontawesome.css&lt;/code&gt; (or their minified versions if
&lt;a href="https://blogmore.davep.dev/configuration/#minify_css" rel="noopener noreferrer" target="_blank"&gt;&lt;code&gt;minify_css&lt;/code&gt;&lt;/a&gt; is
turned on). While this separation of concerns sits well with me, while it
feels like the elegant way of doing things, there is the issue that it
requires 3 trips back to the server to get base styling for any given
page&lt;sup id="fnref:329-1"&gt;&lt;a class="footnote-ref" href="#fn:329-1"&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;So with this new version, if you set
&lt;a href="https://blogmore.davep.dev/configuration/#bundle_css" rel="noopener noreferrer" target="_blank"&gt;&lt;code&gt;bundle_css&lt;/code&gt;&lt;/a&gt; to
&lt;code&gt;true&lt;/code&gt;, those three files are included and delivered as a single
&lt;code&gt;bundle.css&lt;/code&gt; (or &lt;code&gt;bundle.min.css&lt;/code&gt;). This saves a couple of requests.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Theme helper inlining&lt;/strong&gt; -- the lesser of the two main changes. There is
&lt;a href="https://github.com/davep/blogmore/blob/60be38ed295f232fe805826d53c972b7602cd767/src/blogmore/templates/static/theme.js" rel="noopener noreferrer" target="_blank"&gt;some
JavaScript&lt;/a&gt;
that's part of each page that helps with theme switching and also provides
the code to toggle the header display on mobile-sized screens. It's not a
lot of code, but it is another file that has to be fetched. If
&lt;code&gt;inline_theme_js&lt;/code&gt; is set to &lt;code&gt;true&lt;/code&gt;, this code will be included in the
&lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; of every single page generated for the site.&lt;/p&gt;
&lt;p&gt;I suspect I'm going to leave this one off, but it's there if it's helpful to
anyone (and also does let me experiment more with PageSpeed measurements).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Optimised logo&lt;/strong&gt; -- one image that got left out of the work to optimise
images was the &lt;a href="https://blogmore.davep.dev/metadata_and_sidebar/#site-logo" rel="noopener noreferrer" target="_blank"&gt;site
logo&lt;/a&gt;. While an
optimised version of the image was created, no HTML was generated to make
use of it. With this release, if
&lt;a href="https://blogmore.davep.dev/configuration/#optimise_images" rel="noopener noreferrer" target="_blank"&gt;&lt;code&gt;optimise_images&lt;/code&gt;&lt;/a&gt;
is &lt;code&gt;true&lt;/code&gt;, &lt;code&gt;&amp;lt;picture&amp;gt;&lt;/code&gt; will be used for this too.&lt;/p&gt;
&lt;p&gt;With those shameless performance-measurement changes aside, there are a
couple more changes in this release. The first is that the markup for the
site title (that appears below the logo, if you have one) has been changed
away from using a &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; tag. The SEO gods frown on multiple &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt;s on a
page and given the "main" title of any page is also a &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt;, this meant
there were always 2 such tags. Now just the main title will be marked up
this way; the site title becomes a &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; with appropriate styling to
maintain the existing look.&lt;/p&gt;
&lt;p&gt;Finally, this release fixes a small bug in the search index. It was being
created with escaped HTML entities in any text that came out of fenced code
blocks. From now on any text that goes into the search index is unescaped.&lt;/p&gt;
&lt;p&gt;As always: if a blog-oriented static site generator that is all about
Markdown sounds like your thing, &lt;a href="https://blogmore.davep.dev/#installation" rel="noopener noreferrer" target="_blank"&gt;check out the installation
instructions&lt;/a&gt; and give it a go.&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li id="fn:329-1"&gt;
&lt;p&gt;Yes, of course the client-side cache makes this moot after the first
page is loaded. All of this is about making that first load faster, and
so appeasing the PageSpeed Insights gods.&amp;#160;&lt;a class="footnote-backref" href="#fnref:329-1" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content>
    <link href="https://blog.davep.org/2026/05/20/blogmore-v2-25-0.html"/>
    <category term="Coding"/>
    <category term="BlogMore"/>
    <category term="Coding"/>
    <category term="PyPI"/>
    <category term="Python"/>
    <published>2026-05-20T19:34:38+01:00</published>
  </entry>
  <entry>
    <id>https://blog.davep.org/2026/05/19/oldnews-v1-4-2.html</id>
    <title>OldNews v1.4.2</title>
    <updated>2026-05-19T08:20:03+01:00</updated>
    <content type="html">&lt;p&gt;&lt;img alt="OldNews" height="640" loading="lazy" src="https://blog.davep.org/attachments/2026/02/11/oldnews-social-banner.webp#centre" width="1280" /&gt;&lt;/p&gt;
&lt;p&gt;I've made a minor bump to &lt;a href="https://oldnews.davep.dev/" rel="noopener noreferrer" target="_blank"&gt;OldNews&lt;/a&gt;, my
terminal-based client for &lt;a href="https://theoldreader.com" rel="noopener noreferrer" target="_blank"&gt;TheOldReader&lt;/a&gt;. There's
no significant change in this release, but it does change the dependency on
&lt;a href="https://github.com/kreuzberg-dev/html-to-markdown" rel="noopener noreferrer" target="_blank"&gt;html-to-markdown&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Since I &lt;a href="https://blog.davep.org/2026/02/11/oldnews.html"&gt;initially released the application&lt;/a&gt;, this
library seems to have been through a couple of significant changes, and not
every breaking change seems to have resulted in &lt;a href="https://semver.org/" rel="noopener noreferrer" target="_blank"&gt;a major version
bump&lt;/a&gt;. OldNews doesn't pin this dependency to a major
version (I try not to, only ever setting lower-bounds for dependencies where
possible), so it's fair that a change there can break things. I also think
it's fair to hope that minor version changes won't cause trouble.&lt;/p&gt;
&lt;p&gt;Recently, I've seen the library update with a minor version change and it's
flat-out caused runtime errors, either because the API has changed, or
because of an error being thrown by legitimate use of the API.&lt;/p&gt;
&lt;p&gt;Most recently, such an error happened, and was fixed by the time I noticed
it, but the release that was made &lt;a href="https://pypi.org/project/html-to-markdown/" rel="noopener noreferrer" target="_blank"&gt;never made it up to
PyPI&lt;/a&gt;. This left OldNews stuck
not working. Because of this I had to pin the library to an earlier version.&lt;/p&gt;
&lt;p&gt;It's now been updated again and PyPI is correct, so I think it's safe to
relax the pin.&lt;/p&gt;
&lt;p&gt;Fingers crossed...&lt;/p&gt;</content>
    <link href="https://blog.davep.org/2026/05/19/oldnews-v1-4-2.html"/>
    <category term="Coding"/>
    <category term="Markdown"/>
    <category term="OldNews"/>
    <category term="Python"/>
    <category term="RSS"/>
    <category term="atom"/>
    <category term="terminal"/>
    <category term="textual"/>
    <published>2026-05-19T08:20:03+01:00</published>
  </entry>
  <entry>
    <id>https://blog.davep.org/2026/05/18/blogmore-v2-24-0.html</id>
    <title>BlogMore v2.24.0</title>
    <updated>2026-05-18T18:59:06+01:00</updated>
    <content type="html">&lt;p&gt;Quite a few weeks ago now -- I think it was around the time I started work
on &lt;a href="https://github.com/davep/blogmore.el" rel="noopener noreferrer" target="_blank"&gt;&lt;code&gt;blogmore.el&lt;/code&gt;&lt;/a&gt; and got the new
&lt;a href="https://blog.davep.org/2026/03/28/hello-macbook-air-again.html"&gt;MacBook Air&lt;/a&gt; -- I remember
sitting in a cafe in Edinburgh and via Mastodon having a conversation with
&lt;a href="https://mastodon.me.uk/@andyc" rel="noopener noreferrer" target="_blank"&gt;Andy&lt;/a&gt; about tweaking better results out of
&lt;a href="https://pagespeed.web.dev/" rel="noopener noreferrer" target="_blank"&gt;PageSpeed Insights&lt;/a&gt;. I seem to remember him
correctly observing that one of the big hits on the performance score was
the size of images, and also the format, and that some SSG engines would go
to the trouble of converting to the likes of WebP and/or generating
different sizes that are appropriate to different screens, that sort of
thing.&lt;/p&gt;
&lt;p&gt;I can't quite remember where we left it, but I think it was considered more
work than was worth worrying about, and perhaps &lt;a href="https://blog.davep.org/2026/04/16/i-should-use-webp.html"&gt;swapping all images on our
blogs to WebP&lt;/a&gt; would solve most of the
issues.&lt;/p&gt;
&lt;p&gt;For a couple of different reasons, late last week, &lt;a href="https://blog.davep.org/2026/05/16/gemini-cli-vs-github-copilot-redux.html"&gt;I decided it was time to
play with the problem&lt;/a&gt;.
For some reason I've been pretty cautious with &lt;a href="https://github.com/davep/blogmore/pull/492" rel="noopener noreferrer" target="_blank"&gt;this
PR&lt;/a&gt;. I planned it out last
Friday night, kicked off work on it on Saturday morning, and have then been
tinkering and changing it and testing it and iterating over it all weekend.
Something about the nature of the change made me want to go very slowly with
this. I think it was an unease about messing with the images that would get
served, the nature of the new tags that would get emitted, the fact that
there would be even more HTML tinkering going on, the possible complexity of
maintaining the cache... lots of things to consider and this is supposed to
be a nice, simple, unfussy site generator.&lt;/p&gt;
&lt;p&gt;Anyway, I've just released
&lt;a href="https://blogmore.davep.dev/changelog/#v2240" rel="noopener noreferrer" target="_blank"&gt;v2.24.0&lt;/a&gt; with this feature
added. It's off by default, and is turned on by &lt;a href="https://blogmore.davep.dev/configuration/#optimise_images" rel="noopener noreferrer" target="_blank"&gt;setting
&lt;code&gt;optimise_images&lt;/code&gt;&lt;/a&gt;
to &lt;code&gt;true&lt;/code&gt;. Then, when you build your blog, each PNG, JPEG or WebP image will
be converted into one or more WebP images stored below
&lt;code&gt;static/images/optimised&lt;/code&gt;. How many are made for each image will depend on
how &lt;a href="https://blogmore.davep.dev/configuration/#image_widths" rel="noopener noreferrer" target="_blank"&gt;&lt;code&gt;image_widths&lt;/code&gt;&lt;/a&gt;
is set. The physical size of each image (and how the image looks) can be
affected by
&lt;a href="https://blogmore.davep.dev/configuration/#image_quality" rel="noopener noreferrer" target="_blank"&gt;&lt;code&gt;image_quality&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This does have two very obvious effects:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It will result in your generated site being quite a bit bigger, if you
   have lots of images.&lt;/li&gt;
&lt;li&gt;It will result in the build time taking much longer.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The first issue is something I can't do anything about; it is what it is.
The second issue, however, is something that can be dealt with. Given I've
&lt;a href="https://blog.davep.org/2026/05/13/blogmore-v2-22-0.html"&gt;just made a release that speeds up build
times&lt;/a&gt;, this would be a huge step
backwards. So with this in mind, as the optimised images are created, a
cache of them is also created in BlogMore's &lt;a href="https://blogmore.davep.dev/command_line/#cache-command" rel="noopener noreferrer" target="_blank"&gt;cache
directory&lt;/a&gt;. This,
again, does mean that more space is taken on your local storage to build
your site, but it also means that repeated builds will remain fast.&lt;/p&gt;
&lt;p&gt;If you run into problems or need space back, don't forget you can &lt;a href="https://blogmore.davep.dev/command_line/#clear" rel="noopener noreferrer" target="_blank"&gt;easily
clear the cache&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So what's the result of all of this? Is it worth the effort? Well, to be
sure, before I upgraded the version of BlogMore that I build this site with,
I measured its performance.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Built with BlogMore v2.23.0" height="204" loading="lazy" src="https://blog.davep.org/attachments/2026/05/18/before.webp#centre" width="906" /&gt;&lt;/p&gt;
&lt;p&gt;After upgrading and rebuilding, here is how the same home page measures up.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Built with BlogMore v2.24.0" height="212" loading="lazy" src="https://blog.davep.org/attachments/2026/05/18/after.webp#centre" width="906" /&gt;&lt;/p&gt;
&lt;p&gt;I was genuinely surprised by the difference. The settings I used were:&lt;/p&gt;
&lt;div class="highlight" data-lang="yaml"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;optimise_images&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;true&lt;/span&gt;
&lt;span class="nt"&gt;image_quality&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;95&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;and, of course, almost all the images on this site &lt;a href="https://blog.davep.org/2026/05/15/converted-to-webp.html"&gt;are now WebP
anyway&lt;/a&gt;. I think I was expecting it to
have a small impact, but even having those WebP images turned into stepped
sizes seems to have a very measurable effect.&lt;/p&gt;
&lt;p&gt;I'm going to be keeping a close eye on how this works for the next few days.
As I say, I've tested this as much as possible and gone over the code as
carefully as time has allowed. If this feature does break something I hadn't
anticipated I can always just turn it off again anyway. Meanwhile though,
the improvement on mobile does seem genuinely worth it.&lt;/p&gt;</content>
    <link href="https://blog.davep.org/2026/05/18/blogmore-v2-24-0.html"/>
    <category term="Coding"/>
    <category term="BlogMore"/>
    <category term="Coding"/>
    <category term="PyPI"/>
    <category term="Python"/>
    <published>2026-05-18T18:59:06+01:00</published>
  </entry>
  <entry>
    <id>https://blog.davep.org/2026/05/15/braindrop-v1-1-0.html</id>
    <title>Braindrop v1.1.0</title>
    <updated>2026-05-15T16:56:34+01:00</updated>
    <content type="html">&lt;p&gt;&lt;img alt="Braindrop" height="640" loading="lazy" src="https://blog.davep.org/attachments/2025/01/03/braindrop-social-banner.webp#centre" width="1280" /&gt;&lt;/p&gt;
&lt;p&gt;It's now well over a year since I &lt;a href="https://blog.davep.org/2025/01/03/braindrop.html"&gt;released
Braindrop&lt;/a&gt; and it's in constant use by me. I
continue to find &lt;a href="https://raindrop.io/" rel="noopener noreferrer" target="_blank"&gt;raindrop.io&lt;/a&gt; a really useful
resource, and more often than not manage, edit, tag, and review what I save
there with Braindrop, including which &lt;a href="https://raindrop.io/davep/public-46742255/sort=-created&amp;amp;perpage=30&amp;amp;page=0" rel="noopener noreferrer" target="_blank"&gt;become
public&lt;/a&gt;,
and which don't.&lt;/p&gt;
&lt;p&gt;I've made &lt;a href="https://github.com/davep/braindrop/releases" rel="noopener noreferrer" target="_blank"&gt;a few small changes to the application in the past year and a
bit&lt;/a&gt;, but not much. It's been
stable and useful. But on the back of a &lt;a href="https://blog.davep.org/2026/04/15/oldnews-v1-4-0.html"&gt;recent change I made to
OldNews&lt;/a&gt;, I felt I needed to make the same
change here.&lt;/p&gt;
&lt;p&gt;So with the release of v1.1.0 I've added three new commands to the
application:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;JumpToNavigation&lt;/code&gt; - Jump to the navigation panel; bound to &lt;kbd&gt;1&lt;/kbd&gt;
  by default&lt;/li&gt;
&lt;li&gt;&lt;code&gt;JumpToRaindrops&lt;/code&gt; - Jump to the main raindrops list panel; bound to
  &lt;kbd&gt;2&lt;/kbd&gt; by default&lt;/li&gt;
&lt;li&gt;&lt;code&gt;JumpToDetails&lt;/code&gt; - Jump to the details panel for the selected raindrop, if
  the panel is visible; bound to &lt;kbd&gt;3&lt;/kbd&gt; by default&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now it's just a little easier and quicker to get around the UI.&lt;/p&gt;
&lt;p&gt;If raindrop.io is your thing, and you want to work with your saved bookmarks
in the terminal: Braindrop is licensed GPL-3.0 and available &lt;a href="https://github.com/davep/braindrop" rel="noopener noreferrer" target="_blank"&gt;via
GitHub&lt;/a&gt; and also &lt;a href="https://pypi.org/project/braindrop/" rel="noopener noreferrer" target="_blank"&gt;via
PyPI&lt;/a&gt;. It can also be installed using
&lt;a href="https://docs.astral.sh/uv/getting-started/installation/" rel="noopener noreferrer" target="_blank"&gt;&lt;code&gt;uv&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight" data-lang="sh"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;uv&lt;span class="w"&gt; &lt;/span&gt;tool&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;braindrop
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If you don't have &lt;code&gt;uv&lt;/code&gt; installed you can use &lt;a href="https://uvx.sh" rel="noopener noreferrer" target="_blank"&gt;uvx.sh&lt;/a&gt; to
perform the installation. For GNU/Linux or macOS or similar:&lt;/p&gt;
&lt;div class="highlight" data-lang="sh"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-LsSf&lt;span class="w"&gt; &lt;/span&gt;uvx.sh/braindrop/install.sh&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;or on Windows:&lt;/p&gt;
&lt;div class="highlight" data-lang="sh"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;powershell&lt;span class="w"&gt; &lt;/span&gt;-ExecutionPolicy&lt;span class="w"&gt; &lt;/span&gt;ByPass&lt;span class="w"&gt; &lt;/span&gt;-c&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;irm https://uvx.sh/braindrop/install.ps1 | iex&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If &lt;code&gt;uv&lt;/code&gt; isn't your thing then it can also be installed with &lt;code&gt;pipx&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight" data-lang="sh"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pipx&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;braindrop
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Once installed, run the &lt;code&gt;braindrop&lt;/code&gt; command.&lt;/p&gt;</content>
    <link href="https://blog.davep.org/2026/05/15/braindrop-v1-1-0.html"/>
    <category term="Coding"/>
    <category term="Braindrop"/>
    <category term="Coding"/>
    <category term="PyPI"/>
    <category term="Python"/>
    <category term="Raindrop"/>
    <category term="terminal"/>
    <category term="textual"/>
    <published>2026-05-15T16:56:34+01:00</published>
  </entry>
  <entry>
    <id>https://blog.davep.org/2026/05/14/blogmore-v2-23-0.html</id>
    <title>BlogMore v2.23.0</title>
    <updated>2026-05-14T20:23:45+01:00</updated>
    <content type="html">&lt;p&gt;I wasn't quite planning on making a new release of BlogMore so soon after
&lt;a href="https://blog.davep.org/2026/05/13/blogmore-v2-22-0.html"&gt;the previous version&lt;/a&gt;, but I had a
couple of ideas that I wanted to add, and then also got a nifty request too;
so here we are: we have
&lt;a href="https://blogmore.davep.dev/changelog/#v2230" rel="noopener noreferrer" target="_blank"&gt;v2.23.0&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The first couple of changes relate to the cache. In the previous release I
added a cache of the FontAwesome metadata, which in turn means that a cache
directory is being created. I felt it would be fair and useful to provide a
command that both lets the user know where the cache lives, and to also
remove it. So now BlogMore has &lt;a href="https://blogmore.davep.dev/command_line/#cache-command" rel="noopener noreferrer" target="_blank"&gt;a &lt;code&gt;cache&lt;/code&gt;
command&lt;/a&gt; with two
sub-commands:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;location&lt;/code&gt;: tells you where the cache directory is located&lt;/li&gt;
&lt;li&gt;&lt;code&gt;clear&lt;/code&gt;: removes the cache directory&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Also, now that we have a cache directory, it makes sense to use it a bit
more to squeeze even more time out of the build process. So starting with
this release, per content directory, &lt;a href="https://blogmore.davep.dev/metadata_and_sidebar/#what-gets-generated" rel="noopener noreferrer" target="_blank"&gt;the various icons that are created for
the
site&lt;/a&gt;
are cached. This means that if the source image doesn't change, for each
subsequent build there's no conversion and resize for every variation. This
saves a good fraction of a second, making the build of my blog feel
noticeably quicker.&lt;/p&gt;
&lt;p&gt;Finally, earlier today, &lt;a href="https://www.yakshaving.co.uk/" rel="noopener noreferrer" target="_blank"&gt;Andy&lt;/a&gt; asked if it
would be possible to have the BlogMore &lt;code&gt;serve&lt;/code&gt; mode auto-reload any page
being viewed in a browser, when the site is regenerated. It was something
I'd considered myself a couple of times so that was a good reason to finally
look into it. Not knowing how this could be achieved&lt;sup id="fnref:296-1"&gt;&lt;a class="footnote-ref" href="#fn:296-1"&gt;1&lt;/a&gt;&lt;/sup&gt;, I prompted
&lt;a href="https://blog.davep.org/tag/gemini/"&gt;Gemini&lt;/a&gt; for an idea, stressing I wanted a solution that
didn't disturb a generated site; it &lt;a href="https://github.com/davep/blogmore/issues/486" rel="noopener noreferrer" target="_blank"&gt;came up with a convincing
solution&lt;/a&gt;. I let it run at it
and, &lt;a href="https://github.com/davep/blogmore/pull/487" rel="noopener noreferrer" target="_blank"&gt;along with a few changes of my
own&lt;/a&gt;, it seems to be working a
treat.&lt;/p&gt;
&lt;p&gt;This, of course, now makes me want to squeeze even more time out of the
build process.&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li id="fn:296-1"&gt;
&lt;p&gt;Web development has never been my primary area of knowledge.&amp;#160;&lt;a class="footnote-backref" href="#fnref:296-1" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content>
    <link href="https://blog.davep.org/2026/05/14/blogmore-v2-23-0.html"/>
    <category term="Coding"/>
    <category term="BlogMore"/>
    <category term="Coding"/>
    <category term="PyPI"/>
    <category term="Python"/>
    <published>2026-05-14T20:23:45+01:00</published>
  </entry>
  <entry>
    <id>https://blog.davep.org/2026/05/13/blogmore-v2-22-0.html</id>
    <title>BlogMore v2.22.0</title>
    <updated>2026-05-13T13:53:04+01:00</updated>
    <content type="html">&lt;p&gt;As mentioned &lt;a href="https://blog.davep.org/2026/05/11/speeding-up-blogmore.html"&gt;a couple of days ago&lt;/a&gt;,
I've been toying with finding areas of improvement in respect to &lt;a href="https://www.yakshaving.co.uk/posts/blogmore-performance/" rel="noopener noreferrer" target="_blank"&gt;the
performance of
BlogMore&lt;/a&gt;. Until
now, &lt;a href="https://wiki.c2.com/?PrematureOptimization" rel="noopener noreferrer" target="_blank"&gt;for good reasons&lt;/a&gt;, I've
not really paid any attention to how fast (or slow) BlogMore is when it
comes to generating my blog. While it's never been blindingly fast, it's
always been fast enough and I was more keen on &lt;a href="https://wiki.c2.com/?MakeItWorkMakeItRightMakeItFast" rel="noopener noreferrer" target="_blank"&gt;making it work
right&lt;/a&gt;. So for a good
while the focus has been on well-formed output, stuff that keeps the
crawlers happy, that sort of thing.&lt;/p&gt;
&lt;p&gt;But now that I'm in a place where new features aren't really so necessary,
it does feel like a good point to find any easy wins in speeding up the
code. I think it's gone well.&lt;/p&gt;
&lt;p&gt;BlogMore &lt;a href="https://blogmore.davep.dev/changelog/#v2220" rel="noopener noreferrer" target="_blank"&gt;v2.22.0&lt;/a&gt; contains
quite a few internal changes that speed up some core parts of site
generation. Many of the things identified by Gemini, back when I first
kicked this process off, have been done. The amount of Markdown-&amp;gt;HTML
conversion work has been vastly reduced, which has had a pretty big impact
on all sorts of things. There's also caching of the FontAwesome metadata&lt;sup id="fnref:297-1"&gt;&lt;a class="footnote-ref" href="#fn:297-1"&gt;1&lt;/a&gt;&lt;/sup&gt;
which should save a fair bit of time on slower connections. I did avoid the
whole business of parallel processing as I dabbled with this near the start
of the project and I could not wrangle a win out of that at all; given how
much of a win I've had with these changes, I doubt that would change (it
could conceivably make things worse).&lt;/p&gt;
&lt;p&gt;So, how much faster is it? Roughly, based on my tests, a site generates in
about 1/4 of the time it did before. On my M2 Mac Mini my blog builds in
under 3 seconds; with v2.21.0 it took around 13 seconds. In my case that's
with all the optional features of BlogMore turned on.&lt;/p&gt;
&lt;p&gt;Naturally this work has touched on a &lt;em&gt;lot&lt;/em&gt; of internals of the code, and
made significant changes to the generation pipelines of lots of different
pages and features. I've done my absolute best to compare&lt;sup id="fnref:297-2"&gt;&lt;a class="footnote-ref" href="#fn:297-2"&gt;2&lt;/a&gt;&lt;/sup&gt; the output of
v2.21.0 and v2.22.0 and I can't see any significant differences&lt;sup id="fnref:297-3"&gt;&lt;a class="footnote-ref" href="#fn:297-3"&gt;3&lt;/a&gt;&lt;/sup&gt;. When
trying out v2.22.0 I would suggest paying just a little extra attention to
the result, to be sure you're happy that nothing has changed.&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li id="fn:297-1"&gt;
&lt;p&gt;It lives in &lt;code&gt;~/.cache/blogmore&lt;/code&gt; on Unix and Unix-like systems, or
&lt;code&gt;%LOCALAPPDATA%\blogmore\cache&lt;/code&gt; on DOS/VMS-influenced systems.&amp;#160;&lt;a class="footnote-backref" href="#fnref:297-1" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:297-2"&gt;
&lt;p&gt;Lots of &lt;code&gt;diff -rq&lt;/code&gt; and then diffing an assorted sample of files that
showed differences to inspect what was actually different.&amp;#160;&lt;a class="footnote-backref" href="#fnref:297-2" title="Jump back to footnote 2 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:297-3"&gt;
&lt;p&gt;Actually, there's a small difference in the context shown in
backlinks, but this was a deliberate change and a very small cosmetic
enhancement.&amp;#160;&lt;a class="footnote-backref" href="#fnref:297-3" title="Jump back to footnote 3 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content>
    <link href="https://blog.davep.org/2026/05/13/blogmore-v2-22-0.html"/>
    <category term="Coding"/>
    <category term="BlogMore"/>
    <category term="Coding"/>
    <category term="PyPI"/>
    <category term="Python"/>
    <published>2026-05-13T13:53:04+01:00</published>
  </entry>
  <entry>
    <id>https://blog.davep.org/2026/05/11/speeding-up-blogmore.html</id>
    <title>Speeding up BlogMore</title>
    <updated>2026-05-11T12:30:25+01:00</updated>
    <content type="html">&lt;p&gt;As &lt;a href="https://blog.davep.org/2026/05/09/blogmore-v2-20-0.html"&gt;mentioned recently&lt;/a&gt;,
&lt;a href="https://www.yakshaving.co.uk/" rel="noopener noreferrer" target="_blank"&gt;Andy&lt;/a&gt; did a nifty bit of testing of
&lt;a href="https://blogmore.davep.dev/" rel="noopener noreferrer" target="_blank"&gt;BlogMore&lt;/a&gt; to &lt;a href="https://www.yakshaving.co.uk/posts/blogmore-performance/" rel="noopener noreferrer" target="_blank"&gt;measure the performance hit of
each of the optional generation
features&lt;/a&gt;.
Performance is something I haven't really spent much time thinking about; I
cared more about how the result looked than I did about how quickly a site
got generated.&lt;/p&gt;
&lt;p&gt;I seem to remember that, early on, I did have a bit of a play with trying to
get Copilot to tackle the idea of parallel generation of parts of the site,
but &lt;a href="https://github.com/davep/blogmore/pull/283" rel="noopener noreferrer" target="_blank"&gt;I didn't seem to get any sort of win out of
it&lt;/a&gt; so I closed the PR and gave
up on the idea.&lt;/p&gt;
&lt;p&gt;Now though, especially having added the elapsed time of each step to the
progress report during generation, I am curious about what wins there are.
One that came to mind right away, and I have been considering dealing with
for a wee while, &lt;a href="https://fosstodon.org/@davep/116540338542474954" rel="noopener noreferrer" target="_blank"&gt;is the fact that every generation downloads the
FontAwesome metadata&lt;/a&gt;.
Caching that would be a simple win, for example.&lt;/p&gt;
&lt;p&gt;But, if I'm going to do this, it makes sense that I stick to the spirit of
this project and get one or more agents to tackle the problem. So I asked
&lt;a href="https://blog.davep.org/tag/gemini/"&gt;Gemini&lt;/a&gt; CLI to look over the codebase and give me some big
easy wins. I made clear I didn't want it to do any work yet, I just wanted
it to report back what it could find.&lt;/p&gt;
&lt;p&gt;The result made a lot of sense. Here's what it said:&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id="performance-optimization-recommendations-for-blogmore"&gt;Performance Optimization Recommendations for BlogMore&lt;a aria-label="Link to this heading" class="heading-anchor" href="#performance-optimization-recommendations-for-blogmore"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I have conducted a comprehensive review of the &lt;code&gt;blogmore&lt;/code&gt; codebase and
identified several areas where measurable and significant speedups can be
achieved, particularly for larger sites.&lt;/p&gt;
&lt;h3 id="1-backlink-snippet-extraction-highest-impact"&gt;1. Backlink Snippet Extraction (Highest Impact)&lt;a aria-label="Link to this heading" class="heading-anchor" href="#1-backlink-snippet-extraction-highest-impact"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The most significant bottleneck is in &lt;code&gt;src/blogmore/backlinks.py&lt;/code&gt;. The
&lt;code&gt;build_backlink_map&lt;/code&gt; function iterates through every internal link in every
post. For each link, it calls &lt;code&gt;_extract_snippet&lt;/code&gt;, which &lt;strong&gt;re-parses the
entire Markdown content of the source post&lt;/strong&gt; to generate a plain-text
snippet.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The Issue:&lt;/strong&gt; If a post has 20 internal links, it is parsed 20 times just
  for backlinks.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Win:&lt;/strong&gt; Modify the logic to insert unique markers for &lt;em&gt;all&lt;/em&gt; links in
  a post and perform a single Markdown-to-plain-text conversion per post.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="2-redundant-markdown-parsing-for-metadata"&gt;2. Redundant Markdown Parsing for Metadata&lt;a aria-label="Link to this heading" class="heading-anchor" href="#2-redundant-markdown-parsing-for-metadata"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;Post&lt;/code&gt; object in &lt;code&gt;src/blogmore/parser.py&lt;/code&gt; has properties like
&lt;code&gt;description&lt;/code&gt; and &lt;code&gt;reading_time&lt;/code&gt; that are accessed frequently (on the post
page, index pages, RSS feeds, search index, etc.).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The Issue:&lt;/strong&gt; &lt;code&gt;Post.description&lt;/code&gt; calls &lt;code&gt;extract_first_paragraph&lt;/code&gt;, and
  &lt;code&gt;Post.reading_time&lt;/code&gt; calls &lt;code&gt;calculate_reading_time&lt;/code&gt;. Both of these
  functions perform a &lt;strong&gt;full Markdown conversion&lt;/strong&gt; every time they are
  called because they are not cached.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Win:&lt;/strong&gt; Use &lt;code&gt;functools.cached_property&lt;/code&gt; on these fields.
  Additionally, these should be calculated by extracting text from the
  &lt;code&gt;html_content&lt;/code&gt; that has already been generated during the initial parse,
  rather than re-parsing the raw Markdown.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="3-sequential-processing"&gt;3. Sequential Processing&lt;a aria-label="Link to this heading" class="heading-anchor" href="#3-sequential-processing"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The generator currently processes everything sequentially in a single
thread.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The Issue:&lt;/strong&gt; For a site with hundreds or thousands of posts, the initial
  Markdown parsing and the subsequent Jinja2 rendering are strictly serial,
  leaving multi-core CPUs mostly idle.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Win:&lt;/strong&gt; Use &lt;code&gt;multiprocessing&lt;/code&gt; (e.g., &lt;code&gt;ProcessPoolExecutor&lt;/code&gt;) to
  parallelize the parsing of Markdown files and the rendering of HTML pages.
  Since these tasks are CPU-bound and independent, this would scale almost
  linearly with the number of CPU cores.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="4-asset-management-inefficiencies"&gt;4. Asset Management Inefficiencies&lt;a aria-label="Link to this heading" class="heading-anchor" href="#4-asset-management-inefficiencies"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;AssetManager&lt;/code&gt; in &lt;code&gt;src/blogmore/generator/assets.py&lt;/code&gt; performs some
operations that could be optimized:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;FontAwesome Metadata:&lt;/strong&gt; It downloads icon metadata from GitHub on
  &lt;em&gt;every&lt;/em&gt; build to optimize CSS. This can be cached locally to avoid network
  latency.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Icon Generation:&lt;/strong&gt; It regenerates favicons and Apple touch icons on
  every build. This should be skipped if the source icon hasn't changed.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="5-minor-algorithmic-improvements"&gt;5. Minor Algorithmic Improvements&lt;a aria-label="Link to this heading" class="heading-anchor" href="#5-minor-algorithmic-improvements"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Navigation Lookups:&lt;/strong&gt; In &lt;code&gt;PageGenerator.generate_post_page&lt;/code&gt;, the code
  uses &lt;code&gt;all_posts.index(post)&lt;/code&gt; to find the current post's position for
  prev/next links. This is an O(N) operation called N times, leading to
  O(N^2) behavior. Pre-calculating a mapping would make this O(1).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Markdown Instance Reuse:&lt;/strong&gt; Several utility functions
  (&lt;code&gt;markdown_to_plain_text&lt;/code&gt;, &lt;code&gt;extract_first_paragraph&lt;/code&gt;) create a fresh
  &lt;code&gt;markdown.Markdown&lt;/code&gt; instance on every call. Reusing a single instance (and
  calling &lt;code&gt;.reset()&lt;/code&gt;) or caching the instance creation would reduce
  overhead.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="summary"&gt;Summary&lt;a aria-label="Link to this heading" class="heading-anchor" href="#summary"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The "Big Wins" are definitely &lt;strong&gt;optimizing backlink snippet extraction&lt;/strong&gt; and
&lt;strong&gt;implementing parallel parsing/rendering&lt;/strong&gt;. Together, these would likely
reduce generation time for large sites by 60-80% on modern hardware.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;At first glance they all seem pretty sensible, and I like that it seems to
have found some really obvious big improvements, and also the little wins
that might have a cumulative effect. I strongly suspect the parallel
generation suggestion is again going to be a non-win (and so for now I'm
going to pass on it), but most of the other suggestions seem doable and
worth trying out.&lt;/p&gt;
&lt;p&gt;I think what I will do, rather than work through them all in one PR, is
tackle one at a time. So, over the next few days, expect to see some PRs
turn up and get merged (hopefully), as I try and tease some speed wins out
of the code.&lt;/p&gt;</content>
    <link href="https://blog.davep.org/2026/05/11/speeding-up-blogmore.html"/>
    <category term="Coding"/>
    <category term="AI"/>
    <category term="BlogMore"/>
    <category term="Coding"/>
    <category term="Gemini"/>
    <category term="Python"/>
    <published>2026-05-11T12:30:25+01:00</published>
  </entry>
  <entry>
    <id>https://blog.davep.org/2026/05/10/blogmore-v2-21-0.html</id>
    <title>BlogMore v2.21.0</title>
    <updated>2026-05-10T10:32:12+01:00</updated>
    <content type="html">&lt;p&gt;After noticing a broken link in a post yesterday, I got to thinking that it
would be useful to add a linter to &lt;a href="https://blogmore.davep.dev/" rel="noopener noreferrer" target="_blank"&gt;BlogMore&lt;/a&gt;.
So I've released &lt;a href="https://blogmore.davep.dev/changelog/#v2210" rel="noopener noreferrer" target="_blank"&gt;v2.21.0&lt;/a&gt;
which adds &lt;a href="https://blogmore.davep.dev/linting/" rel="noopener noreferrer" target="_blank"&gt;linting support&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A number of things are checked and the results are broken down into things
that are errors or warnings. Errors result from any of these checks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ensures all posts and pages have valid YAML frontmatter. If a file cannot
  be parsed, it is reported as an error.&lt;/li&gt;
&lt;li&gt;Scans the generated HTML for links to non-existent internal paths (other
  posts, pages, categories, tags, archives, site features like search, or
  files in &lt;code&gt;extras/&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Checks that all &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; sources resolve to valid
  internal paths or files in the &lt;code&gt;extras/&lt;/code&gt; directory.&lt;/li&gt;
&lt;li&gt;Checks that the &lt;code&gt;cover&lt;/code&gt; property in a post or page frontmatter points to a
  valid resource.&lt;/li&gt;
&lt;li&gt;Verifies that all page slugs listed in &lt;code&gt;sidebar_pages&lt;/code&gt; actually exist.&lt;/li&gt;
&lt;li&gt;Checks that all internal-looking URLs in the &lt;code&gt;links:&lt;/code&gt; and &lt;code&gt;socials:&lt;/code&gt;
  configuration settings point to valid targets.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;On the other hand, the following just result in a warning:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Flags if a post is missing a &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;category&lt;/code&gt;, &lt;code&gt;tags&lt;/code&gt;, or a &lt;code&gt;date&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Reports if a post's &lt;code&gt;date&lt;/code&gt; or &lt;code&gt;modified&lt;/code&gt; date is set in the future.&lt;/li&gt;
&lt;li&gt;Notes if a post's &lt;code&gt;modified&lt;/code&gt; date is earlier than its original publication
  &lt;code&gt;date&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Identifies if two or more posts share the exact same title.&lt;/li&gt;
&lt;li&gt;Flags inline images missing an &lt;code&gt;alt&lt;/code&gt; attribute, or those with an
  empty/whitespace-only &lt;code&gt;alt&lt;/code&gt; attribute.&lt;/li&gt;
&lt;li&gt;If &lt;a href="https://blogmore.davep.dev/configuration/#clean_urls" rel="noopener noreferrer" target="_blank"&gt;&lt;code&gt;clean_urls&lt;/code&gt;&lt;/a&gt; is
  enabled, warns if internal links point explicitly to &lt;code&gt;index.html&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Reports internal links using the full &lt;code&gt;site_url&lt;/code&gt; (e.g.,
  &lt;code&gt;https://example.com/path/&lt;/code&gt;) instead of a root-relative path (&lt;code&gt;/path/&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I feel like all of these cover most of the things that are low-cost to
detect but have a positive impact on the state of the content of a blog.&lt;/p&gt;
&lt;p&gt;One thing I've &lt;em&gt;not&lt;/em&gt; done is any sort of checking of external links. This
would be costly and could possibly have unintended consequences that I don't
want to be messing with (perhaps a tool to &lt;em&gt;export&lt;/em&gt; the list of external
links for checking could be useful, at some point).&lt;/p&gt;
&lt;p&gt;Having run this against this blog, I did find some things that needed
cleaning up, mostly absolute links that could be turned into root-relative
links (always good for making the content portable).&lt;/p&gt;
&lt;p&gt;I'm going to make this a standard part of my &lt;em&gt;"I'm ready to publish"&lt;/em&gt; check
for this blog, and it should also be helpful as I &lt;a href="https://blog.davep.org/2026/05/06/the-webp-migration-is-under-way.html"&gt;carry on migrating the
images in the blog over to
WebP&lt;/a&gt;.&lt;/p&gt;</content>
    <link href="https://blog.davep.org/2026/05/10/blogmore-v2-21-0.html"/>
    <category term="Coding"/>
    <category term="BlogMore"/>
    <category term="Coding"/>
    <category term="PyPI"/>
    <category term="Python"/>
    <published>2026-05-10T10:32:12+01:00</published>
  </entry>
  <entry>
    <id>https://blog.davep.org/2026/05/10/more-syncing-github-to-gitlab-and-codeberg.html</id>
    <title>More syncing GitHub to GitLab and Codeberg</title>
    <updated>2026-05-10T08:58:21+01:00</updated>
    <content type="html">&lt;p&gt;Following on from &lt;a href="https://blog.davep.org/2026/05/08/syncing-github-to-gitlab-and-codeberg.html"&gt;my first post about
this&lt;/a&gt;, I've tweaked
&lt;a href="https://github.com/davep/setup-forge-sync" rel="noopener noreferrer" target="_blank"&gt;the script I'm using to backup a repo to GitLab and
Codeberg&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight" data-lang="sh"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="ch"&gt;#!/bin/sh&lt;/span&gt;

&lt;span class="c1"&gt;# Check if the current directory is a Git repository&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;!&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;rev-parse&lt;span class="w"&gt; &lt;/span&gt;--is-inside-work-tree&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;/dev/null&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Error: This directory is not a Git repository.&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="nv"&gt;REPO_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;

&lt;span class="c1"&gt;# If no repository name was provided, try to get it from the origin remote&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-z&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$REPO_NAME&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;ORIGIN_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;remote&lt;span class="w"&gt; &lt;/span&gt;get-url&lt;span class="w"&gt; &lt;/span&gt;origin&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&amp;gt;/dev/null&lt;span class="k"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-n&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$ORIGIN_URL&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nv"&gt;REPO_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;basename&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;.git&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$ORIGIN_URL&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Error: No repository name provided and no &amp;#39;origin&amp;#39; remote found.&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Usage: &lt;/span&gt;&lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="s2"&gt; &amp;lt;repo-name&amp;gt;&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Configuring multi-forge backup sync for: &lt;/span&gt;&lt;span class="nv"&gt;$REPO_NAME&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;

&lt;span class="c1"&gt;# Set up the remote called backups. Anchor it to Codeberg.&lt;/span&gt;
git&lt;span class="w"&gt; &lt;/span&gt;remote&lt;span class="w"&gt; &lt;/span&gt;remove&lt;span class="w"&gt; &lt;/span&gt;backups&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;/dev/null&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
git&lt;span class="w"&gt; &lt;/span&gt;remote&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;backups&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ssh://git@codeberg.org/davep/&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;REPO_NAME&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.git&amp;quot;&lt;/span&gt;

&lt;span class="c1"&gt;# Set up the push URLs.&lt;/span&gt;
git&lt;span class="w"&gt; &lt;/span&gt;remote&lt;span class="w"&gt; &lt;/span&gt;set-url&lt;span class="w"&gt; &lt;/span&gt;--push&lt;span class="w"&gt; &lt;/span&gt;backups&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ssh://git@codeberg.org/davep/&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;REPO_NAME&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.git&amp;quot;&lt;/span&gt;
git&lt;span class="w"&gt; &lt;/span&gt;remote&lt;span class="w"&gt; &lt;/span&gt;set-url&lt;span class="w"&gt; &lt;/span&gt;--add&lt;span class="w"&gt; &lt;/span&gt;--push&lt;span class="w"&gt; &lt;/span&gt;backups&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git@gitlab.com:davep/&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;REPO_NAME&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.git&amp;quot;&lt;/span&gt;

&lt;span class="c1"&gt;# Only ever backup main.&lt;/span&gt;
git&lt;span class="w"&gt; &lt;/span&gt;config&lt;span class="w"&gt; &lt;/span&gt;remote.backups.push&lt;span class="w"&gt; &lt;/span&gt;refs/heads/main:refs/heads/main

&lt;span class="c1"&gt;# Also backup all tags.&lt;/span&gt;
git&lt;span class="w"&gt; &lt;/span&gt;config&lt;span class="w"&gt; &lt;/span&gt;--add&lt;span class="w"&gt; &lt;/span&gt;remote.backups.push&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;refs/tags/*:refs/tags/*&amp;#39;&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;----------------------------------------------------&amp;quot;&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Backups configured:&amp;quot;&lt;/span&gt;
git&lt;span class="w"&gt; &lt;/span&gt;remote&lt;span class="w"&gt; &lt;/span&gt;-v
&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;----------------------------------------------------&amp;quot;&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;To perform the initial sync, run: git push backups&amp;quot;&lt;/span&gt;

&lt;span class="c1"&gt;### setup-forge-sync ends here&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The changes from last time include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The repo name now defaults to whatever is used for GitHub, so I don't have
  to copy/paste it or type it out.&lt;/li&gt;
&lt;li&gt;It now backs up all the tags too.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I've been running with this for a couple of days now and it's proving really
useful. Well, when &lt;a href="https://blog.davep.org/tag/codeberg/"&gt;Codeberg&lt;/a&gt; is available to push anything
to...&lt;/p&gt;</content>
    <link href="https://blog.davep.org/2026/05/10/more-syncing-github-to-gitlab-and-codeberg.html"/>
    <category term="Coding"/>
    <category term="Codeberg"/>
    <category term="Coding"/>
    <category term="FOSS"/>
    <category term="GitHub"/>
    <category term="GitLab"/>
    <category term="backups"/>
    <category term="git"/>
    <published>2026-05-10T08:58:21+01:00</published>
  </entry>
  <entry>
    <id>https://blog.davep.org/2026/05/09/blogmore-v2-20-0.html</id>
    <title>BlogMore v2.20.0</title>
    <updated>2026-05-09T10:04:23+01:00</updated>
    <content type="html">&lt;p&gt;I've just released &lt;a href="https://blogmore.davep.dev/" rel="noopener noreferrer" target="_blank"&gt;BlogMore&lt;/a&gt;
&lt;a href="https://blogmore.davep.dev/changelog/#v2200" rel="noopener noreferrer" target="_blank"&gt;v2.20.0&lt;/a&gt;. There are five main
changes in this release, and a lot of changes under the hood.&lt;/p&gt;
&lt;p&gt;First, the under-the-hood stuff: while this isn't going to make a difference
to anyone using BlogMore (at least it &lt;em&gt;shouldn't&lt;/em&gt; make a difference -- if it
does that's a bug that &lt;a href="https://github.com/davep/blogmore/issues" rel="noopener noreferrer" target="_blank"&gt;deserves
reporting&lt;/a&gt;), the &lt;a href="https://github.com/davep/blogmore/tree/main/src/blogmore/generator" rel="noopener noreferrer" target="_blank"&gt;main site
generation
code&lt;/a&gt;
has had a lot of work done on it &lt;a href="https://blog.davep.org/2026/05/04/i-wouldnt-start-from-here.html"&gt;to break it
up&lt;/a&gt;. The motivation for this is
to make the code easier to maintain, and to try and steer it in a direction
closer to how I'd have laid things out had I written it by hand. The outcome
of this is that, where the generator was over 2,000 lines of code in a
single file, it's now a lot more modular and easier to follow.&lt;/p&gt;
&lt;p&gt;Some other internals have been cleaned up too. Generally I've had a period
of reviewing some of the code and reducing obvious duplication of effort,
that sort of thing.&lt;/p&gt;
&lt;p&gt;Now for the visible changes and enhancements in this release:&lt;/p&gt;
&lt;h2 id="improved-word-counts"&gt;Improved word counts&lt;a aria-label="Link to this heading" class="heading-anchor" href="#improved-word-counts"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Until now the word counting (and so the reading time calculations) were done
by stripping most of the Markdown and HTML markup from the Markdown source.
I wasn't too keen on this approach given that the codebase had a method of
turning Markdown into plain text. So in this release the regex-based cleanup
code is gone and word counts (and so reading times) use the same Markdown to
plain text pipeline as anything else that needs to work on plain text.&lt;/p&gt;
&lt;h2 id="fixed-a-word-count-and-reading-time-disparity"&gt;Fixed a word count and reading time disparity&lt;a aria-label="Link to this heading" class="heading-anchor" href="#fixed-a-word-count-and-reading-time-disparity"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;It was possible, in the &lt;a href="https://blog.davep.org/stats/"&gt;stats page&lt;/a&gt;, to have one post appear to
have the lowest or highest word count, but to &lt;em&gt;not&lt;/em&gt; have the lowest or
highest reading time. This was because reading times are always calculated
to the minute and so there could be a disparity due to this rounding. The
calculation of those stats now takes this into account.&lt;/p&gt;
&lt;h2 id="added-an-optional-title-to-the-socials"&gt;Added an optional &lt;code&gt;title&lt;/code&gt; to the &lt;code&gt;socials&lt;/code&gt;&lt;a aria-label="Link to this heading" class="heading-anchor" href="#added-an-optional-title-to-the-socials"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;a href="https://blogmore.davep.dev/configuration/#socials" rel="noopener noreferrer" target="_blank"&gt;&lt;code&gt;socials&lt;/code&gt;&lt;/a&gt; setting
in the configuration file has had an optional &lt;code&gt;title&lt;/code&gt; property added for
each entry. Until now the tooltip for an entry would be whatever the &lt;code&gt;site&lt;/code&gt;
was set to. Generally this works but if you have two or more accounts on the
same site, or if you want to use a &lt;code&gt;site&lt;/code&gt; value for something different,
there was no way of making the tooltip more descriptive.&lt;/p&gt;
&lt;p&gt;As an example, currently &lt;a href="https://github.com/davep/blogmore/discussions/303" rel="noopener noreferrer" target="_blank"&gt;it's not possible to support Codeberg as a
&lt;code&gt;site&lt;/code&gt;&lt;/a&gt;. On the other
hand &lt;code&gt;git&lt;/code&gt; is available so it can be used as a substitute icon. The problem
is, with this:&lt;/p&gt;
&lt;div class="highlight" data-lang="yaml"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;site&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;git&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;https://codeberg.org/davep&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;the tooltip will just say &lt;em&gt;"git"&lt;/em&gt;. With this update you can do this:&lt;/p&gt;
&lt;div class="highlight" data-lang="yaml"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;site&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;git&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;Codeberg&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;https://codeberg.org/davep&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;and the tooltip will say &lt;em&gt;"Codeberg"&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;As mentioned: this is optional. If there is no &lt;code&gt;title&lt;/code&gt; the previous
behaviour still applies.&lt;/p&gt;
&lt;h2 id="wall-clock-time-measurement"&gt;Wall-clock time measurement&lt;a aria-label="Link to this heading" class="heading-anchor" href="#wall-clock-time-measurement"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Yesterday, &lt;a href="https://www.yakshaving.co.uk/" rel="noopener noreferrer" target="_blank"&gt;Andy&lt;/a&gt; posted about &lt;a href="https://www.yakshaving.co.uk/posts/blogmore-performance/" rel="noopener noreferrer" target="_blank"&gt;BlogMore's
performance with respect to the different optional
features&lt;/a&gt;. It's
something I haven't really considered yet (possibly in part because this
blog isn't anywhere near as big as his), but could be a good source of
tinkering in the near future. His work to test the different parts of the
tool did get me thinking though: &lt;a href="https://github.com/davep/blogmore/issues/460" rel="noopener noreferrer" target="_blank"&gt;it would be neat to know how long each
part of the generation process
takes&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So now, when a site is generated (either when using &lt;code&gt;build&lt;/code&gt; or &lt;code&gt;serve&lt;/code&gt;), the
time of each step is printed, as is the overall generation time.&lt;/p&gt;
&lt;h2 id="markdown-in-html-support"&gt;Markdown in HTML support&lt;a aria-label="Link to this heading" class="heading-anchor" href="#markdown-in-html-support"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Yesterday I noticed that, &lt;a href="https://blog.davep.org/2023/10/20/a-new-guitar.html"&gt;on one of my
posts&lt;/a&gt;, what had been written as a simple
caption for an image wasn't rendering as it used to. The actual content of
the Markdown source for the post contained this:&lt;/p&gt;
&lt;div class="highlight" data-lang="markdown"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&amp;lt;center&amp;gt;
*(Yes, the tin was once mine and was once full; the early 90s were a
different time)*
&amp;lt;/center&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;While the text was centred, the raw Markdown was left in place (it should
have been italic text). The reason for this is that BlogMore had never
enabled Markdown-in-HTML support. So, as of this release, if the enclosing
tag has &lt;code&gt;markdown="1"&lt;/code&gt;, any Markdown inside the tags will be parsed. This
means the above becomes this:&lt;/p&gt;
&lt;div class="highlight" data-lang="markdown"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&amp;lt;center markdown=&amp;quot;1&amp;quot;&amp;gt;
*(Yes, the tin was once mine and was once full; the early 90s were a
different time)*
&amp;lt;/center&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I did think about doing something to turn it on by default (the fact that I
didn't have such a "switch" in the post before suggests that
&lt;a href="https://getpelican.com/" rel="noopener noreferrer" target="_blank"&gt;Pelican&lt;/a&gt; did just always do this), but really I
feel this approach is more flexible and less likely to result in unintended
consequences.&lt;/p&gt;</content>
    <link href="https://blog.davep.org/2026/05/09/blogmore-v2-20-0.html"/>
    <category term="Coding"/>
    <category term="BlogMore"/>
    <category term="Coding"/>
    <category term="PyPI"/>
    <category term="Python"/>
    <published>2026-05-09T10:04:23+01:00</published>
  </entry>
  <entry>
    <id>https://blog.davep.org/2026/05/08/more-codeberg-issues.html</id>
    <title>More Codeberg issues</title>
    <updated>2026-05-08T18:08:10+01:00</updated>
    <content type="html">&lt;p&gt;As &lt;a href="https://blog.davep.org/2026/04/29/on-github.html"&gt;I've said earlier&lt;/a&gt;, I'm not looking to move
off GitHub any time soon, but &lt;a href="https://blog.davep.org/2026/05/08/syncing-github-to-gitlab-and-codeberg.html"&gt;I am curious about evaluating the
options&lt;/a&gt;. So far,
while &lt;a href="https://blog.davep.org/2026/05/06/trying-out-codeberg.html"&gt;trying out Codeberg&lt;/a&gt;, I am
finding it to be very unstable.&lt;/p&gt;
&lt;p&gt;A little earlier I was simply browsing some of the repositories I've been
adding and got &lt;em&gt;very&lt;/em&gt; slow load times, and then a &lt;code&gt;500&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Codeberg 500 error" height="555" loading="lazy" src="https://blog.davep.org/attachments/2026/05/08/codeberg-500.webp#centre" width="686" /&gt;&lt;/p&gt;
&lt;p&gt;As I've said elsewhere: I really wouldn't expect perfection. I doubt that
Codeberg has the money behind it that GitHub does. But, again, there is that
issue with moving off GitHub because of
&lt;a href="https://damrnelson.github.io/github-historical-uptime/" rel="noopener noreferrer" target="_blank"&gt;instability&lt;/a&gt;; from
that point of view it would feel like swapping some occasional instability
for what, at the moment, is feeling like regular instability to the point
that I wouldn't get too much done.&lt;/p&gt;</content>
    <link href="https://blog.davep.org/2026/05/08/more-codeberg-issues.html"/>
    <category term="Coding"/>
    <category term="Coding"/>
    <category term="FOSS"/>
    <category term="Codeberg"/>
    <published>2026-05-08T18:08:10+01:00</published>
  </entry>
  <entry>
    <id>https://blog.davep.org/2026/05/08/syncing-github-to-gitlab-and-codeberg.html</id>
    <title>Syncing GitHub to GitLab and Codeberg</title>
    <updated>2026-05-08T08:25:03+01:00</updated>
    <content type="html">&lt;p&gt;I've had a &lt;a href="https://gitlab.com/davep" rel="noopener noreferrer" target="_blank"&gt;GitLab account&lt;/a&gt; since December 2017.
This came about &lt;a href="https://blog.davep.org/2017/12/12/on_to_something_new.html"&gt;because of the new job I started in January
2018&lt;/a&gt;. They used a self-hosted
internal instance of GitLab for all their code, so it made sense I get
familiar with it (it wasn't hard; especially back in 2017 it was near enough
a clone of GitHub in terms of what it did). Since then, though, I've never
really done anything with it. I think I had a repo or two on there for a
short while, but I must have nuked them at some point because the account
has been empty for the longest time.&lt;/p&gt;
&lt;p&gt;A &lt;a href="https://codeberg.org/davep" rel="noopener noreferrer" target="_blank"&gt;Codeberg&lt;/a&gt; account, on the other hand, &lt;a href="https://blog.davep.org/2026/05/06/trying-out-codeberg.html"&gt;only
got created the other day&lt;/a&gt;. Having
created this, I got to thinking about how I might use it. In doing so I
thought back to my GitLab account and then also got to thinking about where
all my public code lives, and how "safe" it is.&lt;/p&gt;
&lt;p&gt;Now, sure, the whole point of &lt;a href="https://blog.davep.org/tag/git/"&gt;Git&lt;/a&gt; is that it's distributed.
Forges are a useful thing to have and work with, but they shouldn't be &lt;em&gt;the&lt;/em&gt;
place where your code lives. On the other hand, I've had so many machines,
and so many work environments, that it has become the case that &lt;a href="https://github.com/davep" rel="noopener noreferrer" target="_blank"&gt;my GitHub
account&lt;/a&gt; has become &lt;em&gt;the&lt;/em&gt; storage location for my
code and projects.&lt;/p&gt;
&lt;p&gt;Mostly this is fine. If GitHub were to disappear tomorrow I imagine we'd all
have bigger things to be worrying about anyway. But the principle stands:
why not distribute the load? Why not distribute the effort when it comes to
sharing code I write?&lt;/p&gt;
&lt;p&gt;So yesterday I finally decided on a plan: for the moment at least, I'm going
to keep using GitHub as my "primary" location for working on stuff. It's
where I'll have WiP branches, it's where I'll keep issues, it's where I'll
encourage people to raise requests and stuff, it's where I'll host this
blog. But I'm going to start syncing projects to both GitLab and Codeberg. I
see this as having two benefits: anyone who doesn't want to interact with
GitHub can now easily fork code, and if they wish they can raise issues and
the like too. Meanwhile, in doing this, I'll also have the added benefit of
my code being "backed up" in at least three different locations&lt;sup id="fnref:331-1"&gt;&lt;a class="footnote-ref" href="#fn:331-1"&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;The approach I've settled on, for the moment, is based around &lt;a href="https://github.com/davep/setup-forge-sync" rel="noopener noreferrer" target="_blank"&gt;this little
shell script&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight" data-lang="sh"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="ch"&gt;#!/bin/sh&lt;/span&gt;

&lt;span class="c1"&gt;# Check if a repository name was provided&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-z&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Error: No repository name provided.&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Usage: &lt;/span&gt;&lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="s2"&gt; &amp;lt;repo-name&amp;gt;&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="nv"&gt;REPO_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;

&lt;span class="c1"&gt;# Check if the current directory is a Git repository&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;!&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;rev-parse&lt;span class="w"&gt; &lt;/span&gt;--is-inside-work-tree&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;/dev/null&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;then&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Error: This directory is not a Git repository.&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Configuring multi-forge backup sync for: &lt;/span&gt;&lt;span class="nv"&gt;$REPO_NAME&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;

&lt;span class="c1"&gt;# Set up the remote called backups. Anchor it to Codeberg.&lt;/span&gt;
git&lt;span class="w"&gt; &lt;/span&gt;remote&lt;span class="w"&gt; &lt;/span&gt;remove&lt;span class="w"&gt; &lt;/span&gt;backups&lt;span class="w"&gt; &lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;/dev/null&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
git&lt;span class="w"&gt; &lt;/span&gt;remote&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;backups&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ssh://git@codeberg.org/davep/&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;REPO_NAME&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.git&amp;quot;&lt;/span&gt;

&lt;span class="c1"&gt;# Set up the push URLs.&lt;/span&gt;
git&lt;span class="w"&gt; &lt;/span&gt;remote&lt;span class="w"&gt; &lt;/span&gt;set-url&lt;span class="w"&gt; &lt;/span&gt;--push&lt;span class="w"&gt; &lt;/span&gt;backups&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ssh://git@codeberg.org/davep/&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;REPO_NAME&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.git&amp;quot;&lt;/span&gt;
git&lt;span class="w"&gt; &lt;/span&gt;remote&lt;span class="w"&gt; &lt;/span&gt;set-url&lt;span class="w"&gt; &lt;/span&gt;--add&lt;span class="w"&gt; &lt;/span&gt;--push&lt;span class="w"&gt; &lt;/span&gt;backups&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;git@gitlab.com:davep/&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;REPO_NAME&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.git&amp;quot;&lt;/span&gt;

&lt;span class="c1"&gt;# Only ever backup main.&lt;/span&gt;
git&lt;span class="w"&gt; &lt;/span&gt;config&lt;span class="w"&gt; &lt;/span&gt;remote.backups.push&lt;span class="w"&gt; &lt;/span&gt;refs/heads/main:refs/heads/main

&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;----------------------------------------------------&amp;quot;&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Backups configured:&amp;quot;&lt;/span&gt;
git&lt;span class="w"&gt; &lt;/span&gt;remote&lt;span class="w"&gt; &lt;/span&gt;-v
&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;----------------------------------------------------&amp;quot;&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;To perform the initial sync, run: git push backups main&amp;quot;&lt;/span&gt;

&lt;span class="c1"&gt;### setup-forge-sync ends here&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I'm going to keep all repo names the same&lt;sup id="fnref:331-2"&gt;&lt;a class="footnote-ref" href="#fn:331-2"&gt;2&lt;/a&gt;&lt;/sup&gt;. So when I use this script
it'll set things up so I can &lt;code&gt;git push backups&lt;/code&gt; and &lt;code&gt;main&lt;/code&gt; will then get
pushed up to both GitLab and Codeberg. I don't feel the need to be keeping
any WiP branches in sync or kicking about, likewise any &lt;code&gt;gh-pages&lt;/code&gt; branches.&lt;/p&gt;
&lt;p&gt;While I'm sure I could have done something a little more automated, this
feels like a neat and simple approach, and also allows me to curate what
appears in the two other places over time (I suppose, eventually, I'll
mirror everything that isn't a dead experimental repo, but meanwhile I'll
prioritise projects that are either still very useful or which I'm actively
developing and maintaining).&lt;/p&gt;
&lt;div class="footnote"&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li id="fn:331-1"&gt;
&lt;p&gt;Yes, I have other backups too, but they're always
current-working-machine type backups.&amp;#160;&lt;a class="footnote-backref" href="#fnref:331-1" title="Jump back to footnote 1 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:331-2"&gt;
&lt;p&gt;Except, perhaps, for any repo whose name starts with &lt;code&gt;.&lt;/code&gt;; I seem to
recall that GitLab can't handle that, for some bizarre reason. Perhaps
that's fixed now?&amp;#160;&lt;a class="footnote-backref" href="#fnref:331-2" title="Jump back to footnote 2 in the text"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content>
    <link href="https://blog.davep.org/2026/05/08/syncing-github-to-gitlab-and-codeberg.html"/>
    <category term="Coding"/>
    <category term="Codeberg"/>
    <category term="Coding"/>
    <category term="FOSS"/>
    <category term="GitHub"/>
    <category term="GitLab"/>
    <category term="backups"/>
    <category term="git"/>
    <published>2026-05-08T08:25:03+01:00</published>
  </entry>
  <entry>
    <id>https://blog.davep.org/2026/05/07/all-i-did-was-open-it.html</id>
    <title>All I did was open it</title>
    <updated>2026-05-07T19:24:32+01:00</updated>
    <content type="html">&lt;p&gt;I'm out and about, &lt;a href="https://blog.davep.org/tag/macbook-air/"&gt;MacBook Air&lt;/a&gt; in tow, with a couple of
hours to kill. So I dip into a coffee shop and decide to tinker with stuff.
I open up &lt;a href="https://blog.davep.org/tag/codeberg/"&gt;Codeberg&lt;/a&gt; and...&lt;/p&gt;
&lt;p&gt;&lt;img alt="Denied" height="908" loading="lazy" src="https://blog.davep.org/attachments/2026/05/07/codeberg.webp#centre" width="1364" /&gt;&lt;/p&gt;
&lt;p&gt;Okay, guess I'll be using something else at the moment then. So far &lt;a href="https://blog.davep.org/2026/05/06/trying-out-codeberg.html"&gt;I'm not
having a lot of success&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;PS: That &lt;code&gt;.&lt;/code&gt; after the &lt;code&gt;:&lt;/code&gt; though...&lt;/p&gt;</content>
    <link href="https://blog.davep.org/2026/05/07/all-i-did-was-open-it.html"/>
    <category term="Coding"/>
    <category term="Coding"/>
    <category term="FOSS"/>
    <category term="Codeberg"/>
    <published>2026-05-07T19:24:32+01:00</published>
  </entry>
  <entry>
    <id>https://blog.davep.org/2026/05/06/trying-out-codeberg.html</id>
    <title>Trying out Codeberg</title>
    <updated>2026-05-06T18:22:29+01:00</updated>
    <content type="html">&lt;p&gt;Following on from &lt;a href="https://blog.davep.org/2026/04/29/on-github.html"&gt;what I wrote about GitHub
recently&lt;/a&gt;, I thought I'd check out
&lt;a href="https://codeberg.org/" rel="noopener noreferrer" target="_blank"&gt;Codeberg&lt;/a&gt;. While I'm aware of it (it is
&lt;a href="https://hike.davep.dev/commands/#codeberg" rel="noopener noreferrer" target="_blank"&gt;supported&lt;/a&gt; by
&lt;a href="https://hike.davep.dev/" rel="noopener noreferrer" target="_blank"&gt;Hike&lt;/a&gt; after all), I've never actually used it and
have never had an account there.&lt;/p&gt;
&lt;p&gt;I was delighted to find &lt;a href="https://codeberg.org/davep" rel="noopener noreferrer" target="_blank"&gt;my preferred user name was actually
available&lt;/a&gt;, which made me more inclined to
create an account. The account creation process itself seemed a bit of a
faff. I can't remember the details now, but it took a couple of goes and
necessitated a password reset and recovery to actually get in (I recall
there was some sort of error when I tried to use the activation link sent in
the email, which took quite a while to turn up, but the rest is hazy, and I
wasn't taking notes). It might be that it didn't help that I was signing up
in a mobile browser.&lt;/p&gt;
&lt;p&gt;That aside, though, I got up and going in the end.&lt;/p&gt;
&lt;p&gt;At this point I'm not quite sure what I want to do with the account. While
I'm nowhere near being willing or able to move my GitHub activity over
there, it at least feels like it might be a viable backup location via which
I can make code available. Also, and probably more importantly, it makes it
easier for me to follow projects that use it as their primary (or only)
forge.&lt;/p&gt;
&lt;p&gt;This morning I found myself following a link to &lt;a href="https://codeberg.org/Izder456/cl-chess" rel="noopener noreferrer" target="_blank"&gt;a project hosted
there&lt;/a&gt;. It looked interesting, it
looked like something I'd want to follow. It didn't go so well. I hit the
star button and... busy circle.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Slow star" height="52" loading="lazy" src="https://blog.davep.org/attachments/2026/05/06/slow-star.webp#centre" width="449" /&gt;&lt;/p&gt;
&lt;p&gt;This lasted for a while with nothing happening. So I refreshed the page, no
star on the repo, so I tried again. Same result. I tried a third time and
got something different right away.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Codeberg error" height="199" loading="lazy" src="https://blog.davep.org/attachments/2026/05/06/star-error.webp#centre" width="1108" /&gt;&lt;/p&gt;
&lt;p&gt;At some point I ended up with a big fat &lt;code&gt;500&lt;/code&gt; page.&lt;/p&gt;
&lt;p&gt;Do I see this as an issue? Of course not. Could be I caught it on a bad day.
Stuff happens. I've seen a &lt;code&gt;500&lt;/code&gt; from things I'm responsible for before now.
I'm not going to expect perfection. But it does remind me that leaping from
one forge to another, to chase platform stability, and in doing so give up a
long history, is a risky endeavour without a guaranteed payoff.&lt;/p&gt;</content>
    <link href="https://blog.davep.org/2026/05/06/trying-out-codeberg.html"/>
    <category term="Coding"/>
    <category term="Coding"/>
    <category term="FOSS"/>
    <category term="Codeberg"/>
    <published>2026-05-06T18:22:29+01:00</published>
  </entry>
  <entry>
    <id>https://blog.davep.org/2026/05/04/blogmore-v2-19-0.html</id>
    <title>BlogMore v2.19.0</title>
    <updated>2026-05-04T09:07:26+01:00</updated>
    <content type="html">&lt;p&gt;While I'm messing around in the background of
&lt;a href="https://blogmore.davep.dev/" rel="noopener noreferrer" target="_blank"&gt;BlogMore&lt;/a&gt;, looking at the state of the code
and looking for opportunities to clean it up, either &lt;a href="https://blog.davep.org/2026/05/02/me-vs-claude.html"&gt;by
hand&lt;/a&gt;, or by &lt;a href="https://blog.davep.org/2026/04/30/a-different-approach.html"&gt;pitting agent against
agent&lt;/a&gt;, I've also been doing the odd
little fix here and there.&lt;/p&gt;
&lt;p&gt;I've just released &lt;a href="https://blogmore.davep.dev/changelog/#v2190" rel="noopener noreferrer" target="_blank"&gt;BlogMore
v2.19.0&lt;/a&gt;, which has a couple of
fixes, and also a small improvement.&lt;/p&gt;
&lt;p&gt;The first fix is something I noticed late on last week when I was sharing
one of the archive pages from my blog with someone. I noticed that the
preview that appeared didn't have the default blog image, nor did it have
any sort of description. This &lt;em&gt;should&lt;/em&gt; happen in that, on any page that
isn't a post with a specific cover image or description, it should fall back
to the blog's defaults. Turns out this logic was missing from things like
the date-based archives, the category and tag archives, and a number of
other parts of the generated output.&lt;/p&gt;
&lt;p&gt;That's &lt;a href="https://github.com/davep/blogmore/pull/432" rel="noopener noreferrer" target="_blank"&gt;now fixed&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The second fix is to the &lt;a href="https://blog.davep.org/2026/04/25/blogmore-v2-16-0.html"&gt;recently-added&lt;/a&gt;
backlinks feature. While reviewing the effect of something else I was
working on, I specifically noticed that &lt;a href="https://blog.davep.org/2016/11/15/seen_by_davep_(the_return).html"&gt;this
post&lt;/a&gt; didn't have a backlink
section at the bottom, despite the fact that it was linked to from &lt;a href="https://blog.davep.org/2017/03/08/hello_google_pixel.html"&gt;this
post&lt;/a&gt;. The cause seemed pretty clear:
the fact that I had parentheses in the URL. My guess was that the regex that
the link-finding code uses wasn't taking this sort of thing into account;
&lt;a href="https://github.com/davep/blogmore/pull/445/changes" rel="noopener noreferrer" target="_blank"&gt;my guess was right&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The final change in this release is that the per-build cache-busting feature
has been extended to &lt;em&gt;all&lt;/em&gt; the JavaScript files that are generated when
building the site. Before it was mostly only applied to the main stylesheets
and a couple of long-standing bits of JavaScript. Now it's added to the code
that's used for &lt;a href="https://blog.davep.org/search/"&gt;search&lt;/a&gt;, the code-block support code, the
&lt;a href="https://blog.davep.org/graph/"&gt;graph&lt;/a&gt;, etc. This means that if there are any changes in those
files between builds and deployments of a site, there's less chance of
unexpected behaviour that needs a "clear the cache first" fix.&lt;/p&gt;</content>
    <link href="https://blog.davep.org/2026/05/04/blogmore-v2-19-0.html"/>
    <category term="Coding"/>
    <category term="BlogMore"/>
    <category term="Coding"/>
    <category term="PyPI"/>
    <category term="Python"/>
    <published>2026-05-04T09:07:26+01:00</published>
  </entry>
  <entry>
    <id>https://blog.davep.org/2026/04/29/on-github.html</id>
    <title>On GitHub</title>
    <updated>2026-04-29T08:30:23+01:00</updated>
    <content type="html">&lt;p&gt;It seems that dunking on &lt;a href="https://github.com/" rel="noopener noreferrer" target="_blank"&gt;GitHub&lt;/a&gt; is the flavour of the
day. At the moment most of the social/news type things I tend to read are
filled with &lt;a href="https://mitchellh.com/writing/ghostty-leaving-github" rel="noopener noreferrer" target="_blank"&gt;the Ghostty
news&lt;/a&gt;, as well as a
small revival of posts and links to blog posts about &lt;a href="https://github.com/outages/github-outages" rel="noopener noreferrer" target="_blank"&gt;all the recent
outages&lt;/a&gt;. It's understandable. It
does seem that something has shifted with GitHub in the last few months.
While it hasn't been the site I used to enjoy for quite some time now, it
just seems to be getting worse at the moment.&lt;/p&gt;
&lt;p&gt;It's even pissing off &lt;a href="https://www.reddit.com/r/GithubCopilot/comments/1sxge4u/github_copilot_is_moving_to_usagebased_billing/" rel="noopener noreferrer" target="_blank"&gt;the loyal AI enthusiasts with the Copilot
changes&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As I read all of this I find myself mostly nodding along. For the most part
I'm &lt;em&gt;not&lt;/em&gt; finding that GitHub is getting in the way and stopping me from
doing the things I want to do, and for the most part it &lt;em&gt;does&lt;/em&gt; act as a
vital tool that lets me get work done, and also lets me enjoy my
&lt;a href="https://blog.davep.org/2023/08/14/website-misc-stuff-moved.html"&gt;longest-enjoyed hobby&lt;/a&gt;. On the
other hand I couldn't help but sigh and think &lt;em&gt;"yeah, I get why this is the
time that people are done"&lt;/em&gt; when I opened up the PR page for this blog, just
now, and saw this:&lt;/p&gt;
&lt;p&gt;&lt;img alt="A warning about my PRs" height="113" loading="lazy" src="https://blog.davep.org/attachments/2026/04/29/github-search.webp#centre" width="558" /&gt;&lt;/p&gt;
&lt;p&gt;It does get me thinking about my relationship with GitHub, and how long I've
been using it. As &lt;a href="https://blog.davep.org/2023/10/01/all-green-on-github.html"&gt;I've written
before&lt;/a&gt;, I created my account back in
2008; I was &lt;a href="https://api.github.com/users/davep" rel="noopener noreferrer" target="_blank"&gt;within the first 30,000
users&lt;/a&gt;. While my use of it was only very
occasional for quite a long time, for the last decade I've been constantly
interacting with it. It is somewhere I visit constantly, not just to do work
on my own projects, but to read what other people are doing. One of the
first things I do every morning, when I sit down at my desk, is open my
GitHub dashboard page and have a scroll through the feed to see what people
I follow have been up to.&lt;/p&gt;
&lt;p&gt;It's generally been the most fulfilling feed I've read.&lt;/p&gt;
&lt;p&gt;But I'm also getting that feeling I got when I hung on to my Twitter account
far longer than I really should have; not just because of the general vibe
of &lt;em&gt;"it's falling apart"&lt;/em&gt;, but also &lt;a href="https://github.com/drop-ice/dear-github-2.0" rel="noopener noreferrer" target="_blank"&gt;other types of questionable
behaviour&lt;/a&gt;. The degrading
performance, the troubling business relationships, the over-emphasis on all
things AI... it adds up.&lt;/p&gt;
&lt;p&gt;There is a sense that some time ago was the time to move elsewhere as my
sociable forge (probably around the time that Microsoft took over), and that
not having done that, now is the second best time. But the effort of making
that move &lt;a href="https://github.com/davep?tab=repositories&amp;amp;type=source" rel="noopener noreferrer" target="_blank"&gt;is
non-trivial&lt;/a&gt; and,
quite frankly, I'd want to see where folk start to land, if they started to
move away in any numbers at all. For me the real utility of GitHub isn't the
&lt;em&gt;"it's somewhere to store my shit"&lt;/em&gt; thing, it's the socially coding thing.&lt;/p&gt;
&lt;p&gt;Then there's the follow-up problem: if some other forge was to become the
next flavour of the decade, it too would probably end up suffering the same
fate as GitHub.&lt;/p&gt;
&lt;p&gt;Perhaps now is the time for me to start looking into options for
collaborative code forges that &lt;a href="https://forgefed.org/" rel="noopener noreferrer" target="_blank"&gt;offer the same sort of
solution&lt;/a&gt; that Mastodon does for Twitter-like
nattering.&lt;/p&gt;</content>
    <link href="https://blog.davep.org/2026/04/29/on-github.html"/>
    <category term="Coding"/>
    <category term="Coding"/>
    <category term="FOSS"/>
    <category term="GitHub"/>
    <published>2026-04-29T08:30:23+01:00</published>
  </entry>
  <entry>
    <id>https://blog.davep.org/2026/04/28/blogmore-v2-18-0.html</id>
    <title>BlogMore v2.18.0</title>
    <updated>2026-04-28T15:28:30+01:00</updated>
    <content type="html">&lt;p&gt;After &lt;a href="https://blog.davep.org/2026/04/27/blogmore-v2-17-0.html"&gt;releasing the graph view
yesterday&lt;/a&gt; I got to thinking that it
might be nice if the "tooltips" for the nodes in the graph were a little
richer. Since we already know how many posts are within a category, or have
a specific tag, it makes sense that those counts should be shown; posts
themselves have descriptions available and some even have cover images that
could be turned into thumbnails. Why not make use of all of that?&lt;/p&gt;
&lt;p&gt;So I've made use of all of that. As mentioned, categories and tags simply
show the count of posts related to them:&lt;/p&gt;
&lt;p&gt;&lt;img alt="A tag tooltip in the graph" height="237" loading="lazy" src="https://blog.davep.org/attachments/2026/04/28/tag-tooltip.webp#centre" width="286" /&gt;&lt;/p&gt;
&lt;p&gt;Posts will show the title, date, description and the cover image if
available:&lt;/p&gt;
&lt;p&gt;&lt;img alt="A post showing its tooltip" height="330" loading="lazy" src="https://blog.davep.org/attachments/2026/04/28/post-tooltip.webp#centre" width="410" /&gt;&lt;/p&gt;
&lt;p&gt;I'll admit that the transparency is a little distracting -- this comes from
the library being used for the graph -- but I kind of like it. I'm going to
roll with it now and see how I feel about it as time goes on. It's not like
I expect a reader to &lt;em&gt;read&lt;/em&gt; the post in the tooltip, it's an invitation to
click through and read the actual post.&lt;/p&gt;
&lt;p&gt;Another small change is something I've been meaning to address for a while.
While &lt;a href="https://blogmore.davep.dev/" rel="noopener noreferrer" target="_blank"&gt;BlogMore&lt;/a&gt; supports &lt;a href="https://blogmore.davep.dev/writing_a_post/#modified" rel="noopener noreferrer" target="_blank"&gt;a &lt;code&gt;modified&lt;/code&gt; time
for a post&lt;/a&gt; it never
shows it or uses it in any meaningful way. So now I've updated the way the
time of a post is displayed so that, if there is a modified time, it's also
shown:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Showing when a post was last modified" height="113" loading="lazy" src="https://blog.davep.org/attachments/2026/04/28/modified-post.webp#centre" width="550" /&gt;&lt;/p&gt;
&lt;p&gt;The final change &lt;a href="https://mastodon.me.uk/@andyc/116476967819138734" rel="noopener noreferrer" target="_blank"&gt;came in as a request over on
Mastodon&lt;/a&gt;. The wish being
that there was an easy method, that didn't require the user spin out their
own copy of a template just to do it, of changing the title of the backlinks
section on a post from &lt;em&gt;"References &amp;amp; Mentions"&lt;/em&gt; to something else. That
seemed fair so I've introduced
&lt;a href="https://blogmore.davep.dev/configuration/#backlinks_title" rel="noopener noreferrer" target="_blank"&gt;&lt;code&gt;backlinks_title&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;</content>
    <link href="https://blog.davep.org/2026/04/28/blogmore-v2-18-0.html"/>
    <category term="Coding"/>
    <category term="BlogMore"/>
    <category term="Coding"/>
    <category term="PyPI"/>
    <category term="Python"/>
    <category term="graph"/>
    <published>2026-04-28T15:28:30+01:00</published>
  </entry>
  <entry>
    <id>https://blog.davep.org/2026/04/27/blogmore-v2-17-0.html</id>
    <title>BlogMore v2.17.0</title>
    <updated>2026-04-27T08:40:40+01:00</updated>
    <content type="html">&lt;p&gt;I did some more tinkering with &lt;a href="https://blogmore.davep.dev/" rel="noopener noreferrer" target="_blank"&gt;BlogMore&lt;/a&gt;
yesterday, adding two new features. The first is one I've been considering
adding for a wee while now.&lt;/p&gt;
&lt;p&gt;For a large part of the lifetime of this blog I used Disqus to provide a
comments section on every post. It was, as you'd imagine for a small
personal blog, a pretty quiet thing; I'd get the odd comment from time to
time but it wasn't significant. This worked well for the longest time, until
Disqus decided that they were going to force adverts into your pages if you
were using the free tier. Now, I'm fine with paying for tools I use, but I
wasn't using Disqus enough to make the cost worth it. I'm also not opposed
to a bit of subtle advertising to help cover costs either.&lt;/p&gt;
&lt;p&gt;What Disqus did wasn't subtle. It was far from subtle. It was a horror show
of the worst kind of sleazy advertising you can imagine.&lt;/p&gt;
&lt;p&gt;So I removed it and called it a day on comments.&lt;/p&gt;
&lt;p&gt;After &lt;a href="https://blog.davep.org/tag/blogmore/"&gt;the work on BlogMore&lt;/a&gt; was well under way I did start
thinking about this problem again. Given how BlogMore is constructed, anyone
using it could override a template and include whatever they want; with this
in mind I looked at static-site-friendly comment options but nothing really
stood out. Every solution seemed to either heavily rely on a third party
service (see above for possible problems), self-hosting such a service
(spinning up hosts and web servers and databases and stuff is the antithesis
of using a static site generator to get stuff done easily), or some hacky
use of a social media platform or other discussion venue that would require
the reader jump through hoops that really looks like &lt;em&gt;"go away, I don't want
to hear from you"&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;So I concluded that it just wasn't worth the effort and I've done nothing
with it.&lt;/p&gt;
&lt;p&gt;Meanwhile: on occasion I &lt;em&gt;have&lt;/em&gt; had people just email me about a post. Good
old email, like in the good old days of the Internet. I kind of liked that.
In fact I really liked that. So over the weekend, after receiving just such
an email the other day, I decided I'd add a feature to BlogMore that
provided just that: an invitation to send an email at the end of every post.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://blogmore.davep.dev/configuration/" rel="noopener noreferrer" target="_blank"&gt;configuration file&lt;/a&gt; now has
two new properties that support this. The first is
&lt;a href="https://blogmore.davep.dev/configuration/#invite_comments" rel="noopener noreferrer" target="_blank"&gt;&lt;code&gt;invite_comments&lt;/code&gt;&lt;/a&gt;.
This is a boolean value that simply turns on or off the feature. The second
is
&lt;a href="https://blogmore.davep.dev/configuration/#invite_comments_to" rel="noopener noreferrer" target="_blank"&gt;&lt;code&gt;invite_comments_to&lt;/code&gt;&lt;/a&gt;.
This should be set to an email address that the reader will be invited to
direct their comment or question or whatever.&lt;/p&gt;
&lt;p&gt;I've made the latter a little smart, in that it's actually a template, so
that you can control the email address used per-post. This could be great
for filtering, etc. Examples could be:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;blog-comment@example.com&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;blog-comment-{year}{month}{day}@example.com&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;{author}+comment@example.com&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And so on. You get the idea.&lt;/p&gt;
&lt;p&gt;Further to this there's also &lt;a href="https://blogmore.davep.dev/writing_a_post/#frontmatter" rel="noopener noreferrer" target="_blank"&gt;post frontmatter
properties&lt;/a&gt; of the
same name. In this case the frontmatter setting &lt;em&gt;always&lt;/em&gt; overrides the
configuration file setting, for that single post. Also the
&lt;code&gt;invite_comments_to&lt;/code&gt; frontmatter setting &lt;em&gt;isn't&lt;/em&gt; a template -- it's being
set for a single post so that didn't seem necessary. The point of the
frontmatter is it gives the flexibility to turn the invite off for an
individual post (or indeed turn it on if the global setting is for it to be
off).&lt;/p&gt;
&lt;p&gt;The effect of all of this is that, if the invitation setting is on and if
there is an email address available, this little box will appear at the
bottom of a post:&lt;/p&gt;
&lt;p&gt;&lt;img alt="An invitation to send me an email" height="75" loading="lazy" src="https://blog.davep.org/attachments/2026/04/27/comment-invite.webp#centre" width="571" /&gt;&lt;/p&gt;
&lt;p&gt;When the reader clicks on the link it should open their
&lt;a href="https://en.wikipedia.org/wiki/Email_client" rel="noopener noreferrer" target="_blank"&gt;MUA&lt;/a&gt; of choice and pre-fill the
&lt;code&gt;to&lt;/code&gt; address, and should also pre-fill the subject with the title of the
post they're emailing from.&lt;/p&gt;
&lt;p&gt;The second addition is prompted by the final paragraph in &lt;a href="https://blog.davep.org/2026/04/25/blogmore-v2-16-0.html"&gt;the post
announcing the previous release of
BlogMore&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;At some point in the future it might be interesting to take this even
further and produce a map of interconnected posts; for now though I think
this is enough.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Apparently "some time in the future" was the following day; because that
also got added while I was hacking on the sofa. There's a new
&lt;a href="https://blogmore.davep.dev/command_line/#-with-graph" rel="noopener noreferrer" target="_blank"&gt;&lt;code&gt;--with-graph&lt;/code&gt; command line
option&lt;/a&gt;, and
&lt;a href="https://blogmore.davep.dev/configuration/#with_graph" rel="noopener noreferrer" target="_blank"&gt;&lt;code&gt;with_graph&lt;/code&gt; configuration file
setting&lt;/a&gt;, that adds a
&lt;code&gt;Graph&lt;/code&gt; page to the top "menu" of the blog. The result looks something like
this:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Initial graph view" height="1101" loading="lazy" src="https://blog.davep.org/attachments/2026/04/27/graph-initial.webp#centre" width="1117" /&gt;&lt;/p&gt;
&lt;p&gt;Given the nature of the graph and that the viewer is naturally going to want
to explore, it can be toggled into a "full screen" (well, "mostly most of
the page") mode too:&lt;/p&gt;
&lt;p&gt;&lt;img alt="In full screen mode" height="1101" loading="lazy" src="https://blog.davep.org/attachments/2026/04/27/graph-full.webp#centre" width="1117" /&gt;&lt;/p&gt;
&lt;p&gt;The graph itself (built using
&lt;a href="https://github.com/vasturiano/force-graph" rel="noopener noreferrer" target="_blank"&gt;force-graph&lt;/a&gt;) can be explored in
the ways you'd reasonably expect, allowing zooming, panning around, dragging
nodes around to get a better view of things, and so on.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Zoomed in on the graph" height="1101" loading="lazy" src="https://blog.davep.org/attachments/2026/04/27/graph-zoom.webp#centre" width="1117" /&gt;&lt;/p&gt;
&lt;p&gt;If you click on any of the nodes the graph will show you everything that's
linked to it:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Highlighted links" height="1101" loading="lazy" src="https://blog.davep.org/attachments/2026/04/27/graph-links.webp" width="1117" /&gt;&lt;/p&gt;
&lt;p&gt;and if you click the node again it will take you to the post, tag archive or
category archive, depending on what it is you are clicking on.&lt;/p&gt;
&lt;p&gt;So far I'm finding this is working really well as yet another method of
discovering posts and themes, etc; it's already helped me find some
"under-used" tags that deserved to be added to posts to better connect
things. I suspect the feature will need refining over time, especially from
a cosmetic point of view, but the result feels very usable as it stands.&lt;/p&gt;</content>
    <link href="https://blog.davep.org/2026/04/27/blogmore-v2-17-0.html"/>
    <category term="Coding"/>
    <category term="BlogMore"/>
    <category term="Coding"/>
    <category term="PyPI"/>
    <category term="Python"/>
    <category term="graph"/>
    <published>2026-04-27T08:40:40+01:00</published>
  </entry>
  <entry>
    <id>https://blog.davep.org/2026/04/25/blogmore-v2-16-0.html</id>
    <title>BlogMore v2.16.0</title>
    <updated>2026-04-25T11:40:38+01:00</updated>
    <content type="html">&lt;p&gt;&lt;a href="https://blogmore.davep.dev/" rel="noopener noreferrer" target="_blank"&gt;BlogMore&lt;/a&gt; has had a new release, bumping the
version to &lt;a href="https://blogmore.davep.dev/changelog/#v2160" rel="noopener noreferrer" target="_blank"&gt;v2.16.0&lt;/a&gt;. There are
two main changes in this update, both coming from a single idea: internal
back-links.&lt;/p&gt;
&lt;p&gt;Where it makes sense, I always try and link posts in this blog to other
related posts, but I've never really had a sense of how interconnected
things are. So, the first new thing I added was a
&lt;a href="https://blogmore.davep.dev/configuration/#with_backlinks" rel="noopener noreferrer" target="_blank"&gt;&lt;code&gt;with_backlinks&lt;/code&gt;&lt;/a&gt;
configuration option. This is off by default, but when turned on, will add a
list of any referring posts to the bottom of a post.&lt;/p&gt;
&lt;p&gt;&lt;img alt="A list of references to a post" height="1516" loading="lazy" src="https://blog.davep.org/attachments/2026/04/25/references.webp#centre" width="1582" /&gt;&lt;/p&gt;
&lt;p&gt;Like some of the work I did in the stats page, this feels like another
interesting method of discovering posts and related subjects within a blog.&lt;/p&gt;
&lt;p&gt;Once this work was done, it seemed to make sense to use the link-gathering
code to then get a sense of which posts are most often linked to within a
blog, and so a table of most-linked posts has been added to the &lt;a href="https://blog.davep.org/stats/"&gt;stats
page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Internal link stats" height="1694" loading="lazy" src="https://blog.davep.org/attachments/2026/04/25/internal-links.webp#centre" width="1572" /&gt;&lt;/p&gt;
&lt;p&gt;This particular table will only appear in the stats if &lt;code&gt;with_backlinks&lt;/code&gt; is
set to &lt;code&gt;true&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;At some point in the future it might be interesting to take this even
further and produce a map of interconnected posts; for now though I think
this is enough.&lt;/p&gt;</content>
    <link href="https://blog.davep.org/2026/04/25/blogmore-v2-16-0.html"/>
    <category term="Coding"/>
    <category term="BlogMore"/>
    <category term="Coding"/>
    <category term="PyPI"/>
    <category term="Python"/>
    <published>2026-04-25T11:40:38+01:00</published>
  </entry>
  <entry>
    <id>https://blog.davep.org/2026/04/24/blogmore-v2-15-0.html</id>
    <title>BlogMore v2.15.0</title>
    <updated>2026-04-24T09:27:15+01:00</updated>
    <content type="html">&lt;p&gt;I've just made a small update to &lt;a href="https://blogmore.davep.dev/" rel="noopener noreferrer" target="_blank"&gt;BlogMore&lt;/a&gt;.
This fixes a minor cosmetic issue that's been bugging me for a while, but
one that I kept forgetting to address. I noticed it again on a recent post.
The issue is that if there are enough tags on a post that the collection of
tags runs to a second line, there was no space between those lines.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Before" height="85" loading="lazy" src="https://blog.davep.org/attachments/2026/04/24/unspaced.webp#centre" width="618" /&gt;&lt;/p&gt;
&lt;p&gt;Now, as of &lt;a href="https://blogmore.davep.dev/changelog/#v2150" rel="noopener noreferrer" target="_blank"&gt;v2.15.0&lt;/a&gt;, there's a
little bit of breathing room between those lines.&lt;/p&gt;
&lt;p&gt;&lt;img alt="After" height="85" loading="lazy" src="https://blog.davep.org/attachments/2026/04/24/spaced.webp#centre" width="607" /&gt;&lt;/p&gt;
&lt;p&gt;Much better.&lt;/p&gt;</content>
    <link href="https://blog.davep.org/2026/04/24/blogmore-v2-15-0.html"/>
    <category term="Coding"/>
    <category term="BlogMore"/>
    <category term="Coding"/>
    <category term="PyPI"/>
    <category term="Python"/>
    <published>2026-04-24T09:27:15+01:00</published>
  </entry>
</feed>
