This is a list of all the stuff I’ve recommended more than once, usually until someone has given me a glassy-eyed stare and agreed to try it. It’s mostly free-as-in-beer, with some paid licences adding more features, or a way to donate.
I like Apple’s desktop OS, and the software below makes it even better than the default experience.
If you ask me, Microsoft got window management right. I’m comfortable with how Mac does it, but these two bring over some of the Windows sparkle, and you’re better off for it.
Makes it easy to move windows around, snap them to an edge, resize them, and move them across monitors. Think of winkey + arrows. Provides key bindings and drag-drop. The shortcuts take some time to learn - I made an Anki deck for this - but now it’s great. I really missed this for the first few years on macOS.
Does what it says on the box, and quite pretty too. At heart, I use it because it’s better than Apple’s ⌘+`
and ⌘+↹
, but it’s also very configurable in terms of keys and appearance, and has some extra functionality.
This does one thing exceptionally well: I use twin Dell monitors connected with USB-C, and this lets you change their brightness just like your laptop screen. 1 When writing this blog post I realised I’d never contributed, so chucked them $10.
Apple’s trackpads are great, but this elevates them to “better than a mouse” for me. For work, anyway. I’ve added some gestures for browsers and file managers: 2-finger swipe up to open a new tab, 2-finger swipe down to close it, and tiptap left/right to change tab. It’s far more powerful than I use it for, and that also makes it fun to play with on long trips without internet service.2
Great fuzzy finder with a ton of built-in features, plus it’s very programmable, which makes it fun to extend. The native Spotlight has supposedly improved since I started using Alfred, but by now, it’s just too powerful (and customised) to leave. I use it daily for instant lookups like country codes, HTTP status codes and IATA codes. I also saved a bunch of snippets so it’s easy to type fancy characters like ⌘
with snip cmd
. Fallback searches are great too; being able to hit ⌘+space
and type wiki foo
to go straight to Wikipedia’s page on Foobar is just lovely.
Everyone should be using a password manager. Given how many passwords your digital life requires, and how common credential stuffing and data breaches are, it’s essential hygiene. I’m not telling you to use 1P necessarily, but it’s been pretty good to me.
Their Families plan is good value for the geek in the family, and I was paying for it for ages. Sometimes, if your employer uses it, the enterprise plans include a free Families licence for you. 😉
Lets you use your phone as a webcam. For a lot of people, your phone is the best camera you have, and far better than whatever your laptop has.
Apple’s Continuity Camera has largely eaten Camo’s lunch, but I still use it for some extra dials and knobs.
The best terminal I’ve used, and I sponsor it on GitHub. Generally really nice with sensible defaults, good support, and powerful features. Heaps of them are useful, like split panes, command-clicking URLs, smart selection, infinite scrollback and shell integration. Heaps are just cool for tinkering, like coprocesses, triggers and showing images. Bonus points for its tmux integration which I don’t use regularly but is amazing when I do - it uses native tabs and panes for tmux’s own.
Amazing software, and it probably helped me get hired at Canva. Quick access to all sorts of documentation3, and in a native app. I adore it for Python. The default docsets, and those submitted by the community, are super broad. Not a fan of its new subscription model though, so I’ll probably be using my Dash 6 licence indefinitely.
The last browser before Chromium eats everything. Also has customisations I value, like separate search and address bars.
Just a good ad blocker. I think ad blockers in general are crucial to a good internet experience. This also has a good picker interface to let you block anything you like, such as Twitter’s “trending” sidebar.
An absolute marvel for Facebook users. You know those chain letters you see about “like and share this post to see your friends’ activity again”? This actually works, and lets you block a lot of the trash injected into the feed and sidebar as well. It surely won’t be around forever, but for now, it does an outsized amount to make the internet a better place for real people.
Brings your YouTube experience up a few IQ points, by reducing clickbait in thumbnails and titles.
You know how you turn off your adblocker, and suddenly the web is a garish carnival of adverts willing to use any trick to grab your attention? Viewing YouTube on a client without DeArrow is starting to feel a bit like that.
Makes it super easy to contribute your own improvements, too. I’m a sucker for editorial control.4
The physical buttons tucked away behind the monitor are annoying to use, and MonitorControl has the same effect. It doesn’t have to fiddle with gamma to fake it. ↩
Everyone finds fun in their own way. I know a guy who went on a spree in his home workshop and put everything on wheels. ↩
It’s not quite “lookups at the speed of thought”, but pretty close. ↩
The before/after comparison on their homepage is pretty cool too. ↩
Our AirBnB host had assured us that the road was only slightly flooded, and we’d also discovered that the only other way there would see us ride in the dark - it needed another 40 minutes on an unsealed road, while watching nervously for kangaroos quite literally jumping out at us.
So: time to try to ford it.
Great trip, even if I wished my bike was actually the BMW GS it dresses up as. We went from Brisbane to Warwick to stay up and watch the Geminid Meteor Shower with dark skies. Didn’t try for any pics of that though!
Dad dug up a photo from my childhood, probably the one time they let me ride a quad bike, adding “You used not to be so careful…”
Maybe that’s why it was the only time. 🤔
]]>It was a lot of fun! Heaps of questions from the floor. Plus, there was a plush shark on my lap the whole time. #blahajlyfe
I was using Terraform to manage some infrastructure, and some resource was giving me grief - breaking the program and giving an unbeatably vague error message when I tried to do a plan involving it. Unfortunately the TF state file was over a thousand lines, and so it was hard to track down the resource in question. It was hard to isolate individual resources in such a big file.
Each domain name creates four kinds of resources from each zone, so that meant working with four separate arrays in the JSON.1 So: I needed to manipulate the 4 kinds of resources created from each zone, directly in tfstate, and remove everything except the broken part to know what’s causing the problem. Processing JSON? Sounds like a task for the sublime jq.
Given a tfstate file like:
{"resources": [
{something boring},
{"name": "parking_dkim",
"instances": [
{"index_key": "lord.geek.nz", ...},
{"index_key": "dal-corp.com", ...},
... lots more instances here
]},
{"name": "parking_dmarc",
"instances": [
{"index_key": "lord.geek.nz", ...},
{"index_key": "dal-corp.com", ...},
... lots more instances here
]}
}
Back up the original file:
cp prod.tfvars prod_full.tfvars
and then run the following examples like this:
jq -f example.jq < prod_full.tfvars > prod.tfvars
Let’s get into it.
Make your own functions for a binary search by chaining these:
def right_half(addr):
del(addr[0:(addr|length)/2]);
def left_half(addr):
del(
addr[
((addr|length)/2 | floor) : ]
);
[range(1; 18)] |
right_half(.) |
left_half(.)
The example starts with the array [1..18], then takes the right half. It slices that up and takes the left half of what’s left, returning [10, 11, 12, 13].
With these functions in hand, you can apply them to do your own binary search in tfstate. Narrow down and see what’s breaking it.
Try modifying the state by removing half of it, then run Terraform again, and see if it breaks:
def right_half(addr):
del(addr.instances[0:(addr.instances|length)/2]);
def left_half(addr):
del(
addr.instances[
((addr.instances|length)/2 | floor) : ]
);
left_half(.resources[1,2,3,4])
If that works fine (no error), try the right half instead:
# declarations as above
right_half(.resources[1,2,3,4])
Say that showed the error, then you’d subdivide it:
# declarations as above
right_half(.resources[1,2,3,4]) |
left_half(.resources[1,2,3,4])
If that shows the error, great, add another | left_half
to keep narrowing it down. If not, try | right_half
again. Keep doing this until you’ve found one set of resources corresponding to one domain. Tada, you’ve done it.
Once you know what you’re looking for, or only a couple, you can try another approach to only keep specific domains’ resources and discard the rest.
This syntax is super useful, using the update operator with map
and select
.
You can filter members of the resources array by whether their index_key has one of the zones you want.
["lord.geek.nz", "dal-corp.com", "angry.nerds"] as $targets |
.resources[1,2,3,4].instances |= map(select([.index_key] | inside($targets))) |
This is not the best explanation I can write (promise!) but I hope these code samples come in handy for you.
The jq manual is packed full of useful information. It’s usually a better place to start than StackOverflow.
Hashicorp discourage you from treating it as JSON, but if I respected warning labels, I wouldn’t have eaten all those paint chips as a kid. Lead is an all-natural sweetener and sometimes you’ve just gotta hack some state. ↩
A British author has 80% of their new novel written, but the last 20% just won't come. They're stumped. After weeks, their eyes turn to the Shakespeare section of their home office. A merry wanderer whispers in his ear, "What would just a little hurt? Just to unblock the spring?"
— dal (@dal_geek) January 22, 2022
They resist, but after another week, the serpent cherub's whisperings lead their frustration to take down the books of the Bard. "Is it plagiarism if it's out of copyright?" they wonder, and then, "What is plagiarism but a rude name for a tribute? A pastiche? An inspiration?"
— dal (@dal_geek) January 22, 2022
They pore over the tomes, and finally decide: they'll use A Midsummer Night's Dream. Just a little bit of setting, a little twist of character, one thing leads to another...
— dal (@dal_geek) January 22, 2022
... and suddenly the climax of the book is yet another fey performance which puts the villagefolk, and then the reader, to sleep, and when they awaken, all the threads of plot have disappeared and been replaced by a neatly woven image. An empty saucer with dregs of milk resides.
— dal (@dal_geek) January 22, 2022
I really don't know what it is with British authors and over-seasoning their novels with Shakespearean references, like a wealthy merchant pouring an ounce of nutmeg into their wine because it's posh - perhaps it's required for entry into the writers' guild? - but it has to stop.
— dal (@dal_geek) January 22, 2022
This level of cultural influence feels like Brisbane insisting that it's a world city because it hosted the World Expo in 1988. Yes, well done, but keep up with the times. Do something new. Don't stand on the shoulders of giants until they're a weathered pair of legs in a desert.
— dal (@dal_geek) January 22, 2022
This rant was brought to you by the creeping horror of reaching Rotherweird's final act and realising that it's been quietly assembling the Midsummer Night Exodia for the last two hundred pages.
— dal (@dal_geek) January 22, 2022
(I did like Rotherweird, and I do like Brisbane, and I even excuse Pratchett given that he referenced the sum total of human history in his time. But I wish Shakspere didn't crop up so often. What magic was there is gone now that we have high-speed rail and paperless offices.)
— dal (@dal_geek) January 22, 2022
I’m pretty happy with this. It was all written off the cuff, tweet by tweet, and incorporates enough references of my own to add some spice. A Midsummer Night’s Dream, pixie folklore, spice trade, Brisbane’s expo, Newton into Ozymandias, Yu-Gi-Oh. It’s scattered but, I dunno, I like that breadth.
However… it’s also, in the cold light of day, a bit ruder than I’m proud of. This is hard to quantify - it’s fun that way, but the internet has enough flame wars, you know?
]]>I’ve also been wanting to play with Rust, because of its attitude to performance and distribution. You can only create so many virtualenvs on so many hosts before Python starts to wear thin.
Figured this would be a good way to see whether the grass really is greener. After some futzing around with lifetimes… maybe not. The most interesting bug was from taking a std::process:ChildStdout
by value, calling .as_raw_fd
by reference in the same expression, leaving the ChildStdout
without a reference and therefore dropping it, and it closed the file descriptor on drop. So it was just closed as soon as I wanted to use it.
The project could really use some tests and probably some modularity, but the perfect is the enemy of the good and all that. Maybe I’ll come back to it.
Thanks heaps to the friendly folks in the UQ Computing Society’s #rust channel in Slack for unblocking me twice.
Anyway, here’s prettypipe.
]]>I wanted to play with Bootstrap out of hours, so I can make front-end changes if need be, and inspiration struck. Figured it’d be worth learning v4 to a) see where things are going and b) be prepared to migrate ours if needed in future.
Anyway, here’s catstrap.
]]>Behind the scenes looks something like:
Adapted from tutvid’s tutorial on countdown timers.
]]>If you want to use them with a regular expression, life can get a lot harder; there are a couple of pitfalls you need to avoid.
This is a quick info post to demonstrate that combination. It uses SQLAlchemy 1.3 and MySQL 5.7.
The queries we want to run will provide boolean attributes called is_alert
and is_update
, which look like this:
It can sometimes be difficult for beginners (like me!) to translate SQL into SQLAlchemy, so the rest of the post will explain how.
Here’s how to create two hybrid attributes, is_alert
and is_update
, which use regular expressions, with a MySQL back-end.
from sqlalchemy import Column, Text
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class Bulletin(Base):
__tablename__ = 'nodes'
title = Column(Text, name='title')
""" The pattern for is_alert is the same in both Python and MySQL, so could be a class constant """
@hybrid_property
def is_alert(self) -> bool:
return bool(re.search(r'- (?:UPDATED? )?ALERT', self.title))
@is_alert.expression
def is_alert(self) -> bool:
return self.title.op('regexp')(r'- (?:UPDATED? )?ALERT')
""" The patterns for is_update are different between Python and MySQL as they use different regex dialects """
@hybrid_property
def is_update(self) -> bool:
return bool(re.search(r'\.\d{4}\.\d+$', self.node_reference))
@is_update.expression
def is_update(self) -> bool:
return self.node_reference.op('regexp')('[[.full-stop.]][[:digit:]]{4}[[.full-stop.]][[:digit:]]+$')
# for more information on this regex dialect, see https://dev.mysql.com/doc/refman/5.7/en/regexp.html
MySQL uses ‘REGEXP’ or ‘RLIKE’, as in title REGEXP 'some pattern'
. If you’re using another back-end, you might need a different operator.
Remember, the queries we want to run will look like this:
SQLAlchemy 1.3 doesn’t have a built-in operator for regular expressions, so we have to use .op
to access it. You don’t need to import anything - it’s available straight from the Column. It takes a string, which is the name of the SQL operator you want.
So to get access to that from Python, we use title.op('regexp')('some pattern')
. We could use title.op('rlike')('some pattern')
, since MySQL considers it a different name for the same thing.
There’s nothing wrong with using SQL functions in general, but I’m highlighting this to make you aware:
SQL functions and SQL operators are different things. Write down the SQL you want to run and make sure you know which you want.
It might sound obvious, but I wish someone had highlighted that while I was trying to solve this - mixing them up will do you no favours. 😄
We are not doing this right now, because it’s a different approach which (in MySQL) requires the EXECUTE
permission:
from sqlalchemy import func
class Bulletin:
...
@is_alert.expression
def is_alert(self) -> bool:
regexp_func = func('regexp')
return regexp_func(self.title, 'some pattern')
This code will give you a SQL function, not a SQL operator. The difference is this:
name | are we using it here? | example |
---|---|---|
operator | yes | title REGEXP pattern |
function | no | regexp_match(title, pattern) |
The regexp_match
function provided by MySQL will do the job, but it’s a function, and this example is using operators. So we’re not using that function.
A hybrid property, in short, is a property on the class which you can use in both of these ways:
# As a Python property (example A):
my_bulletin = db_session.query(Bulletin).first()
if my_bulletin.is_update:
print("Yep, it's an update")
# As an attribute in SQL (example B):
updates = db_session.query(Bulletin).filter(Bulletin.is_update == 1)
from sqlalchemy import not
not_updates = db_session.query(Bulletin).filter(not_(Bulletin.is_update))
The first examples in SQLAlchemy’s documentation use some convenient examples where you can write some Python code which will also be translated implicitly to SQL. That’s cool because it’s good to know that can work. However, it’s often not the case with regexes, and here’s where I stumbled. For this task you need to specify two versions - one native Python (example A) and one in SQLAlchemy’s own Python-that-means-SQL (example B).
To know how these are declared separately, see the top example again. But I hope you now understand why.
There are also hybrid methods, which we aren’t going into here.
You hopefully understand, or at the very least have working example code for, querying on regular expressions in SQLAlchemy.
Cheers!
]]>Hades is superb, with art and writing to match the gameplay, and I’ve played through it enough times to reach ~5 heat with most of the weapons. When we were in managed isolation in early November, it was one of my coping strategies for being stuck in a room with my girlfriend, both working full-time and on video calls, for two weeks. 1
Slay the Spire is also a great game. I tried it a few years ago on iOS, but didn’t really grok it2 until seeing Firebat’s thunder strike combo run last week. It’s the first deck-building game I’ve enjoyed. The card game Dominion is many people’s first introduction to the concept, and it’s all very well, but playing it with other people means you can’t just slow down and really think about your turn.
Although… I did get into Faster Than Light when it was released, and again when they released a free full expansion (!). It also has the core gameplay loop of playing through a map, facing discrete encounters, and accruing loot, abilities and scars. It can be unrelenting at times, where a run of bad luck can throw away an hour’s play, and I ended up playing it on its easier mode for more enjoyment. It also had enough original ideas that Door Monster did a great sketch about it.
Actually… I lied, Hearthstone’s dungeon run mode was probably my first introduction to a fun deck-builder. It takes a game you already know well, with familiar mechanics and art, and then sneaks in cards and abilities, and suddenly you’re vastly overpowered but facing a boss whose mechanics you didn’t anticipate, and trying to figure out how to stay alive one more turn and hang on to the deck you just spent an hour building.
One thing I’d like to see explored in the genre is valuing the player’s time. If you die in a run, you lose a state that you’ve probably spent half an hour assembling. That does introduce a tension which helps make the battle feel meaningful, but… for a working stiff, with finite leisure time, it would be nice to have a “rewind” button.
Dark Souls was really satisfying to learn and get right, and just as unrelenting when you got it wrong, but I never even managed to finish the first game because it also uses this tradeoff of leisure time for tension. It’s very effective, but so costly.
Leisure time should respect the player’s time.
Oh, and one more thing, which seems to be an original thought; Slay the Spire’s boss music put me so strongly in mind of One-Winged Angel that after finishing the last battle, I couldn’t remember the actual music, but I did now have One-Winged Angel stuck in my head. It’s a legendary track, and if you’ve never heard it (Ben) then it’s worth a listen. But when I went looking for discussion on it, I found none. Shame - you always wonder if someone can explain precisely what makes them similar, in fancy music words.
I adore her, but being in one room with one person for a fortnight (plus walking breaks in the prison yard) will strain the sweetest of temperaments, possibly through a sieve. ↩
“Grok” is another word for “understand”, favoured by nerds particularly for its use in the famous StackOverflow post about grokking vim. Wikipedia reckons it showed up in scifi in 1961. ↩