Static site gen is all the rage these days. And why not? Wordpress is horrible. It makes me gag just thinking about it.

Out of all the static site generators, Jekyll gets all the love. Atmittedly, it's a cool little app, perfect for Github Pages, and it couldn't be easier to use. But here's the problem: it was built to compile untrusted content.

Jekyll uses a combination of liquid templates, markdown, and html. While these are great for keeping the idiots at bay, they're not so much fun if you want to hack around, add categories, pull in your twitter feed, etc.

In fact, if you want extend to jekyll, you have to fork the entire project! Now where would we be if you had to fork rails every time you wanted to add a new feature. It's madness!!!

Now, that said, let me introduce you to my little friend, my BFF, my new soul-mate: Nanoc.

How Nanoc is Exactly Like a Ninja

Nanoc is a static site generator, a lot like Jekyll, but about a billion times more flexible. Why?

  • ERB! - Ruby in your pages. In case you need to generate a uuid or something. a4a3e180-50c8-012d-04fb-60fb42ef776a. Holy @#$#$ I JUST DID!

  • Plugins!! - You get a nice /lib directory, just like rails.

  • Compilation Rules!!! - You have super fine-grained control over how your pages are laid out and compiled.

So enough propaganda, let's make a blog:

Install Nanoc

Nanoc comes as a gem, so it's a cinch to install.

  sudo gem install nanoc3 BlueCloth coderay

Take it For a Spin

Now that you have the nanoc gem installed, try typing "nanoc3" in the terminal. You should see a nice help screen.

starr@gummy ~> nanoc3
nanoc, a static site compiler written in Ruby.

Available commands:

    autocompile          start the autocompiler
    compile              compile items of this site
    create_item          create a item
    create_layout        create a layout
    create_site          create a site
    help                 show help for a command
    info                 show info about available plugins
    update               update the data stored by the data source to a newer version

Global options:

    -d --debug           enable debugging (set $DEBUG to true)
    -h --help            show this help message and quit
    -C --no-color        disable color
    -V --verbose         make nanoc output more detailed
    -v --version         show version information and quit
    -w --warn            enable warnings

Create the Empty Site

Much like rails, nanoc will create a project directory for you:


starr@gummy ~/d/projects> nanoc3 create_site blog
      create  config.yaml
      create  Rakefile
      create  Rules
      create  content/index.yaml
      create  content/index.html
      create  layouts/default.yaml
      create  layouts/default.html
Created a blank nanoc site at 'blog'. Enjoy!

That was easy. Now you can fire up the test server and try it out. When nanoc3 is run in 'autocompile' mode, it will detect any changes you make to the source files, and automatically recompile the site, placing the results in the "output" directory. It also provides a simple webrick server for serving up the compiled files, accessible at http://localhost:3000/

You can edit the index.html file and see the changes in your browser if you like.

Some Silly Config

You'll need to add a base_url to your config file like so:

--- 
data_sources: 
- items_root: /
layouts_root: /
type: filesystem_compact
output_dir: output
base_url: http://mysite.com

Help Me Out, Bro!

Nanoc comes with a nice system for creating extensions. It even comes with lots of them, but for some reason these aren't enabled by default. So lets enable them all. Edit your lib/default.rb file so that it looks like this:

# lib/default.rb

include Nanoc3::Helpers::Blogging
include Nanoc3::Helpers::Breadcrumbs
include Nanoc3::Helpers::Capturing
include Nanoc3::Helpers::Filtering
include Nanoc3::Helpers::HTMLEscape
include Nanoc3::Helpers::LinkTo
include Nanoc3::Helpers::Rendering
include Nanoc3::Helpers::Tagging
include Nanoc3::Helpers::Text
include Nanoc3::Helpers::XMLSitemap

# This is just some syntactical sugar that we use later
# Don't worry about it for now.
class Nanoc3::Item
  def content(opts = {})
    opts[:rep] ||= :default
    opts[:snapshot] ||= :last
    reps.find { |r| r.name == opts[:rep] }.content_at_snapshot(opts[:snapshot])
  end

  def name
    identifier.split("/").last 
  end
end

Nanoc will require every file in the lib directory, so that's where you can place any of your own hacks.

Creating your first post

If you were using wordpress (blech), blog posts would live in a database. With nanoc, it's all files. For each blog post or other page, there are two files: item_name.html, which includes the item content, and a file item_name.yaml, which includes metadata such as the title, date published, etc.

starr@gummy ~/d/p/blog> nanoc3 create_item posts/first_post
      create  content/posts/first_post.yaml
      create  content/posts/first_post.html
An item has been created at /posts/first_post/.

We'll be using the Blogging helper, so our yaml file has to contain certain data. Open it up and make it look like so:

--- 
kind: article
created_at: 9/20/2009
title: "Hello World!"

Laid out

Each blog post will be run through one or more "layouts", according to rules that you specify. We'll cover the details in a second, but for now, edit layouts/default.html.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
  <head>
    <title><%= @item[:title] %></title>
  </head>
  <body>

  <%= yield %>

  </body>
</html>

Great Scot! That looks like a rails layout!

Now, if you run "nanoc3 compile && nanoc3 aco', and navigate to http://localhost:3000/posts/first_post/ you should see your first post. It isn't pretty, but we're getting there.

Another Layout for Posts

It's probably not a good idea just to dump the post body into the main layout. We might want to show the author name, or comments. So lets make a new layout called "post".

starr@gummy ~/d/p/blog> nanoc3 create_layout post
      create  layouts/post.yaml
      create  layouts/post.html
A layout has been created at /post/.

Open up layouts/post.html, and make it so:

<h1><%= @item[:title] %></h1>
<h2>Written at: <%= @item[:created_at] %></h2>
<p><%= yield %></p>

Rules

We have to tell nanoc to use the post layout for any posts. There's a file called Rules in your project directory that handles all of that.

Open it, and add the following rule to the top of the file:

compile '/posts/*' do
  rep.filter :erb
  rep.filter :bluecloth
  rep.layout 'post'
  rep.layout 'default'
end

Let's unfold this, because it's pretty cool. This rule says that for all of the pages in the /post directory:

  1. Process any embedded ruby
  2. Process any markdown in the item
  3. Run the item through the "post" layout
  4. Run the result of #3 through the "default" layout.

You have complete control of over the layout and processing of each post. Want to run HTML Tidy on all of your output? No problem. Decided you want to convert all emoticons into rad animated gifs? It's cake.

And - oh yes - this is only the beginning.

The index (or is it?)

We need to make an index page. At this point, you might be thinking to yourself, "Wow, and index page. That's boring." You might think that...if you're a FOOL!

The index page is just a clever ploy to allow us to talk about another cool nanoc feature: Representations, or if you are cool, "reps".

Think about it like this. Your blog post is just a chunk of data. We've already defined one representation of that data, the "full post" page. For the index, we'll define a second, "post summary", representation of the data.

So the first thing to do is to make a new layout called "post_summary".

starr@gummy ~/d/p/blog> nanoc3 create_layout post_summary
      create  layouts/post_summary.yaml
      create  layouts/post_summary.html
A layout has been created at /post_summary/.

Open up post_summary.html, and add this:

<%= link_to(@item[:title], @item.content) %>
<%= @item[:created_at] %> 

Now we need to add a new rule to define the representation:

route '/posts/*', :rep => :summary do
  nil # Don't create files for summaries
end

compile '/posts/*', :rep => :summary do
  rep.filter :erb
  rep.filter :bluecloth
  rep.layout 'post_summary'
end

The "route" statement just tells nanoc that post summaries should not be written to disk. They're just for internal use.

And the "compile" statement works very similarly to the one for full posts. The only difference is that it doesn't run the results through the "default" layout. This makes sense, because the summary is just a little fragment of HTML that we'll use in our index.

Open up the existing index.html file and make it look like so

<% @site.sorted_articles[0, 10].each do |post| %>
  <%= post.content :rep => :summary %>
<% end %>

And BAM! There you have it. A blog.

Resources

Ok, so it's a pretty lame blog. But it's not too hard to flesh out. Be sure to check out: * The users guide * The API docs