require 'cutaneous/compiler/expression' module Cutaneous class Compiler # A single named block of template expressions class Block attr_reader :name def initialize(name) @name = name @expressions = [] end def push(expression) @expressions << expression end alias_method :<<, :push def to_script script = "" @expressions.each do |expression| script << expression.to_script end script end end # Represents the block structure of a top-level master template, # i.e. one with no `extends` call. class BlockSet attr_reader :current_block attr_accessor :loader def initialize @block_order = [] @block_store = {} block_start end def block_start(name = Object.new) @current_block = Block.new(name) @block_order << name @block_store[name] = @current_block end def block_end block_start end def push(tag) @current_block << tag end def block_order @block_order end def super_block raise CompilationError.new("Invalid 'blocksuper' call from top-level template") end def block(name) @block_store[name] end def each_block block_order.each do |block_name| yield block(block_name) end end def to_script script = "" each_block do |block| script << block.to_script end script end end # Represents the structure of a sub-template that inherits its # block structure from some parent template defined by an `extends` # tag. class ExtendedBlockSet < BlockSet def initialize(template_name) @super_template_name = template_name super() end def super_template @super_template ||= @loader.template(@super_template_name) end def super_block super_template.block(current_block.name) end def block(name) return @block_store[name] if @block_store.key?(name) super_template.block(name) end def block_order super_template.block_order end end # Converts a list of expressions into either a master or child block # set. class BlockBuilder def initialize(loader) @loader = loader assign_block_set(BlockSet.new) end def build(expressions) expressions.each do |expression| expression.affect(self) end @block_set end def extends(parent) assign_block_set(ExtendedBlockSet.new(parent)) end def assign_block_set(block_set) @block_set = block_set @block_set.loader = @loader end def current_block @block_set.current_block end def block_start(block_name) @block_set.block_start(block_name) end def block_end @block_set.block_end end def block_super push(@block_set.super_block) end def push(tag) @block_set.push(tag) end end def initialize(lexer, loader) @lexer, @loader = lexer, loader end def blocks @blocks ||= build_hierarchy end def build_hierarchy builder = BlockBuilder.new(@loader) builder.build(expressions) end def expressions expressions = [] strip = nil @lexer.tokens.each do |type, expression, strip_whitespace| case type when :text expressions << Text.new(expression, strip) when :expression expressions << Expression.new(expression) when :escaped_expression expressions << EscapedExpression.new(expression) when :statement expressions << parse_statement(expression) when :comment expressions << Comment.new(expression) end strip = strip_whitespace end expressions end EXTENDS = /\A\s*extends\s+["']([^"']+)["']\s*\z/o BLOCK_START = /\A\s*block\s+:?([a-zA-Z_][a-zA-Z0-9_]*)\s*\z/o BLOCK_END = /\A\s*endblock(?:\s+:?[a-zA-Z_][a-zA-Z0-9_]*)?\s*\z/o BLOCK_SUPER = /\A\s*block_?super\s*\z/o def parse_statement(statement) case statement when EXTENDS Extends.new($1) when BLOCK_START BlockStart.new($1) when BLOCK_END BlockEnd.new when BLOCK_SUPER BlockSuper.new else Statement.new(statement) end end def script blocks.to_script end def block_order blocks.block_order end def block(name) blocks.block(name) end end end