volt 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +21 -0
  3. data/.rspec +2 -0
  4. data/.ruby-version +1 -0
  5. data/Gemfile +37 -0
  6. data/Guardfile +9 -0
  7. data/LICENSE.txt +22 -0
  8. data/Rakefile +23 -0
  9. data/Readme.md +34 -0
  10. data/VERSION +1 -0
  11. data/bin/volt +4 -0
  12. data/docs/GETTING_STARTED.md +7 -0
  13. data/docs/GUIDE.md +33 -0
  14. data/lib/volt.rb +15 -0
  15. data/lib/volt/benchmark/benchmark.rb +25 -0
  16. data/lib/volt/cli.rb +34 -0
  17. data/lib/volt/console.rb +19 -0
  18. data/lib/volt/controllers/model_controller.rb +29 -0
  19. data/lib/volt/extra_core/array.rb +10 -0
  20. data/lib/volt/extra_core/blank.rb +88 -0
  21. data/lib/volt/extra_core/extra_core.rb +7 -0
  22. data/lib/volt/extra_core/numeric.rb +9 -0
  23. data/lib/volt/extra_core/object.rb +36 -0
  24. data/lib/volt/extra_core/string.rb +29 -0
  25. data/lib/volt/extra_core/stringify_keys.rb +7 -0
  26. data/lib/volt/extra_core/true_false.rb +44 -0
  27. data/lib/volt/extra_core/try.rb +31 -0
  28. data/lib/volt/models.rb +5 -0
  29. data/lib/volt/models/array_model.rb +37 -0
  30. data/lib/volt/models/model.rb +210 -0
  31. data/lib/volt/models/model_wrapper.rb +23 -0
  32. data/lib/volt/models/params.rb +67 -0
  33. data/lib/volt/models/url.rb +192 -0
  34. data/lib/volt/page/url_tracker.rb +36 -0
  35. data/lib/volt/reactive/array_extensions.rb +13 -0
  36. data/lib/volt/reactive/event_chain.rb +126 -0
  37. data/lib/volt/reactive/events.rb +283 -0
  38. data/lib/volt/reactive/object_tracker.rb +99 -0
  39. data/lib/volt/reactive/object_tracking.rb +15 -0
  40. data/lib/volt/reactive/reactive_array.rb +222 -0
  41. data/lib/volt/reactive/reactive_tags.rb +64 -0
  42. data/lib/volt/reactive/reactive_value.rb +368 -0
  43. data/lib/volt/reactive/string_extensions.rb +34 -0
  44. data/lib/volt/router/routes.rb +83 -0
  45. data/lib/volt/server.rb +121 -0
  46. data/lib/volt/server/binding_setup.rb +2 -0
  47. data/lib/volt/server/channel_handler.rb +31 -0
  48. data/lib/volt/server/component_handler.rb +88 -0
  49. data/lib/volt/server/if_binding_setup.rb +29 -0
  50. data/lib/volt/server/request_handler.rb +16 -0
  51. data/lib/volt/server/scope.rb +43 -0
  52. data/lib/volt/server/source_map_server.rb +31 -0
  53. data/lib/volt/server/template_parser.rb +452 -0
  54. data/lib/volt/store/mongo.rb +5 -0
  55. data/lib/volt/templates/attribute_binding.rb +110 -0
  56. data/lib/volt/templates/base_binding.rb +37 -0
  57. data/lib/volt/templates/channel.rb +48 -0
  58. data/lib/volt/templates/content_binding.rb +35 -0
  59. data/lib/volt/templates/document_events.rb +80 -0
  60. data/lib/volt/templates/each_binding.rb +115 -0
  61. data/lib/volt/templates/event_binding.rb +51 -0
  62. data/lib/volt/templates/if_binding.rb +74 -0
  63. data/lib/volt/templates/memory_test.rb +26 -0
  64. data/lib/volt/templates/page.rb +146 -0
  65. data/lib/volt/templates/reactive_template.rb +38 -0
  66. data/lib/volt/templates/render_queue.rb +5 -0
  67. data/lib/volt/templates/sub_context.rb +23 -0
  68. data/lib/volt/templates/targets/attribute_section.rb +33 -0
  69. data/lib/volt/templates/targets/attribute_target.rb +18 -0
  70. data/lib/volt/templates/targets/base_section.rb +14 -0
  71. data/lib/volt/templates/targets/binding_document/base_node.rb +3 -0
  72. data/lib/volt/templates/targets/binding_document/component_node.rb +112 -0
  73. data/lib/volt/templates/targets/binding_document/html_node.rb +11 -0
  74. data/lib/volt/templates/targets/dom_section.rb +147 -0
  75. data/lib/volt/templates/targets/dom_target.rb +11 -0
  76. data/lib/volt/templates/template_binding.rb +159 -0
  77. data/lib/volt/templates/template_renderer.rb +50 -0
  78. data/spec/models/event_chain_spec.rb +129 -0
  79. data/spec/models/model_spec.rb +340 -0
  80. data/spec/models/old_model_spec.rb +109 -0
  81. data/spec/models/reactive_array_spec.rb +262 -0
  82. data/spec/models/reactive_tags_spec.rb +35 -0
  83. data/spec/models/reactive_value_spec.rb +336 -0
  84. data/spec/models/string_extensions_spec.rb +57 -0
  85. data/spec/router/routes_spec.rb +24 -0
  86. data/spec/server/template_parser_spec.rb +50 -0
  87. data/spec/spec_helper.rb +20 -0
  88. data/spec/store/mongo_spec.rb +4 -0
  89. data/spec/templates/targets/binding_document/component_node_spec.rb +18 -0
  90. data/spec/templates/template_binding_spec.rb +98 -0
  91. data/templates/.gitignore +12 -0
  92. data/templates/Gemfile.tt +8 -0
  93. data/templates/app/.empty_directory +0 -0
  94. data/templates/app/home/config/routes.rb +1 -0
  95. data/templates/app/home/controllers/index_controller.rb +5 -0
  96. data/templates/app/home/css/.empty_directory +0 -0
  97. data/templates/app/home/models/.empty_directory +0 -0
  98. data/templates/app/home/views/index/about.html +9 -0
  99. data/templates/app/home/views/index/home.html +7 -0
  100. data/templates/app/home/views/index/index.html +28 -0
  101. data/templates/config.ru +4 -0
  102. data/templates/public/css/ansi.css +0 -0
  103. data/templates/public/css/bootstrap-theme.css +459 -0
  104. data/templates/public/css/bootstrap.css +7098 -0
  105. data/templates/public/css/jumbotron.css +79 -0
  106. data/templates/public/fonts/glyphicons-halflings-regular.eot +0 -0
  107. data/templates/public/fonts/glyphicons-halflings-regular.svg +229 -0
  108. data/templates/public/fonts/glyphicons-halflings-regular.ttf +0 -0
  109. data/templates/public/fonts/glyphicons-halflings-regular.woff +0 -0
  110. data/templates/public/index.html +25 -0
  111. data/templates/public/js/bootstrap.js +0 -0
  112. data/templates/public/js/jquery-2.0.3.js +8829 -0
  113. data/templates/public/js/sockjs-0.2.1.min.js +27 -0
  114. data/templates/spec/spec_helper.rb +20 -0
  115. data/volt.gemspec +41 -0
  116. metadata +412 -0
@@ -0,0 +1,31 @@
1
+ class SourceMapServer
2
+ def initialize sprockets
3
+ @sprockets = sprockets
4
+ end
5
+
6
+ attr_reader :sprockets
7
+
8
+ attr_writer :prefix
9
+
10
+ def prefix
11
+ @prefix ||= '/__opal_source_maps__'
12
+ end
13
+
14
+ def inspect
15
+ "#<#{self.class}:#{object_id}>"
16
+ end
17
+
18
+ def call(env)
19
+ path_info = env['PATH_INFO']
20
+
21
+ if path_info =~ /\.js\.map$/
22
+ path = env['PATH_INFO'].gsub(/^\/|\.js\.map$/, '')
23
+ asset = sprockets[path]
24
+ return [404, {}, []] if asset.nil?
25
+
26
+ return [200, {"Content-Type" => "text/json"}, [$OPAL_SOURCE_MAPS[asset.pathname].to_s]]
27
+ else
28
+ return [200, {"Content-Type" => "text/text"}, [File.read(sprockets.resolve(path_info))]]
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,452 @@
1
+ require 'volt/server/scope'
2
+ require 'volt/server/if_binding_setup'
3
+ require 'nokogiri'
4
+
5
+ # TODO: The section_name that we're passing in should probably be
6
+ # abstracted out. Possibly this whole thing needs a rewrite.
7
+
8
+ class Template
9
+ attr_accessor :current_scope, :section_name
10
+
11
+ def initialize(template_parser, section_name, template, scope=Scope.new)
12
+ @binding_number = 0
13
+
14
+ @template_parser = template_parser
15
+ @section_name = section_name
16
+ @template = template
17
+ @scopes = [scope]
18
+ @current_scope = @scopes.first
19
+ end
20
+
21
+ def html
22
+ if @template.respond_to?(:name) && @template.name[0] == ':'
23
+ # Don't return the <:section> tags
24
+ return @template.children.to_html
25
+ else
26
+ # if @template.class == Nokogiri::XML::NodeSet
27
+ # result = ''
28
+ # @template.each do |node|
29
+ # result << node.to_html
30
+ # end
31
+ # else
32
+ result = @template.to_html
33
+ # end
34
+ result
35
+ end
36
+ end
37
+
38
+
39
+ def add_binding(node, content)
40
+ if content[0] == '/'
41
+ add_close_mustache(node)
42
+ elsif content[0] == '#'
43
+ command, *content = content.split(/ /)
44
+ content = content.join(' ')
45
+
46
+ case command
47
+ when '#template'
48
+ return add_template(node, content)
49
+ when '#each'
50
+ return add_each_binding(node, content)
51
+ when '#if'
52
+ return add_if_binding(node, content)
53
+ when '#elsif'
54
+ return add_else_binding(node, content)
55
+ when '#else'
56
+ if content.present?
57
+ # TODO: improve error, include line/file
58
+ raise "#else should not include a condition, use #elsif instead. #{content} was passed as a condition."
59
+ end
60
+
61
+ return add_else_binding(node, nil)
62
+ else
63
+ # TODO: Handle invalid command
64
+ raise "Invalid Command"
65
+ end
66
+ else
67
+ # text binding
68
+ return add_text_binding(content)
69
+ end
70
+ end
71
+
72
+ def add_template(node, content)
73
+ html = "<!-- $#{@binding_number} --><!-- $/#{@binding_number} -->"
74
+
75
+ @current_scope.add_binding(@binding_number, "lambda { |target, context, id| TemplateBinding.new(target, context, id, #{@template_parser.template_path.inspect}, Proc.new { [#{content}] }) }")
76
+
77
+ @binding_number += 1
78
+ return html
79
+ end
80
+
81
+ def add_each_binding(node, content)
82
+ html = "<!-- $#{@binding_number} -->"
83
+
84
+ content, variable_name = content.strip.split(/ as /)
85
+
86
+ template_name = "#{@template_parser.template_path}/#{section_name}/__template/#{@binding_number}"
87
+ @current_scope.add_binding(@binding_number, "lambda { |target, context, id| EachBinding.new(target, context, id, Proc.new { #{content} }, #{variable_name.inspect}, #{template_name.inspect}) }")
88
+
89
+ # Add the node, the binding number, then store the location where the
90
+ # bindings for this block starts.
91
+ @current_scope = Scope.new(@binding_number)
92
+ @scopes << @current_scope
93
+
94
+ @binding_number += 1
95
+ return html
96
+ end
97
+
98
+ def add_if_binding(node, content)
99
+ html = "<!-- $#{@binding_number} -->"
100
+
101
+ template_name = "#{@template_parser.template_path}/#{section_name}/__template/#{@binding_number}"
102
+ if_binding_setup = IfBindingSetup.new
103
+ if_binding_setup.add_branch(content, template_name)
104
+
105
+ @current_scope.start_if_binding(@binding_number, if_binding_setup)
106
+
107
+ # Add the node, the binding number, then store the location where the
108
+ # bindings for this block starts.
109
+ @current_scope = Scope.new(@binding_number)
110
+ @scopes << @current_scope
111
+
112
+ @binding_number += 1
113
+ return html
114
+ end
115
+
116
+ def add_else_binding(node, content)
117
+ html = add_close_mustache(node, false)
118
+
119
+ html += "<!-- $#{@binding_number} -->"
120
+ template_name = "#{@template_parser.template_path}/#{section_name}/__template/#{@binding_number}"
121
+
122
+ @current_scope.current_if_binding[1].add_branch(content, template_name)
123
+
124
+ # Add the node, the binding number, then store the location where the
125
+ # bindings for this block starts.
126
+ @current_scope = Scope.new(@binding_number)
127
+ @scopes << @current_scope
128
+
129
+ @binding_number += 1
130
+
131
+ return html
132
+ end
133
+
134
+ def add_close_mustache(node, close_if=true)
135
+ scope = @scopes.pop
136
+ @current_scope = @scopes.last
137
+
138
+ # Close an outstanding if binding (if it exists)
139
+ @current_scope.close_if_binding! if close_if
140
+
141
+ # Track that this scope was closed out
142
+ @current_scope.add_closed_child_scope(scope)
143
+
144
+ html = "<!-- $/#{scope.outer_binding_number} -->"
145
+
146
+ return html
147
+ end
148
+
149
+ # When we find a binding, we pass it's content in here and replace it with
150
+ # the return value
151
+ def add_text_binding(content)
152
+ html = "<!-- $#{@binding_number} --><!-- $/#{@binding_number} -->"
153
+
154
+ @current_scope.add_binding(@binding_number, "lambda { |target, context, id| ContentBinding.new(target, context, id, Proc.new { #{content} }) }")
155
+
156
+ @binding_number += 1
157
+ return html
158
+ end
159
+
160
+ def setup_node_id(node)
161
+ id = node['id']
162
+ # First assign this node an id if it doesn't have one
163
+ unless id
164
+ id = node['id'] = "id#{@binding_number}"
165
+ @binding_number += 1
166
+ end
167
+ end
168
+
169
+ # Attribute bindings support multiple handlebar listeners
170
+ # Exvoltle:
171
+ # <button click="{_primary} {_important}">...
172
+ #
173
+ # To accomplish this, we create a new listener from the existing ones in the Proc
174
+ # that we pass to the binding when it is created.
175
+ def add_attribute_binding(node, attribute, content)
176
+ setup_node_id(node)
177
+
178
+ if content =~ /^\{[^\{]+\}$/
179
+ # Getter is the content inside of { ... }
180
+ add_single_getter(node, attribute, content)
181
+ else
182
+ add_multiple_getters(node, attribute, content)
183
+ end
184
+
185
+ end
186
+
187
+ def add_single_getter(node, attribute, content)
188
+ if attribute == 'checked' || true
189
+ # For a checkbox, we don't want to add
190
+ getter = content[1..-2]
191
+ else
192
+ # Otherwise we should combine them
193
+ # TODO: We should make .or handle assignment
194
+ getter = "_tmp = #{content[1..-2]}.or('') ; _tmp.reactive_manager.setter! { |val| self.#{content[1..-2]} = val } ; _tmp"
195
+ end
196
+
197
+ @current_scope.add_binding(node['id'], "lambda { |target, context, id| AttributeBinding.new(target, context, id, #{attribute.inspect}, Proc.new { #{getter} }) }")
198
+ end
199
+
200
+ def add_multiple_getters(node, attribute, content)
201
+ case attribute
202
+ when 'checked', 'value'
203
+ if parts.size > 1
204
+ # Multiple ReactiveValue's can not be passed to value or checked attributes.
205
+ raise "Multiple bindings can not be passed to a #{attribute} binding."
206
+ end
207
+ end
208
+
209
+ reactive_template_path = add_reactive_template(content)
210
+
211
+ @current_scope.add_binding(node['id'], "lambda { |target, context, id| AttributeBinding.new(target, context, id, #{attribute.inspect}, Proc.new { ReactiveTemplate.new(context, #{reactive_template_path.inspect}) }) }")
212
+ end
213
+
214
+ # Returns a path to a template for the content. This can be passed
215
+ # into ReactiveTemplate.new, along with the current context.
216
+ def add_reactive_template(content)
217
+ # Return a template path instead
218
+ template_name = "__attribute/#{@binding_number}"
219
+ full_template_path = "#{@template_parser.template_path}/#{section_name}/#{template_name}"
220
+ @binding_number += 1
221
+
222
+ attribute_template = Template.new(@template_parser, section_name, Nokogiri::HTML::DocumentFragment.parse(content))
223
+ @template_parser.add_template("#{section_name}/#{template_name}", attribute_template)
224
+ attribute_template.start_walk
225
+ attribute_template.pull_closed_block_scopes
226
+
227
+ return full_template_path
228
+ end
229
+
230
+ def add_event_binding(node, attribute_name, content)
231
+ setup_node_id(node)
232
+
233
+ event = attribute_name[2..-1]
234
+
235
+ if node.name == 'a'
236
+ # For links, we need to add blank href to make it clickable.
237
+ node['href'] ||= ''
238
+ end
239
+
240
+ @current_scope.add_binding(node['id'], "lambda { |target, context, id| EventBinding.new(target, context, id, #{event.inspect}, Proc.new {|event| #{content} })}")
241
+ end
242
+
243
+ def pull_closed_block_scopes(scope=@current_scope)
244
+ if scope.closed_block_scopes
245
+ scope.closed_block_scopes.each do |sub_scope|
246
+ # Loop through any subscopes first, pull them in.
247
+ pull_closed_block_scopes(sub_scope)
248
+
249
+ # Grab everything between the start/end html comments
250
+ start_node = find_by_comment("$#{sub_scope.outer_binding_number}")
251
+ end_node = find_by_comment("$/#{sub_scope.outer_binding_number}")
252
+
253
+ move_nodes_to_new_template(start_node, end_node, sub_scope)
254
+ end
255
+ end
256
+ end
257
+
258
+
259
+ def move_nodes_to_new_template(start_node, end_node, scope)
260
+ # TODO: currently this doesn't handle spanning nodes within seperate containers.
261
+ # so doing tr's doesn't work for some reason.
262
+
263
+ start_parent = start_node.parent
264
+ start_parent = start_parent.children if start_parent.is_a?(Nokogiri::HTML::DocumentFragment) || start_parent.is_a?(Nokogiri::XML::Element)
265
+ start_index = start_parent.index(start_node) + 1
266
+
267
+ end_parent = end_node.parent
268
+ end_parent = end_parent.children if end_parent.is_a?(Nokogiri::HTML::DocumentFragment) || end_parent.is_a?(Nokogiri::XML::Element)
269
+ end_index = end_parent.index(end_node) - 1
270
+
271
+ move_nodes = start_parent[start_index..end_index]
272
+ move_nodes.remove
273
+
274
+ new_template = Template.new(@template_parser, section_name, move_nodes, scope)
275
+
276
+ @template_parser.add_template("#{section_name}/__template/#{scope.outer_binding_number}", new_template)
277
+ end
278
+
279
+
280
+ def find_by_comment(name)
281
+ return @template.xpath("descendant::comment()[. = ' #{name} ']").first
282
+ end
283
+
284
+ def start_walk
285
+ walk(@template)
286
+ end
287
+
288
+ # We implement a dom walker that can walk down the dom and spit out output
289
+ # html as we go
290
+ def walk(node)
291
+ case node.type
292
+ when 1
293
+ # html node
294
+ walk_html_node(node)
295
+ when 3
296
+ # text node
297
+ walk_text_node(node)
298
+ end
299
+
300
+ node.children.each do |child|
301
+ walk(child)
302
+ end
303
+ end
304
+
305
+ def walk_html_node(node)
306
+ if node.name[0] == ':' && node.path.count('/') > 1
307
+ parse_component(node)
308
+ elsif node.name == 'textarea'
309
+ parse_textarea(node)
310
+ else
311
+ parse_html_node(node)
312
+ end
313
+ end
314
+
315
+ # We provide a quick way to render components with tags starting
316
+ # with a :
317
+ # Count the number of /'s in the path, if we are at the root node
318
+ # we can ignore it, since this is the template its self.
319
+ # TODO: Root node might not be the template if we parsed directly
320
+ # without a subtemplate specifier. We need to find a good way to
321
+ # parse only within the subtemplate.
322
+ def parse_component(node)
323
+ template_path = node.name[1..-1].gsub(':', '/')
324
+
325
+ # Take the attributes and turn them into a hash
326
+ attribute_hash = {}
327
+ node.attribute_nodes.each do |attribute_node|
328
+ content = attribute_node.value
329
+
330
+ if !content.index('{')
331
+ # passing in a string
332
+ value = content.inspect
333
+ elsif content =~ /^\{[^\}]+\}$/
334
+ # Has one binding, just get it
335
+ value = "Proc.new { #{content[1..-2]} }"
336
+ else
337
+ # Has multiple bindings, we need to render a template here
338
+ attr_template_path = add_reactive_template(content)
339
+
340
+ value = "Proc.new { ReactiveTemplate.new(context, #{attr_template_path.inspect}) }"
341
+ end
342
+
343
+ attribute_hash[attribute_node.name] = value
344
+ end
345
+
346
+ attributes_string = attribute_hash.to_a.map do |key, value|
347
+ "#{key.inspect} => #{value}"
348
+ end.join(', ')
349
+
350
+ # Setup the arguments string, which goes to the TemplateBinding
351
+ args_str = "#{template_path.inspect}"
352
+ args_str << ", {#{attributes_string}}" if attribute_hash.size > 0
353
+
354
+ new_html = add_template(node, args_str)
355
+
356
+ node.swap(new_html)#Nokogiri::HTML::DocumentFragment.parse(new_html))
357
+ end
358
+
359
+ def parse_textarea(node)
360
+ # The contents of textareas should really be treated like a
361
+ # value= attribute. So here we pull the content into a value attribute
362
+ # if the textarea has bindings in the content.
363
+ if node.inner_html =~ /\{[^\}]+\}/
364
+ node[:value] = node.inner_html
365
+ node.children.remove
366
+ end
367
+
368
+ parse_html_node(node)
369
+ end
370
+
371
+ def parse_html_node(node)
372
+ node.attribute_nodes.each do |attribute_node|
373
+ if attribute_node.name =~ /^e\-/
374
+ # We have an e- binding
375
+ add_event_binding(node, attribute_node.name, attribute_node.value)
376
+
377
+ # remove the attribute
378
+ attribute_node.remove
379
+ elsif attribute_node.value.match(/\{[^\}]+\}/)
380
+ # Has bindings
381
+ add_attribute_binding(node, attribute_node.name, attribute_node.value)
382
+
383
+ # remove the attribute
384
+ attribute_node.remove
385
+ end
386
+ end
387
+ end
388
+
389
+ def walk_text_node(node)
390
+ new_html = node.content.gsub(/\{([^\}]+)\}/) do |template_binding|
391
+ add_binding(node, $1)
392
+ end
393
+
394
+ # puts "------! #{new_html.inspect} - #{node.class.inspect} - #{node.inspect}"
395
+
396
+ # TODO: Broke here in jruby
397
+ node.swap(new_html)# if new_html.blank?
398
+
399
+ #Nokogiri::HTML::DocumentFragment.parse(new_html))
400
+ end
401
+ end
402
+
403
+ class TemplateParser
404
+ attr_accessor :dom, :bindings, :template_path
405
+
406
+ def initialize(template, template_path)
407
+ @templates = {}
408
+ @template_path = template_path
409
+
410
+ template_fragment = Nokogiri::HTML::DocumentFragment.parse(template)
411
+
412
+ # Add templates for each section
413
+
414
+ # Check for sections
415
+ sections = []
416
+ if template_fragment.children[0].name[0] == ':'
417
+ template_fragment.children.each do |child|
418
+ if child.is_a?(Nokogiri::XML::Element)
419
+ sections << [child, child.name[1..-1]]
420
+ end
421
+ end
422
+ else
423
+ sections << [template_fragment, 'body']
424
+ end
425
+
426
+ sections.each do |section, name|
427
+ template = Template.new(self, name, section)
428
+ add_template(name, template)
429
+ template.start_walk
430
+ template.pull_closed_block_scopes
431
+ end
432
+
433
+ end
434
+
435
+ def add_template(name, template)
436
+ @templates[@template_path + '/' + name] = template
437
+ end
438
+
439
+ # Return the templates, but map the html from nokogiri to html
440
+ def templates
441
+ mapped = {}
442
+ @templates.each_pair do |name, template|
443
+ mapped[name] = {
444
+ 'html' => template.html,
445
+ 'bindings' => template.current_scope.bindings
446
+ }
447
+ end
448
+
449
+ return mapped
450
+ end
451
+
452
+ end