liquid 3.0.6 → 4.0.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.
- checksums.yaml +5 -5
- data/History.md +154 -58
- data/{MIT-LICENSE → LICENSE} +0 -0
- data/README.md +33 -0
- data/lib/liquid/block.rb +42 -125
- data/lib/liquid/block_body.rb +99 -79
- data/lib/liquid/condition.rb +52 -32
- data/lib/liquid/context.rb +57 -51
- data/lib/liquid/document.rb +19 -9
- data/lib/liquid/drop.rb +17 -16
- data/lib/liquid/errors.rb +20 -24
- data/lib/liquid/expression.rb +26 -10
- data/lib/liquid/extensions.rb +19 -7
- data/lib/liquid/file_system.rb +11 -11
- data/lib/liquid/forloop_drop.rb +42 -0
- data/lib/liquid/i18n.rb +6 -6
- data/lib/liquid/interrupts.rb +1 -2
- data/lib/liquid/lexer.rb +12 -8
- data/lib/liquid/locales/en.yml +6 -2
- data/lib/liquid/parse_context.rb +38 -0
- data/lib/liquid/parse_tree_visitor.rb +42 -0
- data/lib/liquid/parser_switching.rb +4 -4
- data/lib/liquid/profiler/hooks.rb +7 -7
- data/lib/liquid/profiler.rb +18 -19
- data/lib/liquid/range_lookup.rb +16 -1
- data/lib/liquid/resource_limits.rb +23 -0
- data/lib/liquid/standardfilters.rb +207 -61
- data/lib/liquid/strainer.rb +15 -8
- data/lib/liquid/tablerowloop_drop.rb +62 -0
- data/lib/liquid/tag.rb +9 -8
- data/lib/liquid/tags/assign.rb +25 -4
- data/lib/liquid/tags/break.rb +0 -3
- data/lib/liquid/tags/capture.rb +1 -1
- data/lib/liquid/tags/case.rb +27 -12
- data/lib/liquid/tags/comment.rb +2 -2
- data/lib/liquid/tags/cycle.rb +16 -8
- data/lib/liquid/tags/decrement.rb +1 -4
- data/lib/liquid/tags/for.rb +103 -75
- data/lib/liquid/tags/if.rb +60 -44
- data/lib/liquid/tags/ifchanged.rb +0 -2
- data/lib/liquid/tags/include.rb +71 -51
- data/lib/liquid/tags/raw.rb +32 -4
- data/lib/liquid/tags/table_row.rb +21 -31
- data/lib/liquid/tags/unless.rb +3 -4
- data/lib/liquid/template.rb +42 -54
- data/lib/liquid/tokenizer.rb +31 -0
- data/lib/liquid/truffle.rb +5 -0
- data/lib/liquid/utils.rb +52 -8
- data/lib/liquid/variable.rb +59 -46
- data/lib/liquid/variable_lookup.rb +14 -6
- data/lib/liquid/version.rb +2 -1
- data/lib/liquid.rb +10 -7
- data/test/integration/assign_test.rb +8 -8
- data/test/integration/blank_test.rb +14 -14
- data/test/integration/block_test.rb +12 -0
- data/test/integration/context_test.rb +2 -2
- data/test/integration/document_test.rb +19 -0
- data/test/integration/drop_test.rb +42 -40
- data/test/integration/error_handling_test.rb +96 -43
- data/test/integration/filter_test.rb +60 -20
- data/test/integration/hash_ordering_test.rb +9 -9
- data/test/integration/output_test.rb +26 -27
- data/test/integration/parse_tree_visitor_test.rb +247 -0
- data/test/integration/parsing_quirks_test.rb +19 -13
- data/test/integration/render_profiling_test.rb +20 -20
- data/test/integration/security_test.rb +23 -7
- data/test/integration/standard_filter_test.rb +426 -46
- data/test/integration/tags/break_tag_test.rb +1 -2
- data/test/integration/tags/continue_tag_test.rb +0 -1
- data/test/integration/tags/for_tag_test.rb +135 -100
- data/test/integration/tags/if_else_tag_test.rb +75 -77
- data/test/integration/tags/include_tag_test.rb +50 -31
- data/test/integration/tags/increment_tag_test.rb +10 -11
- data/test/integration/tags/raw_tag_test.rb +7 -1
- data/test/integration/tags/standard_tag_test.rb +121 -122
- data/test/integration/tags/statements_test.rb +3 -5
- data/test/integration/tags/table_row_test.rb +20 -19
- data/test/integration/tags/unless_else_tag_test.rb +6 -6
- data/test/integration/template_test.rb +199 -49
- data/test/integration/trim_mode_test.rb +529 -0
- data/test/integration/variable_test.rb +27 -13
- data/test/test_helper.rb +33 -6
- data/test/truffle/truffle_test.rb +9 -0
- data/test/unit/block_unit_test.rb +8 -5
- data/test/unit/condition_unit_test.rb +94 -77
- data/test/unit/context_unit_test.rb +69 -72
- data/test/unit/file_system_unit_test.rb +3 -3
- data/test/unit/i18n_unit_test.rb +2 -2
- data/test/unit/lexer_unit_test.rb +12 -9
- data/test/unit/parser_unit_test.rb +2 -2
- data/test/unit/regexp_unit_test.rb +1 -1
- data/test/unit/strainer_unit_test.rb +96 -1
- data/test/unit/tag_unit_test.rb +7 -2
- data/test/unit/tags/case_tag_unit_test.rb +1 -1
- data/test/unit/tags/for_tag_unit_test.rb +2 -2
- data/test/unit/tags/if_tag_unit_test.rb +1 -1
- data/test/unit/template_unit_test.rb +14 -5
- data/test/unit/tokenizer_unit_test.rb +24 -7
- data/test/unit/variable_unit_test.rb +60 -43
- metadata +62 -50
- data/lib/liquid/module_ex.rb +0 -62
- data/lib/liquid/token.rb +0 -18
- data/test/unit/module_ex_unit_test.rb +0 -87
@@ -1,7 +1,8 @@
|
|
1
1
|
require 'test_helper'
|
2
|
+
require 'timeout'
|
2
3
|
|
3
4
|
class TemplateContextDrop < Liquid::Drop
|
4
|
-
def
|
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'] =
|
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' =>
|
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 =
|
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 =
|
92
|
-
assert_equal "Liquid error: Memory limits exceeded", t.render
|
93
|
-
assert t.resource_limits
|
94
|
-
|
95
|
-
|
96
|
-
|
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 =
|
102
|
-
assert_equal "Liquid error: Memory limits exceeded", t.render
|
103
|
-
assert t.resource_limits
|
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 =
|
106
|
-
assert_equal "Liquid error: Memory limits exceeded", t.render
|
107
|
-
assert t.resource_limits
|
108
|
-
|
109
|
-
|
110
|
-
|
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 =
|
116
|
-
assert_equal "Liquid error: Memory limits exceeded", t.render
|
117
|
-
assert t.resource_limits
|
118
|
-
|
119
|
-
|
120
|
-
|
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 =
|
126
|
-
assert_equal "Liquid error: Memory limits exceeded", t.render
|
127
|
-
assert t.resource_limits
|
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
|
134
|
-
assert t.resource_limits
|
135
|
-
assert t.resource_limits
|
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
|
143
|
-
assert context.resource_limits
|
144
|
-
assert context.resource_limits
|
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,118 @@ class TemplateTest < Minitest::Test
|
|
166
215
|
assert_equal 'ruby error in drop', e.message
|
167
216
|
end
|
168
217
|
|
169
|
-
def
|
218
|
+
def test_exception_renderer_that_returns_string
|
170
219
|
exception = nil
|
171
|
-
|
172
|
-
|
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
|
228
|
+
def test_exception_renderer_that_raises
|
176
229
|
exception = nil
|
177
|
-
assert_raises(ZeroDivisionError) do
|
178
|
-
Template.parse("{{ 1 | divided_by: 0 }}").render({},
|
230
|
+
assert_raises(Liquid::ZeroDivisionError) do
|
231
|
+
Template.parse("{{ 1 | divided_by: 0 }}").render({}, exception_renderer: ->(e) { exception = e; raise })
|
179
232
|
end
|
180
|
-
assert exception.is_a?(ZeroDivisionError)
|
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_nil_value_does_not_raise
|
265
|
+
Liquid::Template.error_mode = :strict
|
266
|
+
t = Template.parse("some{{x}}thing")
|
267
|
+
result = t.render!({ 'x' => nil }, strict_variables: true)
|
268
|
+
|
269
|
+
assert_equal 0, t.errors.count
|
270
|
+
assert_equal 'something', result
|
271
|
+
end
|
272
|
+
|
273
|
+
def test_undefined_variables_raise
|
274
|
+
t = Template.parse("{{x}} {{y}} {{z.a}} {{z.b}} {{z.c.d}}")
|
275
|
+
|
276
|
+
assert_raises UndefinedVariable do
|
277
|
+
t.render!({ 'x' => 33, 'z' => { 'a' => 32, 'c' => { 'e' => 31 } } }, { strict_variables: true })
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
def test_undefined_drop_methods
|
282
|
+
d = DropWithUndefinedMethod.new
|
283
|
+
t = Template.new.parse('{{ foo }} {{ woot }}')
|
284
|
+
result = t.render(d, { strict_variables: true })
|
285
|
+
|
286
|
+
assert_equal 'foo ', result
|
287
|
+
assert_equal 1, t.errors.count
|
288
|
+
assert_instance_of Liquid::UndefinedDropMethod, t.errors[0]
|
289
|
+
end
|
290
|
+
|
291
|
+
def test_undefined_drop_methods_raise
|
292
|
+
d = DropWithUndefinedMethod.new
|
293
|
+
t = Template.new.parse('{{ foo }} {{ woot }}')
|
294
|
+
|
295
|
+
assert_raises UndefinedDropMethod do
|
296
|
+
t.render!(d, { strict_variables: true })
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
def test_undefined_filters
|
301
|
+
t = Template.parse("{{a}} {{x | upcase | somefilter1 | somefilter2 | somefilter3}}")
|
302
|
+
filters = Module.new do
|
303
|
+
def somefilter3(v)
|
304
|
+
"-#{v}-"
|
305
|
+
end
|
306
|
+
end
|
307
|
+
result = t.render({ 'a' => 123, 'x' => 'foo' }, { filters: [filters], strict_filters: true })
|
308
|
+
|
309
|
+
assert_equal '123 ', result
|
310
|
+
assert_equal 1, t.errors.count
|
311
|
+
assert_instance_of Liquid::UndefinedFilter, t.errors[0]
|
312
|
+
assert_equal 'Liquid error: undefined filter somefilter1', t.errors[0].message
|
313
|
+
end
|
314
|
+
|
315
|
+
def test_undefined_filters_raise
|
316
|
+
t = Template.parse("{{x | somefilter1 | upcase | somefilter2}}")
|
317
|
+
|
318
|
+
assert_raises UndefinedFilter do
|
319
|
+
t.render!({ 'x' => 'foo' }, { strict_filters: true })
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
def test_using_range_literal_works_as_expected
|
324
|
+
t = Template.parse("{% assign foo = (x..y) %}{{ foo }}")
|
325
|
+
result = t.render({ 'x' => 1, 'y' => 5 })
|
326
|
+
assert_equal '1..5', result
|
327
|
+
|
328
|
+
t = Template.parse("{% assign nums = (x..y) %}{% for num in nums %}{{ num }}{% endfor %}")
|
329
|
+
result = t.render({ 'x' => 1, 'y' => 5 })
|
330
|
+
assert_equal '12345', result
|
181
331
|
end
|
182
332
|
end
|