[Checkins] SVN: z3c.pt/trunk/ Added support for global defines.

Malthe Borch mborch at gmail.com
Mon Aug 18 17:46:30 EDT 2008


Log message for revision 89982:
  Added support for global defines.

Changed:
  U   z3c.pt/trunk/CHANGES.txt
  U   z3c.pt/trunk/src/z3c/pt/clauses.py
  U   z3c.pt/trunk/src/z3c/pt/codegen.py
  U   z3c.pt/trunk/src/z3c/pt/codegen.txt
  U   z3c.pt/trunk/src/z3c/pt/expressions.py
  U   z3c.pt/trunk/src/z3c/pt/generation.py
  U   z3c.pt/trunk/src/z3c/pt/genshi.txt
  U   z3c.pt/trunk/src/z3c/pt/template.py
  U   z3c.pt/trunk/src/z3c/pt/template.txt
  U   z3c.pt/trunk/src/z3c/pt/translation.py
  U   z3c.pt/trunk/src/z3c/pt/types.py

-=-
Modified: z3c.pt/trunk/CHANGES.txt
===================================================================
--- z3c.pt/trunk/CHANGES.txt	2008-08-18 21:44:21 UTC (rev 89981)
+++ z3c.pt/trunk/CHANGES.txt	2008-08-18 21:46:29 UTC (rev 89982)
@@ -4,6 +4,10 @@
 Version 1.0dev
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
+- Implemented TAL global defines. [malthe]
+
+- Added support for variables with global scope. [malthe]
+
 - Curly braces may now be omitted in an expression interpolation if
   the expression is just a variable name; this complies with the
   Genshi syntax. [malthe]

Modified: z3c.pt/trunk/src/z3c/pt/clauses.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/clauses.py	2008-08-18 21:44:21 UTC (rev 89981)
+++ z3c.pt/trunk/src/z3c/pt/clauses.py	2008-08-18 21:46:29 UTC (rev 89982)
@@ -200,7 +200,7 @@
     Tuple assignments:
 
       >>> stream = CodeIO()
-      >>> define = Define(['e', 'f'], pyexp("[1, 2]"))
+      >>> define = Define(types.declaration(('e', 'f')), pyexp("[1, 2]"))
       >>> define.begin(stream)
       >>> exec stream.getvalue()
       >>> e == 1 and f == 2
@@ -243,56 +243,68 @@
       1
     
     """
-    def __init__(self, definition, expression):
-        if not isinstance(definition, (list, tuple)):
-            definition = (definition,)
+    def __init__(self, declaration, expression, dictionary=None):
+        if not isinstance(declaration, types.declaration):
+            declaration = types.declaration((declaration,))
 
-        if len(definition) == 1:
-            variable = definition[0]
+        if len(declaration) == 1:
+            variable = declaration[0]
         else:
-            variable = u"(%s,)" % ", ".join(definition)
+            variable = u"(%s,)" % ", ".join(declaration)
 
+        if dictionary is not None:
+           variable = "%s['%s'] = %s" % (dictionary, variable, variable)
+            
         self.assign = Assign(expression, variable)        
-        self.definitions = definition
+        self.declaration = declaration
+        self.dictionary = dictionary
         
     def begin(self, stream):
-        # save local variables already in in scope
-        for var in self.definitions:
-            temp = stream.save()
+        if self.declaration.global_scope:
+            # if the declaration belongs to a global scope, remove this
+            # symbol from previous scopes
+            for scope in stream.scope:
+                for variable in self.declaration:
+                    if variable in scope:
+                        scope.remove(variable)
+        else:
+            # save local variables already in in scope
+            for var in self.declaration:
+                temp = stream.save()
 
-            # If we didn't set the variable in this scope already
-            if var not in stream.scope[-1]:
+                # If we didn't set the variable in this scope already
+                if var not in stream.scope[-1]:
 
-                # we'll check if it's set in one of the older scopes
-                for scope in stream.scope[:-1]:
-                    if var in scope:
-                        # in which case we back it up
-                        stream.write('%s = %s' % (temp, var))
+                    # we'll check if it's set in one of the older scopes
+                    for scope in stream.scope[:-1]:
+                        if var in scope:
+                            # in which case we back it up
+                            stream.write('%s = %s' % (temp, var))
 
-                stream.scope[-1].add(var)
+                    stream.scope[-1].add(var)
                    
         self.assign.begin(stream)
 
     def end(self, stream):
         self.assign.end(stream)
 
-        # back come the variables that were already in scope in the
-        # first place
-        for var in reversed(self.definitions):
-            temp = stream.restore()
+        if not self.declaration.global_scope:
+            # restore the variables that were previously in scope
+            for var in reversed(self.declaration):
+                temp = stream.restore()
 
-            # If we set the variable in this scope already
-            if var in stream.scope[-1]:
+                # If we set the variable in this scope already
+                if var in stream.scope[-1]:
 
-                # we'll check if it's set in one of the older scopes
-                for scope in stream.scope[:-1]:
-                    if var in scope:
-                        # in which case we restore it
-                        stream.write('%s = %s' % (var, temp))
-                        stream.scope[-1].remove(var)
-                        break
-                else:
-                    stream.write("del %s" % var)
+                    # we'll check if it's set in one of the older scopes
+                    for scope in stream.scope[:-1]:
+                        if var in scope:
+                            # in which case we restore it
+                            stream.write('%s = %s' % (var, temp))
+                            stream.scope[-1].remove(var)
+                            break
+                    else:
+                        stream.write("del %s" % var)
 
 class Condition(object):
     """

Modified: z3c.pt/trunk/src/z3c/pt/codegen.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/codegen.py	2008-08-18 21:44:21 UTC (rev 89981)
+++ z3c.pt/trunk/src/z3c/pt/codegen.py	2008-08-18 21:46:29 UTC (rev 89982)
@@ -3,11 +3,22 @@
 
 from transformer import ASTTransformer
 
+import __builtin__
 
 CONSTANTS = frozenset(['False', 'True', 'None', 'NotImplemented', 'Ellipsis'])
 UNDEFINED = object()
 
+def flatten(items):
+    """Flattens a potentially nested sequence into a flat list."""
 
+    retval = []
+    for item in items:
+        if isinstance(item, (frozenset, list, set, tuple)):
+            retval += flatten(item)
+        else:
+            retval.append(item)
+    return retval
+
 class Lookup(object):
     """Abstract base class for variable lookup implementations."""
 
@@ -18,6 +29,7 @@
         """
         return {
             '_lookup_attr': cls.lookup_attr,
+            '_lookup_name': cls.lookup_name,
         }
 
     @classmethod
@@ -30,16 +42,99 @@
             except (KeyError, TypeError):
                 raise e
 
+    @classmethod
+    def lookup_name(cls, data, name):
+        try:
+            return data[name]
+        except KeyError:
+            raise NameError(name)
+
 class TemplateASTTransformer(ASTTransformer):
-    def __init__(self):
+    """Concrete AST transformer that implements the AST transformations needed
+    for code embedded in templates.
+    """
+
+    def __init__(self, globals):
         self.locals = [CONSTANTS]
+        self.locals.append(set(globals))
+        self.locals.append(set(dir(__builtin__)))
         
+    def visitConst(self, node):
+        if isinstance(node.value, str):
+            try: # If the string is ASCII, return a `str` object
+                node.value.decode('ascii')
+            except ValueError: # Otherwise return a `unicode` object
+                return ast.Const(node.value.decode('utf-8'))
+        return node
+
+    def visitAssName(self, node):
+        if len(self.locals) > 1:
+            if node.flags == 'OP_ASSIGN':
+                self.locals[-1].add(node.name)
+            else:
+                self.locals[-1].remove(node.name)
+        return node
+
+    def visitClass(self, node):
+        if len(self.locals) > 1:
+            self.locals[-1].add(node.name)
+        self.locals.append(set())
+        try:
+            return ASTTransformer.visitClass(self, node)
+        finally:
+            self.locals.pop()
+
+    def visitFor(self, node):
+        self.locals.append(set())
+        try:
+            return ASTTransformer.visitFor(self, node)
+        finally:
+            self.locals.pop()
+
+    def visitFunction(self, node):
+        if len(self.locals) > 1:
+            self.locals[-1].add(node.name)
+        self.locals.append(set(node.argnames))
+        try:
+            return ASTTransformer.visitFunction(self, node)
+        finally:
+            self.locals.pop()
+
+    def visitGenExpr(self, node):
+        self.locals.append(set())
+        try:
+            return ASTTransformer.visitGenExpr(self, node)
+        finally:
+            self.locals.pop()
+
+    def visitLambda(self, node):
+        self.locals.append(set(flatten(node.argnames)))
+        try:
+            return ASTTransformer.visitLambda(self, node)
+        finally:
+            self.locals.pop()
+
+    def visitListComp(self, node):
+        self.locals.append(set())
+        try:
+            return ASTTransformer.visitListComp(self, node)
+        finally:
+            self.locals.pop()
+
+    def visitName(self, node):
+        # If the name refers to a local inside a lambda, list comprehension, or
+        # generator expression, leave it alone
+        if node.name not in flatten(self.locals):
+            # Otherwise, translate the name ref into a context lookup
+            func_args = [ast.Name('_scope'), ast.Const(node.name)]
+            node = ast.CallFunc(ast.Name('_lookup_name'), func_args)
+        return node
+
     def visitGetattr(self, node):
-        """
-        Allow fallback to dictionary lookup if attribute does not exist.
+        """Get attribute with fallback to dictionary lookup.
 
-        Variables starting with an underscore are exempt.
-
+        Note: Variables starting with an underscore are exempt
+        (reserved for internal use).
         """
         
         if hasattr(node.expr, 'name') and node.expr.name.startswith('_'):
@@ -52,16 +147,15 @@
 class Suite(object):
     __slots__ = ['code', '_globals']
 
-    xform = TemplateASTTransformer
     mode = 'exec'
     
-    def __init__(self, source):
+    def __init__(self, source, globals=()):
         """Create the code object from a string."""
 
         node = parse(source, self.mode)
 
         # build tree
-        transform = self.xform()
+        transform = TemplateASTTransformer(globals)
         tree = transform.visit(node)
         filename = tree.filename = '<script>'
 

Modified: z3c.pt/trunk/src/z3c/pt/codegen.txt
===================================================================
--- z3c.pt/trunk/src/z3c/pt/codegen.txt	2008-08-18 21:44:21 UTC (rev 89981)
+++ z3c.pt/trunk/src/z3c/pt/codegen.txt	2008-08-18 21:46:29 UTC (rev 89982)
@@ -17,8 +17,8 @@
   >>> exec suite.code in suite._globals
   Hello World!
 
-AST transformations
--------------------
+Syntax extension: Dictionary lookup using dot operator
+------------------------------------------------------
   
 We allow attribute access to dictionary entries to minimize verbosity
 in templates. It works by wrapping the get attribute nodes in a method
@@ -29,3 +29,12 @@
   ... assert a['b'] == a.b
   ... """)
   >>> exec suite.code in suite._globals
+
+Syntax extension: Dynamic scoping
+---------------------------------
+
+  >>> suite = Suite("""\
+  ... _scope = {'a': 1}
+  ... assert a == 1
+  ... """)
+  >>> exec suite.code in suite._globals

Modified: z3c.pt/trunk/src/z3c/pt/expressions.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/expressions.py	2008-08-18 21:44:21 UTC (rev 89981)
+++ z3c.pt/trunk/src/z3c/pt/expressions.py	2008-08-18 21:46:29 UTC (rev 89982)
@@ -72,7 +72,7 @@
         Single variable:
 
           >>> declaration("variable")
-          declaration('variable',)
+          declaration('variable')
 
         Multiple variables:
 
@@ -165,13 +165,13 @@
         Single define:
         
         >>> definitions("variable expression")
-        definitions((declaration('variable',), value('expression')),)
+        definitions((declaration('variable'), value('expression')),)
         
         Multiple defines:
         
         >>> definitions("variable1 expression1; variable2 expression2")
-        definitions((declaration('variable1',), value('expression1')),
-                    (declaration('variable2',), value('expression2')))
+        definitions((declaration('variable1'), value('expression1')),
+                    (declaration('variable2'), value('expression2')))
         
         Tuple define:
         
@@ -179,35 +179,40 @@
         definitions((declaration('variable1', 'variable2'),
                     value('(expression1, expression2)')),)
 
+        Global defines:
+
+        >>> definitions("global variable expression")
+        definitions((declaration('variable', global_scope=True), value('expression')),)
+
         Space, the 'in' operator and '=' may be used to separate
         variable from expression.
 
         >>> definitions("variable in expression")
-        definitions((declaration('variable',), value('expression')),)        
+        definitions((declaration('variable'), value('expression')),)        
         
         >>> definitions("variable1 = expression1; variable2 = expression2")
-        definitions((declaration('variable1',), value('expression1')),
-                    (declaration('variable2',), value('expression2')))
+        definitions((declaration('variable1'), value('expression1')),
+                    (declaration('variable2'), value('expression2')))
 
         >>> definitions("variable1=expression1; variable2=expression2")
-        definitions((declaration('variable1',), value('expression1')),
-                    (declaration('variable2',), value('expression2')))
+        definitions((declaration('variable1'), value('expression1')),
+                    (declaration('variable2'), value('expression2')))
         
         A define clause that ends in a semicolon:
         
         >>> definitions("variable expression;")
-        definitions((declaration('variable',), value('expression')),)
+        definitions((declaration('variable'), value('expression')),)
         
         A define clause with a trivial expression (we do allow this):
         
         >>> definitions("variable")
-        definitions((declaration('variable',), None),)
+        definitions((declaration('variable'), None),)
         
         A proper define clause following one with a trivial expression:
         
         >>> definitions("variable1 expression; variable2")
-        definitions((declaration('variable1',), value('expression')),
-                    (declaration('variable2',), None))
+        definitions((declaration('variable1'), value('expression')),
+                    (declaration('variable2'), None))
 
         """
 
@@ -216,6 +221,11 @@
         defines = []
         i = 0
         while i < len(string):
+            global_scope = False
+            if string.startswith('global'):
+                global_scope = True
+                i += 6
+
             while string[i] == ' ':
                 i += 1
 
@@ -238,6 +248,8 @@
                 else:
                     var = self.declaration(string[i:j])
 
+            var.global_scope = global_scope
+            
             # get expression
             i = j + len(string) - j - len(string[j:].lstrip())
 
@@ -567,11 +579,11 @@
         Semi-colon literal.
         
         >>> definitions("variable part1;; part2")
-        definitions((declaration('variable',), join('part1; part2',)),)
+        definitions((declaration('variable'), join('part1; part2',)),)
 
         >>> definitions("variable1 part1;; part2; variable2 part3")
-        definitions((declaration('variable1',), join('part1; part2',)),
-                    (declaration('variable2',), join('part3',)))
+        definitions((declaration('variable1'), join('part1; part2',)),
+                    (declaration('variable2'), join('part3',)))
     
         """
 

Modified: z3c.pt/trunk/src/z3c/pt/generation.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/generation.py	2008-08-18 21:44:21 UTC (rev 89981)
+++ z3c.pt/trunk/src/z3c/pt/generation.py	2008-08-18 21:46:29 UTC (rev 89982)
@@ -18,7 +18,7 @@
 \t_domain, _negotiate, _translate = generation.initialize_i18n()
 \t_marker = generation.initialize_helpers()
 \t_path = generation.initialize_traversal()
-
+\t_scope = {}
 \t_target_language = _negotiate(_context, target_language)
 %(code)s
 \treturn _out.getvalue()
@@ -74,7 +74,7 @@
         self.stream = CodeIO(indentation=1, indentation_string="\t")
 
         # initialize variable scope
-        self.stream.scope.append(set(('_out', '_write') + tuple(params)))
+        self.stream.scope.append(set(('_out', '_write', '_scope') + tuple(params)))
 
     def __call__(self):
         params = self.params

Modified: z3c.pt/trunk/src/z3c/pt/genshi.txt
===================================================================
--- z3c.pt/trunk/src/z3c/pt/genshi.txt	2008-08-18 21:44:21 UTC (rev 89981)
+++ z3c.pt/trunk/src/z3c/pt/genshi.txt	2008-08-18 21:46:29 UTC (rev 89982)
@@ -346,7 +346,7 @@
 :: Both py:for and ${..} should be ignored in a comment, or both should be
    handled.
 
-  >>> print render_genshi("""\
+  >> print render_genshi("""\
   ... <div xmlns="http://www.w3.org/1999/xhtml"
   ...       xmlns:py="http://genshi.edgewall.org">
   ... <!--
@@ -394,6 +394,12 @@
   </script>
   </div>
 
+:: XML Inclusions (XIncludes)
 
+Genshi supports inclusion of other templates using the XInclude
+specification.
+
+
+  
 .. _py:with specs: http://genshi.edgewall.org/wiki/Documentation/0.4.x/xml-templates.html#py-with
 

Modified: z3c.pt/trunk/src/z3c/pt/template.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/template.py	2008-08-18 21:44:21 UTC (rev 89981)
+++ z3c.pt/trunk/src/z3c/pt/template.py	2008-08-18 21:46:29 UTC (rev 89982)
@@ -46,8 +46,7 @@
             default_expression=self.default_expression)
         
         source, _globals = generator()
-        
-        suite = codegen.Suite(source)
+        suite = codegen.Suite(source, _globals.keys())
 
         if self.cachedir:
             self.registry.store(params, suite.code)

Modified: z3c.pt/trunk/src/z3c/pt/template.txt
===================================================================
--- z3c.pt/trunk/src/z3c/pt/template.txt	2008-08-18 21:44:21 UTC (rev 89981)
+++ z3c.pt/trunk/src/z3c/pt/template.txt	2008-08-18 21:46:29 UTC (rev 89982)
@@ -138,6 +138,10 @@
   ...      xmlns:metal="http://xml.zope.org/namespaces/metal">
   ...   <div metal:define-macro="greeting">
   ...     Hello, ${name}!
+  ...     <span tal:define="global name 'earth'">
+  ...       Hello, ${name}!
+  ...     </span>
+  ...     Hello, ${name}!
   ...   </div>
   ... </div>""")
 
@@ -147,7 +151,9 @@
   ...      xmlns:metal="http://xml.zope.org/namespaces/metal">
   ...   <div tal:define="name 'world'">
   ...     <div metal:use-macro="template1.macros['greeting']" />
-  ...   </div>  
+  ...     Hello, ${name}!
+  ...   </div>
+  ...   Hello, ${name}!
   ... </div>""")
 
   >>> print template2(template1=template1)
@@ -155,9 +161,15 @@
     <div>
       <div>
       Hello, world!
+      <span>
+        Hello, earth!
+      </span>
+      Hello, earth!
     </div>
   <BLANKLINE>
-    </div>  
+      Hello, world!
+    </div>
+    Hello, earth!
   </div>
   
 Error handling

Modified: z3c.pt/trunk/src/z3c/pt/translation.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/translation.py	2008-08-18 21:44:21 UTC (rev 89981)
+++ z3c.pt/trunk/src/z3c/pt/translation.py	2008-08-18 21:46:29 UTC (rev 89982)
@@ -23,6 +23,7 @@
     metal_slot_prefix = '_fill'
     metal_variable = '_metal'
     macro_variable = '_macro'
+    scope_variable = '_scope'
     
     def start(self, stream):
         self._stream = stream
@@ -85,8 +86,12 @@
 
         # variable definitions
         if self.node.define is not None:
-            for variables, expression in self.node.define:
-                _.append(clauses.Define(variables, expression))
+            for declaration, expression in self.node.define:
+                if declaration.global_scope:
+                    _.append(clauses.Define(
+                        declaration, expression, self.scope_variable))
+                else:
+                    _.append(clauses.Define(declaration, expression))
 
         # macro method
         for element in tuple(self):
@@ -223,7 +228,7 @@
                 
                 subclauses = []
                 subclauses.append(clauses.Define(
-                    ('_out', '_write'),
+                    types.declaration(('_out', '_write')),
                     types.value('generation.initialize_stream()')))
                 subclauses.append(clauses.Visit(element))
                 subclauses.append(clauses.Assign(
@@ -238,7 +243,8 @@
                       itertools.chain(*self.stream.scope))+
                 tuple("%s=%s" % kwarg for kwarg in kwargs))
                 
-            _.append(clauses.Write(types.value("%s(%s)" % (self.metal_variable, arguments))))
+            _.append(clauses.Write(
+                types.value("%s(%s)" % (self.metal_variable, arguments))))
 
         # translate body
         elif self.node.translate is not None:
@@ -261,7 +267,7 @@
 
                 subclauses = []
                 subclauses.append(clauses.Define(
-                    ('_out', '_write'),
+                    types.declaration(('_out', '_write')),
                     types.value('generation.initialize_stream()')))
                 subclauses.append(clauses.Visit(element))
                 subclauses.append(clauses.Assign(

Modified: z3c.pt/trunk/src/z3c/pt/types.py
===================================================================
--- z3c.pt/trunk/src/z3c/pt/types.py	2008-08-18 21:44:21 UTC (rev 89981)
+++ z3c.pt/trunk/src/z3c/pt/types.py	2008-08-18 21:46:29 UTC (rev 89982)
@@ -14,8 +14,13 @@
         return 'join'+tuple.__repr__(self)
 
 class declaration(tuple):
+    global_scope = False
+    
     def __repr__(self):
-        return 'declaration'+tuple.__repr__(self)
+        items = map(repr, self)
+        if self.global_scope:
+            items.append('global_scope=%s' % repr(self.global_scope))
+        return 'declaration(%s)' % ', '.join(items)
 
 class mapping(tuple):
     def __repr__(self):



More information about the Checkins mailing list