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,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
4
  # Context keeps the variable stack and resolves variables, as well as keywords
3
5
  #
@@ -12,37 +14,58 @@ module Liquid
12
14
  #
13
15
  # context['bob'] #=> nil class Context
14
16
  class Context
15
- attr_reader :scopes, :errors, :registers, :environments, :resource_limits
16
- attr_accessor :exception_renderer, :template_name, :partial, :global_filter, :strict_variables, :strict_filters
17
-
18
- def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil)
19
- @environments = [environments].flatten
20
- @scopes = [(outer_scope || {})]
21
- @registers = registers
22
- @errors = []
23
- @partial = false
24
- @strict_variables = false
25
- @resource_limits = resource_limits || ResourceLimits.new(Template.default_resource_limits)
26
- squash_instance_assigns_with_environments
17
+ attr_reader :scopes, :errors, :registers, :environments, :resource_limits, :static_registers, :static_environments
18
+ attr_accessor :exception_renderer, :template_name, :partial, :global_filter, :strict_variables, :strict_filters, :environment
27
19
 
28
- @this_stack_used = false
20
+ # rubocop:disable Metrics/ParameterLists
21
+ def self.build(environment: Environment.default, environments: {}, outer_scope: {}, registers: {}, rethrow_errors: false, resource_limits: nil, static_environments: {}, &block)
22
+ new(environments, outer_scope, registers, rethrow_errors, resource_limits, static_environments, environment, &block)
23
+ end
29
24
 
30
- self.exception_renderer = Template.default_exception_renderer
25
+ def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil, static_environments = {}, environment = Environment.default)
26
+ @environment = environment
27
+ @environments = [environments]
28
+ @environments.flatten!
29
+
30
+ @static_environments = [static_environments].flatten(1).freeze
31
+ @scopes = [outer_scope || {}]
32
+ @registers = registers.is_a?(Registers) ? registers : Registers.new(registers)
33
+ @errors = []
34
+ @partial = false
35
+ @strict_variables = false
36
+ @resource_limits = resource_limits || ResourceLimits.new(environment.default_resource_limits)
37
+ @base_scope_depth = 0
38
+ @interrupts = []
39
+ @filters = []
40
+ @global_filter = nil
41
+ @disabled_tags = {}
42
+
43
+ # Instead of constructing new StringScanner objects for each Expression parse,
44
+ # we recycle the same one.
45
+ @string_scanner = StringScanner.new("")
46
+
47
+ @registers.static[:cached_partials] ||= {}
48
+ @registers.static[:file_system] ||= environment.file_system
49
+ @registers.static[:template_factory] ||= Liquid::TemplateFactory.new
50
+
51
+ self.exception_renderer = environment.exception_renderer
31
52
  if rethrow_errors
32
- self.exception_renderer = ->(e) { raise }
53
+ self.exception_renderer = Liquid::RAISE_EXCEPTION_LAMBDA
33
54
  end
34
55
 
35
- @interrupts = []
36
- @filters = []
37
- @global_filter = nil
56
+ yield self if block_given?
57
+
58
+ # Do this last, since it could result in this object being passed to a Proc in the environment
59
+ squash_instance_assigns_with_environments
38
60
  end
61
+ # rubocop:enable Metrics/ParameterLists
39
62
 
40
63
  def warnings
41
64
  @warnings ||= []
42
65
  end
43
66
 
44
67
  def strainer
45
- @strainer ||= Strainer.create(self, @filters)
68
+ @strainer ||= @environment.create_strainer(self, @filters)
46
69
  end
47
70
 
48
71
  # Adds filters to this context.
@@ -74,10 +97,10 @@ module Liquid
74
97
  @interrupts.pop
75
98
  end
76
99
 
77
- def handle_error(e, line_number = nil, raw_token = nil)
100
+ def handle_error(e, line_number = nil)
78
101
  e = internal_error unless e.is_a?(Liquid::Error)
79
102
  e.template_name ||= template_name
80
- e.line_number ||= line_number
103
+ e.line_number ||= line_number
81
104
  errors.push(e)
82
105
  exception_renderer.call(e).to_s
83
106
  end
@@ -89,7 +112,7 @@ module Liquid
89
112
  # Push new local scope on the stack. use <tt>Context#stack</tt> instead
90
113
  def push(new_scope = {})
91
114
  @scopes.unshift(new_scope)
92
- raise StackLevelError, "Nesting too deep".freeze if @scopes.length > 100
115
+ check_overflow
93
116
  end
94
117
 
95
118
  # Merge a hash of variables in the current local scope
@@ -110,20 +133,33 @@ module Liquid
110
133
  # context['var'] = 'hi'
111
134
  # end
112
135
  #
113
- # context['var] #=> nil
114
- def stack(new_scope = nil)
115
- old_stack_used = @this_stack_used
116
- if new_scope
117
- push(new_scope)
118
- @this_stack_used = true
119
- else
120
- @this_stack_used = false
121
- end
122
-
136
+ # context['var'] #=> nil
137
+ def stack(new_scope = {})
138
+ push(new_scope)
123
139
  yield
124
140
  ensure
125
- pop if @this_stack_used
126
- @this_stack_used = old_stack_used
141
+ pop
142
+ end
143
+
144
+ # Creates a new context inheriting resource limits, filters, environment etc.,
145
+ # but with an isolated scope.
146
+ def new_isolated_subcontext
147
+ check_overflow
148
+
149
+ self.class.build(
150
+ environment: @environment,
151
+ resource_limits: resource_limits,
152
+ static_environments: static_environments,
153
+ registers: Registers.new(registers),
154
+ ).tap do |subcontext|
155
+ subcontext.base_scope_depth = base_scope_depth + 1
156
+ subcontext.exception_renderer = exception_renderer
157
+ subcontext.filters = @filters
158
+ subcontext.strainer = nil
159
+ subcontext.errors = errors
160
+ subcontext.warnings = warnings
161
+ subcontext.disabled_tags = @disabled_tags
162
+ end
127
163
  end
128
164
 
129
165
  def clear_instance_assigns
@@ -132,10 +168,6 @@ module Liquid
132
168
 
133
169
  # Only allow String, Numeric, Hash, Array, Proc, Boolean or <tt>Liquid::Drop</tt>
134
170
  def []=(key, value)
135
- unless @this_stack_used
136
- @this_stack_used = true
137
- push({})
138
- end
139
171
  @scopes[0][key] = value
140
172
  end
141
173
 
@@ -148,7 +180,7 @@ module Liquid
148
180
  # Example:
149
181
  # products == empty #=> products.empty?
150
182
  def [](expression)
151
- evaluate(Expression.parse(expression))
183
+ evaluate(Expression.parse(expression, @string_scanner))
152
184
  end
153
185
 
154
186
  def key?(key)
@@ -160,49 +192,88 @@ module Liquid
160
192
  end
161
193
 
162
194
  # Fetches an object starting at the local scope and then moving up the hierachy
163
- def find_variable(key)
195
+ def find_variable(key, raise_on_not_found: true)
164
196
  # This was changed from find() to find_index() because this is a very hot
165
197
  # path and find_index() is optimized in MRI to reduce object allocation
166
198
  index = @scopes.find_index { |s| s.key?(key) }
167
- scope = @scopes[index] if index
168
-
169
- variable = nil
170
199
 
171
- if scope.nil?
172
- @environments.each do |e|
173
- variable = lookup_and_evaluate(e, key)
174
- unless variable.nil?
175
- scope = e
176
- break
177
- end
178
- end
200
+ variable = if index
201
+ lookup_and_evaluate(@scopes[index], key, raise_on_not_found: raise_on_not_found)
202
+ else
203
+ try_variable_find_in_environments(key, raise_on_not_found: raise_on_not_found)
179
204
  end
180
205
 
181
- scope ||= @environments.last || @scopes.last
182
- variable ||= lookup_and_evaluate(scope, key)
183
-
184
- variable = variable.to_liquid
206
+ # update variable's context before invoking #to_liquid
185
207
  variable.context = self if variable.respond_to?(:context=)
186
208
 
187
- variable
209
+ liquid_variable = variable.to_liquid
210
+
211
+ liquid_variable.context = self if variable != liquid_variable && liquid_variable.respond_to?(:context=)
212
+
213
+ liquid_variable
188
214
  end
189
215
 
190
- def lookup_and_evaluate(obj, key)
191
- if @strict_variables && obj.respond_to?(:key?) && !obj.key?(key)
216
+ def lookup_and_evaluate(obj, key, raise_on_not_found: true)
217
+ if @strict_variables && raise_on_not_found && obj.respond_to?(:key?) && !obj.key?(key)
192
218
  raise Liquid::UndefinedVariable, "undefined variable #{key}"
193
219
  end
194
220
 
195
221
  value = obj[key]
196
222
 
197
223
  if value.is_a?(Proc) && obj.respond_to?(:[]=)
198
- obj[key] = (value.arity == 0) ? value.call : value.call(self)
224
+ obj[key] = value.arity == 0 ? value.call : value.call(self)
199
225
  else
200
226
  value
201
227
  end
202
228
  end
203
229
 
230
+ def with_disabled_tags(tag_names)
231
+ tag_names.each do |name|
232
+ @disabled_tags[name] = @disabled_tags.fetch(name, 0) + 1
233
+ end
234
+ yield
235
+ ensure
236
+ tag_names.each do |name|
237
+ @disabled_tags[name] -= 1
238
+ end
239
+ end
240
+
241
+ def tag_disabled?(tag_name)
242
+ @disabled_tags.fetch(tag_name, 0) > 0
243
+ end
244
+
245
+ protected
246
+
247
+ attr_writer :base_scope_depth, :warnings, :errors, :strainer, :filters, :disabled_tags
248
+
204
249
  private
205
250
 
251
+ attr_reader :base_scope_depth
252
+
253
+ def try_variable_find_in_environments(key, raise_on_not_found:)
254
+ @environments.each do |environment|
255
+ found_variable = lookup_and_evaluate(environment, key, raise_on_not_found: raise_on_not_found)
256
+ if !found_variable.nil? || @strict_variables && raise_on_not_found
257
+ return found_variable
258
+ end
259
+ end
260
+ @static_environments.each do |environment|
261
+ found_variable = lookup_and_evaluate(environment, key, raise_on_not_found: raise_on_not_found)
262
+ if !found_variable.nil? || @strict_variables && raise_on_not_found
263
+ return found_variable
264
+ end
265
+ end
266
+ nil
267
+ end
268
+
269
+ def check_overflow
270
+ raise StackLevelError, "Nesting too deep" if overflow?
271
+ end
272
+
273
+ def overflow?
274
+ base_scope_depth + @scopes.length > Block::MAX_DEPTH
275
+ end
276
+
206
277
  def internal_error
207
278
  # raise and catch to set backtrace and cause on exception
208
279
  raise Liquid::InternalError, 'internal'
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+
5
+ module Liquid
6
+ class Deprecations
7
+ class << self
8
+ attr_accessor :warned
9
+
10
+ Deprecations.warned = Set.new
11
+
12
+ def warn(name, alternative)
13
+ return if warned.include?(name)
14
+
15
+ warned << name
16
+
17
+ caller_location = caller_locations(2, 1).first
18
+ Warning.warn("[DEPRECATION] #{name} is deprecated. Use #{alternative} instead. Called from #{caller_location}\n")
19
+ end
20
+ end
21
+ end
22
+ end
@@ -1,26 +1,64 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
- class Document < BlockBody
4
+ class Document
3
5
  def self.parse(tokens, parse_context)
4
- doc = new
6
+ doc = new(parse_context)
5
7
  doc.parse(tokens, parse_context)
6
8
  doc
7
9
  end
8
10
 
9
- def parse(tokens, parse_context)
10
- super do |end_tag_name, end_tag_params|
11
- unknown_tag(end_tag_name, parse_context) if end_tag_name
11
+ attr_reader :parse_context, :body
12
+
13
+ def initialize(parse_context)
14
+ @parse_context = parse_context
15
+ @body = new_body
16
+ end
17
+
18
+ def nodelist
19
+ @body.nodelist
20
+ end
21
+
22
+ def parse(tokenizer, parse_context)
23
+ while parse_body(tokenizer)
12
24
  end
25
+ @body.freeze
13
26
  rescue SyntaxError => e
14
27
  e.line_number ||= parse_context.line_number
15
28
  raise
16
29
  end
17
30
 
18
- def unknown_tag(tag, parse_context)
31
+ def unknown_tag(tag, _markup, _tokenizer)
19
32
  case tag
20
- when 'else'.freeze, 'end'.freeze
21
- raise SyntaxError.new(parse_context.locale.t("errors.syntax.unexpected_outer_tag".freeze, tag: tag))
33
+ when 'else', 'end'
34
+ raise SyntaxError, parse_context.locale.t("errors.syntax.unexpected_outer_tag", tag: tag)
22
35
  else
23
- raise SyntaxError.new(parse_context.locale.t("errors.syntax.unknown_tag".freeze, tag: tag))
36
+ raise SyntaxError, parse_context.locale.t("errors.syntax.unknown_tag", tag: tag)
37
+ end
38
+ end
39
+
40
+ def render_to_output_buffer(context, output)
41
+ @body.render_to_output_buffer(context, output)
42
+ end
43
+
44
+ def render(context)
45
+ render_to_output_buffer(context, +'')
46
+ end
47
+
48
+ private
49
+
50
+ def new_body
51
+ parse_context.new_block_body
52
+ end
53
+
54
+ def parse_body(tokenizer)
55
+ @body.parse(tokenizer, parse_context) do |unknown_tag_name, unknown_tag_markup|
56
+ if unknown_tag_name
57
+ unknown_tag(unknown_tag_name, unknown_tag_markup, tokenizer)
58
+ true
59
+ else
60
+ false
61
+ end
24
62
  end
25
63
  end
26
64
  end
data/lib/liquid/drop.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'set'
2
4
 
3
5
  module Liquid
@@ -23,9 +25,13 @@ module Liquid
23
25
  class Drop
24
26
  attr_writer :context
25
27
 
28
+ def initialize
29
+ @context = nil
30
+ end
31
+
26
32
  # Catch all for the method
27
33
  def liquid_method_missing(method)
28
- return nil unless @context && @context.strict_variables
34
+ return nil unless @context&.strict_variables
29
35
  raise Liquid::UndefinedDropMethod, "undefined method #{method}"
30
36
  end
31
37
 
@@ -67,7 +73,7 @@ module Liquid
67
73
 
68
74
  if include?(Enumerable)
69
75
  blacklist += Enumerable.public_instance_methods
70
- blacklist -= [:sort, :count, :first, :min, :max, :include?]
76
+ blacklist -= [:sort, :count, :first, :min, :max]
71
77
  end
72
78
 
73
79
  whitelist = [:to_liquid] + (public_instance_methods - blacklist)
@@ -0,0 +1,159 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Liquid
4
+ # The Environment is the container for all configuration options of Liquid, such as
5
+ # the registered tags, filters, and the default error mode.
6
+ class Environment
7
+ # The default error mode for all templates. This can be overridden on a
8
+ # per-template basis.
9
+ attr_accessor :error_mode
10
+
11
+ # The tags that are available to use in the template.
12
+ attr_accessor :tags
13
+
14
+ # The strainer template which is used to store filters that are available to
15
+ # use in templates.
16
+ attr_accessor :strainer_template
17
+
18
+ # The exception renderer that is used to render exceptions that are raised
19
+ # when rendering a template
20
+ attr_accessor :exception_renderer
21
+
22
+ # The default file system that is used to load templates from.
23
+ attr_accessor :file_system
24
+
25
+ # The default resource limits that are used to limit the resources that a
26
+ # template can consume.
27
+ attr_accessor :default_resource_limits
28
+
29
+ class << self
30
+ # Creates a new environment instance.
31
+ #
32
+ # @param tags [Hash] The tags that are available to use in
33
+ # the template.
34
+ # @param file_system The default file system that is used
35
+ # to load templates from.
36
+ # @param error_mode [Symbol] The default error mode for all templates
37
+ # (either :rigid, :strict, :warn, or :lax).
38
+ # @param exception_renderer [Proc] The exception renderer that is used to
39
+ # render exceptions.
40
+ # @yieldparam environment [Environment] The environment instance that is being built.
41
+ # @return [Environment] The new environment instance.
42
+ def build(tags: nil, file_system: nil, error_mode: nil, exception_renderer: nil)
43
+ ret = new
44
+ ret.tags = tags if tags
45
+ ret.file_system = file_system if file_system
46
+ ret.error_mode = error_mode if error_mode
47
+ ret.exception_renderer = exception_renderer if exception_renderer
48
+ yield ret if block_given?
49
+ ret.freeze
50
+ end
51
+
52
+ # Returns the default environment instance.
53
+ #
54
+ # @return [Environment] The default environment instance.
55
+ def default
56
+ @default ||= new
57
+ end
58
+
59
+ # Sets the default environment instance for the duration of the block
60
+ #
61
+ # @param environment [Environment] The environment instance to use as the default for the
62
+ # duration of the block.
63
+ # @yield
64
+ # @return [Object] The return value of the block.
65
+ def dangerously_override(environment)
66
+ original_default = @default
67
+ @default = environment
68
+ yield
69
+ ensure
70
+ @default = original_default
71
+ end
72
+ end
73
+
74
+ # Initializes a new environment instance.
75
+ # @api private
76
+ def initialize
77
+ @tags = Tags::STANDARD_TAGS.dup
78
+ @error_mode = :lax
79
+ @strainer_template = Class.new(StrainerTemplate).tap do |klass|
80
+ klass.add_filter(StandardFilters)
81
+ end
82
+ @exception_renderer = ->(exception) { exception }
83
+ @file_system = BlankFileSystem.new
84
+ @default_resource_limits = Const::EMPTY_HASH
85
+ @strainer_template_class_cache = {}
86
+ end
87
+
88
+ # Registers a new tag with the environment.
89
+ #
90
+ # @param name [String] The name of the tag.
91
+ # @param klass [Liquid::Tag] The class that implements the tag.
92
+ # @return [void]
93
+ def register_tag(name, klass)
94
+ @tags[name] = klass
95
+ end
96
+
97
+ # Registers a new filter with the environment.
98
+ #
99
+ # @param filter [Module] The module that contains the filter methods.
100
+ # @return [void]
101
+ def register_filter(filter)
102
+ @strainer_template_class_cache.clear
103
+ @strainer_template.add_filter(filter)
104
+ end
105
+
106
+ # Registers multiple filters with this environment.
107
+ #
108
+ # @param filters [Array<Module>] The modules that contain the filter methods.
109
+ # @return [self]
110
+ def register_filters(filters)
111
+ @strainer_template_class_cache.clear
112
+ filters.each { |f| @strainer_template.add_filter(f) }
113
+ self
114
+ end
115
+
116
+ # Creates a new strainer instance with the given filters, caching the result
117
+ # for faster lookup.
118
+ #
119
+ # @param context [Liquid::Context] The context that the strainer will be
120
+ # used in.
121
+ # @param filters [Array<Module>] The filters that the strainer will have
122
+ # access to.
123
+ # @return [Liquid::Strainer] The new strainer instance.
124
+ def create_strainer(context, filters = Const::EMPTY_ARRAY)
125
+ return @strainer_template.new(context) if filters.empty?
126
+
127
+ strainer_template = @strainer_template_class_cache[filters] ||= begin
128
+ klass = Class.new(@strainer_template)
129
+ filters.each { |f| klass.add_filter(f) }
130
+ klass
131
+ end
132
+
133
+ strainer_template.new(context)
134
+ end
135
+
136
+ # Returns the names of all the filter methods that are available to use in
137
+ # the strainer template.
138
+ #
139
+ # @return [Array<String>] The names of all the filter methods.
140
+ def filter_method_names
141
+ @strainer_template.filter_method_names
142
+ end
143
+
144
+ # Returns the tag class for the given tag name.
145
+ #
146
+ # @param name [String] The name of the tag.
147
+ # @return [Liquid::Tag] The tag class.
148
+ def tag_for_name(name)
149
+ @tags[name]
150
+ end
151
+
152
+ def freeze
153
+ @tags.freeze
154
+ # TODO: freeze the tags, currently this is not possible because of liquid-c
155
+ # @strainer_template.freeze
156
+ super
157
+ end
158
+ end
159
+ end
data/lib/liquid/errors.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Liquid
2
4
  class Error < ::StandardError
3
5
  attr_accessor :line_number
@@ -5,7 +7,7 @@ module Liquid
5
7
  attr_accessor :markup_context
6
8
 
7
9
  def to_s(with_prefix = true)
8
- str = ""
10
+ str = +""
9
11
  str << message_prefix if with_prefix
10
12
  str << super()
11
13
 
@@ -20,11 +22,11 @@ module Liquid
20
22
  private
21
23
 
22
24
  def message_prefix
23
- str = ""
24
- if is_a?(SyntaxError)
25
- str << "Liquid syntax error"
25
+ str = +""
26
+ str << if is_a?(SyntaxError)
27
+ "Liquid syntax error"
26
28
  else
27
- str << "Liquid error"
29
+ "Liquid error"
28
30
  end
29
31
 
30
32
  if line_number
@@ -38,19 +40,20 @@ module Liquid
38
40
  end
39
41
  end
40
42
 
41
- ArgumentError = Class.new(Error)
42
- ContextError = Class.new(Error)
43
- FileSystemError = Class.new(Error)
44
- StandardError = Class.new(Error)
45
- SyntaxError = Class.new(Error)
46
- StackLevelError = Class.new(Error)
47
- TaintedError = Class.new(Error)
48
- MemoryError = Class.new(Error)
49
- ZeroDivisionError = Class.new(Error)
50
- FloatDomainError = Class.new(Error)
51
- UndefinedVariable = Class.new(Error)
52
- UndefinedDropMethod = Class.new(Error)
53
- UndefinedFilter = Class.new(Error)
54
- MethodOverrideError = Class.new(Error)
55
- InternalError = Class.new(Error)
43
+ ArgumentError = Class.new(Error)
44
+ ContextError = Class.new(Error)
45
+ FileSystemError = Class.new(Error)
46
+ StandardError = Class.new(Error)
47
+ SyntaxError = Class.new(Error)
48
+ StackLevelError = Class.new(Error)
49
+ MemoryError = Class.new(Error)
50
+ ZeroDivisionError = Class.new(Error)
51
+ FloatDomainError = Class.new(Error)
52
+ UndefinedVariable = Class.new(Error)
53
+ UndefinedDropMethod = Class.new(Error)
54
+ UndefinedFilter = Class.new(Error)
55
+ MethodOverrideError = Class.new(Error)
56
+ DisabledError = Class.new(Error)
57
+ InternalError = Class.new(Error)
58
+ TemplateEncodingError = Class.new(Error)
56
59
  end