
Security News
CISA’s 2025 SBOM Guidance Adds Hashes, Licenses, Tool Metadata, and Context
CISA’s 2025 draft SBOM guidance adds new fields like hashes, licenses, and tool metadata to make software inventories more actionable.
Cutaneous adj. Of the skin.
Cutaneous is a Ruby (1.9+) templating engine designed for flexibility and simplicity.
It supports having multiple output formats, multiple syntaxes and borrows a template inheritance mechanism from Python template engines such as Django's, Jinja and Mako.
Cutaneous is the template engine designed for and used by Spontaneous CMS.
The Cutaneous::Engine
class provides two core methods:
Cutaneous::Engine#render(template_path, context, format)
This renders the template at template_path
using the context context
(which must be an instance of Cutaneous::Context (or a subclass)) and the format format
.
template_path
should be specified as either a relative path which will be resolved using a search through the template roots specified in the engine's initialisation call. See below for more information about how templates are specified & resolved.
Cutaneous::Engine#render_string(template, context, format)
This takes the input string as the template and renders it. If this string references other templates through includes (see below) these are resolved as if you had made a call to Engine#render
.
By default Cutaneous templates should be given a .cut
file extension.
Cutaneous generally relies upon relative template names. If we consider the following code:
engine = Cutaneous::Engine.new([
"/home/user/templates",
"/home/user/shared_templates"
])
context = Cutaneous::Context.new(Object.new, title: "Welcome")
result = engine.render("welcome", context, "html")
The #render
call will look for a file called welcome.html.cut
under each of the supplied template roots & return the first one it finds:
# The template resolution is equivalent to the following Ruby code:
search_paths = [
"/home/user/templates/welcome.html.cut",
"/home/user/shared_templates/welcome.html.cut"
]
template_path = search_paths.detect { |path| File.exist?(path) }
Template filenames are derived from [template_relative_path, format, "cut"].join(".")
.
If you specified a different format for the render call, e.g. engine.render("welcome", context, "txt")
then the engine would instead look for a file named welcome.txt.cut
.
If you want to organise your templates into subdirectories then you are completely free to do so, you just need to add the subdirectory name onto the relative path:
result = engine.render("layouts/welcome", context, "html")
#=> Renders the file "/home/user/templates/layouts/welcome.html.cut"
To include one template file into another you use the include
directive within your template:
%{ include 'partial/header' }
This will include the output of rendering the file "/home/user/templates/partial/header.html.cut" directly in your original template's output (assuming you're rendering the "html" format).
Note that Cutaneous has no special treatment of "partials", there is no special partial
command and no preceeding underscore in the template name.
If you want to switch between rendering file based & string based templates then Cutaneous provides a way of using the same Engine#render
call by passing a Proc as the template path:
# This
template = "Hello ${ name }!"
engine.render(Proc.new { template }, context)
# is the same as:
engine.render_string(template, context)
This allows us to use simple strings as template paths and still use a consistent calling mechanism.
Cutaneous features a block based template inheritance mechanism.
Including a %{ extends "parent" }
tag at the start of a template
makes it inherit from the named template ("parent" in this case).
Parent templates define a series of blocks using a %{ block :block_name} ... %{ endblock}
syntax. Child templates can then override any of
these individual blocks with their own content.
Calling %{ blocksuper }
within a child template's block allows you
to insert the code from the parent template (much like calling super
in an object subclass).
So for example using the following templates:
engine.render("child", context, "html")
would result in the following output:
Title
Template inheritance is great!
Template inheritance is great!
Do it like this...
And like this...
The template hierarchy can be as long as you need/like. Template 'd' could extend 'c' which extends 'b' which extends 'a' etc..
Cutaneous allows templates for multiple formats to exist alongside each other. In the examples above the html
format is exclsuively used but instead of this I could render the same template as txt
result = engine.render("quickstart", context, "txt")
This would look for a quickstart.txt.cut
template under the template roots. The format used is maintained across both include
and extend
calls so when using these you should reference them without any extension.
If your standard format isn't "html" then you can set a new default when creating your template engine instance:
engine = Cutaneous::Engine.new("/template/root", Cutaneous::FirstPassSyntax, "txt")
If you want to remove trailing whitespace after a tag, then suffix it with a -
character, e.g.
%{ include "something" -}
If you create an instance of Cutaneous::CachingEngine
instead of the default Engine
class then the compiled templates will be cached in-memory for the lifetime of the engine. In order to render templates Cutaneous converts them to simple Ruby code. As part of the caching this generated code will be written to disk alongside the original template with the extension .format.rb
.
If you want to turn off the writing of the compiled Ruby files (such as in a development environment), set write_compiled_scripts
to false:
engine.write_compiled_scripts = false
The cached ruby will only be used if it is fresher than the template it was compiled from so updating the template will re-write the .rb
equivalent.
Cutaneous supports the concept of syntaxes. This is used by Spontaneous to provide a two-stage rendering process (first-pass templates output second-pass templates which are rendered on demand -- in this way you can create a very responsive dynamic site because you have precached 99% of the page).
The two syntaxes are:
%{ ruby code... }
${ value }
$${ unsafe value }
!{ comment... }
{% ruby code... %}
{{ value }}
{$ unsafe value $}
!{ comment... }
You choose which one of these you wish to use when you create your Cutaneous::Engine
instance:
engine = Cutaneous::Engine.new("/template/root", Cutaneous::SecondPassSyntax)
Cutaneous doesn't try to remove code from your templates it instead allows you to write as much Ruby as you want directly in-place. This is done in order to make the development of your front-end code as quick as possible. If you later want to clean up your template code you can instead use helper methods either on the context you pass to the renderer or the object you wrap that context around.
If you want to add features to your context, or helpers
as they would be known in Rails-land then create a new Context class and include your helper methods there:
module MyHelperMethods
def my_helpful_method
# ... do something complex that you want to keep out of the template
end
end
# You *must* inherit from Cutaneous::Context!
class MyContext < Cutaneous::Context
include MyHelperMethods
end
context = MyContext.new(instance, parameter: "value")
result = engine.render("template", context)
Cutaneous silently swallows errors about missing expressions in templates. If you want to instead report these errors override the __handle_error
context method:
class MyContext < Cutaneous::Context
def __handle_error(e)
logger.warn(e)
end
end
Cutaneous will do its best to keep the line numbers consistent between templates and the generated code (although see "Bugs" below...). This will hopefully make debugging easier.
Cutaneous is released under an MIT license (see LICENSE).
FAQs
Unknown package
We found that cutaneous demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
CISA’s 2025 draft SBOM guidance adds new fields like hashes, licenses, and tool metadata to make software inventories more actionable.
Security News
A clarification on our recent research investigating 60 malicious Ruby gems.
Security News
ESLint now supports parallel linting with a new --concurrency flag, delivering major speed gains and closing a 10-year-old feature request.