Class: Arrow::Template
- Extended by:
- Forwardable
- Includes:
- HashUtilities
- Defined in:
- lib/arrow/template.rb,
lib/arrow/template/nodes.rb
Overview
The Arrow::Template class, instances of which are used to generate output for Arrow applications.
Synopsis
:TODO: Write some useful Arrow::Template examples
Authors
-
Michael Granger <[email protected]>
Please see the file LICENSE in the top-level directory for licensing details.
Defined Under Namespace
Modules: ConditionalDirective Classes: AttrDirective, AttributeDirective, BracketingDirective, CallDirective, CommentDirective, CommentNode, Container, Directive, ElseDirective, ElsifDirective, EscapeDirective, ExportDirective, ForDirective, IfDirective, ImportDirective, IncludeDirective, Iterator, Node, Parser, PrettyPrintDirective, RenderDirective, RenderingScope, SelectListDirective, SetDirective, TextNode, TimeDeltaDirective, URLEncodeDirective, UnlessDirective, YieldDirective
Constant Summary collapse
- DEFAULTS =
Configuration defaults. Valid members are the same as those listed for the
config
item of the #new method. { :parserClass => Arrow::Template::Parser, :elideDirectiveLines => true, :debuggingComments => false, :commentStart => '<!-- ', :commentEnd => ' -->', :strictAttributes => false, }
- DEFAULT_RENDERERS =
A Hash which specifies the default renderers for different classes of objects.
{ Arrow::Template => lambda {|subtempl,templ| subtempl.render( nil, nil, templ ) }, ::Object => :to_s, ::Array => lambda {|ary,tmpl| tmpl.render_objects(*ary) }, ::Hash => lambda {|hsh,tmpl| hsh.collect do |k,v| tmpl.render_objects(k, ": ", v) end }, ::Method => lambda {|meth,tmpl| tmpl.render_objects( meth.call ) }, ::Exception => lambda {|err,tmpl| tmpl.render_comment "%s: %s: %s" % [ err.class.name, err., err.backtrace ? err.backtrace[0] : "Stupid exception with no backtrace.", ] }, }
Constants included from HashUtilities
HashUtilities::HashMergeFunction
Class Attribute Summary collapse
-
.load_path ⇒ Object
Returns the value of attribute load_path.
Class Method Summary collapse
-
.attr_underbarred_accessor(sym) ⇒ Object
Create an attr_accessor method for the specified
sym
, but one which will look for instance variables with any leading underbars removed. -
.attr_underbarred_reader(sym) ⇒ Object
Create an attr_reader method for the specified
sym
, but one which will look for instance variables with any leading underbars removed. -
.find_file(file, path = []) ⇒ Object
Find the specified
file
in the givenpath
(or the Template class’s #load_path if not specified). -
.load(name, path = []) ⇒ Object
Load a template from a file.
Instance Method Summary collapse
-
#_enclosing_template ⇒ Object
Return the template that is enclosing the receiver in the current context, if any.
-
#changed? ⇒ Boolean
Returns
true
if the source file from which the template was read has been modified since the receiver was instantiated. -
#initialize(content = nil, config = {}) ⇒ Template
constructor
Create a new template object with the specified
content
(a String) andconfig
hash. -
#initialize_copy(original) ⇒ Object
Initialize a copy of the
original
template object. -
#inspect ⇒ Object
Return a human-readable representation of the template object.
-
#install_node(node) ⇒ Object
Install the given
node
into the template object. -
#install_syntax_tree(tree) ⇒ Object
Install a new syntax tree in the template object, replacing the old one, if any.
-
#make_rendering_scope ⇒ Object
Create an anonymous module to act as a scope for any evals that take place during a single render.
-
#memsize ⇒ Object
Return the approximate size of the template, in bytes.
-
#parse(source) ⇒ Object
Parse the given template source (a String) and put the resulting nodes into the template’s syntax_tree.
-
#postrender(enclosing_template = nil) ⇒ Object
(also: #after_rendering)
Clean up after template rendering, calling each of its nodes’ #after_rendering hook.
-
#postrender_done? ⇒ Boolean
Returns
true
if this template has already been through a post-render. -
#prerender(enclosing_template = nil) ⇒ Object
(also: #before_rendering)
Prep the template for rendering, calling each of its nodes’ #before_rendering hook.
-
#prerender_done? ⇒ Boolean
Returns
true
if this template has already been through a pre-render. -
#render(nodes = nil, scope = nil, enclosing_template = nil) ⇒ Object
(also: #to_s)
Render the template to text and return it as a String.
-
#render_comment(message) ⇒ Object
Render the given
message
as a comment as specified by the template configuration. -
#render_objects(*objs) ⇒ Object
Render the specified objects into text.
-
#with_overridden_attributes(scope, hash) ⇒ Object
Call the given
block
, overriding the contents of the template’s attributes and the definitions in the specifiedscope
with those from the pairs in the givenhash
.
Methods included from HashUtilities
stringify_keys, symbolify_keys
Methods inherited from Object
deprecate_class_method, deprecate_method, inherited
Constructor Details
#initialize(content = nil, config = {}) ⇒ Template
Create a new template object with the specified content
(a String) and config
hash. The config
can contain one or more of the following keys:
- :parserClass
-
The class object that will be instantiated to parse the template text into nodes. Defaults to Arrow::Template::Parser.
- :elideDirectiveLines
-
If set to a
true
value, lines of the template which contain only whitespace and one or more non-rendering directives will be discarded from the rendered output. - :debuggingComments
-
If set to a
true
value, nodes which are set up to do so will insert a comment with debugging information immediately before their rendered output. - :commentStart
-
The String which will be prepended to all comments rendered in the output. See #render_comment.
- :commentEnd
-
The String which will be appended to all comments rendered in the output. See #render_comment.
- :strictAttributes
-
If set to a
true
value, method calls which don’t match already-extant attributes will result in NameErrors. This isfalse
by default, which causes method calls to generate attributes with the same name.
299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 |
# File 'lib/arrow/template.rb', line 299 def initialize( content=nil, config={} ) @config = DEFAULTS.merge( config, &HashMergeFunction ) @renderers = DEFAULT_RENDERERS.dup @attributes = {} @syntax_tree = [] @source = content @file = nil @creation_time = Time.now @load_path = self.class.load_path.dup @prerender_done = false @postrender_done = false @enclosing_templates = [] case content when String self.parse( content ) when Array self.install_syntax_tree( content ) when NilClass # No-op else raise TemplateError, "Can't handle a %s as template content" % content.class.name end end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(sym, *args, &block) ⇒ Object (protected)
Autoload accessor/mutator methods for attributes.
659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 |
# File 'lib/arrow/template.rb', line 659 def method_missing( sym, *args, &block ) name = sym.to_s.gsub( /=$/, '' ) super unless @attributes.key?( name ) || !@config[:strictAttributes] #self.log.debug "Autoloading for #{sym}" # Mutator if /=$/ =~ sym.to_s #self.log.debug "Autoloading mutator %p" % sym self.add_attribute_mutator( sym ) # Accessor else #self.log.debug "Autoloading accessor %p" % sym self.add_attribute_accessor( sym ) end # Don't use #send to avoid infinite recursion in case method # definition has failed for some reason. self.method( sym ).call( *args ) end |
Class Attribute Details
.load_path ⇒ Object
Returns the value of attribute load_path.
189 190 191 |
# File 'lib/arrow/template.rb', line 189 def load_path @load_path end |
Class Method Details
.attr_underbarred_accessor(sym) ⇒ Object
Create an attr_accessor method for the specified sym
, but one which will look for instance variables with any leading underbars removed.
258 259 260 261 262 263 264 265 266 |
# File 'lib/arrow/template.rb', line 258 def self::( sym ) ivarname = '@' + sym.to_s.gsub( /^_+/, '' ) define_method( sym ) { self.instance_variable_get( ivarname ) } define_method( "#{sym}=" ) {|arg| self.instance_variable_set( ivarname, arg ) } end |
.attr_underbarred_reader(sym) ⇒ Object
Create an attr_reader method for the specified sym
, but one which will look for instance variables with any leading underbars removed.
248 249 250 251 252 253 |
# File 'lib/arrow/template.rb', line 248 def self::( sym ) ivarname = '@' + sym.to_s.gsub( /^_+/, '' ) define_method( sym ) { self.instance_variable_get( ivarname ) } end |
.find_file(file, path = []) ⇒ Object
Find the specified file
in the given path
(or the Template class’s #load_path if not specified).
222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 |
# File 'lib/arrow/template.rb', line 222 def self::find_file( file, path=[] ) raise TemplateError, "Filename #{file} is tainted." if file.tainted? filename = nil path.collect {|dir| File.(file, dir).untaint }.each do |fn| Arrow::Logger[self].debug "Checking path %p" % [ fn ] if File.file?( fn ) Arrow::Logger[self].debug " found the template file at %p" % [ fn ] filename = fn break end Arrow::Logger[self].debug " %p does not exist or is not a plain file." % [ fn ] end raise Arrow::TemplateError, "Template '%s' not found. Search path was %p" % [ file, path ] unless filename return filename end |
.load(name, path = []) ⇒ Object
Load a template from a file.
194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 |
# File 'lib/arrow/template.rb', line 194 def self::load( name, path=[] ) # Find the file on either the specified or default path path = self.load_path if path.empty? Arrow::Logger[self].debug "Searching for template '%s' in %d directories" % [ name, path.size ] filename = self.find_file( name, path ) Arrow::Logger[self].debug "Found '%s'" % [ filename ] # Read the template source source = File.read( filename ) source.untaint # Create a new template object, set its path and filename, then tell it # to parse the loaded source to define its behaviour. Parse is called # after the file and path are set so directives in the template can # use them. obj = new() obj._file = filename obj._load_path.replace( path ) obj.parse( source ) return obj end |
Instance Method Details
#_enclosing_template ⇒ Object
Return the template that is enclosing the receiver in the current context, if any.
379 380 381 |
# File 'lib/arrow/template.rb', line 379 def _enclosing_template self._enclosing_templates.last end |
#changed? ⇒ Boolean
Returns true
if the source file from which the template was read has been modified since the receiver was instantiated. Always returns false
if the template wasn’t loaded from a file.
442 443 444 445 446 447 448 449 450 451 452 453 454 |
# File 'lib/arrow/template.rb', line 442 def changed? return false unless @file if File.exists?( @file ) self.log.debug "Comparing creation time '%s' with file mtime '%s'" % [ @creation_time, File.mtime(@file) ] rval = File.mtime( @file ) > @creation_time end self.log.debug "Template file '%s' has %s" % [ @file, rval ? "changed" : "not changed" ] return rval end |
#initialize_copy(original) ⇒ Object
Initialize a copy of the original
template object.
328 329 330 331 332 333 334 |
# File 'lib/arrow/template.rb', line 328 def initialize_copy( original ) super @attributes = {} tree = original._syntax_tree.collect {|node| node.clone} self.install_syntax_tree( tree ) end |
#inspect ⇒ Object
Return a human-readable representation of the template object.
385 386 387 388 389 390 391 392 |
# File 'lib/arrow/template.rb', line 385 def inspect "#<%s:0x%0x %s (%d nodes)>" % [ self.class.name, self.object_id * 2, @file ? @file : '(anonymous)', @syntax_tree.length, ] end |
#install_node(node) ⇒ Object
Install the given node
into the template object.
423 424 425 426 427 428 429 430 431 432 433 434 435 436 |
# File 'lib/arrow/template.rb', line 423 def install_node( node ) #self.log.debug "Installing a %s %p" % [node.type, node] if node.respond_to?( :name ) && node.name unless @attributes.key?( node.name ) #self.log.debug "Installing an attribute for a node named %p" % node.name @attributes[ node.name ] = nil self.add_attribute_accessor( node.name.to_sym ) self.add_attribute_mutator( node.name.to_sym ) else #self.log.debug "Already have a attribute named %p" % node.name end end end |
#install_syntax_tree(tree) ⇒ Object
Install a new syntax tree in the template object, replacing the old one, if any.
416 417 418 419 |
# File 'lib/arrow/template.rb', line 416 def install_syntax_tree( tree ) @syntax_tree = tree @syntax_tree.each do |node| node.add_to_template(self) end end |
#make_rendering_scope ⇒ Object
Create an anonymous module to act as a scope for any evals that take place during a single render.
530 531 532 533 534 |
# File 'lib/arrow/template.rb', line 530 def make_rendering_scope # self.log.debug "Making rendering scope with attributes: %p" % [@attributes] scope = RenderingScope.new( @attributes ) return scope end |
#memsize ⇒ Object
Return the approximate size of the template, in bytes. Used by Arrow::Cache for size thresholds.
397 398 399 |
# File 'lib/arrow/template.rb', line 397 def memsize @source ? @source.length : 0 end |
#parse(source) ⇒ Object
Parse the given template source (a String) and put the resulting nodes into the template’s syntax_tree.
404 405 406 407 408 409 410 411 |
# File 'lib/arrow/template.rb', line 404 def parse( source ) @source = source parserClass = @config[:parserClass] tree = parserClass.new( @config ).parse( source, self ) #self.log.debug "Parse complete: syntax tree is: %p" % tree return self.install_syntax_tree( tree ) end |
#postrender(enclosing_template = nil) ⇒ Object Also known as: after_rendering
Clean up after template rendering, calling each of its nodes’ #after_rendering hook.
517 518 519 520 521 522 523 524 |
# File 'lib/arrow/template.rb', line 517 def postrender( enclosing_template=nil ) @syntax_tree.each do |node| # self.log.debug " post-rendering %p" % [node] node.after_rendering( self ) if node.respond_to?( :after_rendering ) end @enclosing_templates.pop end |
#postrender_done? ⇒ Boolean
Returns true
if this template has already been through a post-render.
510 511 512 |
# File 'lib/arrow/template.rb', line 510 def postrender_done? return @postrender_done end |
#prerender(enclosing_template = nil) ⇒ Object Also known as: before_rendering
Prep the template for rendering, calling each of its nodes’ #before_rendering hook.
465 466 467 468 469 470 471 472 473 |
# File 'lib/arrow/template.rb', line 465 def prerender( enclosing_template=nil ) @enclosing_templates << enclosing_template @syntax_tree.each do |node| # self.log.debug " pre-rendering %p" % [node] node.before_rendering( self ) if node.respond_to?( :before_rendering ) end end |
#prerender_done? ⇒ Boolean
Returns true
if this template has already been through a pre-render.
458 459 460 |
# File 'lib/arrow/template.rb', line 458 def prerender_done? return @prerender_done end |
#render(nodes = nil, scope = nil, enclosing_template = nil) ⇒ Object Also known as: to_s
Render the template to text and return it as a String. If called with an Array of nodes
, the template will render them instead of its own syntax_tree. If given a scope (a Module object), a Binding of its internal state it will be used as the context of evaluation for the render. If not specified, a new anonymous Module instance is created for the render. If a enclosing_template
is given, make it available during rendering for variable-sharing, etc. Returns the results of each nodes’ render joined together with the default string separator (+$,+).
485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 |
# File 'lib/arrow/template.rb', line 485 def render( nodes=nil, scope=nil, enclosing_template=nil ) rval = [] nodes ||= self.get_prepped_nodes scope ||= self.make_rendering_scope self.prerender( enclosing_template ) # Render each node nodes.each do |node| # self.log.debug " rendering %p" % [ node ] begin rval << node.render( self, scope ) rescue ::Exception => err rval << err end end return self.render_objects( *rval ) ensure self.postrender end |
#render_comment(message) ⇒ Object
Render the given message
as a comment as specified by the template configuration.
569 570 571 572 573 574 575 576 577 |
# File 'lib/arrow/template.rb', line 569 def render_comment( ) comment = "%s%s%s\n" % [ @config[:commentStart], , @config[:commentEnd], ] #self.log.debug "Rendered comment: %s" % comment return comment end |
#render_objects(*objs) ⇒ Object
Render the specified objects into text.
538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 |
# File 'lib/arrow/template.rb', line 538 def render_objects( *objs ) objs.collect do |obj| rval = nil key = (@renderers.keys & obj.class.ancestors).sort {|a,b| a <=> b}.first begin if key case @renderers[ key ] when Proc, Method rval = @renderers[ key ].call( obj, self ) when Symbol methodname = @renderers[ key ] rval = obj.send( methodname ) else raise TypeError, "Unknown renderer type '%s' for %p" % [ @renderers[key], obj ] end else rval = obj.to_s end rescue => err self.log.error "rendering error while rendering %p (a %s): %s" % [obj, obj.class.name, err.] @renderers[ ::Exception ].call( err, self ) end end.join end |
#with_overridden_attributes(scope, hash) ⇒ Object
Call the given block
, overriding the contents of the template’s attributes and the definitions in the specified scope
with those from the pairs in the given hash
.
583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 |
# File 'lib/arrow/template.rb', line 583 def with_overridden_attributes( scope, hash ) oldvals = {} begin hash.each do |name, value| #self.log.debug "Overriding attribute %s with value: %p" % # [ name, value ] oldvals[name] = @attributes.key?( name ) ? @attributes[ name ] : nil @attributes[ name ] = value end scope.override( hash ) do yield( self ) end ensure oldvals.each do |name, value| #self.log.debug "Restoring old value: %s for attribute %p" % # [ name, value ] @attributes.delete( name ) @attributes[ name ] = oldvals[name] if oldvals[name] end end end |