lib/cadenza/text_renderer.rb
# frozen_string_literal: true
require 'cadenza/block_hierarchy'
require 'stringio'
module Cadenza
# The {TextRenderer} is the standard rendering logic for Cadenza. It will
# render an AST according to the rules specified in the Cadenza manual. See
# the manual for details.
class TextRenderer < BaseRenderer
# Renders the document given with the given context directly to a string
# returns.
# @todo move this to a module
# @param [DocumentNode] document_node the root of the AST you want to render.
# @param [Context] context the context object to render the document with
# @return [String] the rendered template in the given context
def self.render(document_node, context)
io = StringIO.new
new(io).render(document_node, context)
io.string
end
def render(node, context, blocks = {})
passed_blocks = blocks.is_a?(BlockHierarchy) ? blocks : BlockHierarchy.new(blocks)
super(node, context, passed_blocks)
end
private
def render_document(node, context, blocks)
# merge the document's blocks into the inherited blocks
blocks.merge(node.blocks)
if node.extends
# load the template of the document and render it to the same output stream
template = context.load_template!(node.extends)
render(template, context, blocks)
else
node.children.each { |x| render(x, context, blocks) }
end
end
def render_block(node, context, blocks)
# create the full inheritance chain with this node on top, making sure
# not to mutate the block hierarchy's internals
chain = blocks[node.name].dup << node
super_fn = lambda do |_params|
parent_node = chain.shift
parent_node.children.each { |x| render(x, context, blocks) } if parent_node
nil
end
context.push('super' => super_fn)
chain.shift.children.each { |x| render(x, context, blocks) }
context.pop
end
def render_text(node, _context, _blocks)
output << node.text
end
def render_if(node, context, blocks)
node.evaluate_expression_for_children(context).each { |x| render(x, context, blocks) }
end
def render_for(node, context, blocks)
# sadly to_enum doesn't work in 1.8.x so we need to array-ify the iterable first
values = node.iterable.eval(context).to_a
iterator = node.iterator.identifier
values.each_with_index do |value, counter|
is_first = counter.zero?
is_last = counter == values.length - 1
# push the inner context with the 'magic' variables
context.push(
iterator => value,
'forloop' => {
'counter' => counter + 1,
'counter0' => counter,
'first' => is_first,
'last' => is_last
}
)
# render each of the child nodes with the context
node.children.each { |x| render(x, context, blocks) }
# pop the inner context off
context.pop
end
end
def render_generic_block(node, context, _blocks)
output << context.evaluate_block(node.identifier, node.children, node.parameters)
end
def render_constant(node, context, _blocks)
output << node.eval(context).to_s
end
alias render_variable render_constant
alias render_operation render_constant
alias render_boolean_inverse render_constant
alias render_filtered_value render_constant
end
end