Marcel Kapfer

Switching my Website to Hugo using ox-hugo

2020-05-15

920 words, ~7min reading time

web emacs orgmode hugo

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.

After the technical decisions where made I started creating and designing my own Hugo theme (in case your interested: it is available at Gitlab: mmk2410/nextDESIGN, although I created it with only my own page in mind, you are free to use it yourself if you want to). My goal for the theme was to be quite light weight (btw. I does not use a single line of JavaScript).

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 file (called 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>&#xa;  :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>&#xa;  :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>&#xa;  </xsl:text>:DESCRIPTION: <xsl:value-of select="description" /></xsl:if>
  <xsl:if test="wp:postmeta/wp:meta_key = '_wp_attached_file'"><xsl:text>&#xa;  </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>&#xa;</xsl:text>
    <xsl:text>&#xa;</xsl:text>
    <xsl:text>  </xsl:text>
  </xsl:if>

  <xsl:value-of select="content:encoded" />
  <xsl:text>&#xa;</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 content-org folder in my website repository:

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.

I would like to hear what you think about this post. Feel free to write me a mail!

Reply by mail