liquid 2.6.1 → 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 +194 -29
- data/{MIT-LICENSE → LICENSE} +0 -0
- data/README.md +60 -2
- data/lib/liquid.rb +25 -14
- data/lib/liquid/block.rb +47 -96
- data/lib/liquid/block_body.rb +143 -0
- data/lib/liquid/condition.rb +70 -39
- data/lib/liquid/context.rb +116 -157
- data/lib/liquid/document.rb +19 -9
- data/lib/liquid/drop.rb +31 -14
- data/lib/liquid/errors.rb +54 -10
- data/lib/liquid/expression.rb +49 -0
- data/lib/liquid/extensions.rb +19 -7
- data/lib/liquid/file_system.rb +25 -14
- data/lib/liquid/forloop_drop.rb +42 -0
- data/lib/liquid/i18n.rb +39 -0
- data/lib/liquid/interrupts.rb +2 -3
- data/lib/liquid/lexer.rb +55 -0
- data/lib/liquid/locales/en.yml +26 -0
- data/lib/liquid/parse_context.rb +38 -0
- data/lib/liquid/parse_tree_visitor.rb +42 -0
- data/lib/liquid/parser.rb +90 -0
- data/lib/liquid/parser_switching.rb +31 -0
- data/lib/liquid/profiler.rb +158 -0
- data/lib/liquid/profiler/hooks.rb +23 -0
- data/lib/liquid/range_lookup.rb +37 -0
- data/lib/liquid/resource_limits.rb +23 -0
- data/lib/liquid/standardfilters.rb +311 -77
- data/lib/liquid/strainer.rb +39 -26
- data/lib/liquid/tablerowloop_drop.rb +62 -0
- data/lib/liquid/tag.rb +28 -11
- data/lib/liquid/tags/assign.rb +34 -10
- data/lib/liquid/tags/break.rb +1 -4
- data/lib/liquid/tags/capture.rb +11 -9
- data/lib/liquid/tags/case.rb +37 -22
- data/lib/liquid/tags/comment.rb +10 -3
- data/lib/liquid/tags/continue.rb +1 -4
- data/lib/liquid/tags/cycle.rb +20 -14
- data/lib/liquid/tags/decrement.rb +4 -8
- data/lib/liquid/tags/for.rb +121 -60
- data/lib/liquid/tags/if.rb +73 -30
- data/lib/liquid/tags/ifchanged.rb +3 -5
- data/lib/liquid/tags/include.rb +77 -46
- data/lib/liquid/tags/increment.rb +4 -8
- data/lib/liquid/tags/raw.rb +35 -10
- data/lib/liquid/tags/table_row.rb +62 -0
- data/lib/liquid/tags/unless.rb +6 -9
- data/lib/liquid/template.rb +130 -32
- data/lib/liquid/tokenizer.rb +31 -0
- data/lib/liquid/truffle.rb +5 -0
- data/lib/liquid/utils.rb +57 -4
- data/lib/liquid/variable.rb +121 -30
- data/lib/liquid/variable_lookup.rb +88 -0
- data/lib/liquid/version.rb +2 -1
- data/test/fixtures/en_locale.yml +9 -0
- data/test/integration/assign_test.rb +48 -0
- data/test/integration/blank_test.rb +106 -0
- data/test/integration/block_test.rb +12 -0
- data/test/{liquid → integration}/capture_test.rb +13 -3
- data/test/integration/context_test.rb +32 -0
- data/test/integration/document_test.rb +19 -0
- data/test/integration/drop_test.rb +273 -0
- data/test/integration/error_handling_test.rb +260 -0
- data/test/integration/filter_test.rb +178 -0
- data/test/integration/hash_ordering_test.rb +23 -0
- data/test/integration/output_test.rb +123 -0
- data/test/integration/parse_tree_visitor_test.rb +247 -0
- data/test/integration/parsing_quirks_test.rb +122 -0
- data/test/integration/render_profiling_test.rb +154 -0
- data/test/integration/security_test.rb +80 -0
- data/test/integration/standard_filter_test.rb +776 -0
- data/test/{liquid → integration}/tags/break_tag_test.rb +2 -3
- data/test/{liquid → integration}/tags/continue_tag_test.rb +1 -2
- data/test/integration/tags/for_tag_test.rb +410 -0
- data/test/integration/tags/if_else_tag_test.rb +188 -0
- data/test/integration/tags/include_tag_test.rb +253 -0
- data/test/integration/tags/increment_tag_test.rb +23 -0
- data/test/{liquid → integration}/tags/raw_tag_test.rb +9 -2
- data/test/integration/tags/standard_tag_test.rb +296 -0
- data/test/integration/tags/statements_test.rb +111 -0
- data/test/{liquid/tags/html_tag_test.rb → integration/tags/table_row_test.rb} +25 -24
- data/test/integration/tags/unless_else_tag_test.rb +26 -0
- data/test/integration/template_test.rb +332 -0
- data/test/integration/trim_mode_test.rb +529 -0
- data/test/integration/variable_test.rb +96 -0
- data/test/test_helper.rb +106 -19
- data/test/truffle/truffle_test.rb +9 -0
- data/test/{liquid/block_test.rb → unit/block_unit_test.rb} +9 -9
- data/test/unit/condition_unit_test.rb +166 -0
- data/test/{liquid/context_test.rb → unit/context_unit_test.rb} +85 -74
- data/test/unit/file_system_unit_test.rb +35 -0
- data/test/unit/i18n_unit_test.rb +37 -0
- data/test/unit/lexer_unit_test.rb +51 -0
- data/test/unit/parser_unit_test.rb +82 -0
- data/test/{liquid/regexp_test.rb → unit/regexp_unit_test.rb} +4 -4
- data/test/unit/strainer_unit_test.rb +164 -0
- data/test/unit/tag_unit_test.rb +21 -0
- data/test/unit/tags/case_tag_unit_test.rb +10 -0
- data/test/unit/tags/for_tag_unit_test.rb +13 -0
- data/test/unit/tags/if_tag_unit_test.rb +8 -0
- data/test/unit/template_unit_test.rb +78 -0
- data/test/unit/tokenizer_unit_test.rb +55 -0
- data/test/unit/variable_unit_test.rb +162 -0
- metadata +157 -77
- data/lib/extras/liquid_view.rb +0 -51
- data/lib/liquid/htmltags.rb +0 -74
- data/lib/liquid/module_ex.rb +0 -62
- data/test/liquid/assign_test.rb +0 -21
- data/test/liquid/condition_test.rb +0 -127
- data/test/liquid/drop_test.rb +0 -180
- data/test/liquid/error_handling_test.rb +0 -81
- data/test/liquid/file_system_test.rb +0 -29
- data/test/liquid/filter_test.rb +0 -125
- data/test/liquid/hash_ordering_test.rb +0 -25
- data/test/liquid/module_ex_test.rb +0 -87
- data/test/liquid/output_test.rb +0 -116
- data/test/liquid/parsing_quirks_test.rb +0 -52
- data/test/liquid/security_test.rb +0 -64
- data/test/liquid/standard_filter_test.rb +0 -251
- data/test/liquid/strainer_test.rb +0 -52
- data/test/liquid/tags/for_tag_test.rb +0 -297
- data/test/liquid/tags/if_else_tag_test.rb +0 -166
- data/test/liquid/tags/include_tag_test.rb +0 -166
- data/test/liquid/tags/increment_tag_test.rb +0 -24
- data/test/liquid/tags/standard_tag_test.rb +0 -295
- data/test/liquid/tags/statements_test.rb +0 -134
- data/test/liquid/tags/unless_else_tag_test.rb +0 -26
- data/test/liquid/template_test.rb +0 -146
- data/test/liquid/variable_test.rb +0 -186
@@ -0,0 +1,143 @@
|
|
1
|
+
module Liquid
|
2
|
+
class BlockBody
|
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/
|
6
|
+
TAGSTART = "{%".freeze
|
7
|
+
VARSTART = "{{".freeze
|
8
|
+
|
9
|
+
attr_reader :nodelist
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@nodelist = []
|
13
|
+
@blank = true
|
14
|
+
end
|
15
|
+
|
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)
|
25
|
+
end
|
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)
|
48
|
+
end
|
49
|
+
parse_context.line_number = tokenizer.line_number
|
50
|
+
end
|
51
|
+
|
52
|
+
yield nil, nil
|
53
|
+
end
|
54
|
+
|
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)
|
63
|
+
end
|
64
|
+
|
65
|
+
def blank?
|
66
|
+
@blank
|
67
|
+
end
|
68
|
+
|
69
|
+
def render(context)
|
70
|
+
output = []
|
71
|
+
context.resource_limits.render_score += @nodelist.length
|
72
|
+
|
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
|
85
|
+
# If we get an Interrupt that means the block must stop processing. An
|
86
|
+
# Interrupt is any command that stops block execution such as {% break %}
|
87
|
+
# or {% continue %}
|
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
|
93
|
+
end
|
94
|
+
idx += 1
|
95
|
+
end
|
96
|
+
|
97
|
+
output.join
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
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)
|
115
|
+
end
|
116
|
+
|
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)
|
124
|
+
token.scan(ContentOfVariable) do |content|
|
125
|
+
markup = content.first
|
126
|
+
return Variable.new(markup, parse_context)
|
127
|
+
end
|
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
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
data/lib/liquid/condition.rb
CHANGED
@@ -3,53 +3,70 @@ 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
|
-
'==' =>
|
12
|
-
'!=' =>
|
13
|
-
'<>' =>
|
14
|
-
'<' => :<,
|
15
|
-
'>' => :>,
|
16
|
-
'>=' => :>=,
|
17
|
-
'<=' => :<=,
|
18
|
-
'contains' => lambda
|
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
|
+
'<'.freeze => :<,
|
15
|
+
'>'.freeze => :>,
|
16
|
+
'>='.freeze => :>=,
|
17
|
+
'<='.freeze => :<=,
|
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
|
19
26
|
}
|
20
27
|
|
21
28
|
def self.operators
|
22
29
|
@@operators
|
23
30
|
end
|
24
31
|
|
25
|
-
attr_reader :attachment
|
32
|
+
attr_reader :attachment, :child_condition
|
26
33
|
attr_accessor :left, :operator, :right
|
27
34
|
|
28
35
|
def initialize(left = nil, operator = nil, right = nil)
|
29
|
-
@left
|
36
|
+
@left = left
|
37
|
+
@operator = operator
|
38
|
+
@right = right
|
30
39
|
@child_relation = nil
|
31
40
|
@child_condition = nil
|
32
41
|
end
|
33
42
|
|
34
43
|
def evaluate(context = Context.new)
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
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
|
44
58
|
end
|
59
|
+
result
|
45
60
|
end
|
46
61
|
|
47
62
|
def or(condition)
|
48
|
-
@child_relation
|
63
|
+
@child_relation = :or
|
64
|
+
@child_condition = condition
|
49
65
|
end
|
50
66
|
|
51
67
|
def and(condition)
|
52
|
-
@child_relation
|
68
|
+
@child_relation = :and
|
69
|
+
@child_condition = condition
|
53
70
|
end
|
54
71
|
|
55
72
|
def attach(attachment)
|
@@ -61,23 +78,27 @@ module Liquid
|
|
61
78
|
end
|
62
79
|
|
63
80
|
def inspect
|
64
|
-
"#<Condition #{[@left, @operator, @right].compact.join(' ')}>"
|
81
|
+
"#<Condition #{[@left, @operator, @right].compact.join(' '.freeze)}>"
|
65
82
|
end
|
66
83
|
|
84
|
+
protected
|
85
|
+
|
86
|
+
attr_reader :child_relation
|
87
|
+
|
67
88
|
private
|
68
89
|
|
69
90
|
def equal_variables(left, right)
|
70
|
-
if left.is_a?(
|
71
|
-
if right.respond_to?(left)
|
72
|
-
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)
|
73
94
|
else
|
74
95
|
return nil
|
75
96
|
end
|
76
97
|
end
|
77
98
|
|
78
|
-
if right.is_a?(
|
79
|
-
if left.respond_to?(right)
|
80
|
-
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)
|
81
102
|
else
|
82
103
|
return nil
|
83
104
|
end
|
@@ -90,31 +111,41 @@ module Liquid
|
|
90
111
|
# If the operator is empty this means that the decision statement is just
|
91
112
|
# a single variable. We can just poll this variable from the context and
|
92
113
|
# return this as the result.
|
93
|
-
return context
|
114
|
+
return context.evaluate(left) if op.nil?
|
94
115
|
|
95
|
-
left
|
116
|
+
left = context.evaluate(left)
|
117
|
+
right = context.evaluate(right)
|
96
118
|
|
97
|
-
operation = self.class.operators[op] || raise(ArgumentError.new("Unknown operator #{op}"))
|
119
|
+
operation = self.class.operators[op] || raise(Liquid::ArgumentError.new("Unknown operator #{op}"))
|
98
120
|
|
99
121
|
if operation.respond_to?(:call)
|
100
122
|
operation.call(self, left, right)
|
101
|
-
elsif left.respond_to?(operation)
|
102
|
-
|
103
|
-
|
104
|
-
|
123
|
+
elsif left.respond_to?(operation) && right.respond_to?(operation) && !left.is_a?(Hash) && !right.is_a?(Hash)
|
124
|
+
begin
|
125
|
+
left.send(operation, right)
|
126
|
+
rescue ::ArgumentError => e
|
127
|
+
raise Liquid::ArgumentError.new(e.message)
|
128
|
+
end
|
105
129
|
end
|
106
130
|
end
|
107
|
-
end
|
108
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
|
109
141
|
|
110
142
|
class ElseCondition < Condition
|
111
143
|
def else?
|
112
144
|
true
|
113
145
|
end
|
114
146
|
|
115
|
-
def evaluate(
|
147
|
+
def evaluate(_context)
|
116
148
|
true
|
117
149
|
end
|
118
150
|
end
|
119
|
-
|
120
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,27 +13,36 @@ module Liquid
|
|
14
13
|
# context['bob'] #=> nil class Context
|
15
14
|
class Context
|
16
15
|
attr_reader :scopes, :errors, :registers, :environments, :resource_limits
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
@
|
21
|
-
@
|
22
|
-
@
|
23
|
-
@
|
24
|
-
@
|
16
|
+
attr_accessor :exception_renderer, :template_name, :partial, :global_filter, :strict_variables, :strict_filters
|
17
|
+
|
18
|
+
def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil)
|
19
|
+
@environments = [environments].flatten
|
20
|
+
@scopes = [(outer_scope || {})]
|
21
|
+
@registers = registers
|
22
|
+
@errors = []
|
23
|
+
@partial = false
|
24
|
+
@strict_variables = false
|
25
|
+
@resource_limits = resource_limits || ResourceLimits.new(Template.default_resource_limits)
|
25
26
|
squash_instance_assigns_with_environments
|
26
27
|
|
28
|
+
@this_stack_used = false
|
29
|
+
|
30
|
+
self.exception_renderer = Template.default_exception_renderer
|
31
|
+
if rethrow_errors
|
32
|
+
self.exception_renderer = ->(e) { raise }
|
33
|
+
end
|
34
|
+
|
27
35
|
@interrupts = []
|
36
|
+
@filters = []
|
37
|
+
@global_filter = nil
|
28
38
|
end
|
29
39
|
|
30
|
-
def
|
31
|
-
|
32
|
-
(@resource_limits[:render_score_limit] && @resource_limits[:render_score_current] > @resource_limits[:render_score_limit] ) ||
|
33
|
-
(@resource_limits[:assign_score_limit] && @resource_limits[:assign_score_current] > @resource_limits[:assign_score_limit] )
|
40
|
+
def warnings
|
41
|
+
@warnings ||= []
|
34
42
|
end
|
35
43
|
|
36
44
|
def strainer
|
37
|
-
@strainer ||= Strainer.create(self)
|
45
|
+
@strainer ||= Strainer.create(self, @filters)
|
38
46
|
end
|
39
47
|
|
40
48
|
# Adds filters to this context.
|
@@ -43,17 +51,17 @@ module Liquid
|
|
43
51
|
# for that
|
44
52
|
def add_filters(filters)
|
45
53
|
filters = [filters].flatten.compact
|
54
|
+
@filters += filters
|
55
|
+
@strainer = nil
|
56
|
+
end
|
46
57
|
|
47
|
-
|
48
|
-
|
49
|
-
Strainer.add_known_filter(f)
|
50
|
-
strainer.extend(f)
|
51
|
-
end
|
58
|
+
def apply_global_filter(obj)
|
59
|
+
global_filter.nil? ? obj : global_filter.call(obj)
|
52
60
|
end
|
53
61
|
|
54
62
|
# are there any not handled interrupts?
|
55
|
-
def
|
56
|
-
|
63
|
+
def interrupt?
|
64
|
+
!@interrupts.empty?
|
57
65
|
end
|
58
66
|
|
59
67
|
# push an interrupt to the stack. this interrupt is considered not handled.
|
@@ -66,26 +74,22 @@ module Liquid
|
|
66
74
|
@interrupts.pop
|
67
75
|
end
|
68
76
|
|
69
|
-
def handle_error(e)
|
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
|
70
81
|
errors.push(e)
|
71
|
-
|
72
|
-
|
73
|
-
case e
|
74
|
-
when SyntaxError
|
75
|
-
"Liquid syntax error: #{e.message}"
|
76
|
-
else
|
77
|
-
"Liquid error: #{e.message}"
|
78
|
-
end
|
82
|
+
exception_renderer.call(e).to_s
|
79
83
|
end
|
80
84
|
|
81
85
|
def invoke(method, *args)
|
82
|
-
strainer.invoke(method, *args)
|
86
|
+
strainer.invoke(method, *args).to_liquid
|
83
87
|
end
|
84
88
|
|
85
89
|
# Push new local scope on the stack. use <tt>Context#stack</tt> instead
|
86
|
-
def push(new_scope={})
|
90
|
+
def push(new_scope = {})
|
87
91
|
@scopes.unshift(new_scope)
|
88
|
-
raise StackLevelError, "Nesting too deep" if @scopes.length >
|
92
|
+
raise StackLevelError, "Nesting too deep".freeze if @scopes.length > Block::MAX_DEPTH
|
89
93
|
end
|
90
94
|
|
91
95
|
# Merge a hash of variables in the current local scope
|
@@ -107,11 +111,19 @@ module Liquid
|
|
107
111
|
# end
|
108
112
|
#
|
109
113
|
# context['var] #=> nil
|
110
|
-
def stack(new_scope=
|
111
|
-
|
114
|
+
def stack(new_scope = nil)
|
115
|
+
old_stack_used = @this_stack_used
|
116
|
+
if new_scope
|
117
|
+
push(new_scope)
|
118
|
+
@this_stack_used = true
|
119
|
+
else
|
120
|
+
@this_stack_used = false
|
121
|
+
end
|
122
|
+
|
112
123
|
yield
|
113
124
|
ensure
|
114
|
-
pop
|
125
|
+
pop if @this_stack_used
|
126
|
+
@this_stack_used = old_stack_used
|
115
127
|
end
|
116
128
|
|
117
129
|
def clear_instance_assigns
|
@@ -120,148 +132,95 @@ module Liquid
|
|
120
132
|
|
121
133
|
# Only allow String, Numeric, Hash, Array, Proc, Boolean or <tt>Liquid::Drop</tt>
|
122
134
|
def []=(key, value)
|
135
|
+
unless @this_stack_used
|
136
|
+
@this_stack_used = true
|
137
|
+
push({})
|
138
|
+
end
|
123
139
|
@scopes[0][key] = value
|
124
140
|
end
|
125
141
|
|
126
|
-
|
127
|
-
|
142
|
+
# Look up variable, either resolve directly after considering the name. We can directly handle
|
143
|
+
# Strings, digits, floats and booleans (true,false).
|
144
|
+
# If no match is made we lookup the variable in the current scope and
|
145
|
+
# later move up to the parent blocks to see if we can resolve the variable somewhere up the tree.
|
146
|
+
# Some special keywords return symbols. Those symbols are to be called on the rhs object in expressions
|
147
|
+
#
|
148
|
+
# Example:
|
149
|
+
# products == empty #=> products.empty?
|
150
|
+
def [](expression)
|
151
|
+
evaluate(Expression.parse(expression))
|
128
152
|
end
|
129
153
|
|
130
|
-
def
|
131
|
-
|
154
|
+
def key?(key)
|
155
|
+
self[key] != nil
|
132
156
|
end
|
133
157
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
'true' => true,
|
138
|
-
'false' => false,
|
139
|
-
'blank' => :blank?,
|
140
|
-
'empty' => :empty?
|
141
|
-
}
|
142
|
-
|
143
|
-
# Look up variable, either resolve directly after considering the name. We can directly handle
|
144
|
-
# Strings, digits, floats and booleans (true,false).
|
145
|
-
# If no match is made we lookup the variable in the current scope and
|
146
|
-
# later move up to the parent blocks to see if we can resolve the variable somewhere up the tree.
|
147
|
-
# Some special keywords return symbols. Those symbols are to be called on the rhs object in expressions
|
148
|
-
#
|
149
|
-
# Example:
|
150
|
-
# products == empty #=> products.empty?
|
151
|
-
def resolve(key)
|
152
|
-
if LITERALS.key?(key)
|
153
|
-
LITERALS[key]
|
154
|
-
else
|
155
|
-
case key
|
156
|
-
when /^'(.*)'$/ # Single quoted strings
|
157
|
-
$1
|
158
|
-
when /^"(.*)"$/ # Double quoted strings
|
159
|
-
$1
|
160
|
-
when /^(-?\d+)$/ # Integer and floats
|
161
|
-
$1.to_i
|
162
|
-
when /^\((\S+)\.\.(\S+)\)$/ # Ranges
|
163
|
-
(resolve($1).to_i..resolve($2).to_i)
|
164
|
-
when /^(-?\d[\d\.]+)$/ # Floats
|
165
|
-
$1.to_f
|
166
|
-
else
|
167
|
-
variable(key)
|
168
|
-
end
|
169
|
-
end
|
170
|
-
end
|
171
|
-
|
172
|
-
# Fetches an object starting at the local scope and then moving up the hierachy
|
173
|
-
def find_variable(key)
|
174
|
-
scope = @scopes.find { |s| s.has_key?(key) }
|
175
|
-
variable = nil
|
176
|
-
|
177
|
-
if scope.nil?
|
178
|
-
@environments.each do |e|
|
179
|
-
if variable = lookup_and_evaluate(e, key)
|
180
|
-
scope = e
|
181
|
-
break
|
182
|
-
end
|
183
|
-
end
|
184
|
-
end
|
185
|
-
|
186
|
-
scope ||= @environments.last || @scopes.last
|
187
|
-
variable ||= lookup_and_evaluate(scope, key)
|
188
|
-
|
189
|
-
variable = variable.to_liquid
|
190
|
-
variable.context = self if variable.respond_to?(:context=)
|
191
|
-
|
192
|
-
return variable
|
193
|
-
end
|
158
|
+
def evaluate(object)
|
159
|
+
object.respond_to?(:evaluate) ? object.evaluate(self) : object
|
160
|
+
end
|
194
161
|
|
195
|
-
|
196
|
-
|
197
|
-
#
|
198
|
-
#
|
199
|
-
|
200
|
-
|
201
|
-
def variable(markup)
|
202
|
-
parts = markup.scan(VariableParser)
|
203
|
-
square_bracketed = /^\[(.*)\]$/
|
162
|
+
# Fetches an object starting at the local scope and then moving up the hierachy
|
163
|
+
def find_variable(key, raise_on_not_found: true)
|
164
|
+
# This was changed from find() to find_index() because this is a very hot
|
165
|
+
# path and find_index() is optimized in MRI to reduce object allocation
|
166
|
+
index = @scopes.find_index { |s| s.key?(key) }
|
167
|
+
scope = @scopes[index] if index
|
204
168
|
|
205
|
-
|
169
|
+
variable = nil
|
206
170
|
|
207
|
-
|
208
|
-
|
171
|
+
if scope.nil?
|
172
|
+
@environments.each do |e|
|
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
|
177
|
+
scope = e
|
178
|
+
break
|
179
|
+
end
|
209
180
|
end
|
181
|
+
end
|
210
182
|
|
211
|
-
|
212
|
-
|
213
|
-
parts.each do |part|
|
214
|
-
part = resolve($1) if part_resolved = (part =~ square_bracketed)
|
183
|
+
scope ||= @environments.last || @scopes.last
|
184
|
+
variable ||= lookup_and_evaluate(scope, key, raise_on_not_found: raise_on_not_found)
|
215
185
|
|
216
|
-
|
217
|
-
|
218
|
-
if object.respond_to?(:[]) and
|
219
|
-
((object.respond_to?(:has_key?) and object.has_key?(part)) or
|
220
|
-
(object.respond_to?(:fetch) and part.is_a?(Integer)))
|
186
|
+
variable = variable.to_liquid
|
187
|
+
variable.context = self if variable.respond_to?(:context=)
|
221
188
|
|
222
|
-
|
223
|
-
|
224
|
-
object = res.to_liquid
|
189
|
+
variable
|
190
|
+
end
|
225
191
|
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
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
|
230
196
|
|
231
|
-
|
197
|
+
value = obj[key]
|
232
198
|
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
199
|
+
if value.is_a?(Proc) && obj.respond_to?(:[]=)
|
200
|
+
obj[key] = (value.arity == 0) ? value.call : value.call(self)
|
201
|
+
else
|
202
|
+
value
|
203
|
+
end
|
204
|
+
end
|
238
205
|
|
239
|
-
|
240
|
-
object.context = self if object.respond_to?(:context=)
|
241
|
-
end
|
242
|
-
end
|
206
|
+
private
|
243
207
|
|
244
|
-
|
245
|
-
|
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
|
246
214
|
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
end # lookup_and_evaluate
|
254
|
-
|
255
|
-
def squash_instance_assigns_with_environments
|
256
|
-
@scopes.last.each_key do |k|
|
257
|
-
@environments.each do |env|
|
258
|
-
if env.has_key?(k)
|
259
|
-
scopes.last[k] = lookup_and_evaluate(env, k)
|
260
|
-
break
|
261
|
-
end
|
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
|
262
221
|
end
|
263
222
|
end
|
264
|
-
end
|
223
|
+
end
|
224
|
+
end # squash_instance_assigns_with_environments
|
265
225
|
end # Context
|
266
|
-
|
267
226
|
end # Liquid
|