| #! /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)
|