Liquid Snake

(This whole post is just an elaborate setup to post Metal Gear memes.)


The Beginnings

I’ve touched a little before on how this blog is written, hosted, and maintained.

I chose Jekyll hosted on GitHub Pages because it’s easy and version-controlled, so you can just slap a theme on your blog (or other type of site) and you’re pretty much good to start writing content. I generally like the theme that I have now, but recently I thought it might be nice to alter it just a bit. Y’know, nothing major, just shift the sidebar around a bit, change the title – that type of thing.

The other thing I wanted to add was the ability to sort posts by date (which you can now do – check it out!).

I’m a web developer, and I think I’m pretty good at what I do, so i didn’t figure digging into Jekyll’s templating language and to tackle this task would take very long. Boy, I was wrong.


The Problems Start

If you’ve never used Liquid before, the first thing you should know is that it’s basically a functional language. Why a templating language needs to be functional… I’m not really sure. So if you want to chain a bunch of functions together, you’d pipe the return value into the next function call. It looks a bit like this:

{% assign current_date = post.date | date: "%B %Y" %}

Liquid code tags begin with {% and end with %}. Variable assignment is an assign call followed by the value. And finally, the assigment here is to take the post date and transform it to the month name (%B) and 4-digit year (%Y).

This is incredibly verbose, to say the least. This is a trivial example!


Grouping, As Far As I Can Tell, Is Impossible

The main point of making a list of posts by date is that they’re grouped by month and year. As far as I can tell, there’s no convenient native function to do this. So, I had to come up with the grossest block of code possible to achieve the task.

The below code grabs all the unique month/year combinations from all the posts on the site:

{% for post in site.posts %}
  {% assign current_date = post.date | date: "%B %Y" %}
  {% if current_date != date %}
    {% assign post_counter = 0 %}
    {% for node in site.posts %}
      {% assign node_date = node.date | date: "%B %Y" %}
      {% if current_date == node_date %}
        {% assign post_counter = post_counter | plus: 1 %}
      {% endif %}
    {% endfor %}
    <li>
      <a href="#{{ current_date | replace:' ','-' }}-ref">
        {{ current_date }} <span class="badge pull-right">{{ post_counter }}</span>
      </a>
    </li>
    {% assign date = current_date %}
  {% endif %}
{% endfor %}

So here, I’m iterating over site.posts, assigning its month/date combo as the current date, and determining if I need to make a new list item for that combination. On top of that, I also need to determine how many posts have that combination, so when this happens I then have to stop and iterate over the posts again to get the number for the little badge next to the heading.

This was so difficult to get right because it’s so verbose.

You could plausibly one-line all the logic for this in Ruby (I’ll use ERB syntax):

{% site.posts.map { |post| post.date.strptime("%B %Y") }.chunk_while { |cur, prv| cur == prv }.each do |post_group| %}
  <li>
    <a href="#{post_group.first.replace(' ', '-')}-ref">
      {%= post_group.first %} <span class="badge pull-right">{%= post_group.size %}</span>
    </a>
  </li>
{% end %}

No need to mentally map out all the counters or handle the “grouping” by instantiating a date string variable and comparing each post’s date to that (heaven forbid you need to sort the dates prior).

This piece is just the left “pane” of tabs that you can click on to select a month. After that, I didn’t show the pane with all the individual posts, where, you guessed it, I had to copy most of this code over to iterate over the posts again and provide the actual links to the posts in each group. There’s a trivial amount of Javascript in there to make the tab panes work, but that already existed and is pretty straight-forward. And we’re only here to bash Liquid, right?


I Hate It More Now That I’ve Written This Post

It turns out escaping all this Liquid in this post is sort of gross, also, as I had to add a bunch of raw/endraw tags around everything. I can’t even show you what it looks like because it’s impossible to escape an endraw tag that’s preceded by a raw tag, and an unterminated raw tag just breaks everything after it. Great.

How do people working on Shopify sites use this? It seems overly verbose and extraordinarily limited, and I can’t believe how hard it was to achieve simple functionality that I could have thrown together in a “real” scripting language like Ruby/Python/Javascript/probably literally anything else you can think of.


I Just Needed to Vent

Sorry.

The feature I was developing is complete and does work, so I guess I’ll quit complaining about Liquid now. Maybe I’ll re-write my blog later to get rid of it, but that seems like overkill for something I don’t have to use much.

That’s all for now. Thanks for reading!


EDIT (11/08/2021):

After editing the pagination for this site I have summoned this eldritch monstrosity:

  <!-- Pagination intermediate variables -->
  {% assign pager_start   = paginator.total_pages | minus: 4 | at_least: 1 %}
  {% assign max_pager_end = pager_start | plus: 4 | at_most: paginator.total_pages %}
  {% assign pager_end     = paginator.total_pages | plus: 1 | minus: pager_start | at_most: max_pager_end %}

WTF

Pictured: me during the summoning ritual.