Pure Ruby library for Handlebars template system. This should become an alternative for using the Handlebars javascript library via handlebars.rb.
Simply run:
gem install mvz-ruby-handlebarsNo need for libv8, ruby-racer or any JS related tool.
A very simple case:
require 'ruby-handlebars'
hbs = Handlebars::Handlebars.new
hbs.compile("Hello {{name}}").call({name: 'world'})
# Gives: "Hello world", how original ...You can also use partials:
hbs.register_partial('full_name', "{{person.first_name}} {{person.last_name}}")
hbs.compile("Hello {{> full_name}}").call({person: {first_name: 'Pinkie', last_name: 'Pie'}})
# Gives: "Hello Pinkie Pie"Partials support parameters:
hbs.register_partial('full_name', "{{fname}} {{lname}}")
hbs.compile("Hello {{> full_name fname='jon' lname='doe'}}")
# Gives: "Hello jon doe"You can also register inline helpers:
hbs.register_helper('strip') {|context, value| value.strip}
hbs.compile("Hello {{strip name}}").call({name: ' world '})
# Will give (again ....): "Hello world"Helper arguments can be variables, quoted strings, numbers or booleans. A
number reaches the helper as an Integer or a Float, and true and false
reach it as booleans:
hbs.register_helper('round') {|context, value, digits| value.round(digits)}
hbs.compile("{{round price 2}}").call({price: 1234.5678})
# Will give: "1234.57"A name that merely begins with a digit or with true/false is still read
as a name, so {{echo 2fast}} looks up 2fast rather than the number 2.
If the templates come from somewhere less trusted than the data, bound what
{{a.b}} may call on a non-hash item:
hbs.restrict_attributes(Handlebars::Context::SAFE_ATTRIBUTES)Unrestricted, any public zero-argument method answers, so {{x.class}} and
{{x.instance_variables}} read out of the object graph. Restricted, only the
listed accessors dispatch and anything else reads as missing, the same as an
absent key. {{items.length}} keeps working either way.
{{#each}} walks whatever collection it is handed, and the template says
nothing about how big that is. Two nested loops turn forty characters into a
million iterations, so bound it when the data is not yours:
hbs.limit_iterations(10_000)The count spans the whole render, not each loop. Reaching it stops every loop in that render and returns what was built so far, so a long list comes back short rather than not at all. That is invisible in the output, so pass a block to record it:
hbs.limit_iterations(10_000) { |limit| warn "render stopped at #{limit} iterations" }or block helpers:
hbs.register_helper('comment') do |context, commenter, block|
block.fn(context).split("\n").map do |line|
"#{commenter} #{line}"
end.join("\n")
end
hbs.compile("{{#comment '//'}}My comment{{/comment}}").call
# Will give: "// My comment"Note that in any block helper you can use an else block:
hbs.register_helper('markdown') do |context, block, else_block|
html = md_to_html(block.fn(context))
html.nil? : else_block.fn(context) : html
end
template = [
"{{#markdown}}",
" {{ description }}",
"{{else}}",
" Description is not valid markdown, no preview available",
"{{/markdown}}"
].join("\n")
hbs.compile(template).call({description: my_description})
# Output will depend on the validity of the 'my_description' variableThree default helpers are provided: each, if and unless.
The each helper let you walk through a list. You can either use the basic notation and referencing the current item as this:
{{#each items}}
{{{ this }}}
{{else}}
No items
{{/each}}
or the "as |name|" notation:
{{#each items as |item| }}
{{{ item }}}
{{else}}
No items
{{/each}}
The if helper can be used to write conditionnal templates:
{{#if my_condition}}
It's ok
{{else}}
or maybe not
{{/if}}
The unless helper works the opposite way to if:
{{#unless my_condition}}
It's not ok
{{else}}
or maybe it is
{{/unless}}
Currently, if you call an unknown helper, it will raise an exception. You can override that by registering your own version of the helperMissing helper. Note that only the name of the missing helper will be provided.
For example:
hbs.register_helper('helperMissing') do |context, name|
puts "No helper found with name #{name}"
endThis gem does not reuse the real Handlebars code (the JS one) and not everything is handled yet (but it will be someday ;) ):
- the parser is not fully tested yet, it may complain with spaces ...
- parsing errors are, well, not helpful at all
This is a fork of the ruby-handlebars gem. Eventually I hope to have all my changes accepted upstream so I can abandon this fork.