[Checkins] SVN: zope.fixers/trunk/zope/fixers/ Added a fixer for implementsOnly. Currently the decorator is called implementer_only. I'm not happy with

Lennart Regebro regebro at gmail.com
Tue Apr 7 12:31:03 EDT 2009


Log message for revision 98979:
  Added a fixer for implementsOnly. Currently the decorator is called implementer_only. I'm not happy with 
  that name, but we can easily change it until we make a first release of a Python 3 compatible 
  zope.interface.
  

Changed:
  A   zope.fixers/trunk/zope/fixers/base.py
  U   zope.fixers/trunk/zope/fixers/fix_implements.py
  A   zope.fixers/trunk/zope/fixers/fix_implements_only.py
  U   zope.fixers/trunk/zope/fixers/tests.py

-=-
Added: zope.fixers/trunk/zope/fixers/base.py
===================================================================
--- zope.fixers/trunk/zope/fixers/base.py	                        (rev 0)
+++ zope.fixers/trunk/zope/fixers/base.py	2009-04-07 16:31:03 UTC (rev 98979)
@@ -0,0 +1,163 @@
+##############################################################################
+#
+# Copyright (c) 2009 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Fixer for class interface declarations to class decorators
+
+$Id$
+"""
+
+# Local imports
+from lib2to3.fixer_base import BaseFix
+from lib2to3.patcomp import PatternCompiler
+from lib2to3.fixer_util import syms, Name
+from lib2to3.fixer_util import Node, Leaf
+
+class Function2DecoratorBase(BaseFix):
+
+    IMPORT_PATTERN = """
+    import_from< 'from' dotted_name< 'zope' '.' 'interface' > 'import' import_as_names< any* (name='%(function_name)s') any* > >
+    |
+    import_from< 'from' dotted_name< 'zope' '.' 'interface' > 'import' name='%(function_name)s' any* >
+    |
+    import_from< 'from' dotted_name< 'zope' > 'import' name='interface' any* >
+    |
+    import_from< 'from' dotted_name< 'zope' '.' 'interface' > 'import' import_as_name< name='%(function_name)s' 'as' rename=(any) any*> >
+    |
+    import_from< 'from' dotted_name< 'zope' > 'import' import_as_name< name='interface' 'as' rename=(any) any*> >
+    |
+    import_from< 'from' 'zope' 'import' import_as_name< 'interface' 'as' interface_rename=(any) > >
+    """
+    
+    CLASS_PATTERN = """
+    classdef< 'class' any* ':' suite< any* simple_stmt< power< statement=(%s) trailer < '(' interface=any ')' > any* > any* > any* > >
+    """
+
+    FUNCTION_PATTERN = """
+    simple_stmt< power< old_statement=(%s) trailer < '(' any* ')' > > any* >
+    """
+    
+    def should_skip(self, node):
+        module = str(node)
+        return not ('zope' in module and 'interface' in module)
+
+    def compile_pattern(self):
+        # Compile the import pattern.
+        self.named_import_pattern = PatternCompiler().compile_pattern(
+            self.IMPORT_PATTERN % {'function_name': self.FUNCTION_NAME})
+        
+    def start_tree(self, tree, filename):
+        # Compile the basic class/function matches. This is done per tree,
+        # as further matches (based on what imports there are) also are done
+        # per tree.
+        self.class_patterns = []
+        self.function_patterns = []
+        self.fixups = []
+
+        self._add_pattern("'%s'" % self.FUNCTION_NAME)
+        self._add_pattern("'interface' trailer< '.' '%s' >" % self.FUNCTION_NAME)
+        self._add_pattern("'zope' trailer< '.' 'interface' > trailer< '.' '%s' >" % self.FUNCTION_NAME)
+    
+    def _add_pattern(self, match):
+            self.class_patterns.append(PatternCompiler().compile_pattern(
+                self.CLASS_PATTERN % match))
+            self.function_patterns.append(PatternCompiler().compile_pattern(
+                self.FUNCTION_PATTERN % match))
+        
+    def match(self, node):
+        # Matches up the imports
+        results = {"node": node}
+        if self.named_import_pattern.match(node, results):
+            return results
+
+        # Now match classes on all import variants found:
+        for pattern in self.class_patterns:
+            if pattern.match(node, results):
+                return results
+
+        # And lastly on all actual calls to the function:
+        for pattern in self.function_patterns:
+            if pattern.match(node, results):
+                return results
+                
+    def transform(self, node, results):
+        if 'name' in results:
+            # This matched an import statement. Fix that up:
+            name = results["name"]
+            name.replace(Name(self.DECORATOR_NAME, prefix=name.get_prefix()))
+        if 'rename' in results:
+            # The import statement use import as
+            self._add_pattern("'%s'" % results['rename'].value)
+        if 'interface_rename' in results:
+            self._add_pattern("'%s' trailer< '.' '%s' > " % (
+                results['interface_rename'].value, self.FUNCTION_NAME))
+        if 'statement' in results:
+            # This matched a class that has an <FUNCTION_NAME>(IFoo) statement.
+            # We must convert that statement to a class decorator
+            # and put it before the class definition.
+            
+            statement = results['statement']
+            if not isinstance(statement, list):
+                statement = [statement]
+            # Make a copy for insertion before the class:
+            statement = [x.clone() for x in statement]
+            # Get rid of leading whitespace:
+            statement[0].prefix = ''
+            # Rename function to decorator:
+            if statement[-1].children:
+                func = statement[-1].children[-1]
+            else:
+                func = statement[-1]
+            if func.value == self.FUNCTION_NAME:
+                func.value = self.DECORATOR_NAME
+            
+            interface = results['interface']
+            if not isinstance(interface, list):
+                interface = [interface]
+            interface = [x.clone() for x in interface]
+
+            # Create the decorator:
+            decorator = Node(syms.decorator, [Leaf(50, '@'),] + statement +
+                             [Leaf(7, '(')] + interface + [Leaf(8, ')')])
+                
+            # Take the current class constructor prefix, and stick it into
+            # the decorator, to set the decorators indentation.
+            prefix = node.get_prefix()
+            decorator.set_prefix(prefix)
+            
+            # Then find the last line of the previous node and use that as
+            # indentation, and add that to the class constructors prefix.
+            prefix = str(node.get_prev_sibling())
+            if not prefix:
+                prefix = node.get_prefix()
+            elif '\n' in prefix:
+                prefix = prefix[prefix.rfind('\n')+1:] + node.get_prefix()
+            else:
+                prefix = prefix + node.get_prefix()
+            if not prefix or prefix[0] != '\n':
+                prefix = '\n' + prefix
+            node.set_prefix(prefix)
+            node.insert_child(0, decorator)
+            
+        if 'old_statement' in results:
+            # This matched an function statement. We'll remove it.
+            self.fixups.append(node)
+    
+    def finish_tree(self, tree, filename):
+        for node in self.fixups:
+            parent = node.parent
+            node.remove()
+            if not str(parent).strip():
+                # This is an empty class. Stick in a pass
+                parent.insert_child(2, Leaf(0, 'pass'))
+                parent.insert_child(3, Leaf(0, '\n'))
+                
\ No newline at end of file

Modified: zope.fixers/trunk/zope/fixers/fix_implements.py
===================================================================
--- zope.fixers/trunk/zope/fixers/fix_implements.py	2009-04-07 15:11:12 UTC (rev 98978)
+++ zope.fixers/trunk/zope/fixers/fix_implements.py	2009-04-07 16:31:03 UTC (rev 98979)
@@ -16,149 +16,8 @@
 $Id$
 """
 
-# Local imports
-from lib2to3.fixer_base import BaseFix
-from lib2to3.patcomp import PatternCompiler
-from lib2to3.fixer_util import syms, Name
-from lib2to3.fixer_util import Node, Leaf
+from .base import Function2DecoratorBase
 
-helper = dict([(b,a) for (a,b) in syms.__dict__.items()])
-
-class FixImplements(BaseFix):
-
-    IMPORT_PATTERN = """
-    import_from< 'from' dotted_name< 'zope' '.' 'interface' > 'import' import_as_names< any* (name='implements') any* > >
-    |
-    import_from< 'from' dotted_name< 'zope' '.' 'interface' > 'import' name='implements' any* >
-    |
-    import_from< 'from' dotted_name< 'zope' > 'import' name='interface' any* >
-    |
-    import_from< 'from' dotted_name< 'zope' '.' 'interface' > 'import' import_as_name< name='implements' 'as' rename=(any) any*> >
-    |
-    import_from< 'from' dotted_name< 'zope' > 'import' import_as_name< name='interface' 'as' rename=(any) any*> >
-    |
-    import_from< 'from' 'zope' 'import' import_as_name< 'interface' 'as' interface_rename=(any) > >
-    """
-    
-    CLASS_PATTERN = """
-    classdef< 'class' any* ':' suite< any* simple_stmt< power< statement=(%s) trailer < '(' interface=any ')' > any* > any* > any* > >
-    """
-
-    IMPLEMENTS_PATTERN = """
-    simple_stmt< power< old_statement=(%s) trailer < '(' any* ')' > > any* >
-    """
-
-    fixups = []
-    
-    def should_skip(self, node):
-        module = str(node)
-        return not ('zope' in module and 'interface' in module)
-
-    def compile_pattern(self):
-        # Compile the import pattern.
-        self.named_import_pattern = PatternCompiler().compile_pattern(self.IMPORT_PATTERN)
-        
-    def start_tree(self, tree, filename):
-        # Compile the basic class/implements matches. This is done per tree,
-        # as further matches (based on what imports there are) also are done
-        # per tree.
-        self.class_patterns = []
-        self.implements_patterns = []
-        self._add_pattern("'implements'")
-        self._add_pattern("'interface' trailer< '.' 'implements' >")
-        self._add_pattern("'zope' trailer< '.' 'interface' > trailer< '.' 'implements' >")
-    
-    def _add_pattern(self, match):
-            self.class_patterns.append(PatternCompiler().compile_pattern(
-                self.CLASS_PATTERN % match))
-            self.implements_patterns.append(PatternCompiler().compile_pattern(
-                self.IMPLEMENTS_PATTERN % match))
-        
-    def match(self, node):
-        # Matches up the imports
-        results = {"node": node}
-        if self.named_import_pattern.match(node, results):
-            return results
-
-        # Now match classes on all import variants found:
-        for pattern in self.class_patterns:
-            if pattern.match(node, results):
-                return results
-
-        # And lastly on all actual calls to implements:
-        for pattern in self.implements_patterns:
-            if pattern.match(node, results):
-                return results
-                
-    def transform(self, node, results):
-        if 'name' in results:
-            # This matched an import statement. Fix that up:
-            name = results["name"]
-            name.replace(Name("implementer", prefix=name.get_prefix()))
-        if 'rename' in results:
-            # The import statement use import as
-            self._add_pattern("'%s'" % results['rename'].value)
-        if 'interface_rename' in results:
-            self._add_pattern("'%s' trailer< '.' 'implements' > " % results['interface_rename'].value)
-        if 'statement' in results:
-            # This matched a class that has an implements(IFoo) statement.
-            # We must convert that statement to a class decorator
-            # and put it before the class definition.
-            
-            statement = results['statement']
-            if not isinstance(statement, list):
-                statement = [statement]
-            # Make a copy for insertion before the class:
-            statement = [x.clone() for x in statement]
-            # Get rid of leading whitespace:
-            statement[0].prefix = ''
-            # Rename implements to implementer:
-            if statement[-1].children:
-                implements = statement[-1].children[-1]
-            else:
-                implements = statement[-1]
-            if implements.value == 'implements':
-                implements.value = 'implementer'
-            
-            interface = results['interface']
-            if not isinstance(interface, list):
-                interface = [interface]
-            interface = [x.clone() for x in interface]
-
-            # Create the decorator:
-            decorator = Node(syms.decorator, [Leaf(50, '@'),] + statement +
-                             [Leaf(7, '(')] + interface + [Leaf(8, ')')])
-                
-            # Take the current class constructor prefix, and stick it into
-            # the decorator, to set the decorators indentation.
-            #import pdb;pdb.set_trace()
-            prefix = node.get_prefix()
-            decorator.set_prefix(prefix)
-            
-            # Then find the last line of the previous node and use that as
-            # indentation, and add that to the class constructors prefix.
-            prefix = str(node.get_prev_sibling())
-            if not prefix:
-                prefix = node.get_prefix()
-            elif '\n' in prefix:
-                prefix = prefix[prefix.rfind('\n')+1:] + node.get_prefix()
-            else:
-                prefix = prefix + node.get_prefix()
-            if not prefix or prefix[0] != '\n':
-                prefix = '\n' + prefix
-            node.set_prefix(prefix)
-            node.insert_child(0, decorator)
-            
-        if 'old_statement' in results:
-            # This matched an implements statement. We'll remove it.
-            self.fixups.append(node)
-    
-    def finish_tree(self, tree, filename):
-        for node in self.fixups:
-            parent = node.parent
-            node.remove()
-            if not str(parent).strip():
-                # This is an empty class. Stick in a pass
-                parent.insert_child(2, Leaf(0, 'pass'))
-                parent.insert_child(3, Leaf(0, '\n'))
-                
\ No newline at end of file
+class FixImplements(Function2DecoratorBase):
+    FUNCTION_NAME = 'implements'
+    DECORATOR_NAME = 'implementer'

Added: zope.fixers/trunk/zope/fixers/fix_implements_only.py
===================================================================
--- zope.fixers/trunk/zope/fixers/fix_implements_only.py	                        (rev 0)
+++ zope.fixers/trunk/zope/fixers/fix_implements_only.py	2009-04-07 16:31:03 UTC (rev 98979)
@@ -0,0 +1,23 @@
+##############################################################################
+#
+# Copyright (c) 2009 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Fixer for implements(IX) -> @implementer(IX).
+
+$Id$
+"""
+
+from .base import Function2DecoratorBase
+
+class FixImplementsOnly(Function2DecoratorBase):
+    FUNCTION_NAME = 'implementsOnly'
+    DECORATOR_NAME = 'implementer_only'

Modified: zope.fixers/trunk/zope/fixers/tests.py
===================================================================
--- zope.fixers/trunk/zope/fixers/tests.py	2009-04-07 15:11:12 UTC (rev 98978)
+++ zope.fixers/trunk/zope/fixers/tests.py	2009-04-07 16:31:03 UTC (rev 98979)
@@ -307,6 +307,11 @@
             msg = msg.replace('\t', '------->')
             msg = ("Test failed at character %i" % i) + msg
             self.fail(msg)
+            
+class ImplementsFixerTest(FixerTest):
+    
+    def setUp(self):
+        self.tool = RefactoringTool(['zope.fixers.fix_implements'])
         
     def test_imports(self):
         self._test(imports_source, imports_target)
@@ -337,3 +342,35 @@
 
     def test_edge_cases(self):
         self._test(edge_cases_source, edge_cases_target)
+
+ 
+implements_only_source = """
+from zope.interface import implementsOnly
+
+class IFoo(Interface):
+    pass
+
+class Foo:
+    "An IFoo class"
+    
+    implementsOnly(IFoo)
+"""
+
+implements_only_target = """
+from zope.interface import implementer_only
+
+class IFoo(Interface):
+    pass
+
+ at implementer_only(IFoo)
+class Foo:
+    "An IFoo class"
+"""
+
+class ImplementsOnlyFixerTest(FixerTest):
+    
+    def setUp(self):
+        self.tool = RefactoringTool(['zope.fixers.fix_implements_only'])
+
+    def test_implements_only(self):
+        self._test(implements_only_source, implements_only_target)



More information about the Checkins mailing list