Jekyll: How to write an Include function

Getting Jekyll running was a little bit of a kerfuffle since I needed to update Ruby, but then Jekyll couldn’t find the right version. Eventually this was resolved and I got Jekyll’s out of the box test site up and running. The fact that Jekyll has a test server that runs on your development machine is very convenient, even if it doesn’t run server side includes and the like.

I mention server side includes because after I got Jekyll running and looked at their install example, I went straight to seeing how it would deal with my existing site (depending on when you look that it, it might still be the original as I am converting to Jekyll page by page and will only be pushing it up Jekyll-ified when the entire site is done). So, my test to see how Jekyll does would begin with converting a static site (in dire need of upgrading) and seeing how easy the hoped for shortcuts would be to implement.

I started with the idea of a central link data file, as previously mentioned for link rot prevention. I think of them as CANonical LINKs, hence canlink, which was easily accomplished with a jekyll-root/_data/links.json file (no need to muck with the _config.yml as the _data directory is a default thing. The file will be accessed via site.data.links.

The intention is just to create an href link with anchor and title like so:

     <a href="url from the datafile"
        title="data or passed in title text or nothing at all">data
	or passed in anchor text, defaulting to the key parameter</a>

Here’s one of the records:

    "key": {
	"url": "http://www.somewhere.url/",
	"anchor": "info about something",
	"comment": "external stuff"
    }

OK, I’ve got my data file, now how do I use it? There’s includes, tags, and filters that all looked like they could to the trick… so I tried them all.

First includes. Jekyll includes are very like standard server side includes, except that you can pass in parameters with space separated name="value" pairs. The fact that the content of the included blob of text can be Liquid code means that you can write functions.

You don’t need to configure includes, but I ended up adding

    exclude:
      - _includes/

to _config.yml because emacs was making backup files would cause Jekyll to reprocess whenever I twitched, but at least I could get Jekyll to ignore it while I was editing in the _includes directory. I am still not able to get Jekyll to ignore the “.#file” or “#file” emergency backup files that emacs makes (pointers would be most welcome!).

My canonical link include, link.liq, is called with: {% include link.liq key="keyExample" anchor="Here" title="for more information" %}

looks like this:

{%- assign linkey = site.data.links[include.key] -%}
{%- if linkey -%}
   {%- if include.anchor -%}
      {%- assign anchor = include.anchor -%}
   {%- elsif linkey.anchor -%}
      {%- assign anchor = linkey.anchor -%}
   {%- else -%}
      {%- assign anchor = include.key -%}
   {%- endif -%}
   {%- if include.title -%}
      {%- assign titletext = ' title="' | append: include.title | append: '"' -%}
   {%- elsif linkey.title -%}
      {%- assign titletext = ' title="' | append: linkey.title | append: '"' -%}
   {%- endif -%}
   <a href="{{ linkey.url }}"{{titletext }}>{{ anchor }}</a>
   {%- assign linkey = null -%}
   {%- assign titletext = null -%}
{%- else -%}
   *error: link for {{ include.key }} not found*
{%- endif -%}

Now with “comments” (Liquid comments are ridiculous, so I’m going to pretend that “#” is a valid comment delimiter, but the following code will not work).

{%- assign linkey = site.data.links[include.key] -%}   # get the requested (key) record from the links.liq file in _data
{%- if linkey -%}                                           # if the record exists
   {%- if include.anchor -%}                               # if an anchor (user defined override) exists
      {%- assign anchor = include.anchor -%}    # set anchor to passed parameter
   {%- elsif linkey.anchor -%}                  # if anchor text is defined in the record
      {%- assign anchor = linkey.anchor -%}       # set anchor to record defined text
   {%- else -%}
      {%- assign anchor = include.key -%}          # default anchor to key
   {%- endif -%}
##################################################################
# since, if there is no title text to be used, we don't want to have 
#    title=""
# in the link, the title text string will be constructed to include the whole title="title text"
##################################################################
   {%- if include.title -%}                        # if title text (user defined override) exists
      {%- assign titletext = ' title="' | append: include.title | append: '"' -%}    # construct titletext 
   {%- elsif linkey.title -%}                      # if anchor text is defined in the record
      {%- assign titletext = ' title="' | append: linkey.title | append: '"' -%}
   {%- endif -%}
   <a href="{{ linkey.url }}"{{titletext }}>{{ anchor }}</a>    # assemble the link text from the parts, this is an include, so no returning or printing needed
   {%- assign linkey = null -%}                    # unset variables that might be tested later
   {%- assign titletext = null -%}
{%- else -%}
   *error: link for {{ include.key }} not found*     # error warning if record does not exist in links.liq
{%- endif -%}

The {%- -%} vs standard {%%} Liquid delimiters mean that the include is not injecting pointless blank lines and white space into your file. The parameters are called with parameter-name="value" and are accessed in the include with include.parameter-name hence the key parameter is referenced as include.key. Data in my links.liq file in the _data directory is referenced by site.data.links[include.key]. That said, I don’t actually expect any parameters or my data records to be complete, so I do some screening and set default values for my three useful parameters, plus an error warning if all else fails. The include method is actually the most robust WRT variables but I’m not fond of the verbose name/value pairs in the calling. Plus, while you can pass in parameters, those parameters are effectively shared and if you’re not paying attention you can get unexpected contamination between calls, which is why I unset the variables to null at the end of the include. Feels kludgy, I wanted to try the other methods to see if I could make something more elegant.

Next up, filters