55 Minutes

Welcome to the 55 Minutes blog.
55 Minutes is a web development consultancy in San Francisco building apps for design-led businesses and startups. On this blog we discuss the technology tools of our trade: Ruby on Rails, Django, Sass, OS X, and more.

Liquid Gist Tag for Jekyll

Code snippets are an integral part of this blog. All the cool kids use Gist these days and so do we. However, it’s a pain to write the markup for a Gist to show up properly in a post and in the RSS feed, so we decided to write a custom Liquid tag.

Proper markup for a Gist file looks like:

<div>
  <!-- For browsers that know JavaScript -->
  <script src="https://gist.github.com/12345.js?file=abc.txt"></script>

  <!-- For browsers or RSS readers that don't deal with JavaScript -->
  <noscript>
    <pre>
Raw content of a Gist file.
    </pre>
  </noscript>
</div>
gist_markup.html – View Gist

Not really something I want to remember how to do. Sure, you can create a macro in your favorite text editor, but I’m not keen on that either.

Creating the tag

Jekyll uses the Liquid template language which makes creating a custom tag quite simple, so let’s dive right in:

require 'cgi'
require 'open-uri'

module Fiftyfive
  module Liquid
    class GistTag < ::Liquid::Tag
      def initialize(tag_name, gist_ref, tokens)
        @gist_ref = gist_ref.strip

        # gist_ref is in the form of `gist_id` or `gist_id/filename`
        @gist_id, @filename = @gist_ref.split('/')
        super
      end
      
      def render(context)
        raw_uri = "https://gist.github.com/raw/#{@gist_id}/#{@filename}"
        script_uri = "https://gist.github.com/#{@gist_id}.js?file=#{@filename}"

        <<MARKUP.strip
<div id="gist-#{@gist_ref.gsub(/[^a-z0-9]/i,'-')}">
<script src="#{script_uri}"></script>
<noscript>
<pre>#{CGI.escapeHTML(open(raw_gist_uri).read.chomp)}</pre>
</noscript>
</div>
MARKUP
      end
    end
  end
end

Liquid::Template.register_tag('gist', Fiftyfive::Liquid::GistTag)
gist_take1.rb – View Gist

Save the code to your Jekyll _plugins directory and you’ll be able to insert Gists into your post:

The tag fetches the raw source from Github and produces the following HTML:

<div id="gist-12345-abc-txt">
<script src="https://gist.github.com/12345.js?file=abc.txt"></script>
<noscript>
<pre>Raw content of a Gist file.</pre>
</noscript>
</div>
gist_tag_output.html – View Gist

This works well, but now generating the blog takes forever. OK, one minute—but that feels like forever and is far too disruptive for the writing rhythm. Not to mention the problem will only get worse over time as the blog references more Gists.

Speeding things up

ActiveSupport::Cache to the rescue. Installing the activesupport gem and some minor refactoring of URI fetching gives us:

require 'cgi'
require 'open-uri'

# Require the ActiveSupport::Cache module
require 'active_support/cache'

module Fiftyfive
  module Liquid
    class GistTag < ::Liquid::Tag
      def initialize(tag_name, gist_ref, tokens)
        @gist_ref = gist_ref.strip
        @gist_id, @filename = @gist_ref.split('/')
        super
      end
      
      # A simple file-based cache in ./_tmp shared by all GistTag instances
      def cache
        @@cache ||= ActiveSupport::Cache::FileStore.new("_tmp/gist")
      end
      
      # Return from cache if possible; otherwise fetch and add it to cache
      def fetch_gist(raw_gist_uri)
        cache.fetch(raw_gist_uri) do
          open(raw_gist_uri).read.chomp
        end
      end
    
      def render(context)
        raw_uri = "https://gist.github.com/raw/#{@gist_id}/#{@filename}"
        script_uri = "https://gist.github.com/#{@gist_id}.js?file=#{@filename}"

        <<MARKUP.strip
<div id="gist-#{@gist_ref.gsub(/[^a-z0-9]/i,'-')}">
<script src="#{script_uri}"></script>
<noscript>
<pre>#{CGI.escapeHTML(fetch_gist(raw_uri))}</pre>
</noscript>
</div>
MARKUP
      end
    end
  end
end

Liquid::Template.register_tag('gist', Fiftyfive::Liquid::GistTag
gist.rb – View Gist

With 53 Gists (and Github being slow right now), the build time went from one minute to one second. If you ever need to clear out the cache, just trash the _tmp directory.

A footnote

We are Jekyll neophytes and wanted to understand the framework as it stands, therefore we didn’t use a framework like Octopress. I realize Octopress has a Gist tag as well, but frankly, I like how concise our Gist tag is. That said, once we’ve had some time with Jekyll we may upgrade to Octopress and start contributing to that community as well.

Feedback? Leave your comments on the Gist for this post.

comments powered by Disqus