From e4108154d7394e218695747b18543376d03ead42 Mon Sep 17 00:00:00 2001 From: Chris Jerdonek Date: Sat, 5 May 2012 14:44:21 -0700 Subject: Parser no longer requires a RenderEngine instance to parse. --- pystache/parsed.py | 4 +- pystache/parser.py | 72 +++++++++++++++++++++++------- pystache/renderengine.py | 112 ++++++++++++++++------------------------------- 3 files changed, 93 insertions(+), 95 deletions(-) (limited to 'pystache') diff --git a/pystache/parsed.py b/pystache/parsed.py index 0055837..eb138cd 100644 --- a/pystache/parsed.py +++ b/pystache/parsed.py @@ -45,9 +45,7 @@ class ParsedTemplate(object): """ # We avoid use of the ternary operator for Python 2.4 support. def get_unicode(val): - if callable(val): - return val(context) - if isinstance(val, basestring): + if type(val) is unicode: return val return val.render(engine, context) parts = map(get_unicode, self._parse_tree) diff --git a/pystache/parser.py b/pystache/parser.py index ad542c3..74ffc01 100644 --- a/pystache/parser.py +++ b/pystache/parser.py @@ -71,7 +71,7 @@ class ChangeNode(object): return u'' -class VariableNode(object): +class Tag(object): def __init__(self, key): self.key = key @@ -119,28 +119,65 @@ class InvertedNode(object): # per the spec. if data: 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 - _template_re = None + # TODO: the template_ and parsed_template_ arguments don't both seem + # 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): - """ - Construct an instance. + def render(self, engine, context): + data = engine.fetch_section_data(context, self.key) + + 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: delimiters = DEFAULT_DELIMITERS self._delimiters = delimiters - self.engine = engine def _compile_delimiters(self): self._template_re = _compile_template_re(self._delimiters) @@ -242,8 +279,9 @@ class Parser(object): parsed_template.add(node) - # Add the remainder of the template. - parsed_template.add(template[start_index:]) + # Avoid adding spurious empty strings to the parse tree. + if start_index != len(template): + parsed_template.add(template[start_index:]) return parsed_template @@ -262,7 +300,7 @@ class Parser(object): return ChangeNode(delimiters) if tag_type == '': - return VariableNode(tag_key) + return Tag(tag_key) if tag_type == '&': return LiteralNode(tag_key) @@ -279,8 +317,8 @@ class Parser(object): """ if tag_type == '#': - return self.engine._make_get_section(tag_key, parsed_section, self._delimiters, - template, section_start_index, section_end_index) + return SectionNode(tag_key, parsed_section, self._delimiters, + template, section_start_index, section_end_index) if tag_type == '^': return InvertedNode(tag_key, parsed_section) diff --git a/pystache/renderengine.py b/pystache/renderengine.py index 39b536c..8a05760 100644 --- a/pystache/renderengine.py +++ b/pystache/renderengine.py @@ -87,12 +87,12 @@ class RenderEngine(object): # The returned value MUST be rendered against the default delimiters, # 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. """ - val = self.resolve_context(context, tag_name) + val = self.resolve_context(context, name) if callable(val): # Return because _render_value() is already a string. @@ -103,81 +103,40 @@ class RenderEngine(object): return val - # TODO: the template_ and parsed_template_ arguments don't both seem - # 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 _make_get_section(self, name, parsed_template, delims, - template, section_start_index, section_end_index): - def get_section_value(context): - """ - Returns: a string of type unicode. - - """ - data = self.resolve_context(context, name) - - # From the spec: + def fetch_section_data(self, context, name): + data = self.resolve_context(context, name) + + # From the spec: + # + # 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`), + # use a single-element list containing the data, otherwise use + # an empty list. + # + if not data: + data = [] + 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 - # as follows: if the data is truthy (e.g. `!!data == true`), - # use a single-element list containing the data, otherwise use - # an empty list. + # http://docs.python.org/library/functions.html#iter # - if not data: - data = [] + # 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] else: - # The least brittle way to determine whether something - # supports iteration is by trying to call iter() on it: - # - # 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. + if is_string(data) or isinstance(data, dict): + # Do not treat strings and dicts (which are iterable) as lists. data = [data] - else: - if is_string(data) or isinstance(data, dict): - # Do not treat strings and dicts (which are iterable) as lists. - 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) + # Otherwise, treat the value as a list. + + return data def _render_value(self, val, context, delimiters=None): """ @@ -191,6 +150,9 @@ class RenderEngine(object): val = self.literal(val) 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): """ Render a unicode template string, and return as unicode. @@ -203,7 +165,7 @@ class RenderEngine(object): context_stack: a ContextStack instance. """ - parser = Parser(self, delimiters=delimiters) + parser = Parser(delimiters=delimiters) parsed_template = parser.parse(template) - return self._render_parsed(parsed_template, context_stack) + return self.render_parsed(parsed_template, context_stack) -- cgit v1.2.1