Initial revision
diff --git a/Demo/scripts/README b/Demo/scripts/README
new file mode 100644
index 0000000..7a2b837
--- /dev/null
+++ b/Demo/scripts/README
@@ -0,0 +1,18 @@
+Contents of this directory:
+
+byteyears.py		Print product of a file's size and age
+eptags.py		Create Emacs TAGS file for Python modules
+fact.py			Factorize numbers
+findlinksto.py		Find symbolic links to a given path (prefix)
+from.py			Summarize mailbox
+lfact.py		Factorize long numbers
+lpwatch.py		Watch BSD line printer queues
+mkreal.py		Turn a symbolic link into a real file or directory
+objgraph.py		Print object graph from nm output on a library
+pdeps.py		Print dependencies between Python modules
+pi.py			Print digits of pi (uses arbitrary precision integers)
+primes.py		Print prime numbers
+ptags.py		Create vi tags file for Python modules
+suff.py			Sort a list of files by suffix
+which.py		Find a program in $PATH
+xxci.py			Wrapper for rcsdiff and ci
diff --git a/Demo/scripts/fact.py b/Demo/scripts/fact.py
new file mode 100755
index 0000000..ba75a04
--- /dev/null
+++ b/Demo/scripts/fact.py
@@ -0,0 +1,45 @@
+#! /usr/local/python
+
+# Factorize numbers, slowly.
+# This version uses plain integers and is thus limited to 2**31-1.
+
+import sys
+from math import sqrt
+
+error = 'fact.error'		# exception
+
+def fact(n):
+	if n < 1: raise error	# fact() argument should be >= 1
+	if n = 1: return []	# special case
+	res = []
+	# Treat even factors special, so we can use i = i+2 later
+	while n%2 = 0:
+		res.append(2)
+		n = n/2
+	# Try odd numbers up to sqrt(n)
+	limit = int(sqrt(float(n+1)))
+	i = 3
+	while i <= limit:
+		if n%i = 0:
+			res.append(i)
+			n = n/i
+			limit = int(sqrt(float(n+1)))
+		else:
+			i = i+2
+	res.append(n)
+	return res
+
+def main():
+	if len(sys.argv) > 1:
+		for arg in sys.argv[1:]:
+			n = int(eval(arg))
+			print n, fact(n)
+	else:
+		try:
+			while 1:
+				n = int(input())
+				print n, fact(n)
+		except EOFError:
+			pass
+
+main()
diff --git a/Demo/scripts/from.py b/Demo/scripts/from.py
new file mode 100755
index 0000000..20771a0
--- /dev/null
+++ b/Demo/scripts/from.py
@@ -0,0 +1,25 @@
+#! /usr/local/python
+
+# Print From and Subject of messages in $MAIL.
+# Extension to multiple mailboxes and other bells & whistles are left
+# as exercises for the reader.
+
+import posix
+
+# Open mailbox file.  Exits with exception when this fails.
+
+mail = open(posix.environ['MAIL'], 'r')
+
+while 1:
+	line = mail.readline()
+	if not line: break # EOF
+	if line[:5] = 'From ':
+		# Start of message found
+		print line[:-1],
+		while 1:
+			line = mail.readline()
+			if not line: break # EOF
+			if line = '\n': break # Blank line ends headers
+			if line[:8] = 'Subject:':
+				print `line[9:-1]`,
+		print
diff --git a/Demo/scripts/lpwatch.py b/Demo/scripts/lpwatch.py
new file mode 100755
index 0000000..294028f
--- /dev/null
+++ b/Demo/scripts/lpwatch.py
@@ -0,0 +1,111 @@
+#! /ufs/guido/bin/sgi/python
+#! /usr/local/python
+
+# Watch line printer queue(s).
+# Intended for BSD 4.3 lpq.
+
+import posix
+import sys
+import time
+import string
+
+DEF_PRINTER = 'psc'
+DEF_DELAY = 10
+
+def main():
+	delay = DEF_DELAY # XXX Use getopt() later
+	try:
+		thisuser = posix.environ['LOGNAME']
+	except:
+		thisuser = posix.environ['USER']
+	printers = sys.argv[1:]
+	if not printers:
+		if posix.environ.has_key('PRINTER'):
+			printers = [posix.environ['PRINTER']]
+		else:
+			printers = [DEF_PRINTER]
+	#
+	clearhome = posix.popen('clear', 'r').read()
+	#
+	while 1:
+		# Pipe output through cat for extra buffering,
+		# so the output (which overwrites the previous)
+		# appears instantaneous.
+		sys.stdout = posix.popen('exec cat', 'w')
+		sys.stdout.write(clearhome)
+		for name in printers:
+			pipe = posix.popen('lpq -P' + name + ' 2>&1', 'r')
+			showstatus(name, pipe, thisuser)
+			sts = pipe.close()
+			if sts:
+				print name + ': *** lpq exit status', sts
+		sts = sys.stdout.close()
+		time.sleep(delay)
+
+def showstatus(name, pipe, thisuser):
+	lines = 0
+	users = {}
+	aheadbytes = 0
+	aheadjobs = 0
+	userseen = 0
+	totalbytes = 0
+	totaljobs = 0
+	while 1:
+		line = pipe.readline()
+		if not line: break
+		fields = string.split(line)
+		n = len(fields)
+		if len(fields) >= 6 and fields[n-1] = 'bytes':
+			rank = fields[0]
+			user = fields[1]
+			job = fields[2]
+			files = fields[3:-2]
+			bytes = eval(fields[n-2])
+			if user = thisuser:
+				userseen = 1
+			elif not userseen:
+				aheadbytes = aheadbytes + bytes
+				aheadjobs = aheadjobs + 1
+			totalbytes = totalbytes + bytes
+			totaljobs = totaljobs + 1
+			if users.has_key(user):
+				ujobs, ubytes = users[user]
+			else:
+				ujobs, ubytes = 0, 0
+			ujobs = ujobs + 1
+			ubytes = ubytes + bytes
+			users[user] = ujobs, ubytes
+		else:
+			if fields and fields[0] <> 'Rank':
+				if line[-1:] = '\n':
+					line = line[:-1]
+				if not lines:
+					print name + ':',
+				else:
+					print
+				print line,
+				lines = lines + 1
+	if totaljobs:
+		if lines > 1:
+			print
+			lines = lines+1
+		print (totalbytes+1023)/1024, 'K',
+		if totaljobs <> len(users):
+			print '(' + `totaljobs` + ' jobs)',
+		if len(users) = 1:
+			print 'for', users.keys()[0],
+		else:
+			print 'for', len(users), 'users',
+		if userseen:
+			if aheadjobs = 0:
+				print '(' + thisuser + ' first)',
+			else:
+				print '(' + `(aheadbytes+1023)/1024`,
+				print 'K before', thisuser + ')'
+	if lines:
+		print
+
+try:
+	main()
+except KeyboardInterrupt:
+	pass
diff --git a/Demo/scripts/pi.py b/Demo/scripts/pi.py
new file mode 100755
index 0000000..5e19db6
--- /dev/null
+++ b/Demo/scripts/pi.py
@@ -0,0 +1,30 @@
+#! /usr/local/python
+
+# Print digits of pi forever.
+#
+# The algorithm, using Python's 'long' integers ("bignums"), works
+# with continued fractions, and was conceived by Lambert Meertens.
+#
+# See also the ABC Programmer's Handbook, by Geurts, Meertens & Pemberton,
+# published by Prentice-Hall (UK) Ltd., 1990.
+
+import sys
+
+def main():
+	k, a, b, a1, b1 = 2l, 4l, 1l, 12l, 4l
+	while 1:
+		# Next approximation
+		p, q, k = k*k, 2l*k+1l, k+1l
+		a, b, a1, b1 = a1, b1, p*a+q*a1, p*b+q*b1
+		# Print common digits
+		d, d1 = a/b, a1/b1
+		#print a, b, a1, b1
+		while d = d1:
+			# Use write() to avoid spaces between the digits
+			sys.stdout.write(`int(d)`)
+			# Flush so the output is seen immediately
+			sys.stdout.flush()
+			a, a1 = 10l*(a%b), 10l*(a1%b1)
+			d, d1 = a/b, a1/b1
+
+main()
diff --git a/Demo/scripts/primes.py b/Demo/scripts/primes.py
new file mode 100755
index 0000000..487acef
--- /dev/null
+++ b/Demo/scripts/primes.py
@@ -0,0 +1,26 @@
+#! /usr/local/python
+
+# Print prime numbers in a given range
+
+def main():
+	import sys
+	min, max = 2, 0x7fffffff
+	if sys.argv[1:]:
+		min = int(eval(sys.argv[1]))
+		if sys.argv[2:]:
+			max = int(eval(sys.argv[2]))
+	primes(min, max)
+
+def primes(min, max):
+	if 2 >= min: print 2
+	primes = [2]
+	i = 3
+	while i <= max:
+		for p in primes:
+			if i%p = 0 or p*p > i: break
+		if i%p <> 0:
+			primes.append(i)
+			if i >= min: print i
+		i = i+2
+
+main()
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())