<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://davemq.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://davemq.github.io/" rel="alternate" type="text/html" /><updated>2026-02-24T20:22:54+00:00</updated><id>https://davemq.github.io/feed.xml</id><title type="html">Dave’s Blog</title><subtitle>Dave&apos;s blog</subtitle><entry><title type="html">Calculating RAGBRAI training actual vs. planned mileage</title><link href="https://davemq.github.io/2026/02/24/ragbrai-training-actuals-vs-plan.html" rel="alternate" type="text/html" title="Calculating RAGBRAI training actual vs. planned mileage" /><published>2026-02-24T00:00:00+00:00</published><updated>2026-02-24T00:00:00+00:00</updated><id>https://davemq.github.io/2026/02/24/ragbrai-training-actuals-vs-plan</id><content type="html" xml:base="https://davemq.github.io/2026/02/24/ragbrai-training-actuals-vs-plan.html"><![CDATA[<p>I’m training to ride <a href="https://ragbrai.com/">RAGBRAI</a> LIII in Iowa in July 2026. RAGBRAI
provides a <a href="https://ragbrai.com/2026-training-plan/">training plan</a> which, if followed, helps riders get ready
for the ride.</p>

<p>Being an Emacs Org geek, I grabbed the provided <a href="https://ragbrai.com/wp-content/uploads/2026/02/RAGBRAI_Training_Plan_2026.xlsx">Excel spreadsheet,</a>
exported it as a CSV (comma separated values) file, then converted
that to an Org table.</p>

<p>The spreadsheet shows two lines per week, one with the planned
mileages, and another to record the actual miles ridden. There’s a
total mileage in column 6, which is filled in for the planned rides,
and you fill in yourself for the actuals.</p>

<p>The first few lines of the spreadsheet look like</p>

<figure class="highlight"><pre><code class="language-org" data-lang="org">|----------------------+-----------------------------+-------------------+------------------+------------------+------------|
|                      | 2026 RAGBRAI® Training Plan |                   |                  |                  |            |
|----------------------+-----------------------------+-------------------+------------------+------------------+------------|
| Week of:             | Weekday 1                   | Weekday 2         | Saturday         | Sunday           | Week Total |
|----------------------+-----------------------------+-------------------+------------------+------------------+------------|
| February 9           | 5 miles                     | 5 miles           | 10 miles         | -                | 20 miles   |
|----------------------+-----------------------------+-------------------+------------------+------------------+------------|
| Actual Ridden        |                             |                   |                  |                  |            |
|----------------------+-----------------------------+-------------------+------------------+------------------+------------|
| February 16          | 10 miles                    | 10 miles          | 10 miles         | -                | 30 miles   |
|----------------------+-----------------------------+-------------------+------------------+------------------+------------|
| Actual Ridden        |                             |                   |                  |                  |            |
|----------------------+-----------------------------+-------------------+------------------+------------------+------------|</code></pre></figure>

<p>I wanted to do two things:</p>

<ul>
  <li>make column 6 the sum of columns 2 through 5, which include two
weekday and two weekend rides</li>
  <li>create a column 7 that’s a percentage of the actual vs. planned
mileage</li>
</ul>

<p>Org tables let you specify formulas for cells, columns, and rows.
Getting the sum of columns 2 through 5 for column 6 can be done a
couple of ways:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">$6=$2+$3+$4+$5</code></li>
  <li><code class="language-plaintext highlighter-rouge">$6=vsum($2..$5)</code></li>
</ul>

<p>I used the former for quite a while, until I realized I could use the
latter. Either way, it works.</p>

<p>I had difficulty getting the formula for column 7 working how I wanted
it. Org tables can do formulas using Calc syntax, which is what I used
for column 6, or using Emacs Lisp forms as formulas. I used Calc
syntax to get this working. I wanted to calculate $7 as 100 times the
value of @0$6, the current row’s sum of miles ridden, divided by the
value of the previous row’s sum of planned miles. It’s easy enough to
just do something like <code class="language-plaintext highlighter-rouge">$7=100*@0$6/@-1$6</code>, but the values on
the planned rides rows were 100 times the planned miles for that week
divided by the actual miles for the previous week, which is usually
some number over 100. And not that interesting to me.</p>

<p>I also got rid of all instances of “miles” in the table. I ended up
with weird things like 70.3 miles / miles, and couldn’t figure out how
to get Org to simplify this. Getting rid of “miles” in the spreadsheet
made things look much better.</p>

<p>I wanted to key on the “Actual Ridden” string in $1. If $1 is “Actual
Ridden”, then use the calculation we’ve been discussing. Otherwise,
use 100 or show nothing. I had great difficulty making this work with
Calc syntax.</p>

<p>So, I tried Emacs Lisp forms as formulas. I ended up with</p>

<figure class="highlight"><pre><code class="language-org" data-lang="org">$7='(if (string-equal $1 "Actual Ridden") (format "%.2f" (* 100 (/ (string-to-number @0$6) (string-to-number @-1$6)))) "")</code></pre></figure>

<p>This is a pretty straightforward translation of the percentage
formula, along with using <code class="language-plaintext highlighter-rouge">format</code> to only show a couple
of decimal places. In the case of other lines, just return an empty
string. The final formula works great.</p>]]></content><author><name></name></author><category term="ragbrai" /><category term="bicycling" /><category term="orgmode" /><category term="emacs" /><category term="org-mode" /><category term="org" /><summary type="html"><![CDATA[I’m training to ride RAGBRAI LIII in Iowa in July 2026. RAGBRAI provides a training plan which, if followed, helps riders get ready for the ride.]]></summary></entry><entry><title type="html">A Rofi workspace switcher for Sway</title><link href="https://davemq.github.io/2026/02/24/rofi-workspace-switcher-sway.html" rel="alternate" type="text/html" title="A Rofi workspace switcher for Sway" /><published>2026-02-24T00:00:00+00:00</published><updated>2026-02-24T00:00:00+00:00</updated><id>https://davemq.github.io/2026/02/24/rofi-workspace-switcher-sway</id><content type="html" xml:base="https://davemq.github.io/2026/02/24/rofi-workspace-switcher-sway.html"><![CDATA[<p>I started out on a journey to try to get <code class="language-plaintext highlighter-rouge">sway</code> to create a new
workspace for me, with a “probably” unique name. How could I do that?</p>

<p><code class="language-plaintext highlighter-rouge">sway</code>, like it’s ancestor <code class="language-plaintext highlighter-rouge">i3</code>, has an IPC mechanism that
allows you to query and control <code class="language-plaintext highlighter-rouge">sway</code> from other programs. You
can play with this with <code class="language-plaintext highlighter-rouge">swaymsg</code>. <code class="language-plaintext highlighter-rouge">swaymsg "workspace foo"</code> will switch your current display to show workspace “foo”,
creating “foo” if it doesn’t exist. So one interesting way to achieve
my goal is</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">swaymsg <span class="s2">"workspace </span><span class="si">$(</span>xkcdpass <span class="nt">-n</span> 1<span class="si">)</span><span class="s2">"</span></code></pre></figure>

<p><code class="language-plaintext highlighter-rouge">xkcdpass -n 1</code> prints a single random word from
<code class="language-plaintext highlighter-rouge">xkcdpass</code>’ word file.</p>

<p>One problem with random words is that we don’t typically have nice key
bindings in <code class="language-plaintext highlighter-rouge">sway</code> to switch to these. You can use the mouse and
click on the workspace name on your bar, but if you’d prefer to only
use your keyboard, too bad.</p>

<p>So I thought to use <code class="language-plaintext highlighter-rouge">rofi</code> to show a list of workspaces and let
you select one. <code class="language-plaintext highlighter-rouge">rofi</code> has no builtin mode for this, but it does
have a way to add modes using scripts. Such a script has two modes
itself:</p>

<ul>
  <li>with no arguments, list the items in question. In this case, list
the workspaces</li>
  <li>with one argument, do something with that item. In our case, switch
to that workspace</li>
</ul>

<p><code class="language-plaintext highlighter-rouge">rofi</code> allows you to type a new item, so in our case that would
be another way to switch to a new workspace.</p>

<p>So, how do you get the list of workspaces? Again, the <code class="language-plaintext highlighter-rouge">sway</code> IPC
mechanism. You can run <code class="language-plaintext highlighter-rouge">swaymsg -t get_workspaces</code> to get the
list of workspaces. By default this will pretty print the list of
workspaces. But if you specify <code class="language-plaintext highlighter-rouge">-r</code> or <code class="language-plaintext highlighter-rouge">--raw</code> or pipe
<code class="language-plaintext highlighter-rouge">swaymsg</code> to another program, it outputs JSON. Here’s an
example:</p>

<figure class="highlight"><pre><code class="language-json" data-lang="json"><span class="p">[</span><span class="w">
  </span><span class="p">{</span><span class="w">
    </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="mi">6</span><span class="p">,</span><span class="w">
    </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"workspace"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"orientation"</span><span class="p">:</span><span class="w"> </span><span class="s2">"horizontal"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"percent"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
    </span><span class="nl">"urgent"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
    </span><span class="nl">"marks"</span><span class="p">:</span><span class="w"> </span><span class="p">[],</span><span class="w">
    </span><span class="nl">"layout"</span><span class="p">:</span><span class="w"> </span><span class="s2">"splith"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"border"</span><span class="p">:</span><span class="w"> </span><span class="s2">"none"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"current_border_width"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
    </span><span class="nl">"rect"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"x"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
      </span><span class="nl">"y"</span><span class="p">:</span><span class="w"> </span><span class="mi">30</span><span class="p">,</span><span class="w">
      </span><span class="nl">"width"</span><span class="p">:</span><span class="w"> </span><span class="mi">2560</span><span class="p">,</span><span class="w">
      </span><span class="nl">"height"</span><span class="p">:</span><span class="w"> </span><span class="mi">1410</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"deco_rect"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"x"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
      </span><span class="nl">"y"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
      </span><span class="nl">"width"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
      </span><span class="nl">"height"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"window_rect"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"x"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
      </span><span class="nl">"y"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
      </span><span class="nl">"width"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
      </span><span class="nl">"height"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"geometry"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"x"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
      </span><span class="nl">"y"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
      </span><span class="nl">"width"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
      </span><span class="nl">"height"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"window"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
    </span><span class="nl">"nodes"</span><span class="p">:</span><span class="w"> </span><span class="p">[],</span><span class="w">
    </span><span class="nl">"floating_nodes"</span><span class="p">:</span><span class="w"> </span><span class="p">[],</span><span class="w">
    </span><span class="nl">"focus"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
      </span><span class="mi">17</span><span class="w">
    </span><span class="p">],</span><span class="w">
    </span><span class="nl">"fullscreen_mode"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
    </span><span class="nl">"sticky"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
    </span><span class="nl">"floating"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
    </span><span class="nl">"scratchpad_state"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
    </span><span class="nl">"num"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
    </span><span class="nl">"output"</span><span class="p">:</span><span class="w"> </span><span class="s2">"DP-9"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"representation"</span><span class="p">:</span><span class="w"> </span><span class="s2">"H[H[emacs]]"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"focused"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
    </span><span class="nl">"visible"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="p">{</span><span class="w">
    </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="mi">18</span><span class="p">,</span><span class="w">
    </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"workspace"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"orientation"</span><span class="p">:</span><span class="w"> </span><span class="s2">"horizontal"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"percent"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
    </span><span class="nl">"urgent"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
    </span><span class="nl">"marks"</span><span class="p">:</span><span class="w"> </span><span class="p">[],</span><span class="w">
    </span><span class="nl">"layout"</span><span class="p">:</span><span class="w"> </span><span class="s2">"splith"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"border"</span><span class="p">:</span><span class="w"> </span><span class="s2">"none"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"current_border_width"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
    </span><span class="nl">"rect"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"x"</span><span class="p">:</span><span class="w"> </span><span class="mi">2560</span><span class="p">,</span><span class="w">
      </span><span class="nl">"y"</span><span class="p">:</span><span class="w"> </span><span class="mi">30</span><span class="p">,</span><span class="w">
      </span><span class="nl">"width"</span><span class="p">:</span><span class="w"> </span><span class="mi">1920</span><span class="p">,</span><span class="w">
      </span><span class="nl">"height"</span><span class="p">:</span><span class="w"> </span><span class="mi">1170</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"deco_rect"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"x"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
      </span><span class="nl">"y"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
      </span><span class="nl">"width"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
      </span><span class="nl">"height"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"window_rect"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"x"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
      </span><span class="nl">"y"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
      </span><span class="nl">"width"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
      </span><span class="nl">"height"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"geometry"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"x"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
      </span><span class="nl">"y"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
      </span><span class="nl">"width"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
      </span><span class="nl">"height"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"window"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
    </span><span class="nl">"nodes"</span><span class="p">:</span><span class="w"> </span><span class="p">[],</span><span class="w">
    </span><span class="nl">"floating_nodes"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
      </span><span class="p">{</span><span class="w">
        </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="mi">9</span><span class="p">,</span><span class="w">
        </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"floating_con"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"orientation"</span><span class="p">:</span><span class="w"> </span><span class="s2">"none"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"percent"</span><span class="p">:</span><span class="w"> </span><span class="mf">0.61330662393162383</span><span class="p">,</span><span class="w">
        </span><span class="nl">"urgent"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
        </span><span class="nl">"marks"</span><span class="p">:</span><span class="w"> </span><span class="p">[],</span><span class="w">
        </span><span class="nl">"focused"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
        </span><span class="nl">"layout"</span><span class="p">:</span><span class="w"> </span><span class="s2">"none"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"border"</span><span class="p">:</span><span class="w"> </span><span class="s2">"normal"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"current_border_width"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w">
        </span><span class="nl">"rect"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="nl">"x"</span><span class="p">:</span><span class="w"> </span><span class="mi">2878</span><span class="p">,</span><span class="w">
          </span><span class="nl">"y"</span><span class="p">:</span><span class="w"> </span><span class="mi">97</span><span class="p">,</span><span class="w">
          </span><span class="nl">"width"</span><span class="p">:</span><span class="w"> </span><span class="mi">1284</span><span class="p">,</span><span class="w">
          </span><span class="nl">"height"</span><span class="p">:</span><span class="w"> </span><span class="mi">1046</span><span class="w">
        </span><span class="p">},</span><span class="w">
        </span><span class="nl">"deco_rect"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="nl">"x"</span><span class="p">:</span><span class="w"> </span><span class="mi">318</span><span class="p">,</span><span class="w">
          </span><span class="nl">"y"</span><span class="p">:</span><span class="w"> </span><span class="mi">40</span><span class="p">,</span><span class="w">
          </span><span class="nl">"width"</span><span class="p">:</span><span class="w"> </span><span class="mi">1284</span><span class="p">,</span><span class="w">
          </span><span class="nl">"height"</span><span class="p">:</span><span class="w"> </span><span class="mi">27</span><span class="w">
        </span><span class="p">},</span><span class="w">
        </span><span class="nl">"window_rect"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="nl">"x"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w">
          </span><span class="nl">"y"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
          </span><span class="nl">"width"</span><span class="p">:</span><span class="w"> </span><span class="mi">1280</span><span class="p">,</span><span class="w">
          </span><span class="nl">"height"</span><span class="p">:</span><span class="w"> </span><span class="mi">1044</span><span class="w">
        </span><span class="p">},</span><span class="w">
        </span><span class="nl">"geometry"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="nl">"x"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
          </span><span class="nl">"y"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
          </span><span class="nl">"width"</span><span class="p">:</span><span class="w"> </span><span class="mi">696</span><span class="p">,</span><span class="w">
          </span><span class="nl">"height"</span><span class="p">:</span><span class="w"> </span><span class="mi">486</span><span class="w">
        </span><span class="p">},</span><span class="w">
        </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"foot"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"window"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
        </span><span class="nl">"nodes"</span><span class="p">:</span><span class="w"> </span><span class="p">[],</span><span class="w">
        </span><span class="nl">"floating_nodes"</span><span class="p">:</span><span class="w"> </span><span class="p">[],</span><span class="w">
        </span><span class="nl">"focus"</span><span class="p">:</span><span class="w"> </span><span class="p">[],</span><span class="w">
        </span><span class="nl">"fullscreen_mode"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
        </span><span class="nl">"sticky"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
        </span><span class="nl">"floating"</span><span class="p">:</span><span class="w"> </span><span class="s2">"user_on"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"scratchpad_state"</span><span class="p">:</span><span class="w"> </span><span class="s2">"fresh"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"pid"</span><span class="p">:</span><span class="w"> </span><span class="mi">5998</span><span class="p">,</span><span class="w">
        </span><span class="nl">"app_id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"foot"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"foreign_toplevel_identifier"</span><span class="p">:</span><span class="w"> </span><span class="s2">"0483dba85d6ad4c7b88b28653765ab03"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"visible"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
        </span><span class="nl">"max_render_time"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
        </span><span class="nl">"allow_tearing"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
        </span><span class="nl">"shell"</span><span class="p">:</span><span class="w"> </span><span class="s2">"xdg_shell"</span><span class="p">,</span><span class="w">
        </span><span class="nl">"inhibit_idle"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
        </span><span class="nl">"sandbox_engine"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
        </span><span class="nl">"sandbox_app_id"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
        </span><span class="nl">"sandbox_instance_id"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
        </span><span class="nl">"idle_inhibitors"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
          </span><span class="nl">"user"</span><span class="p">:</span><span class="w"> </span><span class="s2">"none"</span><span class="p">,</span><span class="w">
          </span><span class="nl">"application"</span><span class="p">:</span><span class="w"> </span><span class="s2">"none"</span><span class="w">
        </span><span class="p">}</span><span class="w">
      </span><span class="p">}</span><span class="w">
    </span><span class="p">],</span><span class="w">
    </span><span class="nl">"focus"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
      </span><span class="mi">12</span><span class="p">,</span><span class="w">
      </span><span class="mi">9</span><span class="w">
    </span><span class="p">],</span><span class="w">
    </span><span class="nl">"fullscreen_mode"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
    </span><span class="nl">"sticky"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
    </span><span class="nl">"floating"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
    </span><span class="nl">"scratchpad_state"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
    </span><span class="nl">"num"</span><span class="p">:</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w">
    </span><span class="nl">"output"</span><span class="p">:</span><span class="w"> </span><span class="s2">"DP-8"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"representation"</span><span class="p">:</span><span class="w"> </span><span class="s2">"H[google-chrome]"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"focused"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
    </span><span class="nl">"visible"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
  </span><span class="p">},</span><span class="w">
  </span><span class="p">{</span><span class="w">
    </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="mi">21</span><span class="p">,</span><span class="w">
    </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"workspace"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"orientation"</span><span class="p">:</span><span class="w"> </span><span class="s2">"horizontal"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"percent"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
    </span><span class="nl">"urgent"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
    </span><span class="nl">"marks"</span><span class="p">:</span><span class="w"> </span><span class="p">[],</span><span class="w">
    </span><span class="nl">"layout"</span><span class="p">:</span><span class="w"> </span><span class="s2">"splith"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"border"</span><span class="p">:</span><span class="w"> </span><span class="s2">"none"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"current_border_width"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
    </span><span class="nl">"rect"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"x"</span><span class="p">:</span><span class="w"> </span><span class="mi">2560</span><span class="p">,</span><span class="w">
      </span><span class="nl">"y"</span><span class="p">:</span><span class="w"> </span><span class="mi">30</span><span class="p">,</span><span class="w">
      </span><span class="nl">"width"</span><span class="p">:</span><span class="w"> </span><span class="mi">1920</span><span class="p">,</span><span class="w">
      </span><span class="nl">"height"</span><span class="p">:</span><span class="w"> </span><span class="mi">1170</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"deco_rect"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"x"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
      </span><span class="nl">"y"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
      </span><span class="nl">"width"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
      </span><span class="nl">"height"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"window_rect"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"x"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
      </span><span class="nl">"y"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
      </span><span class="nl">"width"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
      </span><span class="nl">"height"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"geometry"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="nl">"x"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
      </span><span class="nl">"y"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
      </span><span class="nl">"width"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
      </span><span class="nl">"height"</span><span class="p">:</span><span class="w"> </span><span class="mi">0</span><span class="w">
    </span><span class="p">},</span><span class="w">
    </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"4"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"window"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
    </span><span class="nl">"nodes"</span><span class="p">:</span><span class="w"> </span><span class="p">[],</span><span class="w">
    </span><span class="nl">"floating_nodes"</span><span class="p">:</span><span class="w"> </span><span class="p">[],</span><span class="w">
    </span><span class="nl">"focus"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
      </span><span class="mi">24</span><span class="p">,</span><span class="w">
      </span><span class="mi">25</span><span class="w">
    </span><span class="p">],</span><span class="w">
    </span><span class="nl">"fullscreen_mode"</span><span class="p">:</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
    </span><span class="nl">"sticky"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
    </span><span class="nl">"floating"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
    </span><span class="nl">"scratchpad_state"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
    </span><span class="nl">"num"</span><span class="p">:</span><span class="w"> </span><span class="mi">4</span><span class="p">,</span><span class="w">
    </span><span class="nl">"output"</span><span class="p">:</span><span class="w"> </span><span class="s2">"DP-8"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"representation"</span><span class="p">:</span><span class="w"> </span><span class="s2">"H[V[foot] V[foot]]"</span><span class="p">,</span><span class="w">
    </span><span class="nl">"focused"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span><span class="w">
    </span><span class="nl">"visible"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">]</span></code></pre></figure>

<p>Phew, that’s a lot for 3 workspaces! The only part I care about in
this case is the “name” for each. We could do some weird stuff with
<code class="language-plaintext highlighter-rouge">grep</code> and <code class="language-plaintext highlighter-rouge">sed</code> to get the names, or use <code class="language-plaintext highlighter-rouge">jq</code> to
extract what we want.</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">swaymsg <span class="nt">-t</span> get_workspaces | jq <span class="s1">'.[] | .name'</span></code></pre></figure>

<p>generates</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>"1"
"2"
"4"
</code></pre></div></div>

<p>Perfect!</p>

<p>Putting this all together, here’s a shell script
<code class="language-plaintext highlighter-rouge">sway_list_workspaces</code> that we can use with <code class="language-plaintext highlighter-rouge">rofi</code> to
switch workspaces without using the mouse.</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="c">#!/bin/sh</span>

<span class="k">if</span> <span class="o">[</span> x<span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span> <span class="o">=</span> x <span class="o">]</span>
<span class="k">then
    </span>swaymsg <span class="nt">-t</span> get_workspaces | jq <span class="s1">'.[] | .name'</span>
<span class="k">else
    </span>swaymsg <span class="s2">"workspace </span><span class="nv">$1</span><span class="s2">"</span> <span class="o">&gt;</span>/dev/null
<span class="k">fi</span></code></pre></figure>

<p>Here’s the <code class="language-plaintext highlighter-rouge">rofi</code> command line to use:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">rofi <span class="nt">-modes</span> <span class="s1">'Workspaces:/home/davemarq/bin/sway_list_workspaces'</span> <span class="nt">-show</span> Workspaces</code></pre></figure>

<p>I bind it to “$mod+Shift+w” in my sway config file:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bindsym $mod+Shift+w exec "rofi -modes 'Workspaces:/home/davemarq/bin/sway_list_workspaces' -show Workspaces"
</code></pre></div></div>]]></content><author><name></name></author><category term="sway" /><category term="wayland" /><summary type="html"><![CDATA[I started out on a journey to try to get sway to create a new workspace for me, with a “probably” unique name. How could I do that?]]></summary></entry><entry><title type="html">Tmux and the mouse</title><link href="https://davemq.github.io/2026/01/13/tmux-mouse.html" rel="alternate" type="text/html" title="Tmux and the mouse" /><published>2026-01-13T00:00:00+00:00</published><updated>2026-01-13T00:00:00+00:00</updated><id>https://davemq.github.io/2026/01/13/tmux-mouse</id><content type="html" xml:base="https://davemq.github.io/2026/01/13/tmux-mouse.html"><![CDATA[<p>I use <code class="language-plaintext highlighter-rouge">tmux</code> for just a few things, particular when
connecting to a remote system. But otherwise, I don’t use it for local
terminals, as I can just open up another terminal in Wayland.</p>

<p>I have a Kensington trackball with a big scroll wheel. I’ve gotten
very used to using that scroll wheel for scrolling in terminals,
Emacs, my web browser, etc. But one place it didn’t work was in
<code class="language-plaintext highlighter-rouge">tmux</code>.</p>

<p>But today I finally went searching around for how to turn on mouse
support. Something I read prompted me to think “Oh yeah, how do I do
that?” It turns out <code class="language-plaintext highlighter-rouge">tmux</code> does support the mouse, but not by
default. So you have to enable the support in your tmux configuration
file. I added</p>

<figure class="highlight"><pre><code class="language-ini" data-lang="ini"><span class="err">set</span> <span class="err">-g</span> <span class="err">mouse</span> <span class="err">on</span></code></pre></figure>

<p>to $HOME/.config/tmux/tmux.conf, restarted tmux, and boom, I have
mouse wheel scrolling like most other applications I use.</p>]]></content><author><name></name></author><category term="tmux" /><category term="linux" /><category term="unix" /><summary type="html"><![CDATA[I use tmux for just a few things, particular when connecting to a remote system. But otherwise, I don’t use it for local terminals, as I can just open up another terminal in Wayland.]]></summary></entry><entry><title type="html">Keeping appointments from my Org agenda updated</title><link href="https://davemq.github.io/2026/01/07/org-agenda-to-appt-timer.html" rel="alternate" type="text/html" title="Keeping appointments from my Org agenda updated" /><published>2026-01-07T00:00:00+00:00</published><updated>2026-01-07T00:00:00+00:00</updated><id>https://davemq.github.io/2026/01/07/org-agenda-to-appt-timer</id><content type="html" xml:base="https://davemq.github.io/2026/01/07/org-agenda-to-appt-timer.html"><![CDATA[<p>Among many other things, Emacs’ Org mode manages tasks and allows you
to easily see an agenda of tasks that are scheduled or approaching a
deadline. Emacs also has an appointments system that shows scheduled
appointments at various times ahead of time. Org provides
<code class="language-plaintext highlighter-rouge">org-agenda-to-appt</code> to create appointments from your
agenda items.</p>

<p>So running <code class="language-plaintext highlighter-rouge">org-agenda-to-appt</code> once during Emacs
startup is great, but it has limitations:</p>

<ul>
  <li>it only creates appointments from today’s agenda items</li>
  <li>it has no clue when you’ve added an appointment to one of your
agenda files</li>
</ul>

<p>Assuming you run your Emacs for longer than a day, or that you add
appointments, this is a problem!</p>

<p>My solution is to run <code class="language-plaintext highlighter-rouge">org-agenda-to-appt</code> with
<code class="language-plaintext highlighter-rouge">run-at-time</code> with a repeater of 3600 seconds, i.e. 1
hour, like this, at Emacs start:</p>

<figure class="highlight"><pre><code class="language-emacs-lisp" data-lang="emacs-lisp"><span class="p">(</span><span class="nv">run-at-time</span> <span class="mi">0</span> <span class="mi">3600</span> <span class="ss">'org-agenda-to-appt</span><span class="p">)</span></code></pre></figure>

<p>Time 0 means “right now”, and the repeater is 3600 seconds. After
adding a new task to an agenda file, it shows up as an appointment
within an hour. This may not work if you add an appointment that’s due
in the next hour, as the timer that adds the apointment may run after
the appointment time.</p>]]></content><author><name></name></author><category term="emacs" /><category term="org" /><summary type="html"><![CDATA[Among many other things, Emacs’ Org mode manages tasks and allows you to easily see an agenda of tasks that are scheduled or approaching a deadline. Emacs also has an appointments system that shows scheduled appointments at various times ahead of time. Org provides org-agenda-to-appt to create appointments from your agenda items.]]></summary></entry><entry><title type="html">Creating weekly diary-style timestamps in Org Mode</title><link href="https://davemq.github.io/2026/01/06/org-diary-every-monday.html" rel="alternate" type="text/html" title="Creating weekly diary-style timestamps in Org Mode" /><published>2026-01-06T00:00:00+00:00</published><updated>2026-01-06T00:00:00+00:00</updated><id>https://davemq.github.io/2026/01/06/org-diary-every-monday</id><content type="html" xml:base="https://davemq.github.io/2026/01/06/org-diary-every-monday.html"><![CDATA[<p>With the beginning of the new year, I received meeting invitations for
a twice weekly meeting, on Mondays and Thursdays. I added this to my
Org agenda file, and used a diary-style expression. I <em>thought</em> I
could use <code class="language-plaintext highlighter-rouge">diary-float</code> for every Monday and another for
every Thursday, like this</p>

<figure class="highlight"><pre><code class="language-org" data-lang="org">* Recurring meeting
&lt;%%(diary-float t 1 0) 11:30-12:00&gt;
&lt;%%(diary-float t 4 0) 11:30-12:00&gt;</code></pre></figure>

<p>But when I looked my agenda, I didn’t see anything for Mondays or
Thursdays at 11:30-12:00. Hmm.</p>

<p>The help for <code class="language-plaintext highlighter-rouge">diary-float</code> only specifies how the third
argument, N, applies if it is positive or negative. It would be
nice if 0, or some other value like t, meant every week.</p>

<p>I eventually settled on</p>

<figure class="highlight"><pre><code class="language-org" data-lang="org">* Recurring meeting
&lt;%%(diary-cyclic 7 1 5 2026) 11:30-12:00&gt;
&lt;%%(diary-cyclic 7 1 8 2026) 11:30-12:00&gt;</code></pre></figure>

<p>for the meeting. It works, but it wasn’t obvious to me that this was
the way to go.</p>

<p>I could also have put 5 entries for Mondays and 5 for Thursdays, to
get all five possible occurences of each in a month. Seems kind of
weird, so I didn’t.</p>

<p>And finally, I could just use a regular +1w repeater, but then I need
to have two separate TODOs in my agenda file, one for Mondays and one
for Thursdays.</p>]]></content><author><name></name></author><category term="emacs" /><category term="org" /><summary type="html"><![CDATA[With the beginning of the new year, I received meeting invitations for a twice weekly meeting, on Mondays and Thursdays. I added this to my Org agenda file, and used a diary-style expression. I thought I could use diary-float for every Monday and another for every Thursday, like this]]></summary></entry><entry><title type="html">Possible RAGBRAI routes via Python</title><link href="https://davemq.github.io/2025/12/24/ragbrai-routes.html" rel="alternate" type="text/html" title="Possible RAGBRAI routes via Python" /><published>2025-12-24T00:00:00+00:00</published><updated>2025-12-24T00:00:00+00:00</updated><id>https://davemq.github.io/2025/12/24/ragbrai-routes</id><content type="html" xml:base="https://davemq.github.io/2025/12/24/ragbrai-routes.html"><![CDATA[<h1 id="introduction">Introduction</h1>

<p>I’ve signed up to ride RAGBRAI LIII in July 2026. This is my first
time riding RAGBRAI, and being a geek, I was curious about possible
routes. So, using the first 52 routes, some Python, and the Monte
Carlo method, I wrote a little program to figure out the possible
routes and the likelihood of each route.</p>

<p>There’s a lot to unpack in that first paragraph. So, here goes:</p>

<ul>
  <li>RAGBRAI: This originally meant Register’s Annual Great Bike Ride
Across Iowa. I don’t think this acronym is used these days. But the
name stuck.</li>
  <li>Monte Carlo method: a numerical method that relies on repeated
random sampling to obtain results. So this does simulations with
random number generation to figure out routes.</li>
</ul>

<h1 id="what-do-we-know">What Do We Know?</h1>

<p>The first 52 RAGBRAI routes are published in various places. I grabbed
the routes from this <a href="https://en.wikipedia.org/wiki/List_of_RAGBRAI_overnight_stops#By_year">Wikipedia</a> page, and converted them to a CSV file
using Emacs Org mode and a little hand editing.</p>

<h1 id="now-what">Now what?</h1>

<p>From the CSV file, I can create adjacency lists, which I represent in
Python as a dictionary of unordered lists that describe the neighbors
for each node. In this case, this is representing the overnight towns
and the next or previous town on the route.</p>

<p>I also save a list of starting and ending towns, so I know where
routes have started or ended in the past.</p>

<p>51 of the 52 routes have 8 nodes, as RAGBRAI runs from Sunday to
Saturday. But the first RAGBRAI route only has 7 nodes, so I needed to
take that into account. This was just a little extra code.</p>

<p>Once I’ve got the adjacency lists, I can run the Monte Carlo
simulations. I initially implemented this beginning with a random
starting town. For each town, I picked a random link to the next town.
If there’s a town that next visits the same town multiple times, then
that next visited town will be in the adjacency list multiple times.</p>

<p>But I noticed there were many routes created that didn’t end in one of
the ending towns. Eliminating those, the method was heavily biased
towards only starting a couple of towns. Hmmm.</p>

<p>So I tried started with the end towns and moving backwards. This gave
a much better spread of starting and ending towns in the simulation.
So I stuck with building routes from the end town backwards to the
start.</p>

<h1 id="code">Code</h1>

<p>First there are some <code class="language-plaintext highlighter-rouge">imports</code>:</p>

<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="kn">from</span> <span class="nn">collections</span> <span class="kn">import</span> <span class="n">defaultdict</span>
<span class="kn">import</span> <span class="nn">csv</span>
<span class="kn">import</span> <span class="nn">graphviz</span>
<span class="kn">from</span> <span class="nn">operator</span> <span class="kn">import</span> <span class="n">itemgetter</span>
<span class="kn">import</span> <span class="nn">random</span></code></pre></figure>

<p>Next I set up a tuple of days I can use with the CSV reader to grab
fields from each line of the CSV file.</p>

<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="n">days</span> <span class="o">=</span> <span class="p">(</span>
    <span class="s">"Starting City"</span><span class="p">,</span>
    <span class="s">"Sunday"</span><span class="p">,</span>
    <span class="s">"Monday"</span><span class="p">,</span>
    <span class="s">"Tuesday"</span><span class="p">,</span>
    <span class="s">"Wednesday"</span><span class="p">,</span>
    <span class="s">"Thursday"</span><span class="p">,</span>
    <span class="s">"Friday"</span><span class="p">,</span>
    <span class="s">"Saturday"</span><span class="p">,</span>
<span class="p">)</span></code></pre></figure>

<p>I also create empty lists for the starting towns and ending towns, and
create the adjacencies dictionary using <code class="language-plaintext highlighter-rouge">defaultdict</code> so
each dictionary value will be set up as a list when it is first
accessed.</p>

<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="n">starts</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">ends</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">adjacencies</span> <span class="o">=</span> <span class="n">defaultdict</span><span class="p">(</span><span class="nb">list</span><span class="p">)</span></code></pre></figure>

<h2 id="parsing-the-csv-file-into-adjacency-lists">Parsing the CSV file into adjacency lists</h2>

<p>This function parses the CSV file a couple of times using the Python
“csv” package to read the CSV file as a dictionary. The first time
through is to find the starting and ending towns.</p>

<p>The second time reading the CSV file is to create the adjacency lists.
I start with the days, reversed, to go from the end of a route to the
beginning.</p>

<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">parse</span><span class="p">():</span>
    <span class="k">with</span> <span class="nb">open</span><span class="p">(</span>
        <span class="s">"/home/davemarq/shell-scripts/ragbrai-by-year.csv"</span><span class="p">,</span> <span class="n">newline</span><span class="o">=</span><span class="s">""</span>
    <span class="p">)</span> <span class="k">as</span> <span class="n">csvfile</span><span class="p">:</span>
        <span class="n">reader</span> <span class="o">=</span> <span class="n">csv</span><span class="p">.</span><span class="n">DictReader</span><span class="p">(</span><span class="n">csvfile</span><span class="p">,</span> <span class="n">delimiter</span><span class="o">=</span><span class="s">","</span><span class="p">)</span>
        <span class="k">for</span> <span class="n">row</span> <span class="ow">in</span> <span class="n">reader</span><span class="p">:</span>
            <span class="n">starts</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">row</span><span class="p">[</span><span class="s">"Starting City"</span><span class="p">])</span>
            <span class="k">if</span> <span class="n">row</span><span class="p">[</span><span class="s">"Saturday"</span><span class="p">]</span> <span class="o">!=</span> <span class="s">"N/A"</span><span class="p">:</span>
                <span class="n">ends</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">row</span><span class="p">[</span><span class="s">"Saturday"</span><span class="p">])</span>
            <span class="k">else</span><span class="p">:</span>
                <span class="n">ends</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">row</span><span class="p">[</span><span class="s">"Friday"</span><span class="p">])</span>

    <span class="k">with</span> <span class="nb">open</span><span class="p">(</span>
        <span class="s">"/home/davemarq/shell-scripts/ragbrai-by-year.csv"</span><span class="p">,</span> <span class="n">newline</span><span class="o">=</span><span class="s">""</span>
    <span class="p">)</span> <span class="k">as</span> <span class="n">csvfile</span><span class="p">:</span>
        <span class="n">reader</span> <span class="o">=</span> <span class="n">csv</span><span class="p">.</span><span class="n">DictReader</span><span class="p">(</span><span class="n">csvfile</span><span class="p">,</span> <span class="n">delimiter</span><span class="o">=</span><span class="s">","</span><span class="p">)</span>
        <span class="k">for</span> <span class="n">row</span> <span class="ow">in</span> <span class="n">reader</span><span class="p">:</span>
            <span class="n">prev</span> <span class="o">=</span> <span class="bp">None</span>
            <span class="k">for</span> <span class="n">d</span> <span class="ow">in</span> <span class="nb">reversed</span><span class="p">(</span><span class="n">days</span><span class="p">):</span>
                <span class="k">if</span> <span class="n">prev</span> <span class="ow">is</span> <span class="bp">None</span><span class="p">:</span>
                    <span class="n">prev</span> <span class="o">=</span> <span class="n">d</span>
                <span class="k">elif</span> <span class="n">row</span><span class="p">[</span><span class="n">prev</span><span class="p">]</span> <span class="o">!=</span> <span class="s">"N/A"</span><span class="p">:</span>
                    <span class="n">adjacencies</span><span class="p">[</span><span class="n">row</span><span class="p">[</span><span class="n">prev</span><span class="p">]].</span><span class="n">append</span><span class="p">(</span><span class="n">row</span><span class="p">[</span><span class="n">d</span><span class="p">])</span>
                <span class="n">prev</span> <span class="o">=</span> <span class="n">d</span></code></pre></figure>

<h2 id="making-routes">Making routes</h2>

<p>Once I have the adjacency lists, I want to create random routes. A
route starts with a random end town. For each town, I pick one of its
neighbors based on its adjacency list, using a random number
generator. I append the random choice to the route list. Once I have a
full route, that goes from end to start, I return its reverse.</p>

<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">makeroute</span><span class="p">():</span>
    <span class="n">route</span> <span class="o">=</span> <span class="p">[]</span>
    <span class="n">cur</span> <span class="o">=</span> <span class="n">random</span><span class="p">.</span><span class="n">choice</span><span class="p">(</span><span class="n">ends</span><span class="p">)</span>
    <span class="n">route</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">cur</span><span class="p">)</span>
    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">7</span><span class="p">):</span>
        <span class="k">try</span><span class="p">:</span>
            <span class="nb">next</span> <span class="o">=</span> <span class="n">random</span><span class="p">.</span><span class="n">choice</span><span class="p">(</span><span class="n">adjacencies</span><span class="p">[</span><span class="n">cur</span><span class="p">])</span>
        <span class="k">except</span> <span class="nb">IndexError</span><span class="p">:</span>
            <span class="k">return</span> <span class="bp">None</span>
        <span class="n">route</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="nb">next</span><span class="p">)</span>
        <span class="n">cur</span> <span class="o">=</span> <span class="nb">next</span>
    <span class="k">if</span> <span class="n">cur</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">starts</span><span class="p">:</span>
        <span class="k">return</span> <span class="bp">None</span>

    <span class="k">return</span> <span class="nb">reversed</span><span class="p">(</span><span class="n">route</span><span class="p">)</span></code></pre></figure>

<h2 id="making-a-graph">Making a graph</h2>

<p>I also thought it would be nice to get a graph of all the towns linked
to each other from existing routes. This gives a nice visual
representation showing how often the routes go from town to town. For
example, routes from Sioux City visited Storm Lake next 6 times, so
there will be 6 arrows from Sioux City to Storm Lake.</p>

<p>I inially created this code manually to create a Graphviz directed
graph, or digraph. But I thought, there’s gotta be a Python package
for this. Sure enough, there is.</p>

<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">make_graph</span><span class="p">():</span>
    <span class="n">dot</span> <span class="o">=</span> <span class="n">graphviz</span><span class="p">.</span><span class="n">Digraph</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s">'ragbrai'</span><span class="p">,</span> <span class="nb">format</span><span class="o">=</span><span class="s">'png'</span><span class="p">,</span> <span class="n">engine</span><span class="o">=</span><span class="s">'dot'</span><span class="p">)</span>
    <span class="k">for</span> <span class="n">a</span> <span class="ow">in</span> <span class="n">adjacencies</span><span class="p">:</span>
        <span class="k">for</span> <span class="n">node</span> <span class="ow">in</span> <span class="n">adjacencies</span><span class="p">[</span><span class="n">a</span><span class="p">]:</span>
            <span class="n">dot</span><span class="p">.</span><span class="n">edge</span><span class="p">(</span><span class="n">node</span><span class="p">,</span> <span class="n">a</span><span class="p">)</span>
    <span class="n">dot</span> <span class="o">=</span> <span class="n">dot</span><span class="p">.</span><span class="n">unflatten</span><span class="p">()</span>
    <span class="n">dot</span><span class="p">.</span><span class="n">render</span><span class="p">(</span><span class="n">view</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span></code></pre></figure>

<p>Here’s the graph based on the 52 existing routes.</p>

<p><img src="/images/ragbrai.gv.png" alt="img" /></p>

<h2 id="do-the-work">Do the work!</h2>

<p>Okay, now the main code starts. It parses, then creates the graph.
Finally, it does the Monte Carlo method.</p>

<p>The Monte Carlo method here creates routes 1,000,000 times. It tracks
how many times each route is created through a dictionary. Once
created, We get told how many unique routes were created and then the
routes are sorted by count and printed, along with the count for each.
Typically the method creates a little over 7000 unique routes. The
results are pretty consistent, with a route from Sioux City to Clinton
getting a bit over 10000 hits, i.e. about 1% of the possibilities.
It’s also fun to look at the bottom of the list for those “1 in a
million” routes.</p>

<p>Here’s the first fiew lines of typical output:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Opening in existing browser session.
Created 7230 unique routes
('Sioux City', 'Storm Lake', 'Fort Dodge', 'Iowa Falls', 'Vinton', 'Mount Vernon', 'Maquoketa', 'Clinton') 10433
('Glenwood', 'Shenandoah', 'Creston', 'Adel', 'Pella', 'Ottumwa', 'Mount Pleasant', 'Burlington') 6969
('Rock Rapids', 'Spencer', 'Algona', 'Clear Lake', 'New Hampton', 'Decorah', 'Manchester', 'Dubuque') 6153
('Glenwood', 'Shenandoah', 'Creston', 'Adel', 'Pella', 'Ottumwa', 'Washington', 'Muscatine') 5483
('Glenwood', 'Shenandoah', 'Creston', 'Adel', 'Pella', 'Ottumwa', 'Mount Pleasant', 'Fort Madison') 5261
</code></pre></div></div>

<p>And here’s the final bits of code.</p>

<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="n">parse</span><span class="p">()</span>
<span class="n">make_graph</span><span class="p">()</span>

<span class="n">routes</span> <span class="o">=</span> <span class="n">defaultdict</span><span class="p">(</span><span class="nb">int</span><span class="p">)</span>

<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">1000000</span><span class="p">):</span>
    <span class="n">r</span> <span class="o">=</span> <span class="bp">None</span>
    <span class="k">while</span> <span class="n">r</span> <span class="ow">is</span> <span class="bp">None</span><span class="p">:</span>
        <span class="n">r</span> <span class="o">=</span> <span class="n">makeroute</span><span class="p">()</span>
    <span class="n">routes</span><span class="p">[</span><span class="nb">tuple</span><span class="p">(</span><span class="n">r</span><span class="p">)]</span> <span class="o">+=</span> <span class="mi">1</span>
<span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"Created </span><span class="si">{</span><span class="nb">len</span><span class="p">(</span><span class="n">routes</span><span class="p">)</span><span class="si">}</span><span class="s"> unique routes"</span><span class="p">)</span>

<span class="n">sortedroutes</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">(</span><span class="nb">sorted</span><span class="p">(</span><span class="n">routes</span><span class="p">.</span><span class="n">items</span><span class="p">(),</span> <span class="n">key</span><span class="o">=</span><span class="n">itemgetter</span><span class="p">(</span><span class="mi">1</span><span class="p">),</span> <span class="n">reverse</span><span class="o">=</span><span class="bp">True</span><span class="p">))</span>
<span class="k">for</span> <span class="n">r</span> <span class="ow">in</span> <span class="n">sortedroutes</span><span class="p">:</span>
    <span class="k">print</span><span class="p">(</span><span class="n">r</span><span class="p">,</span> <span class="n">sortedroutes</span><span class="p">[</span><span class="n">r</span><span class="p">])</span></code></pre></figure>]]></content><author><name></name></author><category term="python" /><category term="bicycling" /><summary type="html"><![CDATA[Introduction]]></summary></entry><entry><title type="html">Using Magit to fix a commit</title><link href="https://davemq.github.io/2025/11/25/using-magit-to-fix-a-commit.html" rel="alternate" type="text/html" title="Using Magit to fix a commit" /><published>2025-11-25T00:00:00+00:00</published><updated>2025-11-25T00:00:00+00:00</updated><id>https://davemq.github.io/2025/11/25/using-magit-to-fix-a-commit</id><content type="html" xml:base="https://davemq.github.io/2025/11/25/using-magit-to-fix-a-commit.html"><![CDATA[<p>I’ve been working on a patch series for the Linux kernel. I had
consolidated my changes into four patches, with a few more to go,
when it dawned on me that I should review these patches myself before
sending them to anyone else. Duh!</p>

<p>Reviewing the first patch, I discovered a few things I wanted to fix.
If this was the fourth patch, I could directly use a squash commit to
fix the problem. But a commit that’s had three patches on top of it
was a bit much for me to handle directly. This was a perfect
opportunity to try Magit for this. Magit’s manual claims you can
squash into any commit. Cool.</p>

<p>So, following the manual, I created a squash commit on top of my
fourth patch. Then I requested that Magit squash this into my first
patch commit. This started a series of actions that were</p>

<ul>
  <li>resolve conflicts</li>
  <li>continue the rebase</li>
</ul>

<p>I resolved the conflicts with Emacs’ SMerge mode. As is often the
case, I find Magit’s status in these cases not useful. I consulted
<code class="language-plaintext highlighter-rouge">git status</code> in a terminal window, and it told me I needed to
resolve conflicts. After resolving the conflicts, there was a
suggestion of using <code class="language-plaintext highlighter-rouge">git rebase --continue</code>. So I tried it.</p>

<p>After doing this conflict resolution and continuing the rebase three
times, the work was complete. I was delighted that it worked.</p>

<p>I don’t think I would have liked to try this from the command line!</p>

<p>For the future, I’d like to know if there’s a Magit command to
continue a rebase. It wasn’t obvious to me at the time from the rebase
screen in Magit. But the manual says you can use <code class="language-plaintext highlighter-rouge">r r</code> or
<code class="language-plaintext highlighter-rouge">magit-rebase-continue</code>.</p>

<p>I also wonder if there’s some way I can modify the Magit status window
to be more useful to me. That’s something that’s bothered me for a
while when doing some operations. Many people rave about Magit, me
included, but I find the status screen difficult to parse at times.</p>]]></content><author><name></name></author><category term="emacs" /><category term="magit" /><category term="git" /><summary type="html"><![CDATA[I’ve been working on a patch series for the Linux kernel. I had consolidated my changes into four patches, with a few more to go, when it dawned on me that I should review these patches myself before sending them to anyone else. Duh!]]></summary></entry><entry><title type="html">Pipe to Emacs buffer</title><link href="https://davemq.github.io/2025/11/12/pipe-to-emacs-buffer.html" rel="alternate" type="text/html" title="Pipe to Emacs buffer" /><published>2025-11-12T00:00:00+00:00</published><updated>2025-11-12T00:00:00+00:00</updated><id>https://davemq.github.io/2025/11/12/pipe-to-emacs-buffer</id><content type="html" xml:base="https://davemq.github.io/2025/11/12/pipe-to-emacs-buffer.html"><![CDATA[<h1 id="introduction">Introduction</h1>

<p>An #emacs IRC user wants to pipe from a program into an Emacs buffer.
<code class="language-plaintext highlighter-rouge">emacsclient</code> doesn’t read standard input<sup><a id="fnr.1" class="footref" href="#fn.1" role="doc-backlink">1</a></sup>. So, is there a
way to do this with <code class="language-plaintext highlighter-rouge">emacsclient</code> and not writing a new server
program? I think so.</p>

<p>We’ll use a shell script called “pipe-to-emacs-buffer.sh”. It will
take a single argument, the name of a buffer to receive the data. The
Emacs Lisp code will create this buffer and set it as the current
buffer.</p>

<h1 id="shell-script">Shell script</h1>

<h2 id="start">Start</h2>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="c"># pipe-to-emacs-buffer.sh buffer-name</span>

<span class="k">if</span> <span class="o">[</span> <span class="nv">$# </span><span class="nt">-lt</span> 1 <span class="o">]</span>
<span class="k">then
    </span><span class="nb">echo </span>usage: pipe-to-emacs-buffer.sh buffer-name
    <span class="nb">exit </span>1
<span class="k">fi

</span><span class="nv">buffername</span><span class="o">=</span><span class="s2">"</span><span class="nv">$1</span><span class="s2">"</span></code></pre></figure>

<h2 id="base64-encode-standard-input">Base64 encode standard input</h2>

<p>So why base64 encoding? If standard input has non-text characters,
e.g. a NUL, the string gets mangled. Encoding ensures the data is preserved.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nv">input</span><span class="o">=</span><span class="si">$(</span><span class="nb">base64</span><span class="si">)</span></code></pre></figure>

<p>This assumes that the program feeding the pipe is not a long-running
program that never quits. In the case of a long running program, this
code could be extended to use a loop of the shell’s <code class="language-plaintext highlighter-rouge">read</code>
builtin and encoding to get all of the data over time, or make some
decision not to get all the data.</p>

<h2 id="run-emacsclient">Run emacsclient</h2>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">emacsclient <span class="nt">--eval</span> <span class="s2">"(set-buffer (get-buffer-create </span><span class="se">\"</span><span class="k">${</span><span class="nv">buffername</span><span class="k">}</span><span class="se">\"</span><span class="s2">))"</span> <span class="nt">--eval</span> <span class="s2">"(insert (base64-decode-string </span><span class="se">\"</span><span class="k">${</span><span class="nv">input</span><span class="k">}</span><span class="se">\"</span><span class="s2">))"</span></code></pre></figure>

<p><code class="language-plaintext highlighter-rouge">get-buffer-create</code> will either use an existing buffer
or create one.</p>

<p><code class="language-plaintext highlighter-rouge">insert</code> will insert the text before point.</p>

<h1 id="lets-test-it">Let’s test it!</h1>

<p>Here’s the output of <code class="language-plaintext highlighter-rouge">who</code> on my laptop:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>davemarq seat0        2025-11-11 09:44
davemarq tty2         2025-11-11 09:44
</code></pre></div></div>

<p>So piping who into our program, along with the buffer name who.txt,
should result in a buffer named who.txt in our running Emacs.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>davemarq:~$ who | ./pipe-to-emacs-buffer.sh who.txt
#&lt;buffer who.txt&gt;
nil
</code></pre></div></div>

<p>Looking at our buffer list, I see who.txt:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>* who.txt                                78 Fundamental      
</code></pre></div></div>

<p>Going to the who.txt buffer, I see</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>davemarq seat0        2025-11-11 09:44
davemarq tty2         2025-11-11 09:44
</code></pre></div></div>

<p>Running our pipeline a second time gives me a who.txt buffer that
looks like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>davemarq seat0        2025-11-11 09:44
davemarq tty2         2025-11-11 09:44
davemarq seat0        2025-11-11 09:44
davemarq tty2         2025-11-11 09:44
</code></pre></div></div>

<h1 id="footnotes">Footnotes</h1>

<p><sup><a id="fn.1" href="#fnr.1">1</a></sup> In reading emacsclient.c, it <em>does</em> read standard input, but
prepends “-eval “ to every line before sending to Emacs. But this is
not documented and didn’t work as I expected when I tried it.</p>]]></content><author><name></name></author><category term="emacs" /><summary type="html"><![CDATA[Introduction]]></summary></entry><entry><title type="html">Python2 on Fedora 42</title><link href="https://davemq.github.io/2025/11/11/python2-on-fedora-42.html" rel="alternate" type="text/html" title="Python2 on Fedora 42" /><published>2025-11-11T00:00:00+00:00</published><updated>2025-11-11T00:00:00+00:00</updated><id>https://davemq.github.io/2025/11/11/python2-on-fedora-42</id><content type="html" xml:base="https://davemq.github.io/2025/11/11/python2-on-fedora-42.html"><![CDATA[<p>I have a Python 2 application I use for geocaching, for manipulating
GPX and GGZ files. I’ve looked into porting it to Python 3, but it
seems to be more work than I’d like.</p>

<p>When I reinstalled my laptops with Fedora 41 some time back, I
discovered Python 2 wasn’t available. Oh right, Fedora did announce
they were dropping Python 2 support. Of course, if you already had
Python 2 installed and just upgraded to Fedora 41, you would likely
have kept the packages.</p>

<p>So, what to do?</p>

<p>Fedora has this tool called <code class="language-plaintext highlighter-rouge">toolbox</code> that gives easy access
to Fedora and other distributions at various levels by using
containers. Using <code class="language-plaintext highlighter-rouge">toolbox</code>, I created a Fedora 40 toolbox:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">toolbox create <span class="nt">--distro</span> fedora <span class="nt">--release</span> 40</code></pre></figure>

<p>Once I did that, I entered the toolbox with</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">toolbox enter fedora-toolbox-40</code></pre></figure>

<p>Within the toolbox, I installed Python 2.7:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="nb">sudo </span>dnf <span class="nb">install </span>python2.7</code></pre></figure>

<p>After that I ran <code class="language-plaintext highlighter-rouge">python2</code> and <code class="language-plaintext highlighter-rouge">python2.7</code> and
checked the version:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>⬢ [davemarq@toolbx ~]$ python2 --version
Python 2.7.18
⬢ [davemarq@toolbx ~]$ python2.7 --version
Python 2.7.18
</code></pre></div></div>

<p>I exited the toolbox shell with <code class="language-plaintext highlighter-rouge">exit</code> and used
<code class="language-plaintext highlighter-rouge">toolbox run</code> to run Python2.7 in the Fedora 40 toolbox:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>davemarq:~$ toolbox run -c fedora-toolbox-40 python2 --version
Python 2.7.18
</code></pre></div></div>

<p>Finally, I turned that into a shell alias and ran it:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>davemarq:~$ alias python2='toolbox run -c fedora-toolbox-40 python2'
davemarq:~$ python2 --version
Python 2.7.18
</code></pre></div></div>]]></content><author><name></name></author><category term="linux" /><category term="python" /><category term="fedora" /><summary type="html"><![CDATA[I have a Python 2 application I use for geocaching, for manipulating GPX and GGZ files. I’ve looked into porting it to Python 3, but it seems to be more work than I’d like.]]></summary></entry><entry><title type="html">Hangman tools in Python</title><link href="https://davemq.github.io/2025/10/13/hangman-tools-py.html" rel="alternate" type="text/html" title="Hangman tools in Python" /><published>2025-10-13T00:00:00+00:00</published><updated>2025-10-13T00:00:00+00:00</updated><id>https://davemq.github.io/2025/10/13/hangman-tools-py</id><content type="html" xml:base="https://davemq.github.io/2025/10/13/hangman-tools-py.html"><![CDATA[<p>For several months I’ve been playing a “hangman” style game on
Mastodon, HangTrek, where words and phrases are from the various Star
Trek television shows and films. The game starts with a phrase like</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>_ _ _ / _ _ _ _ _ _ _ / _ _ / _ _ _ / _ _ _ _ _ _ / _ _ _ _ / _ _ _ / _ _ _ _ _ _ _ _ _ _

Wrong guesses: none
</code></pre></div></div>

<p>The underscore (_) characters are unknown (to the players) letters,
and the forward slashes (/) are word separators. For each round a poll
with 4 choices is run for some amount of time, typically 24 hours. The
poll winner is the guess for that round. If the winner matches one of
the unknown letters, for the next round, that letter is filled in. The
the winner isn’t one of the unknown letters, that’s noted below the
puzzle in the following rounds, so it doesn’t get guessed again.</p>

<p>For example, if ‘E’ is a valid guess, in the next round it will show
up like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>_ _ _ / _ _ E _ _ _ _ / _ _ / _ _ _ / _ _ _ _ _ _ / _ _ _ _ / _ _ _ / _ _ _ _ _ _ _ _ _ _

Wrong guesses: none
</code></pre></div></div>

<p>In another example, if ‘Q’ was the poll winner but doesn’t match any
letters, it’s noted in the wrong guesses.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>_ _ _ / _ _ _ _ _ _ _ / _ _ / _ _ _ / _ _ _ _ _ _ / _ _ _ _ / _ _ _ / _ _ _ _ _ _ _ _ _ _

Wrong guesses: Q
</code></pre></div></div>

<p>Some number of Star Trek television and film scripts are available for
download, so I took to searching them with regular expressions. For
example, for a 10 letter word where none of the letters are known,
I’d search for</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[[:alpha:]]\{10\}
</code></pre></div></div>

<p>in the scripts, using a tool like <code class="language-plaintext highlighter-rouge">grep</code>.</p>

<p>Over time, I realized I could write a program to create these regular
expressions, so I did. I have a long history of turning my
non-programming homework into programming homework, back to my college
days 😃</p>

<p>For a whole phrase, there are some things we want to do:</p>

<ul>
  <li>anchor words with ‘\&lt;’ and ‘\&gt;’, so we don’t match words within
larger words</li>
  <li>allow for multiple spaces between words in a phrase, using
‘\+’</li>
  <li>shrink the original ‘[:alpha:]’ character class as letters are
guessed. For correct answers, we see them in the phrase. For
incorrect letters, add a way to specify them, so they’re also
eliminated from the character class</li>
</ul>

<p>So, I wrote a Python program <code class="language-plaintext highlighter-rouge">hangman-regexp.py</code> to accomplish
this. It takes the phrase as a command line argument, and you can
optionally specify wrong guesses with a <code class="language-plaintext highlighter-rouge">-x Q</code> argument.</p>

<h2 id="hangman-regexppy">hangman-regexp.py</h2>

<p>For the first phrase above, the program runs like this:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">hangman-regexp.py <span class="s1">'_ _ _ / _ _ _ _ _ _ _ / _ _ / _ _ _ / _ _ _ _ _ _ / _ _ _ _ / _ _ _ / _ _ _ _ _ _ _ _ _ _'</span></code></pre></figure>

<p>which provides this output:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>\&lt;[abcdefghijklmnopqrstuvwxyz]\{3\}\&gt;[[:space:]]\+\&lt;[abcdefghijklmnopqrstuvwxyz]\{7\}\&gt;[[:space:]]\+\&lt;[abcdefghijklmnopqrstuvwxyz]\{2\}\&gt;[[:space:]]\+\&lt;[abcdefghijklmnopqrstuvwxyz]\{3\}\&gt;[[:space:]]\+\&lt;[abcdefghijklmnopqrstuvwxyz]\{6\}\&gt;[[:space:]]\+\&lt;[abcdefghijklmnopqrstuvwxyz]\{4\}\&gt;[[:space:]]\+\&lt;[abcdefghijklmnopqrstuvwxyz]\{3\}\&gt;[[:space:]]\+\&lt;[abcdefghijklmnopqrstuvwxyz]\{10\}\&gt;
</code></pre></div></div>

<p>For the second example, where we’ve successfully guessed ‘E’, here’s
the command and the output:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">hangman-regexp.py <span class="s1">'_ _ _ / _ _ E _ _ _ _ / _ _ / _ _ _ / _ _ _ _ _ _ / _ _ _ _ / _ _ _ / _ _ _ _ _ _ _ _ _ _'</span></code></pre></figure>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>\&lt;[abcdfghijklmnopqrstuvwxyz]\{3\}\&gt;[[:space:]]\+\&lt;[abcdfghijklmnopqrstuvwxyz]\{2\}e[abcdfghijklmnopqrstuvwxyz]\{4\}\&gt;[[:space:]]\+\&lt;[abcdfghijklmnopqrstuvwxyz]\{2\}\&gt;[[:space:]]\+\&lt;[abcdfghijklmnopqrstuvwxyz]\{3\}\&gt;[[:space:]]\+\&lt;[abcdfghijklmnopqrstuvwxyz]\{6\}\&gt;[[:space:]]\+\&lt;[abcdfghijklmnopqrstuvwxyz]\{4\}\&gt;[[:space:]]\+\&lt;[abcdfghijklmnopqrstuvwxyz]\{3\}\&gt;[[:space:]]\+\&lt;[abcdfghijklmnopqrstuvwxyz]\{10\}\&gt;
</code></pre></div></div>

<p>You’ll note that the character class no longer contains ‘e’, and in
the second word we have the actual ‘e’ embedded in the regular
expression.</p>

<p>For the third example, of ‘Q’ which is not in the phrase, the command
and output are like this:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">hangman-regexp.py <span class="nt">-x</span> q <span class="s1">'_ _ _ / _ _ _ _ _ _ _ / _ _ / _ _ _ / _ _ _ _ _ _ / _ _ _ _ / _ _ _ / _ _ _ _ _ _ _ _ _ _'</span></code></pre></figure>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>\&lt;[abcdefghijklmnoprstuvwxyz]\{3\}\&gt;[[:space:]]\+\&lt;[abcdefghijklmnoprstuvwxyz]\{7\}\&gt;[[:space:]]\+\&lt;[abcdefghijklmnoprstuvwxyz]\{2\}\&gt;[[:space:]]\+\&lt;[abcdefghijklmnoprstuvwxyz]\{3\}\&gt;[[:space:]]\+\&lt;[abcdefghijklmnoprstuvwxyz]\{6\}\&gt;[[:space:]]\+\&lt;[abcdefghijklmnoprstuvwxyz]\{4\}\&gt;[[:space:]]\+\&lt;[abcdefghijklmnoprstuvwxyz]\{3\}\&gt;[[:space:]]\+\&lt;[abcdefghijklmnoprstuvwxyz]\{10\}\&gt;
</code></pre></div></div>

<h2 id="running-a-game-make-hangman-phrasepy">Running a game: make-hangman-phrase.py</h2>

<p>On the other end of this game is the game runner. They think up the
phrase and run the rounds. I thought it would be nice to have a tool
that, given a phrase and zero or more guesses, output the phrase
“hangman style”, along with the “wrong guesses” line. This tool is
<code class="language-plaintext highlighter-rouge">make-hangman-phrase.py</code>.</p>

<p>Let’s suppose you want to run a game with the phrase “There are four
lights”. At the beginning of the game, you would run</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">make-hangman-phrase.py <span class="s1">'There are four lights'</span></code></pre></figure>

<p>Here’s the output:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Phrase:		_ _ _ _ _ / _ _ _ / _ _ _ _ / _ _ _ _ _ _
Occurrences:	None
Wrong guess:	None
</code></pre></div></div>

<p>This provides the phrase and wrong guesses that can be published on
Mastodon. The “Occurrences” line here shows how many times a guess
shows up in the phrase. We’ll see this below.</p>

<p>Let’s suppose for the first round the letter ‘T’ is guessed. In this
case, we run</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">make-hangman-phrase.py <span class="nt">-g</span> T <span class="s1">'There are four lights'</span></code></pre></figure>

<p>We add <code class="language-plaintext highlighter-rouge">-g T</code> to specify that ‘T’ was guessed. The output is
this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Phrase:		T _ _ _ _ / _ _ _ / _ _ _ _ / _ _ _ _ T _
Occurrences:	{'t': 2}
Wrong guess:	None
</code></pre></div></div>

<p>So now the game runner can say “T was the guess, and there are 2 T’s
in the phrase” in their next post.</p>

<p>Let’s say the next poll winner is ‘V’. There’s no ‘V’ in the phrase.
Let’s see what happens.</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">make-hangman-phrase.py <span class="nt">-g</span> T <span class="nt">-g</span> V <span class="s1">'There are four lights'</span></code></pre></figure>

<p>Here’s the output:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Phrase:		T _ _ _ _ / _ _ _ / _ _ _ _ / _ _ _ _ T _
Occurrences:	{'t': 2, 'v': 0}
Wrong guesses:	V
</code></pre></div></div>

<h2 id="code-availability">Code availability</h2>

<p>The code for these tools is at <a href="https://github.com/davemq/hangman-tools">https://github.com/davemq/hangman-tools</a>.</p>]]></content><author><name></name></author><category term="python" /><category term="hangman" /><category term="startrek" /><category term="hangtrek" /><summary type="html"><![CDATA[For several months I’ve been playing a “hangman” style game on Mastodon, HangTrek, where words and phrases are from the various Star Trek television shows and films. The game starts with a phrase like]]></summary></entry></feed>