Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(11)

Unified Diff: third_party/handlebar/handlebar.py

Issue 14693006: Docserver: update handlebar. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 7 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « third_party/handlebar/README.chromium ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: third_party/handlebar/handlebar.py
diff --git a/third_party/handlebar/handlebar.py b/third_party/handlebar/handlebar.py
index c5a2c101629ab0ed9e25444a9d053d941982b7bd..cd381b9c43afd2f00c37940967d90dfa73face63 100644
--- a/third_party/handlebar/handlebar.py
+++ b/third_party/handlebar/handlebar.py
@@ -12,54 +12,68 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+# TODO: Some character other than {{{ }}} to print unescaped content?
+# TODO: Only have @ while in a loop, and only defined in the top context of
+# the loop.
+# TODO: Consider trimming spaces around identifers like {{?t foo}}.
+# TODO: Only transfer global contexts into partials, not the top local.
+# TODO: Pragmas for asserting the presence of variables.
+# TODO: Escaping control characters somehow. e.g. \{{, \{{-.
+# TODO: Dump warnings-so-far into the output.
+
import json
import re
-""" Handlebar templates are mostly-logicless templates inspired by ctemplate
-(or more specifically mustache templates) then taken in their own direction
-because I found those to be inadequate.
+'''Handlebar templates are data binding templates more-than-loosely inspired by
+ctemplate. Use like:
-from handlebar import Handlebar
+ from handlebar import Handlebar
-template = Handlebar('hello {{#foo}}{{bar}}{{/}} world')
-input = {
- 'foo': [
- { 'bar': 1 },
- { 'bar': 2 },
- { 'bar': 3 }
- ]
-}
-print(template.render(input).text)
+ template = Handlebar('hello {{#foo}}{{bar}}{{/}} world')
+ input = {
+ 'foo': [
+ { 'bar': 1 },
+ { 'bar': 2 },
+ { 'bar': 3 }
+ ]
+ }
+ print(template.render(input).text)
Handlebar will use get() on contexts to return values, so to create custom
-getters (e.g. something that populates values lazily from keys) just add
-a get() method.
+getters (for example, something that populates values lazily from keys), just
+provide an object with a get() method.
-class CustomContext(object):
- def get(self, key):
- return 10
+ class CustomContext(object):
+ def get(self, key):
+ return 10
+ print(Handlebar('hello {{world}}').render(CustomContext()).text)
-# Any time {{ }} is used, will fill it with 10.
-print(Handlebar('hello {{world}}').render(CustomContext()).text)
-"""
+will print 'hello 10'.
+'''
class ParseException(Exception):
- """ Exception thrown while parsing the template.
- """
- def __init__(self, error, line):
- Exception.__init__(self, "%s (line %s)" % (error, line.number))
+ '''The exception thrown while parsing a template.
+ '''
+ def __init__(self, error):
+ Exception.__init__(self, error)
class RenderResult(object):
- """ Result of a render operation.
- """
+ '''The result of a render operation.
+ '''
def __init__(self, text, errors):
self.text = text;
self.errors = errors
-class StringBuilder(object):
- """ Mimics Java's StringBuilder for easy porting from the Java version of
- this file to Python.
- """
+ def __str__(self):
+ return self.text
+
+ def __repr__(self):
+ return '%s(text=%s, errors=%s)' % (
+ self.__class__.__name__, self.text, self.errors)
+
+class _StringBuilder(object):
+ '''Efficiently builds strings.
+ '''
def __init__(self):
self._buf = []
@@ -67,680 +81,843 @@ class StringBuilder(object):
self._Collapse()
return len(self._buf[0])
- def append(self, string):
+ def Append(self, string):
+ if not isinstance(string, basestring):
+ string = str(string)
self._buf.append(string)
- def toString(self):
+ def ToString(self):
self._Collapse()
return self._buf[0]
def __str__(self):
- return self.toString()
+ return self.ToString()
def _Collapse(self):
self._buf = [u''.join(self._buf)]
-class RenderState(object):
- """ The state of a render call.
- """
- def __init__(self, globalContexts, localContexts):
- self.globalContexts = globalContexts
- self.localContexts = localContexts
- self.text = StringBuilder()
- self.errors = []
- self._errorsDisabled = False
-
- def inSameContext(self):
- return RenderState(self.globalContexts, self.localContexts)
-
- def getFirstContext(self):
- if len(self.localContexts) > 0:
- return self.localContexts[0]
- if len(self.globalContexts) > 0:
- return self.globalContexts[0]
- return None
-
- def disableErrors(self):
- self._errorsDisabled = True
- return self
+class _Contexts(object):
+ '''Tracks a stack of context objects, providing efficient key/value retrieval.
+ '''
+ class _Node(object):
+ '''A node within the stack. Wraps a real context and maintains the key/value
+ pairs seen so far.
+ '''
+ def __init__(self, value):
+ self._value = value
+ self._value_has_get = hasattr(value, 'get')
+ self._found = {}
+
+ def GetKeys(self):
+ '''Returns the list of keys that |_value| contains.
+ '''
+ return self._found.keys()
+
+ def Get(self, key):
+ '''Returns the value for |key|, or None if not found (including if
+ |_value| doesn't support key retrieval).
+ '''
+ if not self._value_has_get:
+ return None
+ value = self._found.get(key)
+ if value is not None:
+ return value
+ value = self._value.get(key)
+ if value is not None:
+ self._found[key] = value
+ return value
+
+ def __init__(self, globals_):
+ '''Initializes with the initial global contexts, listed in order from most
+ to least important.
+ '''
+ self._nodes = map(_Contexts._Node, globals_)
+ self._first_local = len(self._nodes)
+ self._value_info = {}
+
+ def CreateFromGlobals(self):
+ new = _Contexts([])
+ new._nodes = self._nodes[:self._first_local]
+ new._first_local = self._first_local
+ return new
+
+ def Push(self, context):
+ self._nodes.append(_Contexts._Node(context))
+
+ def Pop(self):
+ node = self._nodes.pop()
+ assert len(self._nodes) >= self._first_local
+ for found_key in node.GetKeys():
+ # [0] is the stack of nodes that |found_key| has been found in.
+ self._value_info[found_key][0].pop()
+
+ def GetTopLocal(self):
+ if len(self._nodes) == self._first_local:
+ return None
+ return self._nodes[-1]._value
- def addError(self, *messages):
- if self._errorsDisabled:
- return self
- buf = StringBuilder()
- for message in messages:
- buf.append(str(message))
- self.errors.append(buf.toString())
- return self
+ def Resolve(self, path):
+ # This method is only efficient at finding |key|; if |tail| has a value (and
+ # |key| evaluates to an indexable value) we'll need to descend into that.
+ key, tail = path.split('.', 1) if '.' in path else (path, None)
- def getResult(self):
- return RenderResult(self.text.toString(), self.errors);
-
-class Identifier(object):
- """ An identifier of the form "@", "foo.bar.baz", or "@.foo.bar.baz".
- """
- def __init__(self, name, line):
- self._isThis = (name == '@')
- if self._isThis:
- self._startsWithThis = False
- self._path = []
- return
+ if key == '@':
+ found = self._nodes[-1]._value
+ else:
+ found = self._FindNodeValue(key)
- thisDot = '@.'
- self._startsWithThis = name.startswith(thisDot)
- if self._startsWithThis:
- name = name[len(thisDot):]
-
- if not re.match('^[a-zA-Z0-9._\\-/]+$', name):
- raise ParseException(name + " is not a valid identifier", line)
- self._path = name.split('.')
-
- def resolve(self, renderState):
- if self._isThis:
- return renderState.getFirstContext()
-
- if self._startsWithThis:
- return self._resolveFromContext(renderState.getFirstContext())
-
- resolved = self._resolveFromContexts(renderState.localContexts)
- if resolved is None:
- resolved = self._resolveFromContexts(renderState.globalContexts)
- if resolved is None:
- renderState.addError("Couldn't resolve identifier ", self._path)
- return resolved
-
- def _resolveFromContexts(self, contexts):
- for context in contexts:
- resolved = self._resolveFromContext(context)
- if resolved is not None:
- return resolved
- return None
-
- def _resolveFromContext(self, context):
- result = context
- for next in self._path:
- # Only require that contexts provide a get method, meaning that callers
- # can provide dict-like contexts (for example, to populate values lazily).
- if result is None or not getattr(result, "get", None):
+ if tail is None:
+ return found
+
+ for part in tail.split('.'):
+ if not hasattr(found, 'get'):
return None
- result = result.get(next)
- return result
+ found = found.get(part)
+ return found
+
+ def _FindNodeValue(self, key):
+ # |found_node_list| will be all the nodes that |key| has been found in.
+ # |checked_node_set| are those that have been checked.
+ info = self._value_info.get(key)
+ if info is None:
+ info = ([], set())
+ self._value_info[key] = info
+ found_node_list, checked_node_set = info
+
+ # Check all the nodes not yet checked for |key|.
+ newly_found = []
+ for node in reversed(self._nodes):
+ if node in checked_node_set:
+ break
+ value = node.Get(key)
+ if value is not None:
+ newly_found.append(node)
+ checked_node_set.add(node)
+
+ # The nodes will have been found in reverse stack order. After extending
+ # the found nodes, the freshest value will be at the tip of the stack.
+ found_node_list.extend(reversed(newly_found))
+ if not found_node_list:
+ return None
+
+ return found_node_list[-1]._value[key]
+
+class _Stack(object):
+ class Entry(object):
+ def __init__(self, name, id_):
+ self.name = name
+ self.id_ = id_
+
+ def __init__(self, entries=[]):
+ self.entries = entries
+
+ def Descend(self, name, id_):
+ descended = list(self.entries)
+ descended.append(_Stack.Entry(name, id_))
+ return _Stack(entries=descended)
+
+class _RenderState(object):
+ '''The state of a render call.
+ '''
+ def __init__(self, name, contexts, _stack=_Stack()):
+ self.text = _StringBuilder()
+ self.contexts = contexts
+ self._name = name
+ self._errors = []
+ self._stack = _stack
+
+ def AddResolutionError(self, id_):
+ self._errors.append(
+ id_.CreateResolutionErrorMessage(self._name, stack=self._stack))
+
+ def Copy(self):
+ return _RenderState(
+ self._name, self.contexts, _stack=self._stack)
+
+ def ForkPartial(self, custom_name, id_):
+ name = custom_name or id_.name
+ return _RenderState(name,
+ self.contexts.CreateFromGlobals(),
+ _stack=self._stack.Descend(name, id_))
+
+ def Merge(self, render_state, text_transform=None):
+ self._errors.extend(render_state._errors)
+ text = render_state.text.ToString()
+ if text_transform is not None:
+ text = text_transform(text)
+ self.text.Append(text)
+
+ def GetResult(self):
+ return RenderResult(self.text.ToString(), self._errors);
+
+class _Identifier(object):
+ ''' An identifier of the form '@', 'foo.bar.baz', or '@.foo.bar.baz'.
+ '''
+ def __init__(self, name, line, column):
+ self.name = name
+ self.line = line
+ self.column = column
+ if name == '':
+ raise ParseException('Empty identifier %s' % self.GetDescription())
+ for part in name.split('.'):
+ if part != '@' and not re.match('^[a-zA-Z0-9_/-]+$', part):
+ raise ParseException('Invalid identifier %s' % self.GetDescription())
+
+ def GetDescription(self):
+ return '\'%s\' at line %s column %s' % (self.name, self.line, self.column)
+
+ def CreateResolutionErrorMessage(self, name, stack=None):
+ message = _StringBuilder()
+ message.Append('Failed to resolve %s in %s\n' % (self.GetDescription(),
+ name))
+ if stack is not None:
+ for entry in stack.entries:
+ message.Append(' included as %s in %s\n' % (entry.id_.GetDescription(),
+ entry.name))
+ return message.ToString()
def __str__(self):
- if self._isThis:
- return '@'
- name = '.'.join(self._path)
- return ('@.' + name) if self._startsWithThis else name
+ raise ValueError()
-class Line(object):
+class _Line(object):
def __init__(self, number):
self.number = number
-class LeafNode(object):
- def __init__(self, line):
- self._line = line
+ def __str__(self):
+ return str(self.number)
- def startsWithNewLine(self):
+class _LeafNode(object):
+ def __init__(self, start_line, end_line):
+ self._start_line = start_line
+ self._end_line = end_line
+
+ def StartsWithNewLine(self):
return False
- def trimStartingNewLine(self):
+ def TrimStartingNewLine(self):
pass
- def trimEndingSpaces(self):
+ def TrimEndingSpaces(self):
return 0
- def trimEndingNewLine(self):
+ def TrimEndingNewLine(self):
pass
- def endsWithEmptyLine(self):
+ def EndsWithEmptyLine(self):
return False
- def getStartLine(self):
- return self._line
+ def GetStartLine(self):
+ return self._start_line
- def getEndLine(self):
- return self._line
+ def GetEndLine(self):
+ return self._end_line
-class DecoratorNode(object):
+class _DecoratorNode(object):
def __init__(self, content):
self._content = content
- def startsWithNewLine(self):
- return self._content.startsWithNewLine()
+ def StartsWithNewLine(self):
+ return self._content.StartsWithNewLine()
- def trimStartingNewLine(self):
- self._content.trimStartingNewLine()
+ def TrimStartingNewLine(self):
+ self._content.TrimStartingNewLine()
- def trimEndingSpaces(self):
- return self._content.trimEndingSpaces()
+ def TrimEndingSpaces(self):
+ return self._content.TrimEndingSpaces()
- def trimEndingNewLine(self):
- self._content.trimEndingNewLine()
+ def TrimEndingNewLine(self):
+ self._content.TrimEndingNewLine()
- def endsWithEmptyLine(self):
- return self._content.endsWithEmptyLine()
+ def EndsWithEmptyLine(self):
+ return self._content.EndsWithEmptyLine()
- def getStartLine(self):
- return self._content.getStartLine()
+ def GetStartLine(self):
+ return self._content.GetStartLine()
- def getEndLine(self):
- return self._content.getEndLine()
+ def GetEndLine(self):
+ return self._content.GetEndLine()
-class InlineNode(DecoratorNode):
+class _InlineNode(_DecoratorNode):
def __init__(self, content):
- DecoratorNode.__init__(self, content)
-
- def render(self, renderState):
- contentRenderState = renderState.inSameContext()
- self._content.render(contentRenderState)
+ _DecoratorNode.__init__(self, content)
- renderState.errors.extend(contentRenderState.errors)
- renderState.text.append(
- contentRenderState.text.toString().replace('\n', ''))
+ def Render(self, render_state):
+ content_render_state = render_state.Copy()
+ self._content.Render(content_render_state)
+ render_state.Merge(content_render_state,
+ text_transform=lambda text: text.replace('\n', ''))
-class IndentedNode(DecoratorNode):
+class _IndentedNode(_DecoratorNode):
def __init__(self, content, indentation):
- DecoratorNode.__init__(self, content)
+ _DecoratorNode.__init__(self, content)
self._indent_str = ' ' * indentation
- def render(self, renderState):
- contentRenderState = renderState.inSameContext()
- self._content.render(contentRenderState)
-
- renderState.errors.extend(contentRenderState.errors)
- renderState.text.append(self._indent_str)
- # TODO: this might introduce an extra \n at the end? need test.
- renderState.text.append(
- contentRenderState.text.toString().replace('\n',
- '\n' + self._indent_str))
- renderState.text.append('\n')
-
-class BlockNode(DecoratorNode):
+ def Render(self, render_state):
+ if isinstance(self._content, _CommentNode):
+ return
+ content_render_state = render_state.Copy()
+ self._content.Render(content_render_state)
+ def AddIndentation(text):
+ buf = _StringBuilder()
+ buf.Append(self._indent_str)
+ buf.Append(text.replace('\n', '\n%s' % self._indent_str))
+ buf.Append('\n')
+ return buf.ToString()
+ render_state.Merge(content_render_state, text_transform=AddIndentation)
+
+class _BlockNode(_DecoratorNode):
def __init__(self, content):
- DecoratorNode.__init__(self, content)
- content.trimStartingNewLine()
- content.trimEndingSpaces()
+ _DecoratorNode.__init__(self, content)
+ content.TrimStartingNewLine()
+ content.TrimEndingSpaces()
- def render(self, renderState):
- self._content.render(renderState)
+ def Render(self, render_state):
+ self._content.Render(render_state)
-class NodeCollection(object):
+class _NodeCollection(object):
def __init__(self, nodes):
- if len(nodes) == 0:
- raise ValueError()
+ assert nodes
self._nodes = nodes
- def render(self, renderState):
+ def Render(self, render_state):
for node in self._nodes:
- node.render(renderState)
+ node.Render(render_state)
- def startsWithNewLine(self):
- return self._nodes[0].startsWithNewLine()
+ def StartsWithNewLine(self):
+ return self._nodes[0].StartsWithNewLine()
- def trimStartingNewLine(self):
- self._nodes[0].trimStartingNewLine()
+ def TrimStartingNewLine(self):
+ self._nodes[0].TrimStartingNewLine()
- def trimEndingSpaces(self):
- return self._nodes[-1].trimEndingSpaces()
+ def TrimEndingSpaces(self):
+ return self._nodes[-1].TrimEndingSpaces()
- def trimEndingNewLine(self):
- self._nodes[-1].trimEndingNewLine()
+ def TrimEndingNewLine(self):
+ self._nodes[-1].TrimEndingNewLine()
- def endsWithEmptyLine(self):
- return self._nodes[-1].endsWithEmptyLine()
+ def EndsWithEmptyLine(self):
+ return self._nodes[-1].EndsWithEmptyLine()
- def getStartLine(self):
- return self._nodes[0].getStartLine()
+ def GetStartLine(self):
+ return self._nodes[0].GetStartLine()
- def getEndLine(self):
- return self._nodes[-1].getEndLine()
+ def GetEndLine(self):
+ return self._nodes[-1].GetEndLine()
-class StringNode(object):
- """ Just a string.
- """
- def __init__(self, string, startLine, endLine):
+class _StringNode(object):
+ ''' Just a string.
+ '''
+ def __init__(self, string, start_line, end_line):
self._string = string
- self._startLine = startLine
- self._endLine = endLine
+ self._start_line = start_line
+ self._end_line = end_line
- def render(self, renderState):
- renderState.text.append(self._string)
+ def Render(self, render_state):
+ render_state.text.Append(self._string)
- def startsWithNewLine(self):
+ def StartsWithNewLine(self):
return self._string.startswith('\n')
- def trimStartingNewLine(self):
- if self.startsWithNewLine():
+ def TrimStartingNewLine(self):
+ if self.StartsWithNewLine():
self._string = self._string[1:]
- def trimEndingSpaces(self):
- originalLength = len(self._string)
- self._string = self._string[:self._lastIndexOfSpaces()]
- return originalLength - len(self._string)
+ def TrimEndingSpaces(self):
+ original_length = len(self._string)
+ self._string = self._string[:self._LastIndexOfSpaces()]
+ return original_length - len(self._string)
- def trimEndingNewLine(self):
+ def TrimEndingNewLine(self):
if self._string.endswith('\n'):
self._string = self._string[:len(self._string) - 1]
- def endsWithEmptyLine(self):
- index = self._lastIndexOfSpaces()
+ def EndsWithEmptyLine(self):
+ index = self._LastIndexOfSpaces()
return index == 0 or self._string[index - 1] == '\n'
- def _lastIndexOfSpaces(self):
+ def _LastIndexOfSpaces(self):
index = len(self._string)
while index > 0 and self._string[index - 1] == ' ':
index -= 1
return index
- def getStartLine(self):
- return self._startLine
+ def GetStartLine(self):
+ return self._start_line
- def getEndLine(self):
- return self._endLine
+ def GetEndLine(self):
+ return self._end_line
-class EscapedVariableNode(LeafNode):
- """ {{foo}}
- """
- def __init__(self, id, line):
- LeafNode.__init__(self, line)
- self._id = id
+class _EscapedVariableNode(_LeafNode):
+ ''' {{foo}}
+ '''
+ def __init__(self, id_):
+ _LeafNode.__init__(self, id_.line, id_.line)
+ self._id = id_
- def render(self, renderState):
- value = self._id.resolve(renderState)
+ def Render(self, render_state):
+ value = render_state.contexts.Resolve(self._id.name)
if value is None:
+ render_state.AddResolutionError(self._id)
return
-
string = value if isinstance(value, basestring) else str(value)
- renderState.text.append(string.replace('&', '&')
- .replace('<', '&lt;')
- .replace('>', '&gt;'))
-
-class UnescapedVariableNode(LeafNode):
- """ {{{foo}}}
- """
- def __init__(self, id, line):
- LeafNode.__init__(self, line)
- self._id = id
-
- def render(self, renderState):
- value = self._id.resolve(renderState)
- if value is None:
- return
- renderState.text.append(
- value if isinstance(value, basestring) else str(value))
-
-class SectionNode(DecoratorNode):
- """ {{#foo}} ... {{/}}
- """
- def __init__(self, id, content):
- DecoratorNode.__init__(self, content)
- self._id = id
-
- def render(self, renderState):
- value = self._id.resolve(renderState)
+ render_state.text.Append(string.replace('&', '&amp;')
+ .replace('<', '&lt;')
+ .replace('>', '&gt;'))
+
+class _UnescapedVariableNode(_LeafNode):
+ ''' {{{foo}}}
+ '''
+ def __init__(self, id_):
+ _LeafNode.__init__(self, id_.line, id_.line)
+ self._id = id_
+
+ def Render(self, render_state):
+ value = render_state.contexts.Resolve(self._id.name)
if value is None:
+ render_state.AddResolutionError(self._id)
return
+ string = value if isinstance(value, basestring) else str(value)
+ render_state.text.Append(string)
+
+class _CommentNode(_LeafNode):
+ '''{{- This is a comment -}}
+ An empty placeholder node for correct indented rendering behaviour.
+ '''
+ def __init__(self, start_line, end_line):
+ _LeafNode.__init__(self, start_line, end_line)
+
+ def Render(self, render_state):
+ pass
+class _SectionNode(_DecoratorNode):
+ ''' {{#foo}} ... {{/}}
+ '''
+ def __init__(self, id_, content):
+ _DecoratorNode.__init__(self, content)
+ self._id = id_
+
+ def Render(self, render_state):
+ value = render_state.contexts.Resolve(self._id.name)
if isinstance(value, list):
for item in value:
- renderState.localContexts.insert(0, item)
- self._content.render(renderState)
- renderState.localContexts.pop(0)
- elif isinstance(value, dict):
- renderState.localContexts.insert(0, value)
- self._content.render(renderState)
- renderState.localContexts.pop(0)
+ # Always push context, even if it's not "valid", since we want to
+ # be able to refer to items in a list such as [1,2,3] via @.
+ render_state.contexts.Push(item)
+ self._content.Render(render_state)
+ render_state.contexts.Pop()
+ elif hasattr(value, 'get'):
+ render_state.contexts.Push(value)
+ self._content.Render(render_state)
+ render_state.contexts.Pop()
else:
- renderState.addError("{{#", self._id,
- "}} cannot be rendered with a ", type(value))
-
-class VertedSectionNode(DecoratorNode):
- """ {{?foo}} ... {{/}}
- """
- def __init__(self, id, content):
- DecoratorNode.__init__(self, content)
- self._id = id
-
- def render(self, renderState):
- value = self._id.resolve(renderState.inSameContext().disableErrors())
- if _VertedSectionNodeShouldRender(value):
- renderState.localContexts.insert(0, value)
- self._content.render(renderState)
- renderState.localContexts.pop(0)
-
-def _VertedSectionNodeShouldRender(value):
- if value is None:
- return False
- if isinstance(value, bool):
- return value
- if (isinstance(value, int) or
- isinstance(value, long) or
- isinstance(value, float)):
- return True
- if isinstance(value, basestring):
- return True
- if isinstance(value, list):
- return len(value) > 0
- if isinstance(value, dict):
+ render_state.AddResolutionError(self._id)
+
+class _VertedSectionNode(_DecoratorNode):
+ ''' {{?foo}} ... {{/}}
+ '''
+ def __init__(self, id_, content):
+ _DecoratorNode.__init__(self, content)
+ self._id = id_
+
+ def Render(self, render_state):
+ value = render_state.contexts.Resolve(self._id.name)
+ if _VertedSectionNode.ShouldRender(value):
+ render_state.contexts.Push(value)
+ self._content.Render(render_state)
+ render_state.contexts.Pop()
+
+ @staticmethod
+ def ShouldRender(value):
+ if value is None:
+ return False
+ if isinstance(value, bool):
+ return value
+ if isinstance(value, list):
+ return len(value) > 0
return True
- raise TypeError("Unhandled type %s" % type(value))
-
-class InvertedSectionNode(DecoratorNode):
- """ {{^foo}} ... {{/}}
- """
- def __init__(self, id, content):
- DecoratorNode.__init__(self, content)
- self._id = id
-
- def render(self, renderState):
- value = self._id.resolve(renderState.inSameContext().disableErrors())
- if not _VertedSectionNodeShouldRender(value):
- self._content.render(renderState)
-
-class JsonNode(LeafNode):
- """ {{*foo}}
- """
- def __init__(self, id, line):
- LeafNode.__init__(self, line)
- self._id = id
-
- def render(self, renderState):
- value = self._id.resolve(renderState)
+
+class _InvertedSectionNode(_DecoratorNode):
+ ''' {{^foo}} ... {{/}}
+ '''
+ def __init__(self, id_, content):
+ _DecoratorNode.__init__(self, content)
+ self._id = id_
+
+ def Render(self, render_state):
+ value = render_state.contexts.Resolve(self._id.name)
+ if not _VertedSectionNode.ShouldRender(value):
+ self._content.Render(render_state)
+
+class _JsonNode(_LeafNode):
+ ''' {{*foo}}
+ '''
+ def __init__(self, id_):
+ _LeafNode.__init__(self, id_.line, id_.line)
+ self._id = id_
+
+ def Render(self, render_state):
+ value = render_state.contexts.Resolve(self._id.name)
if value is None:
+ render_state.AddResolutionError(self._id)
return
- renderState.text.append(json.dumps(value, separators=(',',':')))
-
-class PartialNode(LeafNode):
- """ {{+foo}}
- """
- def __init__(self, id, line):
- LeafNode.__init__(self, line)
- self._id = id
+ render_state.text.Append(json.dumps(value, separators=(',',':')))
+
+class _PartialNode(_LeafNode):
+ ''' {{+foo}}
+ '''
+ def __init__(self, id_):
+ _LeafNode.__init__(self, id_.line, id_.line)
+ self._id = id_
self._args = None
+ self._local_context_id = None
- def render(self, renderState):
- value = self._id.resolve(renderState)
+ def Render(self, render_state):
+ value = render_state.contexts.Resolve(self._id.name)
+ if value is None:
+ render_state.AddResolutionError(self._id)
+ return
if not isinstance(value, Handlebar):
- renderState.addError(self._id, " didn't resolve to a Handlebar")
+ render_state.AddResolutionError(self._id)
return
- argContext = []
- if len(renderState.localContexts) > 0:
- argContext.append(renderState.localContexts[0])
+ partial_render_state = render_state.ForkPartial(value._name, self._id)
- if self._args:
- argContextMap = {}
- for key, valueId in self._args.items():
- context = valueId.resolve(renderState)
- if context:
- argContextMap[key] = context
- argContext.append(argContextMap)
+ # TODO: Don't do this. Force callers to do this by specifying an @ argument.
+ top_local = render_state.contexts.GetTopLocal()
+ if top_local is not None:
+ partial_render_state.contexts.Push(top_local)
- partialRenderState = RenderState(renderState.globalContexts, argContext)
- value._topNode.render(partialRenderState)
+ if self._args is not None:
+ arg_context = {}
+ for key, value_id in self._args.items():
+ context = render_state.contexts.Resolve(value_id.name)
+ if context is not None:
+ arg_context[key] = context
+ partial_render_state.contexts.Push(arg_context)
- text = partialRenderState.text.toString()
- if len(text) > 0 and text[-1] == '\n':
- text = text[:-1]
+ if self._local_context_id is not None:
+ local_context = render_state.contexts.Resolve(self._local_context_id.name)
+ if local_context is not None:
+ partial_render_state.contexts.Push(local_context)
- renderState.text.append(text)
- renderState.errors.extend(partialRenderState.errors)
+ value._top_node.Render(partial_render_state)
- def addArgument(self, key, valueId):
- if not self._args:
+ render_state.Merge(
+ partial_render_state,
+ text_transform=lambda text: text[:-1] if text.endswith('\n') else text)
+
+ def AddArgument(self, key, id_):
+ if self._args is None:
self._args = {}
- self._args[key] = valueId
+ self._args[key] = id_
+
+ def SetLocalContext(self, id_):
+ self._local_context_id = id_
-# List of tokens in order of longest to shortest, to avoid any prefix matching
-# issues.
-TokenValues = []
+_TOKENS = {}
-class Token(object):
- """ The tokens that can appear in a template.
- """
+class _Token(object):
+ ''' The tokens that can appear in a template.
+ '''
class Data(object):
def __init__(self, name, text, clazz):
self.name = name
self.text = text
self.clazz = clazz
- TokenValues.append(self)
-
- def elseNodeClass(self):
- if self.clazz == VertedSectionNode:
- return InvertedSectionNode
- if self.clazz == InvertedSectionNode:
- return VertedSectionNode
- raise ValueError(self.clazz + " can not have an else clause.")
-
- OPEN_START_SECTION = Data("OPEN_START_SECTION" , "{{#", SectionNode)
- OPEN_START_VERTED_SECTION = Data("OPEN_START_VERTED_SECTION" , "{{?", VertedSectionNode)
- OPEN_START_INVERTED_SECTION = Data("OPEN_START_INVERTED_SECTION", "{{^", InvertedSectionNode)
- OPEN_START_JSON = Data("OPEN_START_JSON" , "{{*", JsonNode)
- OPEN_START_PARTIAL = Data("OPEN_START_PARTIAL" , "{{+", PartialNode)
- OPEN_ELSE = Data("OPEN_ELSE" , "{{:", None)
- OPEN_END_SECTION = Data("OPEN_END_SECTION" , "{{/", None)
- OPEN_UNESCAPED_VARIABLE = Data("OPEN_UNESCAPED_VARIABLE" , "{{{", UnescapedVariableNode)
- CLOSE_MUSTACHE3 = Data("CLOSE_MUSTACHE3" , "}}}", None)
- OPEN_COMMENT = Data("OPEN_COMMENT" , "{{-", None)
- CLOSE_COMMENT = Data("CLOSE_COMMENT" , "-}}", None)
- OPEN_VARIABLE = Data("OPEN_VARIABLE" , "{{" , EscapedVariableNode)
- CLOSE_MUSTACHE = Data("CLOSE_MUSTACHE" , "}}" , None)
- CHARACTER = Data("CHARACTER" , "." , None)
-
-class TokenStream(object):
- """ Tokeniser for template parsing.
- """
+ _TOKENS[text] = self
+
+ def ElseNodeClass(self):
+ if self.clazz == _VertedSectionNode:
+ return _InvertedSectionNode
+ if self.clazz == _InvertedSectionNode:
+ return _VertedSectionNode
+ raise ValueError('%s cannot have an else clause.' % self.clazz)
+
+ def __str__(self):
+ return '%s(%s)' % (self.name, self.text)
+
+ OPEN_START_SECTION = Data('OPEN_START_SECTION' , '{{#', _SectionNode)
+ OPEN_START_VERTED_SECTION = Data('OPEN_START_VERTED_SECTION' , '{{?', _VertedSectionNode)
+ OPEN_START_INVERTED_SECTION = Data('OPEN_START_INVERTED_SECTION', '{{^', _InvertedSectionNode)
+ OPEN_START_JSON = Data('OPEN_START_JSON' , '{{*', _JsonNode)
+ OPEN_START_PARTIAL = Data('OPEN_START_PARTIAL' , '{{+', _PartialNode)
+ OPEN_ELSE = Data('OPEN_ELSE' , '{{:', None)
+ OPEN_END_SECTION = Data('OPEN_END_SECTION' , '{{/', None)
+ INLINE_END_SECTION = Data('INLINE_END_SECTION' , '/}}', None)
+ OPEN_UNESCAPED_VARIABLE = Data('OPEN_UNESCAPED_VARIABLE' , '{{{', _UnescapedVariableNode)
+ CLOSE_MUSTACHE3 = Data('CLOSE_MUSTACHE3' , '}}}', None)
+ OPEN_COMMENT = Data('OPEN_COMMENT' , '{{-', _CommentNode)
+ CLOSE_COMMENT = Data('CLOSE_COMMENT' , '-}}', None)
+ OPEN_VARIABLE = Data('OPEN_VARIABLE' , '{{' , _EscapedVariableNode)
+ CLOSE_MUSTACHE = Data('CLOSE_MUSTACHE' , '}}' , None)
+ CHARACTER = Data('CHARACTER' , '.' , None)
+
+class _TokenStream(object):
+ ''' Tokeniser for template parsing.
+ '''
def __init__(self, string):
- self._remainder = string
-
- self.nextToken = None
- self.nextContents = None
- self.nextLine = Line(1)
- self.advance()
+ self.next_token = None
+ self.next_line = _Line(1)
+ self.next_column = 0
+ self._string = string
+ self._cursor = 0
+ self.Advance()
- def hasNext(self):
- return self.nextToken is not None
+ def HasNext(self):
+ return self.next_token is not None
- def advance(self):
- if self.nextContents == '\n':
- self.nextLine = Line(self.nextLine.number + 1)
+ def Advance(self):
+ if self._cursor > 0 and self._string[self._cursor - 1] == '\n':
+ self.next_line = _Line(self.next_line.number + 1)
+ self.next_column = 0
+ elif self.next_token is not None:
+ self.next_column += len(self.next_token.text)
- self.nextToken = None
- self.nextContents = None
+ self.next_token = None
- if self._remainder == '':
+ if self._cursor == len(self._string):
return None
+ assert self._cursor < len(self._string)
- for token in TokenValues:
- if self._remainder.startswith(token.text):
- self.nextToken = token
- break
+ if (self._cursor + 1 < len(self._string) and
+ self._string[self._cursor + 1] in '{}'):
+ self.next_token = (
+ _TOKENS.get(self._string[self._cursor:self._cursor+3]) or
+ _TOKENS.get(self._string[self._cursor:self._cursor+2]))
- if not self.nextToken:
- self.nextToken = Token.CHARACTER
+ if self.next_token is None:
+ self.next_token = _Token.CHARACTER
- self.nextContents = self._remainder[0:len(self.nextToken.text)]
- self._remainder = self._remainder[len(self.nextToken.text):]
+ self._cursor += len(self.next_token.text)
return self
- def advanceOver(self, token):
- if self.nextToken != token:
+ def AdvanceOver(self, token):
+ if self.next_token != token:
raise ParseException(
- "Expecting token " + token.name + " but got " + self.nextToken.name,
- self.nextLine)
- return self.advance()
-
- def advanceOverNextString(self, excluded=''):
- buf = StringBuilder()
- while self.nextToken == Token.CHARACTER and \
- excluded.find(self.nextContents) == -1:
- buf.append(self.nextContents)
- self.advance()
- return buf.toString()
-
- def advanceToNextWhitespace(self):
- return self.advanceOverNextString(excluded=' \n\r\t')
-
- def skipWhitespace(self):
- while len(self.nextContents) > 0 and \
- ' \n\r\t'.find(self.nextContents) >= 0:
- self.advance()
+ 'Expecting token %s but got %s at line %s' % (token.name,
+ self.next_token.name,
+ self.next_line))
+ return self.Advance()
+
+ def AdvanceOverNextString(self, excluded=''):
+ start = self._cursor - len(self.next_token.text)
+ while (self.next_token is _Token.CHARACTER and
+ # Can use -1 here because token length of CHARACTER is 1.
+ self._string[self._cursor - 1] not in excluded):
+ self.Advance()
+ end = self._cursor - (len(self.next_token.text) if self.next_token else 0)
+ return self._string[start:end]
+
+ def AdvanceToNextWhitespace(self):
+ return self.AdvanceOverNextString(excluded=' \n\r\t')
+
+ def SkipWhitespace(self):
+ while (self.next_token is _Token.CHARACTER and
+ # Can use -1 here because token length of CHARACTER is 1.
+ self._string[self._cursor - 1] in ' \n\r\t'):
+ self.Advance()
class Handlebar(object):
- """ A handlebar template.
- """
- def __init__(self, template):
+ ''' A handlebar template.
+ '''
+ def __init__(self, template, name=None):
self.source = template
- tokens = TokenStream(template)
- self._topNode = self._parseSection(tokens)
- if not self._topNode:
- raise ParseException("Template is empty", tokens.nextLine)
- if tokens.hasNext():
- raise ParseException("There are still tokens remaining, "
- "was there an end-section without a start-section:",
- tokens.nextLine)
-
- def _parseSection(self, tokens):
+ self._name = name
+ tokens = _TokenStream(template)
+ self._top_node = self._ParseSection(tokens)
+ if not self._top_node:
+ raise ParseException('Template is empty')
+ if tokens.HasNext():
+ raise ParseException('There are still tokens remaining at %s, '
+ 'was there an end-section without a start-section?'
+ % tokens.next_line)
+
+ def _ParseSection(self, tokens):
nodes = []
- sectionEnded = False
-
- while tokens.hasNext() and not sectionEnded:
- token = tokens.nextToken
-
- if token == Token.CHARACTER:
- startLine = tokens.nextLine
- string = tokens.advanceOverNextString()
- nodes.append(StringNode(string, startLine, tokens.nextLine))
- elif token == Token.OPEN_VARIABLE or \
- token == Token.OPEN_UNESCAPED_VARIABLE or \
- token == Token.OPEN_START_JSON:
- id = self._openSectionOrTag(tokens)
- nodes.append(token.clazz(id, tokens.nextLine))
- elif token == Token.OPEN_START_PARTIAL:
- tokens.advance()
- id = Identifier(tokens.advanceToNextWhitespace(),
- tokens.nextLine)
- partialNode = PartialNode(id, tokens.nextLine)
-
- while tokens.nextToken == Token.CHARACTER:
- tokens.skipWhitespace()
- key = tokens.advanceOverNextString(excluded=':')
- tokens.advance()
- partialNode.addArgument(
- key,
- Identifier(tokens.advanceToNextWhitespace(),
- tokens.nextLine))
-
- tokens.advanceOver(Token.CLOSE_MUSTACHE)
- nodes.append(partialNode)
- elif token == Token.OPEN_START_SECTION:
- id = self._openSectionOrTag(tokens)
- section = self._parseSection(tokens)
- self._closeSection(tokens, id)
- if section:
- nodes.append(SectionNode(id, section))
- elif token == Token.OPEN_START_VERTED_SECTION or \
- token == Token.OPEN_START_INVERTED_SECTION:
- id = self._openSectionOrTag(tokens)
- section = self._parseSection(tokens)
- elseSection = None
- if tokens.nextToken == Token.OPEN_ELSE:
- self._openElse(tokens, id)
- elseSection = self._parseSection(tokens)
- self._closeSection(tokens, id)
- if section:
- nodes.append(token.clazz(id, section))
- if elseSection:
- nodes.append(token.elseNodeClass()(id, elseSection))
- elif token == Token.OPEN_COMMENT:
- self._advanceOverComment(tokens)
- elif token == Token.OPEN_END_SECTION or \
- token == Token.OPEN_ELSE:
- # Handled after running parseSection within the SECTION cases, so this is a
- # terminating condition. If there *is* an orphaned OPEN_END_SECTION, it will be caught
- # by noticing that there are leftover tokens after termination.
- sectionEnded = True
- elif Token.CLOSE_MUSTACHE:
- raise ParseException("Orphaned " + tokens.nextToken.name,
- tokens.nextLine)
+ while tokens.HasNext():
+ if tokens.next_token in (_Token.OPEN_END_SECTION,
+ _Token.OPEN_ELSE):
+ # Handled after running parseSection within the SECTION cases, so this
+ # is a terminating condition. If there *is* an orphaned
+ # OPEN_END_SECTION, it will be caught by noticing that there are
+ # leftover tokens after termination.
+ break
+ elif tokens.next_token in (_Token.CLOSE_MUSTACHE,
+ _Token.CLOSE_MUSTACHE3):
+ raise ParseException('Orphaned %s at line %s' % (tokens.next_token.name,
+ tokens.next_line))
+ nodes += self._ParseNextOpenToken(tokens)
for i, node in enumerate(nodes):
- if isinstance(node, StringNode):
+ if isinstance(node, _StringNode):
continue
- previousNode = nodes[i - 1] if i > 0 else None
- nextNode = nodes[i + 1] if i < len(nodes) - 1 else None
- renderedNode = None
-
- if node.getStartLine() != node.getEndLine():
- renderedNode = BlockNode(node)
- if previousNode:
- previousNode.trimEndingSpaces()
- if nextNode:
- nextNode.trimStartingNewLine()
- elif isinstance(node, LeafNode) and \
- (not previousNode or previousNode.endsWithEmptyLine()) and \
- (not nextNode or nextNode.startsWithNewLine()):
+ previous_node = nodes[i - 1] if i > 0 else None
+ next_node = nodes[i + 1] if i < len(nodes) - 1 else None
+ rendered_node = None
+
+ if node.GetStartLine() != node.GetEndLine():
+ rendered_node = _BlockNode(node)
+ if previous_node:
+ previous_node.TrimEndingSpaces()
+ if next_node:
+ next_node.TrimStartingNewLine()
+ elif (isinstance(node, _LeafNode) and
+ (not previous_node or previous_node.EndsWithEmptyLine()) and
+ (not next_node or next_node.StartsWithNewLine())):
indentation = 0
- if previousNode:
- indentation = previousNode.trimEndingSpaces()
- if nextNode:
- nextNode.trimStartingNewLine()
- renderedNode = IndentedNode(node, indentation)
+ if previous_node:
+ indentation = previous_node.TrimEndingSpaces()
+ if next_node:
+ next_node.TrimStartingNewLine()
+ rendered_node = _IndentedNode(node, indentation)
else:
- renderedNode = InlineNode(node)
+ rendered_node = _InlineNode(node)
- nodes[i] = renderedNode
+ nodes[i] = rendered_node
if len(nodes) == 0:
return None
if len(nodes) == 1:
return nodes[0]
- return NodeCollection(nodes)
-
- def _advanceOverComment(self, tokens):
- tokens.advanceOver(Token.OPEN_COMMENT)
+ return _NodeCollection(nodes)
+
+ def _ParseNextOpenToken(self, tokens):
+ next_token = tokens.next_token
+
+ if next_token is _Token.CHARACTER:
+ start_line = tokens.next_line
+ string = tokens.AdvanceOverNextString()
+ return [_StringNode(string, start_line, tokens.next_line)]
+ elif next_token in (_Token.OPEN_VARIABLE,
+ _Token.OPEN_UNESCAPED_VARIABLE,
+ _Token.OPEN_START_JSON):
+ id_, inline_value_id = self._OpenSectionOrTag(tokens)
+ if inline_value_id is not None:
+ raise ParseException(
+ '%s cannot have an inline value' % id_.GetDescription())
+ return [next_token.clazz(id_)]
+ elif next_token is _Token.OPEN_START_PARTIAL:
+ tokens.Advance()
+ column_start = tokens.next_column + 1
+ id_ = _Identifier(tokens.AdvanceToNextWhitespace(),
+ tokens.next_line,
+ column_start)
+ partial_node = _PartialNode(id_)
+ while tokens.next_token is _Token.CHARACTER:
+ tokens.SkipWhitespace()
+ key = tokens.AdvanceOverNextString(excluded=':')
+ tokens.Advance()
+ column_start = tokens.next_column + 1
+ id_ = _Identifier(tokens.AdvanceToNextWhitespace(),
+ tokens.next_line,
+ column_start)
+ if key == '@':
+ partial_node.SetLocalContext(id_)
+ else:
+ partial_node.AddArgument(key, id_)
+ tokens.AdvanceOver(_Token.CLOSE_MUSTACHE)
+ return [partial_node]
+ elif next_token is _Token.OPEN_START_SECTION:
+ id_, inline_node = self._OpenSectionOrTag(tokens)
+ nodes = []
+ if inline_node is None:
+ section = self._ParseSection(tokens)
+ self._CloseSection(tokens, id_)
+ nodes = []
+ if section is not None:
+ nodes.append(_SectionNode(id_, section))
+ else:
+ nodes.append(_SectionNode(id_, inline_node))
+ return nodes
+ elif next_token in (_Token.OPEN_START_VERTED_SECTION,
+ _Token.OPEN_START_INVERTED_SECTION):
+ id_, inline_node = self._OpenSectionOrTag(tokens)
+ nodes = []
+ if inline_node is None:
+ section = self._ParseSection(tokens)
+ else_section = None
+ if tokens.next_token is _Token.OPEN_ELSE:
+ self._OpenElse(tokens, id_)
+ else_section = self._ParseSection(tokens)
+ self._CloseSection(tokens, id_)
+ if section:
+ nodes.append(next_token.clazz(id_, section))
+ if else_section:
+ nodes.append(next_token.ElseNodeClass()(id_, else_section))
+ else:
+ nodes.append(next_token.clazz(id_, inline_node))
+ return nodes
+ elif next_token is _Token.OPEN_COMMENT:
+ start_line = tokens.next_line
+ self._AdvanceOverComment(tokens)
+ return [_CommentNode(start_line, tokens.next_line)]
+
+ def _AdvanceOverComment(self, tokens):
+ tokens.AdvanceOver(_Token.OPEN_COMMENT)
depth = 1
- while tokens.hasNext() and depth > 0:
- if tokens.nextToken == Token.OPEN_COMMENT:
+ while tokens.HasNext() and depth > 0:
+ if tokens.next_token is _Token.OPEN_COMMENT:
depth += 1
- elif tokens.nextToken == Token.CLOSE_COMMENT:
+ elif tokens.next_token is _Token.CLOSE_COMMENT:
depth -= 1
- tokens.advance()
-
- def _openSectionOrTag(self, tokens):
- openToken = tokens.nextToken
- tokens.advance()
- id = Identifier(tokens.advanceOverNextString(), tokens.nextLine)
- if openToken == Token.OPEN_UNESCAPED_VARIABLE:
- tokens.advanceOver(Token.CLOSE_MUSTACHE3)
+ tokens.Advance()
+
+ def _OpenSectionOrTag(self, tokens):
+ def NextIdentifierArgs():
+ tokens.SkipWhitespace()
+ line = tokens.next_line
+ column = tokens.next_column + 1
+ name = tokens.AdvanceToNextWhitespace()
+ tokens.SkipWhitespace()
+ return (name, line, column)
+ close_token = (_Token.CLOSE_MUSTACHE3
+ if tokens.next_token is _Token.OPEN_UNESCAPED_VARIABLE else
+ _Token.CLOSE_MUSTACHE)
+ tokens.Advance()
+ id_ = _Identifier(*NextIdentifierArgs())
+ if tokens.next_token is close_token:
+ tokens.AdvanceOver(close_token)
+ inline_node = None
else:
- tokens.advanceOver(Token.CLOSE_MUSTACHE)
- return id
-
- def _closeSection(self, tokens, id):
- tokens.advanceOver(Token.OPEN_END_SECTION)
- nextString = tokens.advanceOverNextString()
- if nextString != '' and nextString != str(id):
+ name, line, column = NextIdentifierArgs()
+ tokens.AdvanceOver(_Token.INLINE_END_SECTION)
+ # Support select other types of nodes, the most useful being partial.
+ clazz = _UnescapedVariableNode
+ if name.startswith('*'):
+ clazz = _JsonNode
+ elif name.startswith('+'):
+ clazz = _PartialNode
+ if clazz is not _UnescapedVariableNode:
+ name = name[1:]
+ column += 1
+ inline_node = clazz(_Identifier(name, line, column))
+ return (id_, inline_node)
+
+ def _CloseSection(self, tokens, id_):
+ tokens.AdvanceOver(_Token.OPEN_END_SECTION)
+ next_string = tokens.AdvanceOverNextString()
+ if next_string != '' and next_string != id_.name:
raise ParseException(
- "Start section " + str(id) + " doesn't match end " + nextString)
- tokens.advanceOver(Token.CLOSE_MUSTACHE)
+ 'Start section %s doesn\'t match end %s' % (id_, next_string))
+ tokens.AdvanceOver(_Token.CLOSE_MUSTACHE)
- def _openElse(self, tokens, id):
- tokens.advanceOver(Token.OPEN_ELSE)
- nextString = tokens.advanceOverNextString()
- if nextString != '' and nextString != str(id):
+ def _OpenElse(self, tokens, id_):
+ tokens.AdvanceOver(_Token.OPEN_ELSE)
+ next_string = tokens.AdvanceOverNextString()
+ if next_string != '' and next_string != id_.name:
raise ParseException(
- "Start section " + str(id) + " doesn't match else " + nextString)
- tokens.advanceOver(Token.CLOSE_MUSTACHE)
+ 'Start section %s doesn\'t match else %s' % (id_, next_string))
+ tokens.AdvanceOver(_Token.CLOSE_MUSTACHE)
+
+ def Render(self, *contexts):
+ '''Renders this template given a variable number of contexts to read out
+ values from (such as those appearing in {{foo}}).
+ '''
+ name = self._name or '<root>'
+ render_state = _RenderState(name, _Contexts(contexts))
+ self._top_node.Render(render_state)
+ return render_state.GetResult()
def render(self, *contexts):
- """ Renders this template given a variable number of "contexts" to read
- out values from (such as those appearing in {{foo}}).
- """
- globalContexts = []
- for context in contexts:
- globalContexts.append(context)
- renderState = RenderState(globalContexts, [])
- self._topNode.render(renderState)
- return renderState.getResult()
+ return self.Render(*contexts)
« no previous file with comments | « third_party/handlebar/README.chromium ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698