Runar Ovesen Hjerpbakk

Programmer. Software Architect. Technical Manager.

Sorting tags by number of posts in Jekyll without plugins

Over the years, I’ve amassed quite a few posts on this blog, but I’ve never been pleased with my Archives page. I’m still not completely satisfied, but it’s now improved by showing the tags and the number of posts in them, in addition to a chronological ordering.

I achieved this using only a Liquid template. It doesn’t use any plugins and is compatible with GitHub Pages.

Complete solution

In the following, my_tags is a collection of my tags with a human-readable title, along with the tag itself. The Visual Studio tag as an example is defined like this:

---
slug: vs
title: Visual Studio
hash_tag: VisualStudio
---

My solution loops through the site.tags, sorts them by the number of posts and builds the listing on the archive page using the title of the tag.

<h2>Tags</h2>
{% capture tag_sizes %}{% for tag in site.tags %}{{ tag[1].size | plus: -10000 }} {{ tag[0] }}^{% endfor %}{% endcapture %}
{% assign tags_sorted_by_number_of_posts = tag_sizes | split: '^' | sort %}
{% for pre_processed_tag in tags_sorted_by_number_of_posts %}
  {% assign tag_with_size = pre_processed_tag | split: ' ' %}
  {% assign key = tag_with_size[1] %}
  {% assign tag = site.tags[key] %}
  {% unless forloop.first %}</ul>{% endunless %}
  {% if tag.size > 0 %}
    {% assign thetag = site.my_tags | where: "slug", key %}
    {% assign thetag = thetag[0] %}
    <h3>{{ thetag.title }}&nbsp;({{ tag.size }})</h3>
    <ul>
    {% for post in tag %}
      <li><a href="{{ post.url }}">{{ post.title }}</a></li>
    {% endfor %}
  {% endif %}
{% endfor %}
</ul>

Hurdles

I’m by no means a Liquid or Ruby expert and had some trouble with my understanding along the way.

Lexicographic sorting of numbers

I needed to sort the tags by the number of posts in them, but sort has a limited set of options and tries to sort alphabetically, thus sorting 1, 2, 10 as 1, 10, 2. Not what I wanted.

This StackOverflow answer put me on the right track. By padding the numbers with leading zeros and do reverse sort, tags would be in descending order, but then the secondary alphabetical sorting would be wrong. A better solution, inspired by Güngör Budak, was to subtract 10 000 from the number of posts and do a regular sort:

{% capture tag_sizes %}{% for tag in site.tags %}{{ tag[1].size | plus: -10000 }} {{ tag[0] }}^{% endfor %}{% endcapture %}
{% assign tags_sorted_by_number_of_posts = tag_sizes | split: '^' | sort %}
["-9964 csharp", "-9967 ios", "-9974 xamarin", "-9975 macos", "-9979 vs", "-9984 build", "-9990 tdc", "-9990 windows", "-9992 process", "-9992 vscode", "-9993 dotnet-script", "-9993 jekyll", "-9993 linux", "-9994 docker", "-9994 learning", "-9994 windows-phone", "-9995 aspnetcore", "-9995 book-scanner", "-9995 dotnet", "-9995 ndc", "-9995 programming", "-9995 tooling", "-9996 python", "-9996 xcode", "-9997 app", "-9997 bash", "-9997 git", "-9997 html", "-9997 raspberry-pi", "-9997 raspbian", "-9997 workflow", "-9997 xamarin-forms", "-9998 amazon", "-9998 android", "-9998 blogging", "-9998 css", "-9998 fermi-container", "-9998 github", "-9998 iphone", "-9998 leadership", "-9998 markdown", "-9998 objective-c", "-9998 ruby", "-9998 security", "-9998 slack", "-9998 testing", "-9998 video", "-9999 app-center", "-9999 apple-tv", "-9999 azure", "-9999 bartender", "-9999 blob-storage", "-9999 chocolatey", "-9999 cloudflare", "-9999 cron", "-9999 design", "-9999 devonthink", "-9999 dips", "-9999 dropbox", "-9999 gamedev", "-9999 games", "-9999 golden-ratio", "-9999 goodreads", "-9999 gtd", "-9999 indiedev", "-9999 java", "-9999 js", "-9999 json", "-9999 kindle", "-9999 lightinject", "-9999 omnisharp", "-9999 reactjs", "-9999 resharper", "-9999 rocket", "-9999 simulator-status-magic", "-9999 skepticism", "-9999 spritekit", "-9999 talk", "-9999 tfs", "-9999 windows-home-server", "-9999 winforms", "-9999 xaml", "-9999 xml", "-9999 xnapshot", "-9999 xunit"]

tags.[0] != tags.first

My second issue was that the tags were in an array of tuples. The key was the tag itself, the value the posts with this tag. This confused me, as tags.[0] != tags.first. I needed to index the array using the actual key and not a regular array index.

I learned this by using the handy inspect filter, showing the content of a variable as a string. For instance, {{ site.tags.first | inspect }} yields the tag vs with a bunch of documents as value (truncated here for readability):

["vs", [#<Jekyll::Document _posts/2019-07-05-error-msb4236.md collection=posts>, ... , #<Jekyll::Document _posts/2010-11-9-a-few-drops-of-wix-wisdom.html collection=posts>]]