liquid 3.0.6 → 5.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/History.md +243 -58
- data/README.md +43 -4
- data/lib/liquid/block.rb +57 -123
- data/lib/liquid/block_body.rb +217 -85
- data/lib/liquid/condition.rb +92 -45
- data/lib/liquid/context.rb +154 -89
- data/lib/liquid/document.rb +57 -9
- data/lib/liquid/drop.rb +20 -17
- data/lib/liquid/errors.rb +27 -29
- data/lib/liquid/expression.rb +32 -20
- data/lib/liquid/extensions.rb +21 -7
- data/lib/liquid/file_system.rb +17 -15
- data/lib/liquid/forloop_drop.rb +92 -0
- data/lib/liquid/i18n.rb +10 -8
- data/lib/liquid/interrupts.rb +4 -3
- data/lib/liquid/lexer.rb +37 -26
- data/lib/liquid/locales/en.yml +13 -6
- data/lib/liquid/parse_context.rb +54 -0
- data/lib/liquid/parse_tree_visitor.rb +42 -0
- data/lib/liquid/parser.rb +30 -18
- data/lib/liquid/parser_switching.rb +20 -6
- data/lib/liquid/partial_cache.rb +24 -0
- data/lib/liquid/profiler/hooks.rb +26 -14
- data/lib/liquid/profiler.rb +72 -92
- data/lib/liquid/range_lookup.rb +28 -3
- data/lib/liquid/registers.rb +51 -0
- data/lib/liquid/resource_limits.rb +62 -0
- data/lib/liquid/standardfilters.rb +715 -132
- data/lib/liquid/strainer_factory.rb +41 -0
- data/lib/liquid/strainer_template.rb +62 -0
- data/lib/liquid/tablerowloop_drop.rb +121 -0
- data/lib/liquid/tag/disableable.rb +22 -0
- data/lib/liquid/tag/disabler.rb +21 -0
- data/lib/liquid/tag.rb +35 -12
- data/lib/liquid/tags/assign.rb +57 -18
- data/lib/liquid/tags/break.rb +15 -5
- data/lib/liquid/tags/capture.rb +24 -18
- data/lib/liquid/tags/case.rb +79 -30
- data/lib/liquid/tags/comment.rb +19 -4
- data/lib/liquid/tags/continue.rb +16 -12
- data/lib/liquid/tags/cycle.rb +47 -27
- data/lib/liquid/tags/decrement.rb +23 -24
- data/lib/liquid/tags/echo.rb +41 -0
- data/lib/liquid/tags/for.rb +155 -124
- data/lib/liquid/tags/if.rb +97 -63
- data/lib/liquid/tags/ifchanged.rb +11 -12
- data/lib/liquid/tags/include.rb +82 -73
- data/lib/liquid/tags/increment.rb +23 -17
- data/lib/liquid/tags/inline_comment.rb +43 -0
- data/lib/liquid/tags/raw.rb +50 -8
- data/lib/liquid/tags/render.rb +109 -0
- data/lib/liquid/tags/table_row.rb +57 -41
- data/lib/liquid/tags/unless.rb +38 -20
- data/lib/liquid/template.rb +71 -103
- data/lib/liquid/template_factory.rb +9 -0
- data/lib/liquid/tokenizer.rb +39 -0
- data/lib/liquid/usage.rb +8 -0
- data/lib/liquid/utils.rb +63 -9
- data/lib/liquid/variable.rb +74 -56
- data/lib/liquid/variable_lookup.rb +31 -15
- data/lib/liquid/version.rb +3 -1
- data/lib/liquid.rb +27 -12
- metadata +30 -106
- data/lib/liquid/module_ex.rb +0 -62
- data/lib/liquid/strainer.rb +0 -59
- data/lib/liquid/token.rb +0 -18
- data/test/fixtures/en_locale.yml +0 -9
- data/test/integration/assign_test.rb +0 -48
- data/test/integration/blank_test.rb +0 -106
- data/test/integration/capture_test.rb +0 -50
- data/test/integration/context_test.rb +0 -32
- data/test/integration/drop_test.rb +0 -271
- data/test/integration/error_handling_test.rb +0 -207
- data/test/integration/filter_test.rb +0 -138
- data/test/integration/hash_ordering_test.rb +0 -23
- data/test/integration/output_test.rb +0 -124
- data/test/integration/parsing_quirks_test.rb +0 -116
- data/test/integration/render_profiling_test.rb +0 -154
- data/test/integration/security_test.rb +0 -64
- data/test/integration/standard_filter_test.rb +0 -396
- data/test/integration/tags/break_tag_test.rb +0 -16
- data/test/integration/tags/continue_tag_test.rb +0 -16
- data/test/integration/tags/for_tag_test.rb +0 -375
- data/test/integration/tags/if_else_tag_test.rb +0 -190
- data/test/integration/tags/include_tag_test.rb +0 -234
- data/test/integration/tags/increment_tag_test.rb +0 -24
- data/test/integration/tags/raw_tag_test.rb +0 -25
- data/test/integration/tags/standard_tag_test.rb +0 -297
- data/test/integration/tags/statements_test.rb +0 -113
- data/test/integration/tags/table_row_test.rb +0 -63
- data/test/integration/tags/unless_else_tag_test.rb +0 -26
- data/test/integration/template_test.rb +0 -182
- data/test/integration/variable_test.rb +0 -82
- data/test/test_helper.rb +0 -89
- data/test/unit/block_unit_test.rb +0 -55
- data/test/unit/condition_unit_test.rb +0 -149
- data/test/unit/context_unit_test.rb +0 -492
- data/test/unit/file_system_unit_test.rb +0 -35
- data/test/unit/i18n_unit_test.rb +0 -37
- data/test/unit/lexer_unit_test.rb +0 -48
- data/test/unit/module_ex_unit_test.rb +0 -87
- data/test/unit/parser_unit_test.rb +0 -82
- data/test/unit/regexp_unit_test.rb +0 -44
- data/test/unit/strainer_unit_test.rb +0 -69
- data/test/unit/tag_unit_test.rb +0 -16
- data/test/unit/tags/case_tag_unit_test.rb +0 -10
- data/test/unit/tags/for_tag_unit_test.rb +0 -13
- data/test/unit/tags/if_tag_unit_test.rb +0 -8
- data/test/unit/template_unit_test.rb +0 -69
- data/test/unit/tokenizer_unit_test.rb +0 -38
- data/test/unit/variable_unit_test.rb +0 -145
- /data/{MIT-LICENSE → LICENSE} +0 -0
data/lib/liquid/condition.rb
CHANGED
@@ -1,60 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Liquid
|
2
4
|
# Container for liquid nodes which conveniently wraps decision making logic
|
3
5
|
#
|
4
6
|
# Example:
|
5
7
|
#
|
6
|
-
# c = Condition.new(
|
8
|
+
# c = Condition.new(1, '==', 1)
|
7
9
|
# c.evaluate #=> true
|
8
10
|
#
|
9
|
-
class Condition
|
11
|
+
class Condition # :nodoc:
|
10
12
|
@@operators = {
|
11
|
-
'=='
|
12
|
-
'!='
|
13
|
-
'<>'
|
14
|
-
'<'
|
15
|
-
'>'
|
16
|
-
'>='
|
17
|
-
'<='
|
18
|
-
'contains'
|
19
|
-
left && right && left.respond_to?(:include?)
|
20
|
-
|
13
|
+
'==' => ->(cond, left, right) { cond.send(:equal_variables, left, right) },
|
14
|
+
'!=' => ->(cond, left, right) { !cond.send(:equal_variables, left, right) },
|
15
|
+
'<>' => ->(cond, left, right) { !cond.send(:equal_variables, left, right) },
|
16
|
+
'<' => :<,
|
17
|
+
'>' => :>,
|
18
|
+
'>=' => :>=,
|
19
|
+
'<=' => :<=,
|
20
|
+
'contains' => lambda do |_cond, left, right|
|
21
|
+
if left && right && left.respond_to?(:include?)
|
22
|
+
right = right.to_s if left.is_a?(String)
|
23
|
+
left.include?(right)
|
24
|
+
else
|
25
|
+
false
|
26
|
+
end
|
27
|
+
end,
|
28
|
+
}
|
29
|
+
|
30
|
+
class MethodLiteral
|
31
|
+
attr_reader :method_name, :to_s
|
32
|
+
|
33
|
+
def initialize(method_name, to_s)
|
34
|
+
@method_name = method_name
|
35
|
+
@to_s = to_s
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
@@method_literals = {
|
40
|
+
'blank' => MethodLiteral.new(:blank?, '').freeze,
|
41
|
+
'empty' => MethodLiteral.new(:empty?, '').freeze,
|
21
42
|
}
|
22
43
|
|
23
44
|
def self.operators
|
24
45
|
@@operators
|
25
46
|
end
|
26
47
|
|
27
|
-
|
48
|
+
def self.parse_expression(parse_context, markup)
|
49
|
+
@@method_literals[markup] || parse_context.parse_expression(markup)
|
50
|
+
end
|
51
|
+
|
52
|
+
attr_reader :attachment, :child_condition
|
28
53
|
attr_accessor :left, :operator, :right
|
29
54
|
|
30
55
|
def initialize(left = nil, operator = nil, right = nil)
|
31
|
-
@left
|
56
|
+
@left = left
|
32
57
|
@operator = operator
|
33
|
-
@right
|
58
|
+
@right = right
|
59
|
+
|
34
60
|
@child_relation = nil
|
35
61
|
@child_condition = nil
|
36
62
|
end
|
37
63
|
|
38
|
-
def evaluate(context =
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
64
|
+
def evaluate(context = deprecated_default_context)
|
65
|
+
condition = self
|
66
|
+
result = nil
|
67
|
+
loop do
|
68
|
+
result = interpret_condition(condition.left, condition.right, condition.operator, context)
|
69
|
+
|
70
|
+
case condition.child_relation
|
71
|
+
when :or
|
72
|
+
break if result
|
73
|
+
when :and
|
74
|
+
break unless result
|
75
|
+
else
|
76
|
+
break
|
77
|
+
end
|
78
|
+
condition = condition.child_condition
|
48
79
|
end
|
80
|
+
result
|
49
81
|
end
|
50
82
|
|
51
83
|
def or(condition)
|
52
|
-
@child_relation
|
84
|
+
@child_relation = :or
|
53
85
|
@child_condition = condition
|
54
86
|
end
|
55
87
|
|
56
88
|
def and(condition)
|
57
|
-
@child_relation
|
89
|
+
@child_relation = :and
|
58
90
|
@child_condition = condition
|
59
91
|
end
|
60
92
|
|
@@ -67,23 +99,27 @@ module Liquid
|
|
67
99
|
end
|
68
100
|
|
69
101
|
def inspect
|
70
|
-
"#<Condition #{[@left, @operator, @right].compact.join(' '
|
102
|
+
"#<Condition #{[@left, @operator, @right].compact.join(' ')}>"
|
71
103
|
end
|
72
104
|
|
105
|
+
protected
|
106
|
+
|
107
|
+
attr_reader :child_relation
|
108
|
+
|
73
109
|
private
|
74
110
|
|
75
111
|
def equal_variables(left, right)
|
76
|
-
if left.is_a?(
|
77
|
-
if right.respond_to?(left)
|
78
|
-
return right.send(left.
|
112
|
+
if left.is_a?(MethodLiteral)
|
113
|
+
if right.respond_to?(left.method_name)
|
114
|
+
return right.send(left.method_name)
|
79
115
|
else
|
80
116
|
return nil
|
81
117
|
end
|
82
118
|
end
|
83
119
|
|
84
|
-
if right.is_a?(
|
85
|
-
if left.respond_to?(right)
|
86
|
-
return left.send(right.
|
120
|
+
if right.is_a?(MethodLiteral)
|
121
|
+
if left.respond_to?(right.method_name)
|
122
|
+
return left.send(right.method_name)
|
87
123
|
else
|
88
124
|
return nil
|
89
125
|
end
|
@@ -96,36 +132,47 @@ module Liquid
|
|
96
132
|
# If the operator is empty this means that the decision statement is just
|
97
133
|
# a single variable. We can just poll this variable from the context and
|
98
134
|
# return this as the result.
|
99
|
-
return context
|
135
|
+
return context.evaluate(left) if op.nil?
|
100
136
|
|
101
|
-
left
|
102
|
-
right = context
|
137
|
+
left = Liquid::Utils.to_liquid_value(context.evaluate(left))
|
138
|
+
right = Liquid::Utils.to_liquid_value(context.evaluate(right))
|
103
139
|
|
104
|
-
operation = self.class.operators[op] || raise(Liquid::ArgumentError
|
140
|
+
operation = self.class.operators[op] || raise(Liquid::ArgumentError, "Unknown operator #{op}")
|
105
141
|
|
106
142
|
if operation.respond_to?(:call)
|
107
143
|
operation.call(self, left, right)
|
108
|
-
elsif left.respond_to?(operation)
|
144
|
+
elsif left.respond_to?(operation) && right.respond_to?(operation) && !left.is_a?(Hash) && !right.is_a?(Hash)
|
109
145
|
begin
|
110
146
|
left.send(operation, right)
|
111
147
|
rescue ::ArgumentError => e
|
112
|
-
raise Liquid::ArgumentError
|
148
|
+
raise Liquid::ArgumentError, e.message
|
113
149
|
end
|
114
|
-
else
|
115
|
-
nil
|
116
150
|
end
|
117
151
|
end
|
118
|
-
end
|
119
152
|
|
153
|
+
def deprecated_default_context
|
154
|
+
warn("DEPRECATION WARNING: Condition#evaluate without a context argument is deprecated" \
|
155
|
+
" and will be removed from Liquid 6.0.0.")
|
156
|
+
Context.new
|
157
|
+
end
|
158
|
+
|
159
|
+
class ParseTreeVisitor < Liquid::ParseTreeVisitor
|
160
|
+
def children
|
161
|
+
[
|
162
|
+
@node.left, @node.right,
|
163
|
+
@node.child_condition, @node.attachment
|
164
|
+
].compact
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
120
168
|
|
121
169
|
class ElseCondition < Condition
|
122
170
|
def else?
|
123
171
|
true
|
124
172
|
end
|
125
173
|
|
126
|
-
def evaluate(
|
174
|
+
def evaluate(_context)
|
127
175
|
true
|
128
176
|
end
|
129
177
|
end
|
130
|
-
|
131
178
|
end
|
data/lib/liquid/context.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
+
module Liquid
|
3
4
|
# Context keeps the variable stack and resolves variables, as well as keywords
|
4
5
|
#
|
5
6
|
# context['variable'] = 'testing'
|
@@ -13,46 +14,53 @@ module Liquid
|
|
13
14
|
#
|
14
15
|
# context['bob'] #=> nil class Context
|
15
16
|
class Context
|
16
|
-
attr_reader :scopes, :errors, :registers, :environments, :resource_limits
|
17
|
-
attr_accessor :
|
18
|
-
|
19
|
-
def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil)
|
20
|
-
@environments = [environments].flatten
|
21
|
-
@scopes = [(outer_scope || {})]
|
22
|
-
@registers = registers
|
23
|
-
@errors = []
|
24
|
-
@resource_limits = resource_limits || Template.default_resource_limits.dup
|
25
|
-
@resource_limits[:render_score_current] = 0
|
26
|
-
@resource_limits[:assign_score_current] = 0
|
27
|
-
@parsed_expression = Hash.new{ |cache, markup| cache[markup] = Expression.parse(markup) }
|
28
|
-
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
|
29
19
|
|
30
|
-
|
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)
|
23
|
+
end
|
31
24
|
|
25
|
+
def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil, static_environments = {})
|
26
|
+
@environments = [environments]
|
27
|
+
@environments.flatten!
|
28
|
+
|
29
|
+
@static_environments = [static_environments].flat_map(&:freeze).freeze
|
30
|
+
@scopes = [(outer_scope || {})]
|
31
|
+
@registers = registers.is_a?(Registers) ? registers : Registers.new(registers)
|
32
|
+
@errors = []
|
33
|
+
@partial = false
|
34
|
+
@strict_variables = false
|
35
|
+
@resource_limits = resource_limits || ResourceLimits.new(Template.default_resource_limits)
|
36
|
+
@base_scope_depth = 0
|
37
|
+
@interrupts = []
|
38
|
+
@filters = []
|
39
|
+
@global_filter = nil
|
40
|
+
@disabled_tags = {}
|
41
|
+
|
42
|
+
@registers.static[:cached_partials] ||= {}
|
43
|
+
@registers.static[:file_system] ||= Liquid::Template.file_system
|
44
|
+
@registers.static[:template_factory] ||= Liquid::TemplateFactory.new
|
45
|
+
|
46
|
+
self.exception_renderer = Template.default_exception_renderer
|
32
47
|
if rethrow_errors
|
33
|
-
self.
|
48
|
+
self.exception_renderer = Liquid::RAISE_EXCEPTION_LAMBDA
|
34
49
|
end
|
35
50
|
|
36
|
-
|
37
|
-
@filters = []
|
38
|
-
end
|
51
|
+
yield self if block_given?
|
39
52
|
|
40
|
-
|
41
|
-
|
42
|
-
obj.length
|
43
|
-
else
|
44
|
-
1
|
45
|
-
end
|
53
|
+
# Do this last, since it could result in this object being passed to a Proc in the environment
|
54
|
+
squash_instance_assigns_with_environments
|
46
55
|
end
|
56
|
+
# rubocop:enable Metrics/ParameterLists
|
47
57
|
|
48
|
-
def
|
49
|
-
|
50
|
-
(@resource_limits[:render_score_limit] && @resource_limits[:render_score_current] > @resource_limits[:render_score_limit] ) ||
|
51
|
-
(@resource_limits[:assign_score_limit] && @resource_limits[:assign_score_current] > @resource_limits[:assign_score_limit] )
|
58
|
+
def warnings
|
59
|
+
@warnings ||= []
|
52
60
|
end
|
53
61
|
|
54
62
|
def strainer
|
55
|
-
@strainer ||=
|
63
|
+
@strainer ||= StrainerFactory.create(self, @filters)
|
56
64
|
end
|
57
65
|
|
58
66
|
# Adds filters to this context.
|
@@ -65,8 +73,12 @@ module Liquid
|
|
65
73
|
@strainer = nil
|
66
74
|
end
|
67
75
|
|
76
|
+
def apply_global_filter(obj)
|
77
|
+
global_filter.nil? ? obj : global_filter.call(obj)
|
78
|
+
end
|
79
|
+
|
68
80
|
# are there any not handled interrupts?
|
69
|
-
def
|
81
|
+
def interrupt?
|
70
82
|
!@interrupts.empty?
|
71
83
|
end
|
72
84
|
|
@@ -80,15 +92,12 @@ module Liquid
|
|
80
92
|
@interrupts.pop
|
81
93
|
end
|
82
94
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
end
|
88
|
-
|
95
|
+
def handle_error(e, line_number = nil)
|
96
|
+
e = internal_error unless e.is_a?(Liquid::Error)
|
97
|
+
e.template_name ||= template_name
|
98
|
+
e.line_number ||= line_number
|
89
99
|
errors.push(e)
|
90
|
-
|
91
|
-
Liquid::Error.render(e)
|
100
|
+
exception_renderer.call(e).to_s
|
92
101
|
end
|
93
102
|
|
94
103
|
def invoke(method, *args)
|
@@ -96,9 +105,9 @@ module Liquid
|
|
96
105
|
end
|
97
106
|
|
98
107
|
# Push new local scope on the stack. use <tt>Context#stack</tt> instead
|
99
|
-
def push(new_scope={})
|
108
|
+
def push(new_scope = {})
|
100
109
|
@scopes.unshift(new_scope)
|
101
|
-
|
110
|
+
check_overflow
|
102
111
|
end
|
103
112
|
|
104
113
|
# Merge a hash of variables in the current local scope
|
@@ -119,20 +128,32 @@ module Liquid
|
|
119
128
|
# context['var'] = 'hi'
|
120
129
|
# end
|
121
130
|
#
|
122
|
-
# context['var] #=> nil
|
123
|
-
def stack(new_scope=
|
124
|
-
|
125
|
-
if new_scope
|
126
|
-
push(new_scope)
|
127
|
-
@this_stack_used = true
|
128
|
-
else
|
129
|
-
@this_stack_used = false
|
130
|
-
end
|
131
|
-
|
131
|
+
# context['var'] #=> nil
|
132
|
+
def stack(new_scope = {})
|
133
|
+
push(new_scope)
|
132
134
|
yield
|
133
135
|
ensure
|
134
|
-
pop
|
135
|
-
|
136
|
+
pop
|
137
|
+
end
|
138
|
+
|
139
|
+
# Creates a new context inheriting resource limits, filters, environment etc.,
|
140
|
+
# but with an isolated scope.
|
141
|
+
def new_isolated_subcontext
|
142
|
+
check_overflow
|
143
|
+
|
144
|
+
self.class.build(
|
145
|
+
resource_limits: resource_limits,
|
146
|
+
static_environments: static_environments,
|
147
|
+
registers: Registers.new(registers)
|
148
|
+
).tap do |subcontext|
|
149
|
+
subcontext.base_scope_depth = base_scope_depth + 1
|
150
|
+
subcontext.exception_renderer = exception_renderer
|
151
|
+
subcontext.filters = @filters
|
152
|
+
subcontext.strainer = nil
|
153
|
+
subcontext.errors = errors
|
154
|
+
subcontext.warnings = warnings
|
155
|
+
subcontext.disabled_tags = @disabled_tags
|
156
|
+
end
|
136
157
|
end
|
137
158
|
|
138
159
|
def clear_instance_assigns
|
@@ -141,10 +162,6 @@ module Liquid
|
|
141
162
|
|
142
163
|
# Only allow String, Numeric, Hash, Array, Proc, Boolean or <tt>Liquid::Drop</tt>
|
143
164
|
def []=(key, value)
|
144
|
-
unless @this_stack_used
|
145
|
-
@this_stack_used = true
|
146
|
-
push({})
|
147
|
-
end
|
148
165
|
@scopes[0][key] = value
|
149
166
|
end
|
150
167
|
|
@@ -157,10 +174,10 @@ module Liquid
|
|
157
174
|
# Example:
|
158
175
|
# products == empty #=> products.empty?
|
159
176
|
def [](expression)
|
160
|
-
evaluate(
|
177
|
+
evaluate(Expression.parse(expression))
|
161
178
|
end
|
162
179
|
|
163
|
-
def
|
180
|
+
def key?(key)
|
164
181
|
self[key] != nil
|
165
182
|
end
|
166
183
|
|
@@ -169,52 +186,100 @@ module Liquid
|
|
169
186
|
end
|
170
187
|
|
171
188
|
# Fetches an object starting at the local scope and then moving up the hierachy
|
172
|
-
def find_variable(key)
|
173
|
-
|
189
|
+
def find_variable(key, raise_on_not_found: true)
|
174
190
|
# This was changed from find() to find_index() because this is a very hot
|
175
191
|
# path and find_index() is optimized in MRI to reduce object allocation
|
176
|
-
index = @scopes.find_index { |s| s.
|
177
|
-
scope = @scopes[index] if index
|
192
|
+
index = @scopes.find_index { |s| s.key?(key) }
|
178
193
|
|
179
|
-
variable =
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
variable = lookup_and_evaluate(e, key)
|
184
|
-
unless variable.nil?
|
185
|
-
scope = e
|
186
|
-
break
|
187
|
-
end
|
188
|
-
end
|
194
|
+
variable = if index
|
195
|
+
lookup_and_evaluate(@scopes[index], key, raise_on_not_found: raise_on_not_found)
|
196
|
+
else
|
197
|
+
try_variable_find_in_environments(key, raise_on_not_found: raise_on_not_found)
|
189
198
|
end
|
190
199
|
|
191
|
-
|
192
|
-
variable ||= lookup_and_evaluate(scope, key)
|
193
|
-
|
194
|
-
variable = variable.to_liquid
|
200
|
+
variable = variable.to_liquid
|
195
201
|
variable.context = self if variable.respond_to?(:context=)
|
196
202
|
|
197
|
-
|
203
|
+
variable
|
198
204
|
end
|
199
205
|
|
200
|
-
def lookup_and_evaluate(obj, key)
|
201
|
-
if
|
202
|
-
|
206
|
+
def lookup_and_evaluate(obj, key, raise_on_not_found: true)
|
207
|
+
if @strict_variables && raise_on_not_found && obj.respond_to?(:key?) && !obj.key?(key)
|
208
|
+
raise Liquid::UndefinedVariable, "undefined variable #{key}"
|
209
|
+
end
|
210
|
+
|
211
|
+
value = obj[key]
|
212
|
+
|
213
|
+
if value.is_a?(Proc) && obj.respond_to?(:[]=)
|
214
|
+
obj[key] = value.arity == 0 ? value.call : value.call(self)
|
203
215
|
else
|
204
216
|
value
|
205
217
|
end
|
206
218
|
end
|
207
219
|
|
220
|
+
def with_disabled_tags(tag_names)
|
221
|
+
tag_names.each do |name|
|
222
|
+
@disabled_tags[name] = @disabled_tags.fetch(name, 0) + 1
|
223
|
+
end
|
224
|
+
yield
|
225
|
+
ensure
|
226
|
+
tag_names.each do |name|
|
227
|
+
@disabled_tags[name] -= 1
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
def tag_disabled?(tag_name)
|
232
|
+
@disabled_tags.fetch(tag_name, 0) > 0
|
233
|
+
end
|
234
|
+
|
235
|
+
protected
|
236
|
+
|
237
|
+
attr_writer :base_scope_depth, :warnings, :errors, :strainer, :filters, :disabled_tags
|
238
|
+
|
208
239
|
private
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
240
|
+
|
241
|
+
attr_reader :base_scope_depth
|
242
|
+
|
243
|
+
def try_variable_find_in_environments(key, raise_on_not_found:)
|
244
|
+
@environments.each do |environment|
|
245
|
+
found_variable = lookup_and_evaluate(environment, key, raise_on_not_found: raise_on_not_found)
|
246
|
+
if !found_variable.nil? || @strict_variables && raise_on_not_found
|
247
|
+
return found_variable
|
248
|
+
end
|
249
|
+
end
|
250
|
+
@static_environments.each do |environment|
|
251
|
+
found_variable = lookup_and_evaluate(environment, key, raise_on_not_found: raise_on_not_found)
|
252
|
+
if !found_variable.nil? || @strict_variables && raise_on_not_found
|
253
|
+
return found_variable
|
254
|
+
end
|
255
|
+
end
|
256
|
+
nil
|
257
|
+
end
|
258
|
+
|
259
|
+
def check_overflow
|
260
|
+
raise StackLevelError, "Nesting too deep" if overflow?
|
261
|
+
end
|
262
|
+
|
263
|
+
def overflow?
|
264
|
+
base_scope_depth + @scopes.length > Block::MAX_DEPTH
|
265
|
+
end
|
266
|
+
|
267
|
+
def internal_error
|
268
|
+
# raise and catch to set backtrace and cause on exception
|
269
|
+
raise Liquid::InternalError, 'internal'
|
270
|
+
rescue Liquid::InternalError => exc
|
271
|
+
exc
|
272
|
+
end
|
273
|
+
|
274
|
+
def squash_instance_assigns_with_environments
|
275
|
+
@scopes.last.each_key do |k|
|
276
|
+
@environments.each do |env|
|
277
|
+
if env.key?(k)
|
278
|
+
scopes.last[k] = lookup_and_evaluate(env, k)
|
279
|
+
break
|
216
280
|
end
|
217
281
|
end
|
218
|
-
end
|
282
|
+
end
|
283
|
+
end # squash_instance_assigns_with_environments
|
219
284
|
end # Context
|
220
285
|
end # Liquid
|
data/lib/liquid/document.rb
CHANGED
@@ -1,17 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Liquid
|
2
|
-
class Document
|
3
|
-
def self.parse(tokens,
|
4
|
-
|
5
|
-
|
4
|
+
class Document
|
5
|
+
def self.parse(tokens, parse_context)
|
6
|
+
doc = new(parse_context)
|
7
|
+
doc.parse(tokens, parse_context)
|
8
|
+
doc
|
9
|
+
end
|
10
|
+
|
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)
|
24
|
+
end
|
25
|
+
@body.freeze
|
26
|
+
rescue SyntaxError => e
|
27
|
+
e.line_number ||= parse_context.line_number
|
28
|
+
raise
|
6
29
|
end
|
7
30
|
|
8
|
-
|
9
|
-
|
10
|
-
|
31
|
+
def unknown_tag(tag, _markup, _tokenizer)
|
32
|
+
case tag
|
33
|
+
when 'else', 'end'
|
34
|
+
raise SyntaxError, parse_context.locale.t("errors.syntax.unexpected_outer_tag", tag: tag)
|
35
|
+
else
|
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
|
11
52
|
end
|
12
53
|
|
13
|
-
|
14
|
-
|
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
|
62
|
+
end
|
15
63
|
end
|
16
64
|
end
|
17
65
|
end
|