liquid 3.0.6 → 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 (113) hide show
  1. checksums.yaml +5 -5
  2. data/History.md +243 -58
  3. data/README.md +43 -4
  4. data/lib/liquid/block.rb +57 -123
  5. data/lib/liquid/block_body.rb +217 -85
  6. data/lib/liquid/condition.rb +92 -45
  7. data/lib/liquid/context.rb +154 -89
  8. data/lib/liquid/document.rb +57 -9
  9. data/lib/liquid/drop.rb +20 -17
  10. data/lib/liquid/errors.rb +27 -29
  11. data/lib/liquid/expression.rb +32 -20
  12. data/lib/liquid/extensions.rb +21 -7
  13. data/lib/liquid/file_system.rb +17 -15
  14. data/lib/liquid/forloop_drop.rb +92 -0
  15. data/lib/liquid/i18n.rb +10 -8
  16. data/lib/liquid/interrupts.rb +4 -3
  17. data/lib/liquid/lexer.rb +37 -26
  18. data/lib/liquid/locales/en.yml +13 -6
  19. data/lib/liquid/parse_context.rb +54 -0
  20. data/lib/liquid/parse_tree_visitor.rb +42 -0
  21. data/lib/liquid/parser.rb +30 -18
  22. data/lib/liquid/parser_switching.rb +20 -6
  23. data/lib/liquid/partial_cache.rb +24 -0
  24. data/lib/liquid/profiler/hooks.rb +26 -14
  25. data/lib/liquid/profiler.rb +72 -92
  26. data/lib/liquid/range_lookup.rb +28 -3
  27. data/lib/liquid/registers.rb +51 -0
  28. data/lib/liquid/resource_limits.rb +62 -0
  29. data/lib/liquid/standardfilters.rb +715 -132
  30. data/lib/liquid/strainer_factory.rb +41 -0
  31. data/lib/liquid/strainer_template.rb +62 -0
  32. data/lib/liquid/tablerowloop_drop.rb +121 -0
  33. data/lib/liquid/tag/disableable.rb +22 -0
  34. data/lib/liquid/tag/disabler.rb +21 -0
  35. data/lib/liquid/tag.rb +35 -12
  36. data/lib/liquid/tags/assign.rb +57 -18
  37. data/lib/liquid/tags/break.rb +15 -5
  38. data/lib/liquid/tags/capture.rb +24 -18
  39. data/lib/liquid/tags/case.rb +79 -30
  40. data/lib/liquid/tags/comment.rb +19 -4
  41. data/lib/liquid/tags/continue.rb +16 -12
  42. data/lib/liquid/tags/cycle.rb +47 -27
  43. data/lib/liquid/tags/decrement.rb +23 -24
  44. data/lib/liquid/tags/echo.rb +41 -0
  45. data/lib/liquid/tags/for.rb +155 -124
  46. data/lib/liquid/tags/if.rb +97 -63
  47. data/lib/liquid/tags/ifchanged.rb +11 -12
  48. data/lib/liquid/tags/include.rb +82 -73
  49. data/lib/liquid/tags/increment.rb +23 -17
  50. data/lib/liquid/tags/inline_comment.rb +43 -0
  51. data/lib/liquid/tags/raw.rb +50 -8
  52. data/lib/liquid/tags/render.rb +109 -0
  53. data/lib/liquid/tags/table_row.rb +57 -41
  54. data/lib/liquid/tags/unless.rb +38 -20
  55. data/lib/liquid/template.rb +71 -103
  56. data/lib/liquid/template_factory.rb +9 -0
  57. data/lib/liquid/tokenizer.rb +39 -0
  58. data/lib/liquid/usage.rb +8 -0
  59. data/lib/liquid/utils.rb +63 -9
  60. data/lib/liquid/variable.rb +74 -56
  61. data/lib/liquid/variable_lookup.rb +31 -15
  62. data/lib/liquid/version.rb +3 -1
  63. data/lib/liquid.rb +27 -12
  64. metadata +30 -106
  65. data/lib/liquid/module_ex.rb +0 -62
  66. data/lib/liquid/strainer.rb +0 -59
  67. data/lib/liquid/token.rb +0 -18
  68. data/test/fixtures/en_locale.yml +0 -9
  69. data/test/integration/assign_test.rb +0 -48
  70. data/test/integration/blank_test.rb +0 -106
  71. data/test/integration/capture_test.rb +0 -50
  72. data/test/integration/context_test.rb +0 -32
  73. data/test/integration/drop_test.rb +0 -271
  74. data/test/integration/error_handling_test.rb +0 -207
  75. data/test/integration/filter_test.rb +0 -138
  76. data/test/integration/hash_ordering_test.rb +0 -23
  77. data/test/integration/output_test.rb +0 -124
  78. data/test/integration/parsing_quirks_test.rb +0 -116
  79. data/test/integration/render_profiling_test.rb +0 -154
  80. data/test/integration/security_test.rb +0 -64
  81. data/test/integration/standard_filter_test.rb +0 -396
  82. data/test/integration/tags/break_tag_test.rb +0 -16
  83. data/test/integration/tags/continue_tag_test.rb +0 -16
  84. data/test/integration/tags/for_tag_test.rb +0 -375
  85. data/test/integration/tags/if_else_tag_test.rb +0 -190
  86. data/test/integration/tags/include_tag_test.rb +0 -234
  87. data/test/integration/tags/increment_tag_test.rb +0 -24
  88. data/test/integration/tags/raw_tag_test.rb +0 -25
  89. data/test/integration/tags/standard_tag_test.rb +0 -297
  90. data/test/integration/tags/statements_test.rb +0 -113
  91. data/test/integration/tags/table_row_test.rb +0 -63
  92. data/test/integration/tags/unless_else_tag_test.rb +0 -26
  93. data/test/integration/template_test.rb +0 -182
  94. data/test/integration/variable_test.rb +0 -82
  95. data/test/test_helper.rb +0 -89
  96. data/test/unit/block_unit_test.rb +0 -55
  97. data/test/unit/condition_unit_test.rb +0 -149
  98. data/test/unit/context_unit_test.rb +0 -492
  99. data/test/unit/file_system_unit_test.rb +0 -35
  100. data/test/unit/i18n_unit_test.rb +0 -37
  101. data/test/unit/lexer_unit_test.rb +0 -48
  102. data/test/unit/module_ex_unit_test.rb +0 -87
  103. data/test/unit/parser_unit_test.rb +0 -82
  104. data/test/unit/regexp_unit_test.rb +0 -44
  105. data/test/unit/strainer_unit_test.rb +0 -69
  106. data/test/unit/tag_unit_test.rb +0 -16
  107. data/test/unit/tags/case_tag_unit_test.rb +0 -10
  108. data/test/unit/tags/for_tag_unit_test.rb +0 -13
  109. data/test/unit/tags/if_tag_unit_test.rb +0 -8
  110. data/test/unit/template_unit_test.rb +0 -69
  111. data/test/unit/tokenizer_unit_test.rb +0 -38
  112. data/test/unit/variable_unit_test.rb +0 -145
  113. /data/{MIT-LICENSE → LICENSE} +0 -0
@@ -1,124 +0,0 @@
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
-
28
- end
29
-
30
- class OutputTest < Minitest::Test
31
- include Liquid
32
-
33
- def setup
34
- @assigns = {
35
- 'best_cars' => 'bmw',
36
- 'car' => {'bmw' => 'good', 'gm' => 'bad'}
37
- }
38
- end
39
-
40
- def test_variable
41
- text = %| {{best_cars}} |
42
-
43
- expected = %| bmw |
44
- assert_equal expected, Template.parse(text).render!(@assigns)
45
- end
46
-
47
- def test_variable_traversing_with_two_brackets
48
- text = %({{ site.data.menu[include.menu][include.locale] }})
49
- assert_equal "it works!", Template.parse(text).render!(
50
- "site" => { "data" => { "menu" => { "foo" => { "bar" => "it works!" } } } },
51
- "include" => { "menu" => "foo", "locale" => "bar" }
52
- )
53
- end
54
-
55
- def test_variable_traversing
56
- text = %| {{car.bmw}} {{car.gm}} {{car.bmw}} |
57
-
58
- expected = %| good bad good |
59
- assert_equal expected, Template.parse(text).render!(@assigns)
60
- end
61
-
62
- def test_variable_piping
63
- text = %( {{ car.gm | make_funny }} )
64
- expected = %| LOL |
65
-
66
- assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter])
67
- end
68
-
69
- def test_variable_piping_with_input
70
- text = %( {{ car.gm | cite_funny }} )
71
- expected = %| LOL: bad |
72
-
73
- assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter])
74
- end
75
-
76
- def test_variable_piping_with_args
77
- text = %! {{ car.gm | add_smiley : ':-(' }} !
78
- expected = %| bad :-( |
79
-
80
- assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter])
81
- end
82
-
83
- def test_variable_piping_with_no_args
84
- text = %! {{ car.gm | add_smiley }} !
85
- expected = %| bad :-) |
86
-
87
- assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter])
88
- end
89
-
90
- def test_multiple_variable_piping_with_args
91
- text = %! {{ car.gm | add_smiley : ':-(' | add_smiley : ':-('}} !
92
- expected = %| bad :-( :-( |
93
-
94
- assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter])
95
- end
96
-
97
- def test_variable_piping_with_multiple_args
98
- text = %! {{ car.gm | add_tag : 'span', 'bar'}} !
99
- expected = %| <span id="bar">bad</span> |
100
-
101
- assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter])
102
- end
103
-
104
- def test_variable_piping_with_variable_args
105
- text = %! {{ car.gm | add_tag : 'span', car.bmw}} !
106
- expected = %| <span id="good">bad</span> |
107
-
108
- assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter])
109
- end
110
-
111
- def test_multiple_pipings
112
- text = %( {{ best_cars | cite_funny | paragraph }} )
113
- expected = %| <p>LOL: bmw</p> |
114
-
115
- assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter])
116
- end
117
-
118
- def test_link_to
119
- text = %( {{ 'Typo' | link_to: 'http://typo.leetsoft.com' }} )
120
- expected = %| <a href="http://typo.leetsoft.com">Typo</a> |
121
-
122
- assert_equal expected, Template.parse(text).render!(@assigns, :filters => [FunnyFilter])
123
- end
124
- end # OutputTest
@@ -1,116 +0,0 @@
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
- assert Template.parse("{{|test}}")
32
- with_error_mode(:strict) do
33
- assert_raises(SyntaxError) do
34
- Template.parse("{{test |a|b|}}")
35
- end
36
- end
37
- end
38
-
39
- def test_meaningless_parens_error
40
- with_error_mode(:strict) do
41
- assert_raises(SyntaxError) do
42
- markup = "a == 'foo' or (b == 'bar' and c == 'baz') or false"
43
- Template.parse("{% if #{markup} %} YES {% endif %}")
44
- end
45
- end
46
- end
47
-
48
- def test_unexpected_characters_syntax_error
49
- with_error_mode(:strict) do
50
- assert_raises(SyntaxError) do
51
- markup = "true && false"
52
- Template.parse("{% if #{markup} %} YES {% endif %}")
53
- end
54
- assert_raises(SyntaxError) do
55
- markup = "false || true"
56
- Template.parse("{% if #{markup} %} YES {% endif %}")
57
- end
58
- end
59
- end
60
-
61
- def test_no_error_on_lax_empty_filter
62
- assert Template.parse("{{test |a|b|}}", :error_mode => :lax)
63
- assert Template.parse("{{test}}", :error_mode => :lax)
64
- assert Template.parse("{{|test|}}", :error_mode => :lax)
65
- end
66
-
67
- def test_meaningless_parens_lax
68
- with_error_mode(:lax) do
69
- assigns = {'b' => 'bar', 'c' => 'baz'}
70
- markup = "a == 'foo' or (b == 'bar' and c == 'baz') or false"
71
- assert_template_result(' YES ',"{% if #{markup} %} YES {% endif %}", assigns)
72
- end
73
- end
74
-
75
- def test_unexpected_characters_silently_eat_logic_lax
76
- with_error_mode(:lax) do
77
- markup = "true && false"
78
- assert_template_result(' YES ',"{% if #{markup} %} YES {% endif %}")
79
- markup = "false || true"
80
- assert_template_result('',"{% if #{markup} %} YES {% endif %}")
81
- end
82
- end
83
-
84
- def test_raise_on_invalid_tag_delimiter
85
- assert_raises(Liquid::SyntaxError) do
86
- Template.new.parse('{% end %}')
87
- end
88
- end
89
-
90
- def test_unanchored_filter_arguments
91
- with_error_mode(:lax) do
92
- assert_template_result('hi',"{{ 'hi there' | split$$$:' ' | first }}")
93
-
94
- assert_template_result('x', "{{ 'X' | downcase) }}")
95
-
96
- # After the messed up quotes a filter without parameters (reverse) should work
97
- # but one with parameters (remove) shouldn't be detected.
98
- assert_template_result('here', "{{ 'hi there' | split:\"t\"\" | reverse | first}}")
99
- assert_template_result('hi ', "{{ 'hi there' | split:\"t\"\" | remove:\"i\" | first}}")
100
- end
101
- end
102
-
103
- def test_invalid_variables_work
104
- with_error_mode(:lax) do
105
- assert_template_result('bar', "{% assign 123foo = 'bar' %}{{ 123foo }}")
106
- assert_template_result('123', "{% assign 123 = 'bar' %}{{ 123 }}")
107
- end
108
- end
109
-
110
- def test_extra_dots_in_ranges
111
- with_error_mode(:lax) do
112
- assert_template_result('12345', "{% for i in (1...5) %}{{ i }}{% endfor %}")
113
- end
114
- end
115
-
116
- end # ParsingQuirksTest
@@ -1,154 +0,0 @@
1
- require 'test_helper'
2
-
3
- class RenderProfilingTest < Minitest::Test
4
- include Liquid
5
-
6
- class ProfilingFileSystem
7
- def read_template_file(template_path, context)
8
- "Rendering template {% assign template_name = '#{template_path}'%}\n{{ template_name }}"
9
- end
10
- end
11
-
12
- def setup
13
- Liquid::Template.file_system = ProfilingFileSystem.new
14
- end
15
-
16
- def test_template_allows_flagging_profiling
17
- t = Template.parse("{{ 'a string' | upcase }}")
18
- t.render!
19
-
20
- assert_nil t.profiler
21
- end
22
-
23
- def test_parse_makes_available_simple_profiling
24
- t = Template.parse("{{ 'a string' | upcase }}", :profile => true)
25
- t.render!
26
-
27
- assert_equal 1, t.profiler.length
28
-
29
- node = t.profiler[0]
30
- assert_equal " 'a string' | upcase ", node.code
31
- end
32
-
33
- def test_render_ignores_raw_strings_when_profiling
34
- t = Template.parse("This is raw string\nstuff\nNewline", :profile => true)
35
- t.render!
36
-
37
- assert_equal 0, t.profiler.length
38
- end
39
-
40
- def test_profiling_includes_line_numbers_of_liquid_nodes
41
- t = Template.parse("{{ 'a string' | upcase }}\n{% increment test %}", :profile => true)
42
- t.render!
43
- assert_equal 2, t.profiler.length
44
-
45
- # {{ 'a string' | upcase }}
46
- assert_equal 1, t.profiler[0].line_number
47
- # {{ increment test }}
48
- assert_equal 2, t.profiler[1].line_number
49
- end
50
-
51
- def test_profiling_includes_line_numbers_of_included_partials
52
- t = Template.parse("{% include 'a_template' %}", :profile => true)
53
- t.render!
54
-
55
- included_children = t.profiler[0].children
56
-
57
- # {% assign template_name = 'a_template' %}
58
- assert_equal 1, included_children[0].line_number
59
- # {{ template_name }}
60
- assert_equal 2, included_children[1].line_number
61
- end
62
-
63
- def test_profiling_times_the_rendering_of_tokens
64
- t = Template.parse("{% include 'a_template' %}", :profile => true)
65
- t.render!
66
-
67
- node = t.profiler[0]
68
- refute_nil node.render_time
69
- end
70
-
71
- def test_profiling_times_the_entire_render
72
- t = Template.parse("{% include 'a_template' %}", :profile => true)
73
- t.render!
74
-
75
- assert t.profiler.total_render_time >= 0, "Total render time was not calculated"
76
- end
77
-
78
- def test_profiling_uses_include_to_mark_children
79
- t = Template.parse("{{ 'a string' | upcase }}\n{% include 'a_template' %}", :profile => true)
80
- t.render!
81
-
82
- include_node = t.profiler[1]
83
- assert_equal 2, include_node.children.length
84
- end
85
-
86
- def test_profiling_marks_children_with_the_name_of_included_partial
87
- t = Template.parse("{{ 'a string' | upcase }}\n{% include 'a_template' %}", :profile => true)
88
- t.render!
89
-
90
- include_node = t.profiler[1]
91
- include_node.children.each do |child|
92
- assert_equal "'a_template'", child.partial
93
- end
94
- end
95
-
96
- def test_profiling_supports_multiple_templates
97
- t = Template.parse("{{ 'a string' | upcase }}\n{% include 'a_template' %}\n{% include 'b_template' %}", :profile => true)
98
- t.render!
99
-
100
- a_template = t.profiler[1]
101
- a_template.children.each do |child|
102
- assert_equal "'a_template'", child.partial
103
- end
104
-
105
- b_template = t.profiler[2]
106
- b_template.children.each do |child|
107
- assert_equal "'b_template'", child.partial
108
- end
109
- end
110
-
111
- def test_profiling_supports_rendering_the_same_partial_multiple_times
112
- t = Template.parse("{{ 'a string' | upcase }}\n{% include 'a_template' %}\n{% include 'a_template' %}", :profile => true)
113
- t.render!
114
-
115
- a_template1 = t.profiler[1]
116
- a_template1.children.each do |child|
117
- assert_equal "'a_template'", child.partial
118
- end
119
-
120
- a_template2 = t.profiler[2]
121
- a_template2.children.each do |child|
122
- assert_equal "'a_template'", child.partial
123
- end
124
- end
125
-
126
- def test_can_iterate_over_each_profiling_entry
127
- t = Template.parse("{{ 'a string' | upcase }}\n{% increment test %}", :profile => true)
128
- t.render!
129
-
130
- timing_count = 0
131
- t.profiler.each do |timing|
132
- timing_count += 1
133
- end
134
-
135
- assert_equal 2, timing_count
136
- end
137
-
138
- def test_profiling_marks_children_of_if_blocks
139
- t = Template.parse("{% if true %} {% increment test %} {{ test }} {% endif %}", :profile => true)
140
- t.render!
141
-
142
- assert_equal 1, t.profiler.length
143
- assert_equal 2, t.profiler[0].children.length
144
- end
145
-
146
- def test_profiling_marks_children_of_for_blocks
147
- t = Template.parse("{% for item in collection %} {{ item }} {% endfor %}", :profile => true)
148
- t.render!({"collection" => ["one", "two"]})
149
-
150
- assert_equal 1, t.profiler.length
151
- # Will profile each invocation of the for block
152
- assert_equal 2, t.profiler[0].children.length
153
- end
154
- end
@@ -1,64 +0,0 @@
1
- require 'test_helper'
2
-
3
- module SecurityFilter
4
- def add_one(input)
5
- "#{input} + 1"
6
- end
7
- end
8
-
9
- class SecurityTest < Minitest::Test
10
- include Liquid
11
-
12
- def test_no_instance_eval
13
- text = %( {{ '1+1' | instance_eval }} )
14
- expected = %| 1+1 |
15
-
16
- assert_equal expected, Template.parse(text).render!(@assigns)
17
- end
18
-
19
- def test_no_existing_instance_eval
20
- text = %( {{ '1+1' | __instance_eval__ }} )
21
- expected = %| 1+1 |
22
-
23
- assert_equal expected, Template.parse(text).render!(@assigns)
24
- end
25
-
26
-
27
- def test_no_instance_eval_after_mixing_in_new_filter
28
- text = %( {{ '1+1' | instance_eval }} )
29
- expected = %| 1+1 |
30
-
31
- assert_equal expected, Template.parse(text).render!(@assigns)
32
- end
33
-
34
-
35
- def test_no_instance_eval_later_in_chain
36
- text = %( {{ '1+1' | add_one | instance_eval }} )
37
- expected = %| 1+1 + 1 |
38
-
39
- assert_equal expected, Template.parse(text).render!(@assigns, :filters => SecurityFilter)
40
- end
41
-
42
- def test_does_not_add_filters_to_symbol_table
43
- current_symbols = Symbol.all_symbols
44
-
45
- test = %( {{ "some_string" | a_bad_filter }} )
46
-
47
- template = Template.parse(test)
48
- assert_equal [], (Symbol.all_symbols - current_symbols)
49
-
50
- template.render!
51
- assert_equal [], (Symbol.all_symbols - current_symbols)
52
- end
53
-
54
- def test_does_not_add_drop_methods_to_symbol_table
55
- current_symbols = Symbol.all_symbols
56
-
57
- assigns = { 'drop' => Drop.new }
58
- assert_equal "", Template.parse("{{ drop.custom_method_1 }}", assigns).render!
59
- assert_equal "", Template.parse("{{ drop.custom_method_2 }}", assigns).render!
60
- assert_equal "", Template.parse("{{ drop.custom_method_3 }}", assigns).render!
61
-
62
- assert_equal [], (Symbol.all_symbols - current_symbols)
63
- end
64
- end # SecurityTest