1 #! /usr/bin/env python 2 3 # pdeps 4 # 5 # Find dependencies between a bunch of Python modules. 6 # 7 # Usage: 8 # pdeps file1.py file2.py ... 9 # 10 # Output: 11 # Four tables separated by lines like '--- Closure ---': 12 # 1) Direct dependencies, listing which module imports which other modules 13 # 2) The inverse of (1) 14 # 3) Indirect dependencies, or the closure of the above 15 # 4) The inverse of (3) 16 # 17 # To do: 18 # - command line options to select output type 19 # - option to automatically scan the Python library for referenced modules 20 # - option to limit output to particular modules 21 22 23 import sys 24 import re 25 import os 26 27 28 # Main program 29 # 30 def main(): 31 args = sys.argv[1:] 32 if not args: 33 print 'usage: pdeps file.py file.py ...' 34 return 2 35 # 36 table = {} 37 for arg in args: 38 process(arg, table) 39 # 40 print '--- Uses ---' 41 printresults(table) 42 # 43 print '--- Used By ---' 44 inv = inverse(table) 45 printresults(inv) 46 # 47 print '--- Closure of Uses ---' 48 reach = closure(table) 49 printresults(reach) 50 # 51 print '--- Closure of Used By ---' 52 invreach = inverse(reach) 53 printresults(invreach) 54 # 55 return 0 56 57 58 # Compiled regular expressions to search for import statements 59 # 60 m_import = re.compile('^[ \t]*from[ \t]+([^ \t]+)[ \t]+') 61 m_from = re.compile('^[ \t]*import[ \t]+([^#]+)') 62 63 64 # Collect data from one file 65 # 66 def process(filename, table): 67 fp = open(filename, 'r') 68 mod = os.path.basename(filename) 69 if mod[-3:] == '.py': 70 mod = mod[:-3] 71 table[mod] = list = [] 72 while 1: 73 line = fp.readline() 74 if not line: break 75 while line[-1:] == '\\': 76 nextline = fp.readline() 77 if not nextline: break 78 line = line[:-1] + nextline 79 if m_import.match(line) >= 0: 80 (a, b), (a1, b1) = m_import.regs[:2] 81 elif m_from.match(line) >= 0: 82 (a, b), (a1, b1) = m_from.regs[:2] 83 else: continue 84 words = line[a1:b1].split(',') 85 # print '#', line, words 86 for word in words: 87 word = word.strip() 88 if word not in list: 89 list.append(word) 90 91 92 # Compute closure (this is in fact totally general) 93 # 94 def closure(table): 95 modules = table.keys() 96 # 97 # Initialize reach with a copy of table 98 # 99 reach = {} 100 for mod in modules: 101 reach[mod] = table[mod][:] 102 # 103 # Iterate until no more change 104 # 105 change = 1 106 while change: 107 change = 0 108 for mod in modules: 109 for mo in reach[mod]: 110 if mo in modules: 111 for m in reach[mo]: 112 if m not in reach[mod]: 113 reach[mod].append(m) 114 change = 1 115 # 116 return reach 117 118 119 # Invert a table (this is again totally general). 120 # All keys of the original table are made keys of the inverse, 121 # so there may be empty lists in the inverse. 122 # 123 def inverse(table): 124 inv = {} 125 for key in table.keys(): 126 if not inv.has_key(key): 127 inv[key] = [] 128 for item in table[key]: 129 store(inv, item, key) 130 return inv 131 132 133 # Store "item" in "dict" under "key". 134 # The dictionary maps keys to lists of items. 135 # If there is no list for the key yet, it is created. 136 # 137 def store(dict, key, item): 138 if dict.has_key(key): 139 dict[key].append(item) 140 else: 141 dict[key] = [item] 142 143 144 # Tabulate results neatly 145 # 146 def printresults(table): 147 modules = table.keys() 148 maxlen = 0 149 for mod in modules: maxlen = max(maxlen, len(mod)) 150 modules.sort() 151 for mod in modules: 152 list = table[mod] 153 list.sort() 154 print mod.ljust(maxlen), ':', 155 if mod in list: 156 print '(*)', 157 for ref in list: 158 print ref, 159 print 160 161 162 # Call main and honor exit status 163 if __name__ == '__main__': 164 try: 165 sys.exit(main()) 166 except KeyboardInterrupt: 167 sys.exit(1) 168