Tuesday, August 28, 2012

Using Jekyll to Create a Site on GitHub

I wanted to try building a site on GitHub using Jekyll. I opted to utilize the Jekyll-Bootstrap (JB) as a starting point. I utilized these references as I worked on tweaking the site for my purposes.

The concept behind Jekyll is to build a templated site and then "compile" it into individual static HTML files. The input files can be a variety of formats which is convenient for writing blog posts or other pages. The primary target is for creating a blog style site. However, I already have blog, but wanted a site to link to with full demos of the projects/tutorials I write about here. Coupled with my desire to learn how to leverage GitHub and the various parts of its ecosystem, I decided that it was a perfect opportunity to learn by using Jekyll to generate this sandbox/demo site.

Since the setup is geared towards blog posts, it took a little bit to figure out how to organize my pages and get them to appear where I wanted them. There were two primary issues I had to solve:

  1. Create a hierarchy of pages organized by project and then by individual demos

  2. Leverage the tagging concept outside of blog posts. It did not want to work out-of-the-box for pages


The first problem was solvable by learning how JB was rendering pages. The default setup has a navigation menu that links to pages. If you open up _includes/themes/twitter/default.html you'll find this bit of code (if you're working with a different template, go to that directory instead):


<div class="navbar">
<div class="navbar-inner">
<div class="container">
<a class="brand" href="{{ HOME_PATH }}">{{ site.title }}</a>
<ul class="nav">
{% assign pages_list = site.pages %}
{% assign group = 'navigation' %}
{% include JB/pages_list %}
</ul>
</div>
</div>
</div>


The include JB/pages_list is what's rendering the menu. Its setting up two variables which will probably be used inside that template to do something. So opened _includes/JB/pages_list and the helpful comments indicated that the optional group variable will filter pages that have the specified "group" variable in the YAML Front Matter block.

Now I had what I need to organize my site. I created a projects directory where I placed an index.md file and set the group to 'navigation'. This brought my projects page into the menu. On this page, I added the above code again but with group = 'projects'

... content ...
<ul class="nav">
{% assign pages_list = site.pages %}
{% assign group = 'projects' %}
{% include JB/pages_list %}
</ul>
... more content ...



Now I had a list of individual projects that I've worked on. I created a directory per project inside the projects folder and added another index.md file with the same code again. However, this time I assigned the specific project name as the group so all the related files will appear in the list:

... content ...
<ul class="nav">
{% assign pages_list = site.pages %}
{% assign group = 'colorbox' %}
{% include JB/pages_list %}
</ul>
... more content ...


The order of the lists is the same as the ordering in the filesystem. If you can map everything into this basic nomenclature, then you'll be up and running fairly quickly.

The second item is more complicated. Jekyll is not adding the tags in the YAML to the site.tags variable. As a result, the default JB tags.html page had nothing on it even though I had created some tags. I had to dig through the source to determine its absence. Technically, I really don't need the tags, but thought it would be nice. Plus, now, I can write a Jekyll plugin to do it!

I can't claim full credit for figuring out how to do this. I found an example on Brian Clapper's Brizzled blog. His hacking is more than I needed - I just wanted Jekyll to do the same thing is does for its posts. But he gave the starting point of how to pull this off.

What I'm essentially going to do is copy the code from the post class and monkeypatch the page class with that code. I'll then be able to reference the tags the same for pages as I would for posts. I added the _plugins/page.rb file and put this code in it:


module Jekyll

# Extensions to the Jekyll Page class.

class Page

# Override initialize to also
# parse the tags from the page
# chain original call

attr_accessor :tags

alias orig_initialize initialize
def initialize(site, base, dir, name)
orig_initialize(site, base, dir, name)
self.tags = self.data.pluralized_array("tag", "tags")
end

end
end


Now I thought I was really smart ... but it didn't work.

So I did some more digging and realized that the above enhancement only added the tags to the page object not to the site object. So, I needed to add a little more customization. The Site class has a site_payload function which generates the site object. Part of that process includes creating the hash of individual tags to their related posts. However, this function only performed this operation on posts. So I needed an equivalent function that performed this on pages. Below is my _plugin/site.rb file which adds the page_attr_hash function and then proxies the site_payload function to merge the results from page_attr_hash into the tags object. Since I called the original site_payload, it will contain a "tags" hash for posts. I want to merge the page's tags hash with it so, if I do happen to add posts, those tags will still work:


module Jekyll

class Site

def page_attr_hash(page_attr)
# Build a hash map based on the specified page attribute ( page attr =>
# array of pages )
hash = Hash.new { |hsh, key| hsh[key] = Array.new }
self.pages.each { |p| p.send(page_attr.to_sym).each { |t| hash[t] << p } }
hash
end

alias orig_site_payload site_payload
def site_payload
h = orig_site_payload

payload = h["site"]
payload["tags"] = payload["tags"].merge(page_attr_hash('tags'))
h["site"] = payload

h
end

end
end


Now, when I load the tags page, I have all my pages organized by tags.

So I thought I was good - I checked everything in and went to GitHub to see the fruits of my labor and ... nothing ... my tags were not there. Turns out, GitHub pages disable plugins when rendering your site. The solution: add all my site's source to a different repository, generate the site locally, and push that content to my GitHub pages repository.

Final Thoughts


Jekyll is a great tool for leveraging the GitHub platform and building a static site. If you're goal is to publish a blog style site, it works with little effort as most of the functionality is centered around publishing blog posts. If you want similar features for your pages, you will need to customize Jekyll accordingly. Jekyll-Bootstrap is a great way to spring-board your site by adding time saving tools and predefined layout templates. You don't need JB but it does have some useful pieces that will save you the effort of writing them yourself. Additionally, this setup works for Personal Pages. If you want to make it work for Project Pages, you'll have to do some more work to coordinate the changes to the gh_pages branch of your project.

If you'd like to see any of the source for the site or the actual site itself, you can access it on GitHub at github.com/bseth99/sandbox/.