Categories
Review

“Dance for the Ivory Madonna,” or, “actually, some of this might work”

Don Sakers
This is… the most fun piece of cyberpunk I’ve read. I was going to add a qualifier to that, but in trying to come up with one, I realized it doesn’t need one; it’s just the best one.
Unlike most cyberpunk, it doesn’t feel dated by the technology. Sure, it’s set in the future, which helps, but it’s set in a future that feels like a reasonable future based on our current technology, not based on the 1980s.
The setting is fascinating: the world map has been severely redrawn, most noticeably by the USA splitting into several pieces, and by the fledgling African Union actually taking off and becoming a (if not the) world power. At the same time, however, those national divides have become less important, with the UN finally taking over global police actions, aided by a technocratic NGO, the Nexus.
The protagonist is a Nexus operative, and as the story goes on you find out he’s veritable royalty — his father a founding member of Umoji, the African economic union, his grandmother the person whose ideas gave birth to the Nexus, and a few other fun surprised along the way. (I won’t spoil any more than that, it truly was fun finding things out as I went.)
Throw in the global economy being run by AIs, a well-explained split between AR and VR, and a space program based on a mix of ion thrusters and orbital velocity cannons paired with gigawatt-laser-pumped-solar-sails, and I am sold on this setting.
I’m interested to read more of this — I’m reading the Worlds Afire omnibus, which includes three books in the series, if I’m remembering correctly. However, the series isn’t just in this one era, it’s apparently operating on a truly enormous scale, so it’s very possible that the events of the next book will be more than a billion years removed from what I just read.
As long as the next is further into the future, though, I can reasonably expect to see at least historical references to the characters here — the results of the plot certainly feel big enough that they’d carry a long ways throughout human history.
If I’ve sold you on this book now, which I rather hope I have, because it’s a delight, you can go grab the omnibus.

Categories
Review

“Please Don’t Tell My Parents You Believe Her,” or, “a much better end than I was expecting”

Richard Roberts
This is another book that I put off reading for a while. I knew going in that it was the last in the series — Roberts’ blog made that pretty clear — and then, shortly after I bought it, his publisher went under (or something? I’m unclear) and seemed to pretty effectively tank any hope for future works in the amazing world he’s built here.1
And that’s what always shines to me about his books: the world-building. Roberts has a gift for showing without telling, and manages to perfect balance explaining a little bit and leaving a bit to the imagination. One of my favorite scenes in “… You Believe Her” was Penny, sitting on a train, watching a couple boys study. It’s just that one of them was using his telekinesis to levitate the book instead of holding it with his hands. And she goes off on a little tangent, thinking about the statistics of superpowers, and we find out that the superheroes and supervillains are the statistical outliers, while there are also sorts of normal people who use their powers to… not wear spandex and beat each other up. To study. To do their jobs. To make music, or build cool computers.
That’s what I love about the series. It’s a great big world, and Roberts wants to follow the same “but what about-“ trail of implications that I always do.
It’s also hilarious, if my gushing over the world building hasn’t sold you. This book introduces Gerty the Animatronic Goat, who I described to my friend as “the single best comic-relief character I’ve ever read.” It’s silly, and wholesome, and my jaw is a little sore from how much I smiled while I was reading the book.
And the thing is, Gerty is present throughout the book, and it’s necessary. She’s comic relief, because what’s actually happening in the plot is heavy. It’s probably a requirement to read the previous book first, to be able to follow what’s going on, as it starts off pretty in the middle of things.
It’s dark and sad, and happy and silly. It’s an excellent read. Check it out.2


  1. Happy follow-up, though: I believe he’s since got the rights sorted out enough that he can resume his plans to write more in this world. 
  2. And join me in reading Roberts’ new book, in a totally different setting. I had the chance to read an early-release version of the first couple chapters a while back, and I’ve been looking forward to the full novel ever since. Hopefully I don’t take quite as long to get around to reading it as I did this one. 
Categories
Review

“Red, White, and Royal Blue,” or, “I was wrong about which genre this book was”

Casey McQuiston
I put off reading this one for a while, because it seemed like it was going to be dumb and fluffy, the sort of thing I like to save for when I’m stressed and need something easy and happy. And I’m quite happy to have been wrong about it, in part: while it’s certainly fluffy, it’s less dumb than I was expecting. Sure, the protagonist spends a bit too long not quite grasping what’s going on, but that actually gets turned around pretty well later on. And it’s a surprisingly good political novel, too — the backdrop of “being the son of the President” isn’t left as window-dressing, instead becoming a significant driver of the plot.
The cast is delightful — there’s a good deal of family drama going on, and it feels real, and rough at times.
All in all, I loved this book — stayed up too late reading it, laughed the way through, and would happily read it again. I can heartily recommend it.

Categories
Review

“The Plutonium Files,” or, “‘it’s a good thing we’re the good guys and the laws don’t apply to us,’ they said”

Eileen Welsome
I’m not sure what it says about me that all of my nonfiction reading is about the Cold War, but here we are again.
The funny thing about this book — and there isn’t much of that, because it’s a detailed account of some truly horrible things — is what did and didn’t stick in my mind. A lot of the book was about trying to humanize the victims of the experiments, and that aspect didn’t really land for me. The actual experiments, what was done, did stick, to a degree; having just finished my read, the ones I most remember are the prison experiments in Oregon and Washington, the radioactive iron supplements at Vanderbilt, a bit about the total-body irradiation experiments, and, of course, the titular plutonium trials. Some of the accidents also stood out to me — there’s a discussion of a man who took a plutonium criticality to the face, and the summary of how thoroughly screwed you are by that is that, when he threw up on the floor of the hospital an hour or two later, after they’d cleaned the floor, they had to get out a geiger counter to check if it was safe for anyone to walk there. (He didn’t survive; to add insult to horrible injury, his body was then parceled out to labs around the country, without the permission or knowledge of his next of kin.)
There were also a couple figures, dropped in as part of an anecdote in the portion of the book about the pilots who flew planes through mushroom clouds to measure their effects, that lodged in my head pretty effectively.
The first set of tests after WWII ended were called Operation Crossroads. The second of these was an underwater detonation; I’ve heard the story before that, during the explosion, a Japanese battleship was thrown — 30,000 tons of metal, launched out of the water. (I’ve been trying to confirm this story in writing this, but haven’t found any clear evidence either way, so I’m going to call it apocryphal and move on.)
This story, though, was from the Castle series, Castle Bravo, the first thermonuclear weapon test. 15 megatons of TNT; while it wasn’t a useable weapon — the device was the size of a small building, and had to be constructed in-place on the ‘target’ island — it was mind-boggling in scale. Because, 15 megatons of TNT, that’s… a number. But what the book described was a 20-mile-wide column of water and mud, 45,000 feet tall. Again, mind-boggling in scale, but slightly easier to conceptualize; just imagine a mountain, and then… make it taller.
The figure that truly got to me, though, was the statement that it took hours for the water and mud to finish falling back into the ocean. Hours.
These nuclear tests were also so bright that test animals, 350 miles away, got retinal burns from looking directly at it.
It’s a scale of destruction that I can’t fit in my mind. Humans aren’t equipped to think about this sort of thing.
And it’s not the scariest part, is the thing. Sure, you can erase a city in the blink of an eye.
This is where the book shines: it’s about the radiation, and just how scary and insidious it is. I’ve mentioned before that people aren’t afraid enough of nuclear war; at risk of sounding like a broken record, I’ll say it again. Write your Congresspeople, and advocate for disarmament, everyone.

Categories
Review

“The One Device,” or, “I’m amazed this man didn’t get arrested”

Brian Merchant
It’s rather fitting that I’m writing this review on my iPhone. Parts of the book were written on an iPhone, I suspect, and the author mentioned that a good deal of the interview recordings and photos were made on his iPhone.
Structurally, the book is interesting — there are two through lines, and they’ve got the same writing style but different feels. The more story-like one is the historical aspect, going from the beginning of the project through to the keynote where Steve Jobs introduced the world to the iPhone. And it’s a story, for sure: there’s a narrative to it, characters being introduced, politics and inventions, failures and triumphs. It’s the best telling of the story I’ve read so far, though admittedly I don’t think I’ve actually sat down to read the full story before.1
The other part is more of the ‘now’ aspect, which explores the impact of the iPhone as a product, focusing on the manufacturing process. The author tells how he… made his way into the Foxconn plant where iPhones are assembled; predictably gets hacked immediately after arriving at a hacker convention; goes on a claustrophobic tour of a tin mine; under-details an agoraphobic tour of the salt flats that produce most of the lithium used in the iPhone’s battery; and a few other stops along the way.
All told, it’s an interesting read. Some of the historical context was new to me—the history of ARM was inspiring, for example—and while I already knew a lot of things—photos of those lithium flats are pretty striking—I’m glad I took the time to read it. If you’re at all interested in the history, I can recommend the book.


  1. Creative Selection is on my list to read, so I’ll get there eventually. 
Categories
Technology

State of the Apps 2019

Inspired by CGP Grey’s post that started a Cortex tradition, here’s the current state of my phone:

This is… a work in progress. I got this phone in September, and while it’s been on my mind to do a full reorganization, I haven’t had time to do a full “tear it all down and start from scratch” process. The top two rows, especially, are very temporary — for the first time since iOS 7 came out, I’ve disabled Reduce Motion, and the parallax makes the fake invisible icons trick look terrible.
So rather than go through things in top-to-bottom, right-to-left order, I’m just going to talk about them in whatever order strikes my fancy.

  • Things remains my task management app of choice. I love it across all platforms, and happily recommend it to anyone who’s looking for something more robust than Reminders or a list in Notes. For me, it strikes the right balance of features without getting too heavy, and while I’ve got one or two things I’d like to see added, I have no great complaints.1
  • FoodNoms has been a very nice addition – it replaced Calory, which had replaced MyFitnessPal, which had replaced Lose It!. I’ve got a long history of tracking food, and while I quite liked Calory, FoodNoms is the first time I’ve gone “ah, never mind, don’t need this” and tossed out my notes on how I would build a food-tracking app. I haven’t yet gone for the subscription, because it just doesn’t have any features that interest me, but based on the rate of development, I’m expecting to make that change within the next few months.’
  • Timery is another stellar addition. It’s in that same category as FoodNoms — I had some sketches started of how I’d make an app in this category, and Timery made them completely irrelevant. The last two updates have added some truly excellent Shortcuts integrations — the last one added conversational shortcuts, so I can now just say “Hey Siri, Toggl” and talk through starting a timer with a specific project and description, or kick off a few frequently-used ones with a short phrase. The newest updated added some more programmatic stuff, and I’m planning to take some time over Christmas weekend to rebuild my old Toggl shortcuts, based on Federico Viticci’s examples, with Timery instead of custom web API calls.
  • Toolbox Pro – speaking of Shortcuts, Toolbox Pro is a neat little collection of Shortcuts actions. I’m most excited about the Variables feature, which I’m hoping I can use to improve some of my daily automation stuff.
  • Mail has replaced Airmail. I’d been vaguely looking for a replacement for Airmail, because it had a nasty habit of crashing all the time, and then they did a terrible job of switching to a new business model, and I threw my hands up in the air and decided to try the system default. It’s been working perfectly on iOS; on macOS, I’ve got a cobbled-together system using BetterTouchTool that sorta gives it real keyboard shortcuts,2 and a launchd script that relaunches it when it crashes.3
  • Day One remains my stalwart for journaling, but I’ve been slowly increasing the things I use it for. It’s my archive of Instagram, where I store my sketches, and the app I used to record some interviews I did for class.4
  • Ulysses is where I’m writing this article! It’s still my go-to for any long-form writing, and I love it. I haven’t yet made much use of their recent ability to store Ulysses files in Dropbox (or other arbitrary locations on disk), but I do have a collection of plain-old-markdown files that I edit in Ulysses on my Mac and Sublime Text on Windows.5
  • Reeder is a continuation and an addition all at once — I’ve been using it on my Mac for a while, but didn’t have it on iOS. I hit the maximum number of feeds on the free level of Feedly, and was extremely unimpressed with their paid offerings; I considered making a second account to keep syncing, but decided that was sorta rude to them, and instead opted to not have sync at all. That worked for a while, and then I got a Synology, and after setting it up as a Plex server, spent some time looking into RSS server options. At the moment, I’m using TT-RSS with a plugin for Fever support, but if anyone knows of something that’s easy to set up and has Google Reader API support, I’d appreciate it.6
  • Dark Sky’s recent redesign has me pretty happy. If they let me reorder the types of information, I’d be happier, but the clarity of the “when is it going to rain” charts is still excellent.
  • Overcast remains my podcast app of choice. Podcasts have been a great way to help me establish a gym habit — I established a podcast habit, and then decided that podcasts are things I can only listen to while driving, cleaning, or working out. (If you want podcast recommendations: Cortex, Do By Friday, ATP, 99PI, and MBMBAM are my mainstays.)
  • Strong, speaking of a gym habit, is the driving force of my time at the gym. A couple of my friends have been helping me out with designing actual workout programs to do, but Strong is where I put those in. It’s easy to use, remembers all the numbers so I don’t have to, and has instructions, often accompanied by images or GIFs, on a lot of exercises.
  • Streaks is where I track all my habits, from “did you remember to take your meds” to “do some writing for your blog” to “have you gone to the gym enough times this week?” It’s very good at what it does, and I’m still a fan.
  • Fluidics is a bit self-serving to include here, but I use it all the time. I’m planning to update it eventually — I’d like proper Dark Mode support, at the very least — but it’s hard to find the time.
  • Wallet has gotten more and more important over time, though not as fast as I’d like it to. Let me put my driver’s license in there, already. Apple Card is slowly taking over as my main credit card, Apple Cash is even more handy with the cash back in there, and it’s not too hard to make your own pass of your gym membership.
  • Sleep Cycle is possibly on it’s way out; I’m strongly considering getting a beddit, although I need to do more research — does it have the ‘smart alarm’ feature? How accurate is it? Is Apple going to kill the app soon? Lots of questions.
  • Dark Noise is a new addition in the past few days; I’ve been switching from a ‘sleep’ playlist to white noise in an effort to get Apple Music’s recommendations to not be entirely useless.7 I tried to use Sleep Cycle’s white noise feature for a while, but it assumes that I want the white noise to stop after a while, which is absolutely not the case. Dark Noise’s actual noise is a bit less interesting overall, which is possibly the point, and the app itself is delightfully well-made.

  1. I was, admittedly, tempted by OmniFocus, because OmniFocus for Web means I could have a single unified system across my Mac, iOS devices, and work PC, but it’s still just too much for my needs. And expensive. 
  2. Listen, Apple: you can either comp me the cost of getting a new keyboard that’s got the Inverted T arrangement for the arrow keys, or you can let me go from message to message using j/k. (And even if they did give me a free keyboard, I’d still complain; I’ve been using j/k to get around for two decades now, and it’s just easier.) 
  3. And on Windows, both Outlook and Windows Mail crash frequently, too; apparently IMAP, despite being 30-something-years-old, is still an unsolved problem? 
  4. In typing this, I’ve just realized that I think I’m using Day One the way Evernote wants to be used. Huh. 
  5. All synced by Git, because… why not? 
  6. The Fever API neglected any form of subscription management, and needing to pull up the TT-RSS frontend in a web browser whenever I want to add or remove a subscription just feels silly. 
  7. Fun fact: the “use listening history” setting that all Apple Music clients have? Doesn’t appear to do anything. Neither does “stop recommending music like this.” 
Categories
Programming

SwiftUI’s Picker

I’m very excited about SwiftUI, and have been using what little free time I have to do some tinkering with it. I started during the beta period, which was fun in between being very frustrating; a lovely side effect was that some of the knowledge I picked up is… entirely wrong. One that caught me was the implementation details for the Picker type.
Based on the rather rough state of the SwiftUI documentation for Picker and ForEach,1 I’d assumed that combining the right binding with a .tag(_:) on the items would work:

Form {
    Picker(selection: $selectedItemID, label: Text("Choose Something") {
        ForEach(items){
            Text($0.label).tag($0.value)
        }
    }
    Text("You've selected item \(selectedItemID)!")
}

For reference, the models I’m referring to throughout are pretty simple:

struct CustomModel {
    let value: Int
    let label: String
}

Now this looks like it’s working in simple cases. However, I was trying to interact with a web API, so that items array looked something like this:

var items: CustomModel[] = [
    CustomModel(value: 7, label: "First"),
    CustomModel(value: 3, label: "Second"),
    CustomModel(value: 1, label: "Third")
]

If you tapped “Second” in the picker that SwiftUI generated, however, the text wouldn’t read “You’ve selected item 3!” like it should; it would be “You’ve selected item 1!”
A bit more tinkering revealed that, instead of pulling the value from the .tag(_:) on there, it was just using… the index in the ForEach.2
After some frustrated Googling, utterly despairing of Apple’s documentation, and a lot of StackOverflow searches, I finally figured out the solution:

Form {
    Picker(selection: $selectedItemID, label: Text("Choose Something") {
        ForEach(items, id: \.value){
            Text($0.label).tag($0.value)
        }
    }
    Text("You've selected item \(selectedItemID)!")
}

Quite frankly, I don’t have a good explanation of what’s going on here; last time I was tinkering with Pickers, the .tag(_:) provided SwiftUI with the information it needed to do the binding. (When I’ve got more time, I’d like to do another test — now that I’ve got the id keypath, do I even need the tag?)
I’d love a good explanation of what all the id keypath gets used for, and where else it might be necessary, but alas:


  1. It’s a bit unfair for me to link to No Overview Available when referring to SwiftUI; the coverage is low, but the problem isn’t so much that as the fact that ‘documentation coverage’ just doesn’t work as a metric for something like SwiftUI. The tutorials are a start, and a good sign that Apple was at least trying to rethink their approach to documentation, but they’re not nearly complete enough. 
  2. Zero-based index, of course, which seemed obvious to me, but got me a “???” response when I was complaining about this issue to a non-programmer friend. 
Categories
Programming Technology Tools

Automated Playlist Backup With Swift

I mentioned in my post about scripting with Swift that I’d been working on something that inspired this. Well, here’s what it was: a rewrite of my automated playlist backup AppleScript in Swift. That version ran every hour… ish. Partly that scheduling issue is because launchd doesn’t actually guarantee scheduling, just ‘roughly every n seconds’, and partly it’s because the AppleScript was slow.1
Then I found the iTunesLibrary API docs, such as it is, and thought “well, that’d be a much nicer way to do it.”
And then I remembered that Swift can be used as a scripting language, cracked my knuckles, and got to work. (I also had some lovely reference: I wrote up my very basic intro post, but this post goes further in depth on some of the concepts I touched on.)

Not the best API I’ve ever written, but not bad for something I threw together in a few hours. And I had fun doing it, more so than I did with the AppleScript one.
Oh, and it’s much faster than the AppleScript equivalent: this runs through my ~100 playlists in under a minute. So now I have it run every 15 minutes.2
(The configuration for launchd is about the same, you just replace the /usr/bin/osascript with the path to the Swift file, and make the second argument the full path to the directory where you want your backups going. See the original post for the details.)
I’m a bit tempted to turn this into a macOS app, just so I can play around with SwiftUI on macOS, and make it a bit easier to use. Of course, by ‘a bit tempted’ I mean ‘I already started tinkering,’ but I doubt I’ll have anything to show for a while — near as I can tell, SwiftUI has no equivalent to NSOutlineView as of yet, which makes properly showing the list a challenge. Still, it’s been fun to play with.


  1. I was going to cite this lovely resource, but since that website was built by someone who doesn’t understand the concept of a URL, I can’t link to the relevant section. Click ‘Configuration,’ then the ‘Content’ thing that’s inexplicably sideways on the left side of the screen, and ‘StartInterval’ under ‘When to Start’. 
  2. I’m also looking at the FSEvents API to see how hard it would be to set it up to run whenever Music (née iTunes) updates a playlist, but that… probably won’t happen anytime soon. 
Categories
Programming Technology

Swift Scripting

I’m a bit of a fan of Swift, though I don’t get to tinker with it nearly as much as I’d like. Recently, though, I did some tinkering with Swift as a scripting language, and thought it was pretty fun! (I’m planning another blog post about what, exactly, I was trying to do later, but for now just take it as a given.)
The most important step is to have Swift installed on your machine. As a Mac user, the easiest way is probably just to install Xcode, but if you’re looking for a lighter-weight solution, you can install just the Swift toolchain. Swift is also available for Ubuntu, which, again, takes some doing. If you want Swift on Windows… well, it’s an ongoing project. Personally, I’d say you’ll probably have better luck running it in Ubuntu on the WSL.
Alright, got your Swift installation working? Let’s go.
Step 1: Make your Swift file. We’ll call it main.swift, and in true Tech Tutorial fashion, we’ll keep it simple:

print("Hello world!")

Step 2: Insert a Magic Comment in the first line:

#!/usr/env/swift
print("Hello world!")

Step 3: In your shell of choice, make it executable:

$ chmod +x ./main.swift

Step 4: Run!

$ ./main.swift
> Hello world!

No, really, it’s that simple. The magic comment there tells your interpreter ‘run this using Swift’, and then Swift just… executes your code from top to bottom. And it doesn’t have to be just function calls — you can define classes, structs, enums, whatever. Which is the real benefit to using Swift instead of just writing your script in Bash; object-oriented programming and type safety are lovely, lovely things.

My next post is going to go into some of the more interesting stuff you can do, with a lovely worked example, but for now I’ll add a couple other things:

  • By default, execution ends when it gets to the end of the file; at that point, it will exit with code 0, so Bash (or whatever) will assume it worked correctly. If you want to exit earlier, call exit(_:) with the code you want. exit(0) means “done successfully,” while any other integer in there will be treated as an error.1
  • print(_:) outputs to stdout, which can be piped using |. If you want to output an error (to be piped with 2>, or similar) you need to import Foundation, and then call FileHandle.standardError.write(_:).2
  • To explicitly write to stdout, it’s FileHandle.standardOutput.write(_:).

  1. Which is useful if your script is going to be called programmatically. ./main.swift && echo "It worked!" will print “Hello world” and then “It worked!” with exit(0), but just “Hello world” if you add exit(1) to the end of the file. 
  2. And note the types here – this expects Data, not String, so to write a string, you need to convert it by adding .data(using: .utf8)! 
Categories
Review

“Meddling Kids,” or, “alright, who gave Shaggy a gun”

Edgar Cantero
I am a big fan of Scooby-Doo. It’s got something of that James Bond aesthetic to it — every few years, there’s a new one, and we get to see a new take on the tropes. The recent series have been pretty good — What’s New was, basically, a straight modernized version of the original; Mystery Incorporated did some interesting things with the characters; Be Cool is one of the funniest shows I’ve ever watched; and Guess Who is a love letter to the people who watched the original series.12 The movies are a bit more hit-or-miss, but growing up in the 90s meant I had Zombie Island, Witch’s Ghost, Alien Invaders, and Cyber Chase; Alien Invaders is a hilarious concept, Zombie Island is, I’d argue, the best Scooby-Doo media to date, and Witch’s Ghost is part of why I was so excited to visit New England.3
In short, I love the different takes on the same story; there’s nothing new on Earth, but the different ways people combine ingredients are still creative and interesting. So when I saw that somebody had written a Scooby-Doo book with Lovecraftian influence? Alright, I’m interested.4
The writing style is interesting — Cantero switches back and forth between a more conventional novel style and something inspired by a movie script, with labelled dialogue and annotations for Scooby.5 Where it really shines is the occasional action scene; the sentences get longer, flow together, and it feels like a choreographed fight in a movie.
It’s also definitely creepier than anything in my usual reading; the book is set roughly a decade after the gang broke up, and Fred having died in the interim doesn’t stop him from showing up when Shaggy forgets his meds. The interactions between Shaggy and his hallucination of Fred range from hilarious, through bittersweet, and occasionally into the grotesque.
Plot-wise, the book is enjoyable; it gets a bit weird in places, but eventually pulls itself back together, while leaving room for Cantero to come back and write more if he wants.
Which leads me into something that I felt he did excellently: tie-ins. Because, yes, it’s a Scooby-Doo book that can’t say “Scooby-Doo,” or “Fred,” “Daphne,” “Velma,” or “Shaggy.”6 It’s also a Lovecraft book, though — the Necronomicon is explicitly mentioned, and there’s a passing reference to Miskatonic University, as well as several scenes in Arkham. And other bits and pieces makes appearances — Cantero has certainly read more Lovecraftian horror than I have, but based on what I could pick out as references, there were quite a few that I missed. And it’s tied together to give the book the feeling of being part of a larger universe — you could pick up one of Lovecraft’s books, read it, and mentally slot it in to this same continuity without an issue.
So hey, if you want a creepy, fun little romp in the Pacific Northwest, watching Shaggy, Daphne, and Velma try to deal with PTSD and the results of living in a world that has both “some guy dressed up in a costume to scare people away” and the Necronomicon, I can heartily recommend Meddling Kids.


  1. Seriously, it’s artistically gorgeous, and whoever is doing the soundtrack? Give them a raise, it’s excellent. 
  2. Yes, I know I left one out; we don’t talk about Get a Clue
  3. Zombie Island also made my time in Louisiana more enjoyable, and Cyber Chase is set in an amalgamation of MIT, Stanford, and UC Berkeley, two of which I’ve visited. The moral of the story is, I need to go to Roswell to finish my Scooby-Doo Tour of the United States. 
  4. Admittedly, it’s not an official Scooby-Doo book, so everybody has different names, but it’s also clearly meant to tie in to the same “hey, recognize this trope?” pattern, and gains a lot by then subverting them. 
  5. As mentioned in the previous footnote, the dog isn’t named Scooby, but I don’t feel like writing a conversion chart for what’s meant to be a short review, so I’m just going to do the name-swapping myself. 
  6. It’s possible they could get away with “Scrappy,” but didn’t try. 
Categories
Technology Tools

Automatic OCR with Hazel: The Easy Way

I have previously written about how to run OCR (Optical Character Recognition) on a PDF using Hazel and… a complicated pile of Python scripts and other software. Since I wrote that post, several of those pieces of software have been updated, and the core component has been, apparently, entirely abandoned.
Recently, while I was waiting for yet another keyboard replacement on my MacBook, I took another look at the OCR thing and found that there’s a much easier way available: OCRmyPDF.
It’s easy to install, assuming you’ve already got brew: brew install ocrmypdf
From there, it’s just a single action in Hazel. “Run embedded shell script: ocrmypdf $1
Admittedly, you can use some of their many settings to get something a bit nicer than just OCR; personally, I’m using --rotate-pages --deskew --mask-barcodes – the first two to help with variations in the input because I sometimes use a bed scanner, and the latter to help Tesseract, which can have issues with barcodes..
I’ve also paired it with a couple additional actions, just to keep everything organized:

I also took the time to stop using Dropbox as the go-between for my scanner and the Mac running Hazel; I’d forgotten that the scanner has a USB port. Plug in a cheap flash drive, and it’s available as a (very slow) file server. Mount the drive, add it as a Login Item so it’ll auto-mount on boot, and you can set Hazel automations to run right there. I’m not OCRing them there, though — like I said, it’s a very slow server, so it tags them ‘for OCR’ and moves them to my desktop.1


  1. With iCloud Drive handling my desktop, I’ve found it to be a pretty great ‘intake’ folder for all of my Hazel automations. It’s quite nice to be able to save a PDF from my phone, add a tag, and watch it disappear again as it’s auto-sorted, or throw a PDF on my desktop with a tag and see it pop in and out as the OCR runs. 
Categories
Technology Tools

Automatic Playlist Backup

You may have seen my monthly playlist posts on here; I put those together with a Shortcut that grabs the playlist, runs through all the songs, and makes a spirited attempt to fill in all the links off the iTunes Store Search API without hitting their mysterious rate limits.1
It’s not the be-all end-all, though — I’ve been wanting more and more lately to start making more and smaller playlists, things to match different moods. Y’know, the way normal people do playlists.
But, of course, I’m me, and I want to have the history of my music tastes, because, hey, sometimes you feel like reminiscing.
So, what to do? Well, I’ve done some work with the iTunes Library XML file, and while it’s sorta true that just wrapping that in, like, Git or something for version control could work, there are three problems with that:
1. iTunes is a weird, weird piece of software, and I don’t want to mess with its files too much.
2. The result is not at all human-readable.
3. It isn’t an excuse to learn something new.

So, what else can I do? Well, I’ve done a very light bit of tinkering with AppleScript,2 so I know it can interact with iTunes pretty well; there’s gotta be a way to do it there, right?
There is! I’ll share the script in a moment, but the functionality I wanted was “clear out the folder I give you, replicate my playlist hierarchy as directories, and spit out each playlist as a markdown file listing the title, artist, and album for each track, then commit the changes to a git repository.”
It took a while to get working — I’ve learned that AppleScript’s repeat with in loop is hilariously slow, unless you change it to repeat with in (get). I’ve also found out that the way it works with paths is super annoying, and that while it can write to a file, it can’t conceptualize creating a directory. There’s some great workarounds for that.
Now, here’s the script: I’ve left a couple {replace me} type things where you should fill in variables – namely, the path to your home directory (or wherever else you want it), and your own username, to fix some permission issues that can crop up.3

But wait, there’s a caveat: it’ll fail if the folder you gave it isn’t a git repository. Considering that I wanted this as a ‘set it and forget it’ sort of thing, I figured it wouldn’t be worth the effort to write a bunch of conditional code to do the setup. Do it yourself: git init && touch temp.txt && add temp.txt && git commit -m "Initial commit" takes care of all you need.4
Oh, and if you want it to be pushing the changes somewhere, because you’re paranoid and want everything in someone’s cloud, at least, add the remote and set it as the default upstream: git remote add origin {remote URL} && git push --set-upstream origin master

Set It and Forget It

So that’s pretty neat, but it isn’t really “set it and forget it,” now, is it? You’ve gotta open up Script Editor, pull up the script, and run it every time you want it to back up your playlists. Possibly workable for some people, but I don’t have a home server for nothing. Let’s make this truly automated.
From my prior experience with AppleScript, I know that you can set it off through a shell script by way of /usr/bin/osascript, so my first thought was to add a cron job. After a bit of research, though, I found out that Apple would prefer we use launchd instead, so I set about figuring out how to do that.
Now, if this wasn’t all an excuse to learn how to do something, I’d probably have just bought one of the GUI clients for launchd; Lingon looks pretty nice, and seems to work well.5
The process for writing your own launchd process is actually pretty simple: create a .plist file containing some XML, add it to the launchd queue with launchctl, and you’re off to the races!6
(Hint: if you want an easier way to see if your script runs than waiting and checking git log, you can add a line to the start of the AppleScript: display notification "Running playlist export".)
So, creating the XML: you want it to live in ~/Library/LaunchAgents/, and the convention is the usual reverse-TLD. (You can also use local.{your username}.{your script name}, but I’m so used to using net.twoeighty. in bundle identifiers that I just went with that.)
The important parts are the ProgramArguments array and the StartInterval integer. For ProgramArguments, give it the path to osascript,7 and as your second argument, the full path to the .scpt of the AppleScript.
Then, set the StartInterval to the number of seconds between runs; I’m using 3600, because hourly change tracking seems frequent enough for my purposes.
The result:

(You can skip the StandardErrorPath and StandardOutPath – they help a little with debugging, more so if you’re running a full shell script and not a wrapper on an AppleScript.)
Finally, add it to the queue:

launctl load ~/Library/LaunchAgents/net.twoeighty.backupPlaylists.plist

And there you go – every hour, your iTunes playlists will get backed up to your Git repo, and you’ll have a nice history of your music tastes over time.


  1. iTunes Search is a really fun API to use, because via Shortcuts you only get a single input to it, and it is really bad at finding anything. Seriously — try to find anything off the top charts. As far as iTunes Search is aware, Billie Eilish doesn’t exist. 
  2. In lieu of Shortcuts having a way to set the volume on a HomePod, I’ve achieved a similar result with “run SSH script: osascript -e tell iTunes ...”. 
  3. Related: don’t put this anywhere with weird macOS access control things. Y’know, places like “Documents”, “Desktop”, anywhere in iCloud Drive or Dropbox, or even “Downloads”, which apparently is a much worse work directory than I thought it was. I eventually configured it to run out of and into my Public directory, because I figured that’d be easier than trying to mess around with the permissions somewhere else. 
  4. Without a file there, the git rm -rf . && git clean -fxd bit at the beginning is unhappy. 
  5. I used the ‘free trial’ version as a viewer for my works in progress; I figured if I’d done something really wrong, it’d complain about it being an invalid file or something. 
  6. He said, glossing over the couple hours of “fight me, macOS, why isn’t this working” 
  7. Probably /usr/bin/osascript, but you can use which osascript in Terminal to check. 
Categories
Review

“Radicalized,” or, “this just keeps getting more upsetting”

Cory Doctorow
One of my favorite concepts in science fiction is making one change and extrapolating it forward. What if Tesla and Edison’s war of the currents had resulted in most of the world outlawing electricity and doubling down on steam power? What if Superman had landed in Soviet Russia? What if somebody invented a machine that let you step between parallel worlds?
What Doctorow has done here is that, but instead of making one change, he doesn’t make a change, and extrapolates. What if we never fix copy right law? What if Juicero hadn’t collapsed? What if we never sort out healthcare?
The result is terrifying, because it feels… so very possible. It’s not the first time he’s done it, either — Little Brother was my first introduction to Doctorow, and it remains a poster child for the concept.
Reading both Little Brother and Radicalized, I didn’t feel like I was reading a novel; I felt like I was reading a warning. “We’re on a path that leads to this, or something just like it,” he’s saying. “I’m worried, and you should be too.”
Worry with me; it’s a good read, and well worth the time.1


  1. It also contains a great take on Superman, and a strangely uplifting story about the apocalypse; seriously, read it. 
Categories
Programming Technology

Publishing a Private Angular Library via Git

So, you’ve built yourself a nice new Angular library and you’re excited to put it to use in your apps! It’s an exciting time. But wait: the code you used is proprietary, and you can’t upload it to NPM.1
Good news: thanks to some features of Git, NPM, and most Git hosts, there’s a way to bypass NPM, and you don’t even need to set up your own repository. Sound good? Let’s go.
Now, I’m assuming that you’ve already (a) created your Angular library, and (b) have it in Git. If you haven’t done (a), there’s a pretty good guide on Angular’s site; if you haven’t done (b), allow me to evangelize source control to you: Git is awesome, and you should be using it. This book, available online for free, is an excellent getting-started guide.
So, you’ve got a version-controlled library; how do we make it available?
1. Build the library. ng build {YourLibrary} spits out your library as a ready-made NPM package.
2. Track the built files. git add dist/{your-library}. If you’ve got that in your .gitignore, you’ll need to remove it, or git add -f. I’d recommend the former; you’ll need to do this every time you update the library. Wrap it up with git commit and a nice message explaining what you’ve changed.
3. Set up a second repository. This is where the NPM package version of your repository will live. Leave it empty; we’ll push to it in a moment.
4. Push the subtree. This is the the magic part: we’re going to treat the built files as their own separate repository. git subtree push --prefix dist/{your-library} {path to the second repository}2
5. Tag the release with a semantic version number.3 While it’s possible to do this via the command line, it’s not fun, so I’d recommend using the GUI for your Git host of choice.
6. Generate a read-only token for the second repository. In GitLab, this is under Settings > Repository > Deploy Tokens. Configure it as you’d like, but be sure to enable read_repository.
7. Add the library to your app’s package.json. Give it the Git address for your second repository, and follow it up with either a tag or branch name to pull from. (For example: "your-library": "git+https://{GitLab token username}:{GitLab token password}@gitlab.com/{your username}/{your-second-repo}#master would pull the latest version from master.)

Et voilà; you’ve got a private Angular library, ready for use.


  1. Or maybe you’ve got issues with the fact that NPM is a private company, and can remove, un-delete, or hand over your packages without your permission
  2. i.e.: git subtree push --prefix dist/my-own-library https://github.com/grey280/my-own-library.git 
  3. This isn’t strictly necessary – I’ll explain a bit more in step 7. 
Categories
Programming Technology

Wrapping UserDefaults

UserDefaults, formerly NSUserDefaults, is a pretty handy thing. Simply put, it’s a lightweight way of storing a little bit of data — things on the order of user preferences, though it’s not recommended to throw anything big in there. Think “settings screen,” not “the image cache” or “the database.” It’s all based up on the Defaults system built into macOS and iOS,1 and it’s a delightfully efficient thing, from the docs:

UserDefaults caches the information to avoid having to open the user’s defaults database each time you need a default value. When you set a default value, it’s changed synchronously within your process, and asynchronously to persistent storage and other processes.

How handy is that! All the work of writing to disk, abstracted away just like that. Neat!
Now for the downside: it’s got a very limited range of types it accepts.2 Admittedly, one of these is NSData, but it can be a bit annoying to do all that archiving and unarchiving all the time.
One solution I use is writing a wrapper on UserDefaults. Swift’s computed properties are a very neat way to do it, and any code you write elsewhere in your project will feel neater for it.
The basic idea is this:


There you go: you’ve got an easy accessor for your stored setting.
Of course, we can make this a lot neater; we’ll start by wrapping it up in a class, and make a couple tweaks while we do that:

First, we made a variable to point at UserDefaults.standard instead of doing it directly. This isn’t strictly necessary, but it makes things a lot easier to change if you want to switch to a custom UserDefaults suite later.3
Secondly, we pulled the string literal out and put in a variable instead. Again, this is more about code maintainability than anything else, but that’s certainly a good thing to be working for. Personally, I tend to wrap all my keys up in a single struct, so my code looks more like this:

That’s a matter of personal taste, though.
You might also have noticed that I made both the keys and the UserDefaults.standard private — I’ve set myself a policy that any access of UserDefaults that I do should be via this Settings class, and I make it a rule that I’m not allowed to type UserDefaults anywhere else in the app. As an extension of that policy, anything I want to do through UserDefaults should have a wrapper in my Settings class, and so private it is: any time I need a new setting, I write the wrapper.
There are a few more implementation details you can choose, though; in the example above, I made the accessors static, so you can grab them with Settings.storedSetting. That’s a pretty nice and easy way to do it, but there’s a case to be made for requiring Settings to be initialized: that’s a great place to put in proper default values.4

In that case, accessing settings could be Settings().storedSetting, or

You could also give yourself a Settings singleton, if you like:

I don’t have a strong feeling either way; singletons can be quite useful, depending on context. Go with whichever works best for your project.
And finally, the nicest thing about writing this wrapper: you can save yourself a great deal of repeated code.

Or, if you don’t want to have a default return, make it optional, it’s not much of a change:

You can also do similar things with constructing custom classes from multiple stored values, or whatever else you need; mix and match to fit your project.
(Thoughts? Leave a comment!)


  1. If you’ve ever run defaults write from the Terminal, that’s what we’re talking about. 
  2. If it matters, it’s also not synced; the defaults database gets backed up via iCloud, but if you want syncing, Apple recommends you take a look at NSUbiquitousKeyValueStore
  3. If you want your preferences shared between your app and its widget(s), or between multiple apps, you need to create a custom suite; each app has its own sandboxed set of defaults, which is what UserDefaults.standard connects to. 
  4. UserDefaults provides default values, depending on types, but they may not be the same defaults that you want. If you want a stored NSNumber to default to something other than 0, you’ll need to do that initial setup somewhere.