lib/mustache/generator.rb



class Mustache
  # The Generator is in charge of taking an array of Mustache tokens,
  # usually assembled by the Parser, and generating an interpolatable
  # Ruby string. This string is considered the "compiled" template
  # because at that point we're relying on Ruby to do the parsing and
  # run our code.
  #
  # For example, let's take this template:
  #
  #   Hi {{thing}}!
  #
  # If we run this through the Parser we'll get these tokens:
  #
  #   [:multi,
  #     [:static, "Hi "],
  #     [:mustache, :etag, "thing"],
  #     [:static, "!\n"]]
  #
  # Now let's hand that to the Generator:
  #
  # >> puts Mustache::Generator.new.compile(tokens)
  # "Hi #{CGI.escapeHTML(ctx[:thing].to_s)}!\n"
  #
  # You can see the generated Ruby string for any template with the
  # mustache(1) command line tool:
  #
  #   $ mustache --compile test.mustache
  #   "Hi #{CGI.escapeHTML(ctx[:thing].to_s)}!\n"
  class Generator
    # Options can be used to manipulate the resulting ruby code string behavior.
    def initialize(options = {})
      @options = options
      @option_static_lambdas = options[:static_lambdas] == true
    end

    # Given an array of tokens, returns an interpolatable Ruby string.
    def compile(exp)
      "\"#{compile!(exp)}\""
    end


    private


    # Given an array of tokens, converts them into Ruby code. In
    # particular there are three types of expressions we are concerned
    # with:
    #
    #   :multi
    #     Mixed bag of :static, :mustache, and whatever.
    #
    #   :static
    #     Normal HTML, the stuff outside of {{mustaches}}.
    #
    #   :mustache
    #     Any Mustache tag, from sections to partials.
    #
    # To give you an idea of what you'll be dealing with take this
    # template:
    #
    #   Hello {{name}}
    #   You have just won ${{value}}!
    #   {{#in_ca}}
    #   Well, ${{taxed_value}}, after taxes.
    #   {{/in_ca}}
    #
    # If we run this through the Parser, we'll get back this array of
    # tokens:
    #
    #   [:multi,
    #    [:static, "Hello "],
    #    [:mustache, :etag,
    #     [:mustache, :fetch, ["name"]]],
    #    [:static, "\nYou have just won $"],
    #   [:mustache, :etag,
    #    [:mustache, :fetch, ["value"]]],
    #   [:static, "!\n"],
    #   [:mustache,
    #    :section,
    #    [:mustache, :fetch, ["in_ca"]],
    #   [:multi,
    #    [:static, "Well, $"],
    #    [:mustache, :etag,
    #     [:mustache, :fetch, ["taxed_value"]]],
    #    [:static, ", after taxes.\n"]],
    #    "Well, ${{taxed_value}}, after taxes.\n",
    #    ["{{", "}}"]]]
    def compile!(exp)
      case exp.first
      when :multi
        exp[1..-1].reduce("") { |sum, e| sum << compile!(e) }
      when :static
        str(exp[1])
      when :mustache
        send("on_#{exp[1]}", *exp[2..-1])
      else
        raise "Unhandled exp: #{exp.first}"
      end
    end

    # Callback fired when the compiler finds a section token. We're
    # passed the section name and the array of tokens.
    def on_section(name, offset, content, raw, delims)
      # Convert the tokenized content of this section into a Ruby
      # string we can use.
      code = compile(content)

      # Lambda handling - default handling is to dynamically interpret
      # the returned lambda result as mustache source
      proc_handling = if @option_static_lambdas
        <<-compiled
          v.call(lambda {|v| #{code}}.call(v)).to_s
        compiled
      else
        <<-compiled
          t = Mustache::Template.new(v.call(#{raw.inspect}).to_s)
          def t.tokens(src=@source)
            p = Mustache::Parser.new
            p.otag, p.ctag = #{delims.inspect}
            p.compile(src)
          end
          t.render(ctx.dup)
        compiled
      end

      # Compile the Ruby for this section now that we know what's
      # inside the section.
      ev(<<-compiled)
      case v = #{compile!(name)}
      when NilClass, FalseClass
      when TrueClass
        #{code}
      when Proc
        #{proc_handling}
      when Array, Enumerator, Mustache::Enumerable
        v.map { |_| ctx.push(_); r = #{code}; ctx.pop; r }.join
      else
        ctx.push(v); r = #{code}; ctx.pop; r
      end
      compiled
    end

    # Fired when we find an inverted section. Just like `on_section`,
    # we're passed the inverted section name and the array of tokens.
    def on_inverted_section(name, offset, content, raw, delims)
      # Convert the tokenized content of this section into a Ruby
      # string we can use.
      code = compile(content)

      # Compile the Ruby for this inverted section now that we know
      # what's inside.
      ev(<<-compiled)
      v = #{compile!(name)}
      if v.nil? || v == false || v.respond_to?(:empty?) && v.empty?
        #{code}
      end
      compiled
    end

    # Fired when the compiler finds a partial. We want to return code
    # which calls a partial at runtime instead of expanding and
    # including the partial's body to allow for recursive partials.
    def on_partial(name, offset, indentation)
      ev("ctx.partial(#{name.to_sym.inspect}, #{indentation.inspect})")
    end

    # An unescaped tag.
    def on_utag(name, offset)
      ev(<<-compiled)
        v = #{compile!(name)}
        if v.is_a?(Proc)
          v = #{@option_static_lambdas ? 'v.call' : 'Mustache::Template.new(v.call.to_s).render(ctx.dup)'}
        end
        v.to_s
      compiled
    end

    # An escaped tag.
    def on_etag(name, offset)
      ev(<<-compiled)
        v = #{compile!(name)}
        if v.is_a?(Proc)
          v = #{@option_static_lambdas ? 'v.call' : 'Mustache::Template.new(v.call.to_s).render(ctx.dup)'}
        end
        ctx.escape(v)
      compiled
    end

    def on_fetch(names)
      return "ctx.current" if names.empty?

      names = names.map { |n| n.to_sym }

      initial, *rest = names
      if rest.any?
        <<-compiled
          #{rest.inspect}.reduce(ctx[#{initial.inspect}]) { |value, key| value && ctx.find(value, key) }
        compiled
      else
        <<-compiled
          ctx[#{initial.inspect}]
        compiled
      end
    end

    # An interpolation-friendly version of a string, for use within a
    # Ruby string.
    def ev(s)
      "#\{#{s}}"
    end

    def str(s)
      s.inspect[1..-2]
    end
  end
end