liquid 3.0.0 → 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 (100) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +130 -62
  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 +51 -60
  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.rb +1 -1
  21. data/lib/liquid/parser_switching.rb +4 -4
  22. data/lib/liquid/profiler/hooks.rb +7 -7
  23. data/lib/liquid/profiler.rb +18 -19
  24. data/lib/liquid/range_lookup.rb +16 -1
  25. data/lib/liquid/resource_limits.rb +23 -0
  26. data/lib/liquid/standardfilters.rb +121 -61
  27. data/lib/liquid/strainer.rb +32 -29
  28. data/lib/liquid/tablerowloop_drop.rb +62 -0
  29. data/lib/liquid/tag.rb +9 -8
  30. data/lib/liquid/tags/assign.rb +17 -4
  31. data/lib/liquid/tags/break.rb +0 -3
  32. data/lib/liquid/tags/capture.rb +2 -2
  33. data/lib/liquid/tags/case.rb +19 -12
  34. data/lib/liquid/tags/comment.rb +2 -2
  35. data/lib/liquid/tags/cycle.rb +6 -6
  36. data/lib/liquid/tags/decrement.rb +1 -4
  37. data/lib/liquid/tags/for.rb +95 -75
  38. data/lib/liquid/tags/if.rb +48 -43
  39. data/lib/liquid/tags/ifchanged.rb +0 -2
  40. data/lib/liquid/tags/include.rb +61 -52
  41. data/lib/liquid/tags/raw.rb +32 -4
  42. data/lib/liquid/tags/table_row.rb +12 -31
  43. data/lib/liquid/tags/unless.rb +4 -5
  44. data/lib/liquid/template.rb +42 -54
  45. data/lib/liquid/tokenizer.rb +31 -0
  46. data/lib/liquid/utils.rb +52 -8
  47. data/lib/liquid/variable.rb +46 -45
  48. data/lib/liquid/variable_lookup.rb +9 -5
  49. data/lib/liquid/version.rb +1 -1
  50. data/lib/liquid.rb +9 -7
  51. data/test/integration/assign_test.rb +18 -8
  52. data/test/integration/blank_test.rb +14 -14
  53. data/test/integration/capture_test.rb +10 -0
  54. data/test/integration/context_test.rb +2 -2
  55. data/test/integration/document_test.rb +19 -0
  56. data/test/integration/drop_test.rb +42 -40
  57. data/test/integration/error_handling_test.rb +99 -46
  58. data/test/integration/filter_test.rb +72 -19
  59. data/test/integration/hash_ordering_test.rb +9 -9
  60. data/test/integration/output_test.rb +34 -27
  61. data/test/integration/parsing_quirks_test.rb +15 -13
  62. data/test/integration/render_profiling_test.rb +20 -20
  63. data/test/integration/security_test.rb +9 -7
  64. data/test/integration/standard_filter_test.rb +198 -42
  65. data/test/integration/tags/break_tag_test.rb +1 -2
  66. data/test/integration/tags/continue_tag_test.rb +0 -1
  67. data/test/integration/tags/for_tag_test.rb +133 -98
  68. data/test/integration/tags/if_else_tag_test.rb +96 -77
  69. data/test/integration/tags/include_tag_test.rb +34 -30
  70. data/test/integration/tags/increment_tag_test.rb +10 -11
  71. data/test/integration/tags/raw_tag_test.rb +7 -1
  72. data/test/integration/tags/standard_tag_test.rb +121 -122
  73. data/test/integration/tags/statements_test.rb +3 -5
  74. data/test/integration/tags/table_row_test.rb +20 -19
  75. data/test/integration/tags/unless_else_tag_test.rb +6 -6
  76. data/test/integration/template_test.rb +190 -49
  77. data/test/integration/trim_mode_test.rb +525 -0
  78. data/test/integration/variable_test.rb +23 -13
  79. data/test/test_helper.rb +44 -9
  80. data/test/unit/block_unit_test.rb +8 -5
  81. data/test/unit/condition_unit_test.rb +86 -77
  82. data/test/unit/context_unit_test.rb +48 -57
  83. data/test/unit/file_system_unit_test.rb +3 -3
  84. data/test/unit/i18n_unit_test.rb +2 -2
  85. data/test/unit/lexer_unit_test.rb +11 -8
  86. data/test/unit/parser_unit_test.rb +2 -2
  87. data/test/unit/regexp_unit_test.rb +1 -1
  88. data/test/unit/strainer_unit_test.rb +85 -8
  89. data/test/unit/tag_unit_test.rb +7 -2
  90. data/test/unit/tags/case_tag_unit_test.rb +1 -1
  91. data/test/unit/tags/for_tag_unit_test.rb +2 -2
  92. data/test/unit/tags/if_tag_unit_test.rb +1 -1
  93. data/test/unit/template_unit_test.rb +14 -5
  94. data/test/unit/tokenizer_unit_test.rb +24 -7
  95. data/test/unit/variable_unit_test.rb +66 -43
  96. metadata +55 -50
  97. data/lib/liquid/module_ex.rb +0 -62
  98. data/lib/liquid/token.rb +0 -18
  99. data/test/unit/module_ex_unit_test.rb +0 -87
  100. /data/{MIT-LICENSE → LICENSE} +0 -0
@@ -3,26 +3,36 @@ require 'test_helper'
3
3
  class AssignTest < Minitest::Test
4
4
  include Liquid
5
5
 
6
+ def test_assign_with_hyphen_in_variable_name
7
+ template_source = <<-END_TEMPLATE
8
+ {% assign this-thing = 'Print this-thing' %}
9
+ {{ this-thing }}
10
+ END_TEMPLATE
11
+ template = Template.parse(template_source)
12
+ rendered = template.render!
13
+ assert_equal "Print this-thing", rendered.strip
14
+ end
15
+
6
16
  def test_assigned_variable
7
17
  assert_template_result('.foo.',
8
- '{% assign foo = values %}.{{ foo[0] }}.',
9
- 'values' => %w{foo bar baz})
18
+ '{% assign foo = values %}.{{ foo[0] }}.',
19
+ 'values' => %w(foo bar baz))
10
20
 
11
21
  assert_template_result('.bar.',
12
- '{% assign foo = values %}.{{ foo[1] }}.',
13
- 'values' => %w{foo bar baz})
22
+ '{% assign foo = values %}.{{ foo[1] }}.',
23
+ 'values' => %w(foo bar baz))
14
24
  end
15
25
 
16
26
  def test_assign_with_filter
17
27
  assert_template_result('.bar.',
18
- '{% assign foo = values | split: "," %}.{{ foo[1] }}.',
19
- 'values' => "foo,bar,baz")
28
+ '{% assign foo = values | split: "," %}.{{ foo[1] }}.',
29
+ 'values' => "foo,bar,baz")
20
30
  end
21
31
 
22
32
  def test_assign_syntax_error
23
33
  assert_match_syntax_error(/assign/,
24
- '{% assign foo not values %}.',
25
- 'values' => "foo,bar,baz")
34
+ '{% assign foo not values %}.',
35
+ 'values' => "foo,bar,baz")
26
36
  end
27
37
 
28
38
  def test_assign_uses_error_mode
@@ -9,7 +9,7 @@ class FoobarTag < Liquid::Tag
9
9
  end
10
10
 
11
11
  class BlankTestFileSystem
12
- def read_template_file(template_path, context)
12
+ def read_template_file(template_path)
13
13
  template_path
14
14
  end
15
15
  end
@@ -31,7 +31,7 @@ class BlankTest < Minitest::Test
31
31
  end
32
32
 
33
33
  def test_new_tags_are_not_blank_by_default
34
- assert_template_result(" "*N, wrap_in_for("{% foobar %}"))
34
+ assert_template_result(" " * N, wrap_in_for("{% foobar %}"))
35
35
  end
36
36
 
37
37
  def test_loops_are_blank
@@ -47,7 +47,7 @@ class BlankTest < Minitest::Test
47
47
  end
48
48
 
49
49
  def test_mark_as_blank_only_during_parsing
50
- assert_template_result(" "*(N+1), wrap(" {% if false %} this never happens, but still, this block is not blank {% endif %}"))
50
+ assert_template_result(" " * (N + 1), wrap(" {% if false %} this never happens, but still, this block is not blank {% endif %}"))
51
51
  end
52
52
 
53
53
  def test_comments_are_blank
@@ -60,9 +60,9 @@ class BlankTest < Minitest::Test
60
60
 
61
61
  def test_nested_blocks_are_blank_but_only_if_all_children_are
62
62
  assert_template_result("", wrap(wrap(" ")))
63
- assert_template_result("\n but this is not "*(N+1),
64
- wrap(%q{{% if true %} {% comment %} this is blank {% endcomment %} {% endif %}
65
- {% if true %} but this is not {% endif %}}))
63
+ assert_template_result("\n but this is not " * (N + 1),
64
+ wrap('{% if true %} {% comment %} this is blank {% endcomment %} {% endif %}
65
+ {% if true %} but this is not {% endif %}'))
66
66
  end
67
67
 
68
68
  def test_assigns_are_blank
@@ -76,31 +76,31 @@ class BlankTest < Minitest::Test
76
76
 
77
77
  def test_whitespace_is_not_blank_if_other_stuff_is_present
78
78
  body = " x "
79
- assert_template_result(body*(N+1), wrap(body))
79
+ assert_template_result(body * (N + 1), wrap(body))
80
80
  end
81
81
 
82
82
  def test_increment_is_not_blank
83
- assert_template_result(" 0"*2*(N+1), wrap("{% assign foo = 0 %} {% increment foo %} {% decrement foo %}"))
83
+ assert_template_result(" 0" * 2 * (N + 1), wrap("{% assign foo = 0 %} {% increment foo %} {% decrement foo %}"))
84
84
  end
85
85
 
86
86
  def test_cycle_is_not_blank
87
- assert_template_result(" "*((N+1)/2)+" ", wrap("{% cycle ' ', ' ' %}"))
87
+ assert_template_result(" " * ((N + 1) / 2) + " ", wrap("{% cycle ' ', ' ' %}"))
88
88
  end
89
89
 
90
90
  def test_raw_is_not_blank
91
- assert_template_result(" "*(N+1), wrap(" {% raw %} {% endraw %}"))
91
+ assert_template_result(" " * (N + 1), wrap(" {% raw %} {% endraw %}"))
92
92
  end
93
93
 
94
94
  def test_include_is_blank
95
95
  Liquid::Template.file_system = BlankTestFileSystem.new
96
- assert_template_result "foobar"*(N+1), wrap("{% include 'foobar' %}")
97
- assert_template_result " foobar "*(N+1), wrap("{% include ' foobar ' %}")
98
- assert_template_result " "*(N+1), wrap(" {% include ' ' %} ")
96
+ assert_template_result "foobar" * (N + 1), wrap("{% include 'foobar' %}")
97
+ assert_template_result " foobar " * (N + 1), wrap("{% include ' foobar ' %}")
98
+ assert_template_result " " * (N + 1), wrap(" {% include ' ' %} ")
99
99
  end
100
100
 
101
101
  def test_case_is_blank
102
102
  assert_template_result("", wrap(" {% assign foo = 'bar' %} {% case foo %} {% when 'bar' %} {% when 'whatever' %} {% else %} {% endcase %} "))
103
103
  assert_template_result("", wrap(" {% assign foo = 'else' %} {% case foo %} {% when 'bar' %} {% when 'whatever' %} {% else %} {% endcase %} "))
104
- assert_template_result(" x "*(N+1), wrap(" {% assign foo = 'else' %} {% case foo %} {% when 'bar' %} {% when 'whatever' %} {% else %} x {% endcase %} "))
104
+ assert_template_result(" x " * (N + 1), wrap(" {% assign foo = 'else' %} {% case foo %} {% when 'bar' %} {% when 'whatever' %} {% else %} x {% endcase %} "))
105
105
  end
106
106
  end
@@ -7,6 +7,16 @@ class CaptureTest < Minitest::Test
7
7
  assert_template_result("test string", "{% capture 'var' %}test string{% endcapture %}{{var}}", {})
8
8
  end
9
9
 
10
+ def test_capture_with_hyphen_in_variable_name
11
+ template_source = <<-END_TEMPLATE
12
+ {% capture this-thing %}Print this-thing{% endcapture %}
13
+ {{ this-thing }}
14
+ END_TEMPLATE
15
+ template = Template.parse(template_source)
16
+ rendered = template.render!
17
+ assert_equal "Print this-thing", rendered.strip
18
+ end
19
+
10
20
  def test_capture_to_variable_from_outer_scope_if_existing
11
21
  template_source = <<-END_TEMPLATE
12
22
  {% assign var = '' %}
@@ -18,14 +18,14 @@ class ContextTest < Minitest::Test
18
18
 
19
19
  with_global_filter(global) do
20
20
  assert_equal 'Global test', Template.parse("{{'test' | notice }}").render!
21
- assert_equal 'Local test', Template.parse("{{'test' | notice }}").render!({}, :filters => [local])
21
+ assert_equal 'Local test', Template.parse("{{'test' | notice }}").render!({}, filters: [local])
22
22
  end
23
23
  end
24
24
 
25
25
  def test_has_key_will_not_add_an_error_for_missing_keys
26
26
  with_error_mode :strict do
27
27
  context = Context.new
28
- context.has_key?('unknown')
28
+ context.key?('unknown')
29
29
  assert_empty context.errors
30
30
  end
31
31
  end
@@ -0,0 +1,19 @@
1
+ require 'test_helper'
2
+
3
+ class DocumentTest < Minitest::Test
4
+ include Liquid
5
+
6
+ def test_unexpected_outer_tag
7
+ exc = assert_raises(SyntaxError) do
8
+ Template.parse("{% else %}")
9
+ end
10
+ assert_equal exc.message, "Liquid syntax error: Unexpected outer 'else' tag"
11
+ end
12
+
13
+ def test_unknown_tag
14
+ exc = assert_raises(SyntaxError) do
15
+ Template.parse("{% foo %}")
16
+ end
17
+ assert_equal exc.message, "Liquid syntax error: Unknown tag 'foo'"
18
+ end
19
+ end
@@ -13,13 +13,12 @@ class ContextDrop < Liquid::Drop
13
13
  @context['forloop.index']
14
14
  end
15
15
 
16
- def before_method(method)
17
- return @context[method]
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 before_method(method)
35
- return 'method: ' << method.to_s
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
- def callmenot
57
- "protected"
58
- end
55
+
56
+ def callmenot
57
+ "protected"
58
+ end
59
59
  end
60
60
 
61
61
  class EnumerableDrop < Liquid::Drop
62
- def before_method(method)
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 before_method(method)
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
- tpl.render!('product' => ProductDrop.new)
128
- assert_match /tainted/, tpl.warnings.first
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( ' {{ product.texts.text }} ' ).render!('product' => ProductDrop.new)
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 test_unknown_method
159
- output = Liquid::Template.parse( ' {{ product.catchall.unknown }} ' ).render!('product' => ProductDrop.new)
160
- assert_equal ' method: unknown ', output
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 test_integer_argument_drop
164
- output = Liquid::Template.parse( ' {{ product.catchall[8] }} ' ).render!('product' => ProductDrop.new)
165
- assert_equal ' method: 8 ', output
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( '{% for text in product.texts.array %} {{text}} {% endfor %}' ).render!('product' => ProductDrop.new)
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( ' {{ context.bar }} ' ).render!('context' => ContextDrop.new, 'bar' => "carrot")
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( ' {{ product.context.foo }} ' ).render!('product' => ProductDrop.new, 'foo' => "monkey")
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( ' {{ product.callmenot }} ' ).render!('product' => ProductDrop.new)
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( '{{ context.scopes }}' ).render!('context' => ContextDrop.new)
197
- assert_equal '2', Liquid::Template.parse( '{%for i in dummy%}{{ context.scopes }}{%endfor%}' ).render!('context' => ContextDrop.new, 'dummy' => [1])
198
- assert_equal '3', Liquid::Template.parse( '{%for i in dummy%}{%for i in dummy%}{{ context.scopes }}{%endfor%}{%endfor%}' ).render!('context' => ContextDrop.new, 'dummy' => [1])
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( '{{ s }}' ).render!('context' => ContextDrop.new, 's' => Proc.new{|c| c['context.scopes'] })
203
- assert_equal '2', Liquid::Template.parse( '{%for i in dummy%}{{ s }}{%endfor%}' ).render!('context' => ContextDrop.new, 's' => Proc.new{|c| c['context.scopes'] }, 'dummy' => [1])
204
- assert_equal '3', Liquid::Template.parse( '{%for i in dummy%}{%for i in dummy%}{{ s }}{%endfor%}{%endfor%}' ).render!('context' => ContextDrop.new, 's' => Proc.new{|c| c['context.scopes'] }, 'dummy' => [1])
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( '{% assign a = "variable"%}{{a}}' ).render!('context' => ContextDrop.new)
209
- assert_equal 'variable', Liquid::Template.parse( '{% assign a = "variable"%}{%for i in dummy%}{{a}}{%endfor%}' ).render!('context' => ContextDrop.new, 'dummy' => [1])
210
- assert_equal 'test', Liquid::Template.parse( '{% assign header_gif = "test"%}{{header_gif}}' ).render!('context' => ContextDrop.new)
211
- assert_equal 'test', Liquid::Template.parse( "{% assign header_gif = 'test'%}{{header_gif}}" ).render!('context' => ContextDrop.new)
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( '{% for i in context.scopes_as_array %}{{i}}{% endfor %}' ).render!('context' => ContextDrop.new, 'dummy' => [1])
216
- 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])
217
- 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])
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( '{%for a in dummy%}{{ context.loop_pos }}{% endfor %}' ).render!('context' => ContextDrop.new, 'dummy' => [1,2,3])
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( '{% for c in collection %}{{c}}{% endfor %}').render!('collection' => EnumerableDrop.new)
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( '{{collection.size}}').render!('collection' => EnumerableDrop.new)
231
+ assert_equal '3', Liquid::Template.parse('{{collection.size}}').render!('collection' => EnumerableDrop.new)
230
232
  end
231
233
 
232
- def test_enumerable_drop_will_invoke_before_method_for_clashing_method_names
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( ' {{ errors.standard_error }} ' )
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( ' {{ errors.syntax_error }} ' )
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( ' {{ errors.argument_error }} ' )
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 %} ', :error_mode => :lax)
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,31 +83,47 @@ 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
- template = Liquid::Template.parse(%q{
86
+ Liquid::Template.parse(%q(
106
87
  foobar
107
88
 
108
89
  {% "cat" | foobar %}
109
90
 
110
91
  bla
111
- },
112
- :line_numbers => true
92
+ ),
93
+ line_numbers: true
113
94
  )
114
95
  end
115
96
 
116
- assert_match /Liquid syntax error \(line 4\)/, err.message
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
110
+ )
111
+ end
112
+
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(%q{
117
+ template = Liquid::Template.parse('
121
118
  foobar
122
119
 
123
120
  {% if 1 =! 2 %}ok{% endif %}
124
121
 
125
122
  bla
126
- },
127
- :error_mode => :warn,
128
- :line_numbers => true
129
- )
123
+ ',
124
+ error_mode: :warn,
125
+ line_numbers: true
126
+ )
130
127
 
131
128
  assert_equal ['Liquid syntax error (line 4): Unexpected character = in "1 =! 2"'],
132
129
  template.warnings.map(&:message)
@@ -134,16 +131,16 @@ 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(%q{
134
+ Liquid::Template.parse('
138
135
  foobar
139
136
 
140
137
  {% if 1 =! 2 %}ok{% endif %}
141
138
 
142
139
  bla
143
- },
144
- :error_mode => :strict,
145
- :line_numbers => true
146
- )
140
+ ',
141
+ error_mode: :strict,
142
+ line_numbers: true
143
+ )
147
144
  end
148
145
 
149
146
  assert_equal 'Liquid syntax error (line 4): Unexpected character = in "1 =! 2"', err.message
@@ -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(%q{
151
+ Liquid::Template.parse('
155
152
  foobar
156
153
 
157
154
  {% if 1 != 2 %}
@@ -159,9 +156,9 @@ class ErrorHandlingTest < Minitest::Test
159
156
  {% endif %}
160
157
 
161
158
  bla
162
- },
163
- :line_numbers => true
164
- )
159
+ ',
160
+ line_numbers: true
161
+ )
165
162
  end
166
163
 
167
164
  assert_equal "Liquid syntax error (line 5): Unknown tag 'foo'", err.message
@@ -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 %} ', :error_mode => :strict)
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('{{%%%}}', :error_mode => :strict)
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 %}', :error_mode => :warn)
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 %}", :error_mode => :warn, :line_numbers => true)
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