Shorten Emacs Yes-or-No Confirmation Dialogs

Press ‘y’ to confirm!

Some commands in Emacs and its various packages are destructive. They require a confirmation by the user. These usually use yes-or-no-p, which won’t complete the command until the user replies by writing “yes” and then hits enter, or “no”, or aborts the command with C-g.

Some of the commands that require confirmation in this way are overly protective, I find. Like projectile-kill-buffers (C-p k) which closes all open buffers for a project at once. I use this heavily when e.g. editing my website: jump into the project, edit a file or two, commit, then leave. I don’t want to type “yes” and hit enter just for that. (Please note that killing a buffer this way will still ask me if I want to save or discard changes, so the kill command alone is not destructive.)

There’s a shorter variant of the confirmation, y-or-n-p, where you only have to hit the “y” or “n” key to select an answer. It’s more like a dialog prompt and less like a conversational chat UI.

Ingenious people on the interwebs figured out that you can temporarily, or rather locally, override the yes-or-no-p with the shorter y-or-n-p by using what’s called an “advice”. It’s basically a function decorator. If all you know is Objective-C, think “method swizzling”.

(defun yes-or-no-p->-y-or-n-p (orig-fun &rest r)
  (cl-letf (((symbol-function 'yes-or-no-p) #'y-or-n-p))
    (apply orig-fun r)))

(advice-add 'projectile-kill-buffers :around #'yes-or-no-p->-y-or-n-p)

With this code, when I run projectile-kill-buffers, the actual call ((apply orig-fun r)) is wrapped in a context where all yes-or-no-p calls will actually invoke y-or-n-p. I love it.

Follow Link at Insertion Point in NSTextView

More power for keyboard shortcut lovers is always good!

If you ever wondered how to programmatically trigger a click on a link in a NSTextView, here’s one way to do so.

This assumes that clickable links are not stored as temporary attributes in the NSLayoutManager, but permanently as part of the “model” in your NSTextStorage. You can then ask the storage for the attribute at the cursor/insertion point location:

extension NSTextView {
    func followLinkAtInsertionPoint(_ sender: Any?) {
        guard let textStorage = textStorage else { return }

        let location = selectedRange().location
        let attributes = textStorage.attributes(at: location, effectiveRange: nil)

        guard let url = attributes[.link] as? URL else { return }

        self.clicked(onLink: url, at: location)
    }
}

To further refine this and make it work with selected ranges properly, you’d need to find all links in the selected range. Call NSTextStorage.attributes(at:longestEffectiveRange:in:) repeatedly for this.

For example, if your selection spans let wholeRange = NSRange(location: 100, length: 50), you call attributes(at: 100, longestEffectiveRange: &effectiveRange, in: wholeRange). The storage produces an effectiveRange clipped to wholeRange. It might be smaller, e.g. equal to NSRange(location: 100, length: 10), so the range from 11 to 50 is still uncovered. If effectiveRange is fully contained within wholeRange like this, you can call the method again with the location parameter advanced by effectiveRange.length + 1 to 111. Then you get the next piece. Rinse and repeat until wholeRange is exhausted.

This is how a rich text document with nested styles, e.g. underlining a portion of an italic text, is linearized. Users think of “nested” styles, but the computer would return a range of “italics only”, followed by “italics + underlines”, followed by “italics only” again. That’s how you would iterate over the attributes, too.

Now Hiring: PHP/Laravel Web Developer

I’m looking for a PHP developer starting now. Vanilla PHP and Laravel are both welcome.

The Work

The goal is to self-host a list of online video courses. The course modules are only available to paying customers.

The technical requirements are roughly this:

  1. User authorization. People should be able to register and log in.
  2. Paywall/user authentication. A part of a website should be walled-off for visitors by default. You will need to log in to your account first, and then you need to have the “has paid for product” flag set in your account, in order to access the online content.
  3. FastSpring (e-commerce) API integration. I don’t want to flip the “has paid” switch manually, at least not forever, so the service will need to be able to provide an API endpoint that can be wired with FastSpring’s webhooks. When an order is completed, the webhook is called, the user is looked up in the system, and her “has paid” switch is flipped. This is for one-time purchases, not perpetual/subscription, so a flipping-off only has to be available for admins (me) in the back or command line. Purchasing first and registering second should be possible, too.

That’s the basics to make everything work. We’ll call this “Iteration 1”.

The FastSpring order callback can fail to associate with registered users (think: different email accounts used, so how do you look up the association?).

To mitigate this, in an “Iteration 2”, a manual way to activate a license would be great. Like “enter this serial number in your account to activate the account”, and this is only possible once for each order.

How I’d assemble this, just to illustrate the point: Every order webhook is consumed and an Order is registered in the system. Then Orders and Users are associated automatically, based on e.g. email matching or an order token (register first, order from the user account settings). Manual matching creates the same association, only through a form in the user account page.

The Outlook

This is the first step to a working course platform.

My ultimate goal is to make this robust, test it in the wild, then expand to also work with e.g. my own macOS apps. But the course comes first.

What I want to say is: if you enjoy doing this, and if you love making tools that help indie developers sell their stuff online, then we can collaborate on this project in the long run. We can discuss my top secret world domination plans pertaining this server backend later. Would love to hear what you think.

Why PHP, of all things?!

I love writing Ruby code. Hosting Ruby/Sinatra or Ruby on Rails apps online is simpler than ever. Python gained traction, too. Node is viable. Swift on the Server is getting more attention every year.

So why PHP?

Because you can upload a directory of PHP files to the server, create a symlink from htdocs to the my_app/public directory, set up the database once, and then you’re ready to roll. “Deployment by FTP” is the simplest form of self-hosting I can imagine. You literally cannot convince me of any Kubernetes/Docker setup for this stuff. It’s impossible :)

I am rather certain the problem is not hard, it’s rather stock-standard, but I cannot spare the weeks to work on this, so I’m looking for a helping hand with the perspective to continue our partnership for future iterations.

Get in touch if

  • you are interested in creating a web app for indie devs to make a living,
  • you have experience with the tools and tech involved,
  • you are available “soon”,
  • you can work as a freelancer/contractor,
  • you are able to tackle this problem on your own at your own pace.

So please introduce yourself and tell me a bit about you and your work, and feel free to share this with anyone you know! I can read PHP perfectly fine, so please share some of your code if possible.

Oh, and I’m open to suggestions about the way to pay you. One time fixed cost, billing by the week, or whatever.

Export Day One (macOS) Journals to Emacs org-mode Diary Entries

I noticed that I still had the journaling app Day One (macOS) on my computer. I haven’t touched it in ages. So I figured it’d be time to export to plain text and then shove all the goodness from 2004 and onward into my Emacs org-mode diary.org.

Turns out there’s no Day One to org-mode exporter.

Yet!

Using olivetti-mode, modus-operandi theme, and hiding the leading org asterisks and auto-indenting the text, that’s what it looks like for me

The best StackOverflow topic results in “just regex \tDate:\t and make an org outline”. It’s a good first step. But might just as well add some more cleverness to the conversion to get the year and month-of-year outline items for free, too!

Since my Emacs Lisp is so bad, it’s a Ruby script, of course.

So the result converts a Day One plain text export (usually called Journal.txt) into org-mode journal format. It features

  • Markdown image conversion from ![](thepath.jpg) to org inline image links, [[thepath.jpg]].
  • Extraction of images from the front of an entry; moves it right after the property drawer.
  • Creation of nested year/month/day date sections (see example below).

The output:

* 2020
** 2020-03 March
*** 2020-03-14 Saturday
**** Here's a diary entry imported from Day One.
:PROPERTIES:
:CREATED: [2020-03-14 Sat 14:33]
:END_PROPERTIES:
**** And another one from the same day.
:PROPERTIES:
:CREATED: [2020-03-14 Sat 19:12]
:END_PROPERTIES:
** 2020-06 June
*** 2020-06-22 Monday
**** Year, month, and day sections are created automatically.
:PROPERTIES:
:CREATED: [2020-06-22 Mon 08:06]
:END_PROPERTIES:

The conversion is pretty simple, but I added a bit of edge-case handling. A lot of my entries were a prominent image at the top plus text below, and with Day One putting all images inline in the plain text export, this meant that the first line started with a Markdown image reference followed by some text. Not pretty. The converter splits these.

Source Code

See the public Gist for a comfortable download.

#!/usr/bin/env ruby

require "date"

input, output, *rest = ARGV
if input.nil?
  STDERR.puts "Usage: #{__FILE__} DAYONE_INPUT_PATH ORG_OUTPUT_PATH"
  STDERR.puts "Missing input file path"
  exit 1
elsif output.nil?
  STDERR.puts "Usage: #{__FILE__} DAYONE_INPUT_PATH ORG_OUTPUT_PATH"
  STDERR.puts "Missing output file path"
  exit 1
end

File.open(output, "w") do |out|
  # Cached values to make sub-headings
  year = nil
  month = nil
  day = nil
  props = {}
  File.readlines(input).each do |line|
    if /\A\t(?<key>\w+):\t(?<value>.*+)$/ =~ line
      # Collect metadata in `props` dictionary
      case key
      when "Date"
        date = DateTime.parse(value)
        props["Created"] = date.strftime("[%Y-%m-%d %a %H:%M]")
        # Convert date lines to new entries in org

        # Output:  "* 2020"
        if year != date.year
          out.puts "* #{date.year}"
          year = date.year
          month = nil
        end

        # Output:  "** 2020-03 March"
        if month != date.month
          out.puts "** #{date.strftime("%Y-%m %B")}"
          month = date.month
        end

        # Output:  "*** 2020-03-12 Thursday"
        this_day = date.strftime("%Y-%m-%d %A")
        if day != this_day
          out.puts "*** #{this_day}"
        end
      else
        props[key] = value
      end
    elsif !props.empty?
      # Produce entry title and metadata
      if line.strip.empty?
        # Skip empty line separator after metadata
      else
        # Add entry heading, handling leading entry images
        cached_image = nil
        if /\A!\[]\((?<path>.+)\)(?<rest>.*)$/ =~ line
          cached_image = "[[./#{path}]]"
          out.puts "**** #{rest}"
        else
          out.puts "**** #{line}"
        end

        # Append property drawer
        out.puts ":PROPERTIES:"
        out.puts(props.map { |key, value| ":" + key.upcase.to_s + ":  " + value.to_s }
                      .join("\n"))
        out.puts ":END_PROPERTIES:"
        props = {}

        if !cached_image.nil?
          out.puts ""
          out.puts cached_image
        end
      end
    else
      line = line.gsub(/!\[\]\((.+)\)/) { "[[./#{$1}]]" }
      out.puts line
    end
  end
end

Fix Your Damaged Mac App Store App by Parsing the App Store Receipt Dates Correctly

If your customers get this message when downloading your app from the Mac App Store:

XYZ is damaged, remove it and download again from App Store.

… it might be because you’re parsing the App Store receipt dates wrong!

Apparently, Apple began to change Mac App Store receipt date formats recently:

  • Old format: 2020-10-03T07:12:34Z
  • New format: 2020-10-03T07:12:34.567Z

When you parse receipt dates, account for both variants. To make things easier for you, strongly consider to use ISO8601DateFormatter (NSISO8601DateFormatter in Objective-C) which should (!) handle multiple variants of ISO-8601 dates for you.

Huge thanks to Frank Illenberger of ProjectWizards, makers of project management software Merlin Project, for figuring this out!

See also:

NSRemindersUsageDescription Info.plist Key is Misspelled in the Documentation

I am working on code that reads Apple Reminders data. To fetch reminders from an EKEventStore in EventKit, you need to request permissions.

But you can only request permissions for calendar, event, or reminders access if your app’s Info.plist contains the appropriate key.

Now here’s the bummer: the Apple Developer Documentation contains a typo.

To access the user’s calendar events, reminders, and contacts through EventKitUI, you need to include descriptions for:

  • NSCalendarUsageDescription
  • NSReminderUsageDescription
  • NSContactsUsageDescription

https://developer.apple.com/documentation/eventkit/accessing_the_event_store

It needs to say reminders, plural. It’s NSRemindersUsageDescription, not NSReminderUsageDescription. If you follow the link from the docs you will see the archived developer documentation has the correct spelling.

So now you know why your test code doesn’t show a permission dialog and only logs a message like

2020-09-21 13:20:24.372953+0200 RemidnersFetching[17484:4620827] [error] error: -addPersistentStoreWithType:NSXPCStore configuration:(null) URL:file:///Users/.../Library/Calendars/Calendar%20Cache options:{
    NSInferMappingModelAutomaticallyOption = 1;
    NSMigratePersistentStoresAutomaticallyOption = 1;
    NSPersistentHistoryTrackingKey =     {
        NSPersistentHistoryTrackingEntitiesToExclude =         (
            ChangeRequest
        );
    };
    agentOrDaemon = 1;
    serviceName = "com.apple.CalendarAgent.database";
} ... returned error NSCocoaErrorDomain(134070) with userInfo dictionary {
    Problem = "request failed, insufficient permission";
}

If you get that, double-check your plist keys.

(I submitted FB8723050.)

Indie Support Weeks: Use DropDMG to Create Your App Downloads

I’m continuing the #IndieSupportWeeks today.

In the past months, I deployed a couple of app updates. Not as many as planned, but still. My app downloads are compressed DMG files, or disk images. With these, you don’t need a Zip. DMGs usually come with the app bundle and an alias to /Application so you can “install” an app quickly via drag & drop. This is probably the most successful and easiest way to ship downloads that work with macOS App Translocation. If users run an unzipped app bundle from their downloads folder, they’re screwed.

Screenshot of DropDMG

Over the years, I tried a couple of approaches, and I found that I had the most success with DropDMG. The visual preview is very accurate, and I never had any problem with the resulting DMG files. It beats fiddling with the command line every day.

DropDMG is an essential tool in my workflow to ship applications. It costs US$24 at the moment, and it’s worth the money if you do indie app development to make money.

Make RVM's Ruby Available to Emacs Shell Commands

No matter if you use exec-path-from-shell or not, Emacs will not be able to know your RVM-managed Ruby information. This drove me crazy.

Most Emacs shell commands are invoked in an “inferior” mode, aka a “dumb” shell. This includes M-!, M-x shell, and also the projectile compile commands. That’s when some of your user login scripts will not automatically load, like the entirety of rvm, the Ruby Version Manager.

To add your default Ruby to the PATH environment via rvm in a way that Emacs can understand from the get-go, add this to your .zshrc/.bashrc – or after wherever you export PATH:

[[ "${TERM}" == "dumb" ]] && source $(rvm default do rvm env --path)

Or, if you prefer longer versions:

if [[ "${TERM}" == "dumb" ]]; then
    source $(rvm default do rvm env --path)
fi

# or

case $TERM in
  dumb*)
    # Emacs inferior shell is dumb
    source $(rvm default do rvm env --path)
    ;;
  xterm*)
    # ... handle xterm* here ...
    ;;
esac

That will execute the rvm initialization script and make the default ruby’s PATH available to dumb shells. The $TERM environment variable will probably be xterm-256color or similar when you use a regular terminal emulator.

I managed to survive until now because I discovered that I can build my website from Emacs with projectile by invoking rvm default do bundle exec nanoc compile instead of just nanoc compile. You might want to keep the rvm default do <CMD> handy for an emergency, too.

Get FastComments for a $79 One-Time Payment Instead of $50 Annual Subscription

Google brought up this weird lifetime access to the paid FastComments tier. It’s $79 for life instead of $50 per year.

I asked the FastComments support people if this was scam or a legitimate deal, because I didn’t know anything about bypeople.com – and they verified this is absolutely legit and part of their marketing masterplan.

So if you want to get FastComments on your site and want to get it cheap, here’s a link to the $79 deal. With the weak dollar, thats about 69 EUR at the moment, in case you wonder.

https://www.bypeople.com/lifetime-alternative-to-disqus-fast-bloat-free-comment-system/

The campaign apparently lasts until Aug 16, 2020.

Add HTML Entity Picker to Emacs

Emacs comes with a lot of stuff out of the box, but I was missing TextMates “Insert Entity” action that lets me search through HTML entities by name and then insert &amp;quot; or &amp;trade; for me. I can never remember the names of typographic quotation marks in German, for example.

Some light searching on the web brought up this very cool apprach by @emacs_gifs: copy or scrape the whole List of XML and HTML character entitites reference and then use Emacs’s built-in fuzzy list filter mechanism to pick an item from the resulting list.

The original code used 1 global variable and 2 global functions. While I do agree with the factoring of these three things, I don’t want to expose all of them in my global namespace. And I also don’t want to whip up an .el package just to hide some implementation detail. So I put the variable and the filter function inside the local namespace of the action I’m supposed to invoke.

Modular programming for the win!

Here’s the resulting code as a Gist: https://gist.github.com/DivineDominion/73fc7a5d6dd2cf95767b1264faf1d6f1


→ Blog Archive