Gestimer


I’m firmly in the “set timers on my watch or phone” camp, so this app probably isn’t for me. The mechanism for setting the time, however, is very inventive.


Updating Your Website Via GitHub Actions


For several years I maintained a subdomain on my website where I kept a list of all the books I had read, organized by year. The site was completely hand coded—no CMS involved, no frameworks, just HTML, CSS, and a little JavaScript.

While having the list was pleasing, updating the site was tedious at times. Copy and paste the previous book’s HTML table row, and replace all the values. Then commit the changes to the sites Git repository, and push the changes to GitHub. Finally sign into the host and update the site by updating the local working copy of the repository.

I have been wanting to automate this for some time, and, with a small Go application and a GitHub Action, it now requires far less interaction from me.

GitHub Action

My GitHub Action does these steps:

  • Checks out the site’s Git repository
  • Sets up Go
  • Runs the Go application that builds the index.html file
  • Commits the changes to the Git repository
  • Sets up an ssh key using a Repository Secret
  • Uses rsync to copy the updated site to the web host

Trigger

Currently I have the Action set to run anytime there is a push to the master branch. I am planning on revising this, to only run the Action when a pull request is merged into master. That will allow me to make multiple changes, without repeatedly running the automation.

name: Build Book Website

on:
  push:
    branches:
      - master

Checkout the Repository

The action runs in a “container” which itself is running the latest version of Ubuntu. GitHub provides a clause to specify the OS. GitHub also provides a library to checkout the repository.

jobs:
  # The "build" workflow
  build:
    runs-on: ubuntu-latest
      steps:
        # Checks out repository
      - uses: actions/checkout@v2

Setting up Go

The site is rebuilt using a small Go language application that reads the list of books from a comma-separated-value (CSV) file, and uses Go’s templating feature to build an HTML table with one row per book.

In order for the GitHub Action to run this application, Go needs to be setup. Again, a provided library makes getting Go installed quick.

- name: Setup Go
  uses: actions/setup-go@v2
  with:
    go-version: '1.18.3'

Running the Go Application

With Go installed, and the repository checked out, one command is all that is needed to run the Go application to rebuild the index.html file.

  # Run the Go application to update the site
- name: Run application
  run: go run main.go

Committing the Updated Repository

With the index.html file now updated the change needs to be committed to GitHub. I used an external action for this.

  # Commit the updated index.html file to the repository
- name: Commit updated index
  uses: EndBug/add-and-commit@v9
  with:
    add: 'site/index.html'
    author_name: GitHub Action
    author_email: root@zanshin.net
    message: 'Updated index.html with new listing(s)'
    push: true

There is only one file updated by the Go application, so I’m able to be specific about what is added. push: true is the default, but I put it here to be unambiguous. The Endbug/add-and-commit repository has more information about using this action.

Setting up an ssh Key

Getting an ssh key setup so that rsync can transfer the site from GitHub to the web host was the trickiest part of the entire action. I closely followed Deploying to a server via SSH and Rsync in a GitHub Action to get this working.

Remember, the action is running inside an Ubuntu container, and this will be a different container each time the action executes. The steps in zellwk.com’s article demonstrate how to use repository secrets to put the private half of an ssh key pair in the Ubuntu container, and how to update the .ssh/``known_hosts file. With all the pieces in place, here is the code for setting up the ssh key.

{% raw %}
  # Install the private half of our SSH key via a repository secret
- name: Install SSH Key
  uses: shimataro/ssh-key-action@v2
  with:
    key: {{ secrets.SSH_PRIVATE_KEY }}
    known_hosts: 'just-a-place-holder-so-we-dont-get-errors'

  # Update the know_hosts file
- name: Adding Known Hosts
  run: ssh-keyscan -H ${{ secrets.SSH_HOST }} >> ~/.ssh/known_hosts
{% endraw %}

rsync the Site to the Web Host

The final step uses rsync to copy the updated site to the web host. I restructured my repository to have all the site specific files in a site subdirectory. This makes it possible to copy the site and not the Go application and associated files to the web host.

{% raw %}
  # Use rsync to copy the repo to the host
- name: Deploy with rsync
  run: rsync -avz ./site/ ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }}:/usr/home/mnichols/public_html/books
{% endraw %}

The Complete build.yaml Action

Here is my complete build.yaml action.


Force Safari to Put New Tab at End of Tab Bar


By default Safari wants to position any new tab opened after the last related tab. When you are browsing a site and open links from it in new tabs, having them immediately follow the current one is nice. However, when you simply want to open a new tab and have it appear at the end of the tab bar, you have to first position the focus on the current last tab, and then do CMD-T.

By enabling the “Debug” menu in Safari, you can access the “Tab Ordering” options. To enable the Debug menu run this command in the Terminal.

defaults write com.apple.Safari IncludeInternalDebugMenu 1

Then quit and reopen Safari. The Debug menu should be listed as the last menu in the Safari menu bar.

Locate the “Tab Ordering” menu option, and then hover over “Position of New Tabs”. There are four to choose from:

  • After Current Tab
  • After Related Tabs
  • After Last Related Tab
  • After Last Tab

Click the option you prefer and your choice should take immediate effect.


Code


I recently purchased “Code” by Charles Petzold. The second edition is fantastic. Starting from very basic first principles (flash lights) Mr. Petzold builds layer upon layer using Morse code and Braille along the way to introduce the concepts at the foundation of how computers function. I haven’t completed the book yet, but I highly recommend it.


Desktop Experiment


For the past 15 months I’ve been running an experiment in having a desktop computer, the first desktop computer I’ve owned since 2002. The experiment actually starts with the purchase of a 12.9” iPad Pro.

iPadPro

In March 2021, with a COVID-19 stimulus check in my pocket, I ordered a 12.9 inch iPadPro, a Magic Keyboard, and an Apple Pencil 2. At the time I was considering updating my 15-inch late-2013 MacBook Pro for a newer laptop, but with Apple’s conference looming on the horizon I didn’t want to get a laptop just before new ones were released.

I was attracted to the 12.9 inch iPad as it would work well for digital sheet music. As it turns out, it also works surprisingly well as a laptop replacement. I have used my iPad almost exclusively since it arrived. I rarely tote my laptop around now.

With the Blink terminal app installed I’m able to perform code / nerdy things by remote access into one of my “real” computers. With forScore installed I have access to over 170 pieces of music.

M1 iMac

Having my mobile needs met through the iPad, I was very intrigued by the new M1 iMac, and in June 2021 I bought one. It is a beautiful machine, both to use and to look at. It only has one drawback—it is desk bound.

Since the start of the pandemic I have worked remotely 100% of the time. I have a 2015-era 27” iMac from my employer on my desk, and next to it I have my 24” M1 iMac. Thanks to Universal Control, I’m able to use the keyboard and trackpad from the M1 iMac to access both machines. During the work day I use my computer for my things: email, non-work related browsing, listening to music, etc. I use the work iMac for, well, work.

At the end of my work day, when I go home (yes, going home is something you do even when you work at home), I take my iPad and move to the living room. I don’t use my computer in the evenings, or on the weekends. I remotely access the M1 iMac to do coding work, but I don’t sit at my desk and interact with the computer directly.

M2 MacBook Air Lust

Ever since the new M2 MacBook Air was released, I have had a strong attraction to it. Compact but still capable, comes in a dark, almost black color, and it is eminently portable. However, having a 12.9 inch iPadPro and a 24 inch M1 iMac and an M2 MacBook Air feels like overly conspicuous consumption.

Not that I am opposed to having more computers. I already have two Raspberry Pi (a 3 and a 4b), a 13 inch Asus Q325 running Linux, an Intel NUC also running Linux, a 15” 2009 MacBook Pro that mostly collects dust (I may try Google Chrome OS on it), my 15” 2013 MacBook Pro, which I occasionally use, a Mac Mini, and the afore mentioned 12.9 inch iPadPro and M1 iMac. Oh, and an iPhone and Apple Watch.

Still, having both a desktop that I really don’t make full use of and a MacBook Air seems wasteful and extravagant. When I initially started thinking about trading my M1 iMac in for a MacBook Air, the M1 wasn’t listed as an option on Apple’s trade in calculator. Once it started listing the M1 iMac the trade in value was $800. By craftily not acting on impulse I have managed to lower that trade in value to $600 and now $450.

The configuration I’d want in the MacBook Air (24GB Memory / 2TB Storage / M2 chip with 8‑core CPU, 10‑core GPU, 16‑core Neural Engine) lists for $2499. Subtracting $450 from that brings the cost down to $2049. Using the education discount the starting price is $2299 minus $450 nets $1849.

Through my Apple Card I can get interest free installment payments, spreading the cost out over 12 months, ~ $155 per month.

Buyers Reluctance

As a first born, I find it difficult to ask for things and to let myself have things. Deciding to switch computers—returning the iMac is a surprisingly hard decision to make. I want the MacBook Air, but then I also want the new Apple Watch Ultra, and a Studio Display. I’m attracted to shiny. There isn’t any computing need of mine unmet by my current combination of iPadPro and iMac, except for portability.

The experiment in having a desktop helped to me to realize that for my personal computing needs and habits, a laptop is the ideal solution. There are things that I want to do, and like to do, that cannot be performed on an iPad. There are also things that the iPad can do that a laptop (or desktop) can’t do. For me the ideal combination would be a laptop and an iPad.

Committing $155 a month for a year to reach that ideal is the stumbling block. At least for now.


Moving to an eSim


With an iPhone 14 Pro on order, I’ve been reading about eSIMs. My iPhone 12 Pro has a physical SIM card, and room for an eSIM as well. The new iPhone 14 Pro no longer has a SIM card slot (at least in North America); rather than wait for the day it arrives to convert, I went ahead and did it today.

A quick search on Duck Duck Go brought me to About eSIM on iPhone at Apple Support site. Following the steps outlined in the “Convert a Physical SIM to an eSIM on the same iPhone” section was straight forward and quick to do.

Open up the Settings app, and then tap on Cellular. Tap the “Convert to eSIM” button. Wait. Once it is done, power off the phone, remove the physical SIM, and power the phone back on. Voilà.

Now, when my new phone arrives, I only have to manage the transfer from old to new.


The Last Algorithms Course You'll Need


A free algorithms course on Front End Masters. While algorithms were discussed in some of my college courses, there wasn’t a dedicated course on this subject.


Automating a Reading List


For a very long time I have been searching for, and trying, different note taking schemes in a futile effort to find a way to capture links to pages on the Internet. I wanted a way to find some previously read article or posting about “X”. Searching my browser history sometimes works, but not always. Creating a bookmark would only result in hundreds of bookmarks, and another searching problem. I view a bookmark as a way to return to a frequently visited site. For example, I have a book mark to Hacker News, but I don’t bookmark individual articles I find there.

On Hacker News recently there was a posting about tracking everything a person had read for a year. Underneath the statistics there was an automated way to, with a single click, capture a page you’ve read online. It works by using a JavaScript bookmarklet, some GitHub repository Actions, and a couple of Go language functions.

What You Need

JavaScript Bookmarklet

Copy the readingList.js bookmarklet Gist to your GitHub account. Make it a secret Gist as it will eventually have your GitHub Personal Access Token (PAT) included.

Fork the readingList repository

Fork the readingList repository created by codemicro. It was their idea, they deserve the forks. Once you have your fork, scrub through the code and replace all the references to codemicro with your GitHub ID.

Workflows

The repository contains two GitHub Action workflows in the .github/workflows directory: append and build. Find the run line in both of these and update the path to point to your fork of the repository.

Additionally, in the append.yml file there is an email address for notifications; make sure to update it to your email address.

Go

generator.go

In the generator.go file, toward the end of the code, line 194 in my fork, there is an HTML link that needs to be updated to point to your readingList repository.

manager.go

In the manager.go file you need to update the "github.com/codemicro/readingList/transport" import to reference your repository.

go.mod

Once you’ve finished updating all the code references to your ID or repository, rebuild the go.mod file. I deleted it and ran go mod init and the go mod tidy.

save.html

In the comments to the original readingList.js Gist, jamesmstone put links to his fork of the project. He added a new piece of JavaScript that allows you to use the bookmarklet without a server. In my case I’m using GitHub Pages to host the actual list page, the save.html code the jamesmstone created makes this possible.

Copy his save.html code to the .site directory in your fork of the project. On line 13 of the file, change his account name to yours.

readingList.csv

The links are all appended to the readingList.csv file in the readingList repository. Unless you want all of codemicro’s articles, edit this file and delete all the lines except for the heading line.

GitHub Pages

In your readingList repository you need to setup GitHub Pages. Click on the Settings link in the navigation bar. I set mine up to be <account>.github.io/readingList. For Source I picked “Deploy from a branch”. And for Branch I selected “gh-pages” and “/root”. I also checked the “Enforce HTTPS” option.

Create a Personal Access Token

In order for the JavaScript bookmarklet to function, it need a GitHub Personal Access Token or PAT. This article, Using GitHub Actions with Repository Dispatch Event explains the mechanism used to tie JavaScript and GitHub action together, using a PAT. Creating a personal access token describes how to create and use a PAT.

Add PAT to readingList.js

Once you have created your PAT, you need to add it to line 3 of readingList.js. It becomes the token used by the rest of the code to gain access to your repository.

Update requestURL

You need to update the requestURL value to be the path to your readingList. If you are using GitHub Pages this will look like https://zanshin.github.io/readingList/save.

Create a Bookmarklet

I used How to create a JavaScript Bookmarklet Easily to compress the JavaScript to a single line. Copy that line and make a bookmark of it in your browser(s).

Using the Bookmarklet

With the bookmarklet created you are ready to put the automation to work. Find an article you’d like to add to your list and click the read bookmark and (if you have all the pieces properly lined up) the page should briefly disappear and reappear. The JavaScript interrogates the page, collecting the URL, the title, the meta data description, etc, and triggers the GitHub Action. It then redirects to the original page. By using the Action this way, the adding of the new entry to your reading list happens asynchronously. You can go to the Actions page of your repository and see the status of the actions as they are running. Within a minute or two the readingList site should be regenerated with the new entry added.

Moving Parts

This automation has many moving parts. Two pieces of JavaScript, GitHub Actions, a GitHub PAT, and GitHub Pages. Getting all the references updated from the original project to your account, getting the path to the repository correct in the readingList.js, making sure that GitHub Pages is setup properly; all these steps are crucial. Tracking down one missing or incorrect piece (the repository path, ahem) can be frustrating.

I had never used GitHub Actions prior to this, and my understanding of JavaScript is rather thin. I spent most of a day reading and learning to make all the parts of this make sense in my head, and making them all work together to have a functional reading list.

Update

I’ve been using this automation for several days now and it works very well. So far there has only been one site where the JavaScript bookmarklet wouldn’t work. I’ve been going through the many open tabs in my browsers and adding articles to my read list.


Taz


{{ $image := .ResourceGetMatch “taz.jpg” }}

Taz (2003 - 2022)


How to Setup Lua Autocmds for Neovim


The latest nightly builds of Neovim now have Lua-based autocmd support. Previously autocmd and augroup commands needed to be wrapped inside a vim.cmd block so that the Vimscript-based statements would work.

The nightly builds now include nvim_create_augroup and nvim_create_autocmd commands that allow you to create auto command groups and auto commands without resorting to nesting Vimscript in your Lua-based configuration.

Helpers

I created two helper functions: one for creating auto groups, and one for creating auto commands.

    local agrp = vim.api.nvim_create_augroup
    local acmd = vim.api.nvim_create_autocmd

Auto groups

For each set of related auto commands I wanted, I created a group. So that the group names will sort to the top of the output when running :au <event>, I preceded each name with an underscore. For example, this group is where I keep any command that is aimed at all file types.

    local _general = agrp("_general", { clear = true })

The clear = true isn’t strictly required, as it is the default. But I like it as a reminder that the group will clear previously set commands.

Autocmds

The format of the the nvim_create_autocmd statement took me a little bit of experimenting to figure out. Here is an example of what I ended up with.

    acmd({ "FocusLost" },
         { pattern = "*",
           command = ":wa",
           group = _general })

The first dictionary defines the event (or events) this autocommand is triggered by. The second dictionary defines the pattern to match, the command to run, and identifies the group the command belongs to. In the example above, the event is FocusLost, the pattern is *, the command is :wa, and the group is _general.

I have a number of groups and commands for various file types. Even for relatively short, simple commands I followed the same multi-line format. This way all the autocmd groups in the file look the same. The file itself can be viewed here.