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
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Liquid
4
+ # @liquid_public_docs
5
+ # @liquid_type tag
6
+ # @liquid_category theme
7
+ # @liquid_name render
8
+ # @liquid_summary
9
+ # Renders a [snippet](/themes/architecture#snippets) or [app block](/themes/architecture/sections/section-schema#render-app-blocks).
10
+ # @liquid_description
11
+ # Inside snippets and app blocks, you can't directly access variables that are [created](/api/liquid/tags#variable-tags) outside
12
+ # of the snippet or app block. However, you can [specify variables as parameters](/api/liquid/tags#render-passing-variables-to-snippets)
13
+ # to pass outside variables to snippets.
14
+ #
15
+ # While you can't directly access created variables, you can access global objects, as well as any objects that are
16
+ # directly accessible outside the snippet or app block. For example, a snippet or app block inside the [product template](/themes/architecture/templates/product)
17
+ # can access the [`product` object](/api/liquid/objects#product), and a snippet or app block inside a [section](/themes/architecture/sections)
18
+ # can access the [`section` object](/api/liquid/objects#section).
19
+ #
20
+ # Outside a snippet or app block, you can't access variables created inside the snippet or app block.
21
+ #
22
+ # > Note:
23
+ # > When you render a snippet using the `render` tag, you can't use the [`include` tag](/api/liquid/tags#include)
24
+ # > inside the snippet.
25
+ # @liquid_syntax
26
+ # {% render 'filename' %}
27
+ # @liquid_syntax_keyword filename The name of the snippet to render, without the `.liquid` extension.
28
+ class Render < Tag
29
+ FOR = 'for'
30
+ SYNTAX = /(#{QuotedString}+)(\s+(with|#{FOR})\s+(#{QuotedFragment}+))?(\s+(?:as)\s+(#{VariableSegment}+))?/o
31
+
32
+ disable_tags "include"
33
+
34
+ attr_reader :template_name_expr, :variable_name_expr, :attributes
35
+
36
+ def initialize(tag_name, markup, options)
37
+ super
38
+
39
+ raise SyntaxError, options[:locale].t("errors.syntax.render") unless markup =~ SYNTAX
40
+
41
+ template_name = Regexp.last_match(1)
42
+ with_or_for = Regexp.last_match(3)
43
+ variable_name = Regexp.last_match(4)
44
+
45
+ @alias_name = Regexp.last_match(6)
46
+ @variable_name_expr = variable_name ? parse_expression(variable_name) : nil
47
+ @template_name_expr = parse_expression(template_name)
48
+ @for = (with_or_for == FOR)
49
+
50
+ @attributes = {}
51
+ markup.scan(TagAttributes) do |key, value|
52
+ @attributes[key] = parse_expression(value)
53
+ end
54
+ end
55
+
56
+ def render_to_output_buffer(context, output)
57
+ render_tag(context, output)
58
+ end
59
+
60
+ def render_tag(context, output)
61
+ # The expression should be a String literal, which parses to a String object
62
+ template_name = @template_name_expr
63
+ raise ::ArgumentError unless template_name.is_a?(String)
64
+
65
+ partial = PartialCache.load(
66
+ template_name,
67
+ context: context,
68
+ parse_context: parse_context
69
+ )
70
+
71
+ context_variable_name = @alias_name || template_name.split('/').last
72
+
73
+ render_partial_func = ->(var, forloop) {
74
+ inner_context = context.new_isolated_subcontext
75
+ inner_context.template_name = template_name
76
+ inner_context.partial = true
77
+ inner_context['forloop'] = forloop if forloop
78
+
79
+ @attributes.each do |key, value|
80
+ inner_context[key] = context.evaluate(value)
81
+ end
82
+ inner_context[context_variable_name] = var unless var.nil?
83
+ partial.render_to_output_buffer(inner_context, output)
84
+ forloop&.send(:increment!)
85
+ }
86
+
87
+ variable = @variable_name_expr ? context.evaluate(@variable_name_expr) : nil
88
+ if @for && variable.respond_to?(:each) && variable.respond_to?(:count)
89
+ forloop = Liquid::ForloopDrop.new(template_name, variable.count, nil)
90
+ variable.each { |var| render_partial_func.call(var, forloop) }
91
+ else
92
+ render_partial_func.call(variable, nil)
93
+ end
94
+
95
+ output
96
+ end
97
+
98
+ class ParseTreeVisitor < Liquid::ParseTreeVisitor
99
+ def children
100
+ [
101
+ @node.template_name_expr,
102
+ @node.variable_name_expr,
103
+ ] + @node.attributes.values
104
+ end
105
+ end
106
+ end
107
+
108
+ Template.register_tag('render', Render)
109
+ end
@@ -1,72 +1,88 @@
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](/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
4
28
 
29
+ attr_reader :variable_name, :collection_name, :attributes
30
+
5
31
  def initialize(tag_name, markup, options)
6
32
  super
7
33
  if markup =~ Syntax
8
- @variable_name = $1
9
- @collection_name = $2
10
- @attributes = {}
34
+ @variable_name = Regexp.last_match(1)
35
+ @collection_name = parse_expression(Regexp.last_match(2))
36
+ @attributes = {}
11
37
  markup.scan(TagAttributes) do |key, value|
12
- @attributes[key] = value
38
+ @attributes[key] = parse_expression(value)
13
39
  end
14
40
  else
15
- raise SyntaxError.new(options[:locale].t("errors.syntax.table_row".freeze))
41
+ raise SyntaxError, options[:locale].t("errors.syntax.table_row")
16
42
  end
17
43
  end
18
44
 
19
- def render(context)
20
- collection = context[@collection_name] or return ''.freeze
45
+ def render_to_output_buffer(context, output)
46
+ (collection = context.evaluate(@collection_name)) || (return '')
21
47
 
22
- from = @attributes['offset'.freeze] ? context[@attributes['offset'.freeze]].to_i : 0
23
- to = @attributes['limit'.freeze] ? from + context[@attributes['limit'.freeze]].to_i : nil
48
+ from = @attributes.key?('offset') ? context.evaluate(@attributes['offset']).to_i : 0
49
+ to = @attributes.key?('limit') ? from + context.evaluate(@attributes['limit']).to_i : nil
24
50
 
25
51
  collection = Utils.slice_collection(collection, from, to)
52
+ length = collection.length
26
53
 
27
- length = collection.length
28
-
29
- cols = context[@attributes['cols'.freeze]].to_i
54
+ cols = context.evaluate(@attributes['cols']).to_i
30
55
 
31
- row = 1
32
- col = 0
33
-
34
- result = "<tr class=\"row1\">\n"
56
+ output << "<tr class=\"row1\">\n"
35
57
  context.stack do
58
+ tablerowloop = Liquid::TablerowloopDrop.new(length, cols)
59
+ context['tablerowloop'] = tablerowloop
36
60
 
37
- collection.each_with_index do |item, index|
61
+ collection.each do |item|
38
62
  context[@variable_name] = item
39
- context['tablerowloop'.freeze] = {
40
- 'length'.freeze => length,
41
- 'index'.freeze => index + 1,
42
- 'index0'.freeze => index,
43
- 'col'.freeze => col + 1,
44
- 'col0'.freeze => col,
45
- 'rindex'.freeze => length - index,
46
- 'rindex0'.freeze => length - index - 1,
47
- 'first'.freeze => (index == 0),
48
- 'last'.freeze => (index == length - 1),
49
- 'col_first'.freeze => (col == 0),
50
- 'col_last'.freeze => (col == cols - 1)
51
- }
52
-
53
63
 
54
- col += 1
64
+ output << "<td class=\"col#{tablerowloop.col}\">"
65
+ super
66
+ output << '</td>'
55
67
 
56
- result << "<td class=\"col#{col}\">" << super << '</td>'
57
-
58
- if col == cols and (index != length - 1)
59
- col = 0
60
- row += 1
61
- result << "</tr>\n<tr class=\"row#{row}\">"
68
+ if tablerowloop.col_last && !tablerowloop.last
69
+ output << "</tr>\n<tr class=\"row#{tablerowloop.row + 1}\">"
62
70
  end
63
71
 
72
+ tablerowloop.send(:increment!)
64
73
  end
65
74
  end
66
- result << "</tr>\n"
67
- result
75
+
76
+ output << "</tr>\n"
77
+ output
78
+ end
79
+
80
+ class ParseTreeVisitor < Liquid::ParseTreeVisitor
81
+ def children
82
+ super + @node.attributes.values + [@node.collection_name]
83
+ end
68
84
  end
69
85
  end
70
86
 
71
- Template.register_tag('tablerow'.freeze, TableRow)
87
+ Template.register_tag('tablerow', TableRow)
72
88
  end
@@ -1,31 +1,49 @@
1
- require File.dirname(__FILE__) + '/if'
1
+ # frozen_string_literal: true
2
+
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](/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
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
+ )
11
28
 
12
- # First condition is interpreted backwards ( if not )
13
- first_block = @blocks.first
14
- unless first_block.evaluate(context)
15
- return render_all(first_block.attachment, context)
16
- end
29
+ unless result
30
+ return first_block.attachment.render_to_output_buffer(context, output)
31
+ end
17
32
 
18
- # After the first condition unless works just like if
19
- @blocks[1..-1].each do |block|
20
- if block.evaluate(context)
21
- return render_all(block.attachment, context)
22
- end
23
- end
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
+ )
24
38
 
25
- ''.freeze
39
+ if result
40
+ return block.attachment.render_to_output_buffer(context, output)
41
+ end
26
42
  end
43
+
44
+ output
27
45
  end
28
46
  end
29
47
 
30
- Template.register_tag('unless'.freeze, Unless)
48
+ Template.register_tag('unless', Unless)
31
49
  end
@@ -1,5 +1,6 @@
1
- module Liquid
1
+ # frozen_string_literal: true
2
2
 
3
+ module Liquid
3
4
  # Templates are central to liquid.
4
5
  # Interpretating templates is a two step process. First you compile the
5
6
  # source code you got. During compile time some extensive error checking is performed.
@@ -14,21 +15,19 @@ module Liquid
14
15
  # template.render('user_name' => 'bob')
15
16
  #
16
17
  class Template
17
- DEFAULT_OPTIONS = {
18
- :locale => I18n.new
19
- }
20
-
21
- attr_accessor :root, :resource_limits
22
- @@file_system = BlankFileSystem.new
18
+ attr_accessor :root
19
+ attr_reader :resource_limits, :warnings
23
20
 
24
21
  class TagRegistry
22
+ include Enumerable
23
+
25
24
  def initialize
26
25
  @tags = {}
27
26
  @cache = {}
28
27
  end
29
28
 
30
29
  def [](tag_name)
31
- return nil unless @tags.has_key?(tag_name)
30
+ return nil unless @tags.key?(tag_name)
32
31
  return @cache[tag_name] if Liquid.cache_classes
33
32
 
34
33
  lookup_class(@tags[tag_name]).tap { |o| @cache[tag_name] = o }
@@ -44,10 +43,14 @@ module Liquid
44
43
  @cache.delete(tag_name)
45
44
  end
46
45
 
46
+ def each(&block)
47
+ @tags.each(&block)
48
+ end
49
+
47
50
  private
48
51
 
49
52
  def lookup_class(name)
50
- name.split("::").reject(&:empty?).reduce(Object) { |scope, const| scope.const_get(const) }
53
+ Object.const_get(name)
51
54
  end
52
55
  end
53
56
 
@@ -58,77 +61,57 @@ module Liquid
58
61
  # :lax acts like liquid 2.5 and silently ignores malformed tags in most cases.
59
62
  # :warn is the default and will give deprecation warnings when invalid syntax is used.
60
63
  # :strict will enforce correct syntax.
61
- attr_writer :error_mode
64
+ attr_accessor :error_mode
65
+ Template.error_mode = :lax
62
66
 
63
- # Sets how strict the taint checker should be.
64
- # :lax is the default, and ignores the taint flag completely
65
- # :warn adds a warning, but does not interrupt the rendering
66
- # :error raises an error when tainted output is used
67
- attr_writer :taint_mode
68
-
69
- def file_system
70
- @@file_system
67
+ attr_accessor :default_exception_renderer
68
+ Template.default_exception_renderer = lambda do |exception|
69
+ exception
71
70
  end
72
71
 
73
- def file_system=(obj)
74
- @@file_system = obj
75
- end
72
+ attr_accessor :file_system
73
+ Template.file_system = BlankFileSystem.new
74
+
75
+ attr_accessor :tags
76
+ Template.tags = TagRegistry.new
77
+ private :tags=
76
78
 
77
79
  def register_tag(name, klass)
78
80
  tags[name.to_s] = klass
79
81
  end
80
82
 
81
- def tags
82
- @tags ||= TagRegistry.new
83
- end
84
-
85
- def error_mode
86
- @error_mode || :lax
87
- end
88
-
89
- def taint_mode
90
- @taint_mode || :lax
91
- end
92
-
93
83
  # Pass a module with filter methods which should be available
94
84
  # to all liquid views. Good for registering the standard library
95
85
  def register_filter(mod)
96
- Strainer.global_filter(mod)
86
+ StrainerFactory.add_global_filter(mod)
97
87
  end
98
88
 
99
- def default_resource_limits
100
- @default_resource_limits ||= {}
101
- end
89
+ attr_accessor :default_resource_limits
90
+ Template.default_resource_limits = {}
91
+ private :default_resource_limits=
102
92
 
103
93
  # creates a new <tt>Template</tt> object from liquid source code
104
94
  # To enable profiling, pass in <tt>profile: true</tt> as an option.
105
95
  # See Liquid::Profiler for more information
106
96
  def parse(source, options = {})
107
- template = Template.new
108
- template.parse(source, options)
97
+ new.parse(source, options)
109
98
  end
110
99
  end
111
100
 
112
101
  def initialize
113
- @resource_limits = self.class.default_resource_limits.dup
102
+ @rethrow_errors = false
103
+ @resource_limits = ResourceLimits.new(Template.default_resource_limits)
114
104
  end
115
105
 
116
106
  # Parse source code.
117
107
  # Returns self for easy chaining
118
108
  def parse(source, options = {})
119
- @options = options
120
- @profiling = options[:profile]
121
- @line_numbers = options[:line_numbers] || @profiling
122
- @root = Document.parse(tokenize(source), DEFAULT_OPTIONS.merge(options))
123
- @warnings = nil
109
+ parse_context = configure_options(options)
110
+ tokenizer = parse_context.new_tokenizer(source, start_line_number: @line_numbers && 1)
111
+ @root = Document.parse(tokenizer, parse_context)
124
112
  self
125
113
  end
126
114
 
127
- def warnings
128
- return [] unless @root
129
- @warnings ||= @root.warnings
130
- end
131
-
132
115
  def registers
133
116
  @registers ||= {}
134
117
  end
@@ -160,19 +143,19 @@ module Liquid
160
143
  # filters and tags and might be useful to integrate liquid more with its host application
161
144
  #
162
145
  def render(*args)
163
- return ''.freeze if @root.nil?
146
+ return '' if @root.nil?
164
147
 
165
148
  context = case args.first
166
149
  when Liquid::Context
167
150
  c = args.shift
168
151
 
169
152
  if @rethrow_errors
170
- c.exception_handler = ->(e) { true }
153
+ c.exception_renderer = Liquid::RAISE_EXCEPTION_LAMBDA
171
154
  end
172
155
 
173
156
  c
174
157
  when Liquid::Drop
175
- drop = args.shift
158
+ drop = args.shift
176
159
  drop.context = Context.new([drop, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits)
177
160
  when Hash
178
161
  Context.new([args.shift, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits)
@@ -182,34 +165,33 @@ module Liquid
182
165
  raise ArgumentError, "Expected Hash or Liquid::Context as parameter"
183
166
  end
184
167
 
168
+ output = nil
169
+
185
170
  case args.last
186
171
  when Hash
187
172
  options = args.pop
173
+ output = options[:output] if options[:output]
174
+ static_registers = context.registers.static
188
175
 
189
- if options[:registers].is_a?(Hash)
190
- self.registers.merge!(options[:registers])
176
+ options[:registers]&.each do |key, register|
177
+ static_registers[key] = register
191
178
  end
192
179
 
193
- if options[:filters]
194
- context.add_filters(options[:filters])
195
- end
196
-
197
- if options[:exception_handler]
198
- context.exception_handler = options[:exception_handler]
199
- end
200
- when Module
201
- context.add_filters(args.pop)
202
- when Array
180
+ apply_options_to_context(context, options)
181
+ when Module, Array
203
182
  context.add_filters(args.pop)
204
183
  end
205
184
 
185
+ # Retrying a render resets resource usage
186
+ context.resource_limits.reset
187
+
188
+ if @profiling && context.profiler.nil?
189
+ @profiler = context.profiler = Liquid::Profiler.new
190
+ end
191
+
206
192
  begin
207
193
  # render the nodelist.
208
- # for performance reasons we get an array back here. join will make a string out of it.
209
- result = with_profiling do
210
- @root.render(context)
211
- end
212
- result.respond_to?(:join) ? result.join : result
194
+ @root.render_to_output_buffer(context, output || +'')
213
195
  rescue Liquid::MemoryError => e
214
196
  context.handle_error(e)
215
197
  ensure
@@ -222,45 +204,31 @@ module Liquid
222
204
  render(*args)
223
205
  end
224
206
 
225
- private
226
-
227
- # Uses the <tt>Liquid::TemplateParser</tt> regexp to tokenize the passed source
228
- def tokenize(source)
229
- source = source.source if source.respond_to?(:source)
230
- return [] if source.to_s.empty?
231
-
232
- tokens = calculate_line_numbers(source.split(TemplateParser))
233
-
234
- # removes the rogue empty element at the beginning of the array
235
- tokens.shift if tokens[0] and tokens[0].empty?
236
-
237
- tokens
207
+ def render_to_output_buffer(context, output)
208
+ render(context, output: output)
238
209
  end
239
210
 
240
- def calculate_line_numbers(raw_tokens)
241
- return raw_tokens unless @line_numbers
211
+ private
242
212
 
243
- current_line = 1
244
- raw_tokens.map do |token|
245
- Token.new(token, current_line).tap do
246
- current_line += token.count("\n")
247
- end
213
+ def configure_options(options)
214
+ if (profiling = options[:profile])
215
+ raise "Profiler not loaded, require 'liquid/profiler' first" unless defined?(Liquid::Profiler)
248
216
  end
249
- end
250
217
 
251
- def with_profiling
252
- if @profiling && !@options[:included]
253
- @profiler = Profiler.new
254
- @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
255
225
 
256
- begin
257
- yield
258
- ensure
259
- @profiler.stop
260
- end
261
- else
262
- yield
263
- end
226
+ def apply_options_to_context(context, options)
227
+ context.add_filters(options[:filters]) if options[:filters]
228
+ context.global_filter = options[:global_filter] if options[:global_filter]
229
+ context.exception_renderer = options[:exception_renderer] if options[:exception_renderer]
230
+ context.strict_variables = options[:strict_variables] if options[:strict_variables]
231
+ context.strict_filters = options[:strict_filters] if options[:strict_filters]
264
232
  end
265
233
  end
266
234
  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
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Liquid
4
+ class Tokenizer
5
+ attr_reader :line_number, :for_liquid_tag
6
+
7
+ def initialize(source, line_numbers = false, line_number: nil, for_liquid_tag: false)
8
+ @source = source.to_s.to_str
9
+ @line_number = line_number || (line_numbers ? 1 : nil)
10
+ @for_liquid_tag = for_liquid_tag
11
+ @tokens = tokenize
12
+ end
13
+
14
+ def shift
15
+ (token = @tokens.shift) || return
16
+
17
+ if @line_number
18
+ @line_number += @for_liquid_tag ? 1 : token.count("\n")
19
+ end
20
+
21
+ token
22
+ end
23
+
24
+ private
25
+
26
+ def tokenize
27
+ return [] if @source.empty?
28
+
29
+ return @source.split("\n") if @for_liquid_tag
30
+
31
+ tokens = @source.split(TemplateParser)
32
+
33
+ # removes the rogue empty element at the beginning of the array
34
+ tokens.shift if tokens[0]&.empty?
35
+
36
+ tokens
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Liquid
4
+ module Usage
5
+ def self.increment(name)
6
+ end
7
+ end
8
+ end