Class: Nyara::View

Inherits:
Object show all
Defined in:
lib/nyara/view.rb,
lib/nyara/view_handlers/erb.rb,
lib/nyara/view_handlers/slim.rb,
lib/nyara/view_handlers/haml.rb,
lib/nyara/view_handlers/erubis.rb

Overview

A support class which provides:

  • layout / locals for rendering
  • template search
  • template default content-type mapping
  • template precompile
  • streaming

Streaming is implemented in this way: when +Fiber.yield+ is called, we flush +View#out+ and send the data. This adds a bit limitations to the layouts. Consider this case (+friend+ fills into View#out, while +enemy+ doesn't):

friend_layout { enemy_layout { friend_page } }

Friend layout and friend page shares one buffer, but enemy layout just concats +buffer.join+ before we flush friend layout. So the simple solution is: templates other than stream-friendly ones are not allowed to be a layout.

Defined Under Namespace

Modules: ERB, Haml, Renderable Classes: Buffer, Erubis, Slim, TiltRenderable

Constant Summary collapse

ENGINE_DEFAULT_CONTENT_TYPES =

Path extension (without dot) => most preferrable content type (e.g. "text/html")

ParamHash.new
ENGINE_STREAM_FRIENDLY =

Path extension (without dot) => stream friendly

ParamHash.new
RENDER =

meth name => method obj

{}
LAYOUT =

nested level => layout render, 0 means no layout

{}

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(instance, view_path, layout, locals, opts) ⇒ View

If view_path not given, find template source in opts


275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
# File 'lib/nyara/view.rb', line 275

def initialize instance, view_path, layout, locals, opts
  locals ||= {}
  if view_path
    raise ArgumentError, "unkown options: #{opts.inspect}" unless opts.empty?
    meth, ext = View.template(view_path, locals)

    unless @deduced_content_type = ENGINE_DEFAULT_CONTENT_TYPES[ext]
      raise ArgumentError, "unkown template engine: #{ext.inspect}"
    end
  else
    raise ArgumentError, "too many options, expected only 1: #{opts.inspect}" if opts.size > 1
    ext, @in = opts.first

    unless @deduced_content_type = ENGINE_DEFAULT_CONTENT_TYPES[ext]
      raise ArgumentError, "unkown template engine: #{ext.inspect}"
    end

    meth = RENDER[View.engine2meth(ext)]
  end

  @args = [meth.bind(instance)]
  unless layout.is_a?(Array)
    layout = layout ? [layout] : []
  end
  layout.each do |l|
    pair = View.template(l)
    # see notes on View
    raise "can not use #{pair[1]} as layout" unless ENGINE_STREAM_FRIENDLY[pair[1]]
    @args << pair.first.bind(instance)
  end

  @layout_render = (LAYOUT[@args.size] ||= Renderable.make_layout_method(@args.size - 1))
  @args << locals

  @instance = instance
  @instance.instance_variable_set :@_nyara_view, self
  @out = Buffer.new
end

Class Attribute Details

.rootObject (readonly)

Returns the value of attribute root


132
133
134
# File 'lib/nyara/view.rb', line 132

def root
  @root
end

Instance Attribute Details

#deduced_content_typeObject (readonly)

Returns the value of attribute deduced_content_type


313
314
315
# File 'lib/nyara/view.rb', line 313

def deduced_content_type
  @deduced_content_type
end

#inObject (readonly)

Returns the value of attribute in


313
314
315
# File 'lib/nyara/view.rb', line 313

def in
  @in
end

#outObject (readonly)

Returns the value of attribute out


313
314
315
# File 'lib/nyara/view.rb', line 313

def out
  @out
end

Class Method Details

.engine2meth(engine) ⇒ Object


254
255
256
# File 'lib/nyara/view.rb', line 254

def engine2meth engine
  ":#{engine}"
end

.initObject


121
122
123
124
125
126
127
128
129
130
131
# File 'lib/nyara/view.rb', line 121

def init
  RENDER.delete_if{|k, v| k.start_with?('!') }
  @root = Config['views']
  @meth2ext = {} # meth => ext (without dot)
  @meth2sig = {}
  @ext_list = Tilt.mappings.keys.delete_if(&:empty?).join ','
  if @ext_list !~ /\bslim\b/
    @ext_list = "slim,#{@ext_list}"
  end
  @ext_list = "{#{@ext_list}}"
end

.on_modified(path) ⇒ Object

NOTE: path needs extension

Returns

dot_ext for further use


157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/nyara/view.rb', line 157

def on_modified path
  meth = path2meth path
  return unless @meth2sig[meth] # has not been searched before, see also View.template

  ext = File.extname(path)[1..-1]
  return unless ext
  src = precompile ext do
    Dir.chdir(@root){ File.read path, encoding: 'utf-8' }
  end

  if src
    sig = @meth2sig[meth].map{|k| "#{k}: nil" }.join ','
    sig = '_={}' if sig.empty?
    sig = "(#{sig})" # 2.0.0-p0 requirement
    RENDER[meth] = Renderable.make_render_method path, 0, sig, src
  else
    t = Dir.chdir @root do
      Tilt.new path
    end
    # partly precompiled
    RENDER[meth] = TiltRenderable.new t
  end

  @meth2ext[meth] = ext
end

.on_removed(path) ⇒ Object

NOTE: path needs extension


135
136
137
138
139
140
141
# File 'lib/nyara/view.rb', line 135

def on_removed path
  meth = path2meth path
  RENDER.delete meth

  @meth2ext.delete meth
  @meth2sig.delete meth
end

.on_removed_allObject


143
144
145
146
147
148
149
150
# File 'lib/nyara/view.rb', line 143

def on_removed_all
  meths = @meth2sig
  meths.each do |meth, _|
    RENDER.delete meth
  end
  @meth2sig.clear
  @meth2ext.clear
end

.path2meth(path) ⇒ Object


250
251
252
# File 'lib/nyara/view.rb', line 250

def path2meth path
  "!#{path}"
end

.precompile(ext) ⇒ Object

Block is lazy invoked when it's ok to read the template source.


235
236
237
238
239
240
241
242
243
244
245
246
247
248
# File 'lib/nyara/view.rb', line 235

def precompile ext
  case ext
  when 'slim'
    Slim.src yield
  when 'erb', 'rhtml'
    if Config['prefer_erb']
      ERB.src yield
    else
      Erubis.src yield
    end
  when 'haml'
    Haml.src yield
  end
end

.register_engine(ext, default_content_type, stream_friendly = false) ⇒ Object

Define inline render method and add Content-Type mapping


184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/nyara/view.rb', line 184

def register_engine ext, default_content_type, stream_friendly=false
  # todo figure out fname and line
  meth = engine2meth ext
  file = "file".inspect
  line = 1

  if stream_friendly
    RENDER[meth] = Renderable.make_render_method __FILE__, __LINE__, '(locals={})', <<-RUBY
      @_nyara_locals = locals
      src = locals.map{|k, _| "\#{k} = @_nyara_locals[:\#{k}];" }.join
      src << View.precompile(#{ext.inspect}){ @_nyara_view.in }
      instance_eval src, #{file}, #{line}
    RUBY
    ENGINE_STREAM_FRIENDLY[ext] = true
  else
    RENDER[meth] = Renderable.make_render_method __FILE__, __LINE__, '(locals=nil)', <<-RUBY
      Tilt[#{ext.inspect}].new(#{file}, #{line}){ @_nyara_view.in }.render self, locals
    RUBY
  end
  ENGINE_DEFAULT_CONTENT_TYPES[ext] = default_content_type
end

.template(path, locals = {}) ⇒ Object

Local keys are for first-time code generation, values not used

Returns

[meth_obj, ext_without_dot]


211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
# File 'lib/nyara/view.rb', line 211

def template path, locals={}
  if File.extname(path).empty?
    Dir.chdir @root do
      paths = Dir.glob("#{path}.{#@ext_list}")
      if paths.size > 1
        raise ArgumentError, "more than 1 matching views: #{paths.inspect}, add file extension to distinguish them"
      end
      path = paths.first
    end
  end

  meth = path2meth path
  ext = @meth2ext[meth]
  return [RENDER[meth], ext] if ext

  @meth2sig[meth] = locals.keys
  ext = on_modified path if path
  raise "template not found or not valid in Tilt: #{path}" unless ext
  [RENDER[meth], ext]
end

Instance Method Details

#endObject


343
344
345
346
347
348
349
# File 'lib/nyara/view.rb', line 343

def end
  while @fiber.alive?
    resume
  end
  @instance.send_chunk @rest_result
  Fiber.yield :term_close
end

#partialObject


315
316
317
318
319
320
# File 'lib/nyara/view.rb', line 315

def partial
  @out = @out.push_level
  res = @layout_render.call *@args
  @out = @out.pop_level
  res
end

#renderObject


322
323
324
325
# File 'lib/nyara/view.rb', line 322

def render
  @instance.send_chunk @layout_render.call *@args
  Fiber.yield :term_close
end

#resumeObject


335
336
337
338
339
340
341
# File 'lib/nyara/view.rb', line 335

def resume
  r = @fiber.resume
  Fiber.yield r if r
  unless @out.empty?
    @out.flush @instance
  end
end

#streamObject


327
328
329
330
331
332
333
# File 'lib/nyara/view.rb', line 327

def stream
  @fiber = Fiber.new do
    @rest_result = @layout_render.call *@args
    nil
  end
  self
end