Jekyll: How to write a Tag

Tags and filters are all plugins and I found I could just include my new tag in the exact same file in which I implemented my filter.

Calling my cantag tag looks like so: {% cantag "key", "anchor text", "title text" %}

Tags inherit from Liquid::Tag which means that it has 3 parameters that you can call anything you like. The first one, tag_name in my case, is the name of the tag. Why you would need to have the name of the thing that you needed the name of in order to call passed in as a parameter… well, it becomes useful in our second tag example. The second parameter, params in my case, is all the parameters passed in as a single string. The third parameter, tokens in my case, are the delimiter tokens that the tag was called with, most likely {% and %} but also possibly {%- and -%}, etc.

Note that if your tag only needs a string input or one parameter then you can take it as is. If you need a list of parameters, it is up to you if they will be space delimited or whatever. I chose Comma Separated Values (CSV) since the text of my parameters could have spaces in them (also commas, so it’s not all that simple). Even though the built-in CSV library is not very robust, it is still better than trying to write your own so, idiosyncrasies, ahoy!

require 'csv'   # call Ruby's CSV library

module Jekyll
  ######################################################################
  # this is where my *filter* code is, removed for clarity
  ######################################################################

  ######################################################################
  # Tag parameters come all together as a string that must be parsed if
  # you're expecting more than one.  Considering them to be comma
  # separated values, use the Ruby csv functions to parse them out,
  # except that Ruby's csv is crap and ultrasensitive to extra
  # whitespace.  Note also that blank/nil values are ok as long as they
  # are not followed by non-blank values
  # bad: ["a",,"c"], ok: ["a","","c"], also ok: ["a",,,,]
  # also note that ["a", ,"c"] is the same as ["a"," ","c"] not ["a",,"c"]
  ######################################################################
  class RenderCanTag < Liquid::Tag
    def initialize(tag_name, params, tokens)   #every <strong>tag</strong> needs an initialize function
      super                                    # super must be called.  I have *no* idea what this does
      # strip trailing whitespace
      @params = params.strip # key, anchor, title     # the "@" makes params global in scope (I think)
      # strip whitespace between arguments
      @params.gsub! '", "', '","'                     # comma space bad, replace with just comma
    end

    def render(context)                               # render is where the output happens
      key, anchor, title = CSV.parse_line(@params)    # get the parameters from the input string

      # even with logic preventing its execution, strip will throw a Liquid exception if a variable is nil
      # Liquid can't deal with nil for some reason even though Ruby can, so this next bit of nonsense
      key = "" unless key
      anchor = "" unless anchor
      title = "" unless title
      
      linkdata = context.registers[:site].data["links"]      # the incantation required to access the site data
      linkey = linkdata[key]                                 # getting to my specific links.json data record
      if linkey
        anchor = anchor.strip # strip whitespace
        anchor = nil if anchor == "" # unset if just blank
        anchor = linkey["anchor"] unless anchor # use default from data if not set
        anchor = key unless anchor # use key as anchor if data not set
        title = title.strip
        title = linkey["title"] if title == ""
        title = " title=\"#{title}\""
        url = linkey["url"]
        anchor = "error: URL for #{key} not set" unless url
      else
        return "*error: link for #{ key } not found*"
      end
      return "<a href=\"#{url}\"#{title}>#{anchor}</a>"
    end                         # render
  end                           # class RenderCanTag
end                             # Jekyll module

Liquid::Template.register_tag('cantag', Jekyll::RenderCanTag)    # this is all you need to start using your tags, no _config.yml stuff required

Next up, how to generate new tags on the fly