Jekyll: How to write a Filter

Officially, filters and tags are plugins so they go into the jekyll-root/_plugins directory. Once again, no _config.yml modifications necessary (in the version of Jekyll (3.7.3) that I was using) to enable things.

This is where I admit that I don’t really know how to program Ruby (or Liquid). That said, I have been a programmer for 40 years, although the last 10 have rarely needed more than HTML, so I’m rusty as hell, but programming languages are programming languages (hubris!?) and Google will tell me the extra details I need to know. To get started, in Ruby we don’t need to declare variables before we start using them, Ruby is very white space (indent) sensitive, and getting variable values in a string is done by "#{variable}"

Now, I’m going to try to duplicate what I’ve already gotten working as an include function. This will be Ruby so jekyll-root/_plugins/filename.rb

Changes to plugins requires a full restart of your Jekyll instance, unlike includes where you can just fiddle and reload the page in your browser to see your progress.

It turns out that filters (Ruby?) don’t like blank parameters. If it is possible that a parameter might be there, and it is not then Ruby will not compile/Jekyll will not restart. Even if you test for undefined variables before using them, Ruby balks. So, instead of one include, I ended up with four filters.

They are called like so:

{{ "recordKey" | canlink }}
{{ "recordKey" | canlinka: "anchor text" }}
{{ "recordKey" | canlinkt: "title text=" }}
{{ "recordKey" | canlinkat: "anchor text", "title text" }}

I don’t remember if I needed the module Jekyll right from the beginning or if I added it later, but it doesn’t hurt anything so…

######################################################################
# custom liquid *filter* to generate canonical links based on
# _data/links.json
######################################################################
module Jekyll
  module LinkMunge
    # set anchor text
    def anch(avar, adat, key, url)
      avar = "error: URL for #{key} not set" unless url

      if avar != ""
        return avar
      elsif adat
        return adat
      else
        return key
      end
    end

    # set title text
    def titl(tvar, tdat)
      if tvar != ""
        return ' title="' + tvar + '"'
      elsif tdat
        ' title="' + tdat + '"'
      end
    end    

    # create a link based solely on the json record
    def canlink(key)
      site = @context.registers[:site]    # the incantation required to access the site data
      linkey = site.data["links"][key]      # getting to my specific links.json data record
      if linkey
        url = linkey["url"]
        anchor = anch("", linkey["anchor"], key, url)
        titletext = titl("", linkey["title"])
        # no "return" or print needed.  The following line is the output of the <strong>filter</strong>
        "&lt;a href=\"#{ url }\"#{titletext }&gt;#{ anchor }&lt;/a&gt;"
      else
        "*error: link for #{ key } not found*"
      end
    end

    # create a link with user defined anchor text override
    def canlinka(key, anchor)
      site = @context.registers[:site]
      linkey = site.data["links"][key]
      if linkey
        url = linkey["url"]
        anchor = anch(anchor, linkey["anchor"], key, url)
        titletext = titl("", linkey["title"])
        "&lt;a href=\"#{ url }\"#{titletext }&gt;#{ anchor }&lt;/a&gt;"
      else
        "*error: link for #{ key } not found*"
      end
    end

    # create a link with user defined title text override
    def canlinkt(key, title)
      site = @context.registers[:site]
      linkey = site.data["links"][key]
      if linkey
        url = linkey["url"] 
        anchor = anch("", linkey["anchor"], key, url)
        titletext = titl(title, linkey["title"])
        "&lt;a href=\"#{ url }\"#{titletext }&gt;#{ anchor }&lt;/a&gt;"
      else
        "*error: link for #{ key } not found*"
      end
    end

    # create a link with user defined anchor and title text overrides
    def canlinkat(key, anchor, title)
      site = @context.registers[:site]
      linkey = site.data["links"][key]
      if linkey
        url = linkey["url"]
        anchor = anch(anchor, linkey["anchor"], key, url)
        titletext = titl(title, linkey["title"])
        "<a href=\"#{url}\"#{titletext}>#{anchor}</a>"
      else
        "*error: link for #{ key } not found*"
      end
    end
  end                           # LinkMunge module
end                             # Jekyll module
Liquid::Template.register_filter(Jekyll::LinkMunge)    # this is all you need to start using your filters, no _config.yml stuff required

Trading elegance of implementation (includes) for (sort of!) elegance of use (filters).

Let’s see how it goes with tags.