I’ve been tinkering around with Pico-8 game development recently. I like how limiting it is. Expectations are not too high, which helps given that I’m hopeless at the non-coding aspects of game design, like artwork.

It’s quite a joy. Reminds me a lot of making games in QBasic back in the day.

And while we’re on the subject of tests: make it easy for me to launch tests quickly from the IDE. Refusing to run a test because a particular environment variable is not set is really annoying, especially when a suitable default value would work 90% of the time.

The unit test I’m working on uses so many mocks it’s infuriatingly difficult to change anything. I wonder if a better approach is to just mock out external dependencies and use the actual services in the test. May not be as unit-ey, but would make it easier to move things around.

Finished version 0.0.3 of Audax Toolset yesterday. The code has been ready since the weekend, but it took me Sunday morning and yesterday (Monday) evening to finish updating the website. All done now.

Now the question is whether to continue working on it, or do something different for a change. There are a few people using Dynamo-Browse at work now, so part of me feels like I should continue building features for it. But I also feel like switching to another project, at least for a little while.

I guess we’ll let any squeaky wheels make the decision for me.

πŸ”— emperror.dev/errors

Drop in replacement for the github.com/pkg/errors package. The original package is archived as there’s a plan to change how Go handles errors. But not all of us are ready to adopt this yet.

Detecting When GetItem On DynamoDB Returns Nothing

I was trying to remember how best to detect when a GetItem call to DynamoDB returns no values. That is, when there’s no item with that key in the table. This is in a project that is using v2 of the Go AWS SDK.

After poking through some old code that did this, it looks like the way to do so is to check that the returned Item field is nil:

out, err := p.client.GetItem(ctx, &dynamodb.GetItemInput{
    Key:       key,
    TableName: tableName,
})
if err != nil {
    // Error getting the item
    return nil, err
} else if out.Item == nil {
    // No item found
    return nil, nil
}

// Do the thing with out.Item

Ok, I can live with this approach. I’m kinda happy that I don’t need to check the error message, since doing so using the Go AWS SDK is a bit of a pain.

Some Photos of The Yarra Trail

Went for a very short walk of the Yarra Trail around Heidelberg on Saturday. The evening light was really lovely so I though I’d take some photos.

Milestone

For a while, I’ve been trying to maintain a writing streak. I need to write at least one blog post or journal entry a day. Today that streak has been maintained for a full year.

I will admit that the streak was not completely continuous: I had to go back a few times and retroactively add a post. But even so, I’m quite please with reaching this milestone. Onward to the next one.

It could be that I’m just an old fogie that doesn’t like whimsy, but chalk me up as someone who doesn’t like the wiggling seek bar in Android’s playback notification.

Currently reading: On Writing Well, 30th Anniversary Edition by William Zinsser πŸ“š

Only a few chapters in, but so far a great read.

Via Rec Diffs #188

I make something and not share it with anyone, I become frustrated.

I make something, share it with others, and they don’t use it, I become disappointed.

I make something, share it with others, and they do use it, I become terrified.

There’s no middle ground here, is there?

Knocked off some merge requests from my inbox and about to start work on a nicely sized development task. Looking to be a good Friday for coding. πŸ§‘β€πŸ’»

I’ve been using Dynamo-Browse all morning and I think I’ll make some notes about how the experience went. In short: the command line needs some quality of life improvements. Changing the values of two attributes on two different items, while putting them to the DynamoDB table each time, currently results in too many keystrokes, especially given that I was simply going back and forth between two different values for these attributes.

So, in no particular order, here is what I think needs to be improved with the Dynamo-Browse command line:

  • It needs command line completion. Typing out a full attribute name is so annoying, especially considering that you need to be a little careful you got the attribute name right, lest you actually add a new one.
  • It needs command line history. Pressing up a few times is so much better than typing out the full command again and again. Such a history could be something that lives in the workspace, preserving it across restarts.
  • The set-attr and del-attr commands need a switch to take the value directly, rather than by prompting the user to supply it after entering the command (it can still do that, but have an option to take it as a switch as well). I think having a -set switch after the attribute names would suffice.

Finally, I think it might be time to consider adding more language features to the command line. At the moment the commands are just made up of tokens, coming from a split on whitespace characters (while supporting quotes). But I think it may be necessary to convert this into a proper language grammar, and add some basic control structures to it, such as entering multiple commands in a single line. It doesn’t need to be a super sophisticated language: something similar to the like TCL or shell would be enough at first.

It might be that writing a script would have solved this problem, and it would (to a degree at least). But not everything needs to be a script. I tried writing a script this morning to do the thing I was working on and it felt just so overkill, especially considering how short-lived this script would actually be. Having something that you can whip up in a few minutes can be a great help. It would have probably taken me 15-30 minutes to write the script (plus the whole item update thing hasn’t been fully implemented yet).

Anyway, we’ll see which of the above improvements I’ll get to soon. I’m kinda thinking of putting this project on hold for a little while, so I could work on something different. But if this becomes too annoying, I may get to one or two of these.

Found a service this morning which was sending HTTP requests with a timeout of 1 second. This was causing problems in the service it was calling, which was unable to satisfy the request in that time. Who sets the HTTP request timeout to 1 second? Seems pretty low to me.

You can tell I’m bored at work when I spend time building stupid little utilities for myself instead of actually doing the task I should be doing. Today’s stupid little utility: a TUI tool to list merge requests I’ve posted for review. Saves a trip to the browser.

Thinking About Scripting In Dynamo-Browse

I’ve been using the scripting facilities of dynamo-browse for a little while now. So far they’ve been working reasonably well, but I think there’s room for improvement, especially in how scripts are structured.

At the moment, scripts look a bit like this:

const db = require("audax:dynamo-browse");
const exec = require("audax:x/exec");

db.session.registerCommand("cust", () => {
    db.ui.prompt("Enter UserID: ").then((userId) => {
        return exec.system("/Users/lmika/projects/accounts/lookup-customer-id.sh", userId);
    }).then((customerId) => {
        let userId = output.replace(/\s/g, "");        
        return db.session.query(`pk="CUSTOMER#${customerId}"`, {
            table: `account-service-dev`
        });
    }).then((custResultSet) => {
        if (custResultSet.rows.length == 0) {
            db.ui.print("No such user found");
            return;            
        }
        db.session.resultSet = custResultSet;
    });
});

This example script β€” modified from one that I’m actually using β€” will create a new cust command which will prompt the user for a user ID, run a shell script to return an associated customer ID for that user ID, run a query for that customer ID in the “account-service-dev”, and if there are any results, set that as the displayed result set.

This is all working fine, but there are a few things that I’m a little unhappy about. For example, I don’t really like the idea of scripts creating new commands off the session willy-nilly. Commands feel like they should be associated with the script that are implementing them, and this is sort of done internally but I’d like it to be explicit to the script writer as well.

At the same time, I’m not too keen of requiring the script writer to do things like define a manifest. That would dissuade “casual” script writers from writing scripts to perform one-off tasks.

So, I’m thinking of adding an plugin global object, which provides the hooks that the script writer can use to extend dynamo-browse. This will kinda be like the window global object in browser-based JavaScript environments.

Another thing that I’d like to do is split out the various services of dynamo-browse into separate pseudo packages. In the script above, calling require("audax:dynamo-browse") will return a single dynamo-browse proxy object, which provides access to the various services like running queries or displaying things to the user. This results in a lot of db.session.this or db.ui.that, which is a little unwieldily. Separating these into different packages will allow the script writer to associate them to different package aliases at the top-level.

With these changes, the script above will look a little like this:

const session = require("audax:dynamo-browse/session");
const ui = require("audax:dynamo-browse/ui");
const exec = require("audax:x/exec");

plugin.registerCommand("cust", () => {
    ui.prompt("Enter UserID: ").then((userId) => {
        return exec.system("/Users/lmika/projects/accounts/lookup-customer-id.sh", userId);
    }).then((customerId) => {
        let userId = output.replace(/\s/g, "");        
        return session.query(`pk="CUSTOMER#${customerId}"`, {
            table: `account-service-dev`
        });
    }).then((custResultSet) => {
        if (custResultSet.rows.length == 0) {
            ui.print("No such user found");
            return;            
        }
        session.resultSet = custResultSet;
    });
});

Is this better? Worse? I’ll give it a try and see how this goes.

Some Things I Found Out While Browsing a Substack Newsletter in The Wayback Machine

I did a quick search for that blog post in the the Wayback Machine. I couldn’t find the post but the Substack newsletter was there. I guess Substack does allows archiving of newsletters with the “substack.com” domain after all (if it’s something that they can even control).

Anyway, here are a few things I’ve found out while browsing through a Substack newsletter in the Wayback Machine:

  • Clicking “Let me read it first” works: it slides away and the most recent posts show up. Guess it’s just a simple HTML overlay blocking the home page.
  • Open all links in a new tab. Just clicking them will run some JavaScript which, I guess, tries to load the post directly from Substack, resulting in an error if the newsletter is taken offline. Opening the link in a new tab will get the post directly from the Wayback Machine.
  • Clicking the archive tab seems to bring up the blog archive briefly, but then some JavaScript β€” which I guess is trying to load the archive from Substack? β€” replaces it with an error (why does everything need to be JavaScript?!) I’m guessing that the actual HTML is still there so it might be possible to get it if you disable JavaScript. I haven’t tried this though, so this is only a hypothesis.

As for the post itself, it turns out that it was in my Feedbin archive all along, so the search in Wayback Machine wasn’t actually necessary. Now the trick is to find a way to prevent Feedbin from purging old posts. 😳

Over the weekend, I wanted to revisit a Substack post I read several months ago. Unfortunately, the newsletter was shutdown and the post is no longer available. With a bit of luck it will be available in the Wayback Machine. I’m hoping that works for Substack domain.

Can’t believe it took me this long to get a headset. Knowing that a mic is only a couple of centimetres from my lips makes a huge difference in how I speak. Saved me from killing my voice today after 4.5 hours of online meetings.

Follow-up on my phone issue. I think the problem relates to 5G connections. The drop-outs seem to have stopped after I’ve switched 5G off. Still not sure if the problem is the phone or the SIM, but I’m not sure if I’ll pursue it further. 4G seems to be fast enough for now.