develop with

Custom tag with attributes in Jekyll

Process attributes in a tag within Jekyll

To illustrate how to process attributes in a Jekyll tag, we’ll create a tag that will include a partial file from the includes directory. Jekyll uses Liquid as the template languages. So, in order to hook into the template framework, we’ll need to extend Liquid::Tag. This is pretty straight forward as you just need to override the render method, as seen below.

 class PartialIncludeTag < Liquid::Tag
    def render(context)
       "hello we got a tag!"
    end
 end

And in order to use the tag within your pages you’ll need to register it with Jekyll and call it in your pages. To register see the example below.

Liquid::Template.register_tag('partialinclude', PartialIncludeTag)

This tag can be called by the following:

{{ "{% partialinclude " }}%}

Now, let’s get to the meat of why you got here. Let’s process attributes in our liquid tag. Based on the tag definition of a template definition attribute and a name attribute.

{{ "{% partialinclude template: hello_world.html, name: Hello " }}%}

The tag will evolve to process the attributes and eventually include the template passing the attributes to the template. Here is how you process the attributes in a liquid tag:

 class PartialIncludeTag < Liquid::Tag

    def initialize(tag_name, markup, tokens)
      super
      @attributes = {}
      markup.scan(::Liquid::TagAttributes) do |key, value|
        @attributes[key] = value
      end
      @markup = markup
    end

 	def render(context)
       "hello we got a name: #{@attributes['name']}!"
    end

end

To put it all together, we’ll need to get the source directory and concat it with the _includes directory to retrieve the template. And we’ll need to make sure we can fallback to just having the template defined without a name or template variable. The Liquid::Template.parse method will take the contents of the template and process it with the a map passed into a render method. The file will be loaded in a local method called load_template.

Below is the whole kit and caboodle of the whole tag.

 class PartialIncludeTag < Liquid::Tag

    def initialize(tag_name, markup, tokens)
      super
      @attributes = {}
      markup.scan(::Liquid::TagAttributes) do |key, value|
        @attributes[key] = value
      end
      @markup = markup
    end

    def load_template(file, context)
      includes_dir = File.join(context.registers[:site].source, '_includes')

      if File.symlink?(includes_dir)
        return "Includes directory '#{includes_dir}' cannot be a symlink"
      end

      if file !~ /^[a-zA-Z0-9_\/\.-]+$/ || file =~ /\.\// || file =~ /\/\./
        return "Include file '#{file}' contains invalid characters or sequences"
      end

      Dir.chdir(includes_dir) do
        choices = Dir['**/*'].reject { |x| File.symlink?(x) }
        if choices.include?(file)
          source = File.read(file)
        else
          "Included file '#{file}' not found in _includes directory"
        end
      end

    end

    def render(context)
      template_data = @attributes
	  unless template_file = @attributes["template"]
        template_file = @markup.strip
      end
      template = load_template(template_file, context)
      Liquid::Template.parse(template).render(template_data).gsub(/\t/, '')
    end

  end

comments powered by Disqus

Want to see a topic covered? create a suggestion

Get more developer references and books in the developwith store.