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)
endThe 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
endWriting
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
endThis 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'
endTo 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'
endTo 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'
endFiltering
To filter an item representation, use the #filter method. It takes the following arguments:
- (required) the name of the filter (a
Symbol) - (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'
endFor 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'
endLaying out
To lay out an item representation, use the #layout method. It takes the following arguments:
- (required) the identifier of a layout, or a pattern that matches the identifier of a layout
- (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', :erbThe 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', :erbIf 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', :erbDynamic 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'
endCreating 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'
endHandling 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'
endMatching 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'
endRouting 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)
endThe 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'
endcompile "/*.md" do
filter :kramdown
write item.identifier.without_ext + '/index.html'
endThe following rule will give the item with identifier /404.erb the path /errors/404.php:
route "/404.erb" do
"/errors/404.php"
endThe following rule will prevent all items below /links from being written:
route "/links/**/*" do
nil
endIn 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"
endWhen 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"
endJust 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'
endWhen 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'
endWriting/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.htmlIn 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.htmlIn 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.htmlTo 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:
- (required) the identifier of a layout, or a pattern that matches the identifier of a layout
- (required) the identifier of the filter to use (a
Symbol) - (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 '/**/*', :erbThe 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: :html5The 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/_}, :erbIn 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: :html5Convenience 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
endThe #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
endPreprocessing
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
endHere is an example preprocess block that finds all unique tags, and creates collection pages for them:
preprocess do
tags = @items.map { |i| i[:tags] }.uniq
tags.each do |tag|
content = ''
attributes = { tag: tag }
identifier = "/tags/#{tag}"
@items.create(content, attributes, identifier)
end
endTo 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_codeattribute based on the item path. An item such as/en/about/would get an attributelanguage_codeequal to'en'. This would eliminate the need for helpers such aslanguage_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