|
| 1 | +# Determine the names and filenames of the modules imported by a |
| 2 | +# script, recursively. This is done by scanning for lines containing |
| 3 | +# import statements. (The scanning has only superficial knowledge of |
| 4 | +# Python syntax and no knowledge of semantics, so in theory the result |
| 5 | +# may be incorrect -- however this is quite unlikely if you don't |
| 6 | +# intentionally obscure your Python code.) |
| 7 | + |
| 8 | +import os |
| 9 | +import regex |
| 10 | +import string |
| 11 | +import sys |
| 12 | + |
| 13 | + |
| 14 | +# Top-level interface. |
| 15 | +# First argument is the main program (script). |
| 16 | +# Second optional argument is list of modules to be searched as well. |
| 17 | + |
| 18 | +def findmodules(scriptfile, modules = [], path = sys.path): |
| 19 | + todo = {} |
| 20 | + todo['__main__'] = scriptfile |
| 21 | + for name in modules: |
| 22 | + mod = os.path.basename(name) |
| 23 | + if mod[-3:] == '.py': mod = mod[:-3] |
| 24 | + todo[mod] = name |
| 25 | + done = closure(todo) |
| 26 | + return done |
| 27 | + |
| 28 | + |
| 29 | +# Compute the closure of scanfile() and findmodule(). |
| 30 | +# Return a dictionary mapping module names to filenames. |
| 31 | +# Writes to stderr if a file can't be or read. |
| 32 | + |
| 33 | +def closure(todo): |
| 34 | + done = {} |
| 35 | + while todo: |
| 36 | + newtodo = {} |
| 37 | + for modname in todo.keys(): |
| 38 | + if not done.has_key(modname): |
| 39 | + filename = todo[modname] |
| 40 | + if filename is None: |
| 41 | + filename = findmodule(modname) |
| 42 | + done[modname] = filename |
| 43 | + if filename in ('<builtin>', '<unknown>'): |
| 44 | + continue |
| 45 | + try: |
| 46 | + modules = scanfile(filename) |
| 47 | + except IOError, msg: |
| 48 | + sys.stderr.write("%s: %s\n" % |
| 49 | + (filename, str(msg))) |
| 50 | + continue |
| 51 | + for m in modules: |
| 52 | + if not done.has_key(m): |
| 53 | + newtodo[m] = None |
| 54 | + todo = newtodo |
| 55 | + return done |
| 56 | + |
| 57 | + |
| 58 | +# Scan a file looking for import statements. |
| 59 | +# Return list of module names. |
| 60 | +# Can raise IOError. |
| 61 | + |
| 62 | +importstr = '\(^\|:\)[ \t]*import[ \t]+\([a-zA-Z0-9_, \t]+\)' |
| 63 | +fromstr = '\(^\|:\)[ \t]*from[ \t]+\([a-zA-Z0-9_]+\)[ \t]+import[ \t]+' |
| 64 | +isimport = regex.compile(importstr) |
| 65 | +isfrom = regex.compile(fromstr) |
| 66 | + |
| 67 | +def scanfile(filename): |
| 68 | + allmodules = {} |
| 69 | + f = open(filename, 'r') |
| 70 | + try: |
| 71 | + while 1: |
| 72 | + line = f.readline() |
| 73 | + if not line: break # EOF |
| 74 | + while line[-2:] == '\\\n': # Continuation line |
| 75 | + line = line[:-2] + ' ' |
| 76 | + line = line + f.readline() |
| 77 | + if isimport.search(line) >= 0: |
| 78 | + rawmodules = isimport.group(2) |
| 79 | + modules = string.splitfields(rawmodules, ',') |
| 80 | + for i in range(len(modules)): |
| 81 | + modules[i] = string.strip(modules[i]) |
| 82 | + elif isfrom.search(line) >= 0: |
| 83 | + modules = [isfrom.group(2)] |
| 84 | + else: |
| 85 | + continue |
| 86 | + for mod in modules: |
| 87 | + allmodules[mod] = None |
| 88 | + finally: |
| 89 | + f.close() |
| 90 | + return allmodules.keys() |
| 91 | + |
| 92 | + |
| 93 | +# Find the file containing a module, given its name. |
| 94 | +# Return filename, or '<builtin>', or '<unknown>'. |
| 95 | + |
| 96 | +builtins = sys.builtin_module_names |
| 97 | +if 'sys' not in builtins: builtins.append('sys') |
| 98 | +# XXX this table may have to be changed depending on your platform: |
| 99 | +tails = ['.so', 'module.so', '.py', '.pyc'] |
| 100 | + |
| 101 | +def findmodule(modname, path = sys.path): |
| 102 | + if modname in builtins: return '<builtin>' |
| 103 | + for dirname in path: |
| 104 | + for tail in tails: |
| 105 | + fullname = os.path.join(dirname, modname + tail) |
| 106 | + try: |
| 107 | + f = open(fullname, 'r') |
| 108 | + except IOError: |
| 109 | + continue |
| 110 | + f.close() |
| 111 | + return fullname |
| 112 | + return '<unknown>' |
| 113 | + |
| 114 | + |
| 115 | +# Test the above functions. |
| 116 | + |
| 117 | +def test(): |
| 118 | + if not sys.argv[1:]: |
| 119 | + print 'usage: python findmodules.py scriptfile [morefiles ...]' |
| 120 | + sys.exit(2) |
| 121 | + done = findmodules(sys.argv[1], sys.argv[2:]) |
| 122 | + items = done.items() |
| 123 | + items.sort() |
| 124 | + for mod, file in [('Module', 'File')] + items: |
| 125 | + print "%-15s %s" % (mod, file) |
| 126 | + |
| 127 | +if __name__ == '__main__': |
| 128 | + test() |
0 commit comments