]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
Initial revision
authorGuido van Rossum <guido@python.org>
Tue, 4 Jun 1991 20:36:54 +0000 (20:36 +0000)
committerGuido van Rossum <guido@python.org>
Tue, 4 Jun 1991 20:36:54 +0000 (20:36 +0000)
16 files changed:
Demo/scripts/README [new file with mode: 0644]
Demo/scripts/fact.py [new file with mode: 0755]
Demo/scripts/from.py [new file with mode: 0755]
Demo/scripts/lpwatch.py [new file with mode: 0755]
Demo/scripts/pi.py [new file with mode: 0755]
Demo/scripts/primes.py [new file with mode: 0755]
Tools/scripts/byteyears.py [new file with mode: 0755]
Tools/scripts/eptags.py [new file with mode: 0755]
Tools/scripts/findlinksto.py [new file with mode: 0755]
Tools/scripts/mkreal.py [new file with mode: 0755]
Tools/scripts/objgraph.py [new file with mode: 0755]
Tools/scripts/pdeps.py [new file with mode: 0755]
Tools/scripts/ptags.py [new file with mode: 0755]
Tools/scripts/suff.py [new file with mode: 0755]
Tools/scripts/which.py [new file with mode: 0755]
Tools/scripts/xxci.py [new file with mode: 0755]

diff --git a/Demo/scripts/README b/Demo/scripts/README
new file mode 100644 (file)
index 0000000..7a2b837
--- /dev/null
@@ -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 (executable)
index 0000000..ba75a04
--- /dev/null
@@ -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 (executable)
index 0000000..20771a0
--- /dev/null
@@ -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 (executable)
index 0000000..294028f
--- /dev/null
@@ -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 (executable)
index 0000000..5e19db6
--- /dev/null
@@ -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 (executable)
index 0000000..487acef
--- /dev/null
@@ -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 (executable)
index 0000000..8c5ec69
--- /dev/null
@@ -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 (executable)
index 0000000..1c682d3
--- /dev/null
@@ -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 (executable)
index 0000000..6a2a75b
--- /dev/null
@@ -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 (executable)
index 0000000..3fd4b03
--- /dev/null
@@ -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 (executable)
index 0000000..b45bba2
--- /dev/null
@@ -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 (executable)
index 0000000..2533015
--- /dev/null
@@ -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 (executable)
index 0000000..b3a693e
--- /dev/null
@@ -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 (executable)
index 0000000..7fda113
--- /dev/null
@@ -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 (executable)
index 0000000..b9b888b
--- /dev/null
@@ -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 (executable)
index 0000000..e747c8d
--- /dev/null
@@ -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())