class Rote::Page
A Page
object represents an individual page in the final documentation set, bringing together a source template, optional page code (in Ruby) obtained from various sources (see below), and an optional layout template (with it’s own code) to produce rendered output as a String
. Specifically, Page
provides the following capabilities:
-
Read template files containing ERB code and render them to create output.
-
Locate and evaluate all common and page code in the binding of the
Page
instance itself. -
Apply layout to rendered content using multiple render passes.
-
Apply user-supplied filters to the output to allow transformations and additional processing.
In normal use the instantiation and initialization of Pages will be handled internally by Rote
. From the user point of view most interaction with Rote
from user code takes place via the instance methods of this class.
Template lookup and evaluation¶ ↑
Each Page
instance is provided at instantiation with base paths from which it should resolve both template and layout files when required. Usually these paths are supplied by the Rake
task configuration. The attributes that provide information on template and layout paths (e.g. template_name
, base_layout_name
, and so on) give those paths relative to the base_path
and layout_path
as appropriate.
Common, page and layout code evaluation¶ ↑
Code applied to a given page is found and evaluated in the following order:
-
A block supplied to the <%= section_link ‘extension mappings’, ‘extension mapping’ %> that matched this page (if any).
-
Any COMMON.rb files from the filesystem root down to this directory.
-
This page’s ruby code, basename.rb.
-
In the template itself (via ERB).
When a Page
instance is created, Rote
looks for these, and if found evaluates them, in order, in the Page
instance binding.
Additionally, when layout is used the following evaluation takes place *after rendering the template text* and can be used to make variables available for the layout pass(es), and apply nested layout:
-
This layout’s ruby code, layout_basename.rb.
-
In the layout itself (again, with ERB).
As mentioned, Page
instances serve as the context for page code execution - All user-supplied code (COMMON.rb, page and layout code, and ERB in the templates themselves) is executed in the binding of an instance of this class.
Layout¶ ↑
All pages support layout, which allow common template to be applied across several pages. This is handled via multiple render passes, with each layout responsible for including the previously rendered content (via ERB).
Layout templates include the content rendered by the page (or previous layout, see below) render pass using the instance variable @content_for_layout. This should be a familar pattern for those familiar with the Rails framework.
To apply layout to a page, the layout
method should be called, passing in the base-name (with extension if different from the page template). When issued from common or page code, multiple calls to this method will override any previous setting. It may be called again from layout code, however, in which case the output of the currently-rendering layout will be passed (via the @content_to_layout instance variable) to the specified layout. In this way, Rote
allows layouts to be nested to any level.
Filtering¶ ↑
The page_filter
and post_filter
methods allow filters to be applied to a page. Filters
can be used to support any kind of textual transformation, macro expansion (page filters), or post-render processing (post filters). Rote
includes a number of filters as standard, supporting plain-text markup, syntax highlighting, HTMLTidy postprocessing, and more.
See Rote::Filters
for details of standard filters and their individual use.
Filters
are written in Ruby, and Rote
provides base-classes from which filters can be derived with just a few lines of code (See Rote::Filters::TextFilter
and Rote::Filters::MacroFilter
). Additionally, the page and post filter methods allow text filters to be created from a supplied block.
Rendering¶ ↑
Rendering occurs only once for a given page object, when the render
method is first called. Once a page has been rendered, the instance it is frozen to prevent further modification, and the rendered output is cached. Future calls to render
will return the cached output.
Attributes
The base path for template resolution.
The base path for layout resolution
The text of the layout to use for this page. This is read in when (if) the page source calls layout(basename).
Deprecated This has no knowledge of nested layout, and operates only on the innermost layout.
The array of page filters (applied to this page during the first render pass, before layout is applied). You can use page_filter
to add new page filters, which gives implicit block => Filters::TextFilter
conversion and checks for nil.
The array of post filters (applied to this page output after layout is applied). You can use post_filter
to add new post filters, which gives implicit block => Filters::TextFilter
conversion and checks for nil.
The basename from which this page’s template was read, relative to the base_path
.
The text of the template to use for this page.
Public Class Methods
Reads the template, and evaluates the global and page scripts, if available, using the current binding. You may define any instance variables or methods you like in that code for use in the template, as well as accessing the predefined @template and @template_text variables.
If specified, the layout path will be used to find layouts referenced from templates.
If a block is supplied, it is executed before the global / page code. This will be the block supplied by the file-extension mapping.
# File lib/rote/page.rb 197 def initialize(template_name, pages_dir = '.', layout_dir = pages_dir) 198 @template_text = nil 199 @template_name = nil 200 @layout_names = [] 201 @content_for_layout = nil 202 @result = nil 203 @layout_defext = File.extname(template_name) 204 @layout_path = layout_dir[STRIP_SLASHES,1] 205 @layout_text = nil 206 @base_path = pages_dir[STRIP_SLASHES,1] 207 208 @page_filters, @post_filters = [], [] 209 210 # read in the template. Layout _may_ get configured later in page code 211 # We only add the pages_dir if it's not already there, because it's 212 # easier to pass the whole relative fn from rake... 213 # template_name always needs with no prefix. 214 tfn = template_name 215 read_template(tfn) 216 217 # Yield to the (extension mapping) block 218 yield self if block_given? 219 220 # Eval COMMON.rb's 221 eval_common_rubys 222 223 # get script filenames, and eval them if found 224 tfn = ruby_filename # nil if no file 225 instance_eval(File.read(tfn),tfn) if tfn 226 end
Public Instance Methods
Returns the full filename of the first queued layout. This is the innermost layout, usually specified by the page itself.
# File lib/rote/page.rb 236 def base_layout_filename 237 layout_fn(layout_name) 238 end
The filename of the innermost layout, usually specified by the page itself, relative to the layout_path
. This method should not be used from COMMON.rb since its behaviour is undefined until all page code is evaluated and the final base_layout is known.
# File lib/rote/page.rb 165 def base_layout_name; layout_names.first; end
Sets the page’s base-layout as specified, or applies nested layout if called during a layout render pass. The specified basename should be the name of the layout file relative to the layout_path
. If the layout has the same extension as the page source template, it may be omitted.
The layout is not read by this method. It, and it’s source, are loaded only at rendering time. This prevents multiple calls by various scoped COMMON code, for example, from making a mess in the Page
binding.
This can only be called before the first call to render
returns it’s result. After that the Page
instance is frozen.
# File lib/rote/page.rb 296 def layout(basename) 297 if basename 298 self.layout_names << "#{basename}#{@layout_defext if File.extname(basename).empty?}" 299 end 300 end
Append filter
to this page’s page-filter chain, or create a new Rote::Filters::TextFilter
with the supplied block. This method should be preferred over direct manipulation of the filters
array if you are simply building a chain.
# File lib/rote/page.rb 252 def page_filter(filter = nil, &block) 253 if filter 254 page_filters << filter 255 else 256 if block 257 page_filters << Filters::TextFilter.new(&block) 258 end 259 end 260 end
Append filter
to this page’s post-filter chain. Behaviour is much the same as append_page_filter
.
# File lib/rote/page.rb 264 def post_filter(filter = nil, &block) 265 if filter 266 post_filters << filter 267 else 268 if block 269 post_filters << Filters::TextFilter.new(&block) 270 end 271 end 272 end
Render this page’s textile and ERB, and apply layout. This is only done once - after that, it’s cached for next time. You can also circumvent rendering by setting @result yourself in your page’s ruby.
# File lib/rote/page.rb 277 def render 278 @result or do_render! # sets up result for next time... 279 end
Returns the full filename of this Page’s ruby source. If no source is found for this page (not including common source) this returns nil
.
# File lib/rote/page.rb 243 def ruby_filename 244 fn = Page::page_ruby_filename(template_filename) 245 File.exists?(fn) ? fn : nil 246 end
Returns the full filename of this Page’s template. This is obtained by joining the base path with template name.
# File lib/rote/page.rb 230 def template_filename 231 File.join(base_path,template_name) if template_name 232 end
Private Instance Methods
render, set up @result for next time. Return result too.
# File lib/rote/page.rb 357 def do_render! 358 # Render the page content into the @content_for_layout 359 unless @template_text.nil? 360 # default render_fmt does nothing - different page formats may redefine it. 361 erb = ERB.new(@template_text) 362 erb.filename = template_filename 363 @content_for_layout = render_page_filters( erb.result(binding) ) 364 end 365 366 # FIXME: Quick fix for incorrect COMMON.rb layout nesting. 367 # All we do here is reset the layout to be the last layout 368 # added. 369 # 370 # If it turns out that the ability to nest from COMMON/page 371 # really is useless, we should remove the layout queue entirely, 372 # and then just have the render layout loop run until 373 # layout at end == layout at start. 374 @layout_names = [@layout_names.last] unless layout_names.empty? 375 376 # Do layout _after_ page eval. As we go through this, the layouts 377 # we load may add to the end of the layout names array, so nested 378 # layout is supported by just chasing the end of the array until 379 # it's empty. The process is basically 380 # 381 # Page is loaded, calls 'layout' with it's layout. 382 # During render, that fn is taken, and loaded. Layout code 383 # again calls 'layout'. 384 # On next loop iteration, that new filename is loaded, and it's 385 # code is executed ... and so on. 386 # 387 # Each loop puts the result into @content_for_layout, so that 388 # nested layouts can work just the same as regular. 389 @layout_names.each do |fn| 390 txt = load_layout(fn) 391 392 @layout_text ||= txt # layout_text legacy support vv0.3.2 v-0.4 393 394 # render into the layout if supplied. 395 if txt 396 erb = ERB.new(txt) 397 erb.filename = fn 398 @content_for_layout = erb.result(binding) 399 end 400 end 401 402 @result = render_post_filters(@content_for_layout) 403 freeze 404 405 @result 406 end
Find and evaluate all COMMON.rb files from page dir up to FS root.
# File lib/rote/page.rb 413 def eval_common_rubys 414 common_rbs = Page::resolve_common_rubys(File.expand_path(File.dirname(template_filename))) 415 common_rbs.each { |fn| instance_eval(File.read(fn),fn) } 416 417 true 418 end
# File lib/rote/page.rb 408 def inherit_common # inherit_common is implicit now vv0.2.99 v-0.4 409 warn "Warning: inherit_common is deprecated (inheritance is now implicit)" 410 end
# File lib/rote/page.rb 325 def layout_fn(fn) 326 File.join(layout_path,fn) if fn 327 end
Loads the layout. This method evaluates the layout code and returns it’s text. The layout (and code if found) are also registered as cached deps.
# File lib/rote/page.rb 332 def load_layout(fn) 333 if fn = layout_fn(fn) 334 raise "Layout #{fn} not found" unless File.exists?(fn) 335 336 # layout code 337 cfn = Page::page_ruby_filename(fn) 338 if File.exists?(cfn) 339 instance_eval(File.read(cfn), cfn) 340 Rake.register_dependency(cfn) 341 end 342 343 Rake.register_dependency(fn) 344 File.read(fn) 345 end 346 end
Sets the template from the specified file, or clears the template if nil
is passed in. The specified basename should be the name of the layout file relative to the layout_dir
, with no extension.
# File lib/rote/page.rb 307 def read_template(fn) 308 if fn 309 # if it's already a path that includes the pages path, strip 310 # that to get the name. 311 if fn =~ /#{base_path}/ 312 @template_name = fn[/^#{base_path}\/(.*)/,1] 313 else 314 @template_name = fn 315 end 316 317 raise "Template #{fn} not found" unless File.exists?(template_filename) 318 @template_text = File.read(template_filename) 319 else 320 @template_name = nil 321 @template_text = nil 322 end 323 end
# File lib/rote/page.rb 348 def render_page_filters(text) 349 page_filters.inject(text) { |s, f| f.filter(s, self) } 350 end
# File lib/rote/page.rb 352 def render_post_filters(text) 353 post_filters.inject(text) { |s, f| f.filter(s, self) } 354 end