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
@@ -13,13 +13,12 @@ class ContextDrop < Liquid::Drop
|
|
13
13
|
@context['forloop.index']
|
14
14
|
end
|
15
15
|
|
16
|
-
def
|
17
|
-
|
16
|
+
def liquid_method_missing(method)
|
17
|
+
@context[method]
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
21
|
class ProductDrop < Liquid::Drop
|
22
|
-
|
23
22
|
class TextDrop < Liquid::Drop
|
24
23
|
def array
|
25
24
|
['text1', 'text2']
|
@@ -31,8 +30,8 @@ class ProductDrop < Liquid::Drop
|
|
31
30
|
end
|
32
31
|
|
33
32
|
class CatchallDrop < Liquid::Drop
|
34
|
-
def
|
35
|
-
|
33
|
+
def liquid_method_missing(method)
|
34
|
+
'catchall_method: ' << method.to_s
|
36
35
|
end
|
37
36
|
end
|
38
37
|
|
@@ -53,13 +52,14 @@ class ProductDrop < Liquid::Drop
|
|
53
52
|
end
|
54
53
|
|
55
54
|
protected
|
56
|
-
|
57
|
-
|
58
|
-
|
55
|
+
|
56
|
+
def callmenot
|
57
|
+
"protected"
|
58
|
+
end
|
59
59
|
end
|
60
60
|
|
61
61
|
class EnumerableDrop < Liquid::Drop
|
62
|
-
def
|
62
|
+
def liquid_method_missing(method)
|
63
63
|
method
|
64
64
|
end
|
65
65
|
|
@@ -93,7 +93,7 @@ end
|
|
93
93
|
class RealEnumerableDrop < Liquid::Drop
|
94
94
|
include Enumerable
|
95
95
|
|
96
|
-
def
|
96
|
+
def liquid_method_missing(method)
|
97
97
|
method
|
98
98
|
end
|
99
99
|
|
@@ -124,8 +124,10 @@ class DropsTest < Minitest::Test
|
|
124
124
|
def test_rendering_warns_on_tainted_attr
|
125
125
|
with_taint_mode(:warn) do
|
126
126
|
tpl = Liquid::Template.parse('{{ product.user_input }}')
|
127
|
-
|
128
|
-
|
127
|
+
context = Context.new('product' => ProductDrop.new)
|
128
|
+
tpl.render!(context)
|
129
|
+
assert_equal [Liquid::TaintedError], context.warnings.map(&:class)
|
130
|
+
assert_equal "variable 'product.user_input' is tainted and was not escaped", context.warnings.first.to_s(false)
|
129
131
|
end
|
130
132
|
end
|
131
133
|
|
@@ -151,37 +153,37 @@ class DropsTest < Minitest::Test
|
|
151
153
|
end
|
152
154
|
|
153
155
|
def test_text_drop
|
154
|
-
output = Liquid::Template.parse(
|
156
|
+
output = Liquid::Template.parse(' {{ product.texts.text }} ').render!('product' => ProductDrop.new)
|
155
157
|
assert_equal ' text1 ', output
|
156
158
|
end
|
157
159
|
|
158
|
-
def
|
159
|
-
output = Liquid::Template.parse(
|
160
|
-
assert_equal '
|
160
|
+
def test_catchall_unknown_method
|
161
|
+
output = Liquid::Template.parse(' {{ product.catchall.unknown }} ').render!('product' => ProductDrop.new)
|
162
|
+
assert_equal ' catchall_method: unknown ', output
|
161
163
|
end
|
162
164
|
|
163
|
-
def
|
164
|
-
output = Liquid::Template.parse(
|
165
|
-
assert_equal '
|
165
|
+
def test_catchall_integer_argument_drop
|
166
|
+
output = Liquid::Template.parse(' {{ product.catchall[8] }} ').render!('product' => ProductDrop.new)
|
167
|
+
assert_equal ' catchall_method: 8 ', output
|
166
168
|
end
|
167
169
|
|
168
170
|
def test_text_array_drop
|
169
|
-
output = Liquid::Template.parse(
|
171
|
+
output = Liquid::Template.parse('{% for text in product.texts.array %} {{text}} {% endfor %}').render!('product' => ProductDrop.new)
|
170
172
|
assert_equal ' text1 text2 ', output
|
171
173
|
end
|
172
174
|
|
173
175
|
def test_context_drop
|
174
|
-
output = Liquid::Template.parse(
|
176
|
+
output = Liquid::Template.parse(' {{ context.bar }} ').render!('context' => ContextDrop.new, 'bar' => "carrot")
|
175
177
|
assert_equal ' carrot ', output
|
176
178
|
end
|
177
179
|
|
178
180
|
def test_nested_context_drop
|
179
|
-
output = Liquid::Template.parse(
|
181
|
+
output = Liquid::Template.parse(' {{ product.context.foo }} ').render!('product' => ProductDrop.new, 'foo' => "monkey")
|
180
182
|
assert_equal ' monkey ', output
|
181
183
|
end
|
182
184
|
|
183
185
|
def test_protected
|
184
|
-
output = Liquid::Template.parse(
|
186
|
+
output = Liquid::Template.parse(' {{ product.callmenot }} ').render!('product' => ProductDrop.new)
|
185
187
|
assert_equal ' ', output
|
186
188
|
end
|
187
189
|
|
@@ -193,43 +195,43 @@ class DropsTest < Minitest::Test
|
|
193
195
|
end
|
194
196
|
|
195
197
|
def test_scope
|
196
|
-
assert_equal '1', Liquid::Template.parse(
|
197
|
-
assert_equal '2', Liquid::Template.parse(
|
198
|
-
assert_equal '3', Liquid::Template.parse(
|
198
|
+
assert_equal '1', Liquid::Template.parse('{{ context.scopes }}').render!('context' => ContextDrop.new)
|
199
|
+
assert_equal '2', Liquid::Template.parse('{%for i in dummy%}{{ context.scopes }}{%endfor%}').render!('context' => ContextDrop.new, 'dummy' => [1])
|
200
|
+
assert_equal '3', Liquid::Template.parse('{%for i in dummy%}{%for i in dummy%}{{ context.scopes }}{%endfor%}{%endfor%}').render!('context' => ContextDrop.new, 'dummy' => [1])
|
199
201
|
end
|
200
202
|
|
201
203
|
def test_scope_though_proc
|
202
|
-
assert_equal '1', Liquid::Template.parse(
|
203
|
-
assert_equal '2', Liquid::Template.parse(
|
204
|
-
assert_equal '3', Liquid::Template.parse(
|
204
|
+
assert_equal '1', Liquid::Template.parse('{{ s }}').render!('context' => ContextDrop.new, 's' => proc{ |c| c['context.scopes'] })
|
205
|
+
assert_equal '2', Liquid::Template.parse('{%for i in dummy%}{{ s }}{%endfor%}').render!('context' => ContextDrop.new, 's' => proc{ |c| c['context.scopes'] }, 'dummy' => [1])
|
206
|
+
assert_equal '3', Liquid::Template.parse('{%for i in dummy%}{%for i in dummy%}{{ s }}{%endfor%}{%endfor%}').render!('context' => ContextDrop.new, 's' => proc{ |c| c['context.scopes'] }, 'dummy' => [1])
|
205
207
|
end
|
206
208
|
|
207
209
|
def test_scope_with_assigns
|
208
|
-
assert_equal 'variable', Liquid::Template.parse(
|
209
|
-
assert_equal 'variable', Liquid::Template.parse(
|
210
|
-
assert_equal 'test', Liquid::Template.parse(
|
211
|
-
assert_equal 'test', Liquid::Template.parse(
|
210
|
+
assert_equal 'variable', Liquid::Template.parse('{% assign a = "variable"%}{{a}}').render!('context' => ContextDrop.new)
|
211
|
+
assert_equal 'variable', Liquid::Template.parse('{% assign a = "variable"%}{%for i in dummy%}{{a}}{%endfor%}').render!('context' => ContextDrop.new, 'dummy' => [1])
|
212
|
+
assert_equal 'test', Liquid::Template.parse('{% assign header_gif = "test"%}{{header_gif}}').render!('context' => ContextDrop.new)
|
213
|
+
assert_equal 'test', Liquid::Template.parse("{% assign header_gif = 'test'%}{{header_gif}}").render!('context' => ContextDrop.new)
|
212
214
|
end
|
213
215
|
|
214
216
|
def test_scope_from_tags
|
215
|
-
assert_equal '1', Liquid::Template.parse(
|
216
|
-
assert_equal '12', Liquid::Template.parse(
|
217
|
-
assert_equal '123', Liquid::Template.parse(
|
217
|
+
assert_equal '1', Liquid::Template.parse('{% for i in context.scopes_as_array %}{{i}}{% endfor %}').render!('context' => ContextDrop.new, 'dummy' => [1])
|
218
|
+
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])
|
219
|
+
assert_equal '123', Liquid::Template.parse('{%for a in dummy%}{%for a in dummy%}{% for i in context.scopes_as_array %}{{i}}{% endfor %}{% endfor %}{% endfor %}').render!('context' => ContextDrop.new, 'dummy' => [1])
|
218
220
|
end
|
219
221
|
|
220
222
|
def test_access_context_from_drop
|
221
|
-
assert_equal '123', Liquid::Template.parse(
|
223
|
+
assert_equal '123', Liquid::Template.parse('{%for a in dummy%}{{ context.loop_pos }}{% endfor %}').render!('context' => ContextDrop.new, 'dummy' => [1, 2, 3])
|
222
224
|
end
|
223
225
|
|
224
226
|
def test_enumerable_drop
|
225
|
-
assert_equal '123', Liquid::Template.parse(
|
227
|
+
assert_equal '123', Liquid::Template.parse('{% for c in collection %}{{c}}{% endfor %}').render!('collection' => EnumerableDrop.new)
|
226
228
|
end
|
227
229
|
|
228
230
|
def test_enumerable_drop_size
|
229
|
-
assert_equal '3', Liquid::Template.parse(
|
231
|
+
assert_equal '3', Liquid::Template.parse('{{collection.size}}').render!('collection' => EnumerableDrop.new)
|
230
232
|
end
|
231
233
|
|
232
|
-
def
|
234
|
+
def test_enumerable_drop_will_invoke_liquid_method_missing_for_clashing_method_names
|
233
235
|
["select", "each", "map", "cycle"].each do |method|
|
234
236
|
assert_equal method.to_s, Liquid::Template.parse("{{collection.#{method}}}").render!('collection' => EnumerableDrop.new)
|
235
237
|
assert_equal method.to_s, Liquid::Template.parse("{{collection[\"#{method}\"]}}").render!('collection' => EnumerableDrop.new)
|
@@ -1,24 +1,5 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
|
-
class ErrorDrop < Liquid::Drop
|
4
|
-
def standard_error
|
5
|
-
raise Liquid::StandardError, 'standard error'
|
6
|
-
end
|
7
|
-
|
8
|
-
def argument_error
|
9
|
-
raise Liquid::ArgumentError, 'argument error'
|
10
|
-
end
|
11
|
-
|
12
|
-
def syntax_error
|
13
|
-
raise Liquid::SyntaxError, 'syntax error'
|
14
|
-
end
|
15
|
-
|
16
|
-
def exception
|
17
|
-
raise Exception, 'exception'
|
18
|
-
end
|
19
|
-
|
20
|
-
end
|
21
|
-
|
22
3
|
class ErrorHandlingTest < Minitest::Test
|
23
4
|
include Liquid
|
24
5
|
|
@@ -56,7 +37,7 @@ class ErrorHandlingTest < Minitest::Test
|
|
56
37
|
end
|
57
38
|
|
58
39
|
def test_standard_error
|
59
|
-
template = Liquid::Template.parse(
|
40
|
+
template = Liquid::Template.parse(' {{ errors.standard_error }} ')
|
60
41
|
assert_equal ' Liquid error: standard error ', template.render('errors' => ErrorDrop.new)
|
61
42
|
|
62
43
|
assert_equal 1, template.errors.size
|
@@ -64,7 +45,7 @@ class ErrorHandlingTest < Minitest::Test
|
|
64
45
|
end
|
65
46
|
|
66
47
|
def test_syntax
|
67
|
-
template = Liquid::Template.parse(
|
48
|
+
template = Liquid::Template.parse(' {{ errors.syntax_error }} ')
|
68
49
|
assert_equal ' Liquid syntax error: syntax error ', template.render('errors' => ErrorDrop.new)
|
69
50
|
|
70
51
|
assert_equal 1, template.errors.size
|
@@ -72,7 +53,7 @@ class ErrorHandlingTest < Minitest::Test
|
|
72
53
|
end
|
73
54
|
|
74
55
|
def test_argument
|
75
|
-
template = Liquid::Template.parse(
|
56
|
+
template = Liquid::Template.parse(' {{ errors.argument_error }} ')
|
76
57
|
assert_equal ' Liquid error: argument error ', template.render('errors' => ErrorDrop.new)
|
77
58
|
|
78
59
|
assert_equal 1, template.errors.size
|
@@ -94,7 +75,7 @@ class ErrorHandlingTest < Minitest::Test
|
|
94
75
|
end
|
95
76
|
|
96
77
|
def test_lax_unrecognized_operator
|
97
|
-
template = Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ', :
|
78
|
+
template = Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ', error_mode: :lax)
|
98
79
|
assert_equal ' Liquid error: Unknown operator =! ', template.render
|
99
80
|
assert_equal 1, template.errors.size
|
100
81
|
assert_equal Liquid::ArgumentError, template.errors.first.class
|
@@ -102,30 +83,46 @@ class ErrorHandlingTest < Minitest::Test
|
|
102
83
|
|
103
84
|
def test_with_line_numbers_adds_numbers_to_parser_errors
|
104
85
|
err = assert_raises(SyntaxError) do
|
105
|
-
|
86
|
+
Liquid::Template.parse(%q(
|
106
87
|
foobar
|
107
88
|
|
108
89
|
{% "cat" | foobar %}
|
109
90
|
|
110
91
|
bla
|
111
|
-
|
112
|
-
:
|
92
|
+
),
|
93
|
+
line_numbers: true
|
94
|
+
)
|
95
|
+
end
|
96
|
+
|
97
|
+
assert_match(/Liquid syntax error \(line 4\)/, err.message)
|
98
|
+
end
|
99
|
+
|
100
|
+
def test_with_line_numbers_adds_numbers_to_parser_errors_with_whitespace_trim
|
101
|
+
err = assert_raises(SyntaxError) do
|
102
|
+
Liquid::Template.parse(%q(
|
103
|
+
foobar
|
104
|
+
|
105
|
+
{%- "cat" | foobar -%}
|
106
|
+
|
107
|
+
bla
|
108
|
+
),
|
109
|
+
line_numbers: true
|
113
110
|
)
|
114
111
|
end
|
115
112
|
|
116
|
-
assert_match
|
113
|
+
assert_match(/Liquid syntax error \(line 4\)/, err.message)
|
117
114
|
end
|
118
115
|
|
119
116
|
def test_parsing_warn_with_line_numbers_adds_numbers_to_lexer_errors
|
120
|
-
template = Liquid::Template.parse(
|
117
|
+
template = Liquid::Template.parse('
|
121
118
|
foobar
|
122
119
|
|
123
120
|
{% if 1 =! 2 %}ok{% endif %}
|
124
121
|
|
125
122
|
bla
|
126
|
-
|
127
|
-
:
|
128
|
-
:
|
123
|
+
',
|
124
|
+
error_mode: :warn,
|
125
|
+
line_numbers: true
|
129
126
|
)
|
130
127
|
|
131
128
|
assert_equal ['Liquid syntax error (line 4): Unexpected character = in "1 =! 2"'],
|
@@ -134,15 +131,15 @@ class ErrorHandlingTest < Minitest::Test
|
|
134
131
|
|
135
132
|
def test_parsing_strict_with_line_numbers_adds_numbers_to_lexer_errors
|
136
133
|
err = assert_raises(SyntaxError) do
|
137
|
-
Liquid::Template.parse(
|
134
|
+
Liquid::Template.parse('
|
138
135
|
foobar
|
139
136
|
|
140
137
|
{% if 1 =! 2 %}ok{% endif %}
|
141
138
|
|
142
139
|
bla
|
143
|
-
|
144
|
-
:
|
145
|
-
:
|
140
|
+
',
|
141
|
+
error_mode: :strict,
|
142
|
+
line_numbers: true
|
146
143
|
)
|
147
144
|
end
|
148
145
|
|
@@ -151,7 +148,7 @@ class ErrorHandlingTest < Minitest::Test
|
|
151
148
|
|
152
149
|
def test_syntax_errors_in_nested_blocks_have_correct_line_number
|
153
150
|
err = assert_raises(SyntaxError) do
|
154
|
-
Liquid::Template.parse(
|
151
|
+
Liquid::Template.parse('
|
155
152
|
foobar
|
156
153
|
|
157
154
|
{% if 1 != 2 %}
|
@@ -159,8 +156,8 @@ class ErrorHandlingTest < Minitest::Test
|
|
159
156
|
{% endif %}
|
160
157
|
|
161
158
|
bla
|
162
|
-
|
163
|
-
:
|
159
|
+
',
|
160
|
+
line_numbers: true
|
164
161
|
)
|
165
162
|
end
|
166
163
|
|
@@ -169,18 +166,18 @@ class ErrorHandlingTest < Minitest::Test
|
|
169
166
|
|
170
167
|
def test_strict_error_messages
|
171
168
|
err = assert_raises(SyntaxError) do
|
172
|
-
Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ', :
|
169
|
+
Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ', error_mode: :strict)
|
173
170
|
end
|
174
171
|
assert_equal 'Liquid syntax error: Unexpected character = in "1 =! 2"', err.message
|
175
172
|
|
176
173
|
err = assert_raises(SyntaxError) do
|
177
|
-
Liquid::Template.parse('{{%%%}}', :
|
174
|
+
Liquid::Template.parse('{{%%%}}', error_mode: :strict)
|
178
175
|
end
|
179
176
|
assert_equal 'Liquid syntax error: Unexpected character % in "{{%%%}}"', err.message
|
180
177
|
end
|
181
178
|
|
182
179
|
def test_warnings
|
183
|
-
template = Liquid::Template.parse('{% if ~~~ %}{{%%%}}{% else %}{{ hello. }}{% endif %}', :
|
180
|
+
template = Liquid::Template.parse('{% if ~~~ %}{{%%%}}{% else %}{{ hello. }}{% endif %}', error_mode: :warn)
|
184
181
|
assert_equal 3, template.warnings.size
|
185
182
|
assert_equal 'Unexpected character ~ in "~~~"', template.warnings[0].to_s(false)
|
186
183
|
assert_equal 'Unexpected character % in "{{%%%}}"', template.warnings[1].to_s(false)
|
@@ -189,12 +186,12 @@ class ErrorHandlingTest < Minitest::Test
|
|
189
186
|
end
|
190
187
|
|
191
188
|
def test_warning_line_numbers
|
192
|
-
template = Liquid::Template.parse("{% if ~~~ %}\n{{%%%}}{% else %}\n{{ hello. }}{% endif %}", :
|
189
|
+
template = Liquid::Template.parse("{% if ~~~ %}\n{{%%%}}{% else %}\n{{ hello. }}{% endif %}", error_mode: :warn, line_numbers: true)
|
193
190
|
assert_equal 'Liquid syntax error (line 1): Unexpected character ~ in "~~~"', template.warnings[0].message
|
194
191
|
assert_equal 'Liquid syntax error (line 2): Unexpected character % in "{{%%%}}"', template.warnings[1].message
|
195
192
|
assert_equal 'Liquid syntax error (line 3): Expected id but found end_of_string in "{{ hello. }}"', template.warnings[2].message
|
196
193
|
assert_equal 3, template.warnings.size
|
197
|
-
assert_equal [1,2,3], template.warnings.map(&:line_number)
|
194
|
+
assert_equal [1, 2, 3], template.warnings.map(&:line_number)
|
198
195
|
end
|
199
196
|
|
200
197
|
# Liquid should not catch Exceptions that are not subclasses of StandardError, like Interrupt and NoMemoryError
|
@@ -204,4 +201,60 @@ class ErrorHandlingTest < Minitest::Test
|
|
204
201
|
template.render('errors' => ErrorDrop.new)
|
205
202
|
end
|
206
203
|
end
|
204
|
+
|
205
|
+
def test_default_exception_renderer_with_internal_error
|
206
|
+
template = Liquid::Template.parse('This is a runtime error: {{ errors.runtime_error }}', line_numbers: true)
|
207
|
+
|
208
|
+
output = template.render({ 'errors' => ErrorDrop.new })
|
209
|
+
|
210
|
+
assert_equal 'This is a runtime error: Liquid error (line 1): internal', output
|
211
|
+
assert_equal [Liquid::InternalError], template.errors.map(&:class)
|
212
|
+
end
|
213
|
+
|
214
|
+
def test_setting_default_exception_renderer
|
215
|
+
old_exception_renderer = Liquid::Template.default_exception_renderer
|
216
|
+
exceptions = []
|
217
|
+
Liquid::Template.default_exception_renderer = ->(e) { exceptions << e; '' }
|
218
|
+
template = Liquid::Template.parse('This is a runtime error: {{ errors.argument_error }}')
|
219
|
+
|
220
|
+
output = template.render({ 'errors' => ErrorDrop.new })
|
221
|
+
|
222
|
+
assert_equal 'This is a runtime error: ', output
|
223
|
+
assert_equal [Liquid::ArgumentError], template.errors.map(&:class)
|
224
|
+
ensure
|
225
|
+
Liquid::Template.default_exception_renderer = old_exception_renderer if old_exception_renderer
|
226
|
+
end
|
227
|
+
|
228
|
+
def test_exception_renderer_exposing_non_liquid_error
|
229
|
+
template = Liquid::Template.parse('This is a runtime error: {{ errors.runtime_error }}', line_numbers: true)
|
230
|
+
exceptions = []
|
231
|
+
handler = ->(e) { exceptions << e; e.cause }
|
232
|
+
|
233
|
+
output = template.render({ 'errors' => ErrorDrop.new }, exception_renderer: handler)
|
234
|
+
|
235
|
+
assert_equal 'This is a runtime error: runtime error', output
|
236
|
+
assert_equal [Liquid::InternalError], exceptions.map(&:class)
|
237
|
+
assert_equal exceptions, template.errors
|
238
|
+
assert_equal '#<RuntimeError: runtime error>', exceptions.first.cause.inspect
|
239
|
+
end
|
240
|
+
|
241
|
+
class TestFileSystem
|
242
|
+
def read_template_file(template_path)
|
243
|
+
"{{ errors.argument_error }}"
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
def test_included_template_name_with_line_numbers
|
248
|
+
old_file_system = Liquid::Template.file_system
|
249
|
+
|
250
|
+
begin
|
251
|
+
Liquid::Template.file_system = TestFileSystem.new
|
252
|
+
template = Liquid::Template.parse("Argument error:\n{% include 'product' %}", line_numbers: true)
|
253
|
+
page = template.render('errors' => ErrorDrop.new)
|
254
|
+
ensure
|
255
|
+
Liquid::Template.file_system = old_file_system
|
256
|
+
end
|
257
|
+
assert_equal "Argument error:\nLiquid error (product line 1): argument error", page
|
258
|
+
assert_equal "product", template.errors.first.template_name
|
259
|
+
end
|
207
260
|
end
|
@@ -17,7 +17,7 @@ module CanadianMoneyFilter
|
|
17
17
|
end
|
18
18
|
|
19
19
|
module SubstituteFilter
|
20
|
-
def substitute(input, params={})
|
20
|
+
def substitute(input, params = {})
|
21
21
|
input.gsub(/%\{(\w+)\}/) { |match| params[$1] }
|
22
22
|
end
|
23
23
|
end
|
@@ -39,13 +39,13 @@ class FiltersTest < Minitest::Test
|
|
39
39
|
@context['var'] = 1000
|
40
40
|
@context.add_filters(MoneyFilter)
|
41
41
|
|
42
|
-
assert_equal ' 1000$ ',
|
42
|
+
assert_equal ' 1000$ ', Template.parse("{{var | money}}").render(@context)
|
43
43
|
end
|
44
44
|
|
45
45
|
def test_underscore_in_filter_name
|
46
46
|
@context['var'] = 1000
|
47
47
|
@context.add_filters(MoneyFilter)
|
48
|
-
assert_equal ' 1000$ ',
|
48
|
+
assert_equal ' 1000$ ', Template.parse("{{var | money_with_underscore}}").render(@context)
|
49
49
|
end
|
50
50
|
|
51
51
|
def test_second_filter_overwrites_first
|
@@ -53,67 +53,100 @@ class FiltersTest < Minitest::Test
|
|
53
53
|
@context.add_filters(MoneyFilter)
|
54
54
|
@context.add_filters(CanadianMoneyFilter)
|
55
55
|
|
56
|
-
assert_equal ' 1000$ CAD ',
|
56
|
+
assert_equal ' 1000$ CAD ', Template.parse("{{var | money}}").render(@context)
|
57
57
|
end
|
58
58
|
|
59
59
|
def test_size
|
60
60
|
@context['var'] = 'abcd'
|
61
61
|
@context.add_filters(MoneyFilter)
|
62
62
|
|
63
|
-
assert_equal 4,
|
63
|
+
assert_equal '4', Template.parse("{{var | size}}").render(@context)
|
64
64
|
end
|
65
65
|
|
66
66
|
def test_join
|
67
|
-
@context['var'] = [1,2,3,4]
|
67
|
+
@context['var'] = [1, 2, 3, 4]
|
68
68
|
|
69
|
-
assert_equal "1 2 3 4",
|
69
|
+
assert_equal "1 2 3 4", Template.parse("{{var | join}}").render(@context)
|
70
70
|
end
|
71
71
|
|
72
72
|
def test_sort
|
73
73
|
@context['value'] = 3
|
74
|
-
@context['numbers'] = [2,1,4,3]
|
74
|
+
@context['numbers'] = [2, 1, 4, 3]
|
75
75
|
@context['words'] = ['expected', 'as', 'alphabetic']
|
76
76
|
@context['arrays'] = ['flower', 'are']
|
77
|
+
@context['case_sensitive'] = ['sensitive', 'Expected', 'case']
|
77
78
|
|
78
|
-
assert_equal
|
79
|
-
assert_equal
|
80
|
-
assert_equal
|
81
|
-
assert_equal
|
79
|
+
assert_equal '1 2 3 4', Template.parse("{{numbers | sort | join}}").render(@context)
|
80
|
+
assert_equal 'alphabetic as expected', Template.parse("{{words | sort | join}}").render(@context)
|
81
|
+
assert_equal '3', Template.parse("{{value | sort}}").render(@context)
|
82
|
+
assert_equal 'are flower', Template.parse("{{arrays | sort | join}}").render(@context)
|
83
|
+
assert_equal 'Expected case sensitive', Template.parse("{{case_sensitive | sort | join}}").render(@context)
|
84
|
+
end
|
85
|
+
|
86
|
+
def test_sort_natural
|
87
|
+
@context['words'] = ['case', 'Assert', 'Insensitive']
|
88
|
+
@context['hashes'] = [{ 'a' => 'A' }, { 'a' => 'b' }, { 'a' => 'C' }]
|
89
|
+
@context['objects'] = [TestObject.new('A'), TestObject.new('b'), TestObject.new('C')]
|
90
|
+
|
91
|
+
# Test strings
|
92
|
+
assert_equal 'Assert case Insensitive', Template.parse("{{words | sort_natural | join}}").render(@context)
|
93
|
+
|
94
|
+
# Test hashes
|
95
|
+
assert_equal 'A b C', Template.parse("{{hashes | sort_natural: 'a' | map: 'a' | join}}").render(@context)
|
96
|
+
|
97
|
+
# Test objects
|
98
|
+
assert_equal 'A b C', Template.parse("{{objects | sort_natural: 'a' | map: 'a' | join}}").render(@context)
|
99
|
+
end
|
100
|
+
|
101
|
+
def test_compact
|
102
|
+
@context['words'] = ['a', nil, 'b', nil, 'c']
|
103
|
+
@context['hashes'] = [{ 'a' => 'A' }, { 'a' => nil }, { 'a' => 'C' }]
|
104
|
+
@context['objects'] = [TestObject.new('A'), TestObject.new(nil), TestObject.new('C')]
|
105
|
+
|
106
|
+
# Test strings
|
107
|
+
assert_equal 'a b c', Template.parse("{{words | compact | join}}").render(@context)
|
108
|
+
|
109
|
+
# Test hashes
|
110
|
+
assert_equal 'A C', Template.parse("{{hashes | compact: 'a' | map: 'a' | join}}").render(@context)
|
111
|
+
|
112
|
+
# Test objects
|
113
|
+
assert_equal 'A C', Template.parse("{{objects | compact: 'a' | map: 'a' | join}}").render(@context)
|
82
114
|
end
|
83
115
|
|
84
116
|
def test_strip_html
|
85
117
|
@context['var'] = "<b>bla blub</a>"
|
86
118
|
|
87
|
-
assert_equal "bla blub",
|
119
|
+
assert_equal "bla blub", Template.parse("{{ var | strip_html }}").render(@context)
|
88
120
|
end
|
89
121
|
|
90
122
|
def test_strip_html_ignore_comments_with_html
|
91
123
|
@context['var'] = "<!-- split and some <ul> tag --><b>bla blub</a>"
|
92
124
|
|
93
|
-
assert_equal "bla blub",
|
125
|
+
assert_equal "bla blub", Template.parse("{{ var | strip_html }}").render(@context)
|
94
126
|
end
|
95
127
|
|
96
128
|
def test_capitalize
|
97
129
|
@context['var'] = "blub"
|
98
130
|
|
99
|
-
assert_equal "Blub",
|
131
|
+
assert_equal "Blub", Template.parse("{{ var | capitalize }}").render(@context)
|
100
132
|
end
|
101
133
|
|
102
134
|
def test_nonexistent_filter_is_ignored
|
103
135
|
@context['var'] = 1000
|
104
136
|
|
105
|
-
assert_equal 1000,
|
137
|
+
assert_equal '1000', Template.parse("{{ var | xyzzy }}").render(@context)
|
106
138
|
end
|
107
139
|
|
108
140
|
def test_filter_with_keyword_arguments
|
109
141
|
@context['surname'] = 'john'
|
142
|
+
@context['input'] = 'hello %{first_name}, %{last_name}'
|
110
143
|
@context.add_filters(SubstituteFilter)
|
111
|
-
output =
|
144
|
+
output = Template.parse(%({{ input | substitute: first_name: surname, last_name: 'doe' }})).render(@context)
|
112
145
|
assert_equal 'hello john, doe', output
|
113
146
|
end
|
114
147
|
|
115
148
|
def test_override_object_method_in_filter
|
116
|
-
assert_equal "tap overridden", Template.parse("{{var | tap}}").render!({ 'var' => 1000 }, :
|
149
|
+
assert_equal "tap overridden", Template.parse("{{var | tap}}").render!({ 'var' => 1000 }, filters: [OverrideObjectMethodFilter])
|
117
150
|
|
118
151
|
# tap still treated as a non-existent filter
|
119
152
|
assert_equal "1000", Template.parse("{{var | tap}}").render!({ 'var' => 1000 })
|
@@ -126,8 +159,8 @@ class FiltersInTemplate < Minitest::Test
|
|
126
159
|
def test_local_global
|
127
160
|
with_global_filter(MoneyFilter) do
|
128
161
|
assert_equal " 1000$ ", Template.parse("{{1000 | money}}").render!(nil, nil)
|
129
|
-
assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, :
|
130
|
-
assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, :
|
162
|
+
assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, filters: CanadianMoneyFilter)
|
163
|
+
assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, filters: [CanadianMoneyFilter])
|
131
164
|
end
|
132
165
|
end
|
133
166
|
|
@@ -136,3 +169,10 @@ class FiltersInTemplate < Minitest::Test
|
|
136
169
|
assert_equal " 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, [CanadianMoneyFilter])
|
137
170
|
end
|
138
171
|
end # FiltersTest
|
172
|
+
|
173
|
+
class TestObject < Liquid::Drop
|
174
|
+
attr_accessor :a
|
175
|
+
def initialize(a)
|
176
|
+
@a = a
|
177
|
+
end
|
178
|
+
end
|
@@ -1,18 +1,18 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
class HashOrderingTest < Minitest::Test
|
4
|
+
module MoneyFilter
|
5
|
+
def money(input)
|
6
|
+
sprintf(' %d$ ', input)
|
7
|
+
end
|
6
8
|
end
|
7
|
-
end
|
8
9
|
|
9
|
-
module CanadianMoneyFilter
|
10
|
-
|
11
|
-
|
10
|
+
module CanadianMoneyFilter
|
11
|
+
def money(input)
|
12
|
+
sprintf(' %d$ CAD ', input)
|
13
|
+
end
|
12
14
|
end
|
13
|
-
end
|
14
15
|
|
15
|
-
class HashOrderingTest < Minitest::Test
|
16
16
|
include Liquid
|
17
17
|
|
18
18
|
def test_global_register_order
|