Switching my Website to Hugo using ox-hugo
1172 words, ~ 6 min reading time
To be honest: my website was always more or less just a large playground for me. It started around 2013 when I created my second website (I had a website before, ~2006/2008, I don’t know correctly). Back then I put very much time in designing the thing. In 2014 I taught myself to code and in early 2015 I even wrote a PHP blogging engine called Rangitaki (i have archived it some time ago). Additionally I wrote a script for generating the non-blog websites from markdown files. But I never looked at a static site generator for this purpose.
So it might be a shocker to you that I switched to a self-hosted Wordpress instance in July 2015. The reason was, that I wanted to focus on writing content instead of designing my site. So I also did not create an own theme but just used the ‘twentyfifteen’ one provided by Wordpress (well actually I created a child theme for ripping out the Google Fonts connection and serving the fonts myself).
Well, focusing on content worked… a little bit…
I actually wrote more posts in 2018 than in the years before. But that changed again in 2019 where I did not even publish one post.
Prior to the switch today I had some experiences Hugo as a static side generator. I already wrote a small blog for myself (I think this was around 2016), a complete design for a friend of mine (I think that was around 2016/17) and for a long time my music/composition website was created using Hugo.
I started thinking about migrating a few weeks ago and read about some possible solutions which included Emacs and Org-Mode. What finally convinced my was the combination of the extensibility of Hugo combined with Org-Mode using ox-hugo. ox-hugo is a Emacs package that provides an exporter for Org. That means: once installed you only press a few keys to create a Hugo entry from a text written in Org. ox-hugo provides to options for working with posts: one post per Org file and one post per org subtree (a section in an Org file). Since org handles many subtrees in one file extremely well I decided to use the later (and preferred) mode.
Although I have to say that if there were no ox-hugo I probably would not use Hugo. While it is really extremely powerful it also gave my quite some headaches. Debugging the thing should really be much more easier. Some times I got myself reminded of debugging LaTeX code without an helping environment which translates the errors to human-understandable English.
Next to that I had to somehow migrate my posts from Wordpress to Hugo. While there are quite a few scripts for doing that, I wanted (although it is not necessary) not only to store the new content in Org files but also the existing. And I didn’t find an already available solution for that (tbh: I also didn’t search that much). So I had to create one myself.
Wordpress has the ability to export a modified RSS XML file called
WXR (WordPress eXtended RSS). Well, I never thought (not even in my
deepest/darkest dreams) that I every need to use XSLT. But for
parsing the WXR file it was actually the best tool. Before looking,
what ox-hugo needed (this was a mistake, I should have looked first
or change my XSL file after looking…) I created the following XSL
orgmode.xsl)which helped my transform the WXR files
to Org files without loosing any relevant information.
<?xml version="1.0"?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:excerpt="http://wordpress.org/export/1.2/excerpt/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:wp="http://wordpress.org/export/1.2/"> <xsl:output method="text" /> <xsl:template match="/rss"> <xsl:for-each select="channel/item"> <xsl:sort select="wp:post_date_gmt" order="descending" /> * <xsl:value-of select="title" /> :PROPERTIES: :PUBDATE: <xsl:value-of select="pubDate" /> :POST_DATE: <xsl:value-of select="wp:post_date" /> :POST_DATE_GMT: <xsl:value-of select="wp:post_date_gmt" /> :POST_NAME: <xsl:value-of select="wp:post_name" /> :CUSTOM_ID: <xsl:value-of select="wp:post_id" /> :CREATOR: <xsl:value-of select="dc:creator" /> :STATUS: <xsl:value-of select="wp:status" /> <xsl:if test="string-length(category[@domain='category']) > 0"><xsl:text>
:CATEGORY: </xsl:text><xsl:value-of select="category[@domain='category']/@nicename" /></xsl:if> <xsl:if test="string-length(category[@domain='post_tag']) > 0"> <xsl:text>
:TAGS: </xsl:text> <xsl:for-each select="category[@domain='post_tag']"> <xsl:value-of select="@nicename"/> <xsl:if test="position() != last()"> <xsl:text>, </xsl:text> </xsl:if> </xsl:for-each> </xsl:if> :POST_TYPE: <xsl:value-of select="wp:post_type" /> <xsl:if test="string-length(description) > 0"><xsl:text>
</xsl:text>:DESCRIPTION: <xsl:value-of select="description" /></xsl:if> <xsl:if test="wp:postmeta/wp:meta_key = '_wp_attached_file'"><xsl:text>
</xsl:text>:ATTACHMENT: <xsl:value-of select="wp:postmeta[wp:meta_key='_wp_attached_file']/wp:meta_value" /></xsl:if> :END: <xsl:if test="string-length(excerpt:encoded) > 0"> <xsl:text>*</xsl:text> <xsl:value-of select="excerpt:encoded" /> <xsl:text>*</xsl:text> <xsl:text>
</xsl:text> <xsl:text> </xsl:text> </xsl:if> <xsl:value-of select="content:encoded" /> <xsl:text>
</xsl:text> </xsl:for-each> </xsl:template> </xsl:stylesheet>
(I know that this is not really professional style or in any sense well done but I don’t have any experience in this field and it worked for the task.)
The output generated with
xsltproc orgmode.xsl posts.xml > posts.org was one file which contained all my files with a
structure like the following:
* Quick Deploy Solution :PROPERTIES: :PUBDATE: Tue, 14 Apr 2020 08:31:37 +0000 :POST_DATE: 2020-04-14 10:31:37 :POST_DATE_GMT: 2020-04-14 08:31:37 :POST_NAME: quick-deploy-initial-release :CUSTOM_ID: 940 :CREATOR: marcel_kapfer :STATUS: publish :CATEGORY: code :TAGS: cicd, deploy, git, php, programming, typo3 :POST_TYPE: post :END: RAW HTML Code of the content.
As I said I looked afterwards, what ox-hugo actually needs (and didn’t think of adjusting the XSLT…):
* Quick Deploy Solution :@code:cicd:deploy:git:php:programming:typo3: :PROPERTIES: :EXPORT_DATE: 2020-04-14 10:31:37 :EXPORT_FILE_NAME: quick-deploy-initial-release.md :END: Content in Org syntax
As you may see I could have saved some precious time. However the output that ms XSLT created was not that bad and with a few (~20-30) search-and-replace calls (I used the visual-regexp Emacs package) I got what ox-hugo needed. Due to a wrong search-replace at the end I needed to fix some things by hand but otherwise the approach was still faster than writing an own script for that purpose.
So finally I have three org files which reside in a
folder in my website repository:
blog.org: my blog posts
quotes.org: my quotes posts (I wanted to have them separately)
sites.org: the content for all pages which are not posts
This post is the first one I write in Emacs Org-Mode and I have to say, that it feels quite good doing that in a familiar environment. There is just one thing left to say: how do I publish my site. I earlier mentioned that I have already written a few Hugo sites and so I already had some scripts lying around for doing the job. For now the following bash script does exactly what I want.
#!/bin/bash # Clean aka remove public/ if it exists if [[ -d ./public/ ]]; then rm -rf ./public/ fi # Build the site using hugo hugo # Deploy using rsync rsync \ --archive \ --verbose \ --compress \ --chown=marcel:www-data \ --delete \ --progress \ public/ \ mmk2410.org:/var/www/mmk2410.org/
So this is it. I switched from Wordpress to Hugo using my Emacs, Org-Mode and ox-hugo. Let’s see how this will work out in the future.
If you would like to comment on this post, feel free to write me a mail at comment(at)mmk2410(dot)org!