texttemplate
0.2.0

----------------------------------------------------------------------
SUMMARY

A fast, powerful, easy-to-use text templating engine.

----------------------------------------------------------------------
DESCRIPTION

texttemplate converts text templates into simple Python object models that can be manipulated via callback functions in your scripts.


======= About text templates =======

A text template is a plain text document annotated with 'compiler directives', special tags indicating sections of text to be converted to nodes within the template object model. These tags are normally delimited by '{' and '}', though alternatives may be specified in the Template constructor. Each node is indicated by a pair of start and end tags enclosing other content (plain text and/or other nodes), or by a single self-closing tag containing no initial content:

	{con:foo}...{/con:foo}

	{rep:bar}...{/rep:bar}

	{sep:baz}...{/sep:baz}

	{con:fub/}

	{rep:wib/}

	{sep:wib/}

The tag body consists of two parts: a three-letter type code - 'con', 'rep' or 'sep' - indicating the type of node to create, followed by the node object's name which should be a valid Python identifier. Texttemplate supports three types of compiler directive indicating how tags should be processed:

- 'con' defines a Container node that can appear only once at the given location
- 'rep' defines a Repeater node that can appear any number of times
- 'sep' defines a separator string to be inserted between each iteration of a Repeater object of the same name.


======= The Template Object Model =======

A texttemplate object model is constructed from three types of object: Template, Container and Repeater.

A single Template node forms the object model's root, and contains one or more Container/Repeater nodes as children. 

A Container node represents a section of template whose content can be modified. Container nodes have a one-to-one relationship with their parent nodes, appearing only once [1] when rendered.

A Repeater node is a Container node that has a one-to-many relationship with its parent node, appearing zero or more times: once for each item [1] in the collection - usually a list - being iterated by its repeat() method and/or once for each time its add() method is called [1].

The 'sep' directive indicates the separator string to be inserted between each iteration of a Repeater object with the same name. If no separator is specified, the default separator string is used. Normally this is '\n', but other values may be specified in the Template constructor.

-------

[1] Except when the object's omit() method is called, in which case its content is omitted from the finished page.


======= Compiling a Template =======

To compile a template object model, call the Template constructor with the template's text as its first argument:

	template = texttemplate.Template(text)

Two optional arguments may also be provided:

- tag -- A tuple containing two strings that denote the beginning and end of a compiler directive. The default is ('{', '}') but any other non-empty strings may be used, e.g. ('[!', ']').

- sep - The default separator string to insert between rendered Repeater instances that don't have a matching separator string defined in the template. The default is a single newline, but any other value may be used; e.g. ''


During development, you can check the structure of a compiled Template object by calling its structure() method:

	print template.structure()


======= Rendering a Template =======

Template rendering is a two-step process: insert data into the Template object model, then call the Template object's render() method to render it as text.


A Template object model is made up of named nodes, accessed by name using either standard dot-notation (the preferred form):

	template.foo
	somenode.foo.bar

or key:

	template['foo']
	somenode['foo']['bar']


Nodes containing only plain text allow this content to be manipulated via their content property, e.g.:

	oldvalue = somenode.content
	somenode.content = newvalue


To insert data into a Repeater node, call its add() method with a callback function as its first argument followed by the data itself as subsequent arguments. For example:

	def renderFoo(node, data1, data2, ...):
		# insert data into cloned Repeater node here...

	node.foo.add(renderFoo, data1, data2, ...)

The Repeater node clones itself and passes this copy to the callback function along with the given data, allowing the callback function to insert the data into that node. The finished result is then stored within the original Repeater node. The add() method can be called as often as you like. 

If you have a list of values to iterate over, the Repeater node's repeat() method provides a shorter, more convenient alternative to writing your own 'for' loop:

	node.foo.repeat(renderFoo, datalist, data2, ...)


Once all data has been inserted into the Template, call its render() method to render it to text:

	result = template.render()


======= Reusing compiled Templates =======

A single compiled template can be efficiently used to render any number of pages - there's no need to recompile it each time you wish to render a new page. Just compile the text template beforehand, storing the resulting Template object in a persistent (e.g. global) variable. For each new page you render, call the stored template object's clone() method to create an exact duplicate of it and render the page using this duplicate. Example:

templateOriginal = Template(text)

def renderTemplate():
	templateCopy = templateOriginal.clone()
	# insert data into templateCopy here...
	return templateCopy.render()


----------------------------------------------------------------------
CLASSES

Node -- Abstract base class
	Properties:
		nodetype : str -- the node's type ('con', 'rep' or 'tem')

		nodename : str -- the node's name

		NAME -- a Container/Repeater element (NAME = the element's name)

	Methods:
		__iter__() -- returns a generator object for iterating over sub-nodes
			Result : generator
		
		structure() -- returns a text representation of object model for diagnostic use [1]
			Result : str



Container(Node) -- A mutable block ('con')
	Properties:
		content : str | unicode -- the block's content [2]

	Methods:
		omit() -- don't render this object



Repeater(Container) -- A mutable, repeatable block ('rep')
	Methods:
		add(fn, *args) -- render a new instance of this Repeater object using the supplied function and data
			fn : function -- a callback function that controls rendering of Repeater object
			*args : anything -- any other values to be passed unchanged to the supplied function


		repeat(fn, list, *args) -- calls self.add(fn, item, *args) for each item in list [3]
			fn : function -- a callback function that controls rendering of Repeater objects
			list : list -- the items to pass, one at a time, to the supplied function [4]
			*args : anything -- any other values to be passed unchanged to the supplied function



Template(Node) -- A mutable document
	Constructor:
		Template(text, tag=('{', '}'), sep='\n')
			text : str | unicode -- the text template
			tag : tuple of str/unicode -- pair of strings denoting start and end of a tag, e.g. ('[[', ']]')
			sep : str | unicode -- default separator for Repeater nodes, e.g. ''
	
	Methods:
		clone() -- clone this template
			Result : Template -- an exact copy of this template [5]

		render() -- render this template
			Result : str | unicode

-------

[1] Example output:

	tem:template
		con:title
		rep:item
			con:description

[2] The content property can only be used when the Container/Repeater object is derived from a block that doesn't contain any other Containers/Repeaters, otherwise an exception is raised.

[3] Or any other object that supports iteration.

[4] Each item is passed as the second argument to the supplied callback function.

[5] The copy will contain any changes made to the original template prior to it being cloned. After cloning, the two template objects are completely independent from one another so changes made to one will not affect the other.


----------------------------------------------------------------------
EXAMPLES

- Tutorial_1.py


----------------------------------------------------------------------
NOTES

- Texttemplate is intended for templating plain text: config files, email messages, source code, etc. For (X)HTML, HTMLTemplate is recommended <http://freespace.virgin.net/hamish.sanderson/htmltemplate.html>.

- Don't bother trying to get documentation via pydoc; the modules themselves are (deliberately) left undocumented. The real class structure is more complex than the simplified 'ideal' hierarchy shown here, but users don't need to know (or worry) about this.


======= Template parser notes =======

- If the parser finds two nodes with the same name within the same parent node, an error will be raised.

- A separator must be defined after the Repeater node it applies to, and both must appear within the same parent node. The parser will raise an exception if it encounters a separator for an unknown Repeater.


======= Controller design notes =======

- Object types and names are case-sensitive.

- When assigning a new value to a Container or Repeater element's content, make sure you write:

	node.foo.content = newValue

and not:

	node.foo = newValue

as the two statements have very different meanings. The first assigns newValue as foo's content; the second replaces the con_foo element with newValue (see below).


# TO FINISH: following topics to be covered in more detail: template composition, reuse (optimisation technique), introspection:


======= Composition =======

Compose multiple templates by:

1. rendering sub-templates and inserting results into main template:

from texttemplate import Template
page = '''----------
{con:body/}
----------'''
pageTpl = Template(page)

body = '''{rep:names}- {con:name/}{/rep:names}'''
bodyTpl = Template(body)

def renderNames(node, name):
	node.name.content = name

def renderBody(names):
	tpl = bodyTpl.clone()
	tpl.names.repeat(renderNames, names)
	return tpl.render()

def renderPage(names):
	tpl = pageTpl.clone()
	tpl.body.content = renderBody(names)
	print tpl.render()

renderPage(['Jo', 'Sam', 'Fred'])


2. replacing nodes in main template with other nodes/templates:

from texttemplate import Template

page = '''----------
{con:body/}
----------'''
pageTpl = Template(page)

body = '''{rep:names}- {con:name/}{/rep:names}'''
bodyTpl = Template(body)

def renderNames(node, name):
	node.name.content = name

def renderPage(names):
	tpl = pageTpl.clone()
	tpl.body = bodyTpl
	tpl.body.names.repeat(renderNames, names)
	print tpl.render()

renderPage(['Jo', 'Sam', 'Fred'])


======= Reuse (optimisation technique) =======

- reuse of rendered content by 1. first-time caching of rendered nodes for reuse in subsequent renderings of same template

2. cascaded rendering: clone master > add data common to all pages (e.g. navbar) > clone sub-master > add data unique to each page (e.g. main body):

# (crappy example)
from texttemplate import Template

text = '''{con:title/}
{con:body/}
(C) {con:year/} {con:author/}'''

master = Template(text)

master.year.content = '2005'
master.author.content = 'HAS'

dataset = [('Doc 1', 'Blah-blah'), ('Doc 2', 'Blah-blah-blah')]
pages = []
for title, body in dataset:
	tpl = master.clone()
	tpl.title.content = title
	tpl.body.content = body
	pages.append(tpl.render())
print pages


- metatemplating, where a template contains more than one kind of templating tag, e.g. ('{meta:','}') and ('{','}') , allowing multiple customised templates to be rendered from a single master template


======= Introspection and node iteration =======

# TO DO:

- e.g. auto-insertion of data from dict. Trivial example:

template = Template('{con:foo/} {con:bar/} {con:baz/} ({rep:zip/}{sep:zip}, {/sep:zip})')
data = {'foo':'1', 'bar':'2', 'fub':'3', 'zip': ['Jo', 'Jane', 'Jim']}
for node in template:
	if node.nodetype == 'con':
		node.content = data.get(node.nodename, '?')
	elif node.nodetype == 'rep':
		def renderRepeater(node, item):
			node.content = item
		node.repeat(renderRepeater, data.get(node.nodename, []))
print template.render() # '1 2 ? (Jo, Jane, Jim)'


----------------------------------------------------------------------
DEPENDENCIES

- Python 2.3+

----------------------------------------------------------------------
TO DO

- better [user-]error reporting
- get user feedback
- finish manual and tutorials
- any additional features?
- test
- 1.0.0 release

----------------------------------------------------------------------
HISTORY

2005-01-15 -- 0.2.0; redesigned API, execution model and documentation

2004-01-10 -- 0.1.1; removed automatic str() cast on assignment to text property as this was causing problems with unicode (silly Python) - user now need to cast any non-text values themselves before assignment

2004-01-04 -- 0.1.0; initial release

----------------------------------------------------------------------
AUTHORS

- HAS <hamish.sanderson@virgin.net>

----------------------------------------------------------------------
COPYRIGHT

texttemplate - A fast, powerful, easy-to-use text templating system.

Copyright (C) 2003 HAS <hamish.sanderson@virgin.net>

This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA