<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/assets/rss.xsl" type="text/xsl"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
>
<channel>
	<title>Sacha Chua - category - coding</title>
	<atom:link href="https://sachachua.com/blog/category/coding/feed/index.xml" rel="self" type="application/rss+xml" />
	<atom:link href="https://sachachua.com/blog/category/coding" rel="alternate" type="text/html" />
	<link>https://sachachua.com/blog/category/coding/feed/index.xml</link>
	<description>Emacs, sketches, and life</description>
	<lastBuildDate>Sat, 04 Apr 2026 02:23:03 GMT</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>daily</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>11ty</generator>
  <item>
		<title>Using an Emacs Lisp macro to define quick custom Org Mode links to project files; plus URLs and search</title>
		<link>https://sachachua.com/blog/2024/01/using-an-emacs-lisp-macro-to-define-quick-custom-org-mode-links-to-project-files/</link>
		<dc:creator><![CDATA[Sacha Chua]]></dc:creator>
		<pubDate>Sun, 07 Jan 2024 13:07:09 GMT</pubDate>
    <category>org</category>
<category>emacs</category>
<category>coding</category>
<category>embark</category>
		<guid isPermaLink="false">https://sachachua.com/blog/2024/01/using-an-emacs-lisp-macro-to-define-quick-custom-org-mode-links-to-project-files/</guid>
		<description><![CDATA[<div class="update" id="orgb05ba05">
<ul class="org-ul">
<li><span class="timestamp-wrapper"><span class="timestamp">[2025-03-27 Thu]</span></span>: Use my-org-project- as the prefix to avoid collisions.</li>
<li><span class="timestamp-wrapper"><span class="timestamp">[2024-09-19 Thu]</span></span>: Added function for replacing current link, bound to <code>C-. r</code> (<code>my-embark-replace-link-with-exported-url</code>)</li>
<li><span class="timestamp-wrapper"><span class="timestamp">[2024-01-12 Fri] </span></span> Added embark action to copy the exported link URL.</li>
<li><span class="timestamp-wrapper"><span class="timestamp">[2024-01-11 Thu] </span></span> Switched to using Github links since Codeberg's down.</li>
<li><span class="timestamp-wrapper"><span class="timestamp">[2024-01-11 Thu] </span></span> Updated my-copy-link to just return the link if called from Emacs Lisp. Fix getting the properties.</li>
<li><span class="timestamp-wrapper"><span class="timestamp">[2024-01-08 Mon] </span></span> Add tip from Omar about <code>embark-around-action-hooks</code></li>
<li><span class="timestamp-wrapper"><span class="timestamp">[2024-01-08 Mon] </span></span> Simplify code by using <code>consult&#45;&#45;grep-position</code></li>
</ul>

</div>

<summary id="orgb856eb0">
<p>
Summary (882 words): Emacs macros make it easy to define sets of related functions for custom Org links. This makes it easier to link to projects and export or copy the links to the files in the web-based repos. You can also use that information to consult-ripgrep across lots of projects.
</p>
</summary>

<p>
I'd like to get better at writing notes while coding and at turning
those notes into blog posts and videos. I want to be able to link to
files in projects easily with the ability to complete, follow, and
export links. For example, <code>[[subed:subed.el]]</code> should become
<a href="https://github.com/sachac/subed/blob/main/subed/subed.el">subed.el</a>, which opens the file if I'm in Emacs and exports a
link if I'm publishing a post. I've been making custom link types
using <code>org-link-set-parameters</code>. I think it's time to make a macro
that defines that set of functions for me. Emacs Lisp macros are a
great way to write code to write code.
</p>


<div class="org-src-container">
<pre class="src src-emacs-lisp" id="org995be3e">(<span class="org-keyword">defvar</span> <span class="org-variable-name">my-project-web-base-list</span> nil <span class="org-doc">"Local path . web repo URLs for easy linking."</span>)

(<span class="org-keyword">defmacro</span> <span class="org-function-name">my-org-project-link</span> (type file-path git-url)
  <span class="org-highlight-quoted-quote">`</span>(<span class="org-keyword">progn</span>
     (<span class="org-keyword">defun</span> ,(intern (format <span class="org-string">"my-org-project-%s-complete"</span> type)) ()
       ,(format <span class="org-string">"Complete a file from %s."</span> type)
       (concat ,type <span class="org-string">":"</span> (completing-read <span class="org-string">"File: "</span>
                                          (projectile-project-files ,file-path))))
     (<span class="org-keyword">defun</span> ,(intern (format <span class="org-string">"my-org-project-%s-follow"</span> type)) (link _)
       ,(format <span class="org-string">"Open a file from %s."</span> type)
       (find-file
        (expand-file-name
         link
         ,file-path)))
     (<span class="org-keyword">defun</span> ,(intern (format <span class="org-string">"my-org-project-%s-export"</span> type)) (link desc format _)
       <span class="org-doc">"Export link to file."</span>
       (<span class="org-keyword">setq</span> desc (<span class="org-keyword">or</span> desc link))
       (<span class="org-keyword">when</span> (<span class="org-keyword">and</span> ,git-url link)
         (<span class="org-keyword">setq</span> link (concat ,git-url (replace-regexp-in-string <span class="org-string">"^/"</span> <span class="org-string">""</span> link))))
       (<span class="org-keyword">pcase</span> format
         ((<span class="org-keyword">or</span> <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">html</span> <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">11ty</span>) (format <span class="org-string">"&lt;a href=\"%s\"&gt;%s&lt;/a&gt;"</span>
                                   link
                                   (<span class="org-keyword">or</span> desc link)))
         (<span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">md</span> (<span class="org-keyword">if</span> desc (format <span class="org-string">"[%s](%s)"</span> desc link)
                (format <span class="org-string">"&lt;%s&gt;"</span> link)))
         (<span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">latex</span> (format <span class="org-string">"\\href{%s}{%s}"</span> link desc))
         (<span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">texinfo</span> (format <span class="org-string">"@uref{%s,%s}"</span> link desc))
         (<span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">ascii</span> (format <span class="org-string">"%s (%s)"</span> desc link))
         (_ (format <span class="org-string">"%s (%s)"</span> desc link))))
     (org-link-set-parameters
        ,type
        <span class="org-builtin">:complete</span> (<span class="org-keyword">quote</span> ,(intern (format <span class="org-string">"my-org-project-%s-complete"</span> type)))
        <span class="org-builtin">:export</span> (<span class="org-keyword">quote</span> ,(intern (format <span class="org-string">"my-org-project-%s-export"</span> type)))
        <span class="org-builtin">:follow</span> (<span class="org-keyword">quote</span> ,(intern (format <span class="org-string">"my-org-project-%s-follow"</span> type))))
     (<span class="org-keyword">cl-pushnew</span> (cons (expand-file-name ,file-path) ,git-url)
                 my-project-web-base-list
                 <span class="org-builtin">:test</span> <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">equal</span>)))
</pre>
</div>


<p>
Then I can define projects this way:
</p>


<div class="org-src-container">
<pre class="src src-emacs-lisp">(<span class="org-keyword">with-eval-after-load</span> <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">org</span>
(<span class="org-keyword">my-org-project-link</span> <span class="org-string">"subed"</span>
                     <span class="org-string">"~/proj/subed/subed/"</span>
                     <span class="org-string">"https://github.com/sachac/subed/blob/main/subed/"</span>
                     <span class="org-comment-delimiter">;; </span><span class="org-comment">"https://codeberg.org/sachac/subed/src/branch/main/subed/"</span>
                     )
(<span class="org-keyword">my-org-project-link</span> <span class="org-string">"emacsconf-el"</span>
                     <span class="org-string">"~/proj/emacsconf/lisp/"</span>
                     <span class="org-string">"https://git.emacsconf.org/emacsconf-el/tree/"</span>)
(<span class="org-keyword">my-org-project-link</span> <span class="org-string">"subed-record"</span>
                     <span class="org-string">"~/proj/subed-record/"</span>
                     <span class="org-string">"https://github.com/sachac/subed-record/blob/main/"</span>
                     <span class="org-comment-delimiter">;; </span><span class="org-comment">"https://codeberg.org/sachac/subed-record/src/branch/main/"</span>
                     )
(<span class="org-keyword">my-org-project-link</span> <span class="org-string">"compile-media"</span>
                     <span class="org-string">"~/proj/compile-media/"</span>
                     <span class="org-string">"https://github.com/sachac/compile-media/blob/main/"</span>
                     <span class="org-comment-delimiter">;; </span><span class="org-comment">"https://codeberg.org/sachac/compile-media/src/branch/main/"</span>
                     )
(<span class="org-keyword">my-org-project-link</span> <span class="org-string">"ox-11ty"</span>
                     <span class="org-string">"~/proj/ox-11ty/"</span>
                     <span class="org-string">"https://github.com/sachac/ox-11ty/blob/master/"</span>)
(<span class="org-keyword">my-org-project-link</span> <span class="org-string">"11ty"</span>
                     <span class="org-string">"~/proj/static-blog/"</span>
                     <span class="org-string">"https://github.com/sachac/eleventy-blog-setup/blob/master/"</span>))
</pre>
</div>


<p>
And I can complete them with the usual <code>C-c C-l</code> (<code>org-insert-link</code>) process:
</p>


<figure id="orge06f949">
<img src="https://sachachua.com/blog/2024/01/using-an-emacs-lisp-macro-to-define-quick-custom-org-mode-links-to-project-files/completing-custom-links.gif" alt="completing-custom-links.gif">

<figcaption><span class="figure-number">Figure 1: </span>Completing a custom link with <code>org-insert-link</code></figcaption>
</figure>

<p>
Sketches are handled by <a href="https://sachachua.com/dotemacs#org-mode-sketch-links">my Org Mode sketch links</a>, but we can add them anyway.
</p>


<div class="org-src-container">
<pre class="src src-emacs-lisp">(<span class="org-keyword">cl-pushnew</span> (cons (expand-file-name <span class="org-string">"~/sync/sketches/"</span>) <span class="org-string">"https://sketches.sachachua.com/filename/"</span>)
            my-project-web-base-list
            <span class="org-builtin">:test</span> <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">equal</span>)
</pre>
</div>


<p>
I've been really liking being able to refer to various emacsconf-el
files by just selecting the link type and completing the filename, so
maybe it'll be easier to write about lots of other stuff if I extend
that to my other projects.
</p>
<div id="outline-container-web-link" class="outline-2">
<h3 id="web-link">Copy web link</h3>
<div class="outline-text-2" id="text-web-link">
<div class="update" id="org59d46eb">
<ul class="org-ul">
<li><span class="timestamp-wrapper"><span class="timestamp">[2024-01-20 Sat]</span></span>: Fix Wayback link handling.</li>
<li><span class="timestamp-wrapper"><span class="timestamp">[2024-01-19 Fri]</span></span>: Add Wayback machine.</li>
</ul>

</div>

<p>
Keeping a list of projects and their web versions also makes it easier
for me to get the URL for something. I try to post as much as possible
on the Web so that it's easier for me to find things again and so that
other people can pick up ideas from my notes. Things are a bit
scattered: <a href="https://sachachua.com">my blog</a>, repositories on <a href="https://github.com/sachac/">Github</a> and <a href="https://codeberg.org/sachac/">Codeberg</a>, <a href="https://sketches.sachachua.com">my
sketches</a>&#x2026; I don't want to think about <i>where</i> the code has ended
up, I just want to grab the URL. If I'm going to put the link into an
Org Mode document, that's super easy. I just take advantage of the
things I've added to <code>org-store-link</code>. If I'm going to put it into an
e-mail or a toot or wherever else, I just want the bare URL.
</p>

<p>
I can think of two ways to approach this. One is a command that copies
just the URL by figuring it out from the buffer filename, which allows
me to special-case a bunch of things:
</p>


<div class="org-src-container">
<pre class="src src-emacs-lisp">(<span class="org-keyword">defun</span> <span class="org-function-name">my-copy-link</span> (<span class="org-type">&amp;optional</span> filename skip-links)
  <span class="org-doc">"Return the URL of this file.</span>
<span class="org-doc">If FILENAME is non-nil, use that instead.</span>
<span class="org-doc">If SKIP-LINKS is non-nil, skip custom links.</span>
<span class="org-doc">If we're in a Dired buffer, use the file at point."</span>
  (<span class="org-keyword">interactive</span>)
  (<span class="org-keyword">setq</span> filename (<span class="org-keyword">or</span> filename
                     (<span class="org-keyword">if</span> (derived-mode-p <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">dired-mode</span>) (dired-get-filename))
                     (buffer-file-name)))
  (<span class="org-keyword">if-let*</span>
      ((project-re (concat <span class="org-string">"</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">(</span></span><span class="org-string">"</span> (regexp-opt (mapcar <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">car</span> my-project-web-base-list)) <span class="org-string">"</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">)</span></span><span class="org-string">"</span>
                           <span class="org-string">"</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">(</span></span><span class="org-string">.*</span><span class="org-string"><span class="org-regexp-grouping-backslash">\\</span></span><span class="org-string"><span class="org-regexp-grouping-construct">)</span></span><span class="org-string">"</span>))
       (url (<span class="org-keyword">cond</span>
             ((<span class="org-keyword">and</span> (derived-mode-p <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">org-mode</span>)
                   (eq (org-element-type (org-element-context)) <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">link</span>)
                   (not skip-links))
              (<span class="org-keyword">pcase</span> (org-element-property <span class="org-builtin">:type</span> (org-element-context))
                ((<span class="org-keyword">or</span> <span class="org-string">"https"</span> <span class="org-string">"http"</span>)
                 (org-element-property <span class="org-builtin">:raw-link</span> (org-element-context)))
                (<span class="org-string">"yt"</span>
                 (org-element-property <span class="org-builtin">:path</span> (org-element-context)))
                <span class="org-comment-delimiter">;; </span><span class="org-comment">if it's a custom link, visit it and get the link</span>
                (_
                 (<span class="org-keyword">save-window-excursion</span>
                   (org-open-at-point)
                   (my-copy-link nil t)))))
             <span class="org-comment-delimiter">;; </span><span class="org-comment">links to my config usually have a CUSTOM_ID property</span>
             ((string= (buffer-file-name) (expand-file-name <span class="org-string">"~/sync/emacs/Sacha.org"</span>))
              (concat <span class="org-string">"https://sachachua.com/dotemacs#"</span> (org-entry-get-with-inheritance <span class="org-string">"CUSTOM_ID"</span>)))
             <span class="org-comment-delimiter">;; </span><span class="org-comment">blog post drafts have permalinks</span>
             ((<span class="org-keyword">and</span> (derived-mode-p <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">org-mode</span>) (org-entry-get-with-inheritance <span class="org-string">"EXPORT_ELEVENTY_PERMALINK"</span>))
              (concat <span class="org-string">"https://sachachua.com"</span> (org-entry-get-with-inheritance <span class="org-string">"EXPORT_ELEVENTY_PERMALINK"</span>)))
             <span class="org-comment-delimiter">;; </span><span class="org-comment">some projects have web repos</span>
             ((string-match
               project-re filename)
              (concat (assoc-default (match-string 1 filename) my-project-web-base-list)
                      (url-hexify-string (match-string 2 filename)))))))
      (<span class="org-keyword">progn</span>
        (<span class="org-keyword">when</span> (called-interactively-p <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">any</span>)
          (kill-new url)
          (message <span class="org-string">"%s"</span> url))
        url)
    (<span class="org-warning">error</span> <span class="org-string">"Couldn't figure out URL."</span>)))
</pre>
</div>


<p>
Another approach is to hitch a ride on the Org Mode link storage and
export functions and just grab the URL from whatever link I've stored
with <code>org-store-link</code>, which I've bound to <code>C-c l</code>. I almost always
have an HTML version of the exported link. We can even use XML parsing
instead of regular expressions.
</p>


<div class="org-src-container">
<pre class="src src-emacs-lisp">(<span class="org-keyword">defun</span> <span class="org-function-name">my-org-link-as-url</span> (link)
  <span class="org-doc">"Return the final URL for LINK."</span>
  (<span class="org-keyword">cond</span>
   ((string-match <span class="org-string">"^/"</span> link)
    (concat my-blog-base-url (replace-regexp-in-string <span class="org-string">"^/"</span> <span class="org-string">""</span> link)))
   ((string-match <span class="org-string">"^https://"</span> link)
    link)
   (t
    (<span class="org-keyword">dom-attr</span>
     (dom-by-tag
      (<span class="org-keyword">with-temp-buffer</span>
        (insert (org-export-string-as link <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">html</span> t))
        (xml-parse-region (point-min) (point-max)))
      <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">a</span>)
     <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">href</span>))))

(<span class="org-keyword">defun</span> <span class="org-function-name">my-org-stored-link-as-url</span> (<span class="org-type">&amp;optional</span> link insert)
  <span class="org-doc">"Copy the stored link as a plain URL.</span>
<span class="org-doc">If LINK is specified, use that instead."</span>
  (<span class="org-keyword">interactive</span> (list nil current-prefix-arg))
  (<span class="org-keyword">setq</span> link (<span class="org-keyword">or</span> link (caar org-stored-links)))
  (<span class="org-keyword">let</span> ((url (<span class="org-keyword">if</span> link
                 (my-org-link-as-url link)
               (<span class="org-warning">error</span> <span class="org-string">"No stored link"</span>))))
    (<span class="org-keyword">when</span> (called-interactively-p <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">any</span>)
      (<span class="org-keyword">if</span> url
          (<span class="org-keyword">if</span> insert (insert url) (kill-new url))
        (<span class="org-warning">error</span> <span class="org-string">"Could not find URL."</span>)))
    url))

(<span class="org-keyword">ert-deftest</span> <span class="org-function-name">my-org-stored-link-as-url</span> ()
  (<span class="org-keyword">should</span>
   (string= (my-org-stored-link-as-url <span class="org-string">"[[dotemacs:web-link]]"</span>)
            <span class="org-string">"https://sachachua.com/dotemacs#web-link"</span>))
  (<span class="org-keyword">should</span>
   (string= (my-org-stored-link-as-url <span class="org-string">"[[dotemacs:org-mode-sketch-links][my Org Mode sketch links]]"</span>)
            <span class="org-string">"https://sachachua.com/dotemacs#org-mode-sketch-links"</span>)))

(<span class="org-keyword">defun</span> <span class="org-function-name">my-embark-org-copy-exported-url-as-wayback</span> (link <span class="org-type">&amp;rest</span> _)
  (<span class="org-keyword">interactive</span> <span class="org-string">"MLink: "</span>)
  (<span class="org-keyword">let</span> ((url  (my-embark-org-copy-exported-url link)))
    (<span class="org-keyword">when</span> (not (string-match (regexp-quote <span class="org-string">"^https://web.archive.org"</span>) url))
      (<span class="org-keyword">setq</span> url (concat <span class="org-string">"https://web.archive.org/web/"</span> (format-time-string <span class="org-string">"%Y%m%d%H%M%S/"</span>)
                        url)))
    (<span class="org-keyword">when</span> (called-interactively-p <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">any</span>)
      (kill-new url)
      (message <span class="org-string">"Copied %s"</span> url))
    url))

(<span class="org-keyword">defun</span> <span class="org-function-name">my-embark-org-copy-exported-url</span> (link <span class="org-type">&amp;rest</span> _)
  (<span class="org-keyword">interactive</span> <span class="org-string">"MLink: \np"</span>)
  (<span class="org-keyword">let</span> ((url (my-org-link-as-url link)))
    (<span class="org-keyword">when</span> (<span class="org-keyword">and</span> (derived-mode-p <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">org-mode</span>)
               (org-entry-get-with-inheritance <span class="org-string">"EXPORT_ELEVENTY_PERMALINK"</span>)
               (string-match <span class="org-string">"^/"</span> url))
      <span class="org-comment-delimiter">;; </span><span class="org-comment">local file links are copied to blog directories</span>
      (<span class="org-keyword">setq</span> url (concat <span class="org-string">"https://sachachua.com"</span>
                        (org-entry-get-with-inheritance <span class="org-string">"EXPORT_ELEVENTY_PERMALINK"</span>)
                        (replace-regexp-in-string
                         <span class="org-string">"[\\?&amp;].*"</span>
                         <span class="org-string">""</span>
                         (file-name-nondirectory link)))))
    (<span class="org-keyword">when</span> (called-interactively-p <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">any</span>)
      (kill-new url)
      (message <span class="org-string">"Copied %s"</span> url))
    url))

(<span class="org-keyword">defun</span> <span class="org-function-name">my-embark-replace-link-with-exported-url</span> (link <span class="org-type">&amp;rest</span> _)
  (<span class="org-keyword">interactive</span> (list (org-element-property <span class="org-builtin">:raw-link</span> (org-element-context))))
  (my-insert-or-replace-link (my-org-link-as-url link)))

(<span class="org-keyword">with-eval-after-load</span> <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">embark-org</span>
  (mapc (<span class="org-keyword">lambda</span> (map)
          (keymap-set map <span class="org-string">"u"</span> <span class="org-highlight-quoted-quote">#'</span><span class="org-highlight-quoted-symbol">my-embark-org-copy-exported-url</span>)
          (keymap-set map <span class="org-string">"U"</span> <span class="org-highlight-quoted-quote">#'</span><span class="org-highlight-quoted-symbol">my-embark-org-copy-exported-url-as-wayback</span>)
          (keymap-set map <span class="org-string">"r e"</span> <span class="org-highlight-quoted-quote">#'</span><span class="org-highlight-quoted-symbol">my-embark-replace-link-with-exported-url</span>))
        (list embark-url-map embark-org-link-map embark-org-link-copy-map)))
</pre>
</div>


<p>
We'll see which one I end up using. I think both approaches might come in handy.
</p>
</div>
</div>
<div id="outline-container-org7beda7c" class="outline-2">
<h3 id="org7beda7c">Quickly search my code</h3>
<div class="outline-text-2" id="text-org7beda7c">
<p>
Since <code>my-project-web-base-list</code> is a list of projects I often think
about or write about, I can also make something that searches through
them. That way, I don't have to care about where my code is.
</p>


<div class="org-src-container">
<pre class="src src-emacs-lisp">(<span class="org-keyword">defun</span> <span class="org-function-name">my-consult-ripgrep-code</span> ()
  (<span class="org-keyword">interactive</span>)
  (consult-ripgrep (mapcar <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">car</span> my-project-web-base-list)))
</pre>
</div>


<p>
I can add <code>.rgignore</code> files in directories to tell ripgrep to ignore
things like <code>node_modules</code> or <code>*.json</code>.
</p>

<p>
I also want to search my Emacs configuration at the same time,
although links to my config are handled by <a href="https://sachachua.com/dotemacs#links-to-my-config">my dotemacs link type</a> so
I'll leave the URL as nil. This is also the way I can handle other
unpublished directories.
</p>


<div class="org-src-container">
<pre class="src src-emacs-lisp">(<span class="org-keyword">cl-pushnew</span> (cons (expand-file-name <span class="org-string">"~/sync/emacs/Sacha.org"</span>) nil)
            my-project-web-base-list
            <span class="org-builtin">:test</span> <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">equal</span>)
(<span class="org-keyword">cl-pushnew</span> (cons (expand-file-name <span class="org-string">"~/proj/static-blog/_includes"</span>) nil)
            my-project-web-base-list
            <span class="org-builtin">:test</span> <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">equal</span>)
(<span class="org-keyword">cl-pushnew</span> (cons (expand-file-name <span class="org-string">"~/bin"</span>) nil)
            my-project-web-base-list
            <span class="org-builtin">:test</span> <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">equal</span>)
</pre>
</div>


<p>
Actually, let's throw my blog posts and Org files in there as well,
since I often have code snippets. If it gets to be too much, I can
always have different commands search different things.
</p>


<div class="org-src-container">
<pre class="src src-emacs-lisp">(<span class="org-keyword">cl-pushnew</span> (cons (expand-file-name <span class="org-string">"~/proj/static-blog/blog/"</span>) <span class="org-string">"https://sachachua.com/blog/"</span>)
            my-project-web-base-list
            <span class="org-builtin">:test</span> <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">equal</span>)
(<span class="org-keyword">cl-pushnew</span> (cons (expand-file-name <span class="org-string">"~/sync/orgzly"</span>) nil)
            my-project-web-base-list
            <span class="org-builtin">:test</span> <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">equal</span>)
</pre>
</div>



<figure id="orga881673">
<img src="https://sachachua.com/blog/2024/01/using-an-emacs-lisp-macro-to-define-quick-custom-org-mode-links-to-project-files/ripgrep-code.gif" alt="ripgrep-code.gif">

<figcaption><span class="figure-number">Figure 2: </span>Using my-consult-ripgrep-code</figcaption>
</figure>

<p>
I don't have anything bound to <code>M-s c</code> (code) yet, so let's try that.
</p>


<div class="org-src-container">
<pre class="src src-emacs-lisp">(keymap-global-set <span class="org-string">"M-s c"</span> <span class="org-highlight-quoted-quote">#'</span><span class="org-highlight-quoted-symbol">my-consult-ripgrep-code</span>)
</pre>
</div>


<p>
At some point, it might be fun to get Embark set up so that I can grab
a link to something right from the consult-ripgrep interface. In the
meantime, I can always jump to it and get the link.
</p>
</div>
<div id="outline-container-org81e85f0" class="outline-3">
<h4 id="org81e85f0">Tip from Omar: embark-around-action-hooks</h4>
<div class="outline-text-3" id="text-org81e85f0">
<p>
<span class="timestamp-wrapper"><span class="timestamp">[2024-01-07 Sun] </span></span> I modified oantolin's suggestion from the comments to work with <code>consult-ripgrep</code>, since <code>consult-ripgrep</code> gives me <code>consult-grep</code> targets instead of <code>consult-location</code>:
</p>


<div class="org-src-container">
<pre class="src src-emacs-lisp">(<span class="org-keyword">cl-defun</span> <span class="org-function-name">embark-consult&#45;&#45;at-location</span> (<span class="org-type">&amp;rest</span> args <span class="org-type">&amp;key</span> target type run <span class="org-type">&amp;allow-other-keys</span>)
  <span class="org-doc">"RUN action at the target location."</span>
  (<span class="org-keyword">save-window-excursion</span>
    (<span class="org-keyword">save-excursion</span>
      (<span class="org-keyword">save-restriction</span>
        (<span class="org-keyword">pcase</span> type
          (<span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">consult-location</span> (consult&#45;&#45;jump (consult&#45;&#45;get-location target)))
          (<span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">org-heading</span> (org-goto-marker-or-bmk (get-text-property 0 <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">org-marker</span> target)))
          (<span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">consult-grep</span> (consult&#45;&#45;jump (consult&#45;&#45;grep-position target)))
          (<span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">file</span> (find-file target)))
        (apply run args)))))

(<span class="org-keyword">cl-pushnew</span> <span class="org-highlight-quoted-quote">#'</span><span class="org-highlight-quoted-symbol">embark-consult&#45;&#45;at-location</span> (alist-get <span class="org-highlight-quoted-quote">'</span><span class="org-highlight-quoted-symbol">org-store-link</span> embark-around-action-hooks))
</pre>
</div>


<p>
I think I can use it with <code>M-s c</code> to search for the code, then <code>C-.
C-c l</code> on the matching line, where <code>C-c l</code> is my regular keybinding
for storing links. Thanks, Omar!
</p>

<p>
In general, I don't want to have to think about where something is on
my laptop or where it's published on the Web, I just want to write
about it. One step closer, yay Emacs!</p>
</div>
</div>
</div>

<div class="note">This is part of my <a href="https://sachachua.com/dotemacs#git-projects">Emacs configuration.</a></div><div><a href="https://sachachua.com/blog/2024/01/using-an-emacs-lisp-macro-to-define-quick-custom-org-mode-links-to-project-files/index.org">View org source for this post</a></div>

<p>You can <a href="https://sachachua.com/blog/2024/01/using-an-emacs-lisp-macro-to-define-quick-custom-org-mode-links-to-project-files/#comment">view 6 comments</a> or <a href="mailto:sacha@sachachua.com?subject=Comment%20on%20https%3A%2F%2Fsachachua.com%2Fblog%2F2024%2F01%2Fusing-an-emacs-lisp-macro-to-define-quick-custom-org-mode-links-to-project-files%2F&body=Name%20you%20want%20to%20be%20credited%20by%20(if%20any)%3A%20%0AMessage%3A%20%0ACan%20I%20share%20your%20comment%20so%20other%20people%20can%20learn%20from%20it%3F%20Yes%2FNo%0A">e-mail me at sacha@sachachua.com</a>.</p>]]></description>
		</item><item>
		<title>Avoiding automatic data type conversion in Microsoft Excel and Pandas</title>
		<link>https://sachachua.com/blog/2021/12/avoiding-automatic-data-type-conversion-in-microsoft-excel-and-pandas/</link>
		<dc:creator><![CDATA[Sacha Chua]]></dc:creator>
		<pubDate>Fri, 24 Dec 2021 23:53:07 GMT</pubDate>
    <category>coding</category>
<category>python</category>
		<guid isPermaLink="false">https://sachachua.com/blog/2021/12/avoiding-automatic-data-type-conversion-in-microsoft-excel-and-pandas/</guid>
		<description><![CDATA[<p>
Automatic conversion of data types is often handy, but sometimes it
can mess things up. For example, when you import a CSV into Microsoft
Excel, it will helpfully convert and display dates/times in your
preferred format&#x2013;and it will use your configured format when
exporting back to CSV, which is not cool when your original file had
<code>YYYY-MM-DD HH:MM:SS</code> and someone's computer decided to turn it into
<code>MM/DD/YY HH:MM</code>. To avoid this conversion and import the columns as
strings, you can change the file extension to <code>.txt</code> instead of <code>.csv</code>
and then change each column type that you care about, which can be a
lot of clicking. I had to change things back with a regular expression
along the lines of:
</p>

<div class="org-src-container">
<pre class="src src-python"><span class="org-keyword">import</span> re
<span class="org-variable-name">s</span> = <span class="org-string">"12/9/21 11:23"</span>
<span class="org-variable-name">match</span> = re.match(<span class="org-string">'([0-9]+)/([0-9]+)/([0-9]+)( [0-9]+:[0-9]+)'</span>, s)
<span class="org-variable-name">date</span> = <span class="org-string">'20%s-%s-%s%s:00'</span> % (match.group(3).zfill(2), match.group(1).zfill(2), match.group(2).zfill(2), match.group(4))
<span class="org-keyword">print</span>(date)
</pre>
</div>

<p>
The <code>pandas</code> library for Python also likes to do this kind of data
type conversion for data types and for NaN values. In this particular
situation, I wanted it to leave columns alone and leave the <code>nan</code>
string in my input alone. Otherwise, <code>to_csv</code> would replace <code>nan</code> with
the blank string, which could mess up a different script that used
this data as input. This is the code to do it:
</p>

<div class="org-src-container">
<pre class="src src-python"><span class="org-keyword">import</span> pandas <span class="org-keyword">as</span> pd
<span class="org-variable-name">df</span> = pd.read_csv(<span class="org-string">'filename.csv'</span>, encoding=<span class="org-string">'utf-8'</span>, dtype=<span class="org-builtin">str</span>, na_filter=<span class="org-constant">False</span>)
</pre>
</div>

<p>
I'm probably going to run into this again sometime, so I wanted to
make sure I put my notes somewhere I can find them later.
</p>

<p>You can <a href="mailto:sacha@sachachua.com?subject=Comment%20on%20https%3A%2F%2Fsachachua.com%2Fblog%2F2021%2F12%2Favoiding-automatic-data-type-conversion-in-microsoft-excel-and-pandas%2F&body=Name%20you%20want%20to%20be%20credited%20by%20(if%20any)%3A%20%0AMessage%3A%20%0ACan%20I%20share%20your%20comment%20so%20other%20people%20can%20learn%20from%20it%3F%20Yes%2FNo%0A">e-mail me at sacha@sachachua.com</a>.</p>]]></description>
		</item><item>
		<title>Started learning how to interactively debug Javascript in Emacs with Indium</title>
		<link>https://sachachua.com/blog/2021/08/started-learning-how-to-interactively-debug-javascript-in-emacs-with-indium/</link>
		<dc:creator><![CDATA[Sacha Chua]]></dc:creator>
		<pubDate>Mon, 16 Aug 2021 00:00:00 GMT</pubDate>
    <category>11ty</category>
<category>emacs</category>
<category>coding</category>
		<guid isPermaLink="false">https://sachachua.com/blog/2021/08/started-learning-how-to-interactively-debug-javascript-in-emacs-with-indium/</guid>
		<description><![CDATA[<p>
I noticed something strange in my static blog: my <a href="https://sachachua.com/blog/category/blogging/">blogging category
page</a> didn't list my post on <a href="https://sachachua.com/blog/2021/04/statically-generating-my-blog-with-eleventy/">statically generating my blog with
Eleventy</a>. Now it does, of course, since I fixed it. But it didn't, and
that was weird. I tried using console.log to debug it, but it was
annoying to try to figure out the right thing to print out in a long
list of nested objects. Besides, <code>console.log</code> debugging is so&#x2026; last
century.
</p>

<p>
Since <a href="https://griffa.dev/posts/tips-for-debugging-in-11ty/">these tips for debugging in 11ty</a> mentioned interactively
debugging things in VS Code, I decided it was a good time to learn how
to use <a href="https://github.com/NicolasPetton/Indium">Indium</a>, a Javascript development environment for Emacs.
</p>

<div class="org-src-container">
<pre class="src src-emacs-lisp">(<span class="org-keyword">use-package</span> <span class="org-constant">indium</span> <span class="org-builtin">:hook</span> ((js2-mode . indium-interaction-mode)))
</pre>
</div>

<p>
After some trial and error, this was the <code>.indium.json</code> file that
allowed me to use <code>M-x indium-launch</code> to start the Eleventy process. 
</p>

<pre class="example" id="orgc06463b">
{
  "configurations": [
    {
      "name": "11ty",
      "type": "node",
      "program": "node",
      "args": "./node_modules/.bin/eleventy"
    }
  ]
}
</pre>

<p>
I originally had <code>"inspect-brk": true</code> in it as well, following the
<a href="https://indium.readthedocs.io/en/latest/setup.html#general-configuration">suggested configuration</a>, but I found it easier to just set breakpoints
in my files using <code>indium-add-breakpoint</code> (<code>C-c b b</code>, a keybinding set
up by <a href="https://indium.readthedocs.io/en/latest/code-evaluation.html">indium-interaction-mode</a> in my <code>js2-mode-hook</code>).
</p>

<p>
<a href="https://github.com/NicolasPetton/Indium/issues/243">Conditional breakpoints didn't seem to work</a>, so I just put my logic in
an <code>if</code> and set my breakpoint in there.
</p>

<div class="org-src-container">
<pre class="src src-js2">  categories.forEach((item) =&gt; {
    if (item.slug == 'blogging') {
      let post = data.collections._posts.find(o =&gt; o.inputPath.match(/statically-generating-my-blog-with-eleventy/));
      console.log(post);
    }
    ...
  }
</pre>
</div>

<p>
When I set my breakpoint on the <code>let post...</code> line and ran <code>M-x
indium-launch</code>, I got an interactive debugger at that breakpoint. I
could also switch to the REPL console and type stuff. Yay!
</p>

<p>
As it turned out, the post I wanted wasn't showing up in the list of
posts. It was because I had used <code>eleventyConfig.setTemplateFormats</code>
and forgotten to include <code>md</code> for Markdown files. Once I figured out
what was going on, it was easy to fix. This is what the debugger looks
like. It adds values to the ends of lines, and you can evaluate
things.
</p>


<div id="org2bc94c6" class="figure">
<p><img src="https://sachachua.com/blog/2021/08/started-learning-how-to-interactively-debug-javascript-in-emacs-with-indium/Screenshot_20210816_002331.png" alt="Screenshot_20210816_002331.png">
</p>
</div>

<p>
I'm looking forward to learning more about using Indium to debug
scripts running in Node or Chrome. Slowly, slowly having some focused
time to sharpen the saw!
</p>

<p>
If you use Emacs for Javascript development and you're curious about
Indium, you can check out <a href="https://indium.readthedocs.io/en/latest/">the documentation</a>.
</p>

<p>You can <a href="mailto:sacha@sachachua.com?subject=Comment%20on%20https%3A%2F%2Fsachachua.com%2Fblog%2F2021%2F08%2Fstarted-learning-how-to-interactively-debug-javascript-in-emacs-with-indium%2F&body=Name%20you%20want%20to%20be%20credited%20by%20(if%20any)%3A%20%0AMessage%3A%20%0ACan%20I%20share%20your%20comment%20so%20other%20people%20can%20learn%20from%20it%3F%20Yes%2FNo%0A">e-mail me at sacha@sachachua.com</a>.</p>]]></description>
		</item>
	</channel>
</rss>