Paul Blasucci's Weblog

Thoughts on software development and sundry other topics

weblog index

How the Sausage Gets Made

Published:

A few folks on the internet have inquired about my new, fully bespoke weblog engine -- although “blogging system” might be a more accurate description. So, for those curious (and so future me can regain lost context), what follows is a tour of “How the sausage gets made” (to paraphrase Mr. John Godfrey Saxe).

Background

Why a new blog? Why now?

Why not?!

I don't blog very often -- but I hope to improve the status quo. How? By using tooling which is more conducive to how I like to work. So, I did a bunch of soul-searching and developed the following list of requirements (in descending order of priority):

  1. Fully own my content
  2. Full control over semantics, layout, and styling
  3. Embrace HTML as a writers medium
  4. No comments
  5. No trackers
  6. Minimal Markdown
  7. No YAML -- HTML is perfectly capable of storing metadata
  8. No Node.js (or anything from that ecosystem)
  9. Minimal JavaScript
  10. Embrace Git as versioning, auditing, workflow tool
  11. No backend
  12. Import old content
  13. Single repository
  14. Free hosting

The combination of these, especially the first three requirements, pointed very strongly toward a self-managed solution (as opposed to: WordPress, Medium, et cetera). Further, after doing a bunch of research into popular tooling, the latter half of my requirements meant I needed to code up something “from scratch”.

So, in the end, I developed a workflow which uses PowerShell Core, and some very basic Git-fu, to stitch together a mix of HTML and CSS (and, yes, I did sneak in a bit of Markdown and JavaScript... but in a minimal and focused manner).

Before I wax poetic on the merits of PowerShell, let me spell out my workflow. This will, hopefully, help shine a light on the benefits of the system I've put in place.

My Workflow

I find that blogging, much like the essays I used to write in school, works better if I follow a process. Specifically, each post goes through the following stages:

Lifecycle of a blog post

It's worth noting that I can perform versioning (via git) at any and every stage of the post's lifecycle. This means I can pause work for hours/days/weeks and still pick up right where I left off. Additionally, I only automate the bare minimum, allowing plenty of human oversight and intervention -- and any transition is manually initiated. This, combined with the stand-alone nature of the individual posts (relative to the templating system), means I retain full stylistic control, while trivially permitting “one off” tweaks. What's more: I can have a single post exist in multiple stages at the same time. I do this very very rarely. But it has been useful for fine-tuning the overall process.

Stage 1: Draft

weblog> new-item ./draft/blog-recipe.md && start-process code './draft/blog-recipe.md'

This is the stage of raw ideas, where posts tend to linger the longest. It's just a folder full of Markdown files. Some are barely outlines. Some are novellas. Maybe I drop in an image or some ASCII art. But the goal in this phase is simply letting the concepts flow out of my brain and into the file.

Stage 2: Ready

weblog> ./move-content.ps1 -Stage Render -Include blog-recipe.md && code ./ready/blog-recipe.html
weblog> dotnet serve -d ./ready/ -o

This is where posts go when I'm satisfied with the raw content. It's also where I do the most mundane drudgery. At this point, I transition from Markdown files to proper HTML. Then I get to work: adjusting the layout of the content; tweaking the associated CSS; revising images and other media; and enriching the post with metadata (topical tags, mostly). It's also during this phase that I bring in a hosting tool (usually dotnet serve or JetBrains WebStorm), which allows me to preview everything exactly how the finished product will appear. Finally, I'll give the post a full round of editorial scrunity (i.e. grammar, spelling, and literary style). Only then is it ready for publication.

Stage 3: Final

weblog> ./move-content.ps1 -Stage Publish -Include blog-recipe.html
weblog> dotnet serve -d ./docs/ -o

This is, arguably, the most automation-heavy phase of the lifecycle. Once a post arrives here, it receives another thorough review of everything (again, via a high-fidelity live preview). However, as part of being moved into this stage, the metadata from the previous step has been extended with a timestamp and -- most importantly -- used to generate one, or more, listing pages. In other words, the weblog's “home page” (listing all posts in reverse chronological order) gets updated. But also, similar listing pages are generated corresponding to the tags collected from across all the published and deployed blog posts. All that's left now is to “go live”.

Stage 4: Serve

weblog> git add . && git commit -m "publish blog recipe" && git push
weblog> start-process https://paul.blasuc.ci

'Serving' is the last, and simplest, phase. I commit the published posts, and generated listings, to Git. And I push the commit to GitHub.com, which handles the hosting of the static HTML/CSS/JS/et cetera files automatically, via GitHub Pages.

C'mon, really?! PowerShell?!

I know a lot of folks like to dump on PowerShell.

“It's got a weird syntax. Just use Perl.”

“The world doesn't need yet another shell.”

“Learn a real shell like bash or zsh.”

“Ewwww... Micro$oft!”

And so on, ad nauseam.

So, while I don't think it's the best tool for everything, I do think it's a great fit for this project. Here's why:

Let's unpack this second point. What do I actually need to make things “go”?

Well, first, I need a simple parameterized command-line interface. PowerShell has extensive, first-class support for all manner of CLI arguments... Flags? Lists? Defaults? You name it, it's supported. And with built-in validation to boot!

But I also need pathing and file management (move, copy, drop, read, et cetera). Yup! That's covered, too. But even more so, I need template file management. And this is where PowerShell really shines. Just one little function means that any text file can be used as a template with full data substitution (credit to Pim Brouwers for clueing me into this one...thanks!). Yes, that's right. It does HTML templates. It does JSON templates. Hell! I have a failed project which used this technique with F# templates. And it's tiny. It looks like this:

#
# Utility which helps bind key-value data into textual content
function Invoke-Template {
    param([ScriptBlock] $scriptBlock)

    function Render-Template {
        param([string] $template)

        Invoke-Expression "@`"`r`n$template`r`n`"@"
    }

    & $scriptBlock
}

Oh! But also, I need to convert Markdown to HTML. PowerShell ships with support for CommonMark as a one-liner (where $fullPath is just the location of a *.md file on disk):

$markdown = Get-Content $fullPath -Raw | ConvertFrom-Markdown

Hmm... this is cool. But I also need to work with collections of data (in order to build the listing pages). No worries. Powershell works with objects where most other shells work with file descriptors or text. So, naturally, I have arrays and hashmaps at the ready.

Finally, rounding it out are all the little things one expects from a shell:

Final Solution

For posterity's sake, the complete PowerShell script is available in the source repository for my weblog.