diff options
author | cduvall@chromium.org <cduvall@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-07-02 23:14:13 +0000 |
---|---|---|
committer | cduvall@chromium.org <cduvall@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-07-02 23:14:13 +0000 |
commit | 5365b102cab578a2e94c107ab211862456869595 (patch) | |
tree | 3785341b43f7dc90718b54f1db9b58a71054d69e /third_party/handlebar | |
parent | dd0fcedd1468a08a72dc25fcbfcfbc66611f3254 (diff) | |
download | chromium_src-5365b102cab578a2e94c107ab211862456869595.zip chromium_src-5365b102cab578a2e94c107ab211862456869595.tar.gz chromium_src-5365b102cab578a2e94c107ab211862456869595.tar.bz2 |
Extensions Docs Server: Handlebar updates
Updated the version of Handlebar in third_party, and made some minor tweaks.
BUG=131095
Review URL: https://chromiumcodereview.appspot.com/10697056
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@145214 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'third_party/handlebar')
-rw-r--r-- | third_party/handlebar/README.chromium | 4 | ||||
-rw-r--r-- | third_party/handlebar/handlebar.py | 626 |
2 files changed, 417 insertions, 213 deletions
diff --git a/third_party/handlebar/README.chromium b/third_party/handlebar/README.chromium index a0d7755..fa8a17b 100644 --- a/third_party/handlebar/README.chromium +++ b/third_party/handlebar/README.chromium @@ -2,8 +2,8 @@ Name: Handlebar, logic-less templating system Short Name: handlebar URL: https://github.com/kalman/templates Version: 0 -Date: June 7, 2012 -Revision: commit 3397712d4bb91a5f5e25637580761f6c565a0cd6 +Date: July 2, 2012 +Revision: commit 55b3a48c826a03aaa6898c8c40585396a335bd35 License: Apache License, Version 2.0 Security Critical: no diff --git a/third_party/handlebar/handlebar.py b/third_party/handlebar/handlebar.py index a749ca9..13f4873 100644 --- a/third_party/handlebar/handlebar.py +++ b/third_party/handlebar/handlebar.py @@ -15,9 +15,9 @@ import json import re -""" Handlebar templates are 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 mostly-logicless templates inspired by ctemplate +(or more specifically mustache templates) then taken in their own direction +because I found those to be inadequate. from handlebar import Handlebar @@ -46,8 +46,8 @@ print(Handlebar('hello {{world}}').render(CustomContext()).text) class ParseException(Exception): """ Exception thrown while parsing the template. """ - def __init__(self, message): - Exception.__init__(self, message) + def __init__(self, error, line): + Exception.__init__(self, "%s (line %s)" % (error, line.number)) class RenderResult(object): """ Result of a render operation. @@ -63,36 +63,94 @@ class StringBuilder(object): def __init__(self): self._buf = [] + def __len__(self): + return len(self._buf) + def append(self, obj): self._buf.append(str(obj)) def toString(self): return ''.join(self._buf) -class PathIdentifier(object): - """ An identifier of the form "foo.bar.baz". +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] + elif len(self.globalContexts) > 0: + return self.globalContexts[0] + return None + + def disableErrors(self): + self._errorsDisabled = True + return self + + def addError(self, *messages): + if self._errorsDisabled: + return self + buf = StringBuilder() + for message in messages: + buf.append(message) + self.errors.append(buf.toString()) + return self + + 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): - if name == '': - raise ParseException("Cannot have empty identifiers") - if not re.match('^[a-zA-Z0-9._]*$', name): - raise ParseException(name + " is not a valid identifier") - self.path = name.split(".") - - def resolve(self, contexts, errors): - resolved = None + def __init__(self, name, line): + self._isThis = (name == '@') + if self._isThis: + self._startsWithThis = False + self._path = [] + return + + 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 not resolved: + resolved = self._resolveFromContexts(renderState.globalContexts) + if not resolved: + renderState.addError("Couldn't resolve identifier ", self._path) + return resolved + + def _resolveFromContexts(self, contexts): for context in contexts: - if not context: - continue - resolved = self._resolveFrom(context) + resolved = self._resolveFromContext(context) if resolved: return resolved - _RenderError(errors, "Couldn't resolve identifier ", self.path, " in ", contexts) return None - def _resolveFrom(self, context): + def _resolveFromContext(self, context): result = context - for next in self.path: + 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 not result or not getattr(result, "get", None): @@ -101,49 +159,158 @@ class PathIdentifier(object): return result def __str__(self): - return '.'.join(self.path) + if self._isThis: + return '@' + name = '.'.join(self._path) + return ('@.' + name) if self._startsWithThis else name -class ThisIdentifier(object): - """ An identifier of the form "@". - """ - def resolve(self, contexts, errors): - return contexts[0] +class Line(object): + def __init__(self, number): + self.number = number - def __str__(self): - return '@' +class LeafNode(object): + def trimLeadingNewLine(self): + pass -class SelfClosingNode(object): - """ Nodes which are "self closing", e.g. {{foo}}, {{*foo}}. - """ - def init(self, id): - self.id = id + def trimTrailingSpaces(self): + return 0 -class HasChildrenNode(object): - """ Nodes which are not self closing, and have 0..n children. - """ - def init(self, id, children): - self.id = id - self.children = children + def trimTrailingNewLine(self): + pass + + def trailsWithEmptyLine(self): + return False + +class DecoratorNode(object): + def __init__(self, content): + self._content = content + + def trimLeadingNewLine(self): + self._content.trimLeadingNewLine() + + def trimTrailingSpaces(self): + return self._content.trimTrailingSpaces() + + def trimTrailingNewLine(self): + self._content.trimTrailingNewLine() + + def trailsWithEmptyLine(self): + return self._content.trailsWithEmptyLine() + +class InlineNode(DecoratorNode): + def __init__(self, content): + DecoratorNode.__init__(self, content) + + def render(self, renderState): + contentRenderState = renderState.inSameContext() + self._content.render(contentRenderState) + + renderState.errors.extend(contentRenderState.errors) + + for c in contentRenderState.text.toString(): + if c != '\n': + renderState.text.append(c) + +class IndentedNode(DecoratorNode): + def __init__(self, content, indentation): + DecoratorNode.__init__(self, content) + self._indentation = indentation + + def render(self, renderState): + contentRenderState = renderState.inSameContext() + self._content.render(contentRenderState) + + renderState.errors.extend(contentRenderState.errors) + + self._indent(renderState.text) + for i, c in enumerate(contentRenderState.text.toString()): + renderState.text.append(c) + if c == '\n' and i < len(renderState.text) - 1: + self._indent(renderState.text) + renderState.text.append('\n') + + def _indent(self, buf): + buf.append(' ' * self._indentation) + +class BlockNode(DecoratorNode): + def __init__(self, content): + DecoratorNode.__init__(self, content) + content.trimLeadingNewLine() + content.trimTrailingSpaces() + + def render(self, renderState): + self._content.render(renderState) + +class NodeCollection(object): + def __init__(self, nodes): + self._nodes = nodes + + def render(self, renderState): + for node in self._nodes: + node.render(renderState) + + def trimLeadingNewLine(self): + if len(self._nodes) > 0: + self._nodes[0].trimLeadingNewLine() + + def trimTrailingSpaces(self): + if len(self._nodes) > 0: + return self._nodes[-1].trimTrailingSpaces() + return 0 + + def trimTrailingNewLine(self): + if len(self._nodes) > 0: + self._nodes[-1].trimTrailingNewLine() + + def trailsWithEmptyLine(self): + if len(self._nodes) > 0: + return self._nodes[-1].trailsWithEmptyLine() + return False class StringNode(object): """ Just a string. """ def __init__(self, string): - self.string = string + self._string = string + + def render(self, renderState): + renderState.text.append(self._string) + + def trimLeadingNewLine(self): + if self._string.startswith('\n'): + self._string = self._string[1:] + + def trimTrailingSpaces(self): + originalLength = len(self._string) + self._string = self._string[:self._lastIndexOfSpaces()] + return originalLength - len(self._string) - def render(self, buf, contexts, errors): - buf.append(self.string) + def trimTrailingNewLine(self): + if self._string.endswith('\n'): + self._string = self._string[:len(self._string) - 1] -class EscapedVariableNode(SelfClosingNode): + def trailsWithEmptyLine(self): + index = self._lastIndexOfSpaces() + return index == 0 or self._string[index - 1] == '\n' + + def _lastIndexOfSpaces(self): + index = len(self._string) + while index > 0 and self._string[index - 1] == ' ': + index -= 1 + return index + +class EscapedVariableNode(LeafNode): """ {{foo}} """ - def render(self, buf, contexts, errors): - value = self.id.resolve(contexts, errors) + def __init__(self, id): + self._id = id + + def render(self, renderState): + value = self._id.resolve(renderState) if value: - buf.append(self._htmlEscape(str(value))) + self._appendEscapedHtml(renderState.text, str(value)) - def _htmlEscape(self, unescaped): - escaped = StringBuilder() + def _appendEscapedHtml(self, escaped, unescaped): for c in unescaped: if c == '<': escaped.append("<") @@ -153,21 +320,27 @@ class EscapedVariableNode(SelfClosingNode): escaped.append("&") else: escaped.append(c) - return escaped.toString() -class UnescapedVariableNode(SelfClosingNode): +class UnescapedVariableNode(LeafNode): """ {{{foo}}} """ - def render(self, buf, contexts, errors): - value = self.id.resolve(contexts, errors) + def __init__(self, id): + self._id = id + + def render(self, renderState): + value = self._id.resolve(renderState) if value: - buf.append(value) + renderState.text.append(value) -class SectionNode(HasChildrenNode): +class SectionNode(DecoratorNode): """ {{#foo}} ... {{/}} """ - def render(self, buf, contexts, errors): - value = self.id.resolve(contexts, errors) + def __init__(self, id, content): + DecoratorNode.__init__(self, content) + self._id = id + + def render(self, renderState): + value = self._id.resolve(renderState) if not value: return @@ -176,25 +349,30 @@ class SectionNode(HasChildrenNode): pass elif type_ == list: for item in value: - contexts.insert(0, item) - _RenderNodes(buf, self.children, contexts, errors) - contexts.pop(0) + renderState.localContexts.insert(0, item) + self._content.render(renderState) + renderState.localContexts.pop(0) elif type_ == dict: - contexts.insert(0, value) - _RenderNodes(buf, self.children, contexts, errors) - contexts.pop(0) + renderState.localContexts.insert(0, value) + self._content.render(renderState) + renderState.localContexts.pop(0) else: - _RenderError(errors, "{{#", self.id, "}} cannot be rendered with a ", type_) + renderState.addError("{{#", self._id, + "}} cannot be rendered with a ", type_) -class VertedSectionNode(HasChildrenNode): +class VertedSectionNode(DecoratorNode): """ {{?foo}} ... {{/}} """ - def render(self, buf, contexts, errors): - value = self.id.resolve(contexts, errors) + def __init__(self, id, content): + DecoratorNode.__init__(self, content) + self._id = id + + def render(self, renderState): + value = self._id.resolve(renderState) if value and _VertedSectionNodeShouldRender(value): - contexts.insert(0, value) - _RenderNodes(buf, self.children, contexts, errors) - contexts.pop(0) + renderState.localContexts.insert(0, value) + self._content.render(renderState) + renderState.localContexts.pop(0) def _VertedSectionNodeShouldRender(value): type_ = type(value) @@ -210,63 +388,69 @@ def _VertedSectionNodeShouldRender(value): return len(value) > 0 raise TypeError("Unhandled type: " + str(type_)) -class InvertedSectionNode(HasChildrenNode): +class InvertedSectionNode(DecoratorNode): """ {{^foo}} ... {{/}} """ - def render(self, buf, contexts, errors): - value = self.id.resolve(contexts, errors) + def __init__(self, id, content): + DecoratorNode.__init__(self, content) + self._id = id + + def render(self, renderState): + value = self._id.resolve(renderState) if not value or not _VertedSectionNodeShouldRender(value): - _RenderNodes(buf, self.children, contexts, errors) + self._content.render(renderState) -class JsonNode(SelfClosingNode): +class JsonNode(LeafNode): """ {{*foo}} """ - def render(self, buf, contexts, errors): - value = self.id.resolve(contexts, errors) + def __init__(self, id): + self._id = id + + def render(self, renderState): + value = self._id.resolve(renderState) if value: - buf.append(json.dumps(value, separators=(',',':'))) + renderState.text.append(json.dumps(value, separators=(',',':'))) -class PartialNode(SelfClosingNode): +class PartialNode(LeafNode): """ {{+foo}} """ - def render(self, buf, contexts, errors): - value = self.id.resolve(contexts, errors) + def __init__(self, id): + self._id = id + self._args = None + + def render(self, renderState): + value = self._id.resolve(renderState) if not isinstance(value, Handlebar): - _RenderError(errors, id, " didn't resolve to a Handlebar") + renderState.addError(self._id, " didn't resolve to a Handlebar") return - _RenderNodes(buf, value.nodes, contexts, errors) -class SwitchNode(object): - """ {{:foo}} - """ - def __init__(self, id): - self.id = id - self._cases = {} + argContext = [] + if len(renderState.localContexts) > 0: + argContext.append(renderState.localContexts[0]) - def addCase(self, caseValue, caseNode): - self._cases[caseValue] = caseNode + if self._args: + argContextMap = {} + for key, valueId in self._args.items(): + context = valueId.resolve(renderState) + if context: + argContextMap[key] = context + argContext.append(argContextMap) - def render(self, buf, contexts, errors): - value = self.id.resolve(contexts, errors) - if not value: - _RenderError(errors, id, " didn't resolve to any value") - return - if not (type(value) == str or type(value) == unicode): - _RenderError(errors, id, " didn't resolve to a String") - return - caseNode = self._cases.get(value) - if caseNode: - caseNode.render(buf, contexts, errors) + partialRenderState = RenderState(renderState.globalContexts, argContext) + value._topNode.render(partialRenderState) -class CaseNode(object): - """ {{=foo}} - """ - def __init__(self, children): - self.children = children + text = partialRenderState.text.toString() + lastIndex = len(text) - 1 + if lastIndex >= 0 and text[lastIndex] == '\n': + text = text[:lastIndex] + + renderState.text.append(text) + renderState.errors.extend(partialRenderState.errors) - def render(self, buf, contexts, errors): - for child in self.children: - child.render(buf, contexts, errors) + def addArgument(self, key, valueId): + if not self._args: + self._args = {} + self._args[key] = valueId class Token(object): """ The tokens that can appear in a template. @@ -282,14 +466,14 @@ class Token(object): 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_START_SWITCH = Data("OPEN_START_SWITCH" , "{{:", SwitchNode) - OPEN_CASE = Data("OPEN_CASE" , "{{=", CaseNode) 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" , "." , StringNode) + CHARACTER = Data("CHARACTER" , "." , None) # List of tokens in order of longest to shortest, to avoid any prefix matching # issues. @@ -299,11 +483,11 @@ _tokenList = [ Token.OPEN_START_INVERTED_SECTION, Token.OPEN_START_JSON, Token.OPEN_START_PARTIAL, - Token.OPEN_START_SWITCH, - Token.OPEN_CASE, Token.OPEN_END_SECTION, Token.OPEN_UNESCAPED_VARIABLE, Token.CLOSE_MUSTACHE3, + Token.OPEN_COMMENT, + Token.CLOSE_COMMENT, Token.OPEN_VARIABLE, Token.CLOSE_MUSTACHE, Token.CHARACTER @@ -313,23 +497,22 @@ class TokenStream(object): """ Tokeniser for template parsing. """ def __init__(self, string): - self.nextToken = None self._remainder = string - self._nextContents = None + + self.nextToken = None + self.nextContents = None + self.nextLine = Line(1) self.advance() def hasNext(self): return self.nextToken != None - def advanceOver(self, token): - if self.nextToken != token: - raise ParseException( - "Expecting token " + token.name + " but got " + self.nextToken.name) - return self.advance() - def advance(self): + if self.nextContents == '\n': + self.nextLine = Line(self.nextLine.number + 1) + self.nextToken = None - self._nextContents = None + self.nextContents = None if self._remainder == '': return None @@ -342,99 +525,133 @@ class TokenStream(object): if self.nextToken == None: self.nextToken = Token.CHARACTER - self._nextContents = self._remainder[0:len(self.nextToken.text)] + self.nextContents = self._remainder[0:len(self.nextToken.text)] self._remainder = self._remainder[len(self.nextToken.text):] - return self.nextToken + return self + + def advanceOver(self, token): + if self.nextToken != token: + raise ParseException( + "Expecting token " + token.name + " but got " + self.nextToken.name, + self.nextLine) + return self.advance() - def nextString(self): + def advanceOverNextString(self, excluded=''): buf = StringBuilder() - while self.nextToken == Token.CHARACTER: - buf.append(self._nextContents) + while self.nextToken == Token.CHARACTER and \ + excluded.find(self.nextContents) == -1: + buf.append(self.nextContents) self.advance() return buf.toString() -def _CreateIdentifier(path): - if path == '@': - return ThisIdentifier() - else: - return PathIdentifier(path) - class Handlebar(object): """ A handlebar template. """ def __init__(self, template): - self.nodes = [] - self._parseTemplate(template, self.nodes) - - def _parseTemplate(self, template, nodes): + self.source = template tokens = TokenStream(template) - self._parseSection(tokens, nodes) + self._topNode = self._parseSection(tokens, None) if tokens.hasNext(): raise ParseException("There are still tokens remaining, " - "was there an end-section without a start-section:") + "was there an end-section without a start-section:", + tokens.nextLine) - def _parseSection(self, tokens, nodes): + def _parseSection(self, tokens, previousNode): + nodes = [] sectionEnded = False + while tokens.hasNext() and not sectionEnded: - next = tokens.nextToken - if next == Token.CHARACTER: - nodes.append(StringNode(tokens.nextString())) - elif next == Token.OPEN_VARIABLE or \ - next == Token.OPEN_UNESCAPED_VARIABLE or \ - next == Token.OPEN_START_JSON or \ - next == Token.OPEN_START_PARTIAL: - token = tokens.nextToken - id = self._openSection(tokens) - node = token.clazz() - node.init(id) - nodes.append(node) - elif next == Token.OPEN_START_SECTION or \ - next == Token.OPEN_START_VERTED_SECTION or \ - next == Token.OPEN_START_INVERTED_SECTION: - token = tokens.nextToken - id = self._openSection(tokens) - - children = [] - self._parseSection(tokens, children) - self._closeSection(tokens, id) + token = tokens.nextToken + node = None + + if token == Token.CHARACTER: + node = StringNode(tokens.advanceOverNextString()) + elif token == Token.OPEN_VARIABLE or \ + token == Token.OPEN_UNESCAPED_VARIABLE or \ + token == Token.OPEN_START_JSON: + id = self._openSectionOrTag(tokens) + node = token.clazz(id) + elif token == Token.OPEN_START_PARTIAL: + tokens.advance() + id = Identifier(tokens.advanceOverNextString(excluded=' '), + tokens.nextLine) + partialNode = PartialNode(id) - node = token.clazz() - node.init(id, children) - nodes.append(node) - elif next == Token.OPEN_START_SWITCH: - id = self._openSection(tokens) while tokens.nextToken == Token.CHARACTER: - tokens.advanceOver(Token.CHARACTER) - - switchNode = SwitchNode(id) - nodes.append(switchNode) - - while tokens.hasNext() and tokens.nextToken == Token.OPEN_CASE: - tokens.advanceOver(Token.OPEN_CASE) - caseValue = tokens.nextString() - tokens.advanceOver(Token.CLOSE_MUSTACHE) - - caseChildren = [] - self._parseSection(tokens, caseChildren) - - switchNode.addCase(caseValue, CaseNode(caseChildren)) - + tokens.advance() + key = tokens.advanceOverNextString(excluded=':') + tokens.advance() + partialNode.addArgument( + key, + Identifier(tokens.advanceOverNextString(excluded=' '), + tokens.nextLine)) + + tokens.advanceOver(Token.CLOSE_MUSTACHE) + node = partialNode + elif token == Token.OPEN_START_SECTION or \ + token == Token.OPEN_START_VERTED_SECTION or \ + token == Token.OPEN_START_INVERTED_SECTION: + startLine = tokens.nextLine + + id = self._openSectionOrTag(tokens) + section = self._parseSection(tokens, previousNode) self._closeSection(tokens, id) - elif next == Token.OPEN_CASE: - # See below. - sectionEnded = True - elif next == Token.OPEN_END_SECTION: + + node = token.clazz(id, section) + + if startLine.number != tokens.nextLine.number: + node = BlockNode(node) + if previousNode: + previousNode.trimTrailingSpaces(); + if tokens.nextContents == '\n': + tokens.advance() + elif token == Token.OPEN_COMMENT: + self._advanceOverComment(tokens) + elif token == Token.OPEN_END_SECTION: # 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) + raise ParseException("Orphaned " + tokens.nextToken.name, + tokens.nextLine) + + if not node: + continue - def _openSection(self, tokens): + if not isinstance(node, StringNode) and \ + not isinstance(node, BlockNode): + if (not previousNode or previousNode.trailsWithEmptyLine()) and \ + (not tokens.hasNext() or tokens.nextContents == '\n'): + indentation = 0 + if previousNode: + indentation = previousNode.trimTrailingSpaces() + tokens.advance() + node = IndentedNode(node, indentation) + else: + node = InlineNode(node) + + previousNode = node + nodes.append(node) + + if len(nodes) == 1: + return nodes[0] + return NodeCollection(nodes) + + def _advanceOverComment(self, tokens): + tokens.advanceOver(Token.OPEN_COMMENT) + depth = 1 + while tokens.hasNext() and depth > 0: + if tokens.nextToken == Token.OPEN_COMMENT: + depth += 1 + elif tokens.nextToken == Token.CLOSE_COMMENT: + depth -= 1 + tokens.advance() + + def _openSectionOrTag(self, tokens): openToken = tokens.nextToken tokens.advance() - id = _CreateIdentifier(tokens.nextString()) + id = Identifier(tokens.advanceOverNextString(), tokens.nextLine) if openToken == Token.OPEN_UNESCAPED_VARIABLE: tokens.advanceOver(Token.CLOSE_MUSTACHE3) else: @@ -443,8 +660,8 @@ class Handlebar(object): def _closeSection(self, tokens, id): tokens.advanceOver(Token.OPEN_END_SECTION) - nextString = tokens.nextString() - if nextString != '' and str(id) != nextString: + nextString = tokens.advanceOverNextString() + if nextString != '' and nextString != str(id): raise ParseException( "Start section " + str(id) + " doesn't match end section " + nextString) tokens.advanceOver(Token.CLOSE_MUSTACHE) @@ -453,22 +670,9 @@ class Handlebar(object): """ Renders this template given a variable number of "contexts" to read out values from (such as those appearing in {{foo}}). """ - contextDeque = [] + globalContexts = [] for context in contexts: - contextDeque.append(context) - buf = StringBuilder() - errors = [] - _RenderNodes(buf, self.nodes, contextDeque, errors) - return RenderResult(buf.toString(), errors) - -def _RenderNodes(buf, nodes, contexts, errors): - for node in nodes: - node.render(buf, contexts, errors) - -def _RenderError(errors, *messages): - if not errors: - return - buf = StringBuilder() - for message in messages: - buf.append(message) - errors.append(buf.toString()) + globalContexts.append(context) + renderState = RenderState(globalContexts, []) + self._topNode.render(renderState) + return renderState.getResult() |