From ebbce747c8655b0647d82aee84310af4ec0e1a76 Mon Sep 17 00:00:00 2001
From: Mike Bayer <mike_mp@zzzcomputing.com>
Date: Sat, 3 May 2008 15:27:11 +0000
Subject: [PATCH] - CHANGES cleanup - some functions on Context are now
 private: _push_buffer(), _pop_buffer(), caller_stack._push_frame(),
 caller_stack._pop_frame(). - implemented [ticket:76] inlining of
 context.write()

---
 CHANGES                 | 103 +++++++++++++++++++++++++++-------------
 examples/bench/basic.py |   2 +-
 lib/mako/codegen.py     |  36 +++++++-------
 lib/mako/runtime.py     |  54 +++++++++++++++++----
 test/call.py            |   7 ++-
 test/filters.py         |   1 +
 6 files changed, 141 insertions(+), 62 deletions(-)

diff --git a/CHANGES b/CHANGES
index 1e830ef..7e5c11c 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,36 +1,75 @@
 0.2.0
-- added "attr" accessor to namespaces.  Returns attributes
-  configured as module level attributes, i.e. within 
-  <%! %> sections [ticket:62]
-- fixed bug in python generation when variable names are used
-  with identifiers like "else", "finally", etc. inside them
-  [ticket:68]
-- fixed codegen bug which occured when using <%page> level
-  caching, combined with an expression-based cache_key, 
-  combined with the usage of <%namespace import="*"/>
-- fixed lexer exceptions not cleaning up temporary files, which
-  could lead to a maximum number of file descriptors used in the
-  process [ticket:69]
-- fixed issue with inline format_exceptions that was producing
-  blank exception pages when an inheriting template is 
-  present [ticket:71]
-- format_exceptions will apply the encoding options
-  of html_error_template() to the buffered output
-- rewrote the "whitespace adjuster" function to work with
-  more elaborate combinations of quotes and comments
-  [ticket:75]
-- cache_key argument can now render arguments passed 
-  directly to the %page or %def, i.e.
-  <%def name="foo(x)" cached="True" cache_key="${x}"/>
-  [ticket:78]
-- added "bytestring passthru" mode, via `disable_unicode=True`
-  argument passed to Template or TemplateLookup.  All
-  unicode-awareness and filtering is turned off, and template 
-  modules are generated with the appropriate magic encoding
-  comment.  In this mode, template expressions can only
-  receive raw bytestrings or Unicode objects which represent
-  straight ASCII, and render_unicode() may not be used. 
-  [ticket:77]  (courtesy anonymous guest)
+- Speed improvements (as though we needed them, but people
+  contributed and there you go):
+
+  - added "bytestring passthru" mode, via
+    `disable_unicode=True` argument passed to Template or
+    TemplateLookup. All unicode-awareness and filtering is
+    turned off, and template modules are generated with
+    the appropriate magic encoding comment. In this mode,
+    template expressions can only receive raw bytestrings
+    or Unicode objects which represent straight ASCII, and
+    render_unicode() may not be used if multibyte
+    characters are present. When enabled, speed
+    improvement around 10-20%. [ticket:77] (courtesy
+    anonymous guest)
+
+  - inlined the "write" function of Context into a local
+    template variable. This affords a 12-30% speedup in
+    template render time. (idea courtesy same anonymous
+    guest) [ticket:76]
+  
+- New Features, API changes: 
+ 
+  - added "attr" accessor to namespaces. Returns
+    attributes configured as module level attributes, i.e.
+    within <%! %> sections.  [ticket:62] i.e.:
+    
+    # somefile.html
+    <%! 
+        foo = 27
+    %>
+    
+    # some other template
+    <%namespace name="myns" file="somefile.html"/>
+    ${myns.attr.foo}
+
+    The slight backwards incompatibility here is, you
+    can't have namespace defs named "attr" since the
+    "attr" descriptor will occlude it.
+    
+  - cache_key argument can now render arguments passed
+    directly to the %page or %def, i.e. <%def
+    name="foo(x)" cached="True" cache_key="${x}"/>
+    [ticket:78]
+
+  - some functions on Context are now private:
+    _push_buffer(), _pop_buffer(),
+    caller_stack._push_frame(), caller_stack._pop_frame().
+
+- Bugfixes:
+
+  - fixed bug in python generation when variable names are
+    used with identifiers like "else", "finally", etc.
+    inside them [ticket:68]
+
+  - fixed codegen bug which occured when using <%page>
+    level caching, combined with an expression-based
+    cache_key, combined with the usage of <%namespace
+    import="*"/> - fixed lexer exceptions not cleaning up
+    temporary files, which could lead to a maximum number
+    of file descriptors used in the process [ticket:69]
+
+  - fixed issue with inline format_exceptions that was
+    producing blank exception pages when an inheriting
+    template is present [ticket:71]
+  
+  - format_exceptions will apply the encoding options of
+    html_error_template() to the buffered output
+  
+  - rewrote the "whitespace adjuster" function to work
+    with more elaborate combinations of quotes and
+    comments [ticket:75]
 
 0.1.10
 - fixed propagation of 'caller' such that nested %def calls
diff --git a/examples/bench/basic.py b/examples/bench/basic.py
index c603a26..84c4b11 100644
--- a/examples/bench/basic.py
+++ b/examples/bench/basic.py
@@ -65,7 +65,7 @@ def myghty(dirname, verbose=False):
 def mako(dirname, verbose=False):
     from mako.template import Template
     from mako.lookup import TemplateLookup
-    lookup = TemplateLookup(directories=[dirname], filesystem_checks=False)
+    lookup = TemplateLookup(directories=[dirname], filesystem_checks=False, disable_unicode=True)
     template = lookup.get_template('template.html')
     def render():
         return template.render(title="Just a test", user="joe", list_items=[u'Number %d' % num for num in range(1,15)])
diff --git a/lib/mako/codegen.py b/lib/mako/codegen.py
index d59e487..e6b5476 100644
--- a/lib/mako/codegen.py
+++ b/lib/mako/codegen.py
@@ -163,11 +163,11 @@ class _GenerateRenderMethod(object):
         this could be the main render() method or that of a top-level def."""
         self.printer.writelines(
             "def %s(%s):" % (name, ','.join(args)),
-                "context.caller_stack.push_frame()",
+                "context.caller_stack._push_frame()",
                 "try:"
         )
         if buffered or filtered or cached:
-            self.printer.writeline("context.push_buffer()")
+            self.printer.writeline("context._push_buffer()")
         
         self.identifier_stack.append(self.compiler.identifiers.branch(self.node))
         if not self.in_def and '**pageargs' in args:
@@ -302,6 +302,8 @@ class _GenerateRenderMethod(object):
                 else:
                     self.printer.writeline("%s = context.get(%s, UNDEFINED)" % (ident, repr(ident)))
         
+        self.printer.writeline("__M_writer = context.writer()")
+        
     def write_source_comment(self, node):
         """write a source comment containing the line number of the corresponding template line."""
         if self.last_source_line != node.lineno:
@@ -329,12 +331,12 @@ class _GenerateRenderMethod(object):
         buffered = eval(node.attributes.get('buffered', 'False'))
         cached = eval(node.attributes.get('cached', 'False'))
         self.printer.writelines(
-            "context.caller_stack.push_frame()",
+            "context.caller_stack._push_frame()",
             "try:"
             )
         if buffered or filtered or cached:
             self.printer.writelines(
-                "context.push_buffer()",
+                "context._push_buffer()",
                 )
 
         identifiers = identifiers.branch(node, nested=nested)
@@ -362,16 +364,16 @@ class _GenerateRenderMethod(object):
             if callstack:
                 self.printer.writelines(
                     "finally:",
-                        "context.caller_stack.pop_frame()",
+                        "context.caller_stack._pop_frame()",
                     None
                 )
         if buffered or filtered or cached:
             self.printer.writelines(
                 "finally:",
-                    "__M_buf = context.pop_buffer()"
+                    "__M_buf, __M_writer = context._pop_buffer_and_writer()"
             )
             if callstack:
-                self.printer.writeline("context.caller_stack.pop_frame()")
+                self.printer.writeline("context.caller_stack._pop_frame()")
             s = "__M_buf.getvalue()"
             if filtered:
                 s = self.create_filter_callable(node.filter_args.args, s, False)
@@ -382,7 +384,7 @@ class _GenerateRenderMethod(object):
                 self.printer.writeline("return %s" % s)
             else:
                 self.printer.writelines(
-                    "context.write(%s)" % s,
+                    "__M_writer(%s)" % s,
                     "return ''"
                 )
 
@@ -420,7 +422,7 @@ class _GenerateRenderMethod(object):
             self.printer.writelines("return " + s,None)
         else:
             self.printer.writelines(
-                    "context.write(context.get('local').get_cached(%s, %screatefunc=lambda:__M_%s(%s)))" % (cachekey, ''.join(["%s=%s, " % (k,v) for k, v in cacheargs.iteritems()]), name, ','.join(pass_args)),
+                    "__M_writer(context.get('local').get_cached(%s, %screatefunc=lambda:__M_%s(%s)))" % (cachekey, ''.join(["%s=%s, " % (k,v) for k, v in cacheargs.iteritems()]), name, ','.join(pass_args)),
                     "return ''",
                 None
             )
@@ -460,9 +462,9 @@ class _GenerateRenderMethod(object):
         self.write_source_comment(node)
         if len(node.escapes) or (self.compiler.pagetag is not None and len(self.compiler.pagetag.filter_args.args)) or len(self.compiler.default_filters):
             s = self.create_filter_callable(node.escapes_code.args, "%s" % node.text, True)
-            self.printer.writeline("context.write(%s)" % s)
+            self.printer.writeline("__M_writer(%s)" % s)
         else:
-            self.printer.writeline("context.write(%s)" % node.text)
+            self.printer.writeline("__M_writer(%s)" % node.text)
             
     def visitControlLine(self, node):
         if node.isend:
@@ -472,12 +474,12 @@ class _GenerateRenderMethod(object):
             self.printer.writeline(node.text)
     def visitText(self, node):
         self.write_source_comment(node)
-        self.printer.writeline("context.write(%s)" % repr(node.content))
+        self.printer.writeline("__M_writer(%s)" % repr(node.content))
     def visitTextTag(self, node):
         filtered = len(node.filter_args.args) > 0
         if filtered:
             self.printer.writelines(
-                "context.push_buffer()",
+                "__M_writer = context._push_writer()",
                 "try:",
             )
         for n in node.nodes:
@@ -485,8 +487,8 @@ class _GenerateRenderMethod(object):
         if filtered:
             self.printer.writelines(
                 "finally:",
-                "__M_buf = context.pop_buffer()",
-                "context.write(%s)" % self.create_filter_callable(node.filter_args.args, "__M_buf.getvalue()", False),
+                "__M_buf, __M_writer = context._pop_buffer_and_writer()",
+                "__M_writer(%s)" % self.create_filter_callable(node.filter_args.args, "__M_buf.getvalue()", False),
                 None
                 )
         
@@ -544,7 +546,7 @@ class _GenerateRenderMethod(object):
         buffered = False
         if buffered:
             self.printer.writelines(
-                "context.push_buffer()",
+                "context._push_buffer()",
                 "try:"
             )
         self.write_variable_declares(body_identifiers)
@@ -569,7 +571,7 @@ class _GenerateRenderMethod(object):
             "try:")
         self.write_source_comment(node)
         self.printer.writelines(
-                "context.write(%s)" % self.create_filter_callable([], node.attributes['expr'], True),
+                "__M_writer(%s)" % self.create_filter_callable([], node.attributes['expr'], True),
             "finally:",
                 "context.caller_stack.nextcaller = None",
             None
diff --git a/lib/mako/runtime.py b/lib/mako/runtime.py
index f0ae468..e124b6b 100644
--- a/lib/mako/runtime.py
+++ b/lib/mako/runtime.py
@@ -23,27 +23,61 @@ class Context(object):
         
         # "caller" stack used by def calls with content
         self.caller_stack = data['caller'] = CallerStack()
+        
     lookup = property(lambda self:self._with_template.lookup)
     kwargs = property(lambda self:self._kwargs.copy())
+    
     def push_caller(self, caller):
         self.caller_stack.append(caller)
+        
     def pop_caller(self):
         del self.caller_stack[-1]
+        
     def keys(self):
         return self._data.keys()
+        
     def __getitem__(self, key):
         return self._data[key]
-    def push_buffer(self):
+
+    def _push_writer(self):
+        """push a capturing buffer onto this Context and return the new Writer function."""
+        
+        buf = util.FastEncodingBuffer()
+        self._buffer_stack.append(buf)
+        return buf.write
+
+    def _pop_buffer_and_writer(self):
+        """pop the most recent capturing buffer from this Context 
+        and return the current writer after the pop.
+        
+        """
+
+        buf = self._buffer_stack.pop()
+        return buf, self._buffer_stack[-1].write
+        
+    def _push_buffer(self):
         """push a capturing buffer onto this Context."""
-        self._buffer_stack.append(util.FastEncodingBuffer())
-    def pop_buffer(self):
+        
+        self._push_writer()
+        
+    def _pop_buffer(self):
         """pop the most recent capturing buffer from this Context."""
+        
         return self._buffer_stack.pop()
+        
     def get(self, key, default=None):
         return self._data.get(key, default)
+        
     def write(self, string):
         """write a string to this Context's underlying output buffer."""
+        
         self._buffer_stack[-1].write(string)
+        
+    def writer(self):
+        """return the current writer function"""
+
+        return self._buffer_stack[-1].write
+
     def _copy(self):
         c = Context.__new__(Context)
         c._buffer_stack = self._buffer_stack
@@ -78,10 +112,10 @@ class CallerStack(list):
         return self[-1]
     def __getattr__(self, key):
         return getattr(self._get_caller(), key)
-    def push_frame(self):
+    def _push_frame(self):
         self.append(self.nextcaller or None)
         self.nextcaller = None
-    def pop_frame(self):
+    def _pop_frame(self):
         self.nextcaller = self.pop()
         
         
@@ -217,22 +251,22 @@ class Namespace(object):
 def supports_caller(func):
     """apply a caller_stack compatibility decorator to a plain Python function."""
     def wrap_stackframe(context,  *args, **kwargs):
-        context.caller_stack.push_frame()
+        context.caller_stack._push_frame()
         try:
             return func(context, *args, **kwargs)
         finally:
-            context.caller_stack.pop_frame()
+            context.caller_stack._pop_frame()
     return wrap_stackframe
         
 def capture(context, callable_, *args, **kwargs):
     """execute the given template def, capturing the output into a buffer."""
     if not callable(callable_):
         raise exceptions.RuntimeException("capture() function expects a callable as its argument (i.e. capture(func, *args, **kwargs))")
-    context.push_buffer()
+    context._push_buffer()
     try:
         callable_(*args, **kwargs)
     finally:
-        buf = context.pop_buffer()
+        buf = context._pop_buffer()
     return buf.getvalue()
         
 def _include_file(context, uri, calling_uri, **kwargs):
@@ -296,7 +330,7 @@ def _render(template, callable_, args, data, as_unicode=False):
     context = Context(buf, **data)
     context._with_template = template
     _render_context(template, callable_, context, *args, **_kwargs_for_callable(callable_, data))
-    return context.pop_buffer().getvalue()
+    return context._pop_buffer().getvalue()
 
 def _kwargs_for_callable(callable_, data, **kwargs):
     argspec = inspect.getargspec(callable_)
diff --git a/test/call.py b/test/call.py
index 52ebeb0..0c89694 100644
--- a/test/call.py
+++ b/test/call.py
@@ -105,6 +105,7 @@ class CallTest(unittest.TestCase):
         </%call>
 
         """)
+
         assert result_lines(t.render()) == [
         "<h1>",
         "Some title",
@@ -395,6 +396,8 @@ class CallTest(unittest.TestCase):
         assert result_lines(t.render()) == ['this is a', 'this is b', 'this is c:', "this is the body in b's call", 'the embedded "d" is:', 'this is d']
 
 class SelfCacheTest(unittest.TestCase):
+    """this test uses a now non-public API."""
+    
     def test_basic(self):
         t = Template("""
         <%!
@@ -405,11 +408,11 @@ class SelfCacheTest(unittest.TestCase):
                 global cached
                 if cached:
                     return "cached: " + cached
-                context.push_buffer()
+                __M_writer = context._push_writer()
             %>
             this is foo
             <%
-                buf = context.pop_buffer()
+                buf, __M_writer = context._pop_buffer_and_writer()
                 cached = buf.getvalue()
                 return cached
             %>
diff --git a/test/filters.py b/test/filters.py
index da7caa9..4fdd342 100644
--- a/test/filters.py
+++ b/test/filters.py
@@ -34,6 +34,7 @@ class FilterTest(unittest.TestCase):
             </%def>
             ${foo()}
 """)
+
         assert flatten_result(t.render(x="this is x", myfilter=lambda t: "MYFILTER->%s<-MYFILTER" % t)) == "MYFILTER-> this is foo <-MYFILTER"
 
     def test_import(self):
-- 
GitLab