class Inversion::RenderState
An object that provides an encapsulation of the template’s state while it is rendering.
Attributes
The block passed to the template’s render method, if there was one
The Inversion::RenderState
of the containing template, if any
The default error handler
The stack of rendered output destinations, most-recent last.
The callable object that handles exceptions raised when a node is appended
Fragment nodes, keyed by fragment name
The config options passed in from the template
Published nodes, keyed by subscription
The Time the object was created
Subscribe placeholders for publish/subscribe
Public Class Methods
Create a new RenderState
. If the template is being rendered inside another one, the containing template’s RenderState
will be passed as the ‘containerstate`. The `initial_attributes` will be deep-copied, and the `options` will be merged with Inversion::Template::DEFAULT_CONFIG. The `block` is stored for use by template nodes.
# File lib/inversion/renderstate.rb, line 80 def initialize( containerstate=nil, initial_attributes={}, options={}, &block ) # Shift hash arguments if created without a parent state if containerstate.is_a?( Hash ) options = initial_attributes initial_attributes = containerstate containerstate = nil end # self.log.debug "Creating a render state with attributes: %p" % # [ initial_attributes ] locals = deep_copy( initial_attributes ) @scopes = [ Scope.new(locals) ] @start_time = Time.now @containerstate = containerstate @options = Inversion::Template::DEFAULT_CONFIG.merge( options ) @block = block @default_errhandler = self.method( :default_error_handler ) @errhandler = @default_errhandler @rendering_enabled = true # The rendered output Array, the stack of render destinations and # tag states @output = [] @destinations = [ @output ] @tag_data = [ {} ] # Hash of subscribed Nodes and published data, keyed by the subscription key # as a Symbol @subscriptions = Hash.new {|hsh, k| hsh[k] = [] } # Auto-vivify to an Array @published_nodes = Hash.new {|hsh, k| hsh[k] = [] } @fragments = Hash.new {|hsh, k| hsh[k] = [] } end
Public Instance Methods
Append operator – add an node to the final rendered output. If the ‘node` renders as an object that itself responds to the render method, render will be called and the return value will be appended instead. This will continue until the returned object either doesn’t respond to render or renders as itself.
# File lib/inversion/renderstate.rb, line 274 def <<( node ) # self.log.debug "Appending a %p to %p" % [ node.class, self ] original_node = node original_node.before_rendering( self ) if self.rendering_enabled? self.destination << self.make_node_comment( node ) if self.options[:debugging_comments] previous_node = nil enc = self.options[:encoding] || Encoding.default_internal begin # Allow render to be delegated to subobjects while node.respond_to?( :render ) && node != previous_node # self.log.debug " delegated rendering to: %p" % [ node ] previous_node = node node = node.render( self ) end # self.log.debug " adding a %p (%p; encoding: %s) to the destination (%p)" % # [ node.class, node, node.respond_to?(:encoding) ? node.encoding : 'n/a', self.destination.class ] self.destination << node # self.log.debug " just appended %p to %p" % [ node, self.destination ] rescue ::StandardError => err # self.log.debug " handling a %p while rendering: %s" % [ err.class, err.message ] self.destination << self.handle_render_error( original_node, err ) end end original_node.after_rendering( self ) return self end
Add one or more rendered ‘nodes` to the state as a reusable fragment associated with the specified `name`.
# File lib/inversion/renderstate.rb, line 345 def add_fragment( name, *nodes ) self.log.debug "Adding a %s fragment with %d nodes." % [ name, nodes.size ] nodes.flatten! self.fragments[ name.to_sym ] = nodes self.scope.__fragments__[ name.to_sym ] = nodes end
Backward-compatibility – return the tag locals of the current scope as a Hash.
# File lib/inversion/renderstate.rb, line 175 def attributes return self.scope.__locals__ end
Default exception handler: Handle an ‘exception` while rendering `node` according to the behavior specified by the `on_render_error` option. Returns the string which should be appended to the output, if any.
# File lib/inversion/renderstate.rb, line 390 def default_error_handler( state, node, exception ) case self.options[:on_render_error].to_s when 'ignore' self.log.debug " not rendering anything for the error" return '' when 'comment' self.log.debug " rendering error as a comment" msg = "%s: %s" % [ exception.class.name, exception.message ] if self.options[:debugging_comments] exception.backtrace.each {|line| msg << "\n" << line } end return self.make_comment( msg ) when 'propagate' self.log.debug " propagating error while rendering" raise( exception ) else raise Inversion::OptionsError, "unknown exception-handling mode: %p" % [ self.options[:on_render_error] ] end end
Return the current rendered output destination.
# File lib/inversion/renderstate.rb, line 246 def destination return self.destinations.last end
Disable rendering, causing rendered nodes to be discarded instead of appended.
# File lib/inversion/renderstate.rb, line 428 def disable_rendering @rendering_enabled = false end
Enable rendering, causing nodes to be appended to the rendered output.
# File lib/inversion/renderstate.rb, line 422 def enable_rendering @rendering_enabled = true end
Evaluate the specified ‘code` in the context of itself and return the result.
# File lib/inversion/renderstate.rb, line 168 def eval( code ) # self.log.debug "Evaling: %p" [ code ] return self.scope.instance_eval( code ) end
Handle an ‘exception` that was raised while appending a node by calling the errhandler
.
# File lib/inversion/renderstate.rb, line 364 def handle_render_error( node, exception ) self.log.error "%s while rendering %p: %s" % [ exception.class.name, node.as_comment_body, exception.message ] handler = self.errhandler raise ScriptError, "error handler %p isn't #call-able!" % [ handler ] unless handler.respond_to?( :call ) self.log.debug "Handling %p with handler: %p" % [ exception.class, handler ] return handler.call( self, node, exception ) rescue ::StandardError => err # Handle exceptions from overridden error handlers (re-raised or errors in # the handler itself) via the default handler. if handler && handler != self.default_errhandler self.log.error "%p (re)raised from custom error handler %p" % [ err.class, handler ] self.default_errhandler.call( self, node, exception ) else raise( err ) end end
Return a human-readable representation of the object.
# File lib/inversion/renderstate.rb, line 447 def inspect return "#<%p:0x%08x containerstate: %s, scope locals: %s, destination: %p>" % [ self.class, self.object_id / 2, self.containerstate ? "0x%08x" % [ self.containerstate.object_id ] : "nil", self.scope.__locals__.keys.sort.join(', '), self.destination.class, ] end
Returns a new RenderState
containing the attributes and options of the receiver merged with those of the ‘otherstate`.
# File lib/inversion/renderstate.rb, line 253 def merge( otherstate ) merged = self.dup merged.merge!( otherstate ) return merged end
Merge the attributes and options of the ‘otherstate` with those of the receiver, replacing any with the same keys.
# File lib/inversion/renderstate.rb, line 262 def merge!( otherstate ) @scopes.push( @scopes.pop + otherstate.scope ) # self.attributes.merge!( otherstate.attributes ) self.options.merge!( otherstate.options ) return self end
Publish the given ‘nodes` to all subscribers to the specified `key`.
# File lib/inversion/renderstate.rb, line 314 def publish( key, *nodes ) key = key.to_sym # self.log.debug "[0x%016x] Publishing %p nodes: %p" % [ self.object_id * 2, key, nodes ] self.containerstate.publish( key, *nodes ) if self.containerstate self.subscriptions[ key ].each do |subscriber| # self.log.debug " sending %d nodes to subscriber: %p (a %p)" % # [ nodes.length, subscriber, subscriber.class ] subscriber.publish( *nodes ) end self.published_nodes[ key ].concat( nodes ) end
Return the current fragments Hash rendered as Strings.
# File lib/inversion/renderstate.rb, line 354 def rendered_fragments self.log.debug "Rendering fragments: %p." % [ self.fragments.keys ] return self.fragments.each_with_object( {} ) do |(key, nodes), accum| accum[ key ] = self.stringify_nodes( nodes ) end end
Return ‘true` if rendered nodes are being saved for output.
# File lib/inversion/renderstate.rb, line 416 def rendering_enabled? return @rendering_enabled ? true : false end
Return the hash of attributes that are currently in effect in the rendering state.
# File lib/inversion/renderstate.rb, line 155 def scope return @scopes.last end
Subscribe the given ‘node` to nodes published with the specified `key`.
# File lib/inversion/renderstate.rb, line 330 def subscribe( key, node ) key = key.to_sym self.log.debug "Adding subscription to %p nodes for %p" % [ key, node ] self.subscriptions[ key ] << node # self.log.debug " now have subscriptions for: %p" % [ self.subscriptions.keys ] if self.published_nodes.key?( key ) self.log.debug " re-publishing %d %p nodes to late subscriber" % [ self.published_nodes[key].length, key ] node.publish( *self.published_nodes[key] ) end end
Return a Hash that tags can use to track state for the current render.
# File lib/inversion/renderstate.rb, line 161 def tag_data return @tag_data.last end
Return the number of floting-point seconds that have passed since the object was created. Used to time renders.
# File lib/inversion/renderstate.rb, line 441 def time_elapsed return Time.now - self.start_time end
Turn the rendered node structure into the final rendered String.
# File lib/inversion/renderstate.rb, line 308 def to_s return self.stringify_nodes( @output ) end
Toggle rendering, enabling it if it was disabled, and vice-versa.
# File lib/inversion/renderstate.rb, line 434 def toggle_rendering @rendering_enabled = !@rendering_enabled end
Override the state’s attributes with the given ‘overrides`, call the `block`, then restore the attributes to their original values.
# File lib/inversion/renderstate.rb, line 182 def with_attributes( overrides ) raise LocalJumpError, "no block given" unless block_given? # self.log.debug "Overriding template attributes with: %p" % [ overrides ] begin newscope = self.scope + overrides @scopes.push( newscope ) yield( self ) ensure @scopes.pop end end
Override the state’s render destination, call the block, then restore the original destination when the block returns.
# File lib/inversion/renderstate.rb, line 213 def with_destination( new_destination ) raise LocalJumpError, "no block given" unless block_given? # self.log.debug "Overriding render destination with: %p" % [ new_destination ] begin @destinations.push( new_destination ) yield ensure # self.log.debug " removing overridden render destination: %p" % [ @destinations.last ] @destinations.pop end return new_destination end
Set the state’s error handler to ‘handler` for the duration of the block, restoring the previous handler after the block exits. `Handler` must respond to call, and will be called with two arguments: the node that raised the exception, and the exception object itself.
# File lib/inversion/renderstate.rb, line 233 def with_error_handler( handler ) original_handler = self.errhandler raise ArgumentError, "%p doesn't respond_to #call" unless handler.respond_to?( :call ) @errhandler = handler yield ensure @errhandler = original_handler end
Add an overlay to the current tag state Hash, yield to the provided block, then revert the tag state back to what it was prior to running the block.
# File lib/inversion/renderstate.rb, line 198 def with_tag_data( newhash={} ) raise LocalJumpError, "no block given" unless block_given? # self.log.debug "Overriding tag state with: %p" % [ newhash ] begin @tag_data.push( @tag_data.last.merge(newhash) ) yield( self ) ensure @tag_data.pop end end
Protected Instance Methods
Return the specified ‘content` inside of the configured comment characters.
# File lib/inversion/renderstate.rb, line 470 def make_comment( content ) return [ self.options[:comment_start], content, self.options[:comment_end], ].join end
Return the ‘node` as a comment if debugging comments are enabled.
# File lib/inversion/renderstate.rb, line 463 def make_node_comment( node ) comment_body = node.as_comment_body or return '' return self.make_comment( comment_body ) end
Handle attribute methods.
# File lib/inversion/renderstate.rb, line 494 def method_missing( sym, *args, &block ) return super unless sym.to_s =~ /^\w+$/ # self.log.debug "mapping missing method call to tag local: %p" % [ sym ] return self.scope[ sym ] end
Return the given ‘nodes` as a String in the configured encoding.
# File lib/inversion/renderstate.rb, line 480 def stringify_nodes( nodes ) # self.log.debug "Rendering nodes: %p" % [ nodes ] strings = nodes.flatten.map( &:to_s ) if enc = self.options[ :encoding ] self.log.debug "Encoding rendered template parts to %s" % [ enc ] strings.map! {|str| str.encode(enc, invalid: :replace, undef: :replace) } end return strings.join end