Adventures in Liquid

Kai Wolf

Kai Wolf 13 November 2022

Adventures in Liquid

Liquid is an open-source template language created by the company Shopify, written in Ruby and used by this websites static site generator Jekyll. This language is mainly used to create small logical units that spit out markup. For instance, imagine that we want to display a collection of products on a website which we have saved in a yaml file, we could express this as follows:

<ul>
  {% for product in collection.products %}
    <li>{{ product.title }}</li>
  {% endfor %}
</ul>

Besides loops, it also supports a minimally required set of operations such as if/else statements and variables. Just as in CMake there are no proper types. Essentially everything is of type string. Arrays are comma separated strings and strings may have a truthy or falsy meaning based on the content.

Liquid also ships with some convenience API functions to alter text, which does come in handy for the case at hand. The following example converts a given string to lowercase letters:

{{ "FooBar" | downcase }}

This will output ‘foobar’ as a result. Different operations can also be stacked together via so called pipes that should be very familiar when working in a UNIX environment:

{{ "Just the first word will be printed" | split: " " | first }}

Again, this will output ‘Just’ only.

Advanced example

When I was in the midst of redesigning my website, I came across a slightly more interesting use case for Liquid: As part of my portfolio I do maintain a yaml file that contains all of my previous projects for the last decade or so.

Every entry in this file consists of the project name, an image, a short description and one or more categories that this project is classified into. As part of my consulting services that I provide, I wanted to create a list of relevant projects that I have been working on in the past. For this I had to select all projects that to fall into the same category.

Categories

In other words I wanted to create the intersection from two arrays: Namely, the array with the given project categories and the array with the current page category (which can be one or more).

As it turns out, Liquids limited support for types and arrays for that matter has no operational support to create an intersection of two arrays. So I had to come up with another approach. A brute-force method to achieve this would consist of two concatenated loops, iterating over both arrays and remembering which elements were already visited and removing them from the array.

However, this would have been way to many lines of code for my taste and didn’t seem appropriate. Hence, I came up with the following:

{%- for project in site.data.projects -%}
  {% assign all_cats = project.categories | concat: page.category %}
  {% assign filtered_cats = all_cats | uniq %}
  {%- if all_cats.size > filtered_cats.size -%}
   {{project.description}}
  {%- endif -%}
{%- endfor -%}

First, this creates another array all_cats that consists of both the project and page categories. Secondly, filtered_cats consists of all unique elements from the first array. All that is left to do now is to check, if the original array is larger than the filtered one. As an interesting side note, the actual category isn’t even necessary anymore, as we are only interested in uniqueness.

On the one hand this solution looks admittedly a bit of a stretch. On the other hand, given the constraints of the API this is actually a sufficient elegant solution. For instance, the negation for the case above (the disjoint set in other words) requires to only change the larger then operator to be less then.

I’m available for software consultancy, training and mentoring. Please contact me, if you are interested in my services.