liquid 2.6.1 → 4.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (130) hide show
  1. checksums.yaml +5 -5
  2. data/History.md +194 -29
  3. data/{MIT-LICENSE → LICENSE} +0 -0
  4. data/README.md +60 -2
  5. data/lib/liquid.rb +25 -14
  6. data/lib/liquid/block.rb +47 -96
  7. data/lib/liquid/block_body.rb +143 -0
  8. data/lib/liquid/condition.rb +70 -39
  9. data/lib/liquid/context.rb +116 -157
  10. data/lib/liquid/document.rb +19 -9
  11. data/lib/liquid/drop.rb +31 -14
  12. data/lib/liquid/errors.rb +54 -10
  13. data/lib/liquid/expression.rb +49 -0
  14. data/lib/liquid/extensions.rb +19 -7
  15. data/lib/liquid/file_system.rb +25 -14
  16. data/lib/liquid/forloop_drop.rb +42 -0
  17. data/lib/liquid/i18n.rb +39 -0
  18. data/lib/liquid/interrupts.rb +2 -3
  19. data/lib/liquid/lexer.rb +55 -0
  20. data/lib/liquid/locales/en.yml +26 -0
  21. data/lib/liquid/parse_context.rb +38 -0
  22. data/lib/liquid/parse_tree_visitor.rb +42 -0
  23. data/lib/liquid/parser.rb +90 -0
  24. data/lib/liquid/parser_switching.rb +31 -0
  25. data/lib/liquid/profiler.rb +158 -0
  26. data/lib/liquid/profiler/hooks.rb +23 -0
  27. data/lib/liquid/range_lookup.rb +37 -0
  28. data/lib/liquid/resource_limits.rb +23 -0
  29. data/lib/liquid/standardfilters.rb +311 -77
  30. data/lib/liquid/strainer.rb +39 -26
  31. data/lib/liquid/tablerowloop_drop.rb +62 -0
  32. data/lib/liquid/tag.rb +28 -11
  33. data/lib/liquid/tags/assign.rb +34 -10
  34. data/lib/liquid/tags/break.rb +1 -4
  35. data/lib/liquid/tags/capture.rb +11 -9
  36. data/lib/liquid/tags/case.rb +37 -22
  37. data/lib/liquid/tags/comment.rb +10 -3
  38. data/lib/liquid/tags/continue.rb +1 -4
  39. data/lib/liquid/tags/cycle.rb +20 -14
  40. data/lib/liquid/tags/decrement.rb +4 -8
  41. data/lib/liquid/tags/for.rb +121 -60
  42. data/lib/liquid/tags/if.rb +73 -30
  43. data/lib/liquid/tags/ifchanged.rb +3 -5
  44. data/lib/liquid/tags/include.rb +77 -46
  45. data/lib/liquid/tags/increment.rb +4 -8
  46. data/lib/liquid/tags/raw.rb +35 -10
  47. data/lib/liquid/tags/table_row.rb +62 -0
  48. data/lib/liquid/tags/unless.rb +6 -9
  49. data/lib/liquid/template.rb +130 -32
  50. data/lib/liquid/tokenizer.rb +31 -0
  51. data/lib/liquid/truffle.rb +5 -0
  52. data/lib/liquid/utils.rb +57 -4
  53. data/lib/liquid/variable.rb +121 -30
  54. data/lib/liquid/variable_lookup.rb +88 -0
  55. data/lib/liquid/version.rb +2 -1
  56. data/test/fixtures/en_locale.yml +9 -0
  57. data/test/integration/assign_test.rb +48 -0
  58. data/test/integration/blank_test.rb +106 -0
  59. data/test/integration/block_test.rb +12 -0
  60. data/test/{liquid → integration}/capture_test.rb +13 -3
  61. data/test/integration/context_test.rb +32 -0
  62. data/test/integration/document_test.rb +19 -0
  63. data/test/integration/drop_test.rb +273 -0
  64. data/test/integration/error_handling_test.rb +260 -0
  65. data/test/integration/filter_test.rb +178 -0
  66. data/test/integration/hash_ordering_test.rb +23 -0
  67. data/test/integration/output_test.rb +123 -0
  68. data/test/integration/parse_tree_visitor_test.rb +247 -0
  69. data/test/integration/parsing_quirks_test.rb +122 -0
  70. data/test/integration/render_profiling_test.rb +154 -0
  71. data/test/integration/security_test.rb +80 -0
  72. data/test/integration/standard_filter_test.rb +776 -0
  73. data/test/{liquid → integration}/tags/break_tag_test.rb +2 -3
  74. data/test/{liquid → integration}/tags/continue_tag_test.rb +1 -2
  75. data/test/integration/tags/for_tag_test.rb +410 -0
  76. data/test/integration/tags/if_else_tag_test.rb +188 -0
  77. data/test/integration/tags/include_tag_test.rb +253 -0
  78. data/test/integration/tags/increment_tag_test.rb +23 -0
  79. data/test/{liquid → integration}/tags/raw_tag_test.rb +9 -2
  80. data/test/integration/tags/standard_tag_test.rb +296 -0
  81. data/test/integration/tags/statements_test.rb +111 -0
  82. data/test/{liquid/tags/html_tag_test.rb → integration/tags/table_row_test.rb} +25 -24
  83. data/test/integration/tags/unless_else_tag_test.rb +26 -0
  84. data/test/integration/template_test.rb +332 -0
  85. data/test/integration/trim_mode_test.rb +529 -0
  86. data/test/integration/variable_test.rb +96 -0
  87. data/test/test_helper.rb +106 -19
  88. data/test/truffle/truffle_test.rb +9 -0
  89. data/test/{liquid/block_test.rb → unit/block_unit_test.rb} +9 -9
  90. data/test/unit/condition_unit_test.rb +166 -0
  91. data/test/{liquid/context_test.rb → unit/context_unit_test.rb} +85 -74
  92. data/test/unit/file_system_unit_test.rb +35 -0
  93. data/test/unit/i18n_unit_test.rb +37 -0
  94. data/test/unit/lexer_unit_test.rb +51 -0
  95. data/test/unit/parser_unit_test.rb +82 -0
  96. data/test/{liquid/regexp_test.rb → unit/regexp_unit_test.rb} +4 -4
  97. data/test/unit/strainer_unit_test.rb +164 -0
  98. data/test/unit/tag_unit_test.rb +21 -0
  99. data/test/unit/tags/case_tag_unit_test.rb +10 -0
  100. data/test/unit/tags/for_tag_unit_test.rb +13 -0
  101. data/test/unit/tags/if_tag_unit_test.rb +8 -0
  102. data/test/unit/template_unit_test.rb +78 -0
  103. data/test/unit/tokenizer_unit_test.rb +55 -0
  104. data/test/unit/variable_unit_test.rb +162 -0
  105. metadata +157 -77
  106. data/lib/extras/liquid_view.rb +0 -51
  107. data/lib/liquid/htmltags.rb +0 -74
  108. data/lib/liquid/module_ex.rb +0 -62
  109. data/test/liquid/assign_test.rb +0 -21
  110. data/test/liquid/condition_test.rb +0 -127
  111. data/test/liquid/drop_test.rb +0 -180
  112. data/test/liquid/error_handling_test.rb +0 -81
  113. data/test/liquid/file_system_test.rb +0 -29
  114. data/test/liquid/filter_test.rb +0 -125
  115. data/test/liquid/hash_ordering_test.rb +0 -25
  116. data/test/liquid/module_ex_test.rb +0 -87
  117. data/test/liquid/output_test.rb +0 -116
  118. data/test/liquid/parsing_quirks_test.rb +0 -52
  119. data/test/liquid/security_test.rb +0 -64
  120. data/test/liquid/standard_filter_test.rb +0 -251
  121. data/test/liquid/strainer_test.rb +0 -52
  122. data/test/liquid/tags/for_tag_test.rb +0 -297
  123. data/test/liquid/tags/if_else_tag_test.rb +0 -166
  124. data/test/liquid/tags/include_tag_test.rb +0 -166
  125. data/test/liquid/tags/increment_tag_test.rb +0 -24
  126. data/test/liquid/tags/standard_tag_test.rb +0 -295
  127. data/test/liquid/tags/statements_test.rb +0 -134
  128. data/test/liquid/tags/unless_else_tag_test.rb +0 -26
  129. data/test/liquid/template_test.rb +0 -146
  130. data/test/liquid/variable_test.rb +0 -186
@@ -0,0 +1,123 @@
1
+ require 'test_helper'
2
+
3
+ module FunnyFilter
4
+ def make_funny(input)
5
+ 'LOL'
6
+ end
7
+
8
+ def cite_funny(input)
9
+ "LOL: #{input}"
10
+ end
11
+
12
+ def add_smiley(input, smiley = ":-)")
13
+ "#{input} #{smiley}"
14
+ end
15
+
16
+ def add_tag(input, tag = "p", id = "foo")
17
+ %(<#{tag} id="#{id}">#{input}</#{tag}>)
18
+ end
19
+
20
+ def paragraph(input)
21
+ "<p>#{input}</p>"
22
+ end
23
+
24
+ def link_to(name, url)
25
+ %(<a href="#{url}">#{name}</a>)
26
+ end
27
+ end
28
+
29
+ class OutputTest < Minitest::Test
30
+ include Liquid
31
+
32
+ def setup
33
+ @assigns = {
34
+ 'best_cars' => 'bmw',
35
+ 'car' => { 'bmw' => 'good', 'gm' => 'bad' }
36
+ }
37
+ end
38
+
39
+ def test_variable
40
+ text = %( {{best_cars}} )
41
+
42
+ expected = %( bmw )
43
+ assert_equal expected, Template.parse(text).render!(@assigns)
44
+ end
45
+
46
+ def test_variable_traversing_with_two_brackets
47
+ text = %({{ site.data.menu[include.menu][include.locale] }})
48
+ assert_equal "it works!", Template.parse(text).render!(
49
+ "site" => { "data" => { "menu" => { "foo" => { "bar" => "it works!" } } } },
50
+ "include" => { "menu" => "foo", "locale" => "bar" }
51
+ )
52
+ end
53
+
54
+ def test_variable_traversing
55
+ text = %( {{car.bmw}} {{car.gm}} {{car.bmw}} )
56
+
57
+ expected = %( good bad good )
58
+ assert_equal expected, Template.parse(text).render!(@assigns)
59
+ end
60
+
61
+ def test_variable_piping
62
+ text = %( {{ car.gm | make_funny }} )
63
+ expected = %( LOL )
64
+
65
+ assert_equal expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])
66
+ end
67
+
68
+ def test_variable_piping_with_input
69
+ text = %( {{ car.gm | cite_funny }} )
70
+ expected = %( LOL: bad )
71
+
72
+ assert_equal expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])
73
+ end
74
+
75
+ def test_variable_piping_with_args
76
+ text = %! {{ car.gm | add_smiley : ':-(' }} !
77
+ expected = %| bad :-( |
78
+
79
+ assert_equal expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])
80
+ end
81
+
82
+ def test_variable_piping_with_no_args
83
+ text = %( {{ car.gm | add_smiley }} )
84
+ expected = %| bad :-) |
85
+
86
+ assert_equal expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])
87
+ end
88
+
89
+ def test_multiple_variable_piping_with_args
90
+ text = %! {{ car.gm | add_smiley : ':-(' | add_smiley : ':-('}} !
91
+ expected = %| bad :-( :-( |
92
+
93
+ assert_equal expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])
94
+ end
95
+
96
+ def test_variable_piping_with_multiple_args
97
+ text = %( {{ car.gm | add_tag : 'span', 'bar'}} )
98
+ expected = %( <span id="bar">bad</span> )
99
+
100
+ assert_equal expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])
101
+ end
102
+
103
+ def test_variable_piping_with_variable_args
104
+ text = %( {{ car.gm | add_tag : 'span', car.bmw}} )
105
+ expected = %( <span id="good">bad</span> )
106
+
107
+ assert_equal expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])
108
+ end
109
+
110
+ def test_multiple_pipings
111
+ text = %( {{ best_cars | cite_funny | paragraph }} )
112
+ expected = %( <p>LOL: bmw</p> )
113
+
114
+ assert_equal expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])
115
+ end
116
+
117
+ def test_link_to
118
+ text = %( {{ 'Typo' | link_to: 'http://typo.leetsoft.com' }} )
119
+ expected = %( <a href="http://typo.leetsoft.com">Typo</a> )
120
+
121
+ assert_equal expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter])
122
+ end
123
+ end # OutputTest
@@ -0,0 +1,247 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_helper'
4
+
5
+ class ParseTreeVisitorTest < Minitest::Test
6
+ include Liquid
7
+
8
+ def test_variable
9
+ assert_equal(
10
+ ["test"],
11
+ visit(%({{ test }}))
12
+ )
13
+ end
14
+
15
+ def test_varible_with_filter
16
+ assert_equal(
17
+ ["test", "infilter"],
18
+ visit(%({{ test | split: infilter }}))
19
+ )
20
+ end
21
+
22
+ def test_dynamic_variable
23
+ assert_equal(
24
+ ["test", "inlookup"],
25
+ visit(%({{ test[inlookup] }}))
26
+ )
27
+ end
28
+
29
+ def test_if_condition
30
+ assert_equal(
31
+ ["test"],
32
+ visit(%({% if test %}{% endif %}))
33
+ )
34
+ end
35
+
36
+ def test_complex_if_condition
37
+ assert_equal(
38
+ ["test"],
39
+ visit(%({% if 1 == 1 and 2 == test %}{% endif %}))
40
+ )
41
+ end
42
+
43
+ def test_if_body
44
+ assert_equal(
45
+ ["test"],
46
+ visit(%({% if 1 == 1 %}{{ test }}{% endif %}))
47
+ )
48
+ end
49
+
50
+ def test_unless_condition
51
+ assert_equal(
52
+ ["test"],
53
+ visit(%({% unless test %}{% endunless %}))
54
+ )
55
+ end
56
+
57
+ def test_complex_unless_condition
58
+ assert_equal(
59
+ ["test"],
60
+ visit(%({% unless 1 == 1 and 2 == test %}{% endunless %}))
61
+ )
62
+ end
63
+
64
+ def test_unless_body
65
+ assert_equal(
66
+ ["test"],
67
+ visit(%({% unless 1 == 1 %}{{ test }}{% endunless %}))
68
+ )
69
+ end
70
+
71
+ def test_elsif_condition
72
+ assert_equal(
73
+ ["test"],
74
+ visit(%({% if 1 == 1 %}{% elsif test %}{% endif %}))
75
+ )
76
+ end
77
+
78
+ def test_complex_elsif_condition
79
+ assert_equal(
80
+ ["test"],
81
+ visit(%({% if 1 == 1 %}{% elsif 1 == 1 and 2 == test %}{% endif %}))
82
+ )
83
+ end
84
+
85
+ def test_elsif_body
86
+ assert_equal(
87
+ ["test"],
88
+ visit(%({% if 1 == 1 %}{% elsif 2 == 2 %}{{ test }}{% endif %}))
89
+ )
90
+ end
91
+
92
+ def test_else_body
93
+ assert_equal(
94
+ ["test"],
95
+ visit(%({% if 1 == 1 %}{% else %}{{ test }}{% endif %}))
96
+ )
97
+ end
98
+
99
+ def test_case_left
100
+ assert_equal(
101
+ ["test"],
102
+ visit(%({% case test %}{% endcase %}))
103
+ )
104
+ end
105
+
106
+ def test_case_condition
107
+ assert_equal(
108
+ ["test"],
109
+ visit(%({% case 1 %}{% when test %}{% endcase %}))
110
+ )
111
+ end
112
+
113
+ def test_case_when_body
114
+ assert_equal(
115
+ ["test"],
116
+ visit(%({% case 1 %}{% when 2 %}{{ test }}{% endcase %}))
117
+ )
118
+ end
119
+
120
+ def test_case_else_body
121
+ assert_equal(
122
+ ["test"],
123
+ visit(%({% case 1 %}{% else %}{{ test }}{% endcase %}))
124
+ )
125
+ end
126
+
127
+ def test_for_in
128
+ assert_equal(
129
+ ["test"],
130
+ visit(%({% for x in test %}{% endfor %}))
131
+ )
132
+ end
133
+
134
+ def test_for_limit
135
+ assert_equal(
136
+ ["test"],
137
+ visit(%({% for x in (1..5) limit: test %}{% endfor %}))
138
+ )
139
+ end
140
+
141
+ def test_for_offset
142
+ assert_equal(
143
+ ["test"],
144
+ visit(%({% for x in (1..5) offset: test %}{% endfor %}))
145
+ )
146
+ end
147
+
148
+ def test_for_body
149
+ assert_equal(
150
+ ["test"],
151
+ visit(%({% for x in (1..5) %}{{ test }}{% endfor %}))
152
+ )
153
+ end
154
+
155
+ def test_tablerow_in
156
+ assert_equal(
157
+ ["test"],
158
+ visit(%({% tablerow x in test %}{% endtablerow %}))
159
+ )
160
+ end
161
+
162
+ def test_tablerow_limit
163
+ assert_equal(
164
+ ["test"],
165
+ visit(%({% tablerow x in (1..5) limit: test %}{% endtablerow %}))
166
+ )
167
+ end
168
+
169
+ def test_tablerow_offset
170
+ assert_equal(
171
+ ["test"],
172
+ visit(%({% tablerow x in (1..5) offset: test %}{% endtablerow %}))
173
+ )
174
+ end
175
+
176
+ def test_tablerow_body
177
+ assert_equal(
178
+ ["test"],
179
+ visit(%({% tablerow x in (1..5) %}{{ test }}{% endtablerow %}))
180
+ )
181
+ end
182
+
183
+ def test_cycle
184
+ assert_equal(
185
+ ["test"],
186
+ visit(%({% cycle test %}))
187
+ )
188
+ end
189
+
190
+ def test_assign
191
+ assert_equal(
192
+ ["test"],
193
+ visit(%({% assign x = test %}))
194
+ )
195
+ end
196
+
197
+ def test_capture
198
+ assert_equal(
199
+ ["test"],
200
+ visit(%({% capture x %}{{ test }}{% endcapture %}))
201
+ )
202
+ end
203
+
204
+ def test_include
205
+ assert_equal(
206
+ ["test"],
207
+ visit(%({% include test %}))
208
+ )
209
+ end
210
+
211
+ def test_include_with
212
+ assert_equal(
213
+ ["test"],
214
+ visit(%({% include "hai" with test %}))
215
+ )
216
+ end
217
+
218
+ def test_include_for
219
+ assert_equal(
220
+ ["test"],
221
+ visit(%({% include "hai" for test %}))
222
+ )
223
+ end
224
+
225
+ def test_preserve_tree_structure
226
+ assert_equal(
227
+ [[nil, [
228
+ [nil, [[nil, [["other", []]]]]],
229
+ ["test", []],
230
+ ["xs", []]
231
+ ]]],
232
+ traversal(%({% for x in xs offset: test %}{{ other }}{% endfor %})).visit
233
+ )
234
+ end
235
+
236
+ private
237
+
238
+ def traversal(template)
239
+ ParseTreeVisitor
240
+ .for(Template.parse(template).root)
241
+ .add_callback_for(VariableLookup, &:name)
242
+ end
243
+
244
+ def visit(template)
245
+ traversal(template).visit.flatten.compact
246
+ end
247
+ end
@@ -0,0 +1,122 @@
1
+ require 'test_helper'
2
+
3
+ class ParsingQuirksTest < Minitest::Test
4
+ include Liquid
5
+
6
+ def test_parsing_css
7
+ text = " div { font-weight: bold; } "
8
+ assert_equal text, Template.parse(text).render!
9
+ end
10
+
11
+ def test_raise_on_single_close_bracet
12
+ assert_raises(SyntaxError) do
13
+ Template.parse("text {{method} oh nos!")
14
+ end
15
+ end
16
+
17
+ def test_raise_on_label_and_no_close_bracets
18
+ assert_raises(SyntaxError) do
19
+ Template.parse("TEST {{ ")
20
+ end
21
+ end
22
+
23
+ def test_raise_on_label_and_no_close_bracets_percent
24
+ assert_raises(SyntaxError) do
25
+ Template.parse("TEST {% ")
26
+ end
27
+ end
28
+
29
+ def test_error_on_empty_filter
30
+ assert Template.parse("{{test}}")
31
+
32
+ with_error_mode(:lax) do
33
+ assert Template.parse("{{|test}}")
34
+ end
35
+
36
+ with_error_mode(:strict) do
37
+ assert_raises(SyntaxError) { Template.parse("{{|test}}") }
38
+ assert_raises(SyntaxError) { Template.parse("{{test |a|b|}}") }
39
+ end
40
+ end
41
+
42
+ def test_meaningless_parens_error
43
+ with_error_mode(:strict) do
44
+ assert_raises(SyntaxError) do
45
+ markup = "a == 'foo' or (b == 'bar' and c == 'baz') or false"
46
+ Template.parse("{% if #{markup} %} YES {% endif %}")
47
+ end
48
+ end
49
+ end
50
+
51
+ def test_unexpected_characters_syntax_error
52
+ with_error_mode(:strict) do
53
+ assert_raises(SyntaxError) do
54
+ markup = "true && false"
55
+ Template.parse("{% if #{markup} %} YES {% endif %}")
56
+ end
57
+ assert_raises(SyntaxError) do
58
+ markup = "false || true"
59
+ Template.parse("{% if #{markup} %} YES {% endif %}")
60
+ end
61
+ end
62
+ end
63
+
64
+ def test_no_error_on_lax_empty_filter
65
+ assert Template.parse("{{test |a|b|}}", error_mode: :lax)
66
+ assert Template.parse("{{test}}", error_mode: :lax)
67
+ assert Template.parse("{{|test|}}", error_mode: :lax)
68
+ end
69
+
70
+ def test_meaningless_parens_lax
71
+ with_error_mode(:lax) do
72
+ assigns = { 'b' => 'bar', 'c' => 'baz' }
73
+ markup = "a == 'foo' or (b == 'bar' and c == 'baz') or false"
74
+ assert_template_result(' YES ', "{% if #{markup} %} YES {% endif %}", assigns)
75
+ end
76
+ end
77
+
78
+ def test_unexpected_characters_silently_eat_logic_lax
79
+ with_error_mode(:lax) do
80
+ markup = "true && false"
81
+ assert_template_result(' YES ', "{% if #{markup} %} YES {% endif %}")
82
+ markup = "false || true"
83
+ assert_template_result('', "{% if #{markup} %} YES {% endif %}")
84
+ end
85
+ end
86
+
87
+ def test_raise_on_invalid_tag_delimiter
88
+ assert_raises(Liquid::SyntaxError) do
89
+ Template.new.parse('{% end %}')
90
+ end
91
+ end
92
+
93
+ def test_unanchored_filter_arguments
94
+ with_error_mode(:lax) do
95
+ assert_template_result('hi', "{{ 'hi there' | split$$$:' ' | first }}")
96
+
97
+ assert_template_result('x', "{{ 'X' | downcase) }}")
98
+
99
+ # After the messed up quotes a filter without parameters (reverse) should work
100
+ # but one with parameters (remove) shouldn't be detected.
101
+ assert_template_result('here', "{{ 'hi there' | split:\"t\"\" | reverse | first}}")
102
+ assert_template_result('hi ', "{{ 'hi there' | split:\"t\"\" | remove:\"i\" | first}}")
103
+ end
104
+ end
105
+
106
+ def test_invalid_variables_work
107
+ with_error_mode(:lax) do
108
+ assert_template_result('bar', "{% assign 123foo = 'bar' %}{{ 123foo }}")
109
+ assert_template_result('123', "{% assign 123 = 'bar' %}{{ 123 }}")
110
+ end
111
+ end
112
+
113
+ def test_extra_dots_in_ranges
114
+ with_error_mode(:lax) do
115
+ assert_template_result('12345', "{% for i in (1...5) %}{{ i }}{% endfor %}")
116
+ end
117
+ end
118
+
119
+ def test_contains_in_id
120
+ assert_template_result(' YES ', '{% if containsallshipments == true %} YES {% endif %}', 'containsallshipments' => true)
121
+ end
122
+ end # ParsingQuirksTest