Desktop web site generator Nanoc

Introduction

Nanoc is a Ruby application that can turn a bunch of web pages into a static site, ready to be uploaded to any web server without the complexity and lower performance of PHP and MySQL. Pages can be written in HTML, Markdown (kramdown), Textile, or Haml.

Note that Nanoc uses the term "items" instead of "pages", because a web site can contain pages, pictures, stylesheets, etc. All of those are "items".

Setup

Here's how to install Ruby and Nanoc on a Windows host:

  1. Download and run the RubyInstaller package. Check "Add Ruby executables to your PATH" and "Associate .rb and .rbw files with this Ruby installation". You might need to reboot for the system PATH to be updated
  2. To check that Ruby is up and running and that it has RubyGems installed, open a DOS box, and type:

    irb
    quit
    gem --version

  3. Next, install Nanoc and check that it installed OK:

    gem install nanoc
    nanoc --version
     
  4. Install the Windows console add-on for enhanced display:

    gem install win32console
     
  5. This is only needed if you write input files in kramdown, such as in the example below:

    gem install kramdown
     
  6. Adsf is needed to run a web server:

    gem install adsf

Upgrading Nanoc

gem update nanoc

If it fails with eg. "ERROR:  While executing gem ... (Errno::EACCES) Permission denied - C:/Ruby193/bin/nanoc.bat", make sure it's not an antivirus application blocking the update.

Generating a site

  1. Cd to where you'd like to keep Nanoc projects
  2. nanoc create_site tutorial
  3. cd tutorial
  4. nanoc compile
  5. nanoc view
  6. Aim your browser at http://localhost:3000

Note: If you see strange characters, ????

After first compiling a site, Nanoc creates the following files and directories:

Adding items

Source HTML pages must be located in ./content. Here's how to add a page to the site:

  1. nanoc create_item my_page
  2. Edit content/my_page.html, preserving the metadata at the top
  3. nanoc compile
  4. http://localhost:3000/my_page/

Nanoc created ./output/my_page/index.html. Anytime you add/edit an item, you must run "nanoc compile" to regenerate the site in ./output.

Customizing Nanoc

Embedding Ruby code in layout files

WYSIWYG HTML editors may be unhappy of this, so YMMV.

YAML Metadata

Input files are located in ./content. The sample ./content/index.html contains a bit of YAML metadata at the top:

---
title: Home
---

If you'd rather keep the actual web page and the YAML metadata separate, move the YAML metadata in a file that has the same name as the HTML page but with the .yaml extension, eg. content/about.html matches content/about.yaml.

Filters

It is possible to apply multiple filters to input files. Here's an example:

compile '/docs/*' do

  filter :erb #"Embedded Ruby": processes embedded Ruby within HTML pages

  filter :rdiscount #processes Markdown markup into HTML

  filter :rubypants #performs some cosmetics  (replace -- with a long dash etc)

  layout 'post' #contains some layout elements specifically for the blog part of the site

  layout 'default' #contains the general layout

end

Layout

The default template that Nanoc uses to format web pages is layouts/default.html. If you open this file, you'll notice it contains a bit of Ruby embedded code such as "<title>A Brand New nanoc Site - <%= @item[:title] %></title>".

If you wish to add your own data, edit the metadata in either the input HTML or YAML file, and add some embedded Ruby code in the layout file:

mykeyword: "John Doe"

... followed by this change in layouts/default.html anywhere within the BODY:

<body>
<% if @item[:mykeyword] %>
  <p>Here is the content of the "mykeyword" data: <%= @item[:mykeyword] %>.</p>
<% end %>
</body>

Rules

The Rules file contains instructions to process all items, such as web pages written in kramdown.

"Compilation rules determine the actions that should be executed during compilation (filtering, layouting), routing rules determine the path where the compiled files should be written to, and layouting rules determine the filter that should be used for a given layout.

A rule consists of a selector, which identifies which items the rule should be applicable to, and an action block (except for layouting rules), which contains the actions to perform.

Each item has exactly one compilation rule and one routing rule. Similarly, each layout has exactly one layouting rule. The first matching rule that is found in the rules file is used."

type: compile (filters and layouts to use), route (where the compile page will be written), layout (which layout file to use)

selector: which items or layouts this rule applies to (*, or a specific item)

instructions: code between do...end

Functions

You can write your own functions that will be run by Nanoc before compiling the pages. They should be placed in files in ./lib and be referenced in the layout page.

  1. Edit ./content/my_page.yaml:

    tags:
    - tag1
    - tag2
     
  2. Create lib/my_tags.rb:

    def my_tags_function
      if @item[:tags].nil?
        '(none)'
      else
        @item[:tags].join(', ')
      end
    end
     
  3. Edit ./layouts/default.html:

    <%= my_tags_function %></p>

Default helpers

Nanoc comes with default helpers that you can use. Here's how to display tags instead of using one's own function:

  1. Delete lib/my_tags.rb, and create lib/helper.rb:

    include Nanoc::Helpers::Tagging

  2. Leave content/my_page.yaml as is, and edit layouts/default.html:

    <p>Tags: <%= tags_for(@item) %></p>

Here's the link to the API section.

Q&A

Can the layout read meta-data from the HTML header instead of YAML?

In case your WYSIWYG HTML editor doesn't like having YAML infos in the document, you can tell Nanoc to read the meta-datas from the HEAD section: In the preprocess block, parse HTML (e.g. with Nokogiri) and assign metadata from HTML meta tags to nanoc item attributes.

Here's how to install Nokogiri and use it in Nanoc:

  1. Open a DOS box, and run "gem install nokogiri"
  2. Edit Rules thusly:


    doc = Nokogiri::HTML(open(item.identifier.chop + '.html'))
    mytitle = doc.css('title').text
    puts mytitle
  3.  

How to apply a different layout for the homepage and actual articles?

How to tell Nanoc to create output pages at the root?

By default, each new page will be created in its sub-directory, eg. ./output/my_page/index.html.

config.yaml:

data_sources:

    # The path where items should be mounted (comparable to mount points in

    # Unix-like systems). This is “/” by default, meaning that items will have

    # “/” prefixed to their identifiers. If the items root were “/en/”

    # instead, an item at content/about.html would have an identifier of

    # “/en/about/” instead of just “/about/”.

    items_root: /

 

.\tutorial\Rules

 

Adjust the routing rule from "item.identifier + 'index.html'" to "item.identifier.chop + '.html'" (you just need to be careful for the item with identifier /, because that one needs to be routed to /index.html still).

Alternatively, add metadata (item[:force_output_path] = "/about.html") to the item and use a routing rule that detects this rather than changing the default rule and moving your items ("content/about.txt" by default goes to "output/about/index.html", but could be changed to "content/about/index.txt" to still go to the same place under the example above.)

Yet a third solution is to use this rule:

route '*' do
  else
    if item.binary?
      # Write item with identifier /foo/ to /foo.ext
      item.identifier =~ %r{/([^/]+)/$}
      last_part = $1
      "/assets/img/blog/#{last_part}." + item[:extension]
    else
      # Write item with identifier /foo/ to /foo/index.html
      #item.identifier.sub(/^\/staticSite\//,") + 'index.html'
      item.identifier =~ %r{/([^/]+)/$}
      last_part = $1
      "/#{last_part}.html"
      #item.identifier.chop +  "/index.html"
   end
end

How to include the date the page was last edited?

item[:mtime] will be filled in by the filesystem data source.

How to generate the home page with the list of all pages available?

You can get the children and parent of an item using the #children and #parent methods on Nanoc::Item. For nanoc, there is only one root (item with identifier /) and you can therefore get all pages below the root using @items.find { |i| i.identifier == '/' }.children.  This way, you could also "dynamically" create second-level navigation for each page below the root.

Note that nanoc's parent / child relationships are based off of the content file structure. So, if you have, for example, a blog sructure were posts are filed under "content/blog/year/month/day/article.txt" and will never have any actual pages in year or month, your articles will be orphans.

I've previously posted some code that can be used to calculate "true relationships" (based on path, pages are given a parent that is the closest page in a higher level) and linked those below. It's structured as a nanoc helper that could be dropped into your site's lib directory: https://gist.github.com/1132866

How to tag an article so that it's displayed in the right section in the home page?

Just edit its YAML infos, and write the relevant loop to create different sections in the home page.

Is "nanoc create_item" required to add pages?

... or can I just add my own pages in ./content?

Can I write pages in HTML?

Yes. If you save your pages/items/articles as normal html files, they'll work just fine. Make sure your compilation rules don't add markdown or haml filters, and you'll be set.

When creating content pages for use with nanoc, normally the top of these pages has YAML metadata like so:

---

title: How to Generate a Table of Contents

kind: article

created_at: 2012-07-13

---

[body text here in HTML, Markdown, Textile, etc.]

Komposer or other HTML editor doesn't understand this heading as it's not HTML.  In general this might not be a problem unless you use a feature to reformat HTML but if it is, you can put this medadata in a new file with the same base name as the content file but an extension of .yaml.

Somewhere there was a nanoc filter that scanned an HTML5 page using nokogiri and generated a table of contents based on the type and level of tags used but I can't find it at present. Thought Denis might have written it.

Does Nanoc only recompile pages that were added/edited?

Yes. In practice this can be a bit more complicated than just was this page modified since the last compile, but for the most part, yes.

Can Nanoc insert a table of contents in each page?

Yes. Typically you'll have a template for your overall page layout and you could have custom layouts for different page types (blog post, link post, project page, about, etc.). You can even do things like embed a sub-layout within the master layout so you get the most out of code re-use.

So, it'd be easy enough to have a layout that generates a table of contents from the pages that will be generated from that layout. You could also probably do this on a page by page basis, but that seems less nanoc-y to me.

"compile '/foo/' do

  filter   :markdown

  snapshot :without_toc

  filter   :add_toc

end"

What alternative data sources are available besides filesystem_unified?

In ./Rules, what does "filter :erb" mean?

"Embedded Ruby", ie. items that contain Ruby code within.

Is there a Windows WYSIWYG Editor that supports...

  1. Creating a couple of templates so that I don't have to add eg. "<meta name="category" content="unix">" every time I add a document in Nanoc
  2. Fast

Resources