|
1 "Fixer that translates some APIs ignored by the default 2to3 fixers." |
|
2 |
|
3 # FIXME: This fixer has some ugly hacks. Its main design is based on that of |
|
4 # fix_imports, from lib2to3. Unfortunately, the fix_imports framework only |
|
5 # changes module names "without dots", meaning it won't work for some changes |
|
6 # in the email module/package. Thus this fixer was born. I believe that with a |
|
7 # bit more thinking, a more generic fixer can be implemented, but I'll leave |
|
8 # that as future work. |
|
9 |
|
10 from lib2to3.fixer_util import Name |
|
11 from lib2to3.fixes import fix_imports |
|
12 |
|
13 # This maps the old names to the new names. Note that a drawback of the current |
|
14 # design is that the dictionary keys MUST have EXACTLY one dot (.) in them, |
|
15 # otherwise things will break. (If you don't need a module hierarchy, you're |
|
16 # better of just inherit from fix_imports and overriding the MAPPING dict.) |
|
17 |
|
18 MAPPING = {'email.Utils': 'email.utils', |
|
19 'email.Errors': 'email.errors', |
|
20 'email.Header': 'email.header', |
|
21 'email.Parser': 'email.parser', |
|
22 'email.Encoders': 'email.encoders', |
|
23 'email.MIMEText': 'email.mime.text', |
|
24 'email.MIMEBase': 'email.mime.base', |
|
25 'email.Generator': 'email.generator', |
|
26 'email.MIMEMultipart': 'email.mime.multipart', |
|
27 } |
|
28 |
|
29 def alternates(members): |
|
30 return "(" + "|".join(map(repr, members)) + ")" |
|
31 |
|
32 def build_pattern(mapping=MAPPING): |
|
33 packages = {} |
|
34 for key in mapping: |
|
35 # What we are doing here is the following: with dotted names, we'll |
|
36 # have something like package_name <trailer '.' module>. Then, we are |
|
37 # making a dictionary to copy this structure. For example, if |
|
38 # mapping={'A.B': 'a.b', 'A.C': 'a.c'}, it will generate the dictionary |
|
39 # {'A': ['b', 'c']} to, then, generate something like "A <trailer '.' |
|
40 # ('b' | 'c')". |
|
41 name = key.split('.') |
|
42 prefix = name[0] |
|
43 if prefix in packages: |
|
44 packages[prefix].append(name[1:][0]) |
|
45 else: |
|
46 packages[prefix] = name[1:] |
|
47 |
|
48 mod_list = ' | '.join(["'%s' '.' ('%s')" % |
|
49 (key, "' | '".join(packages[key])) for key in packages]) |
|
50 mod_list = '(' + mod_list + ' )' |
|
51 bare_names = alternates(mapping.keys()) |
|
52 |
|
53 yield """name_import=import_name< 'import' module_name=dotted_name< %s > > |
|
54 """ % mod_list |
|
55 |
|
56 yield """name_import=import_name< 'import' |
|
57 multiple_imports=dotted_as_names< any* |
|
58 module_name=dotted_name< %s > |
|
59 any* > |
|
60 >""" % mod_list |
|
61 |
|
62 packs = ' | '.join(["'%s' trailer<'.' ('%s')>" % (key, |
|
63 "' | '".join(packages[key])) for key in packages]) |
|
64 |
|
65 yield "power< package=(%s) trailer<'.' any > any* >" % packs |
|
66 |
|
67 class FixLeftoverImports(fix_imports.FixImports): |
|
68 # We want to run this fixer after fix_import has run (this shouldn't matter |
|
69 # for hg, though, as setup3k prefers to run the default fixers first) |
|
70 mapping = MAPPING |
|
71 |
|
72 def build_pattern(self): |
|
73 return "|".join(build_pattern(self.mapping)) |
|
74 |
|
75 def transform(self, node, results): |
|
76 # Mostly copied from fix_imports.py |
|
77 import_mod = results.get("module_name") |
|
78 if import_mod: |
|
79 try: |
|
80 mod_name = import_mod.value |
|
81 except AttributeError: |
|
82 # XXX: A hack to remove whitespace prefixes and suffixes |
|
83 mod_name = str(import_mod).strip() |
|
84 new_name = self.mapping[mod_name] |
|
85 import_mod.replace(Name(new_name, prefix=import_mod.prefix)) |
|
86 if "name_import" in results: |
|
87 # If it's not a "from x import x, y" or "import x as y" import, |
|
88 # marked its usage to be replaced. |
|
89 self.replace[mod_name] = new_name |
|
90 if "multiple_imports" in results: |
|
91 # This is a nasty hack to fix multiple imports on a line (e.g., |
|
92 # "import StringIO, urlparse"). The problem is that I can't |
|
93 # figure out an easy way to make a pattern recognize the keys of |
|
94 # MAPPING randomly sprinkled in an import statement. |
|
95 results = self.match(node) |
|
96 if results: |
|
97 self.transform(node, results) |
|
98 else: |
|
99 # Replace usage of the module. |
|
100 # Now this is, mostly, a hack |
|
101 bare_name = results["package"][0] |
|
102 bare_name_text = ''.join(map(str, results['package'])).strip() |
|
103 new_name = self.replace.get(bare_name_text) |
|
104 prefix = results['package'][0].prefix |
|
105 if new_name: |
|
106 bare_name.replace(Name(new_name, prefix=prefix)) |
|
107 results["package"][1].replace(Name('')) |
|
108 |