require 'cgi' require 'mustache/parser' require 'mustache/generator' class Mustache # A Template represents a Mustache template. It compiles and caches # a raw string template into something usable. # # The idea is this: when handed a Mustache template, convert it into # a Ruby string by transforming Mustache tags into interpolated # Ruby. # # You shouldn't use this class directly, instead: # # >> Mustache.render(template, hash) class Template attr_reader :source # Expects a Mustache template as a string along with a template # path, which it uses to find partials. Options may be passed. def initialize(source, options = {}) @source = source @options = options end # Renders the `@source` Mustache template using the given # `context`, which should be a simple hash keyed with symbols. # # The first time a template is rendered, this method is overriden # and from then on it is "compiled". Subsequent calls will skip # the compilation step and run the Ruby version of the template # directly. def render(context) # Compile our Mustache template into a Ruby string compiled = "def render(ctx) #{compile} end" # Here we rewrite ourself with the interpolated Ruby version of # our Mustache template so subsequent calls are very fast and # can skip the compilation stage. instance_eval(compiled, __FILE__, __LINE__ - 1) # Call the newly rewritten version of #render render(context) end # Does the dirty work of transforming a Mustache template into an # interpolation-friendly Ruby string. def compile(src = @source) Generator.new(@options).compile(tokens(src)) end alias_method :to_s, :compile # Returns an array of tokens for a given template. # # @return [Array] Array of tokens. # def tokens(src = @source) Parser.new(@options).compile(src) end # Returns an array of tags. # # Tags that belong to sections will be of the form `section1.tag`. # # @return [Array] Returns an array of tags. # def tags Template.recursor(tokens, []) do |token, section| if [:etag, :utag].include?(token[1]) [ new_token=nil, new_section=nil, result=((section + [token[2][2][0]]).join('.')), stop=true ] elsif [:section, :inverted_section].include?(token[1]) [ new_token=token[4], new_section=(section + [token[2][2][0]]), result=nil, stop=false ] else [ new_token=token, new_section=section, result=nil, stop=false ] end end.flatten.reject(&:nil?).uniq end # Returns an array of sections. # # Sections that belong to other sections will be of the form `section1.childsection` # # @return [Array] Returns an array of section. # def sections Template.recursor(tokens, []) do |token, section| if [:section, :inverted_section].include?(token[1]) new_section=(section + [token[2][2][0]]) [ new_token=token[4], new_section, result=new_section.join('.'), stop=false ] else [ new_token=token, new_section=section, result=nil, stop=false ] end end.flatten.reject(&:nil?).uniq end # Returns an array of partials. # # Partials that belong to sections are included, but the section name is not preserved # # @return [Array] Returns an array of partials. # def partials Template.recursor(tokens, []) do |token, section| if token[1] == :partial [ new_token=token, new_section=section, result=token[2], stop=true ] else [ new_token=token, new_section=section, result=nil, stop=false ] end end.flatten.reject(&:nil?).uniq end # Simple recursive iterator for tokens def self.recursor(toks, section, &block) toks.map do |token| next unless token.is_a? Array if token.first == :mustache new_token, new_section, result, stop = yield(token, section) [ result ] + ( stop ? [] : recursor(new_token, new_section, &block)) else recursor(token, section, &block) end end end end end