[Zope3-checkins] CVS: Zope3/utilities - importchecker.py:1.1
Martijn Faassen
m.faassen at vet.uu.nl
Thu Feb 26 07:35:16 EST 2004
Update of /cvs-repository/Zope3/utilities
In directory cvs.zope.org:/tmp/cvs-serv2790
Added Files:
importchecker.py
Log Message:
Added importchecker under ZPL license. I'll continue maintaining it under
BSD license at cvs.infrae.com/tools/importchecker, but this is so Stephan
Richter can take code for it to put under the ZPL.
=== Added File Zope3/utilities/importchecker.py ===
#!/usr/bin/env python2.3
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (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.
#
##############################################################################
import compiler
import os, os.path
import sys
def _findDottedNamesHelper(node, result):
more_node = node
name = node.__class__.__name__
if name == 'Getattr':
dotted = []
while name == 'Getattr':
dotted.append(node.attrname)
node = node.expr
name = node.__class__.__name__
if name == 'Name':
dotted.append(node.name)
dotted.reverse()
for i in range(1, len(dotted)):
result.append('.'.join(dotted[:i]))
result.append('.'.join(dotted))
return
elif name == 'Name':
result.append(node.name)
return
elif name == 'AssAttr':
return
for child in more_node.getChildNodes():
_findDottedNamesHelper(child, result)
def findDottedNames(node):
"""Find dotted names in an AST tree node
"""
result = []
_findDottedNamesHelper(node, result)
return result
class ImportFinder:
"""An instance of this class will be used to walk over a compiler AST
tree (a module). During that operation, the appropriate methods of
this visitor will be called
"""
def __init__(self):
self._map = {}
def visitFrom(self, stmt):
"""Will be called for 'from foo import bar' statements
"""
module_name, names = stmt.asList()
if module_name == '__future__':
# we don't care what's imported from the future
return
names_dict = {}
for orig_name, as_name in names:
# we don't care about from import *
if orig_name == '*':
continue
if as_name is None:
name = orig_name
else:
name = as_name
names_dict[name] = orig_name
self._map.setdefault(module_name, {'names': names_dict,
'lineno': stmt.lineno})
def visitImport(self, stmt):
"""Will be called for 'import foo.bar' statements
"""
for orig_name, as_name in stmt.names:
if as_name is None:
name = orig_name
else:
name = as_name
self._map.setdefault(orig_name, {'names': {name: orig_name},
'lineno': stmt.lineno})
def getMap(self):
return self._map
def findImports(mod):
"""Find import statements in module and put the result in a mapping.
"""
visitor = ImportFinder()
compiler.walk(mod, visitor)
return visitor.getMap()
class Module:
"""This represents a python module.
"""
def __init__(self, path):
mod = compiler.parseFile(path)
self._path = path
self._map = findImports(mod)
dottednames = {}
self._dottednames = findDottedNames(mod)
def getPath(self):
"""Return the path to this module's file.
"""
return self._path
def getImportedModuleNames(self):
"""Return the names of imported modules.
"""
return self._map.keys()
def getImportNames(self):
"""Return the names of imports; add dottednames as well.
"""
result = []
map = self._map
for module_name in map.keys():
for usedname, originalname in map[module_name]['names'].items():
result.append((originalname, module_name))
# add any other name that we could be using
for dottedname in self._dottednames:
usednamedot = usedname + '.'
if dottedname.startswith(usednamedot):
attrname = dottedname[len(usednamedot):].split('.')[0]
result.append((attrname, module_name))
return result
def getUnusedImports(self):
"""Get unused imports of this module (the whole import info).
"""
result = []
for value in self._map.values():
for usedname, originalname in value['names'].items():
if usedname not in self._dottednames:
result.append((originalname, value['lineno']))
return result
class ModuleFinder:
def __init__(self):
self._files = []
def visit(self, arg, dirname, names):
"""This method will be called when we walk the filesystem
tree. It looks for python modules and stored their filenames.
"""
for name in names:
# get all .py files that aren't weirdo emacs droppings
if name.endswith('.py') and not name.startswith('.#'):
self._files.append(os.path.join(dirname, name))
def getModuleFilenames(self):
return self._files
def findModules(path):
"""Find python modules in the given path and return their absolute
filenames in a sequence.
"""
finder = ModuleFinder()
os.path.walk(path, finder.visit, ())
return finder.getModuleFilenames()
class ImportDatabase:
"""This database keeps tracks of imports.
It allows to NOT report cases where a module imports something
just so that another module can import it (import dependencies).
"""
def __init__(self, root_path):
self._root_path = root_path
self._modules = {}
self._names = {}
def resolveDottedModuleName(self, dotted_name, module):
"""Return path to file representing module, or None if no such
thing. Can do this relative from module.
"""
dotted_path = dotted_name.replace('.', '/')
# try relative import first
path = os.path.join(os.path.dirname(module.getPath()), dotted_path)
path = self._resolveHelper(path)
if path is not None:
return path
# absolute import (assumed to be from this tree)
if os.path.isfile(os.path.join(self._root_path, '__init__.py')):
startpath, dummy = os.path.split(self._root_path)
else:
startpath = self._root_path
return self._resolveHelper(os.path.join(startpath, dotted_path))
def _resolveHelper(self, path):
if os.path.isfile(path + '.py'):
return path + '.py'
if os.path.isdir(path):
path = os.path.join(path, '__init__.py')
if os.path.isfile(path):
return path
return None
def findModules(self):
"""Find modules in the given path.
"""
for modulepath in findModules(self._root_path):
module = Module(modulepath)
self.addModule(module)
def addModule(self, module):
"""Add information about a module to the database. A module in
this case is not a python module object, but an instance of
the above defined Module class.w
"""
self_path = module.getPath()
# do nothing if we already know about it
if self._modules.has_key(self_path):
return
self._modules[self_path] = module
# add imported names to internal names mapping; this will
# allow us identify dependent imports later
names = self._names
for name, from_module_name in module.getImportNames():
path = self.resolveDottedModuleName(from_module_name, module)
t = (path, name)
modulepaths = names.get(t, {})
if not modulepaths.has_key(self_path):
modulepaths[self_path] = 1
names[t] = modulepaths
def getUnusedImports(self):
"""Get unused imports of all known modules.
"""
result = {}
for path, module in self._modules.items():
result[path] = self.getUnusedImportsInModule(module)
return result
def getUnusedImportsInModule(self, module):
"""Get all unused imports in a module.
"""
result = []
for name, lineno in module.getUnusedImports():
if not self.isNameImportedFrom(name, module):
result.append((name, lineno))
return result
def isNameImportedFrom(self, name, module):
"""Return true if name is imported from module by another module.
"""
return self._names.has_key((module.getPath(), name))
def getModulesImportingNameFrom(self, name, module):
"""Return list of known modules that import name from module.
"""
result = []
for path in self._names.get((module.getPath(), name), {}).keys():
result.append(self._modules[path])
return result
def main():
try:
path = sys.argv[1]
except IndexError:
print "No path supplied"
sys.exit(1)
path = os.path.abspath(path)
if not os.path.isdir(path):
print "Unknown path:", path
sys.exit(1)
l = len(path) + 1
db = ImportDatabase(path)
db.findModules()
unused_imports = db.getUnusedImports()
module_paths = unused_imports.keys()
module_paths.sort()
for path in module_paths:
info = unused_imports[path]
path = path[l:]
if not info:
continue
line2names = {}
for name, line in info:
names = line2names.get(line, [])
names.append(name)
line2names[line] = names
lines = line2names.keys()
lines.sort()
for line in lines:
names = ', '.join(line2names[line])
print "%s:%s: %s" % (path, line, names)
if __name__ == '__main__':
main()
More information about the Zope3-Checkins
mailing list