From 52e8c0a3deaae9017c76a17196aeac8b027b933e Mon Sep 17 00:00:00 2001
From: Cody Taylor <codemister99@yahoo.com>
Date: Tue, 24 Mar 2015 20:57:02 -0400
Subject: [PATCH] Add STOP_RENDERING keyword; exiting of a template.

Signed-off-by: Cody Taylor <codemister99@yahoo.com>
---
 doc/build/changelog.rst | 12 ++++++++++++
 doc/build/syntax.rst    | 21 +++++++++++++++------
 mako/__init__.py        |  2 +-
 mako/codegen.py         |  3 ++-
 mako/runtime.py         |  1 +
 test/test_template.py   | 17 +++++++++++++++--
 6 files changed, 46 insertions(+), 10 deletions(-)

diff --git a/doc/build/changelog.rst b/doc/build/changelog.rst
index 03aaec7..9c321bb 100644
--- a/doc/build/changelog.rst
+++ b/doc/build/changelog.rst
@@ -5,6 +5,18 @@ Changelog
 1.0
 ===
 
+.. changelog::
+    :version: 1.0.2
+
+    .. change::
+        :tags: feature
+        :tickets: 236
+
+      Added STOP_RENDERING keyword for returning/exiting from a
+      template early. Previously the docs suggested a bare
+      ``return``, but this could cause ``None`` to appear in the
+      rendered template result.
+
 .. changelog::
     :version: 1.0.1
     :released: Thu Jan 22 2015
diff --git a/doc/build/syntax.rst b/doc/build/syntax.rst
index fe4a860..7ec68cb 100644
--- a/doc/build/syntax.rst
+++ b/doc/build/syntax.rst
@@ -443,19 +443,19 @@ Mako:
         <%def name="x()">${x}</%def>
     </%text>
 
-Returning Early from a Template
-===============================
+Exiting Early from a Template
+=============================
 
 Sometimes you want to stop processing a template or ``<%def>``
 method in the middle and just use the text you've accumulated so
-far. You can use a ``return`` statement inside a Python
-block to do that.
+far. You can ``return`` the ``STOP_RENDERING`` value inside a Python
+block to exit the current rendering process.
 
 .. sourcecode:: mako
 
     % if not len(records):
         No records found.
-        <% return %>
+        <% return STOP_RENDERING %>
     % endif
 
 Or perhaps:
@@ -464,6 +464,15 @@ Or perhaps:
 
     <%
         if not len(records):
-            return
+            return STOP_RENDERING
     %>
 
+In older versions, return an empty string instead to avoid having
+``None`` in your rendered template:
+
+.. sourcecode:: mako
+
+    <% return '' %>
+
+.. versionadded:: 1.0.2
+
diff --git a/mako/__init__.py b/mako/__init__.py
index d963848..59d4060 100644
--- a/mako/__init__.py
+++ b/mako/__init__.py
@@ -5,4 +5,4 @@
 # the MIT License: http://www.opensource.org/licenses/mit-license.php
 
 
-__version__ = '1.0.1'
+__version__ = '1.0.2'
diff --git a/mako/codegen.py b/mako/codegen.py
index 4b0bda8..0226415 100644
--- a/mako/codegen.py
+++ b/mako/codegen.py
@@ -19,7 +19,7 @@ MAGIC_NUMBER = 10
 # names which are hardwired into the
 # template and are not accessed via the
 # context itself
-RESERVED_NAMES = set(['context', 'loop', 'UNDEFINED'])
+RESERVED_NAMES = set(['context', 'loop', 'UNDEFINED', 'STOP_RENDERING'])
 
 def compile(node,
                 uri,
@@ -215,6 +215,7 @@ class _GenerateRenderMethod(object):
                                    (", ".join(self.compiler.future_imports),))
         self.printer.writeline("from mako import runtime, filters, cache")
         self.printer.writeline("UNDEFINED = runtime.UNDEFINED")
+        self.printer.writeline("STOP_RENDERING = runtime.STOP_RENDERING")
         self.printer.writeline("__M_dict_builtin = dict")
         self.printer.writeline("__M_locals_builtin = locals")
         self.printer.writeline("_magic_number = %r" % MAGIC_NUMBER)
diff --git a/mako/runtime.py b/mako/runtime.py
index 6b6a35a..870efcc 100644
--- a/mako/runtime.py
+++ b/mako/runtime.py
@@ -228,6 +228,7 @@ class Undefined(object):
         return False
 
 UNDEFINED = Undefined()
+STOP_RENDERING = ""
 
 class LoopStack(object):
     """a stack for LoopContexts that implements the context manager protocol
diff --git a/test/test_template.py b/test/test_template.py
index c5873dc..a6a491f 100644
--- a/test/test_template.py
+++ b/test/test_template.py
@@ -757,9 +757,22 @@ class UndefinedVarsTest(TemplateTest):
             ['t is: T', 'a,b,c']
         )
 
+class StopRenderingTest(TemplateTest):
+    def test_return_in_template(self):
+        t = Template("""
+           Line one
+           <% return STOP_RENDERING %>
+           Line Three
+        """, strict_undefined=True)
+
+        eq_(
+            result_lines(t.render()),
+            ['Line one']
+        )
+
 class ReservedNameTest(TemplateTest):
     def test_names_on_context(self):
-        for name in ('context', 'loop', 'UNDEFINED'):
+        for name in ('context', 'loop', 'UNDEFINED', 'STOP_RENDERING'):
             assert_raises_message(
                 exceptions.NameConflictError,
                 r"Reserved words passed to render\(\): %s" % name,
@@ -767,7 +780,7 @@ class ReservedNameTest(TemplateTest):
             )
 
     def test_names_in_template(self):
-        for name in ('context', 'loop', 'UNDEFINED'):
+        for name in ('context', 'loop', 'UNDEFINED', 'STOP_RENDERING'):
             assert_raises_message(
                 exceptions.NameConflictError,
                 r"Reserved words declared in template: %s" % name,
-- 
GitLab