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
# 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       = {}

  @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



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

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



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

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?


178
179
180
# File 'lib/liquid/context.rb', line 178

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

#[]=(key, value) ⇒ Object

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



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

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



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

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

#apply_global_filter(obj) ⇒ Object



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

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

#clear_instance_assignsObject



161
162
163
# File 'lib/liquid/context.rb', line 161

def clear_instance_assigns
  @scopes[0] = {}
end

#evaluate(object) ⇒ Object



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

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



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

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



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

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)


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

def interrupt?
  !@interrupts.empty?
end

#invoke(method, *args) ⇒ Object



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

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

#key?(key) ⇒ Boolean

Returns:

  • (Boolean)


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

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

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



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

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



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

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.



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

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:



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

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



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

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.



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

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


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

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

#tag_disabled?(tag_name) ⇒ Boolean

Returns:

  • (Boolean)


237
238
239
# File 'lib/liquid/context.rb', line 237

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

#with_disabled_tags(tag_names) ⇒ Object



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

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