fix_leftover_imports.py
108 lines
| 4.6 KiB
| text/x-python
|
PythonLexer
Renato Cunha
|
r11949 | "Fixer that translates some APIs ignored by the default 2to3 fixers." | ||
# FIXME: This fixer has some ugly hacks. Its main design is based on that of | ||||
# fix_imports, from lib2to3. Unfortunately, the fix_imports framework only | ||||
# changes module names "without dots", meaning it won't work for some changes | ||||
# in the email module/package. Thus this fixer was born. I believe that with a | ||||
# bit more thinking, a more generic fixer can be implemented, but I'll leave | ||||
# that as future work. | ||||
from lib2to3.fixer_util import Name | ||||
from lib2to3.fixes import fix_imports | ||||
# This maps the old names to the new names. Note that a drawback of the current | ||||
# design is that the dictionary keys MUST have EXACTLY one dot (.) in them, | ||||
# otherwise things will break. (If you don't need a module hierarchy, you're | ||||
# better of just inherit from fix_imports and overriding the MAPPING dict.) | ||||
MAPPING = {'email.Utils': 'email.utils', | ||||
'email.Errors': 'email.errors', | ||||
'email.Header': 'email.header', | ||||
'email.Parser': 'email.parser', | ||||
'email.Encoders': 'email.encoders', | ||||
'email.MIMEText': 'email.mime.text', | ||||
'email.MIMEBase': 'email.mime.base', | ||||
'email.Generator': 'email.generator', | ||||
'email.MIMEMultipart': 'email.mime.multipart', | ||||
} | ||||
def alternates(members): | ||||
return "(" + "|".join(map(repr, members)) + ")" | ||||
def build_pattern(mapping=MAPPING): | ||||
packages = {} | ||||
for key in mapping: | ||||
# What we are doing here is the following: with dotted names, we'll | ||||
# have something like package_name <trailer '.' module>. Then, we are | ||||
# making a dictionary to copy this structure. For example, if | ||||
# mapping={'A.B': 'a.b', 'A.C': 'a.c'}, it will generate the dictionary | ||||
# {'A': ['b', 'c']} to, then, generate something like "A <trailer '.' | ||||
# ('b' | 'c')". | ||||
name = key.split('.') | ||||
prefix = name[0] | ||||
if prefix in packages: | ||||
packages[prefix].append(name[1:][0]) | ||||
else: | ||||
packages[prefix] = name[1:] | ||||
mod_list = ' | '.join(["'%s' '.' ('%s')" % | ||||
(key, "' | '".join(packages[key])) for key in packages]) | ||||
mod_list = '(' + mod_list + ' )' | ||||
bare_names = alternates(mapping.keys()) | ||||
yield """name_import=import_name< 'import' module_name=dotted_name< %s > > | ||||
""" % mod_list | ||||
yield """name_import=import_name< 'import' | ||||
multiple_imports=dotted_as_names< any* | ||||
module_name=dotted_name< %s > | ||||
any* > | ||||
>""" % mod_list | ||||
packs = ' | '.join(["'%s' trailer<'.' ('%s')>" % (key, | ||||
"' | '".join(packages[key])) for key in packages]) | ||||
yield "power< package=(%s) trailer<'.' any > any* >" % packs | ||||
class FixLeftoverImports(fix_imports.FixImports): | ||||
# We want to run this fixer after fix_import has run (this shouldn't matter | ||||
# for hg, though, as setup3k prefers to run the default fixers first) | ||||
mapping = MAPPING | ||||
def build_pattern(self): | ||||
return "|".join(build_pattern(self.mapping)) | ||||
def transform(self, node, results): | ||||
# Mostly copied from fix_imports.py | ||||
import_mod = results.get("module_name") | ||||
if import_mod: | ||||
try: | ||||
mod_name = import_mod.value | ||||
except AttributeError: | ||||
# XXX: A hack to remove whitespace prefixes and suffixes | ||||
mod_name = str(import_mod).strip() | ||||
new_name = self.mapping[mod_name] | ||||
import_mod.replace(Name(new_name, prefix=import_mod.prefix)) | ||||
if "name_import" in results: | ||||
# If it's not a "from x import x, y" or "import x as y" import, | ||||
# marked its usage to be replaced. | ||||
self.replace[mod_name] = new_name | ||||
if "multiple_imports" in results: | ||||
# This is a nasty hack to fix multiple imports on a line (e.g., | ||||
# "import StringIO, urlparse"). The problem is that I can't | ||||
# figure out an easy way to make a pattern recognize the keys of | ||||
# MAPPING randomly sprinkled in an import statement. | ||||
results = self.match(node) | ||||
if results: | ||||
self.transform(node, results) | ||||
else: | ||||
# Replace usage of the module. | ||||
# Now this is, mostly, a hack | ||||
bare_name = results["package"][0] | ||||
bare_name_text = ''.join(map(str, results['package'])).strip() | ||||
new_name = self.replace.get(bare_name_text) | ||||
prefix = results['package'][0].prefix | ||||
if new_name: | ||||
bare_name.replace(Name(new_name, prefix=prefix)) | ||||
results["package"][1].replace(Name('')) | ||||