<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[don't wait for the translation]]></title><description><![CDATA[one or zero]]></description><link>https://blog.marksmith.org/</link><generator>Ghost 0.7</generator><lastBuildDate>Wed, 01 Apr 2026 19:11:21 GMT</lastBuildDate><atom:link href="https://blog.marksmith.org/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[maybe?]]></title><description><![CDATA[<p><img src="https://blog.marksmith.org/content/images/2020/04/Untitled.png" alt=""></p>]]></description><link>https://blog.marksmith.org/maybe/</link><guid isPermaLink="false">a84672a0-cf2c-40d3-ae02-78cb7393da4d</guid><dc:creator><![CDATA[Mark Smith]]></dc:creator><pubDate>Tue, 28 Apr 2020 16:22:13 GMT</pubDate><content:encoded><![CDATA[<p><img src="https://blog.marksmith.org/content/images/2020/04/Untitled.png" alt=""></p>]]></content:encoded></item><item><title><![CDATA[??? profit!]]></title><description><![CDATA[<p><img src="https://blog.marksmith.org/content/images/2019/10/wework-1.png" alt=""></p>

<p>-- <a href="https://www.bloomberg.com/opinion/articles/2019-10-23/how-do-you-like-we-now">How Do You Like We Now</a></p>]]></description><link>https://blog.marksmith.org/profit/</link><guid isPermaLink="false">5dc0b5be-bbb2-4575-b369-978d77796150</guid><dc:creator><![CDATA[Mark Smith]]></dc:creator><pubDate>Wed, 23 Oct 2019 19:54:00 GMT</pubDate><content:encoded><![CDATA[<p><img src="https://blog.marksmith.org/content/images/2019/10/wework-1.png" alt=""></p>

<p>-- <a href="https://www.bloomberg.com/opinion/articles/2019-10-23/how-do-you-like-we-now">How Do You Like We Now</a></p>]]></content:encoded></item><item><title><![CDATA[ultra]]></title><description><![CDATA[<p><img src="https://blog.marksmith.org/content/images/2019/01/symbolics_keyboard_pn_365407_348de-1.jpg" alt=""></p>]]></description><link>https://blog.marksmith.org/ultra/</link><guid isPermaLink="false">cdf34dcf-c719-4d70-92a8-1cbb2f7491db</guid><dc:creator><![CDATA[Mark Smith]]></dc:creator><pubDate>Fri, 11 Jan 2019 21:22:39 GMT</pubDate><content:encoded><![CDATA[<p><img src="https://blog.marksmith.org/content/images/2019/01/symbolics_keyboard_pn_365407_348de-1.jpg" alt=""></p>]]></content:encoded></item><item><title><![CDATA[stupid keyboard maestro tricks]]></title><description><![CDATA[<p>Replace the current Safari window with the same URL opened in a private window:</p>

<p><img src="https://blog.marksmith.org/content/images/2017/11/Screen-Shot-2017-11-14-at-12-04-37-AM.png" alt=""></p>]]></description><link>https://blog.marksmith.org/stupid-keyboard-maestro-tricks/</link><guid isPermaLink="false">0dc12704-d7be-4499-a58b-a3652ba3d82d</guid><dc:creator><![CDATA[Mark Smith]]></dc:creator><pubDate>Tue, 14 Nov 2017 05:08:55 GMT</pubDate><content:encoded><![CDATA[<p>Replace the current Safari window with the same URL opened in a private window:</p>

<p><img src="https://blog.marksmith.org/content/images/2017/11/Screen-Shot-2017-11-14-at-12-04-37-AM.png" alt=""></p>]]></content:encoded></item><item><title><![CDATA[tricky]]></title><description><![CDATA[<p><img src="https://blog.marksmith.org/content/images/2017/09/Screen-Shot-2017-09-11-at-10-49-31-PM.png" alt=""></p>]]></description><link>https://blog.marksmith.org/tricky/</link><guid isPermaLink="false">e0c0ab81-b244-42f2-876b-c89de1feb866</guid><dc:creator><![CDATA[Mark Smith]]></dc:creator><pubDate>Tue, 12 Sep 2017 02:59:39 GMT</pubDate><content:encoded><![CDATA[<p><img src="https://blog.marksmith.org/content/images/2017/09/Screen-Shot-2017-09-11-at-10-49-31-PM.png" alt=""></p>]]></content:encoded></item><item><title><![CDATA[müllabfuhrwortmaschine]]></title><description><![CDATA[<p>From <a href="https://www.amazon.com/Mind-Play-Shannon-Invented-Information/dp/1476766681/ref=sr_1_1?ie=UTF8&amp;qid=1504383192&amp;sr=8-1&amp;keywords=mind+at+play">A Mind at Play: How Claude Shannon Invented the Information Age</a>: <br>
<img src="https://blog.marksmith.org/content/images/2017/09/shannon.png" width="500px" height="353"> <br>
Russian Twitter bots, circa 1948.</p>]]></description><link>https://blog.marksmith.org/mullabfuhrwortmaschine/</link><guid isPermaLink="false">61e798c7-4378-4a0a-948d-594c3cea3be4</guid><dc:creator><![CDATA[Mark Smith]]></dc:creator><pubDate>Sat, 02 Sep 2017 20:17:35 GMT</pubDate><content:encoded><![CDATA[<p>From <a href="https://www.amazon.com/Mind-Play-Shannon-Invented-Information/dp/1476766681/ref=sr_1_1?ie=UTF8&amp;qid=1504383192&amp;sr=8-1&amp;keywords=mind+at+play">A Mind at Play: How Claude Shannon Invented the Information Age</a>: <br>
<img src="https://blog.marksmith.org/content/images/2017/09/shannon.png" width="500px" height="353"> <br>
Russian Twitter bots, circa 1948.</p>]]></content:encoded></item><item><title><![CDATA[pretty sure i don't mean that]]></title><description><![CDATA[<p><img src="https://blog.marksmith.org/content/images/2017/08/170815-0002.png" alt=""></p>]]></description><link>https://blog.marksmith.org/pretty-sure-i-dont-mean-that/</link><guid isPermaLink="false">44300ba8-6546-46c9-a1a8-9a9f3705025d</guid><dc:creator><![CDATA[Mark Smith]]></dc:creator><pubDate>Wed, 16 Aug 2017 00:58:03 GMT</pubDate><content:encoded><![CDATA[<p><img src="https://blog.marksmith.org/content/images/2017/08/170815-0002.png" alt=""></p>]]></content:encoded></item><item><title><![CDATA[tiresome humorist is tiresome]]></title><description><![CDATA[<p>The New Yorker's RSS feed distills Andy Borowitz columns into their tedious essence.</p>

<p><img src="https://blog.marksmith.org/content/images/2017/07/Screen-Shot-2017-07-18-at-11-20-40-AM.png" alt=""></p>

<p><img src="https://blog.marksmith.org/content/images/2017/08/Screen-Shot-2017-08-07-at-8-29-26-PM-1.png" alt=""></p>

<p><img src="https://blog.marksmith.org/content/images/2017/08/Screen-Shot-2017-08-16-at-10-07-00-PM-1.png" alt=""></p>]]></description><link>https://blog.marksmith.org/tiresome-humorist-is-tiresome/</link><guid isPermaLink="false">6c46500d-b98f-4292-88db-bd7cf27e7534</guid><dc:creator><![CDATA[Mark Smith]]></dc:creator><pubDate>Tue, 18 Jul 2017 15:27:27 GMT</pubDate><content:encoded><![CDATA[<p>The New Yorker's RSS feed distills Andy Borowitz columns into their tedious essence.</p>

<p><img src="https://blog.marksmith.org/content/images/2017/07/Screen-Shot-2017-07-18-at-11-20-40-AM.png" alt=""></p>

<p><img src="https://blog.marksmith.org/content/images/2017/08/Screen-Shot-2017-08-07-at-8-29-26-PM-1.png" alt=""></p>

<p><img src="https://blog.marksmith.org/content/images/2017/08/Screen-Shot-2017-08-16-at-10-07-00-PM-1.png" alt=""></p>]]></content:encoded></item><item><title><![CDATA[adding a swiftlint build phase to pbxproj files]]></title><description><![CDATA[<p>Vapor project files need to be regenerated after package changes, which means any custom edits will be lost. Here's how I use the <a href="https://rubygems.org/gems/pbxplorer">pbxplorer gem</a> to add a SwiftLint build phase to <code>project.pbxproj</code> after (re-)creating the <code>.xcodeproj</code> directory. Change the two instances of <code>MyApp</code> below as appropriate.</p>

<pre><code>#! /usr/</code></pre>]]></description><link>https://blog.marksmith.org/swiftlint-pbxproj/</link><guid isPermaLink="false">f277a7ad-9bc7-4c71-8cc2-36be5078a989</guid><dc:creator><![CDATA[Mark Smith]]></dc:creator><pubDate>Tue, 11 Jul 2017 16:30:59 GMT</pubDate><content:encoded><![CDATA[<p>Vapor project files need to be regenerated after package changes, which means any custom edits will be lost. Here's how I use the <a href="https://rubygems.org/gems/pbxplorer">pbxplorer gem</a> to add a SwiftLint build phase to <code>project.pbxproj</code> after (re-)creating the <code>.xcodeproj</code> directory. Change the two instances of <code>MyApp</code> below as appropriate.</p>

<pre><code>#! /usr/bin/env ruby

require 'pbxplorer'

# Open the project file and get the project.

project_file = XCProjectFile.new 'MyApp.xcodeproj/project.pbxproj'  
project = project_file.project

# Add the swiftlint build phase.

target = project.targets.find { |t| t['productName'] == 'MyApp' }  
build_phase = project_file.new_object PBXShellScriptBuildPhase, {  
  'name' =&gt; 'SwiftLint',
  'shellPath' =&gt; '/bin/sh',
  'shellScript' =&gt; 'swiftlint',
  'files' =&gt; [],
  'inputPaths' =&gt; [],
  'outputPaths' =&gt; [],
  'buildActionMask' =&gt; 0x7fffffff,
  'runOnlyForDeploymentPostprocessing' =&gt; 0
}
target['buildPhases'].insert 0, build_phase.uuid

# Save the edited project file.

project_file.save  
</code></pre>]]></content:encoded></item><item><title><![CDATA[just no]]></title><description><![CDATA[<p><img src="https://blog.marksmith.org/content/images/2017/07/IMG_1892-2.PNG" alt=""></p>]]></description><link>https://blog.marksmith.org/just-no/</link><guid isPermaLink="false">8120431b-5e8b-4515-a58a-441093778e10</guid><dc:creator><![CDATA[Mark Smith]]></dc:creator><pubDate>Tue, 11 Jul 2017 02:07:44 GMT</pubDate><content:encoded><![CDATA[<p><img src="https://blog.marksmith.org/content/images/2017/07/IMG_1892-2.PNG" alt=""></p>]]></content:encoded></item><item><title><![CDATA[vapor and apns]]></title><description><![CDATA[<p>Sending Apple push notifications from a <a href="https://github.com/vapor/vapor">Vapor</a> web app turns out to be pretty easy using the <a href="https://github.com/siemensikkema/vapor-jwt">JWT</a> package.</p>

<p><small>  </small></p>

<pre><code>import CTLS  
import Foundation  
import HTTP  
import JWT  
import Vapor

public class APNSService: Service {  
  public enum LaunchError: Error {
    case loadAuthKeyFailed
    case setSecretFailed
    case setTokenFailed
  }

  fileprivate static let topic = "&lt;your.app.</code></pre>]]></description><link>https://blog.marksmith.org/vapor-and-apns/</link><guid isPermaLink="false">4f401423-8e75-4fd8-8445-7afe77b2b6e1</guid><dc:creator><![CDATA[Mark Smith]]></dc:creator><pubDate>Sat, 19 Nov 2016 15:52:24 GMT</pubDate><content:encoded><![CDATA[<p>Sending Apple push notifications from a <a href="https://github.com/vapor/vapor">Vapor</a> web app turns out to be pretty easy using the <a href="https://github.com/siemensikkema/vapor-jwt">JWT</a> package.</p>

<p><small>  </small></p>

<pre><code>import CTLS  
import Foundation  
import HTTP  
import JWT  
import Vapor

public class APNSService: Service {  
  public enum LaunchError: Error {
    case loadAuthKeyFailed
    case setSecretFailed
    case setTokenFailed
  }

  fileprivate static let topic = "&lt;your.app.bundle.id&gt;"
  fileprivate static let teamID = "&lt;your team id&gt;"
  fileprivate static let keyID = "&lt;your key id&gt;"
#if Xcode
  fileprivate static let apnsServer = "api.development.push.apple.com"
#else
  fileprivate static let apnsServer = "api.push.apple.com"
#endif
  fileprivate static let tokenUpdateInterval = TimeInterval(50 * 60)
  fileprivate static let tokenQueue = DispatchQueue(label: "token", attributes: .concurrent)

  fileprivate let privateKey: Data
  fileprivate var prevToken: String = ""
  fileprivate var prevTokenDate = Date(timeIntervalSince1970: 0)

  public init(config: Config) throws {
    var pKey = EVP_PKEY_new()
    let fp = fopen("&lt;/path/to/your/.p8 file&gt;", "r")

    guard fp != nil else {
      throw LaunchError.loadAuthKeyFailed
    }

    PEM_read_PrivateKey(fp, &amp;pKey, nil, nil)

    fclose(fp)

    let ecKey = EVP_PKEY_get1_EC_KEY(pKey)

    EC_KEY_set_conv_form(ecKey, POINT_CONVERSION_UNCOMPRESSED)

    guard let privateKeyBigNum = BN_bn2hex(EC_KEY_get0_private_key(ecKey)),
          let privateKeyHex = String(validatingUTF8: privateKeyBigNum),
          let privateKeyData = Data(hexString:  "00" + privateKeyHex) else {
      throw LaunchError.setSecretFailed
    }

    self.privateKey = privateKeyData

    guard !self.token().isEmpty else {
      throw LaunchError.setTokenFailed
    }
  }

  public func sendNotification(to deviceToken: String, title: String?, body: String?, badge: Int?, sound: String?, payload: [String: Node]) {
    guard let url = URL(string: "https://\(APNSService.apnsServer)/3/device/\(deviceToken)") else {
      Logger.logError("invalid url for device token \(deviceToken)")
      return
    }

    var alertDict: [String: Node] = [:]

    if let title = title {
      alertDict["title"] = Node(title)
    }

    if let body = body {
      alertDict["body"] = Node(body)
    }

    var apsDict: [String: Node] = [:]

    if alertDict.count &gt; 0 {
      apsDict["alert"] = Node(alertDict)
    }

    if let badge = badge {
      apsDict["badge"] = Node(badge)
    }

    if let sound = sound {
      apsDict["sound"] = Node(sound)
    }

    var bodyDict = payload

    bodyDict["aps"] = Node(apsDict)

    guard let json = try? JSON(node: bodyDict),
          let bytes = try? json.makeBytes() else {
      Logger.logError("failed to create json for \(bodyDict)")
      return
    }

    var request = URLRequest(url: url)

    request.httpMethod = "POST"
    request.httpBody = Data(bytes)

    request.setValue("0", forHTTPHeaderField: "apns-expiration")
    request.setValue("10", forHTTPHeaderField: "apns-priority")
    request.setValue(APNSService.topic, forHTTPHeaderField: "apns-topic")

    request.setValue("bearer \(self.token())", forHTTPHeaderField: HeaderKey.authorization.key)

    let task = URLSession.shared.dataTask(with: request) { data, response, error in
      if let error = error {
        Logger.logError("send push notification failed with error \(error)")
      }

      if let response = response as? HTTPURLResponse {
        if response.statusCode &lt; 200 || response.statusCode &gt; 299 {
          let reason = String(data: data ?? Data(), encoding: .utf8) ?? "nil"
          Logger.logError("send push notification failed with status code \(response.statusCode), reason \(reason)")
        }
      }
    }

    task.resume()
  }

  fileprivate func token() -&gt; String {
    let now = Date()
    var prevToken = ""
    var prevTokenDate = Date()

    APNSService.tokenQueue.sync() {
      prevToken = self.prevToken
      prevTokenDate = self.prevTokenDate
    }

    if !prevToken.isEmpty &amp;&amp; now.timeIntervalSince(prevTokenDate) &lt; APNSService.tokenUpdateInterval {
      return prevToken
    }

    guard let jwt = try? JWT(additionalHeaders: [KeyIDHeader(identifier: APNSService.keyID)],
                             payload: JSON([IssuerClaim(string: APNSService.teamID), IssuedAtClaim(date: Date())]),
                             signer: ES256(key: Bytes(self.privateKey))),
          let token = try? jwt.createToken() else {
      return prevToken
    }

    APNSService.tokenQueue.sync(flags: .barrier) {
      self.prevToken = token
      self.prevTokenDate = now
    }

    return token
  }
}
</code></pre>

<p></p>

<p>(Updated for Vapor 2. This is not HTTP/2, too much traffic will get you banned.)</p>]]></content:encoded></item><item><title><![CDATA[pro git]]></title><description><![CDATA[<p><img src="https://blog.marksmith.org/content/images/2016/10/Screen-Shot-2016-10-01-at-10-55-53-AM.png" width="774" height="381"></p>

<p>From the Hacker News discussion of <a href="https://news.ycombinator.com/item?id=12616174">Purposes, Concepts, Misfits, and a Redesign of Git</a>.</p>]]></description><link>https://blog.marksmith.org/pro-git/</link><guid isPermaLink="false">b8cd0f80-b2fc-43f7-b5e6-619529e5d173</guid><dc:creator><![CDATA[Mark Smith]]></dc:creator><pubDate>Sat, 01 Oct 2016 15:21:51 GMT</pubDate><content:encoded><![CDATA[<p><img src="https://blog.marksmith.org/content/images/2016/10/Screen-Shot-2016-10-01-at-10-55-53-AM.png" width="774" height="381"></p>

<p>From the Hacker News discussion of <a href="https://news.ycombinator.com/item?id=12616174">Purposes, Concepts, Misfits, and a Redesign of Git</a>.</p>]]></content:encoded></item><item><title><![CDATA[the app store parabola]]></title><description><![CDATA[<p>Lots of fake five-star reviews, lots of unhappy one-star reviews, very little in the middle.  I wonder what percentage of app ratings look like this.</p>

<p><img src="https://blog.marksmith.org/content/images/2016/05/Screen-Shot-2016-05-09-at-3-46-24-PM.png" alt=""></p>]]></description><link>https://blog.marksmith.org/the-app-store-parabola/</link><guid isPermaLink="false">23b8e974-7ebd-4edd-9cd3-bdb1161845e6</guid><dc:creator><![CDATA[Mark Smith]]></dc:creator><pubDate>Mon, 09 May 2016 19:56:50 GMT</pubDate><content:encoded><![CDATA[<p>Lots of fake five-star reviews, lots of unhappy one-star reviews, very little in the middle.  I wonder what percentage of app ratings look like this.</p>

<p><img src="https://blog.marksmith.org/content/images/2016/05/Screen-Shot-2016-05-09-at-3-46-24-PM.png" alt=""></p>]]></content:encoded></item><item><title><![CDATA[mnmlly mnml]]></title><description><![CDATA[<blockquote>
  <p>Enter Andrew Kim, at the time a minimalist young gun. Kim's site is paradoxically called "Minimally Minimal." In other words, according to Kim, the site is the least minimal it could possibly be. But this is the opposite of the case, as the site is actually quite minimal.</p>
</blockquote>

<p><a href="http://www.elischiff.com/blog/2016/4/27/do-anything-iii">Eli Schiff</a></p>]]></description><link>https://blog.marksmith.org/mnmlly-mnml/</link><guid isPermaLink="false">0451bef1-18e0-4c07-8b97-0acb6cd675fc</guid><dc:creator><![CDATA[Mark Smith]]></dc:creator><pubDate>Wed, 27 Apr 2016 15:45:11 GMT</pubDate><content:encoded><![CDATA[<blockquote>
  <p>Enter Andrew Kim, at the time a minimalist young gun. Kim's site is paradoxically called "Minimally Minimal." In other words, according to Kim, the site is the least minimal it could possibly be. But this is the opposite of the case, as the site is actually quite minimal.</p>
</blockquote>

<p><a href="http://www.elischiff.com/blog/2016/4/27/do-anything-iii">Eli Schiff</a> is my favorite grumpy old man.</p>]]></content:encoded></item><item><title><![CDATA[Ma’agalim]]></title><description><![CDATA[<p>Fantastic animation, needs to be seen full-screen.</p>

<iframe src="https://player.vimeo.com/video/162052542" width="500" height="281" frameborder="0" webkitallowfullscreen="" mozallowfullscreen="" allowfullscreen></iframe>]]></description><link>https://blog.marksmith.org/maagalim/</link><guid isPermaLink="false">de024b31-cd3c-46e4-b341-e379b7c44eb0</guid><dc:creator><![CDATA[Mark Smith]]></dc:creator><pubDate>Mon, 11 Apr 2016 16:46:33 GMT</pubDate><content:encoded><![CDATA[<p>Fantastic animation, needs to be seen full-screen.</p>

<iframe src="https://player.vimeo.com/video/162052542" width="500" height="281" frameborder="0" webkitallowfullscreen="" mozallowfullscreen="" allowfullscreen></iframe>]]></content:encoded></item></channel></rss>