Class: Inversion::Template

Inherits:
Object
  • Object
show all
Extended by:
Configurability, Loggability
Includes:
DataUtilities, TemplateTiltAdditions
Defined in:
lib/inversion/tilt.rb,
lib/inversion/template.rb
more...

Overview

The main template class.

Inversion templates are the primary objects you’ll be interacting with. Templates can be created from a string:

Inversion::Template.new( template_source )

or from a file:

Inversion::Template.load( 'path/to/template.tmpl' )

Template Options

Inversion supports the Configurability API, and registers itself with the templates key. This means you can either add a templates section to your Configurability config, or call ::configure yourself with a config Hash (or something that quacks like one).

To set options on a per-template basis, you can pass an options hash to either Inversion::Template::load or Inversion::Template::new, or set them from within the template itself using the config tag.

The available options are:

:ignore_unknown_tags

Setting to false causes unknown tags used in templates to raise an Inversion::ParseError. Defaults to true.

:on_render_error

Dictates the behavior of exceptions during rendering. Defaults to :comment.

:ignore

Exceptions are silently ignored.

:comment

Exceptions are rendered inline as comments.

:propagate

Exceptions bubble up to the caller of Inversion::Template#render.

:debugging_comments

Insert various Inversion parse and render statements while rendering. Defaults to false.

:comment_start

When rendering debugging comments, the comment is started with these characters. Defaults to "<!--".

:comment_end

When rendering debugging comments, the comment is finished with these characters. Defaults to "-->".

:template_paths

An array of filesystem paths to search for templates within, when loaded or included with a relative path. The current working directory is always the last checked member of this. Defaults to [].

:escape_format

The escaping used by tags such as escape and pp. Default: :html.

:strip_tag_lines

If a tag’s presence introduces a blank line into the output, this option removes it. Defaults to true.

:stat_delay

Templates know when they’ve been altered on disk, and can dynamically reload themselves in long running applications. Setting this option creates a purposeful delay between reloads for busy servers. Defaults to 0 (disabled).

:strict_attributes

Disable getting/setting attributes that aren’t explicitly declared with a tag. Trying to get/set an attribute that isn’t declared in the template with this option enabled will result in a NoMethodError being raised.

Defined Under Namespace

Modules: ContainerTag Classes: AttrTag, BeginTag, CallTag, CodeTag, CommentTag, ConfigTag, DefaultTag, ElseTag, ElsifTag, EndTag, EscapeTag, ForTag, FragmentTag, IfTag, ImportTag, IncludeTag, Node, PpTag, PublishTag, RescueTag, SubscribeTag, Tag, TextNode, TimeDeltaTag, UnlessTag, UriencodeTag, YieldTag

Constant Summary collapse

Parser =

Alias to maintain backward compatibility with <0.2.0 code

Inversion::Parser
VALID_ERROR_ACTIONS =

Valid actions for ‘on_render_error’

[
	:ignore,
	:comment,
	:propagate,
]
DEFAULT_CONFIG =

Default config values

{
	# Loading/parsing options
	:ignore_unknown_tags => true,
	:template_paths      => [],
	:stat_delay          => 0,
	:strict_attributes   => false,

	# Rendering options
	:on_render_error     => :comment,
	:debugging_comments  => false,
	:comment_start       => '<!-- ',
	:comment_end         => ' -->',
	:escape_format       => :html,
	:strip_tag_lines     => true,
}.freeze

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from DataUtilities

deep_copy

Methods included from TemplateTiltAdditions

#each

Constructor Details

#initialize(source, parsestate = nil, opts = {}) ⇒ Template

Create a new Inversion:Template with the given source.

[View source]

264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
# File 'lib/inversion/template.rb', line 264

def initialize( source, parsestate=nil, opts={} )
	if parsestate.is_a?( Hash )
		# self.log.debug "Shifting template options: %p" % [ parsestate ]
		opts = parsestate
		parsestate = nil
	else
		self.log.debug "Parse state is: %p" % [ parsestate ]
	end

	@source       = source
	@node_tree    = [] # Parser expects this to always be an Array
	@options      = self.class.config.merge( opts )
	@attributes   = {}
	@fragments    = {}
	@source_file  = nil
	@created_at   = Time.now
	@last_checked = @created_at

	self.parse( source, parsestate )
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(sym, *args, &block) ⇒ Object (protected)

Proxy method: handle attribute readers/writers for attributes that aren’t yet defined.

Raises:

  • (NoMethodError)
[View source]

397
398
399
400
401
402
403
404
405
406
407
408
# File 'lib/inversion/template.rb', line 397

def method_missing( sym, *args, &block )
	return super unless sym.to_s =~ /^([a-z]\w+)=?$/i
	attribute = $1

	raise NoMethodError, "no tag attribute '%s' (strict mode)" % [ attribute ] if
		self.options[:strict_attributes]

	self.install_accessors( attribute )

	# Call the new method via #method to avoid a method_missing loop.
	return self.method( sym ).call( *args, &block )
end

Class Attribute Details

.configObject

Returns the value of attribute config.


144
145
146
# File 'lib/inversion/template.rb', line 144

def config
  @config
end

.template_pathsObject

Returns the value of attribute template_paths.


149
150
151
# File 'lib/inversion/template.rb', line 149

def template_paths
  @template_paths
end

Instance Attribute Details

#attributesObject (readonly)

The Hash of template attributes


305
306
307
# File 'lib/inversion/template.rb', line 305

def attributes
  @attributes
end

#fragmentsObject (readonly)

The Hash of rendered template fragments


308
309
310
# File 'lib/inversion/template.rb', line 308

def fragments
  @fragments
end

#node_treeObject (readonly)

The node tree parsed from the template source


314
315
316
# File 'lib/inversion/template.rb', line 314

def node_tree
  @node_tree
end

#optionsObject (readonly)

The Template’s configuration options hash


311
312
313
# File 'lib/inversion/template.rb', line 311

def options
  @options
end

#sourceObject (readonly)

The raw template source from which the object was parsed.


299
300
301
# File 'lib/inversion/template.rb', line 299

def source
  @source
end

#source_fileObject

The Pathname of the file the source was read from


302
303
304
# File 'lib/inversion/template.rb', line 302

def source_file
  @source_file
end

Class Method Details

.add_extensions(*modules) ⇒ Object

Add one or more extension modules to Inversion::Template. This allows tags to decorate the template class with new functionality.

Each one of the given modules will be included as a mixin, and if it also contains a constant called ClassMethods and/or PrependedMethods, it will also be extended/prepended (respectively) with it.

Example

Add a layout attribute to templates from a ‘layout’ tag:

class Inversion::Template::LayoutTag < Inversion::Tag

  module TemplateExtension

    def layout
      return @layout || 'default.tmpl'
    end

    module PrependedMethods
      def initialize( * )
        super
        @layout = nil
      end
  end

  Inversion::Template.add_extensions( TemplateExtension )

  # ... more tag stuff

end
[View source]

245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
# File 'lib/inversion/template.rb', line 245

def self::add_extensions( *modules )
	self.log.info "Adding extensions to %p: %p" % [ self, modules ]

	modules.each do |mod|
		include( mod )
		if mod.const_defined?( :ClassMethods )
			submod = mod.const_get( :ClassMethods )
			extend( submod )
		end
		if mod.const_defined?( :PrependedMethods )
			submod = mod.const_get( :PrependedMethods )
			prepend( submod )
		end
	end

end

.configure(config) ⇒ Object

Configure the templating system.

[View source]

154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/inversion/template.rb', line 154

def self::configure( config )
	if config
		Inversion.log.debug "Merging config %p with current config %p" % [ config, self.config ]
		merged_config = DEFAULT_CONFIG.merge( config )
		self.template_paths = Array( merged_config.delete(:template_paths) )
		self.config = merged_config
	else
		defaults = DEFAULT_CONFIG.dup
		self.template_paths = defaults.delete( :template_paths )
		self.config = defaults
	end
end

.load(path, parsestate = nil, opts = {}) ⇒ Object

Read a template object from the specified path.

[View source]

169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'lib/inversion/template.rb', line 169

def self::load( path, parsestate=nil, opts={} )

	# Shift the options hash over if there isn't a parse state
	if parsestate.is_a?( Hash )
		opts = parsestate
		parsestate = nil
	end

	tmpl = nil
	path = Pathname( path )
	opts[:template_paths] ||= self.template_paths
	search_path = opts[:template_paths] + [ Dir.pwd ]
	self.log.debug "Searching template paths: %p" % [ search_path ]

	# Unrestricted template location.
	if path.absolute?
		tmpl = path

	# Template files searched under paths specified in 'template_paths', then
	# the current working directory. First match wins.
	else
		tmpl = search_path.collect {|dir| Pathname(dir) + path }.find do |fullpath|
			fullpath.exist?
		end

		raise RuntimeError, "Unable to find template %p within configured paths %p" %
			[ path.to_s, search_path ] if tmpl.nil?
	end

	# We trust files read from disk
	source = if opts.key?( :encoding )
			tmpl.read( encoding: opts[:encoding] )
		else
			tmpl.read
		end

	# Load the instance and set the path to the source
	template = self.new( source, parsestate, opts )
	template.source_file = tmpl

	return template
end

Instance Method Details

#changed?Boolean

Returns true if the template was loaded from a file and the file’s mtime is after the time the template was created.

Returns:

  • (Boolean)
[View source]

330
331
332
333
334
335
336
337
338
339
340
341
# File 'lib/inversion/template.rb', line 330

def changed?
	return false unless file = self.source_file
	now = Time.now

	if now > ( @last_checked + self.options[ :stat_delay ].to_i )
		if file.mtime > @last_checked
			@last_checked = now
			return true
		end
	end
	return false
end

#initialize_copy(other) ⇒ Object

Copy constructor – make copies of some internal data structures, too.

[View source]

287
288
289
290
291
# File 'lib/inversion/template.rb', line 287

def initialize_copy( other )
	@options    = deep_copy( other.options )
	@attributes = deep_copy( other.attributes )
	@fragments  = deep_copy( other.fragments )
end

#inspectObject

Return a human-readable representation of the template object suitable for debugging.

[View source]

373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
# File 'lib/inversion/template.rb', line 373

def inspect
	nodemap = if $DEBUG
			", node_tree: %p" % [ self.node_tree.map(&:as_comment_body) ]
		else
			''
		end

	return "#<%s:%08x (loaded from %s) attributes: %p, options: %p%s>" % [
		self.class.name,
		self.object_id / 2,
		self.source_file ? self.source_file : "memory",
		self.attributes.keys,
		self.options,
		nodemap
	]
end

#reloadObject

If the template was loaded from a file, reload and reparse it from the same file.

[View source]

318
319
320
321
322
323
324
325
# File 'lib/inversion/template.rb', line 318

def reload
	file = self.source_file or
		raise Inversion::Error, "template was not loaded from a file"

	self.log.debug "Reloading from %s" % [ file ]
	source = file.read
	self.parse( source )
end

#render(parentstate = nil, &block) ⇒ Object Also known as: to_s

Render the template, optionally passing a render state (if, for example, the template is being rendered inside another template).

[View source]

346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
# File 'lib/inversion/template.rb', line 346

def render( parentstate=nil, &block )
	self.log.info "rendering template %#x" % [ self.object_id * 2 ]
	opts = self.options
	opts.merge!( parentstate.options ) if parentstate

	self.fragments.clear

	state = Inversion::RenderState.new( parentstate, self.attributes, opts, &block )

	# self.log.debug "  rendering node tree: %p" % [ @node_tree ]
	self.walk_tree {|node| state << node }
	self.log.info "  done rendering template %#x: %0.4fs" %
		[ self.object_id/2, state.time_elapsed ]

	if parentstate
		parentstate.fragments.merge!( state.fragments )
	else
		self.fragments.replace( state.rendered_fragments )
	end

	return state.to_s
end