<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>elisp on
A Scripter's Notes</title><link>https://scripter.co/categories/elisp/</link><description>Recent content in elisp
on A Scripter's Notes</description><language>en-us</language><managingEditor>kaushal.modi@gmail.com (Kaushal Modi)</managingEditor><webMaster>kaushal.modi@gmail.com (Kaushal Modi)</webMaster><lastBuildDate>Wed, 22 Apr 2026 08:24:58 -0400</lastBuildDate><generator>Hugo -- gohugo.io</generator><docs>https://validator.w3.org/feed/docs/rss2.html</docs><atom:link href="https://scripter.co/categories/elisp/index.xml" rel="self" type="application/rss+xml"/><item><title>View GitHub Pull Requests in Magit</title><link>https://scripter.co/view-github-pull-requests-in-magit/</link><description>&lt;blockquote>How to view GitHub Pull Request branches locally in the cloned repo,
and more importantly, how to do that automatically from within Emacs.&lt;/blockquote>&lt;div class="ox-hugo-toc toc">
&lt;div class="heading">Table of Contents&lt;/div>
&lt;ul>
&lt;li>&lt;a href="#locally-creating-a-branch-for-a-pr">Locally creating a branch for a PR&lt;/a>&lt;/li>
&lt;li>&lt;a href="#getting-references-to-all-pull-requests">Getting references to all Pull Requests&lt;/a>&lt;/li>
&lt;li>&lt;a href="#automatically-adding-pr-refs">Automatically adding PR refs&lt;/a>&lt;/li>
&lt;li>&lt;a href="#summary">Summary&lt;/a>&lt;/li>
&lt;li>&lt;a href="#references">References&lt;/a>&lt;/li>
&lt;/ul>
&lt;/div>
&lt;!--endtoc-->
&lt;p>I have a few public projects in git repos, but I don&amp;rsquo;t get that much
traffic in &lt;em>Pull Requests (PR)&lt;/em>
&lt;span class="sidenote-number">&lt;small class="sidenote">
Gitlab calls these &lt;em>Merge Requests&lt;/em> or MRs.
&lt;/small>&lt;/span>
. So when I need to add additional commits to a PR, I would just add
the PR author&amp;rsquo;s &lt;em>remote&lt;/em> to my local repo, &lt;em>check out&lt;/em> their PR
branch, add my own commits and then merge that to my project&amp;rsquo;s &lt;em>main&lt;/em>
branch.&lt;/p>
&lt;p>As these occurrences were few and far apart, I didn&amp;rsquo;t have a need to
view the &lt;em>PR branches&lt;/em> directly from within Emacs/Magit. Though, I
somehow knew that each GitHub Pull Request&amp;rsquo;s &lt;em>HEAD&lt;/em> got assigned a &lt;a href="https://git-scm.com/book/en/v2/Git-Internals-Git-References">git
&lt;strong>reference&lt;/strong>&lt;/a>. But I didn&amp;rsquo;t need to use that knowledge until today
😃.&lt;/p>
&lt;p>Today, when discussing &lt;a href="https://github.com/protesilaos/denote/pull/20">PR # 20&lt;/a> on &lt;a href="https://protesilaos.com/">Prot&amp;rsquo;s&lt;/a> &lt;a href="https://protesilaos.com/emacs/denote">Denote&lt;/a> package&amp;rsquo;s GitHub
mirror
&lt;span class="sidenote-number">&lt;small class="sidenote">
Prot uses &lt;a href="https://git.sr.ht/~protesilaos/denote">SourceHut&lt;/a> as the primary git forge for his Emacs
packages. But I am glad that he doesn&amp;rsquo;t mind the activity in Issues
and Pull Requests on the GitHub mirror.
&lt;/small>&lt;/span>
, he wrote &lt;a href="https://github.com/protesilaos/denote/pull/20#issuecomment-1164676013">this comment&lt;/a>:&lt;/p>
&lt;blockquote>
&lt;p>Now I just need to figure out how best to incorporate your changes
into the &lt;code>org-id&lt;/code> branch so I can add the final bits. I am not too
familiar with the PR workflow &amp;hellip;&lt;/p>
&lt;/blockquote>
&lt;p>.. and that inspired this post today.&lt;/p>
&lt;h2 id="locally-creating-a-branch-for-a-pr">Locally creating a branch for a PR&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#locally-creating-a-branch-for-a-pr">#&lt;/a>&lt;/h2>
&lt;p>From the &lt;a href="https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/checking-out-pull-requests-locally">GitHub docs&lt;/a>, the &lt;code>git&lt;/code> command to create a local branch for a
PR is this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git fetch origin pull/ID/head:BRANCHNAME
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>I have the &lt;code>denote&lt;/code> package cloned from its GitHub mirror. So the
&lt;strong>origin&lt;/strong> remote&amp;rsquo;s &lt;strong>url&lt;/strong> is &lt;code>https://github.com/protesilaos/denote&lt;/code>.&lt;/p>
&lt;div class="note">
&lt;p>Make sure that the remote name used in this command is pointing to a
GitHub repo, and not a mirror forge like GitLab or SourceHut.&lt;/p>
&lt;/div>
&lt;p>When I ran the below command, I got a new branch &lt;strong>pr-20&lt;/strong> pointing to
the latest commit of that PR:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-shell" data-lang="shell">&lt;span class="line">&lt;span class="cl">git fetch origin pull/20/head:pr-20
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Awesome!&lt;/p>
&lt;div class="verse">
&lt;p>.. But that wasn&amp;rsquo;t good enough&lt;br />
    .. Now I wanted more&lt;br />
        .. I didn&amp;rsquo;t want to manually create a branch for each PR.&lt;br />&lt;/p>
&lt;/div>
&lt;h2 id="getting-references-to-all-pull-requests">Getting references to all Pull Requests&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#getting-references-to-all-pull-requests">#&lt;/a>&lt;/h2>
&lt;p>Now that I was on that quest of &amp;ldquo;I want more&amp;rdquo;, it didn&amp;rsquo;t take me long
to re-discover &lt;a href="https://oremacs.com/2015/03/11/git-tricks/#illusion-2-quickly-get-github-pull-requests-on-your-system">this 7-year old nugget&lt;/a> by &lt;a href="https://oremacs.com">Oleh Krehel&lt;/a>. Here are the
relevant bits from that post:&lt;/p>
&lt;ol>
&lt;li>Open the local repo&amp;rsquo;s &lt;code>.git/config&lt;/code> file.&lt;/li>
&lt;li>Find the &lt;code>[remote &amp;quot;origin&amp;quot;]&lt;/code> section&lt;/li>
&lt;li>Modify it by adding this one line with &lt;strong>pull&lt;/strong> refs. &lt;em>This is the
same for all GitHub repositories.&lt;/em>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-cfg" data-lang="cfg">&lt;span class="line">&lt;span class="cl">&lt;span class="k">[remote &amp;#34;origin&amp;#34;]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="na">url&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s">https://github.com/USER/REPO.git
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> fetch = +refs/heads/*:refs/remotes/origin/*
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line hl">&lt;span class="cl">&lt;span class="s"> fetch = +refs/pull/*/head:refs/pull/origin/*&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;/ol>
&lt;p>With that edit in place, when I did &lt;kbd>l&lt;/kbd> &lt;kbd>a&lt;/kbd> (show
the log for all git references), followed by &lt;kbd>f&lt;/kbd> &lt;kbd>a&lt;/kbd>
(fetch all the remotes) in the Magit, I could see the references to
the &lt;code>denote&lt;/code> repo&amp;rsquo;s PRs!&lt;/p>
&lt;p>&lt;a id="figure--magit-denote-pr-refs">&lt;/a>&lt;/p>
&lt;figure>
&lt;img src="https://scripter.co/view-github-pull-requests-in-magit/pr-20-ref.png" alt="Figure 1: Viewing PR references from denote package&amp;rsquo;s GitHub repo"/> &lt;figcaption>
&lt;p>
&lt;span class="figure-number">Figure 1: &lt;/span>Viewing PR references from &lt;code>denote&lt;/code> package&amp;rsquo;s GitHub repo
&lt;/p>
&lt;/figcaption>&lt;/figure>
&lt;div class="verse">
&lt;p>.. But that still wasn&amp;rsquo;t good enough&lt;br />
    .. I didn&amp;rsquo;t want to manually edit the &lt;code>.git/config&lt;/code> in each repo.&lt;br />&lt;/p>
&lt;/div>
&lt;h2 id="automatically-adding-pr-refs">Automatically adding PR refs&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#automatically-adding-pr-refs">#&lt;/a>&lt;/h2>
&lt;p>Of course, I wasn&amp;rsquo;t the first one to think of this!&lt;/p>
&lt;p>Another Emacs veteran &lt;a href="https://endlessparentheses.com/">Artur Malabarba&lt;/a> had already had this covered
also around &lt;a href="https://endlessparentheses.com/automatically-configure-magit-to-access-github-prs.html">7 years back&lt;/a>. Coincidentally, that post was written as a
response to that same blog post by Oleh where he shared the above
&lt;code>.git/config&lt;/code> tip.&lt;/p>
&lt;p>In that post, Artur shares an Emacs Lisp function that uses Magit
functions like &lt;code>magit-get&lt;/code>, &lt;code>magit-get-all&lt;/code> and &lt;code>magit-git-string&lt;/code> to
auto-add the &lt;code>fetch = +refs/pull/*/head:refs/pull/origin/*&lt;/code> line in
the &lt;code>.git/config&lt;/code>. This magic happens after checking that the &lt;strong>origin&lt;/strong>
remote points to a GitHub repo, and if that line doesn&amp;rsquo;t already
exist.&lt;/p>
&lt;p>Here, I am lightly modifying the function shared in that post so that
the &lt;strong>origin&lt;/strong> remote name is not hard-coded
&lt;span class="sidenote-number">&lt;small class="sidenote">
The reason is that sometimes, I name the original remote as &lt;strong>upstream&lt;/strong>
and my fork as &lt;strong>fork&lt;/strong>, and I might have no remote named &lt;strong>origin&lt;/strong>.
&lt;/small>&lt;/span>
. Credit for the main logic in this code still goes to Artur.&lt;/p>
&lt;p>&lt;a id="code-snippet--add-PR-fetch-ref">&lt;/a>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-emacs-lisp" data-lang="emacs-lisp">&lt;span class="line">&lt;span class="cl">&lt;span class="p">(&lt;/span>&lt;span class="nb">defun&lt;/span> &lt;span class="nv">modi/add-PR-fetch-ref&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kp">&amp;amp;optional&lt;/span> &lt;span class="nv">remote-name&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;If refs/pull is not defined on a GH repo, define it.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">If REMOTE-NAME is not specified, it defaults to the &lt;/span>&lt;span class="ss">`remote&amp;#39;&lt;/span>&lt;span class="s"> set
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">for the \&amp;#34;main\&amp;#34; or \&amp;#34;master\&amp;#34; branch.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">let*&lt;/span> &lt;span class="p">((&lt;/span>&lt;span class="nv">remote-name&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">or&lt;/span> &lt;span class="nv">remote-name&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">magit-get&lt;/span> &lt;span class="s">&amp;#34;branch&amp;#34;&lt;/span> &lt;span class="s">&amp;#34;main&amp;#34;&lt;/span> &lt;span class="s">&amp;#34;remote&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">magit-get&lt;/span> &lt;span class="s">&amp;#34;branch&amp;#34;&lt;/span> &lt;span class="s">&amp;#34;master&amp;#34;&lt;/span> &lt;span class="s">&amp;#34;remote&amp;#34;&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">remote-url&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">magit-get&lt;/span> &lt;span class="s">&amp;#34;remote&amp;#34;&lt;/span> &lt;span class="nv">remote-name&lt;/span> &lt;span class="s">&amp;#34;url&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">fetch-refs&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">and&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">stringp&lt;/span> &lt;span class="nv">remote-url&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">string-match&lt;/span> &lt;span class="s">&amp;#34;github&amp;#34;&lt;/span> &lt;span class="nv">remote-url&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">magit-get-all&lt;/span> &lt;span class="s">&amp;#34;remote&amp;#34;&lt;/span> &lt;span class="nv">remote-name&lt;/span> &lt;span class="s">&amp;#34;fetch&amp;#34;&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">;; https://oremacs.com/2015/03/11/git-tricks/&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">fetch-address&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">format&lt;/span> &lt;span class="s">&amp;#34;+refs/pull/*/head:refs/pull/%s/*&amp;#34;&lt;/span> &lt;span class="nv">remote-name&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">when&lt;/span> &lt;span class="nv">fetch-refs&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">unless&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">member&lt;/span> &lt;span class="nv">fetch-address&lt;/span> &lt;span class="nv">fetch-refs&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">magit-git-string&lt;/span> &lt;span class="s">&amp;#34;config&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;--add&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">format&lt;/span> &lt;span class="s">&amp;#34;remote.%s.fetch&amp;#34;&lt;/span> &lt;span class="nv">remote-name&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">fetch-address&lt;/span>&lt;span class="p">)))))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">(&lt;/span>&lt;span class="nv">add-hook&lt;/span> &lt;span class="ss">&amp;#39;magit-mode-hook&lt;/span> &lt;span class="nf">#&amp;#39;&lt;/span>&lt;span class="nv">modi/add-PR-fetch-ref&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="src-block-caption">
&lt;span class="src-block-number">&lt;a href="#code-snippet--add-PR-fetch-ref">Code Snippet 1&lt;/a>:&lt;/span>
Function to auto-add GitHub PR references to the repo's &lt;code>.git/config&lt;/code>
&lt;/div>
&lt;h2 id="summary">Summary&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#summary">#&lt;/a>&lt;/h2>
&lt;p>With the above snippet added to your Emacs config and evaluated, each
time you visit a repo cloned from GitHub in the Magit Status buffer
(&lt;code>M-x magit-status&lt;/code>), the PR refs will get auto-added to that repo&amp;rsquo;s
&lt;code>.git/config&lt;/code> if needed.&lt;/p>
&lt;p>After that, you can easily view the commits from all the PRs by doing
&lt;kbd>l&lt;/kbd> &lt;kbd>a&lt;/kbd> &lt;kbd>f&lt;/kbd> &lt;kbd>a&lt;/kbd>.&lt;/p>
&lt;h2 id="references">References&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#references">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://oremacs.com/2015/03/11/git-tricks/#illusion-2-quickly-get-github-pull-requests-on-your-system">oremacs &amp;ndash; Some git/magit/github tricks&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://endlessparentheses.com/automatically-configure-magit-to-access-github-prs.html">endlessparentheses &amp;ndash; Automatically configure Magit to access Github PRs&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/reviewing-changes-in-pull-requests/checking-out-pull-requests-locally">GitHub &amp;ndash; Checking out PR branches locally&lt;/a>&lt;/li>
&lt;/ul></description><author>Kaushal.Modi@fakeEmailToMakeValidatorHappy.com (Kaushal Modi)</author><category domain="https://scripter.co/categories/emacs">emacs</category><category domain="https://scripter.co/categories/elisp">elisp</category><category domain="https://scripter.co/tags/git">git</category><category domain="https://scripter.co/tags/magit">magit</category><category domain="https://scripter.co/tags/100daystooffload">100DaysToOffload</category><category domain="https://scripter.co/tags/github">github</category><category domain="https://scripter.co/tags/git-reference">git-reference</category><guid>https://scripter.co/view-github-pull-requests-in-magit/</guid><pubDate>Thu, 23 Jun 2022 17:51:00 -0400</pubDate></item><item><title>Emacs Lisp: Advice Combinators</title><link>https://scripter.co/emacs-lisp-advice-combinators/</link><description>&lt;blockquote>My diagrammatic take on summarizing all the Emacs advice combinators.&lt;/blockquote>&lt;div class="ox-hugo-toc toc">
&lt;div class="heading">Table of Contents&lt;/div>
&lt;ul>
&lt;li>&lt;a href="#overview-on-using-advices">Overview on using advices&lt;/a>&lt;/li>
&lt;li>&lt;a href="#advice-combinators--before">&lt;span class="section-num">1&lt;/span> &lt;code>:before&lt;/code>&lt;/a>&lt;/li>
&lt;li>&lt;a href="#advice-combinators--after">&lt;span class="section-num">2&lt;/span> &lt;code>:after&lt;/code>&lt;/a>&lt;/li>
&lt;li>&lt;a href="#advice-combinators--override">&lt;span class="section-num">3&lt;/span> &lt;code>:override&lt;/code>&lt;/a>&lt;/li>
&lt;li>&lt;a href="#advice-combinators--around">&lt;span class="section-num">4&lt;/span> &lt;code>:around&lt;/code>&lt;/a>&lt;/li>
&lt;li>&lt;a href="#advice-combinators--before-while">&lt;span class="section-num">5&lt;/span> &lt;code>:before-while&lt;/code>&lt;/a>&lt;/li>
&lt;li>&lt;a href="#advice-combinators--before-until">&lt;span class="section-num">6&lt;/span> &lt;code>:before-until&lt;/code>&lt;/a>&lt;/li>
&lt;li>&lt;a href="#advice-combinators--after-while">&lt;span class="section-num">7&lt;/span> &lt;code>:after-while&lt;/code>&lt;/a>&lt;/li>
&lt;li>&lt;a href="#advice-combinators--after-until">&lt;span class="section-num">8&lt;/span> &lt;code>:after-until&lt;/code>&lt;/a>&lt;/li>
&lt;li>&lt;a href="#advice-combinators--filter-args">&lt;span class="section-num">9&lt;/span> &lt;code>:filter-args&lt;/code>&lt;/a>&lt;/li>
&lt;li>&lt;a href="#advice-combinators--filter-return">&lt;span class="section-num">10&lt;/span> &lt;code>:filter-return&lt;/code>&lt;/a>&lt;/li>
&lt;li>&lt;a href="#summary">Summary&lt;/a>&lt;/li>
&lt;li>&lt;a href="#advice-combinators--references">References&lt;/a>&lt;/li>
&lt;/ul>
&lt;/div>
&lt;!--endtoc-->
&lt;p>If you have read some of my earlier posts, you would know that I
really enjoy using the Emacs Advice system 😃.&lt;/p>
&lt;p>The &amp;ldquo;advice&amp;rdquo; feature lets you add to the existing definition of a
function, by &lt;em>advising the function&lt;/em>. This is a cleaner method than
redefining the whole function, because it&amp;rsquo;s easier to debug and if you
don&amp;rsquo;t need it, you can just &lt;em>remove&lt;/em> the advice.&lt;/p>
&lt;div class="note">
&lt;p>You can jump to the &lt;a href="#advice-combinators--references">References section&lt;/a> below if you need to look at
the related sections in the Emacs Lisp Manual.&lt;/p>
&lt;/div>
&lt;h2 id="overview-on-using-advices">Overview on using advices&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#overview-on-using-advices">#&lt;/a>&lt;/h2>
&lt;p>I do not plan to write a tutorial on how to write advices in
Emacs-Lisp, but here&amp;rsquo;s a 3-second primer:&lt;/p>
&lt;dl>
&lt;dt>To add an advice&lt;/dt>
&lt;dd>&lt;code class="code-inline language-emacs-lisp">&lt;span class="p">(&lt;/span>&lt;span class="nv">advice-add&lt;/span> &lt;span class="ss">&amp;#39;original-fn&lt;/span> &lt;span class="nv">&amp;lt;combinator&amp;gt;&lt;/span> &lt;span class="nf">#&amp;#39;&lt;/span>&lt;span class="nv">advising-fn&lt;/span>&lt;span class="p">)&lt;/span>&lt;/code>&lt;/dd>
&lt;dt>To remove an advice&lt;/dt>
&lt;dd>&lt;code class="code-inline language-emacs-lisp">&lt;span class="p">(&lt;/span>&lt;span class="nv">advice-remove&lt;/span> &lt;span class="ss">&amp;#39;original-fn&lt;/span> &lt;span class="nf">#&amp;#39;&lt;/span>&lt;span class="nv">advising-fn&lt;/span>&lt;span class="p">)&lt;/span>&lt;/code>&lt;/dd>
&lt;/dl>
&lt;p>This article attempts to briefly describe different ways of advising a
function, using 10 different &lt;em>combinators&lt;/em>. If you have never used
advices in your Emacs config, don&amp;rsquo;t worry. I am hopeful that the
diagrams in this post and the examples linked for some of the
combinators in the Summary section makes this concept a bit easier to
assimilate.&lt;/p>
&lt;dl>
&lt;dt>Diagram Legend&lt;/dt>
&lt;dd>&lt;ul>
&lt;li>Initial black circle: Original Fn input arguments&lt;/li>
&lt;li>Yellow box: Original Fn&lt;/li>
&lt;li>Gray box: Advising Fn&lt;/li>
&lt;li>Black circle inside a white circle: Return values&lt;/li>
&lt;/ul>
&lt;/dd>
&lt;/dl>
&lt;h2 id="advice-combinators--before">&lt;span class="section-num">1&lt;/span> &lt;code>:before&lt;/code>&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#advice-combinators--before">#&lt;/a>&lt;/h2>
&lt;p>&lt;a id="figure--advice-combinators-before">&lt;/a>&lt;/p>
&lt;figure>
&lt;img src="https://scripter.co/emacs-lisp-advice-combinators/before.svg" alt="Figure 1: :before advice"/> &lt;figcaption>
&lt;p>
&lt;span class="figure-number">Figure 1: &lt;/span>&lt;strong>:before&lt;/strong> advice
&lt;/p>
&lt;/figcaption>&lt;/figure>
&lt;h2 id="advice-combinators--after">&lt;span class="section-num">2&lt;/span> &lt;code>:after&lt;/code>&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#advice-combinators--after">#&lt;/a>&lt;/h2>
&lt;p>&lt;a id="figure--advice-combinators-after">&lt;/a>&lt;/p>
&lt;figure>
&lt;img src="https://scripter.co/emacs-lisp-advice-combinators/after.svg" alt="Figure 2: :after advice"/> &lt;figcaption>
&lt;p>
&lt;span class="figure-number">Figure 2: &lt;/span>&lt;strong>:after&lt;/strong> advice
&lt;/p>
&lt;/figcaption>&lt;/figure>
&lt;h2 id="advice-combinators--override">&lt;span class="section-num">3&lt;/span> &lt;code>:override&lt;/code>&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#advice-combinators--override">#&lt;/a>&lt;/h2>
&lt;p>&lt;a id="figure--advice-combinators-override">&lt;/a>&lt;/p>
&lt;figure>
&lt;img src="https://scripter.co/emacs-lisp-advice-combinators/override.svg" alt="Figure 3: :override advice"/> &lt;figcaption>
&lt;p>
&lt;span class="figure-number">Figure 3: &lt;/span>&lt;strong>:override&lt;/strong> advice
&lt;/p>
&lt;/figcaption>&lt;/figure>
&lt;h2 id="advice-combinators--around">&lt;span class="section-num">4&lt;/span> &lt;code>:around&lt;/code>&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#advice-combinators--around">#&lt;/a>&lt;/h2>
&lt;p>&lt;a id="figure--advice-combinators-around">&lt;/a>&lt;/p>
&lt;figure>
&lt;img src="https://scripter.co/emacs-lisp-advice-combinators/around.svg" alt="Figure 4: :around advice"/> &lt;figcaption>
&lt;p>
&lt;span class="figure-number">Figure 4: &lt;/span>&lt;strong>:around&lt;/strong> advice
&lt;/p>
&lt;/figcaption>&lt;/figure>
&lt;h2 id="advice-combinators--before-while">&lt;span class="section-num">5&lt;/span> &lt;code>:before-while&lt;/code>&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#advice-combinators--before-while">#&lt;/a>&lt;/h2>
&lt;p>&lt;a id="figure--advice-combinators-before-while">&lt;/a>&lt;/p>
&lt;figure>
&lt;img src="https://scripter.co/emacs-lisp-advice-combinators/before-while.svg" alt="Figure 5: :before-while advice"/> &lt;figcaption>
&lt;p>
&lt;span class="figure-number">Figure 5: &lt;/span>&lt;strong>:before-while&lt;/strong> advice
&lt;/p>
&lt;/figcaption>&lt;/figure>
&lt;h2 id="advice-combinators--before-until">&lt;span class="section-num">6&lt;/span> &lt;code>:before-until&lt;/code>&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#advice-combinators--before-until">#&lt;/a>&lt;/h2>
&lt;p>&lt;a id="figure--advice-combinators-before-until">&lt;/a>&lt;/p>
&lt;figure>
&lt;img src="https://scripter.co/emacs-lisp-advice-combinators/before-until.svg" alt="Figure 6: :before-until advice"/> &lt;figcaption>
&lt;p>
&lt;span class="figure-number">Figure 6: &lt;/span>&lt;strong>:before-until&lt;/strong> advice
&lt;/p>
&lt;/figcaption>&lt;/figure>
&lt;h2 id="advice-combinators--after-while">&lt;span class="section-num">7&lt;/span> &lt;code>:after-while&lt;/code>&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#advice-combinators--after-while">#&lt;/a>&lt;/h2>
&lt;p>&lt;a id="figure--advice-combinators-after-while">&lt;/a>&lt;/p>
&lt;figure>
&lt;img src="https://scripter.co/emacs-lisp-advice-combinators/after-while.svg" alt="Figure 7: :after-while advice"/> &lt;figcaption>
&lt;p>
&lt;span class="figure-number">Figure 7: &lt;/span>&lt;strong>:after-while&lt;/strong> advice
&lt;/p>
&lt;/figcaption>&lt;/figure>
&lt;h2 id="advice-combinators--after-until">&lt;span class="section-num">8&lt;/span> &lt;code>:after-until&lt;/code>&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#advice-combinators--after-until">#&lt;/a>&lt;/h2>
&lt;p>&lt;a id="figure--advice-combinators-after-until">&lt;/a>&lt;/p>
&lt;figure>
&lt;img src="https://scripter.co/emacs-lisp-advice-combinators/after-until.svg" alt="Figure 8: :after-until advice"/> &lt;figcaption>
&lt;p>
&lt;span class="figure-number">Figure 8: &lt;/span>&lt;strong>:after-until&lt;/strong> advice
&lt;/p>
&lt;/figcaption>&lt;/figure>
&lt;h2 id="advice-combinators--filter-args">&lt;span class="section-num">9&lt;/span> &lt;code>:filter-args&lt;/code>&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#advice-combinators--filter-args">#&lt;/a>&lt;/h2>
&lt;p>&lt;a id="figure--advice-combinators-filter-args">&lt;/a>&lt;/p>
&lt;figure>
&lt;img src="https://scripter.co/emacs-lisp-advice-combinators/filter-args.svg" alt="Figure 9: :filter-args advice"/> &lt;figcaption>
&lt;p>
&lt;span class="figure-number">Figure 9: &lt;/span>&lt;strong>:filter-args&lt;/strong> advice
&lt;/p>
&lt;/figcaption>&lt;/figure>
&lt;h2 id="advice-combinators--filter-return">&lt;span class="section-num">10&lt;/span> &lt;code>:filter-return&lt;/code>&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#advice-combinators--filter-return">#&lt;/a>&lt;/h2>
&lt;p>&lt;a id="figure--advice-combinators-filter-return">&lt;/a>&lt;/p>
&lt;figure>
&lt;img src="https://scripter.co/emacs-lisp-advice-combinators/filter-return.svg" alt="Figure 10: :filter-return advice"/> &lt;figcaption>
&lt;p>
&lt;span class="figure-number">Figure 10: &lt;/span>&lt;strong>:filter-return&lt;/strong> advice
&lt;/p>
&lt;/figcaption>&lt;/figure>
&lt;h2 id="summary">Summary&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#summary">#&lt;/a>&lt;/h2>
&lt;p>Here&amp;rsquo;s a concise summary of what each advice combinator does. For
brevity, the &lt;em>advising function&lt;/em> is called &lt;em>A&lt;/em> and the &lt;em>original
function&lt;/em> is called &lt;em>O&lt;/em>.&lt;/p>
&lt;div class="note">
&lt;p>Once you click on any of the example posts, search for &lt;code>advice-add&lt;/code> on
that page to find the code example.&lt;/p>
&lt;/div>
&lt;p>&lt;a id="table--advice-combinator-summary">&lt;/a>&lt;/p>
&lt;div class="table-caption">
&lt;span class="table-number">&lt;a href="#table--advice-combinator-summary">Table 1&lt;/a>:&lt;/span>
Summary of what each advice combinator means
&lt;/div>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Combinator&lt;/th>
&lt;th>Description&lt;/th>
&lt;th>Example&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>:before&lt;/code>&lt;/td>
&lt;td>&lt;em>A&lt;/em> is called before &lt;em>O&lt;/em>. &lt;em>O&lt;/em> args and return values are not modified.&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>:after&lt;/code>&lt;/td>
&lt;td>&lt;em>A&lt;/em> is called after &lt;em>O&lt;/em>. &lt;em>O&lt;/em> args and return values are not modified.&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>:override&lt;/code>&lt;/td>
&lt;td>&lt;em>A&lt;/em> is called in lieu of &lt;em>O&lt;/em>. &lt;em>A&lt;/em> gets the same args as &lt;em>O&lt;/em>.&lt;/td>
&lt;td>&lt;a href="https://scripter.co/zero-html-validation-errors/">Zero HTML Validation Errors!&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>:around&lt;/code>&lt;/td>
&lt;td>&lt;em>A&lt;/em> is called in lieu of &lt;em>O&lt;/em>. &lt;em>A&lt;/em> gets &lt;em>O&lt;/em> fn + &lt;em>O&lt;/em> args as args.&lt;/td>
&lt;td>&lt;a href="https://scripter.co/using-emacs-advice-to-silence-messages-from-functions/">Using Emacs advice to silence messages from functions&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>:before-while&lt;/code>&lt;/td>
&lt;td>&lt;em>A&lt;/em> is called first. If it returns non-nil, &lt;em>O&lt;/em> is called.&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>:before-until&lt;/code>&lt;/td>
&lt;td>&lt;em>A&lt;/em> is called first. If it returns nil, &lt;em>O&lt;/em> is called.&lt;/td>
&lt;td>&lt;a href="https://scripter.co/org-show-only-post-subtree-headings/">Org: Show only Post subtree headings&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>:after-while&lt;/code>&lt;/td>
&lt;td>&lt;em>O&lt;/em> is called first. If it returns non-nil, &lt;em>A&lt;/em> is called.&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>:after-until&lt;/code>&lt;/td>
&lt;td>&lt;em>O&lt;/em> is called first. If it returns nil, &lt;em>A&lt;/em> is called.&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>:filter-args&lt;/code>&lt;/td>
&lt;td>&lt;em>A&lt;/em> is called first. &lt;em>O&lt;/em> is called next with return value from &lt;em>A&lt;/em> as input.&lt;/td>
&lt;td>&lt;a href="https://scripter.co/narrowing-the-author-column-in-magit/">Narrowing the Author column in Magit&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>:filter-return&lt;/code>&lt;/td>
&lt;td>&lt;em>O&lt;/em> is called first. &lt;em>A&lt;/em> is called next with return value from &lt;em>O&lt;/em> as input.&lt;/td>
&lt;td>&lt;a href="https://scripter.co/zero-html-validation-errors/">Zero HTML Validation Errors!&lt;/a>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>If you have any feedback on how these diagrams can be made easier to
understand, please let me know.&lt;/p>
&lt;h2 id="advice-combinators--references">References&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#advice-combinators--references">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.gnu.org/software/emacs/manual/html_node/elisp/Advising-Functions.html" title="Emacs Lisp: (info &amp;quot;(elisp) Advising Functions&amp;quot;)">Elisp Info: Advising Functions&lt;/a>
&lt;ul>
&lt;li>&lt;a href="https://www.gnu.org/software/emacs/manual/html_node/elisp/Advice-Combinators.html" title="Emacs Lisp: (info &amp;quot;(elisp) Advice Combinators&amp;quot;)">Elisp Info: Advice Combinators&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul></description><author>Kaushal.Modi@fakeEmailToMakeValidatorHappy.com (Kaushal Modi)</author><category domain="https://scripter.co/categories/emacs">emacs</category><category domain="https://scripter.co/categories/elisp">elisp</category><category domain="https://scripter.co/tags/100daystooffload">100DaysToOffload</category><category domain="https://scripter.co/tags/advice">advice</category><category domain="https://scripter.co/tags/plantuml">plantuml</category><guid>https://scripter.co/emacs-lisp-advice-combinators/</guid><pubDate>Fri, 17 Jun 2022 21:30:00 -0400</pubDate></item><item><title>Org: Show only Post subtree headings</title><link>https://scripter.co/org-show-only-post-subtree-headings/</link><description>&lt;blockquote>How to define a custom &lt;code>org-global-cycle&lt;/code>-like command that collapses
only the Org subtrees with specific properties.&lt;/blockquote>&lt;div class="ox-hugo-toc toc">
&lt;div class="heading">Table of Contents&lt;/div>
&lt;ul>
&lt;li>&lt;a href="#org-global-cycle">Org Global Cycle&lt;/a>&lt;/li>
&lt;li>&lt;a href="#skeleton-of-only-post-headings">Skeleton of only Post headings&lt;/a>&lt;/li>
&lt;li>&lt;a href="#the-collapse-all-posts-function">The &amp;ldquo;Collapse All Posts&amp;rdquo; function&lt;/a>&lt;/li>
&lt;li>&lt;a href="#binding-with-c-u-c-c-tab">Binding with &lt;code>C-u C-c TAB&lt;/code>&lt;/a>&lt;/li>
&lt;li>&lt;a href="#result">Result&lt;/a>&lt;/li>
&lt;/ul>
&lt;/div>
&lt;!--endtoc-->
&lt;p>I start this post by introducing what the Org mode global cycling
command does, what kind of subtree folding I actually need, and then
share the solution with code snippets.&lt;/p>
&lt;h2 id="org-global-cycle">Org Global Cycle&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#org-global-cycle">#&lt;/a>&lt;/h2>
&lt;p>Org mode has a built-in &lt;code>org-global-cycle&lt;/code> command that you might be
familiar with. It&amp;rsquo;s bound by default to the &lt;code>S-TAB&lt;/code> key. Each time
this command is called, the Org buffer will cycle through these
states:&lt;/p>
&lt;ol>
&lt;li>Overview: Show only the Level 1 headings and collapse everything
underneath.&lt;/li>
&lt;li>Contents: Show only the Org headings and collapse all the content.&lt;/li>
&lt;li>Show All: Expand all the headings and show their contents too.&lt;/li>
&lt;/ol>
&lt;p>If a numeric prefix &lt;em>N&lt;/em> is used with this command, it will show only
the Org headings up to Level &lt;em>N&lt;/em>. For example, &lt;code>C-2 S-TAB&lt;/code> will show
only the headings up to Level 2.&lt;/p>
&lt;p>This is a really helpful command, but I needed something different ..&lt;/p>
&lt;h2 id="skeleton-of-only-post-headings">Skeleton of only Post headings&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#skeleton-of-only-post-headings">#&lt;/a>&lt;/h2>
&lt;p>I maintain most of this website&amp;rsquo;s content in a single Org file. I have
dozens of blog posts organized in Org subtrees, which I further
organize under &amp;ldquo;category&amp;rdquo; headings .. It kind of looks like the below
mock-up:
&lt;span class="sidenote-number">&lt;small class="sidenote">
It&amp;rsquo;s amazing how many features PlantUML has. If you are interested in
creating diagrams like these, check out the &lt;a href="https://plantuml.com/salt">PlantUML Salt&lt;/a> syntax.
&lt;/small>&lt;/span>&lt;/p>
&lt;p>&lt;a id="figure--post-subtrees-collapsed-mockup">&lt;/a>&lt;/p>
&lt;figure>
&lt;img src="https://scripter.co/org-show-only-post-subtree-headings/post-subtrees.svg" alt="Figure 1: Post Subtrees at arbitrary heading levels"/> &lt;figcaption>
&lt;p>
&lt;span class="figure-number">Figure 1: &lt;/span>Post Subtrees at arbitrary heading levels
&lt;/p>
&lt;/figcaption>&lt;/figure>
&lt;p>As we can see,&lt;/p>
&lt;ul>
&lt;li>All the post subtrees are not at Level 1 headings.&lt;/li>
&lt;li>They are also not at a fixed Level &lt;em>N&lt;/em>.&lt;/li>
&lt;li>The heading level of the post depends on how many parent categories
that post has (and that will also change over time).&lt;/li>
&lt;/ul>
&lt;p>I needed to basically show everything leading up to a post subtree
heading, and then collapse all the content under that post; even the
sub-headings.&lt;/p>
&lt;h2 id="the-collapse-all-posts-function">The &amp;ldquo;Collapse All Posts&amp;rdquo; function&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#the-collapse-all-posts-function">#&lt;/a>&lt;/h2>
&lt;p>The &lt;code>modi/org-hugo-collapse-all-posts&lt;/code> function defined below meets
the above requirement:&lt;/p>
&lt;ol>
&lt;li>It first widens the whole buffer and expands all the headings.&lt;/li>
&lt;li>Then it loops through all the headings and collapses all the &lt;em>post
subtrees&lt;/em> i.e. all the subtrees that have the &lt;code>EXPORT_FILE_NAME&lt;/code>
property set. This is where I use the &lt;a href="https://scripter.co/looping-through-org-mode-headings/">&lt;code>org-map-entries&lt;/code>&lt;/a> magic.&lt;/li>
&lt;li>Finally it looks for Org headings that begin with &amp;ldquo;Footnotes&amp;rdquo; or
&amp;ldquo;COMMENT&amp;rdquo; and collapses them as well.&lt;/li>
&lt;/ol>
&lt;p>I am using the development version of Org mode (version 9.6, yet to be
released as of &lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2022-06-15 Wed&amp;gt;&lt;/span>&lt;/span>) which has the new &lt;code>org-fold&lt;/code>
library. This library obsoletes the use of &lt;code>outline.el&lt;/code> library and
other &lt;em>code-folding&lt;/em> related functions in Org mode. So &lt;code>cl-flet&lt;/code> is
used to create function symbol aliases that use the &lt;code>org-fold-*&lt;/code>
functions if available, otherwise they fall back to the legacy
functions.&lt;/p>
&lt;p>&lt;a id="code-snippet--collapse-all-posts-fn">&lt;/a>&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="hl">&lt;span class="lnt">19
&lt;/span>&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="hl">&lt;span class="lnt">23
&lt;/span>&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;span class="lnt">25
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-emacs-lisp" data-lang="emacs-lisp">&lt;span class="line">&lt;span class="cl">&lt;span class="p">(&lt;/span>&lt;span class="nb">defun&lt;/span> &lt;span class="nv">modi/org-hugo-collapse-all-posts&lt;/span> &lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;Collapse all post subtrees in the current buffer.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">Also collapse the Footnotes subtree and COMMENT subtrees if
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">present.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">A post subtree is one that has the EXPORT_FILE_NAME property
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">set.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">interactive&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">cl-flet&lt;/span> &lt;span class="p">((&lt;/span>&lt;span class="nv">show-all&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">fboundp&lt;/span> &lt;span class="ss">&amp;#39;org-fold-show-all&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nf">#&amp;#39;&lt;/span>&lt;span class="nv">org-fold-show-all&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nf">#&amp;#39;&lt;/span>&lt;span class="nv">org-show-all&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">hide-subtree&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">fboundp&lt;/span> &lt;span class="ss">&amp;#39;org-fold-hide-subtree&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nf">#&amp;#39;&lt;/span>&lt;span class="nv">org-fold-hide-subtree&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nf">#&amp;#39;&lt;/span>&lt;span class="nv">outline-hide-subtree&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">widen&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">show-all&lt;/span> &lt;span class="o">&amp;#39;&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">headings&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">;; Collapse all the post subtrees (ones with EXPORT_FILE_NAME&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">;; property set).&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line hl">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">org-map-entries&lt;/span> &lt;span class="nf">#&amp;#39;&lt;/span>&lt;span class="nv">hide-subtree&lt;/span> &lt;span class="s">&amp;#34;EXPORT_FILE_NAME&amp;lt;&amp;gt;\&amp;#34;\&amp;#34;&amp;#34;&lt;/span> &lt;span class="ss">&amp;#39;file&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">;; Also hide Footnotes and comments.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">save-excursion&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">goto-char&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">point-min&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line hl">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">while&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">re-search-forward&lt;/span> &lt;span class="s">&amp;#34;^\\(\\* Footnotes\\|\\*+ COMMENT\\)&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="no">nil&lt;/span> &lt;span class="nb">:noerror&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">hide-subtree&lt;/span>&lt;span class="p">)))))&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="src-block-caption">
&lt;span class="src-block-number">&lt;a href="#code-snippet--collapse-all-posts-fn">Code Snippet 1&lt;/a>:&lt;/span>
Function that collapses all the post subtrees in the current buffer
&lt;/div>
&lt;h2 id="binding-with-c-u-c-c-tab">Binding with &lt;code>C-u C-c TAB&lt;/code>&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#binding-with-c-u-c-c-tab">#&lt;/a>&lt;/h2>
&lt;p>The function is ready, but let&amp;rsquo;s now add a bit of convenience to it.&lt;/p>
&lt;p>If a point is under a subtree, &lt;code>C-c TAB&lt;/code> will collapse that subtree
while showing only Level 1 headings, and if a numeric prefix is used,
it will show only those many levels of headings. I decided to bind the
above function to &lt;code>C-u C-c TAB&lt;/code> because,&lt;/p>
&lt;ol>
&lt;li>The behavior of &lt;code>modi/org-hugo-collapse-all-posts&lt;/code> falls in the
same category as that of &lt;code>C-c TAB&lt;/code>.&lt;/li>
&lt;li>The &lt;code>C-u C-c ..&lt;/code> binding rolls off the fingers pretty
nicely 😃.&lt;/li>
&lt;/ol>
&lt;p>This &lt;em>binding&lt;/em> is achieved using one of my favorite Emacs features
.. the &lt;strong>advice&lt;/strong> system. The &lt;code>:before-until&lt;/code> &lt;a href="https://www.gnu.org/software/emacs/manual/html_node/elisp/Advice-Combinators.html" title="Emacs Lisp: (info &amp;quot;(elisp) Advice Combinators&amp;quot;)">Advice Combinator&lt;/a> is used
here, which means that if the &lt;em>advising&lt;/em> function (below) returns a
&lt;em>nil&lt;/em>, the &lt;em>advised&lt;/em> or the original function &lt;code>org-ctrl-c-tab&lt;/code> is not
called.&lt;/p>
&lt;p>The &lt;em>advising&lt;/em> function below detects if the &lt;code>C-u&lt;/code> prefix argument is
used. If it is, the &lt;code>modi/org-hugo-collapse-all-posts&lt;/code> function is
called, otherwise the original &lt;code>org-ctrl-c-tab&lt;/code> function is called.&lt;/p>
&lt;p>&lt;a id="code-snippet--collapse-all-posts-binding">&lt;/a>&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="hl">&lt;span class="lnt">4
&lt;/span>&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;span class="lnt">7
&lt;/span>&lt;span class="lnt">8
&lt;/span>&lt;span class="lnt">9
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-emacs-lisp" data-lang="emacs-lisp">&lt;span class="line">&lt;span class="cl">&lt;span class="p">(&lt;/span>&lt;span class="nb">defun&lt;/span> &lt;span class="nv">modi/org-ctrl-c-tab-advice&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kp">&amp;amp;rest&lt;/span> &lt;span class="nv">args&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;Run &lt;/span>&lt;span class="ss">`modi/org-hugo-collapse-all-posts&amp;#39;&lt;/span>&lt;span class="s"> when
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">doing \\[universal-argument] \\[org-ctrl-c-tab].&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line hl">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">let&lt;/span> &lt;span class="p">((&lt;/span>&lt;span class="nv">do-not-run-orig-fn&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">equal&lt;/span> &lt;span class="o">&amp;#39;&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">4&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nv">current-prefix-arg&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">when&lt;/span> &lt;span class="nv">do-not-run-orig-fn&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">modi/org-hugo-collapse-all-posts&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">do-not-run-orig-fn&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">(&lt;/span>&lt;span class="nv">advice-add&lt;/span> &lt;span class="ss">&amp;#39;org-ctrl-c-tab&lt;/span> &lt;span class="nb">:before-until&lt;/span> &lt;span class="nf">#&amp;#39;&lt;/span>&lt;span class="nv">modi/org-ctrl-c-tab-advice&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="src-block-caption">
&lt;span class="src-block-number">&lt;a href="#code-snippet--collapse-all-posts-binding">Code Snippet 2&lt;/a>:&lt;/span>
Bind &lt;code>C-u C-c TAB&lt;/code> to call &lt;code>modi/org-hugo-collapse-all-posts&lt;/code>
&lt;/div>
&lt;h2 id="result">Result&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#result">#&lt;/a>&lt;/h2>
&lt;p>After evaluating the above two snippets, when I do &lt;code>C-u C-c TAB&lt;/code> in
my &amp;ldquo;blog posts&amp;rdquo; Org buffer, I see this:&lt;/p>
&lt;p>&lt;a id="figure--post-subtrees-collapsed">&lt;/a>&lt;/p>
&lt;figure>
&lt;a href="post-subtrees-collapsed.png">
&lt;img src="https://scripter.co/org-show-only-post-subtree-headings/post-subtrees-collapsed.png" alt="Figure 2: My &amp;ldquo;blog posts&amp;rdquo; Org buffer showing only the Post subtree headings"/> &lt;/a>&lt;figcaption>
&lt;p>
&lt;span class="figure-number">Figure 2: &lt;/span>My &amp;ldquo;blog posts&amp;rdquo; Org buffer showing only the Post subtree headings
&lt;/p>
&lt;/figcaption>&lt;/figure>
&lt;p>It matches &lt;a href="#figure--post-subtrees-collapsed-mockup">that earlier mockup&lt;/a> &amp;mdash; Mission accomplished! 💯&lt;/p></description><author>Kaushal.Modi@fakeEmailToMakeValidatorHappy.com (Kaushal Modi)</author><category domain="https://scripter.co/categories/emacs">emacs</category><category domain="https://scripter.co/categories/org">org</category><category domain="https://scripter.co/categories/elisp">elisp</category><category domain="https://scripter.co/tags/100daystooffload">100DaysToOffload</category><category domain="https://scripter.co/tags/subtree">subtree</category><category domain="https://scripter.co/tags/looping">looping</category><category domain="https://scripter.co/tags/advice">advice</category><guid>https://scripter.co/org-show-only-post-subtree-headings/</guid><pubDate>Thu, 16 Jun 2022 00:21:00 -0400</pubDate></item><item><title>Looping through Org mode headings</title><link>https://scripter.co/looping-through-org-mode-headings/</link><description>&lt;blockquote>Using the &lt;code>org-map-entries&lt;/code> API to loop through selected or all
headings in an Org file.&lt;/blockquote>&lt;div class="ox-hugo-toc toc">
&lt;div class="heading">Table of Contents&lt;/div>
&lt;ul>
&lt;li>&lt;a href="#org-map-entries-api">&lt;code>org-map-entries&lt;/code> API&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#match-strings">&lt;em>MATCH&lt;/em> strings&lt;/a>&lt;/li>
&lt;li>&lt;a href="#comparison-types">Comparison Types&lt;/a>&lt;/li>
&lt;li>&lt;a href="#other-notes">Other notes&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#example-modifying-a-property-in-all-headings">Example: Modifying a property in all headings&lt;/a>&lt;/li>
&lt;li>&lt;a href="#org-map-entries-references">&lt;code>org-map-entries&lt;/code> References&lt;/a>&lt;/li>
&lt;/ul>
&lt;/div>
&lt;!--endtoc-->
&lt;p>&lt;a href="https://framapiaf.org/@postroutine/108313152514542145">Below question&lt;/a> on Mastodon by the user &lt;a href="https://framapiaf.org/@postroutine">@postroutine&lt;/a> inspired me to
write this post:&lt;/p>
&lt;blockquote>
&lt;p>I got a lot of Org-Mode headings and I want to modify their properties
(add, remove, edit). Is there a function to do the same modifications
on each heading?&lt;/p>
&lt;/blockquote>
&lt;p>I think that the best solution to that question is using the
&lt;strong>org-map-entries&lt;/strong> function.&lt;/p>
&lt;p>But somehow when replying to that question then, that wasn&amp;rsquo;t what
first came to my mind! .. when ironically that function is the &lt;a href="https://github.com/kaushalmodi/ox-hugo/blob/2b169e5e83d608e80f4faee9d681b98d87041f58/ox-hugo.el#L4781-L4788">main
function&lt;/a> that enables my preferred &lt;em>subtree-based flow&lt;/em> in &lt;code>ox-hugo&lt;/code>
😆. So I am writing this post to better ingrain the following
concept in myself ..&lt;/p>
&lt;div class="note">
&lt;p>If you need to loop through headings in an Org buffer, and especially
if you &lt;strong>need to modify that buffer&lt;/strong> in the process, use
&lt;strong>org-map-entries&lt;/strong>&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>.&lt;/p>
&lt;/div>
&lt;p>Next,&lt;/p>
&lt;ol>
&lt;li>I will give a give introduction to the &lt;code>org-map-entries&lt;/code> API.&lt;/li>
&lt;li>Then provide a super-short solution to the above question.&lt;/li>
&lt;/ol>
&lt;h2 id="org-map-entries-api">&lt;code>org-map-entries&lt;/code> API&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#org-map-entries-api">#&lt;/a>&lt;/h2>
&lt;p>I will give only a broad level overview on how to use this function. I
would encourage the reader to refer to the resources at the end of
this post to learn more about it.&lt;/p>
&lt;p>So let&amp;rsquo;s start by looking at this function&amp;rsquo;s signature:&lt;/p>
&lt;p>&lt;a id="code-snippet--org-map-entries-signature">&lt;/a>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-emacs-lisp" data-lang="emacs-lisp">&lt;span class="line">&lt;span class="cl">&lt;span class="p">(&lt;/span>&lt;span class="nv">org-map-entries&lt;/span> &lt;span class="nv">FUNC&lt;/span> &lt;span class="kp">&amp;amp;optional&lt;/span> &lt;span class="nv">MATCH&lt;/span> &lt;span class="nv">SCOPE&lt;/span> &lt;span class="kp">&amp;amp;rest&lt;/span> &lt;span class="nv">SKIP&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="src-block-caption">
&lt;span class="src-block-number">&lt;a href="#code-snippet--org-map-entries-signature">Code Snippet 1&lt;/a>:&lt;/span>
Signature of the &lt;code>org-map-entries&lt;/code> function
&lt;/div>
&lt;p>The &lt;code>org-map-entries&lt;/code> function iterates through all the headings
meeting the &lt;em>MATCH&lt;/em> criteria in the determined &lt;em>SCOPE&lt;/em>, and then calls
the specified function &lt;em>FUNC&lt;/em> at each of those headings.&lt;/p>
&lt;ul>
&lt;li>The &lt;code>FUNC&lt;/code> function accepts &lt;strong>no&lt;/strong> arguments and is called at the
beginning of each Org heading.&lt;/li>
&lt;li>The optional second argument &lt;em>MATCH&lt;/em> is either &lt;em>nil&lt;/em>, &lt;code>t&lt;/code> or a
&lt;em>search string&lt;/em>.
&lt;ul>
&lt;li>If &lt;em>MATCH&lt;/em> is &lt;em>nil&lt;/em> or &lt;code>t&lt;/code>, &lt;strong>all&lt;/strong> headings will be visited by the
iteration and &lt;em>FUNC&lt;/em> will be called on all of them.&lt;/li>
&lt;li>But if &lt;em>MATCH&lt;/em> is a string, the headings will first be filtered
based on that string and then the &lt;em>FUNC&lt;/em> will be called on only
those.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>For explanations on the optional &lt;em>SCOPE&lt;/em> and &lt;em>SKIP&lt;/em> arguments, see
&lt;a href="https://orgmode.org/manual/Using-the-Mapping-API.html" title="Emacs Lisp: (info &amp;quot;(org) Using the Mapping API&amp;quot;)">Org Info: Using the Mapping API&lt;/a> or &lt;kbd>C-h&lt;/kbd> &lt;kbd>f&lt;/kbd> &lt;code>org-map-entries&lt;/code> from
within Emacs.&lt;/li>
&lt;/ul>
&lt;p>Here&amp;rsquo;s a typical &lt;code>org-map-entries&lt;/code> call that loops through &lt;strong>all&lt;/strong> the
headings in the &lt;strong>visible&lt;/strong> buffer: &lt;code class="code-inline language-emacs-lisp">&lt;span class="p">(&lt;/span>&lt;span class="nv">org-map-entries&lt;/span> &lt;span class="nf">#&amp;#39;&lt;/span>&lt;span class="nv">some-function&lt;/span>&lt;span class="p">)&lt;/span>&lt;/code> where all the optional
argument values are &lt;em>nil&lt;/em>. Next, we&amp;rsquo;ll see some examples of
string-type &lt;em>MATCH&lt;/em> arguments used for filtering the headings.&lt;/p>
&lt;h3 id="match-strings">&lt;em>MATCH&lt;/em> strings&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#match-strings">#&lt;/a>&lt;/h3>
&lt;p>Below table shows few examples of match string patterns.&lt;/p>
&lt;p>&lt;a id="table--org-map-entries-search-strings">&lt;/a>&lt;/p>
&lt;div class="table-caption">
&lt;span class="table-number">&lt;a href="#table--org-map-entries-search-strings">Table 1&lt;/a>:&lt;/span>
String-type &lt;i>MATCH&lt;/i> argument examples for &lt;code>org-map-entries&lt;/code>
&lt;/div>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Search string&lt;/th>
&lt;th>Description&lt;/th>
&lt;th>Example&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>&lt;code>&amp;quot;TAG&amp;quot;&lt;/code>&lt;/strong>&lt;/td>
&lt;td>Tag name&lt;/td>
&lt;td>&lt;code>&amp;quot;foo&amp;quot;&lt;/code> matches all headings with that tag&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>&lt;code>&amp;quot;{TAG REGEXP}&amp;quot;&lt;/code>&lt;/strong>&lt;/td>
&lt;td>Regexp matching tags&lt;/td>
&lt;td>&lt;code>&amp;quot;{f.*}&amp;quot;&lt;/code> matches all headings whose tags match that regexp&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>&lt;code>&amp;quot;TAG1+TAG2+..&amp;quot;&lt;/code>&lt;/strong>&lt;/td>
&lt;td>Tag set intersection&lt;/td>
&lt;td>&lt;code>&amp;quot;foo+bar&amp;quot;&lt;/code> matches all headings with both of those tags&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>&lt;code>&amp;quot;TAG1-TAG2+..&amp;quot;&lt;/code>&lt;/strong>&lt;/td>
&lt;td>Tag set difference&lt;/td>
&lt;td>&lt;code>&amp;quot;foo-bar&amp;quot;&lt;/code> matches all headings with &lt;code>foo&lt;/code> tag but without &lt;code>bar&lt;/code> tag&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>&lt;code>&amp;quot;TAG1&lt;/code>|​&lt;code>TAG2&lt;/code>|​&lt;code>..&amp;quot;&lt;/code>&lt;/strong>&lt;/td>
&lt;td>Tag set union or boolean &lt;em>OR&lt;/em>&lt;/td>
&lt;td>&lt;code>&amp;quot;foo&lt;/code>​|​&lt;code>bar&amp;quot;&lt;/code> matches all headings with either of those tags&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>&lt;code>&amp;quot;TAG1&amp;amp;TAG2&amp;amp;..&amp;quot;&lt;/code>&lt;/strong>&lt;/td>
&lt;td>Tag set intersection or boolean &lt;em>AND&lt;/em>&lt;/td>
&lt;td>&lt;code>&amp;quot;foo&amp;amp;bar&amp;quot;&lt;/code> is same as &lt;code>&amp;quot;foo+bar&amp;quot;&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>&lt;code>&amp;quot;PROP&lt;/code>​=​&lt;code>\&amp;quot;STRVAL\&amp;quot;&amp;quot;&lt;/code>&lt;/strong>&lt;/td>
&lt;td>Specified property value matching a string&lt;/td>
&lt;td>&lt;code>&amp;quot;color&lt;/code>​=​&lt;code>\&amp;quot;blue\&amp;quot;&amp;quot;&lt;/code> matches all headings where &lt;code>color&lt;/code> property is &lt;code>blue&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>&lt;code>&amp;quot;PROP&amp;lt;&amp;gt;\&amp;quot;STRVAL\&amp;quot;&amp;quot;&lt;/code>&lt;/strong>&lt;/td>
&lt;td>Specified property value not matching a string&lt;/td>
&lt;td>&lt;code>&amp;quot;color&amp;lt;&amp;gt;\&amp;quot;blue\&amp;quot;&amp;quot;&lt;/code> matches all headings where &lt;code>color&lt;/code> property is not &lt;code>blue&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>&lt;code>&amp;quot;PROP&lt;/code>​=​&lt;code>{VAL REGEXP}&amp;quot;&lt;/code>&lt;/strong>&lt;/td>
&lt;td>Specified property value matching a regexp&lt;/td>
&lt;td>&lt;code>&amp;quot;color={b.*}&amp;quot;&lt;/code> matches all headings where &lt;code>color&lt;/code> property value matches &amp;lsquo;&lt;code>b.*&lt;/code>&amp;rsquo; regexp&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>&lt;code>&amp;quot;PROP[OP]NUMVAL&amp;quot;&lt;/code>&lt;/strong>&lt;/td>
&lt;td>Specified property value compared with a numeric value&lt;/td>
&lt;td>&lt;code>&amp;quot;some_num&lt;/code>​&amp;gt;=​&lt;code>10&amp;quot;&lt;/code> matches all headings where &lt;code>some_num&lt;/code> property is &amp;gt;=10&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>&lt;code>&amp;quot;LEVEL[OP]VAL&amp;quot;&lt;/code>&lt;/strong>&lt;/td>
&lt;td>Check value of headline&amp;rsquo;s special property &lt;em>LEVEL&lt;/em>&lt;/td>
&lt;td>&lt;code>&amp;quot;level&amp;gt;2&amp;quot;&lt;/code> matches all headlines at levels greater than 2&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>&lt;code>&amp;quot;TODO[OP]\&amp;quot;STRVAL\&amp;quot;&amp;quot;&lt;/code>&lt;/strong>&lt;/td>
&lt;td>Check value of headline&amp;rsquo;s &lt;em>TODO&lt;/em> state&lt;/td>
&lt;td>&lt;code>&amp;quot;TODO&lt;/code>​=​&lt;code>\&amp;quot;DONE\&amp;quot;&amp;quot;&lt;/code> matches all headlines with &lt;em>TODO&lt;/em> state set to &amp;lsquo;DONE&amp;rsquo;&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="comparison-types">Comparison Types&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#comparison-types">#&lt;/a>&lt;/h3>
&lt;ul>
&lt;li>If the comparison value is a plain number, a numerical comparison is
done, and the allowed operators are &amp;lsquo;&amp;lt;&amp;rsquo;, &amp;lsquo;=​&amp;rsquo;, &amp;lsquo;&amp;gt;&amp;rsquo;, &amp;lsquo;&amp;lt;=​&amp;rsquo;, &amp;lsquo;&amp;gt;=​&amp;rsquo;,
and &amp;lsquo;&amp;lt;&amp;gt;&amp;rsquo;.&lt;/li>
&lt;li>If the comparison value is enclosed in double quotes, a string
comparison is done, and the same operators are allowed.&lt;/li>
&lt;li>If the comparison value is enclosed in curly braces, a regexp match
is performed. For this comparison, only &amp;lsquo;=​&amp;rsquo; (regexp matches) and
&amp;lsquo;&amp;lt;&amp;gt;&amp;rsquo; (regexp does not match) operators are allowed.&lt;/li>
&lt;li>Comparison with dates and &lt;a href="https://orgmode.org/manual/Tag-Hierarchy.html" title="Emacs Lisp: (info &amp;quot;(org) Tag Hierarchy&amp;quot;)">Group Tags&lt;/a> is also possible. See
&lt;a href="https://orgmode.org/manual/Matching-tags-and-properties.html" title="Emacs Lisp: (info &amp;quot;(org) Matching tags and properties&amp;quot;)">Org Info: Matching tags and properties&lt;/a> for more details.&lt;/li>
&lt;/ul>
&lt;h3 id="other-notes">Other notes&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#other-notes">#&lt;/a>&lt;/h3>
&lt;ul>
&lt;li>The property names are case-insensitive. So these all work the
same: &lt;code>&amp;quot;COLOR&amp;lt;&amp;gt;\&amp;quot;blue\&amp;quot;&amp;quot;&lt;/code>, &lt;code>&amp;quot;color&amp;lt;&amp;gt;\&amp;quot;blue\&amp;quot;&amp;quot;&lt;/code>,
&lt;code>&amp;quot;Color&amp;lt;&amp;gt;\&amp;quot;blue\&amp;quot;&amp;quot;&lt;/code>.&lt;/li>
&lt;li>The &amp;ldquo;tag&amp;rdquo; and &amp;ldquo;property&amp;rdquo; matches can be mixed up using the boolean
&amp;lsquo;&lt;code>&amp;amp;&lt;/code>&amp;rsquo;, &amp;lsquo;&lt;code>|&lt;/code>&amp;rsquo;, &amp;lsquo;&lt;code>+&lt;/code>&amp;rsquo; and &amp;lsquo;&lt;code>-&lt;/code>​&amp;rsquo; operators. So searching
&amp;lsquo;&lt;code>+LEVEL=3+boss-TODO​=&amp;quot;DONE&amp;quot;&lt;/code>&amp;rsquo; lists all level three headlines
that have the tag &amp;lsquo;boss&amp;rsquo; and are &lt;span class="underline">not&lt;/span> marked with the TODO
keyword &amp;lsquo;DONE&amp;rsquo;.&lt;/li>
&lt;li>&amp;lsquo;&lt;code>&amp;amp;&lt;/code>&amp;rsquo; binds more strongly than &amp;lsquo;&lt;code>|&lt;/code>&amp;rsquo;.&lt;/li>
&lt;li>Grouping of match expressions using parentheses is not
supported.&lt;/li>
&lt;/ul>
&lt;h2 id="example-modifying-a-property-in-all-headings">Example: Modifying a property in all headings&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#example-modifying-a-property-in-all-headings">#&lt;/a>&lt;/h2>
&lt;p>Below is an example solution to the &lt;a href="https://framapiaf.org/@postroutine/108313152514542145">Mastoson question&lt;/a> that I
referenced in the beginning of this post.&lt;/p>
&lt;p>&lt;a id="code-snippet--set-props-all-headings">&lt;/a>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-emacs-lisp" data-lang="emacs-lisp">&lt;span class="line">&lt;span class="cl">&lt;span class="p">(&lt;/span>&lt;span class="nb">defun&lt;/span> &lt;span class="nv">test/set-property-at-heading&lt;/span> &lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;Function to be called at the beginning of an Org heading.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">let&lt;/span> &lt;span class="p">((&lt;/span>&lt;span class="nv">el&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">org-element-at-point&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">org-set-property&lt;/span> &lt;span class="s">&amp;#34;foo&amp;#34;&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">org-element-property&lt;/span> &lt;span class="nb">:title&lt;/span> &lt;span class="nv">el&lt;/span>&lt;span class="p">))))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">(&lt;/span>&lt;span class="nv">org-map-entries&lt;/span> &lt;span class="nf">#&amp;#39;&lt;/span>&lt;span class="nv">test/set-property-at-heading&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="src-block-caption">
&lt;span class="src-block-number">&lt;a href="#code-snippet--set-props-all-headings">Code Snippet 2&lt;/a>:&lt;/span>
Dummy example showing how to set a property for all Org headings using &lt;code>org-map-entries&lt;/code>
&lt;/div>
&lt;ul>
&lt;li>It defines a function that parses the Org element at point using
&lt;code>org-element-at-point&lt;/code>, gets the &lt;code>title&lt;/code> property of the element
&lt;span class="sidenote-number">&lt;small class="sidenote">
This function is designed to be called by &lt;code>org-map-entries&lt;/code> and so
the point at the time of calling this function will always be on a
heading.
&lt;/small>&lt;/span>
, and sets that to the &lt;em>headline&lt;/em> element&amp;rsquo;s &lt;code>foo&lt;/code> property.&lt;/li>
&lt;li>The &lt;code>org-map-entries&lt;/code> call now simply calls this function on each
heading in the visible scope of the Org buffer.&lt;/li>
&lt;/ul>
&lt;h2 id="org-map-entries-references">&lt;code>org-map-entries&lt;/code> References&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#org-map-entries-references">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>&lt;kbd>C-h&lt;/kbd> &lt;kbd>f&lt;/kbd> &lt;code>org-map-entries&lt;/code>&lt;/li>
&lt;li>&lt;a href="https://orgmode.org/manual/Using-the-Mapping-API.html" title="Emacs Lisp: (info &amp;quot;(org) Using the Mapping API&amp;quot;)">Org Info: Using the Mapping API&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://orgmode.org/manual/Matching-tags-and-properties.html" title="Emacs Lisp: (info &amp;quot;(org) Matching tags and properties&amp;quot;)">Org Info: Matching tags and properties&lt;/a>&lt;/li>
&lt;/ul>
&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>Org mode has another popular mapping/looping API function
&lt;strong>org-element-map&lt;/strong>. I won&amp;rsquo;t go into much detail about that in this post
&amp;mdash; I&amp;rsquo;ll just mention that &lt;code>org-element-map&lt;/code> is not the best choice if
you need to modify the original Org buffer. It&amp;rsquo;s main use is to loop
through a parsed &lt;abbr aria-label="Abstract Syntax Tree" tabindex=0>AST&lt;/abbr> of an Org buffer
and optional modify those elements &lt;em>in memory&lt;/em>.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description><author>Kaushal.Modi@fakeEmailToMakeValidatorHappy.com (Kaushal Modi)</author><category domain="https://scripter.co/categories/emacs">emacs</category><category domain="https://scripter.co/categories/org">org</category><category domain="https://scripter.co/categories/elisp">elisp</category><category domain="https://scripter.co/tags/looping">looping</category><category domain="https://scripter.co/tags/100daystooffload">100DaysToOffload</category><guid>https://scripter.co/looping-through-org-mode-headings/</guid><pubDate>Wed, 18 May 2022 23:29:00 -0400</pubDate></item><item><title>Presenting tomelr!</title><link>https://scripter.co/presenting-tomelr/</link><description>&lt;blockquote>In this post, I introduce a little library I created for &lt;code>ox-hugo&lt;/code> to
have a robust mechanism for generating TOML from any Lisp expression.&lt;/blockquote>&lt;div class="ox-hugo-toc toc">
&lt;div class="heading">Table of Contents&lt;/div>
&lt;ul>
&lt;li>&lt;a href="#my-flow-for-creating-this-library">My flow for creating this library&lt;/a>&lt;/li>
&lt;li>&lt;a href="#adapting-the-library-to-fit-ox-hugo">Adapting the library to fit &lt;code>ox-hugo&lt;/code>&lt;/a>&lt;/li>
&lt;li>&lt;a href="#changes-in-ox-hugo-tests">Changes in &lt;code>ox-hugo&lt;/code> tests&lt;/a>&lt;/li>
&lt;li>&lt;a href="#what-s-next">What&amp;rsquo;s next?&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#unblocking-some-future-ox-hugo-improvements">Unblocking some future &lt;code>ox-hugo&lt;/code> improvements&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;!--endtoc-->
&lt;p>In my previous post &lt;a href="https://scripter.co/defining-tomelr/">Defining &lt;em>tomelr&lt;/em>&lt;/a>, I started toying with the idea
of creating a library that would help convert any Lisp expression to a
TOML config, and I share my vision (specification) of what this
library would look like.&lt;/p>
&lt;p>I wasn&amp;rsquo;t even sure if I would be able to make the &lt;a href="https://github.com/kaushalmodi/tomelr">&lt;strong>tomelr&lt;/strong>&lt;/a> library
feature-complete at least to the extent of what &lt;code>ox-hugo&lt;/code> was already
doing! But to my surprise, the library development snowballed to a
completion much earlier than I thought, and additionally it helped fix
some inconsistencies that the older TOML generation code had in
&lt;code>ox-hugo&lt;/code>.&lt;/p>
&lt;p>In this post, I start by (i) giving a broad overview of how the
development of &lt;code>tomelr&lt;/code> happened, then (ii) briefly describe how it
got integrated into &lt;code>ox-hugo&lt;/code>, and finally (iii) how the use of this
library will unblock the path to addition of some cool features to
&lt;code>ox-hugo&lt;/code>.&lt;/p>
&lt;h2 id="my-flow-for-creating-this-library">My flow for creating this library&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#my-flow-for-creating-this-library">#&lt;/a>&lt;/h2>
&lt;ol>
&lt;li>Write the spec for the library.
&lt;ul>
&lt;li>List all the formats of Lisp data I would expect it to process.&lt;/li>
&lt;li>List the corresponding TOML data I would expect it to generate.&lt;/li>
&lt;li>Ensure that I am not inventing my &lt;em>own lisp syntax&lt;/em> by confirming
that the expected TOML output matches the JSON generated from
that same lisp form (using the Emacs built-in &lt;code>json.el&lt;/code> library).&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>That helped me write the tests first! &amp;ndash; &lt;a href="https://en.wikipedia.org/wiki/Test-driven_development">Test Driven Development
(TDD)&lt;/a>.&lt;/li>
&lt;li>I started with writing tests for TOML booleans and then
implementing that (because that was the simplest and easiest). Of
course, I used &lt;a href="https://scripter.co/quick-intro-to-emacs-lisp-regression-testing/">ert&lt;/a> for this! &lt;code>ert&lt;/code> helped me quickly create small
modular tests
&lt;span class="sidenote-number">&lt;small class="sidenote">
&lt;a href="https://github.com/kaushalmodi/tomelr/blob/867c82c9e230309c748de59bf8c0937b10d6fc64/test/tscalar.el#L31-L43">Here&amp;rsquo;s&lt;/a> the &lt;em>ert&lt;/em> test for booleans as an example.
&lt;/small>&lt;/span>
and efficiently iterate through modifications in the library code
until I got the tests to pass.&lt;/li>
&lt;li>Once that got working, I set up a continuous integration system
using &lt;a href="https://docs.github.com/en/actions">GitHub Actions (GHA)&lt;/a>. I used GHA because I host my library on
GitHub. Also I already have a tried and tested setup that I could
get up and going in a matter of few seconds. In general, this
concept would apply to any &lt;em>Continuous Integration system&lt;/em>. The CI
setup step should come early in the development of any project so
that incremental feature additions don&amp;rsquo;t start breaking previously
added features 😃.&lt;/li>
&lt;li>The library development just snowballed after this point .. added
support for integers, floats, regular strings, multi-line strings,
arrays, TOML tables, arrays of TOML tables. By this time, the
library was about 80% finished.&lt;/li>
&lt;li>Then came the difficult part .. stabilizing the library to support
all the varieties of Lisp data I can think of. I must have put in
double the time spent so far to finish the remaining 20% of the
planned features for this library 👉 &lt;a href="https://github.com/kaushalmodi/tomelr/tree/main/test">&lt;strong>tomelr test suite&lt;/strong>&lt;/a>&lt;/li>
&lt;li>Once I had the test suite complete and passing, it was time to do
some code cleanup:
&lt;ul>
&lt;li>Remove duplicate code and break them off into smaller helper
functions.&lt;/li>
&lt;li>See if the function defined in this library is already defined
somewhere else (in this case, I was able to use &lt;code>json-plist-p&lt;/code>
directly from &lt;code>json.el&lt;/code>).&lt;/li>
&lt;li>Proof read the code.&lt;/li>
&lt;li>Proof read the docstrings and run &lt;code>M-x checkdoc&lt;/code> to fix their
formatting.&lt;/li>
&lt;li>Ensure that the code compiles without any warnings.&lt;/li>
&lt;li>Remove unnecessary customization options and case statements from
the library (while continuously ensuring that the &lt;em>ert&lt;/em> tests
still pass).&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;h2 id="adapting-the-library-to-fit-ox-hugo">Adapting the library to fit &lt;code>ox-hugo&lt;/code>&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#adapting-the-library-to-fit-ox-hugo">#&lt;/a>&lt;/h2>
&lt;p>After polishing the library by its stand-alone testing, I decided to
use it with &lt;code>ox-hugo&lt;/code> and see how the test suite in that repo fared.&lt;/p>
&lt;p>Of course I saw that a lot of tests failed now 😁.&lt;/p>
&lt;p>The main issue was that &lt;code>tomelr&lt;/code> was constructing multi-line strings
such that the spaces translated exactly from Lisp data to TOML. So
&lt;code class="code-inline language-emacs-lisp">&lt;span class="p">(&lt;/span>&lt;span class="nv">tomelr-encode&lt;/span> &lt;span class="o">&amp;#39;&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="nv">foo&lt;/span> &lt;span class="o">.&lt;/span> &lt;span class="s">&amp;#34;line1\nline2&amp;#34;&lt;/span>&lt;span class="p">)))&lt;/span>&lt;/code> would generate:&lt;/p>
&lt;p>&lt;a id="code-snippet--tomelr-mls-no-extra-ws">&lt;/a>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-toml" data-lang="toml">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">foo&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">line1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">line2&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="src-block-caption">
&lt;span class="src-block-number">&lt;a href="#code-snippet--tomelr-mls-no-extra-ws">Code Snippet 1&lt;/a>:&lt;/span>
Multi-line string with same white-space as in original data.. but not that "pretty"
&lt;/div>
&lt;p>whereas &lt;code>ox-hugo&lt;/code> expected the same TOML to look like:&lt;/p>
&lt;p>&lt;a id="code-snippet--tomelr-pretty-mls">&lt;/a>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-toml" data-lang="toml">&lt;span class="line">&lt;span class="cl">&lt;span class="nx">foo&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> line1
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> line2
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2"> &amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="src-block-caption">
&lt;span class="src-block-number">&lt;a href="#code-snippet--tomelr-pretty-mls">Code Snippet 2&lt;/a>:&lt;/span>
Pretty multi-line string, but with extra white-space
&lt;/div>
&lt;p>I had intentionally decided for &lt;code>ox-hugo&lt;/code> to have this latter format
for multi-line strings because (i) it made it more readable with the
&lt;em>triple-quotes&lt;/em> out of the way on their own lines, (ii) the indented
lines prevented the multi-line string from getting mixed with
surrounding TOML parameters, and &lt;strong>most importantly&lt;/strong> (iii) these
strings were processed by the Hugo Markdown parser, and so it wasn&amp;rsquo;t
sensitive to horizontal spaces.&lt;/p>
&lt;p>And so the &lt;code>tomelr-indent-multi-line-strings&lt;/code> feature was born
(&lt;a href="https://github.com/kaushalmodi/tomelr/commit/3362213172237f40ff0d9aa3ddf12b4bb00a3564">commit&lt;/a>) which optionally made &lt;code>tomelr&lt;/code> export multi-line strings as
expected by &lt;code>ox-hugo&lt;/code> 😎.&lt;/p>
&lt;h2 id="changes-in-ox-hugo-tests">Changes in &lt;code>ox-hugo&lt;/code> tests&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#changes-in-ox-hugo-tests">#&lt;/a>&lt;/h2>
&lt;p>Once I had finalized the integration of &lt;code>tomelr&lt;/code> into &lt;code>ox-hugo&lt;/code>, I had
only about 30 tests change out of roughly 400 tests. These changes
were welcome as they fixed all the inconsistencies in the older TOML
generation code in &lt;code>ox-hugo&lt;/code>. If interested, you can see &lt;a href="https://github.com/kaushalmodi/ox-hugo/commit/c24ea792484598ffd2f8e786fadb823d48c8ec12">this commit&lt;/a>
for the diff and details, but here&amp;rsquo;s the gist:&lt;/p>
&lt;ol>
&lt;li>Now &lt;em>nil&lt;/em> value of a key in Lisp consistently implies that the key
should not be exported to TOML. So &lt;code class="code-inline language-emacs-lisp">&lt;span class="o">&amp;#39;&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="nv">foo&lt;/span> &lt;span class="o">.&lt;/span> &lt;span class="no">nil&lt;/span>&lt;span class="p">))&lt;/span>&lt;/code> will result in &lt;code>foo&lt;/code> &lt;strong>not&lt;/strong> getting exported
to TOML, whether that&amp;rsquo;s a top-level key or a key in a nested TOML
map or array. If you need to set a key to a boolean &lt;em>false&lt;/em>, use
&lt;code>&amp;quot;false&amp;quot;&lt;/code> or any value from &lt;code>tomelr-false&lt;/code>.&lt;/li>
&lt;li>Earlier &lt;em>empty string&lt;/em> value as in &lt;code class="code-inline language-emacs-lisp">&lt;span class="o">&amp;#39;&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="nv">foo&lt;/span> &lt;span class="o">.&lt;/span> &lt;span class="s">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span>&lt;/code> behaved like the current &lt;em>nil&lt;/em>
implementation. That&amp;rsquo;s not the case any more. Now that empty string
will export as &lt;code class="code-inline language-toml">&lt;span class="nx">foo&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&lt;/span>&lt;/code> in TOML.&lt;/li>
&lt;li>Now if a string has a quote character (&lt;code>&amp;quot;&lt;/code>) in it, that value will
auto-export as TOML multi-line string. I like the readability of
this more than that of backslash-escaped double-quotes.&lt;/li>
&lt;li>Now the nested tables like &lt;code class="code-inline language-toml">&lt;span class="p">[&lt;/span>&lt;span class="nx">menu&lt;/span>&lt;span class="p">.&lt;/span>&lt;span class="s2">&amp;#34;nested menu&amp;#34;&lt;/span>&lt;span class="p">]&lt;/span>&lt;/code> export with their parent table keys like &lt;code class="code-inline language-toml">&lt;span class="p">[&lt;/span>&lt;span class="nx">menu&lt;/span>&lt;span class="p">]&lt;/span>&lt;/code>. As per the TOML spec, this is not required. But now
that &lt;code>tomelr&lt;/code> has added a generic support for any TOML table, this
change happens as a result of consistency 💯.&lt;/li>
&lt;/ol>
&lt;p>In summary, the changes in &lt;code>ox-hugo&lt;/code> TOML front-matter exports were
mostly cosmetic, and if they were not cosmetic, they were consistency
fixes.&lt;/p>
&lt;h2 id="what-s-next">What&amp;rsquo;s next?&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#what-s-next">#&lt;/a>&lt;/h2>
&lt;dl>
&lt;dt>tomelr&lt;/dt>
&lt;dd>The library is pretty much feature complete ✨ as
many of the examples from &lt;a href="https://toml.io/en/v1.0.0">TOML v1.0.0 spec&lt;/a> have been added to its
test suite, and .. it is supporting all the &lt;code>ox-hugo&lt;/code> use cases.
&lt;p>The library though has &lt;a href="https://github.com/kaushalmodi/tomelr#limitations">one limitation&lt;/a> that I&amp;rsquo;d like to resolve at
some point &amp;mdash; Right now, we require the Lisp data to first list all
the scalar keys and then list the TOML tables and arrays of
tables. But at the moment, I don&amp;rsquo;t know how to fix that, and also
&lt;code>ox-hugo&lt;/code> is not affected by that (because it already populates the
front-matter alist in the correct order). So fixing this is not
urgent, but of course, if someone can help me out with that, I&amp;rsquo;d
welcome that! 🙏.&lt;/p>
&lt;/dd>
&lt;dt>ox-hugo&lt;/dt>
&lt;dd>Given that &lt;code>tomelr&lt;/code> allows robustly exporting any Lisp
data expression to TOML, I do not see any value in continuing with
YAML generation support using the old custom code.
&lt;p>📢 In near future, I plan to get rid of the
&lt;code>org-hugo-front-matter-format&lt;/code> customization variable from &lt;code>ox-hugo&lt;/code>
&amp;mdash; thus deprecating YAML export support
&lt;span class="sidenote-number">&lt;small class="sidenote">
This change should not functionally affect the YAML front-matter
fans out there because the front-matter that &lt;code>ox-hugo&lt;/code> is exporting
is mainly for Hugo&amp;rsquo;s consumption. The only scenario where I see that
this change can be breaking is if the user is using YAML format
&lt;em>extra front-matter&lt;/em> blocks. If so, unfortunately, they will need to
convert those to TOML manually.
&lt;/small>&lt;/span>
and sticking with using just TOML for the front-matter.&lt;/p>
&lt;/dd>
&lt;/dl>
&lt;h3 id="unblocking-some-future-ox-hugo-improvements">Unblocking some future &lt;code>ox-hugo&lt;/code> improvements&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#unblocking-some-future-ox-hugo-improvements">#&lt;/a>&lt;/h3>
&lt;p>This decision will open up the doors to add more features to &lt;code>ox-hugo&lt;/code>
like:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>Exporting Org &lt;code>:LOGBOOK:&lt;/code> drawers to TOML front-matter (&lt;a href="https://github.com/kaushalmodi/ox-hugo/pull/504">ox-hugo #
&lt;strong>504&lt;/strong>&lt;/a>)&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Exporting Org Special Blocks to user-configurable front-matter
(&lt;a href="https://github.com/kaushalmodi/ox-hugo/pull/627">ox-hugo # &lt;strong>627&lt;/strong>&lt;/a>)&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Supporting more complex data in Lisp form using
&lt;code>:EXPORT_HUGO_CUSTOM_FRONT_MATTER:&lt;/code> which could translate to nested
TOML tables or arrays of TOML tables.&lt;/p>
&lt;p>Finally, there won&amp;rsquo;t be a need to use the &lt;a href="https://ox-hugo.scripter.co/doc/custom-front-matter/#front-matter-extra">&amp;ldquo;Extra front-matter&amp;rdquo;&lt;/a>
workaround. For example, it would be possible to represent the data
in that first example on that page as &lt;code class="code-inline language-emacs-lisp">&lt;span class="nb">:foo&lt;/span> &lt;span class="p">((&lt;/span>&lt;span class="nb">:bar&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="nb">:zoo&lt;/span> &lt;span class="s">&amp;#34;abc&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">:bar&lt;/span> &lt;span class="mi">2&lt;/span> &lt;span class="nb">:zoo&lt;/span> &lt;span class="s">&amp;#34;def&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span>&lt;/code> in the
&lt;code>:EXPORT_HUGO_CUSTOM_FRONT_MATTER:&lt;/code> property.&lt;/p>
&lt;/li>
&lt;/ol></description><author>Kaushal.Modi@fakeEmailToMakeValidatorHappy.com (Kaushal Modi)</author><category domain="https://scripter.co/categories/web">web</category><category domain="https://scripter.co/categories/org">org</category><category domain="https://scripter.co/categories/hugo">hugo</category><category domain="https://scripter.co/categories/emacs">emacs</category><category domain="https://scripter.co/categories/elisp">elisp</category><category domain="https://scripter.co/tags/ox-hugo">ox-hugo</category><category domain="https://scripter.co/tags/toml">toml</category><category domain="https://scripter.co/tags/100daystooffload">100DaysToOffload</category><guid>https://scripter.co/presenting-tomelr/</guid><pubDate>Wed, 04 May 2022 01:03:00 -0400</pubDate></item><item><title>Defining tomelr – A library for converting Lisp expressions to TOML</title><link>https://scripter.co/defining-tomelr/</link><description>&lt;blockquote>Creating a specification for an Emacs-Lisp library to convert Lisp
data expressions into easy-to-read TOML strings.&lt;/blockquote>&lt;div class="ox-hugo-toc toc">
&lt;div class="heading">Table of Contents&lt;/div>
&lt;ul>
&lt;li>&lt;a href="#using-json-encode-as-reference">Using &lt;code>json-encode&lt;/code> as reference&lt;/a>&lt;/li>
&lt;li>&lt;a href="#mapping-scalar-data-to-toml">Mapping scalar data to TOML&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#about-false">About &lt;code>:false&lt;/code>&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#mapping-lists-to-toml">Mapping lists to TOML&lt;/a>&lt;/li>
&lt;li>&lt;a href="#mapping-lists-of-lists-to-toml">Mapping lists of lists to TOML&lt;/a>&lt;/li>
&lt;li>&lt;a href="#mapping-other-object-types">Mapping other object types&lt;/a>&lt;/li>
&lt;li>&lt;a href="#closing">Closing&lt;/a>&lt;/li>
&lt;/ul>
&lt;/div>
&lt;!--endtoc-->
&lt;p>&lt;code>ox-hugo&lt;/code> has some custom code that generates &lt;a href="https://toml.io/en/">TOML&lt;/a>
&lt;span class="sidenote-number">&lt;small class="sidenote">
I ❤️ TOML. As the makers of this config format put it.. &amp;ldquo;it&amp;rsquo;s a
format &lt;em>for humans&lt;/em>&amp;rdquo;! &amp;mdash; No need to deal with indentations, weird
syntax for multi-line strings, no prohibition on adding comments to
the config, or dealing with careful placement of commas and braces.
&lt;/small>&lt;/span>
for &lt;a href="https://gohugo.io/">Hugo&lt;/a> front-matter, based on the Org keywords and other meta-data
that the user sets in their Org file. But this TOML generation code is
not generic enough for any TOML object type.&lt;/p>
&lt;p>As I am &lt;a href="https://github.com/kaushalmodi/ox-hugo/pull/504#issuecomment-1093592230">writing up a definition&lt;/a> on how to export &lt;code>:LOGBOOK:&lt;/code> &lt;a href="https://orgmode.org/manual/Drawers.html" title="Emacs Lisp: (info &amp;quot;(org) Drawers&amp;quot;)">drawers&lt;/a>,
I felt it&amp;rsquo;s a good time to polish the whole &lt;strong>Lisp data → TOML&lt;/strong>
conversion code, and may be package that into a separate library.&lt;/p>
&lt;div class="verse">
&lt;p>        It&amp;rsquo;s kind of an ambitious project &amp;mdash; I am calling it &lt;a href="https://github.com/kaushalmodi/tomelr">tom​&lt;strong>el&lt;/strong>​r&lt;/a> ✨&lt;br />&lt;/p>
&lt;/div>
&lt;p>It&amp;rsquo;s a big undertaking to create a generic library for this kind of
data format conversion. But even before I start coding or think if I
can complete this project, I need to &lt;em>spec&lt;/em>
&lt;span class="sidenote-number">&lt;small class="sidenote">
I am not sure if it&amp;rsquo;s a widely used verb, but &lt;em>to spec&lt;/em> means &lt;em>to
write a specification for something&lt;/em>.
&lt;/small>&lt;/span>
it. I need to understand early-on how the &lt;em>S-exp&lt;/em> (Symbolic lisp
expression) would needed to look for each kind of generated TOML
object &amp;mdash; scalars, lists, lists of lists, maps, lists of maps, etc.&lt;/p>
&lt;h2 id="using-json-encode-as-reference">Using &lt;code>json-encode&lt;/code> as reference&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#using-json-encode-as-reference">#&lt;/a>&lt;/h2>
&lt;p>The aim of the &lt;code>tomelr&lt;/code> library is to take some &lt;code>(lisp data)&lt;/code> and
convert that TOML. But I did not want to invent my own &lt;em>lisp data
convention&lt;/em> for this! So I decided to stick with the lisp expression
conventions understood by the &lt;code>json-encode&lt;/code> function from the Emacs
core library &lt;code>json.el&lt;/code>.&lt;/p>
&lt;p>Credit for the &lt;code>json.el&lt;/code> idea goes to &lt;a href="https://twitter.com/pdcawley/status/1519007598896369664">this tweet&lt;/a> by &lt;a href="https://twitter.com/pdcawley">Piers Cawley&lt;/a>:&lt;/p>
&lt;blockquote>
&lt;p>I&amp;rsquo;d suggest that
&lt;span class="sidenote-number">&lt;small class="sidenote">
By &amp;ldquo;that&amp;rdquo;, he&amp;rsquo;s referring to adding support for exporting front-matter
to JSON in &lt;code>ox-hugo&lt;/code>.
&lt;/small>&lt;/span>
, since emacs has built in JSON support these days, you
don&amp;rsquo;t really have to worry about the commas and braces, just build the
s-exp you want and export, but it&amp;rsquo;s you that&amp;rsquo;s writing the code and
I&amp;rsquo;m just delighted that it exists.&lt;/p>
&lt;p>Thank you for your efforts.&lt;/p>
&lt;/blockquote>
&lt;p>I am not sold on adding support of yet another front-matter format to
&lt;code>ox-hugo&lt;/code>. I might not use &lt;code>json.el&lt;/code> for that, but it definitely
helped me a lot with coming up with this library&amp;rsquo;s spec 😆.&lt;/p>
&lt;h2 id="mapping-scalar-data-to-toml">Mapping scalar data to TOML&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#mapping-scalar-data-to-toml">#&lt;/a>&lt;/h2>
&lt;p>Figuring out the Lisp representation for scalar (plain key-value
pairs) TOML objects was easy. &lt;code>json.el&lt;/code> helped figure out how to deal
with &lt;em>nil&lt;/em> and &lt;em>false&lt;/em> values.&lt;/p>
&lt;p>&lt;a id="table--mapping-scalar-lisp-to-toml">&lt;/a>&lt;/p>
&lt;div class="table-caption">
&lt;span class="table-number">&lt;a href="#table--mapping-scalar-lisp-to-toml">Table 1&lt;/a>:&lt;/span>
Mapping of &lt;i>scalar&lt;/i> Lisp data to TOML
&lt;/div>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Lisp S-exp&lt;/th>
&lt;th>TOML&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>'((int_key . 123))&lt;/code>&lt;/td>
&lt;td>&lt;code>int_key = 123&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>'((float_key . 1.23))&lt;/code>&lt;/td>
&lt;td>&lt;code>float_key = 1.23&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>'((date_key . 2022-04-27))&lt;/code>&lt;/td>
&lt;td>&lt;code>date_key = 2022-04-27&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>'((bool_key . t))&lt;/code>&lt;/td>
&lt;td>&lt;code>bool_key = true&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>'((any_key . nil))&lt;/code>&lt;/td>
&lt;td>&lt;em>(key removed in TOML)&lt;/em>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>'((bool_key . :false))&lt;/code>&lt;/td>
&lt;td>&lt;code>bool_key = false&lt;/code>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="about-false">About &lt;code>:false&lt;/code>&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#about-false">#&lt;/a>&lt;/h3>
&lt;p>&lt;code>json.el&lt;/code> defines a variable &lt;code>json-false&lt;/code> that&amp;rsquo;s set to the value
&lt;code>:json-false&lt;/code>. This is because in JSON, the &lt;em>null&lt;/em> value is different
from the boolean &lt;code>false&lt;/code> value.&lt;/p>
&lt;ul>
&lt;li>&lt;em>nil&lt;/em> in Lisp → &lt;em>null&lt;/em> in JSON&lt;/li>
&lt;li>&lt;code>:json-false&lt;/code> in Lisp → &lt;code>false&lt;/code> in JSON&lt;/li>
&lt;/ul>
&lt;p>Inspired by that decision of &lt;code>json.el&lt;/code>, I am thinking of using
&lt;code>:false&lt;/code> as the special value that will set the equivalent TOML value
to boolean &lt;code>false&lt;/code>.&lt;/p>
&lt;div class="note">
&lt;p>TOML does not define a &lt;em>null&lt;/em> value, but if the Lisp value is &lt;em>nil&lt;/em>,
that key will simply not be translated to TOML.&lt;/p>
&lt;/div>
&lt;h2 id="mapping-lists-to-toml">Mapping lists to TOML&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#mapping-lists-to-toml">#&lt;/a>&lt;/h2>
&lt;p>Mapping lists was simple.. because in Lisp, a list value of course
looks like &lt;code class="code-inline language-emacs-lisp">&lt;span class="o">&amp;#39;&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="nv">foo&lt;/span> &lt;span class="o">.&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="mi">1&lt;/span> &lt;span class="mi">2&lt;/span> &lt;span class="mi">3&lt;/span> &lt;span class="mi">4&lt;/span> &lt;span class="mi">5&lt;/span>&lt;span class="p">)))&lt;/span>&lt;/code>
😃.&lt;/p>
&lt;p>&lt;a id="table--mapping-list-lisp-to-toml">&lt;/a>&lt;/p>
&lt;div class="table-caption">
&lt;span class="table-number">&lt;a href="#table--mapping-list-lisp-to-toml">Table 2&lt;/a>:&lt;/span>
Mapping of &lt;i>list&lt;/i> Lisp data to TOML
&lt;/div>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Lisp S-exp&lt;/th>
&lt;th>TOML&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>'((list_key1 . (1 2 3)))&lt;/code>&lt;/td>
&lt;td>&lt;code>list_key1 = [1, 2, 3]&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>'((list_key2 . (&amp;quot;a&amp;quot; &amp;quot;b&amp;quot; &amp;quot;c&amp;quot;)))&lt;/code>&lt;/td>
&lt;td>&lt;code>list_key2 = [&amp;quot;a&amp;quot;, &amp;quot;b&amp;quot;, &amp;quot;c&amp;quot;]&lt;/code>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h2 id="mapping-lists-of-lists-to-toml">Mapping lists of lists to TOML&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#mapping-lists-of-lists-to-toml">#&lt;/a>&lt;/h2>
&lt;p>&lt;a id="table--mapping-list-of-list-lisp-to-toml">&lt;/a>&lt;/p>
&lt;div class="table-caption">
&lt;span class="table-number">&lt;a href="#table--mapping-list-of-list-lisp-to-toml">Table 3&lt;/a>:&lt;/span>
Mapping of &lt;i>list of list&lt;/i> Lisp data to TOML
&lt;/div>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Lisp S-exp&lt;/th>
&lt;th>TOML&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>'((lol_key . [(1 2) (3 4)]))&lt;/code>&lt;/td>
&lt;td>&lt;code>lol_key = [ [1, 2], [3, 4] ]&lt;/code>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>I was going to use &lt;code class="code-inline language-emacs-lisp">&lt;span class="o">&amp;#39;&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="nv">lol_key&lt;/span> &lt;span class="o">.&lt;/span> &lt;span class="p">((&lt;/span>&lt;span class="mi">1&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="mi">3&lt;/span> &lt;span class="mi">4&lt;/span>&lt;span class="p">))))&lt;/span>&lt;/code> as the reference Lisp expression for &lt;code class="code-inline language-toml">&lt;span class="nx">lol_key&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="p">[&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">1&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">],&lt;/span> &lt;span class="p">[&lt;/span>&lt;span class="mi">3&lt;/span>&lt;span class="p">,&lt;/span> &lt;span class="mi">4&lt;/span>&lt;span class="p">]&lt;/span> &lt;span class="p">]&lt;/span>&lt;/code>. But I found out that
&lt;code>json-encode&lt;/code> throws an error if you pass it that expression! I don&amp;rsquo;t
understand the reason for that error, and so I have &lt;a href="https://lists.gnu.org/r/help-gnu-emacs/2022-04/msg00240.html">asked for help&lt;/a> on
the &lt;em>help-gnu-emacs&lt;/em> mailing list.&lt;/p>
&lt;p>But while that question gets resolved, I wanted to move forward with
the spec definition. After some trial-and-error and
reverse-engineering &lt;code>json.el&lt;/code>
&lt;span class="sidenote-number">&lt;small class="sidenote">
I knew how I wanted TOML to look. So I used &lt;a href="https://toolkit.site/format.html">an online JSON/TOML
converter&lt;/a> to convert that TOML snippet to JSON, and then used
&lt;code>json-read&lt;/code> to convert JSON to Lisp expression.
&lt;/small>&lt;/span>
, I learned that &lt;em>list of list&lt;/em> data needs to be represented using a &lt;a href="https://www.gnu.org/software/emacs/manual/html_node/elisp/Vector-Type.html" title="Emacs Lisp: (info &amp;quot;(elisp) Vector Type&amp;quot;)">Vector type&lt;/a>, and so &lt;code class="code-inline language-emacs-lisp">&lt;span class="o">&amp;#39;&lt;/span>&lt;span class="p">((&lt;/span>&lt;span class="nv">lol_key&lt;/span> &lt;span class="o">.&lt;/span> &lt;span class="p">[(&lt;/span>&lt;span class="mi">1&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="mi">3&lt;/span> &lt;span class="mi">4&lt;/span>&lt;span class="p">)]))&lt;/span>&lt;/code> would be the correct expression &amp;ndash; &lt;em>Notice the use
of square brackets instead of parentheses for the outer vector&lt;/em>.&lt;/p>
&lt;h2 id="mapping-other-object-types">Mapping other object types&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#mapping-other-object-types">#&lt;/a>&lt;/h2>
&lt;p>Once I figured out how to map the above data types, mapping Lisp data
to &lt;em>TOML Tables&lt;/em> aka &lt;em>dictionaries&lt;/em> and &lt;em>arrays of Tables&lt;/em> was a
breeze
&lt;span class="sidenote-number">&lt;small class="sidenote">
Of course, the &lt;em>breeze&lt;/em> is referring to the ease of writing the spec
for these 😆. Implementation-wise, the &lt;em>tables&lt;/em>, &lt;em>arrays of
tables&lt;/em>, and the especially &lt;strong>nested&lt;/strong> variants of those are going to be
the most challenging.
&lt;/small>&lt;/span>
.&lt;/p>
&lt;h2 id="closing">Closing&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#closing">#&lt;/a>&lt;/h2>
&lt;p>It was fun coming up with an initial draft of the specification for
this library. My next steps would be to gradually add TOML generator
functions (as I find time) to this library, along with &lt;a href="https://scripter.co/quick-intro-to-emacs-lisp-regression-testing/">ERT tests&lt;/a>!
Eventually, I will remove the existing TOML generation code from
&lt;code>ox-hugo&lt;/code> and depend on this library.&lt;/p>
&lt;p>Getting back to &lt;a href="https://github.com/kaushalmodi/ox-hugo/pull/504#issuecomment-1093592230">my plan&lt;/a> for exporting &lt;code>:LOGBOOK:&lt;/code> drawers in
&lt;code>ox-hugo&lt;/code>, based on this spec, I will need to construct this date in
Emacs-Lisp:&lt;/p>
&lt;p>&lt;a id="code-snippet--org-logbook-lisp">&lt;/a>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-emacs-lisp" data-lang="emacs-lisp">&lt;span class="line">&lt;span class="cl">&lt;span class="p">(&lt;/span>&lt;span class="nv">org_logbook&lt;/span> &lt;span class="o">.&lt;/span> &lt;span class="p">(((&lt;/span>&lt;span class="nv">timestamp&lt;/span> &lt;span class="o">.&lt;/span> &lt;span class="nv">2022-04-08T14:53:00-04:00&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">note&lt;/span> &lt;span class="o">.&lt;/span> &lt;span class="s">&amp;#34;This note addition prompt shows up on typing the `C-c C-z` binding.\nSee [org#Drawers](https://www.gnu.org/software/emacs/manual/html_mono/org.html#Drawers).&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">((&lt;/span>&lt;span class="nv">timestamp&lt;/span> &lt;span class="o">.&lt;/span> &lt;span class="nv">2018-09-06T11:45:00-04:00&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">note&lt;/span> &lt;span class="o">.&lt;/span> &lt;span class="s">&amp;#34;Another note **bold** _italics_.&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">((&lt;/span>&lt;span class="nv">timestamp&lt;/span> &lt;span class="o">.&lt;/span> &lt;span class="nv">2018-09-06T11:37:00-04:00&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">note&lt;/span> &lt;span class="o">.&lt;/span> &lt;span class="s">&amp;#34;A note `mono`.&amp;#34;&lt;/span>&lt;span class="p">))))&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="src-block-caption">
&lt;span class="src-block-number">&lt;a href="#code-snippet--org-logbook-lisp">Code Snippet 1&lt;/a>:&lt;/span>
Example data from a &lt;code>:LOGBOOK:&lt;/code> drawer in Lisp format
&lt;/div>
&lt;p>.. will translate to this in TOML:&lt;/p>
&lt;p>&lt;a id="code-snippet--org-logbook-toml">&lt;/a>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-toml" data-lang="toml">&lt;span class="line">&lt;span class="cl">&lt;span class="p">[[&lt;/span>&lt;span class="nx">org_logbook&lt;/span>&lt;span class="p">]]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">timestamp&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="ld">2022-04-08T14:53:00-04:00&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">note&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;This note addition prompt shows up on typing the `C-c C-z` binding.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s2">See [org#Drawers](https://www.gnu.org/software/emacs/manual/html_mono/org.html#Drawers).&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">[[&lt;/span>&lt;span class="nx">org_logbook&lt;/span>&lt;span class="p">]]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">timestamp&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="ld">2018-09-06T11:45:00-04:00&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">note&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;Another note **bold** _italics_.&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">[[&lt;/span>&lt;span class="nx">org_logbook&lt;/span>&lt;span class="p">]]&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">timestamp&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="ld">2018-09-06T11:37:00-04:00&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nx">note&lt;/span> &lt;span class="p">=&lt;/span> &lt;span class="s2">&amp;#34;&amp;#34;&amp;#34;A note `mono`.&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="src-block-caption">
&lt;span class="src-block-number">&lt;a href="#code-snippet--org-logbook-toml">Code Snippet 2&lt;/a>:&lt;/span>
Same example data from the &lt;code>:LOGBOOK:&lt;/code> drawer translated to TOML format
&lt;/div>
&lt;hr>
&lt;p>Check out the below link where I have documented the equivalent
expressions between Lisp, TOML and JSON for all the object types.&lt;/p>
&lt;p>👉 &lt;a href="https://github.com/kaushalmodi/tomelr/blob/main/README.org">tom​&lt;strong>el&lt;/strong>​r Specification&lt;/a>&lt;/p></description><author>Kaushal.Modi@fakeEmailToMakeValidatorHappy.com (Kaushal Modi)</author><category domain="https://scripter.co/categories/emacs">emacs</category><category domain="https://scripter.co/categories/elisp">elisp</category><category domain="https://scripter.co/tags/toml">toml</category><category domain="https://scripter.co/tags/100daystooffload">100DaysToOffload</category><guid>https://scripter.co/defining-tomelr/</guid><pubDate>Thu, 28 Apr 2022 00:08:00 -0400</pubDate></item><item><title>Straight and Curved Quotes in Emacs Lisp</title><link>https://scripter.co/straight-and-curved-quotes-in-emacs-lisp/</link><description>&lt;blockquote>A short guide for getting the single quotes rendered as expected
(straight or curved) in Emacs Lisp function and variable documentation
strings and &lt;code>message&lt;/code> outputs.&lt;/blockquote>&lt;div class="ox-hugo-toc toc">
&lt;div class="heading">Table of Contents&lt;/div>
&lt;ul>
&lt;li>&lt;a href="#emacs-25-curved-quotes-rendering-feature">Emacs 25 &amp;ndash; Curved quotes rendering feature&lt;/a>&lt;/li>
&lt;li>&lt;a href="#preventing-curved-quotes-in-documentation-strings">Preventing curved quotes in documentation strings&lt;/a>&lt;/li>
&lt;li>&lt;a href="#preventing-curved-quotes-in-messages">Preventing curved quotes in messages&lt;/a>&lt;/li>
&lt;li>&lt;a href="#summary">Summary&lt;/a>&lt;/li>
&lt;li>&lt;a href="#references">References&lt;/a>&lt;/li>
&lt;/ul>
&lt;/div>
&lt;!--endtoc-->
&lt;p>This post is related to the curved quotes feature from Emacs 25
(September 2016), but I got inspired to write this as I was reading
the NEWS file of the recently released Emacs 28.1 ✨ .. &lt;a href="https://git.savannah.gnu.org/cgit/emacs.git/tree/etc/NEWS?h=emacs-28&amp;amp;id=886339747b8d34fc09fd69a143cf548daf92dce6#n219">this
specific part related to the &lt;code>shortdoc&lt;/code> library&lt;/a>. Turns out that I had
never used &lt;code>shortdoc&lt;/code>
&lt;span class="sidenote-number">&lt;small class="sidenote">
If you also haven&amp;rsquo;t used this library before, it&amp;rsquo;s basically a &amp;ldquo;cheat
sheet generator&amp;rdquo; or an aggregator of related functions grouped
together with code examples and their outputs &amp;ndash; for example,
functions related to string manipulation, buffer operations, etc. It
is like &lt;a href="https://tldr.sh/">https://tldr.sh/&lt;/a> but for Emacs Lisp functions.
&lt;/small>&lt;/span>
. So as I was reading through the help for various user-facing
&lt;code>shortdoc&lt;/code> functions, &lt;code>C-h f shortdoc-add-function&lt;/code> led me to
this:&lt;/p>
&lt;p>&lt;a id="figure--elisp-curved-quotes--docstring-unexpected-curved-quotes">&lt;/a>&lt;/p>
&lt;figure>
&lt;a href="shortdoc-add-function-curved-quotes-at-wrong-place.png">
&lt;img src="https://scripter.co/straight-and-curved-quotes-in-emacs-lisp/shortdoc-add-function-curved-quotes-at-wrong-place.png" alt="Figure 1: Notice the curved closing single quotations in the Emacs Lisp example in that doc string"/> &lt;/a>&lt;figcaption>
&lt;p>
&lt;span class="figure-number">Figure 1: &lt;/span>Notice the curved closing single quotations in the Emacs Lisp example in that doc string
&lt;/p>
&lt;/figcaption>&lt;/figure>
&lt;p>We don&amp;rsquo;t have curved quotes in Emacs Lisp syntax 🧐
.. So I look up the &lt;a href="https://git.savannah.gnu.org/cgit/emacs.git/tree/lisp/emacs-lisp/shortdoc.el?h=emacs-28&amp;amp;id=98abf01fd681931f8870569ff559b547579d7cef#n1349">source of &lt;code>shortdoc-add-function&lt;/code>&lt;/a>:&lt;/p>
&lt;p>&lt;a id="code-snippet--elisp-curved-quotes--docstring-unescaped-single-quotes">&lt;/a>&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1349
&lt;/span>&lt;span class="lnt">1350
&lt;/span>&lt;span class="lnt">1351
&lt;/span>&lt;span class="lnt">1352
&lt;/span>&lt;span class="lnt">1353
&lt;/span>&lt;span class="lnt">1354
&lt;/span>&lt;span class="lnt">1355
&lt;/span>&lt;span class="lnt">1356
&lt;/span>&lt;span class="hl">&lt;span class="lnt">1357
&lt;/span>&lt;/span>&lt;span class="hl">&lt;span class="lnt">1358
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-emacs-lisp" data-lang="emacs-lisp">&lt;span class="line">&lt;span class="cl">&lt;span class="p">(&lt;/span>&lt;span class="nb">defun&lt;/span> &lt;span class="nv">shortdoc-add-function&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">group&lt;/span> &lt;span class="nv">section&lt;/span> &lt;span class="nv">elem&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;Add ELEM to shortdoc GROUP in SECTION.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">If GROUP doesn&amp;#39;t exist, it will be created.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">If SECTION doesn&amp;#39;t exist, it will be added.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">Example:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> (shortdoc-add-function
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line hl">&lt;span class="cl">&lt;span class="s"> &amp;#39;file \&amp;#34;Predicates\&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line hl">&lt;span class="cl">&lt;span class="s"> &amp;#39;(file-locked-p :no-eval (file-locked-p \&amp;#34;/tmp\&amp;#34;)))&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="src-block-caption">
&lt;span class="src-block-number">&lt;a href="#code-snippet--elisp-curved-quotes--docstring-unescaped-single-quotes">Code Snippet 1&lt;/a>:&lt;/span>
Unescaped single quotes in Emacs Lisp documentation strings
&lt;/div>
&lt;p>The documentation looks correct at first glance, but there&amp;rsquo;s a minor
problem. This post talks about how to fix that documentation and then
a bit more on how to deal with auto-conversion to curved quotes.&lt;/p>
&lt;h2 id="emacs-25-curved-quotes-rendering-feature">Emacs 25 &amp;ndash; Curved quotes rendering feature&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#emacs-25-curved-quotes-rendering-feature">#&lt;/a>&lt;/h2>
&lt;p>Emacs 25 had landed with one of my favorite new features &amp;mdash; the
auto-rendering of grave accents (&lt;code>`&lt;/code>) and straight quotes (&lt;code>'&lt;/code>​) to
curved (or curly) quotes (&lt;code>‘&lt;/code>, &lt;code>’&lt;/code>). From &lt;a href="https://git.savannah.gnu.org/cgit/emacs.git/tree/etc/NEWS?h=emacs-25#n1284">its &lt;code>NEWS&lt;/code> file&lt;/a>:&lt;/p>
&lt;blockquote>
&lt;p>New variable &amp;rsquo;text-quoting-style&amp;rsquo; to control how Emacs translates
quotes. Set it to &amp;lsquo;curve&amp;rsquo; for curved single quotes, to &amp;lsquo;straight&amp;rsquo; for
straight apostrophes, and to &amp;lsquo;grave&amp;rsquo; for grave accent and apostrophe.
The default value nil acts like &amp;lsquo;curve&amp;rsquo; if curved single quotes are
displayable, and like &amp;lsquo;grave&amp;rsquo; otherwise. The new variable affects
display of diagnostics and help, but not of info. As the variable is
not intended for casual use, it is not a user option.&lt;/p>
&lt;/blockquote>
&lt;p>I like the rendering to curved quotes because it improves the
readability of documentation strings in the ∗Help∗ buffers (&lt;code>C-h v&lt;/code>,
&lt;code>C-h f&lt;/code>, etc.), and they look great in ∗Messages∗ buffer. It helps
differentiate between typographical use of quotes like in ‘Hello’
versus the straight quotes used in Emacs Lisp syntax like
&lt;code class="code-inline language-emacs-lisp">&lt;span class="p">(&lt;/span>&lt;span class="nb">setq&lt;/span> &lt;span class="nv">foo&lt;/span> &lt;span class="ss">&amp;#39;some-symbol&lt;/span>&lt;span class="p">)&lt;/span>&lt;/code> or the usage of
grave accents like &lt;code class="code-inline language-emacs-lisp">&lt;span class="p">(&lt;/span>&lt;span class="nb">setq&lt;/span> &lt;span class="nv">foo&lt;/span> &lt;span class="o">`&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="o">,&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nf">+&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)))&lt;/span>&lt;/code>.&lt;/p>
&lt;p>This feature renders grave accents (&lt;code>`&lt;/code>) to &lt;code>‘&lt;/code> and straight quotes
(&lt;code>'&lt;/code>​) to &lt;code>’&lt;/code> by default. But there would be times when you would want
to literally print grave accents or straight quotes in ∗Help∗ and
∗Messages∗ buffers.&lt;/p>
&lt;h2 id="preventing-curved-quotes-in-documentation-strings">Preventing curved quotes in documentation strings&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#preventing-curved-quotes-in-documentation-strings">#&lt;/a>&lt;/h2>
&lt;p>In documentation strings, some times you would want to show literal
straight quotes or grave accents, for instance, when showing some
example Emacs Lisp code snippets in there. &lt;a href="#figure--elisp-curved-quotes--docstring-unexpected-curved-quotes">Above image&lt;/a> shows one of
such examples.&lt;/p>
&lt;p>More than the visual inaccuracy of seeing curved quoted where straight
quotes should be,
&lt;mark>if someone copies that code to try it out, it will
not work&lt;/mark> !&lt;/p>
&lt;p>This should be fixed by escaping the &lt;code>'&lt;/code> characters.&lt;/p>
&lt;div class="note">
&lt;p>Emacs Lisp uses a special escaping sequence in documentation strings:
&lt;code>\\=&lt;/code>.&lt;/p>
&lt;ul>
&lt;li>Escape &lt;code>`&lt;/code> using &lt;code>\\=`&lt;/code>.&lt;/li>
&lt;li>Escape &lt;code>'&lt;/code> using &lt;code>\\='&lt;/code>.&lt;/li>
&lt;/ul>
&lt;/div>
&lt;p>Below we see
&lt;a href="#code-snippet--elisp-curved-quotes--docstring-unescaped-single-quotes">Code Snippet 1&lt;/a> after
this fix:&lt;/p>
&lt;p>&lt;a id="code-snippet--elisp-curved-quotes--docstring-escaped-single-quotes">&lt;/a>&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1349
&lt;/span>&lt;span class="lnt">1350
&lt;/span>&lt;span class="lnt">1351
&lt;/span>&lt;span class="lnt">1352
&lt;/span>&lt;span class="lnt">1353
&lt;/span>&lt;span class="lnt">1354
&lt;/span>&lt;span class="lnt">1355
&lt;/span>&lt;span class="lnt">1356
&lt;/span>&lt;span class="hl">&lt;span class="lnt">1357
&lt;/span>&lt;/span>&lt;span class="hl">&lt;span class="lnt">1358
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-emacs-lisp" data-lang="emacs-lisp">&lt;span class="line">&lt;span class="cl">&lt;span class="p">(&lt;/span>&lt;span class="nb">defun&lt;/span> &lt;span class="nv">shortdoc-add-function&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">group&lt;/span> &lt;span class="nv">section&lt;/span> &lt;span class="nv">elem&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;Add ELEM to shortdoc GROUP in SECTION.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">If GROUP doesn&amp;#39;t exist, it will be created.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">If SECTION doesn&amp;#39;t exist, it will be added.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">Example:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> (shortdoc-add-function
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line hl">&lt;span class="cl">&lt;span class="s"> \\=&amp;#39;file \&amp;#34;Predicates\&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line hl">&lt;span class="cl">&lt;span class="s"> \\=&amp;#39;(file-locked-p :no-eval (file-locked-p \&amp;#34;/tmp\&amp;#34;)))&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="src-block-caption">
&lt;span class="src-block-number">&lt;a href="#code-snippet--elisp-curved-quotes--docstring-escaped-single-quotes">Code Snippet 2&lt;/a>:&lt;/span>
Correctly escaped single quotes in Emacs Lisp documentation strings
&lt;/div>
&lt;p>.. and now the straight quotes get rendered as straight quotes:&lt;/p>
&lt;p>&lt;a id="figure--elisp-curved-quotes--docstring-escaped-single-quotes">&lt;/a>&lt;/p>
&lt;figure>
&lt;a href="shortdoc-add-function-single-quotes-correctly-escaped.png">
&lt;img src="https://scripter.co/straight-and-curved-quotes-in-emacs-lisp/shortdoc-add-function-single-quotes-correctly-escaped.png" alt="Figure 2: Correctly escaped single quotes in documentation strings"/> &lt;/a>&lt;figcaption>
&lt;p>
&lt;span class="figure-number">Figure 2: &lt;/span>Correctly escaped single quotes in documentation strings
&lt;/p>
&lt;/figcaption>&lt;/figure>
&lt;h2 id="preventing-curved-quotes-in-messages">Preventing curved quotes in messages&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#preventing-curved-quotes-in-messages">#&lt;/a>&lt;/h2>
&lt;p>The same auto-rendering of curved quotes happens in the messages that
get printed to the ∗Messages∗ buffer and Echo Area as well. So the
below behavior where &lt;strong>‘Hi’&lt;/strong> gets printed is expected and nice:&lt;/p>
&lt;p>&lt;a id="code-snippet--elisp-curved-quotes--message-lsqm-hi-rsqm">&lt;/a>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-emacs-lisp" data-lang="emacs-lisp">&lt;span class="line">&lt;span class="cl">&lt;span class="p">(&lt;/span>&lt;span class="nf">message&lt;/span> &lt;span class="s">&amp;#34;&lt;/span>&lt;span class="ss">`Hi&amp;#39;&lt;/span>&lt;span class="s">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">;=&amp;gt; &amp;#34;‘Hi’&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="src-block-caption">
&lt;span class="src-block-number">&lt;a href="#code-snippet--elisp-curved-quotes--message-lsqm-hi-rsqm">Code Snippet 3&lt;/a>:&lt;/span>
&lt;code>message&lt;/code> auto-converting &lt;code>`Hi'&lt;/code> to &lt;code>‘Hi’&lt;/code>
&lt;/div>
&lt;p>But if someone does below, the rendered &lt;strong>’Hi’&lt;/strong> looks pretty odd:&lt;/p>
&lt;p>&lt;a id="code-snippet--elisp-curved-quotes--message-rsqm-hi-rsqm">&lt;/a>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-emacs-lisp" data-lang="emacs-lisp">&lt;span class="line">&lt;span class="cl">&lt;span class="p">(&lt;/span>&lt;span class="nf">message&lt;/span> &lt;span class="s">&amp;#34;&amp;#39;Hi&amp;#39;&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">;=&amp;gt; &amp;#34;’Hi’&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="src-block-caption">
&lt;span class="src-block-number">&lt;a href="#code-snippet--elisp-curved-quotes--message-rsqm-hi-rsqm">Code Snippet 4&lt;/a>:&lt;/span>
&lt;code>message&lt;/code> auto-converting &lt;code>'Hi'&lt;/code> to &lt;code>’Hi’&lt;/code> 😮
&lt;/div>
&lt;p>I had come across this surprise rendering in Emacs 25 and had raised
&lt;a href="https://lists.gnu.org/archive/html/bug-gnu-emacs/2015-10/msg00234.html">this issue&lt;/a> then. From Paul Eggert&amp;rsquo;s reply, I learned that to prevent
this curved quote conversion, we should make the &lt;code>message&lt;/code> function
print the string processed by the &lt;code>format&lt;/code> function and not the string
directly. You could do this ..&lt;/p>
&lt;p>&lt;a id="code-snippet--elisp-curved-quotes--fix-message-format">&lt;/a>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-emacs-lisp" data-lang="emacs-lisp">&lt;span class="line">&lt;span class="cl">&lt;span class="p">(&lt;/span>&lt;span class="nf">message&lt;/span> &lt;span class="s">&amp;#34;%s&amp;#34;&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">format&lt;/span> &lt;span class="s">&amp;#34;%s&amp;#34;&lt;/span> &lt;span class="s">&amp;#34;&amp;#39;Hi&amp;#39;&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="c1">;=&amp;gt;&amp;#34;&amp;#39;Hi&amp;#39;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="src-block-caption">
&lt;span class="src-block-number">&lt;a href="#code-snippet--elisp-curved-quotes--fix-message-format">Code Snippet 5&lt;/a>:&lt;/span>
Passing &lt;code>format&lt;/code> formatted string to &lt;code>message&lt;/code>
&lt;/div>
&lt;p>But below will do the same thing and is much easier to type:&lt;/p>
&lt;p>&lt;a id="code-snippet--elisp-curved-quotes--fix-message-format-indirect">&lt;/a>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-emacs-lisp" data-lang="emacs-lisp">&lt;span class="line">&lt;span class="cl">&lt;span class="p">(&lt;/span>&lt;span class="nf">message&lt;/span> &lt;span class="s">&amp;#34;%s&amp;#34;&lt;/span> &lt;span class="s">&amp;#34;&amp;#39;Hi&amp;#39;&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">;=&amp;gt; &amp;#34;&amp;#39;Hi&amp;#39;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">(&lt;/span>&lt;span class="nf">message&lt;/span> &lt;span class="s">&amp;#34;%s&amp;#34;&lt;/span> &lt;span class="s">&amp;#34;&lt;/span>&lt;span class="ss">`Hi&amp;#39;&lt;/span>&lt;span class="s">&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">;=&amp;gt; &amp;#34;`Hi&amp;#39;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="src-block-caption">
&lt;span class="src-block-number">&lt;a href="#code-snippet--elisp-curved-quotes--fix-message-format-indirect">Code Snippet 6&lt;/a>:&lt;/span>
Implicit passing of a string to &lt;code>format&lt;/code> for formatting, before getting printed by &lt;code>message&lt;/code>
&lt;/div>
&lt;h2 id="summary">Summary&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#summary">#&lt;/a>&lt;/h2>
&lt;p>&lt;a id="table--elisp-curved-quotes--summary">&lt;/a>&lt;/p>
&lt;div class="table-caption">
&lt;span class="table-number">&lt;a href="#table--elisp-curved-quotes--summary">Table 1&lt;/a>:&lt;/span>
Summary of techniques for enforcing straight quotes and grave accents in printed messages and documentation strings
&lt;/div>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Description&lt;/th>
&lt;th>Solution&lt;/th>
&lt;th>Outcome&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Render &lt;code>'&lt;/code> in docstring&lt;/td>
&lt;td>&lt;em>doc string snippet&lt;/em>: &lt;code>&amp;quot;foo \\='bar&amp;quot;&lt;/code>&lt;/td>
&lt;td>&lt;code>foo 'bar&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Render &lt;code>`&lt;/code> in docstring&lt;/td>
&lt;td>&lt;em>doc string snippet&lt;/em>: &lt;code>&amp;quot;foo \\=`bar&amp;quot;&lt;/code>&lt;/td>
&lt;td>&lt;code>foo `bar&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Print &lt;code>'&lt;/code> as &lt;code>'&lt;/code>&lt;/td>
&lt;td>&lt;code>(message &amp;quot;%s&amp;quot; &amp;quot;'foo&amp;quot;)&lt;/code>&lt;/td>
&lt;td>&lt;code>'foo&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Print &lt;code>`&lt;/code> as &lt;code>`&lt;/code>&lt;/td>
&lt;td>&lt;code>(message &amp;quot;%s&amp;quot; &amp;quot;`foo&amp;quot;)&lt;/code>&lt;/td>
&lt;td>&lt;code>`foo&lt;/code>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>By the way, I submitted a patch for fixing the escaping of straight
quotes in &lt;code>shortdoc-add-function&lt;/code> documentation string
&lt;span class="sidenote-number">&lt;small class="sidenote">
I planned to fix just this straight quote escaping issue, but then I
also ended up slightly improving the documentation of the &lt;code>(FUNC :eval EVAL)&lt;/code> and other forms used for adding a function&amp;rsquo;s documentation to
&lt;code>shortdoc&lt;/code>.
&lt;/small>&lt;/span>
in &lt;a href="https://debbugs.gnu.org/cgi/bugreport.cgi?bug=54782">debbugs # 54782&lt;/a> and now it&amp;rsquo;s &lt;a href="https://git.savannah.gnu.org/cgit/emacs.git/commit/?id=cca47ae555bfddf87b4871988555738c335f8457">in the emacs &lt;em>master&lt;/em> branch&lt;/a>! 🎉&lt;/p>
&lt;h2 id="references">References&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#references">#&lt;/a>&lt;/h2>
&lt;dl>
&lt;dt>Fixing curved quotes conversion in documentation strings&lt;/dt>
&lt;dd>&lt;ul>
&lt;li>&lt;a href="https://www.gnu.org/software/emacs/manual/html_node/elisp/Text-Quoting-Style.html" title="Emacs Lisp: (info &amp;quot;(elisp) Text Quoting Style&amp;quot;)">Elisp Info: Text Quoting Style&lt;/a>&lt;/li>
&lt;/ul>
&lt;/dd>
&lt;dt>Fixing curved quotes conversion in printed messages&lt;/dt>
&lt;dd>&lt;ul>
&lt;li>&lt;a href="https://www.gnu.org/software/emacs/manual/html_node/elisp/Formatting-Strings.html" title="Emacs Lisp: (info &amp;quot;(elisp) Formatting Strings&amp;quot;)">Elisp Info: Formatting Strings&lt;/a>&lt;/li>
&lt;/ul>
&lt;/dd>
&lt;/dl></description><author>Kaushal.Modi@fakeEmailToMakeValidatorHappy.com (Kaushal Modi)</author><category domain="https://scripter.co/categories/emacs">emacs</category><category domain="https://scripter.co/categories/elisp">elisp</category><category domain="https://scripter.co/tags/100daystooffload">100DaysToOffload</category><category domain="https://scripter.co/tags/single-quotes">single-quotes</category><category domain="https://scripter.co/tags/grave-accents">grave-accents</category><guid>https://scripter.co/straight-and-curved-quotes-in-emacs-lisp/</guid><pubDate>Sun, 10 Apr 2022 10:05:00 -0400</pubDate></item><item><title>Using Emacs advice to silence messages from functions</title><link>https://scripter.co/using-emacs-advice-to-silence-messages-from-functions/</link><description>&lt;blockquote>Using the &lt;code>:around&lt;/code> advice to prevent messages from being displayed in
the &lt;em>echo&lt;/em> area or the ∗Messages∗ buffer.&lt;/blockquote>&lt;div class="ox-hugo-toc toc">
&lt;div class="heading">Table of Contents&lt;/div>
&lt;ul>
&lt;li>&lt;a href="#problem-statement">Problem Statement&lt;/a>&lt;/li>
&lt;li>&lt;a href="#course-of-action">Course of action&lt;/a>&lt;/li>
&lt;li>&lt;a href="#finding-the-culprit-functions">&lt;span class="section-num">1&lt;/span> Finding the culprit functions&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#using-plain-old-grep-or-ripgrep--rg">Using plain-old &lt;em>grep&lt;/em> or &lt;em>ripgrep (rg)&lt;/em>&lt;/a>&lt;/li>
&lt;li>&lt;a href="#using-debug-on-message">Using &lt;code>debug-on-message&lt;/code>&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#figure-out-how-to-silence-those-messages">&lt;span class="section-num">2&lt;/span> Figure out how to silence those messages&lt;/a>&lt;/li>
&lt;li>&lt;a href="#applying-the-silence-advice">&lt;span class="section-num">3&lt;/span> Applying the &amp;lsquo;silence&amp;rsquo; advice&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#limiting-the-scope-of-these-advices">Limiting the scope of these advices&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#conclusion">Conclusion&lt;/a>&lt;/li>
&lt;/ul>
&lt;/div>
&lt;!--endtoc-->
&lt;p>Today I was working on a minor cleanup of the &lt;code>ox-hugo&lt;/code> package and as
a part of that, I figured out a way to reduce the noisy messages
printed in the ∗Messages∗
&lt;span class="sidenote-number">&lt;small class="sidenote">
Random tidbit: You can quickly access the ∗Messages∗ buffer using the
default binding &lt;code>C-h e&lt;/code>.
&lt;/small>&lt;/span>
buffer. This post describes how I used the &lt;a href="https://www.gnu.org/software/emacs/manual/html_node/elisp/Advising-Functions.html">📖 Emacs Advice
feature&lt;/a> to solve the noisy-messages-buffer problem.&lt;/p>
&lt;dl>
&lt;dt>What is this &amp;lsquo;advising&amp;rsquo;?&lt;/dt>
&lt;dd>In Emacs, &lt;em>advising&lt;/em>
&lt;span class="sidenote-number">&lt;small class="sidenote">
&lt;em>Advice&lt;/em> with a &amp;lsquo;c&amp;rsquo; is the &lt;em>noun&lt;/em> form. The &lt;em>noun&lt;/em> form is used
everywhere in the Emacs advising API e.g. &lt;code>advice-add&lt;/code>,
&lt;code>advice-remove&lt;/code>. &lt;em>Advise&lt;/em> with an &amp;rsquo;s&amp;rsquo; is the &lt;em>verb&lt;/em> form. &lt;a href="https://www.merriam-webster.com/words-at-play/advise-vs-advice-usage">More
Reading&lt;/a>.
&lt;/small>&lt;/span>
is a means to modify a function defined in any library &amp;mdash; You can
modify the arguments to the function, return values of that
function, run other functions before and/or after that advised
function, .. or may be just replace that function with something
else entirely!&lt;/dd>
&lt;/dl>
&lt;h2 id="problem-statement">Problem Statement&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#problem-statement">#&lt;/a>&lt;/h2>
&lt;p>If I run &lt;code>C-c C-e H A&lt;/code>
&lt;span class="sidenote-number">&lt;small class="sidenote">
This is the default binding of &lt;code>ox-hugo&lt;/code> to export all the post
subtrees in the current Org file.
&lt;/small>&lt;/span>
in this site&amp;rsquo;s Org file, I will see a wall of messages, which won&amp;rsquo;t be
too useful to look at if I am exporting dozens of posts in one go.&lt;/p>
&lt;p>Here&amp;rsquo;s a snippet from that which shows the text printed while two of
the posts were being exporting:&lt;/p>
&lt;p>&lt;a id="code-snippet--silence-messages-noise">&lt;/a>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">[ox-hugo] 60/ Exporting ‘Nim: Deploying static binaries’ ..
&lt;/span>&lt;/span>&lt;span class="line hl">&lt;span class="cl">org-babel-exp process nim at position 246918...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Evaluation of this nim code block is disabled.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">org-babel-exp process shell at position 246985...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">org-babel-exp process shell at position 247505...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">org-babel-exp process shell at position 248054...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">org-babel-exp process nim at position 250620...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Evaluation of this nim code block (code__musl_config_nims) is disabled.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">org-babel-exp process yaml at position 253830...
&lt;/span>&lt;/span>&lt;span class="line hl">&lt;span class="cl">Wrote /home/kmodi/hugo/scripter.co/content/posts/nim-deploying-static-binaries/index.md
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[ox-hugo] 61/ Exporting ‘Binding Nim to C++ std::list’ ..
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">org-babel-exp process nim at position 261418...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Evaluation of this nim code block (code__nim_bindings_importcpp) is disabled.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">org-babel-exp process nim at position 262802...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Evaluation of this nim code block (code__nim_std_list_bindings) is disabled.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">org-babel-exp process cpp at position 266921...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">org-babel-exp process nim at position 267957...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Evaluation of this nim code block (code__nim_bindings_emit) is disabled.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">org-babel-exp process nim at position 268861...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Evaluation of this nim code block (code__nim_bindings_test_nim_code) is disabled.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">org-babel-exp process text at position 269849...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">org-babel-exp process nim at position 270412...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Evaluation of this nim code block (code__nim_bindings_toSeq) is disabled.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">org-babel-exp process nim at position 271808...
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Evaluation of this nim code block (code__nim_bindings_full_code) is disabled.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">Wrote /home/kmodi/hugo/scripter.co/content/posts/binding-nim-to-c-plus-plus-std-list/index.md
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="src-block-caption">
&lt;span class="src-block-number">&lt;a href="#code-snippet--silence-messages-noise">Code Snippet 1&lt;/a>:&lt;/span>
Snippet of the noise seen in the &amp;lowast;Messages&amp;lowast; buffer when exporting all post subtrees using &lt;code>ox-hugo&lt;/code>
&lt;/div>
&lt;p>After applying the advices described in this post, that output
reduced to this:&lt;/p>
&lt;p>&lt;a id="code-snippet--silence-messages-denoised">&lt;/a>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">[ox-hugo] 60/ Exporting ‘Nim: Deploying static binaries’ ..
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">[ox-hugo] 61/ Exporting ‘Binding Nim to C++ std::list’ ..
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="src-block-caption">
&lt;span class="src-block-number">&lt;a href="#code-snippet--silence-messages-denoised">Code Snippet 2&lt;/a>:&lt;/span>
Snippet of the &amp;lowast;Messages&amp;lowast; buffer after applying the &lt;i>silencing&lt;/i> advices
&lt;/div>
&lt;h2 id="course-of-action">Course of action&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#course-of-action">#&lt;/a>&lt;/h2>
&lt;p>Here is how I tackled this problem:&lt;/p>
&lt;ol>
&lt;li>Find the functions responsible for printing those messages.&lt;/li>
&lt;li>Figure out how to silence those messages.&lt;/li>
&lt;li>Advise those functions (temporarily, while the export is happening)
to not print those messages.&lt;/li>
&lt;/ol>
&lt;h2 id="finding-the-culprit-functions">&lt;span class="section-num">1&lt;/span> Finding the culprit functions&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#finding-the-culprit-functions">#&lt;/a>&lt;/h2>
&lt;p>In &lt;a href="#code-snippet--silence-messages-noise">Code Snippet 1&lt;/a>, I have highlighted 2 lines. I first
needed to figure out which functions printed those messages. I&amp;rsquo;ll
show two methods of finding sources of any printed messages.&lt;/p>
&lt;h3 id="using-plain-old-grep-or-ripgrep--rg">Using plain-old &lt;em>grep&lt;/em> or &lt;em>ripgrep (rg)&lt;/em>&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#using-plain-old-grep-or-ripgrep--rg">#&lt;/a>&lt;/h3>
&lt;p>This method is pretty easy (but not robust) to use if the search
string is unique enough. The first highlighted line above is a good
candidate for this:&lt;/p>
&lt;blockquote>
&lt;p>org-babel-exp process nim at position 246918&amp;hellip;&lt;/p>
&lt;/blockquote>
&lt;p>The message gives me a clue that it&amp;rsquo;s originating somewhere in the
&lt;em>Org Babel&lt;/em> source code (one of the &lt;code>ob-*.el&lt;/code> files). So I &lt;em>cd&lt;/em> to the
Org source directory and search for the &lt;em>&amp;ldquo;org-babel-exp process ..&amp;rdquo;&lt;/em>
string using &lt;a href="https://github.com/BurntSushi/ripgrep">&lt;code>rg&lt;/code>&lt;/a> ..&lt;/p>
&lt;p>&lt;a id="figure--finding-org-babel-exp-process-message">&lt;/a>&lt;/p>
&lt;figure>
&lt;a href="finding-org-babel-exp-process-message.png">
&lt;img src="https://scripter.co/using-emacs-advice-to-silence-messages-from-functions/finding-org-babel-exp-process-message.png" alt="Figure 1: Finding the source of &amp;ldquo;org-babel-exp process ..&amp;rdquo; message using ripgrep (rg) in Org source"/> &lt;/a>&lt;figcaption>
&lt;p>
&lt;span class="figure-number">Figure 1: &lt;/span>Finding the source of &amp;ldquo;org-babel-exp process ..&amp;rdquo; message using ripgrep (&lt;code>rg&lt;/code>) in Org source
&lt;/p>
&lt;/figcaption>&lt;/figure>
&lt;p>.. and sure enough, there is this one and only one hit .. in the
&lt;code>org-babel-exp-src-block&lt;/code> function in &lt;a href="https://git.savannah.gnu.org/cgit/emacs/org-mode.git/tree/lisp/ob-exp.el/?id=91681fc03334285dc0879fcb9a27583bd7ab9782#n93">&lt;code>lisp/ob-exp.el&lt;/code>&lt;/a>.&lt;/p>
&lt;p>But this method is not robust for some obvious reasons like,&lt;/p>
&lt;ul>
&lt;li>the string being &lt;em>grepped&lt;/em> might not be present in raw literal form
like above,&lt;/li>
&lt;li>may be it&amp;rsquo;s created through variable interpolation, or&lt;/li>
&lt;li>may be that whole string is not present on the same line; may be it
wrapped to the next line.&lt;/li>
&lt;/ul>
&lt;p>While this &lt;em>grep&lt;/em> method helped find the source of &lt;em>&amp;ldquo;org-babel-exp
process ..&amp;rdquo;&lt;/em>, let&amp;rsquo;s use a better way of source tracing for the next
message.&lt;/p>
&lt;h3 id="using-debug-on-message">Using &lt;code>debug-on-message&lt;/code>&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#using-debug-on-message">#&lt;/a>&lt;/h3>
&lt;p>From &lt;code>C-h v debug-on-message&lt;/code>, we see:&lt;/p>
&lt;blockquote>
&lt;p>If non-nil, debug if a message matching this regexp is displayed.&lt;/p>
&lt;/blockquote>
&lt;p>This means that each time a message gets displayed that matches the
regular expression set in this variable, we get a backtrace to that
message. &lt;em>Who said we can get backtraces for errors only‽&lt;/em> 🤓&lt;/p>
&lt;p>The second highlighted line from &lt;a href="#code-snippet--silence-messages-noise">Code Snippet 1&lt;/a> is:&lt;/p>
&lt;blockquote>
&lt;p>Wrote /home/kmodi/hugo/scripter.co/content/posts/nim-deploying-static-binaries/index.md&lt;/p>
&lt;/blockquote>
&lt;p>After evaluating &lt;code class="code-inline language-emacs-lisp">&lt;span class="p">(&lt;/span>&lt;span class="nb">setq&lt;/span> &lt;span class="nv">debug-on-message&lt;/span> &lt;span class="s">&amp;#34;Wrote&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;/code>, when I exported this post, I saw a backtrace that looked
like this.&lt;/p>
&lt;p>&lt;a id="code-snippet--debug-on-message-backtrace">&lt;/a>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">Debugger entered--Lisp error: &amp;#34;Wrote /home/kmodi/hugo/scripter.co/content/posts/u...&amp;#34;
&lt;/span>&lt;/span>&lt;span class="line hl">&lt;span class="cl"> write-region(nil nil &amp;#34;/home/kmodi/hugo/scripter.co/ ..
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> basic-save-buffer-2()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> basic-save-buffer-1()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ..
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> basic-save-buffer(nil)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> save-buffer()
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> write-file(&amp;#34;/home/kmodi/hugo/scripter.co/content/posts/using-e...&amp;#34;)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ..
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> org-export-to-file(hugo &amp;#34;/home/kmodi/hugo/scripter.co/content/posts/using-e...&amp;#34; ..
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> org-hugo-export-to-md(nil :subtreep nil)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> ..
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="src-block-caption">
&lt;span class="src-block-number">&lt;a href="#code-snippet--debug-on-message-backtrace">Code Snippet 3&lt;/a>:&lt;/span>
Backtrace created on setting &lt;code>debug-on-message&lt;/code> to &lt;code>"Wrote&lt;/code>"
&lt;/div>
&lt;p>The top line of that backtrace shows the source of that message:
&lt;code>write-region&lt;/code>.&lt;/p>
&lt;div class="note">
&lt;p>Remember to reset &lt;code>debug-on-message&lt;/code> back to &lt;em>nil&lt;/em> by evaluating
&lt;code class="code-inline language-emacs-lisp">&lt;span class="p">(&lt;/span>&lt;span class="nb">setq&lt;/span> &lt;span class="nv">debug-on-message&lt;/span> &lt;span class="no">nil&lt;/span>&lt;span class="p">)&lt;/span>&lt;/code> once
you are done with this debug!&lt;/p>
&lt;/div>
&lt;h2 id="figure-out-how-to-silence-those-messages">&lt;span class="section-num">2&lt;/span> Figure out how to silence those messages&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#figure-out-how-to-silence-those-messages">#&lt;/a>&lt;/h2>
&lt;p>Now that I knew that I needed to alter the &lt;code>org-babel-exp-src-block&lt;/code>
and &lt;code>write-region&lt;/code> functions
&lt;span class="sidenote-number">&lt;small class="sidenote">
There was a third function too that I found using this second method:
&lt;code>table-generate-source&lt;/code>. This was printing &lt;em>&amp;ldquo;Generating source&amp;hellip;&amp;rdquo;&lt;/em>
when parsing each &lt;code>table.el&lt;/code> message.
&lt;/small>&lt;/span>
, I wanted to quiet these messages in both the &lt;em>echo&lt;/em> area and the
∗Messages∗ buffer, but only when exporting all the post
subtrees from a file.&lt;/p>
&lt;p>Emacs provides us ways to separately control the displaying of
messages in those regions:&lt;/p>
&lt;dl>
&lt;dt>Prevent messages in the &lt;em>echo&lt;/em> area&lt;/dt>
&lt;dd>From &lt;a href="https://www.gnu.org/software/emacs/manual/html_node/elisp/Displaying-Messages.html">📖 Elisp &amp;ndash;
Displaying Messages&lt;/a>
&lt;span class="sidenote-number">&lt;small class="sidenote">
You can visit the same manual page from within Emacs by &lt;code>C-h i g (elisp)Displaying Messages&lt;/code>.
&lt;/small>&lt;/span>
, if
&lt;mark>&lt;code>inhibit-message&lt;/code> is set to &lt;em>t&lt;/em>&lt;/mark> , &lt;code>message&lt;/code> outputs are not shown in the &lt;em>echo&lt;/em> area (though they
would still be logged in the ∗Messages∗ buffer.&lt;/dd>
&lt;dt>Prevent messages in the ∗Messages∗ buffer&lt;/dt>
&lt;dd>From &lt;a href="https://www.gnu.org/software/emacs/manual/html_node/elisp/Logging-Messages.html">📖 Elisp
&amp;ndash; Logging Messages&lt;/a>
&lt;span class="sidenote-number">&lt;small class="sidenote">
&lt;code>C-h i g (elisp)Logging Messages&lt;/code>
&lt;/small>&lt;/span>
, if
&lt;mark>&lt;code>message-log-max&lt;/code> is set to &lt;em>nil&lt;/em>&lt;/mark> , no messages will be printed in the ∗Messages∗ buffer.&lt;/dd>
&lt;/dl>
&lt;h2 id="applying-the-silence-advice">&lt;span class="section-num">3&lt;/span> Applying the &amp;lsquo;silence&amp;rsquo; advice&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#applying-the-silence-advice">#&lt;/a>&lt;/h2>
&lt;p>With that information, I created this &lt;em>advising function&lt;/em> which calls
the function name it receives in its first argument.&lt;/p>
&lt;p>&lt;a id="code-snippet--silence-messages-advising-fn">&lt;/a>&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-emacs-lisp" data-lang="emacs-lisp">&lt;span class="line">&lt;span class="cl">&lt;span class="p">(&lt;/span>&lt;span class="nb">defun&lt;/span> &lt;span class="nv">org-hugo--advice-silence-messages&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">orig-fun&lt;/span> &lt;span class="kp">&amp;amp;rest&lt;/span> &lt;span class="nv">args&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;Advice function that silences all messages in ORIG-FUN.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">let&lt;/span> &lt;span class="p">((&lt;/span>&lt;span class="nv">inhibit-message&lt;/span> &lt;span class="no">t&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">;Don&amp;#39;t show the messages in Echo area&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">message-log-max&lt;/span> &lt;span class="no">nil&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="c1">;Don&amp;#39;t show the messages in the *Messages* buffer&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">apply&lt;/span> &lt;span class="nv">orig-fun&lt;/span> &lt;span class="nv">args&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="src-block-caption">
&lt;span class="src-block-number">&lt;a href="#code-snippet--silence-messages-advising-fn">Code Snippet 4&lt;/a>:&lt;/span>
Advising function to silence the messages in the advised &lt;code>orig-fun&lt;/code>
&lt;/div>
&lt;p>Note that the above function does not set the &lt;code>inhibit-message&lt;/code> and
&lt;code>message-log-max&lt;/code> variables globally (e.g. using &lt;code>setq&lt;/code>). We do not
want to permanently stop the printing of messages for all the
functions! That&amp;rsquo;s why they are set in the &lt;code>let&lt;/code> scope or the local
scope, and the &lt;code>orig-fun&lt;/code> is called within that scope.&lt;/p>
&lt;p>This snippet, though, only defines the advising function. The advices
are actually applied using the &lt;code>advice-add&lt;/code>
&lt;span class="sidenote-number">&lt;small class="sidenote">
The counterpart of &lt;code>advice-add&lt;/code> is &lt;code>advice-remove&lt;/code>, to remove the
advising functions from the original functions.
&lt;/small>&lt;/span>
function.&lt;/p>
&lt;p>Emacs provides a &lt;a href="https://www.gnu.org/software/emacs/manual/html_node/elisp/Advice-Combinators.html">variety&lt;/a> of &lt;em>Advice Combinators&lt;/em>. These combinators
are used to specify &lt;em>how&lt;/em> an advice should be applied to the original
function. I have found the &lt;code>:around&lt;/code> combinator to usually fit my
needs because the advices I add typically look like:&lt;/p>
&lt;ol>
&lt;li>Do something before.&lt;/li>
&lt;li>Set some variables in a &lt;code>let&lt;/code> scope.&lt;/li>
&lt;li>Call the original function with its arguments inside that scope.&lt;/li>
&lt;li>Do something afterwards.&lt;/li>
&lt;/ol>
&lt;p>As I wanted to apply the same advice to multiple functions
(&lt;code>org-babel-exp-src-block&lt;/code> and &lt;code>write-region&lt;/code>), I used &lt;code>dolist&lt;/code> to
loop through them and advise them to 🤫 :&lt;/p>
&lt;p>&lt;a id="code-snippet--silence-messages-advice-add">&lt;/a>&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-emacs-lisp" data-lang="emacs-lisp">&lt;span class="line">&lt;span class="cl">&lt;span class="p">(&lt;/span>&lt;span class="nb">dolist&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">fn&lt;/span> &lt;span class="o">&amp;#39;&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nv">org-babel-exp-src-block&lt;/span> &lt;span class="nf">write-region&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">advice-add&lt;/span> &lt;span class="nv">fn&lt;/span> &lt;span class="nb">:around&lt;/span> &lt;span class="nf">#&amp;#39;&lt;/span>&lt;span class="nv">org-hugo--advice-silence-messages&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="src-block-caption">
&lt;span class="src-block-number">&lt;a href="#code-snippet--silence-messages-advice-add">Code Snippet 5&lt;/a>:&lt;/span>
Enabling the advices
&lt;/div>
&lt;h3 id="limiting-the-scope-of-these-advices">Limiting the scope of these advices&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#limiting-the-scope-of-these-advices">#&lt;/a>&lt;/h3>
&lt;p>You might wonder &amp;mdash; If &lt;code>ox-hugo&lt;/code> is applying these advices, wouldn&amp;rsquo;t
it affect the global behavior of &lt;code>write-region&lt;/code>, etc. outside of
&lt;code>ox-hugo&lt;/code> exports as well?&lt;/p>
&lt;div class="verse">
&lt;p>    The answer is &amp;ldquo;no&amp;rdquo;.&lt;br />&lt;/p>
&lt;/div>
&lt;p>In &lt;code>ox-hugo&lt;/code>, the &lt;code>advice-add&lt;/code> calls are made just before the &amp;ldquo;export
all post subtrees&amp;rdquo; operation begins. Once the export command finishes,
&lt;code>advice-remove&lt;/code> calls remove those advices from those functions. So
those advices are effective only &lt;span class="underline">for the duration of the export&lt;/span>.&lt;/p>
&lt;p>Details of the implementation can be seen in &lt;a href="https://github.com/kaushalmodi/ox-hugo/commit/6e4f74d20dcb7e5da65cd504d1c64c944319c5f0">this &lt;code>ox-hugo&lt;/code> commit&lt;/a>.&lt;/p>
&lt;h2 id="conclusion">Conclusion&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#conclusion">#&lt;/a>&lt;/h2>
&lt;p>The main effort here was to (i) identify the problem, (ii) find out
which functions were causing the problem, and (iii) what knobs or
variables are available to us to help fix that.&lt;/p>
&lt;p>Once that is done, you write an &lt;em>advising&lt;/em> function and &lt;code>advice-add&lt;/code>
that function to the original function using a combinator of your
choice
&lt;span class="sidenote-number">&lt;small class="sidenote">
If you are not sure which combinator to use, pick &lt;code>:around&lt;/code> 😃.
&lt;/small>&lt;/span>
.&lt;/p>
&lt;p>In this post, I am using Emacs advices to make &lt;code>ox-hugo&lt;/code> exports less
noisy. But you can add similar advices to your Emacs config
&lt;span class="sidenote-number">&lt;small class="sidenote">
You can find the ways I am using advices in my Emacs config &lt;a href="https://github.com/kaushalmodi/.emacs.d/search?q=advice-add">here&lt;/a>.
&lt;/small>&lt;/span>
to tweak and fine-tune any function that&amp;rsquo;s getting on your nerves
😄.&lt;/p></description><author>Kaushal.Modi@fakeEmailToMakeValidatorHappy.com (Kaushal Modi)</author><category domain="https://scripter.co/categories/emacs">emacs</category><category domain="https://scripter.co/categories/elisp">elisp</category><category domain="https://scripter.co/tags/advice">advice</category><category domain="https://scripter.co/tags/100daystooffload">100DaysToOffload</category><guid>https://scripter.co/using-emacs-advice-to-silence-messages-from-functions/</guid><pubDate>Sat, 19 Mar 2022 02:25:00 -0400</pubDate></item><item><title>Quick Intro to Emacs Lisp Regression Testing</title><link>https://scripter.co/quick-intro-to-emacs-lisp-regression-testing/</link><description>&lt;blockquote>A quick introduction to using ERT (Emacs Lisp Regression Testing) for
your next &lt;em>elisp&lt;/em> library.&lt;/blockquote>&lt;div class="ox-hugo-toc toc">
&lt;div class="heading">Table of Contents&lt;/div>
&lt;ul>
&lt;li>&lt;a href="#test-directory">Test directory&lt;/a>&lt;/li>
&lt;li>&lt;a href="#test-files">Test Files&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#feature-test">Feature-specific &lt;code>t***.el&lt;/code> test files&lt;/a>&lt;/li>
&lt;li>&lt;a href="#wrapper-test-file-all-tests-dot-el">Wrapper test file &lt;code>all-tests.el&lt;/code>&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#makefile">Makefile&lt;/a>&lt;/li>
&lt;li>&lt;a href="#summary">Summary&lt;/a>&lt;/li>
&lt;li>&lt;a href="#references">References&lt;/a>&lt;/li>
&lt;/ul>
&lt;/div>
&lt;!--endtoc-->
&lt;p>&lt;abbr aria-label="Emacs Lisp Regression Testing" tabindex=0>ERT&lt;/abbr> is a testing library
that comes with Emacs i.e. you do not need to install from GNU Elpa or
Melpa. The aim of this post is to serve as a quick guide on how to
start using ERT with your Emacs Lisp library
&lt;span class="sidenote-number">&lt;small class="sidenote">
Here, a &amp;ldquo;library&amp;rdquo; could even be a set of custom Emacs Lisp code that
you wrote for your Emacs config.
&lt;/small>&lt;/span>
.&lt;/p>
&lt;p>The ERT manual
&lt;span class="sidenote-number">&lt;small class="sidenote">
You can quickly visit the ERT manual from within Emacs using the
wonderful &lt;em>Info&lt;/em> system by typing &lt;code>C-h i m ert&lt;/code>, you can visit &lt;a href="https://www.gnu.org/software/emacs/manual/html_node/ert/">Emacs
Manual &amp;ndash; ERT&lt;/a> in a web browser.
&lt;/small>&lt;/span>
is really well-written and here&amp;rsquo;s how it introduces this testing
library:&lt;/p>
&lt;blockquote>
&lt;p>ERT is a tool for automated testing in Emacs Lisp. Its main features
are facilities for defining tests, running them and reporting the
results, and for debugging test failures interactively.&lt;/p>
&lt;p>..it
&lt;mark>works well both for &lt;a href="https://en.wikipedia.org/wiki/Test-driven_development">test-driven development&lt;/a>&lt;/mark> and for traditional software development methods.&lt;/p>
&lt;/blockquote>
&lt;p>The part about &lt;em>test-driven development&lt;/em> is so true! At the moment, I
am developing an Emacs library called &lt;a href="https://github.com/kaushalmodi/baser">&lt;code>baser&lt;/code>&lt;/a> that does signed number
conversions among base 10, base 2 and base 16 formats. Before adding
new features, I first write the &lt;code>ert&lt;/code> tests, and then I develop and
refine the functions until they start passing.&lt;/p>
&lt;p>In this post, I will use that &lt;em>baser&lt;/em> library as an example of how to
set up ERT &amp;ndash; which I believe would be applicable to other Emacs Lisp
projects too.&lt;/p>
&lt;h2 id="test-directory">Test directory&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#test-directory">#&lt;/a>&lt;/h2>
&lt;p>Below shows how the &lt;code>test/&lt;/code> directory is created with respect to the
library being tested.&lt;/p>
&lt;p>&lt;a id="code-snippet--test-dir-structure-ert">&lt;/a>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">baser/ (repo root)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── baser.el
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── ..
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">├── test/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ├── all-tests.el
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ ├── ..
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">│ └── tdec-hex.el
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">└── Makefile
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="src-block-caption">
&lt;span class="src-block-number">&lt;a href="#code-snippet--test-dir-structure-ert">Code Snippet 1&lt;/a>:&lt;/span>
Test directory structure from &lt;a href="https://github.com/kaushalmodi/baser">&lt;b>baser&lt;/b>&lt;/a>
&lt;/div>
&lt;p>Here,&lt;/p>
&lt;ol>
&lt;li>&lt;code>baser.el&lt;/code> is the Emacs Lisp library being tested.&lt;/li>
&lt;li>&lt;code>test/&lt;/code> is the directory containing all the tests.&lt;/li>
&lt;li>&lt;code>Makefile&lt;/code> defines a &lt;code>test&lt;/code> target so that running tests is as easy
as &lt;code>make test&lt;/code>.&lt;/li>
&lt;/ol>
&lt;h2 id="test-files">Test Files&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#test-files">#&lt;/a>&lt;/h2>
&lt;p>In the &lt;code>test/&lt;/code> directory, I have a main &lt;code>all-tests.el&lt;/code> file that
requires all other test files named in &lt;code>t***.el&lt;/code> style.&lt;/p>
&lt;h3 id="feature-test">Feature-specific &lt;code>t***.el&lt;/code> test files&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#feature-test">#&lt;/a>&lt;/h3>
&lt;p>I like to follow the convention of starting all test file names with
&lt;code>t&lt;/code>.&lt;/p>
&lt;p>I break up the test files such that each file tests only a particular
feature. In &lt;em>baser&lt;/em>, I have &lt;code>tdec-hex.el&lt;/code> to test the conversions
between decimal and hexidecimal formats. Similarly I have
&lt;code>tdec-bin.el&lt;/code> and &lt;code>thex-bin.el&lt;/code>.&lt;/p>
&lt;h4 id="general-structure-of-each-feature-test">General structure of each feature test&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#general-structure-of-each-feature-test">#&lt;/a>&lt;/h4>
&lt;p>The structure of each &lt;code>tfeature.el&lt;/code> file looks like this:&lt;/p>
&lt;p>&lt;a id="code-snippet--tfeature-file-structure">&lt;/a>&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code>&lt;span class="lnt" id="org-coderef--7f5520-1">&lt;a style="outline: none; text-decoration:none; color:inherit" href="#org-coderef--7f5520-1"> 1&lt;/a>
&lt;/span>&lt;span class="lnt" id="org-coderef--7f5520-2">&lt;a style="outline: none; text-decoration:none; color:inherit" href="#org-coderef--7f5520-2"> 2&lt;/a>
&lt;/span>&lt;span class="lnt" id="org-coderef--7f5520-3">&lt;a style="outline: none; text-decoration:none; color:inherit" href="#org-coderef--7f5520-3"> 3&lt;/a>
&lt;/span>&lt;span class="lnt" id="org-coderef--7f5520-4">&lt;a style="outline: none; text-decoration:none; color:inherit" href="#org-coderef--7f5520-4"> 4&lt;/a>
&lt;/span>&lt;span class="lnt" id="org-coderef--7f5520-5">&lt;a style="outline: none; text-decoration:none; color:inherit" href="#org-coderef--7f5520-5"> 5&lt;/a>
&lt;/span>&lt;span class="lnt" id="org-coderef--7f5520-6">&lt;a style="outline: none; text-decoration:none; color:inherit" href="#org-coderef--7f5520-6"> 6&lt;/a>
&lt;/span>&lt;span class="lnt" id="org-coderef--7f5520-7">&lt;a style="outline: none; text-decoration:none; color:inherit" href="#org-coderef--7f5520-7"> 7&lt;/a>
&lt;/span>&lt;span class="lnt" id="org-coderef--7f5520-8">&lt;a style="outline: none; text-decoration:none; color:inherit" href="#org-coderef--7f5520-8"> 8&lt;/a>
&lt;/span>&lt;span class="lnt" id="org-coderef--7f5520-9">&lt;a style="outline: none; text-decoration:none; color:inherit" href="#org-coderef--7f5520-9"> 9&lt;/a>
&lt;/span>&lt;span class="lnt" id="org-coderef--7f5520-10">&lt;a style="outline: none; text-decoration:none; color:inherit" href="#org-coderef--7f5520-10">10&lt;/a>
&lt;/span>&lt;span class="lnt" id="org-coderef--7f5520-11">&lt;a style="outline: none; text-decoration:none; color:inherit" href="#org-coderef--7f5520-11">11&lt;/a>
&lt;/span>&lt;span class="lnt" id="org-coderef--7f5520-12">&lt;a style="outline: none; text-decoration:none; color:inherit" href="#org-coderef--7f5520-12">12&lt;/a>
&lt;/span>&lt;span class="lnt" id="org-coderef--7f5520-13">&lt;a style="outline: none; text-decoration:none; color:inherit" href="#org-coderef--7f5520-13">13&lt;/a>
&lt;/span>&lt;span class="lnt" id="org-coderef--7f5520-14">&lt;a style="outline: none; text-decoration:none; color:inherit" href="#org-coderef--7f5520-14">14&lt;/a>
&lt;/span>&lt;span class="lnt" id="org-coderef--7f5520-15">&lt;a style="outline: none; text-decoration:none; color:inherit" href="#org-coderef--7f5520-15">15&lt;/a>
&lt;/span>&lt;span class="lnt" id="org-coderef--7f5520-16">&lt;a style="outline: none; text-decoration:none; color:inherit" href="#org-coderef--7f5520-16">16&lt;/a>
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre tabindex="0" class="chroma">&lt;code class="language-emacs-lisp" data-lang="emacs-lisp">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">;; tfeature.el -*- lexical-binding: t; -*-&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">;; Require the emacs-lisp library being tested.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">;; Example: (require &amp;#39;baser)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">;; Test things that *should* happen.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">(&lt;/span>&lt;span class="nb">ert-deftest&lt;/span> &lt;span class="nv">addition-test&lt;/span> &lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">should&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">=&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">+&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="mi">3&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">;; Test things that your library *should trigger errors* for.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">(&lt;/span>&lt;span class="nb">ert-deftest&lt;/span> &lt;span class="nv">div-by-0-test&lt;/span> &lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">should-error&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">/&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">:type&lt;/span> &lt;span class="ss">&amp;#39;arith-error&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">(&lt;/span>&lt;span class="nb">provide&lt;/span> &lt;span class="ss">&amp;#39;tfeature&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="src-block-caption">
&lt;span class="src-block-number">&lt;a href="#code-snippet--tfeature-file-structure">Code Snippet 2&lt;/a>:&lt;/span>
Structure of each &lt;code>tfeature.el&lt;/code> test file
&lt;/div>
&lt;ul>
&lt;li>Lines &lt;a href="#org-coderef--7f5520-7">7&lt;/a> and &lt;a href="#org-coderef--7f5520-11">11&lt;/a> define the &lt;em>ert&lt;/em> tests using
&lt;code>ert-deftest&lt;/code>.&lt;/li>
&lt;li>The first &lt;code>ert_deftest&lt;/code> uses a &lt;a href="https://www.gnu.org/software/emacs/manual/html_node/ert/The-should-Macro.html">&lt;code>should&lt;/code> macro&lt;/a> that checks if its
body evaluates to &lt;em>non-nil&lt;/em>. It is similar to constructs like
&lt;code class="code-inline language-systemverilog">&lt;span class="k">assert&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="n">something&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="mh">1&lt;/span>&lt;span class="p">);&lt;/span>&lt;/code> or
&lt;code class="code-inline language-nim">&lt;span class="n">doAssert&lt;/span> &lt;span class="n">something&lt;/span> &lt;span class="o">==&lt;/span> &lt;span class="kp">true&lt;/span>&lt;/code> in
&lt;span title="SystemVerilog">other&lt;/span> &lt;span title="Nim">programming&lt;/span> languages.&lt;/li>
&lt;li>The &lt;a href="#org-coderef--7f5520-11">second &lt;code>ert_deftest&lt;/code>​&lt;/a> uses kind the &lt;code>should-error&lt;/code> macro that
asserts if the evaluation of its body resulted in an error. Here,
the test fails if the body does &lt;strong>not&lt;/strong> throw an error. The optional
&lt;code>:type &amp;lt;error type&amp;gt;&lt;/code> argument makes the test stricter; in addition
to checking if evaluating that body throws an error, it also checks
if the thrown error is of that specific type.&lt;/li>
&lt;/ul>
&lt;div class="note">
&lt;p>&lt;code>ert&lt;/code> does not need to be &lt;em>required&lt;/em> manually because &lt;code>ert-deftest&lt;/code>
will autoload it.&lt;/p>
&lt;/div>
&lt;h3 id="wrapper-test-file-all-tests-dot-el">Wrapper test file &lt;code>all-tests.el&lt;/code>&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#wrapper-test-file-all-tests-dot-el">#&lt;/a>&lt;/h3>
&lt;p>A wrapper test file &lt;code>all-tests.el&lt;/code> is created for convenience so that
loading just this one file loads all the tests. Having such a wrapper
file is helpful as you will see in the &lt;em>Makefile&lt;/em> section next.&lt;/p>
&lt;p>This file simply requires all the feature test files like the ones
explained &lt;a href="#feature-test">above&lt;/a>.&lt;/p>
&lt;p>&lt;a id="code-snippet--all-tests-ert">&lt;/a>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-emacs-lisp" data-lang="emacs-lisp">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">;; all-tests.el&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">;; If the directory happens to have both compiled and uncompiled&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">;; version, prefer to use the newer (typically the uncompiled) version.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">(&lt;/span>&lt;span class="nb">setq&lt;/span> &lt;span class="nv">load-prefer-newer&lt;/span> &lt;span class="no">t&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">(&lt;/span>&lt;span class="nb">require&lt;/span> &lt;span class="ss">&amp;#39;tdec-hex&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">;Require the dec&amp;lt;-&amp;gt;hex conversion feature test&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">;; more feature tests ..&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="src-block-caption">
&lt;span class="src-block-number">&lt;a href="#code-snippet--all-tests-ert">Code Snippet 3&lt;/a>:&lt;/span>
Wrapper test file &lt;code>all-tests.el&lt;/code> to require all other tests
&lt;/div>
&lt;h2 id="makefile">Makefile&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#makefile">#&lt;/a>&lt;/h2>
&lt;p>Finally, we create a &lt;code>Makefile&lt;/code> so that we can run &lt;code>make test&lt;/code> to run
the ERT tests.&lt;/p>
&lt;ol>
&lt;li>Run &lt;code>make test&lt;/code> to run all tests.&lt;/li>
&lt;li>Run &lt;code>make test MATCH=foo&lt;/code> to run only the tests whose names match
&amp;ldquo;foo&amp;rdquo;. The test names are the ones defined by the &lt;code>ert-deftest&lt;/code>
macro.&lt;/li>
&lt;/ol>
&lt;!--listend-->
&lt;p>&lt;a id="code-snippet--makefile-ert">&lt;/a>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-makefile" data-lang="makefile">&lt;span class="line">&lt;span class="cl">&lt;span class="nv">EMACS&lt;/span> &lt;span class="o">?=&lt;/span> emacs
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nv">TEST_DIR&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="k">$(&lt;/span>shell &lt;span class="nb">pwd&lt;/span>&lt;span class="k">)&lt;/span>/test
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c"># Run all tests by default.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="nv">MATCH&lt;/span> &lt;span class="o">?=&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf">.PHONY&lt;/span>&lt;span class="o">:&lt;/span> &lt;span class="n">test&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="nf">test&lt;/span>&lt;span class="o">:&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="k">$(&lt;/span>EMACS&lt;span class="k">)&lt;/span> --batch -L . -L &lt;span class="k">$(&lt;/span>TEST_DIR&lt;span class="k">)&lt;/span> -l all-tests.el -eval &lt;span class="s1">&amp;#39;(ert-run-tests-batch-and-exit &amp;#34;$(MATCH)&amp;#34;)&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="src-block-caption">
&lt;span class="src-block-number">&lt;a href="#code-snippet--makefile-ert">Code Snippet 4&lt;/a>:&lt;/span>
&lt;code>make test&lt;/code> recipe for running ERT tests
&lt;/div>
&lt;p>See &lt;a href="https://www.gnu.org/software/emacs/manual/html_node/ert/Running-Tests-in-Batch-Mode.html">ERT &amp;ndash; Running Tests in Batch Mode&lt;/a> for reference.&lt;/p>
&lt;h2 id="summary">Summary&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#summary">#&lt;/a>&lt;/h2>
&lt;p>You can quickly create a nice test setup for your Emacs Lisp library
or Emacs config by adding a test file with tests defined using
&lt;code>erf-deftest&lt;/code> with &lt;code>should&lt;/code> and &lt;code>should-error&lt;/code> macros, and then
running &lt;code>make test&lt;/code> with the help of a &lt;code>Makefile&lt;/code> like the one shown
above.&lt;/p>
&lt;p>While this post might help you get started with using ERT quickly, do
go through its manual &amp;mdash; it&amp;rsquo;s short and sweet, and well-written, and
covers a lot more than this post.&lt;/p>
&lt;h2 id="references">References&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#references">#&lt;/a>&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://www.gnu.org/software/emacs/manual/html_node/ert/">Emacs Manual &amp;ndash; ERT&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://github.com/kaushalmodi/baser">&lt;code>baser&lt;/code> library&lt;/a>&lt;/li>
&lt;/ul></description><author>Kaushal.Modi@fakeEmailToMakeValidatorHappy.com (Kaushal Modi)</author><category domain="https://scripter.co/categories/emacs">emacs</category><category domain="https://scripter.co/categories/elisp">elisp</category><category domain="https://scripter.co/tags/ert">ert</category><category domain="https://scripter.co/tags/testing">testing</category><category domain="https://scripter.co/tags/regression">regression</category><category domain="https://scripter.co/tags/100daystooffload">100DaysToOffload</category><category domain="https://scripter.co/tags/makefile">makefile</category><guid>https://scripter.co/quick-intro-to-emacs-lisp-regression-testing/</guid><pubDate>Sun, 06 Mar 2022 14:40:00 -0500</pubDate></item><item><title>Splitting an Org block into two</title><link>https://scripter.co/splitting-an-org-block-into-two/</link><description>&lt;blockquote>&lt;p>I ventured out to start writing about a 100+ line &lt;a href="https://github.com/kaushalmodi/.emacs.d/blob/e3b17a7fb9cd3d429002b1d339956d394584570d/setup-files/setup-editing.el#L537-L654">Emacs Lisp snippet
in my config&lt;/a>, and then I thought &amp;mdash; Wouldn&amp;rsquo;t it be nice if I can
quickly split out that huge snippet into smaller Org Src blocks?&lt;/p>
&lt;p>And so this blog post happened.&lt;/p>
&lt;/blockquote>&lt;div class="ox-hugo-toc toc">
&lt;div class="heading">Table of Contents&lt;/div>
&lt;ul>
&lt;li>&lt;a href="#problem">Problem&lt;/a>&lt;/li>
&lt;li>&lt;a href="#action-plan">Action Plan&lt;/a>&lt;/li>
&lt;li>&lt;a href="#org-in-any-block-p">Am I in an Org block?&lt;/a>&lt;/li>
&lt;li>&lt;a href="#org-block-split">If so, split the block&lt;/a>&lt;/li>
&lt;li>&lt;a href="#M-return-split-block-dwim">Now make &lt;kbd>M-return&lt;/kbd> do that&lt;/a>&lt;/li>
&lt;li>&lt;a href="#full-code">Full code&lt;/a>&lt;/li>
&lt;/ul>
&lt;/div>
&lt;!--endtoc-->
&lt;h2 id="problem">Problem&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#problem">#&lt;/a>&lt;/h2>
&lt;p>If I have a huge Org Src block, I&amp;rsquo;d like to split it into multiple Org
Src blocks so that I can write my explanations in-between.&lt;/p>
&lt;p>So I&amp;rsquo;d like to quickly split up:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-org" data-lang="org">&lt;span class="line">&lt;span class="cl">&lt;span class="c">#+begin_src &lt;/span>&lt;span class="cs">emacs-lisp&lt;/span>&lt;span class="c">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nf">message&lt;/span> &lt;span class="s">&amp;#34;one&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">(&lt;/span>&lt;span class="nf">message&lt;/span> &lt;span class="s">&amp;#34;two&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">#+end_src&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>into:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-org" data-lang="org">&lt;span class="line">&lt;span class="cl">&lt;span class="c">#+begin_src &lt;/span>&lt;span class="cs">emacs-lisp&lt;/span>&lt;span class="c">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nf">message&lt;/span> &lt;span class="s">&amp;#34;one&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">#+end_src&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">#+begin_src &lt;/span>&lt;span class="cs">emacs-lisp&lt;/span>&lt;span class="c">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nf">message&lt;/span> &lt;span class="s">&amp;#34;two&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">#+end_src&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>.. &lt;a href="org-block-splitting-in-action.gif">like this&lt;/a>.&lt;/p>
&lt;p>☝ &lt;em>Click for animation.&lt;/em>&lt;/p>
&lt;h2 id="action-plan">Action Plan&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#action-plan">#&lt;/a>&lt;/h2>
&lt;ol>
&lt;li>Write a function to return &lt;em>non-nil&lt;/em> if point is in &lt;strong>any&lt;/strong> Org block
&amp;ndash; Not just &amp;ldquo;src&amp;rdquo;, &amp;ldquo;example&amp;rdquo;, &amp;ldquo;export&amp;rdquo; or any of the inbuilt Org
blocks.. but also any Org Special block like &lt;code>#+begin_foo .. #+end_foo&lt;/code>.&lt;/li>
&lt;li>Write a function that does this imagined block splitting.&lt;/li>
&lt;li>Overload the &lt;kbd>M-return&lt;/kbd> binding so that this block splitting
function gets called only when the point is inside an Org block
(detected using that first function).&lt;/li>
&lt;/ol>
&lt;div class="note">
&lt;p>&lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2018-08-26 Sun&amp;gt; &lt;/span>&lt;/span> Thanks to the comment by reader &lt;strong>Mankoff&lt;/strong>, I learnt
about the &lt;code>org-babel-demarcate-block&lt;/code> function (bound by default to
&lt;kbd>C-c C-v d&lt;/kbd> and &lt;kbd>C-c C-v C-d&lt;/kbd>).&lt;/p>
&lt;p>This function varies from the solution in this post in at least two
ways:&lt;/p>
&lt;ol>
&lt;li>It works only for Org Src blocks.&lt;/li>
&lt;li>It splits the block exactly at where the point is, whereas I would
like to always split only at EOL or BOL.&lt;/li>
&lt;/ol>
&lt;p>But I can see that &lt;code>org-babel-demarcate-block&lt;/code> can cover most of the
block splitting use cases.&lt;/p>
&lt;/div>
&lt;h2 id="org-in-any-block-p">Am I in an Org block?&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#org-in-any-block-p">#&lt;/a>&lt;/h2>
&lt;p>Before venturing into writing this function, I looked at these
existing ones, but none did what I exactly wanted:&lt;/p>
&lt;dl>
&lt;dt>&lt;code>org-in-src-block-p&lt;/code>&lt;/dt>
&lt;dd>Returns &lt;em>non-nil&lt;/em> only if the point is in a
&lt;code>#+begin_src .. #+end_src&lt;/code> block; not when point is in any other
Org block.&lt;/dd>
&lt;dt>&lt;code>org-in-block-p&lt;/code>&lt;/dt>
&lt;dd>Returns &lt;em>non-nil&lt;/em> only if the point is in one of
the pre-defined block names passed as a list (&lt;code>'(&amp;quot;src&amp;quot; &amp;quot;example&amp;quot; &amp;quot;quote&amp;quot; ..)&lt;/code>). So this again won&amp;rsquo;t work as I cannot pre-define all
Org Special blocks.&lt;/dd>
&lt;/dl>
&lt;p>So I define the below &lt;code>modi/org-in-any-block-p&lt;/code> function that returns
&lt;em>non-nil&lt;/em> if the point is in-between any &lt;code>#+begin_FOOBAR .. #+end_FOOBAR&lt;/code>. Thankfully, I was able to reuse a lot of logic from
the &lt;code>org-between-regexps-p&lt;/code> function (&lt;code>org-in-block-p&lt;/code> uses that
function internally).&lt;/p>
&lt;p>&lt;a id="code-snippet--modi-org-in-any-block-p">&lt;/a>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-emacs-lisp" data-lang="emacs-lisp">&lt;span class="line">&lt;span class="cl">&lt;span class="p">(&lt;/span>&lt;span class="nb">defun&lt;/span> &lt;span class="nv">modi/org-in-any-block-p&lt;/span> &lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;Return non-nil if the point is in any Org block.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">The Org block can be *any*: src, example, verse, etc., even any
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">Org Special block.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">This function is heavily adapted from &lt;/span>&lt;span class="ss">`org-between-regexps-p&amp;#39;&lt;/span>&lt;span class="s">.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">save-match-data&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">let&lt;/span> &lt;span class="p">((&lt;/span>&lt;span class="nv">pos&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">point&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">case-fold-search&lt;/span> &lt;span class="no">t&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">block-begin-re&lt;/span> &lt;span class="s">&amp;#34;^[[:blank:]]*#\\+begin_\\(?1:.+?\\)\\(?: .*\\)*$&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">limit-up&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">save-excursion&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">outline-previous-heading&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">limit-down&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">save-excursion&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">outline-next-heading&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">beg&lt;/span> &lt;span class="nv">end&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">save-excursion&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">;; Point is on a block when on BLOCK-BEGIN-RE or if&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">;; BLOCK-BEGIN-RE can be found before it...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">and&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">or&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">org-in-regexp&lt;/span> &lt;span class="nv">block-begin-re&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">re-search-backward&lt;/span> &lt;span class="nv">block-begin-re&lt;/span> &lt;span class="nv">limit-up&lt;/span> &lt;span class="nb">:noerror&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">setq&lt;/span> &lt;span class="nv">beg&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">match-beginning&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">;; ... and BLOCK-END-RE after it...&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">let&lt;/span> &lt;span class="p">((&lt;/span>&lt;span class="nv">block-end-re&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">concat&lt;/span> &lt;span class="s">&amp;#34;^[[:blank:]]*#\\+end_&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">match-string-no-properties&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;\\( .*\\)*$&amp;#34;&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">goto-char&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">match-end&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">re-search-forward&lt;/span> &lt;span class="nv">block-end-re&lt;/span> &lt;span class="nv">limit-down&lt;/span> &lt;span class="nb">:noerror&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">&amp;gt;&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">setq&lt;/span> &lt;span class="nv">end&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">match-end&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="nv">pos&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">;; ... without another BLOCK-BEGIN-RE in-between.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">goto-char&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">match-beginning&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">not&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">re-search-backward&lt;/span> &lt;span class="nv">block-begin-re&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">1+&lt;/span> &lt;span class="nv">beg&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nb">:noerror&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">;; Return value.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">cons&lt;/span> &lt;span class="nv">beg&lt;/span> &lt;span class="nv">end&lt;/span>&lt;span class="p">))))))&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="src-block-caption">
&lt;span class="src-block-number">&lt;a href="#code-snippet--modi-org-in-any-block-p">Code Snippet 1&lt;/a>:&lt;/span>
Function to check if point is in any Org block
&lt;/div>
&lt;ul>
&lt;li>&lt;code>(case-fold-search t)&lt;/code> ensures that either &lt;code>#+BEGIN_ ..&lt;/code> or &lt;code>#+begin_ ..&lt;/code> match.&lt;/li>
&lt;li>The regular expression in &lt;code>block-begin-re&lt;/code> matches with
&lt;code>&amp;quot;#+begin_src foo&amp;quot;&lt;/code> or &lt;code>&amp;quot; #+begin_src foo&amp;quot;&lt;/code> or &lt;code>&amp;quot;#+BEGIN_EXAMPLE&amp;quot;&lt;/code>
or &lt;code>&amp;quot;#+begin_FOOBAR&amp;quot;&lt;/code> or ..&lt;/li>
&lt;li>The &lt;code>limit-up&lt;/code> and &lt;code>limit-down&lt;/code> are set to the buffer locations of
the previous and next Org headings. The following regexp searches
are limited to happen in those bounds for better performance.&lt;/li>
&lt;li>The &lt;code>block-end-re&lt;/code> is dynamically constructed based on the string
matched using &lt;code>block-begin-re&lt;/code>. This is so that if &lt;code>&amp;quot;#+begin_quote&amp;quot;&lt;/code>
is found initially, it matches the block ending with specifically
&lt;code>&amp;quot;#+end_quote&amp;quot;&lt;/code> and not something like &lt;code>&amp;quot;#+end_src&amp;quot;&lt;/code>.&lt;/li>
&lt;li>&lt;em>nil&lt;/em> is returned if the point is not between &lt;code>#+begin_FOOBAR .. #+end_FOOBAR&lt;/code>.&lt;/li>
&lt;/ul>
&lt;!--listend-->
&lt;dl>
&lt;dt>Caveat&lt;/dt>
&lt;dd>I haven&amp;rsquo;t gone extra lengths to support nested block cases,
specifically where the point is outside the inner-most
block, but still inside the outer block:
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl">#+begin_src org
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">▮
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">#+begin_src emacs-lisp
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">(message &amp;#34;hello!&amp;#34;)
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">#+end_src
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">#+end_src
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/dd>
&lt;/dl>
&lt;h2 id="org-block-split">If so, split the block&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#org-block-split">#&lt;/a>&lt;/h2>
&lt;p>With the &amp;ldquo;point in an Org block&amp;rdquo; detection working, I now needed the
split to happen with these rules:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>If the point is anywhere on the line, but not at the beginning of
the line (BOL),&lt;/p>
&lt;ul>
&lt;li>
&lt;p>Go to the end of the line, and then split the block.&lt;/p>
&lt;p>So if the point&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup> is after the first &lt;code>message&lt;/code> identifier,
or at the end of that first &lt;code>message&lt;/code> line:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-org" data-lang="org">&lt;span class="line">&lt;span class="cl">&lt;span class="c">#+begin_src &lt;/span>&lt;span class="cs">emacs-lisp&lt;/span>&lt;span class="c">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nf">message&lt;/span> &lt;span class="s">&amp;#34;one&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>&lt;span class="err">▮&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">(&lt;/span>&lt;span class="nf">message&lt;/span> &lt;span class="s">&amp;#34;two&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">#+end_src&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Split the block at the point &lt;strong>after&lt;/strong> &lt;code>(message &amp;quot;one&amp;quot;)&lt;/code> and move
the point to between the split blocks:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-org" data-lang="org">&lt;span class="line">&lt;span class="cl">&lt;span class="c">#+begin_src &lt;/span>&lt;span class="cs">emacs-lisp&lt;/span>&lt;span class="c">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nf">message&lt;/span> &lt;span class="s">&amp;#34;one&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">#+end_src&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">▮
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">#+begin_src &lt;/span>&lt;span class="cs">emacs-lisp&lt;/span>&lt;span class="c">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nf">message&lt;/span> &lt;span class="s">&amp;#34;two&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">#+end_src&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>Otherwise (if point is at BOL),&lt;/p>
&lt;ul>
&lt;li>
&lt;p>Split the block exactly at that point.&lt;/p>
&lt;p>So if the point is at the beginning of the second &lt;code>message&lt;/code> line:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-org" data-lang="org">&lt;span class="line">&lt;span class="cl">&lt;span class="c">#+begin_src &lt;/span>&lt;span class="cs">emacs-lisp&lt;/span>&lt;span class="c">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nf">message&lt;/span> &lt;span class="s">&amp;#34;one&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="err">▮&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nf">message&lt;/span> &lt;span class="s">&amp;#34;two&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">#+end_src&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Split the block at the point &lt;strong>before&lt;/strong> &lt;code>(message &amp;quot;two&amp;quot;)&lt;/code> and move
the point to between the split blocks:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-org" data-lang="org">&lt;span class="line">&lt;span class="cl">&lt;span class="c">#+begin_src &lt;/span>&lt;span class="cs">emacs-lisp&lt;/span>&lt;span class="c">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nf">message&lt;/span> &lt;span class="s">&amp;#34;one&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">#+end_src&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">▮
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">#+begin_src &lt;/span>&lt;span class="cs">emacs-lisp&lt;/span>&lt;span class="c">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="nf">message&lt;/span> &lt;span class="s">&amp;#34;two&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c">#+end_src&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;p>So here&amp;rsquo;s the code that follows that spec:&lt;/p>
&lt;p>&lt;a id="code-snippet--modi-org-split-block">&lt;/a>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-emacs-lisp" data-lang="emacs-lisp">&lt;span class="line">&lt;span class="cl">&lt;span class="p">(&lt;/span>&lt;span class="nb">defun&lt;/span> &lt;span class="nv">modi/org-split-block&lt;/span> &lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;Sensibly split the current Org block at point.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">interactive&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">modi/org-in-any-block-p&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">save-match-data&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">save-restriction&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">widen&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">let&lt;/span> &lt;span class="p">((&lt;/span>&lt;span class="nv">case-fold-search&lt;/span> &lt;span class="no">t&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">at-bol&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">bolp&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">block-start&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">block-end&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">save-excursion&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">re-search-backward&lt;/span> &lt;span class="s">&amp;#34;^\\(?1:[[:blank:]]*#\\+begin_.+?\\)\\(?: .*\\)*$&amp;#34;&lt;/span> &lt;span class="no">nil&lt;/span> &lt;span class="no">nil&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">setq&lt;/span> &lt;span class="nv">block-start&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">match-string-no-properties&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">setq&lt;/span> &lt;span class="nv">block-end&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">replace-regexp-in-string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;begin_&amp;#34;&lt;/span> &lt;span class="s">&amp;#34;end_&amp;#34;&lt;/span> &lt;span class="c1">;Replaces &amp;#34;begin_&amp;#34; with &amp;#34;end_&amp;#34;, &amp;#34;BEGIN_&amp;#34; with &amp;#34;END_&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">match-string-no-properties&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">))))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">;; Go to the end of current line, if not at the BOL&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">unless&lt;/span> &lt;span class="nv">at-bol&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">end-of-line&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">insert&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">concat&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">if&lt;/span> &lt;span class="nv">at-bol&lt;/span> &lt;span class="s">&amp;#34;&amp;#34;&lt;/span> &lt;span class="s">&amp;#34;\n&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">block-end&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;\n\n&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">block-start&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">if&lt;/span> &lt;span class="nv">at-bol&lt;/span> &lt;span class="s">&amp;#34;\n&amp;#34;&lt;/span> &lt;span class="s">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">;; Go to the line before the inserted &amp;#34;#+begin_ ..&amp;#34; line&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">beginning-of-line&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">if&lt;/span> &lt;span class="nv">at-bol&lt;/span> &lt;span class="mi">-1&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)))))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">message&lt;/span> &lt;span class="s">&amp;#34;Point is not in an Org block&amp;#34;&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="src-block-caption">
&lt;span class="src-block-number">&lt;a href="#code-snippet--modi-org-split-block">Code Snippet 2&lt;/a>:&lt;/span>
Function to split the current Org block in sensible fashion
&lt;/div>
&lt;ul>
&lt;li>The regexp for extracting &lt;code>block-start&lt;/code> is the same as
&lt;code>block-begin-re&lt;/code> in &lt;a href="#code-snippet--modi-org-in-any-block-p">Code Snippet 1&lt;/a>, but with
different sub-grouping.&lt;/li>
&lt;li>The &lt;code>block-end&lt;/code> string is derived from sub-group 1 of &lt;code>block-start&lt;/code>
string &amp;ndash; just replacing &amp;ldquo;begin_&amp;rdquo; with &amp;ldquo;end_&amp;rdquo;.&lt;/li>
&lt;li>And then based on if the point was initially at BOL (&lt;code>at-bol&lt;/code>), the
insertion of newlines and movement of point is done accordingly.&lt;/li>
&lt;/ul>
&lt;h2 id="M-return-split-block-dwim">Now make &lt;kbd>M-return&lt;/kbd> do that&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#M-return-split-block-dwim">#&lt;/a>&lt;/h2>
&lt;p>With these two functions evaluated, &lt;code>M-x modi/org-split-block&lt;/code> will
work right away.&lt;/p>
&lt;div class="verse">
&lt;p>    But where&amp;rsquo;s the fun in that‽&lt;br />&lt;/p>
&lt;/div>
&lt;p>I needed to have the Org block splitting happen with an intuitive
binding &amp;mdash; like &lt;kbd>M-return&lt;/kbd>.&lt;/p>
&lt;ul>
&lt;li>By default, &lt;kbd>M-return&lt;/kbd> is used to either create new headings, or do
other things like insert an item, wrap a region in table, etc. based
on the context. See the doc-string of &lt;code>org-meta-return&lt;/code> (function
bound to this key by default) for more info.&lt;/li>
&lt;li>But it doesn&amp;rsquo;t have a context for &amp;ldquo;point in an Org block&amp;rdquo;. So it
tries to create a heading when inside a block too, which doesn&amp;rsquo;t
make much sense.&lt;/li>
&lt;li>So fix that by adding that context.&lt;/li>
&lt;/ul>
&lt;p>So I &lt;strong>advise&lt;/strong> &lt;code>org-meta-return&lt;/code> to call &lt;code>modi/org-split-block&lt;/code> when
the point is inside an Org block.&lt;/p>
&lt;p>The advising function &lt;code>modi/org-meta-return&lt;/code> is the same as the
advised function &lt;code>org-meta-return&lt;/code> (as of &lt;span class="timestamp-wrapper">&lt;span class="timestamp">&amp;lt;2018-08-26 Sun&amp;gt;&lt;/span>&lt;/span>), except
that a new context &lt;code>(modi/org-in-any-block-p)&lt;/code> is added.&lt;/p>
&lt;p>&lt;em>You can tweak the precedence of this new context by moving the
&lt;code>((modi/org-in-any-block-p) #'modi/org-split-block)&lt;/code> form in that
&lt;code>cond&lt;/code> form.&lt;/em>&lt;/p>
&lt;p>&lt;a id="code-snippet--modi-org-meta-return">&lt;/a>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-emacs-lisp" data-lang="emacs-lisp">&lt;span class="line">&lt;span class="cl">&lt;span class="p">(&lt;/span>&lt;span class="nb">defun&lt;/span> &lt;span class="nv">modi/org-meta-return&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kp">&amp;amp;optional&lt;/span> &lt;span class="nv">arg&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;Insert a new heading or wrap a region in a table.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">Calls &lt;/span>&lt;span class="ss">`org-insert-heading&amp;#39;&lt;/span>&lt;span class="s">, &lt;/span>&lt;span class="ss">`org-insert-item&amp;#39;&lt;/span>&lt;span class="s">,
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">&lt;/span>&lt;span class="ss">`org-table-wrap-region&amp;#39;&lt;/span>&lt;span class="s">, or &lt;/span>&lt;span class="ss">`modi/org-split-block&amp;#39;&lt;/span>&lt;span class="s"> depending on
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">context. When called with an argument, unconditionally call
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">&lt;/span>&lt;span class="ss">`org-insert-heading&amp;#39;&lt;/span>&lt;span class="s">.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">interactive&lt;/span> &lt;span class="s">&amp;#34;P&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">org-check-before-invisible-edit&lt;/span> &lt;span class="ss">&amp;#39;insert&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">or&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">run-hook-with-args-until-success&lt;/span> &lt;span class="ss">&amp;#39;org-metareturn-hook&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">call-interactively&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">cond&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">arg&lt;/span> &lt;span class="nf">#&amp;#39;&lt;/span>&lt;span class="nv">org-insert-heading&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">((&lt;/span>&lt;span class="nv">org-at-table-p&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">#&amp;#39;&lt;/span>&lt;span class="nv">org-table-wrap-region&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">((&lt;/span>&lt;span class="nv">org-in-item-p&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">#&amp;#39;&lt;/span>&lt;span class="nv">org-insert-item&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">((&lt;/span>&lt;span class="nv">modi/org-in-any-block-p&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nf">#&amp;#39;&lt;/span>&lt;span class="nv">modi/org-split-block&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="no">t&lt;/span> &lt;span class="nf">#&amp;#39;&lt;/span>&lt;span class="nv">org-insert-heading&lt;/span>&lt;span class="p">)))))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">(&lt;/span>&lt;span class="nv">advice-add&lt;/span> &lt;span class="ss">&amp;#39;org-meta-return&lt;/span> &lt;span class="nb">:override&lt;/span> &lt;span class="nf">#&amp;#39;&lt;/span>&lt;span class="nv">modi/org-meta-return&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="src-block-caption">
&lt;span class="src-block-number">&lt;a href="#code-snippet--modi-org-meta-return">Code Snippet 3&lt;/a>:&lt;/span>
Advising &lt;code>org-meta-return&lt;/code> to add context of point being inside any Org block
&lt;/div>
&lt;p>Now with the point in &lt;strong>any&lt;/strong> Org block, &lt;kbd>M-return&lt;/kbd> away!&lt;/p>
&lt;h2 id="full-code">Full code&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#full-code">#&lt;/a>&lt;/h2>
&lt;p>Look for the source of &lt;code>modi/org-split-block&lt;/code> (and dependent
functions) added to &lt;a href="https://github.com/kaushalmodi/.emacs.d/blob/master/setup-files/setup-org.el">&lt;code>setup-org.el&lt;/code>&lt;/a> in my Emacs config.&lt;/p>
&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>The point is denoted by the &lt;em>BLACK VERTICAL RECTANGLE&lt;/em> unicode
char (▮).&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description><author>Kaushal.Modi@fakeEmailToMakeValidatorHappy.com (Kaushal Modi)</author><category domain="https://scripter.co/categories/emacs">emacs</category><category domain="https://scripter.co/categories/org">org</category><category domain="https://scripter.co/categories/elisp">elisp</category><category domain="https://scripter.co/tags/src-block">src-block</category><category domain="https://scripter.co/tags/org-block">org-block</category><category domain="https://scripter.co/tags/split">split</category><category domain="https://scripter.co/tags/advice">advice</category><guid>https://scripter.co/splitting-an-org-block-into-two/</guid><pubDate>Wed, 22 Aug 2018 17:47:00 -0400</pubDate></item><item><title>Optimize your FontAwesome</title><link>https://scripter.co/optimize-your-fontawesome/</link><description>&lt;blockquote>How to trim the FontAwesome JS for your website, using Emacs Lisp.&lt;/blockquote>&lt;div class="ox-hugo-toc toc">
&lt;div class="heading">Table of Contents&lt;/div>
&lt;ul>
&lt;li>&lt;a href="#fontawesome-5-setup">FontAwesome 5 Setup&lt;/a>&lt;/li>
&lt;li>&lt;a href="#problem">Problem&lt;/a>&lt;/li>
&lt;li>&lt;a href="#solution">Solution&lt;/a>&lt;/li>
&lt;li>&lt;a href="#code">Code&lt;/a>&lt;/li>
&lt;li>&lt;a href="#result">Result&lt;/a>&lt;/li>
&lt;/ul>
&lt;/div>
&lt;!--endtoc-->
&lt;p>This post briefly goes through the FontAwesome 5 setup, the problem
statement and a solution for that.&lt;/p>
&lt;p>I signed up for the &lt;a href="https://www.kickstarter.com/projects/232193852/font-awesome-5">FontAwesome 5 Pro Kickstarter&lt;/a>, and I have to say
that this is one of my few Kickstarter fundings that delivered really
well!&lt;/p>
&lt;div class="verse">
&lt;p>    &lt;em>Thank you FontAwesome!&lt;/em>&lt;br />&lt;/p>
&lt;/div>
&lt;h2 id="fontawesome-5-setup">FontAwesome 5 Setup&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#fontawesome-5-setup">#&lt;/a>&lt;/h2>
&lt;p>FontAwesome 5 supports an awesome new way of loading icons, using
&lt;a href="https://fontawesome.com/get-started/svg-with-js">SVG with JS&lt;/a>. The benefit is that you simply load one
&lt;code>fontawesome-all.js&lt;/code> file and &lt;em>everything just works&lt;/em>!.&lt;/p>
&lt;p>&lt;a id="code-snippet--fa-setup">&lt;/a>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-html" data-lang="html">&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">head&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c">&amp;lt;!--load everything--&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">script&lt;/span> &lt;span class="na">defer&lt;/span> &lt;span class="na">src&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;/static/fontawesome/fontawesome-all.js&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&amp;lt;/&lt;/span>&lt;span class="nt">script&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">head&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">body&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c">&amp;lt;!--icon--&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">&amp;lt;&lt;/span>&lt;span class="nt">i&lt;/span> &lt;span class="na">class&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="s">&amp;#34;fas fa-user&amp;#34;&lt;/span>&lt;span class="p">&amp;gt;&amp;lt;/&lt;/span>&lt;span class="nt">i&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">&amp;lt;/&lt;/span>&lt;span class="nt">body&lt;/span>&lt;span class="p">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="src-block-caption">
&lt;span class="src-block-number">&lt;a href="#code-snippet--fa-setup">Code Snippet 1&lt;/a>:&lt;/span>
Fontawesome 5 Setup
&lt;/div>
&lt;h2 id="problem">Problem&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#problem">#&lt;/a>&lt;/h2>
&lt;p>With FontAwesome 5 Pro, I get about 2700 icons packed in that
&lt;code>fontawesome-all.js&lt;/code>. While that is great, there are 2 issues:&lt;/p>
&lt;ol>
&lt;li>I don&amp;rsquo;t need all of those 2700 icons on my site. That file is
roughly 1.7 &lt;strong>MB&lt;/strong> (minified or not) which I need not unnecessarily
load for all my visitors.&lt;/li>
&lt;li>Anyone can easily pirate the whole JS file and use it for free!&lt;/li>
&lt;/ol>
&lt;h2 id="solution">Solution&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#solution">#&lt;/a>&lt;/h2>
&lt;p>The solution is simple &amp;mdash; I just comment out all the lines with SVG
code for the icons that I don&amp;rsquo;t use.&lt;/p>
&lt;p>But searching for the icons I need in all the icon packs (Solid,
Regular, Light, Brand) in that single 5k+ line JS file is not fun,
especially when I want to add/remove icons occasionally.&lt;/p>
&lt;p>And so &lt;code>fontawesome-choose&lt;/code> was born!&lt;/p>
&lt;ul>
&lt;li>I list the icons I need in &lt;code>fontawesome-choose-icons&lt;/code>.&lt;/li>
&lt;li>Run &lt;code>M-x fontawesome-choose&lt;/code> in the &lt;code>fontawesome-all.js&lt;/code> file
buffer.&lt;/li>
&lt;/ul>
&lt;p>The code is in the next section, but you can also find the latest
version &lt;a href="https://github.com/kaushalmodi/.emacs.d/blob/master/elisp/misc/fontawesome-choose.el">in my repo&lt;/a>.&lt;/p>
&lt;p>&lt;em>Note that while I have used this on Fontawesome 5 Pro, it should work
just as well on Fontawesome 5 Free too.&lt;/em>&lt;/p>
&lt;h2 id="code">Code&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#code">#&lt;/a>&lt;/h2>
&lt;p>&lt;a id="code-snippet--fontawesome-choose">&lt;/a>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-emacs-lisp" data-lang="emacs-lisp">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">;; Helper function `fontawesome-choose&amp;#39; used to uncomment only the&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">;; icons the user cares about in `fontawesome-all.js&amp;#39;.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">(&lt;/span>&lt;span class="nb">defconst&lt;/span> &lt;span class="nv">fontawesome-choose-icons&lt;/span> &lt;span class="o">&amp;#39;&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;list-alt&amp;#34;&lt;/span> &lt;span class="c1">;categories&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;tags&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;rss&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;link&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;heart&amp;#34;&lt;/span> &lt;span class="c1">;like&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;reply&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;retweet&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;github&amp;#34;&lt;/span> &lt;span class="c1">;&amp;#34;github-alt&amp;#34; &amp;#34;github-square&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;twitter&amp;#34;&lt;/span> &lt;span class="c1">;&amp;#34;twitter-square&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;gitlab&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;List of icons to choose from fontawesome-all.js.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">Used in &lt;/span>&lt;span class="ss">`fontawesome-choose&amp;#39;&lt;/span>&lt;span class="s">.&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">(&lt;/span>&lt;span class="nb">defun&lt;/span> &lt;span class="nv">fontawesome-choose&lt;/span> &lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;Comment out all icons in fontawesome-all.js except the selected few.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">Minifying the resultant .js will then remove the commented icons,
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">thus drastically reducing the minified JS size.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">Set the &lt;/span>&lt;span class="ss">`fontawesome-choose-icons&amp;#39;&lt;/span>&lt;span class="s"> variable to the list of icons
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">that you want to keep uncommented.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">interactive&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">let&lt;/span> &lt;span class="p">((&lt;/span>&lt;span class="nv">case-fold-search&lt;/span> &lt;span class="no">nil&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">count&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">save-excursion&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">goto-char&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">point-min&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">while&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">re-search-forward&lt;/span> &lt;span class="s">&amp;#34;^var icons&amp;#34;&lt;/span> &lt;span class="no">nil&lt;/span> &lt;span class="nb">:noerror&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">let&lt;/span> &lt;span class="p">((&lt;/span>&lt;span class="nv">begin&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">progn&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">forward-line&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">point&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">end&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">re-search-forward&lt;/span> &lt;span class="s">&amp;#34;^\\};&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">forward-line&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">backward-char&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">setq&lt;/span> &lt;span class="nv">end&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">point&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">;; First comment all the lines&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">save-excursion&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">narrow-to-region&lt;/span> &lt;span class="nv">begin&lt;/span> &lt;span class="nv">end&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">goto-char&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">point-min&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">while&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">re-search-forward&lt;/span> &lt;span class="s">&amp;#34;^\\s-*\\(\&amp;#34;\\)&amp;#34;&lt;/span> &lt;span class="no">nil&lt;/span> &lt;span class="nb">:noerror&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">replace-match&lt;/span> &lt;span class="s">&amp;#34;// \\1&amp;#34;&lt;/span> &lt;span class="no">nil&lt;/span> &lt;span class="no">nil&lt;/span> &lt;span class="no">nil&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">widen&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">;; Now uncomment only the selected icons&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">save-excursion&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">narrow-to-region&lt;/span> &lt;span class="nv">begin&lt;/span> &lt;span class="nv">end&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">goto-char&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">point-min&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">let*&lt;/span> &lt;span class="p">((&lt;/span>&lt;span class="nv">icon-regexp&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">regexp-opt&lt;/span> &lt;span class="nv">fontawesome-choose-icons&lt;/span> &lt;span class="ss">&amp;#39;symbols&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">regexp&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">format&lt;/span> &lt;span class="s">&amp;#34;^\\s-*\\(//\\s-*\\)\&amp;#34;%s\&amp;#34;:&amp;#34;&lt;/span> &lt;span class="nv">icon-regexp&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">while&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">re-search-forward&lt;/span> &lt;span class="nv">regexp&lt;/span> &lt;span class="no">nil&lt;/span> &lt;span class="nb">:noerror&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">replace-match&lt;/span> &lt;span class="s">&amp;#34;&amp;#34;&lt;/span> &lt;span class="no">nil&lt;/span> &lt;span class="no">nil&lt;/span> &lt;span class="no">nil&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">setq&lt;/span> &lt;span class="nv">count&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">1+&lt;/span> &lt;span class="nv">count&lt;/span>&lt;span class="p">))))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">widen&lt;/span>&lt;span class="p">))))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">message&lt;/span> &lt;span class="s">&amp;#34;fontawesome-choose: Uncommented %d icons matching %S&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">count&lt;/span> &lt;span class="nv">fontawesome-choose-icons&lt;/span>&lt;span class="p">))))&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="src-block-caption">
&lt;span class="src-block-number">&lt;a href="#code-snippet--fontawesome-choose">Code Snippet 2&lt;/a>:&lt;/span>
&lt;code>fontawesome-choose&lt;/code> &amp;#x2014; Comment out all the FontAwesome icons you don't need
&lt;/div>
&lt;h2 id="result">Result&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#result">#&lt;/a>&lt;/h2>
&lt;p>This solution solves the first problem wonderfully &amp;mdash; After
minification, &lt;code>fontawesome-all.js&lt;/code> is just 71 &lt;strong>KB&lt;/strong> (down from 1.7
&lt;strong>MB&lt;/strong>).&lt;/p>
&lt;p>That&amp;rsquo;s a 25x factor reduction! The minification step is important
because that removes all the commented lines from the JS. I do the
minification using &lt;a href="https://github.com/tdewolff/minify">&lt;code>tdewolff/minify&lt;/code>&lt;/a>.&lt;/p>
&lt;p>The second problem, that of piracy, is difficult to solve completely
using this. But whoever pirates this &lt;em>reduced&lt;/em> FontAwesome from my
site won&amp;rsquo;t get the real deal 😎.&lt;/p>
&lt;p>Emacs-Lisp saves the day once again!&lt;/p></description><author>Kaushal.Modi@fakeEmailToMakeValidatorHappy.com (Kaushal Modi)</author><category domain="https://scripter.co/categories/emacs">emacs</category><category domain="https://scripter.co/categories/elisp">elisp</category><category domain="https://scripter.co/categories/web">web</category><category domain="https://scripter.co/tags/fa5">FA5</category><category domain="https://scripter.co/tags/fontawesome">fontawesome</category><category domain="https://scripter.co/tags/javascript">javascript</category><category domain="https://scripter.co/tags/optimize">optimize</category><category domain="https://scripter.co/tags/minified">minified</category><guid>https://scripter.co/optimize-your-fontawesome/</guid><pubDate>Wed, 04 Apr 2018 15:20:00 -0400</pubDate></item><item><title>Your car will be ready in 8000 seconds</title><link>https://scripter.co/convert-seconds-to-human-time/</link><description>&lt;div class="ox-hugo-toc toc">
&lt;div class="heading">Table of Contents&lt;/div>
&lt;ul>
&lt;li>&lt;a href="#code">Code&lt;/a>&lt;/li>
&lt;li>&lt;a href="#tests">Tests&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#test-output">Test Output&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#source">Source&lt;/a>&lt;/li>
&lt;li>&lt;a href="#closing">Closing&lt;/a>&lt;/li>
&lt;li>&lt;a href="#appendix">Appendix&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#test-output-using-format-seconds">Test output using &lt;code>format-seconds&lt;/code>&lt;/a>&lt;/li>
&lt;li>&lt;a href="#code--revision-1">Code (Revision 1)&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/div>
&lt;!--endtoc-->
&lt;div class="verse">
&lt;p>    Huh? 😕&lt;br />&lt;/p>
&lt;/div>
&lt;p>Well, a mechanic usually wouldn&amp;rsquo;t give you a time estimate in seconds,
but a tool I am using prints something like this at the end:&lt;/p>
&lt;blockquote>
&lt;p>The simulation took 54227.9 seconds in CPU time.&lt;/p>
&lt;/blockquote>
&lt;p>That triggered me to write a &amp;ldquo;little&amp;rdquo; script to convert seconds to
&lt;em>human time&lt;/em> i.e. time in &lt;em>days&lt;/em>, &lt;em>hours&lt;/em>, &lt;em>minutes&lt;/em> and &lt;em>seconds&lt;/em>.&lt;/p>
&lt;p>Thanks to &lt;a href="https://www.reddit.com/user/xiongtx">&lt;strong>/u/xiongtx&lt;/strong>&lt;/a> from Reddit, I learned about the built-in function
&lt;a href="https://git.savannah.gnu.org/cgit/emacs.git/tree/lisp/calendar/time-date.el?id=04c5bd5b1f1dc07994d70caa640a07da6b5a54b5#n260">&lt;code>format-seconds&lt;/code>&lt;/a> that does what I wanted to do &amp;ndash; &lt;em>but not exactly in a
way I wanted to see&lt;/em>. Though, &lt;code>format-seconds&lt;/code> gave me an idea for a
big optimization (code &lt;a href="https://github.com/kaushalmodi/.emacs.d/commit/db9e1a7c54a4c822e80b0308b7fc644db44fe1eb">commit diff&lt;/a>).&lt;/p>
&lt;p>Below Code section is updated to reflect that. If you like, you can
review older version of the same section at the &lt;a href="#code--revision-1">end of this
post&lt;/a>. Also, at the end, you will find a &lt;a href="#test-output-using-format-seconds">comparison&lt;/a> between the outputs
from &lt;code>format-seconds&lt;/code> and &lt;code>modi/seconds-to-human-time&lt;/code>.&lt;/p>
&lt;hr>
&lt;h2 id="code">Code&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#code">#&lt;/a>&lt;/h2>
&lt;p>Here&amp;rsquo;s the &lt;strong>updated&lt;/strong> code, and notes about that follow after that:&lt;/p>
&lt;p>&lt;a id="code-snippet--seconds-to-human-time">&lt;/a>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-emacs-lisp" data-lang="emacs-lisp">&lt;span class="line">&lt;span class="cl">&lt;span class="p">(&lt;/span>&lt;span class="nb">defun&lt;/span> &lt;span class="nv">modi/seconds-to-human-time&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kp">&amp;amp;optional&lt;/span> &lt;span class="nv">seconds&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;Convert SECONDS to \&amp;#34;DDd HHh MMm SSs\&amp;#34; string.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">SECONDS is a non-negative integer or fractional number.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">SECONDS can also be a list of such numbers, which is the case
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">when this function is called recursively.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">When called interactively, if a region is selected SECONDS is
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">extracted from that, else the user is prompted to enter those.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">interactive&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">let&lt;/span> &lt;span class="p">((&lt;/span>&lt;span class="nv">inter&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">called-interactively-p&lt;/span> &lt;span class="ss">&amp;#39;interactive&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">when&lt;/span> &lt;span class="nv">inter&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">let&lt;/span> &lt;span class="p">((&lt;/span>&lt;span class="nv">seconds-str&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">use-region-p&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">buffer-substring-no-properties&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">region-beginning&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">region-end&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">read-string&lt;/span> &lt;span class="s">&amp;#34;Enter seconds: &amp;#34;&lt;/span>&lt;span class="p">))))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">setq&lt;/span> &lt;span class="nv">seconds&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">string-to-number&lt;/span> &lt;span class="nv">seconds-str&lt;/span>&lt;span class="p">))))&lt;/span> &lt;span class="c1">;&amp;#34;1&amp;#34; -&amp;gt; 1, &amp;#34;1.2&amp;#34; -&amp;gt; 1.2, &amp;#34;&amp;#34; -&amp;gt; 0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">let*&lt;/span> &lt;span class="p">((&lt;/span>&lt;span class="nv">MINUTE&lt;/span> &lt;span class="mi">60&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">HOUR&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">*&lt;/span> &lt;span class="mi">60&lt;/span> &lt;span class="nv">MINUTE&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">DAY&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">*&lt;/span> &lt;span class="mi">24&lt;/span> &lt;span class="nv">HOUR&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">sec&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">cond&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">((&lt;/span>&lt;span class="nf">listp&lt;/span> &lt;span class="nv">seconds&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">;This is entered only by recursive calls&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">car&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">last&lt;/span> &lt;span class="nv">seconds&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">((&lt;/span>&lt;span class="nb">and&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">numberp&lt;/span> &lt;span class="nv">seconds&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">;This is entered only in the first entry&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">&amp;gt;=&lt;/span> &lt;span class="nv">seconds&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">seconds&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="no">t&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="ne">user-error&lt;/span> &lt;span class="s">&amp;#34;Invalid argument %S&amp;#34;&lt;/span> &lt;span class="nv">seconds&lt;/span>&lt;span class="p">))))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">gen-time-string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">lambda&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">time&lt;/span> &lt;span class="nv">inter&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;Return string representation of TIME.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">TIME is of the type (DD HH MM SS), where each of those elements
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">are numbers. If INTER is non-nil, echo the time string in a
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">well-formatted manner instead of returning it.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">let&lt;/span> &lt;span class="p">((&lt;/span>&lt;span class="nv">filler&lt;/span> &lt;span class="s">&amp;#34; &amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">str&lt;/span> &lt;span class="s">&amp;#34;&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">dolist&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">unit&lt;/span> &lt;span class="o">&amp;#39;&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="s">&amp;#34;d&amp;#34;&lt;/span> &lt;span class="s">&amp;#34;h&amp;#34;&lt;/span> &lt;span class="s">&amp;#34;m&amp;#34;&lt;/span> &lt;span class="s">&amp;#34;s&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">let*&lt;/span> &lt;span class="p">((&lt;/span>&lt;span class="nv">val&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">car&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">rassoc&lt;/span> &lt;span class="nv">unit&lt;/span> &lt;span class="nv">time&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">val-str&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">cond&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">((&lt;/span>&lt;span class="nb">and&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">string=&lt;/span> &lt;span class="nv">unit&lt;/span> &lt;span class="s">&amp;#34;s&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">;0 seconds&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">=&lt;/span> &lt;span class="nv">val&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">string-match-p&lt;/span> &lt;span class="s">&amp;#34;\\&lt;/span>&lt;span class="ss">`\\s-*\\&amp;#39;&lt;/span>&lt;span class="s">&amp;#34;&lt;/span> &lt;span class="nv">str&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34; 0s&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">((&lt;/span>&lt;span class="nb">and&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">string=&lt;/span> &lt;span class="nv">unit&lt;/span> &lt;span class="s">&amp;#34;s&amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">&amp;gt;&lt;/span> &lt;span class="nv">val&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">integerp&lt;/span> &lt;span class="nv">val&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">format&lt;/span> &lt;span class="s">&amp;#34;%2d%s&amp;#34;&lt;/span> &lt;span class="nv">val&lt;/span> &lt;span class="nv">unit&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">format&lt;/span> &lt;span class="s">&amp;#34;%5.2f%s&amp;#34;&lt;/span> &lt;span class="nv">val&lt;/span> &lt;span class="nv">unit&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">((&lt;/span>&lt;span class="nb">and&lt;/span> &lt;span class="nv">val&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">&amp;gt;&lt;/span> &lt;span class="nv">val&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">format&lt;/span> &lt;span class="s">&amp;#34;%2d%s &amp;#34;&lt;/span> &lt;span class="nv">val&lt;/span> &lt;span class="nv">unit&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="no">t&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">filler&lt;/span>&lt;span class="p">))))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">setq&lt;/span> &lt;span class="nv">str&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">concat&lt;/span> &lt;span class="nv">str&lt;/span> &lt;span class="nv">val-str&lt;/span>&lt;span class="p">))))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">;; (message &amp;#34;debug: %S&amp;#34; time)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">if&lt;/span> &lt;span class="nv">inter&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">message&lt;/span> &lt;span class="s">&amp;#34;%0.2f seconds → %s&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">seconds&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">string-trim&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">replace-regexp-in-string&lt;/span> &lt;span class="s">&amp;#34; +&amp;#34;&lt;/span> &lt;span class="s">&amp;#34; &amp;#34;&lt;/span> &lt;span class="nv">str&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">string-trim-right&lt;/span> &lt;span class="nv">str&lt;/span>&lt;span class="p">)))))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">time&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">cond&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">((&lt;/span>&lt;span class="nf">&amp;gt;=&lt;/span> &lt;span class="nv">sec&lt;/span> &lt;span class="nv">DAY&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">;&amp;gt; day&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">let*&lt;/span> &lt;span class="p">((&lt;/span>&lt;span class="nv">days&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">/&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">floor&lt;/span> &lt;span class="nv">sec&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nv">DAY&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">rem&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">-&lt;/span> &lt;span class="nv">sec&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">*&lt;/span> &lt;span class="nv">days&lt;/span> &lt;span class="nv">DAY&lt;/span>&lt;span class="p">))))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">;; Note that (list rem) instead of just `rem&amp;#39; is&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">;; being passed to the recursive call to&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">;; `modi/seconds-to-human-time&amp;#39;. This helps us&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">;; distinguish between direct and re-entrant&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">;; calls to this function.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">append&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">list&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">cons&lt;/span> &lt;span class="nv">days&lt;/span> &lt;span class="s">&amp;#34;d&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">modi/seconds-to-human-time&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">list&lt;/span> &lt;span class="nv">rem&lt;/span>&lt;span class="p">)))))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">((&lt;/span>&lt;span class="nf">&amp;gt;=&lt;/span> &lt;span class="nv">sec&lt;/span> &lt;span class="nv">HOUR&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">;&amp;gt; hour AND &amp;lt; day&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">let*&lt;/span> &lt;span class="p">((&lt;/span>&lt;span class="nv">hours&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">/&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">floor&lt;/span> &lt;span class="nv">sec&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nv">HOUR&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">rem&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">-&lt;/span> &lt;span class="nv">sec&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">*&lt;/span> &lt;span class="nv">hours&lt;/span> &lt;span class="nv">HOUR&lt;/span>&lt;span class="p">))))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">append&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">list&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">cons&lt;/span> &lt;span class="nv">hours&lt;/span> &lt;span class="s">&amp;#34;h&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">modi/seconds-to-human-time&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">list&lt;/span> &lt;span class="nv">rem&lt;/span>&lt;span class="p">)))))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">((&lt;/span>&lt;span class="nf">&amp;gt;=&lt;/span> &lt;span class="nv">sec&lt;/span> &lt;span class="nv">MINUTE&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">;&amp;gt; minute AND &amp;lt; hour&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">let*&lt;/span> &lt;span class="p">((&lt;/span>&lt;span class="nv">mins&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">/&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">floor&lt;/span> &lt;span class="nv">sec&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nv">MINUTE&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">rem&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">-&lt;/span> &lt;span class="nv">sec&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">*&lt;/span> &lt;span class="nv">mins&lt;/span> &lt;span class="nv">MINUTE&lt;/span>&lt;span class="p">))))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">append&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">list&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">cons&lt;/span> &lt;span class="nv">mins&lt;/span> &lt;span class="s">&amp;#34;m&amp;#34;&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">modi/seconds-to-human-time&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">list&lt;/span> &lt;span class="nv">rem&lt;/span>&lt;span class="p">)))))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="no">t&lt;/span> &lt;span class="c1">;&amp;lt; minute&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">list&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">cons&lt;/span> &lt;span class="nv">sec&lt;/span> &lt;span class="s">&amp;#34;s&amp;#34;&lt;/span>&lt;span class="p">))))))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">;; If `seconds&amp;#39; is a number and not a list, this is *not* a&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">;; recursive call. Return the time as a string only then. For&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">;; re-entrant executions, return the `time&amp;#39; list instead.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">numberp&lt;/span> &lt;span class="nv">seconds&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">funcall&lt;/span> &lt;span class="nv">gen-time-string&lt;/span> &lt;span class="nv">time&lt;/span> &lt;span class="nv">inter&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">time&lt;/span>&lt;span class="p">))))&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="src-block-caption">
&lt;span class="src-block-number">&lt;a href="#code-snippet--seconds-to-human-time">Code Snippet 1&lt;/a>:&lt;/span>
Seconds to Human Time
&lt;/div>
&lt;p>Most of this snippet is just the day/hour/minute/second math. Apart
from that, here are some points that I found of interest:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>I did not always want to prompt the user to enter the input
argument. If a region was selected, the function assumes that the
user selected a number, and skips the prompt step. So I used a plain
&lt;code>(interactive)&lt;/code> form instead of using &lt;code>(interactive &amp;quot;sPrompt: &amp;quot;)&lt;/code> or
&lt;code>(interactive &amp;quot;r&amp;quot;)&lt;/code>. See &lt;a href="https://www.gnu.org/software/emacs/manual/html_node/eintr/Interactive-Options.html">&lt;em>(eintr) Interactive Options&lt;/em>&lt;/a> and &lt;a href="https://www.gnu.org/software/emacs/manual/html_node/elisp/Interactive-Codes.html">&lt;em>(elisp)
Interactive Codes&lt;/em>&lt;/a> to learn about &lt;code>interactive&lt;/code> and its codes.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Instead of in-lining a modular chunk of logic, like the one where I
convert a list like &lt;code>(1 2 3 4)&lt;/code> into &lt;code>&amp;quot;1d 2h 3m 4s&amp;quot;&lt;/code>, I assigned it
to a &lt;em>let&lt;/em>-bound symbol &lt;code>gen-time-string&lt;/code>. That allowed the logic to
be more discernible when used in:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-emacs-lisp" data-lang="emacs-lisp">&lt;span class="line">&lt;span class="cl">&lt;span class="p">(&lt;/span>&lt;span class="nb">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">numberp&lt;/span> &lt;span class="nv">seconds&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">funcall&lt;/span> &lt;span class="nv">gen-time-string&lt;/span> &lt;span class="nv">time&lt;/span> &lt;span class="nv">inter&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">time&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>Also interesting is the fact that these &lt;em>let&lt;/em>-bound lambdas can
have their own doc-strings too.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>I make use of &lt;strong>recursion&lt;/strong> in this function! But I needed this
function to return a string (using that &lt;code>gen-time-string&lt;/code> function)
only when all the nested calls to itself were returned. So to
distinguish between a direct call to the function, and re-entrant
calls, when doing the latter, I make the input number a &lt;em>list of
that number&lt;/em>.&lt;/p>
&lt;ul>
&lt;li>So while the function might take an input number like &lt;code>7&lt;/code> for a
direct call, that same number, when needed to call to a recursive
call, would get passed as &lt;code>(list 7)&lt;/code> or &lt;code>'(7)&lt;/code>.&lt;/li>
&lt;li>If you glance back as that little snippet above, I return the
&lt;code>time&lt;/code> as a string only if the input &lt;code>seconds&lt;/code> is a &lt;em>number&lt;/em> &amp;mdash; and
not a list i.e. only when I am in the &amp;ldquo;direct call instance&amp;rdquo;.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>The internal variable &lt;code>time&lt;/code> is now an alist&lt;/strong> and can have up to 4
cons elements. Each cons is of the type &lt;code>(TIMEVALUE . TIMEUNIT)&lt;/code>. So
&lt;code>time&lt;/code> now looks like &lt;code>((DAYS . &amp;quot;d&amp;quot;) (HOURS . &amp;quot;h&amp;quot;) (MINUTES . &amp;quot;m&amp;quot;) (SECONDS . &amp;quot;s&amp;quot;))&lt;/code>.&lt;/p>
&lt;p>If the input &lt;code>seconds&lt;/code> is 7200 seconds i.e. 2 hours, I cannot allow
&lt;code>time&lt;/code> to be just &lt;code>(2)&lt;/code>, because then I wouldn&amp;rsquo;t know the unit of
that &lt;code>2&lt;/code> (2 days? 2 hours? ..). With the above technique to &lt;em>tag&lt;/em> the
time value with its unit (inspired from &lt;code>format-seconds&lt;/code>), the
&lt;code>time&lt;/code> value will be set as &lt;code>((2 . &amp;quot;h&amp;quot;))&lt;/code> instead. That way, it
would read clearly as &lt;em>2 hours, 0 minutes, and 0 seconds&lt;/em>.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Back inside &lt;code>gen-time-string&lt;/code>, I then skip printing the time units
that are 0 (unless everything is 0, in which case I print
&lt;code>&amp;quot;0s&amp;quot;&lt;/code>).&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-emacs-lisp" data-lang="emacs-lisp">&lt;span class="line">&lt;span class="cl">&lt;span class="p">((&lt;/span>&lt;span class="nb">and&lt;/span> &lt;span class="nv">val&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">&amp;gt;&lt;/span> &lt;span class="nv">val&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">format&lt;/span> &lt;span class="s">&amp;#34;%2d%s &amp;#34;&lt;/span> &lt;span class="nv">val&lt;/span> &lt;span class="nv">unit&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">(&lt;/span>&lt;span class="no">t&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">filler&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">;`filler&amp;#39; is just white-space&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>So instead of printing &lt;code>&amp;quot;1d 0h 0m 5s&amp;quot;&lt;/code>, it would print &lt;code>&amp;quot;1d 5s&amp;quot;&lt;/code>.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h2 id="tests">Tests&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#tests">#&lt;/a>&lt;/h2>
&lt;p>&lt;em>The test generator did not need to be updated, because the code
optimization was completely internal &amp;mdash; Return values were not
affected.&lt;/em>&lt;/p>
&lt;p>A code isn&amp;rsquo;t complete without tests!&lt;/p>
&lt;p>As much fun I had writing the above function, I had equal fun in
writing its little tester too.&lt;/p>
&lt;p>&lt;a id="code-snippet--seconds-to-human-time-test">&lt;/a>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-emacs-lisp" data-lang="emacs-lisp">&lt;span class="line">&lt;span class="cl">&lt;span class="p">(&lt;/span>&lt;span class="nb">let*&lt;/span> &lt;span class="p">((&lt;/span>&lt;span class="nv">rand-bool&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">lambda&lt;/span>&lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;(random 2) will return either 1 or 0, so
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> frac will be either t or nil&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">=&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">random&lt;/span> &lt;span class="mi">2&lt;/span>&lt;span class="p">))))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">count&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">secs&lt;/span> &lt;span class="o">&amp;#39;&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="mi">0&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="mi">60&lt;/span> &lt;span class="mi">61&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="mi">3600&lt;/span> &lt;span class="mi">3601&lt;/span> &lt;span class="mi">3660&lt;/span> &lt;span class="mi">3661&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="mi">86400&lt;/span> &lt;span class="mi">86401&lt;/span> &lt;span class="mi">86460&lt;/span> &lt;span class="mi">86461&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="mi">90000&lt;/span> &lt;span class="mi">90001&lt;/span> &lt;span class="mi">90060&lt;/span> &lt;span class="mi">90061&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">len-secs&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">length&lt;/span> &lt;span class="nv">secs&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">secs-rand1&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">mapcar&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">lambda&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">s&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">let&lt;/span> &lt;span class="p">((&lt;/span>&lt;span class="nv">add-sec&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">funcall&lt;/span> &lt;span class="nv">rand-bool&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">add-min&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">funcall&lt;/span> &lt;span class="nv">rand-bool&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">add-hr&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">funcall&lt;/span> &lt;span class="nv">rand-bool&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">add-day&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">funcall&lt;/span> &lt;span class="nv">rand-bool&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">when&lt;/span> &lt;span class="nv">add-sec&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">setq&lt;/span> &lt;span class="nv">s&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">+&lt;/span> &lt;span class="nv">s&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">when&lt;/span> &lt;span class="nv">add-min&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">setq&lt;/span> &lt;span class="nv">s&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">+&lt;/span> &lt;span class="nv">s&lt;/span> &lt;span class="mi">60&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">when&lt;/span> &lt;span class="nv">add-hr&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">setq&lt;/span> &lt;span class="nv">s&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">+&lt;/span> &lt;span class="nv">s&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">*&lt;/span> &lt;span class="mi">60&lt;/span> &lt;span class="mi">60&lt;/span>&lt;span class="p">))))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">when&lt;/span> &lt;span class="nv">add-day&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">setq&lt;/span> &lt;span class="nv">s&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">+&lt;/span> &lt;span class="nv">s&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">*&lt;/span> &lt;span class="mi">60&lt;/span> &lt;span class="mi">60&lt;/span> &lt;span class="mi">24&lt;/span>&lt;span class="p">))))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">s&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">secs&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">secs-rand2&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">dotimes&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">_&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">*&lt;/span> &lt;span class="mi">2&lt;/span> &lt;span class="nv">len-secs&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">let*&lt;/span> &lt;span class="p">((&lt;/span>&lt;span class="nv">frac&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">funcall&lt;/span> &lt;span class="nv">rand-bool&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">sec&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">if&lt;/span> &lt;span class="nv">frac&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">/&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">random&lt;/span> &lt;span class="mi">100000000&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="mf">100.00&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">random&lt;/span> &lt;span class="mi">1000000&lt;/span>&lt;span class="p">))))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">push&lt;/span> &lt;span class="nv">sec&lt;/span> &lt;span class="nv">secs-rand2&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">dolist&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">sec&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">append&lt;/span> &lt;span class="nv">secs&lt;/span> &lt;span class="nv">secs-rand1&lt;/span> &lt;span class="nv">secs-rand2&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">message&lt;/span> &lt;span class="s">&amp;#34;%9.2f seconds → %s&amp;#34;&lt;/span> &lt;span class="nv">sec&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">modi/seconds-to-human-time&lt;/span> &lt;span class="nv">sec&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">cl-incf&lt;/span> &lt;span class="nv">count&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">when&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">=&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">mod&lt;/span> &lt;span class="nv">count&lt;/span> &lt;span class="nv">len-secs&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">message&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">make-string&lt;/span> &lt;span class="mi">40&lt;/span> &lt;span class="sc">?─&lt;/span>&lt;span class="p">)))))&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="src-block-caption">
&lt;span class="src-block-number">&lt;a href="#code-snippet--seconds-to-human-time-test">Code Snippet 2&lt;/a>:&lt;/span>
Test Generator
&lt;/div>
&lt;ul>
&lt;li>The test also makes use of a &lt;em>let&lt;/em>-bound lambda, for the &lt;code>rand-bool&lt;/code>
function which I use to randomly return &lt;code>t&lt;/code> or &lt;code>nil&lt;/code>.&lt;/li>
&lt;li>The &lt;code>secs&lt;/code> list is a set of directed tests, in which the &lt;em>day&lt;/em>,
&lt;em>hour&lt;/em>, &lt;em>minute&lt;/em> and &lt;em>second&lt;/em> units in &lt;code>time&lt;/code> get set to &lt;code>1&lt;/code> in all
possible combinations. (If you are into binary numbers, think of
&lt;code>0000&lt;/code>, &lt;code>0001&lt;/code>, .. up to &lt;code>1111&lt;/code>.)&lt;/li>
&lt;li>The &lt;code>secs-rand1&lt;/code> is a partly randomized version of &lt;code>secs&lt;/code> where one
or more of the above time units would get randomly added by 1.&lt;/li>
&lt;li>The &lt;code>secs-rand2&lt;/code> is a totally randomized list of time in seconds
where the time could be anywhere in the &lt;code>[0, 1000000)&lt;/code> range,
fractional times with 2 decimal places included.&lt;/li>
&lt;/ul>
&lt;h3 id="test-output">Test Output&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#test-output">#&lt;/a>&lt;/h3>
&lt;p>Upon evaluating both &lt;a href="#code-snippet--seconds-to-human-time">Code Snippet 1&lt;/a> and
&lt;a href="#code-snippet--seconds-to-human-time-test">Code Snippet 2&lt;/a>, you will get an output like below:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl"> 0.00 seconds → 0s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 1.00 seconds → 1s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 60.00 seconds → 1m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 61.00 seconds → 1m 1s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 3600.00 seconds → 1h
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 3601.00 seconds → 1h 1s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 3660.00 seconds → 1h 1m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 3661.00 seconds → 1h 1m 1s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 86400.00 seconds → 1d
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 86401.00 seconds → 1d 1s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 86460.00 seconds → 1d 1m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 86461.00 seconds → 1d 1m 1s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 90000.00 seconds → 1d 1h
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 90001.00 seconds → 1d 1h 1s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 90060.00 seconds → 1d 1h 1m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 90061.00 seconds → 1d 1h 1m 1s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">────────────────────────────────────────
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 60.00 seconds → 1m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 86402.00 seconds → 1d 2s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 86521.00 seconds → 1d 2m 1s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 3722.00 seconds → 1h 2m 2s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 3661.00 seconds → 1h 1m 1s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 90061.00 seconds → 1d 1h 1m 1s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 90121.00 seconds → 1d 1h 2m 1s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 93662.00 seconds → 1d 2h 1m 2s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">172861.00 seconds → 2d 1m 1s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">176462.00 seconds → 2d 1h 1m 2s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">176520.00 seconds → 2d 1h 2m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 86521.00 seconds → 1d 2m 1s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">176460.00 seconds → 2d 1h 1m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 90062.00 seconds → 1d 1h 1m 2s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 93660.00 seconds → 1d 2h 1m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 93661.00 seconds → 1d 2h 1m 1s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">────────────────────────────────────────
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">429733.00 seconds → 4d 23h 22m 13s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">902957.30 seconds → 10d 10h 49m 17.30s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">684313.07 seconds → 7d 22h 5m 13.07s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 62058.42 seconds → 17h 14m 18.42s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">799077.55 seconds → 9d 5h 57m 57.55s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">347952.39 seconds → 4d 39m 12.39s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 31041.30 seconds → 8h 37m 21.30s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">242839.97 seconds → 2d 19h 27m 19.97s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">852518.67 seconds → 9d 20h 48m 38.67s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">160038.24 seconds → 1d 20h 27m 18.24s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">689297.00 seconds → 7d 23h 28m 17s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 64048.00 seconds → 17h 47m 28s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">870956.98 seconds → 10d 1h 55m 56.98s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">608767.00 seconds → 7d 1h 6m 7s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">167796.00 seconds → 1d 22h 36m 36s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">114940.07 seconds → 1d 7h 55m 40.07s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">────────────────────────────────────────
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">106163.46 seconds → 1d 5h 29m 23.46s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">701980.00 seconds → 8d 2h 59m 40s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">258706.73 seconds → 2d 23h 51m 46.73s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 33609.98 seconds → 9h 20m 9.98s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">639774.63 seconds → 7d 9h 42m 54.63s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">338533.00 seconds → 3d 22h 2m 13s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">365910.00 seconds → 4d 5h 38m 30s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">140002.00 seconds → 1d 14h 53m 22s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">365024.20 seconds → 4d 5h 23m 44.20s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">497072.00 seconds → 5d 18h 4m 32s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">304307.67 seconds → 3d 12h 31m 47.67s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">337126.00 seconds → 3d 21h 38m 46s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">711862.00 seconds → 8d 5h 44m 22s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">746474.22 seconds → 8d 15h 21m 14.22s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">200503.00 seconds → 2d 7h 41m 43s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">952391.00 seconds → 11d 33m 11s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">────────────────────────────────────────
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>
&lt;h2 id="source">Source&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#source">#&lt;/a>&lt;/h2>
&lt;p>You can find the latest version of this code at
&lt;a href="https://github.com/kaushalmodi/.emacs.d/blob/master/elisp/misc/seconds-to-human-time.el">&lt;code>seconds-to-human-time.el&lt;/code>&lt;/a> (&lt;a href="https://github.com/kaushalmodi/.emacs.d/blob/d620b4afb5ff4c531e2944015646ec6708692bb9/elisp/misc/seconds-to-human-time.el">&lt;em>first revision&lt;/em>&lt;/a>).&lt;/p>
&lt;h2 id="closing">Closing&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#closing">#&lt;/a>&lt;/h2>
&lt;p>&lt;em>Your car will be ready in 2h 13m 20s, and the simulation took 15h 3m
47.90s in CPU time.&lt;/em>&lt;/p>
&lt;figure>
&lt;img src="https://scripter.co/convert-seconds-to-human-time/images/seconds-to-human-time.png" alt="Figure 1: Screenshot of seconds-to-human-time.el in Emacs"/> &lt;figcaption>
&lt;p>
&lt;span class="figure-number">Figure 1: &lt;/span>Screenshot of &lt;code>seconds-to-human-time.el&lt;/code> in Emacs
&lt;/p>
&lt;/figcaption>&lt;/figure>
&lt;div class="center">&lt;b>§&lt;/b>&lt;/div>
&lt;hr>
&lt;h2 id="appendix">Appendix&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#appendix">#&lt;/a>&lt;/h2>
&lt;h3 id="test-output-using-format-seconds">Test output using &lt;code>format-seconds&lt;/code>&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#test-output-using-format-seconds">#&lt;/a>&lt;/h3>
&lt;p>Instead of using &lt;code>(modi/seconds-to-human-time sec)&lt;/code> in the &lt;a href="#tests">test
generator&lt;/a>, if I use the below form using &lt;code>format-seconds&lt;/code> to get as
close as to what I want:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-emacs-lisp" data-lang="emacs-lisp">&lt;span class="line">&lt;span class="cl">&lt;span class="p">(&lt;/span>&lt;span class="nf">message&lt;/span> &lt;span class="s">&amp;#34;%9.2f seconds → %s&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">sec&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">replace-regexp-in-string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34; days?&amp;#34;&lt;/span> &lt;span class="s">&amp;#34;d&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">replace-regexp-in-string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34; hours?&amp;#34;&lt;/span> &lt;span class="s">&amp;#34;h&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">replace-regexp-in-string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34; minutes?&amp;#34;&lt;/span> &lt;span class="s">&amp;#34;m&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">replace-regexp-in-string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34; seconds?&amp;#34;&lt;/span> &lt;span class="s">&amp;#34;s&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">format-seconds&lt;/span> &lt;span class="s">&amp;#34;%2D %2H %2M %z%2S&amp;#34;&lt;/span> &lt;span class="nv">sec&lt;/span>&lt;span class="p">))))))&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>I get the output on the &lt;em>left below&lt;/em>. For brevity, I have pasted only
few snippets of the whole test for comparison:&lt;/p>
&lt;p>&lt;a id="code-snippet--seconds-to-human-time-comparison">&lt;/a>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="cl"> 0.00 seconds → 0s 0.00 seconds → 0s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 1.00 seconds → 1s 1.00 seconds → 1s
&lt;/span>&lt;/span>&lt;span class="line hl">&lt;span class="cl"> 60.00 seconds → 1m 0s 60.00 seconds → 1m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 61.00 seconds → 1m 1s 61.00 seconds → 1m 1s
&lt;/span>&lt;/span>&lt;span class="line hl">&lt;span class="cl"> 3600.00 seconds → 1h 0m 0s 3600.00 seconds → 1h
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 3661.00 seconds → 1h 1m 1s 3661.00 seconds → 1h 1m 1s
&lt;/span>&lt;/span>&lt;span class="line hl">&lt;span class="cl"> 86400.00 seconds → 1d 0h 0m 0s 86400.00 seconds → 1d
&lt;/span>&lt;/span>&lt;span class="line hl">&lt;span class="cl"> 86401.00 seconds → 1d 0h 0m 1s 86401.00 seconds → 1d 1s
&lt;/span>&lt;/span>&lt;span class="line hl">&lt;span class="cl"> 86460.00 seconds → 1d 0h 1m 0s 86460.00 seconds → 1d 1m
&lt;/span>&lt;/span>&lt;span class="line hl">&lt;span class="cl"> 86461.00 seconds → 1d 0h 1m 1s 86461.00 seconds → 1d 1m 1s
&lt;/span>&lt;/span>&lt;span class="line hl">&lt;span class="cl"> 90000.00 seconds → 1d 1h 0m 0s 90000.00 seconds → 1d 1h
&lt;/span>&lt;/span>&lt;span class="line hl">&lt;span class="cl"> 90001.00 seconds → 1d 1h 0m 1s 90001.00 seconds → 1d 1h 1s
&lt;/span>&lt;/span>&lt;span class="line hl">&lt;span class="cl"> 90060.00 seconds → 1d 1h 1m 0s 90060.00 seconds → 1d 1h 1m
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 90061.00 seconds → 1d 1h 1m 1s 90061.00 seconds → 1d 1h 1m 1s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># Below the random numbers are different on both sides, but the thing to note is the loss
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"># fractional values (on the left) when seconds are not integers.
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line hl">&lt;span class="cl">288128.50 seconds → 3d 8h 2m 8s 902957.30 seconds → 10d 10h 49m 17.30s
&lt;/span>&lt;/span>&lt;span class="line hl">&lt;span class="cl">989679.28 seconds → 11d 10h 54m 39s 684313.07 seconds → 7d 22h 5m 13.07s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">803137.00 seconds → 9d 7h 5m 37s 347952.39 seconds → 4d 39m 12.39s
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> 39361.00 seconds → 10h 56m 1s 689297.00 seconds → 7d 23h 28m 17s
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="src-block-caption">
&lt;span class="src-block-number">&lt;a href="#code-snippet--seconds-to-human-time-comparison">Code Snippet 3&lt;/a>:&lt;/span>
Using &lt;code>format-seconds&lt;/code> (&lt;i>Left&lt;/i>), using &lt;code>modi/seconds-to-human-time&lt;/code> (&lt;i>Right&lt;/i>)
&lt;/div>
&lt;ul>
&lt;li>Notice the redundant &lt;code>0h&lt;/code>, &lt;code>0m&lt;/code>, &lt;code>0s&lt;/code> on the left, and also the loss
of seconds precision (&lt;em>the latter point is not a big deal though&lt;/em>).&lt;/li>
&lt;/ul>
&lt;h3 id="code--revision-1">Code (Revision 1)&amp;nbsp;&lt;a class="headline-hash no-text-decoration" href="#code--revision-1">#&lt;/a>&lt;/h3>
&lt;p>Here&amp;rsquo;s the code, and notes about that follow after that:&lt;/p>
&lt;p>&lt;a id="code-snippet--seconds-to-human-time-rev-1">&lt;/a>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-emacs-lisp" data-lang="emacs-lisp">&lt;span class="line">&lt;span class="cl">&lt;span class="p">(&lt;/span>&lt;span class="nb">defun&lt;/span> &lt;span class="nv">modi/seconds-to-human-time&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kp">&amp;amp;optional&lt;/span> &lt;span class="nv">seconds&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;Convert SECONDS to \&amp;#34;DDd HHh MMm SSs\&amp;#34; string.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">SECONDS is a non-negative integer or fractional number.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">SECONDS can also be a list of such numbers, which is the case
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">when this function is called recursively.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">When called interactively, if a region is selected SECONDS is
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">extracted from that, else the user is prompted to enter those.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">interactive&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">let&lt;/span> &lt;span class="p">((&lt;/span>&lt;span class="nv">inter&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">called-interactively-p&lt;/span> &lt;span class="ss">&amp;#39;interactive&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">when&lt;/span> &lt;span class="nv">inter&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">let&lt;/span> &lt;span class="p">((&lt;/span>&lt;span class="nv">seconds-str&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">use-region-p&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">buffer-substring-no-properties&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">region-beginning&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">region-end&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">read-string&lt;/span> &lt;span class="s">&amp;#34;Enter seconds: &amp;#34;&lt;/span>&lt;span class="p">))))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">setq&lt;/span> &lt;span class="nv">seconds&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">string-to-number&lt;/span> &lt;span class="nv">seconds-str&lt;/span>&lt;span class="p">))))&lt;/span> &lt;span class="c1">;&amp;#34;1&amp;#34; -&amp;gt; 1, &amp;#34;1.2&amp;#34; -&amp;gt; 1.2, &amp;#34;&amp;#34; -&amp;gt; 0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">let*&lt;/span> &lt;span class="p">((&lt;/span>&lt;span class="nv">MINUTE&lt;/span> &lt;span class="mi">60&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">HOUR&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">*&lt;/span> &lt;span class="mi">60&lt;/span> &lt;span class="nv">MINUTE&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">DAY&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">*&lt;/span> &lt;span class="mi">24&lt;/span> &lt;span class="nv">HOUR&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">sec&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">cond&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">((&lt;/span>&lt;span class="nf">listp&lt;/span> &lt;span class="nv">seconds&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">;This is entered only by recursive calls&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">car&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">last&lt;/span> &lt;span class="nv">seconds&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">((&lt;/span>&lt;span class="nb">and&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">numberp&lt;/span> &lt;span class="nv">seconds&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">;This is entered only in the first entry&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">&amp;gt;=&lt;/span> &lt;span class="nv">seconds&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">seconds&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="no">t&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="ne">user-error&lt;/span> &lt;span class="s">&amp;#34;Invalid argument %S&amp;#34;&lt;/span> &lt;span class="nv">seconds&lt;/span>&lt;span class="p">))))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">gen-time-string&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">lambda&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">time&lt;/span> &lt;span class="nv">inter&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;Return string representation of TIME.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">TIME is of the type (DD HH MM SS), where each of those elements
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">are numbers. If INTER is non-nil, echo the time string in a
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">well-formatted manner instead of returning it.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">let*&lt;/span> &lt;span class="p">((&lt;/span>&lt;span class="nv">rev-time&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">reverse&lt;/span> &lt;span class="nv">time&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">sec&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">nth&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="nv">rev-time&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">min&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">nth&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="nv">rev-time&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">hr&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">nth&lt;/span> &lt;span class="mi">2&lt;/span> &lt;span class="nv">rev-time&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">day&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">nth&lt;/span> &lt;span class="mi">3&lt;/span> &lt;span class="nv">rev-time&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">filler&lt;/span> &lt;span class="s">&amp;#34; &amp;#34;&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">sec-str&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">cond&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">((&lt;/span>&lt;span class="nf">&amp;gt;&lt;/span> &lt;span class="nv">sec&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">integerp&lt;/span> &lt;span class="nv">sec&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">format&lt;/span> &lt;span class="s">&amp;#34;%2ds&amp;#34;&lt;/span> &lt;span class="nv">sec&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">format&lt;/span> &lt;span class="s">&amp;#34;%5.2fs&amp;#34;&lt;/span> &lt;span class="nv">sec&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">((&lt;/span>&lt;span class="nb">and&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">=&lt;/span> &lt;span class="nv">sec&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">null&lt;/span> &lt;span class="nf">min&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">null&lt;/span> &lt;span class="nv">hr&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">null&lt;/span> &lt;span class="nv">day&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="c1">;0 seconds&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34; 0s&amp;#34;&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">min-str&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">and&lt;/span> &lt;span class="nf">min&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">&amp;gt;&lt;/span> &lt;span class="nf">min&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">format&lt;/span> &lt;span class="s">&amp;#34;%2dm &amp;#34;&lt;/span> &lt;span class="nf">min&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">filler&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">hr-str&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">and&lt;/span> &lt;span class="nv">hr&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">&amp;gt;&lt;/span> &lt;span class="nv">hr&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">format&lt;/span> &lt;span class="s">&amp;#34;%2dh &amp;#34;&lt;/span> &lt;span class="nv">hr&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">filler&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">day-str&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">and&lt;/span> &lt;span class="nv">day&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">&amp;gt;&lt;/span> &lt;span class="nv">day&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">format&lt;/span> &lt;span class="s">&amp;#34;%2dd &amp;#34;&lt;/span> &lt;span class="nv">day&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">filler&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">str&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">string-trim-right&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">concat&lt;/span> &lt;span class="nv">day-str&lt;/span> &lt;span class="nv">hr-str&lt;/span> &lt;span class="nv">min-str&lt;/span> &lt;span class="nv">sec-str&lt;/span>&lt;span class="p">))))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">if&lt;/span> &lt;span class="nv">inter&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">message&lt;/span> &lt;span class="s">&amp;#34;%0.2f seconds → %s&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">seconds&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">string-trim&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">replace-regexp-in-string&lt;/span> &lt;span class="s">&amp;#34; +&amp;#34;&lt;/span> &lt;span class="s">&amp;#34; &amp;#34;&lt;/span> &lt;span class="nv">str&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">str&lt;/span>&lt;span class="p">))))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">time&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">cond&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">((&lt;/span>&lt;span class="nf">&amp;gt;=&lt;/span> &lt;span class="nv">sec&lt;/span> &lt;span class="nv">DAY&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">;&amp;gt; day&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">let*&lt;/span> &lt;span class="p">((&lt;/span>&lt;span class="nv">days&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">/&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">floor&lt;/span> &lt;span class="nv">sec&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nv">DAY&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">rem&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">-&lt;/span> &lt;span class="nv">sec&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">*&lt;/span> &lt;span class="nv">days&lt;/span> &lt;span class="nv">DAY&lt;/span>&lt;span class="p">))))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">cond&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">((&lt;/span>&lt;span class="nf">=&lt;/span> &lt;span class="nv">rem&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">list&lt;/span> &lt;span class="nv">days&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">((&lt;/span>&lt;span class="nf">&amp;lt;&lt;/span> &lt;span class="nv">rem&lt;/span> &lt;span class="nv">MINUTE&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">;; Note that (list rem) instead of just `rem&amp;#39; is being&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">;; passed to the recursive call to&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">;; `modi/seconds-to-human-time&amp;#39;. This helps us&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">;; distinguish between direct and re-entrant calls to&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">;; this function.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">append&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">list&lt;/span> &lt;span class="nv">days&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">modi/seconds-to-human-time&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">list&lt;/span> &lt;span class="nv">rem&lt;/span>&lt;span class="p">))))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">((&lt;/span>&lt;span class="nf">&amp;lt;&lt;/span> &lt;span class="nv">rem&lt;/span> &lt;span class="nv">HOUR&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">append&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">list&lt;/span> &lt;span class="nv">days&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">modi/seconds-to-human-time&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">list&lt;/span> &lt;span class="nv">rem&lt;/span>&lt;span class="p">))))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="no">t&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">append&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">list&lt;/span> &lt;span class="nv">days&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">modi/seconds-to-human-time&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">list&lt;/span> &lt;span class="nv">rem&lt;/span>&lt;span class="p">)))))))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">((&lt;/span>&lt;span class="nf">&amp;gt;=&lt;/span> &lt;span class="nv">sec&lt;/span> &lt;span class="nv">HOUR&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">;&amp;gt; hour AND &amp;lt; day&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">let*&lt;/span> &lt;span class="p">((&lt;/span>&lt;span class="nv">hours&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">/&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">floor&lt;/span> &lt;span class="nv">sec&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nv">HOUR&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">rem&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">-&lt;/span> &lt;span class="nv">sec&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">*&lt;/span> &lt;span class="nv">hours&lt;/span> &lt;span class="nv">HOUR&lt;/span>&lt;span class="p">))))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">cond&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">((&lt;/span>&lt;span class="nf">=&lt;/span> &lt;span class="nv">rem&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">list&lt;/span> &lt;span class="nv">hours&lt;/span> &lt;span class="mi">0&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">((&lt;/span>&lt;span class="nf">&amp;lt;&lt;/span> &lt;span class="nv">rem&lt;/span> &lt;span class="nv">MINUTE&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">append&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">list&lt;/span> &lt;span class="nv">hours&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">modi/seconds-to-human-time&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">list&lt;/span> &lt;span class="nv">rem&lt;/span>&lt;span class="p">))))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="no">t&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">append&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">list&lt;/span> &lt;span class="nv">hours&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">modi/seconds-to-human-time&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">list&lt;/span> &lt;span class="nv">rem&lt;/span>&lt;span class="p">)))))))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">((&lt;/span>&lt;span class="nf">&amp;gt;=&lt;/span> &lt;span class="nv">sec&lt;/span> &lt;span class="nv">MINUTE&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="c1">;&amp;gt; minute AND &amp;lt; hour&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">let*&lt;/span> &lt;span class="p">((&lt;/span>&lt;span class="nv">mins&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">/&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">floor&lt;/span> &lt;span class="nv">sec&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="nv">MINUTE&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">rem&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">-&lt;/span> &lt;span class="nv">sec&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">*&lt;/span> &lt;span class="nv">mins&lt;/span> &lt;span class="nv">MINUTE&lt;/span>&lt;span class="p">))))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">cond&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">((&lt;/span>&lt;span class="nf">=&lt;/span> &lt;span class="nv">rem&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">list&lt;/span> &lt;span class="nv">mins&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="no">t&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">append&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">list&lt;/span> &lt;span class="nv">mins&lt;/span>&lt;span class="p">)&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">modi/seconds-to-human-time&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">list&lt;/span> &lt;span class="nv">rem&lt;/span>&lt;span class="p">)))))))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="no">t&lt;/span> &lt;span class="c1">;&amp;lt; minute&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">list&lt;/span> &lt;span class="nv">sec&lt;/span>&lt;span class="p">)))))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">;; If `seconds&amp;#39; is a number and not a list, this is *not* a recursive&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">;; call. Return the time as a string only then. For re-entrant&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">;; executions, return the `time&amp;#39; list instead.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">numberp&lt;/span> &lt;span class="nv">seconds&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">funcall&lt;/span> &lt;span class="nv">gen-time-string&lt;/span> &lt;span class="nv">time&lt;/span> &lt;span class="nv">inter&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">time&lt;/span>&lt;span class="p">))))&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="src-block-caption">
&lt;span class="src-block-number">&lt;a href="#code-snippet--seconds-to-human-time-rev-1">Code Snippet 4&lt;/a>:&lt;/span>
Seconds to Human Time (Revision 1)
&lt;/div>
&lt;p>Most of this snippet is just the day/hour/minute/second math. Apart
from that, here are some points that I found of interest:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>I did not always want to prompt the user to enter the input
argument. If a region was selected, the function assumes that the
user selected a number, and skips the prompt step. So I used a plain
&lt;code>(interactive)&lt;/code> form instead of using &lt;code>(interactive &amp;quot;sPrompt: &amp;quot;)&lt;/code> or
&lt;code>(interactive &amp;quot;r&amp;quot;)&lt;/code>. See &lt;a href="https://www.gnu.org/software/emacs/manual/html_node/eintr/Interactive-Options.html">&lt;em>(eintr) Interactive Options&lt;/em>&lt;/a> and &lt;a href="https://www.gnu.org/software/emacs/manual/html_node/elisp/Interactive-Codes.html">&lt;em>(elisp)
Interactive Codes&lt;/em>&lt;/a> to learn about &lt;code>interactive&lt;/code> and its codes.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Instead of in-lining a modular chunk of logic, like the one where I
convert a list like &lt;code>(1 2 3 4)&lt;/code> into &lt;code>&amp;quot;1d 2h 3m 4s&amp;quot;&lt;/code>, I assigned it
to a &lt;em>let&lt;/em>-bound symbol &lt;code>gen-time-string&lt;/code>. That allowed the logic to
be more discernible when used in:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-emacs-lisp" data-lang="emacs-lisp">&lt;span class="line">&lt;span class="cl">&lt;span class="p">(&lt;/span>&lt;span class="nb">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">numberp&lt;/span> &lt;span class="nv">seconds&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">funcall&lt;/span> &lt;span class="nv">gen-time-string&lt;/span> &lt;span class="nv">time&lt;/span> &lt;span class="nv">inter&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">time&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>Also interesting is the fact that these &lt;em>let&lt;/em>-bound lambdas can
have their own doc-strings too.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>I make use of &lt;strong>recursion&lt;/strong> in this function! But I needed this
function to return a string (using that &lt;code>gen-time-string&lt;/code> function)
only when all the nested calls to itself were returned. So to
distinguish between a direct call to the function, and re-entrant
calls, when doing the latter, I make the input number a &lt;em>list of
that number&lt;/em>.&lt;/p>
&lt;ul>
&lt;li>So while the function might take an input number like &lt;code>7&lt;/code> for a
direct call, that same number, when needed to call to a recursive
call, would get passed as &lt;code>(list 7)&lt;/code> or &lt;code>'(7)&lt;/code>.&lt;/li>
&lt;li>If you glance back as that little snippet above, I return the
&lt;code>time&lt;/code> as a string only if the input &lt;code>seconds&lt;/code> is a &lt;em>number&lt;/em> &amp;mdash; and
not a list i.e. only when I am in the &amp;ldquo;direct call instance&amp;rdquo;.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>
&lt;p>The internal variable &lt;code>time&lt;/code> is a list and can have up to 4 number
elements: &lt;code>(DAYS HOURS MINUTES SECONDS)&lt;/code>. The key was to always have
each of those elements at their respective positions in the list.&lt;/p>
&lt;p>If the input &lt;code>seconds&lt;/code> is 7200 seconds i.e. 2 hours, I cannot allow
&lt;code>time&lt;/code> to be just &lt;code>(2)&lt;/code>, because then I wouldn&amp;rsquo;t know the unit of
that &lt;code>2&lt;/code> (2 days? 2 hours? ..) &amp;mdash; The nested &lt;code>cond&lt;/code> logic for setting
the &lt;code>time&lt;/code> variable ensures it gets set to &lt;code>(2 0 0)&lt;/code> instead. That
way, it would read clearly as &lt;em>2 hours, 0 minutes, and 0 seconds&lt;/em>.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Back inside &lt;code>gen-time-string&lt;/code>, I then skip printing the time units
that are 0 (unless everything is 0, in which case I print
&lt;code>&amp;quot;0s&amp;quot;&lt;/code>). So instead of printing &lt;code>&amp;quot;1d 0h 0m 5s&amp;quot;&lt;/code>, it would print just
&lt;code>&amp;quot;1d 5s&amp;quot;&lt;/code>.&lt;/p>
&lt;/li>
&lt;/ul></description><author>Kaushal.Modi@fakeEmailToMakeValidatorHappy.com (Kaushal Modi)</author><category domain="https://scripter.co/categories/emacs">emacs</category><category domain="https://scripter.co/categories/elisp">elisp</category><category domain="https://scripter.co/tags/time">time</category><category domain="https://scripter.co/tags/seconds">seconds</category><category domain="https://scripter.co/tags/recursion">recursion</category><guid>https://scripter.co/convert-seconds-to-human-time/</guid><pubDate>Wed, 07 Feb 2018 18:10:00 -0500</pubDate></item><item><title>Converting Org keywords to lower-case</title><link>https://scripter.co/org-keywords-lower-case/</link><description>&lt;p>I never quite liked the trend to have upper-cased keywords in Org
documents, like &lt;code>#+TITLE&lt;/code>.&lt;/p>
&lt;p>So it was a pleasure to see that trend start changing in this &lt;a href="https://code.orgmode.org/bzg/org-mode/commit/13424336a6f30c50952d291e7a82906c1210daf0">Org
commit&lt;/a>.. so that that same keyword would now be written as &lt;code>#+title&lt;/code>.&lt;/p>
&lt;p>But now I have quite a few Org documents with the ALL-CAPS keywords
and block identifiers. So I came up with this little &amp;ldquo;lower-case all
the Org keywords and block identifiers in the current document&amp;rdquo; Elisp
command:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-emacs-lisp" data-lang="emacs-lisp">&lt;span class="line">&lt;span class="cl">&lt;span class="p">(&lt;/span>&lt;span class="nb">defun&lt;/span> &lt;span class="nv">modi/lower-case-org-keywords&lt;/span> &lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;Lower case Org keywords and block identifiers.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">Example: \&amp;#34;#+TITLE\&amp;#34; -&amp;gt; \&amp;#34;#+title\&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> \&amp;#34;#+BEGIN_EXAMPLE\&amp;#34; -&amp;gt; \&amp;#34;#+begin_example\&amp;#34;
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">Inspiration:
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">https://code.orgmode.org/bzg/org-mode/commit/13424336a6f30c50952d291e7a82906c1210daf0.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">interactive&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">save-excursion&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">goto-char&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">point-min&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">let&lt;/span> &lt;span class="p">((&lt;/span>&lt;span class="nv">case-fold-search&lt;/span> &lt;span class="no">nil&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">count&lt;/span> &lt;span class="mi">0&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">;; Match examples: &amp;#34;#+FOO bar&amp;#34;, &amp;#34;#+FOO:&amp;#34;, &amp;#34;=#+FOO=&amp;#34;, &amp;#34;~#+FOO~&amp;#34;,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">;; &amp;#34;‘#+FOO’&amp;#34;, &amp;#34;“#+FOO”&amp;#34;, &amp;#34;,#+FOO bar&amp;#34;,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">;; &amp;#34;#+FOO_bar&amp;lt;eol&amp;gt;&amp;#34;, &amp;#34;#+FOO&amp;lt;eol&amp;gt;&amp;#34;.&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">while&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">re-search-forward&lt;/span> &lt;span class="s">&amp;#34;\\(?1:#\\+[A-Z_]+\\(?:_[[:alpha:]]+\\)*\\)\\(?:[ :=~’”]\\|$\\)&amp;#34;&lt;/span> &lt;span class="no">nil&lt;/span> &lt;span class="nb">:noerror&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">setq&lt;/span> &lt;span class="nv">count&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">1+&lt;/span> &lt;span class="nv">count&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">replace-match&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">downcase&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">match-string-no-properties&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="nb">:fixedcase&lt;/span> &lt;span class="no">nil&lt;/span> &lt;span class="no">nil&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">message&lt;/span> &lt;span class="s">&amp;#34;Lower-cased %d matches&amp;#34;&lt;/span> &lt;span class="nv">count&lt;/span>&lt;span class="p">))))&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Here are &lt;a href="https://github.com/kaushalmodi/ox-hugo/commit/ad1b513c0847383d19bf37becce6413697d14bd0">few&lt;/a> &lt;a href="https://github.com/kaushalmodi/eless/commit/f2eee31be46f6f296541d840f74fdd7dc1f5acd2">examples&lt;/a> where &lt;kbd>M-x&lt;/kbd> &lt;code>modi/lower-case-org-keywords&lt;/code> did
hundreds of replacements for me, saving me a &lt;strong>lot&lt;/strong> of time
😎.&lt;/p>
&lt;p>&lt;a href="https://github.com/kaushalmodi/.emacs.d/blob/56a2d3ad42e6640ddae46b5afd3f93044b6d5172/setup-files/setup-org.el#L267-L286">Source&lt;/a>&lt;/p></description><author>Kaushal.Modi@fakeEmailToMakeValidatorHappy.com (Kaushal Modi)</author><category domain="https://scripter.co/categories/emacs">emacs</category><category domain="https://scripter.co/categories/org">org</category><category domain="https://scripter.co/categories/elisp">elisp</category><guid>https://scripter.co/org-keywords-lower-case/</guid><pubDate>Fri, 02 Feb 2018 13:42:00 -0500</pubDate></item><item><title>Narrowing the Author column in Magit</title><link>https://scripter.co/narrowing-the-author-column-in-magit/</link><description>&lt;p>The Org and &lt;a href="https://orgmode.org/worg/">Worg&lt;/a> git repos can be committed to only via &lt;em>ssh&lt;/em> protocol
(and not &lt;em>https&lt;/em>), and for technical reasons, I cannot commit to those
repos via &lt;em>ssh&lt;/em> from my primary development machine. So I ended up
with a flow that involves pushing commits to those repos using my
Nexus 6p phone.&lt;/p>
&lt;p>&lt;a href="https://magit.vc/">Magit&lt;/a> is how I &lt;em>git&lt;/em>, and I love to primarily work from the
&lt;em>∗magit-log∗&lt;/em> buffer (&lt;code>M-x magit-status&lt;/code>, &lt;kbd>l&lt;/kbd> &lt;kbd>b&lt;/kbd>). But the default
column widths were not optimal on a Nexus 6p 5.7&amp;quot; screen. And that&amp;rsquo;s
what inspired this tweak, which looks great on a regular desktop
monitor too.&lt;/p>
&lt;p>Before I jump to the code, you can see in the below figure how the
author and commit age columns took up roughly half the screen width on
my phone before the tweak (left), and how much easier it is to read
the commit messages after the tweak (right).&lt;/p>
&lt;figure>
&lt;a href="https://scripter.co/images/magit_author_column.png">
&lt;img src="https://scripter.co/images/magit_author_column.png" alt="Figure 1: Org-mode Worg commit log : Before (Left), After (Right)"/> &lt;/a>&lt;figcaption>
&lt;p>
&lt;span class="figure-number">Figure 1: &lt;/span>Org-mode Worg commit log : Before (Left), After (Right)
&lt;/p>
&lt;/figcaption>&lt;/figure>
&lt;p>So the Magit Log columns tweaks are basically:&lt;/p>
&lt;ol>
&lt;li>Abbreviate the authors&amp;rsquo; first names to just their initials.&lt;/li>
&lt;li>Abbreviate the commit ages to 1-character time units.&lt;/li>
&lt;/ol>
&lt;p>To implement those tweaks, I started digging through &lt;code>magit-log.el&lt;/code>
and found that I would need to customize (i) the &lt;code>magit-log-margin&lt;/code>
variable and (ii) &lt;code>magit-log-format-margin&lt;/code> function.&lt;/p>
&lt;ul>
&lt;li>Customizing &lt;code>magit-log-margin&lt;/code> variable allows me to use the
abbreviated age instead of the verbose age strings (minute→m,
hour→h, day→d, week→w, month→M, year→Y). It&amp;rsquo;s also where I specify
how wide I want the author column to be.. I can now reduce that
column width from the default value of 18 to 11 as I am abbreviating
the author name too.&lt;/li>
&lt;li>Reviewing the &lt;code>magit-log.el&lt;/code> code, I realized that I also need to
advise the &lt;code>magit-log-format-margin&lt;/code> function to implement the
author name abbreviation.&lt;/li>
&lt;/ul>
&lt;p>In the below code snippet, I am using &lt;a href="https://github.com/jwiegley/use-package">&lt;code>use-package&lt;/code>&lt;/a>&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup> to customize
that variable and function in &lt;code>magit-log&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-emacs-lisp" data-lang="emacs-lisp">&lt;span class="line">&lt;span class="cl">&lt;span class="p">(&lt;/span>&lt;span class="nb">use-package&lt;/span> &lt;span class="nv">magit-log&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">:init&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">progn&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">;; (setq magit-log-margin &amp;#39;(t age magit-log-margin-width t 18)) ;Default value&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">setq&lt;/span> &lt;span class="nv">magit-log-margin&lt;/span> &lt;span class="o">&amp;#39;&lt;/span>&lt;span class="p">(&lt;/span>&lt;span class="no">t&lt;/span> &lt;span class="nv">age-abbreviated&lt;/span> &lt;span class="nv">magit-log-margin-width&lt;/span> &lt;span class="nb">:author&lt;/span> &lt;span class="mi">11&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nb">:config&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">progn&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">;; Abbreviate author name. I added this so that I can view Magit log without&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">;; too much commit message truncation even on narrow screens (like on phone).&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">defun&lt;/span> &lt;span class="nv">modi/magit-log--abbreviate-author&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="kp">&amp;amp;rest&lt;/span> &lt;span class="nv">args&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;The first arg is AUTHOR, abbreviate it.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">First Last -&amp;gt; F Last
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">First.Last -&amp;gt; F Last
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">Last, First -&amp;gt; F Last
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">First -&amp;gt; First (no change).
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">It is assumed that the author has only one or two names.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">;; ARGS -&amp;gt; &amp;#39;((REV AUTHOR DATE))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">;; (car ARGS) -&amp;gt; &amp;#39;(REV AUTHOR DATE)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">;; (nth 1 (car ARGS)) -&amp;gt; AUTHOR&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">let*&lt;/span> &lt;span class="p">((&lt;/span>&lt;span class="nv">author&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">nth&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">car&lt;/span> &lt;span class="nv">args&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">author-abbr&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">if&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nv">string-match-p&lt;/span> &lt;span class="s">&amp;#34;,&amp;#34;&lt;/span> &lt;span class="nv">author&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">;; Last, First -&amp;gt; F Last&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">replace-regexp-in-string&lt;/span> &lt;span class="s">&amp;#34;\\(.*?\\), *\\(.\\).*&amp;#34;&lt;/span> &lt;span class="s">&amp;#34;\\2 \\1&amp;#34;&lt;/span> &lt;span class="nv">author&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">;; First Last -&amp;gt; F Last&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">replace-regexp-in-string&lt;/span> &lt;span class="s">&amp;#34;\\(.\\).*?[. ]+\\(.*\\)&amp;#34;&lt;/span> &lt;span class="s">&amp;#34;\\1 \\2&amp;#34;&lt;/span> &lt;span class="nv">author&lt;/span>&lt;span class="p">))))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">setf&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">nth&lt;/span> &lt;span class="mi">1&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">car&lt;/span> &lt;span class="nv">args&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="nv">author-abbr&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">car&lt;/span> &lt;span class="nv">args&lt;/span>&lt;span class="p">))&lt;/span> &lt;span class="c1">;&amp;#39;(REV AUTHOR-ABBR DATE)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">advice-add&lt;/span> &lt;span class="ss">&amp;#39;magit-log-format-margin&lt;/span> &lt;span class="nb">:filter-args&lt;/span> &lt;span class="nf">#&amp;#39;&lt;/span>&lt;span class="nv">modi/magit-log--abbreviate-author&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>NOTES&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>I need to set the &lt;code>magit-log-margin&lt;/code> value in the &lt;code>:init&lt;/code> block
because that variable dynamically sets many other variables in
&lt;code>magit-log&lt;/code> at the time of loading. So we cannot wait for the whole
&lt;code>magit-log&lt;/code> to load before setting that variable. I wouldn&amp;rsquo;t need to
worry about this if I were using the Emacs &lt;em>Customize&lt;/em> interface to
set this variable.&lt;/li>
&lt;li>I use &lt;code>modi/magit-log--abbreviate-author&lt;/code> to add a &lt;code>:filter-args&lt;/code>
advice to &lt;code>magit-log-format-margin&lt;/code>. The signature of the advised
function is &lt;code>(magit-log-format-margin REV AUTHOR DATE)&lt;/code> (as of
&lt;em>Magit 20180809.1716&lt;/em>). So the advice basically replaces the
&lt;code>AUTHOR&lt;/code> arg with its abbreviated form as explained in the
&lt;code>modi/magit-log--abbreviate-author&lt;/code> doc-string.&lt;/li>
&lt;/ul>
&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>Feel free to ask for more explanation in comments in the case
you don&amp;rsquo;t use &lt;code>use-package&lt;/code>.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description><author>Kaushal.Modi@fakeEmailToMakeValidatorHappy.com (Kaushal Modi)</author><category domain="https://scripter.co/categories/emacs">emacs</category><category domain="https://scripter.co/categories/elisp">elisp</category><category domain="https://scripter.co/tags/magit">magit</category><category domain="https://scripter.co/tags/git">git</category><category domain="https://scripter.co/tags/org">org</category><category domain="https://scripter.co/tags/log">log</category><guid>https://scripter.co/narrowing-the-author-column-in-magit/</guid><pubDate>Mon, 18 Dec 2017 16:36:00 -0500</pubDate></item><item><title>Do Ediff as I mean</title><link>https://scripter.co/do-ediff-as-i-mean/</link><description>&lt;p>In almost all of my &lt;code>ediff&lt;/code> use cases, I would have windows open
side-by-side in a frame, and then I would want to do a diff between
the two using &lt;code>ediff-buffers&lt;/code>.&lt;/p>
&lt;p>But emacs doesn&amp;rsquo;t know that I obviously want to diff those two
side-by-side buffers! So it always asks me to select the buffers to be
diffed. The same problem is when using &lt;code>ediff-files&lt;/code> too.&lt;/p>
&lt;p>So I came up with the following helper function to pick the correct
&lt;code>ediff&lt;/code> command.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-emacs-lisp" data-lang="emacs-lisp">&lt;span class="line">&lt;span class="cl">&lt;span class="p">(&lt;/span>&lt;span class="nb">defun&lt;/span> &lt;span class="nv">modi/ediff-dwim&lt;/span> &lt;span class="p">()&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="s">&amp;#34;Do ediff as I mean.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">If a region is active, call &lt;/span>&lt;span class="ss">`ediff-regions-wordwise&amp;#39;&lt;/span>&lt;span class="s">.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">Else if the frame has 2 windows with identical major modes,
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> - Do &lt;/span>&lt;span class="ss">`ediff-files&amp;#39;&lt;/span>&lt;span class="s"> if the buffers are associated to files and the buffers
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> have not been modified.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s"> - Do &lt;/span>&lt;span class="ss">`ediff-buffers&amp;#39;&lt;/span>&lt;span class="s"> otherwise.
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">Else if the current is a file buffer with a VC backend, call &lt;/span>&lt;span class="ss">`vc-ediff&amp;#39;&lt;/span>&lt;span class="s">
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="s">Else call &lt;/span>&lt;span class="ss">`ediff-buffers&amp;#39;&lt;/span>&lt;span class="s">.&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">interactive&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">let*&lt;/span> &lt;span class="p">((&lt;/span>&lt;span class="nv">num-win&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">safe-length&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">window-list&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">bufa&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">get-buffer&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">buffer-name&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">filea&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">buffer-file-name&lt;/span> &lt;span class="nv">bufa&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">modea&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">with-current-buffer&lt;/span> &lt;span class="nv">bufa&lt;/span> &lt;span class="nv">major-mode&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="nv">bufb&lt;/span> &lt;span class="nv">fileb&lt;/span> &lt;span class="nv">modeb&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">save-excursion&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">other-window&lt;/span> &lt;span class="mi">1&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">setq&lt;/span> &lt;span class="nv">bufb&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">get-buffer&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">buffer-name&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">setq&lt;/span> &lt;span class="nv">fileb&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">buffer-file-name&lt;/span> &lt;span class="nv">bufb&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">setq&lt;/span> &lt;span class="nv">modeb&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nb">with-current-buffer&lt;/span> &lt;span class="nv">bufb&lt;/span> &lt;span class="nv">major-mode&lt;/span>&lt;span class="p">)))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">cond&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">;; If a region is selected&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">((&lt;/span>&lt;span class="nv">region-active-p&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">call-interactively&lt;/span> &lt;span class="nf">#&amp;#39;&lt;/span>&lt;span class="nv">ediff-regions-wordwise&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">;; Else if 2 windows with same major modes&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">((&lt;/span>&lt;span class="nb">and&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">=&lt;/span> &lt;span class="mi">2&lt;/span> &lt;span class="nv">num-win&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">eq&lt;/span> &lt;span class="nv">modea&lt;/span> &lt;span class="nv">modeb&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">if&lt;/span> &lt;span class="c1">;; If either of the buffers is not associated to a file,&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">;; or if either of the buffers is modified&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">or&lt;/span> &lt;span class="p">(&lt;/span>&lt;span class="nf">null&lt;/span> &lt;span class="nv">filea&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">null&lt;/span> &lt;span class="nv">fileb&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">buffer-modified-p&lt;/span> &lt;span class="nv">bufa&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">buffer-modified-p&lt;/span> &lt;span class="nv">bufb&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">progn&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">message&lt;/span> &lt;span class="s">&amp;#34;Running (ediff-buffers \&amp;#34;%s\&amp;#34; \&amp;#34;%s\&amp;#34;) ..&amp;#34;&lt;/span> &lt;span class="nv">bufa&lt;/span> &lt;span class="nv">bufb&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">ediff-buffers&lt;/span> &lt;span class="nv">bufa&lt;/span> &lt;span class="nv">bufb&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nb">progn&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">message&lt;/span> &lt;span class="s">&amp;#34;Running (ediff-files \&amp;#34;%s\&amp;#34; \&amp;#34;%s\&amp;#34;) ..&amp;#34;&lt;/span> &lt;span class="nv">filea&lt;/span> &lt;span class="nv">fileb&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">ediff-files&lt;/span> &lt;span class="nv">filea&lt;/span> &lt;span class="nv">fileb&lt;/span>&lt;span class="p">))))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">;; Else if file in current buffer has a vc backend&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">((&lt;/span>&lt;span class="nb">and&lt;/span> &lt;span class="nv">filea&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nv">vc-registered&lt;/span> &lt;span class="nv">filea&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">call-interactively&lt;/span> &lt;span class="nf">#&amp;#39;&lt;/span>&lt;span class="nv">vc-ediff&lt;/span>&lt;span class="p">))&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="c1">;; Else call `ediff-buffers&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="no">t&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl"> &lt;span class="p">(&lt;/span>&lt;span class="nf">call-interactively&lt;/span> &lt;span class="nf">#&amp;#39;&lt;/span>&lt;span class="nv">ediff-buffers&lt;/span>&lt;span class="p">)))))&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;a href="https://github.com/search?utf8=%E2%9C%93&amp;amp;q=user:kaushalmodi+extension:el++%22defun+modi/ediff-dwim%22&amp;amp;type=Code">Find this function in my emacs config&lt;/a>.&lt;/p>
&lt;hr>
&lt;p>My favorite &lt;code>ediff&lt;/code> settings are:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-emacs-lisp" data-lang="emacs-lisp">&lt;span class="line">&lt;span class="cl">&lt;span class="c1">;; No separate frame for ediff control buffer&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">(&lt;/span>&lt;span class="nb">setq&lt;/span> &lt;span class="nv">ediff-window-setup-function&lt;/span> &lt;span class="nf">#&amp;#39;&lt;/span>&lt;span class="nv">ediff-setup-windows-plain&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="c1">;; Split windows horizontally in ediff (instead of vertically)&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="cl">&lt;span class="p">(&lt;/span>&lt;span class="nb">setq&lt;/span> &lt;span class="nv">ediff-split-window-function&lt;/span> &lt;span class="nf">#&amp;#39;&lt;/span>&lt;span class="nv">split-window-horizontally&lt;/span>&lt;span class="p">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description><author>Kaushal.Modi@fakeEmailToMakeValidatorHappy.com (Kaushal Modi)</author><category domain="https://scripter.co/categories/emacs">emacs</category><category domain="https://scripter.co/categories/elisp">elisp</category><category domain="https://scripter.co/tags/ediff">ediff</category><guid>https://scripter.co/do-ediff-as-i-mean/</guid><pubDate>Mon, 09 Mar 2015 17:31:45 -0400</pubDate></item></channel></rss>