Class: Arrow::Template
- Inherits:
-
Object
- Object
- Object
- Arrow::Template
- Extends:
- 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
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
- 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
Class Attribute Summary
-
+ (Object) load_path
Returns the value of attribute load_path.
Class Method Summary
-
+ (Object) attr_underbarred_accessor(sym)
Create an attr_accessor method for the specified sym, but one which will look for instance variables with any leading underbars removed.
-
+ (Object) attr_underbarred_reader(sym)
Create an attr_reader method for the specified sym, but one which will look for instance variables with any leading underbars removed.
-
+ (Object) find_file(file, path = [])
Find the specified file in the given path (or the Template class’s #load_path if not specified).
-
+ (Object) load(name, path = [])
Load a template from a file.
Instance Method Summary
-
- (Object) _enclosing_template
Return the template that is enclosing the receiver in the current context, if any.
-
- (Object) add_attribute_accessor(sym)
protected
Add a singleton accessor (getter) method for accessing the attribute specified by sym to the receiver.
-
- (Object) add_attribute_mutator(sym)
protected
Add a singleton mutator (setter) method for accessing the attribute specified by sym to the receiver.
-
- (Boolean) changed?
Returns true if the source file from which the template was read has been modified since the receiver was instantiated.
-
- (Object) get_prepped_nodes
protected
Returns the syntax tree with its nodes prerendered in accordance with the template’s configuration.
-
- (Template) initialize(content = nil, config = {})
constructor
Create a new template object with the specified content (a String) and config hash.
-
- (Object) initialize_copy(original)
Initialize a copy of the original template object.
-
- (Object) inspect
Return a human-readable representation of the template object.
-
- (Object) install_node(node)
Install the given node into the template object.
-
- (Object) install_syntax_tree(tree)
Install a new syntax tree in the template object, replacing the old one, if any.
-
- (Object) make_rendering_scope
Create an anonymous module to act as a scope for any evals that take place during a single render.
-
- (Object) memsize
Return the approximate size of the template, in bytes.
-
- (Object) method_missing(sym, *args, &block)
protected
Autoload accessor/mutator methods for attributes.
-
- (Object) parse(source)
Parse the given template source (a String) and put the resulting nodes into the template’s syntax_tree.
-
- (Object) postrender(enclosing_template = nil)
(also: #after_rendering)
Clean up after template rendering, calling each of its nodes’ #after_rendering hook.
-
- (Boolean) postrender_done?
Returns true if this template has already been through a post-render.
-
- (Object) prerender(enclosing_template = nil)
(also: #before_rendering)
Prep the template for rendering, calling each of its nodes’ #before_rendering hook.
-
- (Boolean) prerender_done?
Returns true if this template has already been through a pre-render.
-
- (Object) render(nodes = nil, scope = nil, enclosing_template = nil)
(also: #to_s)
Render the template to text and return it as a String.
-
- (Object) render_comment(message)
Render the given message as a comment as specified by the template configuration.
-
- (Object) render_objects(*objs)
Render the specified objects into text.
-
- (Object) strip_directive_whitespace(tree)
protected
Strip whitespace from the tails of textnodes before and the head of textnodes after lines consisting only of non-rendering directives in the given template syntax tree.
-
- (Object) with_overridden_attributes(scope, hash)
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.
Methods included from HashUtilities
#stringify_keys, #symbolify_keys
Methods inherited from Object
deprecate_class_method, deprecate_method, inherited
Methods included from Loggable
Constructor Details
- (Template) initialize(content = nil, config = {})
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 is false 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
- (Object) method_missing(sym, *args, &block) (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
+ (Object) load_path
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
+ (Object) attr_underbarred_accessor(sym)
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 |
+ (Object) attr_underbarred_reader(sym)
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 |
+ (Object) find_file(file, path = [])
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 |
+ (Object) load(name, path = [])
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
- (Object) _enclosing_template
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 |
- (Object) add_attribute_accessor(sym) (protected)
Add a singleton accessor (getter) method for accessing the attribute specified by sym to the receiver.
683 684 685 686 687 688 689 690 691 692 693 694 |
# File 'lib/arrow/template.rb', line 683 def add_attribute_accessor( sym ) name = sym.to_s.sub( /=$/, '' ) code = %Q{ def self::#{name} @attributes[#{name.inspect}] end } # $stderr.puts "Auto-defining accessor for #{name}: #{code}" eval( code, nil, "#{name} [Auto-defined]", __LINE__ ) end |
- (Object) add_attribute_mutator(sym) (protected)
Add a singleton mutator (setter) method for accessing the attribute specified by sym to the receiver.
699 700 701 702 703 704 705 706 707 708 709 710 |
# File 'lib/arrow/template.rb', line 699 def add_attribute_mutator( sym ) name = sym.to_s.sub( /=$/, '' ) code = %Q{ def self::#{name}=( arg ) @attributes[ #{name.inspect} ] = arg end } # $stderr.puts "Auto-defining mutator for #{name}: #{code}" eval( code, nil, "#{name}= [Auto-defined]", __LINE__ ) end |
- (Boolean) changed?
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 |
- (Object) get_prepped_nodes (protected)
Returns the syntax tree with its nodes prerendered in accordance with the template’s configuration.
613 614 615 616 617 618 619 620 |
# File 'lib/arrow/template.rb', line 613 def get_prepped_nodes tree = @syntax_tree.dup self.strip_directive_whitespace( tree ) if @config[:elideDirectiveLines] return tree end |
- (Object) initialize_copy(original)
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 |
- (Object) inspect
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 |
- (Object) install_node(node)
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 |
- (Object) install_syntax_tree(tree)
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 |
- (Object) make_rendering_scope
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 |
- (Object) memsize
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 |
- (Object) parse(source)
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 |
- (Object) postrender(enclosing_template = nil) 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 |
- (Boolean) postrender_done?
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 |
- (Object) prerender(enclosing_template = nil) 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 |
- (Boolean) prerender_done?
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 |
- (Object) render(nodes = nil, scope = nil, enclosing_template = nil) 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 |
- (Object) render_comment(message)
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 |
- (Object) render_objects(*objs)
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 |
- (Object) strip_directive_whitespace(tree) (protected)
Strip whitespace from the tails of textnodes before and the head of textnodes after lines consisting only of non-rendering directives in the given template syntax tree.
626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 |
# File 'lib/arrow/template.rb', line 626 def strip_directive_whitespace( tree ) # Make a flat list of all nodes nodes = tree.collect {|node| node.to_a}.flatten # Elide non-rendering directive lines. Match node lists like: # <TextNode> =~ /\n\s*$/ # <NonRenderingNode>* # <TextNode> =~ /^\n/ # removing one "\n" from the tail of the leading textnode and the # head of the trailing textnode. Trailing textnode can also be a # leading textnode for another series. nodes.each_with_index do |node,i| leadingNode = nodes[i-1] # If both the leading node and the current one match the # criteria, look for a trailing node. if i.nonzero? && leadingNode.is_a?( TextNode ) && leadingNode =~ /\n\s*$/s # Find the trailing node. Abandon the search on any # rendering directive or text node that includes a blank line. trailingNode = nodes[i..-1].find do |node| break nil if node.rendering? node.is_a?( TextNode ) && node =~ /^\n/ end leadingNode.body.sub!( /\n\s*$/, '' ) if trailingNode end end end |
- (Object) with_overridden_attributes(scope, hash)
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 |