|
#! /usr/bin/env python |
|
|
|
# pdeps |
|
# |
|
# Find dependencies between a bunch of Python modules. |
|
# |
|
# Usage: |
|
# pdeps file1.py file2.py ... |
|
# |
|
# Output: |
|
# Four tables separated by lines like '--- Closure ---': |
|
# 1) Direct dependencies, listing which module imports which other modules |
|
# 2) The inverse of (1) |
|
# 3) Indirect dependencies, or the closure of the above |
|
# 4) The inverse of (3) |
|
# |
|
# To do: |
|
# - command line options to select output type |
|
# - option to automatically scan the Python library for referenced modules |
|
# - option to limit output to particular modules |
|
|
|
|
|
import sys |
|
import re |
|
import os |
|
|
|
|
|
# Main program |
|
# |
|
def main(): |
|
args = sys.argv[1:] |
|
if not args: |
|
print 'usage: pdeps file.py file.py ...' |
|
return 2 |
|
# |
|
table = {} |
|
for arg in args: |
|
process(arg, table) |
|
# |
|
print '--- Uses ---' |
|
printresults(table) |
|
# |
|
print '--- Used By ---' |
|
inv = inverse(table) |
|
printresults(inv) |
|
# |
|
print '--- Closure of Uses ---' |
|
reach = closure(table) |
|
printresults(reach) |
|
# |
|
print '--- Closure of Used By ---' |
|
invreach = inverse(reach) |
|
printresults(invreach) |
|
# |
|
return 0 |
|
|
|
|
|
# Compiled regular expressions to search for import statements |
|
# |
|
m_import = re.compile('^[ \t]*from[ \t]+([^ \t]+)[ \t]+') |
|
m_from = re.compile('^[ \t]*import[ \t]+([^#]+)') |
|
|
|
|
|
# Collect data from one file |
|
# |
|
def process(filename, table): |
|
fp = open(filename, 'r') |
|
mod = os.path.basename(filename) |
|
if mod[-3:] == '.py': |
|
mod = mod[:-3] |
|
table[mod] = list = [] |
|
while 1: |
|
line = fp.readline() |
|
if not line: break |
|
while line[-1:] == '\\': |
|
nextline = fp.readline() |
|
if not nextline: break |
|
line = line[:-1] + nextline |
|
if m_import.match(line) >= 0: |
|
(a, b), (a1, b1) = m_import.regs[:2] |
|
elif m_from.match(line) >= 0: |
|
(a, b), (a1, b1) = m_from.regs[:2] |
|
else: continue |
|
words = line[a1:b1].split(',') |
|
# print '#', line, words |
|
for word in words: |
|
word = word.strip() |
|
if word not in list: |
|
list.append(word) |
|
|
|
|
|
# Compute closure (this is in fact totally general) |
|
# |
|
def closure(table): |
|
modules = table.keys() |
|
# |
|
# Initialize reach with a copy of table |
|
# |
|
reach = {} |
|
for mod in modules: |
|
reach[mod] = table[mod][:] |
|
# |
|
# Iterate until no more change |
|
# |
|
change = 1 |
|
while change: |
|
change = 0 |
|
for mod in modules: |
|
for mo in reach[mod]: |
|
if mo in modules: |
|
for m in reach[mo]: |
|
if m not in reach[mod]: |
|
reach[mod].append(m) |
|
change = 1 |
|
# |
|
return reach |
|
|
|
|
|
# Invert a table (this is again totally general). |
|
# All keys of the original table are made keys of the inverse, |
|
# so there may be empty lists in the inverse. |
|
# |
|
def inverse(table): |
|
inv = {} |
|
for key in table.keys(): |
|
if not inv.has_key(key): |
|
inv[key] = [] |
|
for item in table[key]: |
|
store(inv, item, key) |
|
return inv |
|
|
|
|
|
# Store "item" in "dict" under "key". |
|
# The dictionary maps keys to lists of items. |
|
# If there is no list for the key yet, it is created. |
|
# |
|
def store(dict, key, item): |
|
if dict.has_key(key): |
|
dict[key].append(item) |
|
else: |
|
dict[key] = [item] |
|
|
|
|
|
# Tabulate results neatly |
|
# |
|
def printresults(table): |
|
modules = table.keys() |
|
maxlen = 0 |
|
for mod in modules: maxlen = max(maxlen, len(mod)) |
|
modules.sort() |
|
for mod in modules: |
|
list = table[mod] |
|
list.sort() |
|
print mod.ljust(maxlen), ':', |
|
if mod in list: |
|
print '(*)', |
|
for ref in list: |
|
print ref, |
|
print |
|
|
|
|
|
# Call main and honor exit status |
|
if __name__ == '__main__': |
|
try: |
|
sys.exit(main()) |
|
except KeyboardInterrupt: |
|
sys.exit(1) |