nanoc notes

Nanoc is a static HTML generator. I use is to generate the content for this site.

Index

Referencing configuration

You can add config to config.yaml file, in YAML format, and use it in your code as follows:

@site.config.your_config

Prevent nanoc assuming a file is binary

Nanoc will assume a file is binary unless it has one of the extensions listed in the text_extensions array in config.yaml. If you attempt to apply a filter to an item without an extension you’ll get a “textual filters cannot be used on binary items” error.

See Feed misidentified as a binary item.

Filtering layouts / partials

To set the filter used for a layout / partial, add a rule to the Rules file that specifies which filter should be used:

layout '/partial/', :kramdown

Where /partial/ is the id of the partial (and its filename is ‘partial’ or ‘partial.kramdown’ or ‘partial.anything’ as far as I know).

At the time of writing, multiple filters are not supported using this mechanism, though they are supported using the compile rules (see nanoc › Basic Concepts.

Multiple filters on partials

As mentioned above, you can’t set multiple filters for a partial using the layout clause in Rules. This means that if you need to create items programatically and have their kramdown content (or whatever markup language) generated via an erb partial, then you need to leverage a compile rule (it’s a little hacky, but it works).

First, create a compile rule (in the Rules file) that checks the extension of an item in order to determine which filter to use (see Using filters based on file extensions for the official documentation on how to achieve this).

The following rule will filter files with a .kramdown extension through the kramdown filter, .haml through the haml filter and won’t filter html files at all.

compile '*' do
  case item[:extension]
    when 'kramdown'
      filter :kramdown
    when 'haml'
      filter :haml
    when 'html'
      # no extra filter for html
  end
end

Next, make sure your partial is being filtered as erb (or whatever you want). The following call in Rules will ensure that all partials are filtered using the erb filter:

layout '*', :erb

You could alternatively choose to use erb for only the partial we’re using to generate the item content:

layout ‘/_the_partial/’, :erb

With these rules in place you can manually set the extension of the item when you create it.

items << Nanoc3::Item.new(
  render('_the_partial'), # Content
  { :title => title, :extension => 'kramdown' }, # Attributes
  'the_page', # Identifier
  :binary => false
)

Now, when _the_partial is rendered it will be ran through the erb filter and will generate kramdown for the item’s content. When the item is compiled it will be ran through the kramdown filter, resulting in your final HTML page.

Hidden files

Nanoc ignores hidden files. To get around this, add a route to the Rules. For example, if we have htpasswd.txt in the ‘content’ directory, then we’d need a rule to compile this to the ‘output’ directory:

route '/htpasswd' do
  '/.htpasswd'
end

Note that you must give the file a .txt extension to prevent nanoc assuming it’s a binary file (in which case you’d get a “textual filters can not be used on binary items” error).

If you want to filter the file, then use a compile rule. This example filters htaccess.erb through the erb filter:

compile '/htaccess' do
  filter :erb
  # don’t layout
end

route '/htaccess' do
  '/.htaccess'
end

Controlling preprocess operations

From Using preprocess or a custom command to compile certain types of item:

In order to control what preprocessing was done I’m now using an environment variable, testing it in the preprocess function to determine what operations to carry out. The environment variable is easily set by using a rake task.

Rakefile:

...
desc 'Build by compiling then copying assets and search to output'
task :build => [ :compile, :copy_assets, :copy_search ]

Rules:

...
preprocess do
  with = ENV['with'].nil?? [] : ENV['with'].split(',')
  create_tag_pages if with.include?('tags')
  create_bookmark_pages if with.include?('bookmarks')
end

This allows me to compile the site (with assets and search data) and control the preprocess, as follows:

rake build
rake build with=tags
rake build with=bookmarks
rake build with=tags,bookmarks

RuntimeError: … does not start with a slash

If you start using a new filename extension, e.g. .kramdown, without adding it to config.yaml, then you’ll probably receive an error like this:

RuntimeError: The path returned for the <Nanoc3::ItemRep:0x3fe48ffd3028 name=default binary=true raw_path= item.identifier=/> item representation, “.kramdown”, does not start with a slash. Please ensure that all routing rules return a path that starts with a slash.

The solution is to add the extension to the text_extensions array in config.yaml, so that nanoc does not assume files with that extension are binary.

Missing attributes

If you get the following error when you compile then you may find that an item has missing attributes:

NoMethodError: undefined method `<=>’ for nil:NilClass

This particular error was complaining about the ‘<=>’ method because it occurred when performing a sort operation. The undefined method could be anything, depending on what the system is doing when it attempts to read a missing attribute.

As with the “does not start with a slash” error, this could be due to using a filename extension that isn’t listed in the text_extensions array in config.yaml.

The solution is to add the extension to the text_extensions array in config.yaml, so that nanoc does not assume files with that extension are binary.

Syntax highlighting with Coderay (via Kramdown)

If you’re using kramdown and you have the coderay gem installed, then simply filter your items with the following:

You may need to install Coderay:

gem install coderay

Configure the filter:

filter :kramdown,
       :enable_coderay => true,
       :coderay_line_numbers => nil

Or:

filter :kramdown,
       :syntax_highlighter => 'coderay',
       :syntax_highlighter_opts => { :line_numbers => nil }

To use, set the language-[lang] class on any code blocks:

 ~~~
 foo = "bar"
 ~~~
 {:.language-ruby}

Or use this shortcut:

 ~~~ ruby
 foo = "bar"
 ~~~

Unfortunately Coderay can’t highlight perl code :-/

References

Syntax highlighting with Rouge (via Kramdown)

Kramdown also supports the Rouge syntax highlighter. This is what I’m using here now, because Coderay doesn’t support Perl.

You’ll need to install rouge:

gem install rouge

Then configure the filter:

filter :kramdown,
       :syntax_highligher => 'rouge',
       :syntax_highlighter_opts => { :line_numbers => false }

You’ll need to grab a pygments, which I got from pygments-css.

To use, set the language-[lang] class on any code blocks:

 ~~~
 foo = "bar"
 ~~~
 {:.language-ruby}

Or use this shortcut:

 ~~~ ruby
 foo = "bar"
 ~~~

PHP not highlighted

Rouge is slightly different to Coderay in that it uses a lexer to check the code before it colours. Unfortunately this means that despite telling it what language to colour it will still use the lexer to check the syntax and may refuse to colour on the basis that the syntax isn’t what the lexer is expecting. For example, the php lexer expects the code to start with <php and if it doesn’t then it won’t be coloured.

To work around this I monkey patched rouge so that it doesn’t bother looking for PHP opening tags. Put this in a file in lib:

# Monkey patch rouge's PHP lexer so that it doesn't bother checking for '<?php'.
require 'rouge'
module Rouge
  module Lexers
    class PHP < TemplateLexer
      start do
        push :php
      end
    end
  end
end

Syntax highlighting with Coderay

This is how I used to do syntax highlighting, before I realised that kramdown supports it out of the box if coderay gem is installed (see above)!

You can have nanoc use Coderay (or one of [a number of other colourisers, as given by the private methods in the ColorizeSyntax class) to highlight syntax by using the colorise_syntax filter:

Put this in your Rules file wherever you want to apply the filter:

    filter :colorize_syntax,
           :default_colorizer => :coderay,
           :coderay    => { :line_numbers => :inline }

It uses Coderay by default, so you can leave out the :default_colorizer line, but I included it because it’ll prevent anything breaking if Nanoc changes their default in the future.. See Class: Nanoc::Filters::ColorizeSyntax.

Get rid of the :line_numbers option if you don’t want line numbers added.

That’ll take care of the markup. To have the styling applied you’ll need to generate a stylesheet with the following command:

coderay stylesheet > coderay.css

Then link to it in your template.

Now, when you want a code block to have its syntax highlighted, add one of the following:

  • A HTML class starting with language- and followed by the code language, as specified by HTML5. For example, <code class="language-ruby">.
  • A comment on the very first line of the code block in the format #!language where language is the language the code is in. For example, #!ruby.

E.g. If using kramdown this is:

 ~~~
 foo = "bar"
 ~~~
 {:.language-ruby}

Or:

 ~~~
 #!ruby
 foo = "bar"
 ~~~

But wait!

At the time of writing the latest nanoc - version 3.4.1 - couldn’t work with the coderay stylesheet without you manually adding two divs around your code block, an outer one with the ‘CodeRay’ class and an inner one with the ‘code’ class. Using Coderay from the command line shows what it expects:

CodeRay.scan("foo = 'bar'", :ruby).div(:css => :class)
=> "<div class=\"CodeRay\">\n  <div class=\"code\"><pre>foo = <span class=\"string\"><span class=\"delimiter\">'</span><span class=\"content\">bar</span><span class=\"delimiter\">'</span></span></pre></div>\n</div>\n"

This has been fixed in the nanoc dev code, so the next release should automatically add these surrounding divs, but in the mean time I’ve found two workarounds.

  1. You can use the :css => :style option to have coderay embed CSS into the markup.

    filter :colorize_syntax,
           :default_colorizer => :coderay,
           :coderay => { :css => :style }
    

    I didn’t actually find the coderay options documented anywhere, it was basically just a lucky guess.

    I don’t like this workaround, because inline styling is bad practice, and I’d much rather be able to use the coderay.css with a coderay div surrounding each code block.

  2. It looked akward to alter the colorizesyntax filter to add a surrounding div (because of nokogiri’s akward html editing capabilities), but inspecting the coderay css it seemed good enough to add the class to the pre tag instead.

    I monkey-patched the run method of ColorizeSyntax filter and added the following to the end of the element loop:

    # Add div with highlighter class
    colorizer = @colorizers[language.to_sym]
    colorizer_class = COLORIZER_CLASSES[colorizer]
    element.parent['class'] = colorizer_class unless colorizer_class.nil?
    

    With COLORIZER_CLASSES = { :coderay => 'CodeRay' } defined elsewhere in the patch (because CSS is case sensitive I couldn’t just use the name of the colorizer).

See Syntax highlighting confusion for the discussion on the nanoc group and Syntax colorizer not suitable for CodeRay stylesheet for the bug report.

Invalid id error

If you get something like the following error…

ArgumentError: Invalid id: '/usr/bin/php' given.

…then you’ve most likely got content that includes a shebang / hashbang, for example #!/bin/sh or #!/usr/bin/php. Old versions of Nanoc misinterpreted everything after the hashbang as the language identifier.

The solution is to update Nanoc. See colorize_syntax filter raises error for code including shebang line.

The :list option is no longer available

If you get the following error…

NotImplementedError: The :list option is no longer available. Use :table.

…then you’ve attempted to use :line_numbers => :list as an option. You could change :list to :table, but this will place the code in an HTML table - something that is both ugly and rings warning bells for accessiblity. I prefer to use :inline.

Last modified: 17/03/2015 Tags: ,

Related Pages

Other pages possibly of interest:

This website is a personal resource. Nothing here is guaranteed correct or complete, so use at your own risk and try not to delete the Internet. -Stephan

Site Info

Privacy policy

Go to top