Class: FlavourSaver::Runtime

Inherits:
Object
  • Object
show all
Defined in:
lib/flavour_saver/runtime.rb

Defined Under Namespace

Classes: BlockRuntime

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(ast, context = nil, locals = {}, helpers = [], parent = nil) ⇒ Runtime

Returns a new instance of Runtime.



18
19
20
21
22
23
24
25
# File 'lib/flavour_saver/runtime.rb', line 18

def initialize(ast, context=nil, locals={}, helpers=[],parent=nil)
  @ast = ast
  @locals = locals
  @helpers = helpers
  @context = context
  @parent = parent
  @privates = parent ? parent.privates.dup : { 'root' => context }
end

Instance Attribute Details

#astObject

Returns the value of attribute ast.



12
13
14
# File 'lib/flavour_saver/runtime.rb', line 12

def ast
  @ast
end

#contextObject

Returns the value of attribute context.



12
13
14
# File 'lib/flavour_saver/runtime.rb', line 12

def context
  @context
end

#parentObject

Returns the value of attribute parent.



12
13
14
# File 'lib/flavour_saver/runtime.rb', line 12

def parent
  @parent
end

#privatesObject

Returns the value of attribute privates.



12
13
14
# File 'lib/flavour_saver/runtime.rb', line 12

def privates
  @privates
end

Class Method Details

.run(ast, context, locals = {}, helpers = []) ⇒ Object



14
15
16
# File 'lib/flavour_saver/runtime.rb', line 14

def self.run(ast, context, locals={}, helpers=[])
  self.new(ast, context, locals, helpers).to_s
end

Instance Method Details

#create_child_runtime(body = []) ⇒ Object



204
205
206
207
# File 'lib/flavour_saver/runtime.rb', line 204

def create_child_runtime(body=[])
  node = body.is_a?(TemplateNode) ? body : TemplateNode.new(body)
  Runtime.new(node,nil,@locals,@helpers,self)
end

#evaluate_argument(arg) ⇒ Object



142
143
144
145
146
147
148
# File 'lib/flavour_saver/runtime.rb', line 142

def evaluate_argument(arg)
  if arg.is_a? Array
    evaluate_object_path(arg)
  else
    evaluate_node(arg)
  end
end

#evaluate_block(node, block_context = @context) ⇒ Object



160
161
162
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
191
192
193
194
195
196
197
198
199
200
201
202
# File 'lib/flavour_saver/runtime.rb', line 160

def evaluate_block(node,block_context=@context)
  call = node.method.first
  content_runtime = create_child_runtime(node.contents)
  alternate_runtime = create_child_runtime(node.alternate) if node.respond_to? :alternate
  block_runtime = BlockRuntime.new(block_context,content_runtime,alternate_runtime)

  result = evaluate_call(call, block_context) { block_runtime }

  # If the helper fails to call it's provided block then all
  # sorts of wacky default behaviour kicks in. I don't like it,
  # but that's the spec.
  if !block_runtime.rendered?

    # If the result is collectiony then act as an implicit
    # "each"
    if result && result.respond_to?(:each)
      if result.respond_to?(:size) && (result.size > 0)
        r = []
        # Not using #each_with_index because the object might
        # not actually be an Enumerable
        count = 0
        result.each do |e|
          r << block_runtime.contents(e, {'index' => count})
          count += 1
        end
        result = r.join('')
      else
        result = block_runtime.inverse
      end

    # Otherwise it behaves as an implicit "if"
    elsif result
      result = block_runtime.contents result
    else
      if block_runtime.has_inverse?
        result = block_runtime.inverse
      else
        result = ''
      end
    end
  end
  result
end

#evaluate_call(call, context = self.context, &block) ⇒ Object



119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/flavour_saver/runtime.rb', line 119

def evaluate_call(call, context=self.context, &block)
  context = Helpers.decorate_with(context,@helpers,@locals) unless context.is_a? Helpers::Decorator
  case call
  when ParentCallNode
    depth = call.depth
    (2..depth).inject(parent) { |p| p.parent }.evaluate_call(call.to_callnode,&block)
  when LiteralCallNode
    result = context.send(:[], call.name)
    result = result.call(*call.arguments.map { |a| evaluate_argument(a) },&block) if result.respond_to? :call
    result
  when LocalVarNode
    result = private_variable_get(call.name)
  else
    if forbidden_method? context, call.name
      raise ForbiddenMethodException, "Refusing to call #{call.name.inspect} from a template: it isn't a helper, a local, or part of the template context's own API. Register a helper if you meant to expose it, or use #{"{{[#{call.name}]}}"} to read a data field of that name."
    end
    if call.parent.is_a? BlockExpressionNode and !context.respond_to? call.name
      raise UnknownHelperException, "Template context doesn't respond to method #{call.name.inspect}."
    end
    context.public_send(call.name, *call.arguments.map { |a| evaluate_argument(a) }, &block)
  end
end

#evaluate_expression(node, &block) ⇒ Object



156
157
158
# File 'lib/flavour_saver/runtime.rb', line 156

def evaluate_expression(node, &block)
  evaluate_object_path(node.method)
end

#evaluate_node(node) ⇒ Object



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/flavour_saver/runtime.rb', line 59

def evaluate_node(node)
  case node
  when TemplateNode
    node.items.map { |n| evaluate_node(n) }.join('')
  when BlockExpressionNode
    evaluate_block(node).to_s
  when OutputNode
    node.value
  when NumberNode
    if node.value =~ /\./
      node.value.to_f
    else
      node.value.to_i
    end
  when ValueNode
    node.value
  when SafeExpressionNode
    evaluate_expression(node).to_s
  when ExpressionNode
    escape(evaluate_expression(node).to_s)
  when CallNode
    evaluate_call(node)
  when Hash
    node.each do |key,value|
      node[key] = evaluate_argument(value)
    end
    node
  when CommentNode
    ''
  when PartialNode
    evaluate_partial(node)
  else
    raise UnknownNodeTypeException, "Don't know how to deal with a node of type #{node.class.to_s.inspect}."
  end
end

#evaluate_object_path(path, &block) ⇒ Object



150
151
152
153
154
# File 'lib/flavour_saver/runtime.rb', line 150

def evaluate_object_path(path, &block)
  path.inject(context) do |context,call|
    context = evaluate_call(call, context, &block)
  end
end

#evaluate_partial(node) ⇒ Object



104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/flavour_saver/runtime.rb', line 104

def evaluate_partial(node)
  _context = context
  _context = evaluate_argument(node.context) if node.context
  if defined?(::Rails)
    context.send(:render, :partial => node.name, :object => _context)
  else
    partial = Partial.fetch(node.name)
    if partial.respond_to? :call
      partial.call(_context)
    else
      create_child_runtime(partial).to_s(_context)
    end
  end
end

#inspectObject



209
210
211
# File 'lib/flavour_saver/runtime.rb', line 209

def inspect
  "#<FlavourSaver::Runtime contents=#{@ast.inspect}>"
end

#parent?Boolean

Returns:

  • (Boolean)


100
101
102
# File 'lib/flavour_saver/runtime.rb', line 100

def parent?
  !!@parent
end

#private_variable_get(name) ⇒ Object



47
48
49
50
51
52
53
# File 'lib/flavour_saver/runtime.rb', line 47

def private_variable_get(name)
  begin
    @privates.fetch(name)
  rescue KeyError
    raise UndefinedPrivateVariableException, "private variable not found @#{name}"
  end
end

#private_variable_set(name, value) ⇒ Object



43
44
45
# File 'lib/flavour_saver/runtime.rb', line 43

def private_variable_set(name,value)
  @privates[name.to_s] = value
end

#strip(tmp_context = nil) ⇒ Object



55
56
57
# File 'lib/flavour_saver/runtime.rb', line 55

def strip(tmp_context = nil)
  self.to_s(tmp_context).gsub(/[\s]+/,' ').strip
end

#to_s(tmp_context = nil, privates = {}) ⇒ Object



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/flavour_saver/runtime.rb', line 27

def to_s(tmp_context = nil,privates={})
  result = nil
  if tmp_context
    old_context = @context
    @context = tmp_context
    old_privates = @privates
    @privates = @privates.dup.merge(privates) if privates.any?
    result = evaluate_node(@ast)
    @privates = old_privates
    @context = old_context
  else
    result = evaluate_node(@ast).to_s.force_encoding(Encoding::default_external)
  end
  result
end