templayer-1.5.1/setup.py0000644000175000017500000000053310505761642013341 0ustar ianian from distutils.core import setup import templayer version = templayer.__version__ setup ( name = "templayer", version = version, description = "Templayer - Layered Template Library for HTML", author = "Ian Ward", author_email = "ian@excess.org", url = "http://excess.org/templayer/", license = "LGPL", py_modules = ['templayer'], ) templayer-1.5.1/templayer.py0000644000175000017500000007155611150403641014205 0ustar ianian#!/usr/bin/python """ templayer.py - Layered Template Library for HTML http://excess.org/templayer/ library to build dynamic html that provides clean separation of form (html+css+javascript etc..) and function (python code) as well as making cross-site scripting attacks and improperly generated html very difficult. Copyright (c) 2003-2009 Ian Ward 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. """ __author__ = "Ian Ward" __version__ = "1.5.1" import string import sys import os import time import re from types import TupleType, ListType, IntType, UnicodeType from inspect import getargspec try: True # old python? except: False, True = 0, 1 SLOT_MARK = "%" BEGIN_MARK = "{" END_MARK = "}" CLOSE_PREFIX = "/" CONTENTS = "contents" CONTENTS_OPEN = BEGIN_MARK+CONTENTS+END_MARK CONTENTS_CLOSE = BEGIN_MARK+CLOSE_PREFIX+CONTENTS+END_MARK COMPLETE_FILE_LAYER = "*" ENTITY_RE = re.compile("^#x[0-9A-Fa-f]+$|^#[0-9]+$|^[a-zA-Z]+$") TEMPLAYER_PAGES_MODULE = "templayer_pages" class TemplateError(Exception): pass class Template(object): """ class Template - common functionality for Template subclasses """ def __init__( self, name, from_string=False, auto_reload='never', allow_degenerate=False, encoding='utf_8'): """ name -- template file name or file-like object from_string -- if True treat name as a string containing the template data instead of a file to read from auto_reload -- if the template has not been checked in this many seconds then check it and reload if there are any changes. If 'never' only load the template file once. Checking and reloading is done by the start_file() function. Ignored if from_string is True or if name is a file-like object. allow_degenerate -- If True then template is allowed to not have a {contents}...{/contents} block. In this case the entire file is available as a layer called '*'. eg. l = tmpl.format('*', ...) encoding -- encoding for template file """ self.allow_degenerate = allow_degenerate self.encoding = encoding self.auto_reload = auto_reload self.template_ts = 0 self.template_name = None if from_string: data = name if type(data)!=UnicodeType: data = data.decode(encoding) self.set_template_data(data) auto_reload = None elif hasattr(name, "read"): self.set_template_data(name.read().decode(encoding)) auto_reload = 'never' else: self.template_name = name self.reload_template() def reload_template(self): """Reload the template data from the template file.""" if not self.template_name: return tmpl = open(self.template_name).read().decode(self.encoding) self.set_template_data(tmpl) def template_file_changed(self): """ Return True if the template file has been modified since the last time it was loaded. """ if self.template_ts == "never": return False if not self.template_name: return True # we don't know if it has changed if os.stat(self.template_name)[8] != self.template_ts: return True def set_template_data(self,tmpl): """Parse and store the template data passed.""" if type(tmpl) != UnicodeType: raise TemplateError("template data must be " "a unicode string") self.cache = {} try: # use the first CONTENTS_BEGIN in the file header, rest = tmpl.split(CONTENTS_OPEN,1) # and the last CONTENTS_CLOSE in the file rest_l = rest.split(CONTENTS_CLOSE) if len(rest_l)<2: raise ValueError footer = rest_l[-1] body = CONTENTS_CLOSE.join(rest_l[:-1]) self.contents_open = CONTENTS_OPEN self.contents_close = CONTENTS_CLOSE except ValueError: if not self.allow_degenerate: raise TemplateError, "Template Contents Block "\ "Missing: %s...%s" % \ (CONTENTS_OPEN, CONTENTS_CLOSE ) # degenerate template header = tmpl footer = body = "" self.contents_open = self.contents_close = "" self.header = header.split(SLOT_MARK) self.footer = footer.split(SLOT_MARK) self.body = [] body_l = body.split(BEGIN_MARK) for b in body_l: self.body.append( b.split(SLOT_MARK) ) def layer_split( self, name ): """Return the layer named name split on SLOT_MARKs.""" if BEGIN_MARK in name or SLOT_MARK in name: raise TemplateError, "Layer names cannot include %s " \ "or %s" % (BEGIN_MARK, SLOT_MARK) if name == COMPLETE_FILE_LAYER: begin = 0 end = len(self.body) elif self.cache.has_key(name): begin, end = self.cache[name] else: begin = None end = None i = 1 for b in self.body[1:]: if begin is None \ and b[0].startswith(name+END_MARK): begin = i if begin is not None and b[0].startswith( CLOSE_PREFIX+name+END_MARK): end = i i += 1 if end is None or begin is None: raise TemplateError, "Template Layer Missing:"\ " %s...%s"%(BEGIN_MARK+name+END_MARK, BEGIN_MARK+CLOSE_PREFIX+name+END_MARK) self.cache[name] = begin, end o = [] for i in range(begin, end): b = self.body[i] if o: # join across BEGIN_MARKs o[-1] = o[-1]+BEGIN_MARK+b[0] o.extend(b[1:]) else: o.extend(b) if name == COMPLETE_FILE_LAYER: # include the header and footer c = self.header[:-1] c.append(self.header[-1] + self.contents_open + o[0]) c.extend(o[1:]) c[-1] = c[-1] + self.contents_close + self.footer[0] c.extend(self.footer[1:]) o = c else: # remove the opening layer tag o[0] = o[0][len(name)+len(END_MARK):] return o def layer( self, name ): """Return the complete layer named name as a string.""" return SLOT_MARK.join(self.layer_split(name)) def missing_slot(self, names, layer): """Called when one or more slots are not found in a layer.""" if layer is None: where = "Header/Footer" else: where = "Layer %s" % (BEGIN_MARK+layer+END_MARK) slots = [SLOT_MARK+name+SLOT_MARK for name in names] if len(slots)>1: slotp = "slots" else: slotp = "slot" raise TemplateError, "%s is missing the following %s: %s" % ( where, slotp, ", ".join(slots)) def format_header_footer( self, **args ): """Returns header and footer with slots filled by args.""" fargs = {} for k, v in args.items(): k, v = self.pre_process(k, v) fargs[k] = v header, missing_h = fill_slots(self.header, **fargs) footer, missing_f = fill_slots(self.footer, **fargs) d = {} for m in missing_h: d[m] = True missing = [m for m in missing_f if d.has_key(m)] if missing: self.missing_slot(missing, None) return self.post_process(header), self.post_process(footer) def format( self, layer_name, ** args ): """ Return a layer with slots filled by args. self.format(...) and self(...) are equivalent. """ s = self.layer_split(layer_name) fargs = {} for k, v in args.items(): k, v = self.pre_process(k, v) fargs[k] = v s, missing = fill_slots(s, **fargs) if missing: self.missing_slot(missing, layer_name) return self.post_process(s) __call__ = format def start_file( self, file=None, encoding='utf_8' ): """ Return a FileWriter object that uses this template. file -- file object or None to use sys.stdout. If self.auto_reload is not None this function will first check and reload the template file if it has changed. """ if self.auto_reload is not None: t = time.time() if self.auto_reload != "never" and \ t > self.template_ts+self.auto_reload: if self.template_file_changed(): self.reload_template() self.template_ts = t return FileWriter( file, self, encoding ) def pre_process( self, key, value ): """ Returns key, filtered/escaped value. Override this function to provide escaping or filtering of text sent from the module user. """ return key, value def post_process( self, value ): """ Returns wrapped value. Override to wrap processed text in order to mark it as having been processed. """ return value def finalize( self, value ): """ Return unwrapped value as a string. Override this function to reverse wrapping applied before sending to the output file. """ return value def fill_slots(layer_split, **args): """Return layer_split filled with args, missing slot list.""" filled = {} o = [] last_is_slot = True for p in layer_split[:-1]: if last_is_slot: last_is_slot = False o.append(p) elif args.has_key(p): o.append(unicode(args[p])) filled[p] = True last_is_slot = True else: o.extend([SLOT_MARK,p]) if not last_is_slot: o.append(SLOT_MARK) o.append(layer_split[-1]) missing = [] if len(filled)content""" return expand_html_markup(('href', link, content)) def target(name): """HTML Markup """ return expand_html_markup(('target', name)) def BR(count=1): """HTML Markup
* count""" return expand_html_markup(('br', count)) def P(content=""): """HTML Markup

content

""" return expand_html_markip(('p', content)) def I(content): """HTML Markup content""" return expand_html_markip(('i', content)) def B(content): """HTML Markup content""" return expand_html_markip(('b', content)) def U(content): """HTML Markup content""" return expand_html_markip(('u', content)) def entity(entity): """HTML Markup &entity;""" return expand_html_markip(('&', entity)) def expand_html_markup( v ): """ Return an HTML string based on markup v. HTML markup is expanded recursively. Each of the content values below are passed to expand_html_markup again before applying the operation on the right: string or unicode string -> HTML-escaped version of string [content1, content2, ...] -> concatenate content1, content2, ... ('join',list,sep) -> join items in list with seperator sep ('pluralize',count,singular_content,plural_content) -> if count == 1 use singular_content, otherwise use plural_content ('urljoin',head,tail) -> join safe url head with unsafe url-ending tail ('href',link,content) -> HTML href to link wrapped around content ('target',name) -> HTML target name ('br',) -> HTML line break ('br',count) -> HTML line break * count ('p',) -> HTML paragraph break ('p',content) -> HTML paragraph ('i',content) -> italics ('b',content) -> bold ('u',content) -> underline ('&',entity) -> HTML entity (entity has no & or ;) RawHTML(value) -> value unmodified """ if isinstance(v,RawHTML): return v.value if type(v) == ListType: l = [] for x in v: l.append( expand_html_markup( x ) ) return "".join(l) if type(v) != TupleType: try: return html_escape(v) except AttributeError, err: raise MarkupError(MKE_BAD_TYPE % repr(type(v))) if v[0] == 'href': if len(v)!=3: raise MarkupError(MKE_INVALID_HREF) if type(v[1]) != TupleType or v[1][0]!='urljoin': v=[ v[0], ('urljoin',v[1],"") ,v[2] ] return html_href(expand_html_markup(v[1]), expand_html_markup(v[2])) if v[0] == 'join': if len(v)!=3 or type(v[1]) != ListType: raise MarkupError(MKE_INVALID_JOIN) sep = expand_html_markup(v[2]) l = [] for x in v[1]: l.append( expand_html_markup( x ) ) return sep.join(l) if v[0] == 'pluralize': if len(v)!=4: raise MarkupError(MKE_INVALID_PLURALIZE) if v[1]==1: return expand_html_markup(v[2]) else: return expand_html_markup(v[3]) if v[0] == 'urljoin': if len(v)!=3: raise MarkupError(MKE_INVALID_URLJOIN) try: return v[1]+html_url_encode(v[2]) except TypeError, err: raise MarkupError(MKE_INVALID_URLJOIN) except AttributeError, err: raise MarkupError(MKE_INVALID_URLJOIN) if v[0] == 'target': if len(v)!=3: raise MarkupError(MKE_INVALID_TARGET) return html_target(html_url_encode(v[1]), expand_html_markup(v[2])) if v[0] == 'p' and len(v) == 2 and type(v[1]) != IntType: return "

"+expand_html_markup(v[1])+"

" if v[0] == 'br' or v[0] == 'p': if len(v)==1: v = (v[0], 1) if len(v)!=2 or type(v[1]) != IntType: raise MarkupError(MKE_INVALID_BR_P %(v[0],v[0])) return ("<"+v[0]+"/>") * v[1] if v[0] == 'b' or v[0] == 'i' or v[0] == 'u': if len(v)!=2: raise MarkupError(MKE_INVALID_B_I_U %(v[0],v[0])) return ("<"+v[0]+">"+ expand_html_markup(v[1])+ "") if v[0] == '&': if len(v)!=2: raise MarkupError(MKE_INVALID_ENTITY) if not ENTITY_RE.match(v[1]): raise MarkupError(MKE_INVALID_ENTITY) return "&"+v[1]+";" raise MarkupError(MKE_UNRECOGNISED) class RawHTML(object): """ class RawHTML - wrap strings of generated html that are passed outside the module so that they aren't escaped when passed back in """ def __init__( self, value ): """value -- segment of HTML as a string""" self.value = value def __repr__( self ): return 'RawHTML(%s)'%repr(self.value) class LayerError(Exception): pass class FileWriter(object): """ class FileWriter - the layer within which all other layers nest, responsible for sending generated text to the output stream """ def __init__( self, file, template, encoding='utf_8' ): """ file -- output stream or None to use sys.stdout template -- template object for generating this file This constructor is usually not called directly. Use template.start_file() to create a FileWriter instead. """ if not file: file = sys.stdout self.out = file self.template = template self.child = None self.encoding = encoding def open( self, ** args ): """ Return a new layer representing the content between the header and footer. Use keyword arguments to fill the slots in the header and footer. """ assert self.child == None header, footer = self.template.format_header_footer(**args) self.child = Layer( self.template, header, footer ) return self.child def flush( self ): """ Flush as much output as possible to the output stream. """ assert self.child != None, "FileWriter already closed!" data ="".join(self.child.flush()) self.out.write(data.encode(self.encoding)) self.out.flush() def close( self ): """ Close all child layers and flush the output to the output stream. This function must be called to generate a complete file. Returns the file object where output was sent. """ assert self.child != None, "FileWriter.close() already called "\ "or .open() was never called!" data ="".join(self.child.close()) self.out.write(data.encode(self.encoding)) self.child = None return self.out # backwards compatibility FileLayer = FileWriter class Layer(object): """ class Layer - holds the state of one of the nested layers while its contents are being written to. Layers are closed implicitly when a parent layer is written to, or explicitly when its parent's close_child is called. """ def __init__( self, template, header, footer ): """ template -- template object for generating this layer header -- text before the content of this layer footer -- text after the content of this layer This constructor should not be called directly. Use the open() function in a FileWriter or the open_layer() function in another Layer object to create a new Layer. """ self.child = None self.template = template header = self.template.finalize(header) self.out = [header] self.footer = self.template.finalize(footer) def close_child(self): """ If we have an open child layer close it and add its output to our own. """ if self.out == None: raise LayerError, "Layer is closed!" if self.child: self.out = self.out + self.child.close() self.child = None def open_layer( self, layer, ** args ): """ layer -- name of layer in the template to open Use keyword arguments to fill this layer's slots. open_layer( layer name, ** args ) -> child Layer object open a layer as a child of this one, filling its slots with values from args """ c = SLOT_MARK+CONTENTS+SLOT_MARK if args.has_key(CONTENTS): raise LayerError, "Cannot specify a value for " + \ c+" when calling open_layer(). Use " + \ "write_layer() instead." block = self.template.format( layer, ** args ) l = block.value.split(c) if len(l) == 1: raise LayerError, "Cannot open layer " + \ BEGIN_MARK+layer+END_MARK+" because it is " + \ "missing a "+c+" slot." if len(l) > 2: raise LayerError, "Cannot open layer " + \ BEGIN_MARK+layer+END_MARK+" because it has " + \ "more than one "+c+" slot." header, footer = l self.close_child() header = self.template.post_process(header) footer = self.template.post_process(footer) self.child = Layer( self.template, header, footer) return self.child def write_layer( self, layer, ** args ): """ layer -- name of layer in the template to write Use keyword arguments to fill this layer's slots. """ self.close_child() result = self.template.format( layer, ** args ) self.out.append( self.template.finalize(result) ) def write( self, text ): """ text -- text or markup as interpreted by the template """ ignore, result = self.template.pre_process("",text) result = self.template.post_process(result) self.close_child() self.out.append( self.template.finalize(result) ) def flush( self ): """ Flush as much output as possible and return it. This function should not be called directly. Use the flush() function in the FileWriter object instead. """ if self.out == None: raise LayerError, "Layer is closed!" output = self.out if self.child: output = output + self.child.flush() self.out = [] return output def close( self ): """ Close this layer and return its output. This function should not be called directly. Use the close() function in the FileWriter object instead. """ self.close_child() final = self.out+[self.footer] self.out = None # break further outputting return final _html_url_valid = string.letters + string.digits + "$-_.!*'()" def html_url_encode(text, query=False): """ text -- text to be included as part of a URL query -- if True use "+" instead ot "%20" for spaces """ url = "" for c in text: if c in _html_url_valid: url += c elif query and c==" ": url += "+" else: url += "%" + "%02x" % ord(c) return url def html_href(link,value): """ Return 'value'. Neither link nor value is escaped by this function. """ return '%s'%(link,value) def html_target(target,caption): """ Return 'caption'. Neither target nor caption is escaped by this function. """ return '%s'%(target,caption) def html_escape(text): """ text -- text to be escaped for inclusion within HTML. """ text = text.replace('&','&') text = text.replace('"','"') # in case we're in an attrib. text = text.replace('<','<') text = text.replace('>','>') return text def django_form(form, errors=True, join_errors=lambda x: ('join', x, ('br,'))): """ Converts a django FormWrapper object to a dictionary that can be used by Templayer. Each field is rendered into a %form.NAME% slot. If errors is True then this function will also render errors into %form.NAME.errors% slots. When there is more than one error on a single field the errors will be joined with the join_errors function. The default join_errors function puts a newline between each error. eg: If tmpl is an HTMLTemplate object with a {form_layer} layer and form is a FormWrapper object with username and password fields, then this code will fill the {form_layer} layer's %title%, %form.username%, %form.password%, %form.username.errors% and %form.password.errors% slots: tmpl.format('form_layer', title="hello", **django_form(form)) """ d = {} for field_name in form.fields: value = form[field_name] d["form."+field_name] = RawHTML(str(value)) if errors: e = [x for x in form.errors.get(field_name,[])] d["form." + field_name + ".errors"] = join_errors(e) return d class DjangoTemplayerError(Exception): pass class _DjangoTemplayer(object): def __init__(self, run, template_name, tmpl, args, optargs): self.run = run self.template_name = template_name self.tmpl = tmpl self.args = args self.optargs = optargs def render(self, context_instance): from django.http import HttpResponse sendargs = {} for arg in self.args: if arg not in context_instance: raise DjangoTemplayerError("Required " "parameter %s was not passed to " "template %s!" % ( repr(arg), repr(self.template_name))) sendargs[arg] = context_instance[arg] for arg in self.optargs: if arg in context_instance: sendargs[arg] = context_instance[arg] # create the file layer before calling our function fwriter = self.tmpl.start_file(HttpResponse()) response = self.run(fwriter, context_instance, **sendargs) # if the file layer (or None) is returned, render it if response is fwriter or response is None: response = fwriter.close() return response.content _django_templates = {} _django_templates_searched = False def django_template_loader(template_name, template_dirs=None): """ This function should be included in settings.TEMPLATE_LOADERS when using the django_template decorator. eg: TEMPLATE_LOADERS += ['templayer.django_template_loader'] """ global _django_templates global _django_templates_searched from django.template import TemplateDoesNotExist if template_name not in _django_templates and \ not _django_templates_searched: _django_templates_searched = True # django template registration is a side-effect of importing # a module with django_template decorators, so try # looking in the views modules of installed applications from django.conf import settings targets = [] for droot in settings.TEMPLATE_DIRS: for d, subdirs, files in os.walk(droot): if TEMPLAYER_PAGES_MODULE+".py" in files: targets.append(d) for t in targets: orig_sys_path = sys.path try: sys.path = [t] + orig_sys_path __import__(TEMPLAYER_PAGES_MODULE) finally: sys.path = orig_sys_path if template_name not in _django_templates: raise TemplateDoesNotExist() run, tmpl, args, optargs = _django_templates[template_name] return (_DjangoTemplayer(run, template_name, tmpl, args, optargs), # make a pretend template name based on the file/fn name run.func_code.co_filename + "." + run.func_code.co_name) # This oddity is reqired for Django template loaders: django_template_loader.is_usable = True def django_template(tmpl, template_name): """ This is a decorator for functions you want to make behave like standard Django templates. When django_template_loader is included in settings.TEMPLATE_LOADERS Django will find the decorated function when looking for template_name. tmpl is the Templayer template object that will be used to create the file layer passed to the decorated function. The decorated function must have a signature like: @templayer.django_template(tmpl, "appname/somepage.html") def mock_template(fwriter, context, optional1, optional2 ...) Where fwriter is a new FileWriter object, context is a context instance and all parameters that follow will have their values copied from the context based on the name of the parameters. The function must call fwriter.open(..) to render the page then return the fwriter object (or None) to send it to the client. fwriter.close() is called automatically. Other objects returned or exceptions raised will be passed through to Django. """ global _django_templates if not _django_templates: _hook_django_template_loading() def register_mock_template(fn): args, varargs, varkw, defaults = getargspec(fn) if len(args) < 2: raise DjangoTemplayerError("django_template()-" "decorated functions must have fwriter " "and context as their first two parameters") nonoptional = len(args) - len(defaults or ()) args, optargs = args[2:nonoptional], args[min(2,nonoptional):] _django_templates[template_name] = (fn, tmpl, args, optargs) return fn return register_mock_template def _hook_django_template_loading(): import django.template.loader orig_gtfs = django.template.loader.get_template_from_string def get_template_from_string(source, *largs, **dargs): if isinstance(source, _DjangoTemplayer): return source return orig_gtfs(source, *largs, **dargs) django.template.loader.get_template_from_string = get_template_from_string def django_view(tmpl): """ This is a decorator for Django view functions that will handle creation of the file layer object from the template tmpl. The function must have a signature like: @templayer.django_view(tmpl) def decorated_view(fwriter, request, ...) fwriter is the new FileWriter object and the rest of the parameters are the same as what was passed to the view. The function must call fwriter.open(..) to render the page then return the fwriter object (or None) to send it to the client. fwriter.close() is called automatically. Other objects returned or exceptions raised will be passed through to Django. """ from django.http import HttpResponse def view_wrapper(fn): def wrapped_view(request, *argl, **argd): # create the file layer before calling our function fwriter = tmpl.start_file(HttpResponse()) response = fn(fwriter, request, *argl, **argd) # if the file layer (or None) is returned, render it if response is fwriter or response is None: response = fwriter.close() return response # do our best to make the decorated function resemble the # original function wrapped_view.__name__ = fn.__name__ wrapped_view.__dict__.update(fn.__dict__) wrapped_view.__doc__ = fn.__doc__ wrapped_view.__module__ = fn.__module__ return wrapped_view return view_wrapper def get_django_template(name, auto_reload='debug', allow_degenerate=False, encoding='utf_8'): """ Use Django's template loading machinery to create an HTMLTemplate object. auto_reload -- when set to 'debug' will auto-reload only when settings.DEBUG is set to True """ from django.template.loader import find_template_source source, obj = find_template_source(name) if auto_reload == 'debug': from django.conf import settings if settings.DEBUG: auto_reload = 0 else: auto_reload = 'never' tmpl = HTMLTemplate(source, from_string=True, auto_reload=auto_reload, encoding=encoding) return tmpl if __name__ == "__main__": main() templayer-1.5.1/docgen_tutorial.py0000755000175000017500000004347211150315456015372 0ustar ianian#!/usr/bin/python """ Tutorial documentation generator for Templayer http://excess.org/templayer/ library to build dynamic html that provides clean separation of form (html+css+javascript etc..) and function (python code) as well as making cross-site scripting attacks and improperly generated html very difficult. Copyright (c) 2003-2009 Ian Ward 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. """ from __future__ import nested_scopes import sys import os import templayer try: True # old python? except: False, True = 0, 1 examples = {} template = {} interp_line = "#!/usr/bin/python\n\n" template["lawn1"] = """ Gordon's Lawn Happenings

Gordon's Lawn Happenings

Sunday

We've got a groundhog. I will have to stay alert.

I lost half a tomato plant to that furry guy.

Monday

The grass grew - I saw it.

""" template["lawn2"] = """ Gordon's Lawn Happenings

Gordon's Lawn Happenings

{contents} {/contents}

Sunday

We've got a groundhog. I will have to stay alert.

I lost half a tomato plant to that furry guy.

Monday

The grass grew - I saw it.

""" examples["lawn2"] = ["example_lawn2"] def example_lawn2(): import templayer import sys sys.stdout.write("Content-type: text/html\r\n\r\n") tmpl = templayer.HTMLTemplate("lawn2.html") file_writer = tmpl.start_file() file_writer.open() file_writer.close() template["lawn3"] = """ %title%

%title%

{contents} {/contents}

Sunday

We've got a groundhog. I will have to stay alert.

I lost half a tomato plant to that furry guy.

Monday

The grass grew - I saw it.


Generated on %date%.

""" examples["lawn3"] = ["example_lawn3"] def example_lawn3(): import templayer import time import sys sys.stdout.write("Content-type: text/html\r\n\r\n") tmpl = templayer.HTMLTemplate("lawn3.html") file_writer = tmpl.start_file() file_writer.open(title="Gordon's Lawn Happenings", date=time.asctime()) file_writer.close() template["lawn4"] = """ %title%

%title%

{contents} {report}

%day%

%happenings% {/report} {/contents}

Generated on %date%.

""" examples["lawn4"] = ["example_lawn4a","example_lawn4b","example_lawn4c"] def example_lawn4a(): import templayer import time import sys sys.stdout.write("Content-type: text/html\r\n\r\n") tmpl = templayer.HTMLTemplate("lawn4.html") file_writer = tmpl.start_file() main_layer = file_writer.open(title="Gordon's Lawn Happenings", date=time.asctime()) main_layer.write_layer('report', day="Sunday", happenings=[ "We've got a groundhog. I will have to stay alert.", "I lost half a tomato plant to that furry guy."]) main_layer.write_layer('report', day="Monday", happenings=[ "The grass grew - I saw it."]) file_writer.close() def example_lawn4b(): import templayer import time import sys sys.stdout.write("Content-type: text/html\r\n\r\n") reports = [ ('Sunday', ["We've got a groundhog. I will have to stay alert.", "I lost half a tomato plant to that furry guy."]), ('Monday', ["The grass grew - I saw it."]), ] tmpl = templayer.HTMLTemplate("lawn4.html") file_writer = tmpl.start_file() main_layer = file_writer.open(title="Gordon's Lawn Happenings", date=time.asctime()) for d, h in reports: main_layer.write_layer('report', day=d, happenings=h) file_writer.close() def example_lawn4c(): import templayer import time import sys sys.stdout.write("Content-type: text/html\r\n\r\n") reports = [ ('Sunday', [('p',"We've got a groundhog. I will have to stay alert."), ('p',"I lost half a tomato plant to that furry guy.")]), ('Monday', [('p',"The grass grew - I saw it.")]), ] tmpl = templayer.HTMLTemplate("lawn4.html") file_writer = tmpl.start_file() main_layer = file_writer.open(title="Gordon's Lawn Happenings", date=time.asctime()) for d, h in reports: main_layer.write_layer('report', day=d, happenings=h) file_writer.close() template["lawn5"] = """ %title%

%title%

{contents} {report}

%day%

%happenings% {/report} {happening}

%what%

{/happening} {/contents}

Generated on %date%.

""" examples["lawn5"] = ["example_lawn5"] def example_lawn5(): import templayer import time import sys sys.stdout.write("Content-type: text/html\r\n\r\n") reports = [ ('Sunday', ["We've got a groundhog. I will have to stay alert.", "I lost half a tomato plant to that furry guy."]), ('Monday', ["The grass grew - I saw it."]), ] tmpl = templayer.HTMLTemplate("lawn5.html") file_writer = tmpl.start_file() main_layer = file_writer.open(title="Gordon's Lawn Happenings", date=time.asctime()) for d, h in reports: happening_list = [] for w in h: formatted = tmpl.format('happening', what=w) happening_list.append(formatted) main_layer.write_layer('report', day=d, happenings=happening_list) file_writer.close() template["lawn6"] = """ %title%

%title%

{contents} {report}

%day%

%contents% {/report} {happening}

%contents%

{/happening} {/contents}

Generated on %date%.

""" examples["lawn6"] = ["example_lawn6a","example_lawn6b","example_lawn6c"] def example_lawn6a(): import templayer import time import sys sys.stdout.write("Content-type: text/html\r\n\r\n") reports = [ ('Sunday', ["We've got a groundhog. I will have to stay alert.", "I lost half a tomato plant to that furry guy."]), ('Monday', ["The grass grew - I saw it."]), ] tmpl = templayer.HTMLTemplate("lawn6.html") file_writer = tmpl.start_file() main_layer = file_writer.open(title="Gordon's Lawn Happenings", date=time.asctime()) for d, h in reports: report_layer = main_layer.open_layer('report', day=d) for happening in h: report_layer.write_layer('happening', contents=happening) file_writer.close() def example_lawn6b(): import templayer import time import sys sys.stdout.write("Content-type: text/html\r\n\r\n") reports = [ ('Sunday', ["We've got a groundhog. I will have to stay alert.", "I lost half a tomato plant to that furry guy."]), ('Monday', ["The grass grew - I saw it."]), ] tmpl = templayer.HTMLTemplate("lawn6.html") file_writer = tmpl.start_file() main_layer = file_writer.open(title="Gordon's Lawn Happenings", date=time.asctime()) for d, h in reports: report_layer = main_layer.open_layer('report', day=d) for happening in h: happening_layer = report_layer.open_layer('happening') happening_layer.write(happening) file_writer.close() def example_lawn6c(): import templayer import time import sys sys.stdout.write("Content-type: text/html\r\n\r\n") reports = [ ('Sunday', ["We've got a groundhog. I will have to stay alert.", "I lost half a tomato plant to that furry guy."]), ('Monday', ["The grass grew - I saw it."]), ] tmpl = templayer.HTMLTemplate("lawn6.html") file_writer = tmpl.start_file() main_layer = file_writer.open(title="Gordon's Lawn Happenings", date=time.asctime()) for d, h in reports: report_layer = main_layer.open_layer('report', day=d) for happening in h: happening_layer = report_layer.open_layer('happening') happening_layer.write(happening) main_layer.close_child() file_writer.flush() file_writer.close() template["simple.templayer"] = """ %title%

%title%

{contents} {/contents} """ examples["simple.templayer"] = ["example_simple_views_1", "example_simple_views_2"] def example_simple_views_1(): from django.http import HttpResponse import datetime import templayer tmpl = templayer.get_django_template("simple.templayer.html") def current_datetime(request): file_writer = tmpl.start_file(HttpResponse()) now = datetime.datetime.now() contents = file_writer.open(title="Current Date and Time") contents.write("It is now %s." % now) return file_writer.close() def example_simple_views_2(): import datetime import templayer tmpl = templayer.get_django_template("simple.templayer.html") @templayer.django_view(tmpl) def current_datetime(file_writer, request): now = datetime.datetime.now() contents = file_writer.open(title="Current Date and Time") contents.write("It is now %s." % now) template["book.templayer"] = """ %title%

%title%

{contents} {new_entry}

Sign the guestbook

Your Name: %form.name% %form.name.errors%
Rate the site: %form.rating% %form.rating.errors%
Your Comments:
%form.comment% %form.comment.errors%
{/new_entry} {/contents} """ examples["book.templayer"] = ["example_book_models", "example_book_views"] def example_book_models(): from django.db import models RATING_CHOICES = ( ('G', "Great!"), ('A', "Average"), ('U', "Uninteresting"), ) class Entry(models.Model): name = models.CharField("Your Name", max_length=100) rating = models.CharField("Rate this site", max_length=1, choices=RATING_CHOICES) comment = models.TextField("Your Comments (optional)", blank=True) posted = models.DateTimeField(auto_now_add=True) def example_book_views(): from book.models import Entry from django.forms import ModelForm import templayer tmpl = templayer.get_django_template("book.templayer.html") class EntryForm(ModelForm): class Meta: model = Entry @templayer.django_view(tmpl) def guest_book(file_layer, request): if request.POST: entry_form = EntryForm(request.POST) # TODO: do something with the data if all is well else: entry_form = EntryForm() contents = file_writer.open(title="Guest Book") contents.write_layer("new_entry", **templayer.django_form(entry_form)) template["emulate.templayer"] = """ %title%

%title%

{contents} {show_404} The url %url% could not be found. {/show_404} {flatpage_body} %contents% {/flatpage_body} {/contents} """ examples["emulate.templayer"] = ["example_templayer_pages"] def example_templayer_pages(): import templayer tmpl = templayer.get_django_template("emulate.templayer.html") @templayer.django_template(tmpl, "404.html") def show_404(file_writer, context, request_path): contents = file_writer.open(title="Page not found") # request_path == context['request_path'] contents.write_layer("show_404", url=request_path) @templayer.django_template(tmpl, "flatpages/default.html") def show_flatpage(file_writer, context, flatpage): contents = file_writer.open(title=flatpage.title) fp_contents = contents.open_layer("flatpage_body") # the flatpage content is HTML, so don't escape it. fp_contents.write(templayer.RawHTML(flatpage.content)) def read_sections(tmpl): """Read section tags, section descriptions, and column breaks from the Templayer template argument. Convert the section data into a Python data structure called sections. Each sublist of sections contains one column. Each column contains a list of (tag, desc.) pairs. Return sections.""" sd = tmpl.layer("section_data") col_break = "---" sections = [[]] for ln in sd.split("\n"): if not ln: continue if ln == col_break: sections.append([]) continue tag, desc = ln.split("\t",1) sections[-1].append( (tag, templayer.RawHTML(desc)) ) return sections def read_example_code(): """By the time this function runs, the examples dictionary contains a list of function names, all starting with "example_". Create a second dictionary called code_blocks. Open the file containing this function. For each function name in examples, read the text of that function into an entry in the code_blocks dictionary. Return the code_blocks dictionary.""" # invert the "examples" dictionary example_fns = {} for tag, l in examples.items(): for i, fn in zip(range(len(l)), l): example_fns[fn] = tag, i # read our own source code # strip trailing spaces and tabs from each line code_blocks = {} current_block = None for ln in open( sys.argv[0], 'r').readlines(): ln = ln.rstrip() if ( ln[:4] == "def " and ln[-3:] == "():" and example_fns.has_key( ln[4:-3] ) ): current_block = ln[4:-3] code_blocks[current_block] = [] continue if ln and ln[:1] != "\t": current_block = None continue if current_block is None: continue if ln[:1] == "\t": ln = ln[1:] code_blocks[current_block].append(ln+"\n") # recombine code lines into a single string each for name, block in code_blocks.items(): code_blocks[name] = "".join( block ) return code_blocks def write_example_files(sections, blocks): for t in template.keys(): for e in examples.get(t, []): assert e.startswith("example_") open(e[8:]+".py", "w").write(blocks[e]) open(t+".html", "w").write(template[t].lstrip()) class SimulatedOutput(object): output = [] def write(self, s): self.output.append(s) def flush(self): pass class NullOutput(object): def write(self, s): pass def get_simulated_output(): s = "".join(SimulatedOutput.output) del SimulatedOutput.output[:] return s class SimulatedHTMLTemplate(templayer.HTMLTemplate): def __init__(self, name): name = name[:-len(".html")] t = template[name] head, t = t.split("",1) t, tail = t.split("",1) t = t.strip() self.encoding = "utf_8" self.set_template_data(template[name].decode(self.encoding)) def start_file(self, file=None): return templayer.FileLayer(SimulatedOutput(), self) def generate_results(): """ Capture the output from the examples and return a dictionary containing one result for each section that needs one. """ def get_result(fn): # divert the output to our simulated version orig_tmpl = templayer.HTMLTemplate orig_stdout = sys.stdout templayer.HTMLTemplate = SimulatedHTMLTemplate sys.stdout = NullOutput() fn() # restore the original values templayer.HTMLTemplate = orig_tmpl sys.stdout = orig_stdout return get_simulated_output() r = {} r['lawn1'] = [get_result(example_lawn2)] r['lawn3'] = [get_result(example_lawn3)] r['lawn4'] = [get_result(example_lawn4b), get_result(example_lawn4c)] return r def generate_body(tmpl, sections, blocks): # put TOC columns into the variables used by the template # assign section numbers # generate HTML form of TOC entries, corresponding document parts assert len(sections) == 2, 'sections has %d columns but should have 2!' % len(sections) toc_slots = {'toc_left':[], 'toc_right':[]} body = [] results = generate_results() snum = inum = 0 for slot, l in zip(['toc_left','toc_right'], sections): for tag, name in l: if not tag: # new section -- do its first item next time snum += 1 inum = 0 t = tmpl.format('toc_section', snum=`snum`, name=name ) toc_slots[slot].append( t ) b = tmpl.format('section_head', snum=`snum`, name=name ) body.append( b ) continue # new item inside a section inum += 1 t = tmpl.format('toc_item', snum=`snum`, inum=`inum`, name=name, tag=tag) toc_slots[slot].append( t ) slots = {} slots['html'] = tmpl.format('html_example', name = tag+".html", contents = template[tag]) i = 0 for fn in examples.get(tag, []): c = tmpl.format('code_example', name = fn[len("example_"):]+".py", contents = blocks[fn]) slots['code[%d]'%i] = c i += 1 i = 0 for r in results.get(tag, []): c = tmpl.format('result', contents = templayer.RawHTML(r)) slots['result[%d]'%i] = c i += 1 b = tmpl.format('body[%s]'%tag, ** slots ) b = tmpl.format('section_body', snum=`snum`, inum=`inum`, name=name, tag=tag, contents = b) body.append( b ) return (body, toc_slots) def parse_options(): usage = "%s [-h|-?|--help]\n%s [-H|--HTML|--html] [-s|--scripts]" % \ (sys.argv[0], sys.argv[0]) help = """%s options: -h, -?, --help Print this message to standard error and exit. -H, --HTML, --html Write the HTML documentation to standard output. -s, --scripts Write runnable scripts to files.""" % sys.argv[0] do_html = False do_scripts = False if len(sys.argv) < 2 or len(sys.argv) > 3: sys.exit(usage) if len(sys.argv) == 2 and (sys.argv[1] in ('-h', '-?', '--help')): sys.exit(help) for arg in sys.argv[1:]: if arg in ('-H', '--HTML', '--html'): if do_html: sys.exit(usage) else: do_html = True elif arg in ('-s', '--scripts'): if do_scripts: sys.exit(usage) else: do_scripts = True else: sys.exit(usage) return (do_html, do_scripts) def main(): (do_html, do_scripts) = parse_options() tmpl = templayer.HTMLTemplate( os.path.join(os.path.dirname(sys.argv[0]), "tmpl_tutorial.html")) sections = read_sections( tmpl ) code_blocks = read_example_code() if do_scripts: write_example_files( sections, code_blocks ) if do_html: out_file = tmpl.start_file() (body, toc_slots) = generate_body( tmpl, sections, code_blocks ) bottom = out_file.open(version=templayer.__version__, ** toc_slots) bottom.write( body ) out_file.close() if __name__=="__main__": main() templayer-1.5.1/tmpl_tutorial.html0000644000175000017500000003712311150315456015414 0ustar ianian Templayer %version% Tutorial

Templayer %version% Tutorial

Templayer Home Page / Tutorial / Reference

%toc_left% %toc_right%
{contents}

Templayer Tutorial Template File

This file is used by docgen_tutorial.py to generate the tutorial documentation tutorial.html.


Items in the list that follows are parsed by docgen_tutorial.py. Each item has a tag and a name, separated by a tab character. Items without tags are new sections. A --- separates the left and right columns in the table of contents.
Tag	Section or Item Name
{section_data}
	CGI Script: Gordon's Lawn Happenings
lawn1	Static HTML (before using Templayer)
lawn2	Minimal Templayer Template
lawn3	Using Slots
lawn4	Using a Simple Layer
lawn5	Using Nested Layers
lawn6	Advanced Nested Layers
---
	Django Applications and Templayer
simple.templayer	Simple Views
book.templayer	Forms
emulate.templayer	Emulating Django Templates
{/section_data}

{toc_section}

%snum%. %name%
{/toc_section}

{toc_item}

%snum%.%inum%. %name%
{/toc_item}

{section_head}

%snum%. %name%

{/section_head}
{section_body}

%snum%.%inum%. %name% [back to top]

%contents%

{/section_body}
{html_example}
%name%
%contents%
{/html_example}
{code_example}
%name%
%contents%
{/code_example}
{result}
%contents%
{/result}
{body[lawn1]}

Our examples will be based on a single-page web site:

%html% %result[0]%

All the example source code may be extracted from the docgen_tutorial.py script that is included with Templayer by running:

python docgen_tutorial.py -s

{/body[lawn1]}
{body[lawn2]}

The first thing we need to do to use this page as a Templayer template file is to add "{contents}" and "{/contents}" labels to the file:

%html%

Templayer searches the template file for the first occurrence of "{contents}" and the last occurrence of "{/contents}". This splits the file into header, contents and footer.

The header and footer will appear in Templayer's output. The contents is described in sections below.

%code[0]%

This is a minimal CGI script that uses Templayer to display the web site. The result of running this script will be the same as if we just used the static page above.

We first create an HTMLTemplate object to handle parsing of the template above.

The start_file function creates a FileWriter object that will use the our HTMLTemplate object. The FileWriter object handles a single output run for our template. The start_file function allows you to pass in a file-like object to use instead of the standard output. For a CGI script we use the default.

The FileWriter object's open function will be explained below.

No output is written until the close function is called at the end of the script. If you don't get any output from your script it might be because you forgot to call the close function on your FileWriter object.

{/body[lawn2]}
{body[lawn3]}

The next step is to add slots to the header and footer of the template. Slots are like variables that can be filled in by the application. In the template file slot names are wrapped in "%" characters:

%html%

We have added two %title% slots to the header and one %date% slot to the footer of this template.

Please note: "%" is not a special character in Templayer templates. You do not need to escape "%" characters that aren't part of a slot. When filling slots Templayer has a list of slot names and it only replaces those slots, leaving all the other "%" characters in the template intact.

%code[0]%

Essentially only one line has changed in the code. The open function takes keyword parameters where the names of the parameters correspond to slots in the header and footer. Here both %title% slots will be filled with "Gordon's Lawn Happenings", and the %date% slot will be filled with the current date and time.

The values passed in to fill slots are interpreted by our HTMLTemplate object. The HTMLTemplate object expects HTML Markup described by the expand_html_markup function. Plain strings are treated as unsafe in HTML Markup and any characters that might be interpreted as HTML are escaped by this function. This means that Templayer's default behaviour is to escape values being filled into HTML templates.

%result[0]% {/body[lawn3]}
{body[lawn4]}

Now we look at what we can do with the content of a template file. You will recall that this refers to everything between the first occurrence of "{contents}" and the last occurrence of "{/contents}" in the template file.

%html%

We have now added a {report} layer to the template, and we have removed the "Lawn Happenings" from the template so that the application can fill them in. Layers are placed one after the next in the template content. The {report} layer extends from the first occurrence of "{report}" to the last occurrence of "{/report}" in the template content. Layers have slots just like the template header and footer.

Splitting HTML into separate layers lets us reuse the layers throughout the generated HTML, consolidating duplicated layout information. This template format can also be loaded in a web browser to test changes without running the application. Most templates can also be validated for compliance with HTML and CSS specs.

%code[0]%

We are now storing the return value of our FileWriter object's open function. The return value is a Layer object that represents the whole file. It knows the value of the header and footer which were filled in the FileWriter object's open function, but it can still have text filled into its content between the header and footer.

Layer objects have a write_layer function that is passed a layer name followed by keyword parameters that correspond to slots in that layer. Here we are adding two instances of our {report} layer to the content of the template's main Layer object. Recall that values passed in to fill slots are interpreted as HTML Markup by the expand_html_markup function. The "happenings" lists are escaped, then concatenated by this function.

Of course, we can separate the "Lawn Happenings" data from the code by creating a structure for it:

%code[1]%

This structure is a list of (day, happenings) tuples that is iterated through in the script. The day and happenings values are treated as HTML Markup

%result[0]%

Unfortunately, the HTML code that this generates is different than before we added the {report} layer — the "Happenings" are no longer in separate paragraphs. One way to fix this is to use HTML Markup and avoid mixing actual HTML into the code:

%code[2]%

The expand_html_markup function will take the ('p', text) tuples, escape the text, wrap them in HTML paragraph tags then concatenate them.

%result[1]% {/body[lawn4]}
{body[lawn5]}

HTML Markup is not intended for anything beyond very simple formatting. A more flexible solution is to create another layer to nest within the first.

%html%

Now the decision to format individual "Happenings" as paragraphs has been moved to the template in a {happening} layer.

%code[0]%

Within the new inner loop we are calling our HTMLTemplate object's format function. This function is similar to the write_layer function except that it returns a RawHTML object instead of adding it to the content of a Layer object. The expand_html_markup function will leave the contents of the RawHTML object intact, so we can use this object within HTML Markup.

{/body[lawn5]}
{body[lawn6]}

Since it is common to nest layers in Templayer another method is provided that is similar to FileWriter's open function. First we need to rename a slot in the {report} to %contents%:

%html%

Now that we have a %contents% slot we can "open" this layer and write into it:

%code[0]%

The Layer object has an open_layer function that returns a new layer object. In this case the new Layer object represents a {report} layer that is being written. The Layer object stores everything above the %contents% slot as its header and everything below as its footer. It can have text filled into its content in the same way as our main Layer object. Here we are now using write_layer to fill {happening} layers into {report} layers.

We also renamed the slot in {happening} to %contents%, so we can use the open_layer function on that as well:

%code[1]%

We want to send text into this new Layer object, so instead of calling write_layer we call the write function. It takes a single parameter that is interpreted as HTML Markup.

When using Templayer to produce very large files, or when parts of an HTML page take longer to complete, you might want to flush your output part of the way through:

%code[2]%

Layer objects have a close_child function that forces any open "child" layers to finalize their output. In this code we are closing the {report} layer. FileWriter objects have a flush function that will send all the output possible. We are first closing our {report} layer so that when we flush the output the whole report will appear.

{/body[lawn6]}
{body[simple.templayer]}

Templayer has a number of functions that help integrate with the Django Web Framework. This is an example of a simple Django view. All the examples below assume you are starting from a working project. See the Django documentation about setting up a project.

First place this Templayer template in your project's template directory:

%html%

Then we can use get_django_template function to find the template in the project's template directory without having to hard-code the system path in our view:

%code[0]%

For this type view function you may also use the django_view decorator. It takes care of creating and cleaning up the file writer object so that you don't need to "import HttpResponse" or remember to "return file_writer.close()" in every view function:

%code[1]% {/body[simple.templayer]}
{body[book.templayer]}

Templayer includes a django_form helper function for working with forms. This function converts a form object to a dictionary of fields suitable for passing to the format, open_layer or write_layer functions:

Start with models like the following:

%code[0]%

This template has two slots for each visible field in the form, one for the HTML input field and one for errors related to that field.

%html%

This code will populate the form widgets, accept input and redisplay the data (with errors if applicable) when the user clicks "Submit":

%code[1]%
Guest Book

Sign the guestbook

Your Name:
Rate the site:
Your Comments:

{/body[book.templayer]} {body[emulate.templayer]}

Existing or third-party Django applications will expect to use the standard Django templating library. To use these applications with Templayer we need to extend the Django template loader and processing mechanism to emulate Django's templates.

This example will show how to use Templayer to create a 404 page and use the "flatpages" application.

First add the django_template_loader to the TEMPLATE_LOADERS in your project's settings.py file:

Your Django project's settings.py
...
TEMPLATE_LOADERS = (
    'django.template.loaders.filesystem.load_template_source',
    'django.template.loaders.app_directories.load_template_source',
...
    'templayer.django_template_loader',
)
...

Also make sure that the "flatpages" application is installed. See the Django flatpages app documentation for details.

Next copy this template into the project's template directory:

%html%

Templayer's Django template emulation will look for a module named "templayer_pages.py" in the template directories when trying to load templates. The django_template decorator defines the Django template name that a function will handle. The function may take parameters with names matching values you know will be present in the context dictionary and the decorator will fill in those values for you.

Put this module in the template directory:

%code[0]%

Now the 404 handler and the flatpages application will be formatted by Templayer.

{/body[emulate.templayer]}
{/contents} templayer-1.5.1/docgen_reference.py0000755000175000017500000001126211143361433015453 0ustar ianian#!/usr/bin/python """ Reference documentation generator for Templayer http://excess.org/templayer/ library to build dynamic html that provides clean separation of form (html+css+javascript etc..) and function (python code) as well as making cross-site scripting attacks and improperly generated html very difficult. Copyright (c) 2003-2009 Ian Ward 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. """ import pydoc import types import templayer html_template = """ Templayer %version% Reference

Templayer %version% Reference

Templayer Home Page / Tutorial / Reference

%toc%
%contents% """ class TemplayerHTMLDoc( pydoc.HTMLDoc ): def heading(self, title, fgcol, bgcol, extras=''): return extras def section(self, title, fgcol, bgcol, contents, width=6, prelude='', marginalia=None, gap=' '): if " = " in title: visible, tail = title.split(" = ",1) aname = tail.split('">',1)[0] aname = aname.split('"',1)[1] aname = aname.replace(" ","_") title = ''+visible return '

%s [back to top]

%s' % (title,contents) def namelink(self, name, *ignore): return name def classlink(self, obj, modname): return obj.__name__ def modulelink(self, obj): return obj.__name__ def modpkglink(self, (name, path, ispackage, shadowed) ): return name def markup(self, text, escape=None, funcs={}, classes={}, methods={}): return pydoc.HTMLDoc.markup( self, text, escape ) def main(): html = TemplayerHTMLDoc() contents = [] doc = [] contents.append('
') for obj, name in [ (None,"Template classes"), (templayer.Template,"Template"), (templayer.HTMLTemplate,"HTMLTemplate"), (None,"Output classes"), (templayer.FileWriter,"FileWriter"), (templayer.Layer,"Layer"), (None,"Django integration"), (templayer.django_form,"django_form"), (templayer.django_template_loader,"django_template_loader"), (templayer.django_template,"django_template"), (templayer.django_view,"django_view"), (templayer.get_django_template,"get_django_template"), (None,"Utility functions"), (templayer.html_url_encode,"html_url_encode"), (templayer.html_href,"html_href"), (templayer.html_target,"html_target"), (templayer.html_escape,"html_escape"), (None,None), (None,"HTML Markup (yes, markup language markup)"), (templayer.join,"join"), (templayer.pluralize,"pluralize"), (templayer.urljoin,"urljoin"), (templayer.href,"href"), (templayer.target,"target"), (templayer.BR,"BR"), (templayer.P,"P"), (templayer.I,"I"), (templayer.B,"B"), (templayer.U,"U"), (templayer.entity,"entity"), (templayer.expand_html_markup,"expand_html_markup"), (templayer.RawHTML,"RawHTML"), ]: if name is None: contents.append('') elif obj is None: contents.append('
%s
' % name) doc.append('

%s

' % name ) else: lname = name if type(obj) != types.ClassType: #dirty hack doc.append('

function %s [back to top]

' % (name,name) ) lname = lname.replace(" ","_") contents.append('
' + '%s
' % (lname,name) ) doc.append( html.document( obj, name ) ) contents.append("
") h = html_template h = h.replace("%toc%", "".join(contents)) h = h.replace("%contents%", "".join(doc)) h = h.replace("%version%", templayer.__version__) print h if __name__ == "__main__": main() templayer-1.5.1/Makefile0000644000175000017500000000223311150315456013261 0ustar ianian RELEASE:=$(shell python -c 'import templayer; print templayer.__version__') SOURCE:=setup.py templayer.py docgen_tutorial.py tmpl_tutorial.html \ docgen_reference.py Makefile DOCS:= docs/reference.html docs/tutorial.html docs/tutorial_examples.tar.gz dist: templayer-$(RELEASE).tar.gz .PHONY: dist templayer-$(RELEASE).tar.gz: $(SOURCE) $(DOCS) tar czvf $@ --transform "s{{templayer-$(RELEASE)/{" $(SOURCE) $(DOCS) docs: mkdir $@ docs/reference.html: docgen_reference.py templayer.py docs # make sure it runs properly before touching target python $< > $@.incoming mv $@.incoming $@ docs/tutorial.html: docgen_tutorial.py templayer.py tmpl_tutorial.html docs # make sure it runs properly before touching target python $< -H > $@.incoming mv $@.incoming $@ docs/tutorial_examples: docs mkdir $@ docs/tutorial_examples.tar.gz: docgen_tutorial.py templayer.py \ tmpl_tutorial.html docs/tutorial_examples ( cd docs/tutorial_examples && python ../../$< -s ) ( cd docs && tar czvf tutorial_examples.tar.gz tutorial_examples ) clean: for fn in docs build templayer.pyc templayer-*.tar.gz; do \ if [ -e "$$fn" ]; then rm -r "$$fn"; fi \ done .PHONY: clean templayer-1.5.1/docs/reference.html0000644000175000017500000010774211155033621015404 0ustar ianian Templayer 1.5.1 Reference

Templayer 1.5.1 Reference

Templayer Home Page / Tutorial / Reference

Template classes
Output classes
Django integration
Utility functions
HTML Markup (yes, markup language markup)

Template classes

function Template [back to top]

class Template(object) [back to top]

Methods defined here:
__call__ = format(self, layer_name, **args)
__init__(self, name, from_string=False, auto_reload='never', allow_degenerate=False, encoding='utf_8')
name -- template file name or file-like object
from_string -- if True treat name as a string containing the
               template data instead of a file to read from
auto_reload -- if the template has not been checked in this
               many seconds then check it and reload if there
               are any changes.  If 'never' only load the 
               template file once.
 
               Checking and reloading is done by the 
               start_file() function.
               
               Ignored if from_string is True or if name is a 
               file-like object.
allow_degenerate -- If True then template is allowed to not
               have a {contents}...{/contents} block.  In this
               case the entire file is available as a layer
               called '*'.  eg. l = tmpl.format('*', ...)
encoding -- encoding for template file
finalize(self, value)
Return unwrapped value as a string.
 
Override this function to reverse wrapping applied before
sending to the output file.
format(self, layer_name, **args)
Return a layer with slots filled by args.
 
format(...) and self(...) are equivalent.
format_header_footer(self, **args)
Returns header and footer with slots filled by args.
layer(self, name)
Return the complete layer named name as a string.
layer_split(self, name)
Return the layer named name split on SLOT_MARKs.
missing_slot(self, names, layer)
Called when one or more slots are not found in a layer.
post_process(self, value)
Returns wrapped value.
 
Override to wrap processed text in order to mark it as 
having been processed.
pre_process(self, key, value)
Returns key, filtered/escaped value.
 
Override this function to provide escaping or filtering of
text sent from the module user.
reload_template(self)
Reload the template data from the template file.
set_template_data(self, tmpl)
Parse and store the template data passed.
start_file(self, file=None, encoding='utf_8')
Return a FileWriter object that uses this template.
 
file -- file object or None to use sys.stdout.
 
If self.auto_reload is not None this function will first 
check and reload the template file if it has changed.
template_file_changed(self)
Return True if the template file has been modified since
the last time it was loaded.

Data descriptors defined here:
__dict__
dictionary for instance variables (if defined)
__weakref__
list of weak references to the object (if defined)

function HTMLTemplate [back to top]

class HTMLTemplate(Template) [back to top]

Method resolution order:
HTMLTemplate
Template
object

Methods defined here:
finalize(self, value)
Unwrap RawHTML object value for output.
post_process(self, value)
Wrap value in RawHTML object.
pre_process(self, key, value)
Use expand_html_markup to process value.

Methods inherited from Template:
__call__ = format(self, layer_name, **args)
Return a layer with slots filled by args.
 
format(...) and self(...) are equivalent.
__init__(self, name, from_string=False, auto_reload='never', allow_degenerate=False, encoding='utf_8')
name -- template file name or file-like object
from_string -- if True treat name as a string containing the
               template data instead of a file to read from
auto_reload -- if the template has not been checked in this
               many seconds then check it and reload if there
               are any changes.  If 'never' only load the 
               template file once.
 
               Checking and reloading is done by the 
               start_file() function.
               
               Ignored if from_string is True or if name is a 
               file-like object.
allow_degenerate -- If True then template is allowed to not
               have a {contents}...{/contents} block.  In this
               case the entire file is available as a layer
               called '*'.  eg. l = tmpl.format('*', ...)
encoding -- encoding for template file
format(self, layer_name, **args)
Return a layer with slots filled by args.
 
format(...) and self(...) are equivalent.
format_header_footer(self, **args)
Returns header and footer with slots filled by args.
layer(self, name)
Return the complete layer named name as a string.
layer_split(self, name)
Return the layer named name split on SLOT_MARKs.
missing_slot(self, names, layer)
Called when one or more slots are not found in a layer.
reload_template(self)
Reload the template data from the template file.
set_template_data(self, tmpl)
Parse and store the template data passed.
start_file(self, file=None, encoding='utf_8')
Return a FileWriter object that uses this template.
 
file -- file object or None to use sys.stdout.
 
If self.auto_reload is not None this function will first 
check and reload the template file if it has changed.
template_file_changed(self)
Return True if the template file has been modified since
the last time it was loaded.

Data descriptors inherited from Template:
__dict__
dictionary for instance variables (if defined)
__weakref__
list of weak references to the object (if defined)

Output classes

function FileWriter [back to top]

class FileWriter(object) [back to top]

Methods defined here:
__init__(self, file, template, encoding='utf_8')
file -- output stream or None to use sys.stdout
template -- template object for generating this file
 
This constructor is usually not called directly.  Use
template.start_file() to create a FileWriter instead.
close(self)
Close all child layers and flush the output to the output 
stream.  This function must be called to generate a complete
file.
 
Returns the file object where output was sent.
flush(self)
Flush as much output as possible to the output stream.
open(self, **args)
Return a new layer representing the content between the
header and footer.  Use keyword arguments to fill the
slots in the header and footer.

Data descriptors defined here:
__dict__
dictionary for instance variables (if defined)
__weakref__
list of weak references to the object (if defined)

function Layer [back to top]

class Layer(object) [back to top]

Methods defined here:
__init__(self, template, header, footer)
template -- template object for generating this layer
header -- text before the content of this layer
footer -- text after the content of this layer
 
This constructor should not be called directly.  Use
the open() function in a FileWriter or the open_layer()
function in another Layer object to create a new Layer.
close(self)
Close this layer and return its output.
 
This function should not be called directly.  Use the
close() function in the FileWriter object instead.
close_child(self)
If we have an open child layer close it and add its
output to our own.
flush(self)
Flush as much output as possible and return it.
 
This function should not be called directly.  Use the
flush() function in the FileWriter object instead.
open_layer(self, layer, **args)
layer -- name of layer in the template to open
Use keyword arguments to fill this layer's slots.
 
open_layer( layer name, ** args ) -> child Layer object
 
open a layer as a child of this one, filling its slots with
values from args
write(self, text)
text -- text or markup as interpreted by the template
write_layer(self, layer, **args)
layer -- name of layer in the template to write
Use keyword arguments to fill this layer's slots.

Data descriptors defined here:
__dict__
dictionary for instance variables (if defined)
__weakref__
list of weak references to the object (if defined)

Django integration

function django_form [back to top]

django_form(form, errors=True, join_errors=<function <lambda> at 0x2b15f74d78c0>)
Converts a django FormWrapper object to a dictionary that
can be used by Templayer.  Each field is rendered into a
%form.NAME% slot.
 
If errors is True then this function will also render
errors into %form.NAME.errors% slots.
 
When there is more than one error on a single field the
errors will be joined with the join_errors function.
The default join_errors function puts a newline between each
error.
 
eg:
If tmpl is an HTMLTemplate object with a {form_layer} layer
and form is a FormWrapper object with username and password
fields, then this code will fill the {form_layer} layer's 
%title%, %form.username%, %form.password%, 
%form.username.errors% and %form.password.errors% slots:
 
tmpl.format('form_layer', title="hello", **django_form(form))

function django_template_loader [back to top]

django_template_loader(template_name, template_dirs=None)
This function should be included in settings.TEMPLATE_LOADERS when
using the django_template decorator.
 
eg:
TEMPLATE_LOADERS += ['templayer.django_template_loader']

function django_template [back to top]

django_template(tmpl, template_name)
This is a decorator for functions you want to make behave like 
standard Django templates.  When django_template_loader is 
included in settings.TEMPLATE_LOADERS Django will find the 
decorated function when looking for template_name.
 
tmpl is the Templayer template object that will be used to create
the file layer passed to the decorated function.
 
The decorated function must have a signature like:
@templayer.django_template(tmpl, "appname/somepage.html")
def mock_template(fwriter, context, optional1, optional2 ...)
 
Where fwriter is a new FileWriter object, context is a context
instance and all parameters that follow will have their values
copied from the context based on the name of the parameters.
 
The function must call fwriter.open(..) to render the page then 
return the fwriter object (or None) to send it to the client.  
fwriter.close() is called automatically.  Other objects returned 
or exceptions raised will be passed through to Django.

function django_view [back to top]

django_view(tmpl)
This is a decorator for Django view functions that will handle
creation of the file layer object from the template tmpl.
 
The function must have a signature like:
@templayer.django_view(tmpl)
def decorated_view(fwriter, request, ...)
 
fwriter is the new FileWriter object and the rest of the parameters
are the same as what was passed to the view.  
 
The function must call fwriter.open(..) to render the page then 
return the fwriter object (or None) to send it to the client.  
fwriter.close() is called automatically.  Other objects returned 
or exceptions raised will be passed through to Django.

function get_django_template [back to top]

get_django_template(name, auto_reload='debug', allow_degenerate=False, encoding='utf_8')
Use Django's template loading machinery to create an
HTMLTemplate object.
 
auto_reload -- when set to 'debug' will auto-reload only when 
        settings.DEBUG is set to True

Utility functions

function html_url_encode [back to top]

html_url_encode(text, query=False)
text -- text to be included as part of a URL
query -- if True use "+" instead ot "%20" for spaces

function html_href [back to top]

html_href(link, value)
Return '<a href="link">value</a>'.
 
Neither link nor value is escaped by this function.

function html_target [back to top]

html_target(target, caption)
Return '<a name="target">caption</a>'.
 
Neither target nor caption is escaped by this function.

function html_escape [back to top]

html_escape(text)
text -- text to be escaped for inclusion within HTML.

HTML Markup (yes, markup language markup)

function join [back to top]

join(markup_list, separator)
HTML Markup like markup_list.join(separator)

function pluralize [back to top]

pluralize(count, sigular_content, plural_content)
HTML Markup (singular_content if count==1 else plural_content)

function urljoin [back to top]

urljoin(head, tail)
HTML Markup join safe url head with unsafe tail

function href [back to top]

href(link, content)
HTML Markup <a href="link">content</a>

function target [back to top]

target(name)
HTML Markup <a name="name"></a>

function BR [back to top]

BR(count=1)
HTML Markup <br/> * count

function P [back to top]

P(content='')
HTML Markup <p>content</p>

function I [back to top]

I(content)
HTML Markup <i>content</i>

function B [back to top]

B(content)
HTML Markup <b>content</b>

function U [back to top]

U(content)
HTML Markup <u>content</u>

function entity [back to top]

entity(entity)
HTML Markup &entity;

function expand_html_markup [back to top]

expand_html_markup(v)
Return an HTML string based on markup v.
 
HTML markup is expanded recursively. Each of the content
values below are passed to expand_html_markup again before 
applying the operation on the right:
 
string or unicode string    ->  HTML-escaped version of string
[content1, content2, ...]   ->  concatenate content1, content2, ...
('join',list,sep)     ->  join items in list with seperator sep
('pluralize',count,singular_content,plural_content)
                      ->  if count == 1 use singular_content,
                          otherwise use plural_content
('urljoin',head,tail) ->  join safe url head with unsafe url-ending tail
('href',link,content) ->  HTML href to link wrapped around content
('target',name)       ->  HTML target name
('br',)               ->  HTML line break
('br',count)          ->  HTML line break * count
('p',)                ->  HTML paragraph break
('p',content)         ->  HTML paragraph
('i',content)         ->  italics
('b',content)         ->  bold
('u',content)         ->  underline
('&',entity)          ->  HTML entity (entity has no & or ;)
RawHTML(value)        ->  value unmodified

function RawHTML [back to top]

class RawHTML(object) [back to top]

Methods defined here:
__init__(self, value)
value -- segment of HTML as a string
__repr__(self)

Data descriptors defined here:
__dict__
dictionary for instance variables (if defined)
__weakref__
list of weak references to the object (if defined)
templayer-1.5.1/docs/tutorial.html0000644000175000017500000007344711155033621015315 0ustar ianian Templayer 1.5.1 Tutorial

Templayer 1.5.1 Tutorial

Templayer Home Page / Tutorial / Reference

1. CGI Script: Gordon's Lawn Happenings
2. Django Applications and Templayer

1. CGI Script: Gordon's Lawn Happenings

1.1. Static HTML (before using Templayer) [back to top]

Our examples will be based on a single-page web site:

lawn1.html
<html>
<head><title>Gordon's Lawn Happenings</title></head>
<body>
<h1>Gordon's Lawn Happenings</h1>
<h3>Sunday</h3>
<p>We've got a groundhog.  I will have to stay alert.</p>
<p>I lost half a tomato plant to that furry guy.</p>
<h3>Monday</h3>
<p>The grass grew - I saw it.</p>
</body>
</html>
Gordon's Lawn Happenings

Gordon's Lawn Happenings

Sunday

We've got a groundhog. I will have to stay alert.

I lost half a tomato plant to that furry guy.

Monday

The grass grew - I saw it.

All the example source code may be extracted from the docgen_tutorial.py script that is included with Templayer by running:

python docgen_tutorial.py -s



1.2. Minimal Templayer Template [back to top]

The first thing we need to do to use this page as a Templayer template file is to add "{contents}" and "{/contents}" labels to the file:

lawn2.html
<html>
<head><title>Gordon's Lawn Happenings</title></head>
<body>
<h1>Gordon's Lawn Happenings</h1>
{contents}

{/contents}
<h3>Sunday</h3>
<p>We've got a groundhog.  I will have to stay alert.</p>
<p>I lost half a tomato plant to that furry guy.</p>
<h3>Monday</h3>
<p>The grass grew - I saw it.</p>
</body>
</html>

Templayer searches the template file for the first occurrence of "{contents}" and the last occurrence of "{/contents}". This splits the file into header, contents and footer.

The header and footer will appear in Templayer's output. The contents is described in sections below.

lawn2.py
import templayer
import sys

sys.stdout.write("Content-type: text/html\r\n\r\n")

tmpl = templayer.HTMLTemplate("lawn2.html")
file_writer = tmpl.start_file()
file_writer.open()
file_writer.close()

This is a minimal CGI script that uses Templayer to display the web site. The result of running this script will be the same as if we just used the static page above.

We first create an HTMLTemplate object to handle parsing of the template above.

The start_file function creates a FileWriter object that will use the our HTMLTemplate object. The FileWriter object handles a single output run for our template. The start_file function allows you to pass in a file-like object to use instead of the standard output. For a CGI script we use the default.

The FileWriter object's open function will be explained below.

No output is written until the close function is called at the end of the script. If you don't get any output from your script it might be because you forgot to call the close function on your FileWriter object.



1.3. Using Slots [back to top]

The next step is to add slots to the header and footer of the template. Slots are like variables that can be filled in by the application. In the template file slot names are wrapped in "%" characters:

lawn3.html
<html>
<head><title>%title%</title></head>
<body>
<h1>%title%</h1>
{contents}

{/contents}
<h3>Sunday</h3>
<p>We've got a groundhog.  I will have to stay alert.</p>
<p>I lost half a tomato plant to that furry guy.</p>
<h3>Monday</h3>
<p>The grass grew - I saw it.</p>

<hr>
<p>Generated on %date%.</p>
</body>
</html>

We have added two %title% slots to the header and one %date% slot to the footer of this template.

Please note: "%" is not a special character in Templayer templates. You do not need to escape "%" characters that aren't part of a slot. When filling slots Templayer has a list of slot names and it only replaces those slots, leaving all the other "%" characters in the template intact.

lawn3.py
import templayer
import time
import sys

sys.stdout.write("Content-type: text/html\r\n\r\n")

tmpl = templayer.HTMLTemplate("lawn3.html")
file_writer = tmpl.start_file()
file_writer.open(title="Gordon's Lawn Happenings",
		date=time.asctime())
file_writer.close()

Essentially only one line has changed in the code. The open function takes keyword parameters where the names of the parameters correspond to slots in the header and footer. Here both %title% slots will be filled with "Gordon's Lawn Happenings", and the %date% slot will be filled with the current date and time.

The values passed in to fill slots are interpreted by our HTMLTemplate object. The HTMLTemplate object expects HTML Markup described by the expand_html_markup function. Plain strings are treated as unsafe in HTML Markup and any characters that might be interpreted as HTML are escaped by this function. This means that Templayer's default behaviour is to escape values being filled into HTML templates.

Gordon's Lawn Happenings

Gordon's Lawn Happenings

Sunday

We've got a groundhog. I will have to stay alert.

I lost half a tomato plant to that furry guy.

Monday

The grass grew - I saw it.


Generated on Sun Mar 8 17:24:33 2009.



1.4. Using a Simple Layer [back to top]

Now we look at what we can do with the content of a template file. You will recall that this refers to everything between the first occurrence of "{contents}" and the last occurrence of "{/contents}" in the template file.

lawn4.html
<html>
<head><title>%title%</title></head>
<body>
<h1>%title%</h1>
{contents}

{report}
<h3>%day%</h3>
%happenings%
{/report}

{/contents}
<hr>
<p>Generated on %date%.</p>
</body>
</html>

We have now added a {report} layer to the template, and we have removed the "Lawn Happenings" from the template so that the application can fill them in. Layers are placed one after the next in the template content. The {report} layer extends from the first occurrence of "{report}" to the last occurrence of "{/report}" in the template content. Layers have slots just like the template header and footer.

Splitting HTML into separate layers lets us reuse the layers throughout the generated HTML, consolidating duplicated layout information. This template format can also be loaded in a web browser to test changes without running the application. Most templates can also be validated for compliance with HTML and CSS specs.

lawn4a.py
import templayer
import time
import sys

sys.stdout.write("Content-type: text/html\r\n\r\n")

tmpl = templayer.HTMLTemplate("lawn4.html")
file_writer = tmpl.start_file()
main_layer = file_writer.open(title="Gordon's Lawn Happenings",
	date=time.asctime())
main_layer.write_layer('report', day="Sunday", happenings=[
	"We've got a groundhog.  I will have to stay alert.",
	"I lost half a tomato plant to that furry guy."])
main_layer.write_layer('report', day="Monday", happenings=[
	"The grass grew - I saw it."])
file_writer.close()

We are now storing the return value of our FileWriter object's open function. The return value is a Layer object that represents the whole file. It knows the value of the header and footer which were filled in the FileWriter object's open function, but it can still have text filled into its content between the header and footer.

Layer objects have a write_layer function that is passed a layer name followed by keyword parameters that correspond to slots in that layer. Here we are adding two instances of our {report} layer to the content of the template's main Layer object. Recall that values passed in to fill slots are interpreted as HTML Markup by the expand_html_markup function. The "happenings" lists are escaped, then concatenated by this function.

Of course, we can separate the "Lawn Happenings" data from the code by creating a structure for it:

lawn4b.py
import templayer
import time
import sys

sys.stdout.write("Content-type: text/html\r\n\r\n")

reports = [
	('Sunday', ["We've got a groundhog.  I will have to stay alert.",
	"I lost half a tomato plant to that furry guy."]),
	('Monday', ["The grass grew - I saw it."]),
]

tmpl = templayer.HTMLTemplate("lawn4.html")
file_writer = tmpl.start_file()
main_layer = file_writer.open(title="Gordon's Lawn Happenings",
	date=time.asctime())
for d, h in reports:
	main_layer.write_layer('report', day=d, happenings=h)
file_writer.close()

This structure is a list of (day, happenings) tuples that is iterated through in the script. The day and happenings values are treated as HTML Markup

Gordon's Lawn Happenings

Gordon's Lawn Happenings

Sunday

We've got a groundhog. I will have to stay alert.I lost half a tomato plant to that furry guy.

Monday

The grass grew - I saw it.

Generated on Sun Mar 8 17:24:33 2009.

Unfortunately, the HTML code that this generates is different than before we added the {report} layer — the "Happenings" are no longer in separate paragraphs. One way to fix this is to use HTML Markup and avoid mixing actual HTML into the code:

lawn4c.py
import templayer
import time
import sys

sys.stdout.write("Content-type: text/html\r\n\r\n")

reports = [
	('Sunday', [('p',"We've got a groundhog.  I will have to stay alert."),
		('p',"I lost half a tomato plant to that furry guy.")]),
	('Monday', [('p',"The grass grew - I saw it.")]),
]

tmpl = templayer.HTMLTemplate("lawn4.html")
file_writer = tmpl.start_file()
main_layer = file_writer.open(title="Gordon's Lawn Happenings",
	date=time.asctime())
for d, h in reports:
	main_layer.write_layer('report', day=d, happenings=h)
file_writer.close()

The expand_html_markup function will take the ('p', text) tuples, escape the text, wrap them in HTML paragraph tags then concatenate them.

Gordon's Lawn Happenings

Gordon's Lawn Happenings

Sunday

We've got a groundhog. I will have to stay alert.

I lost half a tomato plant to that furry guy.

Monday

The grass grew - I saw it.


Generated on Sun Mar 8 17:24:33 2009.



1.5. Using Nested Layers [back to top]

HTML Markup is not intended for anything beyond very simple formatting. A more flexible solution is to create another layer to nest within the first.

lawn5.html
<html>
<head><title>%title%</title></head>
<body>
<h1>%title%</h1>
{contents}

{report}
<h3>%day%</h3>
%happenings%
{/report}

{happening}<p>%what%</p>
{/happening}

{/contents}
<hr>
<p>Generated on %date%.</p>
</body>
</html>

Now the decision to format individual "Happenings" as paragraphs has been moved to the template in a {happening} layer.

lawn5.py
import templayer
import time
import sys

sys.stdout.write("Content-type: text/html\r\n\r\n")

reports = [
	('Sunday', ["We've got a groundhog.  I will have to stay alert.",
		"I lost half a tomato plant to that furry guy."]),
	('Monday', ["The grass grew - I saw it."]),
]

tmpl = templayer.HTMLTemplate("lawn5.html")
file_writer = tmpl.start_file()
main_layer = file_writer.open(title="Gordon's Lawn Happenings",
	date=time.asctime())
for d, h in reports:
	happening_list = []
	for w in h:
		formatted = tmpl.format('happening', what=w)
		happening_list.append(formatted)
	main_layer.write_layer('report', day=d, happenings=happening_list)
file_writer.close()

Within the new inner loop we are calling our HTMLTemplate object's format function. This function is similar to the write_layer function except that it returns a RawHTML object instead of adding it to the content of a Layer object. The expand_html_markup function will leave the contents of the RawHTML object intact, so we can use this object within HTML Markup.



1.6. Advanced Nested Layers [back to top]

Since it is common to nest layers in Templayer another method is provided that is similar to FileWriter's open function. First we need to rename a slot in the {report} to %contents%:

lawn6.html
<html>
<head><title>%title%</title></head>
<body>
<h1>%title%</h1>
{contents}

{report}
<h3>%day%</h3>
%contents%
{/report}

{happening}<p>%contents%</p>
{/happening}

{/contents}
<hr>
<p>Generated on %date%.</p>
</body>
</html>

Now that we have a %contents% slot we can "open" this layer and write into it:

lawn6a.py
import templayer
import time
import sys

sys.stdout.write("Content-type: text/html\r\n\r\n")

reports = [
	('Sunday', ["We've got a groundhog.  I will have to stay alert.",
		"I lost half a tomato plant to that furry guy."]),
	('Monday', ["The grass grew - I saw it."]),
]

tmpl = templayer.HTMLTemplate("lawn6.html")
file_writer = tmpl.start_file()
main_layer = file_writer.open(title="Gordon's Lawn Happenings",
	date=time.asctime())
for d, h in reports:
	report_layer = main_layer.open_layer('report', day=d)
	for happening in h:
		report_layer.write_layer('happening', contents=happening)
file_writer.close()

The Layer object has an open_layer function that returns a new layer object. In this case the new Layer object represents a {report} layer that is being written. The Layer object stores everything above the %contents% slot as its header and everything below as its footer. It can have text filled into its content in the same way as our main Layer object. Here we are now using write_layer to fill {happening} layers into {report} layers.

We also renamed the slot in {happening} to %contents%, so we can use the open_layer function on that as well:

lawn6b.py
import templayer
import time
import sys

sys.stdout.write("Content-type: text/html\r\n\r\n")

reports = [
	('Sunday', ["We've got a groundhog.  I will have to stay alert.",
		"I lost half a tomato plant to that furry guy."]),
	('Monday', ["The grass grew - I saw it."]),
]

tmpl = templayer.HTMLTemplate("lawn6.html")
file_writer = tmpl.start_file()
main_layer = file_writer.open(title="Gordon's Lawn Happenings",
	date=time.asctime())
for d, h in reports:
	report_layer = main_layer.open_layer('report', day=d)
	for happening in h:
		happening_layer = report_layer.open_layer('happening')
		happening_layer.write(happening)
file_writer.close()

We want to send text into this new Layer object, so instead of calling write_layer we call the write function. It takes a single parameter that is interpreted as HTML Markup.

When using Templayer to produce very large files, or when parts of an HTML page take longer to complete, you might want to flush your output part of the way through:

lawn6c.py
import templayer
import time
import sys

sys.stdout.write("Content-type: text/html\r\n\r\n")

reports = [
	('Sunday', ["We've got a groundhog.  I will have to stay alert.",
		"I lost half a tomato plant to that furry guy."]),
	('Monday', ["The grass grew - I saw it."]),
]

tmpl = templayer.HTMLTemplate("lawn6.html")
file_writer = tmpl.start_file()
main_layer = file_writer.open(title="Gordon's Lawn Happenings",
	date=time.asctime())
for d, h in reports:
	report_layer = main_layer.open_layer('report', day=d)
	for happening in h:
		happening_layer = report_layer.open_layer('happening')
		happening_layer.write(happening)
	main_layer.close_child()
	file_writer.flush()
file_writer.close()





Layer objects have a close_child function that forces any open "child" layers to finalize their output. In this code we are closing the {report} layer. FileWriter objects have a flush function that will send all the output possible. We are first closing our {report} layer so that when we flush the output the whole report will appear.



2. Django Applications and Templayer

2.1. Simple Views [back to top]

Templayer has a number of functions that help integrate with the Django Web Framework. This is an example of a simple Django view. All the examples below assume you are starting from a working project. See the Django documentation about setting up a project.

First place this Templayer template in your project's template directory:

simple.templayer.html
<html>
<head><title>%title%</title></head>
<body>

<h1>%title%</h1>
{contents}

{/contents}
</body>
</html>

Then we can use get_django_template function to find the template in the project's template directory without having to hard-code the system path in our view:

simple_views_1.py
from django.http import HttpResponse
import datetime
import templayer

tmpl = templayer.get_django_template("simple.templayer.html")

def current_datetime(request):
	file_writer = tmpl.start_file(HttpResponse())
	now = datetime.datetime.now()
	contents = file_writer.open(title="Current Date and Time")
	contents.write("It is now %s." % now)
	return file_writer.close()

For this type view function you may also use the django_view decorator. It takes care of creating and cleaning up the file writer object so that you don't need to "import HttpResponse" or remember to "return file_writer.close()" in every view function:

simple_views_2.py
import datetime
import templayer

tmpl = templayer.get_django_template("simple.templayer.html")

@templayer.django_view(tmpl)
def current_datetime(file_writer, request):
	now = datetime.datetime.now()
	contents = file_writer.open(title="Current Date and Time")
	contents.write("It is now %s." % now)



2.2. Forms [back to top]

Templayer includes a django_form helper function for working with forms. This function converts a form object to a dictionary of fields suitable for passing to the format, open_layer or write_layer functions:

Start with models like the following:

book_models.py
from django.db import models

RATING_CHOICES = (
	('G', "Great!"),
	('A', "Average"),
	('U', "Uninteresting"),
)

class Entry(models.Model):
	name = models.CharField("Your Name", max_length=100)
	rating = models.CharField("Rate this site", max_length=1,
		choices=RATING_CHOICES)
	comment = models.TextField("Your Comments (optional)", blank=True)
	posted = models.DateTimeField(auto_now_add=True)

This template has two slots for each visible field in the form, one for the HTML input field and one for errors related to that field.

book.templayer.html
<html>
<head><title>%title%</title></head>
<body>

<h1>%title%</h1>
{contents}

{new_entry}
<form method="POST">
<h2>Sign the guestbook</h2>
Your Name: %form.name% %form.name.errors%<br/>
Rate the site: %form.rating% %form.rating.errors%<br/>
Your Comments:<br/>
%form.comment% %form.comment.errors%<br/>
<input type="submit" value="Submit"/>
</form>
{/new_entry}

{/contents}
</body>
</html>

This code will populate the form widgets, accept input and redisplay the data (with errors if applicable) when the user clicks "Submit":

book_views.py
from book.models import Entry
from django.forms import ModelForm

import templayer

tmpl = templayer.get_django_template("book.templayer.html")

class EntryForm(ModelForm):
	class Meta:
		model = Entry

@templayer.django_view(tmpl)
def guest_book(file_layer, request):
	if request.POST:
		entry_form = EntryForm(request.POST)
		# TODO: do something with the data if all is well
	else:
		entry_form = EntryForm()
	contents = file_writer.open(title="Guest Book")
	contents.write_layer("new_entry", **templayer.django_form(entry_form))

Guest Book

Sign the guestbook

Your Name:
Rate the site:
Your Comments:



2.3. Emulating Django Templates [back to top]

Existing or third-party Django applications will expect to use the standard Django templating library. To use these applications with Templayer we need to extend the Django template loader and processing mechanism to emulate Django's templates.

This example will show how to use Templayer to create a 404 page and use the "flatpages" application.

First add the django_template_loader to the TEMPLATE_LOADERS in your project's settings.py file:

Your Django project's settings.py
...
TEMPLATE_LOADERS = (
    'django.template.loaders.filesystem.load_template_source',
    'django.template.loaders.app_directories.load_template_source',
...
    'templayer.django_template_loader',
)
...

Also make sure that the "flatpages" application is installed. See the Django flatpages app documentation for details.

Next copy this template into the project's template directory:

emulate.templayer.html
<html>
<head><title>%title%</title></head>
<body>

<h1>%title%</h1>
{contents}

{show_404}
The url %url% could not be found.
{/show_404}

{flatpage_body}
%contents%
{/flatpage_body}

{/contents}
</body>
</html>

Templayer's Django template emulation will look for a module named "templayer_pages.py" in the template directories when trying to load templates. The django_template decorator defines the Django template name that a function will handle. The function may take parameters with names matching values you know will be present in the context dictionary and the decorator will fill in those values for you.

Put this module in the template directory:

templayer_pages.py
import templayer

tmpl = templayer.get_django_template("emulate.templayer.html")

@templayer.django_template(tmpl, "404.html")
def show_404(file_writer, context, request_path):
	contents = file_writer.open(title="Page not found")

	# request_path == context['request_path']
	contents.write_layer("show_404", url=request_path)

@templayer.django_template(tmpl, "flatpages/default.html")
def show_flatpage(file_writer, context, flatpage):
	contents = file_writer.open(title=flatpage.title)
	fp_contents = contents.open_layer("flatpage_body")

	# the flatpage content is HTML, so don't escape it.
	fp_contents.write(templayer.RawHTML(flatpage.content))




Now the 404 handler and the flatpages application will be formatted by Templayer.



templayer-1.5.1/docs/tutorial_examples.tar.gz0000644000175000017500000000454011155033621017440 0ustar ianian7I]o'.T}nӶ*JK.$yYUߌCB@i{l[%ǎǟl1]~1gKީtvd:4zvѺ~BoJ3CBN{oW %V\x,&xG0>NHgo-x^;a\PJMe$IGϙF؏l3 kl?)I! "2"Ҹ{lGOLg$Ї ۟\6!gILL[R&_׏价P3CvfpI*jP;?{mЌ"KG Ⱈ$1 hp#w^;|j>=T|Px[ǥ30CiJ3 ^ dgU?ґ|ᇖ5"+TMޚ94@B5 *Mj8YTRik\X|4b. 萑ՔjXme՘6傫w%ML&0Ԙ؎k)ؔLݺVr=CCOo6CwzQz]!XԴCuSagJZ"kn/ѽ$jz:鍥ztNr]xhP{0 AYH5`]kZЎ}hΠ8{ZG*h{>{~>? @p^|Tz15~q?$zdt^=m^{z}1}cOl'I~Ȍ?^5Z .F`NiK`W+> *2~RfX5o/ 0IRXuzGO-zK"_d}p=HH73(<ËK<2bQs(h|Z mSNZ)ěeøޟ9 F2 z$P K R z> (WInns]xtap!rCPnDS6Z_`J_GK?bhh+tbWBYg3$-\Q{Q@k4x2Z&-#dpÆvh <lIEjz鏄UqcOPE|a)% AHI/G߼l3kh?~'ظܦAcȷF2ZJ2Jꎯ"n uyH~3gԱu=s0è> cNN&  MR |ܓ81)͗:^0ݷ @MDdtp{"Wkig㶜-U8^mzz_“?a0^p]{+ ~-/$W-q0VbG TBYuxᗤo/0߾< &|R}' hlJWI`0abpPV@??vm3šȩgPnep7eHtΝ|˭\N}¸LlߙhRοuװe[x=mB7=x"+U q5&L=iY+-YO+W '9o_ebbҎ|hgWtW[<[Tc[U׎^Fh="ť%, l#a}TOdOOKYKܵGYnh]UkG[ƅc{mC9{"JbUt__wӣl$96\圩mU_1yd+cӶ