liquid 5.4.0 → 5.6.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/History.md +11 -0
  3. data/README.md +48 -6
  4. data/lib/liquid/block.rb +8 -4
  5. data/lib/liquid/block_body.rb +28 -10
  6. data/lib/liquid/condition.rb +9 -4
  7. data/lib/liquid/const.rb +8 -0
  8. data/lib/liquid/context.rb +24 -14
  9. data/lib/liquid/deprecations.rb +22 -0
  10. data/lib/liquid/drop.rb +4 -0
  11. data/lib/liquid/environment.rb +159 -0
  12. data/lib/liquid/errors.rb +16 -15
  13. data/lib/liquid/expression.rb +101 -22
  14. data/lib/liquid/forloop_drop.rb +2 -5
  15. data/lib/liquid/lexer.rb +155 -44
  16. data/lib/liquid/locales/en.yml +1 -0
  17. data/lib/liquid/parse_context.rb +29 -6
  18. data/lib/liquid/parse_tree_visitor.rb +1 -1
  19. data/lib/liquid/parser.rb +3 -3
  20. data/lib/liquid/partial_cache.rb +12 -3
  21. data/lib/liquid/range_lookup.rb +14 -4
  22. data/lib/liquid/standardfilters.rb +82 -21
  23. data/lib/liquid/tablerowloop_drop.rb +1 -1
  24. data/lib/liquid/tag/disabler.rb +0 -8
  25. data/lib/liquid/tag.rb +13 -3
  26. data/lib/liquid/tags/assign.rb +1 -3
  27. data/lib/liquid/tags/break.rb +1 -3
  28. data/lib/liquid/tags/capture.rb +0 -2
  29. data/lib/liquid/tags/case.rb +1 -3
  30. data/lib/liquid/tags/comment.rb +60 -3
  31. data/lib/liquid/tags/continue.rb +1 -3
  32. data/lib/liquid/tags/cycle.rb +14 -4
  33. data/lib/liquid/tags/decrement.rb +8 -7
  34. data/lib/liquid/tags/echo.rb +2 -4
  35. data/lib/liquid/tags/for.rb +6 -8
  36. data/lib/liquid/tags/if.rb +3 -5
  37. data/lib/liquid/tags/ifchanged.rb +0 -2
  38. data/lib/liquid/tags/include.rb +8 -8
  39. data/lib/liquid/tags/increment.rb +8 -7
  40. data/lib/liquid/tags/inline_comment.rb +0 -15
  41. data/lib/liquid/tags/raw.rb +2 -4
  42. data/lib/liquid/tags/render.rb +14 -12
  43. data/lib/liquid/tags/table_row.rb +18 -6
  44. data/lib/liquid/tags/unless.rb +3 -5
  45. data/lib/liquid/tags.rb +47 -0
  46. data/lib/liquid/template.rb +60 -57
  47. data/lib/liquid/tokenizer.rb +127 -11
  48. data/lib/liquid/variable.rb +14 -8
  49. data/lib/liquid/variable_lookup.rb +13 -5
  50. data/lib/liquid/version.rb +1 -1
  51. data/lib/liquid.rb +15 -16
  52. metadata +37 -10
  53. data/lib/liquid/strainer_factory.rb +0 -41
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c8408df245a1cc22ee1154fe5387e3e41eb3c740f7277e7d3736c1863d387e47
4
- data.tar.gz: 64549a58828fd7e9e0eb7310cb4371e75e64a7c3249fe9ebd021039e3334bba1
3
+ metadata.gz: 33f5ba5a5f6175dc7ddf6f0f618581fac11cf81b33ed508f174590f2a0911b4a
4
+ data.tar.gz: 07e512179364e4c9be0649ada3495a5314e1c57d97e91b697cac098c175bd1c7
5
5
  SHA512:
6
- metadata.gz: 29aaff16e3bc464712cdcac3aa205df943132198ef9568d46f4a123d327849f7ead7bd669a095eae0425581eb59ea08874ac38664bf9d8df00b0aa99917c7768
7
- data.tar.gz: 9f6070c39733b7f4064f70676b1f886fc61070f3ba8921360b9c5fcdb09c217b3e58c3d59d8293b24b6c18e55899ed17552a2f867ea8eb95e7dee9a7deb29ae5
6
+ metadata.gz: 7f54ca814d38f03c384b147776cb645686c61a5962bf9e736220d60bd2b6c0a9e5e853643429349170a1920ce5b1373b3d0c6ff47118afc0568368823e6dc920
7
+ data.tar.gz: 46ecbe3e271d8a7083d8d58f360d9476594d434561cbafec829cd1294ceb44c2a623096e084206ef220808974f7a804a92945b0a60290d414ca9651ceb74f20c
data/History.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Liquid Change Log
2
2
 
3
+ ## 5.6.0 (unreleased)
4
+
5
+ ### Fixes
6
+
7
+ * Fix Tokenizer to handle null source value (#1873) [Bahar Pourazar]
8
+
9
+
10
+ ## 5.5.0 2024-03-21
11
+
12
+ Please reference the GitHub release for more information.
13
+
3
14
  ## 5.4.0 2022-07-29
4
15
 
5
16
  ### Breaking Changes
data/README.md CHANGED
@@ -1,11 +1,11 @@
1
- [![Build Status](https://api.travis-ci.org/Shopify/liquid.svg?branch=master)](http://travis-ci.org/Shopify/liquid)
1
+ [![Build status](https://github.com/Shopify/liquid/actions/workflows/liquid.yml/badge.svg)](https://github.com/Shopify/liquid/actions/workflows/liquid.yml)
2
2
  [![Inline docs](http://inch-ci.org/github/Shopify/liquid.svg?branch=master)](http://inch-ci.org/github/Shopify/liquid)
3
3
 
4
4
  # Liquid template engine
5
5
 
6
6
  * [Contributing guidelines](CONTRIBUTING.md)
7
7
  * [Version history](History.md)
8
- * [Liquid documentation from Shopify](https://shopify.dev/api/liquid)
8
+ * [Liquid documentation from Shopify](https://shopify.dev/docs/api/liquid)
9
9
  * [Liquid Wiki at GitHub](https://github.com/Shopify/liquid/wiki)
10
10
  * [Website](http://liquidmarkup.org/)
11
11
 
@@ -52,6 +52,47 @@ For standard use you can just pass it the content of a file and call render with
52
52
  @template.render('name' => 'tobi') # => "hi tobi"
53
53
  ```
54
54
 
55
+ ### Concept of Environments
56
+
57
+ In Liquid, a "Environment" is a scoped environment that encapsulates custom tags, filters, and other configurations. This allows you to define and isolate different sets of functionality for different contexts, avoiding global overrides that can lead to conflicts and unexpected behavior.
58
+
59
+ By using environments, you can:
60
+
61
+ 1. **Encapsulate Logic**: Keep the logic for different parts of your application separate.
62
+ 2. **Avoid Conflicts**: Prevent custom tags and filters from clashing with each other.
63
+ 3. **Improve Maintainability**: Make it easier to manage and understand the scope of customizations.
64
+ 4. **Enhance Security**: Limit the availability of certain tags and filters to specific contexts.
65
+
66
+ We encourage the use of Environments over globally overriding things because it promotes better software design principles such as modularity, encapsulation, and separation of concerns.
67
+
68
+ Here's an example of how you can define and use Environments in Liquid:
69
+
70
+ ```ruby
71
+ user_environment = Liquid::Environment.build do |environment|
72
+ environment.register_tag("renderobj", RenderObjTag)
73
+ end
74
+
75
+ Liquid::Template.parse(<<~LIQUID, environment: user_environment)
76
+ {% renderobj src: "path/to/model.obj" %}
77
+ LIQUID
78
+ ```
79
+
80
+ In this example, `RenderObjTag` is a custom tag that is only available within the `user_environment`.
81
+
82
+ Similarly, you can define another environment for a different context, such as email templates:
83
+
84
+ ```ruby
85
+ email_environment = Liquid::Environment.build do |environment|
86
+ environment.register_tag("unsubscribe_footer", UnsubscribeFooter)
87
+ end
88
+
89
+ Liquid::Template.parse(<<~LIQUID, environment: email_environment)
90
+ {% unsubscribe_footer %}
91
+ LIQUID
92
+ ```
93
+
94
+ By using Environments, you ensure that custom tags and filters are only available in the contexts where they are needed, making your Liquid templates more robust and easier to manage. For smaller projects, a global environment is available via `Liquid::Environment.default`.
95
+
55
96
  ### Error Modes
56
97
 
57
98
  Setting the error mode of Liquid lets you specify how strictly you want your templates to be interpreted.
@@ -62,9 +103,10 @@ Liquid also comes with a stricter parser that can be used when editing templates
62
103
  when templates are invalid. You can enable this new parser like this:
63
104
 
64
105
  ```ruby
65
- Liquid::Template.error_mode = :strict # Raises a SyntaxError when invalid syntax is used
66
- Liquid::Template.error_mode = :warn # Adds strict errors to template.errors but continues as normal
67
- Liquid::Template.error_mode = :lax # The default mode, accepts almost anything.
106
+ Liquid::Environment.default.error_mode = :strict
107
+ Liquid::Environment.default.error_mode = :strict # Raises a SyntaxError when invalid syntax is used
108
+ Liquid::Environment.default.error_mode = :warn # Adds strict errors to template.errors but continues as normal
109
+ Liquid::Environment.default.error_mode = :lax # The default mode, accepts almost anything.
68
110
  ```
69
111
 
70
112
  If you want to set the error mode only on specific templates you can pass `:error_mode` as an option to `parse`:
@@ -111,4 +153,4 @@ template.render!({ 'x' => 1}, { strict_variables: true })
111
153
 
112
154
  To help track usages of a feature or code path in production, we have released opt-in usage tracking. To enable this, we provide an empty `Liquid:: Usage.increment` method which you can customize to your needs. The feature is well suited to https://github.com/Shopify/statsd-instrument. However, the choice of implementation is up to you.
113
155
 
114
- Once you have enabled usage tracking, we recommend reporting any events through Github Issues that your system may be logging. It is highly likely this event has been added to consider deprecating or improving code specific to this event, so please raise any concerns.
156
+ Once you have enabled usage tracking, we recommend reporting any events through Github Issues that your system may be logging. It is highly likely this event has been added to consider deprecating or improving code specific to this event, so please raise any concerns.
data/lib/liquid/block.rb CHANGED
@@ -36,13 +36,17 @@ module Liquid
36
36
  # @api private
37
37
  def self.raise_unknown_tag(tag, block_name, block_delimiter, parse_context)
38
38
  if tag == 'else'
39
- raise SyntaxError, parse_context.locale.t("errors.syntax.unexpected_else",
40
- block_name: block_name)
39
+ raise SyntaxError, parse_context.locale.t(
40
+ "errors.syntax.unexpected_else",
41
+ block_name: block_name,
42
+ )
41
43
  elsif tag.start_with?('end')
42
- raise SyntaxError, parse_context.locale.t("errors.syntax.invalid_delimiter",
44
+ raise SyntaxError, parse_context.locale.t(
45
+ "errors.syntax.invalid_delimiter",
43
46
  tag: tag,
44
47
  block_name: block_name,
45
- block_delimiter: block_delimiter)
48
+ block_delimiter: block_delimiter,
49
+ )
46
50
  else
47
51
  raise SyntaxError, parse_context.locale.t("errors.syntax.unknown_tag", tag: tag)
48
52
  end
@@ -6,6 +6,7 @@ module Liquid
6
6
  class BlockBody
7
7
  LiquidTagToken = /\A\s*(#{TagName})\s*(.*?)\z/o
8
8
  FullToken = /\A#{TagStart}#{WhitespaceControl}?(\s*)(#{TagName})(\s*)(.*?)#{WhitespaceControl}?#{TagEnd}\z/om
9
+ FullTokenPossiblyInvalid = /\A(.*)#{TagStart}#{WhitespaceControl}?\s*(\w+)\s*(.*)?#{WhitespaceControl}?#{TagEnd}\z/om
9
10
  ContentOfVariable = /\A#{VariableStart}#{WhitespaceControl}?(.*?)#{WhitespaceControl}?#{VariableEnd}\z/om
10
11
  WhitespaceOrNothing = /\A\s*\z/
11
12
  TAGSTART = "{%"
@@ -45,7 +46,13 @@ module Liquid
45
46
  end
46
47
  tag_name = Regexp.last_match(1)
47
48
  markup = Regexp.last_match(2)
48
- unless (tag = registered_tags[tag_name])
49
+
50
+ if tag_name == 'liquid'
51
+ parse_context.line_number -= 1
52
+ next parse_liquid_tag(markup, parse_context)
53
+ end
54
+
55
+ unless (tag = parse_context.environment.tag_for_name(tag_name))
49
56
  # end parsing if we reach an unknown tag and let the caller decide
50
57
  # determine how to proceed
51
58
  return yield tag_name, markup
@@ -109,14 +116,22 @@ module Liquid
109
116
  end
110
117
  end
111
118
 
112
- private def parse_for_document(tokenizer, parse_context)
119
+ private def handle_invalid_tag_token(token, parse_context)
120
+ if token.end_with?('%}')
121
+ yield token, token
122
+ else
123
+ BlockBody.raise_missing_tag_terminator(token, parse_context)
124
+ end
125
+ end
126
+
127
+ private def parse_for_document(tokenizer, parse_context, &block)
113
128
  while (token = tokenizer.shift)
114
129
  next if token.empty?
115
130
  case
116
131
  when token.start_with?(TAGSTART)
117
132
  whitespace_handler(token, parse_context)
118
133
  unless token =~ FullToken
119
- BlockBody.raise_missing_tag_terminator(token, parse_context)
134
+ return handle_invalid_tag_token(token, parse_context, &block)
120
135
  end
121
136
  tag_name = Regexp.last_match(2)
122
137
  markup = Regexp.last_match(4)
@@ -132,7 +147,7 @@ module Liquid
132
147
  next
133
148
  end
134
149
 
135
- unless (tag = registered_tags[tag_name])
150
+ unless (tag = parse_context.environment.tag_for_name(tag_name))
136
151
  # end parsing if we reach an unknown tag and let the caller decide
137
152
  # determine how to proceed
138
153
  return yield tag_name, markup
@@ -231,10 +246,17 @@ module Liquid
231
246
  end
232
247
 
233
248
  def create_variable(token, parse_context)
234
- if token =~ ContentOfVariable
235
- markup = Regexp.last_match(1)
249
+ if token.end_with?("}}")
250
+ i = 2
251
+ i = 3 if token[i] == "-"
252
+ parse_end = token.length - 3
253
+ parse_end -= 1 if token[parse_end] == "-"
254
+ markup_end = parse_end - i + 1
255
+ markup = markup_end <= 0 ? "" : token.slice(i, markup_end)
256
+
236
257
  return Variable.new(markup, parse_context)
237
258
  end
259
+
238
260
  BlockBody.raise_missing_variable_terminator(token, parse_context)
239
261
  end
240
262
 
@@ -247,9 +269,5 @@ module Liquid
247
269
  def raise_missing_variable_terminator(token, parse_context)
248
270
  BlockBody.raise_missing_variable_terminator(token, parse_context)
249
271
  end
250
-
251
- def registered_tags
252
- Template.tags
253
- end
254
272
  end
255
273
  end
@@ -24,6 +24,9 @@ module Liquid
24
24
  else
25
25
  false
26
26
  end
27
+ rescue Encoding::CompatibilityError
28
+ # "✅".b.include?("✅") raises Encoding::CompatibilityError despite being materially equal
29
+ left.b.include?(right.b)
27
30
  end,
28
31
  }
29
32
 
@@ -69,9 +72,9 @@ module Liquid
69
72
 
70
73
  case condition.child_relation
71
74
  when :or
72
- break if result
75
+ break if Liquid::Utils.to_liquid_value(result)
73
76
  when :and
74
- break unless result
77
+ break unless Liquid::Utils.to_liquid_value(result)
75
78
  else
76
79
  break
77
80
  end
@@ -159,8 +162,10 @@ module Liquid
159
162
  class ParseTreeVisitor < Liquid::ParseTreeVisitor
160
163
  def children
161
164
  [
162
- @node.left, @node.right,
163
- @node.child_condition, @node.attachment
165
+ @node.left,
166
+ @node.right,
167
+ @node.child_condition,
168
+ @node.attachment
164
169
  ].compact
165
170
  end
166
171
  end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Liquid
4
+ module Const
5
+ EMPTY_HASH = {}.freeze
6
+ EMPTY_ARRAY = [].freeze
7
+ end
8
+ end
@@ -15,35 +15,40 @@ module Liquid
15
15
  # context['bob'] #=> nil class Context
16
16
  class Context
17
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
18
+ attr_accessor :exception_renderer, :template_name, :partial, :global_filter, :strict_variables, :strict_filters, :environment
19
19
 
20
20
  # rubocop:disable Metrics/ParameterLists
21
- def self.build(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, &block)
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
23
  end
24
24
 
25
- def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil, static_environments = {})
25
+ def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil, static_environments = {}, environment = Environment.default)
26
+ @environment = environment
26
27
  @environments = [environments]
27
28
  @environments.flatten!
28
29
 
29
- @static_environments = [static_environments].flat_map(&:freeze).freeze
30
- @scopes = [(outer_scope || {})]
30
+ @static_environments = [static_environments].flatten(1).freeze
31
+ @scopes = [outer_scope || {}]
31
32
  @registers = registers.is_a?(Registers) ? registers : Registers.new(registers)
32
33
  @errors = []
33
34
  @partial = false
34
35
  @strict_variables = false
35
- @resource_limits = resource_limits || ResourceLimits.new(Template.default_resource_limits)
36
+ @resource_limits = resource_limits || ResourceLimits.new(environment.default_resource_limits)
36
37
  @base_scope_depth = 0
37
38
  @interrupts = []
38
39
  @filters = []
39
40
  @global_filter = nil
40
41
  @disabled_tags = {}
41
42
 
43
+ # Instead of constructing new StringScanner objects for each Expression parse,
44
+ # we recycle the same one.
45
+ @string_scanner = StringScanner.new("")
46
+
42
47
  @registers.static[:cached_partials] ||= {}
43
- @registers.static[:file_system] ||= Liquid::Template.file_system
48
+ @registers.static[:file_system] ||= environment.file_system
44
49
  @registers.static[:template_factory] ||= Liquid::TemplateFactory.new
45
50
 
46
- self.exception_renderer = Template.default_exception_renderer
51
+ self.exception_renderer = environment.exception_renderer
47
52
  if rethrow_errors
48
53
  self.exception_renderer = Liquid::RAISE_EXCEPTION_LAMBDA
49
54
  end
@@ -60,7 +65,7 @@ module Liquid
60
65
  end
61
66
 
62
67
  def strainer
63
- @strainer ||= StrainerFactory.create(self, @filters)
68
+ @strainer ||= @environment.create_strainer(self, @filters)
64
69
  end
65
70
 
66
71
  # Adds filters to this context.
@@ -142,9 +147,10 @@ module Liquid
142
147
  check_overflow
143
148
 
144
149
  self.class.build(
150
+ environment: @environment,
145
151
  resource_limits: resource_limits,
146
152
  static_environments: static_environments,
147
- registers: Registers.new(registers)
153
+ registers: Registers.new(registers),
148
154
  ).tap do |subcontext|
149
155
  subcontext.base_scope_depth = base_scope_depth + 1
150
156
  subcontext.exception_renderer = exception_renderer
@@ -174,7 +180,7 @@ module Liquid
174
180
  # Example:
175
181
  # products == empty #=> products.empty?
176
182
  def [](expression)
177
- evaluate(Expression.parse(expression))
183
+ evaluate(Expression.parse(expression, @string_scanner))
178
184
  end
179
185
 
180
186
  def key?(key)
@@ -197,10 +203,14 @@ module Liquid
197
203
  try_variable_find_in_environments(key, raise_on_not_found: raise_on_not_found)
198
204
  end
199
205
 
200
- variable = variable.to_liquid
206
+ # update variable's context before invoking #to_liquid
201
207
  variable.context = self if variable.respond_to?(:context=)
202
208
 
203
- 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
204
214
  end
205
215
 
206
216
  def lookup_and_evaluate(obj, key, raise_on_not_found: true)
@@ -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
data/lib/liquid/drop.rb CHANGED
@@ -25,6 +25,10 @@ module Liquid
25
25
  class Drop
26
26
  attr_writer :context
27
27
 
28
+ def initialize
29
+ @context = nil
30
+ end
31
+
28
32
  # Catch all for the method
29
33
  def liquid_method_missing(method)
30
34
  return nil unless @context&.strict_variables
@@ -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 :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
@@ -40,19 +40,20 @@ module Liquid
40
40
  end
41
41
  end
42
42
 
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)
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)
58
59
  end