Class: Liquid::Context

Inherits:
Object
  • Object
show all
Defined in:
lib/liquid/context.rb

Overview

Context keeps the variable stack and resolves variables, as well as keywords

context['variable'] = 'testing'
context['variable'] #=> 'testing'
context['true']     #=> true
context['10.2232']  #=> 10.2232

context.stack do
   context['bob'] = 'bobsen'
end

context['bob']  #=> nil  class Context

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil) ⇒ Context

Returns a new instance of Context.



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/liquid/context.rb', line 19

def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil)
  @environments     = [environments].flatten
  @scopes           = [(outer_scope || {})]
  @registers        = registers
  @errors           = []
  @resource_limits  = resource_limits || Template.default_resource_limits.dup
  @resource_limits[:render_score_current] = 0
  @resource_limits[:assign_score_current] = 0
  @parsed_expression = Hash.new{ |cache, markup| cache[markup] = Expression.parse(markup) }
  squash_instance_assigns_with_environments

  @this_stack_used = false

  if rethrow_errors
    self.exception_handler = ->(e) { true }
  end

  @interrupts = []
  @filters = []
end

Instance Attribute Details

#environmentsObject (readonly)

Returns the value of attribute environments.



16
17
18
# File 'lib/liquid/context.rb', line 16

def environments
  @environments
end

#errorsObject (readonly)

Returns the value of attribute errors.



16
17
18
# File 'lib/liquid/context.rb', line 16

def errors
  @errors
end

#exception_handlerObject

Returns the value of attribute exception_handler.



17
18
19
# File 'lib/liquid/context.rb', line 17

def exception_handler
  @exception_handler
end

#registersObject (readonly)

Returns the value of attribute registers.



16
17
18
# File 'lib/liquid/context.rb', line 16

def registers
  @registers
end

#resource_limitsObject (readonly)

Returns the value of attribute resource_limits.



16
17
18
# File 'lib/liquid/context.rb', line 16

def resource_limits
  @resource_limits
end

#scopesObject (readonly)

Returns the value of attribute scopes.



16
17
18
# File 'lib/liquid/context.rb', line 16

def scopes
  @scopes
end

Instance Method Details

#[](expression) ⇒ Object

Look up variable, either resolve directly after considering the name. We can directly handle Strings, digits, floats and booleans (true,false). If no match is made we lookup the variable in the current scope and later move up to the parent blocks to see if we can resolve the variable somewhere up the tree. Some special keywords return symbols. Those symbols are to be called on the rhs object in expressions

Example:

products == empty #=> products.empty?


172
173
174
# File 'lib/liquid/context.rb', line 172

def [](expression)
  evaluate(@parsed_expression[expression])
end

#[]=(key, value) ⇒ Object

Only allow String, Numeric, Hash, Array, Proc, Boolean or Liquid::Drop



156
157
158
159
160
161
162
# File 'lib/liquid/context.rb', line 156

def []=(key, value)
  unless @this_stack_used
    @this_stack_used = true
    push({})
  end
  @scopes[0][key] = value
end

#add_filters(filters) ⇒ Object

Adds filters to this context.

Note that this does not register the filters with the main Template object. see Template.register_filter for that



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/liquid/context.rb', line 62

def add_filters(filters)
  filters = [filters].flatten.compact
  filters.each do |f|
    raise ArgumentError, "Expected module but got: #{f.class}" unless f.is_a?(Module)
    Strainer.add_known_filter(f)
  end

  # If strainer is already setup then there's no choice but to use a runtime
  # extend call. If strainer is not yet created, we can utilize strainers
  # cached class based API, which avoids busting the method cache.
  if @strainer
    filters.each do |f|
      strainer.extend(f)
    end
  else
    @filters.concat filters
  end
end

#clear_instance_assignsObject



151
152
153
# File 'lib/liquid/context.rb', line 151

def clear_instance_assigns
  @scopes[0] = {}
end

#evaluate(object) ⇒ Object



180
181
182
# File 'lib/liquid/context.rb', line 180

def evaluate(object)
  object.respond_to?(:evaluate) ? object.evaluate(self) : object
end

#find_variable(key) ⇒ Object

Fetches an object starting at the local scope and then moving up the hierachy



185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/liquid/context.rb', line 185

def find_variable(key)

  # This was changed from find() to find_index() because this is a very hot
  # path and find_index() is optimized in MRI to reduce object allocation
  index = @scopes.find_index { |s| s.has_key?(key) }
  scope = @scopes[index] if index

  variable = nil

  if scope.nil?
    @environments.each do |e|
      variable = lookup_and_evaluate(e, key)
      unless variable.nil?
        scope = e
        break
      end
    end
  end

  scope     ||= @environments.last || @scopes.last
  variable  ||= lookup_and_evaluate(scope, key)

  variable = variable.to_liquid
  variable.context = self if variable.respond_to?(:context=)

  return variable
end

#handle_error(e, token = nil) ⇒ Object



97
98
99
100
101
102
103
104
105
# File 'lib/liquid/context.rb', line 97

def handle_error(e, token=nil)
  if e.is_a?(Liquid::Error)
    e.set_line_number_from_token(token)
  end

  errors.push(e)
  raise if exception_handler && exception_handler.call(e)
  Liquid::Error.render(e)
end

#has_interrupt?Boolean

are there any not handled interrupts?

Returns:

  • (Boolean)


82
83
84
# File 'lib/liquid/context.rb', line 82

def has_interrupt?
  !@interrupts.empty?
end

#has_key?(key) ⇒ Boolean

Returns:

  • (Boolean)


176
177
178
# File 'lib/liquid/context.rb', line 176

def has_key?(key)
  self[key] != nil
end

#increment_used_resources(key, obj) ⇒ Object



40
41
42
43
44
45
46
# File 'lib/liquid/context.rb', line 40

def increment_used_resources(key, obj)
  @resource_limits[key] += if obj.kind_of?(String) || obj.kind_of?(Array) || obj.kind_of?(Hash)
    obj.length
  else
    1
  end
end

#invoke(method, *args) ⇒ Object



107
108
109
# File 'lib/liquid/context.rb', line 107

def invoke(method, *args)
  strainer.invoke(method, *args).to_liquid
end

#lookup_and_evaluate(obj, key) ⇒ Object



213
214
215
216
217
218
219
# File 'lib/liquid/context.rb', line 213

def lookup_and_evaluate(obj, key)
  if (value = obj[key]).is_a?(Proc) && obj.respond_to?(:[]=)
    obj[key] = (value.arity == 0) ? value.call : value.call(self)
  else
    value
  end
end

#merge(new_scopes) ⇒ Object

Merge a hash of variables in the current local scope



118
119
120
# File 'lib/liquid/context.rb', line 118

def merge(new_scopes)
  @scopes[0].merge!(new_scopes)
end

#popObject

Pop from the stack. use Context#stack instead

Raises:



123
124
125
126
# File 'lib/liquid/context.rb', line 123

def pop
  raise ContextError if @scopes.size == 1
  @scopes.shift
end

#pop_interruptObject

pop an interrupt from the stack



92
93
94
# File 'lib/liquid/context.rb', line 92

def pop_interrupt
  @interrupts.pop
end

#push(new_scope = {}) ⇒ Object

Push new local scope on the stack. use Context#stack instead

Raises:



112
113
114
115
# File 'lib/liquid/context.rb', line 112

def push(new_scope={})
  @scopes.unshift(new_scope)
  raise StackLevelError, "Nesting too deep".freeze if @scopes.length > 100
end

#push_interrupt(e) ⇒ Object

push an interrupt to the stack. this interrupt is considered not handled.



87
88
89
# File 'lib/liquid/context.rb', line 87

def push_interrupt(e)
  @interrupts.push(e)
end

#resource_limits_reached?Boolean

Returns:

  • (Boolean)


48
49
50
51
52
# File 'lib/liquid/context.rb', line 48

def resource_limits_reached?
  (@resource_limits[:render_length_limit] && @resource_limits[:render_length_current] > @resource_limits[:render_length_limit]) ||
  (@resource_limits[:render_score_limit]  && @resource_limits[:render_score_current]  > @resource_limits[:render_score_limit] ) ||
  (@resource_limits[:assign_score_limit]  && @resource_limits[:assign_score_current]  > @resource_limits[:assign_score_limit] )
end

#stack(new_scope = nil) ⇒ Object

Pushes a new local scope on the stack, pops it at the end of the block

Example:

context.stack do
   context['var'] = 'hi'
end

context['var]  #=> nil


136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/liquid/context.rb', line 136

def stack(new_scope=nil)
  old_stack_used = @this_stack_used
  if new_scope
    push(new_scope)
    @this_stack_used = true
  else
    @this_stack_used = false
  end

  yield
ensure
  pop if @this_stack_used
  @this_stack_used = old_stack_used
end

#strainerObject



54
55
56
# File 'lib/liquid/context.rb', line 54

def strainer
  @strainer ||= Strainer.create(self, @filters)
end