Emacs, scripting and anything text oriented.

Auto-count #100DaysToOffload posts

Kaushal Modi

Let Hugo do the counting of how far along I got into the #100DaysToOffload challenge.

I learned about the #100DaysToOffload challenge from my Mastodon stream about a month back. Motivated by that, this year I have now already written a lot more blog posts than my last two years combined  My grand total number of posts from the last two years is zero, and this is my 5th post this year. 😆 .

Sidenotes using only CSS was the first post of my first #100DaysToOffload challenge that I started on <2022-02-03 Thu>. May be I will get to 100 posts by <2023-02-02 Thu> 🤞 .. Just may be. But regardless, I am already enjoying writing once again, and it’s great to see the Day count (counting up to 100) increase with each new post!

But it’s not fun to manually type that Day count in each post. This post is about a little Hugo templating code, a Hugo partial  Think of a Hugo partial as a function which you can then call anywhere in your template or theme files (not in the content files like Org or Markdown). , that does that job for me 😎.

Definition of this mission
I want to have a line at the bottom of all posts reading “This is Day NN of #100DaysToOffload.”. But that should happen only for the posts with a 100DaysToOffload tag .

Day count stub at the bottom of each post #

So I defined a partial that would calculate that Day count, and then I called that partial at the bottom of my single  The single template is the template that will be used to render the content from a single content file, like a post page.. like the page you are seeing right now. Compared to that, the list template is used to render “list” type pages like the home page, pages listing all the posts , all the tags , etc. template.

The template file for single pages is typically layouts/_default/single.html in the site or theme directory. Below snippet is added towards the bottom of that template.

{{ $day := (partial "get-post-index-with-tag.html"
                    (dict "page" . "tag" "100DaysToOffload")) }}
{{ if (gt $day 0) }}
    {{ printf `<small id="day-counter">This is <strong>Day %d</strong> of <a href="https://100daystooffload.com/">#100DaysToOffload</a>.</small>` $day | safeHTML }}
{{ end }}
Code Snippet 1: Using the get-post-index-with-tag.html partial

Explanation of the above snippet:

  • Partial get-post-index-with-tag.html is called with a dict type input (think of a map or associative array) with keys page and tag.
  • This partial will return a number representing the chronological index of the current page in a pool of the specified tag.
  • Almost always, you would want to pass the current page’s context1 – the dot – to the called partial so that it can extract useful metadata from the page’s context, like the page parameters, content, etc. So the “.” or the current page context is passed to the partial using the page key.
  • The tag key is “100DaysToOffload” as we want to know which number post is the current post with that specific tag. For example, this is the 5th post so far.
  • If the partial returns a non-zero value,  The partial call will return a value of 0 if a page is not tagged that “tag” key’s value. the Day count line is printed with the shown HTML formatting.

get-post-index-with-tag.html Partial #

Below is the definition of the partial. It is saved as layouts/partials/get-post-index-with-tag.html in the site or theme directory.

{{ $page := .page }}
{{ $tag := .tag }}
{{ $index := -1 }}
{{ if (in $page.Params.tags $tag) }}
    {{ with site.Taxonomies.tags }}
        {{ $weighted_pages := index . ($tag | lower) }}
        {{ range $idx, $p := $weighted_pages.Pages.ByDate }}
            {{ if (eq $page.Permalink $p.Permalink) }}
                {{ $index = $idx }}
            {{ end }}
        {{ end }}
    {{ end }}
{{ end }}
{{ $index = (add $index 1) }}
{{ return $index }}
Code Snippet 2: Definition of get-post-index-with-tag.html partial
  • The partial was passed in a map with keys “page” and “tag”. We save those to local variables $page and $tag.

  • On line 3, a local variable $index is initialized to -1. Later in the code (line 14), we increment this variable by 1 and return that. So, if $tag is not found for a page, $index + 1 = 0 is returned.

  • Line 4 checks if the current page’s tags front-matter has the $tag (which is set to “100DaysToOffload” in Code Snippet 1).

  • In line 6, we get an object $weighted_pages that has a collection or list of all pages with the $tag tag set. That list will contain the current page too.

  • In line 7, we sort this filtered page list $weighted_pages.Pages by date using the .ByDate method, and then loop through that.  Here, we just get a list of all the posts with that $tag. We don’t deal with cases like.. “What if I were on my second or later attempts of #100DaysToOffload challenge?”. I will deal with that when I need to, and then probably I will have a followup post for that 😄. In this line, $idx is the loop counter variable, and $p will hold the page item from the list.

  • In line 8, the permalink of each page ($p) in that page list is compared with the current page’s permalink. When we find a match, we update the $index variable with that loop counter variable’s value. This match must happen only once because all pages have unique permalinks.  I would have liked to use a break statement once the first match was found, to avoid wasting time going through other pages in the list. But the Hugo/Go templating doesn’t have that feature.

  • The list indices begin with 0. So if this were the first page in the date sorted list, $index would be set to 0. But “Day 0 of 100” would sound weird. So in line 14, we first increment the $index value, and then return that.

Closing #

That’s it!

With that little partial defined and then called in the single.html layout file, you can see how the automatic Day count shows up below.

Happy Hugo Templating! 🎉

  1. You can learn more about Hugo’s dot notion and page context from this wonderfully written post by Regis Philibert. ↩︎

Versions used: hugo 0.92.0
This is Day 5 of #100DaysToOffload.