liquid 4.0.4 → 5.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 (123) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +32 -4
  3. data/README.md +6 -0
  4. data/lib/liquid/block.rb +31 -14
  5. data/lib/liquid/block_body.rb +164 -54
  6. data/lib/liquid/condition.rb +39 -18
  7. data/lib/liquid/context.rb +106 -51
  8. data/lib/liquid/document.rb +47 -9
  9. data/lib/liquid/drop.rb +4 -2
  10. data/lib/liquid/errors.rb +20 -18
  11. data/lib/liquid/expression.rb +29 -34
  12. data/lib/liquid/extensions.rb +2 -0
  13. data/lib/liquid/file_system.rb +6 -4
  14. data/lib/liquid/forloop_drop.rb +11 -4
  15. data/lib/liquid/i18n.rb +5 -3
  16. data/lib/liquid/interrupts.rb +3 -1
  17. data/lib/liquid/lexer.rb +30 -23
  18. data/lib/liquid/locales/en.yml +3 -1
  19. data/lib/liquid/parse_context.rb +16 -4
  20. data/lib/liquid/parse_tree_visitor.rb +2 -2
  21. data/lib/liquid/parser.rb +30 -18
  22. data/lib/liquid/parser_switching.rb +17 -3
  23. data/lib/liquid/partial_cache.rb +24 -0
  24. data/lib/liquid/profiler/hooks.rb +26 -14
  25. data/lib/liquid/profiler.rb +67 -86
  26. data/lib/liquid/range_lookup.rb +5 -3
  27. data/lib/liquid/register.rb +6 -0
  28. data/lib/liquid/resource_limits.rb +47 -8
  29. data/lib/liquid/standardfilters.rb +62 -43
  30. data/lib/liquid/static_registers.rb +44 -0
  31. data/lib/liquid/strainer_factory.rb +36 -0
  32. data/lib/liquid/strainer_template.rb +53 -0
  33. data/lib/liquid/tablerowloop_drop.rb +6 -4
  34. data/lib/liquid/tag/disableable.rb +22 -0
  35. data/lib/liquid/tag/disabler.rb +21 -0
  36. data/lib/liquid/tag.rb +28 -6
  37. data/lib/liquid/tags/assign.rb +24 -10
  38. data/lib/liquid/tags/break.rb +8 -3
  39. data/lib/liquid/tags/capture.rb +11 -8
  40. data/lib/liquid/tags/case.rb +33 -27
  41. data/lib/liquid/tags/comment.rb +5 -3
  42. data/lib/liquid/tags/continue.rb +8 -3
  43. data/lib/liquid/tags/cycle.rb +25 -14
  44. data/lib/liquid/tags/decrement.rb +6 -3
  45. data/lib/liquid/tags/echo.rb +26 -0
  46. data/lib/liquid/tags/for.rb +68 -44
  47. data/lib/liquid/tags/if.rb +35 -23
  48. data/lib/liquid/tags/ifchanged.rb +11 -10
  49. data/lib/liquid/tags/include.rb +34 -47
  50. data/lib/liquid/tags/increment.rb +7 -3
  51. data/lib/liquid/tags/raw.rb +14 -11
  52. data/lib/liquid/tags/render.rb +84 -0
  53. data/lib/liquid/tags/table_row.rb +23 -19
  54. data/lib/liquid/tags/unless.rb +15 -15
  55. data/lib/liquid/template.rb +55 -69
  56. data/lib/liquid/template_factory.rb +9 -0
  57. data/lib/liquid/tokenizer.rb +17 -9
  58. data/lib/liquid/usage.rb +8 -0
  59. data/lib/liquid/utils.rb +5 -3
  60. data/lib/liquid/variable.rb +47 -19
  61. data/lib/liquid/variable_lookup.rb +8 -6
  62. data/lib/liquid/version.rb +2 -1
  63. data/lib/liquid.rb +17 -5
  64. data/test/integration/assign_test.rb +74 -5
  65. data/test/integration/blank_test.rb +11 -8
  66. data/test/integration/block_test.rb +47 -1
  67. data/test/integration/capture_test.rb +18 -10
  68. data/test/integration/context_test.rb +608 -5
  69. data/test/integration/document_test.rb +4 -2
  70. data/test/integration/drop_test.rb +67 -57
  71. data/test/integration/error_handling_test.rb +73 -61
  72. data/test/integration/expression_test.rb +46 -0
  73. data/test/integration/filter_test.rb +53 -42
  74. data/test/integration/hash_ordering_test.rb +5 -3
  75. data/test/integration/output_test.rb +26 -24
  76. data/test/integration/parsing_quirks_test.rb +19 -7
  77. data/test/integration/{render_profiling_test.rb → profiler_test.rb} +84 -25
  78. data/test/integration/security_test.rb +30 -21
  79. data/test/integration/standard_filter_test.rb +339 -281
  80. data/test/integration/tag/disableable_test.rb +59 -0
  81. data/test/integration/tag_test.rb +45 -0
  82. data/test/integration/tags/break_tag_test.rb +4 -2
  83. data/test/integration/tags/continue_tag_test.rb +4 -2
  84. data/test/integration/tags/echo_test.rb +13 -0
  85. data/test/integration/tags/for_tag_test.rb +107 -51
  86. data/test/integration/tags/if_else_tag_test.rb +5 -3
  87. data/test/integration/tags/include_tag_test.rb +70 -54
  88. data/test/integration/tags/increment_tag_test.rb +4 -2
  89. data/test/integration/tags/liquid_tag_test.rb +116 -0
  90. data/test/integration/tags/raw_tag_test.rb +14 -11
  91. data/test/integration/tags/render_tag_test.rb +213 -0
  92. data/test/integration/tags/standard_tag_test.rb +38 -31
  93. data/test/integration/tags/statements_test.rb +23 -21
  94. data/test/integration/tags/table_row_test.rb +2 -0
  95. data/test/integration/tags/unless_else_tag_test.rb +4 -2
  96. data/test/integration/template_test.rb +118 -124
  97. data/test/integration/trim_mode_test.rb +78 -44
  98. data/test/integration/variable_test.rb +43 -32
  99. data/test/test_helper.rb +75 -14
  100. data/test/unit/block_unit_test.rb +19 -24
  101. data/test/unit/condition_unit_test.rb +79 -77
  102. data/test/unit/file_system_unit_test.rb +6 -4
  103. data/test/unit/i18n_unit_test.rb +7 -5
  104. data/test/unit/lexer_unit_test.rb +11 -9
  105. data/test/{integration → unit}/parse_tree_visitor_test.rb +1 -1
  106. data/test/unit/parser_unit_test.rb +37 -35
  107. data/test/unit/partial_cache_unit_test.rb +128 -0
  108. data/test/unit/regexp_unit_test.rb +17 -15
  109. data/test/unit/static_registers_unit_test.rb +156 -0
  110. data/test/unit/strainer_factory_unit_test.rb +100 -0
  111. data/test/unit/strainer_template_unit_test.rb +82 -0
  112. data/test/unit/tag_unit_test.rb +5 -3
  113. data/test/unit/tags/case_tag_unit_test.rb +3 -1
  114. data/test/unit/tags/for_tag_unit_test.rb +4 -2
  115. data/test/unit/tags/if_tag_unit_test.rb +3 -1
  116. data/test/unit/template_factory_unit_test.rb +12 -0
  117. data/test/unit/template_unit_test.rb +19 -10
  118. data/test/unit/tokenizer_unit_test.rb +19 -17
  119. data/test/unit/variable_unit_test.rb +51 -49
  120. metadata +75 -47
  121. data/lib/liquid/strainer.rb +0 -66
  122. data/test/unit/context_unit_test.rb +0 -490
  123. data/test/unit/strainer_unit_test.rb +0 -164
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
4
  # Include allows templates to relate with other templates
3
5
  #
@@ -14,40 +16,49 @@ module Liquid
14
16
  # {% include 'product' for products %}
15
17
  #
16
18
  class Include < Tag
17
- Syntax = /(#{QuotedFragment}+)(\s+(?:with|for)\s+(#{QuotedFragment}+))?/o
19
+ prepend Tag::Disableable
20
+
21
+ SYNTAX = /(#{QuotedFragment}+)(\s+(?:with|for)\s+(#{QuotedFragment}+))?(\s+(?:as)\s+(#{VariableSegment}+))?/o
22
+ Syntax = SYNTAX
18
23
 
19
24
  attr_reader :template_name_expr, :variable_name_expr, :attributes
20
25
 
21
26
  def initialize(tag_name, markup, options)
22
27
  super
23
28
 
24
- if markup =~ Syntax
29
+ if markup =~ SYNTAX
25
30
 
26
- template_name = $1
27
- variable_name = $3
31
+ template_name = Regexp.last_match(1)
32
+ variable_name = Regexp.last_match(3)
28
33
 
29
- @variable_name_expr = variable_name ? Expression.parse(variable_name) : nil
30
- @template_name_expr = Expression.parse(template_name)
31
- @attributes = {}
34
+ @alias_name = Regexp.last_match(5)
35
+ @variable_name_expr = variable_name ? parse_expression(variable_name) : nil
36
+ @template_name_expr = parse_expression(template_name)
37
+ @attributes = {}
32
38
 
33
39
  markup.scan(TagAttributes) do |key, value|
34
- @attributes[key] = Expression.parse(value)
40
+ @attributes[key] = parse_expression(value)
35
41
  end
36
42
 
37
43
  else
38
- raise SyntaxError.new(options[:locale].t("errors.syntax.include".freeze))
44
+ raise SyntaxError, options[:locale].t("errors.syntax.include")
39
45
  end
40
46
  end
41
47
 
42
48
  def parse(_tokens)
43
49
  end
44
50
 
45
- def render(context)
51
+ def render_to_output_buffer(context, output)
46
52
  template_name = context.evaluate(@template_name_expr)
47
- raise ArgumentError.new(options[:locale].t("errors.argument.include")) unless template_name
53
+ raise ArgumentError, options[:locale].t("errors.argument.include") unless template_name
48
54
 
49
- partial = load_cached_partial(template_name, context)
50
- context_variable_name = template_name.split('/'.freeze).last
55
+ partial = PartialCache.load(
56
+ template_name,
57
+ context: context,
58
+ parse_context: parse_context
59
+ )
60
+
61
+ context_variable_name = @alias_name || template_name.split('/').last
51
62
 
52
63
  variable = if @variable_name_expr
53
64
  context.evaluate(@variable_name_expr)
@@ -56,69 +67,45 @@ module Liquid
56
67
  end
57
68
 
58
69
  old_template_name = context.template_name
59
- old_partial = context.partial
70
+ old_partial = context.partial
60
71
  begin
61
72
  context.template_name = template_name
62
- context.partial = true
73
+ context.partial = true
63
74
  context.stack do
64
75
  @attributes.each do |key, value|
65
76
  context[key] = context.evaluate(value)
66
77
  end
67
78
 
68
79
  if variable.is_a?(Array)
69
- variable.collect do |var|
80
+ variable.each do |var|
70
81
  context[context_variable_name] = var
71
- partial.render(context)
82
+ partial.render_to_output_buffer(context, output)
72
83
  end
73
84
  else
74
85
  context[context_variable_name] = variable
75
- partial.render(context)
86
+ partial.render_to_output_buffer(context, output)
76
87
  end
77
88
  end
78
89
  ensure
79
90
  context.template_name = old_template_name
80
- context.partial = old_partial
91
+ context.partial = old_partial
81
92
  end
82
- end
83
93
 
84
- private
94
+ output
95
+ end
85
96
 
86
97
  alias_method :parse_context, :options
87
98
  private :parse_context
88
99
 
89
- def load_cached_partial(template_name, context)
90
- cached_partials = context.registers[:cached_partials] || {}
91
-
92
- if cached = cached_partials[template_name]
93
- return cached
94
- end
95
- source = read_template_from_file_system(context)
96
- begin
97
- parse_context.partial = true
98
- partial = Liquid::Template.parse(source, parse_context)
99
- ensure
100
- parse_context.partial = false
101
- end
102
- cached_partials[template_name] = partial
103
- context.registers[:cached_partials] = cached_partials
104
- partial
105
- end
106
-
107
- def read_template_from_file_system(context)
108
- file_system = context.registers[:file_system] || Liquid::Template.file_system
109
-
110
- file_system.read_template_file(context.evaluate(@template_name_expr))
111
- end
112
-
113
100
  class ParseTreeVisitor < Liquid::ParseTreeVisitor
114
101
  def children
115
102
  [
116
103
  @node.template_name_expr,
117
- @node.variable_name_expr
104
+ @node.variable_name_expr,
118
105
  ] + @node.attributes.values
119
106
  end
120
107
  end
121
108
  end
122
109
 
123
- Template.register_tag('include'.freeze, Include)
110
+ Template.register_tag('include', Include)
124
111
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
4
  # increment is used in a place where one needs to insert a counter
3
5
  # into a template, and needs the counter to survive across
@@ -20,12 +22,14 @@ module Liquid
20
22
  @variable = markup.strip
21
23
  end
22
24
 
23
- def render(context)
25
+ def render_to_output_buffer(context, output)
24
26
  value = context.environments.first[@variable] ||= 0
25
27
  context.environments.first[@variable] = value + 1
26
- value.to_s
28
+
29
+ output << value.to_s
30
+ output
27
31
  end
28
32
  end
29
33
 
30
- Template.register_tag('increment'.freeze, Increment)
34
+ Template.register_tag('increment', Increment)
31
35
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
4
  class Raw < Block
3
5
  Syntax = /\A\s*\z/
@@ -10,20 +12,21 @@ module Liquid
10
12
  end
11
13
 
12
14
  def parse(tokens)
13
- @body = ''
14
- while token = tokens.shift
15
- if token =~ FullTokenPossiblyInvalid
16
- @body << $1 if $1 != "".freeze
17
- return if block_delimiter == $2
15
+ @body = +''
16
+ while (token = tokens.shift)
17
+ if token =~ FullTokenPossiblyInvalid && block_delimiter == Regexp.last_match(2)
18
+ @body << Regexp.last_match(1) if Regexp.last_match(1) != ""
19
+ return
18
20
  end
19
21
  @body << token unless token.empty?
20
22
  end
21
23
 
22
- raise SyntaxError.new(parse_context.locale.t("errors.syntax.tag_never_closed".freeze, block_name: block_name))
24
+ raise_tag_never_closed(block_name)
23
25
  end
24
26
 
25
- def render(_context)
26
- @body
27
+ def render_to_output_buffer(_context, output)
28
+ output << @body
29
+ output
27
30
  end
28
31
 
29
32
  def nodelist
@@ -37,11 +40,11 @@ module Liquid
37
40
  protected
38
41
 
39
42
  def ensure_valid_markup(tag_name, markup, parse_context)
40
- unless markup =~ Syntax
41
- raise SyntaxError.new(parse_context.locale.t("errors.syntax.tag_unexpected_args".freeze, tag: tag_name))
43
+ unless Syntax.match?(markup)
44
+ raise SyntaxError, parse_context.locale.t("errors.syntax.tag_unexpected_args", tag: tag_name)
42
45
  end
43
46
  end
44
47
  end
45
48
 
46
- Template.register_tag('raw'.freeze, Raw)
49
+ Template.register_tag('raw', Raw)
47
50
  end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Liquid
4
+ class Render < Tag
5
+ FOR = 'for'
6
+ SYNTAX = /(#{QuotedString}+)(\s+(with|#{FOR})\s+(#{QuotedFragment}+))?(\s+(?:as)\s+(#{VariableSegment}+))?/o
7
+
8
+ disable_tags "include"
9
+
10
+ attr_reader :template_name_expr, :attributes
11
+
12
+ def initialize(tag_name, markup, options)
13
+ super
14
+
15
+ raise SyntaxError, options[:locale].t("errors.syntax.render") unless markup =~ SYNTAX
16
+
17
+ template_name = Regexp.last_match(1)
18
+ with_or_for = Regexp.last_match(3)
19
+ variable_name = Regexp.last_match(4)
20
+
21
+ @alias_name = Regexp.last_match(6)
22
+ @variable_name_expr = variable_name ? parse_expression(variable_name) : nil
23
+ @template_name_expr = parse_expression(template_name)
24
+ @for = (with_or_for == FOR)
25
+
26
+ @attributes = {}
27
+ markup.scan(TagAttributes) do |key, value|
28
+ @attributes[key] = parse_expression(value)
29
+ end
30
+ end
31
+
32
+ def render_to_output_buffer(context, output)
33
+ render_tag(context, output)
34
+ end
35
+
36
+ def render_tag(context, output)
37
+ # Though we evaluate this here we will only ever parse it as a string literal.
38
+ template_name = context.evaluate(@template_name_expr)
39
+ raise ArgumentError, options[:locale].t("errors.argument.include") unless template_name
40
+
41
+ partial = PartialCache.load(
42
+ template_name,
43
+ context: context,
44
+ parse_context: parse_context
45
+ )
46
+
47
+ context_variable_name = @alias_name || template_name.split('/').last
48
+
49
+ render_partial_func = ->(var, forloop) {
50
+ inner_context = context.new_isolated_subcontext
51
+ inner_context.template_name = template_name
52
+ inner_context.partial = true
53
+ inner_context['forloop'] = forloop if forloop
54
+
55
+ @attributes.each do |key, value|
56
+ inner_context[key] = context.evaluate(value)
57
+ end
58
+ inner_context[context_variable_name] = var unless var.nil?
59
+ partial.render_to_output_buffer(inner_context, output)
60
+ forloop&.send(:increment!)
61
+ }
62
+
63
+ variable = @variable_name_expr ? context.evaluate(@variable_name_expr) : nil
64
+ if @for && variable.respond_to?(:each) && variable.respond_to?(:count)
65
+ forloop = Liquid::ForloopDrop.new(template_name, variable.count, nil)
66
+ variable.each { |var| render_partial_func.call(var, forloop) }
67
+ else
68
+ render_partial_func.call(variable, nil)
69
+ end
70
+
71
+ output
72
+ end
73
+
74
+ class ParseTreeVisitor < Liquid::ParseTreeVisitor
75
+ def children
76
+ [
77
+ @node.template_name_expr,
78
+ ] + @node.attributes.values
79
+ end
80
+ end
81
+ end
82
+
83
+ Template.register_tag('render', Render)
84
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
4
  class TableRow < Block
3
5
  Syntax = /(\w+)\s+in\s+(#{QuotedFragment}+)/o
@@ -7,48 +9,50 @@ module Liquid
7
9
  def initialize(tag_name, markup, options)
8
10
  super
9
11
  if markup =~ Syntax
10
- @variable_name = $1
11
- @collection_name = Expression.parse($2)
12
- @attributes = {}
12
+ @variable_name = Regexp.last_match(1)
13
+ @collection_name = parse_expression(Regexp.last_match(2))
14
+ @attributes = {}
13
15
  markup.scan(TagAttributes) do |key, value|
14
- @attributes[key] = Expression.parse(value)
16
+ @attributes[key] = parse_expression(value)
15
17
  end
16
18
  else
17
- raise SyntaxError.new(options[:locale].t("errors.syntax.table_row".freeze))
19
+ raise SyntaxError, options[:locale].t("errors.syntax.table_row")
18
20
  end
19
21
  end
20
22
 
21
- def render(context)
22
- collection = context.evaluate(@collection_name) or return ''.freeze
23
+ def render_to_output_buffer(context, output)
24
+ (collection = context.evaluate(@collection_name)) || (return '')
23
25
 
24
- from = @attributes.key?('offset'.freeze) ? context.evaluate(@attributes['offset'.freeze]).to_i : 0
25
- to = @attributes.key?('limit'.freeze) ? from + context.evaluate(@attributes['limit'.freeze]).to_i : nil
26
+ from = @attributes.key?('offset') ? context.evaluate(@attributes['offset']).to_i : 0
27
+ to = @attributes.key?('limit') ? from + context.evaluate(@attributes['limit']).to_i : nil
26
28
 
27
29
  collection = Utils.slice_collection(collection, from, to)
30
+ length = collection.length
28
31
 
29
- length = collection.length
30
-
31
- cols = context.evaluate(@attributes['cols'.freeze]).to_i
32
+ cols = context.evaluate(@attributes['cols']).to_i
32
33
 
33
- result = "<tr class=\"row1\">\n"
34
+ output << "<tr class=\"row1\">\n"
34
35
  context.stack do
35
36
  tablerowloop = Liquid::TablerowloopDrop.new(length, cols)
36
- context['tablerowloop'.freeze] = tablerowloop
37
+ context['tablerowloop'] = tablerowloop
37
38
 
38
39
  collection.each do |item|
39
40
  context[@variable_name] = item
40
41
 
41
- result << "<td class=\"col#{tablerowloop.col}\">" << super << '</td>'
42
+ output << "<td class=\"col#{tablerowloop.col}\">"
43
+ super
44
+ output << '</td>'
42
45
 
43
46
  if tablerowloop.col_last && !tablerowloop.last
44
- result << "</tr>\n<tr class=\"row#{tablerowloop.row + 1}\">"
47
+ output << "</tr>\n<tr class=\"row#{tablerowloop.row + 1}\">"
45
48
  end
46
49
 
47
50
  tablerowloop.send(:increment!)
48
51
  end
49
52
  end
50
- result << "</tr>\n"
51
- result
53
+
54
+ output << "</tr>\n"
55
+ output
52
56
  end
53
57
 
54
58
  class ParseTreeVisitor < Liquid::ParseTreeVisitor
@@ -58,5 +62,5 @@ module Liquid
58
62
  end
59
63
  end
60
64
 
61
- Template.register_tag('tablerow'.freeze, TableRow)
65
+ Template.register_tag('tablerow', TableRow)
62
66
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'if'
2
4
 
3
5
  module Liquid
@@ -6,25 +8,23 @@ module Liquid
6
8
  # {% unless x < 0 %} x is greater than zero {% endunless %}
7
9
  #
8
10
  class Unless < If
9
- def render(context)
10
- context.stack do
11
- # First condition is interpreted backwards ( if not )
12
- first_block = @blocks.first
13
- unless first_block.evaluate(context)
14
- return first_block.attachment.render(context)
15
- end
11
+ def render_to_output_buffer(context, output)
12
+ # First condition is interpreted backwards ( if not )
13
+ first_block = @blocks.first
14
+ unless first_block.evaluate(context)
15
+ return first_block.attachment.render_to_output_buffer(context, output)
16
+ end
16
17
 
17
- # After the first condition unless works just like if
18
- @blocks[1..-1].each do |block|
19
- if block.evaluate(context)
20
- return block.attachment.render(context)
21
- end
18
+ # After the first condition unless works just like if
19
+ @blocks[1..-1].each do |block|
20
+ if block.evaluate(context)
21
+ return block.attachment.render_to_output_buffer(context, output)
22
22
  end
23
-
24
- ''.freeze
25
23
  end
24
+
25
+ output
26
26
  end
27
27
  end
28
28
 
29
- Template.register_tag('unless'.freeze, Unless)
29
+ Template.register_tag('unless', Unless)
30
30
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
4
  # Templates are central to liquid.
3
5
  # Interpretating templates is a two step process. First you compile the
@@ -16,13 +18,11 @@ module Liquid
16
18
  attr_accessor :root
17
19
  attr_reader :resource_limits, :warnings
18
20
 
19
- @@file_system = BlankFileSystem.new
20
-
21
21
  class TagRegistry
22
22
  include Enumerable
23
23
 
24
24
  def initialize
25
- @tags = {}
25
+ @tags = {}
26
26
  @cache = {}
27
27
  end
28
28
 
@@ -50,7 +50,7 @@ module Liquid
50
50
  private
51
51
 
52
52
  def lookup_class(name)
53
- name.split("::").reject(&:empty?).reduce(Object) { |scope, const| scope.const_get(const) }
53
+ Object.const_get(name)
54
54
  end
55
55
  end
56
56
 
@@ -61,74 +61,53 @@ module Liquid
61
61
  # :lax acts like liquid 2.5 and silently ignores malformed tags in most cases.
62
62
  # :warn is the default and will give deprecation warnings when invalid syntax is used.
63
63
  # :strict will enforce correct syntax.
64
- attr_writer :error_mode
65
-
66
- # Deprecated. No longer used. Removed in version 5
67
- attr_writer :taint_mode
64
+ attr_accessor :error_mode
65
+ Template.error_mode = :lax
68
66
 
69
67
  attr_accessor :default_exception_renderer
70
68
  Template.default_exception_renderer = lambda do |exception|
71
69
  exception
72
70
  end
73
71
 
74
- def file_system
75
- @@file_system
76
- end
72
+ attr_accessor :file_system
73
+ Template.file_system = BlankFileSystem.new
77
74
 
78
- def file_system=(obj)
79
- @@file_system = obj
80
- end
75
+ attr_accessor :tags
76
+ Template.tags = TagRegistry.new
77
+ private :tags=
81
78
 
82
79
  def register_tag(name, klass)
83
80
  tags[name.to_s] = klass
84
81
  end
85
82
 
86
- def tags
87
- @tags ||= TagRegistry.new
88
- end
89
-
90
- def error_mode
91
- @error_mode ||= :lax
92
- end
93
-
94
- # Deprecated. Removed in version 5
95
- def taint_mode
96
- @taint_mode ||= :lax
97
- end
98
-
99
83
  # Pass a module with filter methods which should be available
100
84
  # to all liquid views. Good for registering the standard library
101
85
  def register_filter(mod)
102
- Strainer.global_filter(mod)
86
+ StrainerFactory.add_global_filter(mod)
103
87
  end
104
88
 
105
- def default_resource_limits
106
- @default_resource_limits ||= {}
107
- end
89
+ attr_accessor :default_resource_limits
90
+ Template.default_resource_limits = {}
91
+ private :default_resource_limits=
108
92
 
109
93
  # creates a new <tt>Template</tt> object from liquid source code
110
94
  # To enable profiling, pass in <tt>profile: true</tt> as an option.
111
95
  # See Liquid::Profiler for more information
112
96
  def parse(source, options = {})
113
- template = Template.new
114
- template.parse(source, options)
97
+ new.parse(source, options)
115
98
  end
116
99
  end
117
100
 
118
101
  def initialize
119
- @rethrow_errors = false
120
- @resource_limits = ResourceLimits.new(self.class.default_resource_limits)
102
+ @rethrow_errors = false
103
+ @resource_limits = ResourceLimits.new(Template.default_resource_limits)
121
104
  end
122
105
 
123
106
  # Parse source code.
124
107
  # Returns self for easy chaining
125
108
  def parse(source, options = {})
126
- @options = options
127
- @profiling = options[:profile]
128
- @line_numbers = options[:line_numbers] || @profiling
129
- parse_context = options.is_a?(ParseContext) ? options : ParseContext.new(options)
130
- @root = Document.parse(tokenize(source), parse_context)
131
- @warnings = parse_context.warnings
109
+ parse_context = configure_options(options)
110
+ @root = Document.parse(tokenize(source), parse_context)
132
111
  self
133
112
  end
134
113
 
@@ -163,19 +142,19 @@ module Liquid
163
142
  # filters and tags and might be useful to integrate liquid more with its host application
164
143
  #
165
144
  def render(*args)
166
- return ''.freeze if @root.nil?
145
+ return '' if @root.nil?
167
146
 
168
147
  context = case args.first
169
148
  when Liquid::Context
170
149
  c = args.shift
171
150
 
172
151
  if @rethrow_errors
173
- c.exception_renderer = ->(e) { raise }
152
+ c.exception_renderer = Liquid::RAISE_EXCEPTION_LAMBDA
174
153
  end
175
154
 
176
155
  c
177
156
  when Liquid::Drop
178
- drop = args.shift
157
+ drop = args.shift
179
158
  drop.context = Context.new([drop, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits)
180
159
  when Hash
181
160
  Context.new([args.shift, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits)
@@ -185,11 +164,18 @@ module Liquid
185
164
  raise ArgumentError, "Expected Hash or Liquid::Context as parameter"
186
165
  end
187
166
 
167
+ output = nil
168
+
169
+ context_register = context.registers.is_a?(StaticRegisters) ? context.registers.static : context.registers
170
+
188
171
  case args.last
189
172
  when Hash
190
173
  options = args.pop
174
+ output = options[:output] if options[:output]
191
175
 
192
- registers.merge!(options[:registers]) if options[:registers].is_a?(Hash)
176
+ options[:registers]&.each do |key, register|
177
+ context_register[key] = register
178
+ end
193
179
 
194
180
  apply_options_to_context(context, options)
195
181
  when Module, Array
@@ -199,13 +185,13 @@ module Liquid
199
185
  # Retrying a render resets resource usage
200
186
  context.resource_limits.reset
201
187
 
188
+ if @profiling && context.profiler.nil?
189
+ @profiler = context.profiler = Liquid::Profiler.new
190
+ end
191
+
202
192
  begin
203
193
  # render the nodelist.
204
- # for performance reasons we get an array back here. join will make a string out of it.
205
- result = with_profiling(context) do
206
- @root.render(context)
207
- end
208
- result.respond_to?(:join) ? result.join : result
194
+ @root.render_to_output_buffer(context, output || +'')
209
195
  rescue Liquid::MemoryError => e
210
196
  context.handle_error(e)
211
197
  ensure
@@ -218,35 +204,35 @@ module Liquid
218
204
  render(*args)
219
205
  end
220
206
 
221
- private
222
-
223
- def tokenize(source)
224
- Tokenizer.new(source, @line_numbers)
207
+ def render_to_output_buffer(context, output)
208
+ render(context, output: output)
225
209
  end
226
210
 
227
- def with_profiling(context)
228
- if @profiling && !context.partial
211
+ private
212
+
213
+ def configure_options(options)
214
+ if (profiling = options[:profile])
229
215
  raise "Profiler not loaded, require 'liquid/profiler' first" unless defined?(Liquid::Profiler)
216
+ end
230
217
 
231
- @profiler = Profiler.new
232
- @profiler.start
218
+ @options = options
219
+ @profiling = profiling
220
+ @line_numbers = options[:line_numbers] || @profiling
221
+ parse_context = options.is_a?(ParseContext) ? options : ParseContext.new(options)
222
+ @warnings = parse_context.warnings
223
+ parse_context
224
+ end
233
225
 
234
- begin
235
- yield
236
- ensure
237
- @profiler.stop
238
- end
239
- else
240
- yield
241
- end
226
+ def tokenize(source)
227
+ Tokenizer.new(source, @line_numbers)
242
228
  end
243
229
 
244
230
  def apply_options_to_context(context, options)
245
231
  context.add_filters(options[:filters]) if options[:filters]
246
- context.global_filter = options[:global_filter] if options[:global_filter]
232
+ context.global_filter = options[:global_filter] if options[:global_filter]
247
233
  context.exception_renderer = options[:exception_renderer] if options[:exception_renderer]
248
- context.strict_variables = options[:strict_variables] if options[:strict_variables]
249
- context.strict_filters = options[:strict_filters] if options[:strict_filters]
234
+ context.strict_variables = options[:strict_variables] if options[:strict_variables]
235
+ context.strict_filters = options[:strict_filters] if options[:strict_filters]
250
236
  end
251
237
  end
252
238
  end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Liquid
4
+ class TemplateFactory
5
+ def for(_template_name)
6
+ Liquid::Template.new
7
+ end
8
+ end
9
+ end