[Python-checkins] distutils2: use_2to3 option added. tests added.

tarek.ziade python-checkins at python.org
Thu Aug 19 08:34:13 CEST 2010


tarek.ziade pushed 991755cb2e16 to distutils2:

http://hg.python.org/distutils2/rev/991755cb2e16
changeset:   560:991755cb2e16
parent:      350:8b73f08d1d8d
user:        Zubin Mithra <zubin.mithra at gmail.com>
date:        Thu Jul 08 15:06:21 2010 +0530
summary:     use_2to3 option added. tests added.
files:       src/distutils2/command/build_py.py, src/distutils2/core.py, src/distutils2/dist.py, src/distutils2/tests/fixer/__init__.py, src/distutils2/tests/fixer/fix_idioms.py, src/distutils2/tests/test_Mixin2to3.py, src/distutils2/tests/test_util.py, src/distutils2/util.py

diff --git a/src/distutils2/command/build_py.py b/src/distutils2/command/build_py.py
--- a/src/distutils2/command/build_py.py
+++ b/src/distutils2/command/build_py.py
@@ -19,6 +19,7 @@
 
 try:
     from distutils2.util import Mixin2to3 as _Mixin2to3
+    from distutils2 import run_2to3_on_doctests
     from lib2to3.refactor import get_fixers_from_package
     _CONVERT = True
     _KLASS = _Mixin2to3
@@ -33,13 +34,16 @@
     yet does nothing in particular with it.
     """
     if _CONVERT:
-        def _run_2to3(self, files, doctests=[]):
+        def _run_2to3(self, files, doctests=[], fixers=[]):
             """ Takes a list of files and doctests, and performs conversion
             on those.
               - First, the files which contain the code(`files`) are converted.
               - Second, the doctests in `files` are converted.
               - Thirdly, the doctests in `doctests` are converted.
             """
+            # if additional fixers are present, use them
+            if fixers:
+                self.fixer_names = fixers
 
             # Convert the ".py" files.
             logging.info("Converting Python code")
@@ -56,12 +60,12 @@
             #    containing doctests. It is set as
             #    distutils2.run_2to3_on_doctests
 
-            if doctests != [] and distutils2.run_2to3_on_doctests:
+            if doctests != [] and run_2to3_on_doctests:
                 logging.info("Converting text files which contain doctests")
                 _KLASS.run_2to3(self, doctests, doctests_only=True)
     else:
         # If run on Python 2.x, there is nothing to do.
-        def _run_2to3(self, files, doctests=[]):
+        def _run_2to3(self, files, doctests=[], fixers=[]):
             pass
 
 class build_py(Command, Mixin2to3):
@@ -147,7 +151,8 @@
             self.build_package_data()
 
         if self.distribution.use_2to3 and self_updated_files:
-            self.run_2to3(self._updated_files, self._doctests_2to3)
+            self.run_2to3(self._updated_files, self._doctests_2to3, 
+                                            self.distribution.use_2to3_fixers)
 
         self.byte_compile(self.get_outputs(include_bytecode=0))
 
diff --git a/src/distutils2/core.py b/src/distutils2/core.py
--- a/src/distutils2/core.py
+++ b/src/distutils2/core.py
@@ -49,8 +49,8 @@
                   'maintainer', 'maintainer_email', 'url', 'license',
                   'description', 'long_description', 'keywords',
                   'platforms', 'classifiers', 'download_url',
-                  'requires', 'provides', 'obsoletes', 'use_2to3',
-                  'convert_2to3_doctests',
+                  'requires', 'provides', 'obsoletes', 'use_2to3_fixers',
+                  'convert_2to3_doctests', 'use_2to3_fixers'
                   )
 
 # Legal keyword arguments for the Extension constructor
diff --git a/src/distutils2/dist.py b/src/distutils2/dist.py
--- a/src/distutils2/dist.py
+++ b/src/distutils2/dist.py
@@ -116,7 +116,9 @@
         ('use-2to3', None,
          "use 2to3 to make source python 3.x compatible"),
         ('convert-2to3-doctests', None,
-         "use 2to3 to convert doctests in seperate text files"), 
+         "use 2to3 to convert doctests in seperate text files"),
+        ('use-2to3-fixers', None,
+         "list additional fixers opted for during 2to3 conversion")
         ]
     display_option_names = map(lambda x: translate_longopt(x[0]),
                                display_options)
@@ -212,6 +214,7 @@
         self.password = ''
         self.use_2to3 = False
         self.convert_2to3_doctests = []
+        self.use_2to3_fixers = []
 
         # And now initialize bookkeeping stuff that can't be supplied by
         # the caller at all.  'command_obj' maps command names to
diff --git a/src/distutils2/tests/fixer/__init__.py b/src/distutils2/tests/fixer/__init__.py
new file mode 100644
diff --git a/src/distutils2/tests/fixer/fix_idioms.py b/src/distutils2/tests/fixer/fix_idioms.py
new file mode 100644
--- /dev/null
+++ b/src/distutils2/tests/fixer/fix_idioms.py
@@ -0,0 +1,134 @@
+"""Adjust some old Python 2 idioms to their modern counterparts.
+
+* Change some type comparisons to isinstance() calls:
+    type(x) == T -> isinstance(x, T)
+    type(x) is T -> isinstance(x, T)
+    type(x) != T -> not isinstance(x, T)
+    type(x) is not T -> not isinstance(x, T)
+
+* Change "while 1:" into "while True:".
+
+* Change both
+
+    v = list(EXPR)
+    v.sort()
+    foo(v)
+
+and the more general
+
+    v = EXPR
+    v.sort()
+    foo(v)
+
+into
+
+    v = sorted(EXPR)
+    foo(v)
+"""
+# Author: Jacques Frechet, Collin Winter
+
+# Local imports
+from lib2to3 import fixer_base
+from lib2to3.fixer_util import Call, Comma, Name, Node, syms
+
+CMP = "(n='!=' | '==' | 'is' | n=comp_op< 'is' 'not' >)"
+TYPE = "power< 'type' trailer< '(' x=any ')' > >"
+
+class FixIdioms(fixer_base.BaseFix):
+
+    explicit = False # The user must ask for this fixer
+
+    PATTERN = r"""
+        isinstance=comparison< %s %s T=any >
+        |
+        isinstance=comparison< T=any %s %s >
+        |
+        while_stmt< 'while' while='1' ':' any+ >
+        |
+        sorted=any<
+            any*
+            simple_stmt<
+              expr_stmt< id1=any '='
+                         power< list='list' trailer< '(' (not arglist<any+>) any ')' > >
+              >
+              '\n'
+            >
+            sort=
+            simple_stmt<
+              power< id2=any
+                     trailer< '.' 'sort' > trailer< '(' ')' >
+              >
+              '\n'
+            >
+            next=any*
+        >
+        |
+        sorted=any<
+            any*
+            simple_stmt< expr_stmt< id1=any '=' expr=any > '\n' >
+            sort=
+            simple_stmt<
+              power< id2=any
+                     trailer< '.' 'sort' > trailer< '(' ')' >
+              >
+              '\n'
+            >
+            next=any*
+        >
+    """ % (TYPE, CMP, CMP, TYPE)
+
+    def match(self, node):
+        r = super(FixIdioms, self).match(node)
+        # If we've matched one of the sort/sorted subpatterns above, we
+        # want to reject matches where the initial assignment and the
+        # subsequent .sort() call involve different identifiers.
+        if r and "sorted" in r:
+            if r["id1"] == r["id2"]:
+                return r
+            return None
+        return r
+
+    def transform(self, node, results):
+        if "isinstance" in results:
+            return self.transform_isinstance(node, results)
+        elif "while" in results:
+            return self.transform_while(node, results)
+        elif "sorted" in results:
+            return self.transform_sort(node, results)
+        else:
+            raise RuntimeError("Invalid match")
+
+    def transform_isinstance(self, node, results):
+        x = results["x"].clone() # The thing inside of type()
+        T = results["T"].clone() # The type being compared against
+        x.set_prefix("")
+        T.set_prefix(" ")
+        test = Call(Name("isinstance"), [x, Comma(), T])
+        if "n" in results:
+            test.set_prefix(" ")
+            test = Node(syms.not_test, [Name("not"), test])
+        test.set_prefix(node.get_prefix())
+        return test
+
+    def transform_while(self, node, results):
+        one = results["while"]
+        one.replace(Name("True", prefix=one.get_prefix()))
+
+    def transform_sort(self, node, results):
+        sort_stmt = results["sort"]
+        next_stmt = results["next"]
+        list_call = results.get("list")
+        simple_expr = results.get("expr")
+
+        if list_call:
+            list_call.replace(Name("sorted", prefix=list_call.get_prefix()))
+        elif simple_expr:
+            new = simple_expr.clone()
+            new.set_prefix("")
+            simple_expr.replace(Call(Name("sorted"), [new],
+                                     prefix=simple_expr.get_prefix()))
+        else:
+            raise RuntimeError("should not have reached here")
+        sort_stmt.remove()
+        if next_stmt:
+            next_stmt[0].set_prefix(sort_stmt.get_prefix())
diff --git a/src/distutils2/tests/test_Mixin2to3.py b/src/distutils2/tests/test_Mixin2to3.py
--- a/src/distutils2/tests/test_Mixin2to3.py
+++ b/src/distutils2/tests/test_Mixin2to3.py
@@ -47,6 +47,24 @@
 
         self.assertEquals(new_doctest_content, converted_doctest_content)
 
+    @unittest.skipUnless(sys.version > '2.6', 'Need >= 2.6')
+    def test_additional_fixers(self):
+        # used to check if use_2to3_fixers works
+        from distutils2.tests import fixer
+        code_content = "type(x) is T"
+        code_handle = self.mktempfile()
+        code_name = code_handle.name
+
+        code_handle.write(code_content)
+        code_handle.flush()
+
+        mixin2to3 = Mixin2to3()
+
+        mixin2to3._run_2to3([code_name], None, ['distutils2.tests.fixer'])
+        converted_code_content = "isinstance(x, T)"
+        new_code_content = "".join(open(code_name).readlines())
+        self.assertEquals(new_code_content, converted_code_content)
+
 def test_suite():
     return unittest.makeSuite(Mixin2to3TestCase)
 
diff --git a/src/distutils2/tests/test_util.py b/src/distutils2/tests/test_util.py
--- a/src/distutils2/tests/test_util.py
+++ b/src/distutils2/tests/test_util.py
@@ -327,6 +327,21 @@
         file_handle.close()
         self.assertEquals(new_content, converted_content)
 
+    @unittest.skipUnless(sys.version > '2.6', 'Need Python 2.6 or more')
+    def test_use_additional_fixers(self):
+        # to check if additional fixers work
+        content = "type(x) is T"
+        converted_content = "isinstance(x, T)"
+        file_handle = self.mktempfile()
+        file_name = file_handle.name
+        file_handle.write(content)
+        file_handle.flush()
+        file_handle.seek(0)
+        from distutils2.util import run_2to3
+        run_2to3([file_name], None, ['distutils2.tests.fixer'])
+        new_content = "".join(file_handle.read())
+        file_handle.close()
+        self.assertEquals(new_content, converted_content)
 
 def test_suite():
     return unittest.makeSuite(UtilTestCase)
diff --git a/src/distutils2/util.py b/src/distutils2/util.py
--- a/src/distutils2/util.py
+++ b/src/distutils2/util.py
@@ -644,7 +644,7 @@
 
 # utility functions for 2to3 support
 
-def run_2to3(files, doctests_only=False, fixer_names=None, options=None,
+def run_2to3(files=[], doctests_only=False, fixer_names=[], options=None,
                                                             explicit=None):
     """ Wrapper function around the refactor() class which
     performs the conversions on a list of python files.
@@ -655,13 +655,23 @@
         return
 
     # Make this class local, to delay import of 2to3
-    from lib2to3.refactor import get_fixers_from_package
-    from distutils2.converter.refactor import DistutilsRefactoringTool
+    from lib2to3.refactor import get_fixers_from_package, RefactoringTool
+    #from distutils2.converter.refactor import DistutilsRefactoringTool
 
-    if fixer_names is None:
-        fixer_names = get_fixers_from_package('lib2to3.fixes')
+    fixers = []
+    
+    # select only the 'default fixers' from the lib2to3 package
+    for fixer in get_fixers_from_package('lib2to3.fixes'):
+        if fixers not in ('lib2to3.fixes.fix_buffer', 'lib2to3.fixes.fix_idioms', 
+                    'lib2to3.fixes.fix_numliterals', 'lib2to3.fixes.fix_set_literal', 
+                    'lib2to3.fixes.fix_ws_comma'):
+            fixers.append(fixer)
 
-    r = DistutilsRefactoringTool(fixer_names, options=options)
+    if fixer_names is not None:
+        for fixer_package in fixer_names:
+            fixers.extend(get_fixers_from_package(fixer_package))
+
+    r = RefactoringTool(fixers, options=options)
     if doctests_only:
         r.refactor(files, doctests_only=True, write=True)
     else:
@@ -676,7 +686,7 @@
     """
     # provide list of fixers to run.
     # defaults to all from lib2to3.fixers
-    fixer_names = None
+    fixer_names = []
 
     # options dictionary
     options = None
@@ -687,4 +697,4 @@
     def run_2to3(self, files, doctests_only=False):
         """ Issues a call to util.run_2to3. """
         return run_2to3(files, doctests_only, self.fixer_names,
-                        self.options, self.explicit)
+                        self.options, self.explicit)
\ No newline at end of file

--
Repository URL: http://hg.python.org/distutils2


More information about the Python-checkins mailing list