summaryrefslogtreecommitdiff
path: root/pystache/renderengine.py
diff options
context:
space:
mode:
authorChris Jerdonek <chris.jerdonek@gmail.com>2011-12-31 20:02:29 -0800
committerChris Jerdonek <chris.jerdonek@gmail.com>2011-12-31 20:02:29 -0800
commit34a1705205253e18aac9b50d0c86bc79334b92c0 (patch)
tree0e9cc7cb2889a551c4de7575075363fd3af02575 /pystache/renderengine.py
parentb8b66bf6c0dc5b8a954d6778e3d470c80637c4f3 (diff)
parentca0ad881a9994b6c0481d253d889933e8f80e7c3 (diff)
downloadpystache-34a1705205253e18aac9b50d0c86bc79334b92c0.tar.gz
Merge branch 'pvande-spec' into branch 'development-spec'
Conflicts: pystache/template.py pystache/view.py tests/test_spec.py
Diffstat (limited to 'pystache/renderengine.py')
-rw-r--r--pystache/renderengine.py275
1 files changed, 275 insertions, 0 deletions
diff --git a/pystache/renderengine.py b/pystache/renderengine.py
index f636e2a..f6172b7 100644
--- a/pystache/renderengine.py
+++ b/pystache/renderengine.py
@@ -5,8 +5,11 @@ Defines a class responsible for rendering logic.
"""
+import cgi
import collections
+import inspect
import re
+import types
try:
@@ -294,3 +297,275 @@ class RenderEngine(object):
output = "".join(output)
return output
+#
+
+
+END_OF_LINE_CHARACTERS = ['\r', '\n']
+
+
+# TODO: what are the possibilities for val?
+def call(val, view, template=None):
+ if callable(val):
+ (args, _, _, _) = inspect.getargspec(val)
+
+ args_count = len(args)
+
+ if not isinstance(val, types.FunctionType):
+ # Then val is an instance method. Subtract one from the
+ # argument count because Python will automatically prepend
+ # self to the argument list when calling.
+ args_count -=1
+
+ if args_count is 0:
+ val = val()
+ elif args_count is 1 and args[0] in ['self', 'context']:
+ val = val(view)
+ elif args_count is 1:
+ val = val(template)
+ else:
+ val = val(view, template)
+
+ if callable(val):
+ val = val(template)
+
+ if val is None:
+ val = ''
+
+ return unicode(val)
+
+def render_parse_tree(parse_tree, view, template):
+ """
+ Convert a parse-tree into a string.
+
+ """
+ get_string = lambda val: call(val, view, template)
+ parts = map(get_string, parse_tree)
+
+ return ''.join(parts)
+
+def inverseTag(name, parsed, template, delims):
+ def func(self):
+ data = self.get(name)
+ if data:
+ return ''
+ return render_parse_tree(parsed, self, delims)
+ return func
+
+class EndOfSection(Exception):
+ def __init__(self, parse_tree, template, position):
+ self.parse_tree = parse_tree
+ self.template = template
+ self.position = position
+
+class Template(object):
+ tag_re = None
+ otag, ctag = '{{', '}}'
+
+ def __init__(self, template=None, context={}, **kwargs):
+ from view import View
+
+ self.template = template
+
+ if kwargs:
+ context.update(kwargs)
+
+ self.view = context if isinstance(context, View) else View(context=context)
+ self._compile_regexps()
+
+ def _compile_regexps(self):
+ tags = {'otag': re.escape(self.otag), 'ctag': re.escape(self.ctag)}
+ tag = r"""
+ (?P<content>[\s\S]*?)
+ (?P<whitespace>[\ \t]*)
+ %(otag)s \s*
+ (?:
+ (?P<change>=) \s* (?P<delims>.+?) \s* = |
+ (?P<raw>{) \s* (?P<raw_name>.+?) \s* } |
+ (?P<tag>\W?) \s* (?P<name>[\s\S]+?)
+ )
+ \s* %(ctag)s
+ """
+ self.tag_re = re.compile(tag % tags, re.M | re.X)
+
+ def to_unicode(self, text):
+ return unicode(text)
+
+ def escape(self, text):
+ return cgi.escape(text, True)
+
+ def partial(self, name, context=None):
+ return context.partial(name)
+
+ def escape_tag_function(self, name):
+ fetch = self.literal_tag_function(name)
+ def func(context):
+ return self.escape(fetch(context))
+ return func
+
+ def literal_tag_function(self, name):
+ def func(context):
+ val = context.get(name)
+ template = call(val=val, view=context)
+ return self.to_unicode(self.render_template(template, context))
+ return func
+
+ def partial_tag_function(self, name, indentation=''):
+ def func(context):
+ nonblank = re.compile(r'^(.)', re.M)
+ template = re.sub(nonblank, indentation + r'\1', self.partial(name, context))
+ return self.render_template(template, context)
+ return func
+
+ def section_tag_function(self, name, parse_tree_, template_, delims):
+ def func(context):
+ template = template_
+ parse_tree = parse_tree_
+ data = context.get(name)
+ if not data:
+ return ''
+ elif callable(data):
+ template = call(val=data, view=context, template=template)
+ parse_tree = self.parse_string_to_tree(template, context, delims)
+ data = [ data ]
+ elif type(data) not in [list, tuple]:
+ data = [ data ]
+
+ parts = []
+ for element in data:
+ context.context_list.insert(0, element)
+ parts.append(render_parse_tree(parse_tree, context, delims))
+ del context.context_list[0]
+
+ return ''.join(parts)
+ return func
+
+ def parse_string_to_tree(self, template, view, delims=('{{', '}}')):
+
+ template = Template(template)
+
+ template.view = view
+ template.to_unicode = self.to_unicode
+ template.escape = self.escape
+ template.partial = self.partial
+ template.otag, template.ctag = delims
+
+ template._compile_regexps()
+
+ return template.parse_to_tree()
+
+ def parse_to_tree(self, index=0):
+ """
+ Parse a template into a syntax tree.
+
+ """
+ parse_tree = []
+ template = self.template
+ start_index = index
+
+ while True:
+ match = self.tag_re.search(template, index)
+
+ if match is None:
+ break
+
+ captures = match.groupdict()
+ match_index = match.end('content')
+ end_index = match.end()
+
+ index = self._handle_match(parse_tree, captures, start_index, match_index, end_index)
+
+ # Save the rest of the template.
+ parse_tree.append(template[index:])
+
+ return parse_tree
+
+ def _handle_match(self, parse_tree, captures, start_index, match_index, end_index):
+ template = self.template
+
+ # Normalize the captures dictionary.
+ if captures['change'] is not None:
+ captures.update(tag='=', name=captures['delims'])
+ elif captures['raw'] is not None:
+ captures.update(tag='{', name=captures['raw_name'])
+
+ parse_tree.append(captures['content'])
+
+ # Standalone (non-interpolation) tags consume the entire line,
+ # both leading whitespace and trailing newline.
+ did_tag_begin_line = match_index == 0 or template[match_index - 1] in END_OF_LINE_CHARACTERS
+ did_tag_end_line = end_index == len(template) or template[end_index] in END_OF_LINE_CHARACTERS
+ is_tag_interpolating = captures['tag'] in ['', '&', '{']
+
+ if did_tag_begin_line and did_tag_end_line and not is_tag_interpolating:
+ if end_index < len(template):
+ end_index += template[end_index] == '\r' and 1 or 0
+ if end_index < len(template):
+ end_index += template[end_index] == '\n' and 1 or 0
+ elif captures['whitespace']:
+ parse_tree.append(captures['whitespace'])
+ match_index += len(captures['whitespace'])
+ captures['whitespace'] = ''
+
+ name = captures['name']
+
+ if captures['tag'] == '!':
+ return end_index
+
+ if captures['tag'] == '=':
+ self.otag, self.ctag = name.split()
+ self._compile_regexps()
+ return end_index
+
+ if captures['tag'] == '>':
+ func = self.partial_tag_function(name, captures['whitespace'])
+ elif captures['tag'] in ['#', '^']:
+
+ try:
+ self.parse_to_tree(index=end_index)
+ except EndOfSection as e:
+ bufr = e.parse_tree
+ tmpl = e.template
+ end_index = e.position
+
+ tag = self.section_tag_function if captures['tag'] == '#' else inverseTag
+ func = tag(name, bufr, tmpl, (self.otag, self.ctag))
+
+ elif captures['tag'] in ['{', '&']:
+
+ func = self.literal_tag_function(name)
+
+ elif captures['tag'] == '':
+
+ func = self.escape_tag_function(name)
+
+ elif captures['tag'] == '/':
+
+ # TODO: don't use exceptions for flow control.
+ raise EndOfSection(parse_tree, template[start_index:match_index], end_index)
+
+ else:
+ raise Exception("'%s' is an unrecognized type!" % captures['tag'])
+
+ parse_tree.append(func)
+
+ return end_index
+
+ def render_template(self, template, view, delims=('{{', '}}')):
+ """
+ Arguments:
+
+ template: template string
+ view: context
+
+ """
+ parse_tree = self.parse_string_to_tree(template, view, delims)
+ return render_parse_tree(parse_tree, view, template)
+
+ def render(self, encoding=None):
+ result = self.render_template(self.template, self.view)
+ if encoding is not None:
+ result = result.encode(encoding)
+
+ return result
+
+