<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
	<title>Passions Play</title>
	<atom:link href="https://passionsplay.com/feed/" rel="self" type="application/rss+xml" />
	<link>https://passionsplay.com/</link>
	<description>Passions Play — Benjamin Turner</description>
	<lastBuildDate>Thu, 30 Apr 2026 11:35:08 GMT</lastBuildDate>
	<language>en-US</language>
	
	<item>
		<title>Scripting PNG to AVIF Conversion for Web Delivery</title>
		<link>https://passionsplay.com/blog/scripting-png-to-avif-conversion-for-web-delivery/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/scripting-png-to-avif-conversion-for-web-delivery/</guid>
		<pubDate>Thu, 30 Apr 2026 11:35:08 GMT</pubDate>
		<description><![CDATA[<p>I was optimizing a featured image for a blog post — a PNG that came out of Gemini at around 8MB — and figured I’d run it through ffmpeg to convert to AVIF before uploading it to WordPress.</p>
<p>The result: 8MB down to 53KB. Same visual quality.</p>
<p>One command:</p>
<pre><code>ffmpeg -i input.png -vf scale=1600:-2 -c:v libsvtav1 -crf 35 -b:v 0 output.avif
</code></pre>
<p>A few things worth knowing about those args:</p>
<ul>
<li><code>-c:v libsvtav1</code> — the SVT-AV1 encoder, which is fast and produces excellent results</li>
<li><code>-crf 35</code> — quality level (0–63, lower is better). 30–40 is the web sweet spot</li>
<li><code>-b:v 0</code> — disables the bitrate ceiling so CRF controls quality purely; these two always travel together</li>
<li><code>-vf scale=1600:-2</code> — scales to 1600px wide, auto-calculates height. The <code>-2</code> (not <code>-1</code>) rounds to an even pixel count, which AV1 requires</li>
</ul>
<p>AVIF is supported in all modern browsers. WordPress serves it without any configuration.</p>
<h2>The Script</h2>
<p>Since I’ll want this more than once, I wrapped it in a small bash script at <code>~/.local/bin/imgopt</code>:</p>
<pre><code>#!/usr/bin/env bash
# Convert an image to AVIF using ffmpeg. Optimized for web delivery.

set -euo pipefail

usage() {
  cat &gt;&amp;2 &lt;&lt;'EOF'
imgopt — Convert images to AVIF for web delivery

Usage:
  imgopt &lt;input&gt; [output] [crf] [max-width]

Arguments:
  input      Source image (PNG, JPG, WebP, etc.)
  output     Output path (default: input basename + .avif)
  crf        Quality level 0-63, lower = better (default: 35)
             28-40 is the web sweet spot
  max-width  Scale width in pixels, preserve aspect ratio (default: no scaling)

Examples:
  imgopt photo.png
  imgopt photo.png photo-web.avif
  imgopt photo.png photo-web.avif 30
  imgopt photo.png photo-web.avif 35 1600
EOF
}

INPUT=&quot;${1:-}&quot;
if [[ -z &quot;$INPUT&quot; || &quot;$INPUT&quot; == &quot;--help&quot; || &quot;$INPUT&quot; == &quot;-h&quot; ]]; then
  usage
  [[ -z &quot;$INPUT&quot; ]] &amp;&amp; exit 1 || exit 0
fi

BASENAME=&quot;${INPUT%.*}&quot;
OUTPUT=&quot;${2:-${BASENAME}.avif}&quot;
CRF=&quot;${3:-35}&quot;
MAX_WIDTH=&quot;${4:-}&quot;

if [[ -n &quot;$MAX_WIDTH&quot; ]]; then
  VF_FILTER=&quot;-vf scale=${MAX_WIDTH}:-2&quot;
else
  VF_FILTER=&quot;-vf scale=trunc(iw/2)*2:trunc(ih/2)*2&quot;
fi

echo &quot;Converting: $INPUT → $OUTPUT (CRF=$CRF${MAX_WIDTH:+, max-width=${MAX_WIDTH}px})&quot;

ERRLOG=$(mktemp)
if ! ffmpeg -v quiet -i &quot;$INPUT&quot; $VF_FILTER -c:v libsvtav1 -crf &quot;$CRF&quot; -b:v 0 &quot;$OUTPUT&quot; 2&gt;&quot;$ERRLOG&quot;; then
  cat &quot;$ERRLOG&quot; &gt;&amp;2
  rm -f &quot;$ERRLOG&quot;
  exit 1
fi
rm -f &quot;$ERRLOG&quot;

ORIGINAL=$(du -sh &quot;$INPUT&quot; | cut -f1)
RESULT=$(du -sh &quot;$OUTPUT&quot; | cut -f1)
echo &quot;Done: $ORIGINAL → $RESULT&quot;Code language: PHP (php)
</code></pre>
<p>Save it, <code>chmod +x ~/.local/bin/imgopt</code>, and make sure <code>~/.local/bin</code> is on your <code>$PATH</code>. Then it’s just:</p>
<pre><code>imgopt photo.png
imgopt photo.png photo-web.avif 30 1600Code language: CSS (css)
</code></pre>
<p><img src="imgopt-sceenshot-usage-5aZQwPXDOke5.png" alt=""></p>
<h2>Quieting the Output</h2>
<p>The first version of the script was noisier than expected. Adding <code>-v quiet</code> to the ffmpeg command silences ffmpeg’s own output — but libsvtav1, the underlying encoder, writes its info lines directly to stderr and ignores that flag entirely. The result was a wall of <code>Svt[info]</code> lines that <code>-v quiet</code> couldn’t touch.</p>
<p>The fix is to redirect stderr to a temp file and only surface it if the command actually fails:</p>
<pre><code>ERRLOG=$(mktemp)
if ! ffmpeg -v quiet -i &quot;$INPUT&quot; ... 2&gt;&quot;$ERRLOG&quot;; then
  cat &quot;$ERRLOG&quot; &gt;&amp;2
  rm -f &quot;$ERRLOG&quot;
  exit 1
fi
rm -f &quot;$ERRLOG&quot;Code language: JavaScript (javascript)
</code></pre>
<p>Silent on success. If something goes wrong, the full error output is still there.</p>
<h2>Guarding Against Odd Dimensions</h2>
<p>Testing the script on a screenshot surfaced another edge case. AV1 requires even pixel dimensions — width and height must both be divisible by 2. The original command used <code>-2</code> instead of <code>-1</code> when scaling to a max width, which handles the height automatically. But when no max-width is set, no scaling happens at all, and an image with an odd width or height goes straight to the encoder and fails:</p>
<pre><code>Svt[error]: Source Width must be even for YUV_420 colorspace
Svt[error]: Source Height must be even for YUV_420 colorspaceCode language: CSS (css)
</code></pre>
<p>Screenshots are a common source of this — window sizes tend to be whatever they happen to be. The fix is to always run a scale pass, even when the dimensions aren’t changing. <code>trunc(iw/2)*2:trunc(ih/2)*2</code> rounds each dimension down to the nearest even number. For images that are already even it’s a no-op; for odd ones it trims a single pixel.</p>
<pre><code># No max-width specified — still enforce even dimensions
-vf scale=trunc(iw/2)*2:trunc(ih/2)*2Code language: PHP (php)
</code></pre>
]]></description>
	</item>
	
	<item>
		<title>I Outgrew My AI Resolution</title>
		<link>https://passionsplay.com/blog/i-outgrew-my-ai-resolution/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/i-outgrew-my-ai-resolution/</guid>
		<pubDate>Tue, 28 Apr 2026 11:24:13 GMT</pubDate>
		<description><![CDATA[<p>I’ve been interested in AI for a while, but keeping up with it is its own full-time job — models change, tools change, whole paradigms shift in the span of a few months. At the start of the year I signed up for the <a href="https://aidbnewyear.com/">AI Daily Brief New Year’s Resolution</a> mostly because it gave me something concrete to work toward. Ten weekends, a structured path, real skills to come out the other end. In a space that moves this fast, an opinionated curriculum is worth something.</p>
<p>Week 1 got done. Week 2 got started but never finished. I found I was outgrowing it.</p>
<h2>Prompting Is on the List. Other Things Jumped the Queue.</h2>
<p>Prompt engineering is a real skill and one I intend to develop. The resolution was a reasonable way to do that — learning to craft better inputs, compare models, understand their strengths. Useful stuff, especially when most of the work happens in a chat window.</p>
<p>What I found was that once I left the chat window, other skills became more pressing. Working with tools like Claude Code, the actual leverage isn’t how cleverly a request is phrased — it’s in how well the context is organized and how clearly tools, like MCP servers, are integrated. A well-structured project beats a well-crafted prompt, pretty much every time. That felt more urgent to get right.</p>
<h2>The Moment the Problem Changes Shape</h2>
<p>I’m a software engineer at WP Engine. Most of what I do day to day is what software engineers have always done: shipping features, fixing bugs, improving existing systems. What’s changed is the assistance layer — tools like Claude Code mean I’m rarely doing that work alone, and the collaboration is different from anything I’ve used before.</p>
<p>But even there, what I’ve noticed is that the prompting skills the resolution was building aren’t the bottleneck. The bottleneck is context — making sure the agent understands the codebase, the constraints, the goals well enough to be a real collaborator rather than a very fast autocomplete. That’s a different skill entirely. And it’s pointing me toward something I want to build next: custom agent harnesses that can do meaningful automated work on projects like <a href="https://localwp.com/">Local</a>. Not just AI-assisted development, but AI-driven workflows.</p>
<p>That distinction — assistance vs. automation — is where things get interesting.</p>
<h2>Two Tracks, One Shift</h2>
<p>I’ve started thinking about my AI work as two tracks that are developing in parallel.</p>
<ul>
<li><strong>Work track:</strong> Right now this is growing my skills around agentic tools and AI-assisted development. The near-term direction is building and maintaining custom agent harnesses that can tackle larger, more complex work. The AI isn’t the product; it’s the infrastructure that makes the engineering better.</li>
<li><strong>Personal track:</strong> Building an actual assistant. Not a chatbot. Not a suite of tools. A named, persistent entity that knows my goals, my context, and my preferences — and works on my behalf without me having to think about it much.</li>
</ul>
<p>Both tracks are moving away from the chat window. Both care a lot more about context architecture than prompting craft. And the personal track in particular led me somewhere that made the resolution feel like a different conversation entirely.</p>
<h2>The Assistance Paradigm</h2>
<p>Daniel Miessler — who created the <a href="https://github.com/danielmiessler/fabric">Fabric</a> toolkit and has been writing about personal AI infrastructure since 2016 — puts it well in his <a href="https://danielmiessler.com/p/personal-ai-maturity-model">Personal AI Maturity Model</a>. The model has three tiers: Chatbots, then Agents, then Assistants. Most people are still deep in the agent tier. The interesting work is starting to happen at the assistant tier.</p>
<table><thead><tr><th>Tier</th><th>Stages</th><th>Where we are</th></tr></thead><tbody><tr><td><strong>Chatbots</strong></td><td>CH1–CH3</td><td>Basic chat → advanced tooling and context (2022–2024)</td></tr><tr><td><strong>Agents</strong></td><td>AG1–AG3</td><td>Standalone frameworks → continuous background operation (late 2024–2027)</td></tr><tr><td><strong>Assistants</strong></td><td>AS1–AS3</td><td>Named companions → proactive, trusted advocates (2027–2030+)</td></tr></tbody></table>
<p>The difference isn’t just complexity. It’s orientation. Chatbots respond to requests. Agents execute assigned tasks. Assistants <em>advocate</em> — understanding goals, monitoring the gap between current state and where things should be, closing it without being asked.</p>
<p>That framing clicked something for me. The AI Daily Brief’s New Year’s Resolution was optimizing me for the chatbot tier. My work track is pushing me into the agent tier. And personally, I’m drawn to the assistant tier — which requires a completely different kind of thinking.</p>
<h2>What I’ve Been Actually Building</h2>
<p>The tool I’ve started exploring on the personal track is <a href="https://github.com/danielmiessler/PAI">PAI</a> (Personal AI Infrastructure), also by Miessler. It’s open source, it’s opinionated, and it’s built around the idea of a single named assistant — in Daniel’s case, his assistant is called Kai — that acts as a persistent interface to everything else.</p>
<p>The setup involves defining goals, context, and preferences — what Miessler calls an “ideal state” — and giving that to the assistant as the thing it’s constantly working toward. The agents, the skills, the workflows all sit behind that interface. There’s no direct interaction with the infrastructure. There’s just my interaction with my assistant.</p>
<p>I’m early in exploring it. But the mental model it requires is so different from “write better prompts” that I couldn’t keep treating both as the same kind of work. His <a href="https://danielmiessler.com/p/personal-ai-maturity-model">Personal AI Maturity Model</a> is worth a read.</p>
<h2>What the Resolution Actually Gave Me</h2>
<p>I didn’t finish the 10 weekends. I also don’t think the resolution failed — it gave me exactly what I needed, just not what I expected.</p>
<p>Week 1 forced me to build something and ship it. That’s always useful. Week 2 pushed me to think systematically about which models are good at what — which turns out to matter when building agent systems, where picking the right model for the right task in a pipeline is a real decision.</p>
<p>But the bigger thing was the clarity. When I stopped trying to complete the resolution and started asking why it didn’t feel urgent anymore, the answer told me a lot about where my head actually was. I wasn’t thinking about chat windows. I was thinking about context, about systems, about what it would look like to have a persistent AI collaborator rather than a tool I pick up and put down.</p>
<p>That’s a different kind of skill to develop. And I think it’s the one worth developing.</p>
]]></description>
	</item>
	
	<item>
		<title>Revisiting My FFmpeg Screencast Workflow</title>
		<link>https://passionsplay.com/blog/mov2mp4/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/mov2mp4/</guid>
		<pubDate>Tue, 28 Apr 2026 00:00:00 GMT</pubDate>
		<description><![CDATA[<p>For a long time I’ve written technical documentation and used short screencasts to help explain
workflows and concepts — ten-second demos, no audio, uploaded directly to WordPress. My go-to
approach was a small bash script that wrapped ffmpeg and helped me remember the right flags
without having to look them up every time.</p>
<p>It worked. But I’d never really interrogated it.</p>
<p>One of the things I’ve been doing lately is revisiting old tools and workflows with the help of an
AI. Not to replace them, but to actually understand them better than I did when I first wrote them.
When I walked through my screencast script in a recent session, I came away with a few things I
genuinely didn’t know — including one flag that explains why some of my existing videos buffer
slightly before playing in a browser.</p>
<h2>What My Original Script Did</h2>
<p>The original was two lines:</p>
<pre class="language-bash"><code class="language-bash">ffmpeg <span class="token parameter variable">-i</span> <span class="token string">"<span class="token variable">${file}</span>"</span> <span class="token parameter variable">-vcodec</span> h264 <span class="token parameter variable">-acodec</span> aac <span class="token string">"<span class="token variable">${slug}</span>.mp4"</span></code></pre>
<p>That’s a functional command, but it has a few issues worth addressing:</p>
<ul>
<li><code>-vcodec</code> and <code>-acodec</code> are <a href="https://ffmpeg.org/ffmpeg-codecs.html">deprecated aliases</a>. The modern flags are <code>-c:v</code> and <code>-c:a</code></li>
<li>There’s no explicit quality setting, so ffmpeg uses its default (CRF 23), which is fine but arbitrary</li>
<li>It doesn’t handle files without audio cleanly</li>
<li>It’s missing <code>-movflags +faststart</code>, which turns out to matter for web playback</li>
</ul>
<h2>The movflags +faststart Flag</h2>
<p>This was the most interesting thing I learned. The <a href="https://ffmpeg.org/ffmpeg-formats.html">ffmpeg formats documentation</a> describes it as:</p>
<blockquote>
<p>Run a second pass moving the index (moov atom) to the beginning of the file.</p>
</blockquote>
<p>In plain terms: an MP4 file contains a metadata block that tells the browser how to decode and
play it. By default, ffmpeg writes that block at the <em>end</em> of the file. That means a browser has
to download the entire file before it can start playing it. With <code>-movflags +faststart</code>, that block
is moved to the front, and playback can begin immediately.</p>
<p>For short screencasts this isn’t dramatic — but it’s the difference between a video that starts
playing and one that sits there for a beat before it does.</p>
<h2>CRF for Screencasts</h2>
<p><a href="https://trac.ffmpeg.org/wiki/Encode/H.264">The ffmpeg H.264 encoding guide</a> documents CRF (Constant Rate Factor) as the recommended
quality control method for libx264. The range is 0–51, lower is better, default is 23.</p>
<p>Screencasts are mostly flat backgrounds and sharp text — a different compression profile than live
video or film. CRF 18–20 produces noticeably crisper text at still-small file sizes for this kind of
content. The <a href="https://slhck.info/video/2017/02/24/crf-guide.html">CRF guide from Sebastian Ebner</a> is a good reference if you want to understand the
tradeoffs in more depth.</p>
<h2>The Updated Script</h2>
<pre class="language-bash"><code class="language-bash"><span class="token shebang important">#!/usr/bin/env bash</span>
<span class="token comment"># Convert MOV (or any ffmpeg-compatible video) to MP4 optimized for web delivery.</span>

<span class="token builtin class-name">set</span> <span class="token parameter variable">-euo</span> pipefail

<span class="token function-name function">usage</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
  <span class="token function">cat</span> <span class="token operator">></span><span class="token file-descriptor important">&amp;2</span> <span class="token operator">&lt;&lt;</span><span class="token string">'EOF'
mov2mp4 — Convert video to web-ready MP4

Usage:
  mov2mp4 &lt;input> [crf]

Arguments:
  input   Source video (MOV, MP4, MKV, etc.)
  crf     Quality level 0-51, lower = better (default: 20)
          18-20 is the sweet spot for screencasts
          23 is ffmpeg's general-purpose default

Examples:
  mov2mp4 demo.mov
  mov2mp4 demo.mov 18
EOF</span>
<span class="token punctuation">}</span>

<span class="token assign-left variable">INPUT</span><span class="token operator">=</span><span class="token string">"<span class="token variable">${1<span class="token operator">:-</span>}</span>"</span>
<span class="token keyword">if</span> <span class="token punctuation">[</span><span class="token punctuation">[</span> <span class="token parameter variable">-z</span> <span class="token string">"<span class="token variable">$INPUT</span>"</span> <span class="token operator">||</span> <span class="token string">"<span class="token variable">$INPUT</span>"</span> <span class="token operator">==</span> <span class="token string">"--help"</span> <span class="token operator">||</span> <span class="token string">"<span class="token variable">$INPUT</span>"</span> <span class="token operator">==</span> <span class="token string">"-h"</span> <span class="token punctuation">]</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">then</span>
  usage
  <span class="token punctuation">[</span><span class="token punctuation">[</span> <span class="token parameter variable">-z</span> <span class="token string">"<span class="token variable">$INPUT</span>"</span> <span class="token punctuation">]</span><span class="token punctuation">]</span> <span class="token operator">&amp;&amp;</span> <span class="token builtin class-name">exit</span> <span class="token number">1</span> <span class="token operator">||</span> <span class="token builtin class-name">exit</span> <span class="token number">0</span>
<span class="token keyword">fi</span>

<span class="token keyword">if</span> <span class="token operator">!</span> ffprobe <span class="token parameter variable">-v</span> error <span class="token parameter variable">-i</span> <span class="token string">"<span class="token variable">$INPUT</span>"</span> <span class="token operator">></span> /dev/null <span class="token operator"><span class="token file-descriptor important">2</span>></span><span class="token file-descriptor important">&amp;1</span><span class="token punctuation">;</span> <span class="token keyword">then</span>
  <span class="token builtin class-name">echo</span> <span class="token string">"Error: not a valid media file: <span class="token variable">$INPUT</span>"</span> <span class="token operator">></span><span class="token file-descriptor important">&amp;2</span>
  <span class="token builtin class-name">exit</span> <span class="token number">1</span>
<span class="token keyword">fi</span>

<span class="token assign-left variable">CRF</span><span class="token operator">=</span><span class="token string">"<span class="token variable">${2<span class="token operator">:-</span>20}</span>"</span>
<span class="token assign-left variable">SLUG</span><span class="token operator">=</span><span class="token string">"<span class="token variable">${INPUT<span class="token operator">%</span>.*}</span>"</span>
<span class="token assign-left variable">OUTPUT</span><span class="token operator">=</span><span class="token string">"<span class="token variable">${SLUG}</span>.mp4"</span>

<span class="token assign-left variable">HAS_AUDIO</span><span class="token operator">=</span><span class="token variable"><span class="token variable">$(</span>ffprobe <span class="token parameter variable">-v</span> error <span class="token parameter variable">-select_streams</span> a <span class="token punctuation">\</span>
  <span class="token parameter variable">-show_entries</span> <span class="token assign-left variable">stream</span><span class="token operator">=</span>codec_type <span class="token punctuation">\</span>
  <span class="token parameter variable">-of</span> <span class="token assign-left variable">default</span><span class="token operator">=</span>noprint_wrappers<span class="token operator">=</span><span class="token number">1</span> <span class="token string">"<span class="token variable">$INPUT</span>"</span> <span class="token operator"><span class="token file-descriptor important">2</span>></span>/dev/null<span class="token variable">)</span></span>

<span class="token assign-left variable">AUDIO_FLAGS</span><span class="token operator">=</span><span class="token string">"-an"</span>
<span class="token punctuation">[</span><span class="token punctuation">[</span> <span class="token parameter variable">-n</span> <span class="token string">"<span class="token variable">$HAS_AUDIO</span>"</span> <span class="token punctuation">]</span><span class="token punctuation">]</span> <span class="token operator">&amp;&amp;</span> <span class="token assign-left variable">AUDIO_FLAGS</span><span class="token operator">=</span><span class="token string">"-c:a aac -ar 44100"</span>

<span class="token builtin class-name">echo</span> <span class="token string">"Converting: <span class="token variable">$INPUT</span> → <span class="token variable">$OUTPUT</span> (CRF=<span class="token variable">$CRF</span>)"</span>

ffmpeg <span class="token parameter variable">-i</span> <span class="token string">"<span class="token variable">$INPUT</span>"</span> <span class="token parameter variable">-c:v</span> libx264 <span class="token parameter variable">-crf</span> <span class="token string">"<span class="token variable">$CRF</span>"</span> <span class="token variable">$AUDIO_FLAGS</span> <span class="token parameter variable">-movflags</span> +faststart <span class="token string">"<span class="token variable">$OUTPUT</span>"</span>

<span class="token assign-left variable">ORIGINAL</span><span class="token operator">=</span><span class="token variable"><span class="token variable">$(</span><span class="token function">du</span> <span class="token parameter variable">-sh</span> <span class="token string">"<span class="token variable">$INPUT</span>"</span> <span class="token operator">|</span> <span class="token function">cut</span> <span class="token parameter variable">-f1</span><span class="token variable">)</span></span>
<span class="token assign-left variable">RESULT</span><span class="token operator">=</span><span class="token variable"><span class="token variable">$(</span><span class="token function">du</span> <span class="token parameter variable">-sh</span> <span class="token string">"<span class="token variable">$OUTPUT</span>"</span> <span class="token operator">|</span> <span class="token function">cut</span> <span class="token parameter variable">-f1</span><span class="token variable">)</span></span>
<span class="token builtin class-name">echo</span> <span class="token string">"Done: <span class="token variable">$ORIGINAL</span> → <span class="token variable">$RESULT</span>"</span></code></pre>
<p>Save it to <code>~/.local/bin/mov2mp4</code>, <code>chmod +x ~/.local/bin/mov2mp4</code>, and make sure
<code>~/.local/bin</code> is on your <code>$PATH</code>.</p>
<p>A few things worth noting about how this differs from the original:</p>
<ul>
<li>Accepts any ffmpeg-compatible input, not just <code>.mov</code> — so uppercase <code>.MOV</code> from an iPhone
works the same as anything from QuickTime or OBS</li>
<li>Detects whether the input has an audio track and handles it correctly either way: silent
screencasts get <code>-an</code> (explicit and clean), files with audio get AAC at 44100Hz</li>
<li>Input validation via <code>ffprobe</code> before attempting conversion</li>
<li>Prints file size before and after, which is a small thing but useful for spot-checking output</li>
</ul>
]]></description>
	</item>
	
	<item>
		<title>AIDBNY – Wk2: A First Pass at Model Mapping</title>
		<link>https://passionsplay.com/blog/aidbny-wk2-a-first-pass-at-model-mapping/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/aidbny-wk2-a-first-pass-at-model-mapping/</guid>
		<pubDate>Wed, 04 Mar 2026 13:31:37 GMT</pubDate>
		<description><![CDATA[<hr>
<p>Week 2 of the <a href="https://aidbnewyear.com/">AI Daily Brief New Year’s resolution</a> (AIDBNY) sessions is about moving away from anecdotal vibes and toward a repeatable way to measure model performance. I’m starting to build a <strong>Model Map</strong>—a systematic comparison of how different LLMs handle specific task types.</p>
<p>This is a first pass. The goal isn’t a definitive ranking yet, but rather a baseline to see where different models succeed or fail when given the same set of constraints.</p>
<h2>The Workflow: Emacs + gptel</h2>
<p>My day-to-day work environment is Emacs, and I’ve been wanting to use <a href="https://github.com/karthink/gptel">gptel</a> in more of my workflows. Gptel’s integration into Emacs is quite good, meaning I can leverage AI within almost any place I can move the cursor.</p>
<h2>The Prompt Library</h2>
<p>I’m sure there’s an exhaustive set of prompts that can be used. But for this initial evaluation, I’ve settled on six prompts categorized by three task types to begin the evaluation process:</p>
<ol>
<li>Code: Mechanical aspects of software development.</li>
<li>Analysis: Use a more advanced level of structural understanding to output insightful and valid diagram syntax.</li>
<li>Creative: Test the model’s ability to synthesize unstructured notes into structured strategy or narrative.</li>
</ol>
<h3>P001 (Code: Refactoring)</h3>
<p>Identifying dependencies and refactoring snippets into reusable, typed functions with proper documentation.</p>
<pre class="language-plain"><code class="language-plain">Refactor this code into a reusable function that accepts all dependencies as parameters: \[CODE\_SNIPPET\]
Requirements:
 \* Identify all variables and functions used in the code block
 \* Create a function signature that accepts these as parameters
 \* Replace hard-coded values with function parameters
 \* Add TypeScript types if applicable
 \* Include JSDoc comments explaining the function's purpose and parameters
 \* Handle edge cases and provide default values where appropriate
 \* Preserve the original functionality exactly</code></pre>
<h3>P002 (Code: Debugging)</h3>
<p>Finding bugs in JavaScript and providing a root-cause explanation alongside corrected code and test cases.</p>
<pre class="language-plain"><code class="language-plain">Debug this JavaScript function and explain the issue: \[CODE\_SNIPPET\]
Requirements:
 \* Identify the bug(s) in the code
 \* Explain why the bug occurs
 \* Provide the corrected code
 \* Include test cases that demonstrate the fix
 \* Explain any potential edge cases</code></pre>
<h3>P003 (Analysis: Call Flow)</h3>
<p>Analyzing a codebase to generate <a href="https://mermaid.js.org/">Mermaid</a> sequence diagrams for specific functions.</p>
<pre class="language-plain"><code class="language-plain">Generate a Mermaid sequence diagram for the call flow of \[FUNCTION\_NAME\] using the provided code context.
Requirements:
 \* Accept multiple files as context (codebase)
 \* Locate the specified function \[FUNCTION\_NAME\]
 \* Analyze the function's call graph and dependencies
 \* Generate a Mermaid sequence diagram showing:
   \* Function calls in chronological order
   \* Parameter passing between functions
   \* Return values and data flow
   \* Error handling paths
 \* Include all nested function calls and their relationships
 \* Use proper Mermaid syntax with sequenceDiagram tags
 \* Provide both the diagram code and a visual representation</code></pre>
<h3>P004 (Analysis: Architecture)</h3>
<p>Using <a href="https://structurizr.com/">Structurizr</a> DSL to map out system architecture at various C4 levels (Context, Container, Component, or Code).</p>
<pre class="language-plain"><code class="language-plain">Generate a C4 diagram using Structurizr syntax for the provided codebase at \[DETAIL\_LEVEL\].
Requirements:
 \* Accept multiple files as context (codebase)
 \* Accept \[DETAIL\_LEVEL\] parameter (C1, C2, C3, or C4)
 \* Generate appropriate C4 diagram based on level:
   \* C1: Context diagram (systems and users)
   \* C2: Container diagram (applications, databases, etc.)
   \* C3: Component diagram (internal components)
   \* C4: Code diagram (classes, interfaces, etc.)
 \* Use proper Structurizr DSL syntax
 \* Include all relevant elements and relationships
 \* Provide both the diagram code and a visual representation
 \* Explain the architectural decisions and patterns identified</code></pre>
<h3>P005 (Creative: Strategy Map)</h3>
<p>Creating a directed graph of content dependencies and clusters needed to reach a specific topical authority.</p>
<pre class="language-plain"><code class="language-plain">Generate a content strategy map and directed graph for achieving \[CONTENT\_DESTINATION\] based on existing content.
Requirements:
 \* Analyze existing content inventory and identify gaps
 \* Define clear \[CONTENT\_DESTINATION\] goal (e.g., "Become recognized expert in AI development")
 \* Create a directed graph showing:
   \* Required content pieces to reach destination
   \* Dependencies between content items
   \* Content hierarchy and relationships
   \* Content clusters and topical authority
 \* Generate a visual representation using Mermaid or Graphviz syntax
 \* Include content type recommendations (blog posts, videos, tutorials, etc.)
 \* Provide timeline and prioritization suggestions
 \* Identify key performance indicators for measuring progress</code></pre>
<h3>P006 (Creative: Contextual Drafting)</h3>
<p>Synthesizing research files and style guidelines into a cohesive post with proper citations and metadata.</p>
<pre class="language-plain"><code class="language-plain">Write a blog post based on provided context notes, source documents, and style guidelines.
Requirements:
 \* Accept multiple context files as research material
 \* Accept \[TARGET\_LENGTH\] parameter (word count)
 \* Accept \[VOICE\_STYLE\_GUIDE\] parameter (brand tone, vocabulary, formatting)
 \* Synthesize information from all sources into a cohesive narrative
 \* Follow the specified voice and style guidelines exactly
 \* Include proper citations and references where appropriate
 \* Generate a compelling headline and meta description
 \* Generate an excerpt of no more than 155 characters
 \* Provide both the full post and an outline structure</code></pre>
<hr>
<h2>Code Tasks and Agents</h2>
<p>While I’m including code refactoring and debugging in this initial test set, I suspect these manual prompts may eventually become redundant. As my workflow leans more toward agentic patterns, the need to explicitly prompt for a refactor starts to feel like an intermediate step.</p>
<p>If an agent is operating with full repository context, these individual tasks should ideally happen in the background. For now, however, testing the underlying logic of the models remains the necessary first step.</p>
]]></description>
	</item>
	
	<item>
		<title>Second Brain Insurance: Restic + Org-Roam</title>
		<link>https://passionsplay.com/blog/second-brain-insurance-restic-org-roam/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/second-brain-insurance-restic-org-roam/</guid>
		<pubDate>Tue, 03 Mar 2026 08:21:17 GMT</pubDate>
		<description><![CDATA[<p>I’ve been hoarding notes in <a href="https://www.orgroam.com/">Org-Roam</a> like a digital dragon. It’s my second brain, my task list, and my scratchpad for everything from TypeScript snippets to local development hacks. As that directory grows, so does the low-level anxiety that a single disk failure or a fat-fingered command could wipe out years of “aha!” moments.</p>
<p>I am in the process of committing these notes to a Git repository, but not everything is ready to be committed.</p>
<p>I needed a backup solution that was three things: <strong>encrypted</strong> (because my notes are private), <strong>deduplicated</strong> (to save space), and <strong>automated</strong>. I landed on <a href="https://restic.net/">Restic</a>, a fast, secure backup tool that fits perfectly into a CLI-centric workflow.</p>
<h2>Lesson Learned: Trust the Tool</h2>
<p>When I first sat down to build this, I spent a bit of time over-engineering my folder structure. I was manually injecting shell variables to keep things organized across different machines. It looked something like this:</p>
<pre class="language-bash"><code class="language-bash"><span class="token punctuation">\</span># The <span class="token string">"Over-Engineered"</span> Way
<span class="token builtin class-name">export</span> backup<span class="token punctuation">\</span>_slug<span class="token operator">=</span><span class="token string">"kb"</span><span class="token builtin class-name">export</span>
restic<span class="token punctuation">\</span>_repo<span class="token operator">=</span><span class="token string">"<span class="token environment constant">$HOME</span>/.backups/<span class="token environment constant">$USER</span>@<span class="token variable"><span class="token variable">$(</span><span class="token function">hostname</span><span class="token variable">)</span></span>--<span class="token variable">${backup\_slug}</span>"</span></code></pre>
<p>After reviewing <a href="https://restic.readthedocs.io/en/stable/045_working_with_repos.html#listing-all-snapshots">Restic’s documentation on listing snapshots</a>, I realized the tool already handles snapshots from multiple hosts natively. Every time you create a snapshot, Restic tags it with the host it came from by default. This means I can keep the repository path simple on my disk (<code>~/.backups/kb</code>) and let Restic’s internal metadata handle the host identification. While the current implementation doesn’t backup multiple hosts to the same remote Restic repo, it positions me to do just that when I eventually save my snapshots to the cloud.</p>
<hr>
<h2>The Manual Foundation</h2>
<p>Before scripting everything away, I like to run the commands manually to make sure the “plumbing” works. Now that I’ve ditched the manual namespacing, the setup is much more straightforward.</p>
<h3>1. Initializing the Repo</h3>
<p>We’ll just use a straightforward <code>kb</code> (knowledge base) directory in our hidden backups folder.</p>
<pre class="language-bash"><code class="language-bash"><span class="token builtin class-name">export</span> restic<span class="token punctuation">\</span>_repo<span class="token operator">=</span><span class="token string">"<span class="token environment constant">$HOME</span>/.backups/kb"</span><span class="token function">mkdir</span> <span class="token parameter variable">-p</span> <span class="token string">"<span class="token variable">${restic\_repo}</span>"</span>
restic <span class="token parameter variable">-r</span> <span class="token string">"<span class="token variable">${restic\_repo}</span>"</span> init</code></pre>
<p><strong>A note on security:</strong> When you run <code>init</code>, Restic will ask for a password. This password is the only way to decrypt your data. There’s no recovery flow here, so keep it somewhere safe like a password manager.</p>
<h3>2. The First Pass</h3>
<p>Next, we push the files. I make sure to exclude metadata folders like <code>.git</code> or <code>.stfolder</code> (from Syncthing). There’s no point in backing up my version control history inside a backup.</p>
<pre class="language-bash"><code class="language-bash">restic <span class="token parameter variable">-r</span> <span class="token string">"<span class="token variable">${restic\_repo}</span>"</span> backup <span class="token punctuation">\</span><span class="token punctuation">\</span>
    <span class="token parameter variable">--exclude</span> <span class="token string">'.git'</span> <span class="token punctuation">\</span><span class="token punctuation">\</span>
    <span class="token parameter variable">--exclude</span> <span class="token string">'.stfolder'</span> <span class="token punctuation">\</span><span class="token punctuation">\</span>
    <span class="token parameter variable">--exclude</span> <span class="token string">'.stignore'</span> <span class="token punctuation">\</span><span class="token punctuation">\</span>
    ~/kb</code></pre>
<p>Once this finishes, you can run <code>restic snapshots</code> and you’ll see your hostname automatically attached to the entry. Clean and simple.</p>
<hr>
<h2>The “Set It and Forget It” Script</h2>
<p>Manual commands are great for learning and one-off execution, but for real-world reliability, we need a script. I’ve wrapped the logic into a Bash script that handles the repo creation automatically.</p>
<pre class="language-bash"><code class="language-bash"><span class="token shebang important">#!/usr/bin/env bash</span>
<span class="token comment"># Backup the Org-roam knowledge base</span>
<span class="token builtin class-name">export</span> backup<span class="token punctuation">\</span>_slug<span class="token operator">=</span><span class="token string">"kb"</span>
<span class="token builtin class-name">export</span> restic<span class="token punctuation">\</span>_repo<span class="token operator">=</span><span class="token string">"<span class="token variable">${<span class="token environment constant">HOME</span>}</span>/.backups/kb"</span>

<span class="token comment"># Check if repo exists, otherwise, create it. By using this paradigm,</span>
<span class="token comment"># I can sync this backup script across hosts and backup the same repo:</span>
<span class="token keyword">if</span> <span class="token punctuation">\</span><span class="token punctuation">[</span> <span class="token operator">!</span> <span class="token parameter variable">-f</span>  <span class="token string">"<span class="token variable">${restic\_repo}</span>/config"</span> <span class="token punctuation">\</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">then</span>
  <span class="token function">mkdir</span> <span class="token parameter variable">-p</span> <span class="token string">"<span class="token variable">${restic\_repo}</span>"</span>
  restic <span class="token parameter variable">-r</span> <span class="token string">"<span class="token variable">${restic\_repo}</span>"</span> init
<span class="token keyword">fi</span>

<span class="token comment"># Create the backup</span>
restic <span class="token parameter variable">-r</span> <span class="token string">"<span class="token variable">${restic\_repo}</span>"</span> backup <span class="token punctuation">\</span><span class="token punctuation">\</span>
  <span class="token parameter variable">--exclude</span> <span class="token string">'.git'</span> <span class="token punctuation">\</span><span class="token punctuation">\</span>
  <span class="token parameter variable">--exclude</span> <span class="token string">'node\_modules'</span> <span class="token punctuation">\</span><span class="token punctuation">\</span>
  <span class="token parameter variable">--exclude</span> <span class="token string">'.stfolder'</span> <span class="token punctuation">\</span><span class="token punctuation">\</span>
  <span class="token parameter variable">--exclude</span> <span class="token string">'.stignore'</span> <span class="token punctuation">\</span><span class="token punctuation">\</span>
  ~/kb</code></pre>
<h2>What’s Next?</h2>
<p>Right now, this is backing up to my local disk. It protects me against accidentally deleting a file or a weird filesystem corruption, but it won’t help if my laptop decides to take a coffee bath.</p>
<p>The next evolution of this is to hook up <strong>Rclone</strong>. Since Restic supports Rclone as a backend, I can eventually pipe these encrypted snapshots straight to Google Drive, a B2 bucket, or an S3 instance. That’ll get me to that “3-2-1” backup gold standard. But for today? My notes are safe, encrypted, and I can get back to building.</p>
]]></description>
	</item>
	
	<item>
		<title>AIDBNY – Wk 1: Building an AI Resolution Tracker</title>
		<link>https://passionsplay.com/blog/aidbny-week-1-building-an-ai-resolution-tracker/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/aidbny-week-1-building-an-ai-resolution-tracker/</guid>
		<pubDate>Fri, 27 Feb 2026 07:23:16 GMT</pubDate>
		<description><![CDATA[<p>I’ve decided to lean into the <a href="https://aidbnewyear.com/program">AI Daily Brief New Year’s Resolution</a>, an accountability framework designed to keep us shipping one AI-centric project a week for ten weeks. As someone who spends my days building <a href="https://localwp.com">Local by WP Engine</a>, I’m usually deep in the flow of the tech-builder’s craft. For Week 1, the goal was simple: build a working web app to track my progress through this 10-weekend sprint.</p>
<p>But as any engineer knows, the “how” is just as important as the “what.” I wanted to see if I could find a workflow that felt like actual engineering—not just a glorified chat interface. I tried two different platforms (<a href="https://replit.com/">Replit</a>, <a href="https://base44.com/">Base44</a>) and a CLI agent (<a href="https://opencode.ai/">OpenCode</a>).</p>
<h3>Friction of no-code Builders</h3>
<p>I started where most people do: the high-abstraction platforms. I tried using the same prompts across the board to see how they’d handle the logic of a resolution tracker.</p>
<p>Both eventually hit a wall where they wanted me to “pony up” some cash before I had a usable tool. For a hobbyist project where the goal is *play*, hitting a paywall five minutes in is a massive vibe-killer.</p>
<h4>Replit</h4>
<p>I set up a project at <a href="%5Bhttps://replit.com/@passionsplay/AI-Progress-Tracker%5D(https://replit.com/@passionsplay/AI-Progress-Tracker)">passionsplay/AI-Progress-Tracker</a>, but the honeymoon phase ended fast. It only gave me about two prompts before the output stalled, and the final product wasn’t exactly something I’d want to ship.</p>
<p><img src="aidbny-w01-replit-progress-tra-vvm5LwgCos7J.png" alt=""></p>
<h4>Base44</h4>
<p>This one was a bit more generous with the prompt count and even dangled a 30% onboarding coupon in front of me. It did a great job of abstracting away the technical decisions, which is cool for a MVP, but as a dev, I kept wanting to peek under the hood.</p>
<p><img src="aidbny-w01-base44-progress-tra-ZM1u7qSinw79.png" alt=""></p>
<h3>OpenCode: Where the Real Work (and Play) Happens</h3>
<p>I eventually pivoted to OpenCode, and this is where things got interesting. It required more setup, but the environment felt familiar—more like my actual day-to-day workbench.</p>
<p>Instead of just asking for a “tracker app,” I collaborated with an agent to handle the heavy lifting while I made the “dev-y” decisions. We walked through a legitimate engineering workflow:</p>
<ul>
<li>Initializing the Git repository.</li>
<li>Building out the initial prototype.</li>
<li>Setting up <a href="https://github.com/microsoft/playwright-cli">Playwright-cli</a> so the agent could actually see and interact with the project it was building.</li>
</ul>
<p>You can check out the final Week 1 result here:</p>
<ul>
<li>Repo: <a href="https://github.com/bgturner/aidb-new-year--wk01-resolution-tracker">https://github.com/bgturner/aidb-new-year–wk01-resolution-tracker</a></li>
<li>Live MVP: <a href="https://bgturner.github.io/aidb-new-year--wk01-resolution-tracker/">https://bgturner.github.io/aidb-new-year–wk01-resolution-tracker/</a></li>
</ul>
<p><img src="aidbny-w01-opencode-progress-t-dnjJpjI9WtUi.png" alt=""></p>
<h3>The Takeaway</h3>
<p>Was OpenCode + Github more work?</p>
<p>Yep.</p>
<p>It’s possible that I still need to learn to let go of caring about the craft of building and just let the AI figure it out.</p>
<p>On the other hand, the output is a real repo that I actually own and can improve upon. Plus, it was effectively free—though I’m sure my interactions are helping train whatever upstream model they’re using.</p>
<p>Week 1 is in the books. I have a tracker, a repository, and a workflow that doesn’t feel like a toy. Onward to Week 2.</p>
]]></description>
	</item>
	
	<item>
		<title>Diving into the AI Daily Brief</title>
		<link>https://passionsplay.com/blog/diving-into-the-ai-daily-brief/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/diving-into-the-ai-daily-brief/</guid>
		<pubDate>Wed, 25 Feb 2026 07:11:50 GMT</pubDate>
		<description><![CDATA[<p>I’ve decided that 2026 is the year I put AI front and center into my workflow. 2025 saw me dabbling with numerous chatbots and coding agents as I built things at WP Engine.</p>
<p>But the space is moving so fast that it’s hard to keep current, especially when it comes to other non-engineering tools I don’t use day-to-day.</p>
<p>I stumbled on the AI Daily Brief podcast (<a href="https://www.youtube.com/@AIDailyBrief">YouTube</a>, <a href="https://open.spotify.com/show/7gKwwMLFLc6RmjmRpbMtEO">Spotify</a>), and like the content so far. But my goal isn’t just to consume more content. Instead, I want to integrate AI into my daily routine.</p>
<p>The <a href="https://aidailybrief.ai/">AI Daily Brief’s site</a> has an interesting UX, and while poking around, I found two guided tracks which help guide you to using and learning AI:</p>
<ol>
<li>Projects to learn and use AI: <a href="https://aidbnewyear.com/program">AI New Years | 10-Week AI Challenge</a></li>
<li>Projects to build you Agentic Swarm: <a href="https://campclaw.ai/">Claw Camp — Build Your Agent Team</a></li>
</ol>
<p>So, nothing concretely built yet, but I’m looking forward to seeing where this structured, playful approach takes my technical workflow over the next few months!</p>
]]></description>
	</item>
	
	<item>
		<title>Securing OpenRouter keys with Direnv and GPG</title>
		<link>https://passionsplay.com/blog/securing-openrouter-keys-with-direnv-and-gpg/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/securing-openrouter-keys-with-direnv-and-gpg/</guid>
		<pubDate>Thu, 29 Jan 2026 14:43:46 GMT</pubDate>
		<description><![CDATA[<p>I really enjoy using <strong>Claude Code</strong>, though I’m not a fan of the common practice of exporting API keys directly in a global shell configuration file like <code>.zshrc</code>. It feels insecure to have secrets floating around, and it increases the risk of accidentally committing them to version control.</p>
<p>A better approach is to keep keys encrypted and only load them into the environment when you are working in a specific project’s directory. This post outlines a straightforward, secure, and directory-aware workflow using GPG and <code>direnv</code>.</p>
<h2>The OpenRouter Context</h2>
<p>For this example, I’m using OpenRouter to access Claude Code, which offers benefits like provider failover and budget controls. According to their <a href="https://openrouter.ai/docs/guides/guides/claude-code-integration">integration guide</a>, the <code>claude</code> CLI requires the following environment variables:</p>
<pre class="language-bash"><code class="language-bash"><span class="token builtin class-name">export</span> ANTHROPIC<span class="token punctuation">\</span>_BASE<span class="token punctuation">\</span>_URL<span class="token operator">=</span><span class="token string">"https://openrouter.ai/api"</span>
<span class="token builtin class-name">export</span> ANTHROPIC<span class="token punctuation">\</span>_AUTH<span class="token punctuation">\</span>_TOKEN<span class="token operator">=</span><span class="token string">"<span class="token variable">$OPENROUTER</span>\_API\_KEY"</span>
<span class="token builtin class-name">export</span> ANTHROPIC<span class="token punctuation">\</span>_API<span class="token punctuation">\</span>_KEY<span class="token operator">=</span><span class="token string">""</span> <span class="token comment"># Must be explicitly empty</span></code></pre>
<p>Our goal is to manage the <code>$OPENROUTER_API_KEY</code> secret securely.</p>
<h2>Create and Encrypt the Secrets File</h2>
<p>Emacs’s built-in EasyPG integration (<code>epa-mode</code>) makes handling GPG-encrypted files transparent.</p>
<ol>
<li><strong>Create the secret file:</strong> In your project, open a new file named <code>.env-secrets.gpg</code> by typing <code>C-x C-f .env-secrets.gpg</code>.</li>
<li><strong>Add all required variables:</strong> Add the complete configuration, including your secret key.</li>
</ol>
<pre class="language-bash"><code class="language-bash"><span class="token builtin class-name">export</span> OPENROUTER<span class="token punctuation">\</span>_API<span class="token punctuation">\</span>_KEY<span class="token operator">=</span><span class="token string">'sk-or-v1-...'</span>
<span class="token builtin class-name">export</span> ANTHROPIC<span class="token punctuation">\</span>_BASE<span class="token punctuation">\</span>_URL<span class="token operator">=</span><span class="token string">"https://openrouter.ai/api"</span>
<span class="token builtin class-name">export</span> ANTHROPIC<span class="token punctuation">\</span>_AUTH<span class="token punctuation">\</span>_TOKEN<span class="token operator">=</span><span class="token string">"<span class="token variable">$OPENROUTER</span>\_API\_KEY"</span>
<span class="token builtin class-name">export</span> ANTHROPIC<span class="token punctuation">\</span>_API<span class="token punctuation">\</span>_KEY<span class="token operator">=</span><span class="token string">""</span></code></pre>
<ol start="3">
<li><strong>Save the file:</strong> When you save (<code>C-x C-s</code>), Emacs will prompt you to select your GPG public key for encryption.</li>
</ol>
<p>Your secrets are now encrypted on disk.</p>
<h2>Configure direnv to Load Secrets</h2>
<p>Next, we use <code>direnv</code> to automatically load the secrets.</p>
<ol>
<li><strong>Create the <code>.envrc</code> file:</strong> Create a file named <code>.envrc</code> in your project root with the following content. The <code>2&gt;/dev/null || true</code> part makes the command more robust by preventing errors if the secrets file doesn’t exist.</li>
</ol>
<pre class="language-plain"><code class="language-plain">\# .envrc
eval $(gpg -dq .env-secrets.gpg 2>/dev/null || true)</code></pre>
<ol start="2">
<li><strong>Allow direnv:</strong> In your terminal, run <code>direnv allow</code>.</li>
</ol>
<p>Now, whenever you <code>cd</code> into this directory, <code>direnv</code> will use GPG to decrypt <code>.env-secrets.gpg</code> in memory and load the variables into your environment. When you <code>cd</code> out, the variables are cleared.</p>
<h2>Prevent Committing Secrets</h2>
<p>Explicitly ignore the encrypted secrets file in your project’s <code>.gitignore</code> to prevent it from ever being committed.</p>
<pre><code>`echo &quot;.env-secrets.gpg&quot; &gt;&gt; .gitignore`Code language: CSS (css)
</code></pre>
<h2>Use Claude Code</h2>
<p>With the environment variables automatically loaded by <code>direnv</code>, you can now run the client:</p>
<pre><code>claude
</code></pre>
<h2>A Better Workflow</h2>
<p>This setup gives me peace of mind. My secrets are never stored in plaintext, they never pollute my global shell, and they can’t be accidentally committed to Git.</p>
<p>When I enter a project directory, the keys are there for me. When I leave, they’re gone. It’s a simple, secure, and automatic workflow that gets out of the way and lets me focus on the actual work.</p>
]]></description>
	</item>
	
	<item>
		<title>Visualize a Typescript Codebase with Dependency Cruiser</title>
		<link>https://passionsplay.com/blog/visualize-a-typescript-codebase-with-dependency-cruiser/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/visualize-a-typescript-codebase-with-dependency-cruiser/</guid>
		<pubDate>Fri, 18 Jul 2025 04:56:40 GMT</pubDate>
		<description><![CDATA[<p><a href="https://github.com/sverweij/dependency-cruiser">Dependency Cruiser</a> is a powerful npm tool designed to analyze and visualize dependencies within JavaScript and TypeScript projects.</p>
<p>I originally came across Dependency Cruiser as a way to help me generate <a href="https://c4model.com/diagrams/component">Component diagrams</a> as defined by the <a href="https://c4model.com/">C4 Model</a>.</p>
<p>This tool has been great at generating diagrams, but one exciting benefit of using dependency-cruiser is the ability to write <a href="https://github.com/sverweij/dependency-cruiser?tab=readme-ov-file#validate-things">architectural rules</a> to enforce boundaries and clean module separation.</p>
<p>Examples of these rules are:</p>
<ul>
<li><a href="https://spin.atomicobject.com/dependency-cruiser-imports/">Restricting imports between the client and server modules</a></li>
<li><a href="https://github.com/sverweij/dependency-cruiser/blob/main/doc/rules-tutorial.md#is-a-module-actually-used">Flagging unused modules</a></li>
<li><a href="https://github.com/sverweij/dependency-cruiser/blob/main/doc/rules-tutorial.md#isolating-peer-folders-from-each-other">Isolating peer folders from seeing each other</a></li>
</ul>
<p>By creating custom rules, you can verify changes adhere to architectural decisions for the project at regular intervals, such as during PR reviews or even as part of an automated AI Agent workflow.</p>
<h2>Installation</h2>
<p>Getting started with Dependency Cruiser involves:</p>
<ol>
<li>Installing the dependency</li>
<li>Initializing the configuration file</li>
</ol>
<pre class="language-plain"><code class="language-plain">npm install --save-dev dependency-cruiser
npx depcruise --init
# creates .dependency-cruiser.js</code></pre>
<h2>Usage</h2>
<p>I’ve found the best DX to be when I pair it with <a href="https://graphviz.org/docs/layouts/dot/">Graphviz’s Dot</a> command to generate SVG and HTML artifacts:</p>
<pre class="language-plain"><code class="language-plain"># Scan the app/main folder and only include items in the the app/main
# folder. Save as an HTML page to interact with.
yarn depcruise app/main -v --include-only '^app/main/' --output-type x-dot-webpage -f docs/architecture/dependency-cruiser/dependency-graph.app_main.html
# Scan the app/main folder and only inclue Site.ts and modules that
# directly depend on it.
yarn depcruise app/main --focus '^app/shared/models/Site.ts$' --output-type x-dot-webpage -f docs/architecture/dependency-cruiser/dependency-graph.html</code></pre>
<p><img src="example-dependency-cruiser-mai-p0nbCqhatOOt.png" alt=""></p>
]]></description>
	</item>
	
	<item>
		<title>Using Structurizr to Create C4 Model Diagrams</title>
		<link>https://passionsplay.com/blog/using-structurizr-to-create-c4-model-diagrams/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/using-structurizr-to-create-c4-model-diagrams/</guid>
		<pubDate>Fri, 18 Jul 2025 04:24:57 GMT</pubDate>
		<description><![CDATA[<p>The <a href="https://c4model.com">C4 Model</a> is a structured and hierarchical approach to visualizing software architecture. I covered the general idea in a <a href="../2025-05-30__the-c4-model-for-documenting-software/">previous post</a>, but to summarize, there are four levels of abstraction:</p>
<ul>
<li><strong>System Context</strong>: Provides a high-level overview of the software system in its environment, showing users and external systems interacting with it.</li>
<li><strong>Container</strong>: Zooms into the system, depicting the major containers (applications, services, databases) that constitute the system and their responsibilities.</li>
<li><strong>Component</strong>: Further decomposes a container into its constituent components, illustrating how the system is internally structured.</li>
<li><strong>Code</strong>: Details the internal structure of components, including classes, interfaces, or functions.</li>
</ul>
<p>Having this tiered model allows all stakeholders to engage with the architectural diagrams depending on their level of expertise and interest.</p>
<p>Traditional ad-hoc architectural sketches, drawn on whiteboards or with generic drawing tools like <a href="https://www.lucidchart.com/">LucidChart</a>, <a href="https://www.microsoft.com/en-us/microsoft-365/visio/flowchart-software">Visio</a>, or <a href="https://draw.io">draw.io</a> don’t enforce naming consistency or relationship correctness. In contrast, the C4 Model provides a framework for creating diagrams that are consistent and precise.</p>
<p>Because the C4 Model is a framework that standardizes the kinds of diagrams to create, there isn’t a formal recommendation for the tooling to use. That being said, the creator of the C4 Model, <a href="https://simonbrown.je/">Simon Brown</a>, also created <a href="https://structurizr.com/">Structurizr</a>, a collection of tools and a domain-specific-language, to help create C4 Model diagrams.</p>
<p>Over the past few weeks, I’ve taken a closer look at the C4 Model by working with Structurizr to better understand the architecture of <a href="https://localwp.com">Local</a>.</p>
<h2>3. Structurizr: Architecture as Code</h2>
<p>With Structurizr, you create a <code>workspace.dsl</code> file to contain the complete model of a software system’s elements and their relationships. From this model, multiple C4 diagram views can be generated: System Context, Container, and Component diagrams. Note that the “Code” view defined by the C4 framework isn’t included. The thinking around this is that it’s too specific and not worth keeping up-to-date within the model. Instead, if you need to visualize the code, you should try to generate it using tooling specific to the language or IDE.</p>
<p>One of Structurizr’s primary benefits is its “diagrams as code” approach. Because the architectural model is defined as text, changes can be tracked in version-control. Using version control allows for branch-based experimentation and collaborative editing which reduces the risk of diagrams becoming outdated or inconsistent. Diagrams can be automatically regenerated as the model is updated.</p>
<p>A simplified example for the Local desktop app along with Local Hub (a website to manage a user’s account) might look like:</p>
<pre class="language-plain"><code class="language-plain">workspace "Local" "The Local product described on https://localwp.com" {
    !identifiers hierarchical
    model {
        // Entities
        wpdev = person "WordPress Developer"
        lc = softwareSystem "Local Core"
        lh = softwareSystem "Local Hub"
        // Relationships
        wpdev -> lc "Uses"
        wpdev -> lh "Uses"
        lc -> lh "Connects to"
    }
    views {
        // https://docs.structurizr.com/dsl/language#theme
        theme default
        systemLandscape local "Local" {
            include *
            autolayout lr
        }
        systemContext lc "LocalCore" {
            include *
            autolayout lr
        }
        systemContext lh "LocalHub" {
            include *
            autolayout lr
        }
    }
}</code></pre>
<p>Which can be used to generate a diagram like:</p>
<p><img src="simple-model-structurizr-local-oOPNuzvDpTev.png" alt=""></p>
<p>A diagram this simple isn’t useful, but by modeling more systems, containers, and components, we can get a much more useful diagram:</p>
<p><img src="complex-model-structurizr-loca-DnpZpexLrPDx.png" alt=""></p>
<p>Structurizr’s <a href="https://structurizr.com/dsl">online sandbox</a> is a great place to try things before installing anything locally.</p>
<h2>Getting Started Locally</h2>
<p>The above sandbox is a decent way to try out the syntax, but eventually, you’ll want to track the model in code. I’ve found that using <a href="https://docs.structurizr.com/lite">Structurizr Lite</a> via a Docker container is a great way to get a feel for the language and have the resulting data saved to a file.</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">docker</span> pull structurizr/lite
<span class="token function">docker</span> run <span class="token parameter variable">-it</span> <span class="token parameter variable">--rm</span> <span class="token parameter variable">-p</span> <span class="token number">8080</span>:8080 <span class="token parameter variable">-v</span> .:/usr/local/structurizr structurizr/lite</code></pre>
<p>From there, just navigate to <a href="http://localhost:8080">localhost:8080</a> to view the compiled diagrams.</p>
<h2>Dynamic View</h2>
<p>One diagram that I found particularly useful recently was the <a href="https://docs.structurizr.com/dsl/language#dynamic-view">dynamic view</a>. Similar in usefulness to a <a href="https://en.wikipedia.org/wiki/Sequence_diagram">sequence diagram</a>, the dynamic view allows you to define a series of steps that are taken over time, through a system. This view shows a series of relationships between entities, an example of which might look like:</p>
<pre class="language-plain"><code class="language-plain">workspace "Local" "The Local product described on https://localwp.com" {
    model {
        // Define the system model ...  
    }  
    view {
        // Define the them and what diagrams to export  ...  
        dynamic lc.app {
            title "Create a new WP site"
            wpdev -> lc.app.apptsx "Clicks the 'Add site' button"

            lc.app.apptsx -> lc.app.maints "Calls addSite() via IPC"
            lc.app.maints -> lc.config "Saves site metadata"
            lc.app.maints -> lc.ls "Provisions services"
            lc.app.maints -> lc.app.apptsx "Notifies UI via IPC"
        }
    }  
}</code></pre>
<p>From there, you can compile and step through the flow to get a better idea of how and when various components interact with each other:</p>
<p><video height="1370" style="aspect-ratio: 2874 / 1370;" width="2874" controls="" src="structurizr-dynamic-view-local-wjOr7QacZZMe.mp4"></video></p>
]]></description>
	</item>
	
	<item>
		<title>The C4 Model for Documenting Software</title>
		<link>https://passionsplay.com/blog/the-c4-model-for-documenting-software/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/the-c4-model-for-documenting-software/</guid>
		<pubDate>Sat, 31 May 2025 04:23:17 GMT</pubDate>
		<description><![CDATA[<p>I’ve been learning about tooling and processes for understanding complex systems.</p>
<p>The main challenges of documenting these systems are determining who your audience is, knowing when to dive into additional detail, and utilizing consistency across various diagramns.</p>
<p>The <a href="https://c4model.com/">C4 Model</a> provides a structured approach to documenting software architecture, helping you communicate effectively with both technical and non-technical stakeholders. By embracing a framework for documenting systems, you can focus on uncovering relationships, letting C4 guide you to the appropriate levels of abstraction and documentation.</p>
<h2>C4 Model Summary</h2>
<p>The above site does a great job of simply describing the C4 model, but to summarize:</p>
<p>The C4 Model is a set of hierarchical diagrams to describe a system, where each successive diagram type is a more detailed view of the system.</p>
<ul>
<li>System Context</li>
<li>Container</li>
<li>Component</li>
<li>Code</li>
</ul>
<h3>System Context diagram</h3>
<p>The <a href="https://c4model.com/abstractions/software-system">System Context</a> diagram describes the highest level of abstraction and shows the top-level system, plus any users, and system dependencies.</p>
<h3>Container diagram</h3>
<p>Not Docker!</p>
<p>The <a href="https://c4model.com/abstractions/container">Container</a> diagram gives the overall shape of the architecture and technology choices of a system. As a rough rule of thumb, a C4 container would be any individually deployable unit that needs to be running for a system to work.</p>
<p>For example, if an individual system was a website, the container diagram might include nodes for the database, one or more backend servers, and a front-end client.</p>
<h3>Component diagram</h3>
<p>The <a href="https://c4model.com/abstractions/component">Component</a> diagram shows groupings of related functionality encapsulated behind a well-defined interface. As opposed to a <em>container</em>, a component is not an individually deployable unit.</p>
<p>On the other hand, while a component could be an individual class, it’s more likely a grouping of classes to represent a piece of functionality.</p>
<h3>Code diagram</h3>
<p>Lastly, a <a href="https://c4model.com/abstractions/code">Code</a> diagram shows the atomic building blocks of components and is often generated as UML. In most cases, you wouldn’t want, or need to explicitly create code diagrams, and instead, should rely on tooling to automatically create code diagrams dynamically while working.</p>
<h2>Applying the C4 Model</h2>
<p>As a concrete example, consider a simple ecommerce web application. Using the C4 Model, you might create:</p>
<ul>
<li>A <strong>System Context diagram</strong> showing the web app, users, and external services, like Stripe.</li>
<li>A <strong>Container diagram</strong> including nodes for the front-end, back-end, and database.</li>
<li>A <strong>Component diagram</strong> of the back-end container, highlighting things like authentication or payment components.</li>
<li><strong>Code diagrams</strong> wouldn’t be made initially – only dynamically created by IDEs as necessary.</li>
</ul>
]]></description>
	</item>
	
	<item>
		<title>Creating a Hugo blog and writing posts in Orgmode</title>
		<link>https://passionsplay.com/blog/creating-a-hugo-blog-and-writing-posts-in-orgmode/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/creating-a-hugo-blog-and-writing-posts-in-orgmode/</guid>
		<pubDate>Tue, 12 Nov 2024 07:52:22 GMT</pubDate>
		<description><![CDATA[<p>I’ve wanted a more streamlined process of writing and publishing content for my blog. While WordPress has powered my site for a long time, I want something more tailored to my Emacs writing workflow.</p>
<p>After reading through the <a href="https://gohugo.io/getting-started/quick-start/">Hugo QuickStart</a> guide and adjusting the installed theme, I created a new site that allows me to write the content using Emacs Org-mode.</p>
<p>The process I followed boiled down to:</p>
<ul>
<li>Create a sandbox folder and a new Hugo project</li>
<li>Add a theme as a git submodule</li>
<li>Add a <a href="https://gohugo.io/content-management/archetypes/">Hugo Archetype</a> for Org-mode files</li>
<li>Create content!</li>
</ul>
<p>I followed the specific process to get a new Hugo site using the <a href="https://github.com/lukeorth/poison">Poison theme</a>.</p>
<h2>Create the project and add a theme</h2>
<pre class="language-bash"><code class="language-bash"><span class="token punctuation">\</span># Create a sandbox <span class="token keyword">for</span> this site
<span class="token function">mkdir</span> <span class="token parameter variable">-p</span> ~/Desktop/hugo-sandbox <span class="token operator">&amp;&amp;</span> <span class="token builtin class-name">cd</span> ~/Desktop/hugo-sandbox

<span class="token comment"># initialize a new hugo site</span>
site<span class="token punctuation">\</span>_slug<span class="token operator">=</span><span class="token string">"pp"</span>
hugo new site <span class="token variable">${site\_slug}</span>
<span class="token builtin class-name">cd</span> <span class="token variable">${site\_slug}</span>
<span class="token function">git</span> init <span class="token parameter variable">--quiet</span> <span class="token operator">&amp;&amp;</span> <span class="token punctuation">\</span><span class="token punctuation">\</span>
  <span class="token function">git</span> branch <span class="token parameter variable">-m</span> <span class="token string">"main"</span> <span class="token parameter variable">--quiet</span> <span class="token operator">&amp;&amp;</span> <span class="token punctuation">\</span><span class="token punctuation">\</span>
  <span class="token function">git</span> <span class="token function">add</span> <span class="token builtin class-name">.</span> <span class="token operator">&amp;&amp;</span> <span class="token punctuation">\</span><span class="token punctuation">\</span>
  <span class="token function">git</span> commit <span class="token parameter variable">-m</span> <span class="token string">"Initial Commit from <span class="token entity" title="\\">\\</span>"</span>hugo new site <span class="token variable">${site\_slug}</span><span class="token punctuation">\</span><span class="token punctuation">\</span><span class="token string">""</span>

<span class="token comment"># Add the Poison theme along with minimal config to the config.toml file</span>
<span class="token function">git</span> submodule <span class="token function">add</span> <span class="token parameter variable">--quiet</span> https://github.com/lukeorth/poison.git themes/poison
<span class="token function">cat</span> <span class="token operator">&lt;&lt;</span> <span class="token string">EOF<span class="token bash punctuation"> <span class="token operator">>></span> config.toml</span>
theme = 'poison'

\[params\]
    brand = "PassionsPlay"
EOF</span>

<span class="token function">git</span> <span class="token function">add</span> <span class="token builtin class-name">.</span> <span class="token operator">&amp;&amp;</span> <span class="token punctuation">\</span><span class="token punctuation">\</span>
  <span class="token function">git</span> commit <span class="token parameter variable">-m</span> <span class="token string">"Add Poison Theme"</span></code></pre>
<p>With that in place, we can start the dev server, but there won’t be much to look at until we get some actual content in place:</p>
<pre class="language-bash"><code class="language-bash">hugo server</code></pre>
<h2>Creating content</h2>
<p>Following the Quickstart guide directs you to create a markdown file for your first piece of content.</p>
<pre class="language-bash"><code class="language-bash">hugo new posts/hello-world.md
<span class="token function">cat</span> <span class="token operator">&lt;&lt;</span> <span class="token string">EOF<span class="token bash punctuation"> <span class="token operator">>></span> content/posts/hello-world.md</span>
# ~~Old~~ New
It's a new world, \*filled\* with \_markup\_.
EOF</span>
<span class="token function">cat</span> content/posts/hello-world.md</code></pre>
<pre class="language-plain"><code class="language-plain">Content "/home/benjamin/Desktop/hugo-sandbox/pp/content/posts/hello-world.md" created
---
title: "Hello World"
date: 2024-11-07T21:04:48-08:00
draft: true
---

# ~~Old~~ New
It's a new world, \*filled\* with \_markup\_.</code></pre>
<p>That’s neat, and we can easily fire up our editor to add some actual content. But having the post set to “draft” (the <code>draft: true</code> part), none of that content will show unless you adjust how you launch the dev server. Since we started the dev server with <code>hugo server</code>, those draft posts won’t display. Stop the server with <code>C-c</code> and start a new instance that includes drafts with this command:</p>
<pre class="language-bash"><code class="language-bash">hugo server <span class="token parameter variable">--buildDrafts</span></code></pre>
<h2>Org-mode Archetype</h2>
<p>That looks good, but we want to write in Org-mode. We could just try using an org filename:</p>
<pre class="language-bash"><code class="language-bash">hugo new posts/hello-org-mode.org
<span class="token function">cat</span> content/posts/hello-org-mode.org</code></pre>
<pre class="language-plain"><code class="language-plain">Content "/home/benjamin/Desktop/hugo-sandbox/new-blog/content/posts/hello-org-mode.org" created
---
title: "Hello Org Mode"
date: 2024-05-08T21:42:29-07:00
draft: true
---</code></pre>
<p>Running the script created the file in the correct location. However, the <a href="https://gohugo.io/content-management/front-matter">Front Matter</a> is using markdown! What we need is a <a href="https://gohugo.io/content-management/archetypes/">Hugo Archetype</a> for Org-mode files. Adding a file at <code>archetypes/default.org</code> means we can automatically generate the front matter using the same Hugo create command:</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">cat</span> <span class="token operator">&lt;&lt;</span> <span class="token string">EOF<span class="token bash punctuation"> <span class="token operator">></span> archetypes/default.org</span>
#+TITLE: {{ replace .Name "-" " " | title }}
#+DATE: {{ .Date }}
#+DRAFT: true
#+AUTHOR:
#+CATEGORY:
#+TAGS\[\]:
EOF</span>
<span class="token function">cat</span> archetypes/default.org</code></pre>
<pre class="language-plain"><code class="language-plain">#+TITLE: {{ replace .Name "-" " " | title }}
#+DATE: {{ .Date }}
#+DRAFT: true
#+AUTHOR:
#+CATEGORY:
#+TAGS\[\]:</code></pre>
<p>With the <code>archetypes/default.org</code> file in place, we can create new posts, in org-mode format, by using the <code>.org</code> suffix:</p>
<pre class="language-bash"><code class="language-bash">hugo new posts/hello-org-mode-2.org
<span class="token function">cat</span> content/posts/hello-org-mode-2.org</code></pre>
<pre class="language-plain"><code class="language-plain">Content "/home/benjamin/Desktop/hugo-sandbox/pp/content/posts/hello-org-mode-2.org" created
#+TITLE: Hello Org Mode 2
#+DATE: 2024-11-07T21:17:25-08:00
#+DRAFT: true
#+AUTHOR:
#+CATEGORY:
#+TAGS\[\]:</code></pre>
]]></description>
	</item>
	
	<item>
		<title>Deno 2.0 and the npm: specifier</title>
		<link>https://passionsplay.com/blog/deno-2-0-and-the-npm-specifier/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/deno-2-0-and-the-npm-specifier/</guid>
		<pubDate>Sat, 12 Oct 2024 01:02:09 GMT</pubDate>
		<description><![CDATA[<p>The Deno 2.0 announcement mentions many <a href="https://deno.com/blog/v2.0">cool new features</a>. I’m still working through things in the blog post, but something as simple as using the npm: specifier will be great for writing one-off scripts using Typescript and any number of great packages in the NPM ecosystem.</p>
<p>For example, here’s a quick script that uses <a href="https://www.npmjs.com/package/chalk">chalk</a> to colorize the args to a <code>index.ts</code> script. With Deno installed, it’s as simple as running with:</p>
<pre class="language-js"><code class="language-js">deno run <span class="token operator">--</span>allow<span class="token operator">-</span>env <span class="token operator">--</span>allow<span class="token operator">-</span>read index<span class="token punctuation">.</span>ts The quick brown fox jumps over the lazy dog</code></pre>
<pre class="language-js"><code class="language-js"><span class="token keyword">import</span> <span class="token punctuation">{</span> parseArgs <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"jsr:@std/cli/parse-args"</span><span class="token punctuation">;</span>
<span class="token keyword">import</span> chalk <span class="token keyword">from</span> <span class="token string">"npm:chalk@5.3.0"</span><span class="token punctuation">;</span>

<span class="token keyword">const</span> message <span class="token operator">=</span> <span class="token function">parseArgs</span><span class="token punctuation">(</span>Deno<span class="token punctuation">.</span>args<span class="token punctuation">)</span><span class="token punctuation">.</span>\_<span class="token punctuation">;</span>

<span class="token keyword">const</span> colorMsg <span class="token operator">=</span> <span class="token punctuation">(</span>words<span class="token operator">:</span> string\<span class="token punctuation">[</span>\<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token parameter"><span class="token keyword">void</span></span> <span class="token operator">=></span> <span class="token punctuation">{</span>
  <span class="token keyword">let</span> i <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>
  <span class="token comment">// chalk, why you no support the Roy G. Biv!?</span>
  <span class="token keyword">const</span> colors <span class="token operator">=</span> \<span class="token punctuation">[</span>
    <span class="token string">"red"</span><span class="token punctuation">,</span>
    <span class="token comment">// "orange",</span>
    <span class="token string">"yellow"</span><span class="token punctuation">,</span>
    <span class="token string">"green"</span><span class="token punctuation">,</span>
    <span class="token string">"blue"</span><span class="token punctuation">,</span>
    <span class="token comment">// "indigo",</span>
    <span class="token comment">// "violet",</span>
  \<span class="token punctuation">]</span><span class="token punctuation">;</span>
  <span class="token keyword">const</span> coloredWords <span class="token operator">=</span> words<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token parameter">word</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span>
    <span class="token keyword">const</span> color <span class="token operator">=</span> chalk\<span class="token punctuation">[</span>colors\<span class="token punctuation">[</span>i\<span class="token punctuation">]</span>\<span class="token punctuation">]</span><span class="token punctuation">;</span>
    i <span class="token operator">=</span> <span class="token punctuation">(</span>i <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">%</span> colors<span class="token punctuation">.</span>length<span class="token punctuation">;</span>
    <span class="token keyword">return</span> <span class="token function">color</span><span class="token punctuation">(</span>word<span class="token punctuation">)</span><span class="token punctuation">;</span>
  <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
  console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>coloredWords<span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token string">" "</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">;</span>
<span class="token function">colorMsg</span><span class="token punctuation">(</span>message<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre>
]]></description>
	</item>
	
	<item>
		<title>Convert to AVIF using the CLI</title>
		<link>https://passionsplay.com/blog/convert-to-avif-using-the-cli/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/convert-to-avif-using-the-cli/</guid>
		<pubDate>Sun, 28 Jul 2024 01:55:52 GMT</pubDate>
		<description><![CDATA[<p>The <a href="https://en.wikipedia.org/wiki/AVIF">AVIF</a> image format offers <a href="https://aomedia.org/blog%20posts/avif-meet-the-next-level-image-file-format/">amazing size and quality improvements over PNG and JPG</a>.</p>
<p>Converting existing images to AVIF is easy to script on the command line using the right tool. On Debian-based systems, the process is easy using apt-get.</p>
<pre class="language-bash"><code class="language-bash">★  ~ % <span class="token function">sudo</span> <span class="token function">apt</span> <span class="token function">install</span> libavif-bin
Reading package lists<span class="token punctuation">..</span>. Done
Building dependency tree<span class="token punctuation">..</span>. Done
Reading state information<span class="token punctuation">..</span>. Done
The following additional packages will be installed:
  libavif13 libgav1-0 libyuv0
The following NEW packages will be installed:
  libavif-bin libavif13 libgav1-0 libyuv0</code></pre>
<p>With the <code>avifenc</code> command installed, converting images is easy and results in impressive reductions in file size (625k -&gt; 62k 🤯)!</p>
<pre class="language-bash"><code class="language-bash">★  Pictures/Screenshots % avifenc avif-image-uploaded-to-wordpress-media-library.png avif-image-uploaded-to-wordpress-media-library.avif
Successfully loaded: avif-image-uploaded-to-wordpress-media-library.png
AVIF to be written: <span class="token punctuation">(</span>Lossy<span class="token punctuation">)</span>
 * Resolution     <span class="token builtin class-name">:</span> 1600x900
 * Bit Depth      <span class="token builtin class-name">:</span> <span class="token number">8</span>
 * Format         <span class="token builtin class-name">:</span> YUV444
 * Alpha          <span class="token builtin class-name">:</span> Not premultiplied
 * Range          <span class="token builtin class-name">:</span> Full
 * Color Primaries: <span class="token number">1</span>
 * Transfer Char. <span class="token builtin class-name">:</span> <span class="token number">13</span>
 * Matrix Coeffs. <span class="token builtin class-name">:</span> <span class="token number">6</span>
 * ICC Profile    <span class="token builtin class-name">:</span> Absent <span class="token punctuation">(</span><span class="token number">0</span> bytes<span class="token punctuation">)</span>
 * XMP Metadata   <span class="token builtin class-name">:</span> Absent <span class="token punctuation">(</span><span class="token number">0</span> bytes<span class="token punctuation">)</span>
 * EXIF Metadata  <span class="token builtin class-name">:</span> Absent <span class="token punctuation">(</span><span class="token number">0</span> bytes<span class="token punctuation">)</span>
 * Transformations: None
 * Progressive    <span class="token builtin class-name">:</span> Unavailable
Encoding with AV1 codec <span class="token string">'aom'</span> speed <span class="token punctuation">[</span><span class="token number">6</span><span class="token punctuation">]</span>, color QP <span class="token punctuation">[</span><span class="token number">24</span> <span class="token punctuation">(</span>Medium<span class="token punctuation">)</span> <span class="token operator">&lt;</span>-<span class="token operator">></span> <span class="token number">26</span> <span class="token punctuation">(</span>Medium<span class="token punctuation">)</span><span class="token punctuation">]</span>, alpha QP <span class="token punctuation">[</span><span class="token number">0</span> <span class="token punctuation">(</span>Lossless<span class="token punctuation">)</span> <span class="token operator">&lt;</span>-<span class="token operator">></span> <span class="token number">0</span> <span class="token punctuation">(</span>Lossless<span class="token punctuation">)</span><span class="token punctuation">]</span>, tileRowsLog2 <span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span>, tileColsLog2 <span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span>, <span class="token number">1</span> worker thread<span class="token punctuation">(</span>s<span class="token punctuation">)</span>, please wait<span class="token punctuation">..</span>.
Encoded successfully.
 * Color AV1 total size: <span class="token number">62961</span> bytes
 * Alpha AV1 total size: <span class="token number">0</span> bytes
Wrote AVIF: avif-image-uploaded-to-wordpress-media-library.avif
★  Pictures/Screenshots % <span class="token function">ls</span> <span class="token parameter variable">-alh</span> avif*
-rw-rw-r-- <span class="token number">1</span> benjamin benjamin  62K Jul <span class="token number">27</span> <span class="token number">11</span>:49 avif-image-uploaded-to-wordpress-media-library.avif
-rw-rw-r-- <span class="token number">1</span> benjamin benjamin 645K Jul <span class="token number">27</span> <span class="token number">11</span>:43 avif-image-uploaded-to-wordpress-media-library.png</code></pre>
<p><img src="avif-image-uploaded-to-wordpre-w7ASTA7oa2Df.avif" alt="An example of the WordPress media library editing an AVIF image."></p>
]]></description>
	</item>
	
	<item>
		<title>AVIF Support in WordPress 6.5</title>
		<link>https://passionsplay.com/blog/avif-support-in-wordpress-6-5/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/avif-support-in-wordpress-6-5/</guid>
		<pubDate>Sun, 28 Jul 2024 00:54:04 GMT</pubDate>
		<description><![CDATA[<p>Earlier this year, WordPress 6.5 shipped with native support for the AVIF image format. This is a big deal when optimizing a site’s performance and image quality. Let’s dive into AVIF, its importance, and how WordPress leverages this technology.</p>
<h2>What is AVIF?</h2>
<p><a href="https://en.wikipedia.org/wiki/AVIF">AVIF</a> (AV1 Image File Format) is a modern image format that offers superior compression and quality compared to traditional formats like JPEG and PNG. It’s based on the AV1 video codec and can significantly reduce image file sizes without compromising quality. This means faster load times and a better user experience on your site.</p>
<p>AVIF is often associated with and compared to <a href="https://en.wikipedia.org/wiki/High_Efficiency_Image_File_Format">HEIF</a>, another high-compression image format. Where AVIF shines is in its open licensing, as opposed to HEIF, which is closed and requires royalty payments to use. This distinction is especially important as it relates to WordPress, which can’t adopt closed solutions into WordPress core.</p>
<h3>Benefits of AVIF Support</h3>
<p>Here are some compelling reasons to consider AVIF for your WordPress site:</p>
<ul>
<li><strong>Better Compression</strong>: AVIF images are smaller than JPEG and PNG, which means faster load times.</li>
<li><strong>Higher Quality</strong>: AVIF maintains high image quality despite the smaller file sizes.</li>
<li><strong>Future-Proofing</strong>: As more browsers and devices support AVIF, enabling it ensures your site is ready for the future.</li>
</ul>
<p>This blog post from the Alliance for Open Media goes into way more detail about the benefits: <a href="https://aomedia.org/blog%20posts/avif-meet-the-next-level-image-file-format/">AVIF: Meet the Next Level Image File Format</a></p>
<h2>AVIF in WordPress 6.5</h2>
<p>With WordPress 6.5, AVIF support is baked right into the core. This means you can upload AVIF images directly to your Media Library, and WordPress will handle them just like any other image format.</p>
<p>For server environments with PHP compiled with AVIF support, WordPress can also edit those images to scale, crop, and rotate.</p>
<p>By supporting AVIF, WordPress sites can use smaller, better-quality images, which improve performance and reduce the amount of data that site visitors need to download.</p>
<p>You can read more about AVIF’s inclusion within the 6.5 WordPress release on the <a href="https://make.wordpress.org/core/2024/02/23/wordpress-6-5-adds-avif-support/">WordPress Core blog</a>.</p>
<p><img src="avif-image-uploaded-to-wordpre-w7ASTA7oa2Df.avif" alt=""></p>
<h3>WP’s Use of AVIF</h3>
<p>WordPress’s emphasis on backward compatibility means that the features it supports are written using robust and defensive coding practices.</p>
<p>One consequence is that even if your server doesn’t support AVIF, WordPress <a href="https://github.com/WordPress/WordPress/blob/3bab2c9131fa7d51e0ee28f7f402a53b66019d54/wp-includes/class-avif-info.php#L12-L16">includes a polyfill</a> to help read AVIF images, allowing you to upload and add AVIF images to your Media Library.</p>
<p>However, this polyfill does come with limitations. While you can upload and insert AVIF images into posts, editing those images is impossible without server support.</p>
<p>For these reasons, ensuring your PHP setup is AVIF-ready is crucial. Most dedicated hosts have already compiled PHP to include support for AVIF. If you’re like me and tasked with getting this working, I’ll go into more detail in upcoming posts about compiling PHP with AVIF support.</p>
]]></description>
	</item>
	
	<item>
		<title>Quick Typescript Sandbox in Emacs</title>
		<link>https://passionsplay.com/blog/quick-typescript-sandbox-in-emacs/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/quick-typescript-sandbox-in-emacs/</guid>
		<pubDate>Thu, 18 Apr 2024 12:43:08 GMT</pubDate>
		<description><![CDATA[<p>I’ve been working towards mastering JavaScript and TypeScript and have found that deep understanding comes through continuous learning and practice. Despite years of experience, I still find nuances of the language I want to tinker with while quickly visualizing abstract concepts.</p>
<p>Having an actual Typescript buffer with the full power of my editor is the most natural way of turning thought into experiment and feels like such an improvement over tinkering in the Node REPL.</p>
<p>In particular, I have so much muscle memory around Emacs’ compilation workflow that I found myself doing the same few things to get a sandbox up and running for working with Typescript.</p>
<p>So, after creating yet another temporary folder, with a <code>*.ts</code> file, saving the file, and compiling/running this file, I decided to automate this workflow for creating these sandboxes.</p>
<p>My Elisp isn’t great, but for a hacky time-saver, it came together fairly quickly:</p>
<pre class="language-plain"><code class="language-plain">(defun bt/create-typescript-sandbox ()
  "Create a temporary Typescript file and set a local variable for
the compile-command."
  (interactive)
  (let ((folder (make-temp-file "typescript-sandbox-" t)))
    (find-file (concat folder "/index.ts"))
    (typescript-mode)
    (set (make-local-variable 'compile-command) (format "deno run %s" (buffer-file-name)))))</code></pre>
<p>The rough outline of what’s going on:</p>
<ul>
<li>Create a folder in the system’s temporary folder location, for example <code>/tmp/typescript-sandbox-xxxyyyzz</code></li>
<li>Create a file called <code>index.ts</code> within the temporary folder</li>
<li>Set the major mode to <code>typescript-mode</code></li>
<li>Set a local variable for the <code>compile</code> command so that it offers to run <code>deno run</code> on the newly created buffer</li>
</ul>
<p>From there, it’s just a quick call to the function to get a Typescript file that compiles and runs quickly (thanks Deno!)</p>
<p><video height="900" style="aspect-ratio: 1600 / 900;" width="1600" controls="" src="quick-typescript-sandbox-in-em-F3LtjjTH5CVI.mp4"></video></p>
]]></description>
	</item>
	
	<item>
		<title>Compiling Emacs 29 on Ubuntu LTS 22.04</title>
		<link>https://passionsplay.com/blog/compiling-emacs-29-on-ubuntu-lts-22-04/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/compiling-emacs-29-on-ubuntu-lts-22-04/</guid>
		<pubDate>Tue, 02 Apr 2024 03:00:00 GMT</pubDate>
		<description><![CDATA[<p>I’ve been on a kick to better understand my developer toolbox, and I recently decided to start by compiling Emacs from source on my trusty Ubuntu LTS 22.04. In the case of Emacs, my primary motivation was to explore the latest features bundled with version 29, such as tree-sitter and eglot. These two packages bundled into Emacs core means not relying on third-party packages for syntax highlighting or LSP support.</p>
<p>Compiling Emacs from scratch on Linux was a new venture, but it was straightforward. One unfamiliar term that popped up was make bootstrap. In Emacs’ case, this essentially wipes the slate clean, deleting any pre-compiled Elisp modules before building from scratch:</p>
<blockquote>
<p>make bootstrap – delete all compiled files to force a new bootstrap from a clean slate, then build in the normal way</p>
</blockquote>
<p>Here’s a condensed version of the process I followed:</p>
<pre class="language-bash"><code class="language-bash"><span class="token comment"># Setting up Emacs in our source directory</span>
<span class="token function">mkdir</span> <span class="token parameter variable">-p</span> ~/src <span class="token operator">&amp;&amp;</span> <span class="token builtin class-name">cd</span> ~/src
<span class="token function">git</span> clone git://git.savannah.gnu.org/emacs.git
<span class="token function">git</span> checkout emacs-29

<span class="token comment"># Enable development libraries and update apt cache</span>
<span class="token function">sudo</span> <span class="token function">sed</span> <span class="token parameter variable">-i</span> <span class="token string">'s/# deb-src/deb-src/'</span> /etc/apt/sources.list <span class="token punctuation">\</span>
  <span class="token operator">&amp;&amp;</span> <span class="token function">apt</span> update

<span class="token comment"># Install necessary dependencies</span>
<span class="token function">sudo</span> <span class="token function">apt</span> build-dep <span class="token parameter variable">-y</span> emacs <span class="token punctuation">\</span>
  <span class="token operator">&amp;&amp;</span> <span class="token function">sudo</span> <span class="token function">apt</span> <span class="token function">install</span> libtree-sitter-dev

<span class="token comment"># Generate the configure file</span>
./autogen.sh

<span class="token comment"># Configure Emacs with desired options</span>
./configure --with-tree-sitter --with-imagemagick --with-json

<span class="token comment"># Compile with 4 cores</span>
<span class="token function">make</span> <span class="token parameter variable">-j4</span> bootstrap

<span class="token comment"># Verify the version</span>
./src/emacs <span class="token parameter variable">--version</span>

<span class="token comment"># Optionally, test things by launching Emacs without any configuration</span>
./src/emacs <span class="token parameter variable">-Q</span>

<span class="token comment"># When things look good, install Emacs system-wide</span>
<span class="token function">sudo</span> <span class="token function">make</span> <span class="token function">install</span></code></pre>
<p><img src="compiling-emacs-29-on-ubuntu-2-mf4d5rU83kP1.png" alt=""></p>
]]></description>
	</item>
	
	<item>
		<title>Installing OBS Studio on Linux</title>
		<link>https://passionsplay.com/blog/installing-obs-studio-on-linux/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/installing-obs-studio-on-linux/</guid>
		<pubDate>Mon, 27 Mar 2023 11:41:13 GMT</pubDate>
		<description><![CDATA[<p><a href="https://obsproject.com/forum/">OBS Studio</a> is a popular open-source video recording and live-streaming software. It’s available for Windows, macOS, and Linux operating systems. This blog post will focus on <a href="https://obsproject.com/wiki/install-instructions#linux">installing OBS Studio on Linux</a>.</p>
<p>There are several ways to install OBS Studio on Linux. One of the easiest methods is through the official OBS Studio PPA (Personal Package Archive) for Ubuntu-based Linux distributions. Here are the general steps to install OBS Studio via the PPA:</p>
<ol>
<li>Open the terminal and add the OBS Studio PPA to your system by running the following command</li>
<li>Update your system’s package list by running</li>
<li>Finally, install OBS Studio</li>
</ol>
<p>Those steps look like this within your terminal:</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">sudo</span> add-apt-repository ppa:obsproject/obs-studio
<span class="token function">sudo</span> <span class="token function">apt-get</span> update
<span class="token function">sudo</span> <span class="token function">apt-get</span> <span class="token function">install</span> obs-studio</code></pre>
<p>Once the installation is complete, you can launch OBS Studio from the Applications menu or by running the following command in the terminal:</p>
<pre class="language-bash"><code class="language-bash">obs</code></pre>
<p>That’s it! You now have OBS Studio installed on your Linux system.</p>
<p>Refer to the <a href="https://docs.obsproject.com/">OBS Studio documentation</a> or seek help from the <a href="https://obsproject.com/forum/">OBS Studio community forums</a> if you have any issues.</p>
<h2>Why use a PPA?</h2>
<p>You may have noticed that OBS Studio is available from the default software repositories for Ubuntu. So why go through the above effort of adding a PPA?</p>
<p>Adding a <a href="https://launchpad.net/ubuntu/+ppas">PPA (Personal Package Archive)</a> is a way to get the latest package version that is not yet available in the official Ubuntu repository. Installing software from a PPA can be a good option for those who want the latest features and improvements before they are officially released.</p>
<p>You can use the <code>apt-cache policy</code> command to view the various software versions associated with different repositories. Here’s how to use to compare the OBS Studio version from the official Ubuntu repositories and the official OBS Studio PPA:</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">apt-cache</span> policy obs-studio</code></pre>
<p>The above command gives us information that looks like this:</p>
<pre class="language-plain"><code class="language-plain">★  Videos/obs % apt-cache policy obs-studio
obs-studio:
  Installed: 29.0.2-0obsproject1~jammy
  Candidate: 29.0.2-0obsproject1~jammy
  Version table:
 \*\*\* 29.0.2-0obsproject1~jammy 500
    500 https://ppa.launchpadcontent.net/obsproject/obs-studio/ubuntu jammy/main amd64 Packages
    100 /var/lib/dpkg/status
     27.2.3+dfsg1-1 500
    500 http://apt.pop-os.org/ubuntu jammy/universe amd64 Packages</code></pre>
<p>Note that as of today (2023-03-26), the version of OBS Studio from the official PPA is two whole versions ahead of what comes from the central Ubuntu repositories (<code>29.0.2</code> vs. <code>27.2.3</code>)</p>
]]></description>
	</item>
	
	<item>
		<title>SVG recordings of Terminal Sessions</title>
		<link>https://passionsplay.com/blog/svg-recordings-of-terminal-sessions/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/svg-recordings-of-terminal-sessions/</guid>
		<pubDate>Wed, 04 Jan 2023 01:00:27 GMT</pubDate>
		<description><![CDATA[<p>I recently stumbled on this cool technique from <a href="https://wasimlorgat.com/">Wasim Lorgat</a> for creating SVG documents of terminal sessions using <a href="https://github.com/asciinema/asciinema">asciinema</a> and <a href="https://github.com/marionebl/svg-term-cli">svg-term-cli</a>:</p>
<ul>
<li><a href="https://wasimlorgat.com/tils/how-to-share-terminal-demos-as-razor-sharp-animated-svg.html">How to share terminal demos as razor-sharp animated SVG — Wasim Lorgat</a></li>
</ul>
<pre class="language-bash"><code class="language-bash"><span class="token comment"># install tools</span>
<span class="token function">npm</span> <span class="token function">install</span> <span class="token parameter variable">-g</span> svg-term-cli
pipx <span class="token function">install</span> asciinema
<span class="token comment"># start recording</span>
asciinema rec emacs.cast
<span class="token comment"># do stuff</span>
<span class="token comment"># stop recording with ctrl-d</span>
<span class="token comment"># convert to svg</span>
svg-term <span class="token parameter variable">--in</span> emacs.cast <span class="token parameter variable">--out</span> emacs.svg <span class="token parameter variable">--window</span> <span class="token parameter variable">--width</span> <span class="token number">141</span> <span class="token parameter variable">--height</span> <span class="token number">22</span> --no-optimize</code></pre>
<p><a href="/wp-content/uploads/2023/01/emacs.svg"><img src="/wp-content/uploads/2023/01/emacs.svg" alt=""></a></p>
]]></description>
	</item>
	
	<item>
		<title>QA FSE 15: Category Customization</title>
		<link>https://passionsplay.com/blog/qa-fse-15-category-customization/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/qa-fse-15-category-customization/</guid>
		<pubDate>Fri, 22 Jul 2022 23:42:04 GMT</pubDate>
		<description><![CDATA[<p>I had some time to contribute to WP, and I’ve always loved how the <a href="https://make.wordpress.org/test/handbook/full-site-editing-outreach-experiment/">Full-site-editing outreach program</a> lets anyone jump in and quickly get to testing specific features and functionality. Today, I worked on QA’ing the latest one: <a href="https://make.wordpress.org/test/2022/07/11/fse-program-testing-call-15-category-customization/">FSE Program Testing Call #15: Category Customization</a></p>
<h2>Questions</h2>
<blockquote>
<p>Did the experience crash at any point?</p>
</blockquote>
<ul>
<li>No crashes! But a few odd states when navigating via keyboard nav.</li>
</ul>
<blockquote>
<p>Did the saving experience work properly?</p>
</blockquote>
<ul>
<li>Yes</li>
</ul>
<blockquote>
<p>What did you find particularly confusing or frustrating about the experience?</p>
</blockquote>
<ul>
<li>While writing posts in Gutenberg, I’ve come to love being able to press enter, then forward slash to choose a new block quickly without leaving the keyboard. Under FSE’s editor, this workflow seems more buggy.</li>
<li>Performing some of the actions would lose focus. As a concrete example, removing any lock setting via keyboard nav seems to drop the user back into the global context, needing to tab to the location they were previously at. I think that’s likely due to the lock DOM element no longer being present.</li>
<li>Working with a group’s layout could be improved via the keyboard. I think getting more comfortable with the UI would help, but having additional feedback around what is selected and where I can move things would be helpful.</li>
</ul>
<blockquote>
<p>What did you especially enjoy or appreciate about the experience?</p>
</blockquote>
<ul>
<li>So cool to see how far FSE has come and all of the improvements related to templates. We’re getting closer to the power of the template hierarchy of traditional themes!</li>
</ul>
<blockquote>
<p>What would have made this experience easier?</p>
</blockquote>
<ul>
<li>This was great! I love how these FSE experiments are targeted and have clear instructions for me to jump in and do some QA as time permits!</li>
</ul>
<blockquote>
<p>Did you find that what you created matched what you saw on your site?</p>
</blockquote>
<ul>
<li>Yes</li>
</ul>
<blockquote>
<p>Did it work using Keyboard only?</p>
</blockquote>
<ul>
<li>Not quite.</li>
</ul>
<blockquote>
<p>Did it work using a screen reader?</p>
</blockquote>
<ul>
<li>Didn’t test.</li>
</ul>
<h2>Video and Timestamps</h2>
<p><video height="1050" style="aspect-ratio: 1680 / 1050;" width="1680" controls="" src="/wp-content/uploads/2022/07/fse-15-category-customization.mp4"></video></p>
<ul>
<li>0:00:00 :: Start recording notes</li>
<li>0:01:43 :: Minor thing — when tabbing to the main editor area, the editor area jumped up 10px</li>
<li>0:03:56 :: Question: Should I be able to navigate through all of the “Patterns” using the arrow keys when inserting a footer?</li>
<li>0:11:34 :: UX consideration: When selecting an option for the Query Loop block, it would be nice to have the left and right arrows (or similar) be able to advance through the various options.</li>
<li>0:20:19 :: Usually when pressing “enter” at the end of a block, a new block will be inserted after the current one. For some reason, this “Directions” block won’t insert a new block after it when pressing enter.</li>
<li>0:22:53 :: Bug: Tabindex lost when lock icon is removed from the block menu.
<ol>
<li>With a locked block focused in the editor</li>
<li>Navigate to the lock setting with keyboard nav using “Shift+Tab”</li>
<li>Disable lock settings.</li>
<li>Note that our focus is now on the main window and not somewhere within the block we were editing.</li>
</ol>
</li>
<li>0:26:05 :: Re the “enter” for new block issue. As a workaround, I think I can hit enter twice, and then backspace once to get to a new block setup.</li>
<li>0:36:15 :: Another instance of not being able to insert a new block by just pressing enter</li>
<li>0:40:15 :: Editing the layout of a group is a little tricky. I think getting more comfortable with the UI would help, but having additional feedback around what is selected and where I can move things would be helpful.</li>
</ul>
]]></description>
	</item>
	
	<item>
		<title>Fixing ERR_CERT_INVALID in Chrome</title>
		<link>https://passionsplay.com/blog/fixing-err_cert_invalid-in-chrome/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/fixing-err_cert_invalid-in-chrome/</guid>
		<pubDate>Wed, 09 Mar 2022 12:07:49 GMT</pubDate>
		<description><![CDATA[<p>I’ve finished my first quarter as <a href="/blog/new-role-software-engineer/">a Software Engineer</a> on the Local team, which means I now have a couple of sprints under my belt!</p>
<p>Because <a href="https://localwp.com">Local</a> is an <a href="https://www.electronjs.org/">Electron</a> app, I pictured myself learning and hacking away at Javascript. While I’ve been learning more about the nuances of Typescript and React in this role, the first couple of sprints were spent investigating an <code>ERR_CERT_INVALID</code> error in Chrome. The specific issue was first reported in the Local Community Forums: <a href="https://community.localwp.com/t/ssl-not-working-on-chrome-with-net-err-cert-invalid/28548?u=ben.turner">SSL Not working on Chrome with NET::ERR_CERT_INVALID</a></p>
<p>This sort of error was difficult to troubleshoot for a few reasons:</p>
<ol>
<li>Chrome doesn’t tell you what <em>exactly</em> is invalid about a certain certificate.</li>
<li>Searching the internet for more info about this error led me to results like the answers in this StackOverflow question, which basically boil down to bypassing HTTPS:
<ul>
<li><a href="https://stackoverflow.com/questions/58802767/no-proceed-anyway-option-on-neterr-cert-invalid-in-chrome-on-macos">No “Proceed Anyway” option on NET::ERR_CERT_INVALID in Chrome on MacOS</a></li>
</ul>
</li>
</ol>
<p>Bypassing security isn’t a solution; it’s a temporary workaround and certainly isn’t an option when you’re the dev who’s making a tool that generates SSL certificates.</p>
<p>I learned a lot while working on this issue and thought it best to document some of the tools and resources I found while investigating this error!</p>
<p><a href="/wp-content/uploads/2022/03/chrome-err_cert_invalid.png"><img src="chrome-err_cert_invalid-768x55-Pmq0VDOHV3Vz.png" alt="Example of the ERR_CERT_INVALID error encountered in the Chrome browser."></a></p>
<p>An example of the ERR_CERT_INVALID error in Chrome.</p>
<h2>Not all ERR_CERT_INVALID errors are the same.</h2>
<p>Because there are many potential reasons for a certificate to be invalid, there isn’t an easy-to-find solution for every case. I couldn’t find a definitive list of what Chrome looks for, but the main reason seems to be incorrect dates.</p>
<h3>Invalid Cert due to Invalid Dates</h3>
<p>When a certificate is generated, it has a <strong>Valid Before</strong> and a <strong>Valid After</strong> date:</p>
<pre class="language-plain"><code class="language-plain">Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            9b:6b:3d:a3:b9:a3:a4:b4
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: CN=local.mydomain.com
        Validity
            Not Before: Nov 19 13:27:30 2016 GMT
            Not After : Nov 19 13:27:30 2017 GMT
        Subject: CN=local.mydomain.com</code></pre>
<p>The above cert will certainly fail on most systems today because most systems think they are ahead of 2017.</p>
<p>Note that I mentioned “most systems think” — remember that a computer needs to set its notion of time from <em>someplace</em>. If the dates for a certificate seem valid, one possible avenue to explore is if a computer’s Date/Time settings are correct. Pay particular attention to if a system is oddly connecting to the network (like a VPN or a “helpful-IT” work network.)</p>
<h3>Something about the chain is invalid.</h3>
<p>I know that heading isn’t helpful, but I encourage you to think creatively about what sort of things would make a browser angry. In my case, the issue was caused by a doubling up of the certificate. In particular, Chrome was reporting two of the same PEM blocks.</p>
<p>In my case, I zeroed in on <em>why</em> Chrome was complaining but now needed to determine <em>how</em> the sites came to be in this state.</p>
<p><a href="/wp-content/uploads/2022/03/chrome-err_cert_invalid-double-pem.png"><img src="chrome-err_cert_invalid-double-m0olxjPZ2lRn.png" alt=""></a></p>
<p>Note the doubled-up PEM certificate.</p>
<h2>Tools for Investigation</h2>
<h3>Investigating an SSL Certificate using Chrome</h3>
<p>I was able to get a general idea of why Chrome didn’t like the site’s certificate by clicking the area to the left of the address bar. You’ll see a lock icon for sites where SSL is working correctly. Sites with miss-configured SSL certificates will be obvious — Chrome won’t let you do anything!</p>
<p>This screencast covers the basics of examining the certificate and drilling down to specific pieces of data. In particular, note that:</p>
<ul>
<li>You can click the icon next to the address bar for a window to drill down to specifics.</li>
<li>For most SSL-related errors, an “Advanced” button will show additional details about what went wrong.</li>
<li>The actual error (in this case <code>ERR_CERT_INVALID</code>) is clickable and allows you to see the raw <a href="#ssl-terms">PEM-encoded</a> certificate.</li>
</ul>
<p><video height="1050" style="aspect-ratio: 1680 / 1050;" width="1680" controls="" src="/wp-content/uploads/2022/03/viewing-ssl-certificates-in-chrome-ubuntu.mp4"></video></p>
<h2>Investigating a certificate using OpenSSL</h2>
<p>I like polished GUIs like anyone else, but Chrome’s certificate navigator wasn’t doing what I needed to do. At this point, I turned to <a href="https://www.openssl.org/">OpenSSL</a> to query and decode the certificate that the server was giving. Here’s a breakdown of the commands that helped me along the way.</p>
<h3>Using OpenSSL as a TLS Client</h3>
<pre class="language-plain"><code class="language-plain">openssl s_client -showcerts -connect example.local:443  -servername example.local</code></pre>
<p>This command allows you to make and test a connection over HTTPS using OpenSSL. You can read the parts of the above command as:</p>
<ul>
<li><code>openssl s_client</code> — Use <a href="https://www.openssl.org/docs/man1.0.2/man1/openssl-s_client.html">s_client</a>, which is OpenSSL’s generic SSL/TLS client for connecting to hosts.</li>
<li><code>-showcerts</code> — Include the server’s certificate in the output.</li>
<li><code>-connect example.local:443</code> — Where the connection should be made. In this case, specifying the specific HTTPS port ensures that we are working with a secure connection.</li>
<li><code>-servername example.local</code> — This indicates what domain you want the cert for. This is most useful when the server responds to more than one domain.</li>
</ul>
<p>The above command should give an output similar to this:</p>
<pre class="language-plain"><code class="language-plain">depth=0 CN = invalid-cert-c1.local, C = XX, ST = XX, L = Fake Locality, O = "Super Fake Company, Fake.", OU = Fake Organizational Unit
verify error:num=18:self signed certificate
verify return:1
depth=0 CN = invalid-cert-c1.local, C = XX, ST = XX, L = Fake Locality, O = "Super Fake Company, Fake.", OU = Fake Organizational Unit
verify return:1
CONNECTED(00000005)
---
Certificate chain
0 s:/CN=invalid-cert-c1.local/C=XX/ST=XX/L=Fake Locality/O=Super Fake Company, Fake./OU=Fake Organizational Unit
    i:/CN=invalid-cert-c1.local/C=XX/ST=XX/L=Fake Locality/O=Super Fake Company, Fake./OU=Fake Organizational Unit
-----BEGIN CERTIFICATE-----
MIIEfTCCA2WgAwIBAgIJHCV7Ifs31UxeMA0GCSqGSIb3DQEBCwUAMIGZMR4wHAYD
VQQDExVpbnZhbGlkLWNlcnQtYzEubG9jYWwxCzAJBgNVBAYTAlhYMQswCQYDVQQI
EwJYWDEWMBQGA1UEBxMNRmFrZSBMb2NhbGl0eTEiMCAGA1UEChMZU3VwZXIgRmFr
ZSBDb21wYW55LCBGYWtlLjEhMB8GA1UECxMYRmFrZSBPcmdhbml6YXRpb25hbCBV
bml0MB4XDTIyMDIwNDE2MDUxMFoXDTMyMDIwNDE2MDUxMFowgZkxHjAcBgNVBAMT
FWludmFsaWQtY2VydC1jMS5sb2NhbDELMAkGA1UEBhMCWFgxCzAJBgNVBAgTAlhY
MRYwFAYDVQQHEw1GYWtlIExvY2FsaXR5MSIwIAYDVQQKExlTdXBlciBGYWtlIENv
bXBhbnksIEZha2UuMSEwHwYDVQQLExhGYWtlIE9yZ2FuaXphdGlvbmFsIFVuaXQw
ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC/BGKuaQTetBNytk54co3u
E7/hbK5b2PnwpWBO7j37+qpqtiyh5iy5RMlXcjEpnTj5oeSGuZppEa0onDO04rFo
Yz9PLawcGlH2NgZqNqAHczVEl5Q5wElqAQNCnXRA3bjvgWzV2VU5bTgVwIMF3ppw
J6eVPfrxT7GBzVzjAXdOMlJVNLETq+yiniEkN1+nEmc2tIq3ypN4YQJlwNryQ6e6
2zEebgnaPDQaQyDkEeOpQM+EYqeqpDjRqR46rG3gGRALlJs1kKigNAI4ozk0eZQK
/bTRNCtIOjcjcTu7wwQNRgqWoeFLR3+d9qA/Sdq0d2xC8tOC/7ua+u1pYYNMIJu1
AgMBAAGjgcUwgcIwCQYDVR0TBAIwADALBgNVHQ8EBAMCAvQwOwYDVR0lBDQwMgYI
KwYBBQUHAwEGCCsGAQUFBwMCBggrBgEFBQcDAwYIKwYBBQUHAwQGCCsGAQUFBwMI
MBEGCWCGSAGG+EIBAQQEAwIA9zAdBgNVHQ4EFgQUssqVADjlsz0/t+bpBK1j2+qy
2hwwOQYDVR0RBDIwMIIVaW52YWxpZC1jZXJ0LWMxLmxvY2FsghcqLmludmFsaWQt
Y2VydC1jMS5sb2NhbDANBgkqhkiG9w0BAQsFAAOCAQEAsjSC8Vlndbz58eZqd/7/
f7j86m3NjZxVzzOKMXWSAnoY+3Uy1z0DT+u4uxDMuY6qx+OskmsUW4nVlidicpye
T2EZUQuykCTJh/aqP6wdPoPuNJ0kYaG+cq/b+T1gDG0eVWT1jER3PmPl1hpOdWpd
kzY1ErUQjN+hQXQ4a31rNUkS7hdMOsu1l+Xwz5n21SkAPc14rIoQfssny0SnCvCQ
kV27Pm5OSAgybmVsD/2WnbIC5aEgcGX2rVgLk4kOB+25REXbwkm1KKggW+1Ae5qN
ptEZ69kacQj2DZxcNyBZEBZbSu9EUFbvrtHkj+boQcZ7AKYdgWGyq8EkuI+okLAl
CA==
-----END CERTIFICATE-----
---
Server certificate
subject=/CN=invalid-cert-c1.local/C=XX/ST=XX/L=Fake Locality/O=Super Fake Company, Fake./OU=Fake Organizational Unit
issuer=/CN=invalid-cert-c1.local/C=XX/ST=XX/L=Fake Locality/O=Super Fake Company, Fake./OU=Fake Organizational Unit
---
No client certificate CA names sent
Server Temp Key: ECDH, P-256, 256 bits
---
SSL handshake has read 1859 bytes and written 352 bytes
---
New, TLSv1/SSLv3, Cipher is ECDHE-RSA-AES128-GCM-SHA256
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
    Protocol  : TLSv1.2
    Cipher    : ECDHE-RSA-AES128-GCM-SHA256
    Session-ID: BA84087FF3DEA801A256A4F0B89D30DDA63098C995B3C1D8770215A2B1DC29D7
    Session-ID-ctx: 
    Master-Key: BF6930C37EABB95DFD70A50780956197743BC23BBE6BA3FCAACA8452C36236365734CE4C7244A6110763675CF954CF64
    TLS session ticket lifetime hint: 300 (seconds)
    TLS session ticket:
    0000 - c7 48 17 29 cf 87 1f 51-0c d2 f8 78 cc 64 6e 9e   .H.)...Q...x.dn.
    0010 - 7f b7 c6 52 cb 71 48 aa-5a a0 54 13 44 7c c2 ba   ...R.qH.Z.T.D|..
    0020 - 00 8e 61 d0 d8 42 21 67-4b 3f 0c 87 64 c0 64 48   ..a..B!gK?..d.dH
    0030 - 91 73 90 30 5f 25 4f 89-56 ea a6 55 85 4f c8 3d   .s.0_%O.V..U.O.=
    0040 - 89 18 b2 bd 81 c3 a9 62-12 7d 76 51 6b 5f 33 90   .......b.}vQk_3.
    0050 - 2b 38 45 38 ce d2 af 91-cf 49 4f 99 0a d9 6c 4c   +8E8.....IO...lL
    0060 - 93 8a 79 fb be 77 03 d8-1a b4 08 bf 77 27 36 b5   ..y..w......w'6.
    0070 - 2e 98 bd 2c 95 e9 ba 79-9f 05 95 0b e6 3c e2 43   ...,...y.....&lt;.C
    0080 - f1 64 b5 25 00 8c f8 b4-3d 01 3f 87 5f dd 97 b4   .d.%....=.?._...
    0090 - f7 12 61 ff 61 c4 39 13-5c fc dc 1d fa 37 11 0f   ..a.a.9.\....7..
    00a0 - c5 d4 10 df cf 9a 87 05-c3 eb 6e 19 97 f6 1f d1   ..........n.....
    00b0 - b6 2b 0b 66 f0 a1 f9 fa-7a ce c1 3d 5a 77 80 48   .+.f....z..=Zw.H
    00c0 - c4 f8 a8 5f 65 f2 7e c3-f9 a3 06 e8 e7 a8 87 be   ..._e.~.........

    Start Time: 1644343457
    Timeout   : 7200 (sec)
    Verify return code: 0 (ok)
---
DONE</code></pre>
<h3>Verifying a certificate with OpenSSL</h3>
<pre class="language-plain"><code class="language-plain">openssl verify -purpose any example.local.pem</code></pre>
<p>Making a secure connection using the <code>s_client</code> command is useful, but what if you wanted to verify and work with the certificate from the above output? The above <code>openssl verify</code> command examines a certificate and determines if it is valid.</p>
<p>As a concrete example, you can save the PEM-encoded certificate in the above output to a file and receive output that looks something like this:</p>
<pre class="language-plain"><code class="language-plain">★  openssl verify -purpose any daniela.pem
daniela.pem: CN = danielaorg.local, C = XX, ST = XX, L = Fake Locality, O = "Super Fake Company, Fake.", OU = Fake Organizational Unit
error 18 at 0 depth lookup:self signed certificate
OK</code></pre>
<p>For me, on my journey of understanding <code>ERR_CERT_INVALID</code>, verifying the cert ended up not helping. It confirmed what I knew, which was that this certificate was self-signed. But my next question was: “What exactly is in that certificate?”</p>
<h3>Viewing the certificate contents with OpenSSL</h3>
<pre class="language-plain"><code class="language-plain">openssl x509 -text -in example.local.pem</code></pre>
<p>The above command decrypts the PEM-formated cert to get something more human-readable. Calling out a few items specifically:</p>
<ol>
<li>Lines 8-10 — Date validity. Double-check that the certificate is for a date range that makes sense.</li>
<li>Lines 7,11 — The cert’s <strong>Subject</strong> and <strong>Issuer</strong> fields are usually different “things”. In the output below, they are the same organization, which implies that this is a self-signed certificate.</li>
<li>Lines 36-37 — CA:FALSE means that this certificate can’t be considered a Certificate Authority. This ended up being the source of my bug. The certificate wasn’t authorized to be a Certificate Authority, but it was a self-signed cert. Making it so these certificates could be considered CAs made Chrome stop complaining!</li>
<li>Lines 39,41 — These lines detail how this certificate can be used. In the previous <code>openssl verify</code> command, we used the <code>-purpose any</code> flag to ask if this certificate was verifiable for ANY use.</li>
</ol>
<pre class="language-plain"><code class="language-plain">Certificate:
    Data:
	Version: 3 (0x2)
	Serial Number:
	    59:c4:b6:1f:0a:8c:81:f6:5b
    Signature Algorithm: sha256WithRSAEncryption
	Issuer: CN=brayanname.local, C=XX, ST=XX, L=Fake Locality, O=Super Fake Company, Fake., OU=Fake Organizational Unit
	Validity
	    Not Before: Feb 15 22:41:37 2022 GMT
	    Not After : Feb 15 22:41:37 2032 GMT
	Subject: CN=brayanname.local, C=XX, ST=XX, L=Fake Locality, O=Super Fake Company, Fake., OU=Fake Organizational Unit
	Subject Public Key Info:
	    Public Key Algorithm: rsaEncryption
		Public-Key: (2048 bit)
		Modulus:
		    00:e8:08:84:51:84:fb:f0:97:4a:c2:4c:d7:87:0e:
		    d5:10:57:41:06:92:08:8a:cd:9e:8e:a1:59:ef:b6:
		    d0:8b:22:a5:1b:ea:4d:77:7d:24:59:d2:1e:2a:54:
		    1d:9a:7f:d2:92:a6:0a:4e:33:63:69:fb:f5:1a:3a:
		    41:0e:11:34:7f:42:62:58:58:ee:a4:f9:88:a1:ed:
		    bf:ba:9e:b3:b9:00:16:77:6c:92:5b:ed:bf:ed:79:
		    89:7b:cd:cc:26:b5:fb:ce:d2:4f:9a:1b:18:01:2b:
		    02:65:f7:f6:90:7e:22:19:ff:f7:d4:3c:8c:09:c8:
		    4f:d1:84:ac:a2:1c:ae:55:1a:87:73:79:63:c2:8d:
		    6b:57:fa:9f:cc:c1:e4:5a:3c:ef:38:17:76:3c:5f:
		    c3:bc:b9:c5:0f:50:dd:4a:7e:bc:00:bc:ca:64:53:
		    af:b1:8d:d2:ad:2e:b6:47:0a:27:a7:03:7b:bb:c0:
		    f4:74:f9:a5:88:9f:ff:59:aa:e1:02:31:ef:db:04:
		    dc:34:a2:49:d9:8f:c8:87:8c:6a:0a:2c:ee:8f:34:
		    b3:7c:d2:98:b5:9e:26:38:8c:33:7c:ab:3b:1f:e7:
		    1d:55:0b:be:76:ac:00:9a:c5:01:f1:1b:47:67:90:
		    d4:37:c2:64:24:38:4e:0c:94:14:ce:3d:a1:77:68:
		    51:d1
		Exponent: 65537 (0x10001)
	X509v3 extensions:
	    X509v3 Basic Constraints: 
		CA:FALSE
	    X509v3 Key Usage: 
		Digital Signature, Non Repudiation, Key Encipherment, Data Encipherment, Certificate Sign
	    X509v3 Extended Key Usage: 
		TLS Web Server Authentication, TLS Web Client Authentication, Code Signing, E-mail Protection, Time Stamping
	    Netscape Cert Type: 
		SSL Client, SSL Server, S/MIME, Object Signing, SSL CA, S/MIME CA, Object Signing CA
	    X509v3 Subject Key Identifier: 
		05:CC:36:C8:0C:42:06:58:5D:DF:DC:47:58:3D:B3:A7:C6:30:75:06
	    X509v3 Subject Alternative Name: 
		DNS:brayanname.local, DNS:*.brayanname.local
    Signature Algorithm: sha256WithRSAEncryption
	 5b:d9:bf:b8:53:7d:2c:48:8c:f4:01:3b:6f:2a:e6:06:c0:07:
	 13:60:82:02:f7:f8:f0:88:4d:c4:20:ca:c3:77:e9:29:21:ea:
	 5b:87:1e:57:12:54:51:c5:ef:32:77:06:3b:78:f8:bd:d7:a9:
	 b4:e2:5c:34:bd:b1:af:b3:ce:54:b3:c1:7a:7e:d9:b6:d4:0a:
	 45:ef:00:6b:78:30:ca:89:f8:f7:d5:91:5c:2d:c6:0f:ce:69:
	 f4:3f:98:17:c2:43:8f:9a:5b:e3:dd:46:81:07:0b:5e:a5:40:
	 fb:81:28:7b:90:20:3a:8d:37:01:d1:93:16:9d:fd:e8:e5:fd:
	 9d:73:b4:5b:73:f7:e6:89:50:ca:74:90:4d:c6:d0:1b:f5:c7:
	 d6:65:12:2c:f2:63:cb:74:34:64:ad:1e:dc:49:9c:c8:74:56:
	 f2:3b:92:df:2f:12:a8:38:5b:22:f0:96:ef:e4:8a:39:b3:64:
	 ed:4f:67:c7:db:38:74:64:58:2c:cc:3b:f9:75:ee:66:81:7c:
	 44:88:07:a9:cf:4c:1c:03:9c:38:90:5e:31:97:ba:2f:5e:0e:
	 30:db:8c:16:5b:01:ec:c3:0d:7d:92:88:da:1a:97:fa:59:d9:
	 b5:9e:4e:b7:5e:7c:20:5d:df:77:6c:8c:73:ff:f4:59:6a:e3:
	 a8:d6:be:02
-----BEGIN CERTIFICATE-----
MIIEaTCCA1GgAwIBAgIJWcS2HwqMgfZbMA0GCSqGSIb3DQEBCwUAMIGUMRkwFwYD
VQQDExBicmF5YW5uYW1lLmxvY2FsMQswCQYDVQQGEwJYWDELMAkGA1UECBMCWFgx
FjAUBgNVBAcTDUZha2UgTG9jYWxpdHkxIjAgBgNVBAoTGVN1cGVyIEZha2UgQ29t
cGFueSwgRmFrZS4xITAfBgNVBAsTGEZha2UgT3JnYW5pemF0aW9uYWwgVW5pdDAe
Fw0yMjAyMTUyMjQxMzdaFw0zMjAyMTUyMjQxMzdaMIGUMRkwFwYDVQQDExBicmF5
YW5uYW1lLmxvY2FsMQswCQYDVQQGEwJYWDELMAkGA1UECBMCWFgxFjAUBgNVBAcT
DUZha2UgTG9jYWxpdHkxIjAgBgNVBAoTGVN1cGVyIEZha2UgQ29tcGFueSwgRmFr
ZS4xITAfBgNVBAsTGEZha2UgT3JnYW5pemF0aW9uYWwgVW5pdDCCASIwDQYJKoZI
hvcNAQEBBQADggEPADCCAQoCggEBAOgIhFGE+/CXSsJM14cO1RBXQQaSCIrNno6h
We+20IsipRvqTXd9JFnSHipUHZp/0pKmCk4zY2n79Ro6QQ4RNH9CYlhY7qT5iKHt
v7qes7kAFndsklvtv+15iXvNzCa1+87ST5obGAErAmX39pB+Ihn/99Q8jAnIT9GE
rKIcrlUah3N5Y8KNa1f6n8zB5Fo87zgXdjxfw7y5xQ9Q3Up+vAC8ymRTr7GN0q0u
tkcKJ6cDe7vA9HT5pYif/1mq4QIx79sE3DSiSdmPyIeMagos7o80s3zSmLWeJjiM
M3yrOx/nHVULvnasAJrFAfEbR2eQ1DfCZCQ4TgyUFM49oXdoUdECAwEAAaOBuzCB
uDAJBgNVHRMEAjAAMAsGA1UdDwQEAwIC9DA7BgNVHSUENDAyBggrBgEFBQcDAQYI
KwYBBQUHAwIGCCsGAQUFBwMDBggrBgEFBQcDBAYIKwYBBQUHAwgwEQYJYIZIAYb4
QgEBBAQDAgD3MB0GA1UdDgQWBBQFzDbIDEIGWF3f3EdYPbOnxjB1BjAvBgNVHREE
KDAmghBicmF5YW5uYW1lLmxvY2FsghIqLmJyYXlhbm5hbWUubG9jYWwwDQYJKoZI
hvcNAQELBQADggEBAFvZv7hTfSxIjPQBO28q5gbABxNgggL3+PCITcQgysN36Skh
6luHHlcSVFHF7zJ3Bjt4+L3XqbTiXDS9sa+zzlSzwXp+2bbUCkXvAGt4MMqJ+PfV
kVwtxg/OafQ/mBfCQ4+aW+PdRoEHC16lQPuBKHuQIDqNNwHRkxad/ejl/Z1ztFtz
9+aJUMp0kE3G0Bv1x9ZlEizyY8t0NGStHtxJnMh0VvI7kt8vEqg4WyLwlu/kijmz
ZO1PZ8fbOHRkWCzMO/l17maBfESIB6nPTBwDnDiQXjGXui9eDjDbjBZbAezDDX2S
iNoal/pZ2bWeTrdefCBd33dsjHP/9Flq46jWvgI=
-----END CERTIFICATE-----</code></pre>
<h2>Resources</h2>
<h3>SSL Terms</h3>
<p>When I was first learning about how the various SSL parts fit together, I found this StackOverflow answer to be excellent in unpacking the jargon:</p>
<ul>
<li><a href="https://serverfault.com/a/9717/402770">What is a Pem file and how does it differ from other OpenSSL Generated Key File Formats?</a></li>
</ul>
<h3>Other Links</h3>
<ul>
<li><a href="https://prateeknischal.github.io/posts/trust-the-certs/">https://prateeknischal.github.io/posts/trust-the-certs/</a>
<ul>
<li>Great post that goes into detail about using <code>openssl</code> and how to understand the output that OpenSSL gives you.</li>
</ul>
</li>
<li><a href="https://security.stackexchange.com/q/143061">StackExchange: Does openssl refuse self signed certificates without basic constraints?</a>
<ul>
<li>This was the main resource that keyed me into the idea that a doubled-up certificate chain would require that certificate to be a CA (certificate authority).</li>
</ul>
</li>
</ul>
]]></description>
	</item>
	
	<item>
		<title>New Role: Software Engineer</title>
		<link>https://passionsplay.com/blog/new-role-software-engineer/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/new-role-software-engineer/</guid>
		<pubDate>Fri, 11 Feb 2022 13:32:09 GMT</pubDate>
		<description><![CDATA[<p>At the start of the year, I transitioned to a new role as a Software Engineer building <a href="https://localwp.com">Local</a>: loving it! I’m so excited to learn from a fantastic team and have the space to focus on profoundly learning Javascript.</p>
<p>Coming into this role is an exciting transition for me. I’ve worked in technology for over nine years, many spent doing “<a href="https://wordpress.org">WordPress</a> developer” things. That means wearing many hats, from code-wrangler to content creator to SEO strategist – when you WordPress, you ensure you have a voice on the web.</p>
<p>So what’s different? Well, as a Software Engineer, my day-to-day work is different than the WordPress sites created while working with Peaceful Media. It’s also less focused on the communication and leadership skills I improved while working in the Flywheel support organization. And I’m not doing as much technical writing or presenting like I was as the Local Community Manager.</p>
<p>Building Local means crafting <a href="https://www.javascript.com/">Javascript</a> (it is built on <a href="https://www.electronjs.org/">Electron</a>), but it also means being proficient in other things too:</p>
<ul>
<li>Compiling and configuring Lightning Services (i.e., native binaries) for various pieces of server software like <a href="https://httpd.apache.org/">Apache</a>, <a href="https://www.nginx.com/">Nginx</a>, <a href="https://www.php.net/">PHP</a>, and <a href="https://www.mysql.com/">MySQL</a>.</li>
<li>Working with <a href="https://en.wikipedia.org/wiki/HTTPS">HTTPS</a> (along with <a href="https://en.wikipedia.org/wiki/Transport_Layer_Security">TLS &amp; SSL</a>) and figuring out how those pieces of tech work under the hood.</li>
<li>Knowing the quirks of WordPress and making the development tool I wish I had when initially doing agency work.</li>
</ul>
<p>So yeah, many of these topics aren’t entirely new to me, but building with Electron definitely is. Also, building with Javascript is unique, with the frameworks and community constantly changing. And I’ve never done this sort of development in <a href="https://www.gnu.org/software/emacs/">Emacs</a>.</p>
<p>There’s much to learn, but that’s what this new role and the new year are for. Learning and improving my workflow, refining my craft as a developer, and writing about the process!</p>
]]></description>
	</item>
	
	<item>
		<title>Scripted IP lookup in the terminal</title>
		<link>https://passionsplay.com/blog/scripted-ip-lookup-in-the-terminal/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/scripted-ip-lookup-in-the-terminal/</guid>
		<pubDate>Wed, 27 Oct 2021 11:36:54 GMT</pubDate>
		<description><![CDATA[<p>The recent mess with the <a href="https://letsencrypt.org/docs/dst-root-ca-x3-expiration-september-2021/">Let’s Encrypt root certificate expiring</a> has meant some weird issues have been popping up.</p>
<p>We found ourselves troubleshooting a potential firewall issue that was affecting only a certain subset of users. There seemed like there was a pattern, but what exactly?</p>
<ul>
<li>Was it location-based?</li>
<li>Was it time-based?</li>
<li>Internet Provider based?</li>
<li>Something else?</li>
</ul>
<p>The only lead we had was that changing to a VPN magically fixed things for the end-users. As a next step, we thought we would examine the metadata associated with the set of IPs that were having problems.</p>
<p>So what do you do when you need to gather lots of info for something you’ve never examined before?</p>
<p>Turn to the terminal and script it!</p>
<h2>The basics</h2>
<p>There’s probably any number of services we could turn to, but <a href="https://ipinfo.io/">ipinfo.io</a> seemed like a good one. A query to get metadata about an IP address looks like this:</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">curl</span> http://ipinfo.io/104.57.176.115</code></pre>
<pre class="language-plain"><code class="language-plain">{
    "ip": "104.57.176.115",
    "hostname": "104-57-176-115.lightspeed.austtx.sbcglobal.net",
    "city": "Austin",
    "region": "Texas",
    "country": "US",
    "loc": "30.2672,-97.7431",
    "org": "AS7018 AT&amp;T Services, Inc.",
    "postal": "78701",
    "timezone": "America/Chicago",
    "readme": "https://ipinfo.io/missingauth"
  }</code></pre>
<h2>Iterating over many IPs</h2>
<p>That’s good for one-off requests, and it also means that we can improve on it to handle a collection of IPs. For example, if a file named <code>ip-list</code> has IP addresses on each line, we can use a BASH <code>while</code> loop to fetch more info about each IP address.</p>
<pre class="language-plain"><code class="language-plain">\# in a file called ip-list
102.176.65.190
104.219.136.17
104.57.176.115
104.57.29.69
107.190.28.137
109.48.146.95
112.134.164.187
116.97.53.100
12.90.145.30
121.200.4.138</code></pre>
<p>We use a <code>while</code> loop to get more info!</p>
<pre class="language-bash"><code class="language-bash"><span class="token punctuation">(</span> <span class="token keyword">while</span> <span class="token builtin class-name">read</span> <span class="token function">ip</span><span class="token punctuation">;</span>
<span class="token keyword">do</span> <span class="token function">curl</span> <span class="token parameter variable">-s</span> <span class="token string">"http://ipinfo.io/<span class="token variable">${ip}</span>"</span><span class="token punctuation">;</span>
<span class="token keyword">done</span> <span class="token operator">&lt;</span> ips-list <span class="token punctuation">)</span> <span class="token operator">></span> ip-info.json</code></pre>
<p>This works and ultimately got me what I needed since I was able to then use <code>jq</code> to investigate various things within the <code>ip-info.json</code> file:</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">cat</span> ip-info.json <span class="token operator">|</span> jq <span class="token parameter variable">-r</span> <span class="token string">'\[.ip, .timezone, .country, .org\] | @tsv'</span></code></pre>
<pre class="language-plain"><code class="language-plain">102.176.65.190	Africa/Accra	GH	AS29614 Ghana Telecommunications Company Limited
104.219.136.17	America/Chicago	US	AS14143 Rock Solid Internet &amp; Telephone
104.57.176.115	America/Chicago	US	AS7018 AT&amp;T Services, Inc.
104.57.29.69	America/New\_York	US	AS7018 AT&amp;T Services, Inc.
107.190.28.137	America/Vancouver	CA	AS5645 TekSavvy Solutions, Inc.
109.48.146.95	Europe/Lisbon	PT	AS2860 NOS COMUNICACOES, S.A.
112.134.164.187	Asia/Colombo	LK	AS9329 Sri Lanka Telecom Internet
116.97.53.100	Asia/Ho\_Chi\_Minh	VN	AS7552 Viettel Group
12.90.145.30	America/Chicago	US	AS7018 AT&amp;T Services, Inc.
121.200.4.138	Australia/Melbourne	AU	AS4764 Aussie Broadband
123.243.115.186	Australia/Sydney	AU	AS7545 TPG Telecom Limited
124.186.3.85	Australia/Brisbane	AU	AS1221 Telstra Corporation Ltd
134.56.52.132	America/New\_York	US	AS23089 Hotwire Communications
136.35.164.218	America/Chicago	US	AS16591 Google Fiber Inc.
136.49.143.29	America/Chicago	US	AS16591 Google Fiber Inc.
136.49.143.31	America/Chicago	US	AS16591 Google Fiber Inc.
136.58.116.229	America/Chicago	US	AS16591 Google Fiber Inc.</code></pre>
<h2>Make it reusable</h2>
<p>The above process was good enough for my needs at that moment, but I wondered if there was a way to make something that was reusable. I pictured the ability to run one command followed by any number of IP addresses which then prints out more information about each IP address in JSON format.</p>
<p>Something like this:</p>
<pre class="language-bash"><code class="language-bash">ipinfo <span class="token number">102.176</span>.65.190 <span class="token number">104.219</span>.136.17 <span class="token number">104.57</span>.176.115</code></pre>
<p>This ended up being fairly easy to create!</p>
<p>Since those arguments are interpreted by Bash as an array of values, we can do a <code>for</code> loop with a quick regex validation to make sure it’s valid IPv4 address before making our request to ipinfo.</p>
<pre class="language-bash"><code class="language-bash"><span class="token shebang important">#!/bin/bash</span>
<span class="token comment"># Save and name this script something like ipinfo and put it in your $PATH</span>
<span class="token keyword">for</span> arg<span class="token punctuation">;</span> <span class="token keyword">do</span>
    <span class="token comment"># Only make requests for valid IPv4 addresses</span>
    <span class="token keyword">if</span> <span class="token punctuation">\</span><span class="token punctuation">[</span><span class="token punctuation">\</span><span class="token punctuation">[</span> <span class="token variable">$arg</span> <span class="token operator">=~</span> ^<span class="token punctuation">\</span><span class="token punctuation">[</span><span class="token number">0</span>-9<span class="token punctuation">\</span><span class="token punctuation">]</span><span class="token punctuation">{</span><span class="token number">1,3</span><span class="token punctuation">}</span><span class="token punctuation">\</span><span class="token punctuation">\</span>.<span class="token punctuation">\</span><span class="token punctuation">[</span><span class="token number">0</span>-9<span class="token punctuation">\</span><span class="token punctuation">]</span><span class="token punctuation">{</span><span class="token number">1,3</span><span class="token punctuation">}</span><span class="token punctuation">\</span><span class="token punctuation">\</span>.<span class="token punctuation">\</span><span class="token punctuation">[</span><span class="token number">0</span>-9<span class="token punctuation">\</span><span class="token punctuation">]</span><span class="token punctuation">{</span><span class="token number">1,3</span><span class="token punctuation">}</span><span class="token punctuation">\</span><span class="token punctuation">\</span>.<span class="token punctuation">\</span><span class="token punctuation">[</span><span class="token number">0</span>-9<span class="token punctuation">\</span><span class="token punctuation">]</span><span class="token punctuation">{</span><span class="token number">1,3</span><span class="token punctuation">}</span>$ <span class="token punctuation">\</span><span class="token punctuation">]</span><span class="token punctuation">\</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">then</span>
	<span class="token function">curl</span> <span class="token parameter variable">-s</span> <span class="token string">"http://ipinfo.io/<span class="token variable">${arg}</span>"</span><span class="token punctuation">;</span>
    <span class="token keyword">fi</span>
<span class="token keyword">done</span><span class="token punctuation">;</span></code></pre>
<p>The ergonomics of this script mean that it’s pretty composable. We can use something like <code>xargs</code> to mash on any number of IP addresses to be queried!</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">cat</span> ips-list <span class="token operator">|</span> <span class="token function">xargs</span> ipinfo                   <span class="token comment"># print to stdout</span>
<span class="token function">cat</span> ips-list <span class="token operator">|</span> <span class="token function">xargs</span> ipinfo <span class="token punctuation">\</span><span class="token punctuation">\</span><span class="token punctuation">;</span> <span class="token operator">></span> ip-info.json <span class="token comment"># redirect to a file</span></code></pre>
]]></description>
	</item>
	
	<item>
		<title>WPCLI Focus Time – 2021-10-20</title>
		<link>https://passionsplay.com/blog/wpcli-focus-time-2021-10-20/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/wpcli-focus-time-2021-10-20/</guid>
		<pubDate>Thu, 21 Oct 2021 04:51:02 GMT</pubDate>
		<description><![CDATA[<p>After missing my blocktime last week to having too many projects open, I came back to my open pull request against the WPCLI <code>core-command</code> repo:</p>
<ul>
<li><a href="https://github.com/wp-cli/core-command/pull/192">Remove language translation check from core update process by bgturner · Pull Request #192 · wp-cli/core-command · GitHub</a></li>
</ul>
<p>The main piece of feedback was:</p>
<blockquote>
<p>ensure the language installation is reliably disabled across all wp versions.</p>
</blockquote>
<p>So, how to do it!?</p>
<h2>Learning more about Behat</h2>
<p>Using my <a href="/blog/wpcli-focus-time-2021-10-01/">notes from the last OSS session</a>, I was able to set Behat to use the MySQL instance from a Local site.</p>
<p>The next thing to learn more about is actually writing new functional tests with Behat. I’ve only ever skimmed this tool’s functionality and have never used it to write my own tests. So what do I need to do to start?</p>
<p>From reading the <a href="https://behat.org/en/latest/quick_start.html">Behat Getting Started guide</a>, the terminology looks like:</p>
<ul>
<li><strong>Behat</strong> is a tool to do Behavor Driven Design (BDD)</li>
<li>The format of the tests are written in <strong>Gherkin</strong></li>
<li>Each <strong>feature</strong> is one file</li>
<li>Each feature file contains a number of different <strong>scenarios</strong></li>
</ul>
<p>So far so good – this feels like other testing tools! One thing I appreciate with other testing frameworks is to be able to zero in on one specific test to run. So using gherkin’s language: “How do I run specific <strong>features</strong>, or even specific <strong>scenarios</strong>?”</p>
<p>Looking at the <a href="https://docs.behat.org/en/v2.5/guides/6.cli.html#gherkin-filters">Behat documentation for Gherkin filters</a>, it looks like we can do both!</p>
<pre class="language-plain"><code class="language-plain">./vendor/bin/behat features/core.feature # run a feature
./vendor/bin/behat --name='Update from ZIP file' # run a scenario</code></pre>
<h3>Now, how to write a new test?</h3>
<p>As I started to investigate how other WPCLI functional tests were written, I realized that I didn’t know what sorts of steps were available to me out of the box.</p>
<p>For example, what <em>exactly</em> am I getting when I write a scenario that starts with <code>Given a WP install</code>?</p>
<p><img src="wp-cli-behat-feature-bF3DJm4RkleL.png" alt="An example of a wpcli behat feature."></p>
<p>As it turns out, there’s an existing issue in the WPCLI handbook (ie the documentation repo) that is asking to clarify how to write Behat tests:</p>
<p><a href="https://github.com/wp-cli/handbook/issues/3">Create a dedicated doc for how to write Behat tests · Issue #3 · wp-cli/handbook · GitHub</a></p>
<p>Four years since that was created, and I gues I’m as good a person as any to start writing that content, so I forked the repo and pushed up a <code>wip</code> commit:</p>
<p><a href="https://github.com/bgturner/handbook/tree/issue/3/writing-behat-tests">GitHub – bgturner/handbook at issue/3/writing-behat-tests</a></p>
]]></description>
	</item>
	
	<item>
		<title>Installing Block-based themes with WPCLI</title>
		<link>https://passionsplay.com/blog/installing-block-based-themes-with-wpcli/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/installing-block-based-themes-with-wpcli/</guid>
		<pubDate>Sat, 02 Oct 2021 05:25:18 GMT</pubDate>
		<description><![CDATA[<p>After some of the great sessions at WordCamp US, I was inspired to learn more about some of the block-based themes found on WordPress.org.</p>
<p>The quickest way to view the published block-based themes is to browse the <a href="https://wordpress.org/themes/tags/full-site-editing/">“Full site editing” tag of themes on WordPress.org.</a></p>
<p>But what if we want a way to quickly download and browse the code of those themes on our own machine?</p>
<h2>Listing WordPress themes from the CLI</h2>
<p>Examining the requests made from the above page, I noticed that the main content is generated from the response to a call to the <code>/themes/info</code> endpoint of <code>api.wordpress.org</code>.</p>
<p><img src="wporg-themes-info-endpoint-v4SZ7BEtGyaI.png" alt="A screenshot of investigating the requests made by api.wordpress.org"></p>
<p>Exploring that endpoint a bit with <a href="https://github.com/pashky/restclient.el"><code>restclient.el</code> package in Emacs</a> showed that there’s an easy way to get the theme slugs!</p>
<p><img src="emacs-restclient-exploring-wpo-99A9Z22fI60q.png" alt="A screenshot of exploring the api.wordpress.org/themes/info endpoint."></p>
<h2>Install all Block-themes with curl, jq, and wpcli</h2>
<p>So we have a URL, and if we have <a href="https://stedolan.github.io/jq/download/"><code>jq</code> installed</a>, then we can pipe our way to installing all of the block-themes!</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">curl</span> https://api.wordpress.org/themes/info/1.2/<span class="token punctuation">\</span>?action<span class="token punctuation">\</span><span class="token operator">=</span>query_themes<span class="token punctuation">\</span><span class="token operator">&amp;</span>request%5Btag%5D%5B%5D<span class="token punctuation">\</span><span class="token operator">=</span>full-site-editing<span class="token punctuation">\</span><span class="token operator">&amp;</span>request%5Bper_page%5D<span class="token punctuation">\</span><span class="token operator">=</span><span class="token number">50</span> <span class="token operator">|</span> jq <span class="token parameter variable">-r</span> <span class="token string">'.themes[].slug'</span> <span class="token operator">|</span> <span class="token function">xargs</span> wp theme <span class="token function">install</span></code></pre>
<p><img src="installing-block-based-themes--07FVZrSOQG5B.png" alt="A screenshot of the script used to download and install all block-based themes."></p>
<p>Once those are downloaded, it’s just a matter of listing the themes and activating one!</p>
<p><img src="install-block-based-themes-cli-I7xbX79jbR8b.png" alt="A screenshot of activating and listing the themes using WPCLI."></p>
]]></description>
	</item>
	
	<item>
		<title>WPCLI Focus Time – 2021-10-01</title>
		<link>https://passionsplay.com/blog/wpcli-focus-time-2021-10-01/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/wpcli-focus-time-2021-10-01/</guid>
		<pubDate>Sat, 02 Oct 2021 02:29:38 GMT</pubDate>
		<description><![CDATA[<p><a href="https://us.wordcamp.org/2021/">WordCamp US 2021</a> is happening, but in between sessions, I’ve been trying to get a better setup for doing development work on <a href="https://github.com/wp-cli">WPCLI</a>.</p>
<h2>Trying out the <code>wp-cli-dev</code> repo</h2>
<p>As I found in <a href="/blog/wpcli-focus-time-2021-09-24/">my last Open-source block time</a>, the commands I had installed locally were missing various subcommands that were installed within my shell of a <a href="https://localwp.com">Local</a> site.</p>
<p>Thanks to a pointer by <a href="https://wordpress.slack.com/archives/C02RP4T41/p1632548407101200">@schlessera in the WordPress #cli slack channel</a>, there’s the <a href="https://github.com/wp-cli/wp-cli-dev"><code>wp-cli/wp-cli-dev</code> package</a> that attempts to make it easier to download and scaffold out a full wpcli dev environment.</p>
<p>It wasn’t a seamless path since it seems like Github was throttling me, or in some way rejecting my key. To solve it, I temporarily commented out a call to refresh the various repos right after cloning them. This allowed the <code>composer install</code> command to finish cleanly and put all the various repos into place.</p>
<p><a href="/wp-content/uploads/2021/10/wpcli-dev-comment-out-refresh-repos-line.png"><img src="wpcli-dev-comment-out-refresh--rKspkfIQxfH1.png" alt="A screenshot showing the commented out line that allowed the composer install command to finish successfully."></a></p>
<h2>I use Local. How do I run Behat?</h2>
<p>Doing the above allowed me to run some of the initial things like <a href="https://github.com/squizlabs/PHP_CodeSniffer">PHP_Codesniffer</a>, but when I ran <a href="https://docs.behat.org/en/latest/">Behat</a> (and probably <a href="https://phpunit.de/">PHPUnit</a>) I wasn’t able to connect to the DB.</p>
<p><a href="/wp-content/uploads/2021/10/wp-cli-dev-cant-connect-to-mysql-sock.png"><img src="wp-cli-dev-cant-connect-to-mys-FOC5c1gFbFcw.png" alt="A screenshot showing the MySQL error related to connecting to the MySQL socket."></a></p>
<p>Here’s that specific error:</p>
<pre class="language-plain"><code class="language-plain">ERROR 2002 (HY000): Can't connect to local MySQL server through socket '/tmp/mysql.sock' (2)</code></pre>
<p>This basically means that MySQL is having issues connecting to the default socket. But since I use <a href="https://localwp.com">Local</a> to quickly create environments instead of directly installing things like MySQL — what do I need to do?</p>
<p>The general flow was to:</p>
<ol>
<li>Find the configuration file for he running MySQL process.</li>
<li>Search that config file for the <code>socket</code>.</li>
<li>Export the <code>WP_CLI_TEST_DBHOST</code> environment variable.</li>
<li>Run the tests!</li>
</ol>
<p>For me, under a Local site’s environment, that process looks something like this:</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">ps</span> aux <span class="token operator">|</span> <span class="token function">grep</span> mysql
<span class="token function">grep</span> <span class="token string">'socket'</span> ~/Library/Application<span class="token punctuation">\</span> Support/Local/run/o5ykeOZFY/conf/mysql/my.cnf                             
<span class="token builtin class-name">export</span> <span class="token assign-left variable">WP_CLI_TEST_DBHOST</span><span class="token operator">=</span><span class="token string">'localhost:/Users/ben.turner/Library/Application Support/Local/run/o5ykeOZFY/mysql/mysqld.sock'</span>
<span class="token function">composer</span> run behat</code></pre>
<p><a href="/wp-content/uploads/2021/10/wp-cli-dev-running-behat.png"><img src="wp-cli-dev-running-behat-Y5HabykacqzG.png" alt="A screenshot of zeroing in on where to find the socket for the Local site."></a></p>
]]></description>
	</item>
	
	<item>
		<title>WPCLI Focus Time – 2021-09-24</title>
		<link>https://passionsplay.com/blog/wpcli-focus-time-2021-09-24/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/wpcli-focus-time-2021-09-24/</guid>
		<pubDate>Sat, 25 Sep 2021 00:52:20 GMT</pubDate>
		<description><![CDATA[<p>Another block of focus time for the <a href="https://github.com/wp-cli">WPCLI</a> project! I continued working on issue 191 of the <code>core command</code> repo:</p>
<ul>
<li><a href="https://github.com/wp-cli/core-command/issues/191">Unexpected HTML output when updating WordPress #191</a></li>
</ul>
<p>Even though the solution was simple and I was able to get a <a href="https://github.com/wp-cli/core-command/pull/192">pull request created</a>, I found that there may be some holes in how the WPCLI project onboards new contributors.</p>
<p>Basically, after following the steps in <a href="https://make.wordpress.org/cli/handbook/contributions/pull-requests/#working-on-the-project-as-a-whole">Working on the project as a whole</a>, I noticed that I was missing a number of subcommands:</p>
<p><img src="wpcli-missing-subcommands-afte-hV5X4XXNqkXd.png" alt="A screenshot comparing two terminal sessions, one using a default installation of wpcli, and one after creating a dev sandbox."></p>
<p>I’m asking for a bit of direction in the <a href="https://wordpress.slack.com/archives/C02RP4T41/p1632505869098700">WordPress #cli slack channel</a>, but my hunch is that the main <code>wp-cli</code> repo needs an update to the <code>[composer.json](https://github.com/wp-cli/wp-cli/blob/77e392bd3b38df599dd26dca66e7ee22b5213dba/composer.json#L19)</code> that includes the various subcommands:</p>
<pre class="language-plain"><code class="language-plain">"require-dev": {
        "roave/security-advisories": "dev-master",
        "wp-cli/db-command": "^1.3 || ^2",
        "wp-cli/entity-command": "^1.2 || ^2",
        "wp-cli/extension-command": "^1.1 || ^2",
        "wp-cli/package-command": "^1 || ^2",
        "wp-cli/wp-cli-tests": "^3.0.7"
    },</code></pre>
]]></description>
	</item>
	
	<item>
		<title>Time-block Open Source Contributions</title>
		<link>https://passionsplay.com/blog/time-block-open-source-contributions/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/time-block-open-source-contributions/</guid>
		<pubDate>Sat, 18 Sep 2021 00:51:51 GMT</pubDate>
		<description><![CDATA[<p>In an effort to become more familiar with the tools I use every day, as well as make them better, I’m starting to carve out time in my week to contribute to Open-source.</p>
<p>This has been a few weeks in the making, where I basically have:</p>
<ol>
<li>Made the case with my employer/manager that I should give back (it was an easy case to make!)</li>
<li>Carve out time on my calendar.</li>
<li>Lurk on projects to see where I should help.</li>
<li>Start helping!</li>
</ol>
<p>Since I love the terminal and am using WordPress daily, helping with <a href="https://github.com/wp-cli/wp-cli">WPCLI</a> makes sense.</p>
<h2>So what did I do today?</h2>
<p>Part of me wanting to write these less polished blog posts is to keep me honest about contributing, as well as help establish the habit of working and writing. Ok, so what did I do during today’s open-source time block?</p>
<p>Two main things:</p>
<ol>
<li>My first pull request to WPCLI was accepted!
<ul>
<li><a href="https://github.com/wp-cli/wp-cli/pull/5562">https://github.com/wp-cli/wp-cli/pull/5562</a></li>
</ul>
</li>
<li>Searched through the <a href="https://github.com/wp-cli/wp-cli/issues?q=is%3Aissue+is%3Aopen+sort%3Acreated-asc">oldest issues for WPCLI on Github</a> to see if I can replicate, clarify, or possibly close any of them.</li>
</ol>
<h2>Replicating an issue with the <code>core update</code> subcommand</h2>
<p>It turns out that the <a href="https://github.com/wp-cli/wp-cli/issues/1501">oldest issue</a> seems to be a <code>good-first-issue</code>.</p>
<p>After a little digging in the source code and trying some things in the terminal, I was able to <a href="https://github.com/wp-cli/wp-cli/issues/1501#issuecomment-921967768">distill down a more direct list of reproduction steps</a>.</p>
<p>My block time is up for today, but leaving myself breadcrumbs in that issue, as well as on this blog, should allow me to dive in soon with a fix!</p>
]]></description>
	</item>
	
	<item>
		<title>Create Minimal Emacs Environments with a Shell Script</title>
		<link>https://passionsplay.com/blog/create-minimal-emacs-environments-with-a-shell-script/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/create-minimal-emacs-environments-with-a-shell-script/</guid>
		<pubDate>Fri, 10 Sep 2021 05:46:08 GMT</pubDate>
		<description><![CDATA[<p>Within the last two years, I’ve started to use Emacs more and more in large part due to the writing and note-taking powers of <a href="https://orgmode.org/">Org-mode</a>.</p>
<p>When I first started to use Emacs, I relied on <a href="https://www.spacemacs.org/">Spacemacs</a>, which is basically an opinionated configuration that has settings that are useful for someone used to Vim’s way of working with text. (yep, that’s me! Why choose between Emacs and Vim when you can have both!)</p>
<p>At a certain point I wanted to have a better understanding of my editor and in particular, wanted to get a feel for how to approach editing “the Emacs way.” Mostly this means trying new packages or built-in functionality with a minimal amount of cruft.</p>
<p>Since there are useful packages out there, and I don’t want to go and break my existing configuration, I found myself needing a way to quickly create a minimal Emacs sandbox. Something that is quick to scaffold up and just as easy to tear down by deleting a folder.</p>
<p>After a bit of hacking, and the help of this <a href="https://stackoverflow.com/a/58039656">StackOverflow answer</a>, the result is a simple bash script that I’ve pushed to Github:</p>
<ul>
<li><a href="https://github.com/bgturner/emacs-sandbox">https://github.com/bgturner/emacs-sandbox</a></li>
</ul>
<h2>Usage</h2>
<p>The Readme should have more details about using the script, but as a quick intro, here’s what you can expect:</p>
<p><a href="/wp-content/uploads/2021/09/emacs-sandbox.gif"><img src="/wp-content/uploads/2021/09/emacs-sandbox.gif" alt="Screen recording of using the emacs-sandbox.sh script to create an Emacs sandbox environment."></a></p>
<pre class="language-bash"><code class="language-bash">emacs-sandbox.sh <span class="token parameter variable">-i</span> straight <span class="token parameter variable">-i</span> evil
<span class="token comment"># Creates the default 'sandbox' environment with the 'straight'</span>
<span class="token comment"># package manager and 'evil' (ie, vi-style bindings)</span>

emacs-sandbox.sh <span class="token parameter variable">-n</span> super-secret <span class="token parameter variable">-i</span> melpa <span class="token parameter variable">-i</span> straight <span class="token parameter variable">-i</span> ivy
<span class="token comment"># Creates the named 'super-secret' environment with the 'melpa'</span>
<span class="token comment"># package repository configured along with 'straight' for package</span>
<span class="token comment"># management  and the 'ivy' package for general completion</span>
<span class="token comment"># functionality.</span></code></pre>
]]></description>
	</item>
	
	<item>
		<title>Using Awk to Convert CSV Crypto Transactions into Ledger-cli entries</title>
		<link>https://passionsplay.com/blog/using-awk-to-convert-csv-crypto-transactions-into-ledger-cli-entries/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/using-awk-to-convert-csv-crypto-transactions-into-ledger-cli-entries/</guid>
		<pubDate>Tue, 31 Aug 2021 05:55:00 GMT</pubDate>
		<description><![CDATA[<p>I recently wrote an article about <a href="/blog/intro-to-using-awk/">using Awk to make sense out of an Nginx access log</a>. But Awk isn’t limited to just sysadmin things; anytime I need to do bulk processing of text, I usually make the first pass with Awk.</p>
<p>You’re right, some (many?) times awk isn’t the right choice — maybe the project has too many edge-cases to consider, or maybe there are too many unknowns that need to be abstracted away. In those cases, turning to a different language with robust libraries makes sense.</p>
<p>On the other hand, the one thing I don’t like about turning to another language is all the scaffolding that’s required to just get working. Usually, to get started, you create a project, initialize a virtual environment, define and install dependencies, mess with configuration because something is updated, and on and on.</p>
<p>Because awk is likely already installed on the system you’re working on, for many problems, you’re just a couple of minutes away from a solution.</p>
<h2>CSV of Nexo Transactions</h2>
<p>So that’s where I found myself the other night — I needed to convert a CSV file of cryptocurrency transactions from <a href="https://nexo.io/ref/xv8yhvpogz?src=web-link">Nexo</a> into a format that <a href="https://www.ledger-cli.org/">Ledger-cli</a> understands.</p>
<p>More specifically, how do I turn something like this:</p>
<pre class="language-plain"><code class="language-plain">Transaction,Type,Currency,Amount,USD Equivalent,Details,Outstanding Loan,Date / Time
NXTnxxxxxxxxx,Interest,ADA,0.06513149,$0.14267261305268,approved / ADA Interest Earned,$0.00,2021-08-16 01:00:04
NXTGxxxxxxxxx,Interest,ADA,0.06887155,$0.15001153355925,approved / ADA Interest Earned,$0.00,2021-08-15 01:00:03
NXTcxxxxxxxxx,Interest,ADA,0.06511621,$0.13219163652648,approved / ADA Interest Earned,$0.00,2021-08-14 01:00:03
NXTaxxxxxxxxx,Exchange,USDC/ADA,-1166.227927 / +571.00203394,$1165.3789130691,approved / Exchange USD Coin to Cardano,$0.00,2021-08-13 15:39:29
NXTDxxxxxxxxx,Deposit,USDC,1166.227927,$1187.6814460844,approved / 0xc000000000000000000000000000000000000000000000000000000000000000,$0.00,2021-08-12 20:48:22</code></pre>
<p>Into this:</p>
<pre class="language-plain"><code class="language-plain">2021-08-16 01:00:04 Interest -- approved / ADA Interest Earned
	Assets:Current:Nexo:ADA    0.06513149 ADA
	Income:Interest:Nexo:ADA    -$0.14267261305268


2021-08-15 01:00:03 Interest -- approved / ADA Interest Earned
	Assets:Current:Nexo:ADA    0.06887155 ADA
	Income:Interest:Nexo:ADA    -$0.15001153355925


2021-08-14 01:00:03 Interest -- approved / ADA Interest Earned
	Assets:Current:Nexo:ADA    0.06511621 ADA
	Income:Interest:Nexo:ADA    -$0.13219163652648


2021-08-13 15:39:29 Exchange -- approved / Exchange USD Coin to Cardano
	Assets:Current:Nexo:ADA     571.00203394 ADA
	Assets:Current:Nexo:USDC    -1166.227927  USDC


2021-08-12 20:48:22 Deposit -- approved / 0xc000000000000000000000000000000000000000000000000000000000000000
	Assets:Current:Nexo:USDC    1166.227927 USDC
	Equity:Transfers    -$1187.6814460844</code></pre>
<h2>Quick Conversion Using Awk</h2>
<p>The actual process of writing the script took a few minutes, the result being this bash script:</p>
<pre class="language-bash"><code class="language-bash"><span class="token shebang important">#!/bin/bash </span>
<span class="token function">awk</span> <span class="token parameter variable">-F</span> <span class="token string">","</span> <span class="token string">'{
if ($2 ~ /Interest/)
   {print $8 " " $2 " -- " $6 "\\n\\tAssets:Current:Nexo:" $3 "    " $4 " " $3 "\\n\\tIncome:Interest:Nexo:" $3 "    -" $5; print "\\n";}
if ($2 ~ /Deposit/)
   {print $8 " " $2 " -- " $6 "\\n\\tAssets:Current:Nexo:" $3 "    " $4 " " $3 "\\n\\tEquity:Transfers    -" $5; print "\\n";}
if ($2 ~ /Exchange/)
   {
   split($3,a,"/");  # split into the two currencies
   split($4,b,"/");  # split into the two amounts
   gsub(/\\+/,"",b\[2\])
   print $8 " " $2 " -- " $6 "\\n\\tAssets:Current:Nexo:" a\[2\] "    " b\[2\] " " a\[2\] "\\n\\tAssets:Current:Nexo:" a\[1\] "    " b\[1\] " " a\[1\];     print "\\n";}
}'</span> nexo<span class="token punctuation">\</span>_transactions.csv <span class="token operator">></span> nexo.ada.ledger</code></pre>
<p>The few things I’ll call out since it wasn’t completely obvious when I started:</p>
<ul>
<li>By default awk’s field separator is a space. Since this is a CSV file, we want to use a comma instead. This is the <code>-F &quot;,&quot;</code> part of the above script.</li>
<li>Because we want slightly different behavior depending on the type of transaction, we need to use <code>if</code> statements that test if a the second (ie the “type”) field is the one we want.</li>
<li>The various “exchange” transactions had additional info packed into the <code>Currency</code> and <code>Amount</code> fields. Basically there were two values that we needed to extract from the one field. Because of this, we use the <code>split(&lt;field&gt;, &lt;local-variable&gt;, &lt;delimiter&gt;)</code> function.</li>
<li>Lastly, I didn’t know until working through this exercise, but Ledger-cli doesn’t like to have a plus preceeding positive amounts. Because Nexo is explicit in adding the plus, I added the <code>gsub(&lt;search-regex&gt;, &lt;replacement&gt;, &lt;string&gt;)</code> function to replace the plus in the “split” amount field.</li>
</ul>
]]></description>
	</item>
	
	<item>
		<title>Intro to Using Awk</title>
		<link>https://passionsplay.com/blog/intro-to-using-awk/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/intro-to-using-awk/</guid>
		<pubDate>Sat, 14 Aug 2021 07:00:00 GMT</pubDate>
		<description><![CDATA[<p><a href="https://en.wikipedia.org/wiki/AWK">Awk</a> is an ancient and powerful tool for working with raw text. Being a command-line utility, it can be overkill in certain situations. At other times, it’s the only thing that can get the precision you need for manipulating text.</p>
<p>Having been created in the 1970s at Bell Labs, it’s been around for a long time. This is useful because it means that awk is probably available in most terminal sessions you start. It also means that the resources available for learning awk don’t lose their relevance. While you can certainly use a search engine to find tutorials, the best resources I learned from were:</p>
<ul>
<li><a href="https://www.grymoire.com/Unix/Awk.html">Awk – A Tutorial and Introduction – by Bruce Barnett</a>
<ul>
<li>Lots of detailed, free information. A somewhat dated site theme, but you’re learning awk, right?</li>
</ul>
</li>
<li><a href="https://amzn.to/3CPUkAi">Sed &amp; Awk – by Dale Dougherty, Tim O’Reilly</a>
<ul>
<li>Great writing and examples. Especially useful if you are wanting to learn about <a href="https://en.wikipedia.org/wiki/Sed">Sed</a> as well.</li>
</ul>
</li>
</ul>
<p>Ok, so that covers <em>how</em> to learn awk, but maybe you’re not convinced about <em>why</em> you should learn it. The rest of this post will take a deeper look into how I include this tool into my workflow.</p>
<h2>Why use awk?</h2>
<p>If all you are doing is trying to search for the occurrence of a string within a block of text, then awk is probably overkill. In those situations, I turn to <code>[grep](https://en.wikipedia.org/wiki/Grep)</code>, with commands that look something like this:</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">cat</span> access.log <span class="token operator">|</span> <span class="token function">grep</span> <span class="token string">" 500 "</span>
<span class="token function">grep</span> <span class="token string">" 500 "</span> access.log</code></pre>
<p>The above commands search a file called <code>access.log</code> for any string that has a space, the number 500, and another space. This is a quick way to check for server 500 errors, like those that are reported in an nginx access log that’s <a href="https://getflywheel.com/wordpress-support/can-i-view-the-access-logs-and-error-logs/">exported from Flywheel’s server</a>.</p>
<p>Grep can perform more powerful searches using regex, however, I turn to awk when I find myself starting to ask more advanced questions that require searching for one thing but printing another. Here are some examples of those kinds of questions:</p>
<ul>
<li>What is the distribution of HTTP response codes for the server? What about for a specific site?</li>
<li>What IPs, and how many times have those IPs attempted to access the <code>wp-login.php</code> file for a site?</li>
<li>What urls are experiencing 50x server errors?</li>
</ul>
<p>We’ll get into the actual examples for the above questions later, but first let’s get a quick overview of how to use awk so that if you need to tweak things, you know where to start looking.</p>
<h2>Basic Usage</h2>
<p>Awk works by searching for strings and then doing something when it finds those strings. You can break down an awk command into this simple structure:</p>
<pre class="language-plain"><code class="language-plain">pattern { action }</code></pre>
<p>The “pattern” part is a description of what lines awk should act upon. The “action” part of an awk statement is wrapped in curly braces and describes what should be done when a match is found.</p>
<p>Since awk is a command-line tool, we wrap the “awk program” that we want to run with single quotes. Here’s an awk command that does the same thing that the above <code>cat</code> and <code>grep</code> commands do:</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">awk</span> <span class="token string">'$0 ~ /500/ { print $0 }'</span> access.log</code></pre>
<p>To break this down into plain English:</p>
<ul>
<li><code>awk</code> will execute the program within the two single quotes <code>'...'</code> on the <code>access.log</code> file.</li>
<li>The program will first search each line (the <code>$0</code> part), and only do something if there is a 500 within it. <code>$0 ~ /500/</code>.
<ul>
<li>Note: This looks more complex than grep because it is a regex statement.</li>
</ul>
</li>
<li>In this case, the awk program prints the whole line <code>{ print $0 }</code>.
<ul>
<li>Note: Printing is the default behavior of awk, so the above could have been simplified to:</li>
</ul>
</li>
</ul>
<pre class="language-bash"><code class="language-bash"><span class="token function">awk</span> <span class="token string">'$0 ~ /500/'</span> access.log</code></pre>
<h2>A little more complex usage</h2>
<p>The above doesn’t give us much benefit beyond what we can do with <code>grep</code>, but we can use awk to ask more complex things.</p>
<p>When awk runs, it breaks each line into fields that we can operate on. You can think of these fields like the cells in a spreadsheet. We can reference these fields by using a dollar sign and the number of the field. So <code>$0</code> is the whole line, while <code>$1</code> is the first field, <code>$2</code> is the second field, and so on.</p>
<p>If we take a look at an example line from our <code>access.log</code> file which is basically the <a href="http://nginx.org/en/docs/http/ngx_http_log_module.html">the default, nginx “combined” format</a>, awk understand the line like so:</p>
<pre class="language-plain"><code class="language-plain">69.162.124.230 - - \[22/Jan/2018:06:25:05 +0000\] "HEAD / HTTP/1.1" 500 0 "http://example.com/" "Mozilla/5.0+(compatible; UptimeRobot/2.0; http://www.example.com/)"

Awk sees the line like this:
\* $01 == 69.162.124.230
\* $02 == -
\* $03 == -
\* $04 == \[22/Jan/2018:06:25:05
\* $05 == +0000\]
\* $06 == "HEAD
\* $07 == /
\* $08 == HTTP/1.1"
\* $09 == 500
\* $10 == 0
\* $11 == "http://example.com/"
\* $12 == "Mozilla/5.0+(compatible;
\* $13 == UptimeRobot/2.0;
\* $14 == http://www.example.com/)"
With some special fields:
\* $0  == The whole line
\* $NF == The last field</code></pre>
<p>Knowing that awk breaks things down this way allows us to start searching exactly through one field and doing something with another field.</p>
<p>For example, we can find all lines that have a <code>500</code> HTTP response code (the ninth field), but only print the IP address for that request:</p>
<pre class="language-plain"><code class="language-plain">$ awk '$9 ~ /500/{ print $1 }' access.log
69.162.124.230
... lots more IPs ...</code></pre>
<h2>Getting more info with additional tools</h2>
<p>Since this is the command line, we can use the output of our awk command as the input for other commands. I often will pipe the output of an awk command to both <code>sort</code> and <code>uniq -c</code> to count of the number of times something is printed from awk.</p>
<p>The <code>uniq</code> unix command selects only the unique lines, but requires those lines to be next to each other. Because of this, you will often see me pipe <code>awk</code> to <code>sort</code> and then to <code>uniq</code>. Finally, since there will be a count of the number of things that have happened, I will pipe the output one more time to <code>sort -h</code>, which will give us an ascending list. That general pattern looks something like this:</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">awk</span> <span class="token string">'pattern{action}'</span> file.log <span class="token operator">|</span> <span class="token function">sort</span> <span class="token operator">|</span> <span class="token function">uniq</span> <span class="token parameter variable">-c</span> <span class="token operator">|</span> <span class="token function">sort</span> <span class="token parameter variable">-h</span></code></pre>
<h2>Awk Cheatsheet for working with access.log</h2>
<p>Great, you know enough about awk to be dangerous! 🙂 Maybe you need a little inspiration. Here are a few examples of questions I ask myself, and the terminal command that I use to get a better understanding of an nginx access log.</p>
<h3>What is the distribution of HTTP response codes for the server?</h3>
<p>Because the HTTP response code is the ninth field in the default nginx combined log, we just need to print that field, sort and count the results! In this case, you can see that by far the highest response is a <code>200</code> — great! There are a handful of <code>50x</code> codes and some <code>40x</code> codes, but overall, this log doesn’t look too bad.</p>
<pre class="language-plain"><code class="language-plain">$ awk '{print $9}' access.log | sort | uniq -c | sort -h
      1 499
      4 504
     13 400
     23 503
     30 500
     36 401
     52 403
     88 405
    247 304
   1057 302
   2774 301
   4522 404
  53296 200</code></pre>
<h3>What IPs, and how many times have those IPs attempted to access the <code>wp-login.php</code> file?</h3>
<p>The seventh field is often then request field and the first field is the IP address. Especially if there’s some sort of bot attack in progress, there might be a large number of different IPs. We can pipe all of our results to the <code>tail</code> command so that only the top ten results are printed.</p>
<pre class="language-plain"><code class="language-plain">$ awk '$7 ~ /wp-login.php/ {print $1}' access.log | sort | uniq -c | sort -h | tail
     15 34.209.136.220
     16 85.140.40.253
     18 46.161.9.3
     23 118.190.78.53
     23 185.85.191.196
     24 177.72.199.27
     28 194.6.231.240
     39 85.97.250.114
    118 2002:c1c9:e0d2::c1c9:e0d2
    360 193.201.224.210</code></pre>
<h3>What urls are experiencing 50x server errors?</h3>
<p>In this case, we’re using a regex to give us all the lines that have a <code>50x</code> response and print the request url. This can be useful in seeing if there are wide-spread site issues, or only problems on one specific page.</p>
<pre class="language-plain"><code class="language-plain">benjamin@fwxxxxxx:~$ awk '($6 ~ /POST/ &amp;&amp; $9 ~ /50\[0-9\]/){print $7}' /flywheel/logs/access.log | sort | uniq -c | sort -h
      1 /jquery-file-upload/server/php/
      1 /text.php
      2 /
      2 /product-16/
      2 /wp-content/plugins/apikey/ini.php
      2 /wp-post.php
     13 /product-12/</code></pre>
]]></description>
	</item>
	
	<item>
		<title>Javascript Dev Environment under Ubuntu in 2021</title>
		<link>https://passionsplay.com/blog/javascript-dev-environment-under-ubuntu-in-2021/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/javascript-dev-environment-under-ubuntu-in-2021/</guid>
		<pubDate>Wed, 04 Aug 2021 11:26:55 GMT</pubDate>
		<description><![CDATA[<p>As I mentioned in a <a href="/blog/python-dev-environment-under-ubuntu-in-2021/">previous post about setting up a Python dev environment</a>, I have a new <a href="https://ubuntu.com/blog/ubuntu-21-04-is-here">Ubuntu 21.04</a> installation and I’m revisiting my day-to-day tools with an eye towards improving how I install and configure them.</p>
<p>Learning more about Node.js as well as having a deeper understanding of modern Javascript development is on my list of things to do. For now, all I need is a minimal environment so that I can make use of <a href="https://microsoft.github.io/language-server-protocol/">Microsoft’s Language Server Protocol (LSP)</a>. If you haven’t taken a look at LSP, you should!</p>
<p>In my case, as an Emacs user, I’ve been able to <a href="https://github.com/bgturner/dot-files/search?q=lsp&amp;type=commits">simplify and streamline my configuration</a> for numerous programming languages, all by exploring the resources on the Emacs <a href="https://emacs-lsp.github.io/lsp-mode/">lsp-mode</a> website.</p>
<p>I won’t go into my editor configuration now, instead, I’ll focus on what needs to be done to get the bare minimum Javascript environment configured for Ubuntu.</p>
<h2>Minimum Javascript Environment</h2>
<p>So what are these core tools to be installed?</p>
<ul>
<li><a href="https://github.com/nvm-sh/nvm">nvm</a> – Installs and manages Node.js versions.</li>
<li><a href="https://nodejs.org/en/">node.js</a> – A runtime so we can execute Javascript code.</li>
<li><a href="https://www.npmjs.com/">npm</a> – The original Javascript package manager.</li>
<li><a href="https://yarnpkg.com/">yarn</a> – Another Javascript package manager because… the community can’t decide on one?</li>
</ul>
<p>As you can see in this screencast, getting things installed is pretty quick and is done in under a minute. Below you can find more details and specific commands related to each tool!</p>
<h3>nvm</h3>
<p>Nvm helps to install and manage different versions of Node. The actual installation of nvm is pretty easy and is outlined within the <a href="https://github.com/nvm-sh/nvm#installing-and-updating">project’s readme on Github</a>. Basically, the steps are to clone the repo and ensure that it’s correctly loaded in your shell.</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">curl</span> -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh <span class="token operator">|</span> <span class="token function">bash</span></code></pre>
<p>Once that repo is in place, we can add a block to our <code>.zshrc</code> file:</p>
<pre class="language-bash"><code class="language-bash"><span class="token punctuation">\</span># Configure <span class="token function">node</span> dev environment
<span class="token keyword">if</span> <span class="token punctuation">\</span><span class="token punctuation">[</span> <span class="token parameter variable">-d</span> <span class="token string">"<span class="token environment constant">$HOME</span>/.nvm"</span> <span class="token punctuation">\</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">then</span>
    <span class="token builtin class-name">export</span> NVM<span class="token punctuation">\</span>_DIR<span class="token operator">=</span><span class="token string">"<span class="token environment constant">$HOME</span>/.nvm"</span>
    <span class="token punctuation">\</span><span class="token punctuation">[</span> <span class="token parameter variable">-s</span> <span class="token string">"<span class="token variable">$NVM</span>\_DIR/nvm.sh"</span> <span class="token punctuation">\</span><span class="token punctuation">]</span> <span class="token operator">&amp;&amp;</span> <span class="token punctuation">\</span><span class="token punctuation">\</span>. <span class="token string">"<span class="token variable">$NVM</span>\_DIR/nvm.sh"</span>  <span class="token comment"># This loads nvm</span>
<span class="token keyword">fi</span></code></pre>
<h3>node.js</h3>
<p>With <code>nvm</code> installed, we’re one command away from having <code>node</code>:</p>
<pre class="language-bash"><code class="language-bash">nvm <span class="token function">install</span> <span class="token function">node</span></code></pre>
<p>This will download the latest version as well as set that version as the default. There are some crufty projects I still haven’t upgraded, so I’ll list other node versions with <code>nvm ls</code> and install an older LTS release with <code>nvm install lts/fermium</code>:</p>
<pre class="language-plain"><code class="language-plain">★  ~ % nvm ls      
->      v16.6.1
default -> node (-> v16.6.1)
iojs -> N/A (default)
unstable -> N/A (default)
node -> stable (-> v16.6.1) (default)
stable -> 16.6 (-> v16.6.1) (default)
lts/\* -> lts/fermium (-> N/A)
lts/argon -> v4.9.1 (-> N/A)
lts/boron -> v6.17.1 (-> N/A)
lts/carbon -> v8.17.0 (-> N/A)
lts/dubnium -> v10.24.1 (-> N/A)
lts/erbium -> v12.22.4 (-> N/A)
lts/fermium -> v14.17.4 (-> N/A)
★  ~ % nvm install lts/fermium
Downloading and installing node v14.17.4...
Downloading https://nodejs.org/dist/v14.17.4/node-v14.17.4-linux-x64.tar.xz...
######################################################################### 100.0%
Computing checksum with sha256sum
Checksums matched!
Now using node v14.17.4 (npm v6.14.14)</code></pre>
<h3>npm</h3>
<p>Having a section for installing <code>npm</code> is a little misleading since installing <code>node</code> also installs <code>npm</code>. One thing I want to call out which wasn’t obvious to me at first – each version of <code>node</code> comes with its own version of <code>npm</code>:</p>
<pre class="language-plain"><code class="language-plain">★  ~ % nvm install lts/fermium
Downloading and installing node v14.17.4...
Downloading https://nodejs.org/dist/v14.17.4/node-v14.17.4-linux-x64.tar.xz...
######################################################################### 100.0%
Computing checksum with sha256sum
Checksums matched!
Now using node v14.17.4 (npm v6.14.14)
★  ~ % npm --version
6.14.14
★  ~ % nvm use stable
Now using node v16.6.1 (npm v7.20.3)
★  ~ % npm --version
7.20.3</code></pre>
<h3>yarn</h3>
<p>Honestly, I’m a little fuzzy when it comes to knowing when to use <code>npm</code> and when to use <code>yarn</code>. My rule of thumb is to look at the repo and see if it has a <code>yarn.lock</code> file.</p>
<p>Anyway, just so we’re prepared for any project that comes our way, we can use <code>npm</code> to install <code>yarn</code> globally for our user:</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">npm</span> <span class="token function">install</span> <span class="token parameter variable">-g</span> <span class="token function">yarn</span></code></pre>
]]></description>
	</item>
	
	<item>
		<title>Python Dev Environment under Ubuntu in 2021</title>
		<link>https://passionsplay.com/blog/python-dev-environment-under-ubuntu-in-2021/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/python-dev-environment-under-ubuntu-in-2021/</guid>
		<pubDate>Mon, 26 Jul 2021 15:00:00 GMT</pubDate>
		<description><![CDATA[<p>Let’s work through how to get a stable and flexible Python development environment configured for Ubuntu 21.04.</p>
<h2>Background</h2>
<p>Throughout my time in tech, I’ve mostly been a generalist and have employed various languages to just get things done. I’ve never focused solely on learning Python, but I do know enough to wrangle data and create some effective visualizations.</p>
<p>Occasionally I like to completely wipe my personal Linux machine and install things from scratch. There’s something wonderful about nuking the cruft and getting back to the bare essentials for getting things done. So here I am; a mostly blank installation of Ubuntu 21.04, ready to get a Python environment installed and set up. Let’s document this process so that future me won’t have to think about how to get up and running with Python dev!</p>
<p>Since I’m not solely focused on Python projects, it makes sense that I should probably search for how “the pros” configure their environment to be productive. There are a lot of guides out there that say they have the answer, and in sometimes that true. Sometimes you go down a fragile rabbit hole. Many of the tutorials and blog posts covered older versions of Python, or in some way didn’t feel right for how I like to configure my system.</p>
<p>In the end, the best post I found <a href="https://jacobian.org/2019/nov/11/python-environment-2020/">was from Jacob Kaplan-Moss</a> (co-creator of Django). It’s specific, direct, and I get the feeling that like me, a crufty machine won’t be tolerated. Even though the post is from 2019, the general ideas and package recommendations are still good:</p>
<ol>
<li><a href="https://github.com/pyenv/pyenv">pyenv</a>
<ul>
<li>Helps to manage multiple versions of Python. Think of <a href="https://github.com/nvm-sh/nvm">nvm</a>, but for Python.</li>
</ul>
</li>
<li><a href="https://pypi.org/project/pipx/">pipx</a>
<ul>
<li>Manage your user’s “global” tooling. Something along the lines of installing a Javascript tool for your “global” user. (think <code>npm install &lt;package&gt; --global</code> )</li>
</ul>
</li>
<li><a href="https://poetry.eustace.io/">poetry</a>
<ul>
<li>Dependency and virtual environment management <em>for specific projects</em>. Think <a href="https://www.npmjs.com/">npm</a> or <a href="https://getcomposer.org/">composer</a>, where you declare a project’s dependencies and let the tool build the environment. Additionally, this configuration is “frozen” with a lock file.</li>
</ul>
</li>
</ol>
<p>So, what are the specifics for getting these things working on a clean install of Ubuntu?</p>
<h2>Installing Ubuntu Dependencies</h2>
<p>In Jacob’s post above, the installation is for a MacOS system. It took a bit of trial and error, but I was able to zero in on the specific mix of packages that need to be installed. There might be other packages that creep in as I start installing and using various Python tools, but for now, this should get me what I need:</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">sudo</span> <span class="token function">apt-get</span> update<span class="token punctuation">;</span>
<span class="token function">sudo</span> <span class="token function">apt-get</span> <span class="token function">install</span> --no-install-recommends <span class="token punctuation">\</span><span class="token punctuation">\</span>
     <span class="token function">make</span> build-essential libssl-dev zlib1g-dev <span class="token punctuation">\</span><span class="token punctuation">\</span>
     libbz2-dev libreadline-dev libsqlite3-dev <span class="token punctuation">\</span><span class="token punctuation">\</span>
     <span class="token function">wget</span> <span class="token function">curl</span> llvm libncurses5-dev xz-utils <span class="token punctuation">\</span><span class="token punctuation">\</span>
     tk-dev libxml2-dev libxmlsec1-dev <span class="token punctuation">\</span><span class="token punctuation">\</span>
     libffi-dev liblzma-dev</code></pre>
<h2><a href="https://github.com/pyenv/pyenv">pyenv</a></h2>
<p><a href="https://github.com/pyenv/pyenv">Pyenv</a> is pretty neat and does it’s fancy management of Python versions by shimming your <code>$PATH</code>.</p>
<p>Installing is easy and is basically:</p>
<ol>
<li>Clone the <a href="https://github.com/pyenv/pyenv">repo</a> to your home directory.</li>
<li>Update your shell config to reference that repo.</li>
</ol>
<p>The above process is outlined in more detail within the <a href="https://github.com/pyenv/pyenv#basic-github-checkout">Basic Github checkout</a> section of the readme. Here’s what that looks like:</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">git</span> clone https://github.com/pyenv/pyenv.git ~/.pyenv</code></pre>
<p>And this <a href="https://github.com/bgturner/dot-files/commit/5a4a8b76fd8f7bc5176b6f6228f52f63038acd4a">shell configuration</a> added to my <code>~/.profile</code> file:</p>
<pre class="language-bash"><code class="language-bash"><span class="token keyword">if</span> <span class="token punctuation">\</span><span class="token punctuation">[</span> <span class="token parameter variable">-d</span> <span class="token string">"<span class="token environment constant">$HOME</span>/.pyenv"</span> <span class="token punctuation">\</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">then</span>
    <span class="token builtin class-name">export</span> PYENV<span class="token punctuation">\</span>_ROOT<span class="token operator">=</span><span class="token string">"<span class="token environment constant">$HOME</span>/.pyenv"</span>
    <span class="token builtin class-name">export</span> <span class="token assign-left variable"><span class="token environment constant">PATH</span></span><span class="token operator">=</span><span class="token string">"<span class="token variable">$PYENV</span>\_ROOT/bin:<span class="token environment constant">$PATH</span>"</span>
    <span class="token builtin class-name">eval</span> <span class="token string">"<span class="token variable"><span class="token variable">$(</span>pyenv init <span class="token parameter variable">--path</span><span class="token variable">)</span></span>"</span>
<span class="token keyword">fi</span></code></pre>
<p>Once Pyenv is configured, we can list and install individual versions of Python from the terminal.</p>
<pre class="language-plain"><code class="language-plain">★  src/python-sandbox % pyenv install --list
pyenv install --list
Available versions:
2.1.3
2.2.3
...
★  src/python-sandbox % pyenv install 3.9.1
pyenv install 3.9.1
★  src/python-sandbox % pyenv global 3.9.1
pyenv global 3.9.1
★  src/python-sandbox % which python
which python
/home/benjamin/.pyenv/shims/python
★  src/python-sandbox % python --version
python --version
Python 3.9.1</code></pre>
<h2><a href="https://pypi.org/project/pipx/">pipx</a></h2>
<p>As someone who doesn’t live and breath Python, I mostly turn to it for quick and simple scripts as well as a few essential cli apps. Because most systems make use of some sort of system-installed version of Python, I like the idea of having my user-specific tooling constrained to my own user.</p>
<p><a href="https://pypi.org/project/pipx/">Pipx</a> makes having a separate, user-focused python environment easy, and the installation is quick using <code>pip</code> from our Pyenv installation:</p>
<pre class="language-plain"><code class="language-plain">★  src/python-sandbox % python -m pip install --user pipx
python -m pip install --user pipx
Collecting pipx
  Downloading pipx-0.15.6.0-py3-none-any.whl (43 kB)
     |████████████████████████████████| 43 kB 1.6 MB/s 
Collecting packaging>=20.0
  Downloading packaging-20.8-py2.py3-none-any.whl (39 kB)
  ...
★  src/python-sandbox % export PATH="$HOME/.local/bin:${PATH}"
export PATH="$HOME/.local/bin:${PATH}"
★  src/python-sandbox % python -m pipx ensurepath
python -m pipx ensurepath
/home/benjamin/.local/bin is already in PATH.
/home/benjamin/.local/bin is already in PATH.</code></pre>
<p>We now have an isolated environment for our user’s Python cli apps, but what now?</p>
<p>One amazing tool that I keep returning to is <a href="https://www.visidata.org/">Visidata</a>, which is like a spreadsheet tool on CLI steroids. It allows you to explore, interact, and clean data from the command line.</p>
<p>In addition to working with <code>csv</code> files, you can extend Visidata by including additional libraries so that it can handle more file formats. For example, installing <a href="https://pandas.pydata.org/">Pandas</a> will <a href="https://www.visidata.org/docs/loading/">allow more loaders within visidata</a> – basically, any <a href="https://pandas.pydata.org/pandas-docs/stable/user_guide/io.html">file format that Pandas knows</a> how to open can be opened in Visidata.</p>
<p>So we want Visidata along with the Pandas boost, but how do we do that with Pipx?</p>
<p>By injecting dependencies!</p>
<pre class="language-plain"><code class="language-plain">★  src/python-sandbox % pipx install visidata
pipx install visidata
  installed package visidata 2.1.1, Python 3.9.1
  These apps are now globally available
    - vd
    - visidata
done! ✨ 🌟 ✨
★  src/python-sandbox % pipx inject visidata pandas
pipx inject visidata pandas
  injected package pandas into venv visidata
done! ✨ 🌟 ✨</code></pre>
<h2><a href="https://poetry.eustace.io/">Poetry</a></h2>
<p>The previous tools have let us sandbox our day-to-day Python tools away from a system configuration of Python, but the missing piece is managing dependencies for an actual Python project.</p>
<p><a href="https://poetry.eustace.io/">Poetry</a> handles the actual dependency and virtual environment management <em>for specific projects</em>. Think of something like <code>yarn</code> or <code>composer</code>, where you can declare your dependencies as well as freeze the configuration by using a “lock” file.</p>
<p>Using Pipx again, installation is easy, and we can quickly initialize a new project and add our dependencies:</p>
<pre class="language-plain"><code class="language-plain">★  src/python-sandbox % pipx install poetry
pipx install poetry
  installed package poetry 1.1.4, Python 3.9.1
  These apps are now globally available
    - poetry
done! ✨ 🌟 ✨
★  src/python-sandbox % poetry init
poetry init

This command will guide you through creating your pyproject.toml config.

Package name \[python-sandbox\]:  

...

★  src/python-sandbox % poetry add pandas numpy bokeh networkx
poetry add pandas numpy bokeh networkx
Creating virtualenv python-sandbox-Z3rSXY9z-py3.9 in /home/benjamin/.cache/pypoetry/virtualenvs
Using version ^1.2.0 for pandas
Using version ^1.19.5 for numpy
Using version ^2.2.3 for bokeh
Using version ^2.5 for networkx

Updating dependencies
...</code></pre>
<p>Nice! Pretty easy!</p>
<h2>Wrap-up</h2>
<p>I’ve been using this setup for a few weeks now and it feels mostly stable and strikes a good balance of:</p>
<ol>
<li>Being flexible for when I need to switch versions of software.</li>
<li>Isolated so that I’m not accidentally breaking other projects.</li>
<li>Good-enough defaults for when I just need a quick snippet of Python to be run.</li>
</ol>
<p>Hope this helps get you up and running quickly and that you can get on with solving interesting problems!</p>
]]></description>
	</item>
	
	<item>
		<title>Scripting Vim from the Command Line</title>
		<link>https://passionsplay.com/blog/scripting-vim-from-the-command-line/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/scripting-vim-from-the-command-line/</guid>
		<pubDate>Tue, 29 Jun 2021 12:28:55 GMT</pubDate>
		<description><![CDATA[<p>One of the more powerful features of Vim is its ability to perform edits to text in a scripted way.</p>
<p>If you view the <code>man</code> page for Vim, there’s an entry for the <code>-c</code> argument that looks like this:</p>
<pre class="language-plain"><code class="language-plain">-c {command}
   {command} will be executed after the first file has been read.  
   {command} is interpreted as an Ex command.  If the  {command}  
   contains spaces it must be enclosed in double quotes (this 
   depends on the shell that is used).  Example: Vim "+set si" main.c
   Note: You can use up to 10 "+" or "-c" commands.</code></pre>
<p>That part about <code>Ex command</code> is key — these are the commands that are executed when we type the colon in Vim.</p>
<p>But how do we go from a man page entry to something useful? How about a case study.</p>
<h2>Manually Removing Wordfence Tables from a SQL Dump</h2>
<p>Let’s say that the <a href="https://www.wordfence.com">Wordfence</a> plugin was exporting data in a binary form which was breaking things when trying to import the database into a new environment. They know about this, and have this helpful guide:</p>
<ul>
<li><a href="https://www.wordfence.com/help/advanced/remove-or-reset/">https://www.wordfence.com/help/advanced/remove-or-reset/</a></li>
</ul>
<p>That’s a fine solution if you have the site up and running, but suppose you have a bunch of <code>sql</code> files that need to be cleaned up so that they can be imported cleanly.</p>
<p>After cleaning up those files manually a few times with Vim, you’ll find that you will be doing a number of common motions like searching, deleting, and saving those files. At a certain point, you might even realize that you can us the <code>global</code> ex command to “do stuff” on each line that matches a regex.</p>
<p>For me, when working through the cleanup process of Wordfence from SQL files, that process generally looked like this:</p>
<ol>
<li>Search the SQL file using the “very magic” setting. This might take some fiddling so that the regex gets the lines that you want. The specific keystrokes might look like:
<ul>
<li><code>**/\vsearch_regex**</code></li>
</ul>
</li>
<li>Using the “global print” ex command with an empty search string (to re-use the last search), do a quick sanity check to make sure the correct lines are being hit. Those keystrokes might look like:
<ul>
<li>:<code>**g//p**</code></li>
</ul>
</li>
<li>Use the “global normal” ex command to execute the normal (as in “normal mode”) keystrokes for every matched line. What this means is that we can do something like “delete the next four paragraphs for each match.” This is helpful in the SQL files that I was working with because each “paragraph” was a block of SQL statements — both definitions as well as data. The keystrokes for that might look like:
<ul>
<li><code>:**g//normal 4dap**</code></li>
</ul>
</li>
</ol>
<p>Those steps are all very abstract, so here’s a screencast working through that process of cleaning up those Wordfence tables:</p>
<p><video height="674" style="aspect-ratio: 1006 / 674;" width="1006" controls="" src="/wp-content/uploads/2021/06/wordfence-delete-tables.mp4"></video></p>
<h2>Automating Vim From a Shell Script</h2>
<p>The above steps are great at doing advanced editing during a session, but returning to the goal of actually automating that editing, you’ll want to make use of that <code>-c</code> flag that we mentioned earlier.</p>
<p>As a concrete example, that process of removing Wordfence tables from a SQL file could be abstracted even more. In this case, I created a shell script that:</p>
<ol>
<li>Emptied out a register (in this case the <code>@t</code> register)</li>
<li>Do some “normal” keystrokes on lines matching <code>\v_wf.{-}</code></li>
<li>Save the edited file</li>
<li>Create a new buffer and paste the contents of the <code>@t</code> buffer into this new buffer</li>
</ol>
<p>Here’s the actual script that I ended up using to process those SQL files:</p>
<pre class="language-bash"><code class="language-bash"><span class="token shebang important">#!/bin/bash</span>
<span class="token assign-left variable">file</span><span class="token operator">=</span><span class="token string">"<span class="token variable">$1</span>"</span>
<span class="token function">vim</span> <span class="token parameter variable">-c</span> <span class="token string">'let @t=""'</span> <span class="token punctuation">\</span>
   <span class="token parameter variable">-c</span> <span class="token string">':g/\v_wf.{-}`/normal "Tyapdap'</span> <span class="token punctuation">\</span>
   <span class="token parameter variable">-c</span> <span class="token string">":sav <span class="token variable">$file</span>"</span> <span class="token punctuation">\</span>
   <span class="token parameter variable">-c</span> <span class="token string">':new wf-tables.sql.bak | :normal "tp'</span> <span class="token punctuation">\</span>
   <span class="token parameter variable">-c</span> <span class="token string">':wa | :qa'</span> <span class="token punctuation">\</span>
   <span class="token string">"<span class="token variable">$file</span>.original"</span></code></pre>
]]></description>
	</item>
	
	<item>
		<title>QA FSE Polished Portfolios</title>
		<link>https://passionsplay.com/blog/qa-fse-polished-portfolios/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/qa-fse-polished-portfolios/</guid>
		<pubDate>Tue, 08 Jun 2021 23:33:45 GMT</pubDate>
		<description><![CDATA[<p>Started doing some QA of the <a href="https://make.wordpress.org/test/2021/05/26/fse-program-testing-call-7-polished-portfolios/">Polished Portfolios testing call</a> to get a better feel for what working with page templates will feel like under Full-site editing.</p>
<h2>Setting up the environment</h2>
<p>According to the above call, the main things that need to be done to get testing are:</p>
<ul>
<li>Install WordPress</li>
<li>Install the <a href="https://wordpress.org/themes/tt1-blocks/">tt1-blocks</a> theme</li>
<li>Install the latest version of Gutenberg (<a href="https://github.com/WordPress/gutenberg/releases/tag/v10.7.1">v10.7.1 as of now</a>)</li>
</ul>
<p>I was able to do this quickly within Local by creating a site, opening the Site Shell and running:</p>
<pre class="language-bash"><code class="language-bash">wp plugin <span class="token function">install</span> gutenberg <span class="token parameter variable">--activate</span>
wp theme <span class="token function">install</span> tt1-blocks <span class="token parameter variable">--activate</span></code></pre>
<p>In addition to the code, the testing call recommends importing some demo content from <a href="https://cloudup.com/cI5WNs9EgEN">here</a>.</p>
<p>Since the above link is a WordPress xml export, we can use <code>curl</code> to follow the redirect and download the xml for importing into WordPress. For this specific testing call, that command looks like:</p>
<pre class="language-bash"><code class="language-bash">wp plugin <span class="token function">install</span> wordpress-importer <span class="token parameter variable">--activate</span>
<span class="token function">curl</span> <span class="token parameter variable">-L</span> https://cloudup.com/files/iEsfmmRZeCg/download <span class="token operator">></span> callfortesting.xml
wp <span class="token function">import</span> callfortesting.xml <span class="token parameter variable">--authors</span><span class="token operator">=</span>create</code></pre>
<h2>Testing the feature</h2>
<p>My goal was to create a landing page that has a featured section with the latest post of the <code>portfolio</code> category as well as a secondary section that has two posts and finally one last section to show everything else.</p>
<p>Here’s a recording of my session along with some notes for things I encountered:</p>
<pre class="language-plain"><code class="language-plain">https://i.getf.ly/xQu7g0mg</code></pre>
<ul>
<li>4:30 – The spacing looked off to me, but then I realized that the heading was the same color as the background so I wasn’t seeing the heading text. Investigating (in the video) and it looks like a specificity issue at the ~6:40 mark. The anchor link within the post heading doesn’t inherit the text color.</li>
<li>14:10 – I tried to adjust the pagination of posts to 10 and the block crashed. I was doing it via keyboard, but the mouse click to increase the number appears to work.</li>
<li>16:30 – Some general usability feedback of the column block: I’d love a way to make the vertical margins disappear so that full-width sections that have background colors don’t show any space between them. I was able to hack together a workaround with a custom utility class (see video), but it would be nice to have the ability to control that sort of design pattern within the column block.</li>
<li>27:00 – General UX – It appears that I didn’t save the template since it’s showing a 404, even though the title says “portfolio.” I think what confused me was the “Publish” button in the upper right corner. Coming from a WP background I think I understand that “Publish” meant to publish the page template I was editing, but on initial use, I was hesitant to push the button because my context was the original page that I had created, not the page template I was editing. Maybe there’s a way to make that button more explicit. So something along the lines of “Save Page Template.”</li>
<li>30:00 – Haha. I broke things. 😀 I tried to move a query to within a column block and hosed this template. Ahh well. It’s time to wrap this up and post my findings!</li>
</ul>
]]></description>
	</item>
	
	<item>
		<title>Getting Started with WordPress Gutenberg Development</title>
		<link>https://passionsplay.com/blog/getting-started-with-wordpress-gutenberg-development/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/getting-started-with-wordpress-gutenberg-development/</guid>
		<pubDate>Sat, 05 Jun 2021 01:34:10 GMT</pubDate>
		<description><![CDATA[<p>I’ve known about and used <a href="https://wordpress.org/gutenberg/">Gutenberg</a> for a while now since it’s been shipped with WordPress core. Until recently, I hadn’t taken a close look at where it’s going and the overall strategy that’s being executed on.</p>
<p>There’s a great overview within the readme of the <a href="https://github.com/WordPress/gutenberg">Gutenberg repo on Github</a>:</p>
<blockquote>
<p>“Gutenberg” is a codename for a whole new paradigm in WordPress site building and publishing, that aims to revolutionize the entire publishing experience as much as Gutenberg did the printed word. Right now, the project is in the second phase of a four-phase process that will touch every piece of WordPress – Editing, Customization (which includes Full Site Editing, Block Patterns, Block Directory and Block based themes), Collaboration, and Multilingual – and is focused on a new editing experience, the block editor.</p>
<p><cite><a href="https://github.com/WordPress/gutenberg">https://github.com/WordPress/gutenberg</a></cite></p>
</blockquote>
<p>To summarize, there are four main phases of Gutenberg project:</p>
<ol>
<li><strong>Editing</strong> (complete, shipped with <a href="https://wordpress.org/support/wordpress-version/version-5-0/">WP Core in v5.0</a>)</li>
<li><strong>Customization</strong> (in-progress: full-site-editing, block-patterns, block directory, block-based themes)</li>
<li><strong>Collaboration</strong></li>
<li><strong>Multilingual</strong></li>
</ol>
<h2>Customization Phase</h2>
<h3>Becoming Familiar with Full-Site Editing</h3>
<p>So we’re in the customization phase of Gutenberg, but from the above outline, there’s a lot of jargon. Where do you go to untangle all of it?</p>
<p>I decided to really focus in on Full-site Editing piece, but searching on Google is hard because of WordPress’ size and the number of entities jumping through the SEO hoops to rank higher than everyone else.</p>
<p>To get oriented quickly, the <a href="https://gutenbergtimes.com/full-site-editing/">Full-Site-Editing – the Ultimate Resource List</a> post from <a href="https://gutenbergtimes.com/author/admin/">Birgit Pauli-Haack</a> on <a href="https://gutenbergtimes.com/">Gutenberg Times</a> seemed to have everything. The post can be informal or rough in places, but that’s more to do with the chaotic nature of open-source communication and having a lot of places where information is changing.</p>
<p>One good way to jump in and start using Full-site editing, is to connect with and contribute to the <a href="https://make.wordpress.org/test/2021/02/05/fse-program-connecting-with-local-communities/">#fse-outreach-experiment</a>. One interesting thing for me, was to take a look at the <a href="https://wordpress.org/themes/tt1-blocks/">tt1-blocks repo</a> to compare the differences between a block-based theme and the default <a href="https://make.wordpress.org/core/2020/09/23/introducing-twenty-twenty-one/">TwentyTwentyOne WordPress theme</a>.</p>
<h3>FSE Outreach Experiment</h3>
<p>As of June 4th, there are a couple of open experiments that are looking for testers:</p>
<ul>
<li><a href="https://make.wordpress.org/test/2021/05/26/fse-program-testing-call-7-polished-portfolios/">FSE Program Testing Call #7: Polished Portfolios – Make WordPress Test</a></li>
<li><a href="https://make.wordpress.org/test/2021/05/21/proposal-test-badges-for-the-fse-outreach-program/">Proposal: Test Badges for the FSE Outreach Program – Make WordPress Test</a></li>
</ul>
<p>While I don’t have enough time to take these for a spin right now. I think this will be the perfect thing to focus on early next week!</p>
]]></description>
	</item>
	
	<item>
		<title>Contributing to Upstream WordPress</title>
		<link>https://passionsplay.com/blog/contributing-to-upstream-wordpress/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/contributing-to-upstream-wordpress/</guid>
		<pubDate>Sat, 23 Jan 2021 04:17:44 GMT</pubDate>
		<description><![CDATA[<p>While reviewing the <a href="https://localwp.com/community/t/starting-development-on-a-reset-site-add-on/12864?u=ben.turner">Site Reset Local Addon</a>, I found that there were issues in the <a href="https://github.com/wp-cli/db-command">WPCLI db-command</a>. I feel like I have a general idea of <a href="https://localwp.com/community/t/starting-development-on-a-reset-site-add-on/12864/37?u=ben.turner">what needs fixing</a>, but navigating the path towards upstream contribution can be overwhelming.</p>
<p>So after bringing it up with my manager, I have the green light to take some time during the week to help out upstream!</p>
<p>Since getting started and finding out where one’s aid can be most beneficial can be challenging, let’s document this process and see where it leads us.</p>
<p>Because my initial inkling is to help out with the db-command, I’ll start by becoming more familiar with the recent messages in the <a href="https://wordpress.slack.com/archives/C02RP4T41">#cli channel in the WP Slack instance</a>.</p>
<p>The first thing I see is that there are weekly office hours on Wednesdays at 15:00 UTC, (or 7:00 am in PDX) in <a href="https://wordpress.slack.com/archives/C02RP4T41">WordPress #cli slack</a>.</p>
<h2>WPCLI Contributing Handbook</h2>
<p>After reviewing the posts in slack for the last ~3months, I discovered that there is a dedicated contribution page within the WP-CLI handbook:</p>
<ul>
<li><a href="https://make.wordpress.org/cli/handbook/contributions/contributing/">https://make.wordpress.org/cli/handbook/contributions/contributing/</a></li>
<li><a href="https://github.com/wp-cli/handbook">https://github.com/wp-cli/handbook</a></li>
</ul>
<h2>WPCLI 2021 Roadmap</h2>
<p>From one of the weekly office hours, there’s a general roadmap from the maintainer, <a href="https://www.alainschlesser.com/about/">schlessera</a>.</p>
<p>My goals for 2021:</p>
<ol>
<li>Migrate Behat from v2 to v3 (needs lots of changes in the test syntax)</li>
<li>Finish new scaffolding engine and introduce the concept of canonical code generation for WordPress (think artisan make:*)</li>
<li>Drastically improve documentation</li>
</ol>
<p>Most of this was already on the agenda for 2020, but (as @jrf as eloquently put it in her post at <a href="https://24daysindecember.net/2020/12/21/a-perfect-storm/">https://24daysindecember.net/2020/12/21/a-perfect-storm/</a>), this year has been a perfect storm of compatibility issues.</p>
<p>Hopefully, 2021 will be a bit more cooperative…</p>
<p>To cultivate contributions towards those goals, I work on:</p>
<ol>
<li>Reducing friction for contributions</li>
<li>Talking about the goals and their purpose</li>
<li>(Going to contributor days to inspire and get people interested) &lt;– maybe this will be possible again in 2021?</li>
</ol>
<p>— <a href="https://wordpress.slack.com/archives/C02RP4T41/p1608736346412600">WordPress #cli Slack channel</a></p>
<p>Documentation, I can help with that!</p>
<h2>Additional notes from reading slack</h2>
<p>Reading through the posts in slack also gave some good links out to various challenges in the larger PHP community.</p>
<h3>PHP 8</h3>
<ul>
<li><a href="https://24daysindecember.net/2020/12/21/a-perfect-storm/">https://24daysindecember.net/2020/12/21/a-perfect-storm/</a>
<ul>
<li>A post outlining why the release of PHP 8 was so hard on open source maintainers.</li>
</ul>
</li>
</ul>
<h3>Migration from Travis CI to Github Actions</h3>
<p>Looks like <a href="https://wordpress.slack.com/archives/C02RP4T41/p1610552808482600">Travis CI changed a things</a> which prevented WP cli from doing automated testing and as a result delayed the release of <code>wp-cli</code> 2.5.0.</p>
<p>Most things appear to have been migrated to Github Actions and there’s just a few remaining pieces before the latest version of WPCLI can be released!</p>
]]></description>
	</item>
	
	<item>
		<title>Extracting Single Files From a Zip</title>
		<link>https://passionsplay.com/blog/extracting-single-files-from-a-zip/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/extracting-single-files-from-a-zip/</guid>
		<pubDate>Thu, 21 Feb 2019 01:58:00 GMT</pubDate>
		<description><![CDATA[<p>I’ve been working with lots of <code>zip</code> files lately – site backups mostly. Often times this is because of some error message referring to a file contained within the archive.</p>
<p>When the <code>zip</code> is quite large, having to extract gigabytes of information just to view the contents of one file is annoying. Because of this I came across this <a href="https://unix.stackexchange.com/questions/14120/extract-only-a-specific-file-from-a-zipped-archive-to-a-given-directory">StackExchange</a> link with a few options to extract just one file.</p>
<p>The are the two main ways I’ve been using, but there are others!</p>
<h2>Extract the thing, right here!</h2>
<p>The typical way which makes sense in most use cases is this:</p>
<pre><code>`unzip -d . site-backup.zip backup.sql`Code language: CSS (css)
</code></pre>
<p>This is basically saying is:</p>
<blockquote>
<p>Within the <code>site-backkup.zip</code> archive, extract the <code>backup.sql</code> file right here (<code>.</code>)</p>
</blockquote>
<p>This is great, and for the most part does what we need it to do.</p>
<h2>What About a Deeply Nested File?</h2>
<p>The previous example is good, but what if I have a file that is deeply nested, something like:</p>
<pre><code>wp-content/plugins/random-plugin/inc/foo/bar/baz.php
</code></pre>
<p>Depending on what <em>I think</em> might be going on, I might just want to take a quick peek. If I used the previous technique, all of those folders would be created and would require me to traverse down to the file before opening. Not too bad, but still a little annoying.</p>
<p>Instead, I can extract that file to <code>stdout</code> and then redirect the output to a file in the current directory. Using the above <code>baz.php</code> file as an example, this would look like:</p>
<pre><code>unzip -p site-backup.zip wp-content/plugins/random-plugin/inc/foo/bar/baz.php &gt; baz.php
</code></pre>
<p>Two main downsides:</p>
<ol>
<li>If it’s code, you’ll likely need to other files unless it’s a really trivial issue</li>
<li>The fileperms aren’t preseverved</li>
</ol>
]]></description>
	</item>
	
	<item>
		<title>Replacing Newlines in Vim Searches</title>
		<link>https://passionsplay.com/blog/replacing-newlines-in-vim-searches/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/replacing-newlines-in-vim-searches/</guid>
		<pubDate>Thu, 15 Nov 2018 01:51:00 GMT</pubDate>
		<description><![CDATA[<p>While trying to get a clearer view of a line of text with 5 million characters in it, I ran into some nuances in Vim’s search and replace.</p>
<p>The main issue was that replaceing the search string with a newline character – <code>\n</code> wasn’t visually creating a new line in the Vim buffer.</p>
<p>I found what I was looking for in this StackOverflow question: <a href="https://stackoverflow.com/questions/71323/how-to-replace-a-character-by-a-newline-in-vim">How to replace a character by a newline in Vim?</a></p>
<p>The general idea is to replace the search string with a <code>\r</code> as opposed to an <code>\n</code>.</p>
<p>For my use case, I was trying to break apart a really long HTML string along its <code>&lt;br&gt;</code> tags. So something along the lines of this:</p>
<pre class="language-plain"><code class="language-plain">:s/\&lt;br>/\&lt;br>\\r/g</code></pre>
<p>Will search for <code>&lt;br&gt;</code> and replace it with a <code>&lt;br&gt;</code> and a visual newline.</p>
]]></description>
	</item>
	
	<item>
		<title>Using Spacemacs</title>
		<link>https://passionsplay.com/blog/using-spacemacs/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/using-spacemacs/</guid>
		<pubDate>Fri, 02 Nov 2018 23:17:00 GMT</pubDate>
		<description><![CDATA[<p>I’ve started falling down the Emacs rabbit hole. I’m still too much of a Vim user to leave behind the modal editing experience. However, <a href="http://spacemacs.org/">Spacemacs</a> has proven to be a pretty good replacement, even though it doesn’t do everything in the “Vim” way.</p>
<h2>Orgmode</h2>
<p><img src="/wp-content/uploads/2018/11/orgmode-with-calendar.png" alt="orgmode-with-calendar.png"></p>
<p>The biggest reason I’ve been using Emacs lately has been the amazing <a href="https://orgmode.org/">Orgmode</a>, and the way in which it can allow me to gather, organize, restructure and refine ideas and tasks as I’m working throughout the day.</p>
<p>If you haven’t taken a look at Orgmode, it’s like markdown, but way more powerful. It includes the basics of being able to markup text with simple conventions but adds to it the power of e-lisp to doing remarkable things like having <a href="https://orgmode.org/worg/org-tutorials/org-spreadsheet-intro.html">full-fledged spreadsheet</a> powered by raw text.</p>
<h2>Publishing Posts using <code>org2blog</code></h2>
<p><img src="/wp-content/uploads/2018/11/writing-blog-post-in-spacemacs.png" alt="writing-blog-post-in-spacemacs.png"></p>
<p>One last piece of this is that the <a href="https://github.com/org2blog/org2blog">org2blog</a> plugin allows me to write things in org-mode and publish them to a WordPress site using WP’s <a href="https://codex.wordpress.org/XML-RPC_Support">XML-RPC interface</a>.</p>
<p>If you’re interested in how some of this is setup, I’ve been commiting and pushing changes to the <a href="https://github.com/bgturner/dot-files/blob/master/.spacemacs">.spacemacs file</a> in my <a href="https://github.com/bgturner/dot-files">dotfiles repo</a>.</p>
]]></description>
	</item>
	
	<item>
		<title>PDX WordCamp 2017 – Trellis, Bedrock and Roots.io Follow Up</title>
		<link>https://passionsplay.com/blog/pdx-wordcamp-2017-trellis-bedrock-and-roots-io-follow-up/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/pdx-wordcamp-2017-trellis-bedrock-and-roots-io-follow-up/</guid>
		<pubDate>Wed, 27 Sep 2017 00:59:05 GMT</pubDate>
		<description><![CDATA[<p>I had a great time last weekend at WordCamp PDX 2017 presenting an overview of Trellis and Bedrock — two projects under the Roots.io umbrella. If you missed the talk, I’ll give an update whenever the talk is uploaded to WordPress.tv.</p>
<p>The slides for the talk are publicly available on <a href="https://docs.google.com/presentation/d/1nTkrVD5-Z0roXES1cFZXllTYQhkCG4ZILEGz2PZ3Q60/edit?usp=sharing">Google Drive</a>.</p>
<p>Additionally, I created an <a href="https://github.com/passionsplay/presentation-intro-to-roots-passionsplay.com">example repo</a> which outlines a number of steps covered in the presentation. Later commits were not a part of the presentation, and touch upon using Trellis to:</p>
<ul>
<li>Provision and deploy to Production</li>
<li>Encrypt the <code>vault.yml</code> files in order to share the site with others, without allowing them to provision and deploy.</li>
</ul>
<p>If you have questions, hit me up on <a href="https://github.com/bgturner">Github</a> or <a href="https://twitter.com/passionsplay">Twitter</a></p>
<h2>Additional Clarification</h2>
<p>There was one question along the lines of:</p>
<blockquote>
<p>How do these projects enforce PHP versions?</p>
</blockquote>
<p>My response ok, but let me attempt to improve on it:</p>
<p>Using Bedrock to manage our WordPress code, we are creating a new composer package. By declaring a dependency along the lines of “this site package requires at least this version of php”, we can ensure that our ‘package’ (ie our site) will fail if the dependencies are not met. This helps us quickly determine and meet the dependency that is lacking (ie, update to a more recent version of php).</p>
<p>Remember that Bedrock doesn’t require Trellis, it’s only concerned with the dependencies of our WordPress site. Because of this, our ‘site package’ can run anywhere the dependencies are met — “Nginx, Apache are fine, but we need at least this version of PHP”.</p>
<p>On the Trellis side, you “can” change the version of php. However, running the most recent version of PHP has so many benefits, predominantly the speed boost, that the effort of downgrading Trellis’ version of php may not be worth it.</p>
<p>Still, if you are undeterred, I recommend looking at the Ansible role controlling the installation of php:</p>
<ul>
<li><a href="https://github.com/roots/trellis/blob/master/roles/php/tasks/main.yml">https://github.com/roots/trellis/blob/master/roles/php/tasks/main.yml</a></li>
</ul>
<p>The Trellis VM is an Ubuntu machine, so any adjustments you can make normally with the shell can be done with Ansible.</p>
<p>I hope that helps clarify things, and if you are in PDX and want to learn more, let’s setup a co-hacking event to share our knowledge!</p>
]]></description>
	</item>
	
	<item>
		<title>Process for Building a Find Command</title>
		<link>https://passionsplay.com/blog/process-for-building-a-find-command/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/process-for-building-a-find-command/</guid>
		<pubDate>Fri, 22 Sep 2017 05:48:51 GMT</pubDate>
		<description><![CDATA[<p>While I was setting up <a href="https://github.com/logrotate/logrotate">logrotate</a> on a server, I needed to traverse a large user directory to find all of the log files, ideally sorted by their size, excluding the mail directory.</p>
<p>This is what I came up with:</p>
<pre class="language-bash"><code class="language-bash">$ <span class="token function">find</span> ~/ <span class="token parameter variable">-not</span> <span class="token punctuation">\</span><span class="token punctuation">\</span><span class="token punctuation">(</span> <span class="token parameter variable">-path</span> ~/mail <span class="token parameter variable">-prune</span> <span class="token punctuation">\</span><span class="token punctuation">\</span><span class="token punctuation">)</span> <span class="token parameter variable">-type</span> f <span class="token parameter variable">-name</span> <span class="token string">'\*\[.\_\]log'</span> <span class="token parameter variable">-exec</span> <span class="token function">du</span> <span class="token parameter variable">-ah</span> <span class="token punctuation">{</span><span class="token punctuation">}</span> + <span class="token operator">|</span> <span class="token function">sort</span> <span class="token parameter variable">-h</span></code></pre>
<h2>Building Up the Command</h2>
<p>I started by doing a simple <code>find</code>, without knowing exactly what I was getting myself into:</p>
<pre class="language-bash"><code class="language-bash">$ <span class="token function">find</span> ~/ <span class="token parameter variable">-type</span> f <span class="token parameter variable">-name</span> <span class="token string">'\*\[.\_\]log'</span></code></pre>
<p>You can read this in simple English as saying “Find in the home directory, any files with a name ending in either ‘.log’ or ‘_log’”. This was a good start, but I had way too many hits and the actual search was taking a couple of seconds to complete — way too long! 😛</p>
<p>Curious about performant ways of excluding directories while using find, I found <a href="https://stackoverflow.com/a/16595367/6107112">this answer on Stackoverflow</a>.</p>
<p>Using the above answer as guidance, I pruned the massive amount of email folders that <code>find</code> was traversing:</p>
<pre class="language-bash"><code class="language-bash">$ <span class="token function">find</span> ~/ <span class="token parameter variable">-not</span> <span class="token punctuation">\</span><span class="token punctuation">\</span><span class="token punctuation">(</span> <span class="token parameter variable">-path</span> ~/mail <span class="token parameter variable">-prune</span> <span class="token punctuation">\</span><span class="token punctuation">\</span><span class="token punctuation">)</span> <span class="token parameter variable">-type</span> f <span class="token parameter variable">-name</span> <span class="token string">'\*\[.\_\]log'</span></code></pre>
<p>At this point, it is a simple matter of executing <code>du</code> to get the file size of each log, and then piping the output to sort the results:</p>
<pre class="language-bash"><code class="language-bash">$ <span class="token function">find</span> ~/-not <span class="token punctuation">\</span><span class="token punctuation">\</span><span class="token punctuation">(</span> <span class="token parameter variable">-path</span> ~/mail <span class="token parameter variable">-prune</span> <span class="token punctuation">\</span><span class="token punctuation">\</span><span class="token punctuation">)</span> <span class="token parameter variable">-type</span> f <span class="token parameter variable">-name</span> <span class="token string">'\*\[.\_\]log'</span> <span class="token parameter variable">-exec</span> <span class="token function">du</span> <span class="token parameter variable">-ah</span> <span class="token punctuation">{</span><span class="token punctuation">}</span> + <span class="token operator">|</span> <span class="token function">sort</span> <span class="token parameter variable">-h</span></code></pre>
]]></description>
	</item>
	
	<item>
		<title>Using Unix Date for Bash Filename Timestamp</title>
		<link>https://passionsplay.com/blog/using-unix-date-for-bash-filename-timestamp/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/using-unix-date-for-bash-filename-timestamp/</guid>
		<pubDate>Tue, 12 Sep 2017 07:47:57 GMT</pubDate>
		<description><![CDATA[<p>I’ve started using a neat Bash trick for inserting time stamps into file names — insert a timestamp using <a href="http://www.tldp.org/LDP/abs/html/commandsub.html">command substitution</a> (<a href="http://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_03">posix specification</a>):</p>
<pre class="language-bash"><code class="language-bash">wp db <span class="token builtin class-name">export</span> example.com--<span class="token variable"><span class="token variable">$(</span><span class="token function">date</span> +%Y-%m-%d--%H-%M-%S<span class="token variable">)</span></span>.sql

<span class="token comment"># gives something like example.com-2017-09-11--14-26-21.sql</span></code></pre>
<p>Yes, this is a pain to type out — the first time.</p>
<p>But going to the effort of writing this out allows us to quickly recall a command which gives us a filename with a “now” time stamp.</p>
<p>I didn’t learn about using <code>ctrl+r</code> in bash until fairly recently. If you don’t know about it, you can do a reverse search through your bash history. Using the above command substitution technique, I can recall a previous dump with a ‘now’ timestamp with just a few keystrokes:</p>
<pre class="language-bash"><code class="language-bash"><span class="token operator">&lt;</span>ctrl+r<span class="token operator">></span> wp db ex<span class="token punctuation">\</span><span class="token operator">&lt;</span>enter<span class="token operator">></span></code></pre>
<p>See the below screencast for an example.</p>
<p>I find this particularly helpful with database dumps and tar creation commands!</p>
]]></description>
	</item>
	
	<item>
		<title>Clearing PageSpeed Cache Using the Browser</title>
		<link>https://passionsplay.com/blog/clearing-pagespeed-cache-using-the-browser/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/clearing-pagespeed-cache-using-the-browser/</guid>
		<pubDate>Sat, 09 Sep 2017 05:15:45 GMT</pubDate>
		<description><![CDATA[<p>I was using the method outlined in my <a href="/blog/finding-and-clearing-pagespeed-cache/">post for clearing the pagespeed cache</a> on a Managed Liquid Web Server, but recently that method stopped working for me.</p>
<p>Using Liquid Web’s chat support I discovered that you can clear the cache using your browser by whitelisting your IP to use the PageSpeed Admin.</p>
<p>To replicate, login as root to the server and edit the <code>pagespeed.conf</code> file:</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">ssh</span> root@xxx.xxx.xxx.xxx
<span class="token punctuation">[</span>root@host ~<span class="token punctuation">]</span><span class="token comment"># vim /etc/httpd/conf/pagespeed.conf</span></code></pre>
<p>There should be a block that looks like this:</p>
<pre class="language-plain"><code class="language-plain">&lt;Location /pagespeed_admin>
Order allow,deny
Allow from localhost
Allow from 127.0.0.1
Allow from xxx.xxx.xxx.xxx # add your IP here
SetHandler pagespeed_admin
&lt;/Location></code></pre>
<p>Save the file and restart Apache. My server was a CentOS one, so:</p>
<pre class="language-bash"><code class="language-bash"><span class="token punctuation">[</span>root@host ~<span class="token punctuation">]</span><span class="token comment"># service httpd restart</span></code></pre>
<p>Doing this should allow you to access the PageSpeed admin by going to the URI at the site’s IP. For example, if the IP for <code>example.com</code> was <code>73.24.10.10</code> — you would navigate to <code>http://73.24.10.10/pagespeed_admin</code></p>
<p>Behold, Web 0.1 in all of it’s glorious styling!</p>
<p>All that’s left is to navigate to the ‘Caches’ page and click ‘Purge Cache’ :</p>
<p><img src="purging-pagespeed-cache-using--rfxcIxgyhLuS.png" alt="Example browser clearing PageSpeed cache"></p>
]]></description>
	</item>
	
	<item>
		<title>Speaking at WordCamp PDX About Roots.io</title>
		<link>https://passionsplay.com/blog/speaking-at-wordcamp-pdx-about-roots-io/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/speaking-at-wordcamp-pdx-about-roots-io/</guid>
		<pubDate>Fri, 25 Aug 2017 03:20:32 GMT</pubDate>
		<description><![CDATA[<p>The <a href="https://2017.portland.wordcamp.org/schedule">schedule</a> has been finalized for the <a href="https://2017.portland.wordcamp.org/">2017 PDX WordCamp</a>, happening on September 23rd at Lewis and Clark College.</p>
<p>There are a lot of great talks, and I’m glad that I can contribute to the community by sharing my knowledge of <a href="https://2017.portland.wordcamp.org/session/managing-a-wordpress-site-using-roots-io-projects">managing a WordPress site using the various Roots.io projects</a>.</p>
<p>As of right now, all <a href="https://2017.portland.wordcamp.org/tickets">tickets have been sold out</a> — however you can get up to date info by <a href="https://twitter.com/wcpdx">following @wcpdx on Twitter</a>.</p>
]]></description>
	</item>
	
	<item>
		<title>Setting Up Vim to Transparently Work With GPG Files</title>
		<link>https://passionsplay.com/blog/setting-up-vim-to-transparently-work-with-gpg-files/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/setting-up-vim-to-transparently-work-with-gpg-files/</guid>
		<pubDate>Wed, 02 Aug 2017 07:45:12 GMT</pubDate>
		<description><![CDATA[<p>I recently stumbled on this blog post covering how to transparently encrypt and de-crypt <code>*.gpg</code> files:</p>
<p><a href="http://blog.endpoint.com/2012/05/vim-working-with-encryption.html">http://blog.endpoint.com/2012/05/vim-working-with-encryption.html</a></p>
<p>The gist is to add this to your <code>.vimrc</code> file:</p>
<pre class="language-plain"><code class="language-plain">" Transparent editing of gpg encrypted files.
" By Wouter Hanegraaff
"    see: http://blog.endpoint.com/2012/05/vim-working-with-encryption.html
"
augroup encrypted
  au!

  " First make sure nothing is written to ~/.viminfo while editing
  " an encrypted file.
  autocmd BufReadPre,FileReadPre \*.gpg set viminfo=
  " We don't want a swap file, as it writes unencrypted data to disk
  autocmd BufReadPre,FileReadPre \*.gpg set noswapfile

  " Switch to binary mode to read the encrypted file
  autocmd BufReadPre,FileReadPre \*.gpg set bin
  autocmd BufReadPre,FileReadPre \*.gpg let ch\_save = &amp;ch|set ch=2
  " (If you use tcsh, you may need to alter this line.)
  autocmd BufReadPost,FileReadPost \*.gpg '\[,'\]!gpg --decrypt 2> /dev/null

  " Switch to normal mode for editing
  autocmd BufReadPost,FileReadPost \*.gpg set nobin
  autocmd BufReadPost,FileReadPost \*.gpg let &amp;ch = ch\_save|unlet ch\_save
  autocmd BufReadPost,FileReadPost \*.gpg execute ":doautocmd BufReadPost " . expand("%:r")

  " Convert all text to encrypted text before writing
  " (If you use tcsh, you may need to alter this line.)
  autocmd BufWritePre,FileWritePre \*.gpg '\[,'\]!gpg --default-recipient-self -ae 2>/dev/null
  " Undo the encryption so we are back in the normal text, directly
  " after the file has been written.
  autocmd BufWritePost,FileWritePost \*.gpg u
augroup END</code></pre>
]]></description>
	</item>
	
	<item>
		<title>WP Asciinema Plugin</title>
		<link>https://passionsplay.com/blog/wp-asciinema-plugin/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/wp-asciinema-plugin/</guid>
		<pubDate>Thu, 27 Jul 2017 05:11:09 GMT</pubDate>
		<description><![CDATA[<p>I created a plugin called <a href="https://github.com/passionsplay/wp-asciinema">WP Asciinema</a> to help add Asciicasts created using <a href="https://asciinema.org/">https://asciinema.org/</a> to WordPress.</p>
<p>This is plugin is meant to help self-host asciicasts on your WordPress site. If you are simply wanting to include asciicasts from asciinema.org, then take a look at their <a href="https://asciinema.org/docs/embedding">embedding guidelines</a>.</p>
<h2>Usage</h2>
<p>This plugin <a href="https://github.com/passionsplay/wp-asciinema#shortcode--asciinema">adds a shortcode</a> to help display the Asciinema player. Once installed, you can add the JSON formated terminal sessions to the ‘asciicast’ directory under the ‘uploads’ folder and reference them using the shortcode. The below terminal session is displayed with:</p>
<pre><code>\[ asciinema src=&quot;wp-asciinema-hello-world.json&quot; speed=&quot;2&quot; \]
</code></pre>
]]></description>
	</item>
	
	<item>
		<title>Bash Scripts to Help Get Remote Databases</title>
		<link>https://passionsplay.com/blog/bash-scripts-to-help-get-remote-databases/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/bash-scripts-to-help-get-remote-databases/</guid>
		<pubDate>Fri, 17 Feb 2017 06:00:37 GMT</pubDate>
		<description><![CDATA[<p>I’ve been using the <a href="https://roots.io/">Roots.io</a> family of technologies to help automate my WordPress development and deployment. One annoyance has been an easy way to retrieve and import database changes on the remote host.</p>
<p>A couple of months ago I started tinkering with bash scripts to automate this process. Since I’ve been using and refining them, I thought I would clean and comment the code as well as provide a detailed readme file.</p>
<p>If you are interested, have a look at the repo:</p>
<p><a href="https://github.com/bgturner/roots_db_scripts">https://github.com/bgturner/roots_db_scripts</a></p>
]]></description>
	</item>
	
	<item>
		<title>Vim Learning Resources</title>
		<link>https://passionsplay.com/blog/vim-learning-resources/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/vim-learning-resources/</guid>
		<pubDate>Fri, 13 Jan 2017 08:04:47 GMT</pubDate>
		<description><![CDATA[<p>It’s been just under two years since I made the switch to using Vim full time, and I can say that it’s been the best thing I’ve done for my programming development.</p>
<p>It certainly wasn’t <em>easy</em>, nor do I think that it is the best thing for every developer to take on.</p>
<p>There might even be some truth to the idea that Vim is a dark, elegant weapon, wielded by neckbeards with whom the <code>Unix</code> is strong.</p>
<p>I do have a beard, but my neck is shaved at least some of the time. But using Vim has served as a powerful gateway for me to think about solutions in an even more ‘Unix-y’ way. This includes becoming more familiar with the terminal, but also more general language tools like Regex — a skill which still elicits feelings of dread and awe when I decide to use it.</p>
<p>Since I didn’t get here all on my own, I thought it would be good to share a few of the resources that I’ve found most helpful for really leveling up my Vim-fu:</p>
<ul>
<li><a href="http://vimcasts.org/">Vimcasts.org</a></li>
<li><a href="https://pragprog.com/book/dnvim2/practical-vim-second-edition">Practical Vim, by Drew Neil</a></li>
<li><a href="https://laracasts.com/series/vim-mastery">Vim Mastery, Laracasts</a></li>
</ul>
<p>Since Vim has such a strong following in the Ruby on Rails world, I really found the Laracasts series to be more applicable to my day to day use as a PHP developer. All in all though, it’s hard to plow through any of these resources once and be done with it. I found myself biting off a chapter here, or watching a screencast there, looking for a way to utilize what I had just learned.</p>
<p>That’s the thing, the concepts only really started clicking when I applied it to real world tasks.</p>
<p>And there’s still more to learn!</p>
<p>My next bit of Vim learning will be working through <a href="http://learnvimscriptthehardway.stevelosh.com/">Learn Vimscript the Hard Way</a> with an eye towards extending this already powerful tool.</p>
<p>With all of this learning I feel as though I’m closer to having something to actually share back. As such, I’m hoping to post more often here about the tools and tinkering I’ve come across, and how they can be leveraged into useful solutions.</p>
]]></description>
	</item>
	
	<item>
		<title>Doug Dingus on Hacking</title>
		<link>https://passionsplay.com/blog/doug-dingus-on-hacking/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/doug-dingus-on-hacking/</guid>
		<pubDate>Tue, 23 Aug 2016 02:24:01 GMT</pubDate>
		<description><![CDATA[<p>I was trawling Quora, putting off some work and found myself reading a <a href="https://www.quora.com/How-does-one-become-a-hacker-2/answer/Doug-Dingus">great answer</a> to the question of “<a href="https://www.quora.com/How-does-one-become-a-hacker-2">How does one become a hacker?</a>”</p>
<p>While reading Doug’s answer I found myself identifying with his stories of how he interacts with the world around him — examining, tinkering, understanding.</p>
<p>I remember my time as a visual artist and approaching problems with a balance of learned technique and playful experimentation. Some of the most successful solutions — both in art and now in tech involve toying with the edges of what something is ‘supposed’ to do. As Doug put it:</p>
<blockquote>
<p>What does it do?  What is [it] supposed to do?</p>
<p>Those are two entirely different questions!</p>
</blockquote>
<p>I’ve always thought that creativity or in this case, hacking — isn’t something that is reserved for “artists” (or whoever your chosen “creative” group is). They are qualities that are intrinsic in all of us. We start our lives wanting to play and interact with the world. The problem is that there are a lot of other things (people, institutions, societal norms, etc) interested in controlling that spark, bending to <em>their</em> interests, or worse extinguishing it when it is inconvenient for them.</p>
<p>Ultimately, I see both creativity and hacking as personal tools for understanding reality. I found the closing paragraph a great summary of what we obtain with the application of these tools by human hands [my emphasis]:</p>
<blockquote>
<p><strong>Anything that can do something</strong>, or that has complex dynamics, or functions, <strong>can be hacked on and the products of that hack are understanding of that thing that goes deeper than the obvious</strong>, or official, or expected types of understanding.</p>
</blockquote>
<p><a href="https://www.quora.com/How-does-one-become-a-hacker-2/answer/Doug-Dingus">https://www.quora.com/How-does-one-become-a-hacker-2/answer/Doug-Dingus</a></p>
]]></description>
	</item>
	
	<item>
		<title>Building a Site Scraper to Find Problem Urls in WordPress</title>
		<link>https://passionsplay.com/blog/building-a-site-scraper-to-find-problem-urls/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/building-a-site-scraper-to-find-problem-urls/</guid>
		<pubDate>Fri, 13 May 2016 12:13:47 GMT</pubDate>
		<description><![CDATA[<p>Sometimes all you need is a quick scraper to help you get the information missed in a database search-and-replace.</p>
<p>I recently hacked together this single use scraper <a href="https://github.com/bgturner/site-crawler-problem-urls">repo</a>. Fork, extend and tweak for your needs!</p>
<h2>Backstory</h2>
<p>I’ve recently had the pleasure of launching a new <a href="https://wordpress.org/">WordPress</a> site for <a href="http://peacefulmedia.com/">Peaceful Media</a> — Yeah!! — but a cursory walk through of the live site had me stumbling across urls still pointing to the development site.</p>
<p>How could this be? I was sure to use <a href="https://wp-cli.org/">wp-cli</a> and its search and replace function:</p>
<pre class="language-bash"><code class="language-bash">wp search-replace <span class="token string">'//dev.example.com'</span> <span class="token string">'//example.com'</span></code></pre>
<p>As it turns out, <a href="http://codecanyon.net/item/visual-composer-page-builder-for-wordpress/242431">Visual Composer</a>, the page layout plugin that was used to craft the various static pages of the site, was saving <em>some</em> urls in slightly different ways to the database. In a few of those cases it was simply that the urls had been percent encoded. So for example, <code>http://dev.example.com</code> becomes <code>http%3A%2F%2Fdev.example.com</code>. Not <em>too</em> bad. I could have just search and replaced on this new string. But what about the more annoying <a href="https://vc.wpbakery.com/features/content-elements/">Raw HTML Element</a>?</p>
<p>The Raw HTML element actually saves content to the database as a base64 encoded string. I actually should give at least a little props to Visual Composer for the clever, albeit <strong>hacky</strong> way they have managed to ensure that raw HTML markup isn’t “helpfully” sanitized by TinyMCE or WordPress on post save. Since the stuff being saved is just a jumble of characters, there is nothing that either TinyMCE, or WordPress can adjust and break.</p>
<p>That’s all fine and good, however, having the content exist encoded in base64 means I can’t easily interact with the content outside of the GUI admin screens — in this case, perform a simple search and replace on the database. Additionally, should we ever migrate away from using Visual Composer, we now have a more complex migration path ahead of us.</p>
<p>Anyway, back to solving the problem at hand — identifying all of the instances of a Url that match a ‘problematic’ pattern — <code>dev.example.com</code>. What I ended up doing was to create a quick and dirty site scraper to help me scan all of the pages that are easily accessible from the homepage and flag any instances of URLs which match a regex of ‘problem’ urls. Then it’s just a matter of hand updating them. It turned out to be pretty easy using the <a href="https://github.com/cgiffard/node-simplecrawler">node-simplescraper</a> module and creating a <a href="https://github.com/bgturner/site-crawler-problem-urls/blob/master/index.js">75 line nodejs file</a>. One secondary benefit of this is that it is not confined to simply scanning the content of the site database.</p>
<p>Since this is an actual headless browser scraping our site, it scans everything that a visitor would have access to. This includes links, images, styles and more. This means that if there were any problem urls in linked files, for example, external Javascript and Css files — those would get flagged as well. Awesome!</p>
<p>The end result is still a duck-taped, immediate use sort of tool, but I think there is something here that I can come back to and extend. Until then, feel free to poke around and fork <a href="https://github.com/bgturner/site-crawler-problem-urls">the repo</a> on Github!</p>
]]></description>
	</item>
	
	<item>
		<title>Finding and Clearing PageSpeed Cache</title>
		<link>https://passionsplay.com/blog/finding-and-clearing-pagespeed-cache/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/finding-and-clearing-pagespeed-cache/</guid>
		<pubDate>Sat, 16 Jan 2016 04:21:46 GMT</pubDate>
		<description><![CDATA[<h2>TLDR;</h2>
<p>How to <a href="https://developers.google.com/speed/pagespeed/module/system#flush_cache">flush mod_pagespeed cache</a> in <a href="https://www.liquidweb.com/">LiquidWeb</a>‘s new <a href="https://www.liquidweb.com/wordpress/">Managed WordPress</a> package:</p>
<p>SSH as root and touch a file:</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">ssh</span> root@<span class="token comment">###.###.###.###</span>
<span class="token function">touch</span> /var/cache/pagespeed/cache.flush</code></pre>
<p>The mod time of this file is used by mod_pagespeed to determine if its cache needs to be flushed.</p>
<h2>Setup</h2>
<p>I’ve recently had the opportunity to try out LiquidWeb’s new Managed WordPress package. The initial setup was fast, and simple with a number of really awesome performance settings, such as HHVM, Memcached and PageSpeed setup out of the box.</p>
<p>So the site has been deployed and is noticeably faster. The only issue was finding a way to flush the mod_pagespeed cache.</p>
<p>According to <a href="https://developers.google.com/speed/pagespeed/module/system#flush_cache">the PageSpeed docs</a>, the most basic method is to</p>
<blockquote>
<p>simply touch the file “cache.flush” in the directory specified by `FileCachePath`</p>
</blockquote>
<p>My process for finding / doing this followed:</p>
<ol>
<li>Determine where `FileCachePath` was on the server</li>
<li>Flush the cache</li>
</ol>
<h2>Finding FileCachePath</h2>
<p>This server was a CentOS one, but this should be similar for other systems. First off, we need to login as root and find the apache configuration:</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">ssh</span> root@<span class="token comment">###.###.###.###</span>
<span class="token function">ps</span> <span class="token parameter variable">-ef</span> <span class="token operator">|</span> <span class="token function">grep</span> apache
<span class="token comment"># gives us:</span>
root <span class="token number">23433</span> <span class="token number">1</span> <span class="token number">0</span> Jan14 ? 00:00:04 /usr/local/apache/bin/httpd <span class="token parameter variable">-k</span> start
nobody <span class="token number">24069</span> <span class="token number">23433</span> <span class="token number">0</span> <span class="token number">12</span>:04 ? 00:00:00 /usr/local/apache/bin/httpd <span class="token parameter variable">-k</span> start
nobody <span class="token number">24070</span> <span class="token number">23433</span> <span class="token number">0</span> <span class="token number">12</span>:04 ? 00:00:12 /usr/local/apache/bin/httpd <span class="token parameter variable">-k</span> start
nobody <span class="token number">24071</span> <span class="token number">23433</span> <span class="token number">0</span> <span class="token number">12</span>:04 ? 00:00:10 /usr/local/apache/bin/httpd <span class="token parameter variable">-k</span> start
nobody <span class="token number">24072</span> <span class="token number">23433</span> <span class="token number">0</span> <span class="token number">12</span>:04 ? 00:00:09 /usr/local/apache/bin/httpd <span class="token parameter variable">-k</span> start
nobody <span class="token number">24240</span> <span class="token number">23433</span> <span class="token number">0</span> <span class="token number">12</span>:04 ? 00:00:18 /usr/local/apache/bin/httpd <span class="token parameter variable">-k</span> start
root <span class="token number">30015</span> <span class="token number">29909</span> <span class="token number">0</span> <span class="token number">13</span>:50 pts/0 00:00:00 <span class="token function">grep</span> <span class="token parameter variable">--color</span><span class="token operator">=</span>auto apache</code></pre>
<p>This gives us to the location of the currently running apache process. Next is to use the above path to find the location of the apache configuration file. For me, the apache path was <code>/usr/local/apache/bin/httpd</code>.</p>
<pre class="language-bash"><code class="language-bash">/usr/local/apache/bin/httpd <span class="token parameter variable">-V</span> <span class="token operator">|</span> <span class="token function">grep</span> SERVER<span class="token punctuation">\</span>_CONFIG<span class="token punctuation">\</span>_FILE
<span class="token comment"># gives us:</span>
<span class="token parameter variable">-D</span> SERVER<span class="token punctuation">\</span>_CONFIG<span class="token punctuation">\</span>_FILE<span class="token operator">=</span><span class="token string">"conf/httpd.conf"</span></code></pre>
<p>In some systems, that’s the full path, but for me it was a relative path, so I needed to find the apache root:</p>
<pre class="language-bash"><code class="language-bash">/usr/local/apache/bin/httpd <span class="token parameter variable">-V</span> <span class="token operator">|</span> <span class="token function">grep</span> HTTPD<span class="token punctuation">\</span>_ROOT
<span class="token comment"># gives us:</span>
<span class="token parameter variable">-D</span> HTTPD<span class="token punctuation">\</span>_ROOT<span class="token operator">=</span><span class="token string">"/usr/local/apache"</span></code></pre>
<p>Opening that file I found that the actual pagespeed configuration was included in an additional sibling file called <code>pagespeed.conf</code>.</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">less</span> /usr/local/apache/conf/httpd.conf
<span class="token comment"># determine that I need to look in 'pagespeed.conf'</span>
<span class="token function">less</span> /usr/local/apache/conf/pagespeed.conf</code></pre>
<p>Being new to PageSpeed, this file was an interesting look into the various options available, however for my purposes, this line was exactly what I needed:</p>
<pre class="language-plain"><code class="language-plain">ModPagespeedFileCachePath "/var/cache/pagespeed/"</code></pre>
<h2>Flush the Cache</h2>
<p>So now we have what we need to flush the cache — simply create a file whose mod time is used by PageSpeed to determine when to flush the cache!</p>
<pre class="language-bash"><code class="language-bash"><span class="token function">touch</span> /var/cache/pagespeed/cache.flush</code></pre>
]]></description>
	</item>
	
	<item>
		<title>Becoming a WordPress Theme Reviewer</title>
		<link>https://passionsplay.com/blog/becoming-a-wordpress-theme-reviewer/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/becoming-a-wordpress-theme-reviewer/</guid>
		<pubDate>Mon, 26 Oct 2015 02:03:20 GMT</pubDate>
		<description><![CDATA[<p>I just attended the <a href="https://twitter.com/hashtag/wcpdx">2015 PDX WordCamp</a> and am now helping out at the contributor day. Of course contributing to core can be done in a <a href="https://make.wordpress.org/">number of ways</a>, but one of the areas that I’ve always wanted to help out with is through <a href="https://make.wordpress.org/themes/">theme review</a>.</p>
<p>The above link to the theme review handbook is quite comprehensive in regards to the steps one needs to take to help out, but this post is meant to be a quick overview of what I did to get up and running:</p>
<ol>
<li>Create a new sandbox site called theme-review.dev to complete the reviews. This is easy if you use <a href="https://github.com/Varying-Vagrant-Vagrants/VVV">vvv</a>, and the extremely helpful scaffolding script <a href="https://github.com/bradp/vv">vv</a>.</li>
<li>Check in with the <a href="https://wordpress.slack.com/messages/themereview/details/">theme review slack channel</a>. This is a great place to introduce yourself and ask questions as they arise. If you don’t have access to WordPress’s slack channels, make sure that you <a href="https://wordpress.org/support/register.php">have a WordPress.org account</a>, and then <a href="https://make.wordpress.org/chat/">ask for an invitation</a>.</li>
<li>Finally – <a href="https://make.wordpress.org/themes/request-a-theme-to-review/">request a theme to review</a>!</li>
</ol>
<p>A couple of helpful plugins and notes in regards to setting up the testing environment.</p>
<ol>
<li>Use the <a href="https://wordpress.org/plugins/developer/">Developer</a> plugin. No need to install all of the additional plugins, but a few of the following are useful.
<ul>
<li>Debug Bar</li>
<li>Log deprecated notices</li>
<li>Monster Widget</li>
<li>Regenerate thumbnails</li>
<li>RTL tester</li>
<li>Theme Check</li>
<li>Beta Tester</li>
</ul>
</li>
<li>Install the <a href="http://codex.wordpress.org/Theme_Unit_Test">theme meta data</a>.</li>
<li>Install the <a href="https://wordpress.org/plugins/theme-check/">theme check plugin</a></li>
</ol>
<p>Next steps — level up theme knowledge!</p>
]]></description>
	</item>
	
	<item>
		<title>Lessons from Cycling the Oregon Coast</title>
		<link>https://passionsplay.com/blog/lessons-from-cycling-the-oregon-coast/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/lessons-from-cycling-the-oregon-coast/</guid>
		<pubDate>Wed, 14 Oct 2015 17:00:02 GMT</pubDate>
		<description><![CDATA[<p>I’ve returned home from riding my bike along the Oregon Coast. It has certainly been a long trek, and while I’m not ready to go another 380 miles this next week, I can say that I had fun, and will likely go on future tours.</p>
<table>
<thead>
<tr>
<th></th>
<th>Distance</th>
<th>Moving Time</th>
<th>Elevation</th>
<th>Strava Link</th>
</tr>
</thead>
<tbody>
<tr>
<td>Day 1</td>
<td>45.3</td>
<td>3:55:06</td>
<td>2,180</td>
<td>Activity – <a href="https://www.strava.com/activities/395076910">395076910</a></td>
</tr>
<tr>
<td>Day 2</td>
<td>44.7</td>
<td>3:51:03</td>
<td>1,988</td>
<td>Activity – <a href="https://www.strava.com/activities/395840477">395840477</a></td>
</tr>
<tr>
<td>Day 3</td>
<td>59.8</td>
<td>5:05:38</td>
<td>2,429</td>
<td>Activity – <a href="https://www.strava.com/activities/396811337">396811337</a></td>
</tr>
<tr>
<td>Day 4</td>
<td>62.1</td>
<td>4:52:56</td>
<td>3,131</td>
<td>Activity – <a href="https://www.strava.com/activities/397449985">397449985</a></td>
</tr>
<tr>
<td>Day 5</td>
<td>55.7</td>
<td>4:27:54</td>
<td>2,364</td>
<td>Activity – <a href="https://www.strava.com/activities/398907047">398907047</a></td>
</tr>
<tr>
<td>Day 6</td>
<td>59.9</td>
<td>5:16:48</td>
<td>2,814</td>
<td>Activity – <a href="https://www.strava.com/activities/398907072">398907072</a></td>
</tr>
<tr>
<td>Day 7</td>
<td>56.2</td>
<td>4:43:03</td>
<td>3,555</td>
<td>Activity – <a href="https://www.strava.com/activities/399560114">399560114</a></td>
</tr>
<tr>
<td>Totals</td>
<td>383.7</td>
<td>32:12:28</td>
<td>18,461</td>
<td></td>
</tr>
</tbody>
</table>
<div class="gallery"><figure>
        <a href="/blog/lessons-from-cycling-the-oregon-coast/bike-the-coast-01-sttzv2dLIQpu.jpg" class="glightbox" data-description="Starting point — heading south">
          <img src="/blog/lessons-from-cycling-the-oregon-coast/bike-the-coast-01-sttzv2dLIQpu.jpg" alt="Starting point — heading south">
        </a>
        <figcaption>Starting point — heading south</figcaption>
      </figure>
<figure>
        <a href="/blog/lessons-from-cycling-the-oregon-coast/bike-the-coast-03-H9uvLBHMg17o.jpg" class="glightbox" data-description="Morning fog on the headland">
          <img src="/blog/lessons-from-cycling-the-oregon-coast/bike-the-coast-03-H9uvLBHMg17o.jpg" alt="Morning fog on the headland">
        </a>
        <figcaption>Morning fog on the headland</figcaption>
      </figure>
<figure>
        <a href="/blog/lessons-from-cycling-the-oregon-coast/bike-the-coast-04-NkeQVvBwHHV5.jpg" class="glightbox" data-description="Beach access trail">
          <img src="/blog/lessons-from-cycling-the-oregon-coast/bike-the-coast-04-NkeQVvBwHHV5.jpg" alt="Beach access trail">
        </a>
        <figcaption>Beach access trail</figcaption>
      </figure>
<figure>
        <a href="/blog/lessons-from-cycling-the-oregon-coast/bike-the-coast-07-AFIn7hrHD1Ah.jpg" class="glightbox" data-description="Camp setup at dusk">
          <img src="/blog/lessons-from-cycling-the-oregon-coast/bike-the-coast-07-AFIn7hrHD1Ah.jpg" alt="Camp setup at dusk">
        </a>
        <figcaption>Camp setup at dusk</figcaption>
      </figure>
<figure>
        <a href="/blog/lessons-from-cycling-the-oregon-coast/bike-the-coast-09-9uymMdJs6glu.jpg" class="glightbox" data-description="Sea stacks at low tide">
          <img src="/blog/lessons-from-cycling-the-oregon-coast/bike-the-coast-09-9uymMdJs6glu.jpg" alt="Sea stacks at low tide">
        </a>
        <figcaption>Sea stacks at low tide</figcaption>
      </figure>
<figure>
        <a href="/blog/lessons-from-cycling-the-oregon-coast/bike-the-coast-11-NYsQVG96bhx9.jpg" class="glightbox" data-description="End of day view">
          <img src="/blog/lessons-from-cycling-the-oregon-coast/bike-the-coast-11-NYsQVG96bhx9.jpg" alt="End of day view">
        </a>
        <figcaption>End of day view</figcaption>
      </figure></div>
<p>When I initially conceived of biking the coast I was in a groundless spot in life. This was early in the summer of 2014, and I was in the process of getting a divorce.</p>
<p>A major life changing event can make you realize that many of the assumptions you have been making in your day-to-day living are no longer relevant. For me, I started to examine <em>everything</em> and asking myself questions like:</p>
<ul>
<li>“Do you really like the house you are living in? How about the city?”</li>
<li>“Are you happy with the job you have?”</li>
<li>“Are you living up to your full potential?”</li>
<li>“Are you actively pursuing your passions?”</li>
</ul>
<p>My initial reaction to the divorce was to leave the house, sell all my belongings, and move to a new city. In short, lose myself and find a new me. But as I examined these aspects of my life, and those underlying motives for change, I realized it wasn’t the shedding of old skin that I wanted, rather what I needed was to rediscover old passions and amplify forgotten, or under-used talents.</p>
<p>This means finding and improving one’s core self – and is much different from maintaining the status-quo. Part of this self introspection had now turned into a visceral need to test myself against something much larger than me. I was out to prove <em>something</em>.</p>
<p>So I turned to biking. One night over drinks I voiced a half-formed, half-joking thought of biking the coast, not really knowing what that entailed. The more I thought about it though, the more it felt like a good step.</p>
<p>So I started small. Since I work remotely, I began to force myself to commute. I would throw my laptop into a bag and ride seven to ten miles into Portland and set to work in one of the many coffee shops. This meant I was logging anywhere from 16 to 20 miles on my bike any day that I was commuting.</p>
<p>Repeating this as my new daily rhythm meant I was logging 80 to 100 miles per week on my bike, and so the prospect of a larger tour didn’t seem so impossible.</p>
<p>Granted, there is a sizeable difference between 100 and 380 miles in a week, but when an idea is large and far off, it is easier to squint your eyes and gloss over the differences. So planning was easy, and my routes were mostly the same paths I ended up riding.</p>
<h2>So what did you learn?</h2>
<p><strong>I like going fast.</strong> Those long slow climbs are not as fun as they say, though really I suppose I have never heard anyone say that they enjoy the climbs. Some of my favorite times on the trip included coasting on the long descents with the wind at my back (Top speed of 47mph on Day 4 — Yes!).</p>
<p><strong>I like the Southern Coast.</strong> Much of the Oregon Coast is beautiful, but I must say that I really was taken away by the beauty south of Port Orford. Many of the awesome geological formations that I love in the north, like Haystack rock in Cannon Beach could be seen everywhere.</p>
<p><strong>Things are more fun when surrounded by good people.</strong> When I initially started to tell people I would be doing this, I was hoping, but not expecting many people to join. While I was the only person in our group to cycle the coast, things were made so much more pleasant by having my parents and girlfriend along to share morning coffee and evening campfires.</p>
<p>But most of all, I’ve learned the importance of <strong>aiming high and finding ways to surprise yourself.</strong></p>
<p>Before starting the trip I knew I could do it. I was cocky, and didn’t really know what was to come on my first cycling tour.</p>
<p>On the third day, on a stop for water in Lincoln City my outlook had changed and I knew I certainly couldn’t finish. I had ten more miles for that day, and around 250 for the rest of the trip. My muscles were sore, and my butt was aching. I now had a more realistic understanding of what I had undertaken.</p>
<p>Still, worrying about large challenges isn’t helpful, so I pushed through and finished that day. I slept. And I finished the next day. And slept. I inched to the top of each cape and bombed each descent. By the end of day 7, I was within 20 miles of the border and I got my first flat of the trip. I fixed it and peddled on. I crossed the border and <strong>finished something I knew I couldn’t do</strong>.</p>
<p>I look back a couple of weeks after finishing, and still can’t quite believe that I finished. That first week back – my butt was still a little sore, but my muscles were mostly better, if just a bit tight. But now, something I thought was impossible, is now just <em>impressive</em>.</p>
<p>This change in perception has secondary benefits as well. <strong>My horizons are wider</strong>. Instead of a list of impossible tasks, and unrealistic dreams , I now have a much longer list of ‘impressive’ possibilities.</p>
]]></description>
	</item>
	
	<item>
		<title>Planning to Cycle the Oregon Coast</title>
		<link>https://passionsplay.com/blog/planning-to-cycle-the-oregon-coast/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/planning-to-cycle-the-oregon-coast/</guid>
		<pubDate>Mon, 11 May 2015 04:15:21 GMT</pubDate>
		<description><![CDATA[<p>I’ve been talking to people for a while now about doing a trip to cycle the Oregon coast — starting from Astoria and working my way to the California border. I’ve been riding my bike a lot in order to begin training, but today I actually took the time to plan out what that trip will look like.</p>
<p>I found that there are lots of places to find information, many of which are free. In particular the Oregon Department of Transportation has <a href="http://www.oregon.gov/odot/hwy/bikeped/docs/oregon_coast_bike_route_map.pdf" title="Oregon Department of Transportation information on Biking the Oregon Coast">this handy pdf</a>. Of particular interest is a table listing all of the state parks and the various amenities that they offer (Hot showers! Yurts!). More detailed information pertaining to the state parks found along the way I found at <a href="http://www.oregonstateparks.org/">oregonstateparks.org</a></p>
<p>While the above information is helpful, the best tool for actually creating my itinerary was <a href="https://www.strava.com/routes" title="Strava's Route Builder">Strava’s Route Builder</a>. Dropping waypoints is easy, and a dynamic profile of the elevation changes is provided. After a half hour of tinkering I not only had a destination for each day, but I also have an idea of the kind of training I need to be doing.</p>
<p>Seeing as the worst day has an elevation gain of over 3,600 feet, I foresee laps up and down Mt. Tabor in my future!</p>
<h2>The Cycle Schedule</h2>
<h3>Day 1 : Astoria to Nehalem Bay State Park</h3>
<p><img src="haystack-rock-sF2Zh8wWNvRg.jpg" alt="Haystack Rock on a partly cloudy day."></p>
<p>Nehalem Bay State Park is located just south of Manzanita, and is sandwiched between the Nehalem River and the Ocean. This day’s ride passes Cannon Beach, whose <a href="https://www.google.com/search?q=Haystack+Rock&amp;client=ubuntu&amp;hs=rIs&amp;es_sm=93&amp;source=lnms&amp;tbm=isch&amp;sa=X&amp;ei=eJ5PVZHZA5baoATJpIAQ&amp;ved=0CAcQ_AUoAQ&amp;biw=1920&amp;bih=993#imgrc=_">Haystack Rock</a> is one of my favorite spots along the coast.</p>
<ul>
<li><a href="http://www.oregonstateparks.org/index.cfm?do=parkPage.dsp_parkPage&amp;parkId=142">Nehalem Bay State Park Information</a></li>
<li><a href="https://www.strava.com/routes/2315116" title="Strava Route for day one of biking the Oregon coast.">Day 1 Strava Route</a></li>
</ul>
<h3>Day 2 : Nehalem Bay State Park to Cape Lookout State Park</h3>
<p><img src="oceanside-3j2rjQbQvsay.jpg" alt="A sunset in Oceanside Oregon">This stretch of the coast includes Tillamook, which everyone knows has ice cream to pair with your Bourbon. Immediately after Tillamook is the Three Capes Scenic Route, with steep climbs — and amazing views.</p>
<ul>
<li><a href="http://www.oregonstateparks.org/index.cfm?do=parkPage.dsp_parkPage&amp;parkId=134">Cape Lookout State Park Information</a></li>
<li><a href="https://www.strava.com/routes/2315051" title="Strava Route for day two of biking the Oregon coast.">Day 2 Strava Route</a></li>
</ul>
<h3>Day 3 : Cape Lookout State Park to Beverly Beach State Park</h3>
<p><img src="depoe-bay-WD2nOnrD5WpB.jpg" alt="depoe-bay"></p>
<p>A big climb up Cape Lookout first thing in the morning, with another equally steep climb up Cascade Head about half way through the day. Beverly Beach State Park is past Lincoln City and Depoe bay, but not quite to Newport.</p>
<ul>
<li><a href="http://www.oregonstateparks.org/index.cfm?do=parkPage.dsp_parkPage&amp;parkId=164">Beverly Beach State Park Information</a></li>
<li><a href="https://www.strava.com/routes/2315191" title="Strava Route for day three of biking the Oregon coast.">Day 3 Strava Route</a></li>
</ul>
<h3>Day 4 : Beverly Beach State Park to Jessie M. Honeyman Memorial State Park</h3>
<p>This day sees us through Yachats and Florence, with the longest day in terms of miles – 60.6.</p>
<ul>
<li><a href="http://www.oregonstateparks.org/index.cfm?do=parkPage.dsp_parkPage&amp;parkId=95">Jessie M. Honeyman Memorial State Park Information</a></li>
<li><a href="https://www.strava.com/routes/2315260" title="Strava Route for day four of biking the Oregon coast.">Day 4 Strava Route</a></li>
</ul>
<h3>Day 5 : Jessie M. Honeyman Memorial State Park to Sunset Bay State Park</h3>
<p>And now we’re at the part of the coast that I don’t know much about — anything south of Florence is bright new territory!</p>
<ul>
<li><a href="http://www.oregonstateparks.org/index.cfm?do=parkPage.dsp_parkPage&amp;parkId=70">Sunset Bay State Park Information</a></li>
<li><a href="https://www.strava.com/routes/2315283" title="Strava Route for day five of biking the Oregon coast.">Day 5 Strava Route</a></li>
</ul>
<h3>Day 6 : Sunset Bay State Park to Humbug Mountain State Park</h3>
<ul>
<li><a href="http://www.oregonstateparks.org/index.cfm?do=parkPage.dsp_parkPage&amp;parkId=40">Humbug Mountain State Park Information</a></li>
<li><a href="https://www.strava.com/routes/2315308" title="Strava Route for day six of biking the Oregon coast.">Day 6 Strava Route</a></li>
</ul>
<h3>Day 7 : Humbug Mountain State Park to California Border</h3>
<ul>
<li><a href="https://www.strava.com/routes/2315343" title="Strava Route for day seven of biking the Oregon coast.">Day 7 Strava Route</a></li>
</ul>
]]></description>
	</item>
	
	<item>
		<title>Password Protecting a Directory Using NGINX</title>
		<link>https://passionsplay.com/blog/password-protecting-directory-using-nginx/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/password-protecting-directory-using-nginx/</guid>
		<pubDate>Fri, 06 Mar 2015 06:02:54 GMT</pubDate>
		<description><![CDATA[<p>Coming from an Apache world, I was familiar with setting up a simple <a href="https://wiki.apache.org/httpd/PasswordBasicAuth">password protected directory using .htaccess</a> rules.</p>
<p>However, in setting up a bare-bones LEMP server on Digital Ocean I wasn’t installing Apache, so I didn’t have access to the <code>htpasswd</code> utility. So, pulling together information from a number of different sources:</p>
<ul>
<li><a href="http://nginxlibrary.com/password-protect-a-directory/">http://nginxlibrary.com/password-protect-a-directory/</a></li>
<li><a href="http://kbeezie.com/protecting-folders-with-nginx/">http://kbeezie.com/protecting-folders-with-nginx/</a></li>
</ul>
<p>I came up with this workflow:</p>
<h2>Determine the Directory To Protect</h2>
<p>Since you are reading this post, you probably already know this. However for the sake of this walk through let’s be thorough.</p>
<p>In a default LEMP stack on Ubuntu 14.04, the NGINX document root is found at:</p>
<pre><code>/usr/share/nginx/html/
</code></pre>
<p>You can verify this by taking a look at the sites that are enabled. Open any files found in <code>/etc/nginx/sites-enabled</code> and look for lines that say <code>root /usr/some/path;</code></p>
<p>For this post I’ll create a new folder and webpage under <code>/usr/share/nginx/html/</code> to test and make sure things work correctly:</p>
<pre><code>cd /usr/share/nginx/html
mkdir private
echo &quot;A Protected Webpage&quot; &gt; private/index.html
</code></pre>
<p>Navigating to the url http://example.com/private/ should give you a white page with the text ‘A Protected Webpage’.</p>
<h2>Create The Password</h2>
<p>Since this is a LEMP stack we have PHP, so we have an easy way to generate the password to use on our protected directory:</p>
<pre><code>php -r 'echo crypt(&quot;password&quot;, &quot;salt&quot;);echo PHP_EOL;'
</code></pre>
<p>The resulting string of text is what we will use in our password file. It should go without saying — don’t use the word <strong>password</strong> as your password. As for <strong>salt</strong> part, that helps by giving some random magic to your password. For more info, checkout the manpage for the <strong>crypt</strong>: <code>man crypt</code></p>
<p>So, if we did in fact use the above values of ‘password’ and ‘salt’, the resulting string should give us <code>sa3tHJ3/KuYvI</code></p>
<p>We simply need to store this string in a file so that NGINX can access this. It is important to create this file outside of the server root. I prefer to put this near the HTML root so that it is easily accessible:</p>
<pre><code>cd /usr/share/nginx
nano .passwords
</code></pre>
<p>Now add the user and password to that .passwords file. For NGINX the syntax is username:passwordhash:anycomments, and the resulting text to paste is:</p>
<pre><code>benjamin:sa3tHJ3/KuYvI:Protecting the private folder
</code></pre>
<p>Save and exit nano.</p>
<h2>Update NGINX Server Conf to Actually Protect the Folder</h2>
<p>Finally we need to let NGINX know which folder to protect, and what file to check for valid user/password combinations. Edit the site configuration file for our site found in the sites-enabled folder. If it is an out of the box setup, that would be the <strong>default</strong> file:</p>
<pre><code>nano /etc/nginx/sites-enabled/default
</code></pre>
<p>Within the <code>server</code> block, you should see a <code>location</code> block that looks something like:</p>
<pre><code>location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass unix:/var/run/php5-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
}
</code></pre>
<p>Directly below this block (but still within the server block) add another location rule for any URLs that match our private folder, and which points to our passwords file:</p>
<pre><code>location ^~ /private/ {
auth_basic &quot;Restricted&quot;;
auth_basic_user_file /usr/share/nginx/.passwords;
}
</code></pre>
<p>Save this file and exit nano.</p>
<p>Now restart NGINX:</p>
<pre><code>service nginx restart
</code></pre>
<p>If all went well, visiting your protected folder in a browser should prompt you for your password!</p>
]]></description>
	</item>
	
	<item>
		<title>Discovering Browser-sync</title>
		<link>https://passionsplay.com/blog/discovering-browser-sync/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/discovering-browser-sync/</guid>
		<pubDate>Thu, 16 Oct 2014 04:57:45 GMT</pubDate>
		<description><![CDATA[<p>There’s a new tool that I’m loving as I explore various ways to streamline my multi-device development.</p>
<p>http://www.browsersync.io/</p>
<p>This tool is a node module, which synchronizes interactions between multiple browsers and devices, enabling multiple, concurrent views of the same state of a website.</p>
<p>So as an example, we would start browser-sync on the root directory of our site. A new node server listening to a specific port would be created on the local network, allowing any device to access that page. Each device that connects to this temporary site syncs various events such as scrolling, touch, active links, etc.</p>
<p>Additionally, when we start this server, we can set it to watch for changes to the underlying files so that all devices are updated when upon saving.</p>
<p>It’s like having LiveReload on steroids.</p>
<p>See the homepage linked to above for more in depth documentation, but really it’s a node module, so installing and immediately using browser-sync on your local site directory:</p>
<pre><code>npm install -g browser-sync
    browser-sync start --server --files &quot;css/*.css&quot;
</code></pre>
<p>Breaking down the second command:</p>
<p><code>browser-sync start</code></p>
<dd>Starts browser-sync.</dd>
<p><code>--server</code></p>
<dd>Tells browser-sync to use its built in server to serve the files. Alternatively we could tell it to use an existing server: <code>--proxy "myproject.dev"</code>.</dd>
<p><code>--files &quot;css/*.css&quot;</code></p>
<dd>What files do you want browser-sync to watch, and reload upon changes to the files. In this case we are watching any CSS files found in the css directory.</dd>]]></description>
	</item>
	
	<item>
		<title>Tools to Measure and Improve Battery Life on Thinkpads in Linux</title>
		<link>https://passionsplay.com/blog/tools-to-measure-and-improve-battery-life-on-thinkpads-in-linux/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/tools-to-measure-and-improve-battery-life-on-thinkpads-in-linux/</guid>
		<pubDate>Thu, 11 Sep 2014 04:53:44 GMT</pubDate>
		<description><![CDATA[<p>Here are some tools to start measuring and improving battery life in Linux.</p>
<h2>Measuring Power Performance</h2>
<p><a href="http://ibam.sourceforge.net/">ibam</a></p>
<h2>Working with Power Settings</h2>
<p>Tool to help set optimal battery settings:</p>
<p><a href="http://linrunner.de/en/tlp/docs/tlp-linux-advanced-power-management.html">http://linrunner.de/en/tlp/docs/tlp-linux-advanced-power-management.html</a></p>
<p>With has a decent intro on <a href="http://www.makeuseof.com/tag/easily-increase-battery-life-tlp-linux/">makeusof</a>.</p>
<p>To summarize, install using apt-get:</p>
<pre><code>sudo add-apt-repository ppa:linrunner/tlp
sudo apt-get update
sudo apt-get install tlp tlp-rdw
</code></pre>
<p><em>Optionall for ThinkPad:</em></p>
<pre><code>sudo apt-get install tp-smapi-dkms acpi-call-tools
</code></pre>
<p>Start the Service:</p>
<pre><code>sudo tlp start
</code></pre>
]]></description>
	</item>
	
	<item>
		<title>Vagrant Tools for WordPress Development</title>
		<link>https://passionsplay.com/blog/vagrant-tools-for-wordpress-development/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/vagrant-tools-for-wordpress-development/</guid>
		<pubDate>Tue, 01 Jul 2014 08:27:27 GMT</pubDate>
		<description><![CDATA[<p>The whole <a href="http://www.vagrantup.com/" title="Vagrant Provisioning Tool">Vagrant</a> ecosystem is developing rapidly with numerous tools being updated and created. While it’s hard to keep up with <a href="http://wptavern.com/13-vagrant-resources-for-wordpress-development" title="Vagrant WordPress Resources">all the new</a> individual software entrants, there are a couple of tools that I keep coming back to as I become more comfortable with my WordPress development workflow.</p>
<h2>Vagrant Tools</h2>
<ul>
<li><a href="https://github.com/Varying-Vagrant-Vagrants/VVV" title="VVV - Varying Vagrant Vagrants">VVV</a> (Varying Vagrant Vagrants)</li>
<li>Alison Barret’s <a href="https://github.com/aliso/vvv-site-wizard">VVV Site Wizard</a></li>
</ul>
]]></description>
	</item>
	
	<item>
		<title>Using Sed to Update PHP Short tags</title>
		<link>https://passionsplay.com/blog/using-sed-to-update-php-short-tags/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/using-sed-to-update-php-short-tags/</guid>
		<pubDate>Mon, 16 Dec 2013 03:21:57 GMT</pubDate>
		<description><![CDATA[<h2>Why?</h2>
<p>There are a couple of Stackoverflow discussions of <a href="http://stackoverflow.com/questions/4567292/overview-of-php-shorthand" title="Stackoverflow - PHP shorthand">what constitutes PHP shorthand</a>, and <a href="http://stackoverflow.com/questions/200640/are-php-short-tags-acceptable-to-use" title="Stackoverflow - Are PHP Short tags Acceptable to use?">why you would possibly not want to use them</a>.</p>
<p>For me, it boils down to portability. It is fairly easy to enable shorttags in the <code>php.ini</code> file by setting</p>
<pre><code>short_open_tag=On
</code></pre>
<p>However, I’d just as soon reduce the troubleshooting that I have to do if I ever move to a new environment. This happened recently as I was importing an old WordPress site into my <a href="https://github.com/bgturner/varying-vagrant-vagrants" title="VVV - Forked from 10up">Varying Vagrant Vagrants</a> machine (which has shorttags disabled by default).</p>
<h2>Using Sed</h2>
<p>I could have used search and replace inside of my editor, however, why not use the power of Sed to quickly replace the tags? Taken even further – why not create a bash script to iterate over all PHP files in a directory?</p>
<h3>The Sed command</h3>
<pre><code>sed 's:&lt;?=:&lt;?php echo:g' index.php | sed 's:&lt;?:&lt;?php:g' | sed 's:&lt;?phpphp:&lt;?php:g'
</code></pre>
<p>At the heart of this is three <a href="http://www.grymoire.com/Unix/Sed.html#uh-1" title="Sed Substitution">sed ‘substitution’ commands</a>, piped together.</p>
<p>The first replaces the echo short tag <code>&lt;?=</code> with the verbose <code>&lt;?php echo</code>. The next pipe replaces the basic opening short tag, while the last pipe reverts any tags that originally started out correctly, but were garbled by the second sed statement.</p>
<p>Also note the <strong>g</strong> at the end of each sed statement. This is the <a href="http://www.grymoire.com/Unix/Sed.html#uh-6" title="Sed Global Replacement Flag">global replacement flag</a>. This is necessary to ensure that sed will operate on all matches within a line of text, not just the first match.</p>
<h2>But it Doesn’t Update the File</h2>
<p>So the above code is good, there is only one problem, it only pipes the output to stdout. Additionally if you try and use our friend the greater than sign to pipe it into the original file – ‘index.php’ in the above example, <a href="http://stackoverflow.com/questions/5171901/sed-command-find-and-replace-in-file-and-overwrite-file-doesnt-work-it-empties" title="Stackoverflow - Sed command ... Empties file">it will completely wipe out that file</a>. Yikes!</p>
<p>Of the listed solutions in the above stackoverflow thread, I particularly like writing the contents of the pipe to a temporary file, and then moving the temporary file to the original file’s location. So our new command, which now writes the changes to the original file, looks like:</p>
<pre><code>sed 's:&lt;?=:&lt;?php echo:g' index.php | sed 's:&lt;?:&lt;?php:g' | sed 's:&lt;?phpphp:&lt;?php:g' &gt; index.php.tmp &amp;&amp; mv index.php.tmp index.php
</code></pre>
<h2>Script It!</h2>
<p>And finally, because it would be tedious to write out that command for <strong>every</strong> file that we want to update, lets make a script file that will run our sed command for every php file:</p>
<pre><code>#! /bin/bash
for f in *.php
do
echo &quot;Converting $f&quot;
sed 's:&lt;?=:&lt;?php echo:g' $f | sed 's:&lt;?:&lt;?php:g' | sed 's:&lt;?phpphp:&lt;?php:g' &gt; $f.tmp &amp;&amp; mv $f.tmp $f
done
</code></pre>
<p>Just create a new file, copy the above code to that file, make it executable, and run it!</p>
]]></description>
	</item>
	
	<item>
		<title>Using Vagrant for WordPress Development</title>
		<link>https://passionsplay.com/blog/using-vagrant-for-wordpress-development/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/using-vagrant-for-wordpress-development/</guid>
		<pubDate>Tue, 09 Jul 2013 10:02:36 GMT</pubDate>
		<description><![CDATA[<p><img src="vagrant-and-wordpress-300x125-ncuU4bXWBbCb.png" alt="Vagrant and WordPress Icons."></p>
<p>Creating WordPress Plugins and Themes is a fairly easy process. However, creating a new WP site on a local machine can be fairly tedious. Add to this the individual quirks of servers, and the setup differences between local and production environments and and you start to appreciate the benefits of a real sand-boxed solution.</p>
<p>The general idea of using Vagrant is the same as the idea and justification for using Virtualbox, or VMWare to run a development server on your local machine.</p>
<h2>What is Vagrant?</h2>
<p>Vagrant helps to build and automate virtual machines. Where it really shines is through its ability to <strong>provision</strong>, or configure the software that the server uses through simple configuration files.</p>
<p>Vagrant uses <strong>base boxes</strong>, common virtual servers as its building blocks for more specific virtual machines. If you are curious about all the different ways that people are using Vagrant, checkout a couple of good introductory articles:</p>
<ul>
<li><a href="http://docs.vagrantup.com/v2/why-vagrant/index.html" title="Why Vagrant?">Why Vagrant?</a> – From Vagrantup.com</li>
<li><a href="http://architects.dzone.com/articles/introduction-vagrant" title="Intro to Vagrant">Intro to Vagrant</a> – dzone.com</li>
<li><a href="http://www.linuxjournal.com/content/introducing-vagrant" title="Thorough but Older Intro">Thorough but Older Intro</a> – LinuxJournal.com</li>
<li><a href="http://docs.vagrantup.com/v2/installation/index.html" title="Installation">Installation</a> – From Vagrantup.com</li>
</ul>
<h2>Vagrant and WordPress</h2>
<p>Because Vagrant can be used to quickly turn a basic virtual server into a specialized WordPress installation, we can use it to easily share the specific settings for our development machine with other developers, as well as mimic the properties of the production server.</p>
<p>Luckily, there are a couple of very good Vagrant projects that have already been setup with WordPress.</p>
<h3>Varying Vagrant Vagrants</h3>
<p>Jeremy Felt has a good article outlining <em>why</em> he has started to use <a href="http://jeremyfelt.com/code/2013/04/08/hi-wordpress-meet-vagrant/" title="Vagrant for WordPress Development">Vagrant</a> for WordPress Development. Development is still progressing quickly, so to see the work that is currently being applied to the Vagrant Boxes running WordPress, checkout 10up’s <a href="https://github.com/10up/varying-vagrant-vagrants" title="Varying Vagrant Vagrants">Varying Vagrant Vagrants (VVV)</a> repo on Github. The code from these guys is really well documented, and the README.md file has a number of good jumping off points for more learning.</p>
<p>In my mind, there is only one real drawback with VVV — its complexity. Even though the code is well documented, there still is a lot going on. There certainly is a lot of <a href="https://github.com/10up/varying-vagrant-vagrants#what-do-you-get" title="power under the hood,">power under the hood</a>, but for smaller, simpler tasks, like a quick test of a new plugin or theme, I found myself wanting a simple, LAMP stack with only one <em>site</em> to choose from.</p>
<h3>Vagrantpress</h3>
<p>And then I found <a href="https://github.com/chad-thompson/vagrantpress" title="Vagrantpress.">Vagrantpress.</a> This is a much simpler setup – just a <a href="https://github.com/chad-thompson/vagrantpress/blob/master/puppet/manifests/init.pp" title="LAMP stack with GIT and ">LAMP stack with GIT and WordPress</a>.</p>
<p>Both of these projects are good for different reasons. In general, VVV is large and powerful — almost too much — but really the right tool for most jobs. But there is something powerful in Vagrantpress’ simplicity.</p>
<p>At the end of the day, both projects should be watched. Active development means that both will change and improve, making collabrative development much faster and much easier.</p>
]]></description>
	</item>
	
	<item>
		<title>Importing OVF Files Into Virtualbox</title>
		<link>https://passionsplay.com/blog/importing-ovf-files-into-virtualbox/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/importing-ovf-files-into-virtualbox/</guid>
		<pubDate>Sat, 16 Feb 2013 10:34:12 GMT</pubDate>
		<description><![CDATA[<p>In order to quickly create a new WordPress virtual machine, download a new appliance from <a href="http://www.turnkeylinux.org/wordpress" title="Turnkey Linux's WordPress Appliance">Turnkey Linux’s WordPress</a> page. For this specific post, download the ‘zip’ file from the ‘OVF’ link.</p>
<p>Once downloaded, unzip the file and hack away! See the screenshots for a quick overview, or dive in and read the article.</p>
<div class="gallery"><figure>
        <a href="/blog/importing-ovf-files-into-virtualbox/virtualbox-full.png" class="glightbox" data-description="The initial view of Virtualbox.">
          <img src="/blog/importing-ovf-files-into-virtualbox/virtualbox-full.png" alt="The initial view of Virtualbox.">
        </a>
        <figcaption>The initial view of Virtualbox.</figcaption>
      </figure>
<figure>
        <a href="/blog/importing-ovf-files-into-virtualbox/virtualbox-open_appliance-full.png" class="glightbox" data-description="The import new appliance dialogue.">
          <img src="/blog/importing-ovf-files-into-virtualbox/virtualbox-open_appliance-full.png" alt="The import new appliance dialogue.">
        </a>
        <figcaption>The import new appliance dialogue.</figcaption>
      </figure>
<figure>
        <a href="/blog/importing-ovf-files-into-virtualbox/virtualbox-import_settings-full.png" class="glightbox" data-description="">
          <img src="/blog/importing-ovf-files-into-virtualbox/virtualbox-import_settings-full.png" alt="">
        </a>
<p></figure></p>
<figure>
        <a href="/blog/importing-ovf-files-into-virtualbox/virtualbox-network_settings-full.png" class="glightbox" data-description="">
          <img src="/blog/importing-ovf-files-into-virtualbox/virtualbox-network_settings-full.png" alt="">
        </a>
<p></figure></p>
<figure>
        <a href="/blog/importing-ovf-files-into-virtualbox/virtualbox-appliance_import-full.png" class="glightbox" data-description="Virtualbox in the process of importing a new appliance.">
          <img src="/blog/importing-ovf-files-into-virtualbox/virtualbox-appliance_import-full.png" alt="Virtualbox in the process of importing a new appliance.">
        </a>
        <figcaption>Virtualbox in the process of importing a new appliance.</figcaption>
      </figure></div>
<h2>Importing a New Appliance</h2>
<ol>
<li>
<p>Select <strong>File</strong> &gt; <strong>Import Appliance</strong></p>
</li>
<li>
<p>Click on ‘<strong>Open appliance</strong>‘ and select the file with the ‘ovf’ extension.</p>
</li>
<li>
<p>Select <strong>Next</strong></p>
</li>
<li>
<p>The next screen is an overview of the settings contained in the ‘ovf’ file. In general the default settings should be good. The only thing I typically change is the first item ‘<strong>Name</strong>‘, changing it to something a little more meaningful.</p>
<p>Scroll down to the ‘<strong>Virtual Disk Image</strong>‘. This is the physical location of the ‘hard drive’ of this appliance and can be located anywhere on your actual hard drive. Simply make sure that the file that is pointed to ends with the ‘vmdk’ file extension.</p>
<p>This new ‘vmdk’ file will be created as a clone of the original, which is found in the same ‘zip’ archive as our ‘ovf’ file.</p>
</li>
<li>
<p>Click import, and watch as Virtualbox clones the disk and imports the settings!</p>
</li>
</ol>
<h2>Configuration and Settings</h2>
<p>When we start this appliance, we can think of it as physically turning on another computer. This computer will power on, execute various startup scripts, and most importantly for our purposes, <strong>connect to a network</strong>.<br>
We can specifically configure this connection, allowing the appliance to be available only to our machine, or appear as a completely separate machine on the local network.<br>
To edit an appliance’s network settings, click <strong>Machine</strong> &gt; <strong>Settings</strong> and select the <strong>Network</strong> item from the list on the left.</p>
<h3>Bridged Adapter</h3>
<p>The <a href="http://www.virtualbox.org/manual/ch06.html#idp11916480" title="bridged adapter">bridged adapter</a> is the default adapter for the <a href="http://www.turnkeylinux.org/wordpress" title="Wordpress appliance from Turnkey Linux.">WordPress appliance from Turnkey Linux.</a> Using this setting, the WordPress appliance will appear as a separate machine from our own computer on the local area network.<br>
So for situations where the local network is trusted, for example, your home, or work network, this is great! It allows other people to connect to this appliance and use it to test functionality or update content — whatever. To the network, this appliance is just another machine, a server, serving up WordPress content.</p>
<h3>Host-only Adapter</h3>
<p>So what to do if you don’t trust the local network? Sure, the coffee shop’s network <em>seems</em> safe, but all the same, let’s keep our WordPress development for our eyes only.<br>
For this, Virtualbox uses the ‘<a href="http://www.virtualbox.org/manual/ch06.html#network_hostonly" title="Host-only Adapter">Host-only Adapter</a>‘ to create – in essence – a network which exists as <strong>software only</strong>. The appliance is not connected to any physical hardware, so it cannot talk to the outside world, but neither can the outside world talk to it. Security!!<br>
Setting up our pretend network is easy. From Virtualbox’s main menu, select <strong>File</strong> &gt; <strong>Preferences</strong>, and select the <strong>Network</strong> item from the list on the left. Click the <strong>Add host-only network</strong> icon on the right.<br>
When we return to the machine’s settings and select ‘Host-only Adapter’, the name should default to the network we just created, which is ‘<strong>vboxnet0</strong>‘ in the screenshot above.</p>
<h2>Setup Complete</h2>
<p>There are of course other settings for Virtualbox’s network. Many of these are not necessarily useful to us for WordPress development, however, for more information on the various network settings available, see the Virtualbox manual: <a href="http://www.virtualbox.org/manual/ch06.html" title="Chapter 6: Virtual networking">Chapter 6: Virtual networking</a>.<br>
From here, our WordPress appliance is setup, but we still need to setup the actual WordPress installation on this appliance. To begin, select the appliance, press <strong>Start</strong> to turn it on, and follow the prompts to create the default passwords for our WordPress appliance.</p>
]]></description>
	</item>
	
	<item>
		<title>A Case for Virtual Machines</title>
		<link>https://passionsplay.com/blog/a-case-for-virtual-machines/</link>
		<guid isPermaLink="true">https://passionsplay.com/blog/a-case-for-virtual-machines/</guid>
		<pubDate>Sat, 16 Feb 2013 10:04:56 GMT</pubDate>
		<description><![CDATA[<h2>When Tinkering Gets Out of Hand</h2>
<p>As a content management system, WordPress excels at tracking your content. By simply applying a new theme, the feeling and tone of the underlying content can be perceived in new and expressive ways.</p>
<p>So, what do you do when you’re ready to go beyond tweaking a few lines in the CSS stylesheet and <em>really</em> begin developing your own themes and plugins for WordPress?</p>
<p>For starters, you can make the changes to files, live on your production server. This is quick, and works. But how long do you really want to be doing your development out in the open, for everyone to see?</p>
<p>Using <a href="http://www.apachefriends.org/en/xampp.html" title="XAMMP">XAMMP</a>, you <em>could</em> make your local machine into a small development server. Similarly, if you are on any of the Ubuntu flavors, you could use <a href="http://www.sudo-juice.com/install-lamp-server-ubuntu/" title="one line in the terminal">one line in the terminal</a> to download and install a fully functional LAMP server, complete with <a href="http://www.phpmyadmin.net/" title="phpMyAdmin">phpMyAdmin</a>:</p>
<pre><code>sudo apt-get install lamp-server^ phpmyadmin
</code></pre>
<p>This works well, and is a setup I use on my Xubuntu laptop to provide a lightweight sandbox for smaller PHP projects.</p>
<p>But at a certain point, setting up multiple WordPress installations becomes tedious.</p>
<h2>The VM Way</h2>
<p>A <a href="http://en.wikipedia.org/wiki/Virtual_machine" title="virtual machine">virtual machine</a> (VM) is a simulation of a machine. Because of this, you can essentially start and stop any number of VMs on one physical machine, the only real limit is the performance of the underlying hardware.</p>
<p>At the end of the day, this VM is just software, which means we can do ‘software things’ to it!</p>
<ul>
<li>For a backup, copy the machine to an external location.</li>
<li>To transfer a project to someone else, simply transfer the VM.</li>
<li>New projects can quickly be cloned from an existing VM.</li>
</ul>
<p>Doing any of these things using XAMMP, or the localhost of our development machine, would require copying the files, along with extracting content from the database. Do-able. And tedious.</p>
<p>And the <strong>drawbacks</strong> to VMs? There is one, and it is significant. VMs can be huge, taking up a few hundred megabytes of memory for each WordPress installation.</p>
<p>Still, the gains of a transferable package, and isolated environment, make a strong case for using a VM.</p>
<p>By the way, did I mention that a lot of the grunt work of setting up useful VMs has already been done:</p>
<ul>
<li><a href="http://www.turnkeylinux.org/" title="Turnkey Linux">Turnkey Linux</a></li>
<li><a href="http://bitnami.org/stacks" title="BitNami">BitNami</a></li>
<li><a href="http://drupal.org/project/quickstart" title="Drupal Quickstart">Drupal Quickstart</a></li>
<li><a href="http://www.oracle.com/technetwork/community/developer-vm/index.html" title="Oracle Developer VMs">Oracle Developer VMs</a></li>
<li><a href="http://www.microsoft.com/en-us/download/details.aspx?id=11575" title="Microsoft IE Application Compatability VPC Images">Microsoft IE Application Compatability VPC Images</a></li>
</ul>
<p>If you’re still intrigued: read about <a href="./Setting_Up_Virtualbox_in_Xubuntu.html" title="Setting Up Virtualbox in Xubuntu">Setting Up Virtualbox in Xubuntu</a> for WordPress Development.</p>
]]></description>
	</item>
	
</channel>
</rss>
