[Checkins] SVN: z3c.pt/trunk/ Engine rewrite. See docs/HISTORY.txt
for a changelog.
Malthe Borch
mborch at gmail.com
Fri Dec 21 12:02:49 EST 2007
Log message for revision 82387:
Engine rewrite. See docs/HISTORY.txt for a changelog.
Changed:
U z3c.pt/trunk/README.txt
A z3c.pt/trunk/docs/
A z3c.pt/trunk/docs/HISTORY.txt
U z3c.pt/trunk/setup.py
U z3c.pt/trunk/z3c/pt/BENCHMARKS.txt
U z3c.pt/trunk/z3c/pt/README.txt
U z3c.pt/trunk/z3c/pt/VERSION.txt
U z3c.pt/trunk/z3c/pt/__init__.py
A z3c.pt/trunk/z3c/pt/attributes.py
A z3c.pt/trunk/z3c/pt/clauses.py
U z3c.pt/trunk/z3c/pt/codegen.py
A z3c.pt/trunk/z3c/pt/i18n.txt
U z3c.pt/trunk/z3c/pt/io.py
U z3c.pt/trunk/z3c/pt/pagetemplate.py
D z3c.pt/trunk/z3c/pt/tal.py
D z3c.pt/trunk/z3c/pt/tal.txt
U z3c.pt/trunk/z3c/pt/tests/test_doctests.py
A z3c.pt/trunk/z3c/pt/tests/view.pt
U z3c.pt/trunk/z3c/pt/translation.py
A z3c.pt/trunk/z3c/pt/utils.py
-=-
Modified: z3c.pt/trunk/README.txt
===================================================================
--- z3c.pt/trunk/README.txt 2007-12-21 11:56:42 UTC (rev 82386)
+++ z3c.pt/trunk/README.txt 2007-12-21 17:02:48 UTC (rev 82387)
@@ -1,6 +1,6 @@
Overview
--------
-
+
The z3c.pt package provides an alternative implementation of the TAL
template language.
@@ -9,9 +9,9 @@
- Templates are bytecode-compiled
- Only Python-expressions are supported
- Depends only on lxml
+
+The METAL macro language is not supported.
-The METAL macro language is not supported; i18n is on the to-do.
-
Template and expression language
--------------------------------
@@ -38,5 +38,4 @@
can be used instead of ``dictionary['key']``.
-*) http://wiki.zope.org/ZPT/TALSpecification14
-
+*) http://wiki.zope.org/ZPT/TALSpecification14
\ No newline at end of file
Added: z3c.pt/trunk/docs/HISTORY.txt
===================================================================
--- z3c.pt/trunk/docs/HISTORY.txt (rev 0)
+++ z3c.pt/trunk/docs/HISTORY.txt 2007-12-21 17:02:48 UTC (rev 82387)
@@ -0,0 +1,28 @@
+Changelog
+---------
+
+Version 0.3 - December 21, 2007
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+- Added ``ViewPageTemplateFile`` class
+ [malthe]
+
+- Added support for i18n
+ [malthe]
+
+- Engine rewrite; improved code generation abstractions
+ [malthe]
+
+
+Version 0.2 - December 5, 2007
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+- Major optimizations to the generated code
+ [malthe]
+
+
+Version 0.1 - December 3, 2007
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+- First public release
+ [malthe]
Modified: z3c.pt/trunk/setup.py
===================================================================
--- z3c.pt/trunk/setup.py 2007-12-21 11:56:42 UTC (rev 82386)
+++ z3c.pt/trunk/setup.py 2007-12-21 17:02:48 UTC (rev 82387)
@@ -1,12 +1,12 @@
from setuptools import setup, find_packages
import sys, os
-version = '0.2'
+version = '0.3'
setup(name='z3c.pt',
version=version,
description="An implementation of the TAL template language.",
- long_description=open("README.txt").read(),
+ long_description=open("README.txt").read() + open("docs/HISTORY.txt").read(),
classifiers=[
"Programming Language :: Python",
"Topic :: Text Processing :: Markup :: HTML",
@@ -25,6 +25,7 @@
install_requires=[
'setuptools',
'lxml',
+ 'zope.i18n',
# -*- Extra requirements: -*-
],
entry_points="""
Modified: z3c.pt/trunk/z3c/pt/BENCHMARKS.txt
===================================================================
--- z3c.pt/trunk/z3c/pt/BENCHMARKS.txt 2007-12-21 11:56:42 UTC (rev 82386)
+++ z3c.pt/trunk/z3c/pt/BENCHMARKS.txt 2007-12-21 17:02:48 UTC (rev 82387)
@@ -2,8 +2,8 @@
==========
zope.pagetemplate z3c.pt
-Hello World 4.482 1
-1000 x 10 table 4.513 1
+Hello World 2.90 1
+1000 x 10 table 6.108 1
Source
------
@@ -42,7 +42,7 @@
... </tr>
... </table>""")
- >>> for i in range(20): a = template(table=table)
+ >>> # for i in range(40): a = template(table=table)
>>> template = z3PageTemplate()
>>> template.pt_edit("""\
@@ -54,7 +54,7 @@
... </tr>
... </table>""", 'text/xhtml')
- >>> # for i in range(20): a = template(table=table)
+ >>> # for i in range(40): a = template(table=table)
>>> from StringIO import StringIO
>>> def bigtable(table):
Modified: z3c.pt/trunk/z3c/pt/README.txt
===================================================================
--- z3c.pt/trunk/z3c/pt/README.txt 2007-12-21 11:56:42 UTC (rev 82386)
+++ z3c.pt/trunk/z3c/pt/README.txt 2007-12-21 17:02:48 UTC (rev 82387)
@@ -23,12 +23,30 @@
>>> from z3c.pt import PageTemplateFile
>>> from z3c.pt import tests
- >>> filename = tests.__path__[0]+'/helloworld.pt'
-
- >>> template = PageTemplateFile(filename)
+ >>> path = tests.__path__[0]
+ >>> template = PageTemplateFile(path+'/helloworld.pt')
>>> print template()
<div>
Hello World!
</div>
Keyword-parameters are passed on to the template namespace as-is.
+
+From a view:
+
+ >>> from z3c.pt import ViewPageTemplateFile
+ >>> class MockView(object):
+ ... template = ViewPageTemplateFile(path+'/view.pt')
+ ...
+ ... def __init__(self):
+ ... self.request = u'my request'
+ ... self.context = u'my context'
+
+ >>> view = MockView()
+ >>> print view.template(test=u'my test')
+ <div>
+ <span><MockView object at ...</span>
+ <span>my context</span>
+ <span>my request</span>
+ <span>{'test': u'my test'}</span>
+ </div>
Modified: z3c.pt/trunk/z3c/pt/VERSION.txt
===================================================================
--- z3c.pt/trunk/z3c/pt/VERSION.txt 2007-12-21 11:56:42 UTC (rev 82386)
+++ z3c.pt/trunk/z3c/pt/VERSION.txt 2007-12-21 17:02:48 UTC (rev 82387)
@@ -1 +1 @@
-0.2
+0.3
Modified: z3c.pt/trunk/z3c/pt/__init__.py
===================================================================
--- z3c.pt/trunk/z3c/pt/__init__.py 2007-12-21 11:56:42 UTC (rev 82386)
+++ z3c.pt/trunk/z3c/pt/__init__.py 2007-12-21 17:02:48 UTC (rev 82387)
@@ -1,2 +1,3 @@
from pagetemplate import PageTemplate
from pagetemplate import PageTemplateFile
+from pagetemplate import ViewPageTemplateFile
Added: z3c.pt/trunk/z3c/pt/attributes.py
===================================================================
--- z3c.pt/trunk/z3c/pt/attributes.py (rev 0)
+++ z3c.pt/trunk/z3c/pt/attributes.py 2007-12-21 17:02:48 UTC (rev 82387)
@@ -0,0 +1,224 @@
+import parser
+
+def name(string):
+ return string
+
+def expression(string):
+ """
+ Specification:
+
+ expression :: = python_expression [ |* expression ]
+ python_expresion ::= a python expression string
+
+ *) Using | as logical or is not supported.
+
+ >>> expression("4 + 5")
+ ['4 + 5']
+
+ Complex expressions:
+
+ >>> expression("a.non_defined_method() | 1")
+ ['a.non_defined_method() ', '1']
+
+ Expression with non-semantic horizontal bar.
+
+ >>> expression("'|'")
+ ["'|'"]
+
+ Expression with non-semantic horizontal bar and semantic bar.
+
+ >>> expression("a.non_defined_method() | '|'")
+ ['a.non_defined_method() ', "'|'"]
+
+ """
+
+ string = string.replace('\n', '').strip()
+
+ if not string:
+ return []
+
+ expressions = []
+
+ i = j = 0
+ while i < len(string):
+ j = string.find('|', j + 1)
+ if j == -1:
+ j = len(string)
+
+ expr = string[i:j].lstrip()
+
+ try:
+ # we use the ``parser`` module to determine if
+ # an expression is a valid python expression
+ parser.expr(expr)
+ except SyntaxError, e:
+ if j < len(string):
+ continue
+
+ raise e
+
+ expressions.append(expr)
+ i = j + 1
+
+ return expressions
+
+def variable(string):
+ """
+ Specification:
+
+ variables :: = variable_name [, variables]
+
+ Single variable:
+
+ >>> variable("variable")
+ ('variable',)
+
+ Multiple variables:
+
+ >>> variable("variable1, variable2")
+ ('variable1', 'variable2')
+
+ """
+
+ variables = []
+ for var in string.split(', '):
+ var = var.strip()
+
+ if var in ('repeat',):
+ raise ValueError, "Invalid variable name '%s' (reserved)." % variable
+
+ if var.startswith('_'):
+ raise ValueError, \
+ "Invalid variable name '%s' (starts with an underscore)." % variable
+ variables.append(var)
+
+ return tuple(variables)
+
+def mapping(string):
+ """
+
+ >>> mapping("abc def")
+ [('abc', 'def')]
+
+ >>> mapping("abc")
+ [('abc', None)]
+
+ >>> mapping("abc; def ghi")
+ [('abc', None), ('def', 'ghi')]
+
+ """
+
+ defs = string.split(';')
+ mappings = []
+ for d in defs:
+ d = d.strip()
+ while ' ' in d:
+ d = d.replace(' ', ' ')
+
+ parts = d.split(' ')
+ if len(parts) == 1:
+ mappings.append((d, None))
+ elif len(parts) == 2:
+ mappings.append((parts[0], parts[1]))
+ else:
+ raise ValueError, "Invalid mapping (%s)." % string
+
+ return mappings
+
+def definitions(string):
+ """
+ Specification:
+
+ argument ::= define_var [';' define_var]
+ define_var ::= Name python_expression
+
+ Single define:
+
+ >>> definitions("variable expression")
+ [(['variable'], ['expression'])]
+
+ Multiple defines:
+
+ >>> definitions("variable1 expression1; variable2 expression2")
+ [(['variable1'], ['expression1']), (['variable2'], ['expression2'])]
+
+ Tuple define:
+
+ >>> definitions("(variable1, variable2) (expression1, expression2)")
+ [(['variable1', 'variable2'], ['(expression1, expression2)'])]
+
+ Use of unescaped semicolon in an expression:
+
+ >>> definitions("variable ';'")
+ [(['variable'], ["';'"])]
+
+ A define clause that ends in a semicolon:
+
+ >>> definitions("variable expression;")
+ [(['variable'], ['expression'])]
+
+ A define clause with a trivial expression (we do allow this):
+
+ >>> definitions("variable")
+ [(['variable'], None)]
+
+ A proper define clause following one with a trivial expression:
+
+ >>> definitions("variable1 expression; variable2")
+ [(['variable1'], ['expression']), (['variable2'], None)]
+
+ """
+
+ string = string.replace('\n', '').strip()
+
+ defines = []
+
+ i = 0
+ while i < len(string):
+ while string[i] == ' ':
+ i += 1
+
+ # get variable definition
+ if string[i] == '(':
+ j = string.find(')', i+1)
+ if j == -1:
+ raise ValueError, "Invalid variable tuple definition (%s)." % string
+ var = variable(string[i+1:j])
+ j += 1
+ else:
+ j = string.find(' ', i + 1)
+ if j == -1:
+ var = variable(string[i:])
+ j = len(string)
+ else:
+ var = variable(string[i:j])
+
+ # get expression
+ i = j
+ while j < len(string):
+ j = string.find(';', j+1)
+ if j == -1:
+ j = len(string)
+
+ try:
+ expr = expression(string[i:j])
+ except SyntaxError, e:
+ if j < len(string):
+ continue
+ raise e
+ break
+ else:
+ expr = None
+
+ defines.append((list(var), expr))
+
+ i = j + 1
+
+ return defines
+
+def definition(string):
+ defs = definitions(string)
+ if len(defs) != 1:
+ raise ValueError, "Multiple definitions not allowed."
+
+ return defs[0]
Added: z3c.pt/trunk/z3c/pt/clauses.py
===================================================================
--- z3c.pt/trunk/z3c/pt/clauses.py (rev 0)
+++ z3c.pt/trunk/z3c/pt/clauses.py 2007-12-21 17:02:48 UTC (rev 82387)
@@ -0,0 +1,458 @@
+from attributes import expression
+import utils
+
+class Assign(object):
+ """
+ >>> from z3c.pt.io import CodeIO; stream = CodeIO()
+ >>> _scope = []
+
+ Simple assignment:
+
+ >>> assign = Assign(expression("1"))
+ >>> assign.begin(stream, 'a')
+ >>> exec stream.getvalue()
+ >>> a == 1
+ True
+ >>> assign.end(stream)
+
+ Try-except chain:
+
+ >>> assign = Assign(expression("float('abc') | 1"))
+ >>> assign.begin(stream, 'b')
+ >>> exec stream.getvalue()
+ >>> b == 1
+ True
+ >>> assign.end(stream)
+
+ """
+
+ def __init__(self, expressions, variable=None):
+ self.expressions = expressions
+ self.variable = variable
+
+ def begin(self, stream, variable=None):
+ """First n - 1 expressions must be try-except wrapped."""
+
+ variable = variable or self.variable
+
+ for expression in self.expressions[:-1]:
+ stream.write("try:")
+ stream.indent()
+ stream.write("%s = %s" % (variable, expression))
+ stream.outdent()
+ stream.write("except Exception, e:")
+ stream.indent()
+
+ expression = self.expressions[-1]
+ stream.write("%s = %s" % (variable, expression))
+
+ stream.outdent(len(self.expressions)-1)
+
+ def end(self, stream):
+ pass
+
+class Define(object):
+ """
+ >>> from z3c.pt.io import CodeIO; stream = CodeIO()
+ >>> _scope = []
+
+ Variable scope:
+
+ >>> define = Define("a", expression("b"))
+ >>> b = object()
+ >>> define.begin(stream)
+ >>> exec stream.getvalue()
+ >>> a is b
+ True
+ >>> del a
+ >>> _scope.remove('a')
+ >>> define.end(stream)
+ >>> exec stream.getvalue()
+ >>> a
+ Traceback (most recent call last):
+ ...
+ NameError: name 'a' is not defined
+ >>> b is not None
+ True
+
+ Multiple defines:
+
+ >>> stream = CodeIO()
+ >>> define1 = Define("a", expression("b"))
+ >>> define2 = Define("c", expression("d"))
+ >>> d = object()
+ >>> define1.begin(stream)
+ >>> define2.begin(stream)
+ >>> exec stream.getvalue()
+ >>> a is b and c is d
+ True
+ >>> define2.end(stream)
+ >>> define1.end(stream)
+ >>> del a; del c
+ >>> _scope.remove('a'); _scope.remove('c')
+ >>> exec stream.getvalue()
+ >>> a
+ Traceback (most recent call last):
+ ...
+ NameError: name 'a' is not defined
+ >>> c
+ Traceback (most recent call last):
+ ...
+ NameError: name 'c' is not defined
+ >>> b is not None and d is not None
+ True
+
+ Tuple assignments:
+
+ >>> stream = CodeIO()
+ >>> define = Define("(e, f)", expression("[1, 2]"))
+ >>> define.begin(stream)
+ >>> exec stream.getvalue()
+ >>> e == 1 and f == 2
+ True
+ >>> define.end(stream)
+
+ Verify scope is preserved on tuple assignment:
+
+ >>> e = None; f = None
+ >>> _scope.append('e'); _scope.append('f')
+ >>> exec stream.getvalue()
+ >>> e is None and f is None
+ True
+
+ Using semicolons in expressions within a define:
+
+ >>> stream = CodeIO()
+ >>> define = Define("a", expression("';'"))
+ >>> define.begin(stream)
+ >>> exec stream.getvalue()
+ >>> a
+ ';'
+ >>> define.end(stream)
+
+ Scope:
+
+ >>> stream = CodeIO()
+ >>> a = 1
+ >>> _scope.append('a')
+ >>> define = Define("a", expression("2"))
+ >>> define.begin(stream)
+ >>> define.end(stream)
+ >>> exec stream.getvalue()
+ >>> a
+ 1
+
+ """
+ def __init__(self, definition, expression, scope=()):
+ if not isinstance(definition, (list, tuple)):
+ definition = (definition,)
+
+ if len(definition) == 1:
+ variable = definition[0]
+ else:
+ variable = u"(%s,)" % ", ".join(definition)
+
+ self.assign = Assign(expression, variable)
+
+ # only register definitions for variables that have not
+ # been defined in this scope
+ self.definitions = [var for var in definition if var not in scope]
+
+ if scope:
+ scope.extend(self.definitions)
+ if not scope:
+ scope = utils.scope()
+
+ self.scope = scope
+
+ def begin(self, stream):
+ temp = stream.savevariable(self.scope, '{}')
+
+ # save local variables already in in scope
+ for var in self.definitions:
+ stream.write("if '%s' in _scope: %s['%s'] = %s" % (var, temp, var, var))
+ stream.write("else: _scope.append('%s')" % var)
+
+ self.assign.begin(stream)
+
+ def end(self, stream):
+ temp = stream.restorevariable(self.scope)
+
+ self.assign.end(stream)
+
+ for var in reversed(self.definitions):
+ stream.write("if '%s' in %s:" % (var, temp))
+ stream.indent()
+ stream.write("%s = %s['%s']" % (var, temp, var))
+ stream.outdent()
+ stream.write("else:")
+ stream.indent()
+ stream.write("del %s" % var)
+ stream.write("_scope.remove('%s')" % var)
+ stream.outdent()
+
+class Condition(object):
+ """
+ >>> from z3c.pt.io import CodeIO
+
+ Unlimited scope:
+
+ >>> stream = CodeIO()
+ >>> true = Condition(expression("True"))
+ >>> false = Condition(expression("False"))
+ >>> true.begin(stream)
+ >>> stream.write("print 'Hello'")
+ >>> true.end(stream)
+ >>> false.begin(stream)
+ >>> stream.write("print 'Universe!'")
+ >>> false.end(stream)
+ >>> stream.write("print 'World!'")
+ >>> exec stream.getvalue()
+ Hello
+ World!
+
+ Limited scope:
+
+ >>> stream = CodeIO()
+ >>> from StringIO import StringIO
+ >>> _out = StringIO()
+ >>> true = Condition(expression("True"), [Write(expression("'Hello'"))])
+ >>> false = Condition(expression("False"), [Write(expression("'Hallo'"))])
+ >>> true.begin(stream)
+ >>> true.end(stream)
+ >>> false.begin(stream)
+ >>> false.end(stream)
+ >>> exec stream.getvalue()
+ >>> _out.getvalue()
+ 'Hello'
+ """
+
+ def __init__(self, expression, clauses=None):
+ self.assign = Assign(expression)
+ self.clauses = clauses
+
+ def begin(self, stream):
+ temp = stream.save()
+ self.assign.begin(stream, temp)
+ stream.write("if %s:" % temp)
+ stream.indent()
+ if self.clauses:
+ for clause in self.clauses:
+ clause.begin(stream)
+ for clause in reversed(self.clauses):
+ clause.end(stream)
+ stream.outdent()
+
+ def end(self, stream):
+ if not self.clauses:
+ stream.outdent()
+ self.assign.end(stream)
+ stream.restore()
+
+class Else(object):
+ def __init__(self, clauses=None):
+ self.clauses = clauses
+
+ def begin(self, stream):
+ stream.write("else:")
+ stream.indent()
+ if self.clauses:
+ for clause in self.clauses:
+ clause.begin(stream)
+ for clause in reversed(self.clauses):
+ clause.end(stream)
+ stream.outdent()
+
+ def end(self, stream):
+ if not self.clauses:
+ stream.outdent()
+
+class Group(object):
+ def __init__(self, clauses):
+ self.clauses = clauses
+
+ def begin(self, stream):
+ for clause in self.clauses:
+ clause.begin(stream)
+ for clause in reversed(self.clauses):
+ clause.end(stream)
+
+ def end(self, stream):
+ pass
+
+class Tag(object):
+ """
+ >>> from z3c.pt.io import CodeIO; stream = CodeIO()
+ >>> _scope = []
+ >>> from StringIO import StringIO
+
+ >>> _out = StringIO()
+ >>> tag = Tag('div', dict(alt=expression(repr('Hello World!'))))
+ >>> tag.begin(stream)
+ >>> stream.out('Hello Universe!')
+ >>> tag.end(stream)
+ >>> exec stream.getvalue()
+ >>> _out.getvalue()
+ '<div alt="Hello World!">Hello Universe!</div>'
+
+ """
+
+ def __init__(self, tag, attributes):
+ i = tag.find('}')
+ if i != -1:
+ self.tag = tag[i+1:]
+ else:
+ self.tag = tag
+
+ self.attributes = attributes
+
+ def begin(self, stream):
+ stream.out('<%s' % self.tag)
+
+ # attributes
+ for attribute, expression in self.attributes.items():
+ stream.out(' %s="' % attribute)
+ write = Write(expression)
+ write.begin(stream)
+ write.end(stream)
+ stream.out('"')
+
+ stream.out(">")
+
+ def end(self, stream):
+ stream.out('</%s>' % self.tag)
+
+class Repeat(object):
+ """
+ >>> from z3c.pt.io import CodeIO; stream = CodeIO()
+ >>> _scope = []
+
+ We need to set up the repeat object.
+
+ >>> from z3c.pt import utils
+ >>> repeat = utils.repeatdict()
+
+ Simple repeat loop and repeat data structure:
+
+ >>> _repeat = Repeat("i", expression("range(5)"))
+ >>> _repeat.begin(stream)
+ >>> stream.write("r = repeat['i']")
+ >>> stream.write("print (i, r.index, r.start, r.end, r.number(), r.odd(), r.even())")
+ >>> exec stream.getvalue()
+ (0, 0, True, False, 1, False, True)
+ (1, 1, False, False, 2, True, False)
+ (2, 2, False, False, 3, False, True)
+ (3, 3, False, False, 4, True, False)
+ (4, 4, False, True, 5, False, True)
+ >>> _repeat.end(stream)
+
+ """
+
+ def __init__(self, v, e, scope=()):
+ self.variable = v
+ self.define = Define(v, expression("None"), scope)
+ self.assign = Assign(e)
+
+ def begin(self, stream):
+ variable = self.variable
+ iterator = stream.save()
+
+ # assign iterator
+ self.assign.begin(stream, iterator)
+
+ # initialize variable scope
+ self.define.begin(stream)
+
+ # initialize iterator
+ stream.write("repeat['%s'] = %s = %s.__iter__()" % (variable, iterator, iterator))
+
+ # loop
+ stream.write("while %s:" % iterator)
+ stream.indent()
+ stream.write("%s = %s.next()" % (variable, iterator))
+
+ def end(self, stream):
+ # cook before leaving loop
+ stream.cook()
+
+ stream.outdent()
+ self.define.end(stream)
+ self.assign.end(stream)
+ stream.restore()
+
+class Write(object):
+ """
+ >>> from z3c.pt.io import CodeIO; stream = CodeIO()
+ >>> from StringIO import StringIO
+
+ >>> _out = StringIO()
+ >>> write = Write(expression("'New York'"))
+ >>> write.begin(stream)
+ >>> write.end(stream)
+ >>> exec stream.getvalue()
+ >>> _out.getvalue()
+ 'New York'
+ >>> _out = StringIO()
+ >>> write = Write(expression("undefined | ', New York!'"))
+ >>> write.begin(stream)
+ >>> write.end(stream)
+ >>> exec stream.getvalue()
+ >>> _out.getvalue()
+ 'New York, New York!'
+ """
+
+ def __init__(self, expressions):
+ self.assign = Assign(expressions)
+ self.expressions = expressions
+ self.count = len(expressions)
+
+ def begin(self, stream):
+ temp = stream.save()
+
+ if self.count == 1:
+ stream.write("_out.write(%s)" % self.expressions[0])
+ else:
+ self.assign.begin(stream, temp)
+ stream.write("_out.write(%s)" % temp)
+
+ def end(self, stream):
+ if self.count != 1:
+ self.assign.end(stream)
+ stream.restore()
+
+class Out(object):
+ """
+ >>> from z3c.pt.io import CodeIO; stream = CodeIO()
+ >>> from StringIO import StringIO
+ >>> _out = StringIO()
+
+ >>> out = Out('Hello World!')
+ >>> out.begin(stream)
+ >>> out.end(stream)
+ >>> exec stream.getvalue()
+ >>> _out.getvalue()
+ 'Hello World!'
+ """
+
+ def __init__(self, string, defer=False):
+ self.string = string
+ self.defer = defer
+
+ def begin(self, stream):
+ if not self.defer:
+ stream.out(self.string)
+
+ def end(self, stream):
+ if self.defer:
+ stream.out(self.string)
+
+class Translate(object):
+ """
+ The translate clause works retrospectively.
+ """
+
+ def begin(self, stream):
+ raise
+
+ def end(self, stream):
+ raise
Modified: z3c.pt/trunk/z3c/pt/codegen.py
===================================================================
--- z3c.pt/trunk/z3c/pt/codegen.py 2007-12-21 11:56:42 UTC (rev 82386)
+++ z3c.pt/trunk/z3c/pt/codegen.py 2007-12-21 17:02:48 UTC (rev 82387)
@@ -42,7 +42,7 @@
def __init__(self, source):
"""Create the code object from a string."""
-
+
node = parse(source, self.mode)
# build tree
Added: z3c.pt/trunk/z3c/pt/i18n.txt
===================================================================
--- z3c.pt/trunk/z3c/pt/i18n.txt (rev 0)
+++ z3c.pt/trunk/z3c/pt/i18n.txt 2007-12-21 17:02:48 UTC (rev 82387)
@@ -0,0 +1,113 @@
+i18n
+----
+
+This document highlights the i18n-support in the engine. The
+implementation is based on this document:
+
+ * http://wiki.zope.org/zope3/ZPTInternationalizationSupport
+
+With the exception of i18n:data and i18n:source, the implementation is
+complete.
+
+Let's provide German mock translations for all msgids:
+
+ >>> from zope import component
+ >>> from zope import interface
+
+ >>> from zope.i18n.interfaces import ITranslationDomain
+ >>> class MockTranslationDomain(object):
+ ... interface.implements(ITranslationDomain)
+ ...
+ ... def translate(self, msgid, mapping=None, context=None,
+ ... target_language=None, default=None):
+ ... if target_language != 'de':
+ ... return default
+ ...
+ ... return "Mock translation of '%s'." % msgid
+
+ >>> td = MockTranslationDomain()
+ >>> component.provideUtility(td, ITranslationDomain, name="test")
+
+Translation of tag contents
+---------------------------
+
+First, a simple example:
+
+ >>> body = """\
+ ... <div xmlns="http://www.w3.org/1999/xhtml"
+ ... xmlns:i18n="http://xml.zope.org/namespaces/i18n">
+ ... <span i18n:domain="test" i18n:translate="test_msgid">
+ ... Default
+ ... </span>
+ ... </div>"""
+
+First we need to turn the template into a callable:
+
+ >>> from z3c.pt.pagetemplate import PageTemplate
+ >>> template = PageTemplate(body)
+
+Let's try rendering this template without passing a target language.
+
+ >>> print template.render()
+ <div>
+ <span>
+ Default
+ </span>
+ </div>
+
+Now we'll render the template again---passing German as the language.
+
+ >>> print template.render(target_language='de')
+ <div>
+ <span>Mock translation of 'test_msgid'.</span>
+ </div>
+
+Let's try infering the translation message id from the tag body.
+
+ >>> body = """\
+ ... <div xmlns="http://www.w3.org/1999/xhtml"
+ ... xmlns:i18n="http://xml.zope.org/namespaces/i18n">
+ ... <span i18n:domain="test" i18n:translate="">
+ ... Default
+ ... </span>
+ ... </div>"""
+
+Not passing a language:
+
+ >>> template = PageTemplate(body)
+ >>> print template.render()
+ <div>
+ <span>
+ Default
+ </span>
+ </div>
+
+Passing German:
+
+ >>> print template.render(target_language='de')
+ <div>
+ <span>Mock translation of 'Default'.</span>
+ </div>
+
+We could also add a named block inside the tag.
+
+ >>> body = """\
+ ... <div xmlns="http://www.w3.org/1999/xhtml"
+ ... xmlns:i18n="http://xml.zope.org/namespaces/i18n">
+ ... <p i18n:domain="test" i18n:translate="">
+ ... <span i18n:name="count">18</span> bananas.
+ ... </p>
+ ... </div>"""
+
+ >>> template = PageTemplate(body)
+ >>> print template.render()
+ <div>
+ <p>
+ <span>18</span> bananas.
+ </p>
+ </div>
+
+ >>> print template.render(target_language='de')
+ <div>
+ <p>Mock translation of '${count} bananas.'.</p>
+ </div>
Modified: z3c.pt/trunk/z3c/pt/io.py
===================================================================
--- z3c.pt/trunk/z3c/pt/io.py 2007-12-21 11:56:42 UTC (rev 82386)
+++ z3c.pt/trunk/z3c/pt/io.py 2007-12-21 17:02:48 UTC (rev 82387)
@@ -5,49 +5,81 @@
A high-level I/O class to write Python code to a stream.
Indentation is managed using ``indent`` and ``outdent``.
- Convenience methods for keeping track of temporary
- variables.
+ Also:
+ * Convenience methods for keeping track of temporary
+ variables (see ``save``, ``restore`` and ``getvariable``).
+
+ * Methods to process clauses (see ``begin`` and ``end``).
+
"""
- variable_prefix = '_saved'
+ t_prefix = '_tmp'
+ v_prefix = '_var'
def __init__(self, indentation=0, indentation_string="\t"):
StringIO.__init__(self)
self.indentation = indentation
self.indentation_string = indentation_string
- self.counter = 0
self.queue = u''
-
+ self.scope = {}
+
+ self.t_counter = 0
+ self.v_counter = 0
+
def save(self, variable=None):
- self.counter += 1
+ self.t_counter += 1
if variable:
- self.write("%s%d = %s" % (self.variable_prefix, self.counter, variable))
+ self.write("%s%d = %s" % (self.t_prefix, self.t_counter, variable))
else:
- return "%s%d" % (self.variable_prefix, self.counter)
+ return "%s%d" % (self.t_prefix, self.t_counter)
def restore(self, variable=None):
if variable:
- self.write("%s = %s%d" % (variable, self.variable_prefix, self.counter))
+ self.write("%s = %s%d" % (variable, self.t_prefix, self.t_counter))
else:
- return "%s%d" % (self.variable_prefix, self.counter)
+ return "%s%d" % (self.t_prefix, self.t_counter)
- self.counter -= 1
-
+ self.t_counter -= 1
+
+ def savevariable(self, obj, expression="None"):
+ if obj in self.scope:
+ return self.scope[obj]
+
+ self.v_counter += 1
+ variable = "%s%d" % (self.v_prefix, self.v_counter)
+
+ self.write("%s = %s" % (variable, expression))
+
+ self.scope[obj] = variable
+ return variable
+
+ def restorevariable(self, obj, expression="None"):
+ if obj in self.scope:
+ return self.scope[obj]
+
+ variable = "%s%d" % (self.v_prefix, self.v_counter)
+ self.v_counter -= 1
+
+ self.scope[obj] = variable
+ return variable
+
def indent(self, amount=1):
self.indentation += amount
def outdent(self, amount=1):
+ self.cook()
self.indentation -= amount
def out(self, string):
+ self.cook()
self.queue += string
def cook(self):
if self.queue:
queue = self.queue
self.queue = ''
- self.write("_out.write('%s')" % queue)
+ self.write("_out.write('%s')" % queue.replace('\n', '\\n'))
def write(self, string):
self.cook()
@@ -56,3 +88,18 @@
def getvalue(self):
self.cook()
return StringIO.getvalue(self)
+
+ def begin(self, clauses):
+ if isinstance(clauses, (list, tuple)):
+ for clause in clauses:
+ self.begin(clause)
+ else:
+ clauses.begin(self)
+
+ def end(self, clauses):
+ if isinstance(clauses, (list, tuple)):
+ for clause in reversed(clauses):
+ self.end(clause)
+ else:
+ clauses.end(self)
+
Modified: z3c.pt/trunk/z3c/pt/pagetemplate.py
===================================================================
--- z3c.pt/trunk/z3c/pt/pagetemplate.py 2007-12-21 11:56:42 UTC (rev 82386)
+++ z3c.pt/trunk/z3c/pt/pagetemplate.py 2007-12-21 17:02:48 UTC (rev 82387)
@@ -1,36 +1,64 @@
import translation
import codegen
-
+import io
+
class PageTemplate(object):
def __init__(self, body):
self.body = body
- self.render = None
+ self.init()
- def cook(self):
- source, _globals = translation.translate(self.body)
+ def init(self):
+ self.registry = {}
+
+ def cook(self, params):
+ source, _globals = translation.translate(self.body, params)
suite = codegen.Suite(source)
_locals = {}
exec suite.code in _globals, _locals
+
+ return _locals['render']
- self.render = _locals['render']
+ def render(self, **kwargs):
+ signature = hash(",".join(kwargs.keys()))
- @property
- def template(self):
- if self.render is None:
- self.cook()
+ template = self.registry.get(signature)
+ if not template:
+ self.registry[signature] = template = self.cook(kwargs.keys())
- return self.render
-
+ return template(**kwargs)
+
def __call__(self, **kwargs):
- return self.template(**kwargs)
+ return self.render(**kwargs)
class PageTemplateFile(PageTemplate):
def __init__(self, filename):
self.filename = filename
- self.render = None
+ def get_filename(self):
+ return getattr(self, '_filename', None)
+
+ def set_filename(self, filename):
+ self._filename = filename
+ self.init()
+
+ filename = property(get_filename, set_filename)
+
@property
def body(self):
return open(self.filename, 'r').read()
+
+class ViewPageTemplateFile(property):
+ def __init__(self, filename):
+ self.template = PageTemplateFile(filename)
+
+ def render(view):
+ def render(**kwargs):
+ return self.template.render(view=view,
+ context=view.context,
+ request=view.request,
+ options=kwargs)
+ return render
+
+ property.__init__(self, render)
Deleted: z3c.pt/trunk/z3c/pt/tal.py
===================================================================
--- z3c.pt/trunk/z3c/pt/tal.py 2007-12-21 11:56:42 UTC (rev 82386)
+++ z3c.pt/trunk/z3c/pt/tal.py 2007-12-21 17:02:48 UTC (rev 82387)
@@ -1,406 +0,0 @@
-import parser
-import cgi
-import xml.sax.saxutils
-import itertools
-
-def expression(string):
- """
- Python-expressions in try-except construct.
-
- Specification:
-
- expression :: = python_expression [ |* expression ]
- python_expresion ::= a python expression string
-
- *) Using | as logical or is not supported.
-
- """
-
- string = string.replace('\n', '')
-
- expression = []
-
- i = j = 0
- while i < len(string):
- j = string.find('|', j + 1)
- if j == -1:
- j = len(string)
-
- expr = string[i:j].lstrip()
-
- try:
- # we use the ``parser`` module to determine if
- # an expression is a valid python expression
- parser.expr(expr)
- except SyntaxError, e:
- if j < len(string):
- continue
-
- raise e
-
- expression.append(expr)
- i = j + 1
-
- return expression
-
-def variable(string):
- for var in string.split(', '):
- var = var.strip()
-
- if var in ('repeat',):
- raise ValueError, "Invalid variable name '%s' (reserved)." % variable
-
- if var.startswith('_'):
- raise ValueError, \
- "Invalid variable name '%s' (starts with an underscore)." % variable
- yield var
-
-def definition(string):
- """
- TAL define-expression:
-
- Specification:
-
- argument ::= define_var [';' define_var]
- define_var ::= Name python_expression
-
- """
-
- string = string.replace('\n', '').strip()
-
- defines = []
-
- i = 0
- while i < len(string):
- while string[i] == ' ':
- i += 1
-
- # get variable definition
- if string[i] == '(':
- j = string.find(')', i+1)
- if j == -1:
- raise ValueError, "Invalid variable tuple definition (%s)." % string
- var = variable(string[i+1:j])
- j += 1
- else:
- j = string.find(' ', i + 1)
- if j == -1:
- raise ValueError, "Invalid define clause (%s)." % string
- var = variable(string[i:j])
-
- # get expression
- i = j
- while j < len(string):
- j = string.find(';', j+1)
- if j == -1:
- j = len(string)
-
- try:
- expr = expression(string[i:j])
- except SyntaxError, e:
- continue
- break
- else:
- raise e
-
- defines.append((list(var), expr))
-
- i = j + 1
-
- return defines
-
-class repeatitem(object):
- def __init__(self, iterator, length):
- self.length = length
- self.iterator = iterator
-
- @property
- def index(self):
- return self.length - len(self.iterator) - 1
-
- @property
- def start(self):
- return self.index == 0
-
- @property
- def end(self):
- return self.index == self.length - 1
-
- def number(self):
- return self.index + 1
-
- def odd(self):
- return bool(self.index % 2)
-
- def even(self):
- return not self.odd()
-
-class repeatdict(dict):
- def __setitem__(self, key, iterator):
- try:
- length = len(iterator)
- except TypeError:
- length = None
-
- dict.__setitem__(self, key, (iterator, length))
-
- def __getitem__(self, key):
- value, length = dict.__getitem__(self, key)
-
- if not isinstance(value, repeatitem):
- value = repeatitem(value, length)
- self.__setitem__(key, value)
-
- return value
-
-class Assign(object):
- def __init__(self, expression):
- self.expressions = expression
-
- def begin(self, stream, variable):
- """First n - 1 expressions must be try-except wrapped."""
-
- for expression in self.expressions[:-1]:
- stream.write("try:")
- stream.indent()
- stream.write("%s = %s" % (variable, expression))
- stream.outdent()
- stream.write("except Exception, e:")
- stream.indent()
-
- expression = self.expressions[-1]
- stream.write("%s = %s" % (variable, expression))
-
- def end(self, stream):
- stream.outdent(len(self.expressions)-1)
-
-class Define(object):
- def __init__(self, value):
- self.defines = [(v, Assign(e)) for v, e in definition(value)]
- self.variables = list(itertools.chain(*[v for (v, e) in self.defines]))
-
- def update(self, node):
- return node
-
- def begin(self, stream):
- # save local variables already in scope
- save = stream.save()
- stream.write("%s = {}" % save)
-
- for var in self.variables:
- stream.write("if '%s' in _scope: %s['%s'] = %s" % (var, save, var, var))
- stream.write("else: _scope.append('%s')" % var)
-
- for variables, assign in self.defines:
- if len(variables) == 1:
- assign.begin(stream, variables[0])
- else:
- assign.begin(stream, u"(%s,)" % ", ".join(variables))
- assign.end(stream)
-
- def end(self, stream):
- restore = stream.restore()
-
- for variable in reversed(self.variables):
- # restore local variables previously in scope
- stream.write("if '%s' in %s:" % (variable, restore))
- stream.indent()
- stream.write("%s = %s['%s']" % (variable, restore, variable))
- stream.outdent()
- stream.write("else:")
- stream.indent()
- stream.write("del %s" % variable)
- stream.write("_scope.remove('%s')" % variable)
- stream.outdent()
-
-class Condition(object):
- def __init__(self, value):
- self.assign = Assign(expression(value))
-
- def update(self, node):
- return node
-
- def begin(self, stream):
- variable = '_condition'
-
- self.assign.begin(stream, variable)
- stream.write("if %s:" % variable)
- stream.indent()
-
- def end(self, stream):
- self.assign.end(stream)
- stream.outdent()
-
-class Repeat(object):
- def __init__(self, value):
- string = value.lstrip().replace('\n', '')
-
- space = string.find(' ')
- if space == -1:
- raise ValueError, "Invalid repeat clause (%s)." % value
-
- self.variable = string[:space]
- self.assign = Assign(expression(string[space:]))
-
- def update(self, node):
- return node
-
- def begin(self, stream):
- variable = self.variable
- iterator = stream.save()
-
- self.assign.begin(stream, iterator)
-
- # define variable scope
- self.define = Define("%s None" % self.variable)
- self.define.begin(stream)
-
- # initialize iterator
- stream.write("repeat['%s'] = %s = %s.__iter__()" % (variable, iterator, iterator))
-
- # loop
- stream.write("while %s:" % iterator)
- stream.indent()
- stream.write("%s = %s.next()" % (variable, iterator))
-
- def end(self, stream):
- # cook before leaving loop
- stream.cook()
-
- stream.outdent()
- self.define.end(stream)
- self.assign.end(stream)
- stream.restore()
-
-class Attribute(object):
- def __init__(self, value):
- self.attributes = [(v, Assign(e)) for v, e in definition(value)]
-
- def update(self, node):
- for variables, expression in self.attributes:
- for variable in variables:
- if variable in node.attrib:
- del node.attrib[variable]
-
- return node
-
- def begin(self, stream):
- stream.write("_attrs = {}")
- for variables, assign in self.attributes:
- for variable in variables:
- assign.begin(stream, "_attrs['%s']" % variable)
- assign.end(stream)
-
- def end(self, stream):
- pass
-
-class Content(object):
- def __init__(self, value):
- self.assign = Assign(expression(value))
-
- def update(self, node):
- node.text = ''
- for element in node.getchildren():
- node.remove(element)
-
- return node
-
- def begin(self, stream):
- self.assign.begin(stream, '_content')
- stream.write("_out.write(_content)")
-
- def end(self, stream):
- self.assign.end(stream)
-
-class Replace(Content):
- def update(self, node):
- return None
-
-class Tag(object):
- def __init__(self, node):
- self.node = node
- self.tail = node.tail
-
- @property
- def tag(self):
- tag = self.node.tag
- if tag.startswith('{'):
- return tag[tag.find('}')+1:]
-
- return tag
-
- def update(self, node):
- self.node = node
- return node
-
- def begin(self, stream):
- if self.node is None:
- return
- stream.out('<%s' % self.tag)
- stream.write("for _name, _value in _attrs.items():")
- stream.indent()
- stream.write("""_out.write(' %s=\"%s\"' % (_name, _escape(_value, '\"')))""")
- stream.outdent()
- stream.write("_attrs.clear()")
- for name, value in self.node.attrib.items():
- stream.out(' %s=%s' % (name, xml.sax.saxutils.quoteattr(value)))
-
- if self.node.text is None:
- stream.out(' />')
- else:
- stream.out('>')
-
- text = self.node.text
- if text is not None:
- text = cgi.escape(text.replace('\n', '\\n'), '"')
- stream.out(text)
-
- def end(self, stream):
- if self.node is not None and self.node.text is not None:
- stream.out('</%s>' % self.tag)
-
- if self.tail is not None:
- tail = cgi.escape(self.tail.replace('\n', '\\n'), "'")
- stream.out(tail)
-
-def handler(key=None):
- def decorate(f):
- def g(node):
- if key is None:
- return f(node, None)
- return f(node, node.get(key))
- g.__ns__ = key
- return g
- return decorate
-
- at handler("{http://xml.zope.org/namespaces/tal}define")
-def define(node, value):
- return Define(value)
-
- at handler("{http://xml.zope.org/namespaces/tal}condition")
-def condition(node, value):
- return Condition(value)
-
- at handler("{http://xml.zope.org/namespaces/tal}repeat")
-def repeat(node, value):
- return Repeat(value)
-
- at handler("{http://xml.zope.org/namespaces/tal}attributes")
-def attribute(node, value):
- return Attribute(value)
-
- at handler("{http://xml.zope.org/namespaces/tal}content")
-def content(node, value):
- return Content(value)
-
- at handler("{http://xml.zope.org/namespaces/tal}replace")
-def replace(node, value):
- return Replace(value)
-
- at handler("{http://xml.zope.org/namespaces/tal}omit-tag")
-def omit(node, value):
- raise NotImplemented, "The tal:omit-tag statement is not supported yet."
-
- at handler()
-def tag(node, value):
- return Tag(node)
Deleted: z3c.pt/trunk/z3c/pt/tal.txt
===================================================================
--- z3c.pt/trunk/z3c/pt/tal.txt 2007-12-21 11:56:42 UTC (rev 82386)
+++ z3c.pt/trunk/z3c/pt/tal.txt 2007-12-21 17:02:48 UTC (rev 82387)
@@ -1,267 +0,0 @@
-TAL
-===
-
-Tests for the TAL language implementation.
-
- >>> from z3c.pt import tal
-
-expression
-----------
-
-Simple expression:
-
- >>> tal.expression("4 + 5")
- ['4 + 5']
-
-Complex expressions:
-
- >>> tal.expression("a.non_defined_method() | 1")
- ['a.non_defined_method() ', '1']
-
-Expression with non-semantic horizontal bar.
-
- >>> tal.expression("'|'")
- ["'|'"]
-
-Expression with non-semantic horizontal bar and semantic bar.
-
- >>> tal.expression("a.non_defined_method() | '|'")
- ['a.non_defined_method() ', "'|'"]
-
-Assign
-------
-
-Simple assignment.
-
- >>> from z3c.pt.io import CodeIO
- >>> stream = CodeIO()
- >>> _scope = []
-
- >>> assign = tal.Assign(tal.expression("1"))
- >>> assign.begin(stream, 'a')
- >>> exec stream.getvalue()
- >>> a == 1
- True
- >>> del a
- >>> assign.end(stream)
- >>> exec stream.getvalue()
-
-Define
-------
-
-Variable scope:
-
- >>> stream = CodeIO()
- >>> define = tal.Define("a b")
- >>> b = object()
- >>> define.begin(stream)
- >>> exec stream.getvalue()
- >>> a is b
- True
- >>> del a
- >>> _scope.remove('a')
- >>> define.end(stream)
- >>> exec stream.getvalue()
- >>> a
- Traceback (most recent call last):
- ...
- NameError: name 'a' is not defined
- >>> b is not None
- True
-
-Multiple defines:
-
- >>> stream = CodeIO()
- >>> define = tal.Define("a b; c d")
- >>> d = object()
- >>> define.begin(stream)
- >>> exec stream.getvalue()
- >>> a is b and c is d
- True
- >>> define.end(stream)
- >>> del a; del c
- >>> _scope.remove('a'); _scope.remove('c')
- >>> exec stream.getvalue()
- >>> a
- Traceback (most recent call last):
- ...
- NameError: name 'a' is not defined
- >>> c
- Traceback (most recent call last):
- ...
- NameError: name 'c' is not defined
- >>> b is not None and d is not None
- True
-
-Tuple assignments:
-
- >>> stream = CodeIO()
- >>> define = tal.Define("(e, f) [1, 2]")
- >>> define.begin(stream)
- >>> exec stream.getvalue()
- >>> e == 1 and f == 2
- True
- >>> define.end(stream)
-
-Verify scope is preserved on tuple assignment:
-
- >>> e = None; f = None
- >>> _scope.append('e'); _scope.append('f')
- >>> exec stream.getvalue()
- >>> e is None and f is None
- True
-
-Using semicolons in expressions within a define:
-
- >>> stream = CodeIO()
- >>> define = tal.Define("a ';'")
- >>> define.begin(stream)
- >>> exec stream.getvalue()
- >>> a
- ';'
- >>> define.end(stream)
-
-Ending an define clause with a semicolon.
-
- >>> stream = CodeIO()
- >>> define = tal.Define("a 4 + 5;")
- >>> define.begin(stream)
- >>> exec stream.getvalue()
- >>> a
- 9
- >>> define.end(stream)
- >>> del a
- >>> _scope.remove('a')
- >>> exec stream.getvalue()
-
-Scope:
-
- >>> stream = CodeIO()
- >>> a = 1
- >>> _scope.append('a')
- >>> define = tal.Define("a 2")
- >>> define.begin(stream)
- >>> define.end(stream)
- >>> exec stream.getvalue()
- >>> a
- 1
-
-Conditions
-----------
-
- >>> stream = CodeIO()
-
-True:
-
- >>> a = 0
- >>> condition = tal.Condition("True")
- >>> define = tal.Define("a 1")
- >>> condition.begin(stream)
- >>> define.begin(stream)
- >>> exec stream.getvalue()
- >>> a
- 1
- >>> define.end(stream)
- >>> condition.end(stream)
-
-False:
-
- >>> a = 0
- >>> condition = tal.Condition("False")
- >>> define = tal.Define("a 1")
- >>> condition.begin(stream)
- >>> define.begin(stream)
- >>> exec stream.getvalue()
- >>> a
- 0
- >>> define.end(stream)
- >>> condition.end(stream)
-
-Repeat
-------
-
- >>> from z3c.pt.tal import repeatdict as _repeat
- >>> repeat = _repeat()
-
- >>> stream = CodeIO()
-
- >>> _repeat = tal.Repeat("i range(5)")
- >>> _repeat.begin(stream)
- >>> stream.write("r = repeat['i']")
- >>> stream.write("print (i, r.index, r.start, r.end, r.number(), r.odd(), r.even())")
- >>> exec stream.getvalue()
- (0, 0, True, False, 1, False, True)
- (1, 1, False, False, 2, True, False)
- (2, 2, False, False, 3, False, True)
- (3, 3, False, False, 4, True, False)
- (4, 4, False, True, 5, False, True)
- >>> _repeat.end(stream)
-
-Attribute
----------
-
- >>> from z3c.pt.tal import Attribute
- >>> stream = CodeIO()
-
- >>> attribute = Attribute("class 'plain'")
- >>> attribute.begin(stream)
- >>> attribute.end(stream)
- >>> exec stream.getvalue()
- >>> _attrs
- {'class': 'plain'}
- >>> del _attrs
-
-Attributes that are bound to expressions will be removed on ``update``:
-
- >>> from lxml.etree import Element
- >>> img = Element('img')
- >>> img.set('src', u'logo.gif')
- >>> img.set('class', u'stx')
- >>> img = attribute.update(img)
- >>> img.attrib
- {'src': 'logo.gif'}
-
-Content
--------
-
- >>> from z3c.pt.tal import Content
- >>> stream = CodeIO()
-
- >>> from StringIO import StringIO
- >>> _out = StringIO()
-
- >>> content = tal.Content(u"'Hello World!'")
- >>> content.begin(stream)
- >>> exec stream.getvalue()
- >>> _out.getvalue()
- 'Hello World!'
- >>> content.end(stream)
-
-Tag
----
-
-Define required global symbols.
-
- >>> from cgi import escape as _escape
- >>> _attrs = {}
-
- >>> stream = CodeIO()
- >>> br = Element('br')
- >>> tag = tal.Tag(br)
- >>> tag.begin(stream)
- >>> tag.end(stream)
- >>> _out = StringIO()
- >>> exec stream.getvalue()
- >>> _out.getvalue()
- '<br />'
-
- >>> stream = CodeIO()
- >>> div = Element('div')
- >>> div.text = ''
- >>> tag = tal.Tag(div)
- >>> tag.begin(stream)
- >>> tag.end(stream)
- >>> _out = StringIO()
- >>> exec stream.getvalue()
- >>> _out.getvalue()
- '<div></div>'
Modified: z3c.pt/trunk/z3c/pt/tests/test_doctests.py
===================================================================
--- z3c.pt/trunk/z3c/pt/tests/test_doctests.py 2007-12-21 11:56:42 UTC (rev 82386)
+++ z3c.pt/trunk/z3c/pt/tests/test_doctests.py 2007-12-21 17:02:48 UTC (rev 82387)
@@ -1,21 +1,24 @@
import zope.testing
import unittest
-OPTIONFLAGS = (zope.testing.doctest.REPORT_ONLY_FIRST_FAILURE |
+OPTIONFLAGS = (#zope.testing.doctest.REPORT_ONLY_FIRST_FAILURE |
zope.testing.doctest.ELLIPSIS |
zope.testing.doctest.NORMALIZE_WHITESPACE)
import zope.component.testing
def test_suite():
- filesuites = ['README.txt', 'BENCHMARKS.txt', 'tal.txt', 'translation.txt', 'codegen.txt']
-
+ filesuites = ['README.txt', 'BENCHMARKS.txt', 'translation.txt', 'i18n.txt', 'codegen.txt']
+ testsuites = ['z3c.pt.translation', 'z3c.pt.attributes', 'z3c.pt.clauses']
+
return unittest.TestSuite(
+ [zope.testing.doctest.DocTestSuite(doctest,
+ optionflags=OPTIONFLAGS) for doctest in testsuites] +
[zope.testing.doctest.DocFileSuite(doctest,
- optionflags=OPTIONFLAGS,
- setUp=zope.component.testing.setUp,
- tearDown=zope.component.testing.tearDown,
- package="z3c.pt") for doctest in filesuites]
+ optionflags=OPTIONFLAGS,
+ setUp=zope.component.testing.setUp,
+ tearDown=zope.component.testing.tearDown,
+ package="z3c.pt") for doctest in filesuites]
)
if __name__ == '__main__':
Added: z3c.pt/trunk/z3c/pt/tests/view.pt
===================================================================
--- z3c.pt/trunk/z3c/pt/tests/view.pt (rev 0)
+++ z3c.pt/trunk/z3c/pt/tests/view.pt 2007-12-21 17:02:48 UTC (rev 82387)
@@ -0,0 +1,7 @@
+<div xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:tal="http://xml.zope.org/namespaces/tal">
+ <span tal:content="view" />
+ <span tal:content="context" />
+ <span tal:content="request" />
+ <span tal:content="options" />
+</div>
Modified: z3c.pt/trunk/z3c/pt/translation.py
===================================================================
--- z3c.pt/trunk/z3c/pt/translation.py 2007-12-21 11:56:42 UTC (rev 82386)
+++ z3c.pt/trunk/z3c/pt/translation.py 2007-12-21 17:02:48 UTC (rev 82387)
@@ -1,76 +1,288 @@
from StringIO import StringIO
import lxml.etree
-import cgi
-import tal
import io
+import utils
+import attributes as attrs
+import clauses
-ns_lookup_table = [(f.__ns__, f) for f in \
- (tal.define,
- tal.condition,
- tal.omit,
- tal.repeat,
- tal.attribute,
- tal.replace,
- tal.tag,
- tal.content)]
+wrapper = """\
+def render(%starget_language=None):
+\tglobal utils
-wrapper = """\
-def render(**_kwargs):
-\trepeat = _repeatdict()
-\t_attrs = {}
-\t_scope = _kwargs.keys()
-\t_out = _StringIO()
-\t_globals = globals()
-\tfor _variable, _value in _kwargs.items():
-\t\t_globals[_variable] = _value
+\t_out = utils.initialize_stream()
+\t_scope = locals().keys()
+
+\t(_attributes, repeat) = utils.initialize_tal()
+\t(_domain, _translate) = utils.initialize_i18n()
+\t(_escape, _marker) = utils.initialize_helpers()
+
+\t_target_language = target_language
%s
\treturn _out.getvalue()
"""
-def translate(body):
- tree = lxml.etree.parse(StringIO(body))
- root = tree.getroot()
+def attribute(ns, factory):
+ def get(self):
+ value = self.attrib.get(ns)
+ if value is not None:
+ return factory(value)
+
+ return property(get)
- stream = io.CodeIO(indentation=1, indentation_string="\t")
- visit(root, stream)
+class Element(lxml.etree.ElementBase):
+ def begin(self, stream):
+ stream.begin(self.clauses())
+
+ def end(self, stream):
+ stream.end(self.clauses())
- source = wrapper % stream.getvalue()
+ def body(self, stream):
+ skip = self.replace or self.content or self.i18n_translate is not None
+ if not skip:
+ for element in self:
+ element.visit(stream)
+
+ def visit(self, stream):
+ self.begin(stream)
+ self.body(stream)
+ self.end(stream)
- _globals = dict(_StringIO=StringIO,
- _repeatdict=tal.repeatdict,
- _escape=cgi.escape)
+ def clauses(self):
+ _ = []
- return source, _globals
+ scope = utils.scope()
-def visit(node, stream):
- """Translates a node and outputs to a code stream."""
+ # i18n domain
+ if self.i18n_domain is not None:
+ _.append(clauses.Define("_domain", [repr(self.i18n_domain)], scope))
+
+ # defines
+ if self.define is not None:
+ for variables, expression in self.define:
+ _.append(clauses.Define(variables, expression, scope))
+
+ # condition
+ if self.condition is not None:
+ _.append(clauses.Condition(self.condition))
+
+ # repeat
+ if self.repeat is not None:
+ variables, expression = self.repeat
+ if len(variables) != 1:
+ raise ValueError, "Cannot unpack more than one variable in a repeat statement."
+ _.append(clauses.Repeat(variables[0], expression, scope))
+
+ # tag tail (deferred)
+ if self.tail:
+ _.append(clauses.Out(self.tail, defer=True))
+
+ # tag
+ if self.replace is None:
+ tag = clauses.Tag(self.tag, self._attributes())
+
+ if self.omit is not None:
+ _.append(clauses.Condition(_not(self.omit), [tag]))
+ else:
+ _.append(tag)
+
+ # tag text (if we're not replacing tag body)
+ if self.text and not (self.replace or self.content) and self.i18n_translate is None:
+ _.append(clauses.Out(self.text))
+
+ # dynamic content and content translation
+ replace = self.replace
+ content = self.content
+
+ if replace and content:
+ raise ValueError, "Can't use replace clause together with content clause."
+
+ expression = replace or content
+ if expression:
+ if self.i18n_translate is not None:
+ if self.i18n_translate != "":
+ raise ValueError, "Can't use message id with dynamic content translation."
+ _.append(clauses.Translate())
+
+ # TODO: structure
+ _.append(clauses.Write(expression))
+ else:
+ if self.i18n_translate is not None:
+ msgid = self.i18n_translate
+ if not msgid:
+ msgid = self._msgid()
+
+ # for each named block, create a new output stream
+ # and use the value in the translation mapping dict
+
+ elements = [e for e in self if e.i18n_name]
+
+ if elements:
+ mapping = '_mapping'
+ _.append(clauses.Assign(['{}'], mapping))
+ else:
+ mapping = 'None'
+
+ for element in elements:
+ name = element.i18n_name
+
+ subclauses = []
+ subclauses.append(clauses.Define('_out', ['utils.initialize_stream()'], scope))
+ subclauses.append(clauses.Group(element.clauses()))
+ subclauses.append(clauses.Assign(['_out.getvalue()'],
+ "%s['%s']" % (mapping, name)))
+
+ _.append(clauses.Group(subclauses))
+
+ _.append(clauses.Assign(
+ _translate([repr(msgid)], mapping=mapping, default='_marker'),
+ '_result'))
+
+ # write translation to output if successful, otherwise
+ # fallback to default rendition;
+
+ _.append(clauses.Condition(['_result is not _marker'],
+ [clauses.Write(['_result'])]))
+
+ subclauses = []
+ if self.text:
+ subclauses.append(clauses.Out(self.text))
+ for element in self:
+ name = element.i18n_name
+ if name:
+ subclauses.append(clauses.Write(["%s['%s']" % (mapping, name)]))
+ #subclauses.append(clauses.Out(element.tail))
+ else:
+ subclauses.append(clauses.Out(lxml.etree.tostring(element)))
+
+ if subclauses:
+ _.append(clauses.Else(subclauses))
+
+ return _
- keys = node.keys() + [None]
+ def _msgid(self):
+ """Create an i18n msgid from the tag contents."""
+
+ out = StringIO(self.text)
+ for element in self:
+ name = element.i18n_name
+ if name:
+ out.write("${%s}" % name)
+ out.write(element.tail)
+ else:
+ out.write(lxml.etree.tostring(element))
+
+ msgid = out.getvalue().strip()
+ msgid = msgid.replace(' ', ' ').replace('\n', '')
+
+ return msgid
+
+ def _attributes(self):
+ """Aggregate static, dynamic and translatable attributes."""
+
+ attributes = {}
+
+ # static attributes are at the bottom of the food chain
+ static = [key for key in self.keys() if not key.startswith('{')]
+ for key in static:
+ attributes[key] = self.attrib[key]
+
+ # dynamic attributes
+ attrs = self.attributes
+ if attrs is not None:
+ for variables, expression in attrs:
+ if len(variables) != 1:
+ raise ValueError, "Tuple definitions in assignment clause is not supported."
+
+ variable = variables[0]
+ attributes[variable] = _escape(expression, '"')
+ else:
+ attrs = []
+
+ dynamic = [key for (key, expression) in attrs]
+
+ # translated attributes
+ if self.i18n_attributes:
+ for variable, msgid in self.i18n_attributes:
+ if variable in static:
+ static_expression = repr(attributes[variable])
+
+ if msgid:
+ if variable in dynamic:
+ raise ValueError, "Message id not allowed in conjunction with " + \
+ "a dynamic attribute."
+
+ expression = [repr(msgid)]
+
+ if variable in static:
+ expression = _translate(expression, default=static_expression)
+ else:
+ expression = _translate(expression)
+ else:
+ if variable in dynamic:
+ expression = _translate(attributes[variable])
+ elif variable in static:
+ expression = _translate(static_expression)
+ else:
+ raise ValueError, "Must be either static or dynamic attribute " + \
+ "when no message id is supplied."
+
+ attributes[variable] = expression
+
+ return attributes
+
+ define = attribute(
+ "{http://xml.zope.org/namespaces/tal}define", attrs.definitions)
+ condition = attribute(
+ "{http://xml.zope.org/namespaces/tal}condition", attrs.expression)
+ repeat = attribute(
+ "{http://xml.zope.org/namespaces/tal}repeat", attrs.definition)
+ attributes = attribute(
+ "{http://xml.zope.org/namespaces/tal}attributes", attrs.definitions)
+ content = attribute(
+ "{http://xml.zope.org/namespaces/tal}content", attrs.expression)
+ replace = attribute(
+ "{http://xml.zope.org/namespaces/tal}replace", attrs.expression)
+ omit = attribute(
+ "{http://xml.zope.org/namespaces/tal}omit-tag", attrs.expression)
+ i18n_translate = attribute(
+ "{http://xml.zope.org/namespaces/i18n}translate", attrs.name)
+ i18n_attributes = attribute(
+ "{http://xml.zope.org/namespaces/i18n}attrs", attrs.mapping)
+ i18n_domain = attribute(
+ "{http://xml.zope.org/namespaces/i18n}domain", attrs.name)
+ i18n_name = attribute(
+ "{http://xml.zope.org/namespaces/i18n}name", attrs.name)
- handlers = [handler(node) for key, handler in ns_lookup_table \
- if key in keys]
+# set up namespace
+lookup = lxml.etree.ElementNamespaceClassLookup()
+parser = lxml.etree.XMLParser()
+parser.setElementClassLookup(lookup)
- # remove namespace attributes
- for key, handler in ns_lookup_table:
- if key is not None and key in node.attrib:
- del node.attrib[key]
+namespace = lxml.etree.Namespace('http://www.w3.org/1999/xhtml')
+namespace[None] = Element
- # update node
- for handler in handlers:
- node = handler.update(node)
+def translate(body, params=[]):
+ tree = lxml.etree.parse(StringIO(body), parser)
+ root = tree.getroot()
- # begin tag
- for handler in handlers:
- handler.begin(stream)
+ stream = io.CodeIO(indentation=1, indentation_string="\t")
- # process children
- if node:
- for element in node:
- visit(element, stream)
+ root.visit(stream)
- # end tag
- for handler in reversed(handlers):
- handler.end(stream)
+ code = stream.getvalue()
+ args = ', '.join(params)
+ if args: args += ', '
+
+ return wrapper % (args, code), {'utils': utils}
- return stream
+def _translate(expressions, mapping=None, default=None):
+ return [("_translate(%s, domain=_domain, mapping=%s, " + \
+ "target_language=_target_language, default=%s)") %
+ (exp, mapping, default) for exp in expressions]
+
+def _escape(expressions, delim):
+ return ["_escape(%s, '\\%s')" % (exp, delim) for exp in expressions]
+
+def _not(expressions):
+ return ["not (%s)" % exp for exp in expressions]
Added: z3c.pt/trunk/z3c/pt/utils.py
===================================================================
--- z3c.pt/trunk/z3c/pt/utils.py (rev 0)
+++ z3c.pt/trunk/z3c/pt/utils.py 2007-12-21 17:02:48 UTC (rev 82387)
@@ -0,0 +1,84 @@
+import zope.i18n
+
+import cgi
+from StringIO import StringIO
+
+def handler(key=None):
+ def decorate(f):
+ def g(node):
+ if key is None:
+ return f(node, None)
+ return f(node, node.get(key))
+ g.__ns__ = key
+ return g
+ return decorate
+
+def initialize_i18n():
+ return (None, zope.i18n.translate)
+
+def initialize_tal():
+ return ({}, repeatdict())
+
+def initialize_helpers():
+ return (cgi.escape, object())
+
+def initialize_stream():
+ return StringIO()
+
+def getLanguage(request):
+ return ''
+
+s_counter = 0
+
+class scope(list):
+ def __init__(self, *args):
+ global s_counter
+ self.hash = s_counter
+ s_counter += 1
+
+ def __hash__(self):
+ return self.hash
+
+class repeatitem(object):
+ def __init__(self, iterator, length):
+ self.length = length
+ self.iterator = iterator
+
+ @property
+ def index(self):
+ return self.length - len(self.iterator) - 1
+
+ @property
+ def start(self):
+ return self.index == 0
+
+ @property
+ def end(self):
+ return self.index == self.length - 1
+
+ def number(self):
+ return self.index + 1
+
+ def odd(self):
+ return bool(self.index % 2)
+
+ def even(self):
+ return not self.odd()
+
+class repeatdict(dict):
+ def __setitem__(self, key, iterator):
+ try:
+ length = len(iterator)
+ except TypeError:
+ length = None
+
+ dict.__setitem__(self, key, (iterator, length))
+
+ def __getitem__(self, key):
+ value, length = dict.__getitem__(self, key)
+
+ if not isinstance(value, repeatitem):
+ value = repeatitem(value, length)
+ self.__setitem__(key, value)
+
+ return value
More information about the Checkins
mailing list