nanoc is a tool that runs on your local computer and compiles documents written in formats such as Markdown, Textile, Haml,… into a static web site consisting of simple HTML files, ready for uploading to any web server.
Installation
Start by installing the following dependencies
# apt-get install ruby-dev zlib1g-dev libglib2.0-dev
Continue on with all the required Gems.
% gem install nanoc
% gem install adsf               # A Dead Simple Fileserver
% gem install fssm               # File System State Monitor
% gem install kramdown           # Markdown parser
% gem install haml               # HTML Abstraction Markup Language
% gem install less               # Invoke the Less CSS compiler from Ruby
% gem install pygments.rb        # Exposes the pygments syntax highlighter to Ruby
% gem install coderay            # Another Syntax Highlightter
% gem install coderay_bash       # Plugin to process Bash
% gem install stringex           # useful extensions to Ruby's String class
% gem install nokogiri           # Parser used by `nanoc validate-links`
% gem install rainpress          # A CSS compressor
% gem install therubyracer       # Call javascript code and manipulate javascript objects from ruby.
% gem install rpeg-multimarkdown # MultiMarkdown processing used in a new Filter. (req. libgtk2.0-dev.)
% gem install nanoc-guard		 # now replace nanoc watch command [see [docs](https://github.com/guard/guard-nanoc)]
Create a web site
create yet subdirectory with a blank web site
% nanoc create_site yet 
Layout
edit the surrounding layout displayed on every page layouts/default.html:
<!DOCTYPE HTML>  
<html lang="en">
	<head>
	  <meta charset="utf-8">
	  <title>My blog - <%= @item[:title] %></title>
	  <link rel="stylesheet" type="text/css" 
	    href="http://twitter.github.com/bootstrap/assets/css/bootstrap.css" 
	    media="screen">
	  <link rel="stylesheet" type="text/css" href="/style.css">
	</head>
	<body>
	  <div class='navbar'>
	    <div class='navbar-inner'>
	      <div class='container'>
	        <a class='brand' href='/'>My Blog</a>
	        <ul class='nav'>
	          <li class='active'><a href='/'>Home</a></li>
	          <li><a href='/about'>About</a></li>
	        </ul>
	      </div>
	    </div>
	  </div>
	  <section class='content'>
	    <%= yield %>
	  </section>
    </body>
</html>
Stylesheet
edit the corresponding stylesheet content/stylesheet.css:
.content {
  width: 800px;
  background: #f5f5f5;
  border: 1px solid #ddd;
  border-top: none;
  margin: 0 auto;
  padding: 60px 20px 0 60px;
}
.post aside {
  color: #888;
  padding-bottom: 8px;
  border-bottom: 1px solid #aaa;
}
.post article {
  margin: 10px 0 60px 0;
}
Compile web site
% nanoc compile
If you don’t want to manually compile after each modification use instead:
% nanoc watch
nanoc looks in your content directory for files and processes them based on rules that you write in Ruby.
- compile rules = how to compile a file. Parser ?
- route rules = output directory ? where to put the compiled file ?
View site
to access your site at http://127.0.0.1:3000 first run:
% nanoc view
Helpers
edit lib/default.rb and paste in the following:
include Nanoc::Helpers::Blogging
include Nanoc::Helpers::Tagging
include Nanoc::Helpers::Rendering
include Nanoc::Helpers::LinkTo
- Blogging add title and created_at fields and provides some helper methods to our layouts to list posts
- Tagging lets us add tags to content items and query them
- Rendering allows us to nest layouts
- LinkTo lets us construct URLs for other items
Creating a Post
% mkdir content/posts
% vi 2012-11-20-first-post.md
---
title: "Just a small test post"
created_at: 2012-11-20 17:30:00 +0000
kind: article
---
This is the first Yet post. There is nothing more right now.
content between --- are metadata that will becomes available within our rules and layouts
kind: article required by Blogging Helper to determine which content items are considered posts
Rules
Rules evaluated in sequential order, the first one that match gets applied, lets add one before the route ‘*’ in our Rules file:
route '/posts/*' do
 	  y,m,d,slug = /([0-9]+)\-([0-9]+)\-([0-9]+)\-([^\/]+)/
       .match(item.identifier).captures
  "/#{y}/#{m}/#{slug}/index.html"
end
slug: is a URL safe version of the post title
item.identifier: is the filename (without extension) of the file currently being processed
Formatting
nanoc offers a great deal of flexibility. Here we will change the Markdown instead of default ERB one. Add this to Rules.
compile '/posts/*' do
  filter :kramdown
  layout 'default'
end
Blog posts Layout
create layouts/post.html with the following content:
<% render 'default' do %>
	<div class='post'>
		<h1><%= item[:title] %></h1>
		<aside>Posted at: <%= item[:created_at] %></aside>
		<article>
			<%= yield %>
		</article>
	</div>
<% end %>
And change Rules
compile '/posts/*' do
  filter :kramdown
  layout 'post'
end
This layout use the Rendering helper added above
Listing recent posts on the index page
edit content/index.html to put the following ERB template:
<% sorted_articles.each do |post| %>
 	<div class='post'>
		<h1><%= link_to post[:title], post.path %></h1>
		<aside>Posted at: <%= post[:created_at] %></aside>
		<article>
			<%= post.compiled_content %>
		</article>
  	</div>
<% end %>
sorted_articles is a variable provided by Blogging helper, contains an ordered list of every Kind: article post.
link_to: generate a link to the full post.
You can now add another post to check it’s working great !!!
Human readable date
Lets create a nanoc helper, add this at the bottom of lib/default.rb
module PostHelper
  def get_pretty_date(post)
    attribute_to_time(post[:created_at]).strftime('%B %-d, %Y')
  end
end
include PostHelper
Use this helper in both layouts/post.html and contents/index.html
<aside>Posted at: <%= get_pretty_date(item) %></aside>
Fold articles on the index page (a fold)
We will use another helper method and a tag like <!-- more -->, first add this tag in one of your article.
Now create add the following helper method to lib/default.rd
def get_post_start(post)
  content = post.compiled_content
  if content =~ /\s<!-- more -->\s/
    content = content.partition('<!-- more -->').first +
    "<div class='read-more'><a href='#{post.path}'>Continue reading ›</a></div>"
  end
  return content
end
You can now use it within content/index.html
<article>
  <%= get_post_start(post) %>
</article>
Use a Rake task to easily create new blog posts
Create Rakefile in your site root with the following content:
# encoding: utf-8
require 'stringex'
desc "Create a new post"
task :new_post, :title do |t, args|
  mkdir_p './content/posts'
  args.with_defaults(:title => 'New Post')
  title = args.title
  filename = "./content/posts/#{Time.now.strftime('%Y-%m-%d')}-#{title.to_url}.md"
  if File.exist?(filename)
    abort('rake aborted!') if ask("#{filename} already exists. Want to overwrite?", ['y','n']) == 'n'
  end
  puts "Creating new post: #{filename}"
  open(filename, 'w') do |post|
    post.puts '---'
    post.puts "title: \"#{title}\""
    post.puts "created_at: #{Time.now}"
    post.puts 'kind: article'
    post.puts 'published: false'
    post.puts "---\n\n"
  end
end
Now you can use the following command to create a new post
rake new_post["ceph"]
Filters
As of today nanoc provides the following filters : AsciiDoc, BlueCloth, CodeRay, CoffeeScript, ColorizeSyntax, ERB, Erubis, Haml, Handlebars, Kramdown, Less, Markaby, Maruku, Mustache, Pandoc, RDiscount, RDoc, Rainpress, RedCloth, Redcarpet, RelativizePaths, RubyPants, Sass, Slim, Typogruby, UglifyJS, XSL, YUICompressor.
Creating a new filter to process MultiMarkdown is quite simple. You just need a RubyGems able to process your content like rpeg-multimarkdown. As you can see below, you just need to subclass Nanoc::Filter and override the #run method in charge of transforming the content :
require 'rubygems'
require 'multimarkdown'
 
 class MultiMarkdown < Nanoc::Filter
   identifier :mmd
 
   def run(content, args)
   	 MultiMarkdown.new(content).to_html
   end
	
end
identifier will then be used in compilation rules to process content this filter.
Tags
Install Nanoc::Helpers::Tagging
Make sure you’ve added Tagging helpers to your lib
File lib/helpers.rb
include Nanoc::Helpers::Tagging
which provides :
- tags_for: return [String] A hyperlinked list of tags for the given item
- items_with_tag: return [Array] All items with the given tag
- link_for_tag: return [String] A link for the given tag and the given base URL
For example to display tags in an article, you can do the following :
File layouts/post.haml
%p= tags_for(@item, :base_url => '/tags/')
Get tagging_extra into your lib
Get the source at github, find more details in this thread.
It provides the following methods:
- tag_set: returns all the tags present in a collection of items or within all the site without collection argument
- has_tag?: return true if an items has the specified tag
- items_with_tag: finds all the items having a specified tag
- count_tags: count the tags in a given collection of items or in overall site if no collection argument
- rank_tags: return a hash such as: { tag => rank } lower rank is better.
Create Tag Page layout
File layouts/_tag_page.haml
---
---
%section{:id => 'content', :class => 'panel'}
	%h2
		%em
			= "#{tag.capitalize} Articles"
%section{:id => 'content', :class => 'blog'}
	- items_with_tag(tag).each do |item|
		.blog-entry
			-#%a(href="#{item}" title="Full article" class="permalink")= "« #{item[:title]}"
			%aside
				.date
					.month= get_post_month(item)
					.day= get_post_day(item)
			%article
				%h2= link_to item[:title], item.path
				= find_and_preserve do
					= get_post_start(item)
Add helpers to generate tag pages
File lib/helpers.rb
# Creates in-memory tag pages from partial: layouts/_tag_page.haml
def create_tag_pages
  tag_set(items).each do |tag|
    items << Nanoc::Item.new(
      "= render('_tag_page', :tag => '#{tag}')",           # use locals to pass data
      { :title => "Category: #{tag}", :is_hidden => true}, # do not include in sitemap.xml
      "/tags/#{tag}/",                                     # identifier
      :binary => false
    )
  end
end
Create All Tags page
File content/tags.haml
---
title: All Tags
is_hidden: true
---
%section{:id => 'content', :class => 'panel'}
	%h2
		%em
			All Tags
	%p Listed are the set of tag links related to articles in this site. The number of articles related to a tag 	succeeds the tag.
	
	.tags-page
		- tags = count_tags()
		%ul
			- tags.sort_by{|k,v| k}.each do |tag_count|
				- tag = tag_count[0]
				- count = tag_count[1]
				%li
					%a(href="/tags/#{tag}/" class='tag')= tag
					= "[#{count}]"
Create a Rules preprocess to generate each tags page item in memory
File Rules
preprocess do
  # authors may unpublish items by setting meta attribute publish: false
  items.delete_if { |item| item[:published] == false }
  create_tag_pages
end
Manage static content
Some content like plain CSS don’t need any nanoc processing, you just need to position them in a static directory below content. The simplest method to copy them to output is to add a copy_static call to your preprocess Rules.
File Rules
preprocess do
  # authors may unpublish items by setting meta attribute publish: false
  items.delete_if { |item| item[:published] == false }
  copy_static
  create_tag_pages
end 
Now, you need to add copy_static to your helpers
File lib/helpers
def copy_static
	FileUtils.cp_r 'static/.', 'output/' 
end
highlighters helpers
Configure colorize_syntax.rb
By using the provided colorize_syntax nanoc helper, you can easily colorize your <pre><code> blocks, like we do in this page. You just have to indent your code block four spaces and put a comment to describe the language in the first line like this:
#!ruby
#!language
example
#!ruby
You’ll find the list of supported language on the Coderay site
CodeRay CSS
Let’s use the trick above and put our coderay.css into our static directory and add it to your default.haml layout.
%link{:rel => 'stylesheet', :type => 'text/css', :href => '/coderay.css'}
This file will be copied at compilation time, see below for the way to do just that.
Call the colorizer in your Rules
By default nanoc use Coderay so you don’t need a line saying :default_colorizer => :coderay
File Rules
compile '/posts/*' do
  filter :mmd
  filter :colorize_syntax,
                :colorizers => { :ruby => :coderay },
                :coderay    => {}
  layout 'post'
end 
Use :coderay    => { :line_numbers => :inline } to add line numbers.
Creating a Portfolio
First create an entry in a new portfolio directory with a name like 2012-11-20-openstack.md containing
---  
title: OpenStack  
url: http://www.openstack.org  
created_at: 2012-20-11  
kind: portfolio  
image_id: openstack  
---
OpenStack Foundation is now officialy created. Let's Join it.
Create Portfolio Helper lib/portfolio.rb
module PortfolioHelper
	
  def portfolios
    @items.select { |item| item[:kind] == 'portfolio' }
  end
  def sorted_portfolios
    portfolios.sort_by { |p| attribute_to_time(p[:created_at]) }.reverse
  end
  def portfolio_image_url(item, type)
    '/images/portfolio/' + item[:image_id] + '_' + type + '.jpg'
  end
end
include PortfolioHelper
Create associated Images
openstack_full.jpg
openstack_small.jpg
openstack_large.jpg
Create content/portfolio.haml for index rendering
%h2 My portfolio
- sorted_portfolios.each do |entry|
  .portfolio-entry
    %h3= link_to entry[:title], entry
    .picture{:style => 'background-image:url(' + portfolio_image_url(entry, 'small') + ')'}
Create layouts/portfolio.haml for portfolio rendering
%h2= item[:title]
.portfolio-full
	- if item[:url]
.url= '<strong>URL:</strong> ' + item[:url]
.picture{:style => 'background-image:url(' + portfolio_image_url(entry, 'full') + ')'}
.details
	= yield
Add a new Rule for portfolio rendering/routing in Rules
compile '/portfolio/*' do
  filter :kramdown 
  layout 'portfolio'
end
route '/portfolio/*' do
  y,m,d,slug = /([0-9]+)\-([0-9]+)\-([0-9]+)\-([^\/]+)/.match(item.identifier).captures
  "/portfolio/#{y}/#{slug}/index.html"
end
References
- This article is built from some Clark Dave blog posts
Links
- nanoc google groups