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