Posts tagged with "Python"

Wasat v0.4.0

2 min read; 10 GFI

By this point today I was hoping to have released a new version of Rogallo, complete with client certificate support. It is more or less all there and ready to go, but I ran into a small problem, something which confused me.

You see, according to the documentation for the Gemini Protocol, when there's a request by a capsule for a client certificate, that certificate should be scoped to the host, port and path and all paths below it. So that means that if example.com/foo causes a certificate to be requested, it's good for example.com/foo and example.com/foo/bar, but it isn't valid for example.com/other.

Makes sense.

The problem I ran into pretty quickly, with my implementation of this in Wasat and Rogallo, was this: I hit a site (a microblogging site of sorts) that had a joining page at example.com/join. Once you joined up and set your user name, you'd normally land at example.com/me. They're sibling paths and so should not use the same certificate. However, this was done via redirection, so I did some work to "clone" a certificate when there's a redirection.

But then it got more complicated. The site also lets you follow other people. This means that if you visit example.com/other-user you should also still have the client certificate in place so the capsule knows who you are (because client certificates are also, in effect, session cookies, as I understand it), so you can perform the follow. Again, this is a sibling path, and there's no redirection, so the certificate is no good because, at this point, it's scoped to example.com/join and example.com/me.

Meanwhile: testing this with Lagrange, it had no such problem whatsoever. How was it getting around this issue? Was I doing something wrong? Was I misreading the specification for the protocol? Was Lagrange being a bit more relaxed about its certificate scope?

After doing some digging, it would seem that it's the latter. It looks like it, and perhaps other clients, take a pragmatic approach to certificate scopes and generally scope them to the host and port alone, ignoring the path (in the case of Lagrange it seems to sort of actually divorce the certificates from the URIs anyway, treating certificates more as identities you can associate with any capsule, etc).

Given this, at least for now, I'm going to take that approach. Any time a certificate needs to be generated in Rogallo, I'm going to give the user the option (on by default) to scope the certificate to the whole host/port combination. Later on I might add the ability to fully manage certificates (right now that can be done by editing the certificates file in the data directory, but a proper UI for it would be nice).

Which brings me to this release of Wasat. v0.4.0 adds some extra functionality to GeminiURI that allows changing and removing individual parts of the URI. There's a new replace method that can be used to create a clone of a URI with various parts changed. Also, if you just want to change one specific part or simply prefer method chaining approaches, I've also added more with_ methods similar to the pre-existing with_query method, so now there's also with_host, with_port and with_path.

With these in place I can go back to Rogallo and carry on with the last bits of work I want to do on client certificate support, with Wasat making it easy for me to clone up and chop and change URIs to suit the scoping requirements.

Wasat v0.3.0

1 min read; 11 GFI

Wasat v0.3.0 is now available. This is a pretty small update, but one that's going to be useful in the next release of Rogallo. I've now got client certificate support up and going in the application, and I thought it might be useful to show the user if a given page is using a client certificate or not (and, perhaps, at some point, make the details of the certificate available as part of some page information dialog).

With this in mind, I've added client_cert_path and client_cert_used properties to the Response class. The former is the path to the certificate used (if one was used), the latter is a simple boolean flag to say if a certificate was used at all.

Of course, client_cert_path could be used for both purposes as it'll be None if one isn't used, but client_cert_used will read better in code if that's all that's needed.

Wasat v0.2.0

1 min read; 10 GFI

I've made a quick update to Wasat, my async Gemini Protocol client library for Python. Now that I'm at a point where I want to add client certificates to Rogallo, I'm essentially shaking down the support for this in Wasat.

One thing I wanted right away was a certificate that, in effect, never expires. So with this release of Wasat I've added an option where the days to expire can be set to None, which results in the expiration date being set to 9999-12-31.

Rogallo v0.5.0

4 min read; 11 GFI

As mentioned in the last release, I've been afk for a week and a bit, enjoying some downtime over in the Netherlands. Although... it wasn't exactly all afk. I did take a keyboard with me and, while time permitted, I did tinker on Rogallo. That's resulted in quite a few TODO items being ticked off, and quite a few changes and improvements being made.

Visible MIME type

It's a small tweak, but I found it useful. The status bar of the main viewer panel now shows the MIME type of the document currently being viewed. Normally it will be showing text/gemini:

text/gemini

but Rogallo can show some other text/ types too, so this will also make it obvious what you're looking at:

text/plain

You might also notice that, depending on the theme being used, the "correct" MIME type gets a "this is fine" colour whereas anything else is in a more "neutral" colour.

Better handling of text/plain and friends

Now, if a document comes in that isn't text/gemini, there is no attempt to parse and render it as Gemtext; it's just shown as a simple block of plain text.

MIME type handling of local files

Given the above changes, Rogallo now makes an effort to guess the MIME type of any local file you view. This should help ensure that only Gemtext files get parsed and rendered as such.

OS hand-off for other MIME types

Where possible, when confronted with a MIME type that Rogallo can't handle, it will attempt to hand it off to your environment's main web browser. While not ideal, I have no wish to turn Rogallo into a terminal-based general content browser/viewer; this is all about Gemini and Gemtext.

History enhancements

The history list has been updated to allow the removal of individual items, and also clearing down the whole history.

Added home page support

It's now possible to set a home page with either a SetHome (which prompts for a URI) or a SetHomeToCurrentLocation (it does what it says) command. There is also a GoHome command that takes you to the home page you've set. The default bindings for all of these can be found by either running rogallo bindings, or by viewing the help screen; pulling up the command palette is also an option.

Added support for bookmarks

Bookmarks are now supported. The ToggleBookmarks will show/hide the bookmarks list. AddLocationToBookmarks will add the current location to the bookmarks. SearchBookmarks lets you search the bookmarks.

More command line suggestions

The completion suggestion facility in the application's command line (where you enter URIs or commands) has been enhanced to include suggestions from the navigation history, the location visit history and the bookmarks. This should make it a little easier to enter a URI you're trying to remember.

Having added the SearchBookmarks command, it made sense to also add a SearchHistory command too. So with that you can quickly search all the locations you've visited (within the limits of the history) and head back to one.

Optimised Gemtext rendering

While not something that should generally be obvious to the user, the Gemtext rendering engine has been optimised a little to reduce the number of Textual widgets used to display a document by consolidating all adjacent plain paragraphs.

Cleaned up quoted paragraphs

I've cleaned up an issue with adjacent quoted paragraphs. Until now there was an empty line between each quote; this spoiled the layout.

View source and ANSI escape sequences

Some Gemini sites use ANSI escape sequences to add colour to pages. While this isn't, as far as I can tell, part of the standard for Gemtext, it's a simple thing to support and so Rogallo has supported rendering this for a while; if a page has colours, they'll be used.

This meant that if you asked to view the source of a page, you'd also see that rendered in colour. I felt that wasn't in the spirit of viewing the source.

So now, if viewing a page like this:

My glower

When you go to view the source, instead of all of the colours still being rendered, you now get to see which ANSI escape codes were used where:

ANSI source

Viewer auto-focus

One small change is that, any time a document is loaded (from bookmarks, history, the command line, etc), the viewer now gains focus. Something I kept forgetting to do and finally I got fed up with having to tab over to the viewer each time.

Content cache

Rogallo now has a content cache for pages loaded from capsules (the aim is to never bother caching content loaded from the filesystem). Any page that is loaded with a success status code (20) gets cached, and when you go back to it, if it's within the TTL of the cache entry (by default set to one hour), the content of the page will be loaded from the cache rather than making a fresh request.

Using the Reload command always bypasses the cache.

There is also a ClearCache command which will delete all cached data.

The TTL of the cache can be changed in the configuration file by updating the value for cache_ttl. Also, if you don't wish to ever use a cache, you can set with_cache to false to disable it.

What's next

With all of the above, I feel this gets Rogallo close to being v1.0.0-ready. Not quite, but very close. The main addition that still needs to be tackled is client-side certificates. I don't anticipate this being too tricky, it's just more a case of needing to play around with the code I've got so far, and needing to think about how to implement it in a TUI-friendly way (if, indeed, there are any considerations there at all).

For now though, as long as you don't need a client-side certificate, there's plenty to play with and try out.

textual-enhanced v1.6.0

1 min read; 10 GFI

I've just made a small update to textual-enhanced, my core library used for most of my Textual-based applications. In v1.6.0 I've extended the "constructor" for ModalInput to allow passing in optional values for password, suggester, title and sub_title.

Most of the time I just want to quickly call on ModalInput to get input from the user. If I need anything more fancy, I roll my own dialog. But in some work I'm doing on Rogallo, it would be helpful for me to at least set the suggester. So, without adding every optional parameter for the Textual Input widget, I've extended what can be passed in to what I think will be a useful subset for my applications.

BagOfStuff v1.2.0

1 min read; 10 GFI

Quick bump to BagOfStuff. v1.2.0 adds something I totally forgot yesterday.

While adding the del method to the history classes, to make it easier for me to manipulate them as if they had the interface of a Python list, I totally forgot to add a clear method. Rogallo will need to be able to 100% clear history, as well as remove individual entries in the history, so that's kind of needed too.

It's there now.

BagOfStuff v1.1.0

1 min read; 12 GFI

I've just updated BagOfStuff with a change and addition, in anticipation of some work I'll be doing on Rogallo in the near future. The change is a small and simple one, adding del support to the history classes.

The addition is a simple cache manager. For now it's just a straightforward bit of code that, given a set of keyword arguments, creates a unique hash, sets up a directory, and returns a base filename within it. From there, any calling code can detect if the file(s) exist and make use of it/them, or otherwise get on with some work and populate the cache.

Of course, in the case of Rogallo, this is all going to be used to cache the Gemtext that is retrieved from capsules.

BlogMore v2.44.1

1 min read; 12 GFI

I've just done a quick update to BlogMore, bumping the version to v2.44.1. This release fixes an issue with auto-cover generation where, if you changed some properties relating to a post (or the blog as a whole), the auto-covers weren't being updated to reflect those changes.

A good example is the description of a post. In the editorial-style cover, the description is shown; this is taken either from the first paragraph of the post or, if you've provided a description front matter value, it's taken from that property. The problem was that if you changed the post such that the text of the description changed, after a cover had been generated, it wasn't regenerated because it was already in the cache.

So this release is a bit more aggressive about when it will ignore the cached cover and generate a new one. The result is that the cover will reflect the changes.

There is, of course, a small downside to all of this (which was also an issue for v2.44.0 too): if you're working on a new post in serve mode, any time you change something that causes the cover to be recreated, the older versions of the cover will be left in the cache; in other words, there's a storage overhead to all of this.

For now, I'm just going to live with this downside (BlogMore has a cache clearing command anyway, so if it becomes an issue you can always use that). In the near future, though, I think I'm going to add a smart-clear sub-command to the cache command (or perhaps a --smart switch to the clear sub-command). This will go through the cache and find all the files that aren't currently "valid" and remove them, leaving all the "good" cache entries intact. That should be useful for occasional housekeeping without needing to wipe out the whole cache, and so greatly slowing the next build of a site because every single cover needs creating again, and every single optimised image needs generating again (if you have image optimisation turned on).

Rogallo v0.4.0

2 min read; 9 GFI

I've updated Rogallo to v0.4.0. The main new feature in this release is support for capsule-requested user input. There are some other simple additions too.

I've added a Reload command, bound to F5 by default. As you might imagine, it reloads whatever page you're looking at right now.

I've also added a pair of commands for copying things to the clipboard. There is CopyLocationToClipboard (bound to ctrl+shift+c by default) which, as the name suggests, copies the current location (either the Gemini URI or the path to the file depending on what you're viewing) to the clipboard. In a similar way, CopyDocumentToClipboard will copy the content of the document you're viewing (bound to alt+shift+c by default).

It's worth noting that the default bindings for both of those aren't going to be ideal for some terminals. They should be fine in any terminal that supports the Kitty keyboard protocol, but will likely do nothing elsewhere. This can be changed to your taste via the configuration file1.

Talking of a document's content: I've also added a ToggleView command (bound to F3 by default) which toggles the document's view between a rendered view and a plain text (source) view. So if you're looking at a page like this:

A rendered Gemini page

and want to know what the underlying source looks like, just toggle the view:

The source view of the page

Finally, the most significant addition is support for capsule-requested user input. This handles a 10 or 11 response from a server, prompts the user for input, and then sends it back as a query.

A request for user input

It's worth noting that the sensitive input (response 11) isn't done in the most obvious way, on purpose. Normally I'd have taken the "do obscured password input" thing, which is supported by Textual's Input widget. The problem there though is that an input request from a Gemini server expects and allows for multi-line input2; that requires the use of a TextArea; it doesn't support password-style input.

So what I've done instead is, if it's a sensitive input request, I simply greatly lower the contrast of the text vs the background. This should match the "reduce shoulder-surfing opportunities" requirement while not making it impossible to see what you're doing.

Normally I wouldn't be satisfied with this approach given that the text will still be visible, but I think it's a fair solution given one glaring problem with Gemini's sensitive input facility: the input is always sent back as a URI query string. That means that the resulting input is part of the URI, will be visible in any URI display on the screen, will be part of the history, etc. The sensitive part is only about making it less obvious at the moment of input, so I think this approach is in that spirit.

So... that's it for v0.4.0, and that's also likely it for the next week or so. I'm going to be super busy in AFK life next week and into the week after, so work on Rogallo will pause. It's almost a shame, I'm having tons of fun working on it.


  1. As mentioned in another post about Rogallo, how to do that will be documented when I get round to writing the documentation for Rogallo. Meanwhile look at similar documentation for Hike to get an idea of how to go about it. 

  2. Well, technically, it's an optional feature of a client; the specification says "Clients MAY allow for the entry of input composed of multiple lines". I wish Rogallo to be one such client. 

BlogMore v2.44.0

3 min read; 11 GFI

It's been a short while since I last made a release of BlogMore; in fact, the time since the last update might be the longest I've gone between versions since the first release. I think this might mean it is actually more or less feature complete!

More or less.

Except... I did have to spend a bit of time (and some Antigravity quota) this morning adding something I've been wanting to add: automatic generation of cover images aimed at social media sites (so the kinds of images that show up when you post to Mastodon, Bluesky, or that other terrible site some people still seem intent on using for some perverse reason).

BlogMore has supported the declaration of a cover from the very first release. This was done in a way that it was up to the author of the post to create and include the image. Personally, in my posts, I've tended to set the cover to point at the most relevant image in the post, if a post has any images. I've also had BlogMore always work such that a post without a cover has the social image set to the site's logo image (if it has one).

This works, but it does mean that all of the posts I make that have no cover (feels like that's roughly half of them -- I could probably do something fun with the dump of posts to know for sure) simply show my masked face when I share them. That's by design, but not ideal.

So v2.44.0 adds support for an "auto covers" feature. I've tried to do this in a way that is fully backward-compatible. The feature itself is off by default, won't override any covers you have specifically set for posts, and can also be used and controlled on a per-post basis.

The core of the feature is controlled by a new auto_covers section in the configuration file. In here you can control if the feature is on or off by default, what layout to use for the cover images, what colours to use, and so on. There's plenty to experiment with and it should be fairly straightforward to create covers that look unique to your blog.

There are three styles of cover that can be generated. The minimalist does as the name suggests: it tries to keep things as minimal as possible (and will generally result in the smallest file).

Minimalist cover

The split type is a little richer, including the site logo if you've set one. Generally the image size will be bigger than minimalist.

Split cover

Finally there is editorial, which includes the title, logo, the description for the post, the category and the tags. Because this is the busiest style it will generally result in the biggest file.

Editorial cover

As you might imagine, generating these images for every post that doesn't have a cover set can be very time-consuming. Because of this the generated images are cached, so subsequent site generations should hardly ever be affected (unless you change any of the parameters relating to cover generation).

ℹ️ Note

As with image optimisation, this does mean that more storage is going to be used between blog builds. If you use this cover feature, not only will more images be created in your static site output, but the BlogMore cache related to the blog will also grow. Keep this in mind when deciding to use this feature.

It might also be the case that you don't want to generate cover images for all of your historical posts, but you do want them for all future posts. That approach is possible. All you need to do is set everything as you want it in the configuration file but set enabled under auto_covers to false. Then, for any post where you do want an auto-generated cover, simply set auto_cover in its frontmatter to an appropriate value. To go with the default settings, set it to default, or if you want to control the layout per-post, set it to the desired layout for that post.

To try and summarise, the rules for selecting a cover for a given post are something like:

  1. If it has a cover set in its frontmatter, that is used.
  2. If it has an auto_cover set to anything other than none in its frontmatter, the desired type of auto-cover will be used.
  3. If it has neither cover nor auto_cover set, a cover will be generated if auto_covers.enabled is set to true.

Hopefully that's clear.

Despite this post having images in it, I've not set a cover for it and I have the following setup in my configuration file:

auto_covers:
  enabled: true
  layout: editorial
  background_type: gradient
  background_color: "#0f172a"
  gradient_colors:
    - "#1e293b"
    - "#0f172a"
  font_family: "Inter"
  text_color: "#f8fafc"
  meta_color: "#94a3b8"
  accent_color: "#38bdf8"
  show_author: true
  show_read_time: true
  show_date: true
  show_logo: true

This should mean that, if I've got this all working correctly, this post, and all historical posts without a cover, get auto-generated covers. This should also be very evident as you hover over posts in the graph.

Fingers crossed it all works out...