liquid 4.0.0 → 5.10.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 (117) hide show
  1. checksums.yaml +5 -5
  2. data/History.md +235 -2
  3. data/README.md +58 -8
  4. data/lib/liquid/block.rb +51 -20
  5. data/lib/liquid/block_body.rb +216 -82
  6. data/lib/liquid/condition.rb +83 -32
  7. data/lib/liquid/const.rb +8 -0
  8. data/lib/liquid/context.rb +130 -59
  9. data/lib/liquid/deprecations.rb +22 -0
  10. data/lib/liquid/document.rb +47 -9
  11. data/lib/liquid/drop.rb +8 -2
  12. data/lib/liquid/environment.rb +159 -0
  13. data/lib/liquid/errors.rb +23 -20
  14. data/lib/liquid/expression.rb +114 -31
  15. data/lib/liquid/extensions.rb +8 -0
  16. data/lib/liquid/file_system.rb +6 -4
  17. data/lib/liquid/forloop_drop.rb +51 -4
  18. data/lib/liquid/i18n.rb +5 -3
  19. data/lib/liquid/interrupts.rb +3 -1
  20. data/lib/liquid/lexer.rb +165 -39
  21. data/lib/liquid/locales/en.yml +16 -6
  22. data/lib/liquid/parse_context.rb +62 -7
  23. data/lib/liquid/parse_tree_visitor.rb +42 -0
  24. data/lib/liquid/parser.rb +31 -19
  25. data/lib/liquid/parser_switching.rb +42 -3
  26. data/lib/liquid/partial_cache.rb +33 -0
  27. data/lib/liquid/profiler/hooks.rb +26 -14
  28. data/lib/liquid/profiler.rb +67 -86
  29. data/lib/liquid/range_lookup.rb +26 -6
  30. data/lib/liquid/registers.rb +51 -0
  31. data/lib/liquid/resource_limits.rb +47 -8
  32. data/lib/liquid/snippet_drop.rb +22 -0
  33. data/lib/liquid/standardfilters.rb +813 -137
  34. data/lib/liquid/strainer_template.rb +62 -0
  35. data/lib/liquid/tablerowloop_drop.rb +64 -5
  36. data/lib/liquid/tag/disableable.rb +22 -0
  37. data/lib/liquid/tag/disabler.rb +13 -0
  38. data/lib/liquid/tag.rb +42 -6
  39. data/lib/liquid/tags/assign.rb +46 -18
  40. data/lib/liquid/tags/break.rb +15 -4
  41. data/lib/liquid/tags/capture.rb +26 -18
  42. data/lib/liquid/tags/case.rb +108 -32
  43. data/lib/liquid/tags/comment.rb +76 -4
  44. data/lib/liquid/tags/continue.rb +15 -13
  45. data/lib/liquid/tags/cycle.rb +117 -34
  46. data/lib/liquid/tags/decrement.rb +30 -23
  47. data/lib/liquid/tags/doc.rb +81 -0
  48. data/lib/liquid/tags/echo.rb +39 -0
  49. data/lib/liquid/tags/for.rb +109 -96
  50. data/lib/liquid/tags/if.rb +72 -41
  51. data/lib/liquid/tags/ifchanged.rb +10 -11
  52. data/lib/liquid/tags/include.rb +89 -63
  53. data/lib/liquid/tags/increment.rb +31 -20
  54. data/lib/liquid/tags/inline_comment.rb +28 -0
  55. data/lib/liquid/tags/raw.rb +25 -13
  56. data/lib/liquid/tags/render.rb +151 -0
  57. data/lib/liquid/tags/snippet.rb +45 -0
  58. data/lib/liquid/tags/table_row.rb +104 -21
  59. data/lib/liquid/tags/unless.rb +37 -20
  60. data/lib/liquid/tags.rb +51 -0
  61. data/lib/liquid/template.rb +90 -106
  62. data/lib/liquid/template_factory.rb +9 -0
  63. data/lib/liquid/tokenizer.rb +143 -13
  64. data/lib/liquid/usage.rb +8 -0
  65. data/lib/liquid/utils.rb +114 -5
  66. data/lib/liquid/variable.rb +119 -45
  67. data/lib/liquid/variable_lookup.rb +35 -13
  68. data/lib/liquid/version.rb +3 -1
  69. data/lib/liquid.rb +31 -18
  70. metadata +56 -107
  71. data/lib/liquid/strainer.rb +0 -66
  72. data/test/fixtures/en_locale.yml +0 -9
  73. data/test/integration/assign_test.rb +0 -48
  74. data/test/integration/blank_test.rb +0 -106
  75. data/test/integration/capture_test.rb +0 -50
  76. data/test/integration/context_test.rb +0 -32
  77. data/test/integration/document_test.rb +0 -19
  78. data/test/integration/drop_test.rb +0 -273
  79. data/test/integration/error_handling_test.rb +0 -260
  80. data/test/integration/filter_test.rb +0 -178
  81. data/test/integration/hash_ordering_test.rb +0 -23
  82. data/test/integration/output_test.rb +0 -123
  83. data/test/integration/parsing_quirks_test.rb +0 -118
  84. data/test/integration/render_profiling_test.rb +0 -154
  85. data/test/integration/security_test.rb +0 -66
  86. data/test/integration/standard_filter_test.rb +0 -535
  87. data/test/integration/tags/break_tag_test.rb +0 -15
  88. data/test/integration/tags/continue_tag_test.rb +0 -15
  89. data/test/integration/tags/for_tag_test.rb +0 -410
  90. data/test/integration/tags/if_else_tag_test.rb +0 -188
  91. data/test/integration/tags/include_tag_test.rb +0 -238
  92. data/test/integration/tags/increment_tag_test.rb +0 -23
  93. data/test/integration/tags/raw_tag_test.rb +0 -31
  94. data/test/integration/tags/standard_tag_test.rb +0 -296
  95. data/test/integration/tags/statements_test.rb +0 -111
  96. data/test/integration/tags/table_row_test.rb +0 -64
  97. data/test/integration/tags/unless_else_tag_test.rb +0 -26
  98. data/test/integration/template_test.rb +0 -323
  99. data/test/integration/trim_mode_test.rb +0 -525
  100. data/test/integration/variable_test.rb +0 -92
  101. data/test/test_helper.rb +0 -117
  102. data/test/unit/block_unit_test.rb +0 -58
  103. data/test/unit/condition_unit_test.rb +0 -158
  104. data/test/unit/context_unit_test.rb +0 -483
  105. data/test/unit/file_system_unit_test.rb +0 -35
  106. data/test/unit/i18n_unit_test.rb +0 -37
  107. data/test/unit/lexer_unit_test.rb +0 -51
  108. data/test/unit/parser_unit_test.rb +0 -82
  109. data/test/unit/regexp_unit_test.rb +0 -44
  110. data/test/unit/strainer_unit_test.rb +0 -148
  111. data/test/unit/tag_unit_test.rb +0 -21
  112. data/test/unit/tags/case_tag_unit_test.rb +0 -10
  113. data/test/unit/tags/for_tag_unit_test.rb +0 -13
  114. data/test/unit/tags/if_tag_unit_test.rb +0 -8
  115. data/test/unit/template_unit_test.rb +0 -78
  116. data/test/unit/tokenizer_unit_test.rb +0 -55
  117. data/test/unit/variable_unit_test.rb +0 -162
@@ -1,54 +1,137 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
4
+ # @liquid_public_docs
5
+ # @liquid_type tag
6
+ # @liquid_category iteration
7
+ # @liquid_name tablerow
8
+ # @liquid_summary
9
+ # Generates HTML table rows for every item in an array.
10
+ # @liquid_description
11
+ # The `tablerow` tag must be wrapped in HTML `<table>` and `</table>` tags.
12
+ #
13
+ # > Tip:
14
+ # > Every `tablerow` loop has an associated [`tablerowloop` object](/docs/api/liquid/objects/tablerowloop) with information about the loop.
15
+ # @liquid_syntax
16
+ # {% tablerow variable in array %}
17
+ # expression
18
+ # {% endtablerow %}
19
+ # @liquid_syntax_keyword variable The current item in the array.
20
+ # @liquid_syntax_keyword array The array to iterate over.
21
+ # @liquid_syntax_keyword expression The expression to render.
22
+ # @liquid_optional_param cols: [number] The number of columns that the table should have.
23
+ # @liquid_optional_param limit: [number] The number of iterations to perform.
24
+ # @liquid_optional_param offset: [number] The 1-based index to start iterating at.
25
+ # @liquid_optional_param range [untyped] A custom numeric range to iterate over.
2
26
  class TableRow < Block
3
27
  Syntax = /(\w+)\s+in\s+(#{QuotedFragment}+)/o
28
+ ALLOWED_ATTRIBUTES = ['cols', 'limit', 'offset', 'range'].freeze
29
+
30
+ attr_reader :variable_name, :collection_name, :attributes
4
31
 
5
32
  def initialize(tag_name, markup, options)
6
33
  super
34
+ parse_with_selected_parser(markup)
35
+ end
36
+
37
+ def rigid_parse(markup)
38
+ p = @parse_context.new_parser(markup)
39
+
40
+ @variable_name = p.consume(:id)
41
+
42
+ unless p.id?("in")
43
+ raise SyntaxError, options[:locale].t("errors.syntax.for_invalid_in")
44
+ end
45
+
46
+ @collection_name = safe_parse_expression(p)
47
+
48
+ p.consume?(:comma)
49
+
50
+ @attributes = {}
51
+ while p.look(:id)
52
+ key = p.consume
53
+ unless ALLOWED_ATTRIBUTES.include?(key)
54
+ raise SyntaxError, options[:locale].t("errors.syntax.table_row_invalid_attribute", attribute: key)
55
+ end
56
+
57
+ p.consume(:colon)
58
+ @attributes[key] = safe_parse_expression(p)
59
+ p.consume?(:comma)
60
+ end
61
+
62
+ p.consume(:end_of_string)
63
+ end
64
+
65
+ def strict_parse(markup)
66
+ lax_parse(markup)
67
+ end
68
+
69
+ def lax_parse(markup)
7
70
  if markup =~ Syntax
8
- @variable_name = $1
9
- @collection_name = Expression.parse($2)
10
- @attributes = {}
71
+ @variable_name = Regexp.last_match(1)
72
+ @collection_name = parse_expression(Regexp.last_match(2))
73
+ @attributes = {}
11
74
  markup.scan(TagAttributes) do |key, value|
12
- @attributes[key] = Expression.parse(value)
75
+ @attributes[key] = parse_expression(value)
13
76
  end
14
77
  else
15
- raise SyntaxError.new(options[:locale].t("errors.syntax.table_row".freeze))
78
+ raise SyntaxError, options[:locale].t("errors.syntax.table_row")
16
79
  end
17
80
  end
18
81
 
19
- def render(context)
20
- collection = context.evaluate(@collection_name) or return ''.freeze
82
+ def render_to_output_buffer(context, output)
83
+ (collection = context.evaluate(@collection_name)) || (return '')
21
84
 
22
- from = @attributes.key?('offset'.freeze) ? context.evaluate(@attributes['offset'.freeze]).to_i : 0
23
- to = @attributes.key?('limit'.freeze) ? from + context.evaluate(@attributes['limit'.freeze]).to_i : nil
85
+ from = @attributes.key?('offset') ? to_integer(context.evaluate(@attributes['offset'])) : 0
86
+ to = @attributes.key?('limit') ? from + to_integer(context.evaluate(@attributes['limit'])) : nil
24
87
 
25
88
  collection = Utils.slice_collection(collection, from, to)
89
+ length = collection.length
26
90
 
27
- length = collection.length
28
-
29
- cols = context.evaluate(@attributes['cols'.freeze]).to_i
91
+ cols = @attributes.key?('cols') ? to_integer(context.evaluate(@attributes['cols'])) : length
30
92
 
31
- result = "<tr class=\"row1\">\n"
93
+ output << "<tr class=\"row1\">\n"
32
94
  context.stack do
33
95
  tablerowloop = Liquid::TablerowloopDrop.new(length, cols)
34
- context['tablerowloop'.freeze] = tablerowloop
96
+ context['tablerowloop'] = tablerowloop
35
97
 
36
- collection.each_with_index do |item, index|
98
+ collection.each do |item|
37
99
  context[@variable_name] = item
38
100
 
39
- result << "<td class=\"col#{tablerowloop.col}\">" << super << '</td>'
101
+ output << "<td class=\"col#{tablerowloop.col}\">"
102
+ super
103
+ output << '</td>'
104
+
105
+ # Handle any interrupts if they exist.
106
+ if context.interrupt?
107
+ interrupt = context.pop_interrupt
108
+ break if interrupt.is_a?(BreakInterrupt)
109
+ end
40
110
 
41
111
  if tablerowloop.col_last && !tablerowloop.last
42
- result << "</tr>\n<tr class=\"row#{tablerowloop.row + 1}\">"
112
+ output << "</tr>\n<tr class=\"row#{tablerowloop.row + 1}\">"
43
113
  end
44
114
 
45
115
  tablerowloop.send(:increment!)
46
116
  end
47
117
  end
48
- result << "</tr>\n"
49
- result
118
+
119
+ output << "</tr>\n"
120
+ output
121
+ end
122
+
123
+ class ParseTreeVisitor < Liquid::ParseTreeVisitor
124
+ def children
125
+ super + @node.attributes.values + [@node.collection_name]
126
+ end
50
127
  end
51
- end
52
128
 
53
- Template.register_tag('tablerow'.freeze, TableRow)
129
+ private
130
+
131
+ def to_integer(value)
132
+ value.to_i
133
+ rescue NoMethodError
134
+ raise Liquid::ArgumentError, "invalid integer"
135
+ end
136
+ end
54
137
  end
@@ -1,30 +1,47 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative 'if'
2
4
 
3
5
  module Liquid
4
- # Unless is a conditional just like 'if' but works on the inverse logic.
5
- #
6
- # {% unless x < 0 %} x is greater than zero {% endunless %}
7
- #
6
+ # @liquid_public_docs
7
+ # @liquid_type tag
8
+ # @liquid_category conditional
9
+ # @liquid_name unless
10
+ # @liquid_summary
11
+ # Renders an expression unless a specific condition is `true`.
12
+ # @liquid_description
13
+ # > Tip:
14
+ # > Similar to the [`if` tag](/docs/api/liquid/tags/if), you can use `elsif` to add more conditions to an `unless` tag.
15
+ # @liquid_syntax
16
+ # {% unless condition %}
17
+ # expression
18
+ # {% endunless %}
19
+ # @liquid_syntax_keyword condition The condition to evaluate.
20
+ # @liquid_syntax_keyword expression The expression to render unless the condition is met.
8
21
  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
22
+ def render_to_output_buffer(context, output)
23
+ # First condition is interpreted backwards ( if not )
24
+ first_block = @blocks.first
25
+ result = Liquid::Utils.to_liquid_value(
26
+ first_block.evaluate(context),
27
+ )
16
28
 
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
22
- end
29
+ unless result
30
+ return first_block.attachment.render_to_output_buffer(context, output)
31
+ end
23
32
 
24
- ''.freeze
33
+ # After the first condition unless works just like if
34
+ @blocks[1..-1].each do |block|
35
+ result = Liquid::Utils.to_liquid_value(
36
+ block.evaluate(context),
37
+ )
38
+
39
+ if result
40
+ return block.attachment.render_to_output_buffer(context, output)
41
+ end
25
42
  end
43
+
44
+ output
26
45
  end
27
46
  end
28
-
29
- Template.register_tag('unless'.freeze, Unless)
30
47
  end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "tags/table_row"
4
+ require_relative "tags/echo"
5
+ require_relative "tags/if"
6
+ require_relative "tags/break"
7
+ require_relative "tags/inline_comment"
8
+ require_relative "tags/for"
9
+ require_relative "tags/assign"
10
+ require_relative "tags/ifchanged"
11
+ require_relative "tags/case"
12
+ require_relative "tags/include"
13
+ require_relative "tags/continue"
14
+ require_relative "tags/capture"
15
+ require_relative "tags/decrement"
16
+ require_relative "tags/unless"
17
+ require_relative "tags/increment"
18
+ require_relative "tags/comment"
19
+ require_relative "tags/raw"
20
+ require_relative "tags/render"
21
+ require_relative "tags/cycle"
22
+ require_relative "tags/doc"
23
+ require_relative "tags/snippet"
24
+
25
+ module Liquid
26
+ module Tags
27
+ STANDARD_TAGS = {
28
+ 'cycle' => Cycle,
29
+ 'render' => Render,
30
+ 'raw' => Raw,
31
+ 'comment' => Comment,
32
+ 'increment' => Increment,
33
+ 'unless' => Unless,
34
+ 'decrement' => Decrement,
35
+ 'capture' => Capture,
36
+ 'continue' => Continue,
37
+ 'include' => Include,
38
+ 'case' => Case,
39
+ 'ifchanged' => Ifchanged,
40
+ 'assign' => Assign,
41
+ 'for' => For,
42
+ '#' => InlineComment,
43
+ 'break' => Break,
44
+ 'if' => If,
45
+ 'echo' => Echo,
46
+ 'tablerow' => TableRow,
47
+ 'doc' => Doc,
48
+ 'snippet' => Snippet,
49
+ }.freeze
50
+ end
51
+ end
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
4
  # Templates are central to liquid.
3
- # Interpretating templates is a two step process. First you compile the
5
+ # Interpreting templates is a two step process. First you compile the
4
6
  # source code you got. During compile time some extensive error checking is performed.
5
7
  # your code should expect to get some SyntaxErrors.
6
8
  #
@@ -13,124 +15,96 @@ module Liquid
13
15
  # template.render('user_name' => 'bob')
14
16
  #
15
17
  class Template
16
- attr_accessor :root
18
+ attr_accessor :root, :name
17
19
  attr_reader :resource_limits, :warnings
18
20
 
19
- @@file_system = BlankFileSystem.new
20
-
21
- class TagRegistry
22
- include Enumerable
23
-
24
- def initialize
25
- @tags = {}
26
- @cache = {}
27
- end
28
-
29
- def [](tag_name)
30
- return nil unless @tags.key?(tag_name)
31
- return @cache[tag_name] if Liquid.cache_classes
32
-
33
- lookup_class(@tags[tag_name]).tap { |o| @cache[tag_name] = o }
34
- end
35
-
36
- def []=(tag_name, klass)
37
- @tags[tag_name] = klass.name
38
- @cache[tag_name] = klass
39
- end
40
-
41
- def delete(tag_name)
42
- @tags.delete(tag_name)
43
- @cache.delete(tag_name)
44
- end
45
-
46
- def each(&block)
47
- @tags.each(&block)
48
- end
49
-
50
- private
51
-
52
- def lookup_class(name)
53
- name.split("::").reject(&:empty?).reduce(Object) { |scope, const| scope.const_get(const) }
54
- end
55
- end
56
-
57
21
  attr_reader :profiler
58
22
 
59
23
  class << self
60
24
  # Sets how strict the parser should be.
61
25
  # :lax acts like liquid 2.5 and silently ignores malformed tags in most cases.
62
26
  # :warn is the default and will give deprecation warnings when invalid syntax is used.
63
- # :strict will enforce correct syntax.
64
- attr_writer :error_mode
65
-
66
- # Sets how strict the taint checker should be.
67
- # :lax is the default, and ignores the taint flag completely
68
- # :warn adds a warning, but does not interrupt the rendering
69
- # :error raises an error when tainted output is used
70
- attr_writer :taint_mode
27
+ # :strict enforces correct syntax for most tags
28
+ # :rigid enforces correct syntax for all tags
29
+ def error_mode=(mode)
30
+ Deprecations.warn("Template.error_mode=", "Environment#error_mode=")
31
+ Environment.default.error_mode = mode
32
+ end
71
33
 
72
- attr_accessor :default_exception_renderer
73
- Template.default_exception_renderer = lambda do |exception|
74
- exception
34
+ def error_mode
35
+ Environment.default.error_mode
75
36
  end
76
37
 
77
- def file_system
78
- @@file_system
38
+ def default_exception_renderer=(renderer)
39
+ Deprecations.warn("Template.default_exception_renderer=", "Environment#exception_renderer=")
40
+ Environment.default.exception_renderer = renderer
79
41
  end
80
42
 
81
- def file_system=(obj)
82
- @@file_system = obj
43
+ def default_exception_renderer
44
+ Environment.default.exception_renderer
83
45
  end
84
46
 
85
- def register_tag(name, klass)
86
- tags[name.to_s] = klass
47
+ def file_system=(file_system)
48
+ Deprecations.warn("Template.file_system=", "Environment#file_system=")
49
+ Environment.default.file_system = file_system
87
50
  end
88
51
 
89
- def tags
90
- @tags ||= TagRegistry.new
52
+ def file_system
53
+ Environment.default.file_system
91
54
  end
92
55
 
93
- def error_mode
94
- @error_mode ||= :lax
56
+ def tags
57
+ Environment.default.tags
95
58
  end
96
59
 
97
- def taint_mode
98
- @taint_mode ||= :lax
60
+ def register_tag(name, klass)
61
+ Deprecations.warn("Template.register_tag", "Environment#register_tag")
62
+ Environment.default.register_tag(name, klass)
99
63
  end
100
64
 
101
65
  # Pass a module with filter methods which should be available
102
66
  # to all liquid views. Good for registering the standard library
103
67
  def register_filter(mod)
104
- Strainer.global_filter(mod)
68
+ Deprecations.warn("Template.register_filter", "Environment#register_filter")
69
+ Environment.default.register_filter(mod)
70
+ end
71
+
72
+ private def default_resource_limits=(limits)
73
+ Deprecations.warn("Template.default_resource_limits=", "Environment#default_resource_limits=")
74
+ Environment.default.default_resource_limits = limits
105
75
  end
106
76
 
107
77
  def default_resource_limits
108
- @default_resource_limits ||= {}
78
+ Environment.default.default_resource_limits
109
79
  end
110
80
 
111
81
  # creates a new <tt>Template</tt> object from liquid source code
112
82
  # To enable profiling, pass in <tt>profile: true</tt> as an option.
113
83
  # See Liquid::Profiler for more information
114
84
  def parse(source, options = {})
115
- template = Template.new
116
- template.parse(source, options)
85
+ environment = options[:environment] || Environment.default
86
+ new(environment: environment).parse(source, options)
117
87
  end
118
88
  end
119
89
 
120
- def initialize
121
- @rethrow_errors = false
122
- @resource_limits = ResourceLimits.new(self.class.default_resource_limits)
90
+ def initialize(environment: Environment.default)
91
+ @environment = environment
92
+ @rethrow_errors = false
93
+ @resource_limits = ResourceLimits.new(environment.default_resource_limits)
123
94
  end
124
95
 
125
96
  # Parse source code.
126
97
  # Returns self for easy chaining
127
98
  def parse(source, options = {})
128
- @options = options
129
- @profiling = options[:profile]
130
- @line_numbers = options[:line_numbers] || @profiling
131
- parse_context = options.is_a?(ParseContext) ? options : ParseContext.new(options)
132
- @root = Document.parse(tokenize(source), parse_context)
133
- @warnings = parse_context.warnings
99
+ parse_context = configure_options(options)
100
+ source = source.to_s.to_str
101
+
102
+ unless source.valid_encoding?
103
+ raise TemplateEncodingError, parse_context.locale.t("errors.syntax.invalid_template_encoding")
104
+ end
105
+
106
+ tokenizer = parse_context.new_tokenizer(source, start_line_number: @line_numbers && 1)
107
+ @root = Document.parse(tokenizer, parse_context)
134
108
  self
135
109
  end
136
110
 
@@ -165,33 +139,39 @@ module Liquid
165
139
  # filters and tags and might be useful to integrate liquid more with its host application
166
140
  #
167
141
  def render(*args)
168
- return ''.freeze if @root.nil?
142
+ return '' if @root.nil?
169
143
 
170
144
  context = case args.first
171
145
  when Liquid::Context
172
146
  c = args.shift
173
147
 
174
148
  if @rethrow_errors
175
- c.exception_renderer = ->(e) { raise }
149
+ c.exception_renderer = Liquid::RAISE_EXCEPTION_LAMBDA
176
150
  end
177
151
 
178
152
  c
179
153
  when Liquid::Drop
180
- drop = args.shift
181
- drop.context = Context.new([drop, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits)
154
+ drop = args.shift
155
+ drop.context = Context.new([drop, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits, {}, @environment)
182
156
  when Hash
183
- Context.new([args.shift, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits)
157
+ Context.new([args.shift, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits, {}, @environment)
184
158
  when nil
185
- Context.new(assigns, instance_assigns, registers, @rethrow_errors, @resource_limits)
159
+ Context.new(assigns, instance_assigns, registers, @rethrow_errors, @resource_limits, {}, @environment)
186
160
  else
187
161
  raise ArgumentError, "Expected Hash or Liquid::Context as parameter"
188
162
  end
189
163
 
164
+ output = nil
165
+
190
166
  case args.last
191
167
  when Hash
192
168
  options = args.pop
169
+ output = options[:output] if options[:output]
170
+ static_registers = context.registers.static
193
171
 
194
- registers.merge!(options[:registers]) if options[:registers].is_a?(Hash)
172
+ options[:registers]&.each do |key, register|
173
+ static_registers[key] = register
174
+ end
195
175
 
196
176
  apply_options_to_context(context, options)
197
177
  when Module, Array
@@ -201,13 +181,15 @@ module Liquid
201
181
  # Retrying a render resets resource usage
202
182
  context.resource_limits.reset
203
183
 
184
+ if @profiling && context.profiler.nil?
185
+ @profiler = context.profiler = Liquid::Profiler.new
186
+ end
187
+
188
+ context.template_name ||= name
189
+
204
190
  begin
205
191
  # render the nodelist.
206
- # for performance reasons we get an array back here. join will make a string out of it.
207
- result = with_profiling(context) do
208
- @root.render(context)
209
- end
210
- result.respond_to?(:join) ? result.join : result
192
+ @root.render_to_output_buffer(context, output || +'')
211
193
  rescue Liquid::MemoryError => e
212
194
  context.handle_error(e)
213
195
  ensure
@@ -220,35 +202,37 @@ module Liquid
220
202
  render(*args)
221
203
  end
222
204
 
223
- private
224
-
225
- def tokenize(source)
226
- Tokenizer.new(source, @line_numbers)
205
+ def render_to_output_buffer(context, output)
206
+ render(context, output: output)
227
207
  end
228
208
 
229
- def with_profiling(context)
230
- if @profiling && !context.partial
231
- raise "Profiler not loaded, require 'liquid/profiler' first" unless defined?(Liquid::Profiler)
209
+ private
232
210
 
233
- @profiler = Profiler.new
234
- @profiler.start
211
+ def configure_options(options)
212
+ if (profiling = options[:profile])
213
+ raise "Profiler not loaded, require 'liquid/profiler' first" unless defined?(Liquid::Profiler)
214
+ end
235
215
 
236
- begin
237
- yield
238
- ensure
239
- @profiler.stop
240
- end
216
+ @options = options
217
+ @profiling = profiling
218
+ @line_numbers = options[:line_numbers] || @profiling
219
+ parse_context = if options.is_a?(ParseContext)
220
+ options
241
221
  else
242
- yield
222
+ opts = options.key?(:environment) ? options : options.merge(environment: @environment)
223
+ ParseContext.new(opts)
243
224
  end
225
+
226
+ @warnings = parse_context.warnings
227
+ parse_context
244
228
  end
245
229
 
246
230
  def apply_options_to_context(context, options)
247
231
  context.add_filters(options[:filters]) if options[:filters]
248
- context.global_filter = options[:global_filter] if options[:global_filter]
232
+ context.global_filter = options[:global_filter] if options[:global_filter]
249
233
  context.exception_renderer = options[:exception_renderer] if options[:exception_renderer]
250
- context.strict_variables = options[:strict_variables] if options[:strict_variables]
251
- 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]
252
236
  end
253
237
  end
254
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