17db96d56Sopenharmony_ci#! /usr/bin/env python3 27db96d56Sopenharmony_ci 37db96d56Sopenharmony_ci# pdeps 47db96d56Sopenharmony_ci# 57db96d56Sopenharmony_ci# Find dependencies between a bunch of Python modules. 67db96d56Sopenharmony_ci# 77db96d56Sopenharmony_ci# Usage: 87db96d56Sopenharmony_ci# pdeps file1.py file2.py ... 97db96d56Sopenharmony_ci# 107db96d56Sopenharmony_ci# Output: 117db96d56Sopenharmony_ci# Four tables separated by lines like '--- Closure ---': 127db96d56Sopenharmony_ci# 1) Direct dependencies, listing which module imports which other modules 137db96d56Sopenharmony_ci# 2) The inverse of (1) 147db96d56Sopenharmony_ci# 3) Indirect dependencies, or the closure of the above 157db96d56Sopenharmony_ci# 4) The inverse of (3) 167db96d56Sopenharmony_ci# 177db96d56Sopenharmony_ci# To do: 187db96d56Sopenharmony_ci# - command line options to select output type 197db96d56Sopenharmony_ci# - option to automatically scan the Python library for referenced modules 207db96d56Sopenharmony_ci# - option to limit output to particular modules 217db96d56Sopenharmony_ci 227db96d56Sopenharmony_ci 237db96d56Sopenharmony_ciimport sys 247db96d56Sopenharmony_ciimport re 257db96d56Sopenharmony_ciimport os 267db96d56Sopenharmony_ci 277db96d56Sopenharmony_ci 287db96d56Sopenharmony_ci# Main program 297db96d56Sopenharmony_ci# 307db96d56Sopenharmony_cidef main(): 317db96d56Sopenharmony_ci args = sys.argv[1:] 327db96d56Sopenharmony_ci if not args: 337db96d56Sopenharmony_ci print('usage: pdeps file.py file.py ...') 347db96d56Sopenharmony_ci return 2 357db96d56Sopenharmony_ci # 367db96d56Sopenharmony_ci table = {} 377db96d56Sopenharmony_ci for arg in args: 387db96d56Sopenharmony_ci process(arg, table) 397db96d56Sopenharmony_ci # 407db96d56Sopenharmony_ci print('--- Uses ---') 417db96d56Sopenharmony_ci printresults(table) 427db96d56Sopenharmony_ci # 437db96d56Sopenharmony_ci print('--- Used By ---') 447db96d56Sopenharmony_ci inv = inverse(table) 457db96d56Sopenharmony_ci printresults(inv) 467db96d56Sopenharmony_ci # 477db96d56Sopenharmony_ci print('--- Closure of Uses ---') 487db96d56Sopenharmony_ci reach = closure(table) 497db96d56Sopenharmony_ci printresults(reach) 507db96d56Sopenharmony_ci # 517db96d56Sopenharmony_ci print('--- Closure of Used By ---') 527db96d56Sopenharmony_ci invreach = inverse(reach) 537db96d56Sopenharmony_ci printresults(invreach) 547db96d56Sopenharmony_ci # 557db96d56Sopenharmony_ci return 0 567db96d56Sopenharmony_ci 577db96d56Sopenharmony_ci 587db96d56Sopenharmony_ci# Compiled regular expressions to search for import statements 597db96d56Sopenharmony_ci# 607db96d56Sopenharmony_cim_import = re.compile('^[ \t]*from[ \t]+([^ \t]+)[ \t]+') 617db96d56Sopenharmony_cim_from = re.compile('^[ \t]*import[ \t]+([^#]+)') 627db96d56Sopenharmony_ci 637db96d56Sopenharmony_ci 647db96d56Sopenharmony_ci# Collect data from one file 657db96d56Sopenharmony_ci# 667db96d56Sopenharmony_cidef process(filename, table): 677db96d56Sopenharmony_ci with open(filename, encoding='utf-8') as fp: 687db96d56Sopenharmony_ci mod = os.path.basename(filename) 697db96d56Sopenharmony_ci if mod[-3:] == '.py': 707db96d56Sopenharmony_ci mod = mod[:-3] 717db96d56Sopenharmony_ci table[mod] = list = [] 727db96d56Sopenharmony_ci while 1: 737db96d56Sopenharmony_ci line = fp.readline() 747db96d56Sopenharmony_ci if not line: break 757db96d56Sopenharmony_ci while line[-1:] == '\\': 767db96d56Sopenharmony_ci nextline = fp.readline() 777db96d56Sopenharmony_ci if not nextline: break 787db96d56Sopenharmony_ci line = line[:-1] + nextline 797db96d56Sopenharmony_ci m_found = m_import.match(line) or m_from.match(line) 807db96d56Sopenharmony_ci if m_found: 817db96d56Sopenharmony_ci (a, b), (a1, b1) = m_found.regs[:2] 827db96d56Sopenharmony_ci else: continue 837db96d56Sopenharmony_ci words = line[a1:b1].split(',') 847db96d56Sopenharmony_ci # print '#', line, words 857db96d56Sopenharmony_ci for word in words: 867db96d56Sopenharmony_ci word = word.strip() 877db96d56Sopenharmony_ci if word not in list: 887db96d56Sopenharmony_ci list.append(word) 897db96d56Sopenharmony_ci 907db96d56Sopenharmony_ci 917db96d56Sopenharmony_ci# Compute closure (this is in fact totally general) 927db96d56Sopenharmony_ci# 937db96d56Sopenharmony_cidef closure(table): 947db96d56Sopenharmony_ci modules = list(table.keys()) 957db96d56Sopenharmony_ci # 967db96d56Sopenharmony_ci # Initialize reach with a copy of table 977db96d56Sopenharmony_ci # 987db96d56Sopenharmony_ci reach = {} 997db96d56Sopenharmony_ci for mod in modules: 1007db96d56Sopenharmony_ci reach[mod] = table[mod][:] 1017db96d56Sopenharmony_ci # 1027db96d56Sopenharmony_ci # Iterate until no more change 1037db96d56Sopenharmony_ci # 1047db96d56Sopenharmony_ci change = 1 1057db96d56Sopenharmony_ci while change: 1067db96d56Sopenharmony_ci change = 0 1077db96d56Sopenharmony_ci for mod in modules: 1087db96d56Sopenharmony_ci for mo in reach[mod]: 1097db96d56Sopenharmony_ci if mo in modules: 1107db96d56Sopenharmony_ci for m in reach[mo]: 1117db96d56Sopenharmony_ci if m not in reach[mod]: 1127db96d56Sopenharmony_ci reach[mod].append(m) 1137db96d56Sopenharmony_ci change = 1 1147db96d56Sopenharmony_ci # 1157db96d56Sopenharmony_ci return reach 1167db96d56Sopenharmony_ci 1177db96d56Sopenharmony_ci 1187db96d56Sopenharmony_ci# Invert a table (this is again totally general). 1197db96d56Sopenharmony_ci# All keys of the original table are made keys of the inverse, 1207db96d56Sopenharmony_ci# so there may be empty lists in the inverse. 1217db96d56Sopenharmony_ci# 1227db96d56Sopenharmony_cidef inverse(table): 1237db96d56Sopenharmony_ci inv = {} 1247db96d56Sopenharmony_ci for key in table.keys(): 1257db96d56Sopenharmony_ci if key not in inv: 1267db96d56Sopenharmony_ci inv[key] = [] 1277db96d56Sopenharmony_ci for item in table[key]: 1287db96d56Sopenharmony_ci store(inv, item, key) 1297db96d56Sopenharmony_ci return inv 1307db96d56Sopenharmony_ci 1317db96d56Sopenharmony_ci 1327db96d56Sopenharmony_ci# Store "item" in "dict" under "key". 1337db96d56Sopenharmony_ci# The dictionary maps keys to lists of items. 1347db96d56Sopenharmony_ci# If there is no list for the key yet, it is created. 1357db96d56Sopenharmony_ci# 1367db96d56Sopenharmony_cidef store(dict, key, item): 1377db96d56Sopenharmony_ci if key in dict: 1387db96d56Sopenharmony_ci dict[key].append(item) 1397db96d56Sopenharmony_ci else: 1407db96d56Sopenharmony_ci dict[key] = [item] 1417db96d56Sopenharmony_ci 1427db96d56Sopenharmony_ci 1437db96d56Sopenharmony_ci# Tabulate results neatly 1447db96d56Sopenharmony_ci# 1457db96d56Sopenharmony_cidef printresults(table): 1467db96d56Sopenharmony_ci modules = sorted(table.keys()) 1477db96d56Sopenharmony_ci maxlen = 0 1487db96d56Sopenharmony_ci for mod in modules: maxlen = max(maxlen, len(mod)) 1497db96d56Sopenharmony_ci for mod in modules: 1507db96d56Sopenharmony_ci list = sorted(table[mod]) 1517db96d56Sopenharmony_ci print(mod.ljust(maxlen), ':', end=' ') 1527db96d56Sopenharmony_ci if mod in list: 1537db96d56Sopenharmony_ci print('(*)', end=' ') 1547db96d56Sopenharmony_ci for ref in list: 1557db96d56Sopenharmony_ci print(ref, end=' ') 1567db96d56Sopenharmony_ci print() 1577db96d56Sopenharmony_ci 1587db96d56Sopenharmony_ci 1597db96d56Sopenharmony_ci# Call main and honor exit status 1607db96d56Sopenharmony_ciif __name__ == '__main__': 1617db96d56Sopenharmony_ci try: 1627db96d56Sopenharmony_ci sys.exit(main()) 1637db96d56Sopenharmony_ci except KeyboardInterrupt: 1647db96d56Sopenharmony_ci sys.exit(1) 165