..

Over-Engineering a #100DaysToOffload Counter in Jekyll

Alright, so I've re-kicked off this #100DaysToOffload challenge, and despite the fact that it's only been like 2 days, I've remembered just how tedious counting the posts can be. I mean, I've gotta open my last post, copy the challenge footer, paste it in the new post, increment the counter... ugh, too much work.

So, I did what any good engineer would do, and I decided to automate it.

As you may have read in my last post, this blog is hosted on Jekyll. It's not perfect, but it fucking works, and I'm at a point in my life where I just don't have the energy to migrate over to another platform, so we're stuck with it... until the end of time.

In writing my series counter (because it's not good development without an unnecessary abstraction), first realized that I needed a way to not only count a number of posts that have a specific metadata (easy), but also that I need to assign and publish the specific counter number for a given post in its footer (less-easy), and then prefix that number with the appropriate number of zeroes to match my design sensibilities (easy, but also Jekyll).

To do all this, I first created a new include called _includes/series.html and put in my stubbed footer:

<p>--</p>
<p><i>This is post # of <a href="https://100daystooffload.com">#100DaysToOffload</a></i></p>

Not much to look at, I know.

With the bare-minimum of our scaffolding out of the way, though, I next needed a way to identify that a post is a part of a series (like #100DaysToOffload). The simplest way to do that is to simply add a series key to a post's front matter, like so:

layout: post
title: "Default Apps"
date: 2023-11-10
series: 100-days-to-offload-2

Alright! We're nearly halfway to halfway there!

Next, we need to actually count the posts. How are we going to do that, you ask? Well, with our trusty friend, loops:

{% assign series_counter = 0 %}

{% for post in site.posts reversed %}
	{% if post.series == page.series %}
		{% assign series_counter = series_counter | plus: 1 %}
	{% endif %}

	{% if post.url == page.url %}
		{% break %}
	{% endif %}
{% endfor %}

<p>--</p>
<p><i>This is post {{ series_counter }} of <a href="https://100daystooffload.com">#100DaysToOffload</a></i></p>

In a nutshell, what the above does is iterate through every site post (in ascending order) and increments the series_counter value every time the post's series value matches the series of the current post (called, confusingly, page.url), and then once it catches up with said post, breaks out of the loop—because we need that counter number, not the grand total.

"But wait," you may be thinking, "I thought you were going to make the series counter a reusable abstraction?"

Well, right you are, anonymous person on the internet. While the counter above does the job for one specific series, what if we want to do this whole thing again for another series? To accomplish that, we need to create a data structurer that defines a list of series, and any relevant metadata we need to publish (such as the name and URL).

To do that, we will want to create a data file, which is basically just a YAML file called series that lives in the _data directory. It will look something like this:

entries:
  100-days-to-offload-2:
    name: '#100DaysToOffload'
    url: https://100daystooffload.com
    digits: 3

The key for each series is the series name (in our case, 100-days-to-offload-2), the name is the link body in the footer, the url is where said link is directed to, and the digits are the eventual number of digits that we want the counter to hold (more on that later).

Now that we've got an appropriate abstraction, let's wire it up to our counter:

{% if site.data.series.entries[page.series] -%}
    {% assign series = site.data.series.entries[page.series] %}
    {% assign series_counter = 0 %}

    {% for post in site.posts reversed %}
        {% if post.series == page.series %}
            {% assign series_counter = series_counter | plus: 1 %}
        {% endif %}

        {% if post.url == page.url %}
            {% break %}
        {% endif %}
    {% endfor %}
{%- endif %}

{% if series %}
	<p>--</p>
	<p><i>This is post {{ series_counter }} of <a href="{{series.url}}">{{ series.name }}</a></i></p>
{% endif %}

The first thing we want to do is check if the current post's series even exists in our data file. If it doesn't, we can just skip the rest of the counter. If it is found, we can then assign it to the series variable, run our counter the same way as before, and then write out our new dynamic series counter footer.

Now, we've got a counter, but it still has to be prefixed with the appropriate number of zeroes. Normally, I'd just use something like printf("%03d") to left-pad the number with some zeroes, but Jekyll disagreed with me, so I decided to call on an old internet classic and make my own left pad plugin (damn we're getting Jekyll-advanced here):

To do that, create a file in _plugins/jekyll-left-pad/jekyll-left-pad.rb and paste this very straightforward left pad code:

module Jekyll
  module LeftPad
    def left_pad(input, length, padding=' ')
        input.to_s.rjust(length, padding)
    end
  end
end

Liquid::Template.register_filter(Jekyll::LeftPad)

What this does is creates a Liquid filter, called left_pad that lets us define the length and padding of the string that gets passed into it. When wired into our counter, it will ultimately look something like this:

{% if site.data.series.entries[page.series] -%}
    {% assign series = site.data.series.entries[page.series] %}
    {% assign series_counter = 0 %}

    {% for post in site.posts reversed %}
        {% if post.series == page.series %}
            {% assign series_counter = series_counter | plus: 1 %}
        {% endif %}

        {% if post.url == page.url %}
            {% break %}
        {% endif %}
    {% endfor %}
{%- endif %}

{% if series %}
	<p>--</p>
	<p><i>This is post {{ series_counter | left_pad: series.digits, '0' }} of <a href="{{series.url}}">{{ series.name }}</a></i></p>
{% endif %}

So, that's the counter, but if you've been paying attention you might have noticed that it hasn't actually been hooked up to the post templates. To do that, I'll be adding the following to the bottom of the _layouts/post.html file (which is what my post template is in):

{% if page.series %}
{% include series.html series=page.series %}
{% endif %}

As you may have noticed, I also opted to inject the page.series variable into the series counter file. This gives us the ability to localize the counter, rather than using the more global page.series variable from within it. The final step in our over-engineered Jekyll series counter is to update it to use the injected series value:

{% if site.data.series.entries[include.series] -%}
    {% assign series = site.data.series.entries[include.series] %}
    {% assign series_counter = 0 %}

    {% for post in site.posts reversed %}
        {% if post.series == include.series %}
            {% assign series_counter = series_counter | plus: 1 %}
        {% endif %}

        {% if post.url == page.url %}
            {% break %}
        {% endif %}
    {% endfor %}
{%- endif %}

{% if series %}
	<p>--</p>
	<p><i>This is post {{ series_counter | left_pad: series.digits, '0' }} of <a href="{{series.url}}">{{ series.name }}</a></i></p>
{% endif %}

And that's it! As you can see in the footer of this very post, our counter works as expected. Feel free to browse around to the other posts and watch those numbers change.

Magic!

--

If you like this post or one of my projects, you can buy me a coffee, or send me a note. I'd love to hear from you!

--

This is post 003 of #100DaysToOffload