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:



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
61
62
63
# File 'lib/liquid/context.rb', line 27

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       = {}
  @expression_cache    = LruRedux::ThreadSafeCache.new(1000)

  # 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.



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

def environment
  @environment
end

#environmentsObject (readonly)

Returns the value of attribute environments.



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

def environments
  @environments
end

#errorsObject

Returns the value of attribute errors.



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

def errors
  @errors
end

#exception_rendererObject

Returns the value of attribute exception_renderer.



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

def exception_renderer
  @exception_renderer
end

#global_filterObject

Returns the value of attribute global_filter.



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

def global_filter
  @global_filter
end

#partialObject

Returns the value of attribute partial.



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

def partial
  @partial
end

#registersObject (readonly)

Returns the value of attribute registers.



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

def registers
  @registers
end

#resource_limitsObject (readonly)

Returns the value of attribute resource_limits.



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

def resource_limits
  @resource_limits
end

#scopesObject (readonly)

Returns the value of attribute scopes.



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

def scopes
  @scopes
end

#static_environmentsObject (readonly)

Returns the value of attribute static_environments.



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

def static_environments
  @static_environments
end

#static_registersObject (readonly)

Returns the value of attribute static_registers.



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

def static_registers
  @static_registers
end

#strainerObject



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

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

#strict_filtersObject

Returns the value of attribute strict_filters.



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

def strict_filters
  @strict_filters
end

#strict_variablesObject

Returns the value of attribute strict_variables.



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

def strict_variables
  @strict_variables
end

#template_nameObject

Returns the value of attribute template_name.



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

def template_name
  @template_name
end

#warningsObject

rubocop:enable Metrics/ParameterLists



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

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



23
24
25
# File 'lib/liquid/context.rb', line 23

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?


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

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

#[]=(key, value) ⇒ Object

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



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

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



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

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

#apply_global_filter(obj) ⇒ Object



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

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

#clear_instance_assignsObject



168
169
170
# File 'lib/liquid/context.rb', line 168

def clear_instance_assigns
  @scopes[0] = {}
end

#evaluate(object) ⇒ Object



193
194
195
# File 'lib/liquid/context.rb', line 193

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



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

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



103
104
105
106
107
108
109
# File 'lib/liquid/context.rb', line 103

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)


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

def interrupt?
  !@interrupts.empty?
end

#invoke(method, *args) ⇒ Object



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

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

#key?(key) ⇒ Boolean

Returns:

  • (Boolean)


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

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

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



219
220
221
222
223
224
225
226
227
228
229
230
231
# File 'lib/liquid/context.rb', line 219

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



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

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.



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

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:



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

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

#pop_interruptObject

pop an interrupt from the stack



99
100
101
# File 'lib/liquid/context.rb', line 99

def pop_interrupt
  @interrupts.pop
end

#push(new_scope = {}) ⇒ Object

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



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

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.



94
95
96
# File 'lib/liquid/context.rb', line 94

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


140
141
142
143
144
145
# File 'lib/liquid/context.rb', line 140

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

#tag_disabled?(tag_name) ⇒ Boolean

Returns:

  • (Boolean)


244
245
246
# File 'lib/liquid/context.rb', line 244

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

#with_disabled_tags(tag_names) ⇒ Object



233
234
235
236
237
238
239
240
241
242
# File 'lib/liquid/context.rb', line 233

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