liquid 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. data/CHANGELOG +38 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Manifest.txt +60 -0
  4. data/README +38 -0
  5. data/Rakefile +24 -0
  6. data/example/server/example_servlet.rb +37 -0
  7. data/example/server/liquid_servlet.rb +28 -0
  8. data/example/server/server.rb +12 -0
  9. data/example/server/templates/index.liquid +6 -0
  10. data/example/server/templates/products.liquid +45 -0
  11. data/init.rb +6 -0
  12. data/lib/extras/liquid_view.rb +27 -0
  13. data/lib/liquid.rb +66 -0
  14. data/lib/liquid/block.rb +101 -0
  15. data/lib/liquid/condition.rb +91 -0
  16. data/lib/liquid/context.rb +216 -0
  17. data/lib/liquid/document.rb +17 -0
  18. data/lib/liquid/drop.rb +48 -0
  19. data/lib/liquid/errors.rb +7 -0
  20. data/lib/liquid/extensions.rb +56 -0
  21. data/lib/liquid/file_system.rb +62 -0
  22. data/lib/liquid/htmltags.rb +64 -0
  23. data/lib/liquid/standardfilters.rb +125 -0
  24. data/lib/liquid/strainer.rb +43 -0
  25. data/lib/liquid/tag.rb +25 -0
  26. data/lib/liquid/tags/assign.rb +22 -0
  27. data/lib/liquid/tags/capture.rb +22 -0
  28. data/lib/liquid/tags/case.rb +68 -0
  29. data/lib/liquid/tags/comment.rb +9 -0
  30. data/lib/liquid/tags/cycle.rb +46 -0
  31. data/lib/liquid/tags/for.rb +81 -0
  32. data/lib/liquid/tags/if.rb +51 -0
  33. data/lib/liquid/tags/ifchanged.rb +20 -0
  34. data/lib/liquid/tags/include.rb +56 -0
  35. data/lib/liquid/tags/unless.rb +29 -0
  36. data/lib/liquid/template.rb +150 -0
  37. data/lib/liquid/variable.rb +39 -0
  38. data/test/block_test.rb +50 -0
  39. data/test/context_test.rb +340 -0
  40. data/test/drop_test.rb +139 -0
  41. data/test/error_handling_test.rb +65 -0
  42. data/test/extra/breakpoint.rb +547 -0
  43. data/test/extra/caller.rb +80 -0
  44. data/test/file_system_test.rb +30 -0
  45. data/test/filter_test.rb +98 -0
  46. data/test/helper.rb +20 -0
  47. data/test/html_tag_test.rb +24 -0
  48. data/test/if_else_test.rb +95 -0
  49. data/test/include_tag_test.rb +91 -0
  50. data/test/output_test.rb +121 -0
  51. data/test/parsing_quirks_test.rb +14 -0
  52. data/test/regexp_test.rb +39 -0
  53. data/test/security_test.rb +41 -0
  54. data/test/standard_filter_test.rb +101 -0
  55. data/test/standard_tag_test.rb +336 -0
  56. data/test/statements_test.rb +137 -0
  57. data/test/strainer_test.rb +16 -0
  58. data/test/template_test.rb +26 -0
  59. data/test/unless_else_test.rb +19 -0
  60. data/test/variable_test.rb +135 -0
  61. metadata +114 -0
@@ -0,0 +1,51 @@
1
+ module Liquid
2
+ class If < Block
3
+ Syntax = /(#{QuotedFragment})\s*([=!<>]+)?\s*(#{QuotedFragment})?/
4
+
5
+ def initialize(markup, tokens)
6
+ @blocks = []
7
+
8
+ push_block('if', markup)
9
+
10
+ super
11
+ end
12
+
13
+ def unknown_tag(tag, markup, tokens)
14
+ if ['elsif', 'else'].include?(tag)
15
+ push_block(tag, markup)
16
+ else
17
+ super
18
+ end
19
+ end
20
+
21
+ def render(context)
22
+ context.stack do
23
+ @blocks.each do |block|
24
+ if block.evaluate(context)
25
+ return render_all(block.attachment, context)
26
+ end
27
+ end
28
+ ''
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def push_block(tag, markup)
35
+
36
+ block = if tag == 'else'
37
+ ElseCondition.new
38
+ elsif markup =~ Syntax
39
+ Condition.new($1, $2, $3)
40
+ else
41
+ raise SyntaxError.new("Syntax Error in tag '#{tag}' - Valid syntax: #{tag} [condition]")
42
+ end
43
+
44
+ @blocks.push(block)
45
+ @nodelist = block.attach(Array.new)
46
+ end
47
+
48
+ end
49
+
50
+ Template.register_tag('if', If)
51
+ end
@@ -0,0 +1,20 @@
1
+ module Liquid
2
+ class Ifchanged < Block
3
+
4
+ def render(context)
5
+ context.stack do
6
+
7
+ output = render_all(@nodelist, context)
8
+
9
+ if output != context.registers[:ifchanged]
10
+ context.registers[:ifchanged] = output
11
+ output
12
+ else
13
+ ''
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ Template.register_tag('ifchanged', Ifchanged)
20
+ end
@@ -0,0 +1,56 @@
1
+ module Liquid
2
+ class Include < Tag
3
+ Syntax = /(#{QuotedFragment}+)(\s+(?:with|for)\s+(#{QuotedFragment}+))?/
4
+
5
+ def initialize(markup, tokens)
6
+ if markup =~ Syntax
7
+
8
+ @template_name = $1
9
+ @variable_name = $3
10
+ @attributes = {}
11
+
12
+ markup.scan(TagAttributes) do |key, value|
13
+ @attributes[key] = value
14
+ end
15
+
16
+ else
17
+ raise SyntaxError.new("Error in tag 'include' - Valid syntax: include '[template]' (with|for) [object|collection]")
18
+ end
19
+
20
+ super
21
+ end
22
+
23
+ def parse(tokens)
24
+ end
25
+
26
+ def render(context)
27
+ source = Liquid::Template.file_system.read_template_file(context[@template_name])
28
+ partial = Liquid::Template.parse(source)
29
+
30
+
31
+ variable = context[@variable_name]
32
+
33
+ context.stack do
34
+ @attributes.each do |key, value|
35
+ context[key] = context[value]
36
+ end
37
+
38
+ if variable.is_a?(Array)
39
+
40
+ variable.collect do |variable|
41
+ context[@template_name[1..-2]] = variable
42
+ partial.render(context)
43
+ end
44
+
45
+ else
46
+
47
+ context[@template_name[1..-2]] = variable
48
+ partial.render(context)
49
+
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ Template.register_tag('include', Include)
56
+ end
@@ -0,0 +1,29 @@
1
+ require File.dirname(__FILE__) + '/if'
2
+
3
+ module Liquid
4
+
5
+ class Unless < If
6
+ def render(context)
7
+ context.stack do
8
+
9
+ # First condition is interpreted backwards ( if not )
10
+ block = @blocks.shift
11
+ unless block.evaluate(context)
12
+ return render_all(block.attachment, context)
13
+ end
14
+
15
+ # After the first condition unless works just like if
16
+ @blocks.each do |block|
17
+ if block.evaluate(context)
18
+ return render_all(block.attachment, context)
19
+ end
20
+ end
21
+
22
+ ''
23
+ end
24
+ end
25
+ end
26
+
27
+
28
+ Template.register_tag('unless', Unless)
29
+ end
@@ -0,0 +1,150 @@
1
+ module Liquid
2
+
3
+ # Templates are central to liquid.
4
+ # Interpretating templates is a two step process. First you compile the
5
+ # source code you got. During compile time some extensive error checking is performed.
6
+ # your code should expect to get some SyntaxErrors.
7
+ #
8
+ # After you have a compiled template you can then <tt>render</tt> it.
9
+ # You can use a compiled template over and over again and keep it cached.
10
+ #
11
+ # Example:
12
+ #
13
+ # template = Liquid::Template.parse(source)
14
+ # template.render('user_name' => 'bob')
15
+ #
16
+ class Template
17
+ attr_accessor :root
18
+ @@file_system = BlankFileSystem.new
19
+
20
+ class <<self
21
+ def file_system
22
+ @@file_system
23
+ end
24
+
25
+ def file_system=(obj)
26
+ @@file_system = obj
27
+ end
28
+
29
+ def register_tag(name, klass)
30
+ tags[name.to_s] = klass
31
+ end
32
+
33
+ def tags
34
+ @tags ||= {}
35
+ end
36
+
37
+ # Pass a module with filter methods which should be available
38
+ # to all liquid views. Good for registering the standard library
39
+ def register_filter(mod)
40
+ Strainer.global_filter(mod)
41
+ end
42
+
43
+ # creates a new <tt>Template</tt> object from liquid source code
44
+ def parse(source)
45
+ template = Template.new
46
+ template.parse(source)
47
+ template
48
+ end
49
+ end
50
+
51
+ # creates a new <tt>Template</tt> from an array of tokens. Use <tt>Template.parse</tt> instead
52
+ def initialize
53
+ end
54
+
55
+ # Parse source code.
56
+ # Returns self for easy chaining
57
+ def parse(source)
58
+ @root = Document.new(tokenize(source))
59
+ self
60
+ end
61
+
62
+ def registers
63
+ @registers ||= {}
64
+ end
65
+
66
+ def assigns
67
+ @assigns ||= {}
68
+ end
69
+
70
+ def errors
71
+ @errors ||= []
72
+ end
73
+
74
+ def handle_error(e)
75
+ errors.push(e)
76
+ raise if @rethrow_errors
77
+
78
+ case e
79
+ when SyntaxError
80
+ "Liquid syntax error: #{e.message}"
81
+ else
82
+ "Liquid error: #{e.message}"
83
+ end
84
+ end
85
+
86
+ # Render takes a hash with local variables.
87
+ #
88
+ # if you use the same filters over and over again consider registering them globally
89
+ # with <tt>Template.register_filter</tt>
90
+ #
91
+ # Following options can be passed:
92
+ #
93
+ # * <tt>filters</tt> : array with local filters
94
+ # * <tt>registers</tt> : hash with register variables. Those can be accessed from
95
+ # filters and tags and might be useful to integrate liquid more with its host application
96
+ #
97
+ def render(*args)
98
+ return '' if @root.nil?
99
+
100
+ context = case args.first
101
+ when Liquid::Context
102
+ args.shift
103
+ when Hash
104
+ self.assigns.merge!(args.shift)
105
+ Context.new(self)
106
+ when nil
107
+ Context.new(self)
108
+ end
109
+
110
+ case args.last
111
+ when Hash
112
+ options = args.pop
113
+
114
+ if options[:registers].is_a?(Hash)
115
+ self.registers.merge!(options[:registers])
116
+ end
117
+
118
+ if options[:filters]
119
+ context.add_filters(options[:filters])
120
+ end
121
+ when Module
122
+ context.add_filters(args.pop)
123
+ when Array
124
+ context.add_filters(args.pop)
125
+ end
126
+
127
+ # render the nodelist.
128
+ # for performance reasons we get a array back here. to_s will make a string out of it
129
+ @root.render(context).to_s
130
+ end
131
+
132
+ def render!(*args)
133
+ @rethrow_errors = true; render(*args)
134
+ end
135
+
136
+ private
137
+
138
+ # Uses the <tt>Liquid::TemplateParser</tt> regexp to tokenize the passed source
139
+ def tokenize(source)
140
+ return [] if source.to_s.empty?
141
+ tokens = source.split(TemplateParser)
142
+
143
+ # removes the rogue empty element at the beginning of the array
144
+ tokens.shift if tokens[0] and tokens[0].empty?
145
+
146
+ tokens
147
+ end
148
+
149
+ end
150
+ end
@@ -0,0 +1,39 @@
1
+ module Liquid
2
+
3
+ # Hols variables. Variables are only loaded "just in time"
4
+ # they are not evaluated as part of the render stage
5
+ class Variable
6
+ attr_accessor :filters, :name
7
+
8
+ def initialize(markup)
9
+ @markup = markup
10
+ @name = markup.match(/\s*(#{QuotedFragment})/)[1]
11
+ if markup.match(/#{FilterSperator}\s*(.*)/)
12
+ filters = Regexp.last_match(1).split(/#{FilterSperator}/)
13
+
14
+ @filters = filters.collect do |f|
15
+ filtername = f.match(/\s*(\w+)/)[1]
16
+ filterargs = f.scan(/(?:#{FilterArgumentSeparator}|#{ArgumentSeparator})\s*(#{QuotedFragment})/).flatten
17
+ [filtername.to_sym, filterargs]
18
+ end
19
+ else
20
+ @filters = []
21
+ end
22
+ end
23
+
24
+ def render(context)
25
+ output = context[@name]
26
+ @filters.inject(output) do |output, filter|
27
+ filterargs = filter[1].to_a.collect do |a|
28
+ context[a]
29
+ end
30
+ begin
31
+ output = context.invoke(filter[0], output, *filterargs)
32
+ rescue FilterNotFound
33
+ raise FilterNotFound, "Error - filter '#{filter[0]}' in '#{@markup.strip}' could not be found."
34
+ end
35
+ end
36
+ output
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,50 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ class VariableTest < Test::Unit::TestCase
4
+ include Liquid
5
+
6
+ def test_blankspace
7
+ template = Liquid::Template.parse(" ")
8
+ assert_equal [" "], template.root.nodelist
9
+ end
10
+
11
+ def test_variable_beginning
12
+ template = Liquid::Template.parse("{{funk}} ")
13
+ assert_equal 2, template.root.nodelist.size
14
+ assert_equal Variable, template.root.nodelist[0].class
15
+ assert_equal String, template.root.nodelist[1].class
16
+ end
17
+
18
+ def test_variable_end
19
+ template = Liquid::Template.parse(" {{funk}}")
20
+ assert_equal 2, template.root.nodelist.size
21
+ assert_equal String, template.root.nodelist[0].class
22
+ assert_equal Variable, template.root.nodelist[1].class
23
+ end
24
+
25
+ def test_variable_middle
26
+ template = Liquid::Template.parse(" {{funk}} ")
27
+ assert_equal 3, template.root.nodelist.size
28
+ assert_equal String, template.root.nodelist[0].class
29
+ assert_equal Variable, template.root.nodelist[1].class
30
+ assert_equal String, template.root.nodelist[2].class
31
+ end
32
+
33
+ def test_variable_many_embedded_fragments
34
+ template = Liquid::Template.parse(" {{funk}} {{so}} {{brother}} ")
35
+ assert_equal 7, template.root.nodelist.size
36
+ assert_equal [String, Variable, String, Variable, String, Variable, String], block_types(template.root.nodelist)
37
+ end
38
+
39
+ def test_with_block
40
+ template = Liquid::Template.parse(" {% comment %} {% endcomment %} ")
41
+ assert_equal [String, Comment, String], block_types(template.root.nodelist)
42
+ assert_equal 3, template.root.nodelist.size
43
+ end
44
+
45
+ private
46
+
47
+ def block_types(nodelist)
48
+ nodelist.collect { |node| node.class }
49
+ end
50
+ end
@@ -0,0 +1,340 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+ class HundredCentes
3
+ def to_liquid
4
+ 100
5
+ end
6
+ end
7
+
8
+ class CentsDrop < Liquid::Drop
9
+ def amount
10
+ HundredCentes.new
11
+ end
12
+ end
13
+
14
+ class ContextSensitiveDrop < Liquid::Drop
15
+ def test
16
+ @context['test']
17
+ end
18
+ end
19
+
20
+
21
+ class ContextTest < Test::Unit::TestCase
22
+ include Liquid
23
+
24
+ def setup
25
+ @template = Liquid::Template.new
26
+ @context = Liquid::Context.new(@template)
27
+ end
28
+
29
+ def test_variables
30
+ @context['string'] = 'string'
31
+ assert_equal 'string', @context['string']
32
+
33
+ @context['num'] = 5
34
+ assert_equal 5, @context['num']
35
+
36
+ @context['time'] = Time.parse('2006-06-06 12:00:00')
37
+ assert_equal Time.parse('2006-06-06 12:00:00'), @context['time']
38
+
39
+ @context['date'] = Date.today
40
+ assert_equal Date.today, @context['date']
41
+
42
+ now = DateTime.now
43
+ @context['datetime'] = now
44
+ assert_equal now, @context['datetime']
45
+
46
+ @context['bool'] = true
47
+ assert_equal true, @context['bool']
48
+
49
+ @context['bool'] = false
50
+ assert_equal false, @context['bool']
51
+
52
+ @context['nil'] = nil
53
+ assert_equal nil, @context['nil']
54
+ assert_equal nil, @context['nil']
55
+ end
56
+
57
+ def test_variables_not_existing
58
+ assert_equal nil, @context['does_not_exist']
59
+ end
60
+
61
+ def test_scoping
62
+ assert_nothing_raised do
63
+ @context.push
64
+ @context.pop
65
+ end
66
+
67
+ assert_raise(Liquid::ContextError) do
68
+ @context.pop
69
+ end
70
+
71
+ assert_raise(Liquid::ContextError) do
72
+ @context.push
73
+ @context.pop
74
+ @context.pop
75
+ end
76
+ end
77
+
78
+ def test_length_query
79
+
80
+ @context['numbers'] = [1,2,3,4]
81
+
82
+ assert_equal 4, @context['numbers.size']
83
+
84
+ @context['numbers'] = {1 => 1,2 => 2,3 => 3,4 => 4}
85
+
86
+ assert_equal 4, @context['numbers.size']
87
+
88
+ @context['numbers'] = {1 => 1,2 => 2,3 => 3,4 => 4, 'size' => 1000}
89
+
90
+ assert_equal 1000, @context['numbers.size']
91
+
92
+ end
93
+
94
+ def test_add_filter
95
+
96
+ filter = Module.new do
97
+ def hi(output)
98
+ output + ' hi!'
99
+ end
100
+ end
101
+
102
+ context = Context.new(@template)
103
+ context.add_filters(filter)
104
+ assert_equal 'hi? hi!', context.invoke(:hi, 'hi?')
105
+
106
+ context = Context.new(@template)
107
+ assert_equal 'hi?', context.invoke(:hi, 'hi?')
108
+
109
+ context.add_filters(filter)
110
+ assert_equal 'hi? hi!', context.invoke(:hi, 'hi?')
111
+
112
+ end
113
+
114
+ def test_override_global_filter
115
+ global = Module.new do
116
+ def notice(output)
117
+ "Global #{output}"
118
+ end
119
+ end
120
+
121
+ local = Module.new do
122
+ def notice(output)
123
+ "Local #{output}"
124
+ end
125
+ end
126
+
127
+ Template.register_filter(global)
128
+ assert_equal 'Global test', Template.parse("{{'test' | notice }}").render
129
+ assert_equal 'Local test', Template.parse("{{'test' | notice }}").render({}, :filters => [local])
130
+ end
131
+
132
+ def test_only_intended_filters_make_it_there
133
+
134
+ filter = Module.new do
135
+ def hi(output)
136
+ output + ' hi!'
137
+ end
138
+ end
139
+
140
+ context = Context.new(@template)
141
+ methods = context.strainer.methods
142
+ context.add_filters(filter)
143
+ assert_equal (methods + ['hi']).sort, context.strainer.methods.sort
144
+ end
145
+
146
+ def test_add_item_in_outer_scope
147
+ @context['test'] = 'test'
148
+ @context.push
149
+ assert_equal 'test', @context['test']
150
+ @context.pop
151
+ assert_equal 'test', @context['test']
152
+ end
153
+
154
+ def test_add_item_in_inner_scope
155
+ @context.push
156
+ @context['test'] = 'test'
157
+ assert_equal 'test', @context['test']
158
+ @context.pop
159
+ assert_equal nil, @context['test']
160
+ end
161
+
162
+ def test_hierachical_data
163
+ @context['hash'] = {"name" => 'tobi'}
164
+ assert_equal 'tobi', @context['hash.name']
165
+ end
166
+
167
+ def test_keywords
168
+ assert_equal true, @context['true']
169
+ assert_equal false, @context['false']
170
+ end
171
+
172
+ def test_digits
173
+ assert_equal 100, @context['100']
174
+ assert_equal 100.00, @context['100.00']
175
+ end
176
+
177
+ def test_strings
178
+ assert_equal "hello!", @context['"hello!"']
179
+ assert_equal "hello!", @context["'hello!'"]
180
+ end
181
+
182
+ def test_merge
183
+ @context.merge({ "test" => "test" })
184
+ assert_equal 'test', @context['test']
185
+ @context.merge({ "test" => "newvalue", "foo" => "bar" })
186
+ assert_equal 'newvalue', @context['test']
187
+ assert_equal 'bar', @context['foo']
188
+ end
189
+
190
+ def test_array_notation
191
+ @context['test'] = [1,2,3,4,5]
192
+
193
+ assert_equal 1, @context['test[0]']
194
+ assert_equal 2, @context['test[1]']
195
+ assert_equal 3, @context['test[2]']
196
+ assert_equal 4, @context['test[3]']
197
+ assert_equal 5, @context['test[4]']
198
+ end
199
+
200
+ def test_recoursive_array_notation
201
+ @context['test'] = {'test' => [1,2,3,4,5]}
202
+
203
+ assert_equal 1, @context['test.test[0]']
204
+
205
+ @context['test'] = [{'test' => 'worked'}]
206
+
207
+ assert_equal 'worked', @context['test[0].test']
208
+ end
209
+
210
+ def test_try_first
211
+ @context['test'] = [1,2,3,4,5]
212
+
213
+ assert_equal 1, @context['test.first']
214
+ assert_equal 5, @context['test.last']
215
+
216
+ @context['test'] = {'test' => [1,2,3,4,5]}
217
+
218
+ assert_equal 1, @context['test.test.first']
219
+ assert_equal 5, @context['test.test.last']
220
+
221
+ @context['test'] = [1]
222
+ assert_equal 1, @context['test.first']
223
+ assert_equal 1, @context['test.last']
224
+ end
225
+
226
+ def test_access_hashes_with_hash_notation
227
+
228
+ @context['products'] = {'count' => 5, 'tags' => ['deepsnow', 'freestyle'] }
229
+ @context['product'] = {'variants' => [ {'title' => 'draft151cm'}, {'title' => 'element151cm'} ]}
230
+
231
+
232
+ assert_equal 5, @context['products[count]']
233
+ assert_equal 'deepsnow', @context['products[tags][0]']
234
+ assert_equal 'deepsnow', @context['products[tags].first']
235
+ assert_equal 'draft151cm', @context['product[variants][0][title]']
236
+ assert_equal 'element151cm', @context['product[variants][1][title]']
237
+ assert_equal 'draft151cm', @context['product[variants][0][title]']
238
+ assert_equal 'element151cm', @context['product[variants][last][title]']
239
+ end
240
+
241
+
242
+ def test_first_can_appear_in_middle_of_callchain
243
+
244
+ @context['product'] = {'variants' => [ {'title' => 'draft151cm'}, {'title' => 'element151cm'} ]}
245
+
246
+ assert_equal 'draft151cm', @context['product.variants[0].title']
247
+ assert_equal 'element151cm', @context['product.variants[1].title']
248
+ assert_equal 'draft151cm', @context['product.variants.first.title']
249
+ assert_equal 'element151cm', @context['product.variants.last.title']
250
+
251
+ end
252
+
253
+ def test_cents
254
+ @context.merge( "cents" => HundredCentes.new )
255
+ assert_equal 100, @context['cents']
256
+ end
257
+
258
+ def test_nested_cents
259
+ @context.merge( "cents" => { 'amount' => HundredCentes.new} )
260
+ assert_equal 100, @context['cents.amount']
261
+
262
+ @context.merge( "cents" => { 'cents' => { 'amount' => HundredCentes.new} } )
263
+ assert_equal 100, @context['cents.cents.amount']
264
+ end
265
+
266
+ def test_cents_through_drop
267
+ @context.merge( "cents" => CentsDrop.new )
268
+ assert_equal 100, @context['cents.amount']
269
+ end
270
+
271
+ def test_nested_cents_through_drop
272
+ @context.merge( "vars" => {"cents" => CentsDrop.new} )
273
+ assert_equal 100, @context['vars.cents.amount']
274
+ end
275
+
276
+ def test_context_from_within_drop
277
+ @context.merge( "test" => '123', "vars" => ContextSensitiveDrop.new )
278
+ assert_equal '123', @context['vars.test']
279
+ end
280
+
281
+ def test_nested_context_from_within_drop
282
+ @context.merge( "test" => '123', "vars" => {"local" => ContextSensitiveDrop.new } )
283
+ assert_equal '123', @context['vars.local.test']
284
+ end
285
+
286
+ def test_cents_through_drop_nestedly
287
+ @context.merge( "cents" => {"cents" => CentsDrop.new} )
288
+ assert_equal 100, @context['cents.cents.amount']
289
+
290
+ @context.merge( "cents" => { "cents" => {"cents" => CentsDrop.new}} )
291
+ assert_equal 100, @context['cents.cents.cents.amount']
292
+ end
293
+
294
+ def test_proc_as_variable
295
+ @context['dynamic'] = Proc.new { 'Hello' }
296
+
297
+ assert_equal 'Hello', @context['dynamic']
298
+ end
299
+
300
+ def test_lambda_as_variable
301
+ @context['dynamic'] = lambda { 'Hello' }
302
+
303
+ assert_equal 'Hello', @context['dynamic']
304
+ end
305
+
306
+ def test_nested_lambda_as_variable
307
+ @context['dynamic'] = { "lambda" => lambda { 'Hello' } }
308
+
309
+ assert_equal 'Hello', @context['dynamic.lambda']
310
+ end
311
+
312
+ def test_lambda_is_called_once
313
+ @context['callcount'] = lambda { @global ||= 0; @global += 1; @global.to_s }
314
+
315
+ assert_equal '1', @context['callcount']
316
+ assert_equal '1', @context['callcount']
317
+ assert_equal '1', @context['callcount']
318
+
319
+ @global = nil
320
+ end
321
+
322
+ def test_nested_lambda_is_called_once
323
+ @context['callcount'] = { "lambda" => lambda { @global ||= 0; @global += 1; @global.to_s } }
324
+
325
+ assert_equal '1', @context['callcount.lambda']
326
+ assert_equal '1', @context['callcount.lambda']
327
+ assert_equal '1', @context['callcount.lambda']
328
+
329
+ @global = nil
330
+ end
331
+
332
+ def test_access_to_context_from_proc
333
+ @context.registers[:magic] = 345392
334
+
335
+ @context['magic'] = lambda { @context.registers[:magic] }
336
+
337
+ assert_equal 345392, @context['magic']
338
+ end
339
+
340
+ end