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 = {}) {|_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
# File 'lib/liquid/context.rb', line 25

def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil, static_environments = {})
  @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(Template.default_resource_limits)
  @base_scope_depth    = 0
  @interrupts          = []
  @filters             = []
  @global_filter       = nil
  @disabled_tags       = {}

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

  self.exception_renderer = Template.default_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

#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



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

def strainer
  @strainer ||= StrainerFactory.create(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



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

def warnings
  @warnings ||= []
end

Class Method Details

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


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

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

#[]=(key, value) ⇒ Object

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



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

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



70
71
72
73
74
# File 'lib/liquid/context.rb', line 70

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

#apply_global_filter(obj) ⇒ Object



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

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

#clear_instance_assignsObject



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

def clear_instance_assigns
  @scopes[0] = {}
end

#evaluate(object) ⇒ Object



184
185
186
# File 'lib/liquid/context.rb', line 184

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



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

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



95
96
97
98
99
100
101
# File 'lib/liquid/context.rb', line 95

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)


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

def interrupt?
  !@interrupts.empty?
end

#invoke(method, *args) ⇒ Object



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

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

#key?(key) ⇒ Boolean

Returns:

  • (Boolean)


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

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

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



210
211
212
213
214
215
216
217
218
219
220
221
222
# File 'lib/liquid/context.rb', line 210

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



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

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.



141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/liquid/context.rb', line 141

def new_isolated_subcontext
  check_overflow

  self.class.build(
    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:



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

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

#pop_interruptObject

pop an interrupt from the stack



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

def pop_interrupt
  @interrupts.pop
end

#push(new_scope = {}) ⇒ Object

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



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

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.



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

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


132
133
134
135
136
137
# File 'lib/liquid/context.rb', line 132

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

#tag_disabled?(tag_name) ⇒ Boolean

Returns:

  • (Boolean)


235
236
237
# File 'lib/liquid/context.rb', line 235

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

#with_disabled_tags(tag_names) ⇒ Object



224
225
226
227
228
229
230
231
232
233
# File 'lib/liquid/context.rb', line 224

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