Porting a 14-year-old Movable Type blog to Hugo


The site:

MovableType 2.64 on a 500MHz Pentium 3 with 256MB of RAM

  • 3,419 blog comments
  • 2,790 blog entries
  • 1,804 Amazon-hosted images
  • 596 linkblog entries
  • 263 quotes
  • 130 locally-hosted images
  • 82 now-playing microblog entries
  • 36 blogroll linkblog entries
  • 12 project linkblog entries
  • 9 misc. external image links

Things I don't like about Hugo

  1. Inconsistent, incomplete, misleading, and/or outsourced documentation.

    • Enhancement: replace "how to do this in Go" links with "how to do this in Hugo" pages. (ex: .Date.Format)

    • Enhancement: extensive one-line code examples:

      • does section $s exist; retrieve/search its pages

      • does an article with title $t exist (opt: in section $s); retrieve it

      • does taxonomy $ta exist; retrieve/search its terms

      • does term $te exist in taxonomy $ta; retrieve/search its pages

      • essentially, when I want to count the number of pages in a specific taxonomy term referenced in the article's front matter, don't show me how to count all terms in every taxonomy, show me how to construct the very-non-obvious (index $.Site.Taxonomies.comments .Params.entry).Count

  2. Speaking of inconsistent and incomplete, index is documented at hugodocs.info/functions, but not gohugo.io/templates/functions/.

  3. No idea which examples work correctly in the current release, which are new, which are deprecated, which don't do what you'd expect.

    • Enhancement: in addition to min_version in theme.toml, add tested_versions=[]
  4. Bug: setting watch=false doesn't work in config.toml for hugo server (Mac v0.19), which would make the "too many open files" issue less annoying. (note: actual open-file count for my site according to lsof: 9,017)

  5. Pluralization adds complexity without benefit. Seriously, when is it "tag" vs. "tags", etc? As far as I can tell, the singular is always used in paths (layouts/taxonomy/tag.html, etc), and the plural is always used in templates, but I don't know if that's correct and complete.

    1. How do this work with i18n?

    2. Where does the use of inflect in pluralizeListTitles come into all of this?

    3. Dumb: section "Elsewhere" title is "Elsewheres"

    4. Dumber: section "Recently spotted" title is "Recently spotteds"

    5. Enhancement: pluralizeListTitles=false should be default

  6. Speaking of which, "You can use the go-i18n tools to manage your translations" is a great example of outsourcing documentation in a way that is completely useless.

  7. Stumbling across preserveTaxonomyNames, good. Finding out that it preserves case (the obvious way to make a category named "TV" or "SF" look right), better. Discovering that it leaves " " in the URLS as well, bad. Breaking .Next.URL, etc in $.Paginator? Priceless.

    • Enhancement: store the original form of the taxonomy term so you can have both a sanitized version for the URL and the user's preferred display form (.Name and .Display). If the tagging was inconsistent, that's the user's problem.

    • How did I do it instead? I created a set of key/value pairs in the data directory (wtf="WTF") and substituted them in:

      <ul>
      {{ range $key, $value := .Site.Taxonomies.categories }}
        <li>{{ index $.Site.Data.casefix $key | default ($key | humanize) }}
      {end}}
      </ul>
      
  8. Poorly-explained template search path with no debug output.

    • Enhancement: debug flag on "hugo server" that embeds HTML comments showing what kind of page it thinks it's rendering, and every place it looked for a template before finding one.
  9. Internal pagination template does not scale at all.

    • Enhancement: partials/pagination.html This automatically got styled nicely by Bootstrap's example Blog theme, by the way.
  10. WTF is up with twenty different context-dependent .ByWhatever and .GroupByWhatever functions?

    1. Is .ByFoo just syntactic sugar for using the sort function?

    2. How do I put "if section $s1, by-date-ascending, else if $s2, alphabetical" into a template? Or better yet, in /data so I can just set:

      [hotnews]
      key=".Date"
      order="desc"
      [quotes]
      key=".Author"
      order="asc"
      [special]
      key=".Params.mysortkey"
      order="asc"
      
  11. I'm still not sure what I did that made hugo dump a meaningless stack trace. Probably an attempt to use the only debugging tool that's available, {{ printf "%#v" . }}, which sometimes returns something useful, but usually just fills the page with noise.

    • Enhancement: pretty-printing the above printf debug statement like Perl's Data::Dumper would be a start.
  12. Still trying to figure out if I can have:

    1. auto-generated monthly archive pages with prev/next-month navigation links, and the ability to link N most recent months in sidebar.

      • Workaround: use a script to fill a section with small content files, and then put something like this in layout/archive/single.html

        {{ $month := .Params.month }}
        {{ range $.Site.Sections.post.Pages.GroupByDate "2006-01" }}
          {{ if eq .Key $month }}
            {{ range .Pages.ByDate }}
              {{ partial "article-summary.html" . }}
            {{end}}
          {{end}}
        {{end}}
        
    2. prev/next-in-section/taxonomy-term navigation links.

    • Enhancement: virtual sections whose pages are defined by an expression in the theme config (the obvious location would be the front matter of theme/mytheme/content/mysection/_index.md, but content pages don't work in themes).
  13. In other words, between the issues with documentation, examples, etc, I still don't know if Hugo today can generate all of the types of static content that MovableType could in 2003, or if so, how to do it.

  14. No standards in themes; once you're past the mockup stage, you really can't switch. Theme A mixes all sections in index.html with no visual differentiation, theme B just shows "post", theme C calls it "posts" instead, theme D only works if you add specific entries to config.toml that theme E uses for something else, etc, etc.

  15. No way to identify CJK content so it can be treated differently. For instance, I want to italicize a list of song titles, unless they include kanji, etc. The Go regexp library doesn't seem to support Unicode blocks, so the best I could come up with quickly was to only italicize 7-bit ASCII titles:

    {{ $count_cjk := findRE "[^\000-\177]" .Title }}
    {{ if ge (len $count_cjk) 1 }}
      {{ .Title | safeHTML }}
    {{else}}
      <i>{{ .Title | safeHTML }}</i>
    {{end}}
    

Late additions...

  1. I forgot to mention one of the first things I had to work around: the useless auto-stripped .Summary. I had to append <!-- more--> to every blog entry and comment that I converted from the MT blog, and add it to archetypes/default.md to make sure I never forget to add it.

    • Enhancement: add a flag to disable auto-summarization and just return .Content if the user didn't manually insert <!-- more-->.
  2. The SmartyPants implementation has some odd behaviors I'll have to try to reproduce sometime, but the most obvious problem I ran into is that you often have to apply it manually: {{ .Title | markdownify }}

Things that are cool about Hugo

  1. Those thousands of pages rebuild in about 15 seconds. Of course, that's comparing to a Pentium III running MovableType, so it may not be that impressive. :-)

  2. Aliases so I can trivially preserve 14-year-old permalinks.

  3. /data has all sorts of uses for extending functionality.

  4. When your site is too big for testing with LiveRebuild: hugo server --contentDir samplecontent --theme newversion

  5. Can completely suppress a page type by creating a zero-length template file, if you can figure out what its name should be (generates a long string of warnings if you use -v, but it works perfectly and speeds up rebuild time):

    • layouts/$section/single.html (no single pages)

    • layouts/section/$section.html (no list page; with above, only visible by taxonomy)

  6. Speaking of which, wikiblog.sh creates a bunch of content entries with random author, categories, date, tags, and text from wikipedia (converted to Markdown with pandoc), and can also optionally assign a batch of entries to a series.

  7. It's kind of hilarious to set paginate=2 and hear my fans spin up.

  8. Despite the problems listed above, it only took about two hours to get to a functional site and (unstyled) theme from scratch. Another three hours and I had pagination and all the side/microblogs working, and had figured out how to attach the old comments to their entries.

  9. Once I had a working ugly site, it took about fifteen minutes to convert the example blog style from the Bootstrap distro into templates. And that's counting the time it took to figure out how to use Bootstrap.