lib/mustache/template.rb



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