<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Primary Unit</title>
    <link>https://www.robjwells.com</link>
    <description>A blog by Rob Wells, mostly about computer stuff.</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-gb</language>
    <copyright>This work is licensed under a Creative Commons Attribution 4.0 International License.</copyright>
    <lastBuildDate>Sat, 24 Dec 2022 13:09:00 +0000</lastBuildDate>
    
	<atom:link href="https://www.robjwells.com/rss.xml" rel="self" type="application/rss+xml" />
    
    
    <item>
      <title>RSS Whitespace and Hugo</title>
      <link>https://www.robjwells.com/2022/12/rss-whitespace-and-hugo/</link>
      <pubDate>Sat, 24 Dec 2022 13:09:00 +0000</pubDate>
      
      <guid>https://www.robjwells.com/2022/12/rss-whitespace-and-hugo/</guid>
      <description>&lt;p&gt;One thing I noticed about &lt;a href=&#34;https://www.robjwells.com/2022/12/sunrise-and-sunset-database/&#34;&gt;yesterday&amp;rsquo;s post&lt;/a&gt; was that the code formatting in
the RSS feed was off. This was due to building the site with &lt;a href=&#34;https://gohugo.io/hugo-pipes/minification/&#34;&gt;Hugo&amp;rsquo;s &lt;code&gt;--minify&lt;/code&gt;
option&lt;/a&gt;, which &amp;ldquo;reached into&amp;rdquo; the &lt;code&gt;&amp;lt;description&amp;gt;&lt;/code&gt; item for the post and
squished the whitespace without regard to whether it was in a &lt;code&gt;&amp;lt;pre&amp;gt;&lt;/code&gt; block.&lt;/p&gt;
&lt;p&gt;This was a simple fix: in Hugo&amp;rsquo;s settings there are a bunch of &lt;a href=&#34;https://gohugo.io/getting-started/configuration/#configure-minify&#34;&gt;options for
&lt;code&gt;minify&lt;/code&gt;&lt;/a&gt;, one of which is &lt;code&gt;disableXML&lt;/code&gt;. Add that to your config
file, rebuild, and you&amp;rsquo;re done:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-toml&#34; data-lang=&#34;toml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;minify&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;disableXML&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description>
    </item>
    
    <item>
      <title>Sunrise and Sunset Database</title>
      <link>https://www.robjwells.com/2022/12/sunrise-and-sunset-database/</link>
      <pubDate>Fri, 23 Dec 2022 20:00:00 +0000</pubDate>
      
      <guid>https://www.robjwells.com/2022/12/sunrise-and-sunset-database/</guid>
      <description>&lt;p&gt;A while ago, I was having trouble with the automatic light-dark theme switching on my laptop, as macOS tries to wait until an opportune moment when the computer is idle to make the change. That&amp;rsquo;s fair enough, as it does involve a moment when the system is not responsive. I was finding that if I kept working it would often be quite a while after sunset that the switch occurred.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://wiki.keyboardmaestro.com/action/Set_System_to_Dark_Mode&#34;&gt;Keyboard Maestro can set the system light-dark theme&lt;/a&gt;, so I thought I&amp;rsquo;d rig it up to periodically check whether sunrise or sunset had occurred, and then force the change rather than being polite. There&amp;rsquo;s a trade off but overall I prefer the automatic switching to take place closer to the sunrise or sunset time rather than the system waiting in an attempt not to inconvenience me briefly.&lt;/p&gt;
&lt;p&gt;It relies on a local SQLite database of sunrise and sunset times for my home, but when I first rigged it up, I &amp;ldquo;only&amp;rdquo; put in the times for 2021 and 2022. It&amp;rsquo;s now time to fill the database for the coming years, so what better time to have a peek at how it works?&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://github.com/robjwells/sunrise-sunset&#34;&gt;You can find the git repository here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;First off, we need a data source. I didn&amp;rsquo;t include a scraper in repo originally, partly out of politeness, but partly also because I hoped to change the data source.&lt;/p&gt;
&lt;p&gt;In the UK, &lt;a href=&#34;http://astro.ukho.gov.uk/&#34;&gt;HM Nautical Almanac Office&lt;/a&gt; (NAO) publishes various rise, set and twilight times (for other celestial objects as well as the sun). After a few &lt;a href=&#34;http://astro.ukho.gov.uk/nao/miscellanea/birs2.html&#34;&gt;false&lt;/a&gt; &lt;a href=&#34;http://astro.ukho.gov.uk/nao/miscellanea/UK_SRSS/uk_dec.html&#34;&gt;starts&lt;/a&gt; trying to find a big list of daily rise and set times for a long period at a time, you&amp;rsquo;ll come across the &lt;a href=&#34;http://astro.ukho.gov.uk/surfbin/runsurf_beta.cgi&#34;&gt;WebSurf 2.0&lt;/a&gt; application, which reassuringly has &lt;code&gt;.cgi&lt;/code&gt; in the URL (and, naturally, you would hope is entirely written in Perl).&lt;/p&gt;
&lt;p&gt;But after clicking your way through, you’ll end up with a big plain-text listing with no markup (here, snipped horizontally and vertically):&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;    2023 Sunrise and Sunset times


         January       February

       Rise   Set     Rise   Set
       h  m   h  m    h  m   h  m
  1   08 06  16 02   07 39  16 50
  2   08 06  16 03   07 38  16 51
  3   08 06  16 04   07 36  16 53
  4   08 06  16 05   07 35  16 55
  5   08 06  16 07   07 33  16 57
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now while this is not impossible to deal with, and I of course hear &lt;a href=&#34;https://www.youtube.com/watch?v=sERiPuOQyvo&#34;&gt;Jerusalem&lt;/a&gt; when thinking about the public servants who went through the trouble of making this data available, at the end of the day I am, in the words of the Tories, a shirker rather than a striver. So let&amp;rsquo;s find an easier way! (Also one that works for people outside Britain.)&lt;/p&gt;
&lt;p&gt;Thankfully, &lt;del&gt;&lt;a href=&#34;https://www.whattimeisitrightnow.com/&#34;&gt;whattimeisitrightnow.com&lt;/a&gt;&lt;/del&gt; &lt;a href=&#34;https://www.timeanddate.com/sun/uk/london&#34;&gt;timeanddate.com&lt;/a&gt; has us covered. Search for your preferred location and go to the &amp;ldquo;Sun and Moon&amp;rdquo; ▸ &amp;ldquo;Sunrise &amp;amp; Sunset&amp;rdquo; view. You&amp;rsquo;ll end up with something like &lt;code&gt;timeanddate.com/sun/uk/london&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;That will show you the rise and set times for the current month, but if you play with the dropdown above the table, you may notice that picking a different month or year is as simple as adding parameters to the URL, like so:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;https://www.timeanddate.com/sun/uk/london?month=&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;month&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;amp;year=&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;year&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Thankfully, too, this data is in the HTML that gets served up, with no JavaScript-dependent front-end framework or scraper-blocker in the way. That leads us naturally onto this simple shell script:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;seq &lt;span class=&#34;m&#34;&gt;2023&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;2030&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;while&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;read&lt;/span&gt; year&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    seq &lt;span class=&#34;m&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;m&#34;&gt;12&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;while&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;read&lt;/span&gt; month&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        curl --silent &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;            &lt;span class=&#34;s2&#34;&gt;&amp;#34;https://www.timeanddate.com/sun/uk/london?month=&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;month&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;amp;year=&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;year&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt; &lt;span class=&#34;se&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;se&#34;&gt;&lt;/span&gt;            &amp;gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;sunrise-&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;year&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;month&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;.html&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This just dumps each month from 2023 through 2030 into an individual HTML file in the current directory. From there we turn to &lt;a href=&#34;https://github.com/robjwells/sunrise-sunset/blob/main/process_html_tables.py&#34;&gt;&lt;code&gt;process_html_tables.py&lt;/code&gt;&lt;/a&gt;. This is pretty straightforward and broken up into nice small parts, so you can read that at your leisure. It leans on Pandas to parse the HTML tables from the downloaded files and Pendulum to correctly handle dates with timezones, but there&amp;rsquo;s not much too it. Do check that you&amp;rsquo;re using the right timezone, though.&lt;/p&gt;
&lt;p&gt;Running that Python file will process all the scraped HTML files in the folder, and print an array of JSON objects, each containing a date, sunrise datetime and sunset datetime. This is an intermediate step that isn&amp;rsquo;t strictly necessary, but you might want that data available in a plain-text file.&lt;/p&gt;
&lt;p&gt;But it also means it&amp;rsquo;s trivial to create a SQLite database file with &lt;a href=&#34;https://sqlite-utils.datasette.io/en/stable/&#34;&gt;&lt;code&gt;sqlite-utils&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;python3 process_html_tables.py &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; sqlite-utils insert sunrise-sunset.db london -
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That will create the &amp;ldquo;london&amp;rdquo; (in this case) table for you, but there&amp;rsquo;s also an SQL file to create both the table for the sunrise and sunset times and also one to log successful checks (&lt;code&gt;create-tables.sql&lt;/code&gt;):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;CREATE&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;TABLE&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;london&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;date&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;text&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;primary&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;key&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;sunrise&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;text&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;not&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;null&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;sunset&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;text&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;not&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;null&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;CREATE&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;TABLE&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;successful_checks&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;date&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;text&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;not&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;null&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;references&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;london&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;date&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;kind&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;text&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;not&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;null&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;primary&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;key&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;kind&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;date&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;check&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;kind&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;in&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;sunrise&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;sunset&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This second table exists so that you can have, say, Keyboard Maestro automatically switch between light and dark modes, but also not have it override any manual change you make after that within the check period (for instance, if it&amp;rsquo;s a cloudy day and you want to keep dark mode on, so you manually re-enable it).&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s &lt;code&gt;check-sunrise.sql&lt;/code&gt; (&lt;code&gt;check-sunset.sql&lt;/code&gt; is similar):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;select&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;time&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;now&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;&amp;gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;time&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;sunrise&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;as&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;sun has risen&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;from&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;london&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;where&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;date&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;date&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;now&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;and&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;not&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;exists&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;select&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;            &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;date&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;from&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;            &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;successful_checks&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;        &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;where&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;            &lt;/span&gt;&lt;span class=&#34;n&#34;&gt;kind&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;sunrise&amp;#39;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;            &lt;/span&gt;&lt;span class=&#34;k&#34;&gt;and&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;date&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;date&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;now&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;    &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Note that it only returns true (well, 1) if there hasn&amp;rsquo;t already been a successful (ie one causing a change) sunrise check today.&lt;/p&gt;
&lt;p&gt;A fair question is: &amp;ldquo;Why keep checking if you&amp;rsquo;ve already passed sunrise or sunset?&amp;rdquo; And the answer is: mostly so I don&amp;rsquo;t have to worry about the procedure for disabling and enabling the relevant Keyboard Maestro macros. Instead, I can have the macro run at a set interval between the earliest and latest sunrise/sunset times, and be confident it won&amp;rsquo;t change anything if it&amp;rsquo;s already run successfully. Here&amp;rsquo;s the sunrise macro itself:&lt;/p&gt;
&lt;p&gt;&lt;img src=&#34;keyboard-maestro-sunrise.png&#34; alt=&#34;A screenshot of the sunrise macro in Keyboard Maestro&#34;&gt;&lt;/p&gt;
&lt;p&gt;As a convenience, there&amp;rsquo;s an SQL script in the repo to find when these times are from the database file.&lt;/p&gt;
&lt;p&gt;One other convenience is a LaunchBar action that shows duration between the current time and today&amp;rsquo;s sunrise and sunset:&lt;/p&gt;
&lt;p&gt;&lt;video src=&#34;launchbar-sunrise-sunset.mp4&#34; controls muted playsinline&gt;&lt;/p&gt;
&lt;p&gt;You&amp;rsquo;ll have to create the actual action yourself through LaunchBar&amp;rsquo;s Action Editor, but the file &lt;a href=&#34;https://github.com/robjwells/sunrise-sunset/blob/main/launchbar-sunrise-sunset.sh&#34;&gt;&lt;code&gt;launchbar-sunrise-sunset.sh&lt;/code&gt;&lt;/a&gt; can be pasted in to the default shell script in a new action, with of course the database file path and table names adjusted.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Typinator is on sale</title>
      <link>https://www.robjwells.com/2022/12/typinator-is-on-sale/</link>
      <pubDate>Fri, 09 Dec 2022 11:37:57 +0000</pubDate>
      
      <guid>https://www.robjwells.com/2022/12/typinator-is-on-sale/</guid>
      <description>&lt;p&gt;Long time! Lots to talk about, some point soon.&lt;/p&gt;
&lt;p&gt;In the mean time, &lt;a href=&#34;https://www.ergonis.com/typinator&#34;&gt;Typinator&lt;/a&gt; is currently on sale (along with all ergonis
software), at 30% off, to celebrate the snazzy new ergonis website.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve much more to say about Typinator, but in short: I was a TextExpander user
for a long time, but grew hesitant to use it because it was slow and awkward.&lt;/p&gt;
&lt;p&gt;Typinator is not! It&amp;rsquo;s fast and flexible. I switched in March 2021 and I use it
a lot more than I did TextExpander, and creating new snippets is
straightforward and pleasant. The snippet search is also really fast and
responsive.&lt;/p&gt;
&lt;p&gt;The snippet language differs from TextExpander&amp;rsquo;s, but it&amp;rsquo;s easy to get used to.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://www.ergonis.com/typinator&#34;&gt;Do give it a try!&lt;/a&gt;&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Populating the books list with AWK</title>
      <link>https://www.robjwells.com/2022/01/populating-the-books-list-with-awk/</link>
      <pubDate>Thu, 20 Jan 2022 20:40:28 +0000</pubDate>
      
      <guid>https://www.robjwells.com/2022/01/populating-the-books-list-with-awk/</guid>
      <description>&lt;p&gt;I read &lt;a href=&#34;https://www.robjwells.com/books/&#34;&gt;48 books&lt;/a&gt; last year, which is a lot for me. At least part of
that, I think, was to keep my mind in gear in a fairly healthy way, when I
didn’t feel like engaging with the world (poor mental health — thanks
once-in-a-century pandemic!)&lt;/p&gt;
&lt;p&gt;What I did &lt;em&gt;not&lt;/em&gt; do, though, is keep the &lt;a href=&#34;https://www.robjwells.com/books/&#34;&gt;list of books&lt;/a&gt; up to date,
not since spring 2020.&lt;/p&gt;
&lt;p&gt;Since I’ve been playing around with AWK a little, and had a big TSV file
containing the details for each of the books, I thought I&amp;rsquo;d pair the two to
fill out the missing files.&lt;/p&gt;
&lt;p&gt;I use &lt;a href=&#34;https://gohugo.io&#34;&gt;Hugo&lt;/a&gt; to generate this site, and each book is represented by a
markdown file containing a metadata block with (at least) the title, author’s
name, and the date on which I finished reading it. For example:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-markdown&#34; data-lang=&#34;markdown&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;title: &amp;#34;Empire of Pain&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;author: &amp;#34;Patrick Radden Keefe&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;finish-date: 2022-01-01
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;---
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;ge&#34;&gt;_Short review to come!_&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;AWK is great for processing lines of text, but it’s missing some of the library
functions you’d like for working with the filesystem. The book files are stored
in this directory structure, where the markdown files are under each year
folder:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;content/books
├── 2019
├── 2020
├── 2021
└── 2022
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Those year folders might not exist (&lt;em&gt;did not&lt;/em&gt; for 2021 and 2022). I could have
just created them by hand, but then we’d miss out on some yak-shaving.&lt;/p&gt;
&lt;p&gt;The path to the markdown file is constructed using the book&amp;rsquo;s title (for the
markdown filename itself) and finish date (for the year directory). We can wrap
the &lt;code&gt;dirname&lt;/code&gt; Unix utility:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-awk&#34; data-lang=&#34;awk&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;ln&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;dirname&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;path&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;ln&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;cmd&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;dirname &amp;#34;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;path&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;ln&#34;&gt;3&lt;/span&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;cmd&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;|&amp;amp;&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;getline&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;result&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;ln&#34;&gt;4&lt;/span&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kr&#34;&gt;close&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;cmd&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;ln&#34;&gt;5&lt;/span&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;result&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;ln&#34;&gt;6&lt;/span&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The funny &lt;code&gt;|&amp;amp;&lt;/code&gt; operator on line 3 executes the command in &lt;code&gt;cmd&lt;/code&gt; and then
&lt;code&gt;getline&lt;/code&gt; stores the first line of the output (there&amp;rsquo;s just one for &lt;code&gt;dirname&lt;/code&gt;) in
&lt;code&gt;result&lt;/code&gt;. We call &lt;code&gt;close&lt;/code&gt; on the command string to release the associated file
descriptor.&lt;/p&gt;
&lt;p&gt;Let’s wrap our wrapper in a function that just makes sure the directory exists,
using &lt;code&gt;mkdir -p&lt;/code&gt;, to make our lives easier:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-awk&#34; data-lang=&#34;awk&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;ln&#34;&gt; 8&lt;/span&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;ensure_dir&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;path&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;ln&#34;&gt; 9&lt;/span&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kr&#34;&gt;system&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;mkdir -p &amp;#34;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;dirname&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;path&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;  
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;ln&#34;&gt;10&lt;/span&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now we can think about the filename itself. It doesn&amp;rsquo;t have to be anything
particular, but I like to keep mine &lt;code&gt;really-simple-like-this.md&lt;/code&gt;. We’re using
the title of the books as the filenames, so the key is just to strip out
non-word characters:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-awk&#34; data-lang=&#34;awk&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;ln&#34;&gt;12&lt;/span&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;safe_name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;ln&#34;&gt;13&lt;/span&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;remove_non_word_characters&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;remove_apostrophes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;tolower&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;string&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;ln&#34;&gt;14&lt;/span&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;ln&#34;&gt;15&lt;/span&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;ln&#34;&gt;16&lt;/span&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;remove_apostrophes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;ln&#34;&gt;17&lt;/span&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;gensub&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sr&#34;&gt;/[&amp;#39;’]/&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;g&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;ln&#34;&gt;18&lt;/span&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;ln&#34;&gt;19&lt;/span&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;ln&#34;&gt;20&lt;/span&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;remove_non_word_characters&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;ln&#34;&gt;21&lt;/span&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;gensub&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sr&#34;&gt;/\W+/&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;-&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;g&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;ln&#34;&gt;22&lt;/span&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;remove_apostrophes&lt;/code&gt; function is not strictly necessary but ensures titles
such as &lt;em&gt;Hitler’s Army&lt;/em&gt; don’t become &lt;code&gt;hitler-s-army&lt;/code&gt; when they go through
&lt;code&gt;remove_non_word_characters&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;With that setup done, we can move on to the meat of the file, the lone
pattern-action statement:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-awk&#34; data-lang=&#34;awk&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;ln&#34;&gt;24&lt;/span&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sr&#34;&gt;/^(2020-(0[6-9]|1[0-2])|202[12])/&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;ln&#34;&gt;25&lt;/span&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;path_template&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;content/books/%s/%s.md&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;ln&#34;&gt;26&lt;/span&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;year&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;substr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;$&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;4&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;ln&#34;&gt;27&lt;/span&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;title_for_path&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;safe_name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;$&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;ln&#34;&gt;28&lt;/span&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;path&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;sprintf&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;path_template&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;year&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;title_for_path&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;ln&#34;&gt;29&lt;/span&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;ln&#34;&gt;30&lt;/span&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;content_template&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt; &lt;span class=&#34;err&#34;&gt;\&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;ln&#34;&gt;31&lt;/span&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;s2&#34;&gt;&amp;#34;---\n&amp;#34;&lt;/span&gt; &lt;span class=&#34;err&#34;&gt;\&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;ln&#34;&gt;32&lt;/span&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;s2&#34;&gt;&amp;#34;title: \&amp;#34;%s\&amp;#34;\n&amp;#34;&lt;/span&gt; &lt;span class=&#34;err&#34;&gt;\&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;ln&#34;&gt;33&lt;/span&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;s2&#34;&gt;&amp;#34;author: \&amp;#34;%s\&amp;#34;\n&amp;#34;&lt;/span&gt; &lt;span class=&#34;err&#34;&gt;\&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;ln&#34;&gt;34&lt;/span&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;s2&#34;&gt;&amp;#34;finish-date: %s\n&amp;#34;&lt;/span&gt; &lt;span class=&#34;err&#34;&gt;\&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;ln&#34;&gt;35&lt;/span&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;s2&#34;&gt;&amp;#34;---\n&amp;#34;&lt;/span&gt; &lt;span class=&#34;err&#34;&gt;\&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;ln&#34;&gt;36&lt;/span&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;s2&#34;&gt;&amp;#34;\n&amp;#34;&lt;/span&gt; &lt;span class=&#34;err&#34;&gt;\&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;ln&#34;&gt;37&lt;/span&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;s2&#34;&gt;&amp;#34;_Short review to come!_&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;ln&#34;&gt;38&lt;/span&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;content&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;sprintf&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;content_template&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;$&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;$&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;3&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;$&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;ln&#34;&gt;39&lt;/span&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;ln&#34;&gt;40&lt;/span&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;ensure_dir&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;path&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;ln&#34;&gt;41&lt;/span&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kr&#34;&gt;print&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;content&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;path&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;ln&#34;&gt;42&lt;/span&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We match lines that start with dates representing June 2020 (the last update
being in May 2020) through 2022. Next, we pull out the year from the date
(field 1), and munge the book title (field 2), before formatting them into the
path template with the built-in &lt;code&gt;sprintf&lt;/code&gt; function.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;content_template&lt;/code&gt; is just the markdown you saw earlier in the post, again
formatted using &lt;code&gt;sprintf&lt;/code&gt; and this time involving the author name (field 3).&lt;/p&gt;
&lt;p&gt;We ensure the directory exists for the target markdown file, and just print the
formatted template via redirection into the file.&lt;/p&gt;
&lt;p&gt;So, would I use AWK over Python for things like this in the future? Maybe. The
disadvantage with AWK is that it’s missing built-in tools for working with
the system, and the work done above would be easier with, say, &lt;a href=&#34;https://docs.python.org/3.10/library/pathlib.html#module-pathlib&#34;&gt;Python’s
pathlib module&lt;/a&gt;. Or, then again, I could have just done &lt;code&gt;mkdir 2021 2022&lt;/code&gt; — but I wanted to get a feel for calling tools from AWK.&lt;/p&gt;
&lt;p&gt;I think this tool is perhaps just on the edge where it could go either way.
Python has more tools available, but AWK is so focussed on text processing that
it does some of that boring work you’d have to do manually in Python.&lt;/p&gt;
&lt;p&gt;It was fun, and I look forward to using AWK for more in the future. (I’ve
bought &lt;a href=&#34;https://archive.org/details/pdfy-MgN0H1joIoDVoIC7&#34;&gt;the book&lt;/a&gt; second-hand.)&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Ejecting all disks</title>
      <link>https://www.robjwells.com/2022/01/ejecting-all-disks/</link>
      <pubDate>Sun, 09 Jan 2022 20:48:27 +0000</pubDate>
      
      <guid>https://www.robjwells.com/2022/01/ejecting-all-disks/</guid>
      <description>&lt;p&gt;David Sparks posted this week about &lt;a href=&#34;https://www.macsparky.com/blog/2022/01/using-keyboard-maestro-and-applescript-to-eject-external-drives/&#34;&gt;ejecting disks using AppleScript
and Keyboard Maestro&lt;/a&gt;. I’m grateful to David for posting
about this — it’s something I’ve needed for a while but hadn’t got
around to doing. Currently I plug my laptop into several external drives
when I’m sat at my desk, and have to eject them before taking it
somewhere more relaxed.&lt;/p&gt;
&lt;p&gt;But David’s script didn’t quite work for me in that a blunt &lt;code&gt;eject the disks&lt;/code&gt; command to the Finder will attempt to eject all the disks that
are considered “ejectable”. For me, this includes APFS snapshots that
aren’t obviously visible in the user interface, where trying to eject
them pops up a dialog referencing &amp;ldquo;Macintosh HD – Data@snap…&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;My alternative is to filter out any drives that contain &amp;ldquo;Macintosh HD&amp;rdquo;
in their displayed name:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-applescript&#34; data-lang=&#34;applescript&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;tell&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;application&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Finder&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nv&#34;&gt;eject&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;every&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;disk&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;where&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;Macintosh HD&amp;#34;&lt;/span&gt; &lt;span class=&#34;ow&#34;&gt;is not&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;its&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;displayed&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;end&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;tell&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Note that it’s important to use the &lt;em&gt;displayed name&lt;/em&gt; property as the
APFS snapshots have UUIDs as their name property.&lt;/p&gt;
&lt;p&gt;I’ve also stripped out David’s error handling, as I find that if there’s
an error ejecting the disk it’s usually communicated by the Finder
itself rather than via an error code visible to AppleScript.&lt;/p&gt;
&lt;p&gt;This doesn’t tackle the problem of the Finder refusing to eject a drive
containing opened files, even if those files are opened by (from the
user’s perspective) unimportant background commands. I spent a chunk of
the morning mulling this over (and looking at &lt;code&gt;lsof&lt;/code&gt;’s inscrutable man
page!), and think I might try to run the output of &lt;code&gt;lsof -F&lt;/code&gt; through AWK
to enable a prompt-to-terminate interface. This is particularly
important for me as I keep my music and photo libraries on an external
drive; the photo library in particular is often opened by background
system tasks, I think as iCloud syncs across new photos from my phone.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>How to wrap bc to do calculations</title>
      <link>https://www.robjwells.com/2021/02/how-to-wrap-bc-to-do-calculations/</link>
      <pubDate>Tue, 23 Feb 2021 18:14:00 +0000</pubDate>
      
      <guid>https://www.robjwells.com/2021/02/how-to-wrap-bc-to-do-calculations/</guid>
      <description>&lt;aside class=&#34;flag&#34;&gt;
    &lt;p&gt;This was &lt;a href=&#34;https://github.com/robjwells/til/blob/main/shell/202102230701%20How%20to%20wrap%20bc%20to%20do%20calculations.md&#34;&gt;original published as a TIL&lt;/a&gt; but I thought it was interesting enough to post on its own.&lt;/p&gt;
    &lt;p&gt;Funnily enough, the planets seem to have aligned and &lt;a href=&#34;https://leancrew.com/all-this/2021/02/some-bc-stuff/&#34;&gt;Dr Drang has also written about &lt;code&gt;bc&lt;/code&gt; today&lt;/a&gt;.&lt;/p&gt;
&lt;/aside&gt;
&lt;hr&gt;
&lt;p&gt;I find &lt;a href=&#34;https://www.gnu.org/software/bc/&#34;&gt;&lt;code&gt;bc&lt;/code&gt;&lt;/a&gt; trips me up when I try to calculations at the shell, so I wrapped it.&lt;/p&gt;
&lt;p&gt;There are two hazards for me:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Failure to handle expressions without a final newline.&lt;/li&gt;
&lt;li&gt;Integer division.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;final-newline-missing&#34;&gt;Final newline missing&lt;/h3&gt;
&lt;p&gt;No 1 often occurs when I produce a list of numbers in &lt;a href=&#34;https://www.barebones.com/products/bbedit/&#34;&gt;BBEdit&lt;/a&gt;, usually extracted and transformed via regexes.&lt;/p&gt;
&lt;p&gt;For example, we get a PDF receipt from Sainbury’s for our online shopping, and to work out my share I like to edit the text from &lt;a href=&#34;https://linux.die.net/man/1/pdftotext&#34;&gt;&lt;code&gt;pdftotext&lt;/code&gt;&lt;/a&gt; in BBEdit, then run the line totals through a pipeline to calculate the total.&lt;/p&gt;
&lt;p&gt;That used to look like this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ pbpaste &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; paste -s -d &lt;span class=&#34;s1&#34;&gt;&amp;#39;+&amp;#39;&lt;/span&gt; - &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; bc
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;m&#34;&gt;609&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;pbpaste&lt;/code&gt; pastes the macOS system clipboard, &lt;a href=&#34;https://linux.die.net/man/1/paste&#34;&gt;&lt;code&gt;paste&lt;/code&gt;&lt;/a&gt; joins those lines with &lt;code&gt;+&lt;/code&gt;, and &lt;code&gt;bc&lt;/code&gt; (should!) print the total.&lt;/p&gt;
&lt;p&gt;Except this happens if you’re missing a final newline:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ pbpaste &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; paste -s -d &lt;span class=&#34;s1&#34;&gt;&amp;#39;+&amp;#39;&lt;/span&gt; - &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; bc
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;standard_in&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; 1: parse error
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;integer-division&#34;&gt;Integer division&lt;/h2&gt;
&lt;p&gt;And secondly, with the default settings, &lt;code&gt;bc&lt;/code&gt; does integer division:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ bc &lt;span class=&#34;o&#34;&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt; 9/2
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;m&#34;&gt;4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Which can be fixed by setting &lt;code&gt;scale&lt;/code&gt;&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ bc &lt;span class=&#34;o&#34;&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;scale=2; 9/2&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;4.50
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;(&lt;code&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/code&gt; starts a “here-string”, which you can learn more about at &lt;code&gt;man -P &amp;quot;less -p &#39;&amp;lt;&amp;lt;&amp;lt;&#39;&amp;quot; zshmisc&lt;/code&gt;, which will take you directly to the right part of the &lt;code&gt;zshmisc&lt;/code&gt; man page. Search the &lt;code&gt;bash&lt;/code&gt; man page if you’re using &lt;code&gt;bash&lt;/code&gt;, but it’s significantly more terse than the &lt;code&gt;zsh&lt;/code&gt; explanation.)&lt;/p&gt;
&lt;h3 id=&#34;wrapping-bc&#34;&gt;Wrapping &lt;code&gt;bc&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;So I wrote a simple wrapper script, &lt;code&gt;calc&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;#!/usr/local/bin/zsh
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cp&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;set&lt;/span&gt; -euo pipefail
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Read expression from $1 or stdin.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;[&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;$#&lt;/span&gt; -ge &lt;span class=&#34;m&#34;&gt;1&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nv&#34;&gt;expression&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nb&#34;&gt;read&lt;/span&gt; expression &lt;span class=&#34;o&#34;&gt;||&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;true&lt;/span&gt;  &lt;span class=&#34;c1&#34;&gt;# Force successful exit&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# Default scale to 3 (.123) if $2 is not given.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;bc &lt;span class=&#34;o&#34;&gt;&amp;lt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;scale=&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;:-&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;3&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;; &lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;$expression&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The conditional in the middle just handles being called in a pipe, with the expression coming on standard input, or having the expression provided as the first argument. The &lt;code&gt;|| true&lt;/code&gt; is necessary after &lt;code&gt;read&lt;/code&gt; as &lt;a href=&#34;https://stackoverflow.com/questions/40547032/bash-read-returns-with-exit-code-1-even-though-it-runs-as-expected&#34;&gt;&lt;code&gt;read&lt;/code&gt; only exits with a success code if it encounters EOF&lt;/a&gt;, and since I have &lt;code&gt;-e&lt;/code&gt; set in the file, that would cause the whole script to exit.&lt;/p&gt;
&lt;p&gt;I set the scale to 3 by default (showing 3 decimal places), but you can configure that with the second argument.&lt;/p&gt;
&lt;p&gt;Using a here-string for &lt;code&gt;bc&lt;/code&gt;’s input means that it should work whether or not there is a final newline.&lt;/p&gt;
&lt;p&gt;The use of &lt;code&gt;read&lt;/code&gt; also allows the use of &lt;code&gt;calc&lt;/code&gt; by itself to print the result of one expression, for example:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ calc
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;2&lt;/span&gt; + 2&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt; / &lt;span class=&#34;o&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;10&lt;/span&gt; / 2&lt;span class=&#34;o&#34;&gt;)&lt;/span&gt;↩
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;.800
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;wrapping-summation&#34;&gt;Wrapping summation&lt;/h3&gt;
&lt;p&gt;I use the “sum this list of numbers” pipeline fairly often, and it’s easy to pull out into its own command. I’m just using an alias, which I’ve added to my &lt;code&gt;.zshrc&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-sh&#34; data-lang=&#34;sh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;alias&lt;/span&gt; ∑&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;paste -s -d &amp;#39;+&amp;#39; - | calc&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;a href=&#34;https://linux.die.net/man/1/sum&#34;&gt;&lt;code&gt;sum&lt;/code&gt;&lt;/a&gt; is already taken, so &lt;a href=&#34;https://en.wikipedia.org/wiki/Summation#Capital-sigma_notation&#34;&gt;∑&lt;/a&gt; seemed appropriate. On macOS you can type it with ⌥w.&lt;/p&gt;
&lt;p&gt;So my pipeline from before just becomes &lt;code&gt;pbpaste | ∑&lt;/code&gt;.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Give Python’s static typing a go</title>
      <link>https://www.robjwells.com/2020/07/give-pythons-static-typing-a-go/</link>
      <pubDate>Wed, 08 Jul 2020 06:00:00 +0100</pubDate>
      
      <guid>https://www.robjwells.com/2020/07/give-pythons-static-typing-a-go/</guid>
      <description>&lt;p&gt;While my go-to language is still Python, at &lt;a href=&#34;https://www.dcs.bbk.ac.uk/&#34;&gt;university&lt;/a&gt; I’ve also used Java and C#.
I have to say, there was a huge amount to like — much to my surprise initially!&lt;/p&gt;
&lt;p&gt;Previously I’d kind of written off Java as being a &lt;a href=&#34;https://github.com/EnterpriseQualityCoding/FizzBuzzEnterpriseEdition&#34;&gt;boring language for boring businesspeople&lt;/a&gt;.
There are certainly elements of that (&lt;code&gt;package com.robjwells.MyPackage;&lt;/code&gt; in &lt;code&gt;src/main/java/com/robjwells/MyPackage.java&lt;/code&gt;) but there’s a lot to like.
Streams are great, lambdas are great, the standard library is great (though not without its rough edges, such as the repeated attempts at date and time), and the tooling is great (I have actually come round to really like &lt;a href=&#34;https://www.jetbrains.com/idea/&#34;&gt;IntelliJ IDEA&lt;/a&gt; — certainly buying a full licence when my academic licence expires).
Plus, it’s really coming along at a clip now with the shorter release cycle.&lt;/p&gt;
&lt;p&gt;And C# was an even more pleasant surprise. It’s easy to get the impression that it’s “Microsoft’s Java”, but that’s really selling it short.
&lt;a href=&#34;https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/linq/&#34;&gt;LINQ&lt;/a&gt; is a joy that makes &lt;a href=&#34;https://www.youtube.com/watch?v=ei71YpmfRX4&#34;&gt;comprehensions in Python&lt;/a&gt; (of which I am a huge fan!) seem… just a bit clunky. But C# is full of features, not just LINQ, that make working in it a real pleasure. (I will say, though, that at least on the Mac &lt;a href=&#34;https://www.jetbrains.com/rider/&#34;&gt;Rider&lt;/a&gt; is a far superior editor than Visual Studio.)&lt;/p&gt;
&lt;p&gt;All of this is to say, in a round-about way, that those &lt;em&gt;weird verbose enterprise-y languages&lt;/em&gt; have a lot going for them — even in the still small-scale things that I’m working on where without this exposure I would just use Python.&lt;/p&gt;
&lt;p&gt;Now, I still &lt;em&gt;am&lt;/em&gt; using Python, but there are things that I miss.
Chief among them in Python is having the type system actively help me out. &lt;a href=&#34;https://www.executeprogram.com/blog/porting-to-typescript-solved-our-api-woes&#34;&gt;Gary Bernhardt writes a bit about this in the context of Ruby and TypeScript&lt;/a&gt;. (Be warned there are trivial examples ahead.)&lt;/p&gt;
&lt;p&gt;Now, I’m using &lt;a href=&#34;https://code.visualstudio.com/&#34;&gt;VSCode&lt;/a&gt; so actually the editor will step in and help you even if you do nothing to aid it. For instance, in the following situation:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# untyped.py v1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;ultimate_answer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;42&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;main&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;u&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;ultimate_answer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;VSCode (using the &lt;a href=&#34;https://marketplace.visualstudio.com/items?itemName=ms-python.python&#34;&gt;Python extension&lt;/a&gt;) will correctly infer that the type of &lt;code&gt;u&lt;/code&gt; is &lt;code&gt;int&lt;/code&gt;. So let’s do something with that:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# untyped.py v2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;ultimate_answer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;42&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;do_something&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;n&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;n&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;11&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;main&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;u&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;do_something&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ultimate_answer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;())&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;VSCode still correctly infers &lt;code&gt;u&lt;/code&gt; to be an &lt;code&gt;int&lt;/code&gt;. Great! But let’s be clear: this is VSCode doing the work so that it can offer you handy things like code completion.&lt;/p&gt;
&lt;p&gt;What happens if we have a change in requirements and we change our API… only we don’t catch everything so we end up with the following:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# untyped.py v3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;ultimate_answer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;42&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;do_something&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;n&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;n&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;11&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;main&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;u&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;do_something&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ultimate_answer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;())&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;At this point, VSCode gives up: &lt;code&gt;u&lt;/code&gt; is an &lt;code&gt;int&lt;/code&gt; or it’s a &lt;code&gt;str&lt;/code&gt;. In fact it’s neither, because &lt;code&gt;do_something()&lt;/code&gt; raises a &lt;code&gt;TypeError&lt;/code&gt; so &lt;code&gt;u&lt;/code&gt; is never assigned. This is “obvious” to a human reading the code, in this simple example, but it’s easy to imagine a complex system where the types get out of line but the definitions are far apart from each other and the eventual call site.&lt;/p&gt;
&lt;p&gt;There are type checkers for Python, the main one being &lt;a href=&#34;http://www.mypy-lang.org/&#34;&gt;mypy&lt;/a&gt; (which is great!). Can mypy help us here?&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ mypy untyped.py
Success: no issues found in 1 source file
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Oh, success! Great.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;$ python3 untyped.py
Traceback (most recent call last):
  File &amp;#34;untyped.py&amp;#34;, line 10, in &amp;lt;module&amp;gt;
    main()
  File &amp;#34;untyped.py&amp;#34;, line 8, in main
    u = do_something(ultimate_answer())
  File &amp;#34;untyped.py&amp;#34;, line 5, in do_something
    return n - 11
TypeError: unsupported operand type(s) for -: &amp;#39;str&amp;#39; and &amp;#39;int&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Ah, no, no magic was performed and we still have a &lt;code&gt;TypeError&lt;/code&gt;. It’s worth pointing out here that mypy is all about gradual typing — adding type annotations to your programs as and when. If there are no annotations, there are no checks performed. It’s not clairvoyant.&lt;/p&gt;
&lt;p&gt;If we switch back to VSCode, what if we try &lt;a href=&#34;https://devblogs.microsoft.com/python/announcing-pylance-fast-feature-rich-language-support-for-python-in-visual-studio-code/&#34;&gt;Microsoft’s shiny new Pylance extension&lt;/a&gt;? In the basic type-checking mode it reports that the type of &lt;code&gt;u&lt;/code&gt; is unknown which … is a step in the right direction? But no warnings.&lt;/p&gt;
&lt;p&gt;If we ratchet up the type-checking mode to strict it reports, with a bunch of red error squiggles, that the return type of &lt;code&gt;do_something()&lt;/code&gt; is unknown and the type of &lt;code&gt;u&lt;/code&gt; is unknown. We get a similar result if we pass the &lt;code&gt;--strict&lt;/code&gt; flag to mypy, which essentially tells the type checker “forget about this gradual business” and attempts to check the whole file. This effectively fails, because we’ve done nothing to help it. Let’s do that now.&lt;/p&gt;
&lt;p&gt;In fact, very little is needed before Pylance starts to push you in the right direction, only this:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# typed.py v1 excerpt&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;do_something&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;n&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;n&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;11&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;After which we’re rewarded with red squiggles underneath the call to &lt;code&gt;ultimate_answer()&lt;/code&gt; that provides the argument inline to &lt;code&gt;do_something()&lt;/code&gt;. The message we get is interesting because it reveals something about the knowledge of the type-checker:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;Argument of type &amp;#34;Literal[&amp;#39;42&amp;#39;]&amp;#34; cannot be assigned to
parameter &amp;#34;n&amp;#34; of type &amp;#34;int&amp;#34; in function &amp;#34;do_something&amp;#34;
  &amp;#34;Literal[&amp;#39;42&amp;#39;]&amp;#34; is incompatible with &amp;#34;int&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We haven’t typed &lt;code&gt;ultimate_answer()&lt;/code&gt;, but it knows that &amp;ldquo;42&amp;rdquo; can’t be treated as an &lt;code&gt;int&lt;/code&gt;. Mypy needs a little more help to get there:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# typed.py v2 excerpt&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;ultimate_answer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;str&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;42&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;do_something&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;n&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;n&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;11&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So, at this point we’ve said that &lt;code&gt;ultimate_answer()&lt;/code&gt; returns a string and that &lt;code&gt;do_something()&lt;/code&gt; takes an integer. What does mypy think?&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;&amp;gt; $ mypy typed.py
Success: no issues found in 1 source file

&amp;gt; $ mypy --strict typed.py
[…snip…]
typed.py:8: error: Argument 1 to &amp;#34;do_something&amp;#34; has
    incompatible type &amp;#34;str&amp;#34;; expected &amp;#34;int&amp;#34;
[…snip…]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is an interesting situation because &amp;ldquo;normal&amp;rdquo; mode mypy reports that this is fine, no problems here. Strict mode complains — among other things — that the types don’t match. Finally, this is what we want.&lt;/p&gt;
&lt;p&gt;But why doesn’t “normal” mode mypy not see the problem? I think this is to do with what is considered a “typed context”. By using &lt;code&gt;--strict&lt;/code&gt; we force everything to be a typed context, so we get a lot more warnings and errors from mypy. But without this, &lt;code&gt;main()&lt;/code&gt; is not a typed context — it has no typed arguments, and no explicit return type, so “normal” mode mypy just skips over it.&lt;/p&gt;
&lt;p&gt;The strength of gradual typing is that if you don’t want to or aren’t ready to add type information, you don’t. But even in this toy example, the standard Python type-checker under its default settings does not pick up this “obvious” (to us!) type error. In &lt;a href=&#34;https://www.youtube.com/watch?v=ST33zDM9vOE&amp;amp;feature=youtu.be&#34;&gt;Dustin Ingram’s Pycon talk about static typing&lt;/a&gt; he says you should use static typing everywhere — for a few reasons, but here we can see that failing to do so leaves a clear error undetected..&lt;/p&gt;
&lt;p&gt;It doesn’t take much to rectify that for mypy, just a return type on &lt;code&gt;main()&lt;/code&gt;, yielding the following:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;# typed.py v3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;ultimate_answer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;str&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;42&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;do_something&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;n&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;int&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;n&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;11&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;main&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;None&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;n&#34;&gt;u&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;do_something&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ultimate_answer&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;())&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And now &lt;code&gt;mypy typed.py&lt;/code&gt; gives the same error that strict mode did for the previous example. Adding the explicit return type to &lt;code&gt;main()&lt;/code&gt; is honestly pretty useless, but now it opens up the definition of &lt;code&gt;main()&lt;/code&gt; to be type checked, at which point the error is spotted.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;I wrote the title of this blog post before I really knew where I was going (I have a Beeminder deadline to hit!) so at this point it feels to me like I haven’t quite delivered on (why you should) “Give Python’s static typing a go”. Really we’re at “If you decide to use Python’s static typing you need to go all-in.” Which actually is something I do believe! I think the strictest settings are the most useful, but leaving something untyped leaves a hole for type errors to sneak through.&lt;/p&gt;
&lt;p&gt;But it is useful in itself. It’s unfamiliar and, honestly, a bit clunky in Python. (The dance for declaring a &lt;code&gt;TypeVar&lt;/code&gt; for a generic function taking some type &lt;code&gt;T&lt;/code&gt; is … Not Good and looks worse once you run your code through a formatter, with it then two lines away.)&lt;/p&gt;
&lt;p&gt;But thinking about types is thinking about design, and thinking about the contract that you’re willing to offer to the outside world. I’ve found that in Java and C# sometimes I’m ready to bound straight into defining a function … only to stop after realising that I haven’t really clarified what expectations I have of the outside world (parameter types) and what expectations the outside world has of me (return type).&lt;/p&gt;
&lt;p&gt;Here’s a dead-obvious example from a simple exercise on &lt;a href=&#34;https://exercism.io/&#34;&gt;Exercism&lt;/a&gt; (sorry for any spoilers but I should hope this one is straightforward to anyone with any knowledge of the &lt;code&gt;datetime&lt;/code&gt; module!):&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kn&#34;&gt;from&lt;/span&gt; &lt;span class=&#34;nn&#34;&gt;datetime&lt;/span&gt; &lt;span class=&#34;kn&#34;&gt;import&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;datetime&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;timedelta&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;GIGASECOND&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;timedelta&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;seconds&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1_000_000_000&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;add&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;moment&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;datetime&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;datetime&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;moment&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;GIGASECOND&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now, &lt;code&gt;add()&lt;/code&gt; is a bad name in general but fine in this confined case, but it’s the simple addition of the &lt;code&gt;datetime&lt;/code&gt; annotations that make it clear what we’re handling here. You give a datetime, receive a datetime. Nothing fancy, but compare with the following signature:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;add&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;moment&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;):&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It’s concise, sure, but is the cost in understanding worth it? Explicitly annotating the types forces you to consider what the interface is and, in return, tools like mypy will give you a hand in finding bugs.&lt;/p&gt;
&lt;p&gt;Anyway, give &lt;a href=&#34;https://www.youtube.com/watch?v=ST33zDM9vOE&amp;amp;feature=youtu.be&#34;&gt;Dustin Ingram’s talk&lt;/a&gt; a watch (check out those t-shirts!) as it’s informative, straightforward and short. Do check out the &lt;a href=&#34;https://mypy.readthedocs.io/en/stable/index.html&#34;&gt;mypy documentation&lt;/a&gt;, as there’s plenty of descriptive information in there beyond the interface to the command line tool and the &lt;code&gt;typing&lt;/code&gt; module. &lt;a href=&#34;https://www.youtube.com/watch?v=7ZbwZgrXnwY&#34;&gt;Jukka Lehtosalo and David Fisher spoke in some detail at Pycon 2017 about mypy&lt;/a&gt;, and I highly recommend &lt;a href=&#34;https://dropbox.tech/application/our-journey-to-type-checking-4-million-lines-of-python&#34;&gt;Jukka’s article on the Dropbox tech blog that looks at the history and practical aspects of mypy&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It’s clear also that this is the direction of travel for Python — there is a lot on the horizon that will make life easier (see &lt;a href=&#34;https://www.python.org/dev/peps/&#34;&gt;the list of PEPs&lt;/a&gt;), and I’m particularly looking forward to seeing what becomes of &lt;a href=&#34;https://www.python.org/dev/peps/pep-0622/&#34;&gt;PEP 622&lt;/a&gt; as at the moment it looks like it will bring with it &lt;a href=&#34;https://fsharpforfunandprofit.com/posts/discriminated-unions/&#34;&gt;sum types&lt;/a&gt;, even if it is a year or two off at this point.&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Scheduling posts in Hugo</title>
      <link>https://www.robjwells.com/2020/06/scheduling-posts-in-hugo/</link>
      <pubDate>Sat, 06 Jun 2020 06:00:00 +0100</pubDate>
      
      <guid>https://www.robjwells.com/2020/06/scheduling-posts-in-hugo/</guid>
      <description>&lt;p&gt;My enthusiasm for writing posts here tends to come in reasonably short bursts, usually over a weekend, so I’ve taken to scheduling the two or three posts I might write over the following weeks.&lt;/p&gt;
&lt;p&gt;It’s reasonably straightforward to schedule publication with &lt;a href=&#34;https://gohugo.io/&#34;&gt;Hugo&lt;/a&gt; by using the &lt;code&gt;publishDate&lt;/code&gt; attribute in your post frontmatter. For instance, this is the frontmatter for this post right now:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;title&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;Scheduling posts in Hugo&amp;#34;&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;date&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;ld&#34;&gt;2020-05-24T09:25:44&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;+01&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;00&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;publishDate&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;ld&#34;&gt;2020-06-06T06:00:00&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;+01&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;m&#34;&gt;00&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;draft&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;date&lt;/code&gt; attribute is filled in by Hugo when I create the post bundle skeleton. I tend to leave this as a marker for when I started writing a post (though I have changed it for posts that I start, leave for a while, and &lt;a href=&#34;https://www.robjwells.com/2020/05/keyboard-maestro-macro-to-insert-images-into-blog-posts/&#34;&gt;finish later&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;publishDate&lt;/code&gt; attribute controls when the post is actually published. Hugo by default doesn’t build posts with this set in the future.&lt;/p&gt;
&lt;p&gt;One important change that I made from the defaults, though, is to define the handling of dates in my site-wide config file (&lt;code&gt;config.toml&lt;/code&gt;) like so:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-toml&#34; data-lang=&#34;toml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;frontmatter&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;date&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;publishDate&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;date&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;:default&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;What this means is that Hugo will prefer the &lt;code&gt;publishDate&lt;/code&gt; as the date of the post, before falling back to the &lt;code&gt;date&lt;/code&gt; attribute, and then resuming its default lookup, which is listed &lt;a href=&#34;https://gohugo.io/getting-started/configuration/#configure-dates&#34;&gt;in the Hugo documentation&lt;/a&gt;.
(At the moment, the only other thing in the default lookup order is the file modification time, but mostly I include &amp;ldquo;:default&amp;rdquo; to be safe if this changes in the future.)&lt;/p&gt;
&lt;p&gt;Otherwise, you might end up with a situation where you write “Post Future”, set a publish date in the future, then write “Post Now” and publish immediately, and when “Post Future” is published it will be shown as being published earlier than “Post Now” because its &lt;code&gt;date&lt;/code&gt; is earlier. Changing the date lookup order in the config will preserve your deliberate schedule.&lt;/p&gt;
&lt;p&gt;(Thanks to “n m” on StackOverflow who got me started &lt;a href=&#34;https://stackoverflow.com/questions/59655470/hugo-date-vs-publishdate/59760977#59760977&#34;&gt;with this answer&lt;/a&gt;.)&lt;/p&gt;
&lt;p&gt;Obviously, just having a bunch of files with the dates set properly doesn’t mean your post will actually be published automatically at the right time.
I have a script on my server that cron runs every 15 minutes that pulls from &lt;a href=&#34;https://github.com/robjwells/primaryunit&#34;&gt;the GitHub repository&lt;/a&gt; and rebuilds the site.&lt;/p&gt;
&lt;p&gt;This set-up is made easy thanks to Hugo being a single binary, so it’s simple to install on the server, whereas &lt;a href=&#34;https://github.com/robjwells/majestic/&#34;&gt;before&lt;/a&gt; I would generally build the site locally and upload it to my server with rsync.&lt;/p&gt;
&lt;p&gt;Hugo can list posts that are scheduled in the future:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-zsh&#34; data-lang=&#34;zsh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ hugo list future
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;content/posts/2020/05/24/scheduling-posts-in-hugo/index.md,2020-06-06T06:00:00+01:00
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;content/posts/2020/05/24/keyboard-maestro-macro-to-insert-images-into-blog-posts/index.md,2020-05-30T06:00:00+01:00
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is a bit noisy, so here’s a sed one-liner to improve things slightly:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-zsh&#34; data-lang=&#34;zsh&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ hugo list future &lt;span class=&#34;p&#34;&gt;|&lt;/span&gt; sed -E &lt;span class=&#34;s1&#34;&gt;&amp;#39;s/^.+\/([^\/]+)\/index.md,(.+)/\2 - \1/&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2020-06-06T06:00:00+01:00 - scheduling-posts-in-hugo
&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2020-05-30T06:00:00+01:00 - keyboard-maestro-macro-to-insert-images-into-blog-posts
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;(This assumes that you’re using &lt;a href=&#34;https://gohugo.io/content-management/page-bundles/&#34;&gt;page bundles&lt;/a&gt;.)&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;I actually now have a &lt;a href=&#34;https://www.beeminder.com/robjwells/blog&#34;&gt;Beeminder goal&lt;/a&gt; to ensure that I write a post a month — well, I was being lenient with myself so it’s every 31 days. It’s also set for a maximum 31 safe days, so I feel that scheduling once a week is enough to tamp down any short-term blog-mania while also not gaming the Beeminder goal too much by scheduling posts at 31-day intervals. (Perhaps this is a sign though that I can set the goal to have a shorter period, but at the moment I have enough commitments that I don’t feel comfortable doing so.)&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>Keyboard Maestro macro to insert images into blog posts</title>
      <link>https://www.robjwells.com/2020/05/keyboard-maestro-macro-to-insert-images-into-blog-posts/</link>
      <pubDate>Sat, 30 May 2020 06:00:00 +0100</pubDate>
      
      <guid>https://www.robjwells.com/2020/05/keyboard-maestro-macro-to-insert-images-into-blog-posts/</guid>
      <description>&lt;p&gt;Here’s a quick Keyboard Maestro macro to make it easier to insert images into blog posts, or any other markdown or HTML document really. The details of the macro are set up to create a &lt;a href=&#34;https://gohugo.io/content-management/shortcodes/#figure&#34;&gt;Hugo figure shortcode&lt;/a&gt;, but the Hugo-specific bits are just scaffolding and could be swapped out for whatever you need.&lt;/p&gt;
&lt;p&gt;You can &lt;a href=&#34;insert-hugo-figure.kmmacros&#34;&gt;download the macro file here&lt;/a&gt;, but the whole thing ended up being a bit long so I’m not going to include the usual image of the whole macro (which is 1,965 pixels tall). Let’s step through it.&lt;/p&gt;
&lt;figure class=&#34;full-width no-border&#34;&gt;&lt;a href=&#34;macro-1-prompt-and-read.png&#34;&gt;&lt;img src=&#34;macro-1-prompt-and-read.png&#34;
         alt=&#34;A screenshot showing a portion of a Keyboard Maestro macro, prompting the user for a file and then reading it.&#34; width=&#34;551&#34; height=&#34;275&#34;/&gt;&lt;/a&gt;&lt;figcaption&gt;
            &lt;p&gt;The first stage of the macro, prompting for the image file and then loading it onto a named clipboard.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;After selecting the image, we need to load it onto a named clipboard because Keyboard Maestro’s image actions generally work on the contents of a clipboard.&lt;/p&gt;
&lt;figure class=&#34;full-width no-border&#34;&gt;&lt;a href=&#34;macro-2-store-properties.png&#34;&gt;&lt;img src=&#34;macro-2-store-properties.png&#34;
         alt=&#34;A screenshot showing a portion of a Keyboard Maestro macro, reading image properties into variables.&#34; width=&#34;551&#34; height=&#34;312&#34;/&gt;&lt;/a&gt;&lt;figcaption&gt;
            &lt;p&gt;Next we extract needed properties from the image into Keyboard Maestro variables.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Then we need to prompt the user to confirm the attributes of the figure.&lt;/p&gt;
&lt;figure class=&#34;full-width no-border&#34;&gt;&lt;a href=&#34;macro-3-prompt-attributes.png&#34;&gt;&lt;img src=&#34;macro-3-prompt-attributes.png&#34;
         alt=&#34;A screenshot showing a portion of a Keyboard Maestro macro, of a prompt to the user to confirm attributes for the figure to be inserted.&#34; width=&#34;551&#34; height=&#34;377&#34;/&gt;&lt;/a&gt;&lt;figcaption&gt;
            &lt;p&gt;The prompt set-up.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&#34;full-width no-border&#34;&gt;&lt;a href=&#34;macro-prompt.png&#34;&gt;&lt;img src=&#34;macro-prompt.png&#34;
         alt=&#34;A screenshot showing a Keyboard Maestro prompt asking for attributes to complete an HTML figure&#34; width=&#34;537&#34; height=&#34;267&#34;/&gt;&lt;/a&gt;&lt;figcaption&gt;
            &lt;p&gt;And the prompt itself.&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Any of these can be empty, so after assembling the shortcode text blank attributes are removed:&lt;/p&gt;
&lt;figure class=&#34;full-width no-border&#34;&gt;&lt;a href=&#34;macro-4-assemble-figure.png&#34;&gt;&lt;img src=&#34;macro-4-assemble-figure.png&#34;
         alt=&#34;A screenshot showing a portion of a Keyboard Maestro macro, creating the figure shortcode from provided attributes and using a regular expression to remove any empty attributes.&#34; width=&#34;551&#34; height=&#34;631&#34;/&gt;&lt;/a&gt;
&lt;/figure&gt;
&lt;p&gt;And then lastly the figure shortcode text is inserted by pasting, which is handy because it end up on the clipboard if anything goes wrong — like it did when I changed the focus when inserting the previous screenshot!&lt;/p&gt;
</description>
    </item>
    
    <item>
      <title>The Atom: A Love Affair</title>
      <link>https://www.robjwells.com/2020/05/the-atom-a-love-affair/</link>
      <pubDate>Sun, 24 May 2020 08:15:00 +0100</pubDate>
      
      <guid>https://www.robjwells.com/2020/05/the-atom-a-love-affair/</guid>
      <description>&lt;p&gt;I watched &lt;a href=&#34;https://theatomfilm.com/&#34;&gt;The Atom: A Love Affair&lt;/a&gt; last night, a new documentary about the history of nuclear power, and it’s really great. It charts the ups and downs of nuclear power, its promoters and opponents, and looks at where we are today. I think its great strength is that it sets out the surrounding context. It doesn’t take a side but gives a fair accounting (which &lt;a href=&#34;https://cnduk.org/campaigns/no-nuclear-power/&#34;&gt;I think leads to a clear conclusion&lt;/a&gt;, though I would, wouldn’t I?).&lt;/p&gt;
&lt;p&gt;You can &lt;a href=&#34;https://www.curzonhomecinema.com/film/watch-atom-a-love-affair-online&#34;&gt;stream it online at Curzon Home Cinema&lt;/a&gt;, and I highly recommend it.&lt;/p&gt;
&lt;p&gt;There’s &lt;a href=&#34;https://www.youtube.com/watch?v=RqUnaEi9UgM&#34;&gt;a Q&amp;amp;A session held after the film’s premiere&lt;/a&gt;, and more info on &lt;a href=&#34;https://www.facebook.com/theatomfilm&#34;&gt;the Facebook page&lt;/a&gt;. There’s also &lt;a href=&#34;https://www.bbc.co.uk/sounds/play/m000jg85&#34;&gt;an episode of Radio 3’s Sound of Cinema with the film’s composer Paul Honey&lt;/a&gt;.&lt;/p&gt;
</description>
    </item>
    
  </channel>
</rss>
