Nate Meyer's Bloghttps://nate.horse2022-07-18T03:30:15ZnateDaylog: keep notes while you work2023-11-14T03:13:51Zhttps://nate.horse/daylog<p>At <a href="https://useoptic.com" rel="nofollow">Optic</a> we share updates at the end of our days to keep the rest of the team in the loop with project status and general goings-on. We do this to try and work as asynchronously as much as we can get away with since we're spread across several timezones.</p>
<p>It's a simple process that works for us (and our admittedly small team): you write your update in whatever format you want and share it on Slack. We seem to have all gravitated to simple bulleted lists. It's pretty efficient; I spend a couple in the morning reading through updates and have a pretty good idea where everything is at.</p>
<p>How I write my updates has evolved with time. At first, I'd just wait until the end of the day and then try and write everything down directly in Slack. On busier days, I'd forget to mention something or have to go back to GitHub and refresh my memory. Not really what I want to do when I'm already mentally checked out.</p>
<p>Later, I moved on to using the Notes app, creating a new note each day, but notably, I started taking notes during the day as I was working. At the end of the day, I'd just copy and paste my notes. This was a vast improvement. While, the Notes app got the job done I never loved it and it felt very utilitarian.</p>
<p>The more I thought about it, the more I wanted something that kept me in the terminal. I'm certainly not the first person with this thought. After looking at a handful of other projects on GitHub they all had one major problem: I hadn't written them. But seriously, they either had too many features, or were too opinionated. In other words, they were someone else's ideal tool. I happen to also be "someone else", so I figured I'd just build my own.</p>
<p>After some messing around I came up with <a href="https://github.com/notnmeyer/daylog-cli" rel="nofollow">daylog</a>. It's not much than a shortcut to open <code>$EDITOR</code> to a specific day's file. Perfect!</p>
<p>Open today's log,</p>
<pre class="chroma"><code><span class="line"><span class="ln">1</span><span class="cl">> daylog
</span></span></code></pre><p>Open tomorrow's log and leave some notes for future me,</p>
<pre class="chroma"><code><span class="line"><span class="ln">1</span><span class="cl">> daylog -- tomorrow
</span></span></code></pre><p>The logs are Markdown and render beautifully in the terminal thanks to <a href="https://github.com/charmbracelet/glamour" rel="nofollow">Glamour</a>,
<img src="/daylog1.png" alt="daylog screenshot"></p>
<p>The raw Markdown can be displayed as well; useful for when I'm copying the notes out at the end of the day,
<img src="/daylog2.png" alt="daylog screenshot 2"></p>
<p>I've been using it for a few weeks now and it fits into my workflow well. Maybe this is useful to someone else out there, but you should probably just build your own!</p>
<p><img src="/dayman.jpg" alt="dayman"></p>
Becoming a notetaker2023-03-17T05:08:50Zhttps://nate.horse/becoming-a-notetaker<p>For a long time I envied friends and coworkers that were disciplined pen-and-paper notetakers. They'd sketch diagrams and write down their thoughts—their notebooks were journeys through their creative process. When I tried, it felt forced. It was difficult to jot my thoughts down. I needed to stew on them, make sure they were good before they were worth writing down. I knew that wasn't how it was supposed to work, but I was blocked regardless. I made myself a pair of cut-off jean shorts and resigned myself to my fate as a never-note.</p>
<p><img src="/nevernude.webp" alt="nevernude"></p>
<p>A friend who has been a long-time notetaker was telling me recently about the japanese notebooks he liked and the pens he used. I ordered his preferred setup: A Midori MD notebook and an Ohto Rays pen.</p>
<p>After they arrived they sat on my desk, mocking me with their pristine, unblemished appearance. Once again, I was worried that I would write something unworthy and somehow ruin the notebook. Stuck again.</p>
<p>A month or so later I had some work that need brainstorming and planning, and for whatever reason I was motivated to try this notetaking thing again. I picked up the pen, opened the notebook to the first page, and just wrote.</p>
<p>This time was different! Whatever my hang up was, I broke through it. Now I was brainstorming and writing down my thoughts, the bad ones and the less bad ones, as they came to me. It felt amazing.</p>
<p>Several months on, I'm still doing it. I have a notebook for work, a notebook for personal projects, a couple waiting in the closet, and space on the bookshelf that needs filling up.</p>
Introducing Tsk2023-03-15T21:29:45Zhttps://nate.horse/introducing-tsk<p>I've been working on my own task runner, <a href="https://github.com/notnmeyer/tsk" rel="nofollow">tsk</a>. I don't know how to pronounce it. I'm criminally terrible at naming things, so the best I could up come up with was just dropping the vowel from "task". Maybe it's an acronym? Who knows.</p>
<p>Anyway, it's a single binary without dependencies and uses TOML to describe tasks.</p>
<p>Here's the requisite "Hello, world!" example,</p>
<pre class="chroma"><code><span class="line"><span class="ln">1</span><span class="cl"><span class="p">[</span><span class="nx">tasks</span><span class="p">.</span><span class="nx">hello_world</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nx">env</span> <span class="p">=</span> <span class="p">{</span><span class="nx">msg</span> <span class="p">=</span> <span class="s2">"Hello, world!"</span><span class="p">}</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="nx">cmds</span> <span class="p">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"> <span class="s2">"echo $msg"</span><span class="p">,</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="p">]</span>
</span></span></code></pre><p>It's pretty simple and bare-bones but has a couple features that I think are neat.</p>
<h3 id="opinionated-support-for-scripts"><a class="anchor" href="#opinionated-support-for-scripts" rel="nofollow">#</a> Opinionated support for scripts</h3>
<p>I've shared my rambling thoughts on task runners <a href="https://nate.prose.sh/task-runners-are-awesome" rel="nofollow">recently</a> and one of my points was including too much shell in a YAML/TOML/whatever task config was an anti-pattern.</p>
<p><code>tsk</code> is opinionated about making it simple to connect a task and a script. When the <code>cmd</code> field is omitted, <code>tsk</code> will look for a script with the same name of the task in the <code>scripts</code> directory. So given the task definition,</p>
<pre class="chroma"><code><span class="line"><span class="ln">1</span><span class="cl"><span class="p">[</span><span class="nx">tasks</span><span class="p">.</span><span class="nx">hello_world</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="nx">env</span> <span class="p">=</span> <span class="p">{</span><span class="nx">msg</span> <span class="p">=</span> <span class="s2">"Hello, world!"</span><span class="p">}</span>
</span></span></code></pre><p><code>tsk</code> will execute <code>scripts/hello_world.sh</code>.</p>
<h3 id="dependency-groups"><a class="anchor" href="#dependency-groups" rel="nofollow">#</a> Dependency groups</h3>
<p>Dependencies or prereqrusites are common features in task runners. What I haven't seen, but wanted, was the ability to express more complicated dependency ordering. In <code>tsk</code>, dependencies are other tasks expressed as nested lists. Tasks within a group run in parallel, while the groups run sequentially.</p>
<p>Example time! This task definition,</p>
<pre class="chroma"><code><span class="line"><span class="ln"> 1</span><span class="cl"><span class="p">[</span><span class="nx">tasks</span><span class="p">.</span><span class="nx">dep_groups</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="nx">deps</span> <span class="p">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"> <span class="p">[</span><span class="s2">"setup1"</span><span class="p">,</span> <span class="s2">"setup2"</span><span class="p">],</span> <span class="c"># setup1 and setup2 run in parallel</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl"> <span class="p">[</span><span class="s2">"setup3"</span><span class="p">],</span> <span class="c"># setup3 runs after the tasks in the previous group complete</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="nx">cmds</span> <span class="p">=</span> <span class="p">[</span><span class="s2">"echo 'running cmd...'"</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl">
</span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="p">[</span><span class="nx">tasks</span><span class="p">.</span><span class="nx">setup1</span><span class="p">]</span>
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="nx">cmds</span> <span class="p">=</span> <span class="p">[</span><span class="s2">"sleep 1"</span><span class="p">,</span> <span class="s2">"echo 'doing setup1...'"</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">
</span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="p">[</span><span class="nx">tasks</span><span class="p">.</span><span class="nx">setup2</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="nx">cmds</span> <span class="p">=</span> <span class="p">[</span><span class="s2">"echo 'doing setup2...'"</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">13</span><span class="cl">
</span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="p">[</span><span class="nx">tasks</span><span class="p">.</span><span class="nx">setup3</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="nx">cmds</span> <span class="p">=</span> <span class="p">[</span><span class="s2">"echo 'doing setup3...'"</span><span class="p">]</span>
</span></span></code></pre><p>produces the output,</p>
<pre class="chroma"><code><span class="line"><span class="ln">1</span><span class="cl">➜ tsk dep_groups
</span></span><span class="line"><span class="ln">2</span><span class="cl">doing setup2...
</span></span><span class="line"><span class="ln">3</span><span class="cl">doing setup1...
</span></span><span class="line"><span class="ln">4</span><span class="cl">doing setup3...
</span></span><span class="line"><span class="ln">5</span><span class="cl">running cmd...
</span></span></code></pre><p><code>tsk</code> has been working well for me on small projects—I even use it to build and release <code>tsk</code> itself!</p>
Task runners are awesome2023-03-15T03:16:21Zhttps://nate.horse/task-runners-are-awesome<p>I don't know what it is, but I really like task runners. Something about taking the various commands you need to build and run software and turning them into something repeatable and consistent appeals to me.</p>
<p>Like a lot of people, I got started with <a href="https://www.gnu.org/software/make/" rel="nofollow">Make</a>. Make and I never really hit it off. I don't think I ever really got it—I blame the terse syntax. If you're thinking I never learned it properly or gave it enough time, then you would be correct. Regardless, I don't like the way it looks.</p>
<p>When I was writing a lot of Ruby, I used and liked Rake, but Rake is way too heavy to use outside of Ruby projects (and it's not viable a tool for running shell anyway). To varying degrees, the same is true for any runner written in an interpretted language that requires you bring an entire toolchain with it.</p>
<p>A few years ago I came across <a href="https://taskfile.dev" rel="nofollow">Task</a>. There was a lot to like, namely that it was a single binary without other dependencies, and its task format is YAML.</p>
<p>Being YAML makes reading and writing the tasks significantly more accessible to folks. Make requires you're familiar with Make. Good luck to anyone opening someone else's complex Makefile for the first time. But someone who has never heard of Task can be reasonably expected to understand what's happening and probably be capable of contributing in only a few minutes.</p>
<p>There are downsides too. Writing shell in YAML is fine to the point you cross some invisible complexity threshold. Shell embedded in YAML files doesn't get nice things like syntax highlighting or tools like <a href="https://shellcheck.net" rel="nofollow">ShellCheck</a>. This isn't a problen unique to Task or task runners, there's lots of tooling where you wind up embedding code in YAML.</p>
<p>Here are a couple common pitfalls, using Task's YAML syntax:</p>
<p>Minor syntax flubs that aren't caught by your editor's tools or highlighting,</p>
<pre class="chroma"><code><span class="line"><span class="ln">1</span><span class="cl"><span class="nt">run</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w"> </span><span class="nt">cmds</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w"> </span>- <span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="sd"> if [ "$BLAH" = BLAH ] then
</span></span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="sd"> echo "do something"
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="sd"> fi</span><span class="w">
</span></span></span></code></pre><p>The example above is contrived and might be obvious, but toss that into a 200 line YAML file and its easier than you'd think to miss. Especially if you don't test your tasks or its something that's not running often or automatically.</p>
<p>Or you have something more complex that would normally be a shell script,</p>
<pre class="chroma"><code><span class="line"><span class="ln"> 1</span><span class="cl"><span class="nt">run</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="w"> </span><span class="nt">cmds</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w"> </span>- <span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="sd"> branch=$(git branch --show-current)
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="sd"> if [ "$branch" = "main" ]; then
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="sd"> echo "do something"
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="sd"> else
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="sd"> echo "do another thing
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="sd"> fi
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="sd">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="sd"> if [ some_other_logic ]; then
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="sd"> do_something
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="sd"> fi
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="sd"> ...</span><span class="w">
</span></span></span></code></pre><p>Nothing wrong with this example, but over time as that changes it'll be increasingly difficult to change and track the code paths. You can solve this by writing tests for the tasks, but that only works if the tasks are easy to test and free of side effects. A task that deploys software or pushes a Docker image to a registry might be more difficult to test in an automated way. Trying to mock or otherwise create a dry-run mode for the code will make the problem worse.</p>
<p>So what do you do? You can destructure a complex bit of shell into smaller tasks,</p>
<pre class="chroma"><code><span class="line"><span class="ln"> 1</span><span class="cl"><span class="nt">do-something</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="w"> </span><span class="nt">cmds</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w"> </span>- <span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="sd"> branch=$(git branch --show-current)
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="sd"> if [ "$BRANCH" = "main" ]; then
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="sd"> echo "do something"
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="sd"> else
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="sd"> echo "do another thing
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="sd"> fi</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="w"></span><span class="nt">do-something-else</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="w"> </span><span class="nt">cmds</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">13</span><span class="cl"><span class="w"> </span>- <span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="ln">14</span><span class="cl"><span class="sd"> if [ some_other_logic ]; then
</span></span></span><span class="line"><span class="ln">15</span><span class="cl"><span class="sd"> do_something_else
</span></span></span><span class="line"><span class="ln">16</span><span class="cl"><span class="sd"> fi</span><span class="w">
</span></span></span><span class="line"><span class="ln">17</span><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="ln">18</span><span class="cl"><span class="w"></span><span class="nt">run</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">19</span><span class="cl"><span class="w"> </span><span class="nt">cmds</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">20</span><span class="cl"><span class="w"> </span>- <span class="nt">task</span><span class="p">:</span><span class="w"> </span><span class="l">do-something</span><span class="w">
</span></span></span><span class="line"><span class="ln">21</span><span class="cl"><span class="w"> </span>- <span class="nt">task</span><span class="p">:</span><span class="w"> </span><span class="l">do-something-else</span><span class="w">
</span></span></span></code></pre><p>This is better in the sense that we've made it simpler to think about only a bit of the shell at a time, but we haven't actually reduced the complexity—we might have even increased it. The code may not be any easier to test. This is a somewhat poor approximation of using Bash functions. So why not just do that?</p>
<p>Another option (and a superior one in my opinion) is to just use a shell script,</p>
<pre class="chroma"><code><span class="line"><span class="ln">1</span><span class="cl"><span class="nt">run</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="w"> </span><span class="nt">cmds</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="w"> </span>- <span class="l">./scripts/do-something</span><span class="w">
</span></span></span></code></pre><p>Now you get syntax highlighting and ShellCheck support! The complexity isn't automatically lowered, but because our code is now in a plain 'ole script it's simpler to implement dry-run support, or otherwise make the code more readily testable.</p>
<p>This is a long way to say that embedding shell (or any code) in a non-native task runner (think shell in YAML/TOML, rather than Ruby in a Rakefile) is only suitable for trivial cases. Once you have more than a few commands, or more than the most trivial branching logic it's better to let the shell be shell.</p>
<h2 id="so-why-use-a-task-runner-at-all"><a class="anchor" href="#so-why-use-a-task-runner-at-all" rel="nofollow">#</a> So why use a task runner at all?</h2>
<p>If we're keeping the embedded code in our tasks to a minimum, why use them at all? Why not just have a <code>scripts</code> directory?</p>
<p>One of a task runner's greatest utilities is in being the documentation on how to interact with a piece of software. Think about a monorepo that may have projects written in different languages. We can reduce the mental overhead of needing to think about the project's toolchain, and have a consistent interface across projects. To build a project, run <code>build</code>, to deploy a project, run <code>deploy</code>.</p>
<p>Task also makes it simple to see the common interactions with a project. <code>task --list</code> to display tasks, and <code>task <task_name> --summary</code> to view what it does.</p>
<p>To me, this makes far more sense than a folder of scripts and manual documentation.</p>
<h2 id="whats-your-point"><a class="anchor" href="#whats-your-point" rel="nofollow">#</a> What's your point?</h2>
<ul>
<li>Task runners are good and you should use one.</li>
<li>Be wary of embedding more than a minimum amount of $SOME_LANGUAGE in YAML/TOML/whatever tasks.</li>
<li>Do write scripts and call them from your tasks.</li>
<li>Treat your tasks as the documentation and entry points to interact with running and building your application.</li>
</ul>