liquid 1.7.0 → 1.9.0

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 (46) hide show
  1. data/CHANGELOG +17 -15
  2. data/History.txt +44 -0
  3. data/MIT-LICENSE +2 -2
  4. data/Manifest.txt +6 -1
  5. data/{README → README.txt} +0 -0
  6. data/Rakefile +3 -3
  7. data/init.rb +5 -3
  8. data/lib/liquid.rb +8 -6
  9. data/lib/liquid/block.rb +6 -9
  10. data/lib/liquid/condition.rb +49 -17
  11. data/lib/liquid/context.rb +67 -41
  12. data/lib/liquid/errors.rb +8 -5
  13. data/lib/liquid/htmltags.rb +17 -7
  14. data/lib/liquid/module_ex.rb +62 -0
  15. data/lib/liquid/standardfilters.rb +39 -0
  16. data/lib/liquid/strainer.rb +20 -11
  17. data/lib/liquid/tag.rb +4 -3
  18. data/lib/liquid/tags/assign.rb +15 -4
  19. data/lib/liquid/tags/capture.rb +15 -2
  20. data/lib/liquid/tags/case.rb +51 -36
  21. data/lib/liquid/tags/cycle.rb +16 -2
  22. data/lib/liquid/tags/for.rb +45 -8
  23. data/lib/liquid/tags/if.rb +35 -7
  24. data/lib/liquid/tags/include.rb +2 -3
  25. data/lib/liquid/tags/unless.rb +6 -2
  26. data/lib/liquid/template.rb +13 -18
  27. data/lib/liquid/variable.rb +25 -12
  28. data/test/block_test.rb +8 -0
  29. data/test/condition_test.rb +109 -0
  30. data/test/context_test.rb +88 -10
  31. data/test/drop_test.rb +3 -1
  32. data/test/error_handling_test.rb +16 -3
  33. data/test/extra/breakpoint.rb +0 -0
  34. data/test/extra/caller.rb +0 -0
  35. data/test/filter_test.rb +3 -3
  36. data/test/html_tag_test.rb +7 -0
  37. data/test/if_else_test.rb +32 -0
  38. data/test/include_tag_test.rb +24 -1
  39. data/test/module_ex_test.rb +89 -0
  40. data/test/parsing_quirks_test.rb +15 -0
  41. data/test/regexp_test.rb +4 -3
  42. data/test/standard_filter_test.rb +27 -2
  43. data/test/standard_tag_test.rb +67 -20
  44. data/test/test_helper.rb +20 -0
  45. data/test/unless_else_test.rb +8 -0
  46. metadata +60 -46
data/test/context_test.rb CHANGED
@@ -9,6 +9,10 @@ class CentsDrop < Liquid::Drop
9
9
  def amount
10
10
  HundredCentes.new
11
11
  end
12
+
13
+ def non_zero?
14
+ true
15
+ end
12
16
  end
13
17
 
14
18
  class ContextSensitiveDrop < Liquid::Drop
@@ -17,13 +21,32 @@ class ContextSensitiveDrop < Liquid::Drop
17
21
  end
18
22
  end
19
23
 
24
+ class Category < Liquid::Drop
25
+ attr_accessor :name
26
+
27
+ def initialize(name)
28
+ @name = name
29
+ end
30
+
31
+ def to_liquid
32
+ CategoryDrop.new(self)
33
+ end
34
+ end
35
+
36
+ class CategoryDrop
37
+ attr_accessor :category, :context
38
+ def initialize(category)
39
+ @category = category
40
+ end
41
+ end
42
+
20
43
 
21
44
  class ContextTest < Test::Unit::TestCase
22
45
  include Liquid
23
46
 
24
47
  def setup
25
48
  @template = Liquid::Template.new
26
- @context = Liquid::Context.new(@template)
49
+ @context = Liquid::Context.new(@template.assigns, @template.registers)
27
50
  end
28
51
 
29
52
  def test_variables
@@ -53,7 +76,7 @@ class ContextTest < Test::Unit::TestCase
53
76
  assert_equal nil, @context['nil']
54
77
  assert_equal nil, @context['nil']
55
78
  end
56
-
79
+
57
80
  def test_variables_not_existing
58
81
  assert_equal nil, @context['does_not_exist']
59
82
  end
@@ -89,6 +112,13 @@ class ContextTest < Test::Unit::TestCase
89
112
 
90
113
  assert_equal 1000, @context['numbers.size']
91
114
 
115
+ end
116
+
117
+ def test_hyphenated_variable
118
+
119
+ @context['oh-my'] = 'godz'
120
+ assert_equal 'godz', @context['oh-my']
121
+
92
122
  end
93
123
 
94
124
  def test_add_filter
@@ -207,6 +237,18 @@ class ContextTest < Test::Unit::TestCase
207
237
  assert_equal 'worked', @context['test[0].test']
208
238
  end
209
239
 
240
+ def test_hash_to_array_transition
241
+ @context['colors'] = {
242
+ 'Blue' => ['003366','336699', '6699CC', '99CCFF'],
243
+ 'Green' => ['003300','336633', '669966', '99CC99'],
244
+ 'Yellow' => ['CC9900','FFCC00', 'FFFF99', 'FFFFCC'],
245
+ 'Red' => ['660000','993333', 'CC6666', 'FF9999']
246
+ }
247
+
248
+ assert_equal '003366', @context['colors.Blue[0]']
249
+ assert_equal 'FF9999', @context['colors.Red[3]']
250
+ end
251
+
210
252
  def test_try_first
211
253
  @context['test'] = [1,2,3,4,5]
212
254
 
@@ -229,13 +271,31 @@ class ContextTest < Test::Unit::TestCase
229
271
  @context['product'] = {'variants' => [ {'title' => 'draft151cm'}, {'title' => 'element151cm'} ]}
230
272
 
231
273
 
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]']
274
+ assert_equal 5, @context['products["count"]']
275
+ assert_equal 'deepsnow', @context['products["tags"][0]']
276
+ assert_equal 'deepsnow', @context['products["tags"].first']
277
+ assert_equal 'draft151cm', @context['product["variants"][0]["title"]']
278
+ assert_equal 'element151cm', @context['product["variants"][1]["title"]']
279
+ assert_equal 'draft151cm', @context['product["variants"][0]["title"]']
280
+ assert_equal 'element151cm', @context['product["variants"].last["title"]']
281
+ end
282
+
283
+ def test_access_variable_with_hash_notation
284
+ @context['foo'] = 'baz'
285
+ @context['bar'] = 'foo'
286
+
287
+ assert_equal 'baz', @context['["foo"]']
288
+ assert_equal 'baz', @context['[bar]']
289
+ end
290
+
291
+ def test_access_hashes_with_hash_access_variables
292
+
293
+ @context['var'] = 'tags'
294
+ @context['nested'] = {'var' => 'tags'}
295
+ @context['products'] = {'count' => 5, 'tags' => ['deepsnow', 'freestyle'] }
296
+
297
+ assert_equal 'deepsnow', @context['products[var].first']
298
+ assert_equal 'freestyle', @context['products[nested.var].last']
239
299
  end
240
300
 
241
301
 
@@ -267,12 +327,17 @@ class ContextTest < Test::Unit::TestCase
267
327
  @context.merge( "cents" => CentsDrop.new )
268
328
  assert_equal 100, @context['cents.amount']
269
329
  end
270
-
330
+
271
331
  def test_nested_cents_through_drop
272
332
  @context.merge( "vars" => {"cents" => CentsDrop.new} )
273
333
  assert_equal 100, @context['vars.cents.amount']
274
334
  end
275
335
 
336
+ def test_drop_methods_with_question_marks
337
+ @context.merge( "cents" => CentsDrop.new )
338
+ assert @context['cents.non_zero?']
339
+ end
340
+
276
341
  def test_context_from_within_drop
277
342
  @context.merge( "test" => '123', "vars" => ContextSensitiveDrop.new )
278
343
  assert_equal '123', @context['vars.test']
@@ -282,6 +347,13 @@ class ContextTest < Test::Unit::TestCase
282
347
  @context.merge( "test" => '123', "vars" => {"local" => ContextSensitiveDrop.new } )
283
348
  assert_equal '123', @context['vars.local.test']
284
349
  end
350
+
351
+ def test_ranges
352
+ @context.merge( "test" => '5' )
353
+ assert_equal (1..5), @context['(1..5)']
354
+ assert_equal (1..5), @context['(1..test)']
355
+ assert_equal (5..5), @context['(test..test)']
356
+ end
285
357
 
286
358
  def test_cents_through_drop_nestedly
287
359
  @context.merge( "cents" => {"cents" => CentsDrop.new} )
@@ -337,4 +409,10 @@ class ContextTest < Test::Unit::TestCase
337
409
  assert_equal 345392, @context['magic']
338
410
  end
339
411
 
412
+ def test_to_liquid_and_context_at_first_level
413
+ @context['category'] = Category.new("foobar")
414
+ assert_kind_of CategoryDrop, @context['category']
415
+ assert_equal @context, @context['category'].context
416
+ end
417
+
340
418
  end
data/test/drop_test.rb CHANGED
@@ -120,8 +120,10 @@ class DropsTest < Test::Unit::TestCase
120
120
  def test_scope_with_assigns
121
121
  assert_equal 'variable', Liquid::Template.parse( '{% assign a = "variable"%}{{a}}' ).render('context' => ContextDrop.new)
122
122
  assert_equal 'variable', Liquid::Template.parse( '{% assign a = "variable"%}{%for i in dummy%}{{a}}{%endfor%}' ).render('context' => ContextDrop.new, 'dummy' => [1])
123
+ assert_equal 'test', Liquid::Template.parse( '{% assign header_gif = "test"%}{{header_gif}}' ).render('context' => ContextDrop.new)
124
+ assert_equal 'test', Liquid::Template.parse( "{% assign header_gif = 'test'%}{{header_gif}}" ).render('context' => ContextDrop.new)
123
125
  end
124
-
126
+
125
127
  def test_scope_from_tags
126
128
  assert_equal '1', Liquid::Template.parse( '{% for i in context.scopes_as_array %}{{i}}{% endfor %}' ).render('context' => ContextDrop.new, 'dummy' => [1])
127
129
  assert_equal '12', Liquid::Template.parse( '{%for a in dummy%}{% for i in context.scopes_as_array %}{{i}}{% endfor %}{% endfor %}' ).render('context' => ContextDrop.new, 'dummy' => [1])
@@ -4,15 +4,15 @@ require File.dirname(__FILE__) + '/helper'
4
4
 
5
5
  class ErrorDrop < Liquid::Drop
6
6
  def standard_error
7
- raise StandardError, 'standard error'
7
+ raise Liquid::StandardError, 'standard error'
8
8
  end
9
9
 
10
10
  def argument_error
11
- raise ArgumentError, 'argument error'
11
+ raise Liquid::ArgumentError, 'argument error'
12
12
  end
13
13
 
14
14
  def syntax_error
15
- raise SyntaxError, 'syntax error'
15
+ raise Liquid::SyntaxError, 'syntax error'
16
16
  end
17
17
 
18
18
  end
@@ -59,6 +59,19 @@ class ErrorHandlingTest < Test::Unit::TestCase
59
59
 
60
60
  end
61
61
 
62
+ def test_unrecognized_operator
63
+
64
+ assert_nothing_raised do
65
+
66
+ template = Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ')
67
+ assert_equal ' Liquid error: Unknown operator =! ', template.render
68
+
69
+ assert_equal 1, template.errors.size
70
+ assert_equal Liquid::ArgumentError, template.errors.first.class
71
+
72
+ end
73
+
74
+ end
62
75
 
63
76
  end
64
77
 
File without changes
data/test/extra/caller.rb CHANGED
File without changes
data/test/filter_test.rb CHANGED
@@ -23,7 +23,7 @@ class FiltersTest < Test::Unit::TestCase
23
23
  include Liquid
24
24
 
25
25
  def setup
26
- @context = Context.new(Liquid::Template.new)
26
+ @context = Context.new
27
27
  end
28
28
 
29
29
  def test_local_filter
@@ -44,9 +44,9 @@ class FiltersTest < Test::Unit::TestCase
44
44
  @context.add_filters(CanadianMoneyFilter)
45
45
  assert_equal ' 1000$ CAD ', Variable.new("var | money").render(@context)
46
46
  end
47
-
47
+
48
48
  def test_size
49
- @context['var'] = 1000
49
+ @context['var'] = 'abcd'
50
50
  @context.add_filters(MoneyFilter)
51
51
  assert_equal 4, Variable.new("var | size").render(@context)
52
52
  end
@@ -21,4 +21,11 @@ class HtmlTagTest < Test::Unit::TestCase
21
21
 
22
22
  end
23
23
 
24
+ def test_html_col_counter
25
+ assert_template_result("<tr class=\"row1\">\n<td class=\"col1\">1</td><td class=\"col2\">2</td></tr>\n<tr class=\"row2\"><td class=\"col1\">1</td><td class=\"col2\">2</td></tr>\n<tr class=\"row3\"><td class=\"col1\">1</td><td class=\"col2\">2</td></tr>\n",
26
+ '{% tablerow n in numbers cols:2%}{{tablerowloop.col}}{% endtablerow %}',
27
+ 'numbers' => [1,2,3,4,5,6])
28
+
29
+ end
30
+
24
31
  end
data/test/if_else_test.rb CHANGED
@@ -18,8 +18,31 @@ class IfElseTest < Test::Unit::TestCase
18
18
 
19
19
  def test_if_boolean
20
20
  assert_template_result(' YES ','{% if var %} YES {% endif %}', 'var' => true)
21
+ end
22
+
23
+ def test_if_or
24
+ assert_template_result(' YES ','{% if a or b %} YES {% endif %}', 'a' => true, 'b' => true)
25
+ assert_template_result(' YES ','{% if a or b %} YES {% endif %}', 'a' => true, 'b' => false)
26
+ assert_template_result(' YES ','{% if a or b %} YES {% endif %}', 'a' => false, 'b' => true)
27
+ assert_template_result('', '{% if a or b %} YES {% endif %}', 'a' => false, 'b' => false)
28
+
29
+ assert_template_result(' YES ','{% if a or b or c %} YES {% endif %}', 'a' => false, 'b' => false, 'c' => true)
30
+ assert_template_result('', '{% if a or b or c %} YES {% endif %}', 'a' => false, 'b' => false, 'c' => false)
31
+ end
32
+
33
+ def test_if_or_with_operators
34
+ assert_template_result(' YES ','{% if a == true or b == true %} YES {% endif %}', 'a' => true, 'b' => true)
35
+ assert_template_result(' YES ','{% if a == true or b == false %} YES {% endif %}', 'a' => true, 'b' => true)
36
+ assert_template_result('','{% if a == false or b == false %} YES {% endif %}', 'a' => true, 'b' => true)
37
+ end
38
+
39
+ def test_if_and
40
+ assert_template_result(' YES ','{% if true and true %} YES {% endif %}')
41
+ assert_template_result('','{% if false and true %} YES {% endif %}')
42
+ assert_template_result('','{% if false and true %} YES {% endif %}')
21
43
  end
22
44
 
45
+
23
46
  def test_hash_miss_generates_false
24
47
  assert_template_result('','{% if foo.bar %} NO {% endif %}', 'foo' => {})
25
48
  end
@@ -92,4 +115,13 @@ class IfElseTest < Test::Unit::TestCase
92
115
  def test_syntax_error_no_variable
93
116
  assert_raise(SyntaxError){ assert_template_result('', '{% if jerry == 1 %}')}
94
117
  end
118
+
119
+ def test_if_with_custom_condition
120
+ Condition.operators['contains'] = :[]
121
+
122
+ assert_template_result('yes', %({% if 'bob' contains 'o' %}yes{% endif %}))
123
+ assert_template_result('no', %({% if 'bob' contains 'f' %}yes{% else %}no{% endif %}))
124
+ ensure
125
+ Condition.operators.delete 'contains'
126
+ end
95
127
  end
@@ -20,6 +20,9 @@ class TestFileSystem
20
20
 
21
21
  when "nested_product_template"
22
22
  "Product: {{ nested_product_template.title }} {%include 'details'%} "
23
+
24
+ when "recursively_nested_template"
25
+ "-{% include 'recursively_nested_template' %}"
23
26
 
24
27
  else
25
28
  template_path
@@ -40,6 +43,11 @@ class IncludeTagTest < Test::Unit::TestCase
40
43
  Template.parse("{% include 'product' with products[0] %}").render( "products" => [ {'title' => 'Draft 151cm'}, {'title' => 'Element 155cm'} ] )
41
44
  end
42
45
 
46
+ def test_include_tag_with_default_name
47
+ assert_equal "Product: Draft 151cm ",
48
+ Template.parse("{% include 'product' %}").render( "product" => {'title' => 'Draft 151cm'} )
49
+ end
50
+
43
51
  def test_include_tag_for
44
52
 
45
53
  assert_equal "Product: Draft 151cm Product: Element 155cm ",
@@ -78,7 +86,22 @@ class IncludeTagTest < Test::Unit::TestCase
78
86
  Template.parse("{% include 'nested_product_template' for products %}").render("products" => [{"title" => 'Draft 151cm'}, {"title" => 'Element 155cm'}])
79
87
 
80
88
  end
81
-
89
+
90
+ def test_recursively_included_template_does_not_produce_endless_loop
91
+
92
+ infinite_file_system = Class.new do
93
+ def read_template_file(template_path)
94
+ "-{% include 'loop' %}"
95
+ end
96
+ end
97
+
98
+ Liquid::Template.file_system = infinite_file_system.new
99
+
100
+ assert_match /-{552}Liquid error: stack level too deep$/,
101
+ Template.parse("{% include 'loop' %}").render
102
+
103
+ end
104
+
82
105
  def test_dynamically_choosen_template
83
106
 
84
107
  assert_equal "Test123", Template.parse("{% include template %}").render("template" => 'Test123')
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env ruby
2
+ require File.dirname(__FILE__) + '/helper'
3
+
4
+ class TestClassA
5
+ liquid_methods :allowedA, :chainedB
6
+ def allowedA
7
+ 'allowedA'
8
+ end
9
+ def restrictedA
10
+ 'restrictedA'
11
+ end
12
+ def chainedB
13
+ TestClassB.new
14
+ end
15
+ end
16
+
17
+ class TestClassB
18
+ liquid_methods :allowedB, :chainedC
19
+ def allowedB
20
+ 'allowedB'
21
+ end
22
+ def chainedC
23
+ TestClassC.new
24
+ end
25
+ end
26
+
27
+ class TestClassC
28
+ liquid_methods :allowedC
29
+ def allowedC
30
+ 'allowedC'
31
+ end
32
+ end
33
+
34
+ class TestClassC::LiquidDropClass
35
+ def another_allowedC
36
+ 'another_allowedC'
37
+ end
38
+ end
39
+
40
+ class ModuleExTest < Test::Unit::TestCase
41
+ include Liquid
42
+
43
+ def setup
44
+ @a = TestClassA.new
45
+ @b = TestClassB.new
46
+ @c = TestClassC.new
47
+ end
48
+
49
+ def test_should_create_LiquidDropClass
50
+ assert TestClassA::LiquidDropClass
51
+ assert TestClassB::LiquidDropClass
52
+ assert TestClassC::LiquidDropClass
53
+ end
54
+
55
+ def test_should_respond_to_liquid
56
+ assert @a.respond_to?(:to_liquid)
57
+ assert @b.respond_to?(:to_liquid)
58
+ assert @c.respond_to?(:to_liquid)
59
+ end
60
+
61
+ def test_should_return_LiquidDropClass_object
62
+ assert @a.to_liquid.is_a?(TestClassA::LiquidDropClass)
63
+ assert @b.to_liquid.is_a?(TestClassB::LiquidDropClass)
64
+ assert @c.to_liquid.is_a?(TestClassC::LiquidDropClass)
65
+ end
66
+
67
+ def test_should_respond_to_liquid_methods
68
+ assert @a.to_liquid.respond_to?(:allowedA)
69
+ assert @a.to_liquid.respond_to?(:chainedB)
70
+ assert @b.to_liquid.respond_to?(:allowedB)
71
+ assert @b.to_liquid.respond_to?(:chainedC)
72
+ assert @c.to_liquid.respond_to?(:allowedC)
73
+ assert @c.to_liquid.respond_to?(:another_allowedC)
74
+ end
75
+
76
+ def test_should_not_respond_to_restricted_methods
77
+ assert ! @a.to_liquid.respond_to?(:restricted)
78
+ end
79
+
80
+ def test_should_use_regular_objects_as_drops
81
+ assert_equal 'allowedA', Liquid::Template.parse("{{ a.allowedA }}").render('a'=>@a)
82
+ assert_equal 'allowedB', Liquid::Template.parse("{{ a.chainedB.allowedB }}").render('a'=>@a)
83
+ assert_equal 'allowedC', Liquid::Template.parse("{{ a.chainedB.chainedC.allowedC }}").render('a'=>@a)
84
+ assert_equal 'another_allowedC', Liquid::Template.parse("{{ a.chainedB.chainedC.another_allowedC }}").render('a'=>@a)
85
+ assert_equal '', Liquid::Template.parse("{{ a.restricted }}").render('a'=>@a)
86
+ assert_equal '', Liquid::Template.parse("{{ a.unknown }}").render('a'=>@a)
87
+ end
88
+
89
+ end
@@ -11,4 +11,19 @@ class ParsingQuirksTest < Test::Unit::TestCase
11
11
  assert_equal text, template.render
12
12
  assert_equal [String], template.root.nodelist.collect {|i| i.class}
13
13
  end
14
+
15
+ def test_raise_on_single_close_bracet
16
+ assert_raise(SyntaxError) do
17
+ Template.parse("text {{method} oh nos!")
18
+ end
19
+ end
20
+
21
+
22
+ def test_error_on_empty_filter
23
+ assert_nothing_raised do
24
+ Template.parse("{{test |a|b|}}")
25
+ Template.parse("{{test}}")
26
+ Template.parse("{{|test|}}")
27
+ end
28
+ end
14
29
  end