Class: Inversion::RenderState
- Inherits:
-
Object
- Object
- Inversion::RenderState
- Extended by:
- Loggability
- Includes:
- DataUtilities
- Defined in:
- lib/inversion/renderstate.rb
Overview
An object that provides an encapsulation of the template’s state while it is rendering.
Defined Under Namespace
Classes: Scope
Instance Attribute Summary collapse
-
#block ⇒ Object
readonly
The block passed to the template’s #render method, if there was one.
-
#containerstate ⇒ Object
readonly
The Inversion::RenderState of the containing template, if any.
-
#default_errhandler ⇒ Object
readonly
The default error handler.
-
#destinations ⇒ Object
readonly
The stack of rendered output destinations, most-recent last.
-
#errhandler ⇒ Object
readonly
The callable object that handles exceptions raised when a node is appended.
-
#fragments ⇒ Object
readonly
Fragment nodes, keyed by fragment name.
-
#options ⇒ Object
readonly
The config options passed in from the template.
-
#published_nodes ⇒ Object
readonly
Published nodes, keyed by subscription.
-
#start_time ⇒ Object
readonly
The Time the object was created.
-
#subscriptions ⇒ Object
readonly
Subscribe placeholders for publish/subscribe.
Instance Method Summary collapse
-
#<<(node) ⇒ Object
Append operator – add an node to the final rendered output.
-
#add_fragment(name, *nodes) ⇒ Object
Add one or more rendered
nodes
to the state as a reusable fragment associated with the specifiedname
. -
#attributes ⇒ Object
Backward-compatibility – return the tag locals of the current scope as a Hash.
-
#default_error_handler(state, node, exception) ⇒ Object
Default exception handler: Handle an
exception
while renderingnode
according to the behavior specified by the ‘on_render_error` option. -
#destination ⇒ Object
Return the current rendered output destination.
-
#disable_rendering ⇒ Object
Disable rendering, causing rendered nodes to be discarded instead of appended.
-
#enable_rendering ⇒ Object
Enable rendering, causing nodes to be appended to the rendered output.
-
#eval(code) ⇒ Object
Evaluate the specified
code
in the context of itself and return the result. -
#handle_render_error(node, exception) ⇒ Object
Handle an
exception
that was raised while appending a node by calling the #errhandler. -
#initialize(containerstate = nil, initial_attributes = {}, options = {}, &block) ⇒ RenderState
constructor
Create a new RenderState.
-
#inspect ⇒ Object
Return a human-readable representation of the object.
-
#merge(otherstate) ⇒ Object
Returns a new RenderState containing the attributes and options of the receiver merged with those of the
otherstate
. -
#merge!(otherstate) ⇒ Object
Merge the attributes and options of the
otherstate
with those of the receiver, replacing any with the same keys. -
#publish(key, *nodes) ⇒ Object
(also: #publish_nodes)
Publish the given
nodes
to all subscribers to the specifiedkey
. -
#rendered_fragments ⇒ Object
Return the current fragments Hash rendered as Strings.
-
#rendering_enabled? ⇒ Boolean
Return
true
if rendered nodes are being saved for output. -
#scope ⇒ Object
Return the hash of attributes that are currently in effect in the rendering state.
-
#subscribe(key, node) ⇒ Object
Subscribe the given
node
to nodes published with the specifiedkey
. -
#tag_data ⇒ Object
Return a Hash that tags can use to track state for the current render.
-
#time_elapsed ⇒ Object
Return the number of floting-point seconds that have passed since the object was created.
-
#to_s ⇒ Object
Turn the rendered node structure into the final rendered String.
-
#toggle_rendering ⇒ Object
Toggle rendering, enabling it if it was disabled, and vice-versa.
-
#with_attributes(overrides) ⇒ Object
Override the state’s attributes with the given
overrides
, call theblock
, then restore the attributes to their original values. -
#with_destination(new_destination) ⇒ Object
Override the state’s render destination, call the block, then restore the original destination when the block returns.
-
#with_error_handler(handler) ⇒ Object
Set the state’s error handler to
handler
for the duration of the block, restoring the previous handler after the block exits. -
#with_tag_data(newhash = {}) ⇒ Object
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.
Methods included from DataUtilities
Constructor Details
#initialize(containerstate = nil, initial_attributes = {}, options = {}, &block) ⇒ RenderState
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.
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
# File 'lib/inversion/renderstate.rb', line 82 def initialize( containerstate=nil, initial_attributes={}, ={}, &block ) # Shift hash arguments if created without a parent state if containerstate.is_a?( Hash ) = 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 = Inversion::Template::DEFAULT_CONFIG.merge( ) @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 |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(sym, *args, &block) ⇒ Object (protected)
Handle attribute methods.
496 497 498 499 500 |
# File 'lib/inversion/renderstate.rb', line 496 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 |
Instance Attribute Details
#block ⇒ Object (readonly)
The block passed to the template’s #render method, if there was one
131 132 133 |
# File 'lib/inversion/renderstate.rb', line 131 def block @block end |
#containerstate ⇒ Object (readonly)
The Inversion::RenderState of the containing template, if any
125 126 127 |
# File 'lib/inversion/renderstate.rb', line 125 def containerstate @containerstate end |
#default_errhandler ⇒ Object (readonly)
The default error handler
149 150 151 |
# File 'lib/inversion/renderstate.rb', line 149 def default_errhandler @default_errhandler end |
#destinations ⇒ Object (readonly)
The stack of rendered output destinations, most-recent last.
143 144 145 |
# File 'lib/inversion/renderstate.rb', line 143 def destinations @destinations end |
#errhandler ⇒ Object (readonly)
The callable object that handles exceptions raised when a node is appended
146 147 148 |
# File 'lib/inversion/renderstate.rb', line 146 def errhandler @errhandler end |
#fragments ⇒ Object (readonly)
Fragment nodes, keyed by fragment name
140 141 142 |
# File 'lib/inversion/renderstate.rb', line 140 def fragments @fragments end |
#options ⇒ Object (readonly)
The config options passed in from the template
128 129 130 |
# File 'lib/inversion/renderstate.rb', line 128 def end |
#published_nodes ⇒ Object (readonly)
Published nodes, keyed by subscription
137 138 139 |
# File 'lib/inversion/renderstate.rb', line 137 def published_nodes @published_nodes end |
#start_time ⇒ Object (readonly)
The Time the object was created
152 153 154 |
# File 'lib/inversion/renderstate.rb', line 152 def start_time @start_time end |
#subscriptions ⇒ Object (readonly)
Subscribe placeholders for publish/subscribe
134 135 136 |
# File 'lib/inversion/renderstate.rb', line 134 def subscriptions @subscriptions end |
Instance Method Details
#<<(node) ⇒ Object
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.
276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 |
# File 'lib/inversion/renderstate.rb', line 276 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.[:debugging_comments] previous_node = nil enc = self.[: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_fragment(name, *nodes) ⇒ Object
Add one or more rendered nodes
to the state as a reusable fragment associated with the specified name
.
347 348 349 350 351 352 |
# File 'lib/inversion/renderstate.rb', line 347 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 |
#attributes ⇒ Object
Backward-compatibility – return the tag locals of the current scope as a Hash.
177 178 179 |
# File 'lib/inversion/renderstate.rb', line 177 def attributes return self.scope.__locals__ end |
#default_error_handler(state, node, exception) ⇒ Object
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.
392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 |
# File 'lib/inversion/renderstate.rb', line 392 def default_error_handler( state, node, exception ) case self.[: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. ] if self.[: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.[:on_render_error] ] end end |
#destination ⇒ Object
Return the current rendered output destination.
248 249 250 |
# File 'lib/inversion/renderstate.rb', line 248 def destination return self.destinations.last end |
#disable_rendering ⇒ Object
Disable rendering, causing rendered nodes to be discarded instead of appended.
430 431 432 |
# File 'lib/inversion/renderstate.rb', line 430 def disable_rendering @rendering_enabled = false end |
#enable_rendering ⇒ Object
Enable rendering, causing nodes to be appended to the rendered output.
424 425 426 |
# File 'lib/inversion/renderstate.rb', line 424 def enable_rendering @rendering_enabled = true end |
#eval(code) ⇒ Object
Evaluate the specified code
in the context of itself and return the result.
170 171 172 173 |
# File 'lib/inversion/renderstate.rb', line 170 def eval( code ) # self.log.debug "Evaling: %p" [ code ] return self.scope.instance_eval( code ) end |
#handle_render_error(node, exception) ⇒ Object
Handle an exception
that was raised while appending a node by calling the #errhandler.
366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 |
# File 'lib/inversion/renderstate.rb', line 366 def handle_render_error( node, exception ) self.log.error "%s while rendering %p: %s" % [ exception.class.name, node.as_comment_body, exception. ] 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 |
#inspect ⇒ Object
Return a human-readable representation of the object.
449 450 451 452 453 454 455 456 457 |
# File 'lib/inversion/renderstate.rb', line 449 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 |
#merge(otherstate) ⇒ Object
Returns a new RenderState containing the attributes and options of the receiver merged with those of the otherstate
.
255 256 257 258 259 |
# File 'lib/inversion/renderstate.rb', line 255 def merge( otherstate ) merged = self.dup merged.merge!( otherstate ) return merged end |
#merge!(otherstate) ⇒ Object
Merge the attributes and options of the otherstate
with those of the receiver, replacing any with the same keys.
264 265 266 267 268 269 |
# File 'lib/inversion/renderstate.rb', line 264 def merge!( otherstate ) @scopes.push( @scopes.pop + otherstate.scope ) # self.attributes.merge!( otherstate.attributes ) self..merge!( otherstate. ) return self end |
#publish(key, *nodes) ⇒ Object Also known as: publish_nodes
Publish the given nodes
to all subscribers to the specified key
.
316 317 318 319 320 321 322 323 324 325 326 327 |
# File 'lib/inversion/renderstate.rb', line 316 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 |
#rendered_fragments ⇒ Object
Return the current fragments Hash rendered as Strings.
356 357 358 359 360 361 |
# File 'lib/inversion/renderstate.rb', line 356 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 |
#rendering_enabled? ⇒ Boolean
Return true
if rendered nodes are being saved for output.
418 419 420 |
# File 'lib/inversion/renderstate.rb', line 418 def rendering_enabled? return @rendering_enabled ? true : false end |
#scope ⇒ Object
Return the hash of attributes that are currently in effect in the rendering state.
157 158 159 |
# File 'lib/inversion/renderstate.rb', line 157 def scope return @scopes.last end |
#subscribe(key, node) ⇒ Object
Subscribe the given node
to nodes published with the specified key
.
332 333 334 335 336 337 338 339 340 341 342 |
# File 'lib/inversion/renderstate.rb', line 332 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 |
#tag_data ⇒ Object
Return a Hash that tags can use to track state for the current render.
163 164 165 |
# File 'lib/inversion/renderstate.rb', line 163 def tag_data return @tag_data.last end |
#time_elapsed ⇒ Object
Return the number of floting-point seconds that have passed since the object was created. Used to time renders.
443 444 445 |
# File 'lib/inversion/renderstate.rb', line 443 def time_elapsed return Time.now - self.start_time end |
#to_s ⇒ Object
Turn the rendered node structure into the final rendered String.
310 311 312 |
# File 'lib/inversion/renderstate.rb', line 310 def to_s return self.stringify_nodes( @output ) end |
#toggle_rendering ⇒ Object
Toggle rendering, enabling it if it was disabled, and vice-versa.
436 437 438 |
# File 'lib/inversion/renderstate.rb', line 436 def toggle_rendering @rendering_enabled = !@rendering_enabled end |
#with_attributes(overrides) ⇒ Object
Override the state’s attributes with the given overrides
, call the block
, then restore the attributes to their original values.
184 185 186 187 188 189 190 191 192 193 194 195 |
# File 'lib/inversion/renderstate.rb', line 184 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 |
#with_destination(new_destination) ⇒ Object
Override the state’s render destination, call the block, then restore the original destination when the block returns.
215 216 217 218 219 220 221 222 223 224 225 226 227 228 |
# File 'lib/inversion/renderstate.rb', line 215 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 |
#with_error_handler(handler) ⇒ Object
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.
235 236 237 238 239 240 241 242 243 244 |
# File 'lib/inversion/renderstate.rb', line 235 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 |
#with_tag_data(newhash = {}) ⇒ Object
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.
200 201 202 203 204 205 206 207 208 209 210 |
# File 'lib/inversion/renderstate.rb', line 200 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 |