[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