liquid 3.0.6 → 4.0.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 (98) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +98 -58
  3. data/README.md +31 -0
  4. data/lib/liquid/block.rb +31 -124
  5. data/lib/liquid/block_body.rb +75 -59
  6. data/lib/liquid/condition.rb +23 -22
  7. data/lib/liquid/context.rb +50 -46
  8. data/lib/liquid/document.rb +19 -9
  9. data/lib/liquid/drop.rb +17 -16
  10. data/lib/liquid/errors.rb +20 -24
  11. data/lib/liquid/expression.rb +15 -3
  12. data/lib/liquid/extensions.rb +13 -7
  13. data/lib/liquid/file_system.rb +11 -11
  14. data/lib/liquid/forloop_drop.rb +42 -0
  15. data/lib/liquid/i18n.rb +5 -5
  16. data/lib/liquid/interrupts.rb +1 -2
  17. data/lib/liquid/lexer.rb +6 -4
  18. data/lib/liquid/locales/en.yml +5 -1
  19. data/lib/liquid/parse_context.rb +37 -0
  20. data/lib/liquid/parser_switching.rb +4 -4
  21. data/lib/liquid/profiler/hooks.rb +7 -7
  22. data/lib/liquid/profiler.rb +18 -19
  23. data/lib/liquid/range_lookup.rb +16 -1
  24. data/lib/liquid/resource_limits.rb +23 -0
  25. data/lib/liquid/standardfilters.rb +121 -61
  26. data/lib/liquid/strainer.rb +14 -7
  27. data/lib/liquid/tablerowloop_drop.rb +62 -0
  28. data/lib/liquid/tag.rb +9 -8
  29. data/lib/liquid/tags/assign.rb +17 -4
  30. data/lib/liquid/tags/break.rb +0 -3
  31. data/lib/liquid/tags/capture.rb +1 -1
  32. data/lib/liquid/tags/case.rb +19 -12
  33. data/lib/liquid/tags/comment.rb +2 -2
  34. data/lib/liquid/tags/cycle.rb +6 -6
  35. data/lib/liquid/tags/decrement.rb +1 -4
  36. data/lib/liquid/tags/for.rb +95 -75
  37. data/lib/liquid/tags/if.rb +49 -44
  38. data/lib/liquid/tags/ifchanged.rb +0 -2
  39. data/lib/liquid/tags/include.rb +61 -52
  40. data/lib/liquid/tags/raw.rb +32 -4
  41. data/lib/liquid/tags/table_row.rb +12 -30
  42. data/lib/liquid/tags/unless.rb +3 -4
  43. data/lib/liquid/template.rb +42 -54
  44. data/lib/liquid/tokenizer.rb +31 -0
  45. data/lib/liquid/utils.rb +52 -8
  46. data/lib/liquid/variable.rb +46 -45
  47. data/lib/liquid/variable_lookup.rb +7 -5
  48. data/lib/liquid/version.rb +1 -1
  49. data/lib/liquid.rb +9 -7
  50. data/test/integration/assign_test.rb +8 -8
  51. data/test/integration/blank_test.rb +14 -14
  52. data/test/integration/context_test.rb +2 -2
  53. data/test/integration/document_test.rb +19 -0
  54. data/test/integration/drop_test.rb +42 -40
  55. data/test/integration/error_handling_test.rb +99 -46
  56. data/test/integration/filter_test.rb +60 -20
  57. data/test/integration/hash_ordering_test.rb +9 -9
  58. data/test/integration/output_test.rb +26 -27
  59. data/test/integration/parsing_quirks_test.rb +15 -13
  60. data/test/integration/render_profiling_test.rb +20 -20
  61. data/test/integration/security_test.rb +9 -7
  62. data/test/integration/standard_filter_test.rb +179 -40
  63. data/test/integration/tags/break_tag_test.rb +1 -2
  64. data/test/integration/tags/continue_tag_test.rb +0 -1
  65. data/test/integration/tags/for_tag_test.rb +133 -98
  66. data/test/integration/tags/if_else_tag_test.rb +75 -77
  67. data/test/integration/tags/include_tag_test.rb +34 -30
  68. data/test/integration/tags/increment_tag_test.rb +10 -11
  69. data/test/integration/tags/raw_tag_test.rb +7 -1
  70. data/test/integration/tags/standard_tag_test.rb +121 -122
  71. data/test/integration/tags/statements_test.rb +3 -5
  72. data/test/integration/tags/table_row_test.rb +20 -19
  73. data/test/integration/tags/unless_else_tag_test.rb +6 -6
  74. data/test/integration/template_test.rb +190 -49
  75. data/test/integration/trim_mode_test.rb +525 -0
  76. data/test/integration/variable_test.rb +23 -13
  77. data/test/test_helper.rb +33 -5
  78. data/test/unit/block_unit_test.rb +8 -5
  79. data/test/unit/condition_unit_test.rb +86 -77
  80. data/test/unit/context_unit_test.rb +48 -57
  81. data/test/unit/file_system_unit_test.rb +3 -3
  82. data/test/unit/i18n_unit_test.rb +2 -2
  83. data/test/unit/lexer_unit_test.rb +11 -8
  84. data/test/unit/parser_unit_test.rb +2 -2
  85. data/test/unit/regexp_unit_test.rb +1 -1
  86. data/test/unit/strainer_unit_test.rb +80 -1
  87. data/test/unit/tag_unit_test.rb +7 -2
  88. data/test/unit/tags/case_tag_unit_test.rb +1 -1
  89. data/test/unit/tags/for_tag_unit_test.rb +2 -2
  90. data/test/unit/tags/if_tag_unit_test.rb +1 -1
  91. data/test/unit/template_unit_test.rb +14 -5
  92. data/test/unit/tokenizer_unit_test.rb +24 -7
  93. data/test/unit/variable_unit_test.rb +60 -43
  94. metadata +19 -14
  95. data/lib/liquid/module_ex.rb +0 -62
  96. data/lib/liquid/token.rb +0 -18
  97. data/test/unit/module_ex_unit_test.rb +0 -87
  98. /data/{MIT-LICENSE → LICENSE} +0 -0
@@ -1,7 +1,8 @@
1
1
  require 'test_helper'
2
+ require 'timeout'
2
3
 
3
4
  class TemplateContextDrop < Liquid::Drop
4
- def before_method(method)
5
+ def liquid_method_missing(method)
5
6
  method
6
7
  end
7
8
 
@@ -14,12 +15,10 @@ class TemplateContextDrop < Liquid::Drop
14
15
  end
15
16
  end
16
17
 
17
- class SomethingWithLength
18
+ class SomethingWithLength < Liquid::Drop
18
19
  def length
19
20
  nil
20
21
  end
21
-
22
- liquid_methods :length
23
22
  end
24
23
 
25
24
  class ErroneousDrop < Liquid::Drop
@@ -28,6 +27,12 @@ class ErroneousDrop < Liquid::Drop
28
27
  end
29
28
  end
30
29
 
30
+ class DropWithUndefinedMethod < Liquid::Drop
31
+ def foo
32
+ 'foo'
33
+ end
34
+ end
35
+
31
36
  class TemplateTest < Minitest::Test
32
37
  include Liquid
33
38
 
@@ -37,6 +42,16 @@ class TemplateTest < Minitest::Test
37
42
  assert_equal 'from instance assigns', t.parse("{{ foo }}").render!
38
43
  end
39
44
 
45
+ def test_warnings_is_not_exponential_time
46
+ str = "false"
47
+ 100.times do
48
+ str = "{% if true %}true{% else %}#{str}{% endif %}"
49
+ end
50
+
51
+ t = Template.parse(str)
52
+ assert_equal [], Timeout.timeout(1) { t.warnings }
53
+ end
54
+
40
55
  def test_instance_assigns_persist_on_same_template_parsing_between_renders
41
56
  t = Template.new.parse("{{ foo }}{% assign foo = 'foo' %}{{ foo }}")
42
57
  assert_equal 'foo', t.render!
@@ -64,7 +79,7 @@ class TemplateTest < Minitest::Test
64
79
 
65
80
  def test_lambda_is_called_once_from_persistent_assigns_over_multiple_parses_and_renders
66
81
  t = Template.new
67
- t.assigns['number'] = lambda { @global ||= 0; @global += 1 }
82
+ t.assigns['number'] = -> { @global ||= 0; @global += 1 }
68
83
  assert_equal '1', t.parse("{{number}}").render!
69
84
  assert_equal '1', t.parse("{{number}}").render!
70
85
  assert_equal '1', t.render!
@@ -73,7 +88,7 @@ class TemplateTest < Minitest::Test
73
88
 
74
89
  def test_lambda_is_called_once_from_custom_assigns_over_multiple_parses_and_renders
75
90
  t = Template.new
76
- assigns = {'number' => lambda { @global ||= 0; @global += 1 }}
91
+ assigns = { 'number' => -> { @global ||= 0; @global += 1 } }
77
92
  assert_equal '1', t.parse("{{number}}").render!(assigns)
78
93
  assert_equal '1', t.parse("{{number}}").render!(assigns)
79
94
  assert_equal '1', t.render!(assigns)
@@ -82,69 +97,103 @@ class TemplateTest < Minitest::Test
82
97
 
83
98
  def test_resource_limits_works_with_custom_length_method
84
99
  t = Template.parse("{% assign foo = bar %}")
85
- t.resource_limits = { :render_length_limit => 42 }
100
+ t.resource_limits.render_length_limit = 42
86
101
  assert_equal "", t.render!("bar" => SomethingWithLength.new)
87
102
  end
88
103
 
89
104
  def test_resource_limits_render_length
90
105
  t = Template.parse("0123456789")
91
- t.resource_limits = { :render_length_limit => 5 }
92
- assert_equal "Liquid error: Memory limits exceeded", t.render()
93
- assert t.resource_limits[:reached]
94
- t.resource_limits = { :render_length_limit => 10 }
95
- assert_equal "0123456789", t.render!()
96
- refute_nil t.resource_limits[:render_length_current]
106
+ t.resource_limits.render_length_limit = 5
107
+ assert_equal "Liquid error: Memory limits exceeded", t.render
108
+ assert t.resource_limits.reached?
109
+
110
+ t.resource_limits.render_length_limit = 10
111
+ assert_equal "0123456789", t.render!
112
+ refute_nil t.resource_limits.render_length
97
113
  end
98
114
 
99
115
  def test_resource_limits_render_score
100
116
  t = Template.parse("{% for a in (1..10) %} {% for a in (1..10) %} foo {% endfor %} {% endfor %}")
101
- t.resource_limits = { :render_score_limit => 50 }
102
- assert_equal "Liquid error: Memory limits exceeded", t.render()
103
- assert t.resource_limits[:reached]
117
+ t.resource_limits.render_score_limit = 50
118
+ assert_equal "Liquid error: Memory limits exceeded", t.render
119
+ assert t.resource_limits.reached?
120
+
104
121
  t = Template.parse("{% for a in (1..100) %} foo {% endfor %}")
105
- t.resource_limits = { :render_score_limit => 50 }
106
- assert_equal "Liquid error: Memory limits exceeded", t.render()
107
- assert t.resource_limits[:reached]
108
- t.resource_limits = { :render_score_limit => 200 }
109
- assert_equal (" foo " * 100), t.render!()
110
- refute_nil t.resource_limits[:render_score_current]
122
+ t.resource_limits.render_score_limit = 50
123
+ assert_equal "Liquid error: Memory limits exceeded", t.render
124
+ assert t.resource_limits.reached?
125
+
126
+ t.resource_limits.render_score_limit = 200
127
+ assert_equal (" foo " * 100), t.render!
128
+ refute_nil t.resource_limits.render_score
111
129
  end
112
130
 
113
131
  def test_resource_limits_assign_score
114
132
  t = Template.parse("{% assign foo = 42 %}{% assign bar = 23 %}")
115
- t.resource_limits = { :assign_score_limit => 1 }
116
- assert_equal "Liquid error: Memory limits exceeded", t.render()
117
- assert t.resource_limits[:reached]
118
- t.resource_limits = { :assign_score_limit => 2 }
119
- assert_equal "", t.render!()
120
- refute_nil t.resource_limits[:assign_score_current]
133
+ t.resource_limits.assign_score_limit = 1
134
+ assert_equal "Liquid error: Memory limits exceeded", t.render
135
+ assert t.resource_limits.reached?
136
+
137
+ t.resource_limits.assign_score_limit = 2
138
+ assert_equal "", t.render!
139
+ refute_nil t.resource_limits.assign_score
140
+ end
141
+
142
+ def test_resource_limits_assign_score_nested
143
+ t = Template.parse("{% assign foo = 'aaaa' | reverse %}")
144
+
145
+ t.resource_limits.assign_score_limit = 3
146
+ assert_equal "Liquid error: Memory limits exceeded", t.render
147
+ assert t.resource_limits.reached?
148
+
149
+ t.resource_limits.assign_score_limit = 5
150
+ assert_equal "", t.render!
121
151
  end
122
152
 
123
153
  def test_resource_limits_aborts_rendering_after_first_error
124
154
  t = Template.parse("{% for a in (1..100) %} foo1 {% endfor %} bar {% for a in (1..100) %} foo2 {% endfor %}")
125
- t.resource_limits = { :render_score_limit => 50 }
126
- assert_equal "Liquid error: Memory limits exceeded", t.render()
127
- assert t.resource_limits[:reached]
155
+ t.resource_limits.render_score_limit = 50
156
+ assert_equal "Liquid error: Memory limits exceeded", t.render
157
+ assert t.resource_limits.reached?
128
158
  end
129
159
 
130
160
  def test_resource_limits_hash_in_template_gets_updated_even_if_no_limits_are_set
131
161
  t = Template.parse("{% for a in (1..100) %} {% assign foo = 1 %} {% endfor %}")
132
- t.render!()
133
- assert t.resource_limits[:assign_score_current] > 0
134
- assert t.resource_limits[:render_score_current] > 0
135
- assert t.resource_limits[:render_length_current] > 0
162
+ t.render!
163
+ assert t.resource_limits.assign_score > 0
164
+ assert t.resource_limits.render_score > 0
165
+ assert t.resource_limits.render_length > 0
166
+ end
167
+
168
+ def test_render_length_persists_between_blocks
169
+ t = Template.parse("{% if true %}aaaa{% endif %}")
170
+ t.resource_limits.render_length_limit = 7
171
+ assert_equal "Liquid error: Memory limits exceeded", t.render
172
+ t.resource_limits.render_length_limit = 8
173
+ assert_equal "aaaa", t.render
174
+
175
+ t = Template.parse("{% if true %}aaaa{% endif %}{% if true %}bbb{% endif %}")
176
+ t.resource_limits.render_length_limit = 13
177
+ assert_equal "Liquid error: Memory limits exceeded", t.render
178
+ t.resource_limits.render_length_limit = 14
179
+ assert_equal "aaaabbb", t.render
180
+
181
+ t = Template.parse("{% if true %}a{% endif %}{% if true %}b{% endif %}{% if true %}a{% endif %}{% if true %}b{% endif %}{% if true %}a{% endif %}{% if true %}b{% endif %}")
182
+ t.resource_limits.render_length_limit = 5
183
+ assert_equal "Liquid error: Memory limits exceeded", t.render
184
+ t.resource_limits.render_length_limit = 11
185
+ assert_equal "Liquid error: Memory limits exceeded", t.render
186
+ t.resource_limits.render_length_limit = 12
187
+ assert_equal "ababab", t.render
136
188
  end
137
189
 
138
190
  def test_default_resource_limits_unaffected_by_render_with_context
139
191
  context = Context.new
140
192
  t = Template.parse("{% for a in (1..100) %} {% assign foo = 1 %} {% endfor %}")
141
193
  t.render!(context)
142
- assert context.resource_limits[:assign_score_current] > 0
143
- assert context.resource_limits[:render_score_current] > 0
144
- assert context.resource_limits[:render_length_current] > 0
145
- refute Template.default_resource_limits.key?(:assign_score_current)
146
- refute Template.default_resource_limits.key?(:render_score_current)
147
- refute Template.default_resource_limits.key?(:render_length_current)
194
+ assert context.resource_limits.assign_score > 0
195
+ assert context.resource_limits.render_score > 0
196
+ assert context.resource_limits.render_length > 0
148
197
  end
149
198
 
150
199
  def test_can_use_drop_as_context
@@ -157,7 +206,7 @@ class TemplateTest < Minitest::Test
157
206
  end
158
207
 
159
208
  def test_render_bang_force_rethrow_errors_on_passed_context
160
- context = Context.new({'drop' => ErroneousDrop.new})
209
+ context = Context.new({ 'drop' => ErroneousDrop.new })
161
210
  t = Template.new.parse('{{ drop.bad_method }}')
162
211
 
163
212
  e = assert_raises RuntimeError do
@@ -166,17 +215,109 @@ class TemplateTest < Minitest::Test
166
215
  assert_equal 'ruby error in drop', e.message
167
216
  end
168
217
 
169
- def test_exception_handler_doesnt_reraise_if_it_returns_false
218
+ def test_exception_renderer_that_returns_string
170
219
  exception = nil
171
- Template.parse("{{ 1 | divided_by: 0 }}").render({}, exception_handler: ->(e) { exception = e; false })
172
- assert exception.is_a?(ZeroDivisionError)
220
+ handler = ->(e) { exception = e; '<!-- error -->' }
221
+
222
+ output = Template.parse("{{ 1 | divided_by: 0 }}").render({}, exception_renderer: handler)
223
+
224
+ assert exception.is_a?(Liquid::ZeroDivisionError)
225
+ assert_equal '<!-- error -->', output
173
226
  end
174
227
 
175
- def test_exception_handler_does_reraise_if_it_returns_true
228
+ def test_exception_renderer_that_raises
176
229
  exception = nil
177
- assert_raises(ZeroDivisionError) do
178
- Template.parse("{{ 1 | divided_by: 0 }}").render({}, exception_handler: ->(e) { exception = e; true })
230
+ assert_raises(Liquid::ZeroDivisionError) do
231
+ Template.parse("{{ 1 | divided_by: 0 }}").render({}, exception_renderer: ->(e) { exception = e; raise })
232
+ end
233
+ assert exception.is_a?(Liquid::ZeroDivisionError)
234
+ end
235
+
236
+ def test_global_filter_option_on_render
237
+ global_filter_proc = ->(output) { "#{output} filtered" }
238
+ rendered_template = Template.parse("{{name}}").render({ "name" => "bob" }, global_filter: global_filter_proc)
239
+
240
+ assert_equal 'bob filtered', rendered_template
241
+ end
242
+
243
+ def test_global_filter_option_when_native_filters_exist
244
+ global_filter_proc = ->(output) { "#{output} filtered" }
245
+ rendered_template = Template.parse("{{name | upcase}}").render({ "name" => "bob" }, global_filter: global_filter_proc)
246
+
247
+ assert_equal 'BOB filtered', rendered_template
248
+ end
249
+
250
+ def test_undefined_variables
251
+ t = Template.parse("{{x}} {{y}} {{z.a}} {{z.b}} {{z.c.d}}")
252
+ result = t.render({ 'x' => 33, 'z' => { 'a' => 32, 'c' => { 'e' => 31 } } }, { strict_variables: true })
253
+
254
+ assert_equal '33 32 ', result
255
+ assert_equal 3, t.errors.count
256
+ assert_instance_of Liquid::UndefinedVariable, t.errors[0]
257
+ assert_equal 'Liquid error: undefined variable y', t.errors[0].message
258
+ assert_instance_of Liquid::UndefinedVariable, t.errors[1]
259
+ assert_equal 'Liquid error: undefined variable b', t.errors[1].message
260
+ assert_instance_of Liquid::UndefinedVariable, t.errors[2]
261
+ assert_equal 'Liquid error: undefined variable d', t.errors[2].message
262
+ end
263
+
264
+ def test_undefined_variables_raise
265
+ t = Template.parse("{{x}} {{y}} {{z.a}} {{z.b}} {{z.c.d}}")
266
+
267
+ assert_raises UndefinedVariable do
268
+ t.render!({ 'x' => 33, 'z' => { 'a' => 32, 'c' => { 'e' => 31 } } }, { strict_variables: true })
179
269
  end
180
- assert exception.is_a?(ZeroDivisionError)
270
+ end
271
+
272
+ def test_undefined_drop_methods
273
+ d = DropWithUndefinedMethod.new
274
+ t = Template.new.parse('{{ foo }} {{ woot }}')
275
+ result = t.render(d, { strict_variables: true })
276
+
277
+ assert_equal 'foo ', result
278
+ assert_equal 1, t.errors.count
279
+ assert_instance_of Liquid::UndefinedDropMethod, t.errors[0]
280
+ end
281
+
282
+ def test_undefined_drop_methods_raise
283
+ d = DropWithUndefinedMethod.new
284
+ t = Template.new.parse('{{ foo }} {{ woot }}')
285
+
286
+ assert_raises UndefinedDropMethod do
287
+ t.render!(d, { strict_variables: true })
288
+ end
289
+ end
290
+
291
+ def test_undefined_filters
292
+ t = Template.parse("{{a}} {{x | upcase | somefilter1 | somefilter2 | somefilter3}}")
293
+ filters = Module.new do
294
+ def somefilter3(v)
295
+ "-#{v}-"
296
+ end
297
+ end
298
+ result = t.render({ 'a' => 123, 'x' => 'foo' }, { filters: [filters], strict_filters: true })
299
+
300
+ assert_equal '123 ', result
301
+ assert_equal 1, t.errors.count
302
+ assert_instance_of Liquid::UndefinedFilter, t.errors[0]
303
+ assert_equal 'Liquid error: undefined filter somefilter1', t.errors[0].message
304
+ end
305
+
306
+ def test_undefined_filters_raise
307
+ t = Template.parse("{{x | somefilter1 | upcase | somefilter2}}")
308
+
309
+ assert_raises UndefinedFilter do
310
+ t.render!({ 'x' => 'foo' }, { strict_filters: true })
311
+ end
312
+ end
313
+
314
+ def test_using_range_literal_works_as_expected
315
+ t = Template.parse("{% assign foo = (x..y) %}{{ foo }}")
316
+ result = t.render({ 'x' => 1, 'y' => 5 })
317
+ assert_equal '1..5', result
318
+
319
+ t = Template.parse("{% assign nums = (x..y) %}{% for num in nums %}{{ num }}{% endfor %}")
320
+ result = t.render({ 'x' => 1, 'y' => 5 })
321
+ assert_equal '12345', result
181
322
  end
182
323
  end