diff --git a/mako/codegen.py b/mako/codegen.py index b5293f4..8839462 100644 --- a/mako/codegen.py +++ b/mako/codegen.py @@ -893,6 +893,8 @@ def visitTextTag(self, node): def visitCode(self, node): if not node.ismodule: + self.printer.writeline("context.code_block_entry_mark()") + self.printer.write_indented_block( node.text, starting_lineno=node.lineno ) diff --git a/mako/runtime.py b/mako/runtime.py index 23401b7..6beb045 100644 --- a/mako/runtime.py +++ b/mako/runtime.py @@ -35,6 +35,7 @@ def __init__(self, buffer, **data): self._with_template = None self._outputting_as_unicode = None self.namespaces = {} + self._code_block_entry_mark = None # "capture" function which proxies to the # generic "capture" function @@ -137,10 +138,27 @@ def get(self, key, default=None): return self._data.get(key, builtins.__dict__.get(key, default)) - def write(self, string): + def code_block_entry_mark(self): + """Mark the current location in the buffer.""" + self._code_block_entry_mark = self._buffer_stack[-1].getpos() + + def write(self, string, indent_relative_to_code_block_entry=False): """Write a string to this :class:`.Context` object's underlying output buffer.""" + if (len(string) == 0): + return + + if ((indent_relative_to_code_block_entry) and (self._code_block_entry_mark is not None)): + text_before_code_block_entry = self._buffer_stack[-1].getvalue(self._code_block_entry_mark) + indent_len = len(text_before_code_block_entry)-(text_before_code_block_entry.rfind("\n") + 1) + indent = " " * indent_len + + text_before_write = self._buffer_stack[-1].getvalue() + current_indent_len = len(text_before_write)-(text_before_write.rfind("\n") + 1) + + string = (" " * max(len(indent) - current_indent_len, 0)) + string[:-1].replace("\n", "\n" + indent) + string[-1] + self._buffer_stack[-1].write(string) def writer(self): @@ -156,6 +174,7 @@ def _copy(self): c._with_template = self._with_template c._outputting_as_unicode = self._outputting_as_unicode c.namespaces = self.namespaces + c._code_block_entry_mark = self._code_block_entry_mark c.caller_stack = self.caller_stack return c diff --git a/mako/util.py b/mako/util.py index 470713a..568a1f3 100644 --- a/mako/util.py +++ b/mako/util.py @@ -5,7 +5,6 @@ # the MIT License: http://www.opensource.org/licenses/mit-license.php from ast import parse import codecs -import collections import operator import os import re @@ -143,23 +142,26 @@ class FastEncodingBuffer: and supports unicode data.""" def __init__(self, encoding=None, errors="strict"): - self.data = collections.deque() + self.data = [] self.encoding = encoding self.delim = "" self.errors = errors self.write = self.data.append def truncate(self): - self.data = collections.deque() + self.data = [] self.write = self.data.append - def getvalue(self): + def getvalue(self, uptopos=None): if self.encoding: - return self.delim.join(self.data).encode( + return self.delim.join(self.data[:uptopos]).encode( self.encoding, self.errors ) else: - return self.delim.join(self.data) + return self.delim.join(self.data[:uptopos]) + + def getpos(self): + return len(self.data) class LRUCache(dict): diff --git a/test/test_runtime.py b/test/test_runtime.py index 0d6fce3..db095b8 100644 --- a/test/test_runtime.py +++ b/test/test_runtime.py @@ -2,6 +2,7 @@ """ from mako import runtime from mako.testing.assertions import eq_ +from mako.template import Template class ContextTest: @@ -17,3 +18,79 @@ def test_locals_kwargs(self): eq_(d.kwargs, {"foo": "bar"}) eq_(d._data["zig"], "zag") + + def test_align_on_code_block_entry_1(self): + """ + No "\n" in the text before "<%" + No identation is added before the first character of the written text because the text before "<%" already contains the indentation + Any "\n" in the written text make the text just after "\n" also indented + Last character in the written text is "\n" and no indentation is added after the "\n" + """ + template = Template(""" <% context.write("text 1\\ntext 2\\n", True) %>""") + + assert ( + template.render() + == +"""\ + text 1 + text 2 +""" + ) + + def test_align_on_code_block_entry_2(self): + """ + "\n" just before "<%" + No identation is added before the first character of the written text because the text before "<%" already contains the indentation + Any "\n" in the written text make the text just after "\n" also indented + Last character in the written text is not "\n" + """ + template = Template("""---\n<% context.write("text 1\\ntext 2", True) %>""") + + assert ( + template.render() + == +"""\ +--- +text 1 +text 2\ +""" + ) + + def test_align_on_code_block_entry_3(self): + """ + Any characters except "\n" before "<%" are part of the indentation to be taken into account while indenting the written text + The indentation of "<%" that is taken into account is the indentation in the generated text, not in the template + Any text written before the next writing with indentation is taken into account for the indentation of this next writing with indentation. + If this text is longer than the block indentation, then no indentation occurs for the first character of the next writing with indentation. + """ + template = Template( +""" +Hel\\ +lo <% + context.write("text 1\\ntext 2\\n", True) + context.write("012") + context.write("text 3\\n", True) +%>\\ +Good ${what} to you <% + context.write("\\ntext 4\\ntext 5", True) + context.write("6789") + context.write("text 6\\ntext 7\\n", True) +%>\\ +Bye +""" + ) + + assert ( + template.render(what="day") + == +""" +Hello text 1 + text 2 +012 text 3 +Good day to you + text 4 + text 56789text 6 + text 7 +Bye +""" + ) diff --git a/test/test_util.py b/test/test_util.py index 95c1cb4..90cb170 100644 --- a/test/test_util.py +++ b/test/test_util.py @@ -36,6 +36,15 @@ def test_fast_buffer_encoded(self): buf.write(s[10:]) eq_(buf.getvalue(), s.encode("utf-8")) + def test_fast_buffer_get_partial_value(self): + buf = util.FastEncodingBuffer() + buf.write("string a ") + buf.write("string b ") + pos = buf.getpos() + buf.write("string c ") + buf.write("string d") + eq_(buf.getvalue(pos), "string a string b ") + def test_read_file(self): fn = os.path.join(os.path.dirname(__file__), "test_util.py") data = util.read_file(fn, "rb")