Initial revision
diff --git a/Tools/scripts/pdeps.py b/Tools/scripts/pdeps.py
new file mode 100755
index 0000000..2533015
--- /dev/null
+++ b/Tools/scripts/pdeps.py
@@ -0,0 +1,167 @@
+#! /usr/local/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 regexp
+import path
+import string
+
+
+# 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 = regexp.compile('^[ \t]*from[ \t]+([^ \t]+)[ \t]+')
+m_from = regexp.compile('^[ \t]*import[ \t]+([^#]+)')
+
+
+# Collect data from one file
+#
+def process(filename, table):
+	fp = open(filename, 'r')
+	mod = 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
+		result = m_import.exec(line)
+		if not result:
+			result = m_from.exec(line)
+		if result:
+			(a, b), (a1, b1) = result
+			words = string.splitfields(line[a1:b1], ',')
+			# print '#', line, words
+			for word in words:
+				word = string.strip(word)
+				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 string.ljust(mod, maxlen), ':',
+		if mod in list:
+			print '(*)',
+		for ref in list:
+			print ref,
+		print
+
+
+# Call main and honor exit status
+try:
+	sys.exit(main())
+except KeyboardInterrupt:
+	sys.exit(1)