Blog Migration Notes
Hopefully you can’t notice it much, but I recently ported this blog from Pelican to Hugo.
There were a few reasons driving this:
-
I was using reStructuredText and wanted to move to something supporting markdown. Pelican supports both formats so that wasn’t enough to move to another platform.
-
The provisioning for my Pelican environment wasn’t captured anywhere and the thought of capturing the state of the Python packages and Pelican themes was daunting. The single-executable Gitea program has been working out well, so I hoped the same with the single-executable Hugo package.
Had I poked around a little on the web first, I may have followed the approach from this article on using Pelican with nix, but I ended up with a pretty basic flake.nix setup instead.
-
I wanted to try out something new. OK, so maybe that’s the only reason I migrated.
Switching themes
The old blog was using the Pelican flex theme.
At first, I tried using the Hugo flex theme, but it didn’t support that sweet left sidebar, so I went with the Hugo hyde theme instead and overrode the layout and CSS until it looked as close as possible to the old blog.
This is another area where if I had spent more time looking for themes I probably would have found one closer to what I was already using. But tweaking the hyde theme gave me a excuse to realize how much I still dislike working with CSS.
Go templates are so ugly
Tweaking the theme layouts was my first exposure to Go templates. I get that they’re popular since they’re in the Go standard library, and they’re designed for speed, but after working with Jinja for many years I didn’t find them easy to work worth. Some weirdness I ran into:
- Prefix notation for conditionals ala
{{ if eq $value 1}}. - Use of
indexinstead of array-style syntax ala{{ index some_dict value }}instead of{{ some_dict[value] }} - The
.value changing based on context
Actual post migration
I wrote a quick and dirty script to port the Pelican content over to Hugo. The script extracted the Pelican-style meta data, then called pandoc to convert the reStructuredText to markdown, and then added Hugo-style metadata back to the post.
This got the content about 90% converted. One problem I ran into was that Hugo uses draft: true to signify draft posts, where Pelican uses status: Draft. I fixed these manually since I have so few posts.
The second issue was that pandoc’s choice of syntax tags on the code blocks was often wrong, and again I fixed these blocks manually.
Click here to see the full migration script in all it's glory.
# convert the pelican rst files to Hugo md
import json
import os
import re
import subprocess
import sys
import yaml
re_title = re.compile(r'^\#*$')
re_meta = re.compile(r'^:([^:]+):\s+(.*)$')
def convert_meta(filename):
meta_data = {}
regular_data = ""
title = ''
with open(filename,"r") as a_file:
# Going to do this quick and dirty.
# Read the file line by line and look ones that match ':something:
for a_line in a_file:
if title == "":
title_match = re_title.match(a_line)
if not title_match:
title = a_line.rstrip()
meta_data['title'] = title
a_match = re_meta.match(a_line)
if a_match:
value = a_match.group(2)
if ',' in value:
rhs = value.split(',')
else:
rhs = value
meta_data[a_match.group(1)] = rhs
else:
regular_data += a_line
meta_dump = yaml.safe_dump(meta_data, default_flow_style=False)
temp_filename = "/tmp/temp.rst"
with open(temp_filename, "w") as temp_file:
temp_file.write(regular_data)
name, extension = os.path.splitext(filename)
new_name = name + ".md"
# Have pandoc convert the file and read the output to a string
subprocess.run('pandoc -f rst -t markdown -o /tmp/temp.md /tmp/temp.rst', shell=True)
with open("/tmp/temp.md", "r") as temp_out:
temp_contents = temp_out.read()
with open(new_name, "w") as out_file:
out_file.write('---\n')
out_file.write(meta_dump)
out_file.write('---\n\n')
out_file.write(temp_contents)
# Walk through all the rst files under the directory.
for root, dirs, files in os.walk('.'):
for a_file in files:
if a_file.endswith('.rst'):
convert_meta(root + "/" + a_file)
Preprocessing of diagrams
I had one post in the old blog that used the Pelican PlantUML plugin, but Hugo doesn’t have something comparable. I poked around with converting that diagram to Mermaid as per https://gohugo.io/content-management/diagrams/, but pulling in a mermaid.js file that was over two megs to render a single static diagram seemed like overkill.
Instead I switched to preprocessing the diagram with PLantUML and updating the post to show the SVG image instead.
Here’s my Makefile for running plantuml on any of the diagrams that need updating. I went with make so I won’t have to re-render unchanged diagrams on each blog post. Yes, I spent more than five seconds remembering how to use pattern rules even though I currently have only one PlantUML diagram.
PUMLS := $(wildcard diagrams/*.puml)
SVGS := $(PUMLS:diagrams/%.puml=static/images/%.svg)
.PHONY: plantuml
plantuml: ${SVGS}
static/images/%.svg: diagrams/%.puml
plantuml -tsvg $< -o ../static/images
It didn’t consider using GoAT diagrams since I wanted the diagrams to be in GraphViz style so I didn’t have to worry about layout issues.
Vale is pretty nice
I also started using Vale as a prose linter and it’s working out OK after I turned off the ’no first person’ and ’no use of the verb to-be’ rules. Here’s the .vale.ini I’m currently using:
IgnoredScopes = code
StylesPath = .styles
MinAlertLevel = suggestion
Vocab = Base
Packages = Google, write-good
[*]
BasedOnStyles = Vale, Google, write-good
[*.md]
Google.FirstPerson = off
Google.Parens = off
write-good.E-Prime = off
Vale.Terms = off