<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Nicholas Cloud</title>
    <description>The latest articles on Forem by Nicholas Cloud (@nicholascloud).</description>
    <link>https://forem.com/nicholascloud</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F93039%2Fef6ee127-2972-4e0f-bad0-b936e14342ec.jpg</url>
      <title>Forem: Nicholas Cloud</title>
      <link>https://forem.com/nicholascloud</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/nicholascloud"/>
    <language>en</language>
    <item>
      <title>Goodbye Evernote</title>
      <dc:creator>Nicholas Cloud</dc:creator>
      <pubDate>Sun, 31 Jan 2021 23:09:41 +0000</pubDate>
      <link>https://forem.com/nicholascloud/goodbye-evernote-1b6f</link>
      <guid>https://forem.com/nicholascloud/goodbye-evernote-1b6f</guid>
      <description>&lt;p&gt;(This article originally published at &lt;a href="https://www.nicholascloud.com/2021/01/goodbye-evernote/" rel="noopener noreferrer"&gt;nicholascloud.com&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;I've been a paying Evernote user for years; and before that, a "free" Evernote user for even longer. Evernote has some seriously powerful features, among which is the Evernote Web Clipper extension available on all major browsers, the excellent PDF markup features, flawless sync across devices, etc. It is solid software, backed by a solid service.&lt;/p&gt;

&lt;p&gt;But I've left Evernote, likely for good.&lt;/p&gt;

&lt;p&gt;In the wake of efforts by "Big Tech" companies to censor, deplatform, or control the data that belongs to customers, I've been cutting ties with as many Big Tech companies as I'm able. And Evernote, though I have never experienced any negative service from them, Evernote does not offer end-to-end encryption for it's services (meaning notes stored on Evernote servers are accessible by Evernote employees), and that has become a deal-breaker for me. I value my privacy, and my personal notes are where I can explore ideas and record my thoughts. And I don't want to use any service that doesn't respect and protect that.&lt;/p&gt;

&lt;p&gt;But what to do with GIGABYTES of notes, clipped articles, recipes, photos, and annotated PDFs?&lt;/p&gt;

&lt;p&gt;When I ditched GMail for ProtonMail, it took me &lt;em&gt;months&lt;/em&gt; to dig through all my archived mail. I deleted, exported, or printed each piece, then had to update the sender's settings with my new email address (or unsubscribe, if it was a newsletter). I transferred all of my contacts to ProtonMail, then reviewed them all to put the exported information into ProtonMail's custom contact fields. It was a long, tedious, tiresome process, but I did it.&lt;/p&gt;

&lt;p&gt;I expected my transition away from Evernote to be just as challenging. I came up with a list of goals for my new notebook scheme:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;My notes should be plain text (well, &lt;a href="https://www.markdownguide.org/" rel="noopener noreferrer"&gt;technically Markdown&lt;/a&gt;) files that link to any relevant external assets, such as images, PDF files, etc.&lt;/li&gt;
&lt;li&gt;Markdown files and assets should be organized in a uniform way.&lt;/li&gt;
&lt;li&gt;Markdown files should have a uniform naming convention (all lower-case words, separated by hyphens).&lt;/li&gt;
&lt;li&gt;I should be able to easily search for note content.&lt;/li&gt;
&lt;li&gt;My notes should be available on multiple devices.&lt;/li&gt;
&lt;li&gt;I should be able to clip content from the web and easily add it as a Markdown file to my notebook.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1: Get my notes out of Evernote
&lt;/h2&gt;

&lt;p&gt;There are two Evernote applications you can use on your device: the slick, newer version, and the old, legacy version. The new version looks nice, but it dropped a significant feature I thought would be available to me, and that is the ability to export &lt;em&gt;all notes at once&lt;/em&gt; in HTML format. Even the legacy version seems to lack this feature (maybe just on OSX?), though in the legacy version you can still export individual notebooks as HTML collections. In the newer version you can only export notebooks as Evernote's own ENEX file format (a kind of XML archive of notebook content). This seemed like it was going to be a show-stopper, since I had no clue how I would convert ENEX files into Markdown files, but a friend pointed me to the excellent utility &lt;a href="https://github.com/wormi4ok/evernote2md" rel="noopener noreferrer"&gt;evernote2md&lt;/a&gt; which does exactly that. Since I had 132 individual notebooks, it took me a while to export them all, then to convert them to Markdown with this utility, but once done, I had all of my saved notes in Markdown format (along with their attachments).&lt;/p&gt;

&lt;p&gt;The total size of my exported Markdown notes and attachments is around 2GB.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Fixing file names
&lt;/h2&gt;

&lt;p&gt;I noticed two things pretty quickly after my initial export:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Most Markdown file names were derived from the Evernote note title, which means they were typically in Title Case with underscores for space separators.&lt;/li&gt;
&lt;li&gt;MANY Markdown files, for one reason or another, had leading or trailing underscores.&lt;/li&gt;
&lt;li&gt;Some Markdown files -- mostly ones clipped from Reddit threads -- had strange naming conventions, e.g., &lt;code&gt;_r_&amp;lt;subreddit-name&amp;gt;_&amp;lt;super-long-post-title&amp;gt;&lt;/code&gt;. This is because Evernote automatically uses the webpage title tag as the title of a note imported with it's web clipper.&lt;/li&gt;
&lt;li&gt;MANY Markdown files were named "untitled-XX.md" (where XX is some number). Did these notes not have titles?&lt;/li&gt;
&lt;li&gt;MANY Markdown files had the word &lt;code&gt;undefined&lt;/code&gt; randomly peppered through the file name, e.g., &lt;code&gt;A_Historyundefined_and_Timeline_of_the_World.md&lt;/code&gt;. (I later realized this only occurred immediately preceding the word &lt;code&gt;and&lt;/code&gt;, of which I speculate that the ampersand was actually used in the original title and evernote2md has a bug that does not translate it correctly.)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So I had my work cut out for me. The first thing I decided to tackle was normalizing the file name case and space separator concerns. I prefer all lower-case file names, with hyphens for space separators. I hacked together a simple node.js script to traverse all of my exported notebooks and make this change.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// npm install globby&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;globby&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;globby&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;renameSync&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;renameSync&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;execSync&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;child_process&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;execSync&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dirs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="c1"&gt;// top-level notebook names omitted for REASONS&lt;/span&gt;
&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dir&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;**/*.md&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;filePaths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;globby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dirs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fixedPaths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;filePaths&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pathDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pathName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;basename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.md&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;newPathName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pathName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[^\w\d&lt;/span&gt;&lt;span class="sr"&gt;-&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.md&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nx"&gt;newPathName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;newPathName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/_/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pathDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;newPathName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;oldPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;newPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;fixedPaths&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fixedPath&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`fixing path &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;fixedPath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;oldPath&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;...`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;renameSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fixedPath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;oldPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fixedPath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newPath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;all done.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;})();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This script worked well and addressed the first naming problem -- all files names are lowercase, and instead of underscores, hyphens delimit words -- but there were still problems to address.&lt;/p&gt;

&lt;p&gt;My initial gut instinct was to begin modifying my script to handle the remaining naming problems, but that just made me tired, so I turned to the INTERNET to figure out if there was a better way to do this.&lt;/p&gt;

&lt;p&gt;Sweet Baby Jesus there is.&lt;/p&gt;

&lt;p&gt;There is a wonderful utility called &lt;code&gt;perl-rename&lt;/code&gt; that uses Perl's regular expression engine to bulk rename files in-place. It's very similar to how Vim performs find/replace, and it helped me solve two of my other problems in quick order.&lt;/p&gt;

&lt;p&gt;** Getting rid of &lt;code&gt;undefined&lt;/code&gt; **&lt;/p&gt;

&lt;p&gt;To get rid of the pesky word &lt;code&gt;undefined&lt;/code&gt; in my note file names, I used the &lt;a href="https://www.nicholascloud.com/2018/11/using-the-find-command/" rel="noopener noreferrer"&gt;&lt;code&gt;find&lt;/code&gt; command&lt;/a&gt; to traverse my entire notebook structure, find all the Markdown files that contained that word, then pass along those file paths to the &lt;code&gt;perl-rename&lt;/code&gt; utility which renamed the file without its troublesome intruder.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd $NOTEBOOK
find . -iname "*undefined*.md" -exec perl-rename --verbose --dry-run -- 's/undefined//g' '{}' \;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The actual heavy lifting is done in the substitution string: &lt;code&gt;s/undefined//g&lt;/code&gt;, which reads like this: &lt;code&gt;&amp;lt;substitute&amp;gt;/&amp;lt;the word undefined&amp;gt;/&amp;lt;with nothing&amp;gt;/&amp;lt;anywhere in the file name (globally)&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;(Note that the &lt;code&gt;--dry-run&lt;/code&gt; flag will show you what &lt;em&gt;would&lt;/em&gt; happen if the &lt;code&gt;perl-rename&lt;/code&gt; command succeeded; to actually make the changes permanent the flag must be removed from the command.)&lt;/p&gt;

&lt;p&gt;So far so good -- no more &lt;code&gt;undefined&lt;/code&gt; in file names. What about leading and trailing spaces? Easy peasy.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd $NOTEBOOK
find . -iname "*.md" -exec perl-rename --verbose --dry-run -- 's/^-//' '{}' \;
find . -iname "*.md" -exec perl-rename --verbose --dry-run -- 's/-$//' '{}' \;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, the magic is in the substitution.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In the first command, the substitution reads: &lt;code&gt;&amp;lt;substitute&amp;gt;/&amp;lt;a dash at the beginning of the file name&amp;gt;/&amp;lt;with nothing&amp;gt;&lt;/code&gt;. (The caret &lt;code&gt;^&lt;/code&gt; symbol represents the beginning of a series of characters.)&lt;/li&gt;
&lt;li&gt;In the second command, the substitution reads: &lt;code&gt;&amp;lt;substitute&amp;gt;/&amp;lt;a dash at the end of the file name&amp;gt;/&amp;lt;with nothing&amp;gt;&lt;/code&gt;. (The dollar sign &lt;code&gt;$&lt;/code&gt; symbol represents the end of a series of characters.)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now for those pesky Reddit notes. Since I'd eliminated leading dashes in file names, clipped notes from Reddit would now have a file name like &lt;code&gt;r-&amp;lt;subreddit&amp;gt;-&amp;lt;note-title&amp;gt;&lt;/code&gt;. I still wanted to know these notes were from Reddit, so I decided the following substitution was best.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd $NOTEBOOK
find . -iname "r-*.md" -exec perl-rename --verbose --dry-run -- 's/^r-/reddit-/' '{}' \;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The substitution reads (as you probably know by now): &lt;code&gt;&amp;lt;substitute&amp;gt;/&amp;lt;an r- at the beginning of the file name&amp;gt;/&amp;lt;with reddit- &amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/9WXyFIDv2PyBq/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/9WXyFIDv2PyBq/giphy.gif" alt="Perfection." title="Perfection." width="500" height="289"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;But Nick, what about all those untitled-XX.md notes?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I'm glad you asked. There's nothing to do with those notes but manually examine them and rename them according to their content. Which would absolutely be a pain in the ass if not for the terminal file manager &lt;a href="https://github.com/ranger/ranger" rel="noopener noreferrer"&gt;ranger&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Renaming untitled notes
&lt;/h2&gt;

&lt;p&gt;Ever since &lt;a href="https://www.youtube.com/watch?v=L6Vu7WPkoJo" rel="noopener noreferrer"&gt;I watched Luke Smith demonstrate the ranger file manager&lt;/a&gt; I've had major boner for it, and wanted a real chance to kick its tires. The challenge of renaming all these untitled files gave me the opportunity.&lt;/p&gt;

&lt;p&gt;Briefly, ranger is a terminal file manager that emulates some of Vim's modal editor behavior. For example, to move through directories you use the home-row keys h, j, k, and l. To run commands you press the colon key, then enter the command name. It's both sexy and dangerous, and since I'm a kinky guy it was love at first sight.&lt;/p&gt;

&lt;p&gt;Since I had never used ranger for any serious file system work before, this was a great way to get used to its navigation controls and command capabilities. I quickly figured out that the home row was my navigation center, but ALSO that I wanted to move through &lt;em&gt;pages&lt;/em&gt; of files at a time rather than just hitting j and k repeatedly. Turns out if you hold &lt;code&gt;shift&lt;/code&gt; and hit those same keys ranger will move you half-page at a time. Excellent. I traversed each notebook and used ranger's &lt;code&gt;find&lt;/code&gt; command -- hitting &lt;code&gt;/&lt;/code&gt; followed by a file name string -- to quickly jump to the first instance of a file named &lt;code&gt;untitled...&lt;/code&gt;. Ranger has a great file preview pane that immediately let me inspect the contents of each file, from which I could easily determine what the real file name should be. Renaming each file was easy enough -- I typed the command &lt;code&gt;:rename &amp;lt;new-file-name&amp;gt;&lt;/code&gt; and that did the trick. If I perchance needed to &lt;em&gt;edit&lt;/em&gt; the file, I simply hit the l key to &lt;em&gt;enter the file itself&lt;/em&gt;, which opened my default text editor (set by the &lt;code&gt;EDITOR&lt;/code&gt; and &lt;code&gt;VISUAL&lt;/code&gt; environment variables) for immediate access. Quitting the editor returned me immediately to ranger. Hitting the &lt;code&gt;n&lt;/code&gt; key repeated my search. And so it went, until I had renamed all &lt;code&gt;untitled-XX.md&lt;/code&gt; files in each notebook directory.&lt;/p&gt;

&lt;p&gt;Occasionally I realized that a note I was viewing in ranger really needed to be in another directory (notebook). So I initiated an external shell command by typing &lt;code&gt;!&lt;/code&gt; (alternatively I could have typed &lt;code&gt;:shell&lt;/code&gt;) and then typed my typical shell command: &lt;code&gt;mv &amp;lt;file-name&amp;gt; &amp;lt;other-directory&amp;gt;/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;All without leaving ranger.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/gwBouIIUuVvuE/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/gwBouIIUuVvuE/giphy.gif" alt="Gandalf weeping" title="Gandalf weeping" width="244" height="150"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Prune unused assets
&lt;/h2&gt;

&lt;p&gt;By far the bulk of the disk space in each notebook is allotted to assets attached to notes -- be they images, or PDFs, or audio files. Markdown files, being plain text, require little space to store -- but assets, being binary, are pigs.&lt;/p&gt;

&lt;p&gt;When I exported my notes to markdown, &lt;code&gt;evernote2md&lt;/code&gt; created two directories in each notebook for assets: &lt;code&gt;file&lt;/code&gt; and &lt;code&gt;image&lt;/code&gt;. This was uniform across notebooks, which worked to my advantage. After I exported my notes I started rummaging through each notebook directory, purging notes that were either no longer important, or too badly mangled by the export process to be of any value. But how to remove their assets as well? I hacked together another node.js script to help me find assets that were no longer referenced by any notes in a given notebook.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cp"&gt;#!node
&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;execSync&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;child_process&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;execSync&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;assetDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// e.g., file, or image -- assume this script is executed in an asset directory&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mdDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;..&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lsResults&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;execSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`ls &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt; &lt;span class="nx"&gt;assetDir&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;noResults&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="nx"&gt;lsResults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`grep -c -H -l "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt; &lt;span class="nx"&gt;mdDir&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/*.md`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;grepResults&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;grepResults&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;execSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;cmd&lt;/span&gt; &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// empty&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;grepResults&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;grepResults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// does not appear in any file&lt;/span&gt;
    &lt;span class="nx"&gt;noResults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;noResults&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;noResults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`to remove - rm &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt; &lt;span class="nx"&gt;noResults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This script uses the &lt;code&gt;grep&lt;/code&gt; command to determine if an asset filename appears in the text content of any note; if it does &lt;em&gt;not&lt;/em&gt;, it is included in output at the end that builds up a long &lt;code&gt;rm&lt;/code&gt; command string that can be copied and then run to eliminate unused assets for a given notebook directory.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;grep&lt;/code&gt; command flags are important here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-c&lt;/code&gt; means to generate a &lt;em&gt;count&lt;/em&gt; of the matching lines in a file for the given search string (in this case, the asset file name)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-H&lt;/code&gt; means to print the file name in which the match occurred&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-l&lt;/code&gt; means to restrict output to matching file names only (instead of matching lines within a file)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This combination of flags produces one line per search file that will only be present if the asset name is found within the file, allowing the script to know how many times the asset itself is referenced. If it &lt;em&gt;isn't&lt;/em&gt; referenced at all, it's safe to delete. And so it goes.&lt;/p&gt;

&lt;p&gt;This process is still a work in progress. As I review each notebook, I'm pruning its assets, and keeping track of those I've completed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Add front-matter to notebooks
&lt;/h2&gt;

&lt;p&gt;Several Evernote alternatives (e.g., Boostnote) and many static website generators use YAML metadata markup at in Markdown files to render them appropriately. This front-matter appears at the top of the file, and follows a schema similar to the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;link&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;keywords&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;publisher&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;stats&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My exported Evernote notes do &lt;em&gt;not&lt;/em&gt; have this front-matter, but as will be demonstrated later, it is critically important for targeted note searches.&lt;/p&gt;

&lt;p&gt;So since you're wondering, yes, I did hack together another script to inject this front-matter into every existing Markdown note within a notebook directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="cp"&gt;#!node
&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;execSync&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;child_process&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;execSync&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;readFileSync&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mdDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// assume this command is in a notebook directory&lt;/span&gt;


&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;frontMatter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`
---
link:
title: &amp;lt;title&amp;gt;
description:
keywords:
author:
date:
publisher:
stats:
tags:
---
`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;capitalize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;charAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toUpperCase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lsResults&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;execSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`ls &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt; &lt;span class="nx"&gt;mdDir&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/*.md`&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!!&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lsResults&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;lsResults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fileContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;fileContent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;---&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; has front matter, skipping...`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fileName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;basename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;formattedFrontMatter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;frontMatter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nf"&gt;capitalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fileName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/-/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.md&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt; &lt;span class="nx"&gt;formattedFrontMatter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt; &lt;span class="nx"&gt;fileContent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;newContent&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since most notes already had filenames derived from their Evernote titles, I took advantage of that fact and turned those filenames into the note's front-matter title -- sans hyphens, and with sentence case. It's rough, I know, but better than nothing. The rest of the information I will have to add manually. The most important fields to me are title, author, and tags. (Are tags different than keywords? I don't know.) On these fields -- and the note's file name -- I will most frequently perform targeted searches.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: Searching for notes
&lt;/h2&gt;

&lt;p&gt;Searching for files by name is easy. If I want to search for a file with the word &lt;code&gt;taxes&lt;/code&gt; in it, I simply use the find command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd $NOTEBOOK
find . -iname "*taxes*"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will give me a list of file paths in which the word &lt;code&gt;taxes&lt;/code&gt; appears. I try to name my notes intelligently so this kind of search can be productive. But sometimes I want to be more specific.&lt;/p&gt;

&lt;p&gt;In that case I can rely on the &lt;code&gt;tags&lt;/code&gt; front-matter that I've added to each note. For example, I have a recipe for a mixed drink that my brother recommends. I've tagged this mixed drink with &lt;code&gt;alcohol&lt;/code&gt;, and can quickly find it using the &lt;a href="https://beyondgrep.com/" rel="noopener noreferrer"&gt;&lt;code&gt;ack&lt;/code&gt;&lt;/a&gt; command (you could use &lt;a href="https://www.man7.org/linux/man-pages/man1/grep.1.html" rel="noopener noreferrer"&gt;&lt;code&gt;grep&lt;/code&gt;&lt;/a&gt; as well, but I prefer &lt;code&gt;ack&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ack '^tags.*alcohol.*'
alcohol/super-complex-highly-rewarding-concotion-to-drink.md
10:tags: alcohol, don-julio, grand-marnier, kombucha, cocktail
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This simple command reveals that the file I'm looking for is &lt;code&gt;alcohol/super-complex-highly-rewarding-concotion-to-drink.md&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In fact, I could use &lt;code&gt;ack&lt;/code&gt; to search for &lt;em&gt;any&lt;/em&gt; front-matter field, simply by using the correct search expression. (The observant reader will notice that the expression resembles those I used when renaming files with &lt;code&gt;perl-rename&lt;/code&gt;. The syntax is very similar.) In this case, the search expression reads: &lt;code&gt;files that contain a line that beings with 'tags' (^tags) followed by any other characters (.*) but ALSO that has the world 'alcohol' in it, followed by any other characters (.*)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If I want to cast a wider net, I can also use ack to search for &lt;em&gt;any&lt;/em&gt; term that occurs in any of my notes with the simple command: &lt;code&gt;ack &amp;lt;search-term&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 7: Creating new notes
&lt;/h2&gt;

&lt;p&gt;Now that my notes are exported, cleaned, organized, and "front-mattered", how do I add new notes to my notebooks?&lt;/p&gt;

&lt;p&gt;Adding a new Markdown file is as simple as using your favorite text editor to save a file with the &lt;code&gt;.md&lt;/code&gt; extension. Because the &lt;code&gt;evernote2md&lt;/code&gt; export favored &lt;code&gt;file&lt;/code&gt; and &lt;code&gt;image&lt;/code&gt; directories for external assets, I use those same conventions for my own notes. If I had a notebook directory called &lt;code&gt;economics&lt;/code&gt;, for example, and I had a note called &lt;code&gt;the-history-of-economics.md&lt;/code&gt;, I might reference assets like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;The Author Adam Smith wrote the seminal work, The Wealth of Nations.

&lt;span class="c"&gt;&amp;lt;!-- this is an image of Adam Smith --&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;![&lt;/span&gt;&lt;span class="nv"&gt;Picture of Adam Smith&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;image/adam-smith.png&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- this is a link to the das-kapital.pdf file --&amp;gt;&lt;/span&gt;
Later, Karl Marx challenged Adam Smith's ideas in his work, &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;Das Kapital&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;files/das-kapital.pdf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, the vast majority of my notes are articles clipped from the Internet with the Evernote Web Clipper. As I've stated, this is one of the &lt;em&gt;strongest&lt;/em&gt; features of Evernote, and the one I'll probably miss the most.&lt;/p&gt;

&lt;p&gt;However, I've since discovered the &lt;a href="https://www.npmjs.com/package/clean-mark" rel="noopener noreferrer"&gt;clean-mark&lt;/a&gt; npm package which will do the exact same thing a) by exporting a web page in well-formatted Markdown, and b) adding front-matter by default. This is now my go-to method of snipping articles from the Internet. The only caveat is that all assets referenced by an article will &lt;em&gt;not&lt;/em&gt; be downloaded, but instead referenced by their individual Internet URLs. If images or external files are an integral part of an article, it will be up to me to download them manually and adjust the links accordingly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 8: Accessing notes from multiple devices
&lt;/h2&gt;

&lt;p&gt;So far I've entertained two methods for accessing my notes on multiple devices.&lt;/p&gt;

&lt;p&gt;I use the &lt;a href="https://mega.nz" rel="noopener noreferrer"&gt;MegaSync&lt;/a&gt; cloud storage service to back up and synchronize files across devices. It is, by far, the best cloud storage service I've used. Mega has clients that work on Windows, OSX, Linux, and Android -- which is awesome since I have devices that run each of those. (Also supports iOS but I have an Android phone so I don't care :D ). Synchronizing notes and files is flawless -- the only downside is that the Android client does &lt;em&gt;not&lt;/em&gt; render Markdown files, or show their plain text content, which obviously makes it an non-ideal mobile client solution. This is my only real gripe about Mega's mobile offering. It's 2021: everything should render Markdown.&lt;/p&gt;

&lt;p&gt;As an alternative, I've also contemplated using Github to manage all of my notes. I am very familiar with Git and letting it manage versions of my files and track individual commits is very appealing to me. Synchronizing across devices is trivial, as Github's web interface will render Markdown files (including embedded images) in any web browser -- mobile or not. My only hesitation is that Github (unlike Mega) does not offer end-to-end encryption (my original issue with Evernote) which does not offer me the measure of privacy I desire.&lt;/p&gt;

&lt;p&gt;This is the last big issue I need to solve before I have a complete Evernote replacement that meets all of my needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Leaving Evernote has been an adventure, but I've learned a lot along the way -- mostly that the tools I need to achieve my values are already within my reach, and they demand nothing but the time to learn. It's amazing how much of our personal lives we heft into the "cloud", to Big Tech services that don't actually give a crap about our privacy, and will use our own data against us when we don't bring our thoughts in line with whatever pre-established narrative to which they beat their drums. If you don't control your data, you roll the dice on ever more tenuous odds.&lt;/p&gt;

&lt;p&gt;Reclaiming my data -- and making it my &lt;em&gt;own&lt;/em&gt; again -- has been one of the most humanizing experiences I've had in a long time. I hope this inspires others to embark on a similar quest, for freedom -- for knowledge -- for autonomy.&lt;/p&gt;

</description>
      <category>evernote</category>
      <category>bigtech</category>
    </item>
    <item>
      <title>import facepalm;</title>
      <dc:creator>Nicholas Cloud</dc:creator>
      <pubDate>Wed, 11 Mar 2020 23:35:54 +0000</pubDate>
      <link>https://forem.com/nicholascloud/import-facepalm-323b</link>
      <guid>https://forem.com/nicholascloud/import-facepalm-323b</guid>
      <description>&lt;p&gt;&lt;em&gt;This article was originally published at &lt;a href="https://www.nicholascloud.com/2020/03/import-facepalm/" rel="noopener noreferrer"&gt;nicholascloud.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Sometimes bugs can be particularly evasive, and today I had such a one.&lt;/p&gt;

&lt;p&gt;A module in deep in our codebase was throwing an &lt;code&gt;Error&lt;/code&gt;, but only in Mozilla's Firefox browser.&lt;/p&gt;

&lt;p&gt;The error was &lt;a href="https://developer.mozilla.org/en-US/docs/Mozilla/Errors" rel="noopener noreferrer"&gt;&lt;code&gt;NS_ERROR_ILLEGAL_VALUE&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I did some quick DuckDuckGoing and found that the error occurs when a native DOM function in Firefox is passed a value of a type it does not expect.&lt;/p&gt;

&lt;p&gt;The stack trace led back to this line in our application code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;original&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;obj&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nx"&gt;hit&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;hit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// ...some time later...&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;someUncachedObject&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;"@-E$!&amp;amp;&amp;amp;@#"&lt;/em&gt;, I thought. &lt;em&gt;"Why is &lt;a href="https://lodash.com/docs/4.17.15#find" rel="noopener noreferrer"&gt;lodash's &lt;code&gt;find()&lt;/code&gt;&lt;/a&gt; function passing a bad value to a native function?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You see, I use &lt;a href="https://lodash.com/" rel="noopener noreferrer"&gt;&lt;code&gt;lodash&lt;/code&gt;&lt;/a&gt; all the time. So much, in fact, that I made one fatal error in my diagnosis.&lt;/p&gt;

&lt;p&gt;I assumed that because the &lt;code&gt;find()&lt;/code&gt; function was defined, that &lt;code&gt;lodash&lt;/code&gt; had indeed been imported.&lt;/p&gt;

&lt;p&gt;How. Wrong. I. Was.&lt;/p&gt;

&lt;p&gt;It turns out that &lt;code&gt;window.find()&lt;/code&gt; is, in fact, a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/find" rel="noopener noreferrer"&gt;non-standard, but nevertheless nearly omnipresent function&lt;/a&gt; that is designed to search a DOM document for a particular string. And since any function attached to &lt;code&gt;window&lt;/code&gt; is global, a missing import of the same name -- say, a missing &lt;code&gt;lodash/find&lt;/code&gt; import -- would not raise any alarms. The code built. The code ran. And it ran &lt;em&gt;without error in every browser but Firefox&lt;/em&gt;. Why?&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;window.find()&lt;/code&gt; function expects a first argument of type &lt;code&gt;String&lt;/code&gt;. In modern browsers other than Firefox (pretty much all the Chromium-based browsers), passing a non-&lt;code&gt;String&lt;/code&gt; argument to &lt;code&gt;window.find()&lt;/code&gt; will simply cause the function to return &lt;code&gt;false&lt;/code&gt;. As you can see in the snippet above, though rendering the cache useless, the application nevertheless continued to work. In &lt;code&gt;Firefox&lt;/code&gt;, however, &lt;code&gt;window.find()&lt;/code&gt; will &lt;em&gt;throw&lt;/em&gt; if its first argument is not of type &lt;code&gt;String&lt;/code&gt;. Thus my bug.&lt;/p&gt;

&lt;p&gt;I am ashamed to say how long it took me to realize &lt;code&gt;lodash/find&lt;/code&gt; was not the function being called.&lt;/p&gt;

&lt;p&gt;In the end I applied the great wisdom of Spock's ancestors, and started considering things that &lt;em&gt;could not possibly be the case&lt;/em&gt;, until it dawned on me that perhaps -- just perhaps -- &lt;code&gt;find()&lt;/code&gt; was not what it appeared to be after all.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fuevr4pl14fk3fvuo4z21.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fuevr4pl14fk3fvuo4z21.jpg" alt="The Undiscovered Code more like it..." width="500" height="216"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And a single &lt;code&gt;import find from "lodash/find";&lt;/code&gt; statement fixed the bug.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>lodash</category>
    </item>
    <item>
      <title>Fun with Homebrew casks</title>
      <dc:creator>Nicholas Cloud</dc:creator>
      <pubDate>Tue, 07 Jan 2020 03:31:58 +0000</pubDate>
      <link>https://forem.com/nicholascloud/fun-with-homebrew-casks-39jn</link>
      <guid>https://forem.com/nicholascloud/fun-with-homebrew-casks-39jn</guid>
      <description>&lt;p&gt;&lt;em&gt;This post was originally published at &lt;a href="https://www.nicholascloud.com/2020/01/fun-with-homebrew-casks/" rel="noopener noreferrer"&gt;nicholascloud.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;One of my favorite utilities for OSX is &lt;a href="https://brew.sh/" rel="noopener noreferrer"&gt;Homebrew&lt;/a&gt;, a package manager that lets you easily install programs from the terminal.&lt;/p&gt;

&lt;p&gt;One of my favorite &lt;em&gt;pastimes&lt;/em&gt; is thumbing through the &lt;a href="https://github.com/Homebrew/homebrew-cask/tree/master/Casks" rel="noopener noreferrer"&gt;Homebrew Cask&lt;/a&gt; recipes to find new programs and utilities to install. Some are pretty nifty, like &lt;a href="https://www.zotero.org/" rel="noopener noreferrer"&gt;Zotero&lt;/a&gt; which manages research bibliographies. Or &lt;a href="https://electricsheep.org/" rel="noopener noreferrer"&gt;Electric Sheep&lt;/a&gt; which harnesses the power of your "sleeping" devices to crowdsource digital artwork. Or &lt;a href="https://github.com/johnste/finicky" rel="noopener noreferrer"&gt;Finicky&lt;/a&gt;, which lets you specify which of your web browsers you want to open specific links. (Maybe you use Brave for normal browsing but want to open all google.com links in Chrome.)&lt;/p&gt;

&lt;p&gt;Unfortunately the Cask recipe files have no real descriptions, so I usually just fish through them and dig out the homepage links of any Cask recipe file that has an interesting name. It's kind of like a digital treasure hunt.&lt;/p&gt;

&lt;p&gt;To make things even more fun, I cloned the &lt;code&gt;homebrew-cask&lt;/code&gt; repo and came up with a simple shell script that will randomly choose a recipe and open its homepage for me.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;find ~/projects/homebrew-cask/Casks &lt;span class="nt"&gt;-type&lt;/span&gt; f | &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nb"&gt;shuf&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; 1 | &lt;span class="se"&gt;\&lt;/span&gt;
xargs &lt;span class="nb"&gt;cat&lt;/span&gt; | &lt;span class="se"&gt;\&lt;/span&gt;
ack homepage | &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print $2}'&lt;/span&gt; | &lt;span class="se"&gt;\&lt;/span&gt;
xargs open
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>homebrew</category>
      <category>cask</category>
    </item>
    <item>
      <title>Protip - return from exceptional conditions early</title>
      <dc:creator>Nicholas Cloud</dc:creator>
      <pubDate>Tue, 03 Dec 2019 02:04:24 +0000</pubDate>
      <link>https://forem.com/nicholascloud/protip-return-from-exceptional-conditions-early-lb4</link>
      <guid>https://forem.com/nicholascloud/protip-return-from-exceptional-conditions-early-lb4</guid>
      <description>&lt;p&gt;This article was originally posted on &lt;a href="https://www.nicholascloud.com/2019/11/protip-return-from-exceptional-conditions-early/" rel="noopener noreferrer"&gt;nicholascloud.com&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;During a recent code interview, I noticed a React component with a render method written in the following (abbreviated) form,&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ComponentWithLotsOfProps&lt;/span&gt;
      &lt;span class="na"&gt;prop1&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;
      &lt;span class="na"&gt;prop2&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;
      &lt;span class="na"&gt;propN&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;
      &lt;span class="err"&gt;...&lt;/span&gt;
    &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;''&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;where &lt;code&gt;ComponentWithLotsOfProps&lt;/code&gt; had at least a dozen props, some of which were not simple primitive values. &lt;/p&gt;

&lt;p&gt;While there is nothing &lt;em&gt;technically&lt;/em&gt; wrong with this render method, it could be better. It suffers from a few deficiencies.&lt;/p&gt;

&lt;p&gt;First, ternaries are objectively difficult to read when they are not &lt;em&gt;short&lt;/em&gt;. It is difficult to grok what the method actually produces because the whole ternary is returned, requiring the reader to do double work to find the "implicit" returns (there are two) rather than looking for the easily identifiable &lt;code&gt;return&lt;/code&gt; keyword.&lt;/p&gt;

&lt;p&gt;Second, one must read the entire method to know what gets returned if there are no items in state. Is it a component? Is it &lt;code&gt;null&lt;/code&gt;? Is it an empty string? That is unknown until the whole method has been read.&lt;/p&gt;

&lt;p&gt;Third, if additional conditions are required in future work to determine what will be rendered, they cannot easily be introduced in this method.&lt;/p&gt;

&lt;p&gt;A better alternative is to omit the ternary, and explicitly return the &lt;em&gt;exceptional&lt;/em&gt; condition values first.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ComponentWithLotsOfProps&lt;/span&gt;
      &lt;span class="na"&gt;prop1&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;
      &lt;span class="na"&gt;prop2&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;
      &lt;span class="na"&gt;propN&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;
      &lt;span class="err"&gt;...&lt;/span&gt;
    &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Due to reduced nesting, this is far easier to read, and return values are also easily identifiable. If additional conditions must be evaluated in the future, modifying this method becomes much simpler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SingleItemComponent&lt;/span&gt; &lt;span class="na"&gt;item&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ComponentWithLotsOfProps&lt;/span&gt;
      &lt;span class="na"&gt;prop1&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;
      &lt;span class="na"&gt;prop2&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;
      &lt;span class="na"&gt;propN&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;
      &lt;span class="err"&gt;...&lt;/span&gt;
    &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As with most things in programming: the simpler, more explicit, the better.&lt;/p&gt;

</description>
      <category>protip</category>
      <category>react</category>
    </item>
    <item>
      <title>Book Review - The Mythical Man-Month</title>
      <dc:creator>Nicholas Cloud</dc:creator>
      <pubDate>Mon, 02 Dec 2019 22:43:41 +0000</pubDate>
      <link>https://forem.com/nicholascloud/book-review-the-mythical-man-month-1a4b</link>
      <guid>https://forem.com/nicholascloud/book-review-the-mythical-man-month-1a4b</guid>
      <description>&lt;p&gt;&lt;em&gt;This book review originally posted at &lt;a href="https://www.nicholascloud.com/2019/12/book-review-mythical-man-month/"&gt;nicholascloud.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The Mythical Man-Month&lt;/em&gt; is one of those books that is, well, mythical in the circles to which it pertains -- that is, the software engineering and computer science fields. It is mythical because many people have heard of it, most agree that it is "classic", but not many remember exactly why. Perhaps they have never read it, only absorbed its ideas through hearsay. Or perhaps they did read it, but so long ago that its principles have been taken only in the tide of time.&lt;/p&gt;

&lt;p&gt;Either way, I have finally finished the book in what I consider to be an unreasonable amount of time. It's not overly long, or overly verbose, but I have a bad habit reading a little from a lot of books at the same time, which means I don't finish a book for a while. I took notes as I went so that hopefully time will be more gracious to my mind when someone asks me, in the years to come, if I've read Frederick Brooks.&lt;/p&gt;

&lt;p&gt;Widely considered the central theme of the book, Brooks's Law, in summary, is that adding programmers to a late software project will not make it go faster, but rather slower. This was a pattern Brooks saw during his years as a manager in larger companies that needed many engineers to write software either for internal use, or eventually for sale as products. Managers assumed that the central problem of software development -- why projects did not finish on time or on budget -- was a mechanical one that could be resolved with mechanical solutions. Maybe there just wasn't enough manpower. Or maybe the tooling was inferior, and retarded progress. Or maybe the wrong language had been chosen for the task. While all of these things can, and do, affect software engineering endeavors, Brooks's major insight was that they were the &lt;em&gt;accidents&lt;/em&gt; of software engineering, and not its &lt;em&gt;essence&lt;/em&gt;; and that the &lt;em&gt;essence&lt;/em&gt; is what causes projects to fail.&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;essence&lt;/em&gt; of "systems programming" (as Brooks called it) is one of &lt;em&gt;complexity&lt;/em&gt; -- an irreducible complexity&lt;sup&gt;1&lt;/sup&gt; -- that of itself cannot be fixed by mechanical solutions. This complexity arises from the fact that software development is a creative, human process. The engineer must, to write a program, conceptualize the problem space correctly and &lt;em&gt;then&lt;/em&gt; use the tools at his disposal to create a solution. As projects grow, engineers are added, the consequence of which, as Brooks keenly observed, tends to make the project &lt;em&gt;slower&lt;/em&gt; because it increases the number of communication pathways among team members (with every addition), and the conceptual foundation of the project becomes spread among many minds, in ways that are often fragmented and incorrect. This, Brooks argues, is the core problem, and the solution to the problem is to adapt to it rather than try to conquer it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Complexity &lt;em&gt;is&lt;/em&gt; the business we are in, and complexity is what limits us."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;How does one adapt to the problem of conceptual complexity in software engineering? Brooks proposed a number of solutions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conceptual integrity and communication
&lt;/h2&gt;

&lt;p&gt;Brooks proposed that the conceptual integrity of a project -- the core ideas about what the problems are and the models used to represent those problems -- are of primary importance and must be safeguarded. The most efficient way to ensure this happens is to reduce the responsibility of that integrity to one, or at most, a couple of individuals, that will be responsible for enforcing that conceptual integrity by vetting the work of other team members on the project. They become the source of conceptual truth.&lt;/p&gt;

&lt;h2&gt;
  
  
  Communication and team structure
&lt;/h2&gt;

&lt;p&gt;Because complexity scales with the number of communication pathways in a team, Brooks proposed that "surgical teams" be used in most software projects. These teams will be composed of the conceptual guardian(s) (the "surgeon"), and as &lt;em&gt;few people as possible to get the work done&lt;/em&gt;. These teams are part of an organization as a whole, however, and there is always a management structure with which they must integrate. The key to good management, according to Brooks, is to realize that management is about action and communication. The person at the top should rely on his subordinate program managers to take &lt;em&gt;action&lt;/em&gt; when needed, and he should give them the authority to do so. He should never, ever demand action when reviewing a general status report, however, because this will debilitate his program managers, and move the decision making power further from the decisions that needs to be made. Project managers should be concerned almost exclusively with managing the lines of communication in the team, and not with making decisions at all. The whole process of pushing decision making "down" to the program manager is effective because it gives program managers a stake in the total authority of the company, and therefore preserves the total authority of the company.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The purpose of organization is to reduce the amount of communication and coordination necessary..."&lt;/p&gt;

&lt;p&gt;"...the center gains in real authority by delegating power, and the organization as a whole is happier and more prosperous."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Complexity in code
&lt;/h2&gt;

&lt;p&gt;Complexity can be addressed in the code itself by reducing the mental burden a programmer has to carry while implementing code that has conceptual integrity. In the first edition of Brook's book, he insisted that all programmers on a team be familiar with all modules (or entities) within a software project. In Brooks's mind, this was a good way to safeguard the integrity of the system, because everyone would have a working understanding of all code. In a subsequent edition of the book, he backtracked on this position, because it essentially suffered from the mental equivalent of the communication problem. Code changes over time; no programmer &lt;em&gt;ever&lt;/em&gt; has a complete and accurate understanding of system because it &lt;em&gt;is not static&lt;/em&gt;. Brooks eventually came around to a view promoted by Canadian engineer David Parnas:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"[David] Parnas argues strongly that the goal of everyone seeing everything is &lt;em&gt;totally wrong&lt;/em&gt;; parts should be encapsulated so that no one needs to or is allowed to see the internals of any parts other than his own, but should see only the interfaces... [I initially proposed that] Parnas's proposal is a recipe for disaster [but] I have been quite convinced otherwise by Parnas, and totally changed my mind."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Information hiding, or encapsulation, allows a programmer to "use" or take advantage of, code, without having to know &lt;em&gt;how it works&lt;/em&gt; internally, only &lt;em&gt;how to ask it for something&lt;/em&gt;. The mental footprint of understanding an interface (the &lt;em&gt;way&lt;/em&gt; to ask code for something) is orders of magnitude smaller than the mental footprint required to understand the implementation behind the interface. And interfaces don't change nearly as often as implementations (in a well-designed system).&lt;/p&gt;

&lt;p&gt;Side-effects (changes created by a piece of code that change things beyond that code, or even system), likewise, should all be identified, well understood, and encapsulated (or eliminated) to reduce the mental burden of worrying about tangential consequences to implementations, which are often causes for bugs, and project delay.&lt;/p&gt;

&lt;h2&gt;
  
  
  Documentation
&lt;/h2&gt;

&lt;p&gt;Documentation is central to adapting to complexity. Documenting decisions made in a software project is part of fostering the creative process itself:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"...writing the decisions down is essential. Only when one writes do the gaps appear and the inconsistencies protrude. The act of writing turns out to require hundreds of mini-decisions, and it is the existence of these that distinguishes clear, exact policies from fuzzy ones."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Documentation need not be overly verbose, however; although overly verbose documentation is better than no documentation, Brooks believed. And the documentation regarding technical decisions -- design and implementation -- should be as close to the code itself as possible (even within the same code files) to ensure the documentation will be maintained and updated as the code itself changes. The goal of documentation should be twofold: 1) to create an &lt;em&gt;overview&lt;/em&gt; of the particular system concern the documentation addresses, and 2) to identify the &lt;em&gt;purpose&lt;/em&gt; (the &lt;em&gt;why&lt;/em&gt;) of the decisions made in regard to that concern. Documentation is not only for &lt;em&gt;other&lt;/em&gt; programmers to read; it is often to benefit the original author as well.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Even for the most private of programs, prose documentation is necessary, for memory will fail the user-author."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;There are many more things I could say about Mythical Man-Month. More subtle insights, longer expositions on points articulated above. But these points are the ones that stuck with me most, the ones I felt were immediately relevant to my own work and career. Mythical Man-Month is one part history, one part knowledge, and one part wisdom. It lets us know that great minds in past struggled with, devised solutions to, and retracted and revised solutions to, the problems that make programming a struggle of genuine creativity. &lt;/p&gt;

&lt;p&gt;This last quote is taken from a critique of Brooks, one that he found to be excellent, and expresses the same conclusion (albeit maybe a bit more pessimistic) at which Brooks himself arrived: the central problem of software is complexity, not tools or processes, and that will never change.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Turski, in his excellent response paper [to No Silver Bullet] at the IFIP Conference, said eloquently: 'Of all misguided scientific endeavors, none are more pathetic than the search for the philosopher's stone, a substance supposed to change base metals into gold. The supreme object of alchemy, ardently pursued by generations of researchers generously funded by secular and spiritual rulers, is an undiluted extract of wishful thinking, of the common assumption that things are as we would like them to be. It is a very human belief. It takes a lot of effort to accept the existence of insoluble problems. The wish to see a way out, against all odds, even when it is proven that it does not exist, is very, very strong. And most of us have a lot of sympathy for those courageous souls who try to achieve the impossible. And so it continues. Dissertations on squaring a circle are being written. Lotions to restore lost hair are concocted and sell well. Methods to improve software productivity are hatched and sell very well. All too often we are inclined to follow our own optimism (or exploit the hopes of our sponsors). All too often we are willing to disregard the voice of reason and heed the siren calls of panacea pushers.'"&lt;/p&gt;
&lt;/blockquote&gt;




&lt;ol&gt;
&lt;li&gt;Not to be confused with the same term used by some creationists to defend their particular ideas about the origin of the universe.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>bookreview</category>
      <category>softwaredesign</category>
      <category>mythicalmanmonth</category>
      <category>frederickbrooks</category>
    </item>
    <item>
      <title>Destructuring Reconsidered</title>
      <dc:creator>Nicholas Cloud</dc:creator>
      <pubDate>Fri, 03 May 2019 13:42:01 +0000</pubDate>
      <link>https://forem.com/nicholascloud/destructuring-reconsidered-3jdp</link>
      <guid>https://forem.com/nicholascloud/destructuring-reconsidered-3jdp</guid>
      <description>&lt;p&gt;(This post originally appeared on &lt;a href="https://www.nicholascloud.com/2019/05/destructuring-reconsidered/" rel="noopener noreferrer"&gt;nicholascloud.com&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;While working with React for the last five months, I've noticed that React developers make extensive use of object destructuring, especially in function signatures. The more I use React the less I like this trend, and here are a few, short reasons why.&lt;/p&gt;

&lt;p&gt;There are countless books by wise industry sages&lt;sup&gt;1&lt;/sup&gt; that discuss how to write good functions. Functions should do one thing, and one thing only; they should be named concisely; their parameters should be closely related; etc. My observation is that destructured function parameters tend to quickly lead to violations of these best practices.&lt;/p&gt;

&lt;p&gt;First, destructuring function parameters encourages "grab bag" functions where the destructured parameters &lt;em&gt;are unrelated&lt;/em&gt; to each other. From a practical point of view, it is the destructured properties of the &lt;em&gt;actual&lt;/em&gt; parameters that are considered, mentally, as parameters to a function. At least, the signature of a destructured function reads as if they are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;baz&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;buzz&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A developer will read this as if &lt;code&gt;bar&lt;/code&gt;, &lt;code&gt;baz&lt;/code&gt;, and &lt;code&gt;buzz&lt;/code&gt; are the actual parameters of the function (you could re-write the function this way, so they might as well be), but this is incorrect; the real parameters are &lt;code&gt;buzz&lt;/code&gt; and some other object, which, according to best practice &lt;em&gt;should&lt;/em&gt; be related to &lt;code&gt;buzz&lt;/code&gt;. But because the first parameter (param1) is destructured, we now have properties &lt;code&gt;bar&lt;/code&gt; and &lt;code&gt;baz&lt;/code&gt; which are one step removed from &lt;code&gt;buzz&lt;/code&gt;, and therefore the relationship between param1 and &lt;code&gt;buzz&lt;/code&gt; is obscured.&lt;/p&gt;

&lt;p&gt;This can go one of three ways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;if param1 and &lt;code&gt;buzz&lt;/code&gt; &lt;em&gt;are&lt;/em&gt; related, we do not know why;&lt;/li&gt;
&lt;li&gt;if param1 and &lt;code&gt;buzz&lt;/code&gt; &lt;em&gt;are not&lt;/em&gt; related (but &lt;code&gt;bar&lt;/code&gt; and &lt;code&gt;baz&lt;/code&gt; are related to &lt;code&gt;buzz&lt;/code&gt;) then the function is poorly written;&lt;/li&gt;
&lt;li&gt;if &lt;code&gt;bar&lt;/code&gt;, &lt;code&gt;baz&lt;/code&gt;, param1, and &lt;code&gt;buzz&lt;/code&gt; are all closely related, then the function is still poorly written, as it now has three "virtual parameters" instead of just two actual parameters. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Second, destructured functions encourage an excessive number of "virtual parameters". For some reason developers think this function signature is well written:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sendMail&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;address1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;sendSnailMail&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="c1"&gt;// function sendMail(user, address, mailPreferences) {}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;"But it only has three parameters!", they say. While technically true, the point of short function signatures is to scope the function to a single, tangible task and to &lt;em&gt;reduce cognitive overhead&lt;/em&gt;. For all practical purposes this function has eight parameters. And while the purpose of this function is fairly obvious based on its name, less expressive functions are far more difficult to grok.&lt;/p&gt;

&lt;p&gt;Third, destructuring makes refactoring difficult. Sure, our tools will catch up some day. But from what I've seen modern editors and IDEs cannot intelligently refactor a function signature with destructured parameters, especially in a dynamic/weak typed language like JavaScript. The IDE or editor would need to infer the parameters passed into the function by examining invocations elsewhere in code, and then infer the assignments to those parameters to determine which constructor function or object literal produced them, then rewrite the properties within those objects... and you can see how this is a near impossible feat. Or at the very least, how even the best IDEs and editors would introduce so many bugs in the process that the feature would be avoided anyway. &lt;/p&gt;

&lt;p&gt;Fourth. Often developers must trace the invocation of a function to its definition. In my experience, code bases typically have many functions &lt;em&gt;with the same name&lt;/em&gt; used in different contexts. Modern tools are smart, and examine function signatures to try and link definitions to invocations, but destructuring makes this process far more difficult. Given the following function definition, the invocations would all be valid (since JS functions are variadic), but if a code base had more than one function named &lt;code&gt;foo&lt;/code&gt;, determining which invocation is linked to which definition is something of a special nightmare.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// in the main module&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;baz&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;bin&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;buzz&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="c1"&gt;// in the bakery module&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;baz&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="c1"&gt;// invocations&lt;/span&gt;
&lt;span class="nf"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;bar&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;baz&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;anObject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;anotherObject&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nf"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;bin&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In contrast, functions with explicitly named parameters (usually the signature parameters are named the same as the variables and properties used to invoke the function) make these functions an order of magnitude easier to trace.&lt;/p&gt;

&lt;p&gt;Fifth, destructured parameters obscure the interfaces of the objects to which they belong, leaving the developer clueless as to the related properties and methods on the &lt;em&gt;actual&lt;/em&gt; parameter that might have use within the function. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What else, besides &lt;code&gt;code&lt;/code&gt; may exist in the first parameter that will allow me to more adequately "handle" whatever it is that I'm handling? The implicit assumption here is that &lt;code&gt;code&lt;/code&gt; will be all I ever need to do my job, but any developer will smirk knowingly at the naivety of that assumption. To get the information I need about this parameter I have to scour the documentation (hahahahaha documentation) in hopes that it reveals the actual parameter being passed (and doesn't just document the destructured property), or manually log the parameter to figure out what other members it possesses. Which brings me to my last point: &lt;/p&gt;

&lt;p&gt;Logging. I cannot count the number of times I have had to de-destructure a function parameter in order to log the complete object being passed to the function, because I needed to know some contextual information about that object. The same applies for debugging with breakpoints. (I love when Webpack has to rebuild my client code because I just wanted to see what actual parameter was passed to a function. Good times.)&lt;/p&gt;

&lt;p&gt;Don't get me wrong -- I'm not completely against destructuring. I actually like it quite a bit when used in a way that does not obscure code, hinder development, or hamstring debugging. Personally I avoid destructuring function parameters in the &lt;em&gt;signature&lt;/em&gt;, and instead destructure them on the first line of the function, if I want to alias properties with shorter variable names within the function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sendEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mailPreferences&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;address1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;zip&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;sendSnailMail&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;preferences&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;//...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pattern both conforms to best practices for defining functions, and also gives me a lightweight way to extract the bits of information I need from broader parameters, without making it painful to get additional information from those parameters if I need it.&lt;/p&gt;

&lt;p&gt;Don't use the new shiny just because it's what all the cool kids do. Remember the wisdom that came before, because it came at a cost that we don't want to pay again.&lt;/p&gt;




&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://www.amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882/ref=sr_1_2" rel="noopener noreferrer"&gt;Clean Code&lt;/a&gt;, &lt;a href="https://www.amazon.com/Code-Complete-Practical-Handbook-Construction/dp/0735619670/ref=pd_sbs_14_2/140-0462844-1402620" rel="noopener noreferrer"&gt;Code Complete&lt;/a&gt;, etc.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>javascript</category>
      <category>destructuring</category>
    </item>
    <item>
      <title>Creating Reusable Code</title>
      <dc:creator>Nicholas Cloud</dc:creator>
      <pubDate>Tue, 08 Jan 2019 21:49:18 +0000</pubDate>
      <link>https://forem.com/nicholascloud/creating-reusable-code-5cjp</link>
      <guid>https://forem.com/nicholascloud/creating-reusable-code-5cjp</guid>
      <description>&lt;p&gt;Create reusable software is challenging, especially when that software may be reused in situations or scenarios for which it may not necessarily have been designed. We've all had that meeting where a boss or manager asked the question: "What you've designed is great, but can we also use it &lt;em&gt;here&lt;/em&gt;?"&lt;/p&gt;

&lt;p&gt;In the last month I've had this exact experience, from which I've learned a number of valuable lessons about crafting reusable software.&lt;/p&gt;

&lt;h2&gt;
  
  
  eTexts and annotations
&lt;/h2&gt;

&lt;p&gt;When I first started working for eNotes, my initial task was to fix some code related to electronic texts that we displayed on our site (e.g., Shakespeare, Poe, Twain, etc.). We have a significant collection of annotations for many texts, and those annotations were displayed to users when highlights in the text were clicked. A couple of years ago we spun this technology off into a separate product, &lt;a href="https://www.owleyes.org" rel="noopener noreferrer"&gt;Owl Eyes&lt;/a&gt;, with additional teacher tools and classroom management features. Because of my experience with the existing eText and annotation code, and because I am primarily responsible for front-end JavaScript, I was tasked with building a &lt;a href="https://www.owleyes.org/text/hamlet/read/act-i-scene-i" rel="noopener noreferrer"&gt;"Kindle-like" experience in the browser&lt;/a&gt; for these eTexts. (This is one of the highlights of my career. The work was hard, and the edge cases were many, but it works very well across devices, and has some pretty cool features.)&lt;/p&gt;

&lt;h3&gt;
  
  
  Filtering, serializing, and fetching annotation data
&lt;/h3&gt;

&lt;p&gt;The teacher and classroom features introduced some additional challenges that were not present when the eText content was first hosted on enotes.com. First, classrooms had to be isolated from one another, meaning that if a teacher or student left an annotation in an eText &lt;em&gt;for their classroom&lt;/em&gt;, it would not be visible to anyone outside the classroom. Also, a teacher needed the ability to duplicate annotations across classrooms if they taught multiple courses with the same eText. Eventually we introduced paid subscriptions for premium features, which made annotation visibility rules even more complicated. All Owl Eyes Official annotations are available for free, public viewing, but certain premium educator annotations are restricted to paid subscribers. (Also, students in a classroom taught by a teacher with a paid subscription are considered subscribers, but &lt;em&gt;only&lt;/em&gt; within that classroom's texts!) It was complicated.&lt;/p&gt;

&lt;p&gt;We devised a strategy whereby a chain of composable rules could be applied to any set of annotations, to filter them by our business requirements. These rules implemented a simple, identical interface, and each could be passed as an argument to another to form aggregates. The filtered annotation data was then serialized as JSON and emitted onto the page server-side. When the reader renders in the client this data is deserialized and the client-side application script takes over.&lt;/p&gt;

&lt;p&gt;The role that a given user possesses in the system often determines if they can see additional meta-data related to annotations, or whether they can perform certain actions on those annotations. These were communicated to the front-end to enable/disable features as needed, and then enforced on the back-end should a clever user attempt to subvert the limitations of his own role. To keep the data footprint as light as possible on the page, we developed a composable serialization scheme that could be applied to any entity in our application. The generic serialization classes break down an entity's data into a JSON structure, while more specialized serialization classes add or remove data based on a user's role and permissions. In this way a given annotation might contain meta-data of interest to teachers, but would exclude that meta-data for students. Additional information is added if the user is an administrator, to give them better control over the data on the front-end.&lt;/p&gt;

&lt;p&gt;The end result is that, from a user's perspective, the annotations visible to them, and the data within those annotations, are tailor-made to the user when the eText reader is opened.&lt;/p&gt;

&lt;p&gt;Fast-forward to the present day. I have recently been tasked with bringing our eTexts and annotations full circle, back to enotes.com. We brainstormed about the best way to make this happen, as enotes.com lacks the full eText and annotation data, as well as the rich front-end reading experience.&lt;/p&gt;

&lt;p&gt;We decided that since the eText and annotation data was already being serialized as JSON for client-side consumption in owleyes.org, it would be trivial to make that same data available via an API. I implemented a simple controller that made use of Symfony's authentication mechanisms for authenticating signed requests via API key pair, and returned annotation JSON data in the exact same manner that would be used for rendering that data in the eText reader. On inspection, I realized that some of the annotation data wasn't relevant to what we wanted to display on enotes.com, so I quickly created new serialization classes that made use of existing serialization classes, but plucked unwanted data from their generated JSON structures before returning it. No changes were necessary to the annotation filtering rules, as an API user is, from the ruleset's perspective, a "public user", and so would see the same annotation data that users who aren't logged in on the site would see.&lt;/p&gt;

&lt;p&gt;Fetching this data on enotes.com was a simple matter of using PHP's CURL classes to request data from the owleyes.org endpoint.&lt;/p&gt;

&lt;h3&gt;
  
  
  The user interface
&lt;/h3&gt;

&lt;p&gt;The eText reader JavaScript code on owleyes.org is complex; it is composed of many different modules -- view modules, state modules, utility modules, messaging modules, etc. -- that interact together to form a smooth, reading experience. It is far more interactive than the pages we wanted to display on enotes.com, so I initially worried that the code would not be entirely reusable because of its complexity.&lt;/p&gt;

&lt;p&gt;I was pleasantly wrong.&lt;/p&gt;

&lt;p&gt;When I write software I take great pains to decouple code, favor composition over inheritance, and observe clear, strict, and course API boundaries in my modules and classes. I, as every programmer does, have a particular "style" of programming -- the way I think about and model problems -- which, in this case, served me very well.&lt;/p&gt;

&lt;p&gt;I copied modules from the owleyes.org codebase into the enotes.com codebase that I &lt;em&gt;knew&lt;/em&gt; would be necessary for the new eText pages to function. With some minor adjustments (mostly related to DOM element identifiers and classes) the code worked almost flawlessly. Where I needed to introduce new code (we're using a popup to display annotations in enotes.com, whereas in owleyes.org we use a footer "flyout" that cycles through annotations in a carousel) the APIs in existing code were so well defined that I was able to adapt to them with few issues. Where differing page behavior was desired (e.g., the annotation popup shifts below the annotation when it gets too close to the top of the screen as the reader scrolls, and above otherwise) the decoupled utility modules that track window and page state already provided me with the events and information I needed to painlessly implement those behaviors. And because the schema of the serialized annotation data delivered over the API was identical to the JSON data embedded in the owleyes.org reader, the modules that filtered, sorted, and otherwise manipulated that data did not change at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it worked
&lt;/h2&gt;

&lt;p&gt;Needless to say, this project left me very satisfied as a developer. When your code is painlessly reused in other contexts it means you've something right. I've made some observation about what made this reuse possible.&lt;/p&gt;

&lt;p&gt;First, reusable code should model a problem, or a system, in such a way that the constituent components of that model can act together, or be used in isolation, without affecting the other parts of the model. Modules, classes, and functions are the tangible building blocks we use to express these models in software, and they should correspond with the way we &lt;em&gt;think&lt;/em&gt; about these models in our heads. Each should be named appropriately, corresponding to some concept in the model, and the connections between them should be well understood and obvious. For example, in the eText reader, a tooltip is a highlighted portion of text that may be clicked on to display an annotation popup, which displays annotation information. The tooltip and annotation popup are components in the &lt;em&gt;visual&lt;/em&gt; model; they are named appropriately, and the relationship between them is one-way, from tooltip to popup.&lt;/p&gt;

&lt;p&gt;Second, a given problem may in fact be composed of &lt;em&gt;multiple&lt;/em&gt; models that are being run at the same time. Modules that control the UI are part of the &lt;em&gt;visual&lt;/em&gt; or &lt;em&gt;display&lt;/em&gt; model; modules that control the access to, and filtering of, data are part of the &lt;em&gt;domain&lt;/em&gt; model. Modules track mouse movements, or enable/disable features based on user interaction, are part of the &lt;em&gt;interaction&lt;/em&gt; model. Within these models, objects or modules should only perform work that makes sense within the purpose of the model. Objects in the &lt;em&gt;visual&lt;/em&gt; model should not apply business rules to data, for example. When one or more objects exhibit behaviors from multiple models, extracting and encapsulating the behavior that is not part of each object's primary model makes that object more reusable.&lt;/p&gt;

&lt;p&gt;Third, objects within a model should have well-defined, &lt;em&gt;coarse&lt;/em&gt; APIs. (In the context of objects, an API is an object's "public" methods to outside callers, or to the objects that extend it.) A coarse API is one that provides the least amount of functionality that its responsibilities require. Yes, the &lt;em&gt;least&lt;/em&gt;. An object either stands alone, or makes use of other objects to do its work. If the methods on an object are numerous the object can likely be broken down into several smaller objects to which it will delegate and on which it will depend to do its work internally. Ask: what abstraction does this object represent, and which methods fulfill that abstraction. Likewise the parameters to an object's methods can often be reduced by passing known state to the object's constructor (or factory function, or whatever means are used to create the object). This chains the behavior of the object to a predetermined state -- all remaining method arguments are only &lt;em&gt;augmentations&lt;/em&gt; to this state. If the state needs to change, another object of the same type, with different state, is created and used in its stead. The API is coarse because the methods are few, and their parameters are sparse.&lt;/p&gt;

&lt;p&gt;Fourth an object's state should be stable at all times. Its initial state should be set, completely, through the object's source of construction (whether by data provided via parameters, or sensible defaults, or both). Properties on objects should be considered read-only, as they represent a "window" into the object's state. Computed properties should be calculated whenever an object's relevant internal state changes, usually the result of a method invocation. I avoid exposing objects that can be manipulated by reference through properties; properties are always primatves that can be re-produced or re-calculated, or collections of other "data" objects that have the same characteristics (usually cloned or reduced from some other source). If an object needs to expose information from one of its internal children, I copy that information from the internal source to a primitive property on the external object itself. If the information is itself in the form of an object with multiple properties, I flatten those into individual properties on the external object. The end result is that an object's state is always generated internally, as a consequence of method invocations, and cannot be manipulated externally, except by way of its public API (methods).&lt;/p&gt;

&lt;p&gt;Finally, shared data should exist in "bags" -- objects that jealously guard data and only &lt;em&gt;deliver&lt;/em&gt; data by value to callers when asked. For example, on owleyes.org a given chapter in Hamlet may contain hundreds of annotations. Annotations may be crated, edited, deleted, and receive replies in client code. The annotation bag is responsible for holding the annotation data and delivering it, in read-only format, to other modules as requested so that they can render themselves (or perform computations) accordingly. When an annotation &lt;em&gt;changes&lt;/em&gt; -- when an owleyes.org PUT request is sent to the API and a successful response is received -- a method on the bag is invoked to update the annotation. Because annotations are only fetched by value, it does no good for the module that initiated the update to directly manipulate the properties on its own annotation object. No other module will receive the change. Instead, the responsible module tells the bag to update the annotation by passing it the new annotation deserialized from the API response. The bag replaces the annotation in its internal collection and then raises an event to notify listening modules that the given annotation has changed. Any module interested in that annotation -- or all annotations -- then requests the updated data (in read-only format) and re-renders itself (or re-computes its internal state). The bag, then, is the shared resource among modules (&lt;em&gt;not&lt;/em&gt; the data, directly) and it is the source of Truth for all data requests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Epilogue
&lt;/h2&gt;

&lt;p&gt;There is more I could say on the patterns and principles that arose during the execution of this project, but those enumerated above were of the most import and consequence while porting existing code into its new context. Reusable code is not easy to write. It is not automatic. It is the result of thought and discipline that slowly become habit as exercised.&lt;/p&gt;

&lt;p&gt;Not all code will be reused; most won't, in fact. But writing code with a view of extension and reuse in mind can pay off in time and effort in the long run. This is a trade-off, though. The more reusable code tends to be, the more layers of redirection it will possess, necessitating an increase in the number of modules, classes, functions, etc. that need be created. This is a trade-off that can be mitigated by keeping code as &lt;em&gt;simple&lt;/em&gt; as possible. Code can be navigated with relative ease if one can reason about it, divining what modules (etc.) do and how they are related through inference.&lt;/p&gt;

&lt;p&gt;While I can't guarantee your experience will be as pleasant as mine, I do believe that if you think about and put these patterns and principles into action you will one day experience the joy of &lt;em&gt;truthfully&lt;/em&gt; telling your manager, "oh, that will only take two weeks!" because your diligence produced well-crafted, reusable code.&lt;/p&gt;




&lt;p&gt;This article originally appeared at &lt;a href="https://www.nicholascloud.com/2018/12/creating-reusable-code/" rel="noopener noreferrer"&gt;nicholascloud.com&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>code</category>
      <category>reuse</category>
    </item>
    <item>
      <title>Using the find command</title>
      <dc:creator>Nicholas Cloud</dc:creator>
      <pubDate>Fri, 09 Nov 2018 21:30:40 +0000</pubDate>
      <link>https://forem.com/nicholascloud/using-the-find-command-5ffp</link>
      <guid>https://forem.com/nicholascloud/using-the-find-command-5ffp</guid>
      <description>&lt;p&gt;The &lt;code&gt;find&lt;/code&gt; command is used to recursively locate files in a directory hierarchy. Since programmers and system administrators spend a great deal of time working with files, familiarity with this command can make each more efficient at the terminal.&lt;/p&gt;

&lt;p&gt;The command is composed of four parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the command name&lt;/li&gt;
&lt;li&gt;options that control &lt;em&gt;how&lt;/em&gt; the command searches (optional)&lt;/li&gt;
&lt;li&gt;the path(s) to search (required)&lt;/li&gt;
&lt;li&gt;expressions (composed of "primaries) and "operators" that filter files by name (optional)&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;A primary is a switch, such as &lt;code&gt;-name&lt;/code&gt; or &lt;code&gt;-regex&lt;/code&gt; that may or may not have additional arguments. An operator is a primary such as &lt;code&gt;-or&lt;/code&gt; and &lt;code&gt;-not&lt;/code&gt; that combines expressions in logical ways.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h1&gt;
  
  
  Basic usage
&lt;/h1&gt;

&lt;p&gt;Finding files by name is perhaps the most common use of the &lt;code&gt;find&lt;/code&gt; command. Its output consists of all paths in which the file name appears within the directory structure it searches.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ find . -name README.md
./node_modules/hexo-renderer-stylus/README.md
./node_modules/is-extendable/README.md
./node_modules/striptags/README.md
...
./README.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: All terminal examples were generated within the directory structure of my blog, created by the &lt;a href="https://hexo.io/" rel="noopener noreferrer"&gt;Hexo&lt;/a&gt; static site generator.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The path given to &lt;code&gt;find&lt;/code&gt; in the first argument is the path prepended to each result. The &lt;code&gt;.&lt;/code&gt; path instructs &lt;code&gt;find&lt;/code&gt; to search under the working directory and generate relative paths in the output. If an absolute path to the working directory is used, however, the full path will appear in results. Command substitution may be used to strike a compromise between brevity and more detailed output.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ find `pwd` -name README.md
/Users/nicholascloud/projects/nicholascloud.com/node_modules/hexo-renderer-stylus/README.md
/Users/nicholascloud/projects/nicholascloud.com/node_modules/is-extendable/README.md
/Users/nicholascloud/projects/nicholascloud.com/node_modules/striptags/README.md
...
/Users/nicholascloud/projects/nicholascloud.com/README.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a filtering expression is omitted, &lt;code&gt;find&lt;/code&gt; will return &lt;em&gt;all file paths&lt;/em&gt; within its search purview.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ find .
.
./scaffolds
./scaffolds/draft.md
./scaffolds/post.md
./scaffolds/page.md
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Other scenarios
&lt;/h1&gt;

&lt;p&gt;The &lt;code&gt;find&lt;/code&gt; command can do much more than locate files by exact name, however. It can find files according to a wide swath of criteria in many different scenarios.&lt;/p&gt;

&lt;h2&gt;
  
  
  We may not know the exact name of the file for which we are searching
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;-name&lt;/code&gt; primary supports wildcard searches.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an asterisk (&lt;code&gt;*&lt;/code&gt;) can replace any consecutive number of characters&lt;/li&gt;
&lt;li&gt;a question mark (&lt;code&gt;?&lt;/code&gt;) can replace any single character
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ find . -name '*javascript*.md'
./source/_posts/javascript-frameworks-for-modern-web-dev.md
./source/_posts/l33t-literals-in-javascript.md
./source/_posts/historical-javascript-objects.md
./source/_posts/maintainable-javascript-book-review.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default, the &lt;code&gt;-name&lt;/code&gt; primary is case-sensitive. To conduct a case-*in*sensitive search, we can use the &lt;code&gt;-iname&lt;/code&gt; primary.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ find . -iname '*JAVA*.md'
./source/_posts/java-4-ever.md
./source/_posts/javascript-frameworks-for-modern-web-dev.md
./source/_posts/l33t-literals-in-javascript.md
./source/_posts/historical-javascript-objects.md
./source/_posts/maintainable-javascript-book-review.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For more complex searches, we can use the power of regular expressions with the &lt;code&gt;-regex&lt;/code&gt; and &lt;code&gt;-iregex&lt;/code&gt; (case-insensitive) primaries.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; Use the &lt;code&gt;-E&lt;/code&gt; option to specify that &lt;em&gt;extended&lt;/em&gt; regular expressions should be used instead of &lt;em&gt;basic&lt;/em&gt; regular expressions.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; $ find -E . -regex '.*package(-lock)?\.json'
./node_modules/hexo-renderer-stylus/package.json
./node_modules/is-extendable/package.json
./node_modules/striptags/package.json
./node_modules/babylon/package.json
...
./package-lock.json
./package.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  We may want to limit our results to a specific path, or a pattern that matches multiple paths
&lt;/h2&gt;

&lt;p&gt;To filter results by a path mask, we can specify a pattern with &lt;code&gt;-path&lt;/code&gt; and &lt;code&gt;-ipath&lt;/code&gt; (case-insensitive). Both support the asterisk and question mark wildcards.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ find . -path './node_modules/*/lib/*' -regex '.*hexo.*'
./node_modules/hexo-renderer-stylus/lib/renderer.js
./node_modules/hexo-renderer-marked/lib/renderer.js
./node_modules/hexo-generator-archive/lib/generator.js
./node_modules/hexo-migrator-wordpress/node_modules/async/lib/async.js
./node_modules/hexo-log/lib/log.js
./node_modules/hexo-generator-category/lib/generator.js
./node_modules/hexo-i18n/lib/i18n.js
./node_modules/hexo-pagination/lib/pagination.js
./node_modules/hexo-generator-index/lib/generator.js
./node_modules/hexo-util/lib/pattern.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using the &lt;code&gt;-path&lt;/code&gt; primary does not change the top-level directory in which &lt;code&gt;find&lt;/code&gt; begins its search; it merely filters results by sub-directory.&lt;/p&gt;

&lt;h2&gt;
  
  
  We may want detailed information about the files we find
&lt;/h2&gt;

&lt;p&gt;To see detailed information about a file, in a manner similar to &lt;a href="http://man7.org/linux/man-pages/man1/ls.1.html" rel="noopener noreferrer"&gt;&lt;code&gt;ls -l&lt;/code&gt;&lt;/a&gt;, the &lt;code&gt;-ls&lt;/code&gt; primary may be appended to the list of primaries.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ find . -name *.md -path '*/_posts/*' -ls
8636130637  8 -rw-r--r--  1 nicholascloud  staff  490 Oct 24 12:25 ./source/_posts/strange-loop-2010-video-release-schedule-posted.md
8637286945  8 -rw-r--r--  1 nicholascloud  staff  1570 Oct 24 12:45 ./source/_posts/god-mode-in-windows-7-not-as-cool-as-rise-of-the-triad.md
8636130716  8 -rw-r--r--  1 nicholascloud  staff  204 Oct 24 12:25 ./source/_posts/what-writing-fiction-taught-me-about-writing-software.md
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(As an alternative to the &lt;code&gt;-ls&lt;/code&gt; primary, the &lt;code&gt;-exec&lt;/code&gt; primary may be used to invoke &lt;a href="http://man7.org/linux/man-pages/man1/ls.1.html" rel="noopener noreferrer"&gt;&lt;code&gt;ls&lt;/code&gt;&lt;/a&gt;, or the &lt;a href="http://man7.org/linux/man-pages/man1/xargs.1.html" rel="noopener noreferrer"&gt;&lt;code&gt;xargs&lt;/code&gt;&lt;/a&gt; command may be used for the same purpose.)&lt;/p&gt;

&lt;h2&gt;
  
  
  We may want to stop descending into a hierarchy once we've found the file(s) for which we've searched
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;-prune&lt;/code&gt; primary causes &lt;code&gt;find&lt;/code&gt; to stop traversing a particular directory path once it has found a result that matches its expression. It will, however, continue to search &lt;em&gt;at the same directory level&lt;/em&gt; as a found result for other potential matches.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; $ find . -regex '.*middleware.*' -prune
./source/_posts/new-appendto-blog-post-streams-and-middleware-in-strata-js.md
./node_modules/stylus/lib/middleware.js
./node_modules/hexo-server/lib/middlewares
./public/2013/06/new-appendto-blog-post-streams-and-middleware-in-strata-js
./.deploy_git/2013/06/new-appendto-blog-post-streams-and-middleware-in-strata-js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By using the &lt;code&gt;diff&lt;/code&gt; tool and IO redirection we can compare the output of a "pruned" result set with the output of unpruned results to see what paths were omitted. For example, in the diff below, the remaining paths that matched &lt;code&gt;/node_modules/hexo-server/lib/middlewares/*&lt;/code&gt; were omitted once &lt;code&gt;/node_modules/hexo-server/lib/middlewares&lt;/code&gt; had been added to the result set.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ diff &amp;lt;(find . -regex '.*middleware.*') &amp;lt;(find . -regex '.*middleware.*' -prune)
4,9d3
&amp;lt; ./node_modules/hexo-server/lib/middlewares/route.js
&amp;lt; ./node_modules/hexo-server/lib/middlewares/redirect.js
&amp;lt; ./node_modules/hexo-server/lib/middlewares/logger.js
&amp;lt; ./node_modules/hexo-server/lib/middlewares/gzip.js
&amp;lt; ./node_modules/hexo-server/lib/middlewares/header.js
&amp;lt; ./node_modules/hexo-server/lib/middlewares/static.js
11d4
&amp;lt; ./public/2013/06/new-appendto-blog-post-streams-and-middleware-in-strata-js/index.html
13d5
&amp;lt; ./.deploy_git/2013/06/new-appendto-blog-post-streams-and-middleware-in-strata-js/index.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  We may only want to search &lt;em&gt;to&lt;/em&gt; a particular depth OR search &lt;em&gt;beyond&lt;/em&gt; a particular depth
&lt;/h2&gt;

&lt;p&gt;Several primaries control depth traversal, or how far &lt;code&gt;find&lt;/code&gt; will go to locate results.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;-maxdepth&lt;/code&gt; controls the path depth &lt;em&gt;to which&lt;/em&gt; &lt;code&gt;find&lt;/code&gt; will traverse before stopping.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ find . -name *.css -maxdepth 3
./public/fancybox/jquery.fancybox.css
./public/css/style.css
./.deploy_git/fancybox/jquery.fancybox.css
./.deploy_git/css/style.css
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;-mindepth&lt;/code&gt; controls the path depth &lt;em&gt;at which&lt;/em&gt; &lt;code&gt;find&lt;/code&gt; will start to search.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ find . -name *.css -mindepth 6
./node_modules/hexo/node_modules/hexo-cli/assets/themes/landscape/source/fancybox/jquery.fancybox.css
./node_modules/hexo/node_modules/hexo-cli/assets/themes/landscape/source/fancybox/helpers/jquery.fancybox-thumbs.css
./node_modules/hexo/node_modules/hexo-cli/assets/themes/landscape/source/fancybox/helpers/jquery.fancybox-buttons.css
./themes/landscape/source/fancybox/helpers/jquery.fancybox-thumbs.css
./themes/landscape/source/fancybox/helpers/jquery.fancybox-buttons.css
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;-depth&lt;/code&gt; specifies the &lt;em&gt;exact&lt;/em&gt; depth at which &lt;code&gt;find&lt;/code&gt; will search.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ find . -name *.css -depth 5
./node_modules/async-limiter/coverage/lcov-report/prettify.css
./node_modules/async-limiter/coverage/lcov-report/base.css
./themes/landscape/source/fancybox/jquery.fancybox.css
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  We may want to find files that are newer/older relative to another file
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;-newer&lt;/code&gt; primary will find files that are newer than the specified file by comparing the modification times of each.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ls -l source/_drafts/
-rw-r--r--  1 nicholascloud  staff  189 Nov    7 11:51:20 2018 the-importance-of-names.md
-rw-r--r--  1 nicholascloud  staff  353 Nov    7 11:50:49 2018 the-most-satisfying-thing.md
-rw-r--r--  1 nicholascloud  staff  10812 Nov  8 19:13:09 2018 using-the-find-command.md

$ find . -newer source/_drafts/the-importance-of-names.md -path '*_drafts*'
./source/_drafts/using-the-find-command.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For more fine-grained control, use the &lt;code&gt;-newer[XY]&lt;/code&gt; primary, where values of &lt;code&gt;X&lt;/code&gt; and &lt;code&gt;Y&lt;/code&gt; represent different kinds of file timestamps (see table below). The timestamp for &lt;code&gt;X&lt;/code&gt; applies to the files that &lt;code&gt;find&lt;/code&gt; evaluates; that of &lt;code&gt;Y&lt;/code&gt; applies to the file path argument supplied for comparison&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;
&lt;code&gt;X&lt;/code&gt;/&lt;code&gt;Y&lt;/code&gt; flags&lt;/th&gt;
&lt;th&gt;value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;a&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;access time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;B&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;inode creation time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;c&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;change time (file attributes)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;m&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;modification time (file contents)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;t&lt;/code&gt; (&lt;code&gt;y&lt;/code&gt; only)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;file&lt;/code&gt; is interpreted as a date understood by &lt;code&gt;cvs(1)&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For example, the command &lt;code&gt;find . -neweram foo.txt&lt;/code&gt; will find all files that have a newer &lt;em&gt;access time&lt;/em&gt; than the &lt;em&gt;modification time&lt;/em&gt; of &lt;code&gt;foo.txt&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For each &lt;code&gt;X&lt;/code&gt; flag there are shortcut primaries that make a comparison against the modification time of the file argument.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-anewer&lt;/code&gt; compares the access time of each file in the result set to the modification time of the specified file.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-bnewer&lt;/code&gt; compares the inode creation time of each file in the result set to the modification time of the specified file.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-cnewer&lt;/code&gt; compares the change time of each file in the result set to the modification time of the specified file.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-mnewer&lt;/code&gt; compares the modification time of each file in the result set to the modification time of the specified file, and is identical to &lt;code&gt;-newer&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  We may want to search for a particular kind of "file" (file, directory, symlink, etc.)
&lt;/h2&gt;

&lt;p&gt;In Unix-like systems, &lt;a href="https://en.wikipedia.org/wiki/Everything_is_a_file" rel="noopener noreferrer"&gt;"everything is a file"&lt;/a&gt;, and these files have types. The &lt;code&gt;find&lt;/code&gt; command can detect file type, and filter results accordingly. Regular files (for which we search most often) have a type of &lt;code&gt;f&lt;/code&gt;; directories have a type of &lt;code&gt;d&lt;/code&gt;. Block files -- disks, for example -- have a type of &lt;code&gt;b&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In OSX it is easy to find all block files that represent disks (physical and logical).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ find /dev -name 'disk*' -type b
/dev/disk0
/dev/disk0s1
/dev/disk0s2
/dev/disk1
/dev/disk1s2
/dev/disk1s3
/dev/disk1s1
/dev/disk1s4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The table below lists each file type that the &lt;code&gt;find&lt;/code&gt; command may detect.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Flag&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;block special&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;c&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;character special&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;d&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;directory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;f&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;regular file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;l&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;symbolic link&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;p&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;FIFO&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;s&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;socket&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  We may want to search for files that a particular user or group owns (or inversely, that are not owned by a known user or group)
&lt;/h2&gt;

&lt;p&gt;Users and groups are identified by name and numeric ID on Unix-like systems. In OSX the &lt;code&gt;id&lt;/code&gt; command tells me my user ID and group ID(s).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ id
uid=501(nicholascloud) gid=20(staff)...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;find&lt;/code&gt; command accepts primaries that filter file results by user and/or group.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-uid &amp;lt;uid&amp;gt;&lt;/code&gt; and &lt;code&gt;-user &amp;lt;username&amp;gt;&lt;/code&gt; filter results by owning user. If the argument to &lt;code&gt;-user&lt;/code&gt; is numeric, and no group exists with that name, it is assumed to be a user ID.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-gid &amp;lt;id&amp;gt;&lt;/code&gt; and &lt;code&gt;-group &amp;lt;groupname&amp;gt;&lt;/code&gt;  filter results by owning group. The same caveat applies to &lt;code&gt;groupname&lt;/code&gt; as &lt;code&gt;username&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I write code for a website called OwlEyes.org, which is a PHP application served by the apache2 web server. If I search for files in my home directory owned by the &lt;code&gt;www-data&lt;/code&gt; user (the typical apache2 user), I see some interesting results.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ find . -user www-data
./projects/enotes/owleyesorg/app/logs/apache-error.log
./projects/enotes/owleyesorg/app/logs/apache-custom.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every other file in my project directory is owned by my user, but the apache2 log files are written by the web server, and are therefore owned by its user.&lt;/p&gt;

&lt;p&gt;To find files that aren't owned by any known user and/or group, the inverse primaries may be used.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-nouser &amp;lt;username&amp;gt;&lt;/code&gt; shows results that &lt;em&gt;do not&lt;/em&gt; belong to a known user.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-nogroup &amp;lt;groupname&amp;gt;&lt;/code&gt; shows results that &lt;em&gt;do not&lt;/em&gt; belong to a known group.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  We may want to find empty files or directories
&lt;/h2&gt;

&lt;p&gt;To find empty (0 byte files or directories with no files) files append the &lt;code&gt;-empty&lt;/code&gt; primary to the &lt;code&gt;find&lt;/code&gt; command. This can be useful, for example, to see what log files are empty on your system.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ sudo find /var/log -empty
./appfirewall.log
./ppp
./alf.log
./apache2
./com.apple.xpc.launchd
./cups
./CoreDuet
./uucp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  We may want to run a utility on the files identified by &lt;code&gt;find&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;-exec&lt;/code&gt; and &lt;code&gt;-ok&lt;/code&gt; primaries may be used to run a command on each file in &lt;code&gt;find&lt;/code&gt;'s result set. The two primaries are identical but &lt;code&gt;-ok&lt;/code&gt; will request user confirmation for each file before executing the specified command.&lt;/p&gt;

&lt;p&gt;The syntax for executing a command with &lt;code&gt;find&lt;/code&gt; is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;find &amp;lt;expression(s)&amp;gt; -exec &amp;lt;command&amp;gt; \;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The command is written in standard form, as you would type it in a terminal. If the string &lt;code&gt;'{}'&lt;/code&gt; appears anywhere in the command, it will be replaced by the file path of each result as find iterates over them. Commands &lt;em&gt;must&lt;/em&gt; be terminated by a &lt;code&gt;\;&lt;/code&gt;. (The escape character is necessary when executing within a shell environment.)&lt;/p&gt;

&lt;p&gt;The command &lt;code&gt;find . -newer db.json -type f -exec cp '{}' ~/tmp \;&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;starts in the current directory&lt;/li&gt;
&lt;li&gt;finds files that were modified after &lt;code&gt;db.json&lt;/code&gt; (the database file that stores blog post information)&lt;/li&gt;
&lt;li&gt;finds files of type "regular file"&lt;/li&gt;
&lt;li&gt;and copies each one to &lt;code&gt;~/tmp&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ls -lh db.json
-rw-r--r--  1 nicholascloud  staff   2.6M Nov  8 19:15 db.json

$ find . -newer db.json -type f -exec cp '{}' ~/tmp \;

$ ls -l ~/tmp
-rw-r--r--   1 nicholascloud  staff    61B Nov  9 10:46 README.md
-rw-r--r--   1 nicholascloud  staff    14K Nov  9 10:46 using-the-find-command.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two corresponding primaries, &lt;code&gt;-execdir&lt;/code&gt; and &lt;code&gt;-okdir&lt;/code&gt; do the same thing as &lt;code&gt;-exec&lt;/code&gt; and &lt;code&gt;-ok&lt;/code&gt;, however &lt;code&gt;'{}'&lt;/code&gt; is replaced with &lt;em&gt;as many file paths as possible from the result set&lt;/em&gt;, making these primaries akin to &lt;a href="http://man7.org/linux/man-pages/man1/xargs.1.html" rel="noopener noreferrer"&gt;&lt;code&gt;xargs&lt;/code&gt;&lt;/a&gt;. For example, to archive files in a &lt;code&gt;find&lt;/code&gt; result set, one could use &lt;code&gt;-execdir&lt;/code&gt; to create a tarball.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ find . -newer db.json -type f -execdir tar cvzf ~/tmp/back.tar.gz '{}' \;
a using-the-find-command.md
a README.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  We may want to format &lt;code&gt;find&lt;/code&gt;'s output
&lt;/h2&gt;

&lt;p&gt;The output from &lt;code&gt;find&lt;/code&gt; can be formatted in two ways.&lt;/p&gt;

&lt;p&gt;By specifying the &lt;code&gt;-print&lt;/code&gt; primary, the file path of each result in &lt;code&gt;find&lt;/code&gt;'s result set is printed to standard output, terminated by a newline. This is the way &lt;code&gt;find&lt;/code&gt; displays results by default. However, some primaries, such as &lt;code&gt;-exec&lt;/code&gt;, might &lt;em&gt;not&lt;/em&gt; print each file to the terminal. The command &lt;code&gt;find . -newer db.json -type f -print -exec cp '{}' ~/tmp \;&lt;/code&gt; will copy all files newer than &lt;code&gt;db.json&lt;/code&gt; to &lt;code&gt;~/tmp&lt;/code&gt;, but the output will remain empty (the default behavior of the &lt;code&gt;cp&lt;/code&gt; command). To force each file to be displayed, the &lt;code&gt;-print&lt;/code&gt; primary may be added before &lt;code&gt;-exec&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ find . -newer db.json -type f -print -exec cp '{}' ~/tmp \;
./source/_drafts/using-the-find-command.md
./README.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;-print0&lt;/code&gt; primary creates a space-delimited string of all file paths, and can be useful when piping the output of &lt;code&gt;find&lt;/code&gt; to &lt;a href="http://man7.org/linux/man-pages/man1/xargs.1.html" rel="noopener noreferrer"&gt;&lt;code&gt;xargs&lt;/code&gt;&lt;/a&gt; or some similar command that expects  input in such a format.&lt;/p&gt;

&lt;h2&gt;
  
  
  We may want to combine expressions to refine our search
&lt;/h2&gt;

&lt;p&gt;By default primaries are combined and applied together to form an expression, but &lt;code&gt;find&lt;/code&gt; supports two operators that change the way expressions are applied. If two expressions are separated by the &lt;code&gt;-or&lt;/code&gt; operator, then they will be applied in a boolean &lt;code&gt;OR&lt;/code&gt; fashion; results will be returned that match either expression, or both.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ find . -name '*eclipse*' -or -name '*clean*'
./source/images/2011/10/eclipse-example.png
./source/images/2011/10/eclipse-example-150x127.png
...
./source/images/2011/08/clean-coders1.png
./source/images/2011/08/clean-coders-150x117.png
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the &lt;code&gt;-not&lt;/code&gt; (or &lt;code&gt;!&lt;/code&gt;) operator precedes an expressison, it will negate it and remove matching file paths from the result set.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ find . -name '*eclipse*'
./source/images/2011/10/eclipse-example.png
./source/images/2011/10/eclipse-example-150x127.png
./source/images/2011/10/eclipse-example-2-300x97.png
./source/images/2011/10/eclipse-example-2.png

$ find -E . -name '*eclipse*' ! -regex '.*[0-9]+x[0-9]+.*'
./source/images/2011/10/eclipse-example.png
./source/images/2011/10/eclipse-example-2.png
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Recall that the &lt;code&gt;-E&lt;/code&gt; option in the example above forces &lt;code&gt;find&lt;/code&gt; to use extended regular expressions when evaluating the &lt;code&gt;-regex&lt;/code&gt; primary.)&lt;/p&gt;

&lt;h2&gt;
  
  
  We may want to delete found files
&lt;/h2&gt;

&lt;p&gt;While possible to use &lt;code&gt;-execdir rm '{}' \;&lt;/code&gt; to delete files in a result set, &lt;code&gt;find&lt;/code&gt; supports a shorter primary, &lt;code&gt;-delete&lt;/code&gt; that accomplishes the same task. By default, &lt;code&gt;-delete&lt;/code&gt; will not show output for each file that is removed; use the &lt;code&gt;-print&lt;/code&gt; primary in conjunction with &lt;code&gt;-delete&lt;/code&gt; to see which files were removed from the file system.&lt;/p&gt;

</description>
      <category>find</category>
      <category>unix</category>
      <category>linux</category>
      <category>shell</category>
    </item>
  </channel>
</rss>
