Commit e4108154 by Chris Jerdonek

Parser no longer requires a RenderEngine instance to parse.

parent a3999641
...@@ -45,9 +45,7 @@ class ParsedTemplate(object): ...@@ -45,9 +45,7 @@ class ParsedTemplate(object):
""" """
# We avoid use of the ternary operator for Python 2.4 support. # We avoid use of the ternary operator for Python 2.4 support.
def get_unicode(val): def get_unicode(val):
if callable(val): if type(val) is unicode:
return val(context)
if isinstance(val, basestring):
return val return val
return val.render(engine, context) return val.render(engine, context)
parts = map(get_unicode, self._parse_tree) parts = map(get_unicode, self._parse_tree)
......
...@@ -71,7 +71,7 @@ class ChangeNode(object): ...@@ -71,7 +71,7 @@ class ChangeNode(object):
return u'' return u''
class VariableNode(object): class Tag(object):
def __init__(self, key): def __init__(self, key):
self.key = key self.key = key
...@@ -119,28 +119,65 @@ class InvertedNode(object): ...@@ -119,28 +119,65 @@ class InvertedNode(object):
# per the spec. # per the spec.
if data: if data:
return u'' return u''
return engine._render_parsed(self.parsed_section, context) return engine.render_parsed(self.parsed_section, context)
class Parser(object): class SectionNode(object):
_delimiters = None # TODO: the template_ and parsed_template_ arguments don't both seem
_template_re = None # to be necessary. Can we remove one of them? For example, if
# callable(data) is True, then the initial parsed_template isn't used.
def __init__(self, key, parsed_contents, delimiters, template, section_begin_index, section_end_index):
self.delimiters = delimiters
self.key = key
self.parsed_contents = parsed_contents
self.template = template
self.section_begin_index = section_begin_index
self.section_end_index = section_end_index
def __init__(self, engine, delimiters=None): def render(self, engine, context):
""" data = engine.fetch_section_data(context, self.key)
Construct an instance.
parts = []
for val in data:
if callable(val):
# Lambdas special case section rendering and bypass pushing
# the data value onto the context stack. From the spec--
#
# When used as the data value for a Section tag, the
# lambda MUST be treatable as an arity 1 function, and
# invoked as such (passing a String containing the
# unprocessed section contents). The returned value
# MUST be rendered against the current delimiters, then
# interpolated in place of the section.
#
# Also see--
#
# https://github.com/defunkt/pystache/issues/113
#
# TODO: should we check the arity?
val = val(self.template[self.section_begin_index:self.section_end_index])
val = engine._render_value(val, context, delimiters=self.delimiters)
parts.append(val)
continue
Arguments: context.push(val)
parts.append(engine.render_parsed(self.parsed_contents, context))
context.pop()
engine: a RenderEngine instance. return unicode(''.join(parts))
"""
class Parser(object):
_delimiters = None
_template_re = None
def __init__(self, delimiters=None):
if delimiters is None: if delimiters is None:
delimiters = DEFAULT_DELIMITERS delimiters = DEFAULT_DELIMITERS
self._delimiters = delimiters self._delimiters = delimiters
self.engine = engine
def _compile_delimiters(self): def _compile_delimiters(self):
self._template_re = _compile_template_re(self._delimiters) self._template_re = _compile_template_re(self._delimiters)
...@@ -242,8 +279,9 @@ class Parser(object): ...@@ -242,8 +279,9 @@ class Parser(object):
parsed_template.add(node) parsed_template.add(node)
# Add the remainder of the template. # Avoid adding spurious empty strings to the parse tree.
parsed_template.add(template[start_index:]) if start_index != len(template):
parsed_template.add(template[start_index:])
return parsed_template return parsed_template
...@@ -262,7 +300,7 @@ class Parser(object): ...@@ -262,7 +300,7 @@ class Parser(object):
return ChangeNode(delimiters) return ChangeNode(delimiters)
if tag_type == '': if tag_type == '':
return VariableNode(tag_key) return Tag(tag_key)
if tag_type == '&': if tag_type == '&':
return LiteralNode(tag_key) return LiteralNode(tag_key)
...@@ -279,8 +317,8 @@ class Parser(object): ...@@ -279,8 +317,8 @@ class Parser(object):
""" """
if tag_type == '#': if tag_type == '#':
return self.engine._make_get_section(tag_key, parsed_section, self._delimiters, return SectionNode(tag_key, parsed_section, self._delimiters,
template, section_start_index, section_end_index) template, section_start_index, section_end_index)
if tag_type == '^': if tag_type == '^':
return InvertedNode(tag_key, parsed_section) return InvertedNode(tag_key, parsed_section)
......
...@@ -87,12 +87,12 @@ class RenderEngine(object): ...@@ -87,12 +87,12 @@ class RenderEngine(object):
# The returned value MUST be rendered against the default delimiters, # The returned value MUST be rendered against the default delimiters,
# then interpolated in place of the lambda. # then interpolated in place of the lambda.
# #
def fetch_string(self, context, tag_name): def fetch_string(self, context, name):
""" """
Get a value from the given context as a basestring instance. Get a value from the given context as a basestring instance.
""" """
val = self.resolve_context(context, tag_name) val = self.resolve_context(context, name)
if callable(val): if callable(val):
# Return because _render_value() is already a string. # Return because _render_value() is already a string.
...@@ -103,81 +103,40 @@ class RenderEngine(object): ...@@ -103,81 +103,40 @@ class RenderEngine(object):
return val return val
# TODO: the template_ and parsed_template_ arguments don't both seem def fetch_section_data(self, context, name):
# to be necessary. Can we remove one of them? For example, if data = self.resolve_context(context, name)
# callable(data) is True, then the initial parsed_template isn't used.
def _make_get_section(self, name, parsed_template, delims, # From the spec:
template, section_start_index, section_end_index): #
def get_section_value(context): # If the data is not of a list type, it is coerced into a list
""" # as follows: if the data is truthy (e.g. `!!data == true`),
Returns: a string of type unicode. # use a single-element list containing the data, otherwise use
# an empty list.
""" #
data = self.resolve_context(context, name) if not data:
data = []
# From the spec: else:
# The least brittle way to determine whether something
# supports iteration is by trying to call iter() on it:
# #
# If the data is not of a list type, it is coerced into a list # http://docs.python.org/library/functions.html#iter
# as follows: if the data is truthy (e.g. `!!data == true`),
# use a single-element list containing the data, otherwise use
# an empty list.
# #
if not data: # It is not sufficient, for example, to check whether the item
data = [] # implements __iter__ () (the iteration protocol). There is
# also __getitem__() (the sequence protocol). In Python 2,
# strings do not implement __iter__(), but in Python 3 they do.
try:
iter(data)
except TypeError:
# Then the value does not support iteration.
data = [data]
else: else:
# The least brittle way to determine whether something if is_string(data) or isinstance(data, dict):
# supports iteration is by trying to call iter() on it: # Do not treat strings and dicts (which are iterable) as lists.
#
# http://docs.python.org/library/functions.html#iter
#
# It is not sufficient, for example, to check whether the item
# implements __iter__ () (the iteration protocol). There is
# also __getitem__() (the sequence protocol). In Python 2,
# strings do not implement __iter__(), but in Python 3 they do.
try:
iter(data)
except TypeError:
# Then the value does not support iteration.
data = [data] data = [data]
else: # Otherwise, treat the value as a list.
if is_string(data) or isinstance(data, dict):
# Do not treat strings and dicts (which are iterable) as lists. return data
data = [data]
# Otherwise, treat the value as a list.
parts = []
for val in data:
if callable(val):
# Lambdas special case section rendering and bypass pushing
# the data value onto the context stack. From the spec--
#
# When used as the data value for a Section tag, the
# lambda MUST be treatable as an arity 1 function, and
# invoked as such (passing a String containing the
# unprocessed section contents). The returned value
# MUST be rendered against the current delimiters, then
# interpolated in place of the section.
#
# Also see--
#
# https://github.com/defunkt/pystache/issues/113
#
# TODO: should we check the arity?
val = val(template[section_start_index:section_end_index])
val = self._render_value(val, context, delimiters=delims)
parts.append(val)
continue
context.push(val)
parts.append(self._render_parsed(parsed_template, context))
context.pop()
return unicode(''.join(parts))
return get_section_value
def _render_parsed(self, parsed_template, context_stack):
return parsed_template.render(self, context_stack)
def _render_value(self, val, context, delimiters=None): def _render_value(self, val, context, delimiters=None):
""" """
...@@ -191,6 +150,9 @@ class RenderEngine(object): ...@@ -191,6 +150,9 @@ class RenderEngine(object):
val = self.literal(val) val = self.literal(val)
return self.render(val, context, delimiters) return self.render(val, context, delimiters)
def render_parsed(self, parsed_template, context_stack):
return parsed_template.render(self, context_stack)
def render(self, template, context_stack, delimiters=None): def render(self, template, context_stack, delimiters=None):
""" """
Render a unicode template string, and return as unicode. Render a unicode template string, and return as unicode.
...@@ -203,7 +165,7 @@ class RenderEngine(object): ...@@ -203,7 +165,7 @@ class RenderEngine(object):
context_stack: a ContextStack instance. context_stack: a ContextStack instance.
""" """
parser = Parser(self, delimiters=delimiters) parser = Parser(delimiters=delimiters)
parsed_template = parser.parse(template) parsed_template = parser.parse(template)
return self._render_parsed(parsed_template, context_stack) return self.render_parsed(parsed_template, context_stack)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment