Permalink
Browse files

* first stab at aggressively cached mustache templates. Only variable…

… access supported.. no sections
  • Loading branch information...
apg committed May 6, 2010
1 parent c514948 commit f704b37e478442454c63e94a941689ef6e9231f2
Showing with 131 additions and 201 deletions.
  1. +1 −7 pystache/__init__.py
  2. +130 −81 pystache/template.py
  3. +0 −113 pystache/view.py
View
@@ -1,7 +1 @@
from pystache.template import Template
from pystache.view import View
def render(template, context=None, **kwargs):
context = context and context.copy() or {}
context.update(kwargs)
return Template(template, context).render()
from template import Template
View
@@ -16,10 +16,8 @@ def set_modifier(func):
return func
return set_modifier
class Template(object):
# The regular expression used to find a #section
section_re = None
class Template(object):
# The regular expression used to find a tag.
tag_re = None
@@ -32,106 +30,157 @@ class Template(object):
def __init__(self, template, context=None):
self.template = template
self.context = context or {}
self.compile_regexps()
def render(self, template=None, context=None, encoding=None):
"""Turns a Mustache template into something wonderful."""
template = template or self.template
context = context or self.context
self._closure_map = {}
self._closures = []
template = self.render_sections(template, context)
result = self.render_tags(template, context)
if encoding is not None:
result = result.encode(encoding)
return result
self.compile_regexps()
self.compile(template)
self.update(**self.context)
def update(self, **context):
for var, val in context.items():
updater = self._closure_map.get(var, None)
if updater:
updater(**{var: val})
def render(self, fileobj=None, encoding=None):
if fileobj:
for c in self._closures:
tmp = c()
if encoding:
tmp = tmp.encode(encoding)
fileobj.write(tmp)
else:
tmp = ''.join([c() for c in self._closures])
if encoding:
tmp = tmp.encode(encoding)
return tmp
def compile_regexps(self):
"""Compiles our section and tag regular expressions."""
tags = { 'otag': re.escape(self.otag), 'ctag': re.escape(self.ctag) }
section = r"%(otag)s[\#|^]([^\}]*)%(ctag)s\s*(.+?)\s*%(otag)s/\1%(ctag)s"
self.section_re = re.compile(section % tags, re.M|re.S)
# section = r"%(otag)s[\#|^]([^\}]*)%(ctag)s\s*(.+?)\s*%(otag)s/\1%(ctag)s"
# self.section_re = re.compile(section % tags, re.M|re.S)
tag = r"%(otag)s(#|=|&|!|>|\{)?(.+?)\1?%(ctag)s+"
self.tag_re = re.compile(tag % tags)
def render_sections(self, template, context):
"""Expands sections."""
while 1:
match = self.section_re.search(template)
if match is None:
break
section, section_name, inner = match.group(0, 1, 2)
section_name = section_name.strip()
it = context.get(section_name, None)
replacer = ''
if it and hasattr(it, '__call__'):
replacer = it(inner)
elif it and not hasattr(it, '__iter__'):
if section[2] != '^':
replacer = inner
elif it:
insides = []
for item in it:
insides.append(self.render(inner, item))
replacer = ''.join(insides)
elif not it and section[2] == '^':
replacer = inner
template = template.replace(section, replacer)
return template
def render_tags(self, template, context):
"""Renders all the tags in a template for a context."""
while 1:
match = self.tag_re.search(template)
if match is None:
break
# def render_sections(self, template, context):
# """Expands sections."""
# while 1:
# match = self.section_re.search(template)
# if match is None:
# break
# section, section_name, inner = match.group(0, 1, 2)
# section_name = section_name.strip()
# it = context.get(section_name, None)
# replacer = ''
# if it and hasattr(it, '__call__'):
# replacer = it(inner)
# elif it and not hasattr(it, '__iter__'):
# if section[2] != '^':
# replacer = inner
# elif it:
# insides = []
# for item in it:
# insides.append(self.render(inner, item))
# replacer = ''.join(insides)
# elif not it and section[2] == '^':
# replacer = inner
# print "replacing:", section, "\n\nwith: ", replacer
# template = template.replace(section, replacer)
# return template
def compile(self, template):
"""Compiles all the tags and text into a list of closures
"""
tmp_len = len(template)
pos = 0
for match in self.tag_re.finditer(template):
start = match.start()
end = match.end()
# have we skipped any text? If so create a text closure
if pos < start:
func = modifiers['TEXT'](self, template[pos:start])
self._closures.append(func)
tag, tag_type, tag_name = match.group(0, 1, 2)
tag_name = tag_name.strip()
func = modifiers[tag_type]
replacement = func(self, tag_name, context)
template = template.replace(tag, replacement)
return template
func = modifiers[tag_type](self, tag_name)
if func:
self._closures.append(func)
self._closure_map[tag_name] = func
pos = end
else:
if pos < tmp_len:
self._closures.append(modifiers['TEXT'](template[pos:]))
@modifier(None)
def render_tag(self, tag_name, context):
def compile_tag(self, tag_name):
"""Given a tag name and context, finds, escapes, and renders the tag."""
raw = context.get(tag_name, '')
if not raw and raw is not 0:
return ''
return cgi.escape(unicode(raw))
rendered_text = ['']
def _inner(**context):
if len(context):
self.context.update(context)
raw = context.get(tag_name, '')
if not raw and raw is not None:
rendered_text[0] = ''
else:
rendered_text[0] = cgi.escape(unicode(raw))
return None
else:
return rendered_text[0]
return _inner
@modifier('!')
def render_comment(self, tag_name=None, context=None):
def compile_comment(self, tag_name=None, context=None):
"""Rendering a comment always returns nothing."""
return ''
return None
@modifier('{')
@modifier('&')
def render_unescaped(self, tag_name=None, context=None):
def compile_unescaped(self, tag_name=None):
"""Render a tag without escaping it."""
return unicode(context.get(tag_name, ''))
@modifier('>')
def render_partial(self, tag_name=None, context=None):
"""Renders a partial within the current context."""
# Import view here to avoid import loop
from pystache.view import View
view = View(context=context)
view.template_name = tag_name
return view.render()
@modifier('=')
def render_delimiter(self, tag_name=None, context=None):
"""Changes the Mustache delimiter."""
self.otag, self.ctag = tag_name.split(' ')
self.compile_regexps()
return ''
rendered_text = ['']
def _inner(**context):
if len(context):
self.context.update(context)
rendered_text[0] = unicode(context.get(tag_name, ''))
return None
else:
return rendered_text[0]
return _inner
@modifier('TEXT')
def compile_text(self, text=''):
def _inner(**context):
if len(context):
return None
return text
return _inner
# @modifier('>')
# def render_partial(self, tag_name=None, context=None):
# """Renders a partial within the current context."""
# # Import view here to avoid import loop
# from pystache.view import View
# view = View(context=context)
# view.template_name = tag_name
# return view.render()
# @modifier('=')
# def render_delimiter(self, tag_name=None, context=None):
# """Changes the Mustache delimiter."""
# self.otag, self.ctag = tag_name.split(' ')
# self.compile_regexps()
# return ''
View
@@ -1,113 +0,0 @@
from pystache import Template
import os.path
import re
class View(object):
# Path where this view's template(s) live
template_path = '.'
# Extension for templates
template_extension = 'mustache'
# The name of this template. If none is given the View will try
# to infer it based on the class name.
template_name = None
# Absolute path to the template itself. Pystache will try to guess
# if it's not provided.
template_file = None
# Contents of the template.
template = None
# Character encoding of the template file. If None, Pystache will not
# do any decoding of the template.
template_encoding = None
def __init__(self, template=None, context=None, **kwargs):
self.template = template
self.context = context or {}
# If the context we're handed is a View, we want to inherit
# its settings.
if isinstance(context, View):
self.inherit_settings(context)
if kwargs:
self.context.update(kwargs)
def inherit_settings(self, view):
"""Given another View, copies its settings."""
if view.template_path:
self.template_path = view.template_path
if view.template_name:
self.template_name = view.template_name
def __contains__(self, needle):
return hasattr(self, needle)
def __getitem__(self, attr):
return getattr(self, attr)()
def load_template(self):
if self.template:
return self.template
if self.template_file:
return self._load_template()
name = self.get_template_name() + '.' + self.template_extension
if isinstance(self.template_path, basestring):
self.template_file = os.path.join(self.template_path, name)
return self._load_template()
for path in self.template_path:
self.template_file = os.path.join(path, name)
if os.path.exists(self.template_file):
return self._load_template()
raise IOError('"%s" not found in "%s"' % (name, ':'.join(self.template_path),))
def _load_template(self):
f = open(self.template_file, 'r')
try:
template = f.read()
if self.template_encoding:
template = unicode(template, self.template_encoding)
finally:
f.close()
return template
def get_template_name(self, name=None):
"""TemplatePartial => template_partial
Takes a string but defaults to using the current class' name or
the `template_name` attribute
"""
if self.template_name:
return self.template_name
if not name:
name = self.__class__.__name__
def repl(match):
return '_' + match.group(0).lower()
return re.sub('[A-Z]', repl, name)[1:]
def get(self, attr, default):
attr = self.context.get(attr, getattr(self, attr, default))
if hasattr(attr, '__call__'):
return attr()
else:
return attr
def render(self, encoding=None):
template = self.load_template()
return Template(template, self).render(encoding=encoding)
def __str__(self):
return self.render()

0 comments on commit f704b37

Please sign in to comment.