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 specified `name`.
-
#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 rendering `node` 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 specified `key`.
-
#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 specified `key`.
-
#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 the `block`, 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.
81 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 |
# File 'lib/inversion/renderstate.rb', line 81 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 @options = 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.
495 496 497 498 499 |
# File 'lib/inversion/renderstate.rb', line 495 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
130 131 132 |
# File 'lib/inversion/renderstate.rb', line 130 def block @block end |
#containerstate ⇒ Object (readonly)
The Inversion::RenderState of the containing template, if any
124 125 126 |
# File 'lib/inversion/renderstate.rb', line 124 def containerstate @containerstate end |
#default_errhandler ⇒ Object (readonly)
The default error handler
148 149 150 |
# File 'lib/inversion/renderstate.rb', line 148 def default_errhandler @default_errhandler end |
#destinations ⇒ Object (readonly)
The stack of rendered output destinations, most-recent last.
142 143 144 |
# File 'lib/inversion/renderstate.rb', line 142 def destinations @destinations end |
#errhandler ⇒ Object (readonly)
The callable object that handles exceptions raised when a node is appended
145 146 147 |
# File 'lib/inversion/renderstate.rb', line 145 def errhandler @errhandler end |
#fragments ⇒ Object (readonly)
Fragment nodes, keyed by fragment name
139 140 141 |
# File 'lib/inversion/renderstate.rb', line 139 def fragments @fragments end |
#options ⇒ Object (readonly)
The config options passed in from the template
127 128 129 |
# File 'lib/inversion/renderstate.rb', line 127 def @options end |
#published_nodes ⇒ Object (readonly)
Published nodes, keyed by subscription
136 137 138 |
# File 'lib/inversion/renderstate.rb', line 136 def published_nodes @published_nodes end |
#start_time ⇒ Object (readonly)
The Time the object was created
151 152 153 |
# File 'lib/inversion/renderstate.rb', line 151 def start_time @start_time end |
#subscriptions ⇒ Object (readonly)
Subscribe placeholders for publish/subscribe
133 134 135 |
# File 'lib/inversion/renderstate.rb', line 133 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.
275 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 |
# File 'lib/inversion/renderstate.rb', line 275 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`.
346 347 348 349 350 351 |
# File 'lib/inversion/renderstate.rb', line 346 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.
176 177 178 |
# File 'lib/inversion/renderstate.rb', line 176 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.
391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 |
# File 'lib/inversion/renderstate.rb', line 391 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.
247 248 249 |
# File 'lib/inversion/renderstate.rb', line 247 def destination return self.destinations.last end |
#disable_rendering ⇒ Object
Disable rendering, causing rendered nodes to be discarded instead of appended.
429 430 431 |
# File 'lib/inversion/renderstate.rb', line 429 def disable_rendering @rendering_enabled = false end |
#enable_rendering ⇒ Object
Enable rendering, causing nodes to be appended to the rendered output.
423 424 425 |
# File 'lib/inversion/renderstate.rb', line 423 def enable_rendering @rendering_enabled = true end |
#eval(code) ⇒ Object
Evaluate the specified ‘code` in the context of itself and return the result.
169 170 171 172 |
# File 'lib/inversion/renderstate.rb', line 169 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.
365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 |
# File 'lib/inversion/renderstate.rb', line 365 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.
448 449 450 451 452 453 454 455 456 |
# File 'lib/inversion/renderstate.rb', line 448 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`.
254 255 256 257 258 |
# File 'lib/inversion/renderstate.rb', line 254 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.
263 264 265 266 267 268 |
# File 'lib/inversion/renderstate.rb', line 263 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`.
315 316 317 318 319 320 321 322 323 324 325 326 |
# File 'lib/inversion/renderstate.rb', line 315 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.
355 356 357 358 359 360 |
# File 'lib/inversion/renderstate.rb', line 355 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.
417 418 419 |
# File 'lib/inversion/renderstate.rb', line 417 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.
156 157 158 |
# File 'lib/inversion/renderstate.rb', line 156 def scope return @scopes.last end |
#subscribe(key, node) ⇒ Object
Subscribe the given ‘node` to nodes published with the specified `key`.
331 332 333 334 335 336 337 338 339 340 341 |
# File 'lib/inversion/renderstate.rb', line 331 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.
162 163 164 |
# File 'lib/inversion/renderstate.rb', line 162 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.
442 443 444 |
# File 'lib/inversion/renderstate.rb', line 442 def time_elapsed return Time.now - self.start_time end |
#to_s ⇒ Object
Turn the rendered node structure into the final rendered String.
309 310 311 |
# File 'lib/inversion/renderstate.rb', line 309 def to_s return self.stringify_nodes( @output ) end |
#toggle_rendering ⇒ Object
Toggle rendering, enabling it if it was disabled, and vice-versa.
435 436 437 |
# File 'lib/inversion/renderstate.rb', line 435 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.
183 184 185 186 187 188 189 190 191 192 193 194 |
# File 'lib/inversion/renderstate.rb', line 183 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.
214 215 216 217 218 219 220 221 222 223 224 225 226 227 |
# File 'lib/inversion/renderstate.rb', line 214 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.
234 235 236 237 238 239 240 241 242 243 |
# File 'lib/inversion/renderstate.rb', line 234 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.
199 200 201 202 203 204 205 206 207 208 209 |
# File 'lib/inversion/renderstate.rb', line 199 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 |