3
# Context keeps the variable stack and resolves variables, as well as keywords
5
# context['variable'] = 'testing'
6
# context['variable'] #=> 'testing'
7
# context['true'] #=> true
8
# context['10.2232'] #=> 10.2232
11
# context['bob'] = 'bobsen'
14
# context['bob'] #=> nil class Context
16
attr_reader :scopes, :errors, :registers, :environments
18
def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false)
19
@environments = [environments].flatten
20
@scopes = [(outer_scope || {})]
21
@registers = registers
23
@rethrow_errors = rethrow_errors
24
squash_instance_assigns_with_environments
28
@strainer ||= Strainer.create(self)
31
# Adds filters to this context.
33
# Note that this does not register the filters with the main Template object. see <tt>Template.register_filter</tt>
35
def add_filters(filters)
36
filters = [filters].flatten.compact
39
raise ArgumentError, "Expected module but got: #{f.class}" unless f.is_a?(Module)
46
raise if @rethrow_errors
50
"Liquid syntax error: #{e.message}"
52
"Liquid error: #{e.message}"
56
def invoke(method, *args)
57
if strainer.respond_to?(method)
58
strainer.__send__(method, *args)
64
# Push new local scope on the stack. use <tt>Context#stack</tt> instead
65
def push(new_scope={})
66
@scopes.unshift(new_scope)
67
raise StackLevelError, "Nesting too deep" if @scopes.length > 100
70
# Merge a hash of variables in the current local scope
72
@scopes[0].merge!(new_scopes)
75
# Pop from the stack. use <tt>Context#stack</tt> instead
77
raise ContextError if @scopes.size == 1
81
# Pushes a new local scope on the stack, pops it at the end of the block
85
# context['var'] = 'hi'
88
# context['var] #=> nil
89
def stack(new_scope={})
96
def clear_instance_assigns
100
# Only allow String, Numeric, Hash, Array, Proc, Boolean or <tt>Liquid::Drop</tt>
102
@scopes[0][key] = value
115
nil => nil, 'nil' => nil, 'null' => nil, '' => nil,
122
# Look up variable, either resolve directly after considering the name. We can directly handle
123
# Strings, digits, floats and booleans (true,false).
124
# If no match is made we lookup the variable in the current scope and
125
# later move up to the parent blocks to see if we can resolve the variable somewhere up the tree.
126
# Some special keywords return symbols. Those symbols are to be called on the rhs object in expressions
129
# products == empty #=> products.empty?
131
if LITERALS.key?(key)
135
when /^'(.*)'$/ # Single quoted strings
137
when /^"(.*)"$/ # Double quoted strings
139
when /^(\d+)$/ # Integer and floats
141
when /^\((\S+)\.\.(\S+)\)$/ # Ranges
142
(resolve($1).to_i..resolve($2).to_i)
143
when /^(\d[\d\.]+)$/ # Floats
151
# Fetches an object starting at the local scope and then moving up the hierachy
152
def find_variable(key)
153
scope = @scopes.find { |s| s.has_key?(key) }
156
@environments.each do |e|
157
if variable = lookup_and_evaluate(e, key)
164
scope ||= @environments.last || @scopes.last
165
variable ||= lookup_and_evaluate(scope, key)
167
variable = variable.to_liquid
168
variable.context = self if variable.respond_to?(:context=)
173
# Resolves namespaced queries gracefully.
176
# @context['hash'] = {"name" => 'tobi'}
177
# assert_equal 'tobi', @context['hash.name']
178
# assert_equal 'tobi', @context['hash["name"]']
180
parts = markup.scan(VariableParser)
181
square_bracketed = /^\[(.*)\]$/
183
first_part = parts.shift
185
if first_part =~ square_bracketed
186
first_part = resolve($1)
189
if object = find_variable(first_part)
192
part = resolve($1) if part_resolved = (part =~ square_bracketed)
194
# If object is a hash- or array-like object we look for the
195
# presence of the key and if its available we return it
196
if object.respond_to?(:[]) and
197
((object.respond_to?(:has_key?) and object.has_key?(part)) or
198
(object.respond_to?(:fetch) and part.is_a?(Integer)))
200
# if its a proc we will replace the entry with the proc
201
res = lookup_and_evaluate(object, part)
202
object = res.to_liquid
204
# Some special cases. If the part wasn't in square brackets and
205
# no key with the same name was found we interpret following calls
206
# as commands and call them on the current object
207
elsif !part_resolved and object.respond_to?(part) and ['size', 'first', 'last'].include?(part)
209
object = object.send(part.intern).to_liquid
211
# No key was present with the desired value and it wasn't one of the directly supported
212
# keywords either. The only thing we got left is to return nil
217
# If we are dealing with a drop here we have to
218
object.context = self if object.respond_to?(:context=)
225
def lookup_and_evaluate(obj, key)
226
if (value = obj[key]).is_a?(Proc) && obj.respond_to?(:[]=)
227
obj[key] = (value.arity == 0) ? value.call : value.call(self)
231
end # lookup_and_evaluate
233
def squash_instance_assigns_with_environments
234
@scopes.last.each_key do |k|
235
@environments.each do |env|
237
scopes.last[k] = lookup_and_evaluate(env, k)
242
end # squash_instance_assigns_with_environments