Jekyll2021-01-04T22:28:51+00:00https://socsieng.github.io/feed.xmlSoc’s BlogMusings of an old Software Engineer.Released: sendkeys 2.02021-01-04T21:47:58+00:002021-01-04T21:47:58+00:00https://socsieng.github.io/tools/2021/01/04/released-sendkeys-2-0<p>I’m happy to announce the release of <a href="https://github.com/socsieng/sendkeys"><code class="language-plaintext highlighter-rouge">sendkeys 2.0</code></a>. 🚀🎉!</p>
<p>This is a major release and includes the following:</p>
<ul>
<li>Rewrite from JavaScript to Swift.</li>
<li>Installable via <a href="https://brew.sh/">homebrew</a>.</li>
<li><strong>Support for automating mouse events!!!</strong></li>
</ul>
<h2 id="installation">Installation</h2>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>brew <span class="nb">install </span>socsieng/tap/sendkeys
</code></pre></div></div>
<h2 id="example-usage">Example usage</h2>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sendkeys <span class="nt">--application-name</span> <span class="s2">"Notes"</span> <span class="nt">--characters</span> <span class="s2">"Hello<p:1> world<c:left:option,shift><c:i:command>"</span>
</code></pre></div></div>
<p><img src="https://github.com/socsieng/sendkeys/raw/main/docs/images/example1.gif" alt="hello world example" /></p>
<p><em>Activates the Notes application and types <code class="language-plaintext highlighter-rouge">Hello</code> (followed by a 1 second pause) and <code class="language-plaintext highlighter-rouge">world</code>, and then selects the word
<code class="language-plaintext highlighter-rouge">world</code> and changes the font to italics with <code class="language-plaintext highlighter-rouge">command</code> + <code class="language-plaintext highlighter-rouge">i</code>.</em></p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sendkeys <span class="nt">-c</span> <span class="s2">"<m:100,300,300,300:0.5><p:0.5><m:100,300:0.5>"</span>
</code></pre></div></div>
<p><img src="https://github.com/socsieng/sendkeys/raw/main/docs/images/mouse.gif" alt="mouse move example" /></p>
<p><em>Moves the mouse cursor from (100, 300) to (300, 300), pausing for 0.5 seconds and moving back to (100, 300).</em></p>
<h2 id="why-rewrite">Why rewrite?</h2>
<p>You might be wondering about why anyone would bother rewriting anything from JavaScript to Swift. In my case, it was to
provide mouse automation capabilties and overall simplification. With the rewrite, I was able to unify how keyboard and
mouse events are dispatched to macOS using Quartz instead of AppleScript (<code class="language-plaintext highlighter-rouge">osascript</code> for keyboard) and Quartz for
mouse.</p>
<h2 id="prerequisites">Prerequisites</h2>
<p><code class="language-plaintext highlighter-rouge">sendkeys</code> requires macOS 10.11 or later.</p>
<p>When running from the terminal, ensure that the terminal has permission to use accessibility features. This can be done
by navigating to System Preferences > Security & Privacy > Privacy > Accessibility and adding your terminal application
there.</p>
<p><img src="https://github.com/socsieng/sendkeys/raw/main/docs/images/accessibility.gif" alt="accessibility settings" /></p>
<h2 id="whats-next">What’s next?</h2>
<p>There are some additional features and learnings that I got out of this release that I plan to cover in subsequent
posts. Keep an eye out for:</p>
<ul>
<li>Recording and replaying mouse events, and</li>
<li>Automating releases with GitHub Actions and <code class="language-plaintext highlighter-rouge">brew</code>.</li>
</ul>I’m happy to announce the release of sendkeys 2.0. 🚀🎉!Released: sendkeys-macos Now Stable 🎉2020-09-17T03:57:49+00:002020-09-17T03:57:49+00:00https://socsieng.github.io/tools/2020/09/17/released-sendkeys-macos-now-stable<p>I’ve just released a stable version of <a href="https://www.npmjs.com/package/sendkeys-macos"><code class="language-plaintext highlighter-rouge">sendkeys-macos</code></a> as v1.0.1.</p>
<p>I’ve been wanting to do this for a while now, but the main thing stopping was the absence of unit tests and some
additional project hygiene.</p>
<p>Hygiene related activities include:</p>
<ul>
<li>Unit tests</li>
<li>Automation
<ul>
<li>Add <a href="/tools/2020/09/11/automated-code-formatting-with-prettier.html">Prettier for consistent automatic code
formatting</a></li>
<li>Add <a href="https://github.com/typicode/husky">Husky</a> git hooks</li>
<li>Auto-generating change logs with <a href="https://github.com/GoogleCloudPlatform/release-please-action"><code class="language-plaintext highlighter-rouge">release-please</code></a></li>
<li>Automated publish to npm</li>
</ul>
</li>
</ul>
<p>Checkout the project on <a href="https://github.com/socsieng/sendkeys-macos">github</a>. Let me know if you have any issues. Pull
requests welcome.</p>
<p><img src="/assets/img/google-pay-size.gif" alt="Google Pay button size example" /> <em>The latest example of the <code class="language-plaintext highlighter-rouge">sendkeys-macos</code> in action
(and gratuitous animation).</em></p>I’ve just released a stable version of sendkeys-macos as v1.0.1.Eleventy as a JavaScript Blogging Platform2020-09-14T02:41:46+00:002020-09-14T02:41:46+00:00https://socsieng.github.io/blogging/2020/09/14/eleventy-as-a-javascript-blogging-platform<p><a href="https://github.com/google/eleventy-high-performance-blog">Eleventy High Performance Blog</a> made it into my feed today. I
hadn’t even heard of <a href="https://github.com/11ty/eleventy">Eleventy (11ty)</a> before today so I thought I’d check it out.</p>
<p>What piqued my interest about <a href="https://github.com/11ty/eleventy">Eleventy</a> was this:</p>
<blockquote>
<p>A simpler static site generator. An alternative to Jekyll. Written in <strong>JavaScript</strong>. Transforms a directory of
templates (of varying types) into HTML.</p>
</blockquote>
<p><strong>Emphasis</strong> mine.</p>
<p><a href="https://imgflip.com/i/4evbw0"><img src="/assets/img/distracted-eleventy.jpg" alt="Distracted boyfriend meme - Eleventy, Me, and Jekyll" /></a></p>
<p>It’s actually more like:</p>
<p><a href="https://imgflip.com/i/4evcet"><img src="/assets/img/distracted-javascript.jpg" alt="Distracted boyfriend meme - JavaScript, Me, and Ruby" /></a></p>
<p>I’m much more familiar with JavaScript than I am with Ruby (Jekyll is built with Ruby). While the idea of being able
easily make changes to the static site generator sounds compelling, the truth is that I just don’t have the time. Had I
known about Eleventy when I started blogging (or done some more up front analysis), I may have chosen it as my blogging
platform.</p>
<p>As a software engineer, I feel like I am constantly being tempted by the next new thing whether it’s a new platform,
language, or framework. I think it’s important to be aware of what is happening around you, but to also think
objectively about the benefits. In this case, it would be a new capability that I have no plans to use. As such, I’ve
got no plans to switch.</p>Eleventy High Performance Blog made it into my feed today. I hadn’t even heard of Eleventy (11ty) before today so I thought I’d check it out.Automated Code Formatting With Prettier2020-09-11T02:46:00+00:002020-09-11T02:46:00+00:00https://socsieng.github.io/tools/2020/09/11/automated-code-formatting-with-prettier<p>If you’re not using an automatic code formatter, you’re wasting time (and money). Boost your team’s productivity by
integrating one into your workflow today.</p>
<p>Consistency is important when maintaining large code bases, across multiple contributors (especially for open source
projects). If consistency is important to you, then there are a few ways to maintain consistency in your code base:</p>
<ol>
<li>Code review</li>
<li>Linting</li>
<li>Automatic formatting</li>
</ol>
<p>Each option is built on top of the options before them.</p>
<h2 id="during-code-review">During code review</h2>
<p>I order to maintain consistency during code reviews, you need to start with a coding standards/guidelines.</p>
<p>It <del>could</del> should be as simple as:</p>
<blockquote>
<p>We use the <a href="https://github.com/airbnb/javascript">Airbnb JavaScript Style Guide</a> here.</p>
</blockquote>
<p>Stick to a well known style guide unless you have a good reason not to… and don’t forget to actually do the code
review.</p>
<h2 id="linting">Linting</h2>
<p>Linting builds on top of guidelines. It takes these guidelines and <em>codifies</em> them, identifying violations before the
code is reviewed by another human.</p>
<p><a href="https://eslint.org/">ESLint</a> is pretty much <em>the</em> standard for linting JavaScript.</p>
<h2 id="automatic-formatting">Automatic formatting</h2>
<p>Automatic code formatting takes linting one step further. Instead of simply reporting violations, automatic code
formatting can fix these violations for you. Why waste your time adding a semicolon here, comma there? Put those idle
clock cycles to work.</p>
<p><a href="https://prettier.io/">Prettier</a> with <a href="https://prettier.io/#:~:text=Editor%20Support">editor plugins</a> is a great way to
automatically format your code on <code class="language-plaintext highlighter-rouge">save</code>.</p>
<p><img src="/assets/img/prettier-example.gif" alt="Prettier in action" /> <em>Prettier in action with Visual Studio Code.</em></p>
<p>I personally spend a lot of time with Visual Studio Code working with TypeScript and JavaScript so Prettier works really
well for me. If you’re working with other technologies, look around for their equivalent tools.</p>
<h2 id="getting-started-with-prettier">Getting started with Prettier</h2>
<p>Using Prettier on a new code base is straight forward:</p>
<ol>
<li>Install Prettier into your npm project: <code class="language-plaintext highlighter-rouge">npm install --save-dev prettier</code></li>
<li>
<p>Install Prettier plugin for your editor (<a href="https://github.com/prettier/prettier-vscode">VSCode extension</a>)</p>
<p>Recommended: configure VSCode to format on <code class="language-plaintext highlighter-rouge">save</code> with <code class="language-plaintext highlighter-rouge">.vscode/settings.json</code>:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"editor.formatOnSave"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div> </div>
</li>
<li>
<p>Configure Prettier (start with the defaults and tweak to your liking)</p>
<p>This is the <code class="language-plaintext highlighter-rouge">.prettierrc.yml</code> for one of my projects:</p>
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">arrowParens</span><span class="pi">:</span> <span class="s">avoid</span>
<span class="na">printWidth</span><span class="pi">:</span> <span class="m">120</span>
<span class="na">quoteProps</span><span class="pi">:</span> <span class="s">consistent</span>
<span class="na">semi</span><span class="pi">:</span> <span class="no">true</span>
<span class="na">singleQuote</span><span class="pi">:</span> <span class="no">true</span>
<span class="na">tabWidth</span><span class="pi">:</span> <span class="m">2</span>
<span class="na">trailingComma</span><span class="pi">:</span> <span class="s">all</span>
<span class="na">overrides</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">files</span><span class="pi">:</span> <span class="s1">'</span><span class="s">*.md'</span>
<span class="na">options</span><span class="pi">:</span>
<span class="na">parser</span><span class="pi">:</span> <span class="s">markdown</span>
<span class="na">proseWrap</span><span class="pi">:</span> <span class="s">always</span>
</code></pre></div> </div>
</li>
<li>
<p>I recommend adding a couple of scripts to your <code class="language-plaintext highlighter-rouge">package.json</code> to help with integration into automated builds</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"scripts"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"pretty"</span><span class="p">:</span><span class="w"> </span><span class="s2">"prettier . --write --loglevel warn"</span><span class="p">,</span><span class="w">
</span><span class="nl">"pretty:check"</span><span class="p">:</span><span class="w"> </span><span class="s2">"prettier . --check"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div> </div>
<p>I use the first with <a href="https://www.npmjs.com/package/husky"><code class="language-plaintext highlighter-rouge">husky</code></a> on <code class="language-plaintext highlighter-rouge">pre-commit</code> to fix up formatting where I’ve
used an editor without the plugin, and the second as a verification step on <code class="language-plaintext highlighter-rouge">build</code> as a catch-all where the git hook
hasn’t been installed. An example might be external contributors who use a different set of tools.</p>
</li>
</ol>
<h2 id="using-prettier-on-an-established-code-base">Using Prettier on an established code base</h2>
<p>It’s easy to add Prettier to a new or small code base, but what about a large/established code base?</p>
<p>You’ve got two options:</p>
<ol>
<li><strong>Just Do It</strong>: let your team know what you’re about to do, get those outstanding pull requests merged… Any pull
requests the haven’t been merged in time are going to have a rough time with merge conflicts.</li>
<li><strong>Softly-softly</strong>: turn Prettier on for new files only, and gradually enable it for the rest of your project one file
at a time.</li>
</ol>
<p>If you choose the second option the <code class="language-plaintext highlighter-rouge">.prettierignore</code> file is your friend.</p>
<p>Add an entry for all the files that you’re not ready to switch over into <code class="language-plaintext highlighter-rouge">.prettierignore</code>. Any new files added to your
project will have automated code formatting applied and when you’re ready to enable Prettier on an existing file, simply
remove it from <code class="language-plaintext highlighter-rouge">.prettierignore</code>.</p>
<h2 id="do-your-team-a-favor">Do your team a favor</h2>
<p>Automatic code formatting will help boost your team’s productivity and produce more consistent code. You’ll no longer
have to worry about the little things like whitespace, commas, and semicolons, and you can focus on the things that
matter… the actual code.</p>
<p><em>In my previous <a href="/tools/2020/09/10/making-prettier-prettier.html">post</a>, I show how you can fix one the few issues
that I have with Prettier.</em></p>If you’re not using an automatic code formatter, you’re wasting time (and money). Boost your team’s productivity by integrating one into your workflow today.Making Prettier Prettier2020-09-10T01:44:03+00:002020-09-10T01:44:03+00:00https://socsieng.github.io/tools/2020/09/10/making-prettier-prettier<p>I like having consistently formatted code, but I hate formatting code. I’m also super pedantic about how my code should
be formatted.</p>
<p><img src="/assets/img/prettier-code.png" alt="Prettier example" /></p>
<p>In this post, I’ll show you how to apply a work-around for <a href="https://prettier.io/">Prettier</a> that for me was always a
blemish on an otherwise awesome code formatter. But first, some context.</p>
<h2 id="context">Context</h2>
<p>Prettier describes itself as <em>an opinionated code formatter</em> which I’m cool with, as long as these opinions are the same
as mine.</p>
<p>Where Prettier and I diverge is whether or not operators should appear at the beginning or the end of a line.</p>
<p>Prettier:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// prettier-ignore</span>
<span class="kd">const</span> <span class="nx">booleanResult</span> <span class="o">=</span>
<span class="nx">condition1</span> <span class="o">===</span> <span class="kc">true</span> <span class="o">&&</span>
<span class="nx">condition2</span> <span class="o">===</span> <span class="kc">true</span> <span class="o">&&</span>
<span class="nx">condition3</span> <span class="o">===</span> <span class="kc">true</span><span class="p">;</span>
</code></pre></div></div>
<p>Me (<a href="https://github.com/prettier/prettier/issues/3806">plus a number of people on GitHub</a>):</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// prettier-ignore</span>
<span class="kd">const</span> <span class="nx">booleanResult</span> <span class="o">=</span>
<span class="nx">condition1</span> <span class="o">===</span> <span class="kc">true</span>
<span class="o">&&</span> <span class="nx">condition2</span> <span class="o">===</span> <span class="kc">true</span>
<span class="o">&&</span> <span class="nx">condition3</span> <span class="o">===</span> <span class="kc">true</span><span class="p">;</span>
</code></pre></div></div>
<p>Prettier is so useful that I was willing to overlook the cancerous eyesore 🤮 that is the first example if it mean that
I didn’t have to format the code myself. So I sucked it up and moved on… That was until this weekend when I stumbled
upon some
<a href="https://github.com/prettier/prettier/issues/3806#issuecomment-687278788">new activity on a related Github issue</a>.
<a href="https://github.com/btmills">@btmills</a> had updated his <a href="https://github.com/prettier/prettier/pull/7111">pull request</a>
and included instructions on how to use it. 🎉</p>
<p>I <a href="https://github.com/google-pay/google-pay-button/commit/95bc7ced6b1b37bde2ac21267ba8e781bdff0501">applied</a> it to one
of <a href="https://github.com/google-pay/google-pay-button/">repositories</a> that I manage but found that it wasn’t completely
straight forward so I thought I’d share the additional steps required to get it working.</p>
<h2 id="environment">Environment</h2>
<p>I’m using npm (instead of yarn) with the following npm scripts in my <code class="language-plaintext highlighter-rouge">package.json</code> which is where I think the problem
lies:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"scripts"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"pretty"</span><span class="p">:</span><span class="w"> </span><span class="s2">"prettier . --write --loglevel warn"</span><span class="p">,</span><span class="w">
</span><span class="nl">"pretty:check"</span><span class="p">:</span><span class="w"> </span><span class="s2">"prettier . --check"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<h2 id="installation">Installation</h2>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm <span class="nb">install</span> <span class="nt">--save-dev</span> prettier@npm:@btmills/prettier@^2.1.1
</code></pre></div></div>
<p>For some reason, <code class="language-plaintext highlighter-rouge">prettier@npm:@btmills/prettier@^2.1.1</code> does not create an executable under
<code class="language-plaintext highlighter-rouge">node_modules/.bin/prettier</code>, however, <code class="language-plaintext highlighter-rouge">prettier</code> does. So as a work-around, I referenced
<code class="language-plaintext highlighter-rouge">node_modules/prettier/bin-prettier.js</code> directly instead of <code class="language-plaintext highlighter-rouge">prettier</code> in my <code class="language-plaintext highlighter-rouge">package.json</code>:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="nl">"scripts"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"pretty"</span><span class="p">:</span><span class="w"> </span><span class="s2">"node_modules/prettier/bin-prettier.js . --write --loglevel warn"</span><span class="p">,</span><span class="w">
</span><span class="nl">"pretty:check"</span><span class="p">:</span><span class="w"> </span><span class="s2">"node_modules/prettier/bin-prettier.js . --check"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>I’m told that the package name format (<code class="language-plaintext highlighter-rouge">prettier@npm:@btmills/prettier@^2.1.1</code>) is specific to yarn, and works as
expected with yarn.</p>
<p>As an alternative, I could have used the github package name <code class="language-plaintext highlighter-rouge">npm install --save-dev prettier/prettier#pull/7111/head</code>
but I found that the installation process was significantly slower than <code class="language-plaintext highlighter-rouge">prettier@npm:@btmills/prettier@^2.1.1</code>. I also
liked the fact that the yarn syntax references an npm package and is therefore immutable. So I stuck with that.</p>
<h2 id="looking-forward">Looking forward</h2>
<p>Yes, this is a work-around that I’d prefer not to have to make, but at the same time, it doesn’t cause my eyes to tear
up when I see my operators in the wrong location.</p>
<p>I plan to stick to this until Prettier <code class="language-plaintext highlighter-rouge">3.0</code> is released with the <em>correct</em> opinion applied.</p>I like having consistently formatted code, but I hate formatting code. I’m also super pedantic about how my code should be formatted.Using GitHub Actions Over GitHub Pages2020-09-07T20:30:19+00:002020-09-07T20:30:19+00:00https://socsieng.github.io/blogging/2020/09/07/using-github-actions-over-github-pages<p>I’m making the move from GitHub’s built-in Jekyll integration to GitHub Actions.</p>
<p>Jekyll lists a number of
<a href="https://jekyllrb.com/docs/continuous-integration/github-actions/#advantages-of-using-actions">advantages of using GitHub Actions</a>,
however, I’m primarily interested in:</p>
<ul>
<li>Improved indexing for <code class="language-plaintext highlighter-rouge">related_posts</code> with <code class="language-plaintext highlighter-rouge">--lsi</code>
(<a href="https://en.wikipedia.org/wiki/Latent_semantic_analysis#Latent_semantic_indexing">latent semantic indexing</a>) which is
not available with GitHub pages,</li>
<li>Access to more plugins (not just those permitted by GitHub pages),</li>
<li>More fine-grained control over how the site is generated - the current GitHub pages set up is a bit too magic-y and I
don’t have visibility of what is actually happening when I commit changes, and</li>
<li>Using the latest version of Jekyll</li>
</ul>
<h2 id="reverting-to-the-latest-version-of-jekyll">Reverting to the latest version of Jekyll</h2>
<p>Step 1: Remove <code class="language-plaintext highlighter-rouge">github-pages</code> from <code class="language-plaintext highlighter-rouge">Gemfile</code>, and add <code class="language-plaintext highlighter-rouge">jekyll</code></p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># gem "github-pages", group: :jekyll_plugins</span>
<span class="n">gem</span> <span class="s2">"jekyll"</span><span class="p">,</span> <span class="s2">"~> 4.1.1"</span>
</code></pre></div></div>
<p>One of the things that broke for me was that the theme I was using didn’t work with the latest version of <code class="language-plaintext highlighter-rouge">jekyll</code>. So I
carefully copied the files from <code class="language-plaintext highlighter-rouge">gems/jekyll-theme-midnight-0.1.1</code> into the root of my repository, and added the plugins
that the theme referenced into my <code class="language-plaintext highlighter-rouge">Gemfile</code>:</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">group</span> <span class="ss">:jekyll_plugins</span> <span class="k">do</span>
<span class="c1"># ...</span>
<span class="n">gem</span> <span class="s2">"jekyll-seo-tag"</span><span class="p">,</span> <span class="s2">"~> 2.0"</span>
<span class="k">end</span>
</code></pre></div></div>
<p>Verify that the site continues to work as expected.</p>
<h2 id="adding-latent-semantic-indexing-locally">Adding latent semantic indexing (locally)</h2>
<p>Latent semantic indexing support requires the <code class="language-plaintext highlighter-rouge">classifier-reborn</code> plugin. This is added by updating the <code class="language-plaintext highlighter-rouge">Gemfile</code>:</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">group</span> <span class="ss">:jekyll_plugins</span> <span class="k">do</span>
<span class="c1"># ...</span>
<span class="n">gem</span> <span class="s2">"classifier-reborn"</span><span class="p">,</span> <span class="s2">"~> 2.2.0"</span>
<span class="k">end</span>
</code></pre></div></div>
<p>And then enabling it with the <code class="language-plaintext highlighter-rouge">--lsi</code> option:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># install plugin</span>
bundle <span class="nb">install</span>
<span class="c"># start server locally</span>
bundle <span class="nb">exec </span>jekyll serve <span class="nt">--lsi</span>
</code></pre></div></div>
<p>This is the list of related posts before:</p>
<blockquote>
<h3 id="related-posts">Related posts</h3>
<ul>
<li>Maintaining a Jekyll Blog with Gitpod</li>
<li>Scripting a New Blog Post</li>
<li>We Don’t Look Alike</li>
<li>Automation Challenge: Image Optimization</li>
<li>Customizing The Blog With Related Posts</li>
<li>How Breaking My Hand Improved My Typing</li>
<li>Adding Google Analytics</li>
<li>Updating the Blog’s Theme</li>
<li>Blogging for free with Jekyll</li>
</ul>
</blockquote>
<p>And after:</p>
<blockquote>
<h3 id="related-posts-1">Related posts</h3>
<ul>
<li>Blogging for free with Jekyll</li>
<li>Using GitHub Actions Over GitHub Pages</li>
<li>Updating the Blog’s Theme</li>
<li>Customizing The Blog With Related Posts</li>
<li>Automation Challenge: Image Optimization</li>
<li>How Breaking My Hand Improved My Typing</li>
<li>Adding Google Analytics</li>
<li>Maintaining a Jekyll Blog with Gitpod</li>
<li>Scripting a New Blog Post</li>
<li>We Don’t Look Alike</li>
</ul>
</blockquote>
<p>Trust me, the updated list is much more relevant than a <em>recent posts</em> list</p>
<h2 id="adding-the-github-action-workflow">Adding the GitHub Action workflow</h2>
<p>Previously, <a href="https://socsieng.github.io/">socsieng.github.io</a> was automatically setup to build a static Jekyll site and
publish to GitHub Pages. Without this automatic process, I now need publish my static site to a GitHub Pages branch
(<code class="language-plaintext highlighter-rouge">gh-pages</code> by convention).</p>
<p>Jekyll provides a
<a href="https://jekyllrb.com/docs/continuous-integration/github-actions/#setting-up-the-action">sample workflow file</a>, which I
use as a starting point for <code class="language-plaintext highlighter-rouge">.github/workflows/jekyll.yml</code>:</p>
<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s">jekyll build</span>
<span class="na">on</span><span class="pi">:</span>
<span class="na">push</span><span class="pi">:</span>
<span class="na">branches</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">main</span>
<span class="na">jobs</span><span class="pi">:</span>
<span class="na">build</span><span class="pi">:</span>
<span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
<span class="na">steps</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/checkout@v2</span>
<span class="c1"># Use GitHub Actions' cache to shorten build times and decrease load on servers</span>
<span class="pi">-</span> <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/cache@v1</span>
<span class="na">with</span><span class="pi">:</span>
<span class="na">path</span><span class="pi">:</span> <span class="s">vendor/bundle</span>
<span class="na">key</span><span class="pi">:</span> <span class="s">${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }}</span>
<span class="na">restore-keys</span><span class="pi">:</span> <span class="pi">|</span>
<span class="s">${{ runner.os }}-gems-</span>
<span class="pi">-</span> <span class="na">uses</span><span class="pi">:</span> <span class="s">socsieng/jekyll-action@master</span>
<span class="na">with</span><span class="pi">:</span>
<span class="na">target_branch</span><span class="pi">:</span> <span class="s">gh-pages</span>
<span class="na">env</span><span class="pi">:</span>
<span class="na">JEKYLL_PAT</span><span class="pi">:</span> <span class="s">${{ secrets.JEKYLL_PAT }}</span>
<span class="na">JEKYLL_ARGS</span><span class="pi">:</span> <span class="s">--lsi</span>
</code></pre></div></div>
<p>I’ve had a look at the <a href="https://github.com/helaili/jekyll-action/"><code class="language-plaintext highlighter-rouge">helaili/jekyll-action</code></a> repository that the Jekyll
documentation referenced and used some of the sample configuration there as well.</p>
<p>Note that I’ve forked <a href="https://github.com/helaili/jekyll-action/"><code class="language-plaintext highlighter-rouge">helaili/jekyll-action</code></a> to add support for additional
<code class="language-plaintext highlighter-rouge">JEKYLL_ARGS</code> parameters so that I can pass in the <code class="language-plaintext highlighter-rouge">--lsi</code>. I’ve also raised a
<a href="https://github.com/helaili/jekyll-action/pull/56">pull request</a> back to the original repository.</p>
<p>A prerequisite to enabling this workflow is that you need to provide and configure a
<a href="https://github.com/settings/tokens">GitHub Personal Access Token</a> with the <code class="language-plaintext highlighter-rouge">public_repo</code> scope for the workflow to use
to publish site changes. I <strong>strongly</strong> recommend creating a new token instead of reusing an existing one.</p>
<h2 id="configuring-github-pages">Configuring GitHub pages</h2>
<p>Once the workflow executes, a new branch is created <code class="language-plaintext highlighter-rouge">gh-pages</code> (updated if you already had this branch).</p>
<p>The final step is to configure GitHub Pages to use this new branch:</p>
<p><img src="/assets/img/github-pages-config2.png" alt="GitHub pages configuration" /> <em>Configure GitHub Pages.</em></p>
<h2 id="summing-up">Summing up</h2>
<p>I switched over to GitHub Actions so that I could have more control over how my Jekyll site is generated. It is more
involved than simply using the default GitHub/Jekyll integration, but I think it will pay off in the long run with more
control and flexibility at my finger tips.</p>
<p>I hope this helps anybody else thinking about making the switch.</p>I’m making the move from GitHub’s built-in Jekyll integration to GitHub Actions.Maintaining a Jekyll Blog with Gitpod2020-09-05T05:00:16+00:002020-09-05T05:00:16+00:00https://socsieng.github.io/tools/2020/09/05/maintaining-a-jekyll-blog-with-gitpod<p>I’m editing this post with on my iPad with <a href="https://gitpod.io/">gitpod.io</a>, and I’m very impressed. And I’m not easily
impressed.</p>
<p>I first heard about gitpod.io when saw their post about how
<a href="https://www.gitpod.io/blog/opensource/">Gitpod is now Open Source 🎉</a>. When I started looking for solutions to manage
the blog on my iPad, I thought I’d give it a try.</p>
<p>The environment is so familiar. It is pretty much Visual Studio in the browser, with some linux based environment
accessible through the integrated terminal.</p>
<p>Almost everything that I’ve tried so far has worked (note that I’m not trying to break it… just use it):</p>
<ul class="task-list">
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" checked="checked" /><code class="language-plaintext highlighter-rouge">command</code> + <code class="language-plaintext highlighter-rouge">shift</code> + <code class="language-plaintext highlighter-rouge">p</code> to toggle word wrap,</li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" checked="checked" />Install <code class="language-plaintext highlighter-rouge">prettier</code> extension to automatically format posts as I save them,</li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" checked="checked" />Executing and running the Jekyll server (<code class="language-plaintext highlighter-rouge">bundle exec jekyll serve --watch</code>),</li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" checked="checked" />Previewing the site on Gitpod,</li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" checked="checked" />Creating a new post with a custom script (<code class="language-plaintext highlighter-rouge">scripts/new-post.sh tools "Maintaining a Jekyll Blog with Gitpod"</code>),</li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" checked="checked" />Syntax highlighting,</li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" checked="checked" />Commit files locally, and</li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" checked="checked" />Push files to GitHub.</li>
</ul>
<p>Things that don’t work:</p>
<ul class="task-list">
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Upload an image (e.g. screenshot) via Gitpod (haven’t figured out how to do this yet),</li>
<li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Terminating a command in the terminal with <code class="language-plaintext highlighter-rouge">ctrl</code> + <code class="language-plaintext highlighter-rouge">c</code> (at lease on the iPad with the Smart Keyboard),</li>
</ul>
<p>I haven’t spent very long with Gitpod, but I’m happy with the experience so far. It isn’t going to replace my laptop
environment, but I think its a viable alternative for when I’m on the iPad (with the Apple Smart Keyboard).</p>
<p>I’ll keep this post updated as I learn more.</p>I’m editing this post with on my iPad with gitpod.io, and I’m very impressed. And I’m not easily impressed.Scripting a New Blog Post2020-09-04T20:02:27+00:002020-09-04T20:02:27+00:00https://socsieng.github.io/tools/2020/09/04/scripting-a-new-blog-post<p>I’m tired of manually creating a new Jekyll blog post which for me included copying an existing post, renaming the file
with today’s date and the title of the post, updating the <code class="language-plaintext highlighter-rouge">title</code> attribute of the markdown file, and deleting the
contents of the copied post.</p>
<p>That workflow ends today.</p>
<p>I’ve created a script (<code class="language-plaintext highlighter-rouge">scripts/new-post.sh</code>):</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/usr/bin/env bash</span>
<span class="nb">set</span> <span class="nt">-e</span>
<span class="nv">repo_folder</span><span class="o">=</span><span class="sb">`</span>git rev-parse <span class="nt">--show-toplevel</span><span class="sb">`</span>
<span class="nv">category</span><span class="o">=</span><span class="nv">$1</span>
<span class="nv">title</span><span class="o">=</span><span class="nv">$2</span>
<span class="nv">timestamp</span><span class="o">=</span><span class="sb">`</span><span class="nb">date</span> <span class="s2">"+%F %T %z"</span><span class="sb">`</span>
<span class="nv">date_value</span><span class="o">=</span><span class="sb">`</span><span class="nb">date</span> <span class="s2">"+%F"</span><span class="sb">`</span>
<span class="nv">file</span><span class="o">=</span><span class="sb">`</span><span class="nb">echo</span> <span class="nv">$title</span> | <span class="nb">awk</span> <span class="s1">'{print tolower($0)}'</span> | <span class="nb">sed</span> <span class="nt">-e</span> <span class="s1">'s/[^a-zA-Z0-9]/-/g'</span><span class="sb">`</span>
<span class="nv">file_path</span><span class="o">=</span><span class="s2">"</span><span class="nv">$repo_folder</span><span class="s2">/_posts/</span><span class="nv">$date_value</span><span class="s2">-</span><span class="nv">$file</span><span class="s2">.md"</span>
<span class="nb">echo</span> <span class="s2">"---
layout: post
title: '</span><span class="nv">$title</span><span class="s2">'
date: </span><span class="nv">$timestamp</span><span class="s2">
categories: </span><span class="nv">$category</span><span class="s2">
tags: </span><span class="nv">$category</span><span class="s2">
---
"</span> <span class="o">></span> <span class="nv">$file_path</span>
<span class="nb">echo</span> <span class="nv">$file_path</span>
</code></pre></div></div>
<p>Usage:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>scripts/new-post.sh <category> <span class="s2">"<title>"</span>
</code></pre></div></div>
<p>It takes two arguments. <code class="language-plaintext highlighter-rouge">category</code> and <code class="language-plaintext highlighter-rouge">title</code>. Your title will probably contain spaces so you’ll want to generally wrap
your blog post title in quotes.</p>
<p>Example:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>scripts/new-post.sh blogging <span class="s2">"My New Post"</span>
</code></pre></div></div>
<p>I’ve decided to write the new file path to <code class="language-plaintext highlighter-rouge">stdout</code> so that I can <code class="language-plaintext highlighter-rouge">command</code> + click the file name to quickly start
editing in Visual Studio Code.</p>
<p>Demo:</p>
<p><img src="/assets/img/new-post.gif" alt="New post script in action" /></p>
<p>Edit:</p>
<p>Since writing this post, I found out that there is a plugin <a href="https://github.com/jekyll/jekyll-compose"><code class="language-plaintext highlighter-rouge">jekyll-compose</code></a>
which provides this functionality as well as more. I plan to stick with this script though. Its one less dependency, and
does everything I need.</p>I've created a script to help me create new Jekyll blog posts. Before this was copy/pasting an existing post... no more...We Don’t Look Alike2020-09-03T07:53:00+00:002020-09-03T07:53:00+00:00https://socsieng.github.io/stuff/2020/09/03/ted-lieu<p>My wife saw U.S. Representative <a href="https://en.wikipedia.org/wiki/Ted_Lieu">Ted Lieu</a> on TV today and thinks we look the
same. For the record, we don’t.</p>
<p><img src="/assets/img/ted-lieu.jpg" alt="Ted and I" /> <em>For anyone else who thinks they’re the same people, that’s me on the left and
Ted Lieu on the right.</em></p>
<p>I think she might be racist. That or she needs new glasses.</p>
<p>The sad thing is that Ted is 11 years my senior and looks younger than me. 😭.</p>My wife saw U.S. Representative Ted Lieu on TV today and thinks we look the same. For the record, we don’t.Automation Challenge: Image Optimization2020-09-03T03:13:00+00:002020-09-03T03:13:00+00:00https://socsieng.github.io/tools/2020/09/03/automation-challeng-image-optimizations<p>I’m looking for things to automate and I challenge you to do the same.</p>
<p>In this post, I’m going to automate a task that does not <em>spark joy</em>. This time I’ve decided that it’s going to be
optimizing images for this blog.</p>
<h2 id="optimizing-images">Optimizing images</h2>
<p>This blog is built with Jekyll and is running on GitHub pages. Jekyll is built with Ruby (a new language to me), so I’ll
be using a Ruby gem called <a href="https://github.com/toy/image_optim"><code class="language-plaintext highlighter-rouge">image_optim</code></a> to apply the optimizations. I do this by adding the dependency
to my <code class="language-plaintext highlighter-rouge">Gemfile</code>:</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">group</span> <span class="ss">:development</span> <span class="k">do</span>
<span class="n">gem</span> <span class="s2">"image_optim"</span><span class="p">,</span> <span class="ss">require: </span><span class="kp">false</span>
<span class="n">gem</span> <span class="s2">"image_optim_pack"</span><span class="p">,</span> <span class="ss">require: </span><span class="kp">false</span>
<span class="k">end</span>
</code></pre></div></div>
<p>And then installing the dependency:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bundle <span class="nb">install</span>
</code></pre></div></div>
<p>I’m going to organize scripts in the aptly named <code class="language-plaintext highlighter-rouge">scripts</code> folder, and the Ruby script to optimize images will be named
<code class="language-plaintext highlighter-rouge">scripts/optimize-images.rb</code> 🤯:</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">#!/usr/bin/env ruby</span>
<span class="nb">require</span> <span class="s1">'rubygems'</span>
<span class="nb">require</span> <span class="s1">'bundler/setup'</span>
<span class="nb">require</span> <span class="s1">'image_optim'</span>
<span class="c1"># resolve the root folder of the repository</span>
<span class="n">root_folder</span> <span class="o">=</span> <span class="sb">`git rev-parse --show-toplevel`</span><span class="p">.</span><span class="nf">strip</span>
<span class="k">if</span> <span class="o">!</span><span class="no">File</span><span class="p">.</span><span class="nf">directory?</span><span class="p">(</span><span class="n">root_folder</span><span class="p">)</span>
<span class="n">root_folder</span> <span class="o">=</span> <span class="sb">`pwd`</span><span class="p">.</span><span class="nf">strip</span>
<span class="k">end</span>
<span class="c1"># files to optimize: jpg, png, and gif files under assets/img</span>
<span class="n">target_files</span> <span class="o">=</span> <span class="s2">"</span><span class="si">#{</span><span class="n">root_folder</span><span class="si">}</span><span class="s2">/assets/img/*.{jpg,png,gif}"</span>
<span class="c1"># instantiate ImageOtim with configuration options</span>
<span class="n">image_optim</span> <span class="o">=</span> <span class="no">ImageOptim</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span>
<span class="ss">:pngout</span> <span class="o">=></span> <span class="kp">false</span><span class="p">,</span>
<span class="ss">:svgo</span> <span class="o">=></span> <span class="kp">false</span><span class="p">,</span>
<span class="ss">:cache_dir</span> <span class="o">=></span> <span class="s2">"</span><span class="si">#{</span><span class="n">root_folder</span><span class="si">}</span><span class="s2">/temp"</span>
<span class="p">)</span>
<span class="c1"># execute image optimization</span>
<span class="n">image_optim</span><span class="p">.</span><span class="nf">optimize_images!</span><span class="p">(</span><span class="no">Dir</span><span class="p">[</span><span class="n">target_files</span><span class="p">])</span> <span class="k">do</span> <span class="o">|</span><span class="n">unoptimized</span><span class="p">,</span> <span class="n">optimized</span><span class="o">|</span>
<span class="k">if</span> <span class="n">optimized</span>
<span class="c1"># print filename to stdout</span>
<span class="nb">puts</span> <span class="s2">"</span><span class="si">#{</span><span class="n">optimized</span><span class="si">}</span><span class="s2">"</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<p><em>Nothing too crazy going on here. Its not rocket surgery… 🚀🩺</em></p>
<p>A couple of things to note:</p>
<ol>
<li>I’ve specified the option <code class="language-plaintext highlighter-rouge">:cache_dir</code> which is a performance optimization. This allows <code class="language-plaintext highlighter-rouge">image_optim</code> to <em>remember</em>
that it has already seen and optimized an image, and it won’t do it again. The image optimization process can be slow
and taxing on the CPU.</li>
<li>I am writing the file path of the optimized image (<code class="language-plaintext highlighter-rouge">puts "#{optimized}"</code>). This will come in handy later.</li>
</ol>
<p>I can trigger an image optimization by executing the following command:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ruby scripts/optimize-images.rb
</code></pre></div></div>
<h2 id="automation">Automation</h2>
<p>Having a script to optimize images is a great first step, but it still doesn’t spark any joy.</p>
<p>I need to figure out which event I should use as a trigger and I’ve settled on <code class="language-plaintext highlighter-rouge">pre-commit</code> (instead of something like
<em>build</em>). The reason being that this is required step in my workflow which is create/edit a post locally on my computer,
commit the changes, and push to GitHub.</p>
<p>I’m creating a script called <code class="language-plaintext highlighter-rouge">scripts/pre-commit.sh</code>:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/usr/bin/env bash</span>
<span class="nb">set</span> <span class="nt">-e</span>
<span class="c"># get the repository root</span>
<span class="nv">repo_folder</span><span class="o">=</span><span class="sb">`</span>git rev-parse <span class="nt">--show-toplevel</span><span class="sb">`</span>
<span class="c"># use repository root if there is a value, otherwise use the current folder</span>
<span class="nv">root_folder</span><span class="o">=</span><span class="k">${</span><span class="nv">repo_folder</span><span class="k">:-</span><span class="sb">`</span><span class="nb">pwd</span><span class="sb">`</span><span class="k">}</span>
<span class="nb">echo</span> <span class="s2">"Performing image optimization for the first time can take a while. Sit tight..."</span>
<span class="c"># execute image optimization and store results in a variable</span>
<span class="nv">optimized_images</span><span class="o">=</span><span class="sb">`</span>ruby <span class="nv">$root_folder</span>/scripts/optimize-images.rb<span class="sb">`</span>
<span class="k">if</span> <span class="o">[</span> <span class="nt">-n</span> <span class="s2">"</span><span class="nv">$optimized_images</span><span class="s2">"</span> <span class="o">]</span>
<span class="k">then
</span><span class="nb">echo</span> <span class="s2">"</span><span class="nv">$optimized_images</span><span class="s2">"</span> | <span class="k">while </span><span class="nb">read </span>image<span class="p">;</span> <span class="k">do
</span><span class="nb">echo</span> <span class="s2">" - Optimized </span><span class="nv">$image</span><span class="s2">"</span>
git add <span class="s2">"</span><span class="nv">$image</span><span class="s2">"</span>
<span class="k">done
fi
</span><span class="nb">echo</span> <span class="s2">"Done."</span>
</code></pre></div></div>
<p><em>Don’t forget to make sure this script is executable (<code class="language-plaintext highlighter-rouge">chmod +x scripts/pre-commit.sh</code>).</em></p>
<p>This is where output-ing the file path from the <code class="language-plaintext highlighter-rouge">scripts/optimize-images.rb</code> script comes into play. It is assigned to
the <code class="language-plaintext highlighter-rouge">$optimized_images</code> variable where we loop over each of them optimized images and stage them to the repository with
<code class="language-plaintext highlighter-rouge">git add</code>.</p>
<p>In order to get this script executed on <code class="language-plaintext highlighter-rouge">pre-commit</code>, I need to install it as a git <code class="language-plaintext highlighter-rouge">pre-commit</code> hook which can be done
by copying this file to <code class="language-plaintext highlighter-rouge">.git/hooks/pre-commit</code> (without the <code class="language-plaintext highlighter-rouge">.sh</code> extension). I’ve created a little script to help me
install it in case I need to re-install it on another device (<code class="language-plaintext highlighter-rouge">scripts/install-pre-commit.sh</code>):</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/usr/bin/env bash</span>
<span class="nb">set</span> <span class="nt">-e</span>
<span class="nv">script_folder</span><span class="o">=</span><span class="sb">`</span><span class="nb">cd</span> <span class="si">$(</span><span class="nb">dirname</span> <span class="nv">$0</span><span class="si">)</span> <span class="o">&&</span> <span class="nb">pwd</span><span class="sb">`</span>
<span class="nv">repo_folder</span><span class="o">=</span><span class="sb">`</span>git rev-parse <span class="nt">--show-toplevel</span><span class="sb">`</span>
<span class="nb">cp</span> <span class="nt">-f</span> <span class="nv">$script_folder</span>/pre-commit.sh <span class="nv">$repo_folder</span>/.git/hooks/pre-commit
</code></pre></div></div>
<p><em>Again, don’t forget to make this script executable. Install using <code class="language-plaintext highlighter-rouge">scripts/install-pre-commit.sh</code>.</em></p>
<p>Once installed, any images that have not already been optimized every time I call <code class="language-plaintext highlighter-rouge">git commit</code>.</p>
<h2 id="sparking-joy">Sparking joy</h2>
<p>With very little effort, I’ve been able to automate an otherwise mindless activity.</p>
<p>And here are the results:</p>
<table>
<thead>
<tr>
<th style="text-align: left">Image</th>
<th style="text-align: right">Size before</th>
<th style="text-align: right">Size after</th>
<th style="text-align: right">Improvement</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left">github-pages-config.png</td>
<td style="text-align: right">102K</td>
<td style="text-align: right">51K</td>
<td style="text-align: right">51K (50%)</td>
</tr>
<tr>
<td style="text-align: left">google-pay-vue.gif</td>
<td style="text-align: right">2.9M</td>
<td style="text-align: right">2.4M</td>
<td style="text-align: right">0.5M (17%)</td>
</tr>
<tr>
<td style="text-align: left">jekyll-screenshot.png</td>
<td style="text-align: right">520K</td>
<td style="text-align: right">272K</td>
<td style="text-align: right">248K (48%)</td>
</tr>
<tr>
<td style="text-align: left">jekyll-theming-1.png</td>
<td style="text-align: right">570K</td>
<td style="text-align: right">307K</td>
<td style="text-align: right">263K (46%)</td>
</tr>
<tr>
<td style="text-align: left">jekyll-theming-2.png</td>
<td style="text-align: right">536K</td>
<td style="text-align: right">285K</td>
<td style="text-align: right">251K (47%)</td>
</tr>
<tr>
<td style="text-align: left">jekyll-theming-3.png</td>
<td style="text-align: right">569K</td>
<td style="text-align: right">311K</td>
<td style="text-align: right">258K (45%)</td>
</tr>
</tbody>
</table>
<p><img src="/assets/img/marie-kondo.png" alt="Discard everything that does not spark joy - Marie Kondo" /> <em>I’m sure Marie Kondo would
approve.</em></p>
<p>I challenge you to find something/anything to automate. I want to hear about it: <a href="https://twitter.com/aussoc">@aussoc</a>.</p>I’m looking for things to automate and I challenge you to do the same.