Gemini and Hugo

For a few years, I have been using Hugo, a static site generator, to produce these pages.

At the same time very fast and corresponding perfectly to my needs, it is above all very modular. I was therefore little surprised to see that it was quite easy to convert, with little effort, my site for the Gemini protocol. Let’s see how!

Declaring Gemini as an output format

Hugo can output content in multiple formats: most of them are already predefined, but it is also possible to create your own. This is what we are going to do for Gemini.

First, in the configuration file config.yml we will declare a new type text/gemini with the file suffix .gmi:

mediaTypes:
  text/gemini:
    suffixes:
      - "gmi"

Once this is done, we declare a new output format, which uses this type, which is given the name GEMINI.

This is an opportunity to specify the protocol for our links (gemini://) and to build the site to a separate folder (gemini) which will make it easier to export the files to our server:

outputFormats:
  Gemini:
    name: GEMINI
    isPlainText: true
    isHTML: false
    mediaType: text/gemini
    protocol: "gemini://"
    permalinkable: true
    path: "gemini/"

Finally, it only remains to ask Hugo to generate pages for the different contents. For example, in my case:

outputs:
  home: ["HTML", "RSS", "GEMINI"]
  page: ["HTML", "GEMINI"]

Layouts

To be able to generate the files, it is now necessary to create layouts to see how to display them!

To start with the index, we can start with layout/index.gmi. In my case, it’s a simple text, followed by a list of posts:

Hello world!

{{ range (where .Site.Pages "Section" "articles") }}
=> {{ replace .Permalink "/gemini" "" 1}} {{ .Title }}
{{ end }}

As you noted, I have to manually remove references to the /gemini subpath to prevent them from appearing in the links.

For posts, we can create a layout/_default/single.gmi. Basically, it would suffice to display the title and content:

# {{ .Title }}

{{ .RawContent }}

Nevertheless, it is possible to process it with some filters to adapt the content in markdown. For example, the following lines allow you to remove the inline code or italics :

{{ $content := .RawContent -}}
{{ $content := $content | replaceRE "`(.+?)`" "$1" -}}
{{ $content := $content | replaceRE "`" "```" -}}
{{ $content := $content | replaceRE "\\*(.+?)\\*" "$1" -}}
{{ $content }}

Finally, I use these two following lines for the links. The first one converts an inline link into a gemini link, isolated. The second converts links that are already on an isolated line into a gemini link more easily.

{{ $content := $content | replaceRE " \\[(.+?)\\]\\((.+?)\\)" "\n\n=> $2 $1\n\n" -}}
{{ $content := $content | replaceRE "\\[(.+?)\\]\\((.+?)\\)" "=> $2 $1" -}}

I also like to add links for previous and next articles with:

{{ if .Next }}=> {{ replace .Next.Permalink "/gemini" "" 1}} ← Newer: {{ .Next.Title }}{{ end }}
{{ if .Prev -}}=> {{ replace .Prev.Permalink "/gemini" "" 1}} → Older: {{ .Prev.Title }}{{- end }}

Export

I use rsync to easily export my files to the server. The public folder locally should be exported to /var/www remotely, without public/gemini, which should be exported to /var/gemini. I use:

hugo

rsync -avz --no-perms --no-owner --no-group \
      --no-times --delete --exclude "gemini" \
      public/ myserver:/var/www/sylvaindurand

rsync -avz --no-perms --no-owner --no-group \
      --no-times --delete \
       public/gemini/ vps:/var/gemini

rm -rf public

This last folder is then read by a gemini server, as explained in the previous article:

Discovering the Gemini protocol