Merge ~cjwatson/launchpad:remove-findimports into launchpad:master

Proposed by Colin Watson
Status: Merged
Approved by: Colin Watson
Approved revision: b38c1c4ca6c7f0a918f5331a4e3111855c023d30
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~cjwatson/launchpad:remove-findimports
Merge into: launchpad:master
Diff against target: 367 lines (+0/-361)
1 file modified
dev/null (+0/-361)
Reviewer Review Type Date Requested Status
Cristian Gonzalez (community) Approve
Review via email: mp+406491@code.launchpad.net

Commit message

Remove utilities/findimports.py

Description of the change

It's just a lightly-modified old version of the `findimports` module on PyPI; we could upgrade it to a more current version and gain Python 3 support, but it isn't used by anything in our tree, so it doesn't seem worth the effort. If you need it then you should just install it from PyPI.

To post a comment you must log in.
Revision history for this message
Cristian Gonzalez (cristiangsp) wrote :

In love with all red MPs...

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/utilities/findimports.py b/utilities/findimports.py
0deleted file mode 1007550deleted file mode 100755
index 90367ed..0000000
--- a/utilities/findimports.py
+++ /dev/null
@@ -1,361 +0,0 @@
1#!/usr/bin/python2
2#
3# Copyright 2009 Canonical Ltd. This software is licensed under the
4# GNU Affero General Public License version 3 (see the file LICENSE).
5
6"""
7FindImports is a script that processes Python module dependencies. Currently
8it can be used for finding unused imports and graphing module dependencies
9(with graphviz). FindImports requires Python 2.3.
10
11Syntax: findimports.py [options] [filename|dirname ...]
12
13Options:
14 -h, --help This help message
15
16 -i, --imports Print dependency graph (default action).
17 -d, --dot Print dependency graph in dot format.
18 -n, --names Print dependency graph with all imported names.
19
20 -u, --unused Print unused imports.
21 -a, --all Print all unused imports (use together with -u).
22
23Copyright (c) 2003, 2004 Marius Gedminas <marius@pov.lt>
24
25This program is free software; you can redistribute it and/or modify it under
26the terms of the GNU General Public License as published by the Free Software
27Foundation; either version 2 of the License, or (at your option) any later
28version.
29
30This program is distributed in the hope that it will be useful, but WITHOUT ANY
31WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
32PARTICULAR PURPOSE. See the GNU General Public License for more details.
33
34You should have received a copy of the GNU General Public License along with
35this program; if not, write to the Free Software Foundation, Inc., 675 Mass
36Ave, Cambridge, MA 02139, USA.
37"""
38
39from __future__ import absolute_import, print_function
40
41import compiler
42from compiler.visitor import ASTVisitor
43import getopt
44import linecache
45import os
46import sys
47
48import six
49
50
51class ImportFinder(ASTVisitor):
52 """AST visitor that collects all imported names in its imports attribute.
53
54 For example, the following import statements in the AST tree
55
56 import a, b.c, d as e
57 from q.w.e import x, y as foo, z
58 from woof import *
59
60 will cause imports to contain
61
62 a
63 b.c
64 d
65 q.w.e.x
66 q.w.e.y
67 q.w.e.z
68 woof.*
69 """
70
71 def __init__(self):
72 self.imports = []
73
74 def visitImport(self, node):
75 for name, imported_as in node.names:
76 self.imports.append(name)
77
78 def visitFrom(self, node):
79 for name, imported_as in node.names:
80 self.imports.append('%s.%s' % (node.modname, name))
81
82
83class UnusedName(object):
84
85 def __init__(self, name, lineno):
86 self.name = name
87 self.lineno = lineno
88
89
90class ImportFinderAndNametracker(ImportFinder):
91 """ImportFinder that also keeps track on used names."""
92
93 def __init__(self):
94 ImportFinder.__init__(self)
95 self.unused_names = {}
96
97 def visitImport(self, node):
98 ImportFinder.visitImport(self, node)
99 for name, imported_as in node.names:
100 if not imported_as:
101 imported_as = name
102 if imported_as != "*":
103 self.unused_names[imported_as] = UnusedName(imported_as,
104 node.lineno)
105
106 def visitFrom(self, node):
107 ImportFinder.visitFrom(self, node)
108 for name, imported_as in node.names:
109 if not imported_as:
110 imported_as = name
111 if imported_as != "*":
112 self.unused_names[imported_as] = UnusedName(imported_as,
113 node.lineno)
114
115 def visitName(self, node):
116 if node.name in self.unused_names:
117 del self.unused_names[node.name]
118
119 def visitGetattr(self, node):
120 full_name = [node.attrname]
121 parent = node.expr
122 while isinstance(parent, compiler.ast.Getattr):
123 full_name.append(parent.attrname)
124 parent = parent.expr
125 if isinstance(parent, compiler.ast.Name):
126 full_name.append(parent.name)
127 full_name.reverse()
128 name = ""
129 for part in full_name:
130 if name: name = '%s.%s' % (name, part)
131 else: name += part
132 if name in self.unused_names:
133 del self.unused_names[name]
134 for c in node.getChildNodes():
135 self.visit(c)
136
137
138def find_imports(filename):
139 """Find all imported names in a given file."""
140 ast = compiler.parseFile(filename)
141 visitor = ImportFinder()
142 compiler.walk(ast, visitor)
143 return visitor.imports
144
145def find_imports_and_track_names(filename):
146 """Find all imported names in a given file."""
147 ast = compiler.parseFile(filename)
148 visitor = ImportFinderAndNametracker()
149 compiler.walk(ast, visitor)
150 return visitor.imports, visitor.unused_names
151
152
153class Module(object):
154
155 def __init__(self, modname, filename):
156 self.modname = modname
157 self.filename = filename
158
159
160class ModuleGraph(object):
161
162 trackUnusedNames = False
163 all_unused = False
164
165 def __init__(self):
166 self.modules = {}
167 self.path = sys.path
168 self._module_cache = {}
169 self._warned_about = set()
170
171 def parsePathname(self, pathname):
172 if os.path.isdir(pathname):
173 for root, dirs, files in os.walk(pathname):
174 for fn in files:
175 # ignore emacsish junk
176 if fn.endswith('.py') and not fn.startswith('.#'):
177 self.parseFile(os.path.join(root, fn))
178 else:
179 self.parseFile(pathname)
180
181 def parseFile(self, filename):
182 modname = self.filenameToModname(filename)
183 module = Module(modname, filename)
184 self.modules[modname] = module
185 if self.trackUnusedNames:
186 module.imported_names, module.unused_names = \
187 find_imports_and_track_names(filename)
188 else:
189 module.imported_names = find_imports(filename)
190 module.unused_names = None
191 dir = os.path.dirname(filename)
192 module.imports = set([self.findModuleOfName(name, filename, dir)
193 for name in module.imported_names])
194
195 def filenameToModname(self, filename):
196 for ext in ('.py', '.so', '.dll'):
197 if filename.endswith(ext):
198 break
199 else:
200 print(
201 "%s: unknown file name extension" % filename, file=sys.stderr)
202 longest_prefix_len = 0
203 filename = os.path.abspath(filename)
204 for prefix in self.path:
205 prefix = os.path.abspath(prefix)
206 if (filename.startswith(prefix)
207 and len(prefix) > longest_prefix_len):
208 longest_prefix_len = len(prefix)
209 filename = filename[longest_prefix_len:-len('.py')]
210 if filename.startswith(os.path.sep):
211 filename = filename[len(os.path.sep):]
212 modname = ".".join(filename.split(os.path.sep))
213 return modname
214
215 def findModuleOfName(self, dotted_name, filename, extrapath=None):
216 if dotted_name.endswith('.*'):
217 return dotted_name[:-2]
218 name = dotted_name
219 while name:
220 candidate = self.isModule(name, extrapath)
221 if candidate:
222 return candidate
223 candidate = self.isPackage(name, extrapath)
224 if candidate:
225 return candidate
226 name = name[:name.rfind('.')]
227 if dotted_name not in self._warned_about:
228 print(
229 "%s: could not find %s" % (filename, dotted_name),
230 file=sys.stderr)
231 self._warned_about.add(dotted_name)
232 return dotted_name
233
234 def isModule(self, dotted_name, extrapath=None):
235 try:
236 return self._module_cache[(dotted_name, extrapath)]
237 except KeyError:
238 pass
239 if dotted_name in sys.modules:
240 return dotted_name
241 filename = dotted_name.replace('.', os.path.sep)
242 if extrapath:
243 for ext in ('.py', '.so', '.dll'):
244 candidate = os.path.join(extrapath, filename) + ext
245 if os.path.exists(candidate):
246 modname = self.filenameToModname(candidate)
247 self._module_cache[(dotted_name, extrapath)] = modname
248 return modname
249 try:
250 return self._module_cache[(dotted_name, None)]
251 except KeyError:
252 pass
253 for dir in self.path:
254 for ext in ('.py', '.so', '.dll'):
255 candidate = os.path.join(dir, filename) + ext
256 if os.path.exists(candidate):
257 modname = self.filenameToModname(candidate)
258 self._module_cache[(dotted_name, extrapath)] = modname
259 self._module_cache[(dotted_name, None)] = modname
260 return modname
261 return None
262
263 def isPackage(self, dotted_name, extrapath=None):
264 candidate = self.isModule(dotted_name + '.__init__', extrapath)
265 if candidate:
266 candidate = candidate[:-len(".__init__")]
267 return candidate
268
269 def listModules(self):
270 modules = list(self.modules.items())
271 modules.sort()
272 return [module for name, module in modules]
273
274 def printImportedNames(self):
275 for module in self.listModules():
276 print("%s:" % module.modname)
277 print(" %s" % "\n ".join(module.imported_names))
278
279 def printImports(self):
280 for module in self.listModules():
281 print("%s:" % module.modname)
282 imports = list(module.imports)
283 imports.sort()
284 print(" %s" % "\n ".join(imports))
285
286 def printUnusedImports(self):
287 for module in self.listModules():
288 names = [(unused.lineno, unused.name)
289 for unused in six.itervalues(module.unused_names)]
290 names.sort()
291 for lineno, name in names:
292 if not self.all_unused:
293 line = linecache.getline(module.filename, lineno)
294 if '#' in line:
295 continue # assume there's a comment explaining why it
296 # is not used
297 print("%s:%s: %s not used" % (module.filename, lineno, name))
298
299 def printDot(self):
300 print("digraph ModuleDependencies {")
301 print(" node[shape=box];")
302 allNames = set()
303 nameDict = {}
304 for n, module in enumerate(self.listModules()):
305 module._dot_name = "mod%d" % n
306 nameDict[module.modname] = module._dot_name
307 print(" %s[label=\"%s\"];" % (module._dot_name, module.modname))
308 for name in module.imports:
309 if name not in self.modules:
310 allNames.add(name)
311 print(" node[style=dotted];")
312 names = list(allNames)
313 names.sort()
314 for n, name in enumerate(names):
315 nameDict[name] = id = "extmod%d" % n
316 print(" %s[label=\"%s\"];" % (id, name))
317 for module in self.modules.values():
318 for other in module.imports:
319 print(" %s -> %s;" % (nameDict[module.modname],
320 nameDict[other]))
321 print("}")
322
323
324def main(argv=sys.argv):
325 progname = os.path.basename(argv[0])
326 helptext = __doc__.strip().replace('findimports.py', progname)
327 g = ModuleGraph()
328 action = g.printImports
329 try:
330 opts, args = getopt.getopt(argv[1:], 'duniah',
331 ['dot', 'unused', 'all', 'names', 'imports',
332 'help'])
333 except getopt.error as e:
334 print("%s: %s" % (progname, e), file=sys.stderr)
335 print("Try %s --help." % progname, file=sys.stderr)
336 return 1
337 for k, v in opts:
338 if k in ('-d', '--dot'):
339 action = g.printDot
340 elif k in ('-u', '--unused'):
341 action = g.printUnusedImports
342 elif k in ('-a', '--all'):
343 g.all_unused = True
344 elif k in ('-n', '--names'):
345 action = g.printImportedNames
346 elif k in ('-i', '--imports'):
347 action = g.printImports
348 elif k in ('-h', '--help'):
349 print(helptext)
350 return 0
351 g.trackUnusedNames = (action == g.printUnusedImports)
352 if not args:
353 args = ['.']
354 for fn in args:
355 g.parsePathname(fn)
356 action()
357 return 0
358
359if __name__ == '__main__':
360 sys.exit(main())
361

Subscribers

People subscribed via source and target branches

to status/vote changes: