Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions mako/codegen.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand Down
21 changes: 20 additions & 1 deletion mako/runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this new argument needs to be keyword only:

 def write(self, string, *, new_keyword=False):

I think the name indent_relative_to_code_block_entry is too long of a name. needs to be a short name.

"""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):
Expand All @@ -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

Expand Down
14 changes: 8 additions & 6 deletions mako/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -143,23 +142,26 @@ class FastEncodingBuffer:
and supports unicode data."""

def __init__(self, encoding=None, errors="strict"):
self.data = collections.deque()
self.data = []
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this has to remain a deque(). a deque is much more efficient for what we're doing here

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):
Expand Down
77 changes: 77 additions & 0 deletions test/test_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"""
from mako import runtime
from mako.testing.assertions import eq_
from mako.template import Template


class ContextTest:
Expand All @@ -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
"""
)
9 changes: 9 additions & 0 deletions test/test_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down