Class: Liquid::Context

Inherits:
Object
  • Object
show all
Includes:
ContextProfilingHook
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

Attributes included from ContextProfilingHook

#profiler

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil, static_environments = {}, environment = Environment.default) {|_self| ... } ⇒ Context

Returns a new instance of Context.

Yields:

  • (_self)

Yield Parameters:



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/liquid/context.rb', line 25

def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil, static_environments = {}, environment = Environment.default)
  @environment = environment
  @environments = [environments]
  @environments.flatten!

  @static_environments = [static_environments].flatten(1).freeze
  @scopes              = [outer_scope || {}]
  @registers           = registers.is_a?(Registers) ? registers : Registers.new(registers)
  @errors              = []
  @partial             = false
  @strict_variables    = false
  @resource_limits     = resource_limits || ResourceLimits.new(environment.default_resource_limits)
  @base_scope_depth    = 0
  @interrupts          = []
  @filters             = []
  @global_filter       = nil
  @disabled_tags       = {}

  # Instead of constructing new StringScanner objects for each Expression parse,
  # we recycle the same one.
  @string_scanner = StringScanner.new("")

  @registers.static[:cached_partials] ||= {}
  @registers.static[:file_system] ||= environment.file_system
  @registers.static[:template_factory] ||= Liquid::TemplateFactory.new

  self.exception_renderer = environment.exception_renderer
  if rethrow_errors
    self.exception_renderer = Liquid::RAISE_EXCEPTION_LAMBDA
  end

  yield self if block_given?

  # Do this last, since it could result in this object being passed to a Proc in the environment
  squash_instance_assigns_with_environments
end

Instance Attribute Details

#environmentObject

Returns the value of attribute environment.



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

def environment
  @environment
end

#environmentsObject (readonly)

Returns the value of attribute environments.



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

def environments
  @environments
end

#errorsObject

Returns the value of attribute errors.



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

def errors
  @errors
end

#exception_rendererObject

Returns the value of attribute exception_renderer.



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

def exception_renderer
  @exception_renderer
end

#global_filterObject

Returns the value of attribute global_filter.



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

def global_filter
  @global_filter
end

#partialObject

Returns the value of attribute partial.



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

def partial
  @partial
end

#registersObject (readonly)

Returns the value of attribute registers.



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

def registers
  @registers
end

#resource_limitsObject (readonly)

Returns the value of attribute resource_limits.



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

def resource_limits
  @resource_limits
end

#scopesObject (readonly)

Returns the value of attribute scopes.



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

def scopes
  @scopes
end

#static_environmentsObject (readonly)

Returns the value of attribute static_environments.



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

def static_environments
  @static_environments
end

#static_registersObject (readonly)

Returns the value of attribute static_registers.



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

def static_registers
  @static_registers
end

#strainerObject



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

def strainer
  @strainer ||= @environment.create_strainer(self, @filters)
end

#strict_filtersObject

Returns the value of attribute strict_filters.



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

def strict_filters
  @strict_filters
end

#strict_variablesObject

Returns the value of attribute strict_variables.



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

def strict_variables
  @strict_variables
end

#template_nameObject

Returns the value of attribute template_name.



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

def template_name
  @template_name
end

#warningsObject

rubocop:enable Metrics/ParameterLists



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

def warnings
  @warnings ||= []
end

Class Method Details

.build(environment: Environment.default, environments: {}, outer_scope: {}, registers: {}, rethrow_errors: false, resource_limits: nil, static_environments: {}, &block) ⇒ Object

rubocop:disable Metrics/ParameterLists



21
22
23
# File 'lib/liquid/context.rb', line 21

def self.build(environment: Environment.default, environments: {}, outer_scope: {}, registers: {}, rethrow_errors: false, resource_limits: nil, static_environments: {}, &block)
  new(environments, outer_scope, registers, rethrow_errors, resource_limits, static_environments, environment, &block)
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?


182
183
184
# File 'lib/liquid/context.rb', line 182

def [](expression)
  evaluate(Expression.parse(expression, @string_scanner))
end

#[]=(key, value) ⇒ Object

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



170
171
172
# File 'lib/liquid/context.rb', line 170

def []=(key, value)
  @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



75
76
77
78
79
# File 'lib/liquid/context.rb', line 75

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

#apply_global_filter(obj) ⇒ Object



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

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

#clear_instance_assignsObject



165
166
167
# File 'lib/liquid/context.rb', line 165

def clear_instance_assigns
  @scopes[0] = {}
end

#evaluate(object) ⇒ Object



190
191
192
# File 'lib/liquid/context.rb', line 190

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



195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/liquid/context.rb', line 195

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) }

  variable = if index
    lookup_and_evaluate(@scopes[index], key, raise_on_not_found: raise_on_not_found)
  else
    try_variable_find_in_environments(key, raise_on_not_found: raise_on_not_found)
  end

  # update variable's context before invoking #to_liquid
  variable.context = self if variable.respond_to?(:context=)

  liquid_variable = variable.to_liquid

  liquid_variable.context = self if variable != liquid_variable && liquid_variable.respond_to?(:context=)

  liquid_variable
end

#handle_error(e, line_number = nil) ⇒ Object



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

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)


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

def interrupt?
  !@interrupts.empty?
end

#invoke(method, *args) ⇒ Object



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

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

#key?(key) ⇒ Boolean

Returns:

  • (Boolean)


186
187
188
# File 'lib/liquid/context.rb', line 186

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

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



216
217
218
219
220
221
222
223
224
225
226
227
228
# File 'lib/liquid/context.rb', line 216

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



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

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

#new_isolated_subcontextObject

Creates a new context inheriting resource limits, filters, environment etc., but with an isolated scope.



146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/liquid/context.rb', line 146

def new_isolated_subcontext
  check_overflow

  self.class.build(
    environment: @environment,
    resource_limits: resource_limits,
    static_environments: static_environments,
    registers: Registers.new(registers),
  ).tap do |subcontext|
    subcontext.base_scope_depth   = base_scope_depth + 1
    subcontext.exception_renderer = exception_renderer
    subcontext.filters  = @filters
    subcontext.strainer = nil
    subcontext.errors   = errors
    subcontext.warnings = warnings
    subcontext.disabled_tags = @disabled_tags
  end
end

#popObject

Pop from the stack. use Context#stack instead

Raises:



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

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

#pop_interruptObject

pop an interrupt from the stack



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

def pop_interrupt
  @interrupts.pop
end

#push(new_scope = {}) ⇒ Object

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



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

def push(new_scope = {})
  @scopes.unshift(new_scope)
  check_overflow
end

#push_interrupt(e) ⇒ Object

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



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

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

#stack(new_scope = {}) ⇒ 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


137
138
139
140
141
142
# File 'lib/liquid/context.rb', line 137

def stack(new_scope = {})
  push(new_scope)
  yield
ensure
  pop
end

#tag_disabled?(tag_name) ⇒ Boolean

Returns:

  • (Boolean)


241
242
243
# File 'lib/liquid/context.rb', line 241

def tag_disabled?(tag_name)
  @disabled_tags.fetch(tag_name, 0) > 0
end

#with_disabled_tags(tag_names) ⇒ Object



230
231
232
233
234
235
236
237
238
239
# File 'lib/liquid/context.rb', line 230

def with_disabled_tags(tag_names)
  tag_names.each do |name|
    @disabled_tags[name] = @disabled_tags.fetch(name, 0) + 1
  end
  yield
ensure
  tag_names.each do |name|
    @disabled_tags[name] -= 1
  end
end