Help » Manual »
Chapter 3: Extending nanoc
« Back to the Manual Table of Contents
nanoc is quite customizable. Apart from being able to write arbitrary code that is executed before a site is compiled, it is also possible to write custom (binary) filters, data sources and routers.
Writing Data Sources ¶
A data source is responsible for loading and storing a site’s data: pages, page defaults, assets, asset defaults, templates, layouts and code.
Data sources are classes that inherit from Nanoc::DataSource. There is no need to explicitly register data source classes; nanoc will find all data source classes automatically.
Each data source has an identifier. This is a unique name that is used in a site’s configuration file to specify which data source should be used to fetch data. It is specified like this:
class SampleDataSource < Nanoc::DataSource
identifier :sample
end
All methods in the data source have access to the @site object, which represents the site. One useful thing that can be done with this is request the configuration hash, using @site.config.
Preparation ¶
There are two methods you may want to implement first: up and down. up is executed when the data source is loaded. For example, this would be the ideal place to establish a connection to the database. down is executed when the data source is unloaded, so this is the ideal place to undo what up did.
The setup method is used to create the initial site structure. For example, a database data source could create the necessary tables here. The destroy method should do the opposite of what setup does. For example, a database data source would drop the created tables in this method.
The update method is used for updating the format in which the data is stored. For example, a database data source could add necessary new columns here.
None of these methods need to be implemented; you can simply leave the method definitions out.
Functions ¶
The following methods (grouped by data type) are required to be implemented by any data source:
- Pages —
Nanoc::Page -
pages()save_page(page)move_page(page, new_path)destroy_page(page)
- Page Defaults (singular) —
Nanoc::PageDefaults -
page_defaults()save_page_defaults(page_defaults)
- Assets —
Nanoc::Asset -
assets()save_asset(asset)move_asset(asset, new_path)destroy_asset(asset)
- Asset Defaults (singular) —
Nanoc::AssetDefaults -
asset_defaults()save_asset_defaults(asset_defaults)
- Layouts —
Nanoc::AssetDefaults -
layouts()save_layout(layout)move_layout(layout, new_path)destroy_layout(layout)
- Templates —
Nanoc::Template -
templates()save_template(template)move_template(template, new_path)destroy_template(template)
- Code (singular) —
Nanoc::Code -
code()save_code(code)
The “singular” indicates that there is only one resource of the specified kind. Such resources therefore lack move_x and destroy_x functions, as they cannot be moved nor destroyed.
The class name next to each data type is the object type that is passed to functions or is returned from functions. For example, the save_page function will get a Nanoc::Page while the layouts function will return an array of Nanoc::Layouts.
The loading functions (pages, page_defaults, …) should return an array of objects, or, if the resource is singular, a single object. These functions do not take any parameters.
The saving functions (save_page, save_page_defaults, …) should store the given object. The argument is the object to be stored.
The moving functions (move_page, …) should change the given object’s name or path. The first argument is the object to be moved, while the second argument is the new path or new name.
The destroying functions (destroy_page, …) should delete the given object. The argument is the object to be destroyed.
If all this sounds a bit vague and weird, do check out the source of a data source, and the documentation of Nanoc::DataSource itself. The code is well-documented and should help you to get started quickly.
Writing Filters ¶
Filters are classes that inherit from Nanoc::Filter. They do not need to be explicitly registered; nanoc will find filter classes automatically.
A filter has an identifier, which is an unique name that is used in a page’s list of filters. It is set like this:
class CensorFilter < Nanoc::Filter
identifier :censor
end
A filter can have multiple identifiers. Specify multiple identifiers using identifiers instead of identifier, like this:
class CensorFilter < Nanoc::Filter
identifiers :censor, :stop_the_hate
end
A filter needs to implement only one method, run. This method takes the unfiltered content as its only argument, and returns the filtered content. For example:
def run(content)
content.gsub('nanoc sucks', 'nanoc rocks')
end
The filter has access to several useful instance variables:
@page_rep- Contains the page representation that is being filtered.
@page- Contains the current page.
@asset_rep- Contains the asset representation that is being filtered.
@asset- Contains the current asset.
@site- Contains the site.
Writing Binary Filters ¶
Binary filters are very similar to normal filters, except that they work with File objects instead of actual content. They inherit from Nanoc::BinaryFilter.
Just like with normal filters, binary filters can have one or more identifiers, and they do not need to be registered explicitly.
A binary filter needs to implement the run method, which takes a File argument and returns a File object. The returned File object should be a reference to a new file. The File argument should not be modified (otherwise the original asset will be changed).
Writing Routers ¶
Routers inherit from Nanoc::Router and must implement the path_for_page_rep and path_for_asset_rep methods. The former takes a page representation (Nanoc::PageRep) as an argument while the second takes an asset representation (Nanoc::AssetRep). The methods should return the output path of the given representation, starting with a slash representing the web root. Also see the RDoc documentation for Nanoc::Router.
For example, this is the default router’s implementation of path_for_asset_rep:
def path_for_asset_rep(asset_rep)
# Get data we need
extension = asset_rep.attribute_named(:extension)
modified_path = asset_rep.asset.path[0..-2]
version = asset_rep.attribute_named(:version)
# Initialize path
assets_prefix = @site.config[:assets_prefix] || '/assets'
path = assets_prefix + modified_path
# Add rep name if necessary
unless asset_rep.name == :default
path += '-' + asset_rep.name.to_s
end
# Add extension
path += '.' + extension
# Done
path
end
Writing Layout Processors ¶
As of nanoc 2.1, layout processors are filters, so there is no need to write separate layout processors—simply write a filter and use that one.