liquid 3.0.6 → 4.0.3
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 +154 -58
- data/{MIT-LICENSE → LICENSE} +0 -0
- data/README.md +33 -0
- data/lib/liquid/block.rb +42 -125
- data/lib/liquid/block_body.rb +99 -79
- data/lib/liquid/condition.rb +52 -32
- data/lib/liquid/context.rb +57 -51
- data/lib/liquid/document.rb +19 -9
- data/lib/liquid/drop.rb +17 -16
- data/lib/liquid/errors.rb +20 -24
- data/lib/liquid/expression.rb +26 -10
- data/lib/liquid/extensions.rb +19 -7
- data/lib/liquid/file_system.rb +11 -11
- data/lib/liquid/forloop_drop.rb +42 -0
- data/lib/liquid/i18n.rb +6 -6
- data/lib/liquid/interrupts.rb +1 -2
- data/lib/liquid/lexer.rb +12 -8
- data/lib/liquid/locales/en.yml +6 -2
- data/lib/liquid/parse_context.rb +38 -0
- data/lib/liquid/parse_tree_visitor.rb +42 -0
- data/lib/liquid/parser_switching.rb +4 -4
- data/lib/liquid/profiler/hooks.rb +7 -7
- data/lib/liquid/profiler.rb +18 -19
- data/lib/liquid/range_lookup.rb +16 -1
- data/lib/liquid/resource_limits.rb +23 -0
- data/lib/liquid/standardfilters.rb +207 -61
- data/lib/liquid/strainer.rb +15 -8
- data/lib/liquid/tablerowloop_drop.rb +62 -0
- data/lib/liquid/tag.rb +9 -8
- data/lib/liquid/tags/assign.rb +25 -4
- data/lib/liquid/tags/break.rb +0 -3
- data/lib/liquid/tags/capture.rb +1 -1
- data/lib/liquid/tags/case.rb +27 -12
- data/lib/liquid/tags/comment.rb +2 -2
- data/lib/liquid/tags/cycle.rb +16 -8
- data/lib/liquid/tags/decrement.rb +1 -4
- data/lib/liquid/tags/for.rb +103 -75
- data/lib/liquid/tags/if.rb +60 -44
- data/lib/liquid/tags/ifchanged.rb +0 -2
- data/lib/liquid/tags/include.rb +71 -51
- data/lib/liquid/tags/raw.rb +32 -4
- data/lib/liquid/tags/table_row.rb +21 -31
- data/lib/liquid/tags/unless.rb +3 -4
- data/lib/liquid/template.rb +42 -54
- data/lib/liquid/tokenizer.rb +31 -0
- data/lib/liquid/truffle.rb +5 -0
- data/lib/liquid/utils.rb +52 -8
- data/lib/liquid/variable.rb +59 -46
- data/lib/liquid/variable_lookup.rb +14 -6
- data/lib/liquid/version.rb +2 -1
- data/lib/liquid.rb +10 -7
- data/test/integration/assign_test.rb +8 -8
- data/test/integration/blank_test.rb +14 -14
- data/test/integration/block_test.rb +12 -0
- data/test/integration/context_test.rb +2 -2
- data/test/integration/document_test.rb +19 -0
- data/test/integration/drop_test.rb +42 -40
- data/test/integration/error_handling_test.rb +96 -43
- data/test/integration/filter_test.rb +60 -20
- data/test/integration/hash_ordering_test.rb +9 -9
- data/test/integration/output_test.rb +26 -27
- data/test/integration/parse_tree_visitor_test.rb +247 -0
- data/test/integration/parsing_quirks_test.rb +19 -13
- data/test/integration/render_profiling_test.rb +20 -20
- data/test/integration/security_test.rb +23 -7
- data/test/integration/standard_filter_test.rb +426 -46
- data/test/integration/tags/break_tag_test.rb +1 -2
- data/test/integration/tags/continue_tag_test.rb +0 -1
- data/test/integration/tags/for_tag_test.rb +135 -100
- data/test/integration/tags/if_else_tag_test.rb +75 -77
- data/test/integration/tags/include_tag_test.rb +50 -31
- data/test/integration/tags/increment_tag_test.rb +10 -11
- data/test/integration/tags/raw_tag_test.rb +7 -1
- data/test/integration/tags/standard_tag_test.rb +121 -122
- data/test/integration/tags/statements_test.rb +3 -5
- data/test/integration/tags/table_row_test.rb +20 -19
- data/test/integration/tags/unless_else_tag_test.rb +6 -6
- data/test/integration/template_test.rb +199 -49
- data/test/integration/trim_mode_test.rb +529 -0
- data/test/integration/variable_test.rb +27 -13
- data/test/test_helper.rb +33 -6
- data/test/truffle/truffle_test.rb +9 -0
- data/test/unit/block_unit_test.rb +8 -5
- data/test/unit/condition_unit_test.rb +94 -77
- data/test/unit/context_unit_test.rb +69 -72
- data/test/unit/file_system_unit_test.rb +3 -3
- data/test/unit/i18n_unit_test.rb +2 -2
- data/test/unit/lexer_unit_test.rb +12 -9
- data/test/unit/parser_unit_test.rb +2 -2
- data/test/unit/regexp_unit_test.rb +1 -1
- data/test/unit/strainer_unit_test.rb +96 -1
- data/test/unit/tag_unit_test.rb +7 -2
- data/test/unit/tags/case_tag_unit_test.rb +1 -1
- data/test/unit/tags/for_tag_unit_test.rb +2 -2
- data/test/unit/tags/if_tag_unit_test.rb +1 -1
- data/test/unit/template_unit_test.rb +14 -5
- data/test/unit/tokenizer_unit_test.rb +24 -7
- data/test/unit/variable_unit_test.rb +60 -43
- metadata +62 -50
- data/lib/liquid/module_ex.rb +0 -62
- data/lib/liquid/token.rb +0 -18
- data/test/unit/module_ex_unit_test.rb +0 -87
data/lib/liquid/block_body.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
module Liquid
|
2
2
|
class BlockBody
|
3
|
-
FullToken = /\A#{TagStart}
|
4
|
-
ContentOfVariable = /\A#{VariableStart}(
|
3
|
+
FullToken = /\A#{TagStart}#{WhitespaceControl}?\s*(\w+)\s*(.*?)#{WhitespaceControl}?#{TagEnd}\z/om
|
4
|
+
ContentOfVariable = /\A#{VariableStart}#{WhitespaceControl}?(.*?)#{WhitespaceControl}?#{VariableEnd}\z/om
|
5
|
+
WhitespaceOrNothing = /\A\s*\z/
|
5
6
|
TAGSTART = "{%".freeze
|
6
7
|
VARSTART = "{{".freeze
|
7
8
|
|
@@ -12,89 +13,85 @@ module Liquid
|
|
12
13
|
@blank = true
|
13
14
|
end
|
14
15
|
|
15
|
-
def parse(
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
# fetch the tag from registered blocks
|
25
|
-
if tag = Template.tags[tag_name]
|
26
|
-
markup = token.child(markup) if token.is_a?(Token)
|
27
|
-
new_tag = tag.parse(tag_name, markup, tokens, options)
|
28
|
-
new_tag.line_number = token.line_number if token.is_a?(Token)
|
29
|
-
@blank &&= new_tag.blank?
|
30
|
-
@nodelist << new_tag
|
31
|
-
else
|
32
|
-
# end parsing if we reach an unknown tag and let the caller decide
|
33
|
-
# determine how to proceed
|
34
|
-
return yield tag_name, markup
|
35
|
-
end
|
36
|
-
else
|
37
|
-
raise SyntaxError.new(options[:locale].t("errors.syntax.tag_termination".freeze, :token => token, :tag_end => TagEnd.inspect))
|
38
|
-
end
|
39
|
-
when token.start_with?(VARSTART)
|
40
|
-
new_var = create_variable(token, options)
|
41
|
-
new_var.line_number = token.line_number if token.is_a?(Token)
|
42
|
-
@nodelist << new_var
|
43
|
-
@blank = false
|
44
|
-
else
|
45
|
-
@nodelist << token
|
46
|
-
@blank &&= !!(token =~ /\A\s*\z/)
|
47
|
-
end
|
16
|
+
def parse(tokenizer, parse_context)
|
17
|
+
parse_context.line_number = tokenizer.line_number
|
18
|
+
while token = tokenizer.shift
|
19
|
+
next if token.empty?
|
20
|
+
case
|
21
|
+
when token.start_with?(TAGSTART)
|
22
|
+
whitespace_handler(token, parse_context)
|
23
|
+
unless token =~ FullToken
|
24
|
+
raise_missing_tag_terminator(token, parse_context)
|
48
25
|
end
|
49
|
-
|
50
|
-
|
51
|
-
|
26
|
+
tag_name = $1
|
27
|
+
markup = $2
|
28
|
+
# fetch the tag from registered blocks
|
29
|
+
unless tag = registered_tags[tag_name]
|
30
|
+
# end parsing if we reach an unknown tag and let the caller decide
|
31
|
+
# determine how to proceed
|
32
|
+
return yield tag_name, markup
|
33
|
+
end
|
34
|
+
new_tag = tag.parse(tag_name, markup, tokenizer, parse_context)
|
35
|
+
@blank &&= new_tag.blank?
|
36
|
+
@nodelist << new_tag
|
37
|
+
when token.start_with?(VARSTART)
|
38
|
+
whitespace_handler(token, parse_context)
|
39
|
+
@nodelist << create_variable(token, parse_context)
|
40
|
+
@blank = false
|
41
|
+
else
|
42
|
+
if parse_context.trim_whitespace
|
43
|
+
token.lstrip!
|
44
|
+
end
|
45
|
+
parse_context.trim_whitespace = false
|
46
|
+
@nodelist << token
|
47
|
+
@blank &&= !!(token =~ WhitespaceOrNothing)
|
52
48
|
end
|
49
|
+
parse_context.line_number = tokenizer.line_number
|
53
50
|
end
|
54
51
|
|
55
52
|
yield nil, nil
|
56
53
|
end
|
57
54
|
|
58
|
-
def
|
59
|
-
|
55
|
+
def whitespace_handler(token, parse_context)
|
56
|
+
if token[2] == WhitespaceControl
|
57
|
+
previous_token = @nodelist.last
|
58
|
+
if previous_token.is_a? String
|
59
|
+
previous_token.rstrip!
|
60
|
+
end
|
61
|
+
end
|
62
|
+
parse_context.trim_whitespace = (token[-3] == WhitespaceControl)
|
60
63
|
end
|
61
64
|
|
62
|
-
def
|
63
|
-
|
64
|
-
nodelist.each do |node|
|
65
|
-
all_warnings.concat(node.warnings) if node.respond_to?(:warnings) && node.warnings
|
66
|
-
end
|
67
|
-
all_warnings
|
65
|
+
def blank?
|
66
|
+
@blank
|
68
67
|
end
|
69
68
|
|
70
69
|
def render(context)
|
71
70
|
output = []
|
72
|
-
context.resource_limits
|
73
|
-
context.resource_limits[:render_score_current] += @nodelist.length
|
71
|
+
context.resource_limits.render_score += @nodelist.length
|
74
72
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
73
|
+
idx = 0
|
74
|
+
while node = @nodelist[idx]
|
75
|
+
case node
|
76
|
+
when String
|
77
|
+
check_resources(context, node)
|
78
|
+
output << node
|
79
|
+
when Variable
|
80
|
+
render_node_to_output(node, output, context)
|
81
|
+
when Block
|
82
|
+
render_node_to_output(node, output, context, node.blank?)
|
83
|
+
break if context.interrupt? # might have happened in a for-block
|
84
|
+
when Continue, Break
|
80
85
|
# If we get an Interrupt that means the block must stop processing. An
|
81
86
|
# Interrupt is any command that stops block execution such as {% break %}
|
82
87
|
# or {% continue %}
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
token_output = render_token(token, context)
|
89
|
-
|
90
|
-
unless token.is_a?(Block) && token.blank?
|
91
|
-
output << token_output
|
92
|
-
end
|
93
|
-
rescue MemoryError => e
|
94
|
-
raise e
|
95
|
-
rescue ::StandardError => e
|
96
|
-
output << context.handle_error(e, token)
|
88
|
+
context.push_interrupt(node.interrupt)
|
89
|
+
break
|
90
|
+
else # Other non-Block tags
|
91
|
+
render_node_to_output(node, output, context)
|
92
|
+
break if context.interrupt? # might have happened through an include
|
97
93
|
end
|
94
|
+
idx += 1
|
98
95
|
end
|
99
96
|
|
100
97
|
output.join
|
@@ -102,22 +99,45 @@ module Liquid
|
|
102
99
|
|
103
100
|
private
|
104
101
|
|
105
|
-
def
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
102
|
+
def render_node_to_output(node, output, context, skip_output = false)
|
103
|
+
node_output = node.render(context)
|
104
|
+
node_output = node_output.is_a?(Array) ? node_output.join : node_output.to_s
|
105
|
+
check_resources(context, node_output)
|
106
|
+
output << node_output unless skip_output
|
107
|
+
rescue MemoryError => e
|
108
|
+
raise e
|
109
|
+
rescue UndefinedVariable, UndefinedDropMethod, UndefinedFilter => e
|
110
|
+
context.handle_error(e, node.line_number)
|
111
|
+
output << nil
|
112
|
+
rescue ::StandardError => e
|
113
|
+
line_number = node.is_a?(String) ? nil : node.line_number
|
114
|
+
output << context.handle_error(e, line_number)
|
113
115
|
end
|
114
116
|
|
115
|
-
def
|
117
|
+
def check_resources(context, node_output)
|
118
|
+
context.resource_limits.render_length += node_output.length
|
119
|
+
return unless context.resource_limits.reached?
|
120
|
+
raise MemoryError.new("Memory limits exceeded".freeze)
|
121
|
+
end
|
122
|
+
|
123
|
+
def create_variable(token, parse_context)
|
116
124
|
token.scan(ContentOfVariable) do |content|
|
117
|
-
markup =
|
118
|
-
return Variable.new(markup,
|
125
|
+
markup = content.first
|
126
|
+
return Variable.new(markup, parse_context)
|
119
127
|
end
|
120
|
-
|
128
|
+
raise_missing_variable_terminator(token, parse_context)
|
129
|
+
end
|
130
|
+
|
131
|
+
def raise_missing_tag_terminator(token, parse_context)
|
132
|
+
raise SyntaxError.new(parse_context.locale.t("errors.syntax.tag_termination".freeze, token: token, tag_end: TagEnd.inspect))
|
133
|
+
end
|
134
|
+
|
135
|
+
def raise_missing_variable_terminator(token, parse_context)
|
136
|
+
raise SyntaxError.new(parse_context.locale.t("errors.syntax.variable_termination".freeze, token: token, tag_end: VariableEnd.inspect))
|
137
|
+
end
|
138
|
+
|
139
|
+
def registered_tags
|
140
|
+
Template.tags
|
121
141
|
end
|
122
142
|
end
|
123
143
|
end
|
data/lib/liquid/condition.rb
CHANGED
@@ -3,28 +3,33 @@ module Liquid
|
|
3
3
|
#
|
4
4
|
# Example:
|
5
5
|
#
|
6
|
-
# c = Condition.new(
|
6
|
+
# c = Condition.new(1, '==', 1)
|
7
7
|
# c.evaluate #=> true
|
8
8
|
#
|
9
9
|
class Condition #:nodoc:
|
10
10
|
@@operators = {
|
11
|
-
'=='.freeze =>
|
12
|
-
'!='.freeze =>
|
13
|
-
'<>'.freeze =>
|
11
|
+
'=='.freeze => ->(cond, left, right) { cond.send(:equal_variables, left, right) },
|
12
|
+
'!='.freeze => ->(cond, left, right) { !cond.send(:equal_variables, left, right) },
|
13
|
+
'<>'.freeze => ->(cond, left, right) { !cond.send(:equal_variables, left, right) },
|
14
14
|
'<'.freeze => :<,
|
15
15
|
'>'.freeze => :>,
|
16
16
|
'>='.freeze => :>=,
|
17
17
|
'<='.freeze => :<=,
|
18
|
-
'contains'.freeze => lambda
|
19
|
-
left && right && left.respond_to?(:include?)
|
20
|
-
|
18
|
+
'contains'.freeze => lambda do |cond, left, right|
|
19
|
+
if left && right && left.respond_to?(:include?)
|
20
|
+
right = right.to_s if left.is_a?(String)
|
21
|
+
left.include?(right)
|
22
|
+
else
|
23
|
+
false
|
24
|
+
end
|
25
|
+
end
|
21
26
|
}
|
22
27
|
|
23
28
|
def self.operators
|
24
29
|
@@operators
|
25
30
|
end
|
26
31
|
|
27
|
-
attr_reader :attachment
|
32
|
+
attr_reader :attachment, :child_condition
|
28
33
|
attr_accessor :left, :operator, :right
|
29
34
|
|
30
35
|
def initialize(left = nil, operator = nil, right = nil)
|
@@ -36,16 +41,22 @@ module Liquid
|
|
36
41
|
end
|
37
42
|
|
38
43
|
def evaluate(context = Context.new)
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
44
|
+
condition = self
|
45
|
+
result = nil
|
46
|
+
loop do
|
47
|
+
result = interpret_condition(condition.left, condition.right, condition.operator, context)
|
48
|
+
|
49
|
+
case condition.child_relation
|
50
|
+
when :or
|
51
|
+
break if result
|
52
|
+
when :and
|
53
|
+
break unless result
|
54
|
+
else
|
55
|
+
break
|
56
|
+
end
|
57
|
+
condition = condition.child_condition
|
48
58
|
end
|
59
|
+
result
|
49
60
|
end
|
50
61
|
|
51
62
|
def or(condition)
|
@@ -70,20 +81,24 @@ module Liquid
|
|
70
81
|
"#<Condition #{[@left, @operator, @right].compact.join(' '.freeze)}>"
|
71
82
|
end
|
72
83
|
|
84
|
+
protected
|
85
|
+
|
86
|
+
attr_reader :child_relation
|
87
|
+
|
73
88
|
private
|
74
89
|
|
75
90
|
def equal_variables(left, right)
|
76
|
-
if left.is_a?(
|
77
|
-
if right.respond_to?(left)
|
78
|
-
return right.send(left.
|
91
|
+
if left.is_a?(Liquid::Expression::MethodLiteral)
|
92
|
+
if right.respond_to?(left.method_name)
|
93
|
+
return right.send(left.method_name)
|
79
94
|
else
|
80
95
|
return nil
|
81
96
|
end
|
82
97
|
end
|
83
98
|
|
84
|
-
if right.is_a?(
|
85
|
-
if left.respond_to?(right)
|
86
|
-
return left.send(right.
|
99
|
+
if right.is_a?(Liquid::Expression::MethodLiteral)
|
100
|
+
if left.respond_to?(right.method_name)
|
101
|
+
return left.send(right.method_name)
|
87
102
|
else
|
88
103
|
return nil
|
89
104
|
end
|
@@ -96,36 +111,41 @@ module Liquid
|
|
96
111
|
# If the operator is empty this means that the decision statement is just
|
97
112
|
# a single variable. We can just poll this variable from the context and
|
98
113
|
# return this as the result.
|
99
|
-
return context
|
114
|
+
return context.evaluate(left) if op.nil?
|
100
115
|
|
101
|
-
left = context
|
102
|
-
right = context
|
116
|
+
left = context.evaluate(left)
|
117
|
+
right = context.evaluate(right)
|
103
118
|
|
104
119
|
operation = self.class.operators[op] || raise(Liquid::ArgumentError.new("Unknown operator #{op}"))
|
105
120
|
|
106
121
|
if operation.respond_to?(:call)
|
107
122
|
operation.call(self, left, right)
|
108
|
-
elsif left.respond_to?(operation)
|
123
|
+
elsif left.respond_to?(operation) && right.respond_to?(operation) && !left.is_a?(Hash) && !right.is_a?(Hash)
|
109
124
|
begin
|
110
125
|
left.send(operation, right)
|
111
126
|
rescue ::ArgumentError => e
|
112
127
|
raise Liquid::ArgumentError.new(e.message)
|
113
128
|
end
|
114
|
-
else
|
115
|
-
nil
|
116
129
|
end
|
117
130
|
end
|
118
|
-
end
|
119
131
|
|
132
|
+
class ParseTreeVisitor < Liquid::ParseTreeVisitor
|
133
|
+
def children
|
134
|
+
[
|
135
|
+
@node.left, @node.right,
|
136
|
+
@node.child_condition, @node.attachment
|
137
|
+
].compact
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
120
141
|
|
121
142
|
class ElseCondition < Condition
|
122
143
|
def else?
|
123
144
|
true
|
124
145
|
end
|
125
146
|
|
126
|
-
def evaluate(
|
147
|
+
def evaluate(_context)
|
127
148
|
true
|
128
149
|
end
|
129
150
|
end
|
130
|
-
|
131
151
|
end
|
data/lib/liquid/context.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
module Liquid
|
2
|
-
|
3
2
|
# Context keeps the variable stack and resolves variables, as well as keywords
|
4
3
|
#
|
5
4
|
# context['variable'] = 'testing'
|
@@ -14,41 +13,32 @@ module Liquid
|
|
14
13
|
# context['bob'] #=> nil class Context
|
15
14
|
class Context
|
16
15
|
attr_reader :scopes, :errors, :registers, :environments, :resource_limits
|
17
|
-
attr_accessor :
|
16
|
+
attr_accessor :exception_renderer, :template_name, :partial, :global_filter, :strict_variables, :strict_filters
|
18
17
|
|
19
18
|
def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil)
|
20
19
|
@environments = [environments].flatten
|
21
20
|
@scopes = [(outer_scope || {})]
|
22
21
|
@registers = registers
|
23
22
|
@errors = []
|
24
|
-
@
|
25
|
-
@
|
26
|
-
@resource_limits
|
27
|
-
@parsed_expression = Hash.new{ |cache, markup| cache[markup] = Expression.parse(markup) }
|
23
|
+
@partial = false
|
24
|
+
@strict_variables = false
|
25
|
+
@resource_limits = resource_limits || ResourceLimits.new(Template.default_resource_limits)
|
28
26
|
squash_instance_assigns_with_environments
|
29
27
|
|
30
28
|
@this_stack_used = false
|
31
29
|
|
30
|
+
self.exception_renderer = Template.default_exception_renderer
|
32
31
|
if rethrow_errors
|
33
|
-
self.
|
32
|
+
self.exception_renderer = ->(e) { raise }
|
34
33
|
end
|
35
34
|
|
36
35
|
@interrupts = []
|
37
36
|
@filters = []
|
37
|
+
@global_filter = nil
|
38
38
|
end
|
39
39
|
|
40
|
-
def
|
41
|
-
@
|
42
|
-
obj.length
|
43
|
-
else
|
44
|
-
1
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
def resource_limits_reached?
|
49
|
-
(@resource_limits[:render_length_limit] && @resource_limits[:render_length_current] > @resource_limits[:render_length_limit]) ||
|
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] )
|
40
|
+
def warnings
|
41
|
+
@warnings ||= []
|
52
42
|
end
|
53
43
|
|
54
44
|
def strainer
|
@@ -65,8 +55,12 @@ module Liquid
|
|
65
55
|
@strainer = nil
|
66
56
|
end
|
67
57
|
|
58
|
+
def apply_global_filter(obj)
|
59
|
+
global_filter.nil? ? obj : global_filter.call(obj)
|
60
|
+
end
|
61
|
+
|
68
62
|
# are there any not handled interrupts?
|
69
|
-
def
|
63
|
+
def interrupt?
|
70
64
|
!@interrupts.empty?
|
71
65
|
end
|
72
66
|
|
@@ -80,15 +74,12 @@ module Liquid
|
|
80
74
|
@interrupts.pop
|
81
75
|
end
|
82
76
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
end
|
88
|
-
|
77
|
+
def handle_error(e, line_number = nil)
|
78
|
+
e = internal_error unless e.is_a?(Liquid::Error)
|
79
|
+
e.template_name ||= template_name
|
80
|
+
e.line_number ||= line_number
|
89
81
|
errors.push(e)
|
90
|
-
|
91
|
-
Liquid::Error.render(e)
|
82
|
+
exception_renderer.call(e).to_s
|
92
83
|
end
|
93
84
|
|
94
85
|
def invoke(method, *args)
|
@@ -96,9 +87,9 @@ module Liquid
|
|
96
87
|
end
|
97
88
|
|
98
89
|
# Push new local scope on the stack. use <tt>Context#stack</tt> instead
|
99
|
-
def push(new_scope={})
|
90
|
+
def push(new_scope = {})
|
100
91
|
@scopes.unshift(new_scope)
|
101
|
-
raise StackLevelError, "Nesting too deep".freeze if @scopes.length >
|
92
|
+
raise StackLevelError, "Nesting too deep".freeze if @scopes.length > Block::MAX_DEPTH
|
102
93
|
end
|
103
94
|
|
104
95
|
# Merge a hash of variables in the current local scope
|
@@ -120,7 +111,7 @@ module Liquid
|
|
120
111
|
# end
|
121
112
|
#
|
122
113
|
# context['var] #=> nil
|
123
|
-
def stack(new_scope=nil)
|
114
|
+
def stack(new_scope = nil)
|
124
115
|
old_stack_used = @this_stack_used
|
125
116
|
if new_scope
|
126
117
|
push(new_scope)
|
@@ -157,10 +148,10 @@ module Liquid
|
|
157
148
|
# Example:
|
158
149
|
# products == empty #=> products.empty?
|
159
150
|
def [](expression)
|
160
|
-
evaluate(
|
151
|
+
evaluate(Expression.parse(expression))
|
161
152
|
end
|
162
153
|
|
163
|
-
def
|
154
|
+
def key?(key)
|
164
155
|
self[key] != nil
|
165
156
|
end
|
166
157
|
|
@@ -169,36 +160,43 @@ module Liquid
|
|
169
160
|
end
|
170
161
|
|
171
162
|
# Fetches an object starting at the local scope and then moving up the hierachy
|
172
|
-
def find_variable(key)
|
173
|
-
|
163
|
+
def find_variable(key, raise_on_not_found: true)
|
174
164
|
# This was changed from find() to find_index() because this is a very hot
|
175
165
|
# path and find_index() is optimized in MRI to reduce object allocation
|
176
|
-
index = @scopes.find_index { |s| s.
|
166
|
+
index = @scopes.find_index { |s| s.key?(key) }
|
177
167
|
scope = @scopes[index] if index
|
178
168
|
|
179
169
|
variable = nil
|
180
170
|
|
181
171
|
if scope.nil?
|
182
172
|
@environments.each do |e|
|
183
|
-
variable = lookup_and_evaluate(e, key)
|
184
|
-
|
173
|
+
variable = lookup_and_evaluate(e, key, raise_on_not_found: raise_on_not_found)
|
174
|
+
# When lookup returned a value OR there is no value but the lookup also did not raise
|
175
|
+
# then it is the value we are looking for.
|
176
|
+
if !variable.nil? || @strict_variables && raise_on_not_found
|
185
177
|
scope = e
|
186
178
|
break
|
187
179
|
end
|
188
180
|
end
|
189
181
|
end
|
190
182
|
|
191
|
-
scope
|
192
|
-
variable
|
183
|
+
scope ||= @environments.last || @scopes.last
|
184
|
+
variable ||= lookup_and_evaluate(scope, key, raise_on_not_found: raise_on_not_found)
|
193
185
|
|
194
186
|
variable = variable.to_liquid
|
195
187
|
variable.context = self if variable.respond_to?(:context=)
|
196
188
|
|
197
|
-
|
189
|
+
variable
|
198
190
|
end
|
199
191
|
|
200
|
-
def lookup_and_evaluate(obj, key)
|
201
|
-
if
|
192
|
+
def lookup_and_evaluate(obj, key, raise_on_not_found: true)
|
193
|
+
if @strict_variables && raise_on_not_found && obj.respond_to?(:key?) && !obj.key?(key)
|
194
|
+
raise Liquid::UndefinedVariable, "undefined variable #{key}"
|
195
|
+
end
|
196
|
+
|
197
|
+
value = obj[key]
|
198
|
+
|
199
|
+
if value.is_a?(Proc) && obj.respond_to?(:[]=)
|
202
200
|
obj[key] = (value.arity == 0) ? value.call : value.call(self)
|
203
201
|
else
|
204
202
|
value
|
@@ -206,15 +204,23 @@ module Liquid
|
|
206
204
|
end
|
207
205
|
|
208
206
|
private
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
207
|
+
|
208
|
+
def internal_error
|
209
|
+
# raise and catch to set backtrace and cause on exception
|
210
|
+
raise Liquid::InternalError, 'internal'
|
211
|
+
rescue Liquid::InternalError => exc
|
212
|
+
exc
|
213
|
+
end
|
214
|
+
|
215
|
+
def squash_instance_assigns_with_environments
|
216
|
+
@scopes.last.each_key do |k|
|
217
|
+
@environments.each do |env|
|
218
|
+
if env.key?(k)
|
219
|
+
scopes.last[k] = lookup_and_evaluate(env, k)
|
220
|
+
break
|
216
221
|
end
|
217
222
|
end
|
218
|
-
end
|
223
|
+
end
|
224
|
+
end # squash_instance_assigns_with_environments
|
219
225
|
end # Context
|
220
226
|
end # Liquid
|
data/lib/liquid/document.rb
CHANGED
@@ -1,17 +1,27 @@
|
|
1
1
|
module Liquid
|
2
|
-
class Document <
|
3
|
-
def self.parse(tokens,
|
4
|
-
|
5
|
-
|
2
|
+
class Document < BlockBody
|
3
|
+
def self.parse(tokens, parse_context)
|
4
|
+
doc = new
|
5
|
+
doc.parse(tokens, parse_context)
|
6
|
+
doc
|
6
7
|
end
|
7
8
|
|
8
|
-
|
9
|
-
|
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
|
12
|
+
end
|
13
|
+
rescue SyntaxError => e
|
14
|
+
e.line_number ||= parse_context.line_number
|
15
|
+
raise
|
11
16
|
end
|
12
17
|
|
13
|
-
|
14
|
-
|
18
|
+
def unknown_tag(tag, parse_context)
|
19
|
+
case tag
|
20
|
+
when 'else'.freeze, 'end'.freeze
|
21
|
+
raise SyntaxError.new(parse_context.locale.t("errors.syntax.unexpected_outer_tag".freeze, tag: tag))
|
22
|
+
else
|
23
|
+
raise SyntaxError.new(parse_context.locale.t("errors.syntax.unknown_tag".freeze, tag: tag))
|
24
|
+
end
|
15
25
|
end
|
16
26
|
end
|
17
27
|
end
|