Class: Mustache::Generator

Inherits:
Object
  • Object
show all
Defined in:
lib/mustache/generator.rb

Overview

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 #.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"

Instance Method Summary collapse

Constructor Details

#initialize(options = {}) ⇒ Generator

Options are unused for now but may become useful in the future.



31
32
33
# File 'lib/mustache/generator.rb', line 31

def initialize(options = {})
  @options = options
end

Instance Method Details

#compile(exp) ⇒ Object

Given an array of tokens, returns an interpolatable Ruby string.



36
37
38
# File 'lib/mustache/generator.rb', line 36

def compile(exp)
  "\"#{compile!(exp)}\""
end

#compile!(exp) ⇒ Object

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",
 ["{{", "}}"]]]


83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/mustache/generator.rb', line 83

def compile!(exp)
  case exp.first
  when :multi
    exp[1..-1].map { |e| compile!(e) }.join
  when :static
    str(exp[1])
  when :mustache
    send("on_#{exp[1]}", *exp[2..-1])
  else
    raise "Unhandled exp: #{exp.first}"
  end
end

#ev(s) ⇒ Object

An interpolation-friendly version of a string, for use within a Ruby string.



192
193
194
# File 'lib/mustache/generator.rb', line 192

def ev(s)
  "#\{#{s}}"
end

#on_etag(name, offset) ⇒ Object

An escaped tag.



163
164
165
166
167
168
169
170
171
# File 'lib/mustache/generator.rb', line 163

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

#on_fetch(names) ⇒ Object



173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/mustache/generator.rb', line 173

def on_fetch(names)
  names = names.map { |n| n.to_sym }

  if names.length == 0
    "ctx[:to_s]"
  elsif names.length == 1
    "ctx[#{names.first.to_sym.inspect}]"
  else
    initial, *rest = names
    <<-compiled
      #{rest.inspect}.inject(ctx[#{initial.inspect}]) { |value, key|
        value && ctx.find(value, key)
      }
    compiled
  end
end

#on_inverted_section(name, offset, content, raw, delims) ⇒ Object

Fired when we find an inverted section. Just like `on_section`, we're passed the inverted section name and the array of tokens.



129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/mustache/generator.rb', line 129

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

#on_partial(name, offset, indentation) ⇒ Object

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.



147
148
149
# File 'lib/mustache/generator.rb', line 147

def on_partial(name, offset, indentation)
  ev("ctx.partial(#{name.to_sym.inspect}, #{indentation.inspect})")
end

#on_section(name, offset, content, raw, delims) ⇒ Object

Callback fired when the compiler finds a section token. We're passed the section name and the array of tokens.



98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/mustache/generator.rb', line 98

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)

  # Compile the Ruby for this section now that we know what's
  # inside the section.
  ev(<<-compiled)
  if v = #{compile!(name)}
    if v == true
      #{code}
    elsif v.is_a?(Proc)
      t = Mustache::Template.new(v.call(#{raw.inspect}).to_s)
      def t.tokens(src=@source)
        p = Parser.new
        p.otag, p.ctag = #{delims.inspect}
        p.compile(src)
      end
      t.render(ctx.dup)
    else
      # Shortcut when passed non-array
      v = [v] unless v.is_a?(Array) || defined?(Enumerator) && v.is_a?(Enumerator)

      v.map { |h| ctx.push(h); r = #{code}; ctx.pop; r }.join
    end
  end
  compiled
end

#on_utag(name, offset) ⇒ Object

An unescaped tag.



152
153
154
155
156
157
158
159
160
# File 'lib/mustache/generator.rb', line 152

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

#str(s) ⇒ Object



196
197
198
# File 'lib/mustache/generator.rb', line 196

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