liquid 5.4.0 → 5.6.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/History.md +11 -0
- data/README.md +48 -6
- data/lib/liquid/block.rb +8 -4
- data/lib/liquid/block_body.rb +28 -10
- data/lib/liquid/condition.rb +9 -4
- data/lib/liquid/const.rb +8 -0
- data/lib/liquid/context.rb +24 -14
- data/lib/liquid/deprecations.rb +22 -0
- data/lib/liquid/drop.rb +4 -0
- data/lib/liquid/environment.rb +159 -0
- data/lib/liquid/errors.rb +16 -15
- data/lib/liquid/expression.rb +101 -22
- data/lib/liquid/forloop_drop.rb +2 -5
- data/lib/liquid/lexer.rb +155 -44
- data/lib/liquid/locales/en.yml +1 -0
- data/lib/liquid/parse_context.rb +29 -6
- data/lib/liquid/parse_tree_visitor.rb +1 -1
- data/lib/liquid/parser.rb +3 -3
- data/lib/liquid/partial_cache.rb +12 -3
- data/lib/liquid/range_lookup.rb +14 -4
- data/lib/liquid/standardfilters.rb +82 -21
- data/lib/liquid/tablerowloop_drop.rb +1 -1
- data/lib/liquid/tag/disabler.rb +0 -8
- data/lib/liquid/tag.rb +13 -3
- data/lib/liquid/tags/assign.rb +1 -3
- data/lib/liquid/tags/break.rb +1 -3
- data/lib/liquid/tags/capture.rb +0 -2
- data/lib/liquid/tags/case.rb +1 -3
- data/lib/liquid/tags/comment.rb +60 -3
- data/lib/liquid/tags/continue.rb +1 -3
- data/lib/liquid/tags/cycle.rb +14 -4
- data/lib/liquid/tags/decrement.rb +8 -7
- data/lib/liquid/tags/echo.rb +2 -4
- data/lib/liquid/tags/for.rb +6 -8
- data/lib/liquid/tags/if.rb +3 -5
- data/lib/liquid/tags/ifchanged.rb +0 -2
- data/lib/liquid/tags/include.rb +8 -8
- data/lib/liquid/tags/increment.rb +8 -7
- data/lib/liquid/tags/inline_comment.rb +0 -15
- data/lib/liquid/tags/raw.rb +2 -4
- data/lib/liquid/tags/render.rb +14 -12
- data/lib/liquid/tags/table_row.rb +18 -6
- data/lib/liquid/tags/unless.rb +3 -5
- data/lib/liquid/tags.rb +47 -0
- data/lib/liquid/template.rb +60 -57
- data/lib/liquid/tokenizer.rb +127 -11
- data/lib/liquid/variable.rb +14 -8
- data/lib/liquid/variable_lookup.rb +13 -5
- data/lib/liquid/version.rb +1 -1
- data/lib/liquid.rb +15 -16
- metadata +37 -10
- data/lib/liquid/strainer_factory.rb +0 -41
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 33f5ba5a5f6175dc7ddf6f0f618581fac11cf81b33ed508f174590f2a0911b4a
|
4
|
+
data.tar.gz: 07e512179364e4c9be0649ada3495a5314e1c57d97e91b697cac098c175bd1c7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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::
|
66
|
-
Liquid::
|
67
|
-
Liquid::
|
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(
|
40
|
-
|
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(
|
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
|
data/lib/liquid/block_body.rb
CHANGED
@@ -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
|
-
|
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
|
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
|
-
|
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 =
|
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
|
235
|
-
|
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
|
data/lib/liquid/condition.rb
CHANGED
@@ -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,
|
163
|
-
@node.
|
165
|
+
@node.left,
|
166
|
+
@node.right,
|
167
|
+
@node.child_condition,
|
168
|
+
@node.attachment
|
164
169
|
].compact
|
165
170
|
end
|
166
171
|
end
|
data/lib/liquid/const.rb
ADDED
data/lib/liquid/context.rb
CHANGED
@@ -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].
|
30
|
-
@scopes = [
|
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(
|
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] ||=
|
48
|
+
@registers.static[:file_system] ||= environment.file_system
|
44
49
|
@registers.static[:template_factory] ||= Liquid::TemplateFactory.new
|
45
50
|
|
46
|
-
self.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 ||=
|
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
|
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
@@ -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
|
44
|
-
ContextError
|
45
|
-
FileSystemError
|
46
|
-
StandardError
|
47
|
-
SyntaxError
|
48
|
-
StackLevelError
|
49
|
-
MemoryError
|
50
|
-
ZeroDivisionError
|
51
|
-
FloatDomainError
|
52
|
-
UndefinedVariable
|
53
|
-
UndefinedDropMethod
|
54
|
-
UndefinedFilter
|
55
|
-
MethodOverrideError
|
56
|
-
DisabledError
|
57
|
-
InternalError
|
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
|