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 23import sys 24import re 25import os 26 27 28# Main program 29# 30def 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# 60m_import = re.compile('^[ \t]*from[ \t]+([^ \t]+)[ \t]+') 61m_from = re.compile('^[ \t]*import[ \t]+([^#]+)') 62 63 64# Collect data from one file 65# 66def 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# 94def 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# 123def 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# 137def 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# 146def 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 163if __name__ == '__main__': 164 try: 165 sys.exit(main()) 166 except KeyboardInterrupt: 167 sys.exit(1) 168