liquid 3.0.6 → 5.4.0

Sign up to get free protection for your applications and to get access to all the features.
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