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:
- Process any embedded ruby
- Process any markdown in the item
- Run the item through the "post" layout
- 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