cpython/Tools/scripts/pdeps.py

165 lines
3.9 KiB
Python
Raw Normal View History

#! /usr/bin/env python3
1991-06-04 17:36:54 -03:00
# pdeps
#
# Find dependencies between a bunch of Python modules.
#
# Usage:
2001-01-17 04:48:39 -04:00
# pdeps file1.py file2.py ...
1991-06-04 17:36:54 -03:00
#
# 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
1992-12-09 20:00:58 -04:00
import os
1991-06-04 17:36:54 -03:00
# Main program
#
def main():
2001-01-17 04:48:39 -04:00
args = sys.argv[1:]
if not args:
print('usage: pdeps file.py file.py ...')
2001-01-17 04:48:39 -04:00
return 2
#
table = {}
for arg in args:
process(arg, table)
#
print('--- Uses ---')
2001-01-17 04:48:39 -04:00
printresults(table)
#
print('--- Used By ---')
2001-01-17 04:48:39 -04:00
inv = inverse(table)
printresults(inv)
#
print('--- Closure of Uses ---')
2001-01-17 04:48:39 -04:00
reach = closure(table)
printresults(reach)
#
print('--- Closure of Used By ---')
2001-01-17 04:48:39 -04:00
invreach = inverse(reach)
printresults(invreach)
#
return 0
1991-06-04 17:36:54 -03:00
# Compiled regular expressions to search for import statements
#
m_import = re.compile('^[ \t]*from[ \t]+([^ \t]+)[ \t]+')
m_from = re.compile('^[ \t]*import[ \t]+([^#]+)')
1991-06-04 17:36:54 -03:00
# Collect data from one file
#
def process(filename, table):
with open(filename, encoding='utf-8') as fp:
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
m_found = m_import.match(line) or m_from.match(line)
if m_found:
(a, b), (a1, b1) = m_found.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)
1991-06-04 17:36:54 -03:00
# Compute closure (this is in fact totally general)
#
def closure(table):
2008-05-16 12:23:30 -03:00
modules = list(table.keys())
2001-01-17 04:48:39 -04:00
#
# 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
1991-06-04 17:36:54 -03:00
# 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):
2001-01-17 04:48:39 -04:00
inv = {}
for key in table.keys():
if key not in inv:
2001-01-17 04:48:39 -04:00
inv[key] = []
for item in table[key]:
store(inv, item, key)
return inv
1991-06-04 17:36:54 -03:00
# 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):
2008-05-16 12:23:30 -03:00
if key in dict:
2001-01-17 04:48:39 -04:00
dict[key].append(item)
else:
dict[key] = [item]
1991-06-04 17:36:54 -03:00
# Tabulate results neatly
#
def printresults(table):
2008-05-16 12:23:30 -03:00
modules = sorted(table.keys())
2001-01-17 04:48:39 -04:00
maxlen = 0
for mod in modules: maxlen = max(maxlen, len(mod))
for mod in modules:
2008-05-16 12:23:30 -03:00
list = sorted(table[mod])
print(mod.ljust(maxlen), ':', end=' ')
2001-01-17 04:48:39 -04:00
if mod in list:
print('(*)', end=' ')
2001-01-17 04:48:39 -04:00
for ref in list:
print(ref, end=' ')
print()
1991-06-04 17:36:54 -03:00
# Call main and honor exit status
if __name__ == '__main__':
try:
sys.exit(main())
except KeyboardInterrupt:
sys.exit(1)