<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Nathan Tebbs</title>
    <link>https://vetr0s.dev/</link>
    <description>Notes on software and the things I learn while building.</description>
    <language>en</language>
    <lastBuildDate>Fri, 23 Oct 2026 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://vetr0s.dev/rss.xml" rel="self" type="application/rss+xml"></atom:link>
    <item>
      <title>Adding swag to my zero-dependency static website generator</title>
      <link>https://vetr0s.dev/posts/adding-swag-to-my-zero-dependency-static-website-generator.html</link>
      <guid isPermaLink="true">https://vetr0s.dev/posts/adding-swag-to-my-zero-dependency-static-website-generator.html</guid>
      <pubDate>Fri, 23 Oct 2026 00:00:00 +0000</pubDate>
      <description>&lt;p&gt;A month ago I wrote about building this site with nothing but Emacs&#xA;and Org mode. I called it a &lt;a&#xA;href=&#34;zero-dependency-static-site-generator.html&#34;&gt;zero-dependency static&#xA;website generator&lt;/a&gt;. This site now builds with a tool I wrote instead.&#xA;It is called swag, short for Static Website Assembler and Generator. The&#xA;switch made me count what the site really depends on, and the old count&#xA;was wrong.&lt;/p&gt;&#xA;&lt;h2 id=&#34;what-changed&#34;&gt;What changed&lt;/h2&gt;&#xA;&lt;p&gt;The posts are now Markdown files, and I write them in neovim. A&#xA;single Go binary called &lt;code&gt;swag&lt;/code&gt; turns the&#xA;&lt;code&gt;content/&lt;/code&gt; directory into the &lt;code&gt;docs/&lt;/code&gt; directory&#xA;that GitHub Pages serves. Every URL from the Org version still works, so&#xA;old links and the RSS feed did not move.&lt;/p&gt;&#xA;&lt;p&gt;The repository looks like this now:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;content/    Markdown pages and posts&#xA;layouts/    HTML templates that wrap each page&#xA;static/     CSS, images and CNAME, copied as is&#xA;docs/       generated site served by GitHub Pages&#xA;swag.conf   site title, URL and description&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;The config is a plain &lt;code&gt;key = value&lt;/code&gt; file:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;site.title        = Nathan Tebbs&#xA;site.url          = https://vetr0s.dev&#xA;site.description  = Notes on software and the things I learn while building.&#xA;params.stylesheet = /style.css&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Every post starts with a front matter block:&lt;/p&gt;&#xA;&lt;div class=&#34;sourceCode&#34; id=&#34;cb3&#34;&gt;&lt;pre&#xA;class=&#34;sourceCode markdown&#34;&gt;&lt;code class=&#34;sourceCode markdown&#34;&gt;&lt;span id=&#34;cb3-1&#34;&gt;&lt;a href=&#34;#cb3-1&#34; aria-hidden=&#34;true&#34; tabindex=&#34;-1&#34;&gt;&lt;/a&gt;&lt;span class=&#34;co&#34;&gt;---&lt;/span&gt;&lt;/span&gt;&#xA;&lt;span id=&#34;cb3-2&#34;&gt;&lt;a href=&#34;#cb3-2&#34; aria-hidden=&#34;true&#34; tabindex=&#34;-1&#34;&gt;&lt;/a&gt;&lt;span class=&#34;an&#34;&gt;title:&lt;/span&gt;&lt;span class=&#34;co&#34;&gt; Adding swag to my zero-dependency static website generator&lt;/span&gt;&lt;/span&gt;&#xA;&lt;span id=&#34;cb3-3&#34;&gt;&lt;a href=&#34;#cb3-3&#34; aria-hidden=&#34;true&#34; tabindex=&#34;-1&#34;&gt;&lt;/a&gt;&lt;span class=&#34;an&#34;&gt;date:&lt;/span&gt;&lt;span class=&#34;co&#34;&gt; 2026-10-23&lt;/span&gt;&lt;/span&gt;&#xA;&lt;span id=&#34;cb3-4&#34;&gt;&lt;a href=&#34;#cb3-4&#34; aria-hidden=&#34;true&#34; tabindex=&#34;-1&#34;&gt;&lt;/a&gt;&lt;span class=&#34;an&#34;&gt;description:&lt;/span&gt;&lt;span class=&#34;co&#34;&gt; Swapping Emacs for a small Go program.&lt;/span&gt;&lt;/span&gt;&#xA;&lt;span id=&#34;cb3-5&#34;&gt;&lt;a href=&#34;#cb3-5&#34; aria-hidden=&#34;true&#34; tabindex=&#34;-1&#34;&gt;&lt;/a&gt;&lt;span class=&#34;co&#34;&gt;---&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;That is not YAML. Each line is a key, a colon and a value. swag has&#xA;no YAML parser, and it doesn&#39;t need one.&lt;/p&gt;&#xA;&lt;h2 id=&#34;why-i-left-org-publish&#34;&gt;Why I left Org Publish&lt;/h2&gt;&#xA;&lt;p&gt;My editor changed. I moved my day-to-day work from Emacs to neovim,&#xA;and the build had to move out of the editor with it. The Org setup only&#xA;ran inside Emacs, either through &lt;code&gt;M-x&lt;/code&gt; or through a batch&#xA;Emacs process. Keeping Emacs installed just to publish a blog felt&#xA;backwards.&lt;/p&gt;&#xA;&lt;p&gt;I also wanted a few things Org Publish made awkward:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;A preview server that rebuilds and reloads the browser when I&#xA;save.&lt;/li&gt;&#xA;&lt;li&gt;A build that fails on a broken internal link.&lt;/li&gt;&#xA;&lt;li&gt;Errors that name a file and a line, so neovim can jump to them.&lt;/li&gt;&#xA;&lt;li&gt;Output that stays byte-identical between builds, so&#xA;&lt;code&gt;git diff docs/&lt;/code&gt; shows only real changes.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;I did not want a framework. swag has no plugin system, no themes, no&#xA;tags and no pagination. It is about 2,200 lines of Go plus 1,400 lines&#xA;of tests, and I can read the whole thing in an afternoon.&lt;/p&gt;&#xA;&lt;h2 id=&#34;counting-dependencies-honestly&#34;&gt;Counting dependencies&#xA;honestly&lt;/h2&gt;&#xA;&lt;p&gt;&#34;Zero dependency&#34; was never literal. Here is how I count now.&lt;/p&gt;&#xA;&lt;p&gt;Level 0 is the platform the generator runs on. Every option needs&#xA;one. Before, the platform was Emacs. Org mode ships with Emacs, so I&#xA;counted Org Publish as free. That was only true because Emacs was&#xA;already my editor. Syntax highlighting also needed &lt;code&gt;htmlize&lt;/code&gt;,&#xA;which is a third-party package. Doom Emacs happened to install it for&#xA;me, so I never noticed.&lt;/p&gt;&#xA;&lt;p&gt;Now the platform is Go. Go compiles swag, and swag uses only the&#xA;standard library. Its &lt;code&gt;go.mod&lt;/code&gt; has no &lt;code&gt;require&lt;/code&gt;&#xA;lines:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;module github.com/vetr0s/swag&#xA;&#xA;go 1.27.3&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;A test fails if that ever changes. The compiled result is one 13 MB&#xA;binary, and running it needs no Go toolchain.&lt;/p&gt;&#xA;&lt;p&gt;That leaves one real addition, which is pandoc. swag contains no&#xA;Markdown parser. For every &lt;code&gt;.md&lt;/code&gt; file it runs a command from&#xA;the config, writes the post body to that command&#39;s stdin, and reads HTML&#xA;back from its stdout. The default command is pandoc:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;render.md = pandoc --from=gfm --to=html5&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Pandoc also does the syntax highlighting, so &lt;code&gt;htmlize&lt;/code&gt; is&#xA;gone. A site written in plain &lt;code&gt;.html&lt;/code&gt; files would not need&#xA;pandoc at all.&lt;/p&gt;&#xA;&lt;p&gt;So the honest before and after:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;before:  Emacs (platform) + Org (bundled) + htmlize&#xA;after:   Go (platform)    + pandoc&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;I swapped one platform for another and traded &lt;code&gt;htmlize&lt;/code&gt;&#xA;for pandoc. The count did not drop to zero. It stayed at one, and this&#xA;time I can name it.&lt;/p&gt;&#xA;&lt;h2 id=&#34;how-i-use-it&#34;&gt;How I use it&lt;/h2&gt;&#xA;&lt;p&gt;Writing a post starts with &lt;code&gt;swag new&lt;/code&gt;. It prints only the&#xA;path of the file it creates, so it composes with the editor:&lt;/p&gt;&#xA;&lt;div class=&#34;sourceCode&#34; id=&#34;cb7&#34;&gt;&lt;pre class=&#34;sourceCode sh&#34;&gt;&lt;code class=&#34;sourceCode bash&#34;&gt;&lt;span id=&#34;cb7-1&#34;&gt;&lt;a href=&#34;#cb7-1&#34; aria-hidden=&#34;true&#34; tabindex=&#34;-1&#34;&gt;&lt;/a&gt;&lt;span class=&#34;ex&#34;&gt;nvim&lt;/span&gt; &lt;span class=&#34;st&#34;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&#34;va&#34;&gt;$(&lt;/span&gt;&lt;span class=&#34;ex&#34;&gt;swag&lt;/span&gt; new &lt;span class=&#34;st&#34;&gt;&amp;#39;Adding swag to my zero-dependency static website generator&amp;#39;&lt;/span&gt;&lt;span class=&#34;va&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;st&#34;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;The new file starts as a draft. While I write,&#xA;&lt;code&gt;swag serve&lt;/code&gt; runs in another terminal. It builds into a&#xA;temporary directory, never into &lt;code&gt;docs/&lt;/code&gt;, and serves the&#xA;result on localhost. It checks for changes every 500 milliseconds and&#xA;reloads the browser after each rebuild.&lt;/p&gt;&#xA;&lt;p&gt;Inside neovim, &lt;code&gt;swag build&lt;/code&gt; works as a make program:&lt;/p&gt;&#xA;&lt;pre class=&#34;vim&#34;&gt;&lt;code&gt;:set makeprg=swag\ build&#xA;:make&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;Errors come out as &lt;code&gt;path:line: message&lt;/code&gt;, so a bad date or&#xA;a broken link lands in the quickfix list:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;content/posts/about-me.md:3: invalid date &amp;quot;2026-13-01&amp;quot;: use YYYY-MM-DD&#xA;content/posts/colophon.md: broken link &amp;quot;/posts/old.html&amp;quot;&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;A full build of this site takes under a second. Nearly all of that is&#xA;pandoc rendering this post, which is the longest one. swag builds into a&#xA;temporary directory and replaces &lt;code&gt;docs/&lt;/code&gt; only after every&#xA;step has passed. A broken post can&#39;t leave half a site behind.&lt;/p&gt;&#xA;&lt;p&gt;Publishing did not change. I run &lt;code&gt;swag build&lt;/code&gt;, commit&#xA;&lt;code&gt;docs/&lt;/code&gt; and push, and GitHub Pages serves the files.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-neovim-plugin&#34;&gt;The neovim plugin&lt;/h2&gt;&#xA;&lt;p&gt;Since writing this I added &lt;a&#xA;href=&#34;https://github.com/vetr0s/swag.nvim&#34;&gt;swag.nvim&lt;/a&gt;, a small plugin&#xA;that runs swag from inside neovim. It replaces the &lt;code&gt;makeprg&lt;/code&gt;&#xA;setup above. It is about 150 lines of Lua and has no dependencies beyond&#xA;swag itself.&lt;/p&gt;&#xA;&lt;p&gt;It adds four commands:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;:SwagNew {title}    create a draft post and open it (:SwagNew! makes a page)&#xA;:SwagBuild          build in the background; errors go to the quickfix list&#xA;:SwagServe          start the preview server&#xA;:SwagStop           stop it&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;While &lt;code&gt;:SwagServe&lt;/code&gt; runs, every rebuild replaces the&#xA;quickfix list with that rebuild&#39;s errors. A bad date shows up about a&#xA;second after I save, and the list empties when I fix it. Broken links&#xA;show up as warnings until I build for real. Every command runs swag from&#xA;the current buffer&#39;s directory, so it finds the site from any file in&#xA;it.&lt;/p&gt;&#xA;&lt;p&gt;Neovim 0.12 has a built-in package manager, so installing it is one&#xA;line in my config:&lt;/p&gt;&#xA;&lt;div class=&#34;sourceCode&#34; id=&#34;cb11&#34;&gt;&lt;pre&#xA;class=&#34;sourceCode lua&#34;&gt;&lt;code class=&#34;sourceCode lua&#34;&gt;&lt;span id=&#34;cb11-1&#34;&gt;&lt;a href=&#34;#cb11-1&#34; aria-hidden=&#34;true&#34; tabindex=&#34;-1&#34;&gt;&lt;/a&gt;&lt;span class=&#34;kw&#34;&gt;local&lt;/span&gt; &lt;span class=&#34;va&#34;&gt;gh&lt;/span&gt; &lt;span class=&#34;op&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;kw&#34;&gt;function&lt;/span&gt;&lt;span class=&#34;op&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;va&#34;&gt;repo&lt;/span&gt;&lt;span class=&#34;op&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;cf&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;st&#34;&gt;&amp;#39;https://github.com/&amp;#39;&lt;/span&gt; &lt;span class=&#34;op&#34;&gt;..&lt;/span&gt; &lt;span class=&#34;va&#34;&gt;repo&lt;/span&gt; &lt;span class=&#34;kw&#34;&gt;end&lt;/span&gt;&lt;/span&gt;&#xA;&lt;span id=&#34;cb11-2&#34;&gt;&lt;a href=&#34;#cb11-2&#34; aria-hidden=&#34;true&#34; tabindex=&#34;-1&#34;&gt;&lt;/a&gt;&lt;/span&gt;&#xA;&lt;span id=&#34;cb11-3&#34;&gt;&lt;a href=&#34;#cb11-3&#34; aria-hidden=&#34;true&#34; tabindex=&#34;-1&#34;&gt;&lt;/a&gt;&lt;span class=&#34;va&#34;&gt;vim&lt;/span&gt;&lt;span class=&#34;op&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;va&#34;&gt;pack&lt;/span&gt;&lt;span class=&#34;op&#34;&gt;.&lt;/span&gt;add&lt;span class=&#34;op&#34;&gt;({&lt;/span&gt;&lt;/span&gt;&#xA;&lt;span id=&#34;cb11-4&#34;&gt;&lt;a href=&#34;#cb11-4&#34; aria-hidden=&#34;true&#34; tabindex=&#34;-1&#34;&gt;&lt;/a&gt;  gh&lt;span class=&#34;op&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;st&#34;&gt;&amp;#39;vetr0s/swag.nvim&amp;#39;&lt;/span&gt;&lt;span class=&#34;op&#34;&gt;),&lt;/span&gt;&lt;/span&gt;&#xA;&lt;span id=&#34;cb11-5&#34;&gt;&lt;a href=&#34;#cb11-5&#34; aria-hidden=&#34;true&#34; tabindex=&#34;-1&#34;&gt;&lt;/a&gt;&lt;span class=&#34;op&#34;&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;There is no &lt;code&gt;setup()&lt;/code&gt; call. If you want keys for the&#xA;commands, map them like any other:&lt;/p&gt;&#xA;&lt;div class=&#34;sourceCode&#34; id=&#34;cb12&#34;&gt;&lt;pre&#xA;class=&#34;sourceCode lua&#34;&gt;&lt;code class=&#34;sourceCode lua&#34;&gt;&lt;span id=&#34;cb12-1&#34;&gt;&lt;a href=&#34;#cb12-1&#34; aria-hidden=&#34;true&#34; tabindex=&#34;-1&#34;&gt;&lt;/a&gt;&lt;span class=&#34;va&#34;&gt;vim&lt;/span&gt;&lt;span class=&#34;op&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;va&#34;&gt;keymap&lt;/span&gt;&lt;span class=&#34;op&#34;&gt;.&lt;/span&gt;set&lt;span class=&#34;op&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;st&#34;&gt;&amp;#39;n&amp;#39;&lt;/span&gt;&lt;span class=&#34;op&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;st&#34;&gt;&amp;#39;&amp;lt;leader&amp;gt;wn&amp;#39;&lt;/span&gt;&lt;span class=&#34;op&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;st&#34;&gt;&amp;#39;:SwagNew &amp;#39;&lt;/span&gt;&lt;span class=&#34;op&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;op&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;va&#34;&gt;desc&lt;/span&gt; &lt;span class=&#34;op&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;st&#34;&gt;&amp;#39;New post&amp;#39;&lt;/span&gt; &lt;span class=&#34;op&#34;&gt;})&lt;/span&gt;&lt;/span&gt;&#xA;&lt;span id=&#34;cb12-2&#34;&gt;&lt;a href=&#34;#cb12-2&#34; aria-hidden=&#34;true&#34; tabindex=&#34;-1&#34;&gt;&lt;/a&gt;&lt;span class=&#34;va&#34;&gt;vim&lt;/span&gt;&lt;span class=&#34;op&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;va&#34;&gt;keymap&lt;/span&gt;&lt;span class=&#34;op&#34;&gt;.&lt;/span&gt;set&lt;span class=&#34;op&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;st&#34;&gt;&amp;#39;n&amp;#39;&lt;/span&gt;&lt;span class=&#34;op&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;st&#34;&gt;&amp;#39;&amp;lt;leader&amp;gt;wb&amp;#39;&lt;/span&gt;&lt;span class=&#34;op&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;st&#34;&gt;&amp;#39;&amp;lt;cmd&amp;gt;SwagBuild&amp;lt;CR&amp;gt;&amp;#39;&lt;/span&gt;&lt;span class=&#34;op&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;op&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;va&#34;&gt;desc&lt;/span&gt; &lt;span class=&#34;op&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;st&#34;&gt;&amp;#39;Build the site&amp;#39;&lt;/span&gt; &lt;span class=&#34;op&#34;&gt;})&lt;/span&gt;&lt;/span&gt;&#xA;&lt;span id=&#34;cb12-3&#34;&gt;&lt;a href=&#34;#cb12-3&#34; aria-hidden=&#34;true&#34; tabindex=&#34;-1&#34;&gt;&lt;/a&gt;&lt;span class=&#34;va&#34;&gt;vim&lt;/span&gt;&lt;span class=&#34;op&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;va&#34;&gt;keymap&lt;/span&gt;&lt;span class=&#34;op&#34;&gt;.&lt;/span&gt;set&lt;span class=&#34;op&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;st&#34;&gt;&amp;#39;n&amp;#39;&lt;/span&gt;&lt;span class=&#34;op&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;st&#34;&gt;&amp;#39;&amp;lt;leader&amp;gt;ws&amp;#39;&lt;/span&gt;&lt;span class=&#34;op&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;st&#34;&gt;&amp;#39;&amp;lt;cmd&amp;gt;SwagServe&amp;lt;CR&amp;gt;&amp;#39;&lt;/span&gt;&lt;span class=&#34;op&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;op&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;va&#34;&gt;desc&lt;/span&gt; &lt;span class=&#34;op&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;st&#34;&gt;&amp;#39;Preview the site&amp;#39;&lt;/span&gt; &lt;span class=&#34;op&#34;&gt;})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;h2 id=&#34;where-it-lives&#34;&gt;Where it lives&lt;/h2&gt;&#xA;&lt;p&gt;swag is on GitHub at &lt;a&#xA;href=&#34;https://github.com/vetr0s/swag&#34;&gt;vetr0s/swag&lt;/a&gt; under the MIT&#xA;license. The plugin is at &lt;a&#xA;href=&#34;https://github.com/vetr0s/swag.nvim&#34;&gt;vetr0s/swag.nvim&lt;/a&gt;, and the&#xA;source for this site is at &lt;a&#xA;href=&#34;https://github.com/vetr0s/vetr0s.dev&#34;&gt;vetr0s/vetr0s.dev&lt;/a&gt;. If&#xA;you have Go installed, this gets you the same binary:&lt;/p&gt;&#xA;&lt;div class=&#34;sourceCode&#34; id=&#34;cb13&#34;&gt;&lt;pre&#xA;class=&#34;sourceCode sh&#34;&gt;&lt;code class=&#34;sourceCode bash&#34;&gt;&lt;span id=&#34;cb13-1&#34;&gt;&lt;a href=&#34;#cb13-1&#34; aria-hidden=&#34;true&#34; tabindex=&#34;-1&#34;&gt;&lt;/a&gt;&lt;span class=&#34;ex&#34;&gt;go&lt;/span&gt; install github.com/vetr0s/swag@latest&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;I built swag for one site and one maintainer. If it fits your site&#xA;too, take it.&lt;/p&gt;</description>
    </item>
    <item>
      <title>Colophon</title>
      <link>https://vetr0s.dev/posts/colophon.html</link>
      <guid isPermaLink="true">https://vetr0s.dev/posts/colophon.html</guid>
      <pubDate>Fri, 18 Sep 2026 00:00:00 +0000</pubDate>
      <description>&lt;p&gt;This website is written in Markdown in neovim and built into plain&#xA;HTML by &lt;a href=&#34;https://github.com/vetr0s/swag&#34;&gt;swag&lt;/a&gt;, a small&#xA;static site generator I wrote in Go. Pandoc renders the Markdown and&#xA;highlights the code. A small CSS file handles the presentation, and the&#xA;pages use system fonts. There is no client-side JavaScript.&lt;/p&gt;&#xA;&lt;p&gt;Posts and pages live in &lt;code&gt;content/&lt;/code&gt;, the page templates in&#xA;&lt;code&gt;layouts/&lt;/code&gt;, and static assets in &lt;code&gt;static/&lt;/code&gt;. swag&#xA;writes the finished site to &lt;code&gt;docs/&lt;/code&gt;, which GitHub Pages&#xA;serves. I describe the setup in &lt;a&#xA;href=&#34;adding-swag-to-my-zero-dependency-static-website-generator.html&#34;&gt;Adding&#xA;swag to my zero-dependency static website generator&lt;/a&gt;. The Emacs and&#xA;Org setup it replaced is in &lt;a&#xA;href=&#34;zero-dependency-static-site-generator.html&#34;&gt;A zero-dependency&#xA;static website generator&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;The site intentionally has no analytics, tracking, comments, or&#xA;cookie banner. You can follow new posts through &lt;a&#xA;href=&#34;https://vetr0s.dev/rss.xml&#34;&gt;RSS&lt;/a&gt; or reach me at &lt;a&#xA;href=&#34;mailto:nate@vetr0s.dev&#34;&gt;nate@vetr0s.dev&lt;/a&gt;.&lt;/p&gt;</description>
    </item>
    <item>
      <title>A zero-dependency static website generator</title>
      <link>https://vetr0s.dev/posts/zero-dependency-static-site-generator.html</link>
      <guid isPermaLink="true">https://vetr0s.dev/posts/zero-dependency-static-site-generator.html</guid>
      <pubDate>Wed, 16 Sep 2026 00:00:00 +0000</pubDate>
      <description>&lt;p&gt;If Emacs is already your primary editor, you have a static website&#xA;generator. Org mode and its HTML publishing tools ship with Emacs, so&#xA;there is no separate generator to install, package manager to maintain,&#xA;or application to run in production. Compared with adding Hugo or&#xA;another tool to my setup, this site is effectively a zero-dependency&#xA;build.&lt;/p&gt;&#xA;&lt;p&gt;That does not mean the website depends on literally nothing. It means&#xA;Emacs is the only build tool, and I already use it to write the source&#xA;files.&lt;/p&gt;&#xA;&lt;h2 id=&#34;project-layout&#34;&gt;Project layout&lt;/h2&gt;&#xA;&lt;p&gt;The repository separates source files from generated files:&lt;/p&gt;&#xA;&lt;pre&gt;&lt;code&gt;content/    Org pages and posts&#xA;static/     CSS and other files copied without changes&#xA;docs/       generated site served by GitHub Pages&#xA;publish.el  Org Publish configuration&lt;/code&gt;&lt;/pre&gt;&#xA;&lt;p&gt;The files in &lt;code&gt;docs/&lt;/code&gt; are generated from&#xA;&lt;code&gt;content/&lt;/code&gt; and &lt;code&gt;static/&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;h2 id=&#34;publish-configuration&#34;&gt;Publish configuration&lt;/h2&gt;&#xA;&lt;p&gt;Org Publish already has the two operations this site needs: export&#xA;Org files to HTML and copy static files. The configuration combines both&#xA;into one project named &lt;code&gt;blog&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;div class=&#34;sourceCode&#34; id=&#34;cb2&#34;&gt;&lt;pre&#xA;class=&#34;sourceCode commonlisp&#34;&gt;&lt;code class=&#34;sourceCode commonlisp&#34;&gt;&lt;span id=&#34;cb2-1&#34;&gt;&lt;a href=&#34;#cb2-1&#34; aria-hidden=&#34;true&#34; tabindex=&#34;-1&#34;&gt;&lt;/a&gt;(&lt;span class=&#34;kw&#34;&gt;setq&lt;/span&gt; org-publish-project-alist&lt;/span&gt;&#xA;&lt;span id=&#34;cb2-2&#34;&gt;&lt;a href=&#34;#cb2-2&#34; aria-hidden=&#34;true&#34; tabindex=&#34;-1&#34;&gt;&lt;/a&gt;      &amp;#39;((&lt;span class=&#34;st&#34;&gt;&amp;quot;blog-pages&amp;quot;&lt;/span&gt;&lt;/span&gt;&#xA;&lt;span id=&#34;cb2-3&#34;&gt;&lt;a href=&#34;#cb2-3&#34; aria-hidden=&#34;true&#34; tabindex=&#34;-1&#34;&gt;&lt;/a&gt;         :publishing-function org-html-publish-to-html)&lt;/span&gt;&#xA;&lt;span id=&#34;cb2-4&#34;&gt;&lt;a href=&#34;#cb2-4&#34; aria-hidden=&#34;true&#34; tabindex=&#34;-1&#34;&gt;&lt;/a&gt;        (&lt;span class=&#34;st&#34;&gt;&amp;quot;blog-static&amp;quot;&lt;/span&gt;&lt;/span&gt;&#xA;&lt;span id=&#34;cb2-5&#34;&gt;&lt;a href=&#34;#cb2-5&#34; aria-hidden=&#34;true&#34; tabindex=&#34;-1&#34;&gt;&lt;/a&gt;         :publishing-function org-publish-attachment)&lt;/span&gt;&#xA;&lt;span id=&#34;cb2-6&#34;&gt;&lt;a href=&#34;#cb2-6&#34; aria-hidden=&#34;true&#34; tabindex=&#34;-1&#34;&gt;&lt;/a&gt;        (&lt;span class=&#34;st&#34;&gt;&amp;quot;blog&amp;quot;&lt;/span&gt;&lt;/span&gt;&#xA;&lt;span id=&#34;cb2-7&#34;&gt;&lt;a href=&#34;#cb2-7&#34; aria-hidden=&#34;true&#34; tabindex=&#34;-1&#34;&gt;&lt;/a&gt;         :components (&lt;span class=&#34;st&#34;&gt;&amp;quot;blog-pages&amp;quot;&lt;/span&gt; &lt;span class=&#34;st&#34;&gt;&amp;quot;blog-static&amp;quot;&lt;/span&gt;))))&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;The real configuration derives its directories from the location of&#xA;&lt;code&gt;publish.el&lt;/code&gt;. This keeps the build independent of the&#xA;directory where Emacs starts. Org supplies a baseline stylesheet; the&#xA;small site stylesheet loads after it and adjusts the reading width,&#xA;links, and navigation.&lt;/p&gt;&#xA;&lt;h2 id=&#34;publishing-from-emacs&#34;&gt;Publishing from Emacs&lt;/h2&gt;&#xA;&lt;p&gt;My Doom Emacs configuration loads the project file:&lt;/p&gt;&#xA;&lt;div class=&#34;sourceCode&#34; id=&#34;cb3&#34;&gt;&lt;pre&#xA;class=&#34;sourceCode commonlisp&#34;&gt;&lt;code class=&#34;sourceCode commonlisp&#34;&gt;&lt;span id=&#34;cb3-1&#34;&gt;&lt;a href=&#34;#cb3-1&#34; aria-hidden=&#34;true&#34; tabindex=&#34;-1&#34;&gt;&lt;/a&gt;(&lt;span class=&#34;kw&#34;&gt;load&lt;/span&gt; (expand-file-name &lt;span class=&#34;st&#34;&gt;&amp;quot;~/source/blog/publish.el&amp;quot;&lt;/span&gt;) &lt;span class=&#34;kw&#34;&gt;nil&lt;/span&gt; &lt;span class=&#34;dt&#34;&gt;&amp;#39;nomessage&lt;/span&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;That registers the publish projects and defines&#xA;&lt;code&gt;vetr0s-blog-publish&lt;/code&gt;. I run it with&#xA;&lt;code&gt;M-x vetr0s-blog-publish&lt;/code&gt;. A prefix argument forces Emacs to&#xA;rebuild every file.&lt;/p&gt;&#xA;&lt;p&gt;The same project works in a clean Emacs session, without Doom:&lt;/p&gt;&#xA;&lt;div class=&#34;sourceCode&#34; id=&#34;cb4&#34;&gt;&lt;pre&#xA;class=&#34;sourceCode bash&#34;&gt;&lt;code class=&#34;sourceCode bash&#34;&gt;&lt;span id=&#34;cb4-1&#34;&gt;&lt;a href=&#34;#cb4-1&#34; aria-hidden=&#34;true&#34; tabindex=&#34;-1&#34;&gt;&lt;/a&gt;&lt;span class=&#34;ex&#34;&gt;emacs&lt;/span&gt; &lt;span class=&#34;at&#34;&gt;--batch&lt;/span&gt; &lt;span class=&#34;at&#34;&gt;-Q&lt;/span&gt; &lt;span class=&#34;at&#34;&gt;--load&lt;/span&gt; ./publish.el &lt;span class=&#34;at&#34;&gt;--funcall&lt;/span&gt; vetr0s-blog-publish&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;p&gt;Batch builds always force a full rebuild. During interactive builds,&#xA;Org keeps its incremental build cache in &lt;code&gt;.org-timestamps/&lt;/code&gt;.&#xA;If the optional &lt;code&gt;htmlize&lt;/code&gt; package is present, source blocks&#xA;get syntax colors; the site still builds without it.&lt;/p&gt;&#xA;&lt;h2 id=&#34;deployment&#34;&gt;Deployment&lt;/h2&gt;&#xA;&lt;p&gt;The build produces plain files under &lt;code&gt;docs/&lt;/code&gt;. GitHub Pages&#xA;serves that directory from the repository&#39;s main branch at &lt;a&#xA;href=&#34;https://vetr0s.dev&#34;&gt;vetr0s.dev&lt;/a&gt;. There is no application&#xA;server, database, JavaScript runtime, or generator-specific deployment&#xA;service.&lt;/p&gt;&#xA;&lt;p&gt;Publishing means running the batch build, reviewing the generated&#xA;files, and pushing them to &lt;a&#xA;href=&#34;https://github.com/vetr0s/vetr0s.dev&#34;&gt;GitHub&lt;/a&gt;. For a personal&#xA;website, Emacs, Org, and a little CSS are enough.&lt;/p&gt;</description>
    </item>
    <item>
      <title>About Me</title>
      <link>https://vetr0s.dev/posts/about-me.html</link>
      <guid isPermaLink="true">https://vetr0s.dev/posts/about-me.html</guid>
      <pubDate>Sun, 23 Aug 2026 00:00:00 +0000</pubDate>
      <description>&lt;h2 id=&#34;foundations&#34;&gt;Foundations&lt;/h2&gt;&#xA;&lt;p&gt;I grew up in Olympia, Washington. My first experience with&#xA;programming was through Scratch block programming in a 7th-grade visual&#xA;communications class. We were given an open-ended assignment where the&#xA;objective was to create anything that interested us. I ended up creating&#xA;a pretty simple Space Invaders clone with a couple of levels, a final&#xA;boss, sprites, and a &lt;strong&gt;very&lt;/strong&gt; rudimentary bullet-physics&#xA;system. While most of the work was just learning from examples on&#xA;Scratch&#39;s website, this first interaction with programming concepts made&#xA;me want to keep exploring. My teacher was pretty impressed and invited&#xA;me to join the 8th-grade robotics class. That class introduced me to&#xA;competitive robotics, which became a major part of my high school&#xA;experience.&lt;/p&gt;&#xA;&lt;p&gt;Around this time I also discovered &#34;real&#34; programming languages. I&#xA;made the naive mistake of searching the internet for &#34;What programming&#xA;languages are used to make video games?&#34; This sent me down a rabbit hole&#xA;of trying to use C++ without understanding OOP, C, or pretty much&#xA;anything else that you would need to know to even start learning C++.&#xA;The only reason I didn&#39;t give up instantly was that I was following a&#xA;beginner tutorial and framework on YouTube.&lt;/p&gt;&#xA;&lt;p&gt;Shortly after, I had successfully set up Visual Studio 2017 and&#xA;created a basic game. It opened a window, rendered individual&#xA;&lt;code&gt;putPixel(x, y)&lt;/code&gt; calls to form a crosshair, and polled&#xA;keyboard arrow input to move the crosshair between set locations.&#xA;However, I soon realized that I was in over my head, both academically&#xA;and technically.&lt;/p&gt;&#xA;&lt;p&gt;This inspired me to research more entry-level paths to programming,&#xA;which led me to learn the basics of HTML, CSS, JavaScript, and&#xA;Python.&lt;/p&gt;&#xA;&lt;p&gt;I have not lost my passion for &#34;lower-level&#34; development or game&#xA;development. I still enjoy working close to the machine through projects&#xA;like &lt;a href=&#34;https://github.com/vetr0s/mach&#34;&gt;Mach/mach.h&lt;/a&gt;, and I&#xA;want to spend more time exploring compilers and programming language&#xA;implementation.&lt;/p&gt;&#xA;&lt;h2 id=&#34;work-experience&#34;&gt;Work experience&lt;/h2&gt;&#xA;&lt;h3 id=&#34;cloud-computing-preceptorship&#34;&gt;Cloud Computing&#xA;Preceptorship&lt;/h3&gt;&#xA;&lt;p&gt;During spring 2026, I worked as a teaching assistant for CSc 346:&#xA;Cloud Computing at the University of Arizona.&lt;/p&gt;&#xA;&lt;p&gt;Every week I held office hours where I helped students debug projects&#xA;and development environments. The course covered cloud fundamentals&#xA;through hands-on projects using services like AWS, EC2, S3, and&#xA;Lambda.&lt;/p&gt;&#xA;&lt;p&gt;Helping teach the course was just as valuable as taking it myself, if&#xA;not more so. Debugging with students taught me to explain problems&#xA;clearly and improved my technical communication skills.&lt;/p&gt;&#xA;&lt;h3 id=&#34;sound-psychiatric-services&#34;&gt;Sound Psychiatric Services&lt;/h3&gt;&#xA;&lt;p&gt;I have worked as a contract software engineer for Sound Psychiatric&#xA;Services since January 2021. I met the business owner through community&#xA;outreach I was a part of in my high school robotics program, and they&#xA;asked me to build the practice&#39;s public website. The site directs&#xA;existing patients to its electronic medical record, payment, and&#xA;telehealth portals. It also gives prospective patients the information&#xA;they need before a visit. The initial design and implementation loop&#xA;took about a month, and then my role shifted toward occasional&#xA;maintenance tasks.&lt;/p&gt;&#xA;&lt;p&gt;Once I started college, I wanted to do more than just front-end&#xA;development for the practice. Initially, I identified that the practice&#xA;was subscribed to a business web hosting package that was overkill for&#xA;the simple static site. So I moved the website from the paid plan to&#xA;free static hosting via Cloudflare Pages. More importantly, the&#xA;transition was seamless for the business because the migration kept&#xA;business-critical features such as domain and email services in&#xA;place.&lt;/p&gt;&#xA;&lt;p&gt;I also support custom in-house software for the practice that deals&#xA;with non-confidential data. For example, I built a tool that reads&#xA;serial data from a medical scale, displaying live weight on the doctor&#39;s&#xA;PC. I built this after the scale&#39;s original display failed.&lt;/p&gt;&#xA;&lt;p&gt;Work like this has taught me how to change small systems without&#xA;disrupting the people who depend on them.&lt;/p&gt;&#xA;&lt;h2 id=&#34;education&#34;&gt;Education&lt;/h2&gt;&#xA;&lt;p&gt;I graduated from the University of Arizona in May 2026 with a B.S. in&#xA;Computer Science. Before Arizona, I earned my associate&#39;s degree&#xA;alongside my high school diploma through Washington state&#39;s Running&#xA;Start program.&lt;/p&gt;&#xA;&lt;h3 id=&#34;high-school-robotics&#34;&gt;High School Robotics&lt;/h3&gt;&#xA;&lt;p&gt;For four years, I was part of Olympia Robotics Federation 4450, an&#xA;FRC robotics team. I joined as a freshman when the team was quite small;&#xA;four of us became its dedicated core that season. By the time I&#xA;graduated, the team had grown to nearly 50 members. During my final two&#xA;years on the team, I served as Safety Captain and Build Team Lead. This&#xA;gave me direct responsibility for shop, robot, and event operations and&#xA;safety.&lt;/p&gt;&#xA;&lt;p&gt;FRC shaped how I think about engineering more than almost anything&#xA;else I&#39;ve done. I entered high school wanting to study computer science&#xA;in college. I left knowing I wanted a career built around difficult&#xA;technical problems, small teams, and building things that make a real&#xA;impact.&lt;/p&gt;&#xA;&lt;h4 id=&#34;egypt-expedition&#34;&gt;Egypt expedition&lt;/h4&gt;&#xA;&lt;p&gt;During my third year, one of our mentors connected us with a mother&#xA;whose son attended a STEM school in Cairo that used LEGO-based robotics.&#xA;We decided to help bring metal-based competitive robotics to the school&#xA;since FTC had yet to expand to the region.&lt;/p&gt;&#xA;&lt;p&gt;Over the next 10 months, our team raised around $60,000 through&#xA;community outreach. The following summer, 17 of us traveled to Cairo&#xA;with several FTC kits, a custom curriculum, and an accompanying&#xA;end-of-week challenge that I helped design.&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;&lt;img src=&#34;../img/cairo_kids.webp&#34; alt=&#34;Two mentors and two students pose behind a small robot in Cairo&#34;&gt;&#xA;&lt;figcaption&gt;Students and mentors with one of the FTC robots in Cairo.&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&#xA;&lt;p&gt;The following season, our team also reached the FRC World&#xA;Championship in Houston to compete for the Impact Award because of the&#xA;team&#39;s outreach efforts in Egypt.&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;&lt;img src=&#34;../img/huston_pit.webp&#34; alt=&#34;Olympia Robotics Federation and Cairo robotics students pose in their pit&#34;&gt;&#xA;&lt;figcaption&gt;Our shared pit at the FRC World Championship in Houston.&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&#xA;&lt;h2 id=&#34;hackaz-2026&#34;&gt;HackAZ 2026&lt;/h2&gt;&#xA;&lt;p&gt;In April 2026, I spent 24 hours with three teammates building&#xA;GridWise Energy at Hack Arizona, the University&#39;s flagship hackathon,&#xA;for the AI Environmental Sustainability track.&lt;/p&gt;&#xA;&lt;p&gt;GridWise is a personal electricity carbon-footprint tracker for&#xA;Arizona residents. Users log high-draw appliances such as HVAC systems,&#xA;EV chargers, pool pumps, and dryers. The app estimates their emissions&#xA;using their local grid&#39;s energy-source mix, tracks the grid&#39;s carbon&#xA;intensity, and encourages users to shift heavy loads toward periods&#xA;where solar and Palo Verde&#39;s nuclear generation are supplying more of&#xA;the grid&#39;s energy.&lt;/p&gt;&#xA;&lt;p&gt;We shipped it at &lt;a href=&#34;https://griddaddy.us&#34;&gt;griddaddy.us&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;figure&gt;&#xA;&lt;img src=&#34;../img/hackaz_team.webp&#34; alt=&#34;Four GridWise teammates stand behind their project laptop at Hack Arizona&#34;&gt;&#xA;&lt;figcaption&gt;The GridWise team at Hack Arizona 2026.&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&#xA;&lt;blockquote&gt;&#xA;&lt;p&gt;We chose a less serious name in an effort to compete for the most&#xA;creative or funny name, so we took inspiration from Scrub Daddy.&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;The stack included React, TypeScript, Vite, Tailwind CSS, shadcn/ui,&#xA;Vercel serverless functions, Neon Postgres, and Auth0. For the AI&#xA;portion, we used a Gemini-backed assistant for usage insights and&#xA;trained a small ridge-regression model to forecast grid carbon&#xA;intensity.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-name&#34;&gt;The name&lt;/h2&gt;&#xA;&lt;p&gt;&lt;strong&gt;vetr0s&lt;/strong&gt; comes from the Italian &lt;em&gt;vetri&lt;/em&gt;, plural&#xA;of &lt;em&gt;vetro&lt;/em&gt;, meaning &#34;glass&#34; or &#34;windowpane.&#34; I enjoy having an&#xA;online alias for development-related things for a couple of reasons.&#xA;Growing up playing Xbox, gamertags were an essential part of my&#xA;introduction to the internet. When I got older, I found that a lot of&#xA;programmers I look up to use Latin-inspired aliases, such as nothings&#xA;and Tsoding. I also just think the domain looks better than something&#xA;like nathantebbs.com.&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
