Rules

The Rules file contains the processing instructions for all items in a Nanoc site. Three different kinds of rules exist:

compilation rules
These rules describe the actions that should be executed during compilation (filtering, layouting, and snapshotting).
routing rules
These rules describe the path where the compiled items should be written to.
layouting rules
These rules describe the filter that should be used for a given layout.

For every item, Nanoc finds the first matching compilation rule. Similarly, for every layout, Nanoc finds the first matching layouting rule. The first matching rule that is found in the rules file is used. If an item or a layout is not using the correct rule, double-check to make sure that the rules are in the correct order.

Compilation rules

A compilation rule describes how an item should be transformed. It has the following shape:

compile "/some/pattern.*" do
  # (compilation code here)
end

The argument for the #compile method call is a pattern.

The code block should execute the necessary actions for compiling the item. The return value of the block is ignored. There are three kinds actions that can be performed:

  • filter items, transforming their content
  • layout items, placing their content inside a layout
  • snapshot items, remembering their content at that point in time for reuse

Lastly, a compilation block can end with a write action, which will write the compiled content to a file with the given name. (Alternatively, you can use routing rules to describe where compiled content should be written to.)

The code block does not need to execute anything. An empty #compile block will not execute anything. The following rule will not perform any actions, i.e. the item will not be filtered nor laid out:

compile '/images/**/*' do
end

Writing

A compilation rule can contain a #write call, which will write out the item representation in its current state. It can be called in three ways:

with a string
writes the item rep to the given path, relative from the output directory
with nil
prevents Nanoc from applying a matching routing rule (see below for details)
with :ext
writes the item rep to a path that is the same as the identifier, but with a different extension

For example, this compilation rule will copy /images/denis.jpg without further processing:

compile '/images/**/*' do
  write item.identifier.to_s
end

This compilation rule will copy all .jpg and .jpeg files, and give them the extension .jpg:

compile '/**/*.{jpg,jpeg}' do
  write item.identifier.without_exts + '.jpg'
end

To keep the basename of an item and only change the extension when writing, you can pass the ext param, specifying an extension. The following compilation rule is the same as the above one, but more concise:

compile '/**/*.{jpg,jpeg}' do
  write ext: 'jpg'
end

To prevent Nanoc from applying a matching routing rule, call write nil at the end of a compilation rule. In the following example, no items in snippets/ will be written out, because the routing rule is skipped:

compile '/snippets/*.md' do
  filter :kramdown
  write nil
end

route '/**/*.md' do
  item.identifier.without_ext + '/index.html'
end

Filtering

To filter an item representation, use the #filter method. It takes the following arguments:

  1. (required) the name of the filter (a Symbol)
  2. (optional) additional arguments which will be passed on to the filter

For example, the following rule will filter items with identifiers ending in .md using the :kramdown filter, but not perform any layouting, and then write it with a .html extension, so that /about.md is written to /about.html:

compile '/**/*.md' do
  filter :kramdown
  write @item.identifier.without_ext + '.html'
end

For example, the following rule calls both the :sass filter as well as the :relativize_paths filter with the extra arguments:

compile '/**/*.sass' do
  filter :sass, style: :compact
  filter :relativize_paths, type: :css
  write ext: '.css'
end

Laying out

To lay out an item representation, use the #layout method. It takes the following arguments:

  1. (required) the identifier of a layout, or a pattern that matches the identifier of a layout
  2. (optional) additional arguments which will be passed on to the filter

For example, the following rule will (among other things) lay out the item representation using the layout that matches the /shiny.* pattern:

compile '/about.*' do
  filter :erb
  layout '/shiny.*'
  filter :rubypants
  write @item.identifier.without_ext + '/index.html'
end

layout '/*.erb', :erb

The following example is similar, but passes extra arguments (:locals) to the filter associated with the layout:

compile '/about.*' do
  filter :erb
  layout '/shiny.*', locals: { daleks: 'exterminate' }
  filter :rubypants
  write @item.identifier.without_ext + '/index.html'
end

layout '/*.erb', :erb

If layout is called multiple times, the content is wrapped in each of the specified layouts. In the example below, markdown articles are filtered through the kramdown filter, then wrapped in the article.erb layout, and finally wrapped in the default.erb layout.

compile '/articles/*.md' do
  filter :kramdown
  layout '/article.*'
  layout '/default.*'
end

layout '/*.erb', :erb

Dynamic rules

In the code block, Nanoc exposes @item and @rep, among others. See the Variables page for details.

The following rule will only invoke the :erb filter if the item’s :is_dynamic attribute is set:

compile '/about.*' do
  filter :erb if @item[:is_dynamic]
  write @item.identifier.without_ext + '/index.html'
end

Creating snapshots

To take a snapshot of an item representation, call #snapshot and pass the snapshot name as argument. For example, the following rule will create a snapshot named :without_toc so that the content at that snapshot can then later be reused elsewhere:

compile '/foo/*' do
  filter :markdown
  snapshot :without_toc
  filter :add_toc
  write @item.identifier.without_ext + '/index.html'
end

Handling non-default representations

A :rep argument can be passed to the #compile call. This argument contains the name of the representation that is generated by this rule. This is :default by default.

The following rule will generate a :text representation for all items below /people:

compile '/people/**/*', rep: :text do
  write @item.identifier.without_ext + '.txt'
end

Matching with regular expressions

When using a regular expression to match items, the block arguments will contain all matched groups. This is more useful for routing rules than it is for compilation rules. For example, the following rule will be matched using a regular expression instead of with a wildcard string:

compile %r<\A/blog/\d{4}/.*> do
  filter :kramdown
  write @item.identifier.without_ext + '/index.html'
end

Routing rules

Routing rules are an alternative way to specify where a compiled item should be written to. It has the following shape:

route '/some/pattern.*' do
  # (routing code here)
end

The argument for the #route method call is a pattern.

The code block should return the routed path for the relevant item. The code block can return nil, in which case the item will not be written.

A compilation rule that ends with a #write call can be written as a combination of a compilation rule and a routing rule. Typically, using #write in the compile block leads to a more compact and easier-to-understand Rules file, but separate #route calls can nonetheless be useful.

The following compile/route rules are equivalent:

compile "/*.md" do
  filter :kramdown
end

route "/*.md" do
  item.identifier.without_ext + '/index.html'
end
compile "/*.md" do
  filter :kramdown
  write item.identifier.without_ext + '/index.html'
end

The following rule will give the item with identifier /404.erb the path /errors/404.php:

route "/404.erb" do
  "/errors/404.php"
end

The following rule will prevent all items below /links from being written:

route "/links/**/*" do
  nil
end

In the code block, Nanoc exposes @item and @rep, among others. See the Variables page for details.

The following rule will give all identifiers for which no prior matching rule exists a path based directly on its identifier (for example, the item /foo/bar.html would get the path /foo/bar/index.html):

route "/**/*" do
  @item.identifier.without_ext + "/index.html"
end

When using a regular expression to match items, the block arguments will contain all matched groups.

The following rule will capture regex matches and provide them as block arguments (for example, the item with identifier /blog/2015-05-19-something.md will be routed to /blog/2015/05/something/index.html):

route %r[/blog/([0-9]+)\-([0-9]+)\-([0-9]+)\-([^\/]+)\..*] do |_, y, m, d, slug|
  "/blog/#{y}/#{m}/#{slug}/index.html"
end

Just like with #compile calls, a :rep argument can be passed to the #route call. This argument contains the name of the representation that this rule applies to.

The following rule will apply to all textual representations of all items below /people (for example, the item /people/denis.md would get the path /people/denis.txt):

route "/people/**/*", rep: :text do
  item.identifier.without_ext + '.txt'
end

When a :snapshot argument is passed to a routing rule definition, then that routing rule applies to the given snapshot only. The default value for the :snapshot argument is :last, meaning that compiled items will only be written once they have been fully compiled.

The following rules will apply to the raw snapshot of all items below /people (for example, the raw snapshot of the item /people/denis.md would get the path /people/denis.txt):

route "/people/**/*", snapshot: :raw do
  item.identifier.without_ext + '.txt'
end

Writing/routing pitfalls

A Rules file that combines #write calls with routing can be confusing. For this reason, we recommend either using compilation rules that use #write, or routing rules, but not both. This section intends to give more clarity on how #write calls and routing rules interact.

The main point: If a compilation rule does not end with a #write call, Nanoc will find the routing rule that matches this item representation, and use it to write.

For example, here the routing rule is used for the /about.md item:

compile '/about.md' do
  filter :kramdown
end

route '/about.md' do
  '/about.html'
end

# output files created:
#   /about.html

In the following example, on the other hand, the routing rule is not used, because the compilation rule ends with a #write call:

compile '/about.md' do
  filter :kramdown
  write '/about.html'
end

route '/about.md' do
  '/about-irrelevant.html'
end

# output files created:
#   /about.html

In the next example, the routing rule is used, because even though there is a #write call, the compilation rule does not end with one:

compile '/about.md' do
  filter :kramdown
  write '/about-inbetween.html'
  layout '/default.*'
end

route '/about.md' do
  '/about.html'
end

# output files created:
#   /about-inbetween.html
#   /about.html

To prevent Nanoc from applying a matching routing rule, call write nil at the end of a compilation rule. Refer to the paragraph describing write nil for details.

To reduce potential confusion, avoid mixing routing rules and #write calls. Both can be used to specify the path for an item representation, but mixing them will reduce the clarity of the Rules file.

Layouting rules

To specify the filter used for a layout, use the #layout method. It takes the following arguments:

  1. (required) the identifier of a layout, or a pattern that matches the identifier of a layout
  2. (required) the identifier of the filter to use (a Symbol)
  3. (optional) additional arguments which will be passed on to the filter

If addition arguments are given to both a layouting rule, as well as a #layout call for the same layout inside a compilation block, the arguments will be merged.

The following rule will make all layouts use the :erb filter:

layout '/**/*', :erb

The following rule will make all layouts with the haml extension use the haml filter, with an additional format argument that will be passed to the haml filter:

layout '/*.haml', :haml, format: :html5

The following rule will be applied to all layouts with identifiers starting with a slash followed by an underscore. For example, /foo.erb and /foo/_bar.haml would not match, but /_foo.erb, /_foo/bar.html, and even /_foo/_bar.erb would:

layout %r{\A/_}, :erb

In the following example, the layout is filtered using the haml filter, with arguments ugly: true and format: :html5:

compile '/*.md' do
  filter :kramdown
  layout '/shiny.*', ugly: true
  write ext: 'html'
end

layout '/*.haml', :haml, format: :html5

Convenience methods

The #passthrough method does no filtering or laying out, and copies the matched item as-is. For example:

passthrough '/assets/images/**/*'

This is a shorthand for the following:

route '/assets/images/**/*' do
  item.identifier.to_s
end

compile '/assets/images/**/*' do
end

The #ignore method does no filtering or laying out, and does not write out the matched item. This is useful for items that are only intended to be included in other items. For example:

ignore '/assets/style/_*'

This is a shorthand for the following:

route '/assets/style/_*' do
  nil
end

compile '/assets/style/_*' do
end

Preprocessing

The Rules file can contain a #preprocess block. This preprocess block is executed before the site is compiled, and has access to all site data (@config, @items, and @layouts). Preprocessors can modify data coming from data sources before it is compiled. It can change item attributes, content, and the path, but also add and remove items.

Here is an example preprocess block that sets the author attribute to denis on every HTML document:

preprocess do
  @items.each do |item|
    item[:author] = 'denis' if item.identifier.ext == 'html'
  end
end

Here is an example preprocess block that finds all unique tags, and creates collection pages for them:

preprocess do
  tags = @items.flat_map { |i| i[:tags] || [] }.uniq
  tags.each do |tag|
    content = ''
    attributes = { tag: tag }
    identifier = "/tags/#{tag}.erb"
    @items.create(content, attributes, identifier)
  end
end

To make the tag pages work, you’d handle tag items specifically (using a glob /tags/* to match them) by applying a layout that finds and shows all items for the tag page’s tag. Doing this is left as an exercise to the reader.

Preprocessors can be used for various purposes. Here are two sample uses:

  • A preprocessor could set a language_code attribute based on the item path. An item such as /en/about/ would get an attribute language_code equal to 'en'. This would eliminate the need for helpers such as language_code_of.
  • A preprocessor could create new (in-memory) items for a given set of items. This can be useful for creating pages that contain paginated items.

Post-processing

The Rules file can contain a #postprocess block. This post-process block is executed after the site is compiled. At this point, all output files have been updated.

Post-processing is useful to model tasks that are tricky to model in Nanoc’s workflow, such as incrementally updating a search index. For example:

postprocess do
  items.flat_map(&:modified_reps).each do |rep|
    update_search_index(rep.path, rep.compiled_content(snapshot: :last))
  end
end