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