liquid 5.3.0 → 5.4.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 (96) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +16 -1
  3. data/README.md +2 -2
  4. data/lib/liquid/block_body.rb +4 -4
  5. data/lib/liquid/context.rb +6 -2
  6. data/lib/liquid/forloop_drop.rb +44 -1
  7. data/lib/liquid/locales/en.yml +6 -5
  8. data/lib/liquid/partial_cache.rb +3 -3
  9. data/lib/liquid/{static_registers.rb → registers.rb} +13 -10
  10. data/lib/liquid/standardfilters.rb +406 -57
  11. data/lib/liquid/strainer_factory.rb +4 -0
  12. data/lib/liquid/strainer_template.rb +4 -0
  13. data/lib/liquid/tablerowloop_drop.rb +58 -1
  14. data/lib/liquid/tags/assign.rb +12 -8
  15. data/lib/liquid/tags/break.rb +8 -0
  16. data/lib/liquid/tags/capture.rb +13 -10
  17. data/lib/liquid/tags/case.rb +21 -0
  18. data/lib/liquid/tags/comment.rb +13 -0
  19. data/lib/liquid/tags/continue.rb +8 -9
  20. data/lib/liquid/tags/cycle.rb +12 -11
  21. data/lib/liquid/tags/decrement.rb +16 -17
  22. data/lib/liquid/tags/echo.rb +16 -9
  23. data/lib/liquid/tags/for.rb +22 -43
  24. data/lib/liquid/tags/if.rb +11 -9
  25. data/lib/liquid/tags/include.rb +15 -13
  26. data/lib/liquid/tags/increment.rb +16 -14
  27. data/lib/liquid/tags/inline_comment.rb +43 -0
  28. data/lib/liquid/tags/raw.rb +11 -0
  29. data/lib/liquid/tags/render.rb +29 -4
  30. data/lib/liquid/tags/table_row.rb +22 -0
  31. data/lib/liquid/tags/unless.rb +15 -4
  32. data/lib/liquid/template.rb +2 -3
  33. data/lib/liquid/variable.rb +4 -4
  34. data/lib/liquid/variable_lookup.rb +10 -7
  35. data/lib/liquid/version.rb +1 -1
  36. data/lib/liquid.rb +2 -2
  37. metadata +7 -123
  38. data/lib/liquid/register.rb +0 -6
  39. data/test/fixtures/en_locale.yml +0 -9
  40. data/test/integration/assign_test.rb +0 -117
  41. data/test/integration/blank_test.rb +0 -109
  42. data/test/integration/block_test.rb +0 -58
  43. data/test/integration/capture_test.rb +0 -58
  44. data/test/integration/context_test.rb +0 -634
  45. data/test/integration/document_test.rb +0 -21
  46. data/test/integration/drop_test.rb +0 -257
  47. data/test/integration/error_handling_test.rb +0 -272
  48. data/test/integration/expression_test.rb +0 -46
  49. data/test/integration/filter_kwarg_test.rb +0 -24
  50. data/test/integration/filter_test.rb +0 -189
  51. data/test/integration/hash_ordering_test.rb +0 -25
  52. data/test/integration/output_test.rb +0 -125
  53. data/test/integration/parsing_quirks_test.rb +0 -134
  54. data/test/integration/profiler_test.rb +0 -240
  55. data/test/integration/security_test.rb +0 -89
  56. data/test/integration/standard_filter_test.rb +0 -925
  57. data/test/integration/tag/disableable_test.rb +0 -59
  58. data/test/integration/tag_test.rb +0 -45
  59. data/test/integration/tags/break_tag_test.rb +0 -17
  60. data/test/integration/tags/continue_tag_test.rb +0 -17
  61. data/test/integration/tags/echo_test.rb +0 -13
  62. data/test/integration/tags/for_tag_test.rb +0 -466
  63. data/test/integration/tags/if_else_tag_test.rb +0 -190
  64. data/test/integration/tags/include_tag_test.rb +0 -269
  65. data/test/integration/tags/increment_tag_test.rb +0 -25
  66. data/test/integration/tags/liquid_tag_test.rb +0 -116
  67. data/test/integration/tags/raw_tag_test.rb +0 -34
  68. data/test/integration/tags/render_tag_test.rb +0 -213
  69. data/test/integration/tags/standard_tag_test.rb +0 -303
  70. data/test/integration/tags/statements_test.rb +0 -113
  71. data/test/integration/tags/table_row_test.rb +0 -66
  72. data/test/integration/tags/unless_else_tag_test.rb +0 -28
  73. data/test/integration/template_test.rb +0 -340
  74. data/test/integration/trim_mode_test.rb +0 -563
  75. data/test/integration/variable_test.rb +0 -138
  76. data/test/test_helper.rb +0 -207
  77. data/test/unit/block_unit_test.rb +0 -53
  78. data/test/unit/condition_unit_test.rb +0 -181
  79. data/test/unit/file_system_unit_test.rb +0 -37
  80. data/test/unit/i18n_unit_test.rb +0 -39
  81. data/test/unit/lexer_unit_test.rb +0 -53
  82. data/test/unit/parse_tree_visitor_test.rb +0 -261
  83. data/test/unit/parser_unit_test.rb +0 -84
  84. data/test/unit/partial_cache_unit_test.rb +0 -128
  85. data/test/unit/regexp_unit_test.rb +0 -46
  86. data/test/unit/static_registers_unit_test.rb +0 -156
  87. data/test/unit/strainer_factory_unit_test.rb +0 -101
  88. data/test/unit/strainer_template_unit_test.rb +0 -82
  89. data/test/unit/tag_unit_test.rb +0 -23
  90. data/test/unit/tags/case_tag_unit_test.rb +0 -12
  91. data/test/unit/tags/for_tag_unit_test.rb +0 -15
  92. data/test/unit/tags/if_tag_unit_test.rb +0 -10
  93. data/test/unit/template_factory_unit_test.rb +0 -12
  94. data/test/unit/template_unit_test.rb +0 -87
  95. data/test/unit/tokenizer_unit_test.rb +0 -62
  96. data/test/unit/variable_unit_test.rb +0 -164
@@ -1,189 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'test_helper'
4
-
5
- module MoneyFilter
6
- def money(input)
7
- format(' %d$ ', input)
8
- end
9
-
10
- def money_with_underscore(input)
11
- format(' %d$ ', input)
12
- end
13
- end
14
-
15
- module CanadianMoneyFilter
16
- def money(input)
17
- format(' %d$ CAD ', input)
18
- end
19
- end
20
-
21
- module SubstituteFilter
22
- def substitute(input, params = {})
23
- input.gsub(/%\{(\w+)\}/) { |_match| params[Regexp.last_match(1)] }
24
- end
25
- end
26
-
27
- class FiltersTest < Minitest::Test
28
- include Liquid
29
-
30
- module OverrideObjectMethodFilter
31
- def tap(_input)
32
- "tap overridden"
33
- end
34
- end
35
-
36
- def setup
37
- @context = Context.new
38
- end
39
-
40
- def test_local_filter
41
- @context['var'] = 1000
42
- @context.add_filters(MoneyFilter)
43
-
44
- assert_equal(' 1000$ ', Template.parse("{{var | money}}").render(@context))
45
- end
46
-
47
- def test_underscore_in_filter_name
48
- @context['var'] = 1000
49
- @context.add_filters(MoneyFilter)
50
- assert_equal(' 1000$ ', Template.parse("{{var | money_with_underscore}}").render(@context))
51
- end
52
-
53
- def test_second_filter_overwrites_first
54
- @context['var'] = 1000
55
- @context.add_filters(MoneyFilter)
56
- @context.add_filters(CanadianMoneyFilter)
57
-
58
- assert_equal(' 1000$ CAD ', Template.parse("{{var | money}}").render(@context))
59
- end
60
-
61
- def test_size
62
- @context['var'] = 'abcd'
63
- @context.add_filters(MoneyFilter)
64
-
65
- assert_equal('4', Template.parse("{{var | size}}").render(@context))
66
- end
67
-
68
- def test_join
69
- @context['var'] = [1, 2, 3, 4]
70
-
71
- assert_equal("1 2 3 4", Template.parse("{{var | join}}").render(@context))
72
- end
73
-
74
- def test_sort
75
- @context['value'] = 3
76
- @context['numbers'] = [2, 1, 4, 3]
77
- @context['words'] = ['expected', 'as', 'alphabetic']
78
- @context['arrays'] = ['flower', 'are']
79
- @context['case_sensitive'] = ['sensitive', 'Expected', 'case']
80
-
81
- assert_equal('1 2 3 4', Template.parse("{{numbers | sort | join}}").render(@context))
82
- assert_equal('alphabetic as expected', Template.parse("{{words | sort | join}}").render(@context))
83
- assert_equal('3', Template.parse("{{value | sort}}").render(@context))
84
- assert_equal('are flower', Template.parse("{{arrays | sort | join}}").render(@context))
85
- assert_equal('Expected case sensitive', Template.parse("{{case_sensitive | sort | join}}").render(@context))
86
- end
87
-
88
- def test_sort_natural
89
- @context['words'] = ['case', 'Assert', 'Insensitive']
90
- @context['hashes'] = [{ 'a' => 'A' }, { 'a' => 'b' }, { 'a' => 'C' }]
91
- @context['objects'] = [TestObject.new('A'), TestObject.new('b'), TestObject.new('C')]
92
-
93
- # Test strings
94
- assert_equal('Assert case Insensitive', Template.parse("{{words | sort_natural | join}}").render(@context))
95
-
96
- # Test hashes
97
- assert_equal('A b C', Template.parse("{{hashes | sort_natural: 'a' | map: 'a' | join}}").render(@context))
98
-
99
- # Test objects
100
- assert_equal('A b C', Template.parse("{{objects | sort_natural: 'a' | map: 'a' | join}}").render(@context))
101
- end
102
-
103
- def test_compact
104
- @context['words'] = ['a', nil, 'b', nil, 'c']
105
- @context['hashes'] = [{ 'a' => 'A' }, { 'a' => nil }, { 'a' => 'C' }]
106
- @context['objects'] = [TestObject.new('A'), TestObject.new(nil), TestObject.new('C')]
107
-
108
- # Test strings
109
- assert_equal('a b c', Template.parse("{{words | compact | join}}").render(@context))
110
-
111
- # Test hashes
112
- assert_equal('A C', Template.parse("{{hashes | compact: 'a' | map: 'a' | join}}").render(@context))
113
-
114
- # Test objects
115
- assert_equal('A C', Template.parse("{{objects | compact: 'a' | map: 'a' | join}}").render(@context))
116
- end
117
-
118
- def test_strip_html
119
- @context['var'] = "<b>bla blub</a>"
120
-
121
- assert_equal("bla blub", Template.parse("{{ var | strip_html }}").render(@context))
122
- end
123
-
124
- def test_strip_html_ignore_comments_with_html
125
- @context['var'] = "<!-- split and some <ul> tag --><b>bla blub</a>"
126
-
127
- assert_equal("bla blub", Template.parse("{{ var | strip_html }}").render(@context))
128
- end
129
-
130
- def test_capitalize
131
- @context['var'] = "blub"
132
-
133
- assert_equal("Blub", Template.parse("{{ var | capitalize }}").render(@context))
134
- end
135
-
136
- def test_nonexistent_filter_is_ignored
137
- @context['var'] = 1000
138
-
139
- assert_equal('1000', Template.parse("{{ var | xyzzy }}").render(@context))
140
- end
141
-
142
- def test_filter_with_keyword_arguments
143
- @context['surname'] = 'john'
144
- @context['input'] = 'hello %{first_name}, %{last_name}'
145
- @context.add_filters(SubstituteFilter)
146
- output = Template.parse(%({{ input | substitute: first_name: surname, last_name: 'doe' }})).render(@context)
147
- assert_equal('hello john, doe', output)
148
- end
149
-
150
- def test_override_object_method_in_filter
151
- assert_equal("tap overridden", Template.parse("{{var | tap}}").render!({ 'var' => 1000 }, filters: [OverrideObjectMethodFilter]))
152
-
153
- # tap still treated as a non-existent filter
154
- assert_equal("1000", Template.parse("{{var | tap}}").render!('var' => 1000))
155
- end
156
-
157
- def test_liquid_argument_error
158
- source = "{{ '' | size: 'too many args' }}"
159
- exc = assert_raises(Liquid::ArgumentError) do
160
- Template.parse(source).render!
161
- end
162
- assert_match(/\ALiquid error: wrong number of arguments /, exc.message)
163
- assert_equal(exc.message, Template.parse(source).render)
164
- end
165
- end
166
-
167
- class FiltersInTemplate < Minitest::Test
168
- include Liquid
169
-
170
- def test_local_global
171
- with_global_filter(MoneyFilter) do
172
- assert_equal(" 1000$ ", Template.parse("{{1000 | money}}").render!(nil, nil))
173
- assert_equal(" 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, filters: CanadianMoneyFilter))
174
- assert_equal(" 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, filters: [CanadianMoneyFilter]))
175
- end
176
- end
177
-
178
- def test_local_filter_with_deprecated_syntax
179
- assert_equal(" 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, CanadianMoneyFilter))
180
- assert_equal(" 1000$ CAD ", Template.parse("{{1000 | money}}").render!(nil, [CanadianMoneyFilter]))
181
- end
182
- end # FiltersTest
183
-
184
- class TestObject < Liquid::Drop
185
- attr_accessor :a
186
- def initialize(a)
187
- @a = a
188
- end
189
- end
@@ -1,25 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'test_helper'
4
-
5
- class HashOrderingTest < Minitest::Test
6
- module MoneyFilter
7
- def money(input)
8
- format(' %d$ ', input)
9
- end
10
- end
11
-
12
- module CanadianMoneyFilter
13
- def money(input)
14
- format(' %d$ CAD ', input)
15
- end
16
- end
17
-
18
- include Liquid
19
-
20
- def test_global_register_order
21
- with_global_filter(MoneyFilter, CanadianMoneyFilter) do
22
- assert_equal(" 1000$ CAD ", Template.parse("{{1000 | money}}").render(nil, nil))
23
- end
24
- end
25
- end
@@ -1,125 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'test_helper'
4
-
5
- module FunnyFilter
6
- def make_funny(_input)
7
- 'LOL'
8
- end
9
-
10
- def cite_funny(input)
11
- "LOL: #{input}"
12
- end
13
-
14
- def add_smiley(input, smiley = ":-)")
15
- "#{input} #{smiley}"
16
- end
17
-
18
- def add_tag(input, tag = "p", id = "foo")
19
- %(<#{tag} id="#{id}">#{input}</#{tag}>)
20
- end
21
-
22
- def paragraph(input)
23
- "<p>#{input}</p>"
24
- end
25
-
26
- def link_to(name, url)
27
- %(<a href="#{url}">#{name}</a>)
28
- end
29
- end
30
-
31
- class OutputTest < Minitest::Test
32
- include Liquid
33
-
34
- def setup
35
- @assigns = {
36
- 'best_cars' => 'bmw',
37
- 'car' => { 'bmw' => 'good', 'gm' => 'bad' },
38
- }
39
- end
40
-
41
- def test_variable
42
- text = %( {{best_cars}} )
43
-
44
- expected = %( bmw )
45
- assert_equal(expected, Template.parse(text).render!(@assigns))
46
- end
47
-
48
- def test_variable_traversing_with_two_brackets
49
- text = %({{ site.data.menu[include.menu][include.locale] }})
50
- assert_equal("it works!", Template.parse(text).render!(
51
- "site" => { "data" => { "menu" => { "foo" => { "bar" => "it works!" } } } },
52
- "include" => { "menu" => "foo", "locale" => "bar" }
53
- ))
54
- end
55
-
56
- def test_variable_traversing
57
- text = %( {{car.bmw}} {{car.gm}} {{car.bmw}} )
58
-
59
- expected = %( good bad good )
60
- assert_equal(expected, Template.parse(text).render!(@assigns))
61
- end
62
-
63
- def test_variable_piping
64
- text = %( {{ car.gm | make_funny }} )
65
- expected = %( LOL )
66
-
67
- assert_equal(expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter]))
68
- end
69
-
70
- def test_variable_piping_with_input
71
- text = %( {{ car.gm | cite_funny }} )
72
- expected = %( LOL: bad )
73
-
74
- assert_equal(expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter]))
75
- end
76
-
77
- def test_variable_piping_with_args
78
- text = %! {{ car.gm | add_smiley : ':-(' }} !
79
- expected = %| bad :-( |
80
-
81
- assert_equal(expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter]))
82
- end
83
-
84
- def test_variable_piping_with_no_args
85
- text = %( {{ car.gm | add_smiley }} )
86
- expected = %| bad :-) |
87
-
88
- assert_equal(expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter]))
89
- end
90
-
91
- def test_multiple_variable_piping_with_args
92
- text = %! {{ car.gm | add_smiley : ':-(' | add_smiley : ':-('}} !
93
- expected = %| bad :-( :-( |
94
-
95
- assert_equal(expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter]))
96
- end
97
-
98
- def test_variable_piping_with_multiple_args
99
- text = %( {{ car.gm | add_tag : 'span', 'bar'}} )
100
- expected = %( <span id="bar">bad</span> )
101
-
102
- assert_equal(expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter]))
103
- end
104
-
105
- def test_variable_piping_with_variable_args
106
- text = %( {{ car.gm | add_tag : 'span', car.bmw}} )
107
- expected = %( <span id="good">bad</span> )
108
-
109
- assert_equal(expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter]))
110
- end
111
-
112
- def test_multiple_pipings
113
- text = %( {{ best_cars | cite_funny | paragraph }} )
114
- expected = %( <p>LOL: bmw</p> )
115
-
116
- assert_equal(expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter]))
117
- end
118
-
119
- def test_link_to
120
- text = %( {{ 'Typo' | link_to: 'http://typo.leetsoft.com' }} )
121
- expected = %( <a href="http://typo.leetsoft.com">Typo</a> )
122
-
123
- assert_equal(expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter]))
124
- end
125
- end # OutputTest
@@ -1,134 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'test_helper'
4
-
5
- class ParsingQuirksTest < Minitest::Test
6
- include Liquid
7
-
8
- def test_parsing_css
9
- text = " div { font-weight: bold; } "
10
- assert_equal(text, Template.parse(text).render!)
11
- end
12
-
13
- def test_raise_on_single_close_bracet
14
- assert_raises(SyntaxError) do
15
- Template.parse("text {{method} oh nos!")
16
- end
17
- end
18
-
19
- def test_raise_on_label_and_no_close_bracets
20
- assert_raises(SyntaxError) do
21
- Template.parse("TEST {{ ")
22
- end
23
- end
24
-
25
- def test_raise_on_label_and_no_close_bracets_percent
26
- assert_raises(SyntaxError) do
27
- Template.parse("TEST {% ")
28
- end
29
- end
30
-
31
- def test_error_on_empty_filter
32
- assert(Template.parse("{{test}}"))
33
-
34
- with_error_mode(:lax) do
35
- assert(Template.parse("{{|test}}"))
36
- end
37
-
38
- with_error_mode(:strict) do
39
- assert_raises(SyntaxError) { Template.parse("{{|test}}") }
40
- assert_raises(SyntaxError) { Template.parse("{{test |a|b|}}") }
41
- end
42
- end
43
-
44
- def test_meaningless_parens_error
45
- with_error_mode(:strict) do
46
- assert_raises(SyntaxError) do
47
- markup = "a == 'foo' or (b == 'bar' and c == 'baz') or false"
48
- Template.parse("{% if #{markup} %} YES {% endif %}")
49
- end
50
- end
51
- end
52
-
53
- def test_unexpected_characters_syntax_error
54
- with_error_mode(:strict) do
55
- assert_raises(SyntaxError) do
56
- markup = "true && false"
57
- Template.parse("{% if #{markup} %} YES {% endif %}")
58
- end
59
- assert_raises(SyntaxError) do
60
- markup = "false || true"
61
- Template.parse("{% if #{markup} %} YES {% endif %}")
62
- end
63
- end
64
- end
65
-
66
- def test_no_error_on_lax_empty_filter
67
- assert(Template.parse("{{test |a|b|}}", error_mode: :lax))
68
- assert(Template.parse("{{test}}", error_mode: :lax))
69
- assert(Template.parse("{{|test|}}", error_mode: :lax))
70
- end
71
-
72
- def test_meaningless_parens_lax
73
- with_error_mode(:lax) do
74
- assigns = { 'b' => 'bar', 'c' => 'baz' }
75
- markup = "a == 'foo' or (b == 'bar' and c == 'baz') or false"
76
- assert_template_result(' YES ', "{% if #{markup} %} YES {% endif %}", assigns)
77
- end
78
- end
79
-
80
- def test_unexpected_characters_silently_eat_logic_lax
81
- with_error_mode(:lax) do
82
- markup = "true && false"
83
- assert_template_result(' YES ', "{% if #{markup} %} YES {% endif %}")
84
- markup = "false || true"
85
- assert_template_result('', "{% if #{markup} %} YES {% endif %}")
86
- end
87
- end
88
-
89
- def test_raise_on_invalid_tag_delimiter
90
- assert_raises(Liquid::SyntaxError) do
91
- Template.new.parse('{% end %}')
92
- end
93
- end
94
-
95
- def test_unanchored_filter_arguments
96
- with_error_mode(:lax) do
97
- assert_template_result('hi', "{{ 'hi there' | split$$$:' ' | first }}")
98
-
99
- assert_template_result('x', "{{ 'X' | downcase) }}")
100
-
101
- # After the messed up quotes a filter without parameters (reverse) should work
102
- # but one with parameters (remove) shouldn't be detected.
103
- assert_template_result('here', "{{ 'hi there' | split:\"t\"\" | reverse | first}}")
104
- assert_template_result('hi ', "{{ 'hi there' | split:\"t\"\" | remove:\"i\" | first}}")
105
- end
106
- end
107
-
108
- def test_invalid_variables_work
109
- with_error_mode(:lax) do
110
- assert_template_result('bar', "{% assign 123foo = 'bar' %}{{ 123foo }}")
111
- assert_template_result('123', "{% assign 123 = 'bar' %}{{ 123 }}")
112
- end
113
- end
114
-
115
- def test_extra_dots_in_ranges
116
- with_error_mode(:lax) do
117
- assert_template_result('12345', "{% for i in (1...5) %}{{ i }}{% endfor %}")
118
- end
119
- end
120
-
121
- def test_blank_variable_markup
122
- assert_template_result('', "{{}}")
123
- end
124
-
125
- def test_lookup_on_var_with_literal_name
126
- assigns = { "blank" => { "x" => "result" } }
127
- assert_template_result('result', "{{ blank.x }}", assigns)
128
- assert_template_result('result', "{{ blank['x'] }}", assigns)
129
- end
130
-
131
- def test_contains_in_id
132
- assert_template_result(' YES ', '{% if containsallshipments == true %} YES {% endif %}', 'containsallshipments' => true)
133
- end
134
- end # ParsingQuirksTest