• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#! /usr/bin/env python3
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    with open(filename, encoding='utf-8') as fp:
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            m_found = m_import.match(line) or m_from.match(line)
80            if m_found:
81                (a, b), (a1, b1) = m_found.regs[:2]
82            else: continue
83            words = line[a1:b1].split(',')
84            # print '#', line, words
85            for word in words:
86                word = word.strip()
87                if word not in list:
88                    list.append(word)
89
90
91# Compute closure (this is in fact totally general)
92#
93def closure(table):
94    modules = list(table.keys())
95    #
96    # Initialize reach with a copy of table
97    #
98    reach = {}
99    for mod in modules:
100        reach[mod] = table[mod][:]
101    #
102    # Iterate until no more change
103    #
104    change = 1
105    while change:
106        change = 0
107        for mod in modules:
108            for mo in reach[mod]:
109                if mo in modules:
110                    for m in reach[mo]:
111                        if m not in reach[mod]:
112                            reach[mod].append(m)
113                            change = 1
114    #
115    return reach
116
117
118# Invert a table (this is again totally general).
119# All keys of the original table are made keys of the inverse,
120# so there may be empty lists in the inverse.
121#
122def inverse(table):
123    inv = {}
124    for key in table.keys():
125        if key not in inv:
126            inv[key] = []
127        for item in table[key]:
128            store(inv, item, key)
129    return inv
130
131
132# Store "item" in "dict" under "key".
133# The dictionary maps keys to lists of items.
134# If there is no list for the key yet, it is created.
135#
136def store(dict, key, item):
137    if key in dict:
138        dict[key].append(item)
139    else:
140        dict[key] = [item]
141
142
143# Tabulate results neatly
144#
145def printresults(table):
146    modules = sorted(table.keys())
147    maxlen = 0
148    for mod in modules: maxlen = max(maxlen, len(mod))
149    for mod in modules:
150        list = sorted(table[mod])
151        print(mod.ljust(maxlen), ':', end=' ')
152        if mod in list:
153            print('(*)', end=' ')
154        for ref in list:
155            print(ref, end=' ')
156        print()
157
158
159# Call main and honor exit status
160if __name__ == '__main__':
161    try:
162        sys.exit(main())
163    except KeyboardInterrupt:
164        sys.exit(1)
165