42

I am using the YAML heading of a markdown file to add an excerpt variable to blog posts that I can use elsewhere. In one of these excerpts I refer to an earlier blog post via markdown link markup, and I use the liquid template data variable {{ site.url }} in place of the base URL of the site.

So I have something like (trimmed it somewhat)

--- 
title: "Decluttering ordination plots in vegan part 2: orditorp()"
status: publish
layout: post
published: true
tags: 
- tag1
- tag2
excerpt: In the [earlier post in this series]({{ site.url }}/2013/01/12/
decluttering-ordination-plots-in-vegan-part-1-ordilabel/ "Decluttering ordination
plots in vegan part 1: ordilabel()") I looked at the `ordilabel()` function
----

However, jekyll and the Maruku md parser don't like this, which makes me suspect that you can't use liquid markup in the YAML header.

Is it possible to use liquid markup in the YAML header of pages handled by jekyll?

  1. If it is, what I am I doing wrong in the example shown?
  2. If it is not allowed, who else can I achieve what I intended? I am currently developing my site on my laptop and don't want to hard code the base URL as it'll have to change when I am ready to deploy.

The errors I am getting from Maruku are:

| Maruku tells you:
+---------------------------------------------------------------------------
| Must quote title
| ---------------------------------------------------------------------------
|  the [earlier post in this series]({{ site.url }}/2013/01/12/decluttering-o
| --------------------------------------|-------------------------------------
|                                       +--- Byte 40

and

| Maruku tells you:
+---------------------------------------------------------------------------
| Unclosed link
| ---------------------------------------------------------------------------
| the [earlier post in this series]({{ site.url }}/2013/01/12/decluttering-or
| --------------------------------------|-------------------------------------
|                                       +--- Byte 41

and

| Maruku tells you:
+---------------------------------------------------------------------------
| No closing ): I will not create the link for ["earlier post in this series"]
| ---------------------------------------------------------------------------
| the [earlier post in this series]({{ site.url }}/2013/01/12/decluttering-or
| --------------------------------------|-------------------------------------
|                                       +--- Byte 41
Gavin Simpson
  • 170,508
  • 25
  • 396
  • 453

4 Answers4

51

Today I ran into a similar problem. As a solution I created the following simple Jekyll filter-plugin which allows to expand nested liquid-templates in (e.g. liquid-variables in the YAML front matter):

module Jekyll
  module LiquifyFilter
    def liquify(input)
      Liquid::Template.parse(input).render(@context)
    end
  end
end

Liquid::Template.register_filter(Jekyll::LiquifyFilter)

Filters can be added to a Jekyll site by placing them in the '_plugins' sub-directory of the site-root dir. The above code can be simply pasted into a yoursite/_plugins/liquify_filter.rb file.

After that a template like...

---
layout: default
first_name: Harry
last_name: Potter
greetings: Greetings {{ page.first_name }} {{ page.last_name }}!
---
{{ page.greetings | liquify }}

... should render some output like "Greetings Harry Potter!". The expansion works also for deeper nested structures - as long as the liquify filter is also specified on the inner liquid output-blocks. Something like {{ site.url }} works of course, too.

Update - looks like this is now available as a Ruby gem: https://github.com/gemfarmer/jekyll-liquify.

Ohad Schneider
  • 36,600
  • 15
  • 168
  • 198
blueling
  • 2,003
  • 1
  • 17
  • 16
  • While this is the best answer I've heard so far, it also means that I have to remember to use the `liquify` filter every time i want to use a variable, and if I forget, I silently get `{{ }}` in my page, making me want to put it on EVERY variable expansion just in case... :( – KFunk Jul 03 '16 at 02:44
  • 4
    This is perfect, exactly what I was looking for. This plugin should totally be a feature of core jekyll – alxrb Nov 10 '16 at 10:17
  • 1
    Very elegant solution. Thank you! – Dan Ancona Jan 12 '17 at 19:35
  • 2
    is there any way to use this in github pages? – cregox Mar 13 '17 at 15:46
  • 3
    @cregox GitHub Pages does not allow building with plugins. There's an info tip in the Jekyll docs on plugins, "GitHub Pages is powered by Jekyll. However, all Pages sites are generated using the `--safe` option to disable custom plugins for security reasons. Unfortunately, this means your plugins won’t work if you’re deploying to GitHub Pages. You can still use GitHub Pages to publish your site, but you’ll need to convert the site locally and push the generated static files to your GitHub repository instead of the Jekyll source files." source: http://jekyllrb.com/docs/plugins/ – Brian Zelip Mar 31 '17 at 23:20
  • 1
    @BrianZ it does say "custom plugins" so how can we make something like this non-custom or official or whatever? :) – cregox Apr 01 '17 at 10:16
  • 1
    @cregox "custom plugins" is the same thing as "plugins" -- a resource not available in jekyll core, ie, a resource created externally to jekyll. You'd have to either get the plugin included in jekyll core, or become a GitHub boss to change their policy :). Just use the plugin for local development, then push the static build to your GH repo like the last part of the above quote mentions. Cheers – Brian Zelip Apr 01 '17 at 15:05
  • 2
    @cregox Or use Netlify in conjunction with GitHub, https://netlify.com. – Brian Zelip Apr 01 '17 at 15:06
  • 3
    @BrianZ thanks for the netlify hint! I had seen it in the past and even created an account there but for some reason abandoned it in the middle... if anyone else might be interested, they offer a free pro plan for non commercial open source and integrate quite simply with jekyll https://www.netlify.com/blog/2015/10/28/a-step-by-step-guide-jekyll-3.0-on-netlify/ - I'll try to set it up today. :) – cregox Apr 06 '17 at 16:58
  • @cregox GitLab is also an option for custom plugins (for an example check out my blog repo: https://gitlab.com/ohadschn/ohadschn.gitlab.io). – Ohad Schneider Sep 10 '17 at 14:26
36

I don't believe it's possible to nest liquid variables inside YAML. At least, I haven't figure out how to do it.

One approach that will work is to use a Liquid's replace filter. Specifically, define a string that you want to use for the variable replacement (e.g. !SITE_URL!). Then, use the replace filter to switch that to your desired Jekyll variable (e.g. site.url) during the output. Here's a cut down .md file that behaves as expected on my jekyll 0.11 install:

---
layout: post

excerpt: In the [earlier post in this series](!SITE_URL!/2013/01/12/)

---

{{ page.excerpt | replace: '!SITE_URL!', site.url }}

Testing that on my machine, the URL is inserted properly and then translated from markdown into an HTML link as expected. If you have more than one item to replace, you can string multiple replace calls together.

---
layout: post

my_name: Alan W. Smith
multi_replace_test: 'Name: !PAGE_MY_NAME! - Site: [!SITE_URL!](!SITE_URL!)'

---

{{ page.multi_replace_test | replace: '!SITE_URL!', site.url | replace: '!PAGE_MY_NAME!', page.my_name }}

An important note is that you must explicitly set the site.url value. You don't get that for free with Jekyll. You can either set it in your _config.yml file with:

url: http://alanwsmith.com

Or, define it when you call jekyll:

jekyll --url http://alanwsmith.com
Alan W. Smith
  • 24,647
  • 4
  • 70
  • 96
  • 2
    +1 Thanks Alan for your thorough and well illustrated answer. (I have `site.url` set in my `_config.yml` and should have mentioned that.) I'll wait a tad longer to see if anyone has any other ideas/suggestions, but if not I'll accept this. Cheers. – Gavin Simpson Feb 01 '13 at 18:58
0

If you need to replace values in data/yml from another data/yml file, I wrote plugin. It's not so elegant but works :

I did some code improvements. Now it catch all occurrences in one string and work with nested values.

module LiquidReplacer
  class Generator < Jekyll::Generator
    REGEX = /\!([A-Za-z0-9]|_|\.){1,}\!/
  
    def replace_str(str)
      out = str
      str.to_s.to_enum(:scan, REGEX).map { 
        m = Regexp.last_match.to_s
        val = m.gsub('!', '').split('.')
        vv = $site_data[val[0]]
        val.delete_at(0)
        val.length.times.with_index do |i|
          if val.nil? || val[i].nil? || vv.nil? ||vv[val[i]].nil?
            puts "ERROR IN BUILDING YAML WITH KEY:\n#{m}"
          else
            vv = vv[val[i]]
          end
        end
        out = out.gsub(m, vv)
      }
      out
    end

    def deeper(in_hash)
      if in_hash.class == Hash || in_hash.class == Array
        _in_hash = in_hash.to_a
        _out_hash = {}
        _in_hash.each do |dd|
          case dd
          when Hash
            _dd = dd.to_a
            _out_hash[_dd[0]] = deeper(_dd[1])
          when Array
            _out_hash[dd[0]] = deeper(dd[1])
          else
            _out_hash = replace_str(dd)
          end
        end
      else
        _out_hash = replace_str(in_hash)
      end
      return _out_hash
    end

    def generate(site)
        $site_data = site.data
        site.data.each do |data|
            site.data[data[0]] = deeper(data[1])
        end
    end
  end
end

place this code in site/_plugins/liquid_replacer.rb

in yml file use !something.someval! like as site.data.something.someval but without site.data part.

example :

_data/one.yml

foo: foo

_data/two.yml

bar: "!one.foo!bar"

calling {{ site.data.two.bar }} will produce foobar

======= OLD CODE ======

module LiquidReplacer
  class Generator < Jekyll::Generator
    REGEX = /\!([A-Za-z0-9]|_|\.){1,}\!/

    def generate(site)
      site.data.each do |d|
        d[1].each_pair do |k,v|
          v.to_s.match(REGEX) do |m|
            val = m[0].gsub('!', '').split('.')
            vv = site.data[val[0]]
            val.delete_at(0)
            val.length.times.with_index do |i|
              vv = vv[val[i]]
            end
            d[1][k] = d[1][k].gsub(m[0], vv)
          end
        end
      end
    end
  end
end
dude
  • 1
  • 1
0

Another approach would be to add an IF statement to your head.html.

Instead of using page.layout like I did on my example below, you could use any variable from the page YAML header.

<title>
  {% if page.layout == 'post' %}
    Some text with {{ site.url }} variable
  {% else %}
    {{ site.description | escape }}
  {% endif %}
</title>
Rafal Enden
  • 3,028
  • 1
  • 21
  • 16