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.


18
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 18

def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil)
  @environments     = [environments].flatten
  @scopes           = [(outer_scope || {})]
  @registers        = registers
  @errors           = []
  @partial          = false
  @strict_variables = false
  @resource_limits  = resource_limits || ResourceLimits.new(Template.default_resource_limits)
  squash_instance_assigns_with_environments

  @this_stack_used = false

  self.exception_renderer = Template.default_exception_renderer
  if rethrow_errors
    self.exception_renderer = ->(e) { raise }
  end

  @interrupts = []
  @filters = []
  @global_filter = nil
end

Instance Attribute Details

#environmentsObject (readonly)

Returns the value of attribute environments


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

def environments
  @environments
end

#errorsObject (readonly)

Returns the value of attribute errors


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

def errors
  @errors
end

#exception_rendererObject

Returns the value of attribute exception_renderer


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

def exception_renderer
  @exception_renderer
end

#global_filterObject

Returns the value of attribute global_filter


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

def global_filter
  @global_filter
end

#partialObject

Returns the value of attribute partial


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

def partial
  @partial
end

#registersObject (readonly)

Returns the value of attribute registers


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

def registers
  @registers
end

#resource_limitsObject (readonly)

Returns the value of attribute resource_limits


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

def resource_limits
  @resource_limits
end

#scopesObject (readonly)

Returns the value of attribute scopes


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

def scopes
  @scopes
end

#strict_filtersObject

Returns the value of attribute strict_filters


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

def strict_filters
  @strict_filters
end

#strict_variablesObject

Returns the value of attribute strict_variables


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

def strict_variables
  @strict_variables
end

#template_nameObject

Returns the value of attribute template_name


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

def template_name
  @template_name
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?

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

def [](expression)
  evaluate(Expression.parse(expression))
end

#[]=(key, value) ⇒ Object

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


134
135
136
137
138
139
140
# File 'lib/liquid/context.rb', line 134

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


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

def add_filters(filters)
  filters = [filters].flatten.compact
  @filters += filters
  @strainer = nil
end

#apply_global_filter(obj) ⇒ Object


58
59
60
# File 'lib/liquid/context.rb', line 58

def apply_global_filter(obj)
  global_filter.nil? ? obj : global_filter.call(obj)
end

#clear_instance_assignsObject


129
130
131
# File 'lib/liquid/context.rb', line 129

def clear_instance_assigns
  @scopes[0] = {}
end

#evaluate(object) ⇒ Object


158
159
160
# File 'lib/liquid/context.rb', line 158

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

#find_variable(key, raise_on_not_found: true) ⇒ Object

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


163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
# File 'lib/liquid/context.rb', line 163

def find_variable(key, raise_on_not_found: true)
  # 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.key?(key) }
  scope = @scopes[index] if index

  variable = nil

  if scope.nil?
    @environments.each do |e|
      variable = lookup_and_evaluate(e, key, raise_on_not_found: raise_on_not_found)
      # When lookup returned a value OR there is no value but the lookup also did not raise
      # then it is the value we are looking for.
      if !variable.nil? || @strict_variables && raise_on_not_found
        scope = e
        break
      end
    end
  end

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

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

  variable
end

#handle_error(e, line_number = nil) ⇒ Object


77
78
79
80
81
82
83
# File 'lib/liquid/context.rb', line 77

def handle_error(e, line_number = nil)
  e = internal_error unless e.is_a?(Liquid::Error)
  e.template_name ||= template_name
  e.line_number ||= line_number
  errors.push(e)
  exception_renderer.call(e).to_s
end

#interrupt?Boolean

are there any not handled interrupts?

Returns:

  • (Boolean)

63
64
65
# File 'lib/liquid/context.rb', line 63

def interrupt?
  !@interrupts.empty?
end

#invoke(method, *args) ⇒ Object


85
86
87
# File 'lib/liquid/context.rb', line 85

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

#key?(key) ⇒ Boolean

Returns:

  • (Boolean)

154
155
156
# File 'lib/liquid/context.rb', line 154

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

#lookup_and_evaluate(obj, key, raise_on_not_found: true) ⇒ Object


192
193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/liquid/context.rb', line 192

def lookup_and_evaluate(obj, key, raise_on_not_found: true)
  if @strict_variables && raise_on_not_found && obj.respond_to?(:key?) && !obj.key?(key)
    raise Liquid::UndefinedVariable, "undefined variable #{key}"
  end

  value = obj[key]

  if value.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


96
97
98
# File 'lib/liquid/context.rb', line 96

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

#popObject

Pop from the stack. use Context#stack instead

Raises:


101
102
103
104
# File 'lib/liquid/context.rb', line 101

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

#pop_interruptObject

pop an interrupt from the stack


73
74
75
# File 'lib/liquid/context.rb', line 73

def pop_interrupt
  @interrupts.pop
end

#push(new_scope = {}) ⇒ Object

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

Raises:


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

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

#push_interrupt(e) ⇒ Object

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


68
69
70
# File 'lib/liquid/context.rb', line 68

def push_interrupt(e)
  @interrupts.push(e)
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

114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/liquid/context.rb', line 114

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


44
45
46
# File 'lib/liquid/context.rb', line 44

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

#warningsObject


40
41
42
# File 'lib/liquid/context.rb', line 40

def warnings
  @warnings ||= []
end