diff --git a/2to3/fix_import.py b/2to3/fix_import.py new file mode 100644 index 000000000..9d3147dfa --- /dev/null +++ b/2to3/fix_import.py @@ -0,0 +1,105 @@ +"""Fixer for import statements. +If spam is being imported from the local directory, this import: + from spam import eggs +Becomes: + from .spam import eggs + +And this import: + import spam +Becomes: + from . import spam +""" + +# Local imports +from lib2to3 import fixer_base +from os.path import dirname, join, exists, sep, split, isdir +from os import listdir +from lib2to3.fixer_util import FromImport, syms, token + + +def traverse_imports(names): + """ + Walks over all the names imported in a dotted_as_names node. + """ + pending = [names] + while pending: + node = pending.pop() + if node.type == token.NAME: + yield node.value + elif node.type == syms.dotted_name: + yield "".join([ch.value for ch in node.children]) + elif node.type == syms.dotted_as_name: + pending.append(node.children[0]) + elif node.type == syms.dotted_as_names: + pending.extend(node.children[::-2]) + else: + raise AssertionError("unkown node type") + + +class FixImport(fixer_base.BaseFix): + BM_compatible = True + + PATTERN = """ + import_from< 'from' imp=any 'import' ['('] any [')'] > + | + import_name< 'import' imp=any > + """ + + def start_tree(self, tree, name): + super(FixImport, self).start_tree(tree, name) + self.skip = "absolute_import" in tree.future_features + + def transform(self, node, results): + if self.skip: + return + imp = results['imp'] + + if node.type == syms.import_from: + # Some imps are top-level (eg: 'import ham') + # some are first level (eg: 'import ham.eggs') + # some are third level (eg: 'import ham.eggs as spam') + # Hence, the loop + while not hasattr(imp, 'value'): + imp = imp.children[0] + if self.probably_a_local_import(imp.value): + imp.value = "." + imp.value + imp.changed() + else: + have_local = False + have_absolute = False + for mod_name in traverse_imports(imp): + if self.probably_a_local_import(mod_name): + have_local = True + else: + have_absolute = True + if have_absolute: + if have_local: + # We won't handle both sibling and absolute imports in the + # same statement at the moment. + self.warning(node, "absolute and local imports together") + return + + new = FromImport(".", [imp]) + new.prefix = node.prefix + return new + + def probably_a_local_import(self, imp_name): + if imp_name.startswith("."): + # Relative imports are certainly not local imports. + return False + imp_name = imp_name.split(".", 1)[0] + base_path = dirname(self.filename) + base_path = join(base_path, imp_name) + # If there is no __init__.py next to the file its not in a package + # so can't be a relative import. + if not exists(join(dirname(base_path), "__init__.py")): + return False + if isdir(base_path): + return True + for ext in [".py", ".pyc", ".so", ".sl", ".pyd"]: + (path, filename) = split(base_path) + if (filename + ext) in listdir(path): + # We use this instead of os.path.exists because of case-insensitivity + # on Windows + return True + return False diff --git a/2to3/run.py b/2to3/run.py index 5234e2ef7..8f0f420e3 100755 --- a/2to3/run.py +++ b/2to3/run.py @@ -26,17 +26,18 @@ os.chdir('py3k') files = ['run.py', 'src', 'plugins', 'test', 'setup.py'] + glob('scripts/*') args = ['-wn'] fixers = [] -for fix in ['all', 'def_iteritems', 'def_itervalues', 'def_iterkeys', 'reload']: +for fix in ['all', 'def_iteritems', 'def_itervalues', 'def_iterkeys', 'reload', 'import']: fixers += ['-f', fix] sys.argv = files + args + fixers + sys.argv sys.argc = len(sys.argv) -import fix_def_iteritems, fix_def_itervalues, fix_def_iterkeys, fix_reload +from . import fix_def_iteritems, fix_def_itervalues, fix_def_iterkeys, fix_reload, fix_import # Hacks sys.modules['lib2to3.fixes.fix_def_iteritems'] = fix_def_iteritems sys.modules['lib2to3.fixes.fix_def_itervalues'] = fix_def_itervalues sys.modules['lib2to3.fixes.fix_def_iterkeys'] = fix_def_iterkeys sys.modules['lib2to3.fixes.fix_reload'] = fix_reload +sys.modules['lib2to3.fixes.fix_import'] = fix_import sys.exit(main("lib2to3.fixes")) diff --git a/setup.py b/setup.py index bcfe2b443..2e5d4a18f 100644 --- a/setup.py +++ b/setup.py @@ -145,6 +145,7 @@ try: log.debug(msg, *args) fixer_names = get_fixers_from_package('lib2to3.fixes') + fixer_names.remove('lib2to3.fixes.fix_import') fixer_names += get_fixers_from_package('2to3') r = DistutilsRefactoringTool(fixer_names, options=options) r.refactor(files, write=True)