=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- title: Over-Engineering a #100DaysToOffload Counter in Jekyll date: 2023-11-12 00:00:00 =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- Alright, so I've re-kicked off this #100DaysToOffload [0] 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 [1], 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 [2] called `_includes/series.html` and put in my stubbed footer: ```html

--

This is post # of #100DaysToOffload

``` 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 [3], like so: ```yaml 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: ```liquid {% 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 %}

--

This is post {{ series_counter }} of #100DaysToOffload

``` 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: ```yml 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: ```liquid {% 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 %}

--

This is post {{ series_counter }} of {{ series.name }}

{% 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: ```ruby 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: ```liquid {% 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 %}

--

This is post {{ series_counter | left_pad: series.digits, '0' }} of {{ series.name }}

{% 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): ```liquid {% 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: ```liquid {% 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 %}

--

This is post {{ series_counter | left_pad: series.digits, '0' }} of {{ series.name }}

{% 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!* --- [0]: https://100daystooffload.com [1]: http://flower.codes/2023/11/11/jekyll-is-good-enough.html [2]: https://jekyllrb.com/docs/includes/ [3]: https://jekyllrb.com/docs/front-matter/ --- >> This is post 003 of #100DaysToOffload EOF