Apparently the code to forestall Tk eating events was too aggressive (Tk user input...
[python/dscho.git] / Tools / scripts / pdeps.py
blob18b582b693e06924842f1c048f3872389decd7e6
1 #! /usr/bin/env python
3 # pdeps
5 # Find dependencies between a bunch of Python modules.
7 # Usage:
8 # pdeps file1.py file2.py ...
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)
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
23 import sys
24 import regex
25 import os
26 import string
29 # Main program
31 def main():
32 args = sys.argv[1:]
33 if not args:
34 print 'usage: pdeps file.py file.py ...'
35 return 2
37 table = {}
38 for arg in args:
39 process(arg, table)
41 print '--- Uses ---'
42 printresults(table)
44 print '--- Used By ---'
45 inv = inverse(table)
46 printresults(inv)
48 print '--- Closure of Uses ---'
49 reach = closure(table)
50 printresults(reach)
52 print '--- Closure of Used By ---'
53 invreach = inverse(reach)
54 printresults(invreach)
56 return 0
59 # Compiled regular expressions to search for import statements
61 m_import = regex.compile('^[ \t]*from[ \t]+\([^ \t]+\)[ \t]+')
62 m_from = regex.compile('^[ \t]*import[ \t]+\([^#]+\)')
65 # Collect data from one file
67 def process(filename, table):
68 fp = open(filename, 'r')
69 mod = os.path.basename(filename)
70 if mod[-3:] == '.py':
71 mod = mod[:-3]
72 table[mod] = list = []
73 while 1:
74 line = fp.readline()
75 if not line: break
76 while line[-1:] == '\\':
77 nextline = fp.readline()
78 if not nextline: break
79 line = line[:-1] + nextline
80 if m_import.match(line) >= 0:
81 (a, b), (a1, b1) = m_import.regs[:2]
82 elif m_from.match(line) >= 0:
83 (a, b), (a1, b1) = m_from.regs[:2]
84 else: continue
85 words = string.splitfields(line[a1:b1], ',')
86 # print '#', line, words
87 for word in words:
88 word = string.strip(word)
89 if word not in list:
90 list.append(word)
93 # Compute closure (this is in fact totally general)
95 def closure(table):
96 modules = table.keys()
98 # Initialize reach with a copy of table
100 reach = {}
101 for mod in modules:
102 reach[mod] = table[mod][:]
104 # Iterate until no more change
106 change = 1
107 while change:
108 change = 0
109 for mod in modules:
110 for mo in reach[mod]:
111 if mo in modules:
112 for m in reach[mo]:
113 if m not in reach[mod]:
114 reach[mod].append(m)
115 change = 1
117 return reach
120 # Invert a table (this is again totally general).
121 # All keys of the original table are made keys of the inverse,
122 # so there may be empty lists in the inverse.
124 def inverse(table):
125 inv = {}
126 for key in table.keys():
127 if not inv.has_key(key):
128 inv[key] = []
129 for item in table[key]:
130 store(inv, item, key)
131 return inv
134 # Store "item" in "dict" under "key".
135 # The dictionary maps keys to lists of items.
136 # If there is no list for the key yet, it is created.
138 def store(dict, key, item):
139 if dict.has_key(key):
140 dict[key].append(item)
141 else:
142 dict[key] = [item]
145 # Tabulate results neatly
147 def printresults(table):
148 modules = table.keys()
149 maxlen = 0
150 for mod in modules: maxlen = max(maxlen, len(mod))
151 modules.sort()
152 for mod in modules:
153 list = table[mod]
154 list.sort()
155 print string.ljust(mod, maxlen), ':',
156 if mod in list:
157 print '(*)',
158 for ref in list:
159 print ref,
160 print
163 # Call main and honor exit status
164 try:
165 sys.exit(main())
166 except KeyboardInterrupt:
167 sys.exit(1)