Class: Webby::Renderer

Inherits:
Object
  • Object
show all
Includes:
ERB::Util
Defined in:
lib/webby/renderer.rb

Overview

The Webby::Renderer is used to filter and layout the text found in the resource page files in the content directory.

A page is filtered based on the settings of the ‘filter’ option in the page’s meta-data information. For example, if ‘textile’ is specified as a filter, then the page will be run through the RedCloth markup filter. More than one filter can be used on a page; they will be run in the order specified in the meta-data.

A page is rendered into a layout specified by the ‘layout’ option in the page’s meta-data information.

Constant Summary collapse

@@stack =

:stopdoc:

[]

Constants included from ERB::Util

ERB::Util::HTML_ESCAPE

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from ERB::Util

#html_escape

Constructor Details

#initialize(page) ⇒ Renderer

call-seq:

Renderer.new( page )

Create a new renderer for the given page. The renderer will apply the desired filters to the page (from the page’s meta-data) and then render the filtered page into the desired layout.



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/webby/renderer.rb', line 61

def initialize( page )
  unless page.instance_of? Resources::Page
    raise ArgumentError,
          "only page resources can be rendered '#{page.path}'"
  end

  @page = page
  @pages = Resources.pages
  @partials = Resources.partials
  @content = nil
  @config = ::Webby.site

  @_bindings = []
  @_content_for = {}
  @logger = Logging::Logger[self]
end

Instance Attribute Details

#loggerObject (readonly)

Returns the value of attribute logger.



52
53
54
# File 'lib/webby/renderer.rb', line 52

def logger
  @logger
end

Class Method Details

.write(page) ⇒ Object

call-seq:

Renderer.write( page )

Render the given page and write the resulting output to the page’s destination. If the page uses pagination, then multiple destination files will be created – one for each paginated data set in the page.



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/webby/renderer.rb', line 35

def self.write( page )
  renderer = self.new(page)

  loop {
    dest = page.destination
    FileUtils.mkdir_p ::File.dirname(dest)
    journal.create_or_update(page)

    text = renderer._layout_page
    unless text.nil?
      ::File.open(dest, 'w') {|fd| fd.write(text)}
    end

    break unless renderer._next_page
  }
end

Instance Method Details

#_bindingObject

Returns the binding in the scope of this Renderer object.



381
# File 'lib/webby/renderer.rb', line 381

def _binding() binding end

#_configure_locals(locals) ⇒ Object

call-seq:

_configure_locals( locals )

Configure local variables in the scope of the current binding returned by the get_binding method. The locals should be given as a hash of name / value pairs.



328
329
330
331
332
333
334
335
336
# File 'lib/webby/renderer.rb', line 328

def _configure_locals( locals )
  return if locals.nil?

  locals.each do |k,v|
    Thread.current[:value] = v
    definition = "#{k} = Thread.current[:value]"
    eval(definition, get_binding)
  end
end

#_find_partial(part) ⇒ Object

Attempts to locate a partial by name. If only the partial name is given, then the current directory of the page being rendered is searched for the partial. If a full path is given, then the partial is searched for in that directory.

Raies a Webby::Error if the partial could not be found.



345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
# File 'lib/webby/renderer.rb', line 345

def _find_partial( part )
  case part
  when String
    part_dir = ::File.dirname(part)
    part_dir = @page.dir if part_dir == '.'

    part_fn = ::File.basename(part)
    part_fn = '_' + part_fn unless part_fn =~ %r/^_/

    p = Resources.partials.find(
        :filename => part_fn, :in_directory => part_dir ) rescue nil
    raise ::Webby::Error, "could not find partial '#{part}'" if p.nil?
    p
  when ::Webby::Resources::Partial
    part
  else raise ::Webby::Error, "expecting a partial or a partial name" end
end

#_guard(str) ⇒ Object

This method will put filter guards around the given input string. This will protect the string from being processed by any remaining filters (specifically the textile filter).

The string is returned unchanged if there are no remaining filters to guard against.



370
371
372
373
374
375
376
377
# File 'lib/webby/renderer.rb', line 370

def _guard( str )
  return str unless @_cursor

  if @_cursor.remaining_filters.include? 'textile'
    str = "<notextile>\n%s\n</notextile>" % str
  end
  str
end

#_layout_pageObject

call-seq:

_layout_page    => string

Apply the desired filters to the page and then render the filtered page into the desired layout. The filters to apply to the page are determined from the page’s meta-data. The layout to use is also determined from the page’s meta-data.



228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
# File 'lib/webby/renderer.rb', line 228

def _layout_page
  @content = _render_page

  _track_rendering(@page.path) {
    _render_layout_for(@page)
  }
  raise ::Webby::Error, "rendering stack corrupted" unless @@stack.empty?

  @content
rescue ::Webby::Error => err
  logger.error "while rendering page '#{@page.path}'"
  logger.error err.message
  return nil
rescue => err
  logger.error "while rendering page '#{@page.path}'"
  logger.fatal err
  exit 1
ensure
  @content = nil
  @@stack.clear
end

#_next_pageObject

call-seq:

_next_page    => true or false

Returns true if there is a next page to render. Returns false if there is no next page or if pagination has not been configured for the current page.



274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
# File 'lib/webby/renderer.rb', line 274

def _next_page
  return false unless defined? @pager and @pager

  # go to the next page; break out if there is no next page
  if @pager.next?
    @pager = @pager.next
    @_content_for.clear
    @_bindings.clear
  else
    @pager.pager.reset
    @pager = nil
    return false
  end

  true
end

#_render_layout_for(res) ⇒ Object

call-seq:

_render_layout_for( resource )

Render the layout for the given resource. If the resource does not have a layout, then this method returns immediately.



256
257
258
259
260
261
262
263
264
265
# File 'lib/webby/renderer.rb', line 256

def _render_layout_for( res )
  return unless res.layout
  lyt = Resources.find_layout(res.layout)
  return if lyt.nil?

  _track_rendering(lyt.path) {
    @content = Filters.process(self, lyt, lyt._read)
    _render_layout_for(lyt)
  }
end

#_render_pageObject

call-seq:

_render_page    => string

Apply the desired filters to the page. The filters to apply are determined from the page’s meta-data.



200
201
202
203
204
# File 'lib/webby/renderer.rb', line 200

def _render_page
  _track_rendering(@page.path) {
    Filters.process(self, @page, @page._read)
  }
end

#_render_partial(part, opts = {}) ⇒ Object

call-seq:

_render_partial( partial, :locals => {} )    => string

Render the given partial into the current page. The :locals are a hash of key / value pairs that will be set as local variables in the scope of the partial when it is rendered.



213
214
215
216
217
218
# File 'lib/webby/renderer.rb', line 213

def _render_partial( part, opts = {} )
  _track_rendering(part.path) {
    _configure_locals(opts[:locals])
    Filters.process(self, part, part._read)
  }
end

#_track_rendering(path) ⇒ Object

call-seq:

_track_rendering( path ) {block}

Keep track of the page rendering for the given path. The block is where the the page will be rendered.

This method keeps a stack of the current pages being rendeered. It looks for duplicates in the stack – an indication of a rendering loop. When a rendering loop is detected, an error is raised.

This method returns whatever is returned from the block.



303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
# File 'lib/webby/renderer.rb', line 303

def _track_rendering( path )
  loop_error = @@stack.include? path
  @@stack << path
  @_bindings << _binding

  if loop_error
    msg = "rendering loop detected for '#{path}'\n"
    msg << "    current rendering stack\n\t"
    msg << @@stack.join("\n\t")
    raise ::Webby::Error, msg
  end

  yield
ensure
  @@stack.pop if path == @@stack.last
  @_bindings.pop
end

#get_bindingObject

call-seq:

get_binding    => binding

Returns the current binding for the renderer.



190
191
192
# File 'lib/webby/renderer.rb', line 190

def get_binding
  @_bindings.last
end

#paginate(items, count, &block) ⇒ Object

call-seq:

paginate( items, per_page ) {|item| block}

Iterate the given block for each item selected from the items array using the given number of items per_page. The first time the page is rendered, the items passed to the block are selected using the range (0…per_page). The next rendering selects (per_page…2*per_page). This continues until all items have been paginated.

Calling this method creates a @pager object that can be accessed from the page. The @pager contains information about the next page, the current page number, the previous page, and the number of items in the current page.



177
178
179
180
181
182
183
# File 'lib/webby/renderer.rb', line 177

def paginate( items, count, &block )
  @pager ||= Paginator.new(items.length, count, @page) do |offset, per_page|
    items[offset,per_page]
  end.first

  @pager.each(&block)
end

#render(*args) ⇒ Object

call-seq:

render( resource = nil, opts = {} )    => string

Render the given resource (a page or a partial) and return the results as a string. If a resource is not given, then the options hash should contain the name of a partial to render (:partial => ‘name’).

When a partial name is given, the partial is found by looking in the directory of the current page being rendered. Otherwise, the full path to the partial can be given.

If a :guard option is given as true, then the resulting string will be protected from processing by subsequent filters. Currently this only protects against the textile filter.

When rendering partials, local variables can be passed to the partial by setting them in hash passed as the :locals option.

Options

:partial<String>

The partial to render

:locals<Hash>

Locals values to define when rendering a partial

:guard<Boolean>

Prevents the resulting string from being processed by subsequent filters (only textile for now)

Returns

A string that is the rendered page or partial.

Examples

# render the partial "foo" using the given local variables
render( :partial => "foo", :locals => {:bar => "value for bar"} )

# find another page and render it into this page and protect the
# resulting contents from further filters
page = @pages.find( :title => "Chicken Coop" )
render( page, :guard => true )

# find a partial and render it using the given local variables
partial = @partials.find( :filename => "foo", :in_directory => "/path" )
render( partial, :locals => {:baz => "baztastic"} )


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

def render( *args )
  opts = Hash === args.last ? args.pop : {}
  resource = args.first
  resource = _find_partial(opts[:partial]) if resource.nil?

  str = case resource
    when Resources::Page
      ::Webby::Renderer.new(resource)._render_page
    when Resources::Partial
      _render_partial(resource, opts)
    when Resources::Static
      resource._read
    else
      raise ::Webby::Error, "expecting a page or a partial but got '#{resource.class.name}'"
    end

  str = _guard(str) if opts[:guard]
  str
end

#render_pageObject

call-seq:

render_page    => string

This method is being deprecated. It is being made internal to the framework and really shouldn’t be used anymore.



147
148
149
150
# File 'lib/webby/renderer.rb', line 147

def render_page
  Webby.deprecated "render_page", "this method is being made internal to the framework"
  _render_page
end

#render_partial(part, opts = {}) ⇒ Object

call-seq:

render_partial( partial, :locals => {} )    => string

This method is being deprecated. Please use the render method instead.



157
158
159
160
161
# File 'lib/webby/renderer.rb', line 157

def render_partial( part, opts = {} )
  Webby.deprecated "render_partial", "it is being replaced by the Renderer#render() method"
  opts[:partial] = part
  render opts
end