Creating Custom Tags in Jekyll, with an actual example

Read the article on DEV.to

Shields.io is a “service for concise, consistent, and legible badges in SVG and raster format, which can easily be included in GitHub readmes or any other web page.”

https://shields.io/

Shields.io: Quality metadata badges for open source projects

Pixel-perfect   Retina-ready   Fast   Consistent   Hackable   No tracking...

Their service takes query parameters to their API endpoint to generate a badge / shield. For example:

https://img.shields.io/static/v1?label=Find%20me%20on&message=GitHub&color=181717&style=flat&logo=github

becomes:

My GitHub

I was planning to use this in this website, when I realized a problem; it is hard to maintain. While it may be rare to change the content of those shields, it would be nice to have a maintainable way to put a shield on my blog.

Since I use Jekyll, I can define a custom ‘Liquid tag.’ Just like we can embed stuff from compatible services with liquid tags over at DEV.to, e.g., {% github repo %} and {% embed website %}, we can make a custom tag, say, {% shields_io payload %}, to display a shield.

How does Liquid tag on Jekyll work?

The Liquid tag on Jekyll takes the following format:

{% tag_name [tag_argument] %}

It has a tag name, and one optional argument.

See the problem here? There is at most one value accepted as an argument. Shields.io takes way more than that, and putting query parameter here does not solve anything.

There is still hope, though; let’s take a look at the code we’ll be writing.

module Jekyll
  class CustomTag < Liquid::Tag
    def initialize(tag_name, arg, parse_context)
      super
      # @type [String]
      @arg = arg
    end
    def render(_context)
      "The argument is #{@arg}" # return the render result here
    end
  end
end

The arg argument is the argument passed from the tag. It’s a string. So we can do something with the input.
And looking back at the original problem, we are trying to pass a set of key-value pairs to an API endpoint.

So, I decided to pass a JSON payload here; it can be prettified for our purposes, and Ruby supports JSON deserialization out of the box.

How to turn it into the URL

Since Ruby can turn the input JSON into a hash, we can iterate on this hash and construct the query parameter.

So the idea is to deserialize the JSON and store it in a variable:

    def initialize(tag_name, input, parse_context)
      super
      # @type [Hash]
      @config = JSON.load(input.strip)
    end

And construct the query parameter. I also decided to include href and alt for other purposes into the JSON payload, but they are not relevant for the request. So I extract their values and remove it from the input hash before turning the rest into the query parameter.

def render(_context)
  href = @config[:href]
  alt = @config[:alt]
  @config.delete(:href)
  @config.delete(:alt)
  shield_tag = <<HTML
  <img src="https://img.shields.io/static/v1?#{hash_to_query}"
HTML
  if alt != nil
    shield_tag += " alt=\"#{alt}\" />"
  else
    shield_tag += " />"
  end
  if href != nil
    <<HTML
<a href="#{href}">
#{shield_tag}
</a>
HTML
  else
    shield_tag
  end
end

private
def hash_to_query
  @config.to_a.map { |k, v|
    "#{k}=#{v}"
  }.join '&'
end

Result

Here is my creation - copy the last two files and run a local server!