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):
- Fully own my content
- Full control over semantics, layout, and styling
- Embrace HTML as a writers medium
- No comments
- No trackers
- Minimal Markdown
- No YAML -- HTML is perfectly capable of storing metadata
- No Node.js (or anything from that ecosystem)
- Minimal JavaScript
- Embrace Git as versioning, auditing, workflow tool
- No backend
- Import old content
- Single repository
- 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:
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:
- It's simple... a single script handles everything (and really, it's just 3 functions).
- It's robust... everything I need is “in-the-box” (no messing about with dependencies).
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:
- Regular expressions... to extract values from text files.
- Conditionals... with which to make decisions.
- Full access to the underlying OS... for launching processes and such.
Final Solution
For posterity's sake, the complete PowerShell script is available in the source repository for my weblog.