Archive for May, 2020

“mint” (a simple SwiftPM installer) has improved my command line life

SwiftPM’s lack of an easy install feature has long been an issue of mine and for other people too. As the linked forums thread suggests, to accomplish this for a general audience requires some careful thinking: adding to /usr/local/bin is not always the best solution for every user.

Still, it’s a feature whose absence is notable. To fill the gap, I’ve discovered Yonas Kolb’s mint. Thank you to Leo Dion, who introduced this to me. It is ridiculously simple to use. Could it get easier than mint install erica/now? Admittedly my github id is short and so is my project name, but I think my point stands…

Yesterday, I hastily added a SwiftPM project to remind so it too could be installed via mint. Suddenly, adding the project specification is no longer an afterthought but a driving feature. It’s made SwiftPM support far more valuable to me for executables.

Make sure you name your primary file main.swift, your product to .executable, and if you’re developing in Xcode, override your path to point to the Xcode-project-style folder for the source files, usually the same name as the project itself. I mention this because Xcode by default (File > New > Swift Package) builds library projects, not executable ones. At the command-line use swift package init --type executable.

// swift-tools-version:5.2
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "remind",
    platforms: [
        .macOS(.v10_12)
    ],
    products: [
        .executable(
            name: "remind",
            targets: ["remind"]),
    ],
    dependencies: [
        .package(url: "https://github.com/apple/swift-argument-parser", from: "0.0.6"),
    ],
    targets: [
        .target(
            name: "remind",
            dependencies: [.product(name: "ArgumentParser", package: "swift-argument-parser")],
            path: "remind/"
        ),
    ],
    swiftLanguageVersions: [
        .v5
    ]
)

As for mint itself, you can build it or install it via HomeBrew: brew install mint.

If you have any suggestions on how I can improve my SwiftPM work, better integrate tagging, or any other tips, please let me know. Thanks!

Catalina GIFfing: Quick workflow from screen to animated GIFs

All the leaves are green.

And the sky is blue.

I’ve been at my desk.

With screenshot play…

(To be fully truthful, it’s currently raining cats, dogs, kittens, and puppies. But it’s lovely here in the high desert.)

With my newly updated workflow creating the following GIF took about a minute maybe from start to post. The secret? QuickTime Screen Recording (bless you ⌘-Shift-5) and “gifify” courtesy of Homebrew. Set record, demo, stop, convert, drop into WordPress:

I love how easy it is to invoke screen recording these days with macOS’s updated capture interface. It’s especially nice how the optional delay time allows me to get into the zone before recording actually starts.

Back to installation, the blocker was getting homebrew to get itself into position to fully support Catalina. I had to apply homebrew update and homebrew upgrade and homebrew doctor a number of times. Not only did I get gifify installed, but ffmpeg is finally back to working and I once again have emacs for all my git needs.

I’ll spare you how bad the emacs transition was other than to say if you have to disable system integrity and mount read-write by hand, you’re probably doing it the wrong way.

With ffmpeg, it was the dependent libraries including the ones already installed into macOS (like openssl ). Homebrew refused to link:

Warning: Refusing to link macOS provided/shadowed software: openssl@1.1

I wish I had known early about the update/upgrade/doctor approach applied multiple times by the way, not just once, until everything stops complaining and the doctor says “Your system is ready to brew”. Because at that point, installs are a breeze. Installing before then, when the configuration seemed irreparably broken was probably a bad choice.

I spent a bit of time after removing my current bandaid symbolic links. It seems to have helped that I ended up granting separate privileges to ruby in Security & Privacy a while ago. I don’t remember why I did but it’s in there and I vaguely remember going through the process while cursing Cat.

June’s almost here and I wonder if Catalina.successor() will be better or more of the same. It hasn’t been a good Cat year for me.

Musings on `Result` and building a command line utility with completion handlers

Collaborating with Paul Hudson is a pleasure but the time difference can be, well, confusing. So when I started putting together an outline about the new command line argument parser for Pragmatic, one of the first things I wrote was a command line utility to tell me what time it was in Bath, UK:

% now bath uk
Bath 4:16:42 PM (GMT+1 United Kingdom Time)

I use CLGeocoder to use whatever terms I enter after the command as the hints to look up places of interests. I grab the first match and pull the time zone from that match (or throw an error if there’s no possible match).

Command-line utilities are not particularly well known for their asynchronous feature support. Because geocodeAddressString does run asynchronously, I use the quick and dirty trick of starting a runloop that executes until the completion handler finishes. I’m basically adapting an async method to sync.

This gave me an opportunity to finally get around to using the new Result type. Restrictions on other projects prevented me from kicking its wheels (or I was already using my own version from way back).

I struggled a little with how to incorporate Result. Here’s an earlier go at this. I use an optional resultto store the result, which is then set in completion scope:

var result: Result<[CLPlacemark], Error>?

CLGeocoder().geocodeAddressString(hint) { placemarks, error in
    switch (placemarks, error) {
    case (_, let error?):
        result = .failure(error)
    case (let placemarks?, _):
        result = .success(placemarks)
    default:
        fatalError("Geocoder error, no results.")
    }
    CFRunLoopStop(CFRunLoopGetCurrent())
}
CFRunLoopRun()

This code bothered me, and not just because I had to test and unwrap result when control returned to the main part of my method. It seems so obvious that Result should have a simple initializer based on the common elements found in legacy completion handlers. This would collapse those arguments to, for example, completion(Result(error, data)). Unless I’m missing something big here, I thought to build my own convenience initializer:

extension Result {
    init(_ success: Success?, _ failure: Failure?) {
        precondition(!(success == nil && failure == nil))
        switch (success, failure) {
        case (let success?, _):
            self = .success(success)
        case (_, .let failure?):
            self = .failure(failure)
        default:
            fatalError("Cannot initialize `Result` without success or failure")
        }
    }
}

That extension allowed me to collapse the code down to this. Notice how I can use the initializer here to eliminate the optional result, simplifying my extraction with get, towards the end. Admittedly, it’s not always easy to come up with an initial value that can be overwritten this way, but here it worked. The handler became two lines long, and processing the result to get my placemark (including all error handling), another two lines:

static func fetchPlaceMark(from hint: String) throws -> CLPlacemark {
    var result: Result<[CLPlacemark], Error> = Result([], nil)

    CLGeocoder().geocodeAddressString(hint) { placemarks, error in
        result = Result(placemarks, error)
        CFRunLoopStop(CFRunLoopGetCurrent())
    }
    CFRunLoopRun()

    let placemarks = try result.get()
    return placemarks[0]
}

I quite like my initializer and wonder why something like that doesn’t already exist unlike init(catching:() -> Success). I’m curious as surely I’ve missed something important. Even in normal completion handlers, I’d imagine using the legacy Error? and Data? optionals would be a common use-case for Result

Let me know.

Broken App Store downloads on Mojave: We could not complete your purchase

This has been happening to a lot of people recently. You open App Store and try to update apps or download new ones. Instead:

And if you have 48 apps to update, you have to click OK 48 times. Argh.

I spent nearly two hours with Apple yesterday trying to resolve.

Instead, I should have just tweeted because when I did Bas Broek had the answer almost immediately:

I had already rebooted, reset NVRAM/PRAM, cleaned out my Application Support for the App Store, and, get this, at the advice of Apple itself, reinstalled freaking Mojave to try to resolve it.

What a waste of time.

I hope this may come up in someone’s Google search to save them time.

  1. Quit App Store
  2. At the terminal: open $TMPDIR/../C/com.apple.appstore/
  3. In Finder: trash everything in that folder including any pending updates / stuck items.
  4. Relaunch App Store
  5. Done

Update: Gwynne Raskind adds: “$TMPDIR/../C is confstr(_CS_DARWIN_USER_CACHE_DIR)”.

Catalina permissions: Chrome, Zoom, etc

Ran into trouble this weekend where I was unable to add permissions for a number of apps to allow access to my microphone and camera.  (And yes, I’m aware of the security horrors of Zoom but I had work to do.)

They wouldn’t give the normal permissions request:

Instead, I got a message directing me to System Preferences:

Once there, the prefs did not list the app for normal check-to-enable:

I couldn’t unlock and drag on an app.

With some help from Bas Broek and this article, which specifically addressed the inability to grant access in Catalina, I discovered that rebooting with a NVRAM/PRAM reset might help. It sounded like sacrificing chicken entrails but it worked. While a regular reboot didn’t help, the Cmd+Option+PR reboot did.

Apple Support Article: How to reset NVRAM or PRAM on your Mac.

I hope this helps someone else to avoid the time I wasted.