Initial revision
diff --git a/Tools/scripts/byteyears.py b/Tools/scripts/byteyears.py
new file mode 100755
index 0000000..8c5ec69
--- /dev/null
+++ b/Tools/scripts/byteyears.py
@@ -0,0 +1,29 @@
+#! /usr/local/python
+
+# byteyears file ...
+#
+# Print a number representing the product of age and size of each file,
+# in suitable units.
+
+import sys, posix, time
+from stat import *
+
+secs_per_year = 365.0 * 24.0 * 3600.0
+now = time.time()
+status = 0
+
+for file in sys.argv[1:]:
+	try:
+		st = posix.stat(file)
+	except posix.error, msg:
+		sys.stderr.write('can\'t stat ' + `file` + ': ' + `msg` + '\n')
+		status = 1
+		st = ()
+	if st:
+		mtime = st[ST_MTIME]
+		size = st[ST_SIZE]
+		age = now - mtime
+		byteyears = float(size) * float(age) / secs_per_year
+		print file + '\t\t' + `int(byteyears)`
+
+sys.exit(status)
diff --git a/Tools/scripts/eptags.py b/Tools/scripts/eptags.py
new file mode 100755
index 0000000..1c682d3
--- /dev/null
+++ b/Tools/scripts/eptags.py
@@ -0,0 +1,50 @@
+#! /usr/local/python
+
+# eptags
+#
+# Create a TAGS file for Python programs, usable with GNU Emacs (version 18).
+# Tagged are:
+# - functions (even inside other defs or classes)
+# - classes
+# Warns about files it cannot open.
+# No warnings about duplicate tags.
+
+import sys
+import regexp
+
+def main():
+	outfp = open('TAGS', 'w')
+	args = sys.argv[1:]
+	for file in args:
+		treat_file(file, outfp)
+
+matcher = regexp.compile('^[ \t]*(def|class)[ \t]+([a-zA-Z0-9_]+)[ \t]*\(')
+
+def treat_file(file, outfp):
+	try:
+		fp = open(file, 'r')
+	except:
+		print 'Cannot open', file
+		return
+	charno = 0
+	lineno = 0
+	tags = []
+	size = 0
+	while 1:
+		line = fp.readline()
+		if not line: break
+		lineno = lineno + 1
+		res = matcher.exec(line)
+		if res:
+			(a, b), (a1, b1), (a2, b2) = res
+			name = line[a2:b2]
+			pat = line[a:b]
+			tag = pat + '\177' + `lineno` + ',' + `charno` + '\n'
+			tags.append(name, tag)
+			size = size + len(tag)
+		charno = charno + len(line)
+	outfp.write('\f\n' + file + ',' + `size` + '\n')
+	for name, tag in tags:
+		outfp.write(tag)
+
+main()
diff --git a/Tools/scripts/findlinksto.py b/Tools/scripts/findlinksto.py
new file mode 100755
index 0000000..6a2a75b
--- /dev/null
+++ b/Tools/scripts/findlinksto.py
@@ -0,0 +1,29 @@
+#! /usr/local/python
+
+# findlinksto
+#
+# find symbolic links to a given path
+
+import posix, path, sys
+
+def visit(pattern, dirname, names):
+	if path.islink(dirname):
+		names[:] = []
+		return
+	if path.ismount(dirname):
+		print 'descend into', dirname
+	n = len(pattern)
+	for name in names:
+		name = path.cat(dirname, name)
+		try:
+			linkto = posix.readlink(name)
+			if linkto[:n] = pattern:
+				print name, '->', linkto
+		except posix.error:
+			pass
+
+def main(pattern, args):
+	for dirname in args:
+		path.walk(dirname, visit, pattern)
+
+main(sys.argv[1], sys.argv[2:])
diff --git a/Tools/scripts/mkreal.py b/Tools/scripts/mkreal.py
new file mode 100755
index 0000000..3fd4b03
--- /dev/null
+++ b/Tools/scripts/mkreal.py
@@ -0,0 +1,65 @@
+#! /usr/local/python
+
+# mkreal
+#
+# turn a symlink to a directory into a real directory
+
+import sys
+import posix
+import path
+from stat import *
+
+cat = path.cat
+
+error = 'mkreal error'
+
+BUFSIZE = 32*1024
+
+def mkrealfile(name):
+	st = posix.stat(name) # Get the mode
+	mode = S_IMODE(st[ST_MODE])
+	linkto = posix.readlink(name) # Make sure again it's a symlink
+	f_in = open(name, 'r') # This ensures it's a file
+	posix.unlink(name)
+	f_out = open(name, 'w')
+	while 1:
+		buf = f_in.read(BUFSIZE)
+		if not buf: break
+		f_out.write(buf)
+	del f_out # Flush data to disk before changing mode
+	posix.chmod(name, mode)
+
+def mkrealdir(name):
+	st = posix.stat(name) # Get the mode
+	mode = S_IMODE(st[ST_MODE])
+	linkto = posix.readlink(name)
+	files = posix.listdir(name)
+	posix.unlink(name)
+	posix.mkdir(name, mode)
+	posix.chmod(name, mode)
+	linkto = cat('..', linkto)
+	#
+	for file in files:
+		if file not in ('.', '..'):
+			posix.symlink(cat(linkto, file), cat(name, file))
+
+def main():
+	sys.stdout = sys.stderr
+	progname = path.basename(sys.argv[0])
+	args = sys.argv[1:]
+	if not args:
+		print 'usage:', progname, 'path ...'
+		sys.exit(2)
+	status = 0
+	for name in args:
+		if not path.islink(name):
+			print progname+':', name+':', 'not a symlink'
+			status = 1
+		else:
+			if path.isdir(name):
+				mkrealdir(name)
+			else:
+				mkrealfile(name)
+	sys.exit(status)
+
+main()
diff --git a/Tools/scripts/objgraph.py b/Tools/scripts/objgraph.py
new file mode 100755
index 0000000..b45bba2
--- /dev/null
+++ b/Tools/scripts/objgraph.py
@@ -0,0 +1,213 @@
+#!/usr/local/python
+
+# objgraph
+#
+# Read "nm -o" input (on IRIX: "nm -Bo") of a set of libraries or modules
+# and print various interesting listings, such as:
+#
+# - which names are used but not defined in the set (and used where),
+# - which names are defined in the set (and where),
+# - which modules use which other modules,
+# - which modules are used by which other modules.
+#
+# Usage: objgraph [-cdu] [file] ...
+# -c: print callers per objectfile
+# -d: print callees per objectfile
+# -u: print usage of undefined symbols
+# If none of -cdu is specified, all are assumed.
+# Use "nm -o" to generate the input (on IRIX: "nm -Bo"),
+# e.g.: nm -o /lib/libc.a | objgraph
+
+
+import sys
+import string
+import path
+import getopt
+import regexp
+
+# Types of symbols.
+#
+definitions = 'TRGDSBAEC'
+externals = 'UV'
+ignore = 'Nntrgdsbavuc'
+
+# Regular expression to parse "nm -o" output.
+#
+matcher = regexp.compile('(.*):\t?........ (.) (.*)$')
+
+# 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]
+
+# Return a flattened version of a list of strings: the concatenation
+# of its elements with intervening spaces.
+#
+def flat(list):
+	s = ''
+	for item in list:
+		s = s + ' ' + item
+	return s[1:]
+
+# Global variables mapping defined/undefined names to files and back.
+#
+file2undef = {}
+def2file = {}
+file2def = {}
+undef2file = {}
+
+# Read one input file and merge the data into the tables.
+# Argument is an open file.
+#
+def readinput(file):
+	while 1:
+		s = file.readline(200) # Arbitrary, but reasonable limit
+		if not s:
+			break
+		# If you get an exception on this line,
+		# it is probably caused by an unexpected input line:
+		(ra, rb), (r1a, r1b), (r2a, r2b), (r3a, r3b) = matcher.exec(s)
+		fn, name, type = s[r1a:r1b], s[r3a:r3b], s[r2a:r2b]
+		if type in definitions:
+			store(def2file, name, fn)
+			store(file2def, fn, name)
+		elif type in externals:
+			store(file2undef, fn, name)
+			store(undef2file, name, fn)
+		elif not type in ignore:
+			print fn + ':' + name + ': unknown type ' + type
+
+# Print all names that were undefined in some module and where they are
+# defined.
+#
+def printcallee():
+	flist = file2undef.keys()
+	flist.sort()
+	for file in flist:
+		print file + ':'
+		elist = file2undef[file]
+		elist.sort()
+		for ext in elist:
+			if len(ext) >= 8:
+				tabs = '\t'
+			else:
+				tabs = '\t\t'
+			if not def2file.has_key(ext):
+				print '\t' + ext + tabs + ' *undefined'
+			else:
+				print '\t' + ext + tabs + flat(def2file[ext])
+
+# Print for each module the names of the other modules that use it.
+#
+def printcaller():
+	files = file2def.keys()
+	files.sort()
+	for file in files:
+		callers = []
+		for label in file2def[file]:
+			if undef2file.has_key(label):
+				callers = callers + undef2file[label]
+		if callers:
+			callers.sort()
+			print file + ':'
+			lastfn = ''
+			for fn in callers:
+				if fn <> lastfn:
+					print '\t' + fn
+				lastfn = fn
+		else:
+			print file + ': unused'
+
+# Print undefine names and where they are used.
+#
+def printundef():
+	undefs = {}
+	for file in file2undef.keys():
+		for ext in file2undef[file]:
+			if not def2file.has_key(ext):
+				store(undefs, ext, file)
+	elist = undefs.keys()
+	elist.sort()
+	for ext in elist:
+		print ext + ':'
+		flist = undefs[ext]
+		flist.sort()
+		for file in flist:
+			print '\t' + file
+
+# Print warning messages about names defined in more than one file.
+#
+def warndups():
+	savestdout = sys.stdout
+	sys.stdout = sys.stderr
+	names = def2file.keys()
+	names.sort()
+	for name in names:
+		if len(def2file[name]) > 1:
+			print 'warning:', name, 'multiply defined:',
+			print flat(def2file[name])
+	sys.stdout = savestdout
+
+# Main program
+#
+def main():
+	try:
+		optlist, args = getopt.getopt(sys.argv[1:], 'cdu')
+	except getopt.error:
+		sys.stdout = sys.stderr
+		print 'Usage:', path.basename(sys.argv[0]), '[-cdu] [file] ...'
+		print '-c: print callers per objectfile'
+		print '-d: print callees per objectfile'
+		print '-u: print usage of undefined symbols'
+		print 'If none of -cdu is specified, all are assumed.'
+		print 'Use "nm -o" to generate the input (on IRIX: "nm -Bo"),'
+		print 'e.g.: nm -o /lib/libc.a | objgraph'
+		return 1
+	optu = optc = optd = 0
+	for opt, void in optlist:
+		if opt = '-u':
+			optu = 1
+		elif opt = '-c':
+			optc = 1
+		elif opt = '-d':
+			optd = 1
+	if optu = optc = optd = 0:
+		optu = optc = optd = 1
+	if not args:
+		args = ['-']
+	for file in args:
+		if file = '-':
+			readinput(sys.stdin)
+		else:
+			readinput(open(file, 'r'))
+	#
+	warndups()
+	#
+	more = (optu + optc + optd > 1)
+	if optd:
+		if more:
+			print '---------------All callees------------------'
+		printcallee()
+	if optu:
+		if more:
+			print '---------------Undefined callees------------'
+		printundef()
+	if optc:
+		if more:
+			print '---------------All Callers------------------'
+		printcaller()
+	return 0
+
+# Call the main program.
+# Use its return value as exit status.
+# Catch interrupts to avoid stack trace.
+#
+try:
+	sys.exit(main())
+except KeyboardInterrupt:
+	sys.exit(1)
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)
diff --git a/Tools/scripts/ptags.py b/Tools/scripts/ptags.py
new file mode 100755
index 0000000..b3a693e
--- /dev/null
+++ b/Tools/scripts/ptags.py
@@ -0,0 +1,49 @@
+#! /usr/local/python
+
+# ptags
+#
+# Create a tags file for Python programs, usable with vi.
+# Tagged are:
+# - functions (even inside other defs or classes)
+# - classes
+# - filenames
+# Warns about files it cannot open.
+# No warnings about duplicate tags.
+
+import sys
+import regexp
+import path
+
+tags = []	# Modified global variable!
+
+def main():
+	args = sys.argv[1:]
+	for file in args: treat_file(file)
+	if tags:
+		fp = open('tags', 'w')
+		tags.sort()
+		for s in tags: fp.write(s)
+
+matcher = regexp.compile('^[ \t]*(def|class)[ \t]+([a-zA-Z0-9_]+)[ \t]*\(')
+
+def treat_file(file):
+	try:
+		fp = open(file, 'r')
+	except:
+		print 'Cannot open', file
+		return
+	base = path.basename(file)
+	if base[-3:] = '.py': base = base[:-3]
+	s = base + '\t' + file + '\t' + '1\n'
+	tags.append(s)
+	while 1:
+		line = fp.readline()
+		if not line: break
+		res = matcher.exec(line)
+		if res:
+			(a, b), (a1, b1), (a2, b2) = res
+			name = line[a2:b2]
+			s = name + '\t' + file + '\t/^' + line[a:b] + '/\n'
+			tags.append(s)
+
+main()
diff --git a/Tools/scripts/suff.py b/Tools/scripts/suff.py
new file mode 100755
index 0000000..7fda113
--- /dev/null
+++ b/Tools/scripts/suff.py
@@ -0,0 +1,29 @@
+#! /usr/local/python
+
+# suff
+#
+# show different suffixes amongst arguments
+
+import sys
+
+def main():
+	files = sys.argv[1:]
+	suffixes = {}
+	for file in files:
+		suff = getsuffix(file)
+		if not suffixes.has_key(suff):
+			suffixes[suff] = []
+		suffixes[suff].append(file)
+	keys = suffixes.keys()
+	keys.sort()
+	for suff in keys:
+		print `suff`, len(suffixes[suff])
+
+def getsuffix(file):
+	suff = ''
+	for i in range(len(file)):
+		if file[i] = '.':
+			suff = file[i:]
+	return suff
+
+main()
diff --git a/Tools/scripts/which.py b/Tools/scripts/which.py
new file mode 100755
index 0000000..b9b888b
--- /dev/null
+++ b/Tools/scripts/which.py
@@ -0,0 +1,44 @@
+#! /usr/local/python
+
+# Variant of "which".
+# On stderr, near and total misses are reported.
+
+import sys, posix, string, path
+from stat import *
+
+def msg(str):
+	sys.stderr.write(str + '\n')
+
+pathlist = string.splitfields(posix.environ['PATH'], ':')
+
+sts = 0
+
+for prog in sys.argv[1:]:
+	ident = ()
+	for dir in pathlist:
+		file = path.cat(dir, prog)
+		try:
+			st = posix.stat(file)
+			if S_ISREG(st[ST_MODE]):
+				mode = S_IMODE(st[ST_MODE])
+				if mode % 2 or mode/8 % 2 or mode/64 % 2:
+					if ident:
+						if st[:3] = ident:
+							s = ': same as '
+						else:
+							s = ': also '
+						msg(prog + s + file)
+					else:
+						print file
+						ident = st[:3]
+				else:
+					msg(file + ': not executable')
+			else:
+				msg(file + ': not a disk file')
+		except posix.error:
+			pass
+	if not ident:
+		msg(prog + ': not found')
+		sts = 1
+
+sys.exit(sts)
diff --git a/Tools/scripts/xxci.py b/Tools/scripts/xxci.py
new file mode 100755
index 0000000..e747c8d
--- /dev/null
+++ b/Tools/scripts/xxci.py
@@ -0,0 +1,77 @@
+#! /usr/local/python
+
+# xxci
+#
+# check in files for which rcsdiff returns nonzero exit status
+
+import sys
+import posix
+import stat
+import path
+import commands
+
+MAXSIZE = 200*1024 # Files this big must be binaries and are skipped.
+
+def getargs():
+	args = sys.argv[1:]
+	if args:
+		return args
+	print 'No arguments, checking almost *'
+	for file in posix.listdir('.'):
+		if not skipfile(file):
+			args.append(file)
+	if not args:
+		print 'Nothing to do -- exit 1'
+		sys.exit(1)
+	args.sort()
+	return args
+
+badnames = ['tags', 'xyzzy']
+badprefixes = ['.', ',', '@', '#', 'o.']
+badsuffixes = \
+	['~', '.a', '.o', '.old', '.bak', '.orig', '.new', '.prev', '.not']
+# XXX Should generalize even more to use fnmatch!
+
+def skipfile(file):
+	if file in badnames or \
+		badprefix(file) or badsuffix(file) or \
+		path.islink(file) or path.isdir(file):
+		return 1
+	# Skip huge files -- probably binaries.
+	try:
+		st = posix.stat(file)
+	except posix.error:
+		return 1 # Doesn't exist -- skip it
+	return st[stat.ST_SIZE] >= MAXSIZE
+
+def badprefix(file):
+	for bad in badprefixes:
+		if file[:len(bad)] = bad: return 1
+	return 0
+
+def badsuffix(file):
+	for bad in badsuffixes:
+		if file[-len(bad):] = bad: return 1
+	return 0
+
+def go(args):
+	for file in args:
+		print file + ':'
+		if run('rcsdiff -c', file):
+			if askyesno('Check in ' + file + ' ? '):
+				sts = run('rcs -l', file) # ignored
+				# can't use run() here because it's interactive
+				sts = posix.system('ci -l ' + file)
+
+def run(cmd, file):
+	sts, output = commands.getstatusoutput(cmd + commands.mkarg(file))
+	if sts:
+		print output
+		print 'Exit status', sts
+	return sts
+
+def askyesno(prompt):
+	s = raw_input(prompt)
+	return s in ['y', 'yes']
+
+go(getargs())