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.
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.
My GitHub Action does these steps:
index.html filersync to copy the updated site to the web hostCurrently 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:
- masterThe 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@v2The 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'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.goWith 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: trueThere 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.
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 %}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 %}Here is my complete build.yaml action.
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:
Click the option you prefer and your choice should take immediate effect.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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 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.
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.
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.
In the manager.go file you need to update the "github.com/codemicro/readingList/transport"
import to reference your repository.
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.
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.
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.
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.
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.
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.
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.
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).
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.
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.
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.
{{ $image := .ResourceGetMatch “taz.jpg” }}
Taz (2003 - 2022)
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.
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_autocmdFor 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.
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.