Posts tagged with "coding"

blogmore.el v4.1

1 min read

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

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

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

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

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

boxquote.el v2.4

2 min read

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

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

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

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

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

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

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

Or perhaps getting help with a particular key combination:

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

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

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

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

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

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

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

The boxquote transient in action

Now I can have:

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

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

BlogMore v2.14.0

1 min read

Quick little update for BlogMore, with a bump up to v2.14.0. This release comes from another feature request from Andy1, where he asked if it would be possible to have a year-based bar chart in the stats page.

Funnily enough I'd been thinking about the same thing just yesterday. I'd been wondering if it was worth adding, or if it would be overkill given the numbers can be seen in the archive. Having been asked by someone else... that was all the prompting I needed to kick that off.

Posts per year for my blog

Now I'm glad I did this. I like the result, it's a different way to visualise the values, and it's yet another way for people to discover past posts on the blog.

For sure BlogMore is now feature complete.


  1. Who recently wrote an interesting article about his experience of migrating his blog from Hugo to BlogMore 

slstats.el v1.11

1 min read

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

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

An overview of the grid

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

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

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

Region information for Da Boom

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

wordcloud.el v1.4

1 min read

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

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

Word cloud in action

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

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

NGMCP v0.2.0

1 min read

The experiment with building an MCP server continues, with some hacking on it happening over a couple of hours while killing time in an Edinburgh coffee shop.

It is, of course, a solution looking for a problem, and I suspect I'm the only person who will ever use it, and even then only as a test, but building it is proving interesting.

The main changes in v0.2.0 are:

  • I turned the current search_guide tool into a line_search_guide tool (because that's what it was doing: a line-by-line search).
  • I added a body_search_guide tool that treats all the lines in an entry as a single block of text and then does the search in that (so searches over line breaks will work).
  • I added a read_entry_source tool that, rather than rendering the entry of a guide as plain text with all the markup removed, it instead delivers the underlying "source" for the entry; something that could be useful if you wanted to get an agent to convert it into another marked-up body of text.
  • I added a markup glossary resource, which technically tells an agent everything it needs to know about Norton Guide markup.

The latter one is interesting. I added it and did some experimenting locally and it seemed to be helpful and I could ask questions about markup and Copilot seemed to use it. Meanwhile, having installed v0.2.0 globally on my machine, and having enabled it, I'm finding that Copilot seems to have zero clue about the markup and instead is using the server to go off and read the guides to work out the markup1.

On the other hand, the new "get source" tool seems to work a treat.

Peeking at the source for an entry

So I suspect I still have some reading/experimenting to do when it comes to resources, so I can better understand why I'd want to provide them and what problem they solve.


  1. All credit to it: it did find CREATING.NG and read the markup out of that. 

Global and local MCP servers with Copilot CLI

1 min read

This morning I'm tinkering some more with NGMCP. Having done a release yesterday and tested it out by globally installing it with:

uv tool install ngmcp

I was then left with the question: how do I easily test the version of the code I'm working on, when I now have it set up globally? Having done the global installation I had ~/.copilot/mcp-config.json looking like this:

{
  "mcpServers": {
    "ngmcp": {
      "command": "ngmcp",
      "args": [],
      "env": {
        "NGMCP_GUIDE_DIRS": "/Users/davep/Documents/Norton Guides"
      }
    }
  }
}

whereas before it looked like this:

{
  "mcpServers": {
    "ngmcp": {
      "command": "uv",
      "args": ["run", "ngmcp"],
      "env": {
        "NGMCP_GUIDE_DIRS": "/Users/davep/Documents/Norton Guides"
      }
    }
  }
}

But now I want both. Ideally I'd want to be able to set up an override for a specific server in a specific repository. I did some searching and reading of the documentation and, from what I can tell, there's no method of doing that right now1. So I've settled on this:

{
  "mcpServers": {
    "ngmcp-global": {
      "command": "ngmcp",
      "args": [],
      "env": {
        "NGMCP_GUIDE_DIRS": "/Users/davep/Documents/Norton Guides"
      }
    },
    "ngmcp-local": {
      "command": "uv",
      "args": ["run", "ngmcp"],
      "env": {
        "NGMCP_GUIDE_DIRS": "/Users/davep/Documents/Norton Guides"
      }
    }
  }
}

and then in Copilot CLI I just use the /mcp command to enable one and disable the other. It's kind of clunky, but it works.


  1. I did see the suggestion that you can write your MCP server so that it does a non-response depending on the context, but that seems horribly situation-specific and wouldn't really help in this case anyway because I want it to work in both contexts, depending on what I'm doing. 

NGMCP - An MCP experiment

2 min read

Recently I've been thinking that it would be interesting to get to know a little about the Model Context Protocol and see what it's about and get a feel for how useful it might be, if at all, for anything I do.

As always happens when I want to try out something new, I reached for a problem I know well so I don't have to get bogged down in solving the problem itself. As almost always happens, I decided I should base it around Norton Guides.

Part of the point of MCP seems to be providing an interface over sources of data and actions, that an LLM might not otherwise be able to cope with, and so it sounded to me like providing a bridge to the content of Norton Guide files would be a perfect test. Of course, this isn't the first time I've bridged LLMs and NG files, but this is obviously intended to be a more generic solution than throwing a Markdown file at NotebookLM.

Earlier this afternoon I sat down and did some reading, and then decided to throw the problem at GitHub Copilot. I told it I wanted to use my NGDB library as the core of the tool, and that it should wrap it up with FastMCP. The initial result was... a bit of a mess. It sort of worked, sort of, but it also seemed to try and put together a project that mostly looked how my Python repos look, but with some bits just wrong.

I did some cleaning up, did some testing, did some tweaking, and eventually I had something working.

Asking what NGMCP can do

So far I've given the code a fairly quick read over, and I can see what it's doing and how it's going about this. This approach obviously has the disadvantage that I didn't hand-write it so there's still a lot to read to really appreciate what's going on; on the other hand, it does have the advantage that it's implemented a tool based on my library so I know what to expect it to be doing.

There will be more code reading happening, and I also intend to look to tidy up the code more and perhaps hand-add some more features.

Looking at the credits of a guide

I very much doubt that this particular MCP server is going to be any use to anyone, but as a proof of concept it works well for me. If I were in a position of needing to build something genuinely useful, I now have a start and a vague idea.

Reading some text from a guide

On the other hand: once again, as with other projects I've done related to Norton Guides, this is a tool that helps keep the content available and accessible; that alone is one reason for me to tidy this up and move it towards v1.0.0 and keep it maintained.

If you fancy having a play, some (currently Copilot-generated) documentation can be found on the server's dedicated site. When I get a bit more time I'm going to flesh this out.

BlogMore v2.13.0

1 min read

Following on from yesterday's release of BlogMore, I've been looking at some more information in the Google Search Console, which helped me uncover a couple more bugs in relation to URL generation.

This time I noticed a couple of issues, both related to the clean_urls setting. The first was that, in the recently added calendar page, all of the URLs for the links into the date-based archive weren't taking clean_urls into account. That's now fixed.

The second problem was the canonical <link> tag in the headers of the various archive pages (categories, tags, date-based): none of the URLs used in the tag were being cleaned up if clean_urls was true. That's now also fixed.

The main "problem" those two issues were causing was Google was seeing the sitemap for my blog declare one URL, but discovering different versions of the URL elsewhere; the main offending part here being the canonical URL declaration that disagreed with the sitemap.

To the best of my understanding the above fixes should clean a lot of that up.

Also in this new release is a small new feature. After cleaning up the sitemap generation in v2.12.0 I got to thinking that, perhaps, there would be occasions where a user would want to be able to add extra items to the sitemap. With this in mind I've added the sitemap_extras configuration property. With this you can declare extra URLs to drop into the sitemap, if one is being generated.

sitemap_extras:
  - /some/path/
  - /some/file.html

I don't think I have a use for this right now, I'm not sure I'll ever have a use for it, but it feels like a low-cost feature to add that could be useful to someone at some point.

BlogMore v2.12.0

1 min read

Since kicking off building BlogMore and swapping this blog over to using it I've been playing with the Google Search Console. It's something I've not used in decades, but felt it was time to dip back in again and understand how it works these days.

There are two motivations for this: the first is that, when it comes to my day job, I have cause to interact with people who do use the search console a lot, and so it's worth understanding what they work with and why it matters to them. The second reason is it's a reasonable measure of how good a site BlogMore generates.

Page index inclusion progress

So far the results have been pretty good, and the console has helped me find oddities and things that need tidying up.

So this release of BlogMore includes a couple of changes that stem from looking at the latest updates in the console.

The first is that I've cleaned up how the sitemap.xml gets generated. I noticed that if I had any HTML inside my extras directory it was turning up in the sitemap; something I didn't intend and didn't want. So that's now fixed: only pages generated by BlogMore will appear in sitemap.xml1.

The second is that the stats page, despite being in the sitemap, had a noindex header for some reason. That's now been fixed. The only generated page I've intentionally set up so that it isn't indexed is the search page.

Finally, there's one change unrelated to the above: I realised that if you have with_read_time set to false, the reading time stats still appeared on the stats page; that seems unnecessary and unwanted on a site that doesn't show reading times. So, as of v2.12.0, that section of the stats won't show if reading times are turned off.


  1. Now I think about it, I suppose there might be occasion where someone wants extra HTML to appear in the sitemap. I might consider the idea of allowing extra entries to be declared via the configuration file.