[Checkins] SVN: z3c.pt/trunk/ Refactored compiler classes; reimplemented disk-cache module.

Malthe Borch mborch at gmail.com
Fri Aug 22 21:38:38 EDT 2008


Log message for revision 90140:
  Refactored compiler classes; reimplemented disk-cache module.

Changed:
  U   z3c.pt/trunk/CHANGES.txt
  U   z3c.pt/trunk/benchmark/benchmark/tests.py
  U   z3c.pt/trunk/src/z3c/pt/clauses.py
  U   z3c.pt/trunk/src/z3c/pt/config.py
  U   z3c.pt/trunk/src/z3c/pt/etree.py
  U   z3c.pt/trunk/src/z3c/pt/filecache.py
  U   z3c.pt/trunk/src/z3c/pt/generation.py
  U   z3c.pt/trunk/src/z3c/pt/genshi.py
  U   z3c.pt/trunk/src/z3c/pt/loader.py
  U   z3c.pt/trunk/src/z3c/pt/pagetemplate.py
  U   z3c.pt/trunk/src/z3c/pt/template.py
  U   z3c.pt/trunk/src/z3c/pt/testing.py
  U   z3c.pt/trunk/src/z3c/pt/tests/test_doctests.py
  U   z3c.pt/trunk/src/z3c/pt/texttemplate.py
  U   z3c.pt/trunk/src/z3c/pt/translation.py
  U   z3c.pt/trunk/src/z3c/pt/zpt.py

-=-
Modified: z3c.pt/trunk/CHANGES.txt
===================================================================
--- z3c.pt/trunk/CHANGES.txt	2008-08-22 22:33:56 UTC (rev 90139)
+++ z3c.pt/trunk/CHANGES.txt	2008-08-23 01:38:29 UTC (rev 90140)
@@ -4,6 +4,12 @@
 Version 1.0dev
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
+- Reimplemented the disk cache to correctly restore all template
+  data. This implementation keeps a cache in a pickled format in a
+  file next to the original template. [malthe]
+
+- Refactored compilation classes to better separate concerns. [malthe]
+
 - Genshi macros (py:def) are now available globally. [malthe]
 
 - A syntax error is now raised when an interpolation expression is not

Modified: z3c.pt/trunk/benchmark/benchmark/tests.py
===================================================================
--- z3c.pt/trunk/benchmark/benchmark/tests.py	2008-08-22 22:33:56 UTC (rev 90139)
+++ z3c.pt/trunk/benchmark/benchmark/tests.py	2008-08-23 01:38:29 UTC (rev 90140)
@@ -8,11 +8,12 @@
 
 import zope.component.testing
 import zope.configuration.xmlconfig
-
 import zope.pagetemplate.pagetemplatefile
 import z3c.pt
-from z3c.pt import generation
 
+from z3c.pt import config
+from z3c.pt import filecache
+
 from lxml import etree
 
 def benchmark(title):
@@ -163,7 +164,7 @@
     def testCompilation(self):
         table = self.table
 
-        t_z3c = timing(self.bigtable_python_z3c.cook, ['table'])
+        t_z3c = timing(self.bigtable_python_z3c.cook, params=('table',))
         t_zope = timing(self.bigtable_python_zope._cook)
 
         print "z3c.pt:            %.2f" % t_z3c
@@ -186,22 +187,24 @@
         table = self.table
 
         z3cfile = z3c.pt.PageTemplateFile(
-            self._testfile('bigtable_python_z3c.pt'),
-            cachedir=self.cache)
-
+            self._testfile('bigtable_python_z3c.pt'))
+        z3cfile.registry = filecache.TemplateCache(z3cfile.filename)
+        z3cfile.cook_check(None, ())
+        assert len(z3cfile.registry) == 1
+        z3cfile.registry.save()
+    
         zopefile = zope.pagetemplate.pagetemplatefile.PageTemplateFile(
             self._testfile('bigtable_python_zope.pt'))
 
-        t_cached_z3c = timing(z3cfile.registry.load, z3cfile)
-        t_cook_z3c = timing(z3cfile.cook, ['table'])
-
+        t_cached_z3c = timing(z3cfile.registry.load)
+        t_cook_z3c = timing(z3cfile.cook, params=('table',))
         t_zope = timing(zopefile._cook)
 
         print "z3c.pt cooking:    %.3f" % t_cook_z3c
-        print ""
+        print "--------------------------"
         print "z3c.pt cached:     %.3f" % t_cached_z3c
         print "zope.pagetemplate: %.3f" % t_zope
-        print "                   %.2fX" % (t_zope/t_cached_z3c)
+        print "ratio to zpt:      %.2fX" % (t_zope/t_cached_z3c)
 
     @benchmark(u"Big table (python) File")
     def testBigTablePythonFile(self):
@@ -278,11 +281,11 @@
         catalog = SimpleTranslationDomain('domain')
         zope.component.provideUtility(catalog, ITranslationDomain, 'domain')
         self.files = os.path.abspath(os.path.join(__file__, '..', 'input'))
-        self.disable = generation.DISABLE_I18N
+        self.disable = config.DISABLE_I18N
 
     def tearDown(self):
         BaseTestCase.tearDown(self)
-        generation.DISABLE_I18N = self.disable
+        config.DISABLE_I18N = self.disable
 
     def _testfile(self, name):
         return os.path.join(self.files, name)
@@ -299,8 +302,8 @@
 
         # In order to have a fair comparision, we need real zope.i18n handling
         zopefile.pt_getEngineContext = _pt_getEngineContext
-
-        t_z3c = timing(z3cfile, table=table, _context=self.env)
+        
+        t_z3c = timing(z3cfile, table=table, context=self.env)
         t_zope = timing(zopefile, table=table, env=self.env)
 
         print "z3c.pt:            %.2f" % t_z3c
@@ -320,7 +323,7 @@
         zopefile.pt_getEngineContext = _pt_getEngineContext
 
         # Let's disable i18n for this test
-        generation.DISABLE_I18N = True
+        config.DISABLE_I18N = True
 
         t_z3c = timing(z3cfile, table=table, _context=self.env)
         t_zope = timing(zopefile, table=table, env=self.env)
@@ -330,6 +333,8 @@
         print "                   %.2fX" % (t_zope/t_z3c)
 
 def test_suite():
+    config.DISK_CACHE = False
+    
     return unittest.TestSuite((
         unittest.makeSuite(BenchmarkTestCase),
         unittest.makeSuite(FileBenchmarkTestCase),

Modified: z3c.pt/trunk/src/z3c/pt/clauses.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/clauses.py	2008-08-22 22:33:56 UTC (rev 90139)
+++ z3c.pt/trunk/src/z3c/pt/clauses.py	2008-08-23 01:38:29 UTC (rev 90140)
@@ -791,9 +791,7 @@
                 raise ImportError(
                     "ElementTree (required when XML validation is enabled).")
 
-            stream.write("import z3c.pt.etree")
-            stream.write("_ET = z3c.pt.etree.import_elementtree()")
-            write("_ET.fromstring('<div>%%s</div>' %% %(tmp)s)")
+            write("%(elementtree)s.fromstring('<div>%%s</div>' %% %(tmp)s)")
 
     def end(self, stream):
         if self.assign:
@@ -888,18 +886,16 @@
 
 class Method(object):
     """
-      >>> from z3c.pt.generation import CodeIO
-      >>> from z3c.pt.testing import pyexp
-      >>> from StringIO import StringIO
+    >>> from z3c.pt import testing
       
-      >>> _out = StringIO(); _write = _out.write; stream = CodeIO()
-      >>> method = Method('test', ('a', 'b', 'c'))
-      >>> method.begin(stream)
-      >>> stream.write('print a, b, c')
-      >>> method.end(stream)
-      >>> exec stream.getvalue()
-      >>> test(1, 2, 3)
-      1 2 3
+    >>> _out, _write, stream = testing.setup_stream()
+    >>> method = Method('test', ('a', 'b', 'c'))
+    >>> method.begin(stream)
+    >>> stream.write('print a, b, c')
+    >>> method.end(stream)
+    >>> exec stream.getvalue()
+    >>> test(1, 2, 3)
+    1 2 3
       
     """
 

Modified: z3c.pt/trunk/src/z3c/pt/config.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/config.py	2008-08-22 22:33:56 UTC (rev 90139)
+++ z3c.pt/trunk/src/z3c/pt/config.py	2008-08-23 01:38:29 UTC (rev 90140)
@@ -1,19 +1,28 @@
 import os
 
-truevals = ('y', 'yes', 't', 'true', 'on', '1')
+# define which values are read as true
+TRUEVALS = ('y', 'yes', 't', 'true', 'on', '1')
 
+# in debug-mode, templates on disk are reloaded if they're modified
 DEBUG_MODE_KEY = 'Z3C_PT_DEBUG'
 DEBUG_MODE = os.environ.get(DEBUG_MODE_KEY, 'false')
-DEBUG_MODE = DEBUG_MODE.lower() in truevals
+DEBUG_MODE = DEBUG_MODE.lower() in TRUEVALS
 
-PROD_MODE = not DEBUG_MODE
+# disable disk-cache to prevent the compiler from caching on disk
+DISK_CACHE = not DEBUG_MODE
+CACHE_EXTENSION = "cache"
 
+# when validation is enabled, dynamically inserted content is
+# validated against the XHTML standard
 VALIDATION = DEBUG_MODE
 
+# use the disable-i18n flag to disable the translation machinery; this
+# will speed up templates that use internationalization
 DISABLE_I18N_KEY = 'Z3C_PT_DISABLE_I18N'
 DISABLE_I18N = os.environ.get(DISABLE_I18N_KEY, 'false')
-DISABLE_I18N = DISABLE_I18N.lower() in truevals
+DISABLE_I18N = DISABLE_I18N.lower() in TRUEVALS
 
+# these definitions are standard---change at your own risk!
 XHTML_NS = "http://www.w3.org/1999/xhtml"
 TAL_NS = "http://xml.zope.org/namespaces/tal"
 META_NS = "http://xml.zope.org/namespaces/meta"
@@ -22,7 +31,9 @@
 PY_NS = "http://genshi.edgewall.org"
 NS_MAP = dict(py=PY_NS, tal=TAL_NS, metal=METAL_NS)
 
+# the symbols table below is used internally be the compiler
 class SYMBOLS(object):
+    init = '_init'
     slot = '_slot'
     metal = '_metal'
     macro = '_macro'
@@ -38,10 +49,10 @@
     attributes = '_attributes'
     negotiate = '_negotiate'
     translate = '_translate'
+    elementtree = '_et'
     path = '_path'
     repeat = 'repeat'
     language = 'target_language'
-    generation = 'generation'
 
     @classmethod
     def as_dict(cls):

Modified: z3c.pt/trunk/src/z3c/pt/etree.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/etree.py	2008-08-22 22:33:56 UTC (rev 90139)
+++ z3c.pt/trunk/src/z3c/pt/etree.py	2008-08-23 01:38:29 UTC (rev 90140)
@@ -7,22 +7,21 @@
 
 def import_elementtree():
     try:
-        import elementtree.ElementTree as ET
-    except ImportError:
+        import xml.etree.ElementTree as ET
+    except:
         try:
+            import elementtree.ElementTree as ET
+        except ImportError:
             import cElementTree as ET
-        except ImportError:
-            import xml.etree.ElementTree as ET
-
+        
     return ET
 
 class Parser(object):
     element_mapping = {}
 
     @classmethod
-    def parse(self, body):
-        global parse
-        return parse(body, self.element_mapping)
+    def parse(cls, body):
+        return parse(body, cls.element_mapping)
         
 try:
     import lxml.etree
@@ -179,7 +178,8 @@
 
 except ImportError:
     ET = import_elementtree()
-            
+    from pdis.xpath import XPath
+    
     class ElementBase(object, ET._ElementInterface):
         _parent = None
         
@@ -211,8 +211,9 @@
         def tostring(self):
             return ET.tostring(self)
 
-        def xpath(self, expression, namespaces={}):
-            return []
+        def xpath(self, path, namespaces={}):
+            xpath = XPath(path, namespace_mapping=namespaces)
+            return xpath.evaluate(self)
             
         @property
         def nsmap(self):

Modified: z3c.pt/trunk/src/z3c/pt/filecache.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/filecache.py	2008-08-22 22:33:56 UTC (rev 90139)
+++ z3c.pt/trunk/src/z3c/pt/filecache.py	2008-08-23 01:38:29 UTC (rev 90140)
@@ -1,64 +1,51 @@
 import os
-import marshal
-import py_compile
-import struct
+import config
+import cPickle as pickle
 
-from imp import get_magic
-from sha import sha
-from UserDict import UserDict
+class TemplateCache(object):
+    def __init__(self, filename):
+        self.filename = filename
+        self.registry = {}
+        self.load()
+        
+    def __getitem__(self, key):
+        return self.registry[key]
 
-MAGIC = get_magic()
+    def __setitem__(self, key, template):
+        self.registry[key] = template
+        self.save()
 
-def code_read(filename, timestamp):
-    code = None
-    if os.path.isfile(filename):
-        fd = open(filename, 'rb')
-        if fd.read(4) == MAGIC:
-            ctimestamp = struct.unpack('<I', fd.read(4))[0]
-            if ctimestamp == timestamp:
-                code = marshal.load(fd)
-        fd.close()
-    return code
+    def __len__(self):
+        return len(self.registry)
+    
+    def get(self, key, default=None):
+        return self.registry.get(key, default)
+    
+    @property
+    def module_filename(self):
+        return self.filename + os.extsep + config.CACHE_EXTENSION
+    
+    def load(self):
+        try:
+            module_filename = self.module_filename
+            f = open(module_filename, 'rb')
+        except IOError:
+            return
 
-def code_write(code, filename, timestamp):
-    try:
-        fd = open(filename, 'wb')
         try:
-            # Create a byte code file. See the py_compile module
-            fd.write('\0\0\0\0')
-            fd.write(struct.pack("<I", int(timestamp)))
-            marshal.dump(code, fd)
-            fd.flush()
-            fd.seek(0, 0)
-            fd.write(MAGIC)
+            self.registry = pickle.load(f)
+        except EOFError:
+            pass
         finally:
-            fd.close()
-        py_compile.set_creator_type(filename)
-    except (IOError, OSError):
-        pass
+            f.close()
+        
+    def save(self):
+        try:
+            f = open(self.module_filename, 'wb')
+        except IOError:
+            return
 
-class CachingDict(UserDict):
-
-    def __init__(self, cachedir, filename, mtime):
-        UserDict.__init__(self)
-        signature = sha(filename).hexdigest()
-        self.cachedir = os.path.join(cachedir, signature)
-        self.mtime = mtime
-
-    def load(self, pagetemplate):
-        if os.path.isdir(self.cachedir):
-            for cachefile in os.listdir(self.cachedir):
-                value = None
-                cachepath = os.path.join(self.cachedir, cachefile)
-                code = code_read(cachepath, self.mtime)
-                if code is not None:
-                    self[int(cachefile)] = pagetemplate.execute(code)
-                    pagetemplate._v_last_read = self.mtime
-
-    def store(self, params, code):
-        key = hash(''.join(params))
-        if not os.path.isdir(self.cachedir):
-            os.mkdir(self.cachedir)
-
-        cachefile = os.path.join(self.cachedir, str(key))
-        code_write(code, cachefile, self.mtime)
+        try:
+            pickle.dump(self.registry, f)
+        finally:
+            f.close()

Modified: z3c.pt/trunk/src/z3c/pt/generation.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/generation.py	2008-08-22 22:33:56 UTC (rev 90139)
+++ z3c.pt/trunk/src/z3c/pt/generation.py	2008-08-23 01:38:29 UTC (rev 90140)
@@ -1,41 +1,40 @@
 from zope.i18n import interpolate
-from zope.i18n import negotiate
 from zope.i18n import translate
 from zope.i18nmessageid import Message
 
-import expressions
 import utils
+import etree
+import expressions
 
-import z3c.pt.generation
-from z3c.pt.config import DISABLE_I18N
-
 template_wrapper = """\
-def render(%(args)s%(extra)s%(language)s=None):
-\tglobal %(generation)s
-
-\t%(out)s, %(write)s = generation.initialize_stream()
-\t%(attributes)s, %(repeat)s = generation.initialize_tal()
-\t%(domain)s, %(negotiate)s, %(translate)s = generation.initialize_i18n()
-\t%(marker)s = %(generation)s.initialize_helpers()
-\t%(path)s = %(generation)s.initialize_traversal()
+def render(%(init)s, %(args)s%(extra)s%(language)s=None):
+\t%(out)s, %(write)s = %(init)s.initialize_stream()
+\t%(attributes)s, %(repeat)s = %(init)s.initialize_tal()
+\t%(marker)s = %(init)s.initialize_helpers()
+\t%(path)s = %(init)s.initialize_traversal()
+\t%(translate)s = %(init)s.fast_translate
+\t%(elementtree)s = %(init)s.initialize_elementtree()
 \t%(scope)s = {}
 
-\t%(language)s = %(negotiate)s(%(context)s, %(language)s)
-%(code)s
+%(body)s
 \treturn %(out)s.getvalue()
 """
 
 macro_wrapper = """\
-def render(%(kwargs)s%(extra)s):
-\tglobal %(generation)s
-%(code)s
+def render(%(init)s, %(kwargs)s%(extra)s):
+%(body)s
 """
 
-def _fake_negotiate(context, target_language):
-    return target_language
-
-def _fake_translate(msgid, domain=None, mapping=None, context=None,
-                    target_language=None, default=None):
+def fast_translate(msgid, domain=None, mapping=None, context=None,
+                   target_language=None, default=None):
+    """This translation function does not attempt to negotiate a
+    language if ``None`` is passed."""
+    
+    if target_language is not None:
+        return translate(
+            msgid, domain=domain, mapping=mapping, context=context,
+            target_language=target_language, default=default)
+    
     if isinstance(msgid, Message):
         default = msgid.default
         mapping = msgid.mapping
@@ -43,18 +42,11 @@
     if default is None:
         default = unicode(msgid)
 
+    if not isinstance(default, (str, unicode)):
+        return default
+    
     return interpolate(default, mapping)
 
-def _negotiate(context, target_language):
-    if target_language is not None:
-        return target_language
-    return negotiate(context)
-
-def initialize_i18n():
-    if DISABLE_I18N:
-        return (None, _fake_negotiate, _fake_translate)
-    return (None, _negotiate, translate)
-
 def initialize_tal():
     return ({}, utils.repeatdict())
 
@@ -68,6 +60,12 @@
 def initialize_traversal():
     return expressions.path_translation.traverse
 
+def initialize_elementtree():
+    try:
+        return etree.import_elementtree()
+    except ImportError:
+        return None
+    
 class BufferIO(list):
     write = list.append
 
@@ -75,18 +73,10 @@
         return ''.join(self)
 
 class CodeIO(BufferIO):
-    """A I/O stream class that provides facilities to generate Python code.
+    """Stream buffer suitable for writing Python-code. This class is
+    used internally by the compiler to manage variable scopes, source
+    annotations and temporary variables."""
 
-    * Indentation is managed using ``indent`` and ``outdent``.
-
-    * Annotations can be assigned on a per-line basis using ``annotate``.
-
-    * Convenience methods for keeping track of temporary variables
-   
-    * Methods to process clauses (see ``begin`` and ``end``).
-    
-    """
-
     t_prefix = '_tmp'
     v_prefix = '_var'
 
@@ -99,7 +89,6 @@
         self.scope = [set()]
         self.selectors = {}
         self.annotations = {}
-        
         self._variables = {}
         self.t_counter = 0
         self.l_counter = 0

Modified: z3c.pt/trunk/src/z3c/pt/genshi.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/genshi.py	2008-08-22 22:33:56 UTC (rev 90139)
+++ z3c.pt/trunk/src/z3c/pt/genshi.py	2008-08-23 01:38:29 UTC (rev 90140)
@@ -149,6 +149,9 @@
                 expression = "%s(%s)" % (name, select)
                 match.attrib[utils.py_attr('replace')] = expression
 
+                # save select-variable as element attribute
+                match.attrib[utils.meta_attr('select')] = select
+
         # Step 3: Variable interpolation
         translation.VariableInterpolation.update(self)        
 

Modified: z3c.pt/trunk/src/z3c/pt/loader.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/loader.py	2008-08-22 22:33:56 UTC (rev 90139)
+++ z3c.pt/trunk/src/z3c/pt/loader.py	2008-08-23 01:38:29 UTC (rev 90140)
@@ -7,40 +7,33 @@
     """Template loader tool.
     """
 
-    def __init__(self, search_path=None, auto_reload=False, cachedir=None, parser=None):
+    def __init__(self, search_path=None, auto_reload=False, parser=None):
         if search_path is None:
             search_path = []
         if isinstance(search_path, basestring):
             search_path = [search_path]
         self.search_path = search_path
         self.auto_reload = auto_reload
-        self.cachedir = cachedir
         self.parser = parser
-        if cachedir is not None:
-            if not os.path.isdir(cachedir):
-                raise ValueError("Invalid cachedir")
 
-
     def _load(self, filename, klass):
         if os.path.isabs(filename):
-            return PageTemplateFile(filename)
+            return klass(filename, self.parser)
 
         for path in self.search_path:
             path = os.path.join(path, filename)
             try:
-                return klass(path, auto_reload=self.auto_reload,
-                                cachedir=self.cachedir, parser=self.parser)
+                return klass(
+                    path, self.parser, auto_reload=self.auto_reload)
             except OSError, e:
                 if e.errno != errno.ENOENT:
                     raise
 
         raise ValueError("Can not find template %s" % filename)
 
-
     def load_page(self, filename):
         return self._load(filename, PageTemplateFile)
 
-
     def load_text(self, filename):
         return self._load(filename, TextTemplateFile)
 

Modified: z3c.pt/trunk/src/z3c/pt/pagetemplate.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/pagetemplate.py	2008-08-22 22:33:56 UTC (rev 90139)
+++ z3c.pt/trunk/src/z3c/pt/pagetemplate.py	2008-08-23 01:38:29 UTC (rev 90140)
@@ -1,18 +1,48 @@
+import zope.i18n
+
 import translation
+import generation
 import template
+import config
+import zpt
 
+def prepare_language_support(kwargs):
+    target_language = kwargs.get('target_language')
+
+    if config.DISABLE_I18N:
+        if target_language:
+            del kwargs['target_language']
+        return
+    
+    if not target_language:
+        context = kwargs.get('context')
+        target_language = zope.i18n.negotiate(context)
+
+        if target_language:
+            kwargs['target_language'] = target_language    
+
 class PageTemplate(template.BaseTemplate):
     __doc__ = template.BaseTemplate.__doc__ # for Sphinx autodoc
-    @property
-    def translate(self):
-        return translation.translate_xml
 
+    def __init__(self, body, parser=None):
+        if parser is None:
+            parser = zpt.ZopePageTemplateParser
+        super(PageTemplate, self).__init__(body, parser)
+
+    def prepare(self, kwargs):
+        prepare_language_support(kwargs)
+
 class PageTemplateFile(template.BaseTemplateFile):
     __doc__ = template.BaseTemplateFile.__doc__ # for Sphinx autodoc
-    @property
-    def translate(self):
-        return translation.translate_xml
+    
+    def __init__(self, filename, parser=None, **kwargs):
+        if parser is None:
+            parser = zpt.ZopePageTemplateParser
+        super(PageTemplateFile, self).__init__(filename, parser, **kwargs)
 
+    def prepare(self, kwargs):
+        prepare_language_support(kwargs)
+
 class ViewPageTemplate(property):
     def __init__(self, body, **kwargs):
         self.template = PageTemplate(body, **kwargs)

Modified: z3c.pt/trunk/src/z3c/pt/template.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/template.py	2008-08-22 22:33:56 UTC (rev 90139)
+++ z3c.pt/trunk/src/z3c/pt/template.py	2008-08-23 01:38:29 UTC (rev 90140)
@@ -1,12 +1,9 @@
 import os
 import macro
-import codegen
-import zpt
+import config
+import filecache
+import translation
 
-from z3c.pt.config import DEBUG_MODE
-from z3c.pt import filecache
-import z3c.pt.generation
-
 class BaseTemplate(object):
     """ Constructs a template object.  Must be passed an input string
     as ``body``. ``parser`` is the parser implementation
@@ -16,76 +13,50 @@
     (typically either the string ``path`` or the string ``python``);
     ``python`` is the default if nothing is passed."""
 
-    registry = {}
-    cachedir = None
-    parser = zpt.ZopePageTemplateParser
-    default_expression = 'python'
-
-    def __init__(self, body, parser=None, default_expression=None):
+    def __init__(self, body, parser):
         self.body = body
+        self.parser = parser        
         self.signature = hash(body)
-        self.source = ''
-
-        if default_expression:
-            self.default_expression = default_expression
-
-        if parser:
-            self.parser = parser
-            
+        self.registry = {}
+        
     @property
     def translate(self):
-        return NotImplementedError("Must be implemented by subclass.")
+        return NotImplementedError("Must be provided by subclass.")
 
     @property
     def macros(self):
         return macro.Macros(self.render)
 
-    def cook(self, params, macro=None):
-        generator = self.translate(
-            self.body, self.parser, macro=macro, params=params,
-            default_expression=self.default_expression)
-        
-        source, _globals = generator()
-        suite = codegen.Suite(source, _globals.keys())
-
-        if self.cachedir:
-            self.registry.store(params, suite.code)
-
-        self.source = source
-        self.selectors = generator.stream.selectors
-        self.annotations = generator.stream.annotations
-        
-        _globals.update(suite._globals)
-        return self.execute(suite.code, _globals)
-
+    @property
+    def compiler(self):
+        return translation.Compiler(self.body, self.parser)
+    
+    def cook(self, **kwargs):
+        return self.compiler(**kwargs)
+    
     def cook_check(self, macro, params):
-        signature = self.signature, macro, params
-        template = self.registry.get(signature, None)
+        key = self.signature, macro, params
+        template = self.registry.get(key, None)
         if template is None:
-            template = self.cook(params, macro=macro)
-            self.registry[signature] = template
+            template = self.cook(macro=macro, params=params)
+            self.registry[key] = template
             
         return template
-    
-    def execute(self, code, _globals):
-        _locals = {}
-        exec code in _globals, _locals
-        return _locals['render']
 
+    def prepare(self, kwargs):
+        pass
+    
     def render(self, macro=None, **kwargs):
+        self.prepare(kwargs)
         template = self.cook_check(macro, tuple(kwargs))
-        
-        # pass in selectors
-        kwargs.update(self.selectors)
+        kwargs.update(template.selectors)
+        return template.render(**kwargs)
 
-        return template(**kwargs)
-
-    def __call__(self, **kwargs):
-        return self.render(**kwargs)
-
     def __repr__(self):
         return u"<%s %d>" % (self.__class__.__name__, id(self))
 
+    __call__ = render
+    
 class BaseTemplateFile(BaseTemplate):
     """ Constructs a template object.  Must be passed an absolute (or
     current-working-directory-relative) filename as ``filename``. If
@@ -98,27 +69,23 @@
     the string ``path`` or the string ``python``); ``python`` is the
     default if nothing is passed."""
 
-    def __init__(self, filename, auto_reload=False, cachedir=None,
-                 parser=None, default_expression=None):
+    def __init__(self, filename, parser, auto_reload=False):
         BaseTemplate.__init__(
-            self, None, parser=parser, default_expression=default_expression)
-        self.auto_reload = auto_reload
-        self.cachedir = cachedir
+            self, None, parser)
 
-        filename = os.path.abspath(
+        self.auto_reload = auto_reload
+        self.filename = filename = os.path.abspath(
             os.path.normpath(os.path.expanduser(filename)))
 
         # make sure file exists
         os.lstat(filename)
-        self.filename = filename
-        if self.cachedir:
-            self.registry = filecache.CachingDict(cachedir, filename,
-                                                  self.mtime())
-            self.registry.load(self)
-        else:
-            self.registry = {}
 
+        # read template
         self.read()
+
+        # persist template registry on disk
+        if config.DISK_CACHE:
+            self.registry = filecache.TemplateCache(filename)
         
     def _get_filename(self):
         return getattr(self, '_filename', None)
@@ -129,21 +96,6 @@
 
     filename = property(_get_filename, _set_filename)
 
-    def _get_source(self):
-        return self._source
-
-    def _set_source(self, source):
-        self._source = source
-
-        # write source to disk
-        if source and self.filename and DEBUG_MODE:
-            filename = "%s.py" % self.filename
-            fs = open(filename, 'w')
-            fs.write(source)
-            fs.close()
-
-    source = property(_get_source, _set_source)
-
     def read(self):
         fd = open(self.filename, 'r')
         self.body = body = fd.read()
@@ -151,15 +103,6 @@
         self.signature = hash(body)
         self._v_last_read = self.mtime()
 
-    def execute(self, code, _globals=None):
-        # TODO: This is evil. We need a better way to get all the globals
-        # independent from the generation step
-        if _globals is None:
-            _globals = codegen.Lookup.globals()
-            _globals['generation'] = z3c.pt.generation
-
-        return BaseTemplate.execute(self, code, _globals)
-
     def cook_check(self, *args):
         if self.auto_reload and self._v_last_read != self.mtime():
             self.read()

Modified: z3c.pt/trunk/src/z3c/pt/testing.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/testing.py	2008-08-22 22:33:56 UTC (rev 90139)
+++ z3c.pt/trunk/src/z3c/pt/testing.py	2008-08-23 01:38:29 UTC (rev 90140)
@@ -24,36 +24,26 @@
     stream = generation.CodeIO(symbols)
     return out, write, stream
 
-def cook(generator, **kwargs):
-    source, _globals = generator()
-    _locals = {}
-    exec source in _globals, _locals
-    return _locals['render']
-
-def _render(generator, **kwargs):
-    cooked = cook(generator, **kwargs)
-    kwargs.update(generator.stream.selectors)
-    return cooked(**kwargs)
-
 def render_xhtml(body, **kwargs):
-    generator = translation.translate_xml(
-        body, MockParser, params=sorted(kwargs.keys()))
-    return _render(generator, **kwargs)
-
+    compiler = translation.Compiler(body, MockParser)
+    template = compiler(params=sorted(kwargs.keys()))
+    return template.render(**kwargs)    
+    
 def render_text(body, **kwargs):
-    generator = translation.translate_text(
-        body, MockParser, params=sorted(kwargs.keys()))
-    return _render(generator, **kwargs)
+    compiler = translation.Compiler.from_text(body, MockParser)
+    template = compiler(params=sorted(kwargs.keys()))
+    return template.render(**kwargs)    
 
 def render_zpt(body, **kwargs):
-    generator = translation.translate_xml(
-        body, zpt.ZopePageTemplateParser, params=sorted(kwargs.keys()))
-    return _render(generator, **kwargs)
+    compiler = translation.Compiler(body, zpt.ZopePageTemplateParser)
+    template = compiler(params=sorted(kwargs.keys()))
+    return template.render(**kwargs)    
 
 def render_genshi(body, **kwargs):
-    generator = translation.translate_xml(
-        body, genshi.GenshiParser, params=sorted(kwargs.keys()))
-    return _render(generator, **kwargs)
+    compiler = translation.Compiler(body, genshi.GenshiParser)
+    template = compiler(params=sorted(kwargs.keys()))
+    kwargs.update(template.selectors)
+    return template.render(**kwargs)    
 
 class MockTemplate(object):
     def __init__(self, body, parser):
@@ -63,9 +53,9 @@
     @property
     def macros(self):
         def render(macro=None, **kwargs):
-            generator = translation.translate_xml(
-                self.body, self.parser, macro=macro, params=kwargs.keys())
-            return _render(generator, **kwargs)
+            compiler = translation.Compiler(self.body, self.parser)
+            template = compiler(macro=macro, params=kwargs.keys())
+            return template.render(**kwargs)
         return macro.Macros(render)
 
 class MockElement(translation.Element, translation.VariableInterpolation):

Modified: z3c.pt/trunk/src/z3c/pt/tests/test_doctests.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/tests/test_doctests.py	2008-08-22 22:33:56 UTC (rev 90139)
+++ z3c.pt/trunk/src/z3c/pt/tests/test_doctests.py	2008-08-23 01:38:29 UTC (rev 90140)
@@ -7,7 +7,7 @@
 import zope.component.testing
 import zope.configuration.xmlconfig
 
-import z3c.pt
+import z3c.pt.config
 
 def setUp(suite):
     zope.component.testing.setUp(suite)
@@ -18,6 +18,8 @@
                   'i18n.txt', 'codegen.txt', 'translation.txt')
     testsuites = ('z3c.pt.translation', 'z3c.pt.clauses', 'z3c.pt.expressions')
 
+    z3c.pt.config.DISK_CACHE = False
+    
     return unittest.TestSuite(
         [zope.testing.doctest.DocTestSuite(
         doctest, optionflags=OPTIONFLAGS,

Modified: z3c.pt/trunk/src/z3c/pt/texttemplate.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/texttemplate.py	2008-08-22 22:33:56 UTC (rev 90139)
+++ z3c.pt/trunk/src/z3c/pt/texttemplate.py	2008-08-23 01:38:29 UTC (rev 90140)
@@ -1,17 +1,17 @@
-import template
+import pagetemplate
 import translation
 
-class TextTemplate(template.BaseTemplate):
-    __doc__ = template.BaseTemplate.__doc__ # for Sphinx autodoc
+class TextTemplate(pagetemplate.PageTemplate):
+    __doc__ = pagetemplate.PageTemplate.__doc__ # for Sphinx autodoc
     @property
-    def translate(self):
-        return translation.translate_text
+    def compiler(self):
+        return translation.Compiler.from_text(self.body, self.parser)
 
-class TextTemplateFile(template.BaseTemplateFile):
-    __doc__ = template.BaseTemplateFile.__doc__ # for Sphinx autodoc
+class TextTemplateFile(pagetemplate.PageTemplateFile):
+    __doc__ = pagetemplate.PageTemplateFile.__doc__ # for Sphinx autodoc
     @property
-    def translate(self):
-        return translation.translate_text
+    def compiler(self):
+        return translation.Compiler.from_text(self.body, self.parser)
 
 class ViewTextTemplate(property):
     def __init__(self, body):

Modified: z3c.pt/trunk/src/z3c/pt/translation.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/translation.py	2008-08-22 22:33:56 UTC (rev 90139)
+++ z3c.pt/trunk/src/z3c/pt/translation.py	2008-08-23 01:38:29 UTC (rev 90140)
@@ -3,6 +3,7 @@
 from StringIO import StringIO
 
 import generation
+import codegen
 import clauses
 import interfaces
 import expressions
@@ -11,7 +12,8 @@
 import utils
 import config
 import etree
-
+import marshal
+    
 class Node(object):
     """Element translation class.
 
@@ -152,13 +154,13 @@
 
                 if variable in attributes:
                     default = '"%s"' % attributes[variable]
-                    expression = _translate(value, default=default)
+                    expression = self.translate_expression(value, default=default)
                 else:
-                    expression = _translate(value)
+                    expression = self.translate_expression(value)
             else:
                 if variable in dynamic_attributes or variable in attributes:
                     text = '"%s"' % attributes[variable]
-                    expression = _translate(text)
+                    expression = self.translate_expression(text)
                 else:
                     raise ValueError("Must be either static or dynamic "
                                      "attribute when no message id "
@@ -176,7 +178,7 @@
                 cdata=self.cdata is not None)
             if self.omit:
                 _.append(clauses.Condition(
-                    _not(self.omit), [tag], finalize=False))
+                    types.value("not (%s)" % self.omit), [tag], finalize=False))
             else:
                 _.append(tag)
 
@@ -210,7 +212,7 @@
                 subclauses = []
                 subclauses.append(clauses.Define(
                     types.declaration((self.symbols.out, self.symbols.write)),
-                    types.template('%(generation)s.initialize_stream()')))
+                    types.template('%(init)s.initialize_stream()')))
                 subclauses.append(clauses.Visit(element.node))
                 subclauses.append(clauses.Assign(
                     types.template('%(out)s.getvalue()'), variable))
@@ -249,7 +251,7 @@
                 subclauses = []
                 subclauses.append(clauses.Define(
                     types.declaration((self.symbols.out, self.symbols.write)),
-                    types.template('%(generation)s.initialize_stream()')))
+                    types.template('%(init)s.initialize_stream()')))
                 subclauses.append(clauses.Visit(element.node))
                 subclauses.append(clauses.Assign(
                     types.template('%(out)s.getvalue()'),
@@ -258,8 +260,9 @@
                 _.append(clauses.Group(subclauses))
 
             _.append(clauses.Assign(
-                _translate(types.value(repr(msgid)), mapping=mapping,
-                           default=self.symbols.marker), self.symbols.result))
+                self.translate_expression(
+                types.value(repr(msgid)), mapping=mapping,
+                default=self.symbols.marker), self.symbols.result))
 
             # write translation to output if successful, otherwise
             # fallback to default rendition; 
@@ -313,6 +316,15 @@
         
         return msgid
 
+    def translate_expression(self, value, mapping=None, default=None):
+        if default is not None and not isinstance(default, (str, unicode)):
+            import pdb; pdb.set_trace()
+        
+        format = "_translate(%s, domain=%%(domain)s, mapping=%s, context=%%(context)s, " \
+                 "target_language=%%(language)s, default=%s)"
+        return types.template(
+            format % (value, mapping, default))
+
 class Element(etree.ElementBase):
     """Template element class.
 
@@ -402,112 +414,165 @@
                 else:
                     self.attrib[attributes] = expr
 
-def translate_xml(body, parser, *args, **kwargs):
-    root, doctype = parser.parse(body)
-    return translate_etree(root, doctype=doctype, *args, **kwargs)
+class Compiler(object):
+    """Template compiler."""
+    
+    def __init__(self, body, parser):
+        self.root, self.doctype = parser.parse(body)
+        self.parser = parser
 
-def translate_etree(root, macro=None, doctype=None,
-                    params=[], default_expression='python'):
-    if not isinstance(root, Element):
-        raise ValueError("Must define valid namespace for tag: '%s.'" % root.tag)
+    @classmethod
+    def from_text(cls, body, parser):
+        compiler = Compiler(
+            "<html xmlns='%s'></html>" % config.XHTML_NS, parser)
+        compiler.root.text = body
+        compiler.root.attrib[utils.meta_attr('omit-tag')] = ""
+        return compiler
 
-    # skip to macro
-    if macro is not None:
-        elements = root.xpath(
-            'descendant-or-self::*[@metal:define-macro="%s"]' % macro,
-            namespaces={'metal': config.METAL_NS})
+    def __call__(self, macro=None, params=()):
+        if not isinstance(self.root, Element):
+            raise ValueError(
+                "Must define valid namespace for tag: '%s.'" % self.root.tag)
 
-        if not elements:
-            raise ValueError("Macro not found: %s." % macro)
+        # if macro is non-trivial, start compilation at the element
+        # where the macro is defined
+        if macro is not None:
+            elements = self.root.xpath(
+                'descendant-or-self::*[@metal:define-macro="%s"]' % macro,
+                namespaces={'metal': config.METAL_NS})
 
-        root = elements[0]
-        del root.attrib[utils.metal_attr('define-macro')]
-        
-    # set default expression name
-    if utils.get_namespace(root) == config.TAL_NS:
-        tag = 'default-expression'
-    else:
-        tag = utils.tal_attr('default-expression')
+            if not elements:
+                raise ValueError("Macro not found: %s." % macro)
 
-    if not root.attrib.get(tag):
-        root.attrib[tag] = default_expression
+            self.root = elements[0]
+            del self.root.attrib[utils.metal_attr('define-macro')]
 
-    # set up code generation stream
-    if macro is not None:
-        wrapper = generation.macro_wrapper
-    else:
-        wrapper = generation.template_wrapper
+        # set up code generation stream
+        if macro is not None:
+            wrapper = generation.macro_wrapper
+        else:
+            wrapper = generation.template_wrapper
 
-    # initialize code stream object
-    stream = generation.CodeIO(
-        root.node.symbols, indentation=1, indentation_string="\t")
+        # initialize code stream object
+        stream = generation.CodeIO(
+            self.root.node.symbols, indentation=1, indentation_string="\t")
 
-    # initialize variable scope
-    stream.scope.append(set(
-        (stream.symbols.out, stream.symbols.write, stream.symbols.scope) + \
-        tuple(params)))
+        # initialize variable scope
+        stream.scope.append(set(
+            (stream.symbols.out, stream.symbols.write, stream.symbols.scope) + \
+            tuple(params)))
 
-    # output doctype if any
-    if doctype and isinstance(doctype, (str, unicode)):
-        dt = (doctype +'\n').encode('utf-8')
-        doctype = clauses.Out(dt)
-        stream.scope.append(set())
-        stream.begin([doctype])
-        stream.end([doctype])
-        stream.scope.pop()
+        # output doctype if any
+        if self.doctype and isinstance(self.doctype, (str, unicode)):
+            doctype = (self.doctype +'\n').encode('utf-8')
+            out = clauses.Out(doctype)
+            stream.scope.append(set())
+            stream.begin([out])
+            stream.end([out])
+            stream.scope.pop()
 
-    # start generation
-    root.start(stream)
+        # start generation
+        self.root.start(stream)
 
-    extra = ''
+        # prepare args
+        ignore = 'target_language',
+        args = ', '.join((param for param in params if param not in ignore))
+        if args:
+            args += ', '
 
-    # prepare args
-    args = ', '.join(params)
-    if args:
-        args += ', '
+        # prepare kwargs
+        kwargs = ', '.join("%s=None" % param for param in params)
+        if kwargs:
+            kwargs += ', '
 
-    # prepare kwargs
-    kwargs = ', '.join("%s=None" % param for param in params)
-    if kwargs:
-        kwargs += ', '
+        # prepare selectors
+        extra = ''
+        for selector in stream.selectors:
+            extra += '%s=None, ' % selector
 
-    # prepare selectors
-    for selector in stream.selectors:
-        extra += '%s=None, ' % selector
+        # we need to ensure we have _context for the i18n handling in
+        # the arguments. the default template implementations pass
+        # this in explicitly.
+        if stream.symbols.context not in params:
+            extra += '%s=None, ' % stream.symbols.context
 
-    # we need to ensure we have _context for the i18n handling in
-    # the arguments. the default template implementations pass
-    # this in explicitly.
-    if stream.symbols.context not in params:
-        extra += '%s=None, ' % stream.symbols.context
+        # wrap generated Python-code in function definition
+        body = stream.getvalue()
+        mapping = dict(
+            args=args, kwargs=kwargs, extra=extra, body=body)
+        mapping.update(stream.symbols.__dict__)
+        source = wrapper % mapping
+        
+        # compile code
+        suite = codegen.Suite(source)
+        _locals = {}
+        _globals = {}
+        exec suite.code in suite._globals, _locals
 
-    code = stream.getvalue()
+        return ByteCodeTemplate(
+            _locals['render'], self.root, self.parser, stream)
 
-    class generator(object):
-        @property
-        def stream(self):
-            return stream
-        
-        def __call__(self):
-            parameters = dict(
-                args=args, kwargs=kwargs, extra=extra, code=code)
-            parameters.update(stream.symbols.__dict__)
+class ByteCodeTemplate(object):
+    """Template compiled to byte-code."""
 
-            return wrapper % parameters, {stream.symbols.generation: generation}
+    def __init__(self, func, root, parser, stream):
+        self.func = func
+        self.root = root
+        self.parser = parser
+        self.stream = stream
 
-    return generator()
+    def __reduce__(self):
+        reconstructor, (cls, base, state), kwargs = \
+                       GhostedByteCodeTemplate(self).__reduce__()
+        return reconstructor, (ByteCodeTemplate, base, state), kwargs
 
-def translate_text(body, parser, *args, **kwargs):
-    root, doctype = parser.parse("<html xmlns='%s'></html>" % config.XHTML_NS)
-    root.text = body
-    root.attrib[utils.meta_attr('omit-tag')] = ''
-    return translate_etree(root, doctype=doctype, *args, **kwargs)
+    def __setstate__(self, state):
+        self.__dict__.update(GhostedByteCodeTemplate.rebuild(state))
+
+    def render(self, *args, **kwargs):
+        return self.func(generation, *args, **kwargs)
     
-def _translate(value, mapping=None, default=None):
-    format = "_translate(%s, domain=%%(domain)s, mapping=%s, context=%%(context)s, " \
-             "target_language=%%(language)s, default=%s)"
-    return types.template(
-        format % (value, mapping, default))
+    @property
+    def source(self):
+        return self.stream.getvalue()
+    
+    @property
+    def selectors(self):
+        selectors = getattr(self, '_selectors', None)
+        if selectors is not None:
+            return selectors
 
-def _not(value):
-    return types.value("not (%s)" % value)
+        self._selectors = selectors = {}
+        for element in self.root.xpath(
+            './/*[@meta:select]', namespaces={'meta': config.META_NS}):
+            name = element.attrib[utils.meta_attr('select')]
+            selectors[name] = element.xpath
+
+        return selectors
+
+class GhostedByteCodeTemplate(object):
+    suite = codegen.Suite("def render(): pass")
+    
+    def __init__(self, template):
+        self.code = marshal.dumps(template.func.func_code)
+        self.defaults = len(template.func.func_defaults or ())
+        self.parser = template.parser
+        self.xmldoc = template.root.tostring()
+        self.stream = template.stream
+
+    @classmethod
+    def rebuild(cls, state):
+        _locals = {}
+        exec cls.suite.code in cls.suite._globals, _locals
+        func = _locals['render']
+        func.func_defaults = ((None,)*state['defaults']) or None
+        func.func_code = marshal.loads(state['code'])
+        parser = state['parser']
+        root, doctype = state['parser'].parse(state['xmldoc'])
+        stream = state['stream']
+        return dict(
+            func=func,
+            parser=parser,
+            root=root,
+            stream=stream)
+            

Modified: z3c.pt/trunk/src/z3c/pt/zpt.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/zpt.py	2008-08-22 22:33:56 UTC (rev 90139)
+++ z3c.pt/trunk/src/z3c/pt/zpt.py	2008-08-23 01:38:29 UTC (rev 90140)
@@ -189,3 +189,22 @@
         config.META_NS: {None: XHTMLElement},
         config.TAL_NS: {None: TALElement},
         config.METAL_NS: {None: METALElement}}
+
+    default_expression = 'python'
+
+    @classmethod
+    def parse(cls, body):
+        root, doctype = super(ZopePageTemplateParser, cls).parse(body)
+
+        # if a default expression is not set explicitly in the
+        # template, use the TAL-attribute ``default-expression``
+        # to set it
+        if utils.get_namespace(root) == config.TAL_NS:
+            tag = 'default-expression'
+        else:
+            tag = utils.tal_attr('default-expression')
+
+        if not root.attrib.get(tag):
+            root.attrib[tag] = cls.default_expression
+
+        return root, doctype



More information about the Checkins mailing list