<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>hi, it&#39;s mike</title>
    <link>https://mike.puddingtime.org/tags/ruby/</link>
    <description>Recent content on hi, it&#39;s mike</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <managingEditor>mike@puddingtime.org (mike)</managingEditor>
    <webMaster>mike@puddingtime.org (mike)</webMaster>
    <copyright>© 2026, mike</copyright>
    <lastBuildDate>Mon, 29 Jan 2024 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://mike.puddingtime.org/tags/ruby/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>A Wallabag bookmarking script for Newsboat</title>
      <link>https://mike.puddingtime.org/posts/2024-01-29-a-wallabag-bookmarking-script-for-newsboat/</link>
      <pubDate>Mon, 29 Jan 2024 00:00:00 +0000</pubDate><author>mike@puddingtime.org (mike)</author>
      <guid>https://mike.puddingtime.org/posts/2024-01-29-a-wallabag-bookmarking-script-for-newsboat/</guid>
      <description>Ruby and httparty worked where a simple bash script wouldn&amp;rsquo;t. Letting Newsboat have two bookmarking commands.</description>
      <content:encoded><![CDATA[<p>I don&rsquo;t have any idea what was going wrong where, but I found a &ldquo;post to Wallabag&rdquo; (<a href="/posts/2024-01-28-daily-notes/">previously</a>) sh script that worked great from the command line, but wouldn&rsquo;t run within Newsboat. So I took the script apart, converted it to Ruby/httparty, and it runs fine in this form.</p>
<p>Now I have two scripts for bookmarking in Newsboat, but it only has one reserved key for bookmarking a post. It does have macros, though, so you can use one to set the bookmarking command to a given script and bind it to something memorable. My mutt convention is to lead macros with a period, so I kept that here by unbinding Newsboat&rsquo;s default macro prefix (,) and setting &ldquo;.&rdquo;.</p>
<p>So, <kbd>.l</kbd> to save to Linkding, <kbd>.w</kbd> to save to Wallabag:</p>






<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># macros</span>
</span></span><span class="line"><span class="cl">bind-key . macro-prefix
</span></span><span class="line"><span class="cl">unbind-key ,
</span></span><span class="line"><span class="cl">macro w <span class="nb">set</span> bookmark-cmd <span class="s2">&#34;op run --env-file=\&#34;</span><span class="nv">$HOME</span><span class="s2">/.env\&#34; -- ~/bin/wallabag.rb&#34;</span><span class="p">;</span> bookmark
</span></span><span class="line"><span class="cl">macro l <span class="nb">set</span> bookmark-cmd <span class="s2">&#34;op run --env-file=\&#34;</span><span class="nv">$HOME</span><span class="s2">/.env\&#34; -- ~/bin/linkding.rb&#34;</span><span class="p">;</span> bookmark</span></span></code></pre></div>
<p>And here&rsquo;s the Wallabag bookmarking script:</p>






<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="ch">#!/usr/bin/env ruby</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">require</span> <span class="s1">&#39;httparty&#39;</span>
</span></span><span class="line"><span class="cl"><span class="nb">require</span> <span class="s1">&#39;json&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">entry_url</span> <span class="o">=</span> <span class="no">ARGV</span><span class="o">[</span><span class="mi">0</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">wallabag_url</span><span class="o">=</span><span class="s2">&#34;https://reader.example.net&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">client_id</span> <span class="o">=</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;WALLABAG_CLIENT&#39;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="n">client_secret</span> <span class="o">=</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;WALLABAG_SECRET&#39;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="n">username</span> <span class="o">=</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;WALLABAG_USER&#39;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="n">password</span> <span class="o">=</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;WALLABAG_PASSWORD&#39;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">token_params</span> <span class="o">=</span> <span class="p">{</span><span class="ss">grant_type</span><span class="p">:</span> <span class="s2">&#34;password&#34;</span><span class="p">,</span> <span class="ss">client_id</span><span class="p">:</span> <span class="n">client_id</span><span class="p">,</span> <span class="ss">client_secret</span><span class="p">:</span> <span class="n">client_secret</span><span class="p">,</span> <span class="ss">username</span><span class="p">:</span> <span class="n">username</span><span class="p">,</span> <span class="ss">password</span><span class="p">:</span> <span class="n">password</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="n">token_req</span> <span class="o">=</span> <span class="no">HTTParty</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="s2">&#34;</span><span class="si">#{</span><span class="n">wallabag_url</span><span class="si">}</span><span class="s2">/oauth/v2/token&#34;</span><span class="p">,</span> <span class="ss">body</span><span class="p">:</span> <span class="n">token_params</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">access_token</span> <span class="o">=</span> <span class="n">token_req</span><span class="o">[</span><span class="s2">&#34;access_token&#34;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">headers</span> <span class="o">=</span> <span class="p">{</span><span class="s1">&#39;Content-Type&#39;</span> <span class="o">=&gt;</span> <span class="s2">&#34;application/json&#34;</span><span class="p">,</span> <span class="s1">&#39;Authorization&#39;</span> <span class="o">=&gt;</span> <span class="s2">&#34;Bearer </span><span class="si">#{</span><span class="n">access_token</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="n">params</span> <span class="o">=</span> <span class="p">{</span><span class="ss">url</span><span class="p">:</span> <span class="n">entry_url</span><span class="p">,</span> <span class="ss">starred</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> <span class="ss">archive</span><span class="p">:</span> <span class="mi">0</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="n">resp</span> <span class="o">=</span> <span class="no">HTTParty</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="s2">&#34;</span><span class="si">#{</span><span class="n">wallabag_url</span><span class="si">}</span><span class="s2">/api/entries.json&#34;</span><span class="p">,</span> <span class="ss">body</span><span class="p">:</span> <span class="n">params</span><span class="o">.</span><span class="n">to_json</span><span class="p">,</span> <span class="ss">headers</span><span class="p">:</span> <span class="n">headers</span><span class="p">)</span></span></span></code></pre></div>
]]></content:encoded>
    </item>
    <item>
      <title>Daily Notes for 2024-01-26</title>
      <link>https://mike.puddingtime.org/posts/2024-01-26-daily-notes/</link>
      <pubDate>Fri, 26 Jan 2024 00:00:00 +0000</pubDate><author>mike@puddingtime.org (mike)</author>
      <guid>https://mike.puddingtime.org/posts/2024-01-26-daily-notes/</guid>
      <description>Sorta the mutt of RSS readers. Scripting the Linkding API.</description>
      <content:encoded><![CDATA[<h2 id="sorta-the-mutt-of-rss-readers">Sorta the mutt of RSS readers</h2>
<p><a href="https://newsboat.org/">Newsboat</a> is pretty cool! It&rsquo;s a plaintext RSS reader that has strong affinities for mutt in look and configuration.</p>
<p>Like mutt, it might not crowd out everything else in the toolbox but it can help you burn through the subscription list and triage even if you have more comfortable ways of reading the content you process.</p>
<p>It also has built-in filtering. If you&rsquo;re using an RSS provider that already does that, e.g. FreshRSS or Feedly, that might not be super valuable, but it&rsquo;s easy to make a killfile either way:</p>






<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">ignore-mode display
</span></span><span class="line"><span class="cl">ignore-article &#34;*&#34; &#34;link =~ \&#34;/ducks/\&#34;&#34;
</span></span><span class="line"><span class="cl">ignore-article &#34;*&#34; &#34;link =~ \&#34;/advice/\&#34;&#34;
</span></span><span class="line"><span class="cl">ignore-article &#34;*&#34; &#34;link =~ \&#34;/nfl/\&#34;&#34;
</span></span><span class="line"><span class="cl">ignore-article &#34;*&#34; &#34;link =~ \&#34;/beavers/\&#34;&#34;
</span></span><span class="line"><span class="cl">ignore-article &#34;*&#34; &#34;link =~ \&#34;/blazers/\&#34;&#34;
</span></span><span class="line"><span class="cl">ignore-article &#34;*&#34; &#34;link =~ \&#34;/highschoolsports/\&#34;&#34;
</span></span><span class="line"><span class="cl">ignore-article &#34;*&#34; &#34;link =~ \&#34;/entertainment/\&#34;&#34;
</span></span><span class="line"><span class="cl">ignore-article &#34;*&#34; &#34;link =~ \&#34;/realestate-news/\&#34;&#34;
</span></span><span class="line"><span class="cl">ignore-article &#34;*&#34; &#34;link =~ \&#34;/food/\&#34;&#34;
</span></span><span class="line"><span class="cl">ignore-article &#34;*&#34; &#34;link =~ \&#34;/hawks/\&#34;&#34;</span></span></code></pre></div>
<h2 id="scripting-the-linkding-api-to-make-a-bookmark-function-for-newsboat">Scripting the Linkding API to make a bookmark function for Newsboat</h2>
<p>If you like reading longform plaintext maybe Newsboat is all you need. I tend to treat RSS as a two-step process: See an interesting thing, send it to some sort of RIL or bookmarking service. Newsboat has a bookmarking function you can customize with your own scripts. It just passes the article URL, title, description, and website description to your script, which has to talk to whatever API. There are a bunch of <a href="https://github.com/newsboat/newsboat/tree/master/contrib">examples in the Newsboat contrib directory</a> for things like Pocket, Pinboard, and Evernote. No Linkding, but <a href="https://github.com/sissbruecker/linkding/blob/master/docs/API.md">the Linkding API</a> is simple enough.</p>
<p>Could be simpler but I bounced off of <kbd>net:http</kbd> in my formative years:</p>






<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="ch">#!/usr/bin/env ruby</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">require</span> <span class="s1">&#39;httparty&#39;</span>
</span></span><span class="line"><span class="cl"><span class="nb">require</span> <span class="s1">&#39;json&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">linkding_uri</span> <span class="o">=</span> <span class="s2">&#34;https://links.puddingtime.net/api/bookmarks/&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Get your Linkding API key from Settings &gt; Integrations</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">token</span> <span class="o">=</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;LINKDING&#39;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">link_url</span> <span class="o">=</span> <span class="no">ARGV</span><span class="o">[</span><span class="mi">0</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="n">link_title</span> <span class="o">=</span> <span class="no">ARGV</span><span class="o">[</span><span class="mi">1</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="n">description</span> <span class="o">=</span> <span class="no">ARGV</span><span class="o">[</span><span class="mi">2</span><span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="n">website_title</span> <span class="o">=</span> <span class="no">ARGV</span><span class="o">[</span><span class="mi">3</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">params</span> <span class="o">=</span> <span class="p">{</span><span class="ss">url</span><span class="p">:</span> <span class="no">URI</span><span class="p">(</span><span class="n">link_url</span><span class="p">),</span> <span class="ss">title</span><span class="p">:</span> <span class="n">link_title</span><span class="p">,</span> <span class="ss">website_title</span><span class="p">:</span> <span class="n">website_title</span><span class="p">,</span> <span class="ss">unread</span><span class="p">:</span> <span class="kp">true</span><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="n">headers</span> <span class="o">=</span> <span class="p">{</span><span class="s1">&#39;Content-Type&#39;</span> <span class="o">=&gt;</span> <span class="s2">&#34;application/json&#34;</span><span class="p">,</span> <span class="s1">&#39;Authorization&#39;</span> <span class="o">=&gt;</span> <span class="s2">&#34;Token </span><span class="si">#{</span><span class="n">token</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">resp</span> <span class="o">=</span> <span class="no">HTTParty</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="n">linkding_uri</span><span class="p">,</span> <span class="ss">body</span><span class="p">:</span> <span class="n">params</span><span class="o">.</span><span class="n">to_json</span><span class="p">,</span> <span class="ss">headers</span><span class="p">:</span> <span class="n">headers</span><span class="p">)</span></span></span></code></pre></div>
]]></content:encoded>
    </item>
    <item>
      <title>Daily notes for 2024-01-09 (mutt noodling edition)</title>
      <link>https://mike.puddingtime.org/posts/2024-01-09-daily-notes/</link>
      <pubDate>Tue, 09 Jan 2024 10:41:05 -0800</pubDate><author>mike@puddingtime.org (mike)</author>
      <guid>https://mike.puddingtime.org/posts/2024-01-09-daily-notes/</guid>
      <description>Multi-account, GPG-secure mutt configs. Mutt message scoring with Ruby, and score color-coding.</description>
      <content:encoded><![CDATA[<h2 id="multi-account-gpg-secured-mutt-config">Multi-account, GPG-secured mutt config</h2>
<p>I keep having to reinvent this every few years, and I always stitch it together from assorted sources, mostly because Google sort of shifts around now and then. So:</p>
<ul>
<li>Given a Gmail account with IMAP access turned on</li>
<li>Given a Fastmail account using IMAP</li>
<li>Given mutt, with your configuration in <code>~/.mutt</code> and with <code>muttrc</code> and <code>macros</code> files.</li>
<li>Given a working gpg config you can use to encrypt/decrypt</li>
</ul>
<p>There are all sorts of ways to handle mutt config for assorted providers. The examples here are working right now, in early 2024. They probably have bits of cruft and lint because my config has been a work in progress since some time in the late 20th century.</p>
<h3 id="overview">Overview</h3>
<p>You&rsquo;re making profiles to do this: One for each of your accounts that will hold account specific config information. If you currently have a monolith config in mutt, you can lift a lot of stuff out of it and move it into a profile, then source the profile in your main <code>muttrc</code>.</p>
<p>You&rsquo;re also going to make and encrypt a credential file for each account. Some people do this all in one file and use account hooks to make sure <code>imap_user</code>, <code>imap_password</code> and <code>smtp_password</code> are set correcctly depending on the account you&rsquo;re operating in. I chose to make a file for each account.</p>
<p>You&rsquo;re going to make macros that source the profiles when you want to switch between them.</p>
<h3 id="0-pre-config-with-gmail-and-fastmail">0. Pre-config with Gmail and Fastmail</h3>
<p>I&rsquo;m not going to go into a ton of detail here:</p>
<ul>
<li>Gmail needs to have less secure app access turned on. Find it in your account settings. If you&rsquo;re doing this for a work account, it may be your admin hasn&rsquo;t enabled this. Have fun fighting city hall, in that case.</li>
<li>If you have a GSuite admin, they need to have enabled all IMAP clients, not just OAuth ones.</li>
<li>If you have 2FA turned on with Google, you will need to enable an application password.</li>
</ul>
<p>For Fastmail:</p>
<ul>
<li>You need to have an app password set up for mutt. <code>Settings -&gt; Privacy and Security -&gt; Integrations -&gt; App passwords</code></li>
</ul>
<h3 id="1-the-profile-files">1. The profile files</h3>
<p>Make profile files for each of your accounts. I name them <code>workplace.profile</code>, <code>fastmail.profile</code>, etc. It doesn&rsquo;t matter there&rsquo;s no required convention. It&rsquo;s a good idea to use the first one as the template for the second one.</p>
<p>This is an example of my Fastmail profile. Note line 6:</p>
<p><code>source &quot;gpg -d ~/.mutt/passwords.gpg |&quot;</code></p>
<p>That&rsquo;s where your credentials will come from. I&rsquo;ll show that file next.</p>






<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># -*- muttrc -*-</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Mutt sender profile : personal/default</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">unset</span> folder
</span></span><span class="line"><span class="cl"><span class="nb">set</span> <span class="nv">smtp_authenticators</span> <span class="o">=</span> <span class="s1">&#39;gssapi:login&#39;</span> <span class="c1"># fastmail needs this</span>
</span></span><span class="line"><span class="cl"><span class="nb">set</span> <span class="nv">imap_authenticators</span> <span class="o">=</span> <span class="s1">&#39;&#39;</span>
</span></span><span class="line"><span class="cl"><span class="nb">source</span> <span class="s2">&#34;gpg -d ~/.mutt/passwords.gpg |&#34;</span> 
</span></span><span class="line"><span class="cl"><span class="nb">set</span> <span class="nv">spoolfile</span> <span class="o">=</span> <span class="s2">&#34;imaps://imap.fastmail.com&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">set</span> <span class="nv">folder</span> <span class="o">=</span> <span class="s2">&#34;imaps://imap.fastmail.com/INBOX&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">set</span> <span class="nv">postponed</span><span class="o">=</span><span class="s2">&#34;+Drafts&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">set</span> <span class="nv">hostname</span><span class="o">=</span><span class="s2">&#34;yourdomain.com&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">set</span> <span class="nv">signature</span><span class="o">=</span> <span class="s2">&#34;~/.mutt/personal.sig&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">set</span> <span class="nv">from</span><span class="o">=</span> <span class="s2">&#34;Bob Jones &lt;bob@yourdomain.com&gt;&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">set</span> <span class="nv">realname</span> <span class="o">=</span> <span class="s2">&#34;Bob Jones&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">set</span> <span class="nv">smtp_url</span> <span class="o">=</span> <span class="s2">&#34;smtps://bobjones@fastmail.com@smtp.fastmail.com:465&#34;</span> <span class="c1"># use your fastmail username, not your email address</span>
</span></span><span class="line"><span class="cl"><span class="nb">set</span> <span class="nv">imap_user</span> <span class="o">=</span> <span class="s2">&#34;bobjones@fastmail.com&#34;</span> <span class="c1"># use your fastmail username here, too</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># set the status to show which profile I&#39;m using</span>
</span></span><span class="line"><span class="cl"><span class="nb">set</span> <span class="nv">status_format</span><span class="o">=</span> <span class="s2">&#34;-%r-Fastmail: %f [Msgs:%?M?%M/?%m%?n? New:%n?%?o? Old:%o?%?d? Del:%d?%?F? Flag:%F?%?t? Tag:%t?%?p? Post:%p?%?b? Inc:%b?%?l? %l?]---(%s/%S)-%&gt;-(%P)---\n&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">unmy_hdr *
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">my_hdr From: Bob Jones &lt;bob@yourdomain.com&gt;
</span></span><span class="line"><span class="cl">my_hdr Organization: yourdomain.com
</span></span><span class="line"><span class="cl">my_hdr Sender: Bob Jones &lt;bob@yourdomain.com&gt;
</span></span><span class="line"><span class="cl">my_hdr Return-Path: &lt;bob@yourdomain.com&gt;
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># clear the existing mailboxes list</span>
</span></span><span class="line"><span class="cl">unmailboxes *
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># load up mailboxes appropriate to this profile</span>
</span></span><span class="line"><span class="cl">mailboxes + <span class="s2">&#34;=Spam&#34;</span>
</span></span><span class="line"><span class="cl">mailboxes + <span class="s2">&#34;=disposable&#34;</span>
</span></span><span class="line"><span class="cl">mailboxes + <span class="s2">&#34;=Newsletters&#34;</span>
</span></span><span class="line"><span class="cl">mailboxes + <span class="s2">&#34;=Sent&#34;</span>
</span></span><span class="line"><span class="cl">mailboxes + <span class="s2">&#34;=Archive&#34;</span></span></span></code></pre></div>
<h3 id="2-make-credentials-files">2. Make credentials files</h3>
<p>For each account, you need to make a file for your credentials.</p>






<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">set imap_user=bob@bobjones.com
</span></span><span class="line"><span class="cl">set imap_pass=&#34;klatu barada nikto&#34;
</span></span><span class="line"><span class="cl">set smtp_pass=&#34;klatu barada nikto&#34;</span></span></code></pre></div>
<p>Name it whatever. <code>passwords-accountname</code> works.</p>
<p>Once you&rsquo;ve created the file, encrypt it with gpg:</p>
<p><code>gpg -r your-gpg-key@yourdomain.com -e passwords-fastmail</code></p>
<p>Test it:</p>
<p><code>gpg -d passwords-fastmail.gpg</code></p>
<p>Then shred the plaintext original:</p>
<p><code>shred -u passwords-fastmail</code></p>
<p>Make sure that your profile (from the previous step) is sourcing the gpg file in line 6 of my example, e.g.</p>
<p><code>source &quot;gpg -d ~/.mutt/passwords-fastmail.gpg |&quot;</code></p>
<h3 id="3-do-a-quick-mid-config-check">3. Do a quick mid-config check</h3>
<p>Might as well test it now.  You can do that by sourcing one of your profiles in your <code>muttrc</code>:</p>
<p><code>source ~/.mutt/fastmail.profile</code></p>
<p>When you run mutt the first time in this login session, you should get a gpg prompt for your credentials so mutt can decrypt your password file and use it to log in.</p>
<p>If it&rsquo;s working, now&rsquo;s the time to make your second profile and credentials files using the above steps since it&rsquo;ll be good to know what they&rsquo;re all called for the next step, which is making macros.</p>
<h3 id="4-make-macros">4. Make macros</h3>
<p>I keep my macros in their own file under <code>~/.mutt</code> just to keep things modular. You can put these in your main <code>muttrc</code>. Whatever you prefer. If you have a separate file, make sure to source it in <code>muttrc</code>:</p>
<p><code>source ~/.mutt/macros</code></p>
<p>Now add something like this for each account:</p>






<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">macro index .cf &#39;&lt;sync-mailbox&gt;&lt;enter-command&gt;source ~/.mutt/fastmail.profile&lt;enter&gt;&lt;change-folder&gt;!&lt;enter&gt;&#39;
</span></span><span class="line"><span class="cl">macro index .cg &#39;&lt;sync-mailbox&gt;&lt;enter-command&gt;source ~/.mutt/google.profile&lt;enter&gt;&lt;change-folder&gt;!&lt;enter&gt;&#39;</span></span></code></pre></div>
<p>That just does one last sync, then sources your profile, then changes folders to the inbox of that profile.</p>
<p>Restart mutt. From the index, if all is working correctly, the macro <code>.cf</code> will source your <code>fastmail.profile</code> and the macro <code>.cg</code> will source your <code>google.profile</code> file (both of which also source/decrypt their respective credential files).</p>
<h3 id="5-in-conclusion">5. In conclusion</h3>
<p>Once it&rsquo;s all wired up and running, you should be able to switch back and forth between accounts with just a few seconds of latency as the inbox syncs on exit and the new inbox syncs on login.</p>
<h2 id="the-pleasures-of-mutt">The pleasures of mutt</h2>
<p>I went on a mutt revival kick early last year. It remains a land of contrasts. I never end up sticking to it 100 percent of the time but instead prefer to use it as a quick triage tool: It&rsquo;s easy to make macros and keybindings that speed up inbox processing. Sometimes it&rsquo;s easier to just bail out to the web mail interface, but during the day it&rsquo;s helpful to just burn through the inbox never taking my hands off the keyboard.</p>
<h2 id="mutt-scoring-and-color-treatments">mutt scoring and color treatments</h2>
<p>One last thing, I guess, since I&rsquo;m documenting stuff.  One of the reasons I like mutt for triage so much is my ability to add a little visual treatment to messages based on their scores. That makes it easy to see what in my inbox has more priority.</p>
<p>I&rsquo;ve got this little script in my <code>~/.mutt</code>:</p>






<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="ch">#!/usr/bin/env ruby</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">require</span> <span class="s1">&#39;mail&#39;</span>
</span></span><span class="line"><span class="cl"><span class="nb">require</span> <span class="s1">&#39;tempfile&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Wants a +/- integer, e.g. +20</span>
</span></span><span class="line"><span class="cl"><span class="n">score</span> <span class="o">=</span> <span class="no">ARGV</span><span class="o">.</span><span class="n">first</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">score_file</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="si">#{</span><span class="no">Dir</span><span class="o">.</span><span class="n">home</span><span class="si">}</span><span class="s2">/.mutt/scored&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">msg</span> <span class="o">=</span> <span class="no">Tempfile</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="s1">&#39;msg&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">msg</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="vg">$stdin</span><span class="o">.</span><span class="n">read</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">mail</span> <span class="o">=</span> <span class="no">Mail</span><span class="o">.</span><span class="n">read</span><span class="p">(</span><span class="n">msg</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">from</span> <span class="o">=</span> <span class="n">mail</span><span class="o">.</span><span class="n">from</span><span class="o">.</span><span class="n">first</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="no">File</span><span class="o">.</span><span class="n">open</span><span class="p">(</span><span class="n">score_file</span><span class="p">,</span> <span class="s2">&#34;a&#34;</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="n">f</span><span class="o">|</span> <span class="n">f</span><span class="o">.</span><span class="n">write</span> <span class="s2">&#34;score ~f</span><span class="si">#{</span><span class="n">from</span><span class="si">}</span><span class="s2"> </span><span class="si">#{</span><span class="n">score</span><span class="si">}</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">msg</span><span class="o">.</span><span class="n">close</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">msg</span><span class="o">.</span><span class="n">unlink</span></span></span></code></pre></div>
<p>And I&rsquo;ve got these macros in my <code>~/.mutt/macros</code> file:</p>






<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># Score messages</span>
</span></span><span class="line"><span class="cl">macro index,browser .sp <span class="s2">&#34;&lt;pipe-entry&gt;~/.mutt/mailscore.rb +5\n&lt;enter-command&gt;source ~/.mutt/scored&lt;enter&gt;&#34;</span> <span class="c1"># score sender +5</span>
</span></span><span class="line"><span class="cl">macro index,browser .sP <span class="s2">&#34;&lt;pipe-entry&gt;~/.mutt/mailscore.rb +20\n&lt;enter-command&gt;source ~/.mutt/scored&lt;enter&gt;&#34;</span> <span class="c1"># score sender +20</span>
</span></span><span class="line"><span class="cl">macro index,browser .sm <span class="s2">&#34;&lt;pipe-entry&gt;~/.mutt/mailscore.rb -5\n&lt;enter-command&gt;source ~/.mutt/scored&lt;enter&gt;&#34;</span> <span class="c1"># score sender -5</span>
</span></span><span class="line"><span class="cl">macro index,browser .sM <span class="s2">&#34;&lt;pipe-entry&gt;~/.mutt/mailscore.rb -20\n&lt;enter-command&gt;source ~/.mutt/scored&lt;enter&gt;&#34;</span> <span class="c1"># score sender -20</span></span></span></code></pre></div>
<p>And I&rsquo;ve got a few lines in my <code>~/.mutt/colors</code> file:</p>






<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">color index cyan default <span class="s2">&#34;~n 0-2 !~p&#34;</span>
</span></span><span class="line"><span class="cl">color index magenta default <span class="s2">&#34;~n &lt;5&#34;</span>
</span></span><span class="line"><span class="cl">color index brightyellow default <span class="s2">&#34;~n &gt;15&#34;</span>
</span></span><span class="line"><span class="cl">color index brightred default <span class="s2">&#34;~n &gt;19&#34;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#</span></span></span></code></pre></div>
<p>The macros pipe a given message into the script, the script extracts the sender, and the script writes a line into my <code>~/.mutt/scored</code> file. Then the <code>~/.mutt/colors</code> file (which you need to source in <code>muttrc</code>) assigns colors to certain scores. I have a few other rules in <code>~/.mutt/scores</code>, as well:</p>






<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># Date-based scoring penalties -- older things fall down</span>
</span></span><span class="line"><span class="cl">score ~d&gt;3d -1
</span></span><span class="line"><span class="cl">score ~d&gt;7d -3
</span></span><span class="line"><span class="cl">score ~d&gt;14d -10
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">score <span class="s2">&#34;~O&#34;</span> +10 <span class="c1"># old = +10 so I don&#39;t miss it</span>
</span></span><span class="line"><span class="cl">score <span class="s2">&#34;~F&#34;</span> +20 <span class="c1"># flagged = +20 so it stays in the interesting view for a while, even if old</span>
</span></span><span class="line"><span class="cl">score <span class="s2">&#34;!~p ~d&gt;7d&#34;</span> -10 <span class="c1"># not for me directly, getting old, let it fade away</span>
</span></span><span class="line"><span class="cl">score <span class="s2">&#34;!~l&#34;</span> +2 <span class="c1"># to a known list, give it a bump</span></span></span></code></pre></div>
]]></content:encoded>
    </item>
    <item>
      <title>Daily Notes for 2023-05-03</title>
      <link>https://mike.puddingtime.org/posts/2023-05-03-daily-notes/</link>
      <pubDate>Wed, 03 May 2023 00:00:00 +0000</pubDate><author>mike@puddingtime.org (mike)</author>
      <guid>https://mike.puddingtime.org/posts/2023-05-03-daily-notes/</guid>
      <description>A Ruby/CLI-based plaintext PRM, Robert DeNiro on exporting org-mode to JSON, blogging with ox-hugo, that Royal Enfield Himalayan</description>
      <content:encoded><![CDATA[<h2 id="friends-a-plaintext-ruby-based-prm">&ldquo;friends&rdquo; - a plaintext, Ruby-based PRM</h2>
<p>Looking around for other people who have done CRM/PRM-ish things in plaintext, I found <a href="https://github.com/JacobEvelyn/friends">JacobEvelyn/friends</a>. It&rsquo;s written in Ruby, uses Markdown for its home format, and gives you a command line interface to a record of your friends and activities.  I appreciate how thoroughly it thinks about what it is trying to do, and I sense a set of concerns similar to mine about the &ldquo;keeping up with personal contacts&rdquo; challenge.</p>
<p>Some things I like about it:</p>
<ul>
<li>Simple CLI data entry syntax</li>
<li>Some &ldquo;habit tracking&rdquo; style reporting to help you understand if you&rsquo;re keeping up your practice.</li>
<li>Clean reporting with a lot of flexibility that would let you build more reporting.</li>
<li>Simple use of Markdown, with no elaborate syntactical overlay.  If you gave up on friends, your data would be easily readable.</li>
</ul>
<p>It&rsquo;s probably good to note that friends isn&rsquo;t a full contact management system. It&rsquo;s better to think of it as a sort of journaling and habit tracking tool with a tight focus on keeping up with people, not a way to manage all your contact details. If I could extend one thing about it, it would probably be to be able to store email addresses with contacts: Email addresses aren&rsquo;t <em>great</em> keys, but also they&rsquo;re fine keys sometimes, and they&rsquo;d open friends up to interacting with other tools.</p>
<h2 id="ox-json-and-the-wisdom-of-neil-mccauley">ox-json and the wisdom of Neil McCauley</h2>
<p>As I was looking through the docs for friends and waming up to it some I wondered how readily I could migrate my org-contacts information. My home language is Ruby, so I tend to start there when I&rsquo;m looking for a library. There&rsquo;s one org-mode gem I&rsquo;m aware of, but its primary preoccupation is converting org-mode to HTML or Textile for presentation purposes.</p>
<p>Another way to come at the problem is to get the org markup into something more universally parseable, which is where <a href="https://github.com/jlumpe/ox-json">ox-json</a> could help. Does what it says on the tin: Converts an org-mode file into JSON, including, crucially, the data stored in the  <code>:PROPERTIES:</code>  drawer. Currently it passes by the <code>:LOGBOOK:</code> drawer, so that limits what you can do with it, but it still opens up possibilities.</p>
<p>Why?</p>
<div style="text-align:center;">
<iframe width="560" height="315" src="https://www.youtube.com/embed/rGPWW9Pjzto" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
</div>
<h2 id="ox-hugo-update">ox-hugo update</h2>
<p>I started blogging with <a href="https://github.com/kaushalmodi/ox-hugo">ox-hugo</a> several weeks ago, going into it a little warily.</p>
<p>Recap:</p>
<ul>
<li>You write all your posts in a monolithic org-mode file.</li>
<li>Each heading is a post.</li>
<li>Heading tags become post tags.</li>
<li>Headings in a <code>TODO</code> state are drafts.</li>
<li>Metadata can be stored in the <code>:PROPERTIES:</code> drawer (tidy, but the templating syntax gets cluttery if you&rsquo;re not a lisp native) or additional metadata src blocks (more visually cluttered when writing, but easier to read the template  if you&rsquo;re a YAML native)</li>
<li>You can set it up to automatically export the Markdown version into your Hugo content hierarchy whenever you save the buffer.</li>
</ul>
<p>Why would you want to do this?</p>
<p>As someone who does a lot of digest posts, I like having my pre-publication notes, links, etc. in the org-mode ecosystem, with all of its text manipulation affordances.  If a topic I&rsquo;m working on isn&rsquo;t ready when it&rsquo;s time to publish that day, I just <code>refile</code> the subheading under my <code>* Daily Post Overflow</code>  heading and keep going. I also like org-mode&rsquo;s structure editing features. It&rsquo;s simple to move headings and their content around within a post.</p>
<p>I thought the &ldquo;all-in-one-file&rdquo; thing would annoy me, and there is part of me that still doesn&rsquo;t like seeing all the surrounding context, but that&rsquo;s what <code>subtree to indirect buffer</code> is for. I drop into an indirect buffer for the long-haul writing, then pop back out of it if I need to pull things in from the overflow area or check on something from a previous post.</p>
<p>I did stub my toe on one thing, which was that the org-capture template I found to make the post setup simpler was setting <code>:EXPORT-HUGO-DATE:</code>, which updates dynamically when you save a post heading. I went back to make some edits to a post, saved my work, and it altered the date metadata in the Markdown output and jumbled my post order. The answer seemed to be to switch that to <code>:EXPORT_DATE:</code>, and now it behaves.</p>
<p>I also put off cleaning up my capture template so all the metadata could go in the <code>:PROPERTIES:</code> drawer. At first It was easier  to just embed some YAML at the top of the post body with <code>#+begin_src yaml :front_matter_extra t</code> rather than working out the Lispier syntax for post image and cover image in the context of writing a capture template.  It just took a few minutes to fix once I decided to bother with it, and the template now outputs <code>:PROPERTIES:</code> metadata:</p>






<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-org" data-lang="org"><span class="line"><span class="cl">:EXPORT_HUGO_CUSTOM_FRONT_MATTER<span class="gd">+: :cover &#39;((image . &#34;&#34; ) (caption . &#34;&#34; ))
</span></span></span><span class="line"><span class="cl"><span class="gd">:EXPORT_HUGO_CUSTOM_FRONT_MATTER+</span>: :images &#39;(/momo-logo.jpg)
</span></span><span class="line"><span class="cl">:EXPORT_DESCRIPTION:</span></span></code></pre></div>
<p>I only sometimes use cover images, but I like to include my site logo in social posts, etc. when I don&rsquo;t have some other image to show, so the template defaults to an empty cover image and <code>image</code> metadata that Hugo&rsquo;s OpenGraph templating can pull in.</p>
<p>Several weeks in, I like the workflow. One tiny part of my soul is troubled that I have org source and Markdown output, but on the other hand the org source overwrites the Markdown output on edit, so the two don&rsquo;t drift. Realistically, the Markdown would be the more migratable content were I to shift off of Hugo, and it&rsquo;s simply better to author in org-mode.  So there&rsquo;s no associated toil and each format gets to be useful in the way it is best suited to be so.</p>
<h2 id="that-royal-enfield-himalayan">That Royal Enfield Himalayan</h2>
<p>I complained a little about my Royal Enfield Himalayan a few days ago: a little big for the power it has, and it had some QA problems that took some time to track down  I am pretty sure I am going to sell it to fund something similar.  But I did swap in a fresh battery and cleaned it up from winter storage, and rode it up to St. Johns for lunch yesterday, which meant a few dozen miles. It ran pretty well!</p>
<p>Last year, after dealing with rough idling and stalls, I finally broke down and installed a <a href="https://www.boosterplug.com/shop/frontpage.html">BoosterPlug</a>. Himalayans run too lean out of the factory, and the difference after installing one was pretty amazing. It was about a five-minute operation and it made the difference between a very rough first five minutes and &ldquo;let it idle for 30 seconds and it&rsquo;ll be fine.&rdquo; The machine never stalls now. I do think it still idles a little low, but that&rsquo;s a fine-tuning thing.</p>
<p>Anyhow, it was nice to ride around. Yeah, it&rsquo;s a little big, but it&rsquo;s not a big bike. There&rsquo;s plenty of pep for the city. Running up HWY 30, it did fine with the lunch crowd and there was plenty of power to overtake or squeeze out of spots at urban parkway speeds. I&rsquo;d do exit-to-exit on the Portland bypasses with it.</p>
<p>I was also glad to see <a href="https://www.sabatinomoto.com">an RE dealership up in St. John.</a>  Wasn&rsquo;t a fan of the Harley dealership I was getting service at and had to do a lot of research on my own to get help when it was suffering from factory QA problems.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Daily Notes for 2023-05-02</title>
      <link>https://mike.puddingtime.org/posts/2023-05-02-daily-notes/</link>
      <pubDate>Tue, 02 May 2023 00:00:00 -0700</pubDate><author>mike@puddingtime.org (mike)</author>
      <guid>https://mike.puddingtime.org/posts/2023-05-02-daily-notes/</guid>
      <description>A Mackup/Dropbox glitch, integrating org-contacts and Things, conversations not interviews.</description>
      <content:encoded><![CDATA[<h2 id="mackup-and-dropbox">Mackup and Dropbox</h2>
<p>I recently recommended <a href="https://github.com/lra/mackup">Mackup</a>, a Mac config syncronization tool, but I&rsquo;m having a few issues with it now. In general, it does a pretty good job with most apps, but I ran into a weird bug with Mailmate where it kept forgetting all my settings. After a few go-rounds I opened up the Console and searched for Mailmate messages and found it wasn&rsquo;t able to write to its prefs file. I put Mailmate in Mackup&rsquo;s skip list, removed the symlinks and let it write its files again and all was well. Searching Mackup&rsquo;s issues, <a href="https://github.com/lra/mackup/issues/1891">I found someone experiencing a similar issue with Xcode</a> and learned it seems to be a thing with Dropbox and iCloud and certain apps. In the case of Dropbox, it has come with that app&rsquo;s move to the <code>CloudStorage</code> folder.</p>
<p>I&rsquo;m not sure this is enough to get me quit using it. It works quite well with my Emacs config, gpg, ssh, zsh, and other stuff. I also like using it for syncing my <code>~/bin</code>.  It doesn&rsquo;t work so well with Terminal.app, and gets a little weird now and then with a few other things.</p>
<p>Just &hellip; proceed with caution, I guess is the advice.  For now I&rsquo;ve got Mailmate, Terminal.app, karabiner, and Bartender on the skip list. That&rsquo;s fine for most of them: They&rsquo;re generally best configured a little different between laptop and desktop anyhow.</p>
<h2 id="my-org-contacts-file-and-things">My  org-contacts file and Things</h2>
<p>I stopped using mu4e. I was uncomfortable with the interplay between several different clients (both automated and user-facing) and my Maildir and IMAP. That left a a small hole in the functionality I&rsquo;d built into my org-mode PRM: being able to quickly mail a contact from a Doom Emacs menu. So I made a quick function that just turns the email address in the org-contacts record into a <code>mailto:</code> link and <code>open</code> call to the system that invokes my preferred mail client (Mailmate at this point). So if the point is over an org-contacts heading I can <code>SPC C m</code> (&ldquo;leader - CRM - mail&rdquo;)  and get a new message.</p>
<p>I&rsquo;m on the record somewhere about not liking the emphasis on URL schemes for Mac automation. I don&rsquo;t like the ins and outs of encoding values and cramming data into that format. At the same time, it <em>does</em> seem to have kept the idea of Mac end-user automation from fading away. So as I sat there looking at my new mailto function, I wondered about how all the contact data I&rsquo;m keeping could interact with the wider Mac ecosystem in a sort of &ldquo;if needed&rdquo; manner, hence this little thing.</p>
<p>It just provides an interactive menu for selecting a contact activity (ping, call, write, etc.) and an interactive date picker, then makes a Thing todo that includes the tags for the contact, with a &ldquo;start date.&rdquo; I can get at it with <code>SPC C g</code> (&ldquo;leader CRM thinGs&rdquo;).  I don&rsquo;t mean to use it? I was just curious. I&rsquo;m not sure.</p>
<p>What I am learning as I use org-mode day-to-day again is that there are things that come naturally to it and that do not come naturally to it. I&rsquo;ve got working integrations with my calendar, for instance, but calendar syncing is another one of those things that eats the one thread you have to work with when it runs, and sometimes it does mysterious things if you mess with a plaintext representation of a more complex data structure that was never written with direct human interaction in mind.</p>
<p>That&rsquo;s always the struggle with Emacs: What <em>can</em> it do, and what <em>should</em> it do?</p>
<p>The temptation is to crawl into a uni-environment and torture everything into some kind of alignment, but that&rsquo;s brittle. It might <em>feel</em> good if your temperament or proclivities lead you to feeling comfortable with that particular shape, but there are tradeoffs whether you acknowledge them or not. In this particular case, the line I am sensing is the line between &ldquo;getting things done&rdquo; in a very mixed, tactical, &ldquo;chores, obligations, and interrupts&rdquo; kind of way, and getting things done in a very &ldquo;life is an information problem&rdquo; kind of way.</p>
<p>I love org-mode as a way of organizing information and thoughts. In particular, I am very fond of all the refiling capabilities it offers, because ideas and information can be shuffled around between different contexts inside the broader org-mode context without lifting a hand from the keyboard. As a day-to-day &ldquo;chores and household projects&rdquo; tool, I&rsquo;m a little less certain about it, mainly because of the mobile piece. <a href="https://beorgapp.com">beorg</a> is great, but it is also a little bit of work to use, and its syncing model is borrowed, so it&rsquo;s not as good as a purpose-built solution. Further, it is not consistent with my desktop org-mode environments when it comes to things like the agenda views.</p>
<p>So, you know, the interesting thing to me becomes &ldquo;how can this sophisticated text manipulation environment fit into a broader toolkit?&rdquo; How can all these things interconnect and complement each other? What are the kinds of work that makes sense living in a purpose-built tool because their typical context favors less thinking and less complexity, vs. the kinds of work that are broadly the same thing (&ldquo;a thing I need to do&rdquo;) that benefit from more thinking and more complexity? What kinds of tasks can be &ldquo;dead&rdquo; and in a little purpose-built silo, and what kinds of tasks benefit from a little bit of added complexity to exist in a better context? How could a thing move from one environment to the other?</p>
<p>Interesting to me, anyhow, because my tendency, at rest &ndash; my unconscious tendency &ndash; is to want everything in one tool, but I continue to learn over time that the one-tool outlook breeds its own kinds of complexity.</p>
<p>Anyhow, here&rsquo;s that function. It works okay so far. The one glitch is that the Things URL scheme won&rsquo;t make a tag if it doesn&rsquo;t exist, so I had to go in and tag an existing todo with all my contact types (friend, network, recruiter, etc.) to get the function to properly tag a contact todo.</p>






<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-emacs-lisp" data-lang="emacs-lisp"><span class="line"><span class="cl"><span class="p">(</span><span class="nb">defun</span> <span class="nv">mph/org-contacts-to-things</span> <span class="p">(</span><span class="nv">contact-kind</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="s">&#34;Create a Things to-do item based on the current Org Contacts record.
</span></span></span><span class="line"><span class="cl"><span class="s">   CONTACT-KIND is a string that specifies the kind of contact (&#39;ping&#39;, &#39;call&#39;, &#39;write&#39;, &#39;schedule&#39;, or &#39;follow up&#39;).&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="p">(</span><span class="nb">interactive</span>
</span></span><span class="line"><span class="cl">   <span class="p">(</span><span class="nf">list</span>
</span></span><span class="line"><span class="cl">    <span class="p">(</span><span class="nf">completing-read</span> <span class="s">&#34;Contact Kind: &#34;</span> <span class="o">&#39;</span><span class="p">(</span><span class="s">&#34;ping&#34;</span> <span class="s">&#34;call&#34;</span> <span class="s">&#34;write&#34;</span> <span class="s">&#34;schedule&#34;</span> <span class="s">&#34;follow up&#34;</span><span class="p">))))</span>
</span></span><span class="line"><span class="cl">  <span class="p">(</span><span class="nb">let*</span> <span class="p">((</span><span class="nv">name</span> <span class="p">(</span><span class="nv">org-entry-get</span> <span class="no">nil</span> <span class="s">&#34;Name&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">         <span class="p">(</span><span class="nv">email</span> <span class="p">(</span><span class="nv">org-entry-get</span> <span class="no">nil</span> <span class="s">&#34;Email&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">         <span class="p">(</span><span class="nv">phone</span> <span class="p">(</span><span class="nv">org-entry-get</span> <span class="no">nil</span> <span class="s">&#34;Phone&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">         <span class="p">(</span><span class="nv">note</span> <span class="p">(</span><span class="nf">read-string</span> <span class="s">&#34;Note: &#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">         <span class="p">(</span><span class="nv">notes</span> <span class="p">(</span><span class="nf">format</span> <span class="s">&#34;Email: %s\nPhone: %s\nNote: %s&#34;</span> <span class="nv">email</span> <span class="nv">phone</span> <span class="nv">note</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">         <span class="p">(</span><span class="nv">start-date</span> <span class="p">(</span><span class="nv">org-read-date</span> <span class="no">nil</span> <span class="no">nil</span> <span class="no">nil</span> <span class="s">&#34;Start Date: &#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">         <span class="p">(</span><span class="nv">start-date-string</span> <span class="p">(</span><span class="nf">format-time-string</span> <span class="s">&#34;%Y-%m-%d&#34;</span> <span class="p">(</span><span class="nv">org-time-string-to-time</span> <span class="nv">start-date</span><span class="p">)))</span>
</span></span><span class="line"><span class="cl">         <span class="p">(</span><span class="nv">tags</span> <span class="p">(</span><span class="nv">org-get-tags</span> <span class="no">nil</span> <span class="no">t</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">         <span class="p">(</span><span class="nv">tag-string</span> <span class="p">(</span><span class="nb">if</span> <span class="nv">tags</span> <span class="p">(</span><span class="nf">mapconcat</span> <span class="ss">&#39;identity</span> <span class="nv">tags</span> <span class="s">&#34;,&#34;</span><span class="p">)</span> <span class="s">&#34;&#34;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">         <span class="p">(</span><span class="nv">title</span> <span class="p">(</span><span class="nf">format</span> <span class="s">&#34;%s: %s&#34;</span> <span class="nv">contact-kind</span> <span class="nv">name</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">         <span class="p">(</span><span class="nv">url</span> <span class="p">(</span><span class="nf">format</span> <span class="s">&#34;things:///add?title=%s&amp;notes=%s&amp;when=%s&amp;tags=%s&#34;</span>
</span></span><span class="line"><span class="cl">                      <span class="p">(</span><span class="nv">url-encode-url</span> <span class="nv">title</span><span class="p">)</span> <span class="p">(</span><span class="nv">url-encode-url</span> <span class="nv">notes</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                      <span class="p">(</span><span class="nv">url-encode-url</span> <span class="nv">start-date-string</span><span class="p">)</span> <span class="p">(</span><span class="nv">url-encode-url</span> <span class="nv">tag-string</span><span class="p">))))</span>
</span></span><span class="line"><span class="cl">    <span class="p">(</span><span class="nv">start-process-shell-command</span> <span class="s">&#34;open&#34;</span> <span class="no">nil</span> <span class="p">(</span><span class="nf">format</span> <span class="s">&#34;open \&#34;%s\&#34;&#34;</span> <span class="nv">url</span><span class="p">))))</span></span></span></code></pre></div>
<h2 id="conversations-not-interviews">Conversations, not interviews</h2>
<p>Refreshing interview closer of the month:</p>
<p>&ldquo;We have a few minutes left, any questions of me?&rdquo;</p>
<p>&ldquo;No. I came into this thinking you&rsquo;d either say &lsquo;did you even read the job description? Now good day while I go fire the recruiter,&rsquo; or you&rsquo;d see something that would lead you to want a conversation, which I hope we&rsquo;ll continue so I can learn more.&rdquo;</p>
<p>And there we were, having a conversation.</p>
<p>I&rsquo;ve been very lucky to have had several <em>conversations</em> recently. It&rsquo;s reminding me of the times I had <em>interviews</em> and how those things went wrong down the road. It&rsquo;s great to end a conversation hearing the person you were conversing with say &ldquo;wow, the time flew by &hellip; but this felt so organic.&rdquo; You can enter a conversation with curiosity, and with a good conversational partner you can see where things go, make connections to your experience in the moment, change course or call up other experiences when they say &ldquo;well, that&rsquo;s not quite what we&rsquo;re dealing with here.&rdquo; That&rsquo;s much better than  pre-thinking a bunch of answers and poring over &ldquo;ten most common questions&rdquo; or (if Nigel or Chris are reading) &ldquo;you&rsquo;re trapped in a 20&rsquo; blender&rdquo; scenarios.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Exporting a DayOne commonplace book to org-roam</title>
      <link>https://mike.puddingtime.org/posts/2023-04-30-exporting-a-dayone-commonplace-book-to-org-roam/</link>
      <pubDate>Sun, 30 Apr 2023 00:00:00 +0000</pubDate><author>mike@puddingtime.org (mike)</author>
      <guid>https://mike.puddingtime.org/posts/2023-04-30-exporting-a-dayone-commonplace-book-to-org-roam/</guid>
      <description>A very cheap and cheerful DayOne-to-org-roam exporter and a link to a useful org-roam search function.</description>
      <content:encoded><![CDATA[<p>I&rsquo;ve kept some quotes in DayOne for years, and I frequently find myself coming back to them when I&rsquo;m writing, either to actually use them or to just remember an idea. I thought it&rsquo;d be handy to have them in my writing tool, so I used ChatGPT to help me write a quick exporter.</p>
<p>I&rsquo;ll include that below, but the other thing to link to before your eyes glaze over with a hunk of Ruby is <a href="https://org-roam.discourse.group/t/using-consult-ripgrep-with-org-roam-for-searching-notes/1226/7">this useful post on using consult-ripgrep with org-roam for fulltext search from the org-roam Discourse.</a> I&rsquo;ve been careful to tag everything I&rsquo;ve put in so far, but I won&rsquo;t get to that with this batch of files, and I usually remember a keyword, anyhow. So they&rsquo;re just tagged  with &ldquo;quotes&rdquo; and &ldquo;commonplace&rdquo; for now.</p>
<p>The script just consumes the JSON that DayOne&rsquo;s export function provides and coughs out formatted org-roam nodes. It uses DayOne&rsquo;s uuid&rsquo;s, but tacks on a few words &ndash; just in case? Maybe over time I&rsquo;ll go clean up the titles but for now they&rsquo;re just there to provide a hint when I search.</p>






<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="ch">#!/usr/bin/env ruby</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">require</span> <span class="s1">&#39;json&#39;</span>
</span></span><span class="line"><span class="cl"><span class="nb">require</span> <span class="s1">&#39;date&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Set these up before running</span>
</span></span><span class="line"><span class="cl"><span class="n">src_json</span> <span class="o">=</span> <span class="s2">&#34;/path/to/file.json&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">destination_dir</span> <span class="o">=</span> <span class="s2">&#34;/path/to/export/dir&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Read the JSON file</span>
</span></span><span class="line"><span class="cl"><span class="n">json_str</span> <span class="o">=</span> <span class="no">File</span><span class="o">.</span><span class="n">read</span><span class="p">(</span><span class="n">src_json</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Parse the JSON string</span>
</span></span><span class="line"><span class="cl"><span class="n">data</span> <span class="o">=</span> <span class="no">JSON</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="n">json_str</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">write_json_to_org_roam_files</span><span class="p">(</span><span class="n">json_str</span><span class="p">,</span> <span class="n">path</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">data</span> <span class="o">=</span> <span class="no">JSON</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="n">json_str</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">entries</span> <span class="o">=</span> <span class="n">data</span><span class="o">[</span><span class="s1">&#39;entries&#39;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">  <span class="n">entries</span><span class="o">.</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">entry</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">    <span class="n">created_date</span> <span class="o">=</span> <span class="no">DateTime</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="n">entry</span><span class="o">[</span><span class="s1">&#39;creationDate&#39;</span><span class="o">]</span><span class="p">)</span><span class="o">.</span><span class="n">strftime</span><span class="p">(</span><span class="s1">&#39;%Y%m%d%H%M%S&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">title</span> <span class="o">=</span> <span class="n">entry</span><span class="o">[</span><span class="s1">&#39;text&#39;</span><span class="o">].</span><span class="n">gsub</span><span class="p">(</span><span class="sr">/^&#34;/</span><span class="p">,</span> <span class="s1">&#39;&#39;</span><span class="p">)</span><span class="o">.</span><span class="n">gsub</span><span class="p">(</span><span class="sr">/&#34;$/</span><span class="p">,</span> <span class="s1">&#39;&#39;</span><span class="p">)</span><span class="o">.</span><span class="n">gsub</span><span class="p">(</span><span class="sr">/[^a-zA-Z0-9 ]/</span><span class="p">,</span> <span class="s1">&#39; &#39;</span><span class="p">)</span><span class="o">.</span><span class="n">squeeze</span><span class="p">(</span><span class="s1">&#39; &#39;</span><span class="p">)</span><span class="o">.</span><span class="n">strip</span> <span class="c1"># replace non-alphanumeric with space, remove extra spaces</span>
</span></span><span class="line"><span class="cl">    <span class="n">first_words</span> <span class="o">=</span> <span class="n">title</span><span class="o">.</span><span class="n">split</span><span class="o">.</span><span class="n">first</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="s1">&#39; &#39;</span><span class="p">)</span><span class="o">.</span><span class="n">gsub</span><span class="p">(</span><span class="sr">/[^a-zA-Z0-9]/</span><span class="p">,</span> <span class="s1">&#39;-&#39;</span><span class="p">)</span> <span class="c1"># replace non-alphanumeric with dash</span>
</span></span><span class="line"><span class="cl">    <span class="n">uuid</span> <span class="o">=</span> <span class="n">first_words</span> <span class="o">+</span> <span class="s1">&#39;-&#39;</span> <span class="o">+</span> <span class="n">entry</span><span class="o">[</span><span class="s1">&#39;uuid&#39;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">    <span class="n">content</span> <span class="o">=</span> <span class="no">JSON</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="n">entry</span><span class="o">[</span><span class="s1">&#39;richText&#39;</span><span class="o">]</span><span class="p">)</span><span class="o">[</span><span class="s1">&#39;contents&#39;</span><span class="o">].</span><span class="n">map</span> <span class="p">{</span> <span class="o">|</span><span class="n">c</span><span class="o">|</span> <span class="n">c</span><span class="o">[</span><span class="s1">&#39;text&#39;</span><span class="o">]</span> <span class="p">}</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">filename</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="si">#{</span><span class="n">created_date</span><span class="si">}</span><span class="s2">-</span><span class="si">#{</span><span class="n">first_words</span><span class="si">}</span><span class="s2">.org&#34;</span><span class="o">.</span><span class="n">gsub</span><span class="p">(</span><span class="sr">/^-/</span><span class="p">,</span> <span class="s1">&#39;&#39;</span><span class="p">)</span><span class="o">.</span><span class="n">gsub</span><span class="p">(</span><span class="sr">/-$/</span><span class="p">,</span> <span class="s1">&#39;&#39;</span><span class="p">)</span><span class="o">.</span><span class="n">gsub</span><span class="p">(</span><span class="sr">/-{2,}/</span><span class="p">,</span><span class="s1">&#39;-&#39;</span><span class="p">)</span> <span class="c1"># remove leading/trailing dashes and collapse multiple dashes</span>
</span></span><span class="line"><span class="cl">    <span class="n">file_content</span> <span class="o">=</span> <span class="o">&lt;&lt;~</span><span class="no">ORG_NODE</span><span class="o">.</span><span class="n">gsub</span><span class="p">(</span><span class="sr">/^\s*/</span><span class="p">,</span> <span class="s1">&#39;&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="ss">:PROPERTIES</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">      <span class="ss">:ID</span><span class="p">:</span> <span class="c1">#{uuid}</span>
</span></span><span class="line"><span class="cl">      <span class="ss">:END</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">      <span class="c1">#+title: #{first_words}</span>
</span></span><span class="line"><span class="cl">      <span class="c1">#+filetags: :commonplace:quotes:</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="c1">#{content}</span>
</span></span><span class="line"><span class="cl">    <span class="no">ORG_NODE</span>
</span></span><span class="line"><span class="cl">    <span class="n">file</span> <span class="o">=</span> <span class="no">File</span><span class="o">.</span><span class="n">open</span><span class="p">(</span><span class="no">File</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">filename</span><span class="p">),</span> <span class="s1">&#39;w&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">file</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">file_content</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">file</span><span class="o">.</span><span class="n">close</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Iterate over each entry</span>
</span></span><span class="line"><span class="cl"><span class="n">data</span><span class="o">[</span><span class="s1">&#39;entries&#39;</span><span class="o">].</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">entry</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="n">write_json_to_org_roam_files</span><span class="p">(</span><span class="n">json_str</span><span class="p">,</span> <span class="no">File</span><span class="o">.</span><span class="n">expand_path</span><span class="p">(</span><span class="n">destination_dir</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div>
]]></content:encoded>
    </item>
    <item>
      <title>An org-contacts source for lbdb</title>
      <link>https://mike.puddingtime.org/posts/2023-04-16-an-org-contacts-source-for-lbdb/</link>
      <pubDate>Sun, 16 Apr 2023 00:00:00 +0000</pubDate><author>mike@puddingtime.org (mike)</author>
      <guid>https://mike.puddingtime.org/posts/2023-04-16-an-org-contacts-source-for-lbdb/</guid>
      <description>I modified a Perl lbdb backend by ‪@publicvoit@graz.social ‬to use my org-contacts with mutt</description>
      <content:encoded><![CDATA[<p>This is a ruby-based back-end for <a href="https://www.spinnaker.de/lbdb/">The Little Brother&rsquo;s Database (lbdb)</a> that looks at a hard-coded <code>org-contacts</code> file. The idea comes from <a href="https://lists.gnu.org/archive/html/emacs-orgmode/2011-10/msg01059.html">a 2011 Perl implementation by Karl Voit</a>. Because I am not a Perl person, when it didn&rsquo;t work out of the box I converted it to Ruby.</p>
<p>And because I&rsquo;ve chosen to treat org-contacts as <code>TODO</code> items for purposes of remembering who to <code>PING</code>, <code>FOLLOWUP</code>, <code>SKED</code>, etc. it has to take the extra step of stripping those keywords from the returned name. Otherwise, my mails to Joe Grudd would be addressed to <code>FOLLOWUP Joe Grudd</code>.</p>
<p>That&rsquo;s a small bit of inelegance in my plaintext CRM setup:  As I&rsquo;ve figured out more about how <code>org-super-agenda</code> works, I&rsquo;ve had glimpses of how the plaintext CRM metadata could just be content in the <code>:PROPERTIES:</code> drawer, and hence invisible for purposes of tools like this, but a few other pieces of passive automation would have to become some sort of org-mode hook and I&rsquo;d lose the utility of tools like Beorg, which can&rsquo;t provide the automation of native Emacs.</p>
<p>For now, it&rsquo;s one of those &ldquo;this design isn&rsquo;t the cleanest, but it&rsquo;s simple and only creates a few easily solved problems&rdquo; things.</p>
<p>Here&rsquo;s the script itself. I put it in <code>~/bin</code> as <code>orgcontact.rb</code>:</p>






<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="ch">#!/usr/bin/env ruby</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Get the query string</span>
</span></span><span class="line"><span class="cl"><span class="n">query</span> <span class="o">=</span> <span class="no">ARGV</span><span class="o">[</span><span class="mi">0</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Set the path to your Org-contacts file</span>
</span></span><span class="line"><span class="cl"><span class="n">orgmodefile</span> <span class="o">=</span> <span class="s2">&#34;</span><span class="si">#{</span><span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;HOME&#39;</span><span class="o">]</span><span class="si">}</span><span class="s2">/org/contacts.org&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Read in the whole contact file</span>
</span></span><span class="line"><span class="cl"><span class="n">raw_contacts</span> <span class="o">=</span> <span class="no">File</span><span class="o">.</span><span class="n">read</span><span class="p">(</span><span class="n">orgmodefile</span><span class="p">)</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">&#34;</span><span class="se">\n</span><span class="s2">** &#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Iterate through each contact</span>
</span></span><span class="line"><span class="cl"><span class="n">raw_contacts</span><span class="o">.</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">contact</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="n">contact</span><span class="o">.</span><span class="n">match?</span><span class="p">(</span><span class="sr">/</span><span class="si">#{</span><span class="n">query</span><span class="si">}</span><span class="sr">/i</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Extract the name and email from the contact</span>
</span></span><span class="line"><span class="cl">    <span class="nb">name</span> <span class="o">=</span> <span class="n">contact</span><span class="o">[</span><span class="sr">/^[^\n]*/</span><span class="o">].</span><span class="n">gsub</span><span class="p">(</span><span class="sr">/(PING|INVITE|WRITE|PINGED|FOLLOWUP|SKED|NOTES|SCHEDULED|TIMEOUT|OK)\s+/i</span><span class="p">,</span> <span class="s2">&#34;&#34;</span><span class="p">)</span><span class="o">.</span><span class="n">strip</span>
</span></span><span class="line"><span class="cl">    <span class="n">email</span> <span class="o">=</span> <span class="n">contact</span><span class="o">[</span><span class="sr">/:EMAIL:\s+(.*)$/</span><span class="p">,</span> <span class="mi">1</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># Remove tags from the name</span>
</span></span><span class="line"><span class="cl">    <span class="nb">name</span><span class="o">.</span><span class="n">gsub!</span><span class="p">(</span><span class="sr">/:\S+:/</span><span class="p">,</span> <span class="s1">&#39;&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="nb">puts</span> <span class="s2">&#34;</span><span class="si">#{</span><span class="n">email</span><span class="si">}</span><span class="se">\t</span><span class="si">#{</span><span class="nb">name</span><span class="si">}</span><span class="se">\t</span><span class="s2">(org-contacts)&#34;</span> <span class="k">if</span> <span class="nb">name</span> <span class="o">&amp;&amp;</span> <span class="n">email</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div>
<p>Adding <code>m_org_contacts</code> to the <code>METHODS</code> setting then including a little wrapper in <code>~/.lbdbrc</code> doesn&rsquo;t follow the canonical advice on how to configure an lbdb backend, but it works, and it&rsquo;s one less file to put somewhere:</p>






<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">METHODS=&#34;m_osx_addressbook m_org_contacts&#34;
</span></span><span class="line"><span class="cl">MODULES_PATH=&#34;$MODULES_PATH $HOME/bin/lbdb&#34;
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">m_org_contacts_query() {
</span></span><span class="line"><span class="cl">   ~/bin/orgcontact.rb &#34;$1&#34;
</span></span><span class="line"><span class="cl">}</span></span></code></pre></div>
<p>So, the outcome is just:</p>
<ol>
<li>
<p>Start a new message in mutt and start typing the name/address/etc.</p>
</li>
<li>
<p>lbdb provides a list of matches from a few sources I&rsquo;ve set up: org-contacts and macOS address book</p>
</li>
<li>
<p><code>ENTER</code> to select a candidate</p>
<p>b</p>
</li>
</ol>
]]></content:encoded>
    </item>
    <item>
      <title>Writing elisp, Puppet code, and Ruby with ChatGPT.</title>
      <link>https://mike.puddingtime.org/posts/2023-04-10-elisp-puppet-ruby-chatgpt/</link>
      <pubDate>Mon, 10 Apr 2023 09:36:26 -0700</pubDate><author>mike@puddingtime.org (mike)</author>
      <guid>https://mike.puddingtime.org/posts/2023-04-10-elisp-puppet-ruby-chatgpt/</guid>
      <description>I finally took the time to play with ChatGPT to configure Emacs and write some Ruby.</description>
      <content:encoded><![CDATA[<p>I finally got curious enough, and had rendered a particular Google account unimportant enough, to give ChatGPT a try. I&rsquo;ll leave out the obvious goofs &ndash; asking it to deliver the Gettysburg Address in the style of Jeff Lebowski &ndash; and write up a few slightly more complex requests. Trying to get Eliza to say swears in eighth grade got boring fast, so nothing I asked it to do was borne out of a spirit of malice toward it. Just curiosity.</p>
<p>Overall, it was a land of contrasts. It did some things really well, or got to &ldquo;really well&rdquo; with a few followup requests. But on the other end of the spectrum it simply invented non-existent functionality then documented how to configure it.</p>
<p>I don&rsquo;t have any particularly original takeaways. Put me in the broad camp of &ldquo;human venality is going to be the real problem here,&rdquo; in ways it already is where other kinds of automation are concerned. We live in a society where people not only, like, read a newspaper in their Tesla, but literally crawl into the back seat and take naps.</p>
<p>The trivial tasks I fed it were limited, so the errors it made were obvious.  I wasn&rsquo;t trying to prove or disprove its &hellip; quality?  If you&rsquo;ve ever gotten into an argument with a word processor&rsquo;s grammar checker you&rsquo;ll sort of understand what you&rsquo;re up against in this case, too, with the improvement over that scenario being that instead of staring at the alleged bad grammar and ultimately learning you&rsquo;re either a &ldquo;leave the blue squiggles&rdquo; person or a &ldquo;can&rsquo;t tolerate any blue squiggles&rdquo; person, you can tell it you didn&rsquo;t care for the answer and it often takes the hint and fixes the problem.</p>
<p>But you have to know that you don&rsquo;t like the answer. So when it wrote a Puppet module for me and did so with insecure code, I noticed that and told it to do better and it did. Nothing got into production. When it gave me unreadable output for a train schedule, its first correction was at least obviously and intuitively wrong. Nothing got into production.</p>
<p>When I think about more complex code I&rsquo;ve written the human dimension of the problem stands out more. I once modeled website revenue for a Rails app, which involved a lot of sorting out when data was sampled vs. when it wasn&rsquo;t, recursive reasoning around costs, etc. and remember the many ways I could lose my train of thought and introduce stuff that looked right even to my experienced eye as a domain expert who knew the problem space as a practitioner <em>and</em> who was describing that expertise in code. No misunderstood requirements, no senior dev fighting with the product owner because reality is stupid, no UX designer arguing that beauty is truth. Just me, passionately invested in the problem, and still introducing errors I couldn&rsquo;t spot on review &hellip; that I could only spot with tedious testing.</p>
<p>Anyhow. Nothing new.</p>
<p>Some of the things I tried:</p>
<h3 id="create-an-org-capture-template-for-daily-health-logging">Create an org capture template for daily health logging</h3>
<p>It did the request perfectly, but slightly idiosyncratically for a log format (it stuck the date in the :DRAWER: instead of as an inactive date label in the head). Subsequent conversational-style prompts (&ldquo;okay, but could you include weight and hours slept?&rdquo;) caused it to add prompts for those to the template, then &ldquo;could you make that information that appends to an org table instead&rdquo; generated a capture template appropriate to appending to a table.</p>
<p>A similar request to create an org-capture template for Hugo blogging was mostly correct, but had a few glitches and the verbose instructions left out a key variable. It was debuggable in a few minutes.</p>
<p>I&rsquo;d score it pretty highly, and using it for that task is just straightforward &ldquo;get to the point of the tool, not the labor involved in the tool&rdquo; utility. Mostly I appreciated that it matched all the parens correctly.</p>
<h3 id="tell-me-how-to-configure-offlineimap-for-use-with-mutt-on-a-mac">Tell me how to configure OfflineIMAP for use with mutt on a Mac</h3>
<p>Did I say yesterday I don&rsquo;t believe in that? I did. But it was on my mind.</p>
<p>It did this pretty well, delivering instructions tailored to the specific &ldquo;mutt + offlineimap&rdquo; use case that were as good as any tutorial, missing only the things that are idiosyncratic to Fastmail, which I forgot to mention to it. I should have thought to tell it I was getting the errors I got to see how it handled that. Instead I just searched for them on DuckDuckGo and got unstuck.</p>
<p>Interestingly, and this happened one other time, I lost the original request to a glitch in the web app. When I restated it only slightly differently &hellip; not in a way that you&rsquo;d think would materially affect the output &hellip; it came up with something slightly different that didn&rsquo;t leave out a key detail the original output did.</p>
<p>I&rsquo;d score it highly again, minus maybe its willingness to make message deletion live by default without warning. Other examples and tutorials I found mention that.</p>
<p>I followed up by asking it to show me how to capture that configuration with Puppet and hiera, and it produced a serviceable OfflineIMAP module. It would have had me storing my credentials in the plain in the Hiera YAML. I responded that I preferred not to do that so it provided me with an example that used eyaml.</p>
<h3 id="tell-me-how-to-use-mutt-with-a-bayesian-filter">Tell me how to use mutt with a Bayesian filter</h3>
<p>It went completely off the rails, inventing filtering functionality for mutt and offering configuration examples that looked &ndash; mutt-like? &ndash; but inventing a configuration variable that doesn&rsquo;t exist, near as I can tell, to invoke a configuration file mutt wouldn&rsquo;t look for to support the non-existent functionality.</p>
<p>Maybe the interesting thing that came out of the interaction was the way it cooked up a mutt-like filtering setup could work in a way that seemed idiomatically correct for mutt. It just did the technical equivalent of adding a sixth finger to the left hand by assuming a generic bayesian filter of <em>some kind</em> and taking the plumbing to connect it for granted.</p>
<h3 id="how-would-i-go-about-adding-a-second-rss-feed-with-a-different-template-for-a-hugo-site">How would I go about adding a second RSS feed with a different template for a hugo site?</h3>
<p>Another miss, with very reasonable-looking instructions that simply didn&rsquo;t work as proposed. I am not sure how close it actually got. That&rsquo;s a problem I spent some time trying to solve yesterday and it seemed close but missed some connections between content and template.</p>
<p>It gave its configuration examples in TOML, and responded correctly to a conversational &ldquo;could I have those examples in YAML&rdquo; prompt.</p>
<h3 id="tell-me-how-to-write-a-sinatra-app-to-download-an-rss-feed-filter-it-for-keywords-and-save-another-version-of-the-feed">Tell me how to write a Sinatra app to download an rss feed, filter it for keywords and save another version of the feed</h3>
<p>Prompted by a Mastodon conversation yesterday about RSS readers that could be used to filter sponsored content posts.</p>
<p>Up front, it&rsquo;s sort of a weird request on my part: I was just lazily typing in part of an idea, including Sinatra as a dependency but not explaining why (my idea would involve creating a sort of RSS proxy with dynamic filtering during each client request). I just wanted to see what it would do without putting a lot of thought into it, or sticking around to narrow things down. So I got a ruby script wrapped in a Sinatra route, honoring the request whether it made a ton of sense or not.</p>
<p>I&rsquo;ve had it do a couple of Ruby scripts, and it remembered to include `require` lines for gems. It didn&rsquo;t do that in this case. I added them and it ran. I had to tweak a few things to get it to run without error, and the filtering didn&rsquo;t work.</p>
<p>I might goof around with this one a bit more to see where it&rsquo;s going wrong.</p>
<p>This lined up with things I&rsquo;ve read from others over the past few months: It produced a scaffold of mostly working code. If I decide to mess around with it more, I&rsquo;ll have been saved digging around for examples of the basic syntax for the assorted parts of the workflow that I know from experience are correct.</p>
<h3 id="what-s-a-good-strategy-for-switching-between-regular-and-ortholinear-keyboards">&ldquo;What&rsquo;s a good strategy for switching between regular and ortholinear keyboards?&rdquo;</h3>
<p>Useless. It provided very reasonable instructions for how to learn how to use an ortholinear keyboard, but didn&rsquo;t address the actual request.</p>
<h3 id="tell-me-how-to-write-a-ruby-script-to-download-train-times-for-a-given-stop-in-portland-oregon">&ldquo;Tell me how to write a ruby script to download train times for a given stop in Portland oregon&rdquo;</h3>
<p>It produced working code that showed me the next arrival times for the next train at my neighborhood stop. It provided the correct link to sign up with TriMet to get an API key.</p>
<p>&ldquo;A-&rdquo; because it didn&rsquo;t translate epoch time to human time in the output. When I asked it to do that, it tried to comply and used the obvious syntax to convert the integer it got back from the API, but got confused by the millisecond format.</p>
<p>I tried the code and replied with:</p>
<p>&ldquo;That time format is still incorrect. I think the timestamps include miliseconds.&rdquo;</p>
<p>It replied with:</p>
<p>&ldquo;You&rsquo;re correct, I apologize for the oversight. It appears that the TriMet API returns timestamps with milliseconds.&rdquo;</p>
<p>Then it produced working code that did a simple operation on the timestamp and passed it along to <code>Time</code> and <code>strftime</code> with correctly formatted output.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Macros to score mail in mutt</title>
      <link>https://mike.puddingtime.org/posts/2023-04-09-macros-to-score-mail-in-mutt/</link>
      <pubDate>Sun, 09 Apr 2023 10:11:27 -0700</pubDate><author>mike@puddingtime.org (mike)</author>
      <guid>https://mike.puddingtime.org/posts/2023-04-09-macros-to-score-mail-in-mutt/</guid>
      <description>Some ruby wired up to mutt macros allows for on-the-fly sender scoring and a color-coded message index.</description>
      <content:encoded><![CDATA[<p>In my early mutt days I used procmail and SpamAssassin on my desktop machines. I had a bunch of mutt macros set up to help score email and mark things as spam or not-spam. Once I started using IMAP on someone else&rsquo;s server that all fell by the wayside, but I really missed being able to score mail while I was processing it. I use Fastmail these days, and they support Sieve scripts, but haven&rsquo;t chosen to expose the API for that. That&rsquo;s too bad.</p>
<p>Mutt does, however, have pretty good facilities for scoring individual properties of a message, then do different color treatments for a given message&rsquo;s score in the index. Scoring and coloring can be driven by mutt&rsquo;s <a href="https://www.sendmail.org/~ca/email/mutt/manual-4.html#ss4.1">extensive list of search patterns.</a>.</p>
<p>This is a lightly annotated selection from the top of my mutt scores file, which I source in <code>~/.mutt/muttrc</code> at startup:</p>






<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl"># -*- mode: conf-unix -*-
</span></span><span class="line"><span class="cl"># Mutt scoring file
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">unscore * # start fresh
</span></span><span class="line"><span class="cl">score ~p +5 # Mail addressed to me or one of my alternates gets 5 points
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"># Date-based scoring penalties -- older things fall down
</span></span><span class="line"><span class="cl">score ~d&gt;3d -1
</span></span><span class="line"><span class="cl">score ~d&gt;7d -3
</span></span><span class="line"><span class="cl">score ~d&gt;14d -10
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">score &#34;~O&#34; +10 # &#34;Old&#34; in mutt is &#34;seen but unread&#34;. +10 so I don&#39;t miss it
</span></span><span class="line"><span class="cl">score &#34;~F&#34; +20 # flagged = +20 so it stays in the interesting view for a while, even if older
</span></span><span class="line"><span class="cl">score &#34;!~p ~d&gt;7d&#34; -10 # not for me directly, getting old, let it fade away
</span></span><span class="line"><span class="cl">score &#34;!~l&#34; +2 # to a known list, give it a bump</span></span></code></pre></div>
<p>Given some scores, you can add color treatments:</p>






<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">color index white default &#34;~n 6-9&#34;
</span></span><span class="line"><span class="cl">color index white red &#34;~f trimet ~s Service\\ Alert&#34;
</span></span><span class="line"><span class="cl">color index brightblack default &#34;~f trimet ~s Service\\ Alert ~d&gt;1d&#34;
</span></span><span class="line"><span class="cl">color index magenta default &#34;~n &lt;5&#34;
</span></span><span class="line"><span class="cl">color index brightyellow default &#34;~n &gt;10&#34;
</span></span><span class="line"><span class="cl">color index brightred default &#34;~n &gt;19&#34;</span></span></code></pre></div>
<p>Adding a score to the scores file manually is no big deal. Mine tends to include specific people, work email lists, things I definitely don&rsquo;t want to miss (e.g. service alerts from TriMet), and things that I should keep seeing but want to deemphasize (like business messages from hosting providers). But it&rsquo;d also be nice to do some scoring as I&rsquo;m reviewing mail to deal with new important senders, necessary nuisances, etc.</p>
<p>So I wrote some ruby and mutt macros that let me pipe a given message into a script that extracts the sender and writes a +/- modifier to their score in a <code>scored</code> file I source alongside my <code>scores</code> file.</p>






<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="ch">#!/usr/bin/env ruby</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">require</span> <span class="s1">&#39;mail&#39;</span>
</span></span><span class="line"><span class="cl"><span class="nb">require</span> <span class="s1">&#39;tempfile&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Wants a +/- integer, e.g. +20</span>
</span></span><span class="line"><span class="cl"><span class="n">score</span> <span class="o">=</span> <span class="no">ARGV</span><span class="o">.</span><span class="n">first</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">score_file</span> <span class="o">=</span> <span class="s2">&#34;/Users/mph/.mutt/scored&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">msg</span> <span class="o">=</span> <span class="no">Tempfile</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="s1">&#39;msg&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">msg</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="vg">$stdin</span><span class="o">.</span><span class="n">read</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">mail</span> <span class="o">=</span> <span class="no">Mail</span><span class="o">.</span><span class="n">read</span><span class="p">(</span><span class="n">msg</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">from</span> <span class="o">=</span> <span class="n">mail</span><span class="o">.</span><span class="n">from</span><span class="o">.</span><span class="n">first</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="no">File</span><span class="o">.</span><span class="n">open</span><span class="p">(</span><span class="n">score_file</span><span class="p">,</span> <span class="s2">&#34;a&#34;</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="n">f</span><span class="o">|</span> <span class="n">f</span><span class="o">.</span><span class="n">write</span> <span class="s2">&#34;score ~f</span><span class="si">#{</span><span class="n">from</span><span class="si">}</span><span class="s2"> </span><span class="si">#{</span><span class="n">score</span><span class="si">}</span><span class="se">\n</span><span class="s2">&#34;</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">msg</span><span class="o">.</span><span class="n">close</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">msg</span><span class="o">.</span><span class="n">unlink</span></span></span></code></pre></div>
<p>Pretty much just &ldquo;parse the mail for the first <code>from</code> address, wrap it in a mutt <code>score</code> stanza, tack that on to the end of <code>scored</code>.&rdquo;</p>
<p>Then I added some macros:</p>






<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl"># Score messages
</span></span><span class="line"><span class="cl">macro index,browser .sp &#34;&lt;pipe-entry&gt;~/.mutt/mailscore.rb +5\n&lt;enter-command&gt;source ~/.mutt/scored&lt;enter&gt;&#34; # score sender +5
</span></span><span class="line"><span class="cl">macro index,browser .sP &#34;&lt;pipe-entry&gt;~/.mutt/mailscore.rb +20\n&lt;enter-command&gt;source ~/.mutt/scored&lt;enter&gt;&#34; # score sender +20
</span></span><span class="line"><span class="cl">macro index,browser .sm &#34;&lt;pipe-entry&gt;~/.mutt/mailscore.rb -5\n&lt;enter-command&gt;source ~/.mutt/scored&lt;enter&gt;&#34; # score sender -5
</span></span><span class="line"><span class="cl">macro index,browser .sM &#34;&lt;pipe-entry&gt;~/.mutt/mailscore.rb -20\n&lt;enter-command&gt;source ~/.mutt/scored&lt;enter&gt;&#34; # score sender -20</span></span></code></pre></div>
<p>They just pipe the message into the Ruby script with a scoring argument, the script modifies <code>scored</code>, then the macro re-sources <code>scored</code> so the index reflects the new scoring.</p>
<p>Tapping <code>.sp</code> (score plus) in the index scores a sender up, <code>.sm</code> (score minus) scores them down. <code>.sP</code> and <code>.sM</code> are embiggened scores.</p>
<p>The way mutt works, it doesn&rsquo;t accumulate these pluses and minuses, so when it parses the <code>scored</code> file it&rsquo;s just going to use the last modifier I added. That seems fine, since they&rsquo;re broad and there are other scoring factors at work. Over time the file will get long. Maybe the right thing to do is read the file in, try to match on a sender, and overwrite their entry, just to cut down on duplication.</p>
<p>The net effect of the current system is that the index is color-coded into four broad tiers: The super important, the pretty important, kinda average and should be read, and not particularly interesting and maybe worth an unsubscribe.</p>
]]></content:encoded>
    </item>
    <item>
      <title>mutt to org-contacts</title>
      <link>https://mike.puddingtime.org/posts/2023-04-15-mutt-to-org-contacts/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><author>mike@puddingtime.org (mike)</author>
      <guid>https://mike.puddingtime.org/posts/2023-04-15-mutt-to-org-contacts/</guid>
      <description>A little script to copy address information from mutt messages into an org-contacts file.</description>
      <content:encoded><![CDATA[<p>Part of making the whole <a href="https://mike.puddingtime.org/posts/20230413-making-a-plaintext-personal-crm-with-org-contacts/">plaintext CRM</a>  thing work involves capture: Getting contact information into the database. I made a capture template for <a href="https://beorgapp.com/">beorg</a> to make it easier to capture a contact on mobile, but I have a bunch of recent email contacts I&rsquo;ve wanted to add, too, and no easy way to get them out of mutt.</p>
<p>For all of its talents, mutt doesn&rsquo;t choose to expose any message variables to its macro functionality. If you want to extract a sender&rsquo;s name or email address or whatever, the message has to be piped. I put something like that together to <a href="https://mike.puddingtime.org/posts/2023-04-09-macros-to-score-mail-in-mutt/">add senders to a mutt score file</a>, and there are a few other things out there in the mutt ecosystem, like lbdb, that capture email addresses. lbdb could have been great for this, but its whole point is capture to a database of its own.</p>
<p>I&rsquo;ve been poking around with getting ChatGPT to write little utilities for me, so I put this problem to it, asking for a Ruby script that could pipe a message through formail to extract the needed name and email address.</p>
<p>It took a second prompt &ndash; initially it just wrote the code to process the input without specifying the input &ndash; but it produced something workable to get the two pieces of data out, and I added an org-contacts template as a <code>HERE</code> doc the script writes into a <code>mutt_contacts.org</code> file. I could have sent it straight to <code>contacts.org</code> but prefer to automate into buffer files to keep the chance of conflicts, sync or otherwise, to a minimum.</p>
<p>The accompanying mutt macro looks like this:</p>
<p><code>macro index,pager .oc &quot;|~/.mutt/org_contact.rb\n&quot;</code></p>
<p>I tap <code>.oc</code> when positioned on a message and it pipes into the script.</p>
<p>I&rsquo;d like to extend the macro to include a quick note, but for now it just adds the <code>NOTES</code> todo state and schedules the entry a day out to remind me to put something in there soon: The new entries turn up in my org-mode agenda for processing and re-filing into my <code>contacts.org</code> file.</p>
<p>And it worked well enough: Once I set it up I was able to run through and add nine or ten new contacts in a few seconds, then visit them in org-mode and refile them all.</p>
<p>And yeah &hellip;still holding the line against adding an MUA to Emacs proper. I just don&rsquo;t want to do the whole OfflineIMAP thing or similar, and I am still keeping things relatively simple. Adding <code>org-caldav</code> today was a small step in the wrong direction, but I like having calendar entries in my org agenda, it syncs relatively quickly once it does the initial download, and it has no system dependencies.</p>






<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="ch">#!/usr/bin/env ruby</span>
</span></span><span class="line"><span class="cl"><span class="nb">require</span> <span class="s1">&#39;open3&#39;</span>
</span></span><span class="line"><span class="cl"><span class="nb">require</span> <span class="s1">&#39;date&#39;</span>
</span></span><span class="line"><span class="cl"><span class="n">contacts_file</span> <span class="o">=</span> <span class="s2">&#34;~/org/mutt_contacts.org&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">message_contents</span> <span class="o">=</span> <span class="vg">$stdin</span><span class="o">.</span><span class="n">read</span>
</span></span><span class="line"><span class="cl"><span class="c1"># Define the command to extract headers with formail</span>
</span></span><span class="line"><span class="cl"><span class="n">command</span> <span class="o">=</span> <span class="s1">&#39;formail -X From: -X Sender: -X Reply-To: -x To: -x Cc: -x Bcc:&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Execute the command and capture the output</span>
</span></span><span class="line"><span class="cl"><span class="n">output</span><span class="p">,</span> <span class="n">status</span> <span class="o">=</span> <span class="no">Open3</span><span class="o">.</span><span class="n">capture2</span><span class="p">(</span><span class="n">command</span><span class="p">,</span> <span class="ss">stdin_data</span><span class="p">:</span> <span class="n">message_contents</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="n">status</span><span class="o">.</span><span class="n">success?</span>
</span></span><span class="line"><span class="cl">  <span class="n">email</span> <span class="o">=</span> <span class="kp">nil</span>
</span></span><span class="line"><span class="cl">  <span class="nb">name</span> <span class="o">=</span> <span class="kp">nil</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># Parse the output and extract the email and name from the From, Sender, and Reply-To headers</span>
</span></span><span class="line"><span class="cl">  <span class="n">output</span><span class="o">.</span><span class="n">lines</span><span class="o">.</span><span class="n">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">line</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">    <span class="k">case</span> <span class="n">line</span>
</span></span><span class="line"><span class="cl">    <span class="k">when</span> <span class="sr">/^From:\s*(.*)$/i</span><span class="p">,</span> <span class="sr">/^Sender:\s*(.*)$/i</span><span class="p">,</span> <span class="sr">/^Reply-To:\s*(.*)$/i</span>
</span></span><span class="line"><span class="cl">      <span class="n">from</span> <span class="o">=</span> <span class="vg">$1</span><span class="o">.</span><span class="n">strip</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="c1"># Extract the email and name from the From, Sender, or Reply-To header</span>
</span></span><span class="line"><span class="cl">      <span class="k">if</span> <span class="n">from</span> <span class="o">=~</span> <span class="sr">/(.+?)\s*&lt;(.+?)&gt;/</span>
</span></span><span class="line"><span class="cl">        <span class="n">email</span> <span class="o">=</span> <span class="vg">$2</span>
</span></span><span class="line"><span class="cl">        <span class="nb">name</span> <span class="o">=</span> <span class="vg">$1</span><span class="o">.</span><span class="n">strip</span>
</span></span><span class="line"><span class="cl">      <span class="k">else</span>
</span></span><span class="line"><span class="cl">        <span class="n">email</span> <span class="o">=</span> <span class="n">from</span>
</span></span><span class="line"><span class="cl">        <span class="nb">name</span> <span class="o">=</span> <span class="s2">&#34;No Name Found&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="c1"># Found the email and name, so break the loop</span>
</span></span><span class="line"><span class="cl">      <span class="k">break</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">org_template</span> <span class="o">=</span> <span class="o">&lt;&lt;~</span><span class="no">TEMPLATE</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="o">**</span> <span class="no">NOTES</span> <span class="c1">#{name} :mutt:</span>
</span></span><span class="line"><span class="cl">  <span class="ss">SCHEDULED</span><span class="p">:</span> <span class="c1">#{(Date.today + 1).strftime(&#34;%Y-%m-%d&#34;)}</span>
</span></span><span class="line"><span class="cl">  <span class="ss">:PROPERTIES</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">  <span class="ss">:EMAIL</span><span class="p">:</span> <span class="c1">#{email}</span>
</span></span><span class="line"><span class="cl">  <span class="ss">:PHONE</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">  <span class="ss">:BIRTHDAY</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">  <span class="ss">:CONTACTED</span><span class="p">:</span> <span class="c1">#{Date.today.strftime(&#34;%Y-%m-%d&#34;)}</span>
</span></span><span class="line"><span class="cl">  <span class="ss">:END</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="no">TEMPLATE</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="no">File</span><span class="o">.</span><span class="n">open</span><span class="p">(</span><span class="n">contacts_file</span><span class="p">,</span> <span class="s2">&#34;a&#34;</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="n">f</span><span class="o">|</span> <span class="n">f</span><span class="o">.</span><span class="n">write</span> <span class="n">org_template</span><span class="p">}</span></span></span></code></pre></div>
]]></content:encoded>
    </item>
  </channel>
</rss>
