Markdown all the things

Posted on 2024-11-04 21:00 +0000 in Coding • Tagged with Python, Twitter, Journey, Evernote • 4 min read

Recently I've been on a bit of a "turn stuff into Markdown files and slap them in an Obsidian Vault" trip. This kicked off a couple of months back when I made a decision unrelated to coding.

On and off, since my teenage years, I've kept journals. Since those teenage years it's been more off than on, but a couple of times in my adult life it's been really helpful to actually write one. The last time this happened was early 2019. It was pretty vital I did that at the time and it was a really sensible and helpful decision, and an approach to the situation I was in that I'd recommend to anyone (and have done on occasion to anyone going through the same thing).

The actual motivation for starting that particular journal is long behind me, but I'd got into the habit of writing it and so, until a couple or so months back, I kept jotting something down every day. But I came to the realisation that I didn't need to and that it had become something of a chore.

I'd been using an application called Journey. It's a great app, does the job well, but was also suffering from the creep of "AI" (I've had a few apps ion my arsenal that don't need it, acquire a useless "AI" feature). This privacy-problematic change of direction, combined with the realisation that I didn't need to write about my day, every day, any more, made me decide it was time to stop and cancel the subscription.

Thankfully Journey has a pretty comprehensive export option so I used it and didn't think too much more about it for a while.

Meanwhile I also had a subscription to Evernote that I didn't really use any more. Within it I had held a handful of years of journal entries from a decade or so ago, along with other "remember this for some point in the future" stuff. For the longest time I was on some really cheap tier that didn't exist any more, one that was low enough that I didn't really notice the cost go out each month so I kept putting off exporting things and closing it all down until "next month".

Then I got an email from them to say they were forcing me onto some new tier that was more expensive. So that was the final straw there. I made an export of what I had in Evernote and closed that account down too.

A wee while went past and then I got to thinking that it might be interesting to try and combine both these sources into one archived journal. I had stuff from around 2010 to 2015, and I also had stuff from 2019 until 2024; the former in the Evernote archive and the latter in the Journey archive. Surely I could write a couple of tools to turn that data into one consolidated Obsidian Vault?

Over the course of a couple of weekends journey2md and evernote2md were born. While both of those tools work differently, they're both designed to populate the same Obsidian Vault. Once I was happy with this I did the mass conversion and I was happy with the result.

Now I have years of journal entries, all converted to Markdown files and made available for reading via an application that lets me rummage through history using dates and tags and all sorts of other searching.

So I was happy with that and didn't give it much more though.

Then last week I got to thinking...

Twitter has turned into the worst place possible and I can't for the life of me think why any right-thinking person who has an ounce of humanity or has anything approaching a humanistic outlook on life would remain an active user. Honestly I stuck it out longer than was sensible, but in June 2023 I finally quit for good.

Back when the new owner was confirmed I, like a lot of people, extracted my archive. It's since been sat in storage doing nothing, yet there's a lot of data in there that could be interesting to work with, or just to go back and look through. So last week's thought was "why don't I also turn this into an Obsidian Vault?".

So I did...

The graph of my Twitter Obsidian Vault

The tool I built to do this is bird2glass. As you'll see in the README it makes a few assumptions about the state of Twitter archive dumps and also what a user wants from this. Personally I'm pleased with the result.

The main aim of the tool is to break the tweets down into a hierarchy of year, month and day...

Viewing a tweet

...and also to connect them with any account that was being replied to or mentioned in some way...

Viewing a user

This user view is handy when viewing backlinks, as it gives you a list of all the tweets that mention that user (and, of course, if you're into Obsidian's graph it will make for some interesting connections within there).

I sense there's more I can do with this, and I imagine I will continue to tinker with it. Meanwhile though, if that sounds like something you'd benefit from do feel free to grab it and play with it and hack on it. Keep in mind the notes and assumptions that are in the README, and really be prepared for a lot of files to be created if you did a lot of tweeting like I did (I do think that over 50,000 individual files for an Obsidian Vault is a bit silly, if I'm honest).

Meanwhile... I might need to look at other applications and think about how I can turn the data into useful Markdown collections!


Paindrop v1.0.0

Posted on 2024-08-18 10:57 +0100 in Coding • Tagged with Python, pinboard, raindrop • 4 min read

I was quite late discovering Pinboard; by the looks of things I created my account and paid my first subscription for it in early 2019. Since then I've been a pretty avid user and found it really useful. I've even written a couple of clients for it (one for Emacs and one for the terminal).

During that time it's had its fair share of hiccups and outages, but on the whole I've found it a stable and useful service.

The service does have its detractors, and concerns over its long-term stability and how well it's maintained are fairly common. I half paid attention to these, and had started to think about where I might go if there was an issue.

While maintaining and syncing bookmarks isn't exactly a difficult or unsolved problem, and while it's also true that it could be fun to roll my own solution, there are a couple of things I need that would make building my own approach a bit of a chore.

Things important to me are:

  • An extension for any random browser I might find myself using
  • A good mobile client for at least iOS and iPadOS
  • A good API so I can write my own tools if I need to
  • A clean and focused backend website

I kept these things in mind and kept an eye out but I'd never really felt the need to actively start looking around.

Then I stumbled on this after posting about another Pinboard outage.

That... yeah, that was the final push I needed to start to think seriously about where to move and how.

A couple of people suggested Raindrop, and from what I could tell it was coming up as a pretty popular service that some Pinboard users were migrating to. I had a look and it wasn't quite what I was after; but close.

You see, there's two things I really like about Pinboard that Raindop didn't seem to cover:

  • Simple support for "this shit is unread". I see things, I share to whatever Pinboard app I have on my phone or tablet, etc, and then I review some time later (normally in Tinboard).
  • Support for Private and Public pins. I've liked having a feed of bookmarks I can let people see and Raindrop doesn't have this.

I looked around at some blogs that talked about Pinboard vs Raindrop and didn't see any that really dived into this particular aspect of migrating; I also asked a couple of folk who'd made the move about this and they didn't really have any insight (mainly because they didn't care about those particular uses).

One thing I did notice though was that Raindrop does support making individual collections public. So, if I was willing to sacrifice any other uses for collections (a bookmark in Raindrop can only be in one collection), I could simply have a Public and a Private collection and import pins into the appropriate one. Also, unread pins could be left out of the collections and I could use that to signify unread status.

This seemed fine as I'm heavy on the tags anyway.

Now... Raindrop has a pretty comprehensive import facility built in. I gave it a try with Pinboard's backup file and it worked really well. That is... really well except it just threw away the public/private/unread aspect of the pins. There was only one thing for it then: I had to write my own importer!

Which brings me to Paindrop. It's a quick hack but it does the job, and it does the import just how I wanted. The result of the first test was pretty much spot on (in this image I'm comparing what Raindrop says vs what Tinboard says I have in Pinboard):

Comparing Raindrop and Tinboard contents

Usage is pretty straightforward. You create Public and Private collections in Raindrop, you create an app in Raindrop and get the access token, you grab your Pinboard access token and then:

$ paindrop example:xxxxxxxxxxxxxxxxxxxx xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

where the first parameter is the Pinboard access token and the second the Raindrop access token.

If all goes well, after a few moments, the importer should finish and you should find that all of your pins have migrated to Raindrop, all public pins are in the Public collection and all private pins are in the Private collection. Any pins that were marked as unread will be Unsorted.

Note that if you used different names for your public and private collections you can pass those names to paindrop with the --public and --private switches.

If you're looking to move your bookmarking history out of Pinboard and want to keep the same sort of structure I had I hope Paindrop will be useful to you too.

Paindrop can be installed with pip or (ideally) pipx from PyPi. It can also be installed with Homebrew by tapping davep/homebrew and then installing paindrop:

$ brew tap davep/homebrew
$ brew install paindrop

The source is available on GitHub.

PS: As for the name... originally it was pin2rain but then Darren Burns pointed out the obvious and it had to happen.


Tinboard v0.14.0

Posted on 2024-05-14 08:02 +0100 in Coding • Tagged with Python, terminal, textual, YouTube • 1 min read

I've just released Tinboard v0.14.0. This release adds a feature that a user requested, where you can set the default values for the privacy and read-later status of a new bookmark:

The application settings dialog

So, any time you create a new bookmark, the edit dialog will use those values by default. It's a feature that makes perfect sense but I didn't think to add it early on because... well, I set the defaults to my preference.

Tinboard can be installed with pip or (ideally) pipx from PyPi. It can also be installed with Homebrew by tapping davep/homebrew and then installing tinboard:

$ brew tap davep/homebrew
$ brew install tinboard

The source is available on GitHub.


Tinboard v0.12.0

Posted on 2024-04-18 16:46 +0100 in Coding • Tagged with Python, terminal, textual, YouTube • 2 min read

Tinboard has turned into a tool I use pretty much every day; it's probably my most-used Textual/Python-developed application at this point. This is causing me to think more and more about how I can add things to it that are related to the core purpose, but are also outside of the main "interface with Pinboard" thing.

A thing with keeping bookmarks for a long time is that some of them go stale, go away. Some will just plain 404, others the whole site will disappear. If I find myself going back to a bookmark and seeing this is the case, I'll hit the Wayback Machine and see if there's an archive there.

So I got to thinking: what if I add the ability to perform this check into Tinboard itself? So I did just that.

Now, in the application, if you press w with a bookmark highlighted, it will check with the Wayback Machine to see if the bookmark is in the archive. If it isn't you see this:

No archive result

On the other hand, if it is in the archive, you'll see something like this:

Is in the archive result

I sense this is going to be the first step in a couple of features related to this. I'm thinking that I may go on to add a "swap the URL for this bookmark with the Wayback Machine archive URL" feature, which will be handy for those bookmarks that have one away, and it would also be useful to look at the options for a "please archive a copy of this bookmark" feature.

But, for now, v0.12.0 is available and has this handy (for me anyway) first step.

Tinboard can be installed with pip or (ideally) pipx from PyPi. It can also be installed with Homebrew by tapping davep/homebrew and then installing tinboard:

$ brew tap davep/homebrew
$ brew install tinboard

The source is available on GitHub.


PISpy v0.6.0

Posted on 2024-04-17 11:30 +0100 in Coding • Tagged with Python, terminal, textual, YouTube • 1 min read

Back in the very early days of the Textual adventure, within the first month or so of working on the framework, we had a period of dogfooding. One of the projects I wrote during that time was a little tool I called PISpy.

The initial version was pretty much a quick hack; during that dogfooding period I did my best to try and develop a new project every couple of days. Since then I've let PISpy descend into bit rot.

In the last week or so I've turned my attention back to it and made an effort to tidy up the code, tidy it some more, and some more, and even some more.

This morning I put the finishing touches to these changes and released v0.6.0.

PISpy in action

PISpy can be installed with pip or (ideallty) pipx from PyPI. It can also be installed with Homebrew by tapping davep/homebrew and then installing pispy:

$ brew tap davep/homebrew
$ brew install pispy

The source is available on GitHub.


Tinboard v0.11.0

Posted on 2024-04-09 15:43 +0100 in Coding • Tagged with Python, terminal, textual, YouTube • 1 min read

While my time working on Textual might have come to an end, my time working with Textual hasn't. Three days back I experimented with Textual's newly-added "inline mode":

In doing so I extended the application so that it's possible to run tinboard add and quickly enter a new bookmark and then carry on in the terminal, without needing to "go fullscreen". I'll admit it's of limited use, but it seemed like a good shakedown of the feature and in working on it I was able to discover a couple of bugs (#4385, #4403).

The effect of this is this:

Tinboard inline addition in action

Tinboard can be installed with pip or (ideally) pipx from PyPi. It can also be installed with Homebrew by tapping davep/homebrew and then installing tinboard:

$ brew tap davep/homebrew
$ brew install tinboard

The source is available on GitHub.


Homebrew all the Python things

Posted on 2024-03-10 14:12 +0000 in Coding • Tagged with Python, terminal, textual, Homebrew, Makefile • 4 min read

Over the past year and a half I've written a lot of Python code, and a lot of that Python code has been Textual applications; most of those Textual applications have been very quick demonstration or test applications built to help support someone asking for help; some of them have been less-trivial applications written in my own time and for my own use and amusement. Of them I'd say there are two near-daily-drivers, and a couple that I either have more plans for, or like to maintain just for the hell of it.

Those latter applications are all ones that I've deployed to PyPI, and because of that are all ones that I've recommenced be installed using pipx. During that time though I've had half an inclination to make them installable via Homebrew. While probably not installable from the core Homebrew repository1, at least installable from a "tap"2 that's under my own GitHub account or something.

To this end I've had a blog post about packaging Python apps for Homebrew saved in Pinboard for a while now, and every time I look at it I think "this is a lot of faff, maybe later". Today was that "later".

As it turned out, it was way easier than I first realised. The evolution of today pretty much went like this:

Deciding to use a single repository as the "tap"

The blog post above seemed to suggest that for every application repository you want a tap for, you probably want a parallel homebrew--prefixed repository. This in turn would suggest that every time someone wants to install one of your tools, they'd need to add a new tap3. As I looked at it this seemed like way too much faff, so in the end I decided to create a single repository that I'd keep all my formula files in. The naming of homebrew-homebrew meant that the tap name would simply be davep/homebrew.

Simple and clean, I think: things for homebrew, things that can be installed via homebrew, that come from davep. To add the tap it's simply:

$ brew tap davep/homebrew

Ensuring that all my applications and libraries publish source

Although it seems that it might be (possibly, maybe, perhaps, who can tell?) deprecated, it looked like homebrew-pypi-poet was a tool I'd need to do all the heavy work on making the formula file. A quick test threw up a problem where it was complaining that my test package (one of my own applications) didn't have an sdist. Sure enough, through nothing more than never having bothered to make it happen, the source of my libraries and applications wasn't been uploaded to PyPI when I published.

So I went through some of my repositories and fixed that, making patch releases as I went.

Making a Makefile to let me be lazy

The next thing to do was to figure out the most lazy way of building the formula files. From what I could see the main steps to making all of this work were:

  • Make a venv and activate it
  • Install homebrew-pypi-poet
  • Install the package you want to package for Homebrew
  • Run poet to make the formula

Seemed simple enough. For all sorts of lazy reasons I still tend to use pipenv to get things done quickly, and that seemed to work fine here too. I'm also a fan of PIPENV_VENV_IN_PROJECT=true which makes things clean and tidy, so I figured a rule in a Makefile like this:

clean:
        rm -rf .venv
        rm -f Pipfile Pipfile.lock
        pipenv --python 3.12
        pipenv install --dev homebrew-pypi-poet

would be fine to make a clean venv ready to build the formula, and then I'd have a rule for the package itself that depended on the above, like this:

oshit: clean
        pipenv install oshit
        pipenv run poet -f oshit > Formula/oshit.rb

Fixing the package description

The above was great, and worked really well. But there was one issue that I could see: the resulting formula file always had this desc inside it:

desc "Shiny new formula"

From what I could see there was no way to tell poet what I wanted the description to be, and neither did I want to have to remember to edit that line each time I regenerated the formula file. So sed to the rescue then I guess, with this sort of thing:

sed -i '' 's/Shiny new formula/The actual text I want/' Formula/coolapp.rb

The result

The result of all of this is that I now have a repository that I or anyone else can use as a tap to be able to install my stuff using the brew command. So now if you want a little Hacker News reader for the terminal but you don't want to be messing with installing pipx and the like, but you do use brew on your machine, it's just this:

$ brew tap davep/homebrew
$ brew install oshit

Fingers crossed it all "just works" when I next upgrade one of those packages. I will, of course, have to remember to go into davep/homebrew-homebrew and make the-app for the relevant application, and then commit and push the changes, but that's really not too difficult to remember and do.

Hopefully it'll then all just work.


  1. I do actually have one package in Homebrew, but it wasn't me who put it there. 

  2. I really like Homebrew as a tool for getting stuff installed, by oh my gods the naming of things in its ecosystem is terrible and confusing! 

  3. No, really, I mean it, this naming convention is kinda cringe right? 


Tinboard v0.10.0

Posted on 2024-03-07 08:45 +0000 in Coding • Tagged with Python, terminal, textual • 2 min read

I just realised that it's been a while since I last posted an update about tinboard. This is probably my most-used Textual-based application, and one I'm constantly tinkering with, and just this morning I published v0.10.0.

Often the changes are small tweaks or fixes to how it works, sometimes they're simply updates to the version of Textual used, making use of some new feature or other; I've yet to add another "major" feature so far. They will come, but so far the ideas I have for the application haven't actually felt that necessary. Although I say so myself it does what I need it to do and it does it really well.

So, as a quick catch-up of what's changed since v0.4.0 (which was the last version I posted about):

  • v0.5.0 was released 2024-01-04; this included all the tags of a bookmark when doing full-text searching.
  • v0.6.0 was released 2024-01-10; it fixed a small bug where the tag suggestion facility got confused by trailing spaces in the input field.
  • v0.7.0 was released 2024-02-02; this updated the minimum Textual version to v0.48.2 and removed all the custom changes to the Textual TextArea widget, making use of the updates to TextArea that version of Textual made available.
  • v0.8.0 was released 2024-02-18; this fixed a crash on startup caused by a newer release of Textual (the fault was in tinboard; the update to Textual helped reveal the problem).
  • v0.9.0 was released 2024-02-29; it simply added support for using Esc at the top level of the application to quit (I like to camp on Esc to GTFO).

Then, just now, I released v0.10.0. This release makes full use of some work I recently did to enhance Textual's CommandPalette widget, which added a "discover" system. The change in tinboard is that all of the command palette providers now have discover methods too. The result of this change is that when you open the command palette in tinboard (ctrl+p) you can see every possible command right away.

The command palette in discovery mode

Tinboard can be installed with pip or (ideally) pipx from PyPi. The source is available on GitHub.


How not to ask for help

Posted on 2024-02-04 11:27 +0000 in Coding • Tagged with free-software, foss, help • 9 min read

My association with Textual works on two levels: on the one hand, sure, it's currently my day job; on the other hand it's a FOSS project that I'm keen to support so "free time me" tries to work with it and support others working with it too. For this reason you'll often see me being terminally1 online in the Textual Discord, trying to answer questions as they come up, every waking free moment.

Almost without exception the people who ask for help are appreciative and ask in the spirit of wanting help and wanting to work together with whoever is helping them to get an answer. That... that's actually quite a cool thing to be part of. I like the sense of community that comes from someone going "bah I'm trying to do this thing and it isn't working PLEASE HELP!".

And then... well, let's just say that sometimes the odd question will crop up that seems to be asked from a less collaborative position.

Without wanting to appear to dunk on an individual (I don't wish to), I want to break down an example that happened yesterday. For some background, I'd been AFK all day, having a wonderful time in town with a friend, shopping, lunch, a movie, that sort of thing. A nicely-chilled day where I didn't even look at the Discord notifications that had popped up on my watch and phone.

However, later on that evening, finally home and flopped on the sofa, I saw a question pop up that, while lacking any useful detail and possibly suffering a wee bit from being an XY problem, the immediate answer was clear:

BadIdentifier: 'test.udp_json_client-input' is an invalid id; identifiers must contain only letters, numbers, underscores, or hyphens, and must not begin with a number.

????????????????? a dot isn't allowed?

Like I say: it lacks context and detail, and the number of question marks doesn't really clarify much, but the core question that seems to be at play here is "is it true that a full stop can't be used as part of the ID of a widget?".

The answer is: no, it can't. There's a reason for that too, and if someone were to take a step back for a moment and think about how IDs play a part in queries and how they'd be used in a stylesheet, the reason for that might pop out. So, to help the person asking the question walk in the direction of the answer, I reply:

When you come to query that how would the parser know it’s not ID “test” combined with a class, if dot was allowed?

Before we go on, to illustrate my point, consider this ID: foo.bar. When you come to query that back, or use it in a stylesheet, how would #foo.bar look? Is it a widget with the ID foo.bar; or is it a widget with the id foo and the class bar?

As far as Textual's CSS is concerned, it would be the latter.

But at this point it didn't seem necessary to get into all that detail; I like to try and assume knowledge on the part of the person asking the question, sans any other evidence, so for the moment I'll assume a "oh, right, yeah, that's a damn good point" kind of reply. Or if not, perhaps a "I don't quite follow, could you explain?" reply, in which case I'm happy to go into all the detail.

The reply was neither:

why are element ID and classes co-mingled?

this was previously allowed so your question doesn't really make sense to me

Now I'm confused. Asking why element IDs and classes are co-mingled seems odd; but I'm used to chatting with people who don't have English as a first language so I'm going to assume it's just a wording choice; but the latter part is very odd: this has never been allowed. Or, more to the point... without any proper context I can't really appreciate what claim is being made here.

You see... I did notice a bug in Textual recently, when it came to widget IDs. Long story short: when you set your ID for the widget in your code, no actual validation of the ID was being done. This was an oversight that was fixed in the latest release.

But knowing that that's the case would be guesswork on my part; I'm also fresh at my desk after a day out; I'm probably not quite in the coding/Textual zone yet, so rather than try and guess half of the conversation, it's easier to just ask the person who is asking. So I ask them to restate the question, and give some more background.

The reply is:

the objective is to put a string like test.udp_json_client-input as the label of a tab, which previously just used the ID property. from skimming release notes, is it better to explicitly set the label, and then assign something compliant for the ID separately?

Wait... what? I thought we were talking about valid widget IDs, now we're talking about tabs and labels? Do we mean TabbeContent and the labels of a TabPane? This is a bit different. So I'm sat there trying to figure out this person's thought process so I can offer the help they're after and this follows:

okay @davep, you have a real bug. --content-tab- prefix is not ephemeral. if I create a tab and grab .id, that prefix comes with it, so if you save it for later and try to set .active, assigning .active doesn't agree that there is a tab --content-tab-thing

so I need to de-mangle the name manually before assigning .active I guess?

Wut? Like... wut? Okay, we do seem to be talking about TabbedContent, I recognise the values being mentioned here; we did some work late on last year that added some namespacing to parts of the TabbedContent widget in an effort to reduce some foot-gun situations.

But... there's no . being used in the IDs as part of that; why are we now proclaiming a bug in an unrelated PR? That's quite the leap with zero evidence. Like... sure, I'm all for being alerted to bugs and fixing them, but this doesn't seem like that.

And then there's the "so I need to..." conclusion that also seems to have no connection to the original question.

Anyone who has ever done support will recognise this situation, I'm sure. Someone has seen a problem, they've dug around a little and reached a conclusion about what the cause is, and turns up looking for help with the conclusion they've reached (very much a variant of a XY problem).

That almost never gets us where we want to go, so I do the obvious thing; I try and reboot the question; I try and get us back to the start and try and get some clarity; I try and encourage asking the question with zero assumptions:

I'm afraid I'm still not really understanding your question, as it now no longer seems to relate to what you very first asked. Perhaps you could start again, ideally with an MRE of what you're looking at and trying to do, for clarity?

I figure, whatever the problem is, it can be illustrated with like a dozen lines of code. Also, when asking people to do this, it often actually helps them rubber-duck their own problem. There's been plenty of times on Discord where someone's "found a bug" in Textual, they're asked to make an MRE of it, and they come back and go "oh, shoot, right, I did that and realised the bug was in my code". It's cool when they happens; everyone learns something.

So... no MRE comes back, but this is the reply:

I'm trying to fix multiple breakages in my application from some recent changes. Right now I can't wrap my head around what to assign a tabbed_content.active for it to work how it did before (where if you have a tab with ID sample, you can assign tabbed_content.active = "sample", but you can't do that anymore)

While not an MRE, I can work with this. It seems clear that they have a TabbedContent where they have a TabPane with the ID "sample" and they are struggling to make it the active tab by setting active to "sample". That seems hugely unlikely, this is what TabbedContent is all about, I think we'd have noticed (I'm petty sure we've got unit tests that cover this), but I'm game. I can test this. And the MRE I write will illustrate there isn't a problem.

So I reply:

Again, I can only suggest that you make an MRE of the issue you're seeing. For example, here's me making a set of tabs, the last of which has the ID "four", and I set the active to "four":

and provide the code:

from textual.app import App, ComposeResult
from textual.widgets import TabbedContent, TabPane, Label

class TabbedContentApp(App[None]):

    def compose(self) -> ComposeResult:
        with TabbedContent():
            with TabPane("One", id="one"):
                yield Label("One")
            with TabPane("Two", id="two"):
                yield Label("Two")
            with TabPane("Three", id="three"):
                yield Label("Three")
            with TabPane("Four", id="four"):
                yield Label("Four")

    def on_mount(self) -> None:
        self.query_one(TabbedContent).active = "four"

if __name__ == "__main__":
    TabbedContentApp().run()

Based on what they've most-recently said is the problem, I'm confident they'll see that this MRE is their situation in a nutshell, and we can work out from there and figure out what the problem is they're seeing and where this . in their IDs is coming from (because I'm very confident it isn't coming from the work that was done on TabbedContent).

This is good. We're getting close to heading down a good path; I can feel it!

I was wrong.

https://github.com/Textualize/textual/blob/main/src/textual/widgets/_tabbed_content.py#L513

there's no way you can deny you just added a metric ton of shenanigans with the tab ID stuff. I can't get it to work at all anymore (assigning .active), but yes I will either come up with an MRE or find the bug and let you know

So, rather than back up a wee bit, work with the MRE I wrote for them so we can take a walk through the problem, they instead decide to tell me that the PR I did last year (which still isn't implicated in any of this outwith of them seemingly assuming it's the cause of all the issues, presented with zero evidence that it is) was simply "a metric ton of shenanigans".

No!

Stop!

This is not how you ask for help.

This isn't how you ask for help from a product or service you pay for. This really isn't how you ask for help from a Free Software project, where the people who are offering you help are doing so in their free time because they want people to be able to build cool things with it.

It really isn't hard at all to show just a wee bit of respect for people's time and willingness to try and help you.

Now... I get it. I can imagine a scenario where someone has just updated Textual and their application suddenly starts throwing all sorts of weird and new errors. That happens. That happened to me on Thursday evening just gone. But that's no reason for approaching getting help like this.

The way to approach it is this: pin the problem dependency, perhaps publish a new version of your application so there's no accidental update of the dependency, then head to any of the help resources for the dependency has and work with people who want to help you to find the cause of the problem. Trust me, it'll go a lot faster if you work with them, take on board suggestions (no matter how odd they might first appear), and really don't call their code "a metric ton of shenanigans".

The conclusion to all of this? The person asking the question eventually found they were setting some widget's ID to an invalid ID; one with a . in it. So as I suspected and wanted to walk them to: they had invalid IDs all along and they only found out about this because ID validation was fixed.

Perhaps one day they'll retract the claim that my actually-unrelated code that wasn't "just" released but was from last year is "a metric ton of shenanigans". ¯\_(ツ)_/¯


  1. Geddit? GEDDIT? 


Quizzical

Posted on 2024-01-29 21:30 +0000 in Coding • Tagged with Python, terminal, textual • 2 min read

I feel like I'm on a bit of a roll when it comes to building applications for the terminal at the moment; while I'm still tinkering and improving tinboard and OSHit, I had the urge to tackle another idea that's been on my TODO list for a while.

This is something I did for Emacs back in 2017 and I felt it was a perfect candidate for a Textual-based project. It's a terminal-based trivia quiz game, using the Open Trivia Database as the source of questions.

Quizzical

I've just published an early version to PyPI; it still needs some polish and I have a few other ideas for it, but as it stands I feel it's a fun little game to mess around with.

The idea is pretty straightforward: you can run it up and create lots of different quizzes, there are various parameters you can use to create lots of different kinds of challenges:

Building a new quiz

Once you're created a quiz, you can run it and answer away:

An example question

Right now the idea is that you answer by pressing either 1, 2, 3 or 4 (or just 1 or 2 for true/false questions); when I get a moment I'll also enable mouse support for selecting an answer too (honestly I feel keyboard-answering feels far more natural).

Once the quiz is done you can review your answers and see which were right and which were wrong:

Viewing results

As I say: there's a bunch of other things I want to add to this (keeping track of scores, adding session token support to reduce the chances of repeat questions, etc), but this felt like a good spot to make a v0.1.0 available if anyone else wanted to have a play.

Anyway, if this sounds like your sort of thing, it can be installed with pip or (ideally) pipx from PyPi. The source is available over on GitHub.

PS: Now you can see why I made textual-countdown.