mirror of https://github.com/acidanthera/audk.git
AppPkg/Applications/Python: Add Python 2.7.2 sources since the release of Python 2.7.3 made them unavailable from the python.org web site.
These files are a subset of the python-2.7.2.tgz distribution from python.org. Changed files from PyMod-2.7.2 have been copied into the corresponding directories of this tree, replacing the original files in the distribution. Signed-off-by: daryl.mcdaniel@intel.com git-svn-id: https://edk2.svn.sourceforge.net/svnroot/edk2/trunk/edk2@13197 6f19259b-4bc3-4df7-8a09-765794883524
This commit is contained in:
parent
cbc6b5e545
commit
4710c53dca
|
@ -0,0 +1,40 @@
|
|||
This directory contains various demonstrations of what you can do with
|
||||
Python. They were all written by me except where explicitly stated
|
||||
otherwise -- in general, demos contributed by others ends up in the
|
||||
../Contrib directory, unless I think they're of utmost general
|
||||
importance (like Matt Conway's Tk demos).
|
||||
|
||||
A fair number of utilities that are useful when while developing
|
||||
Python code can be found in the ../Tools directory -- some of these
|
||||
can also be considered good examples of how to write Python code.
|
||||
|
||||
Finally, in order to save disk space and net bandwidth, not all
|
||||
subdirectories listed here are distributed. They are listed just
|
||||
in case I change my mind about them.
|
||||
|
||||
|
||||
cgi CGI examples (see also ../Tools/faqwiz/.)
|
||||
|
||||
classes Some examples of how to use classes.
|
||||
|
||||
comparisons A set of responses to a really old language-comparison
|
||||
challenge.
|
||||
|
||||
md5test Test program for the optional md5 module.
|
||||
|
||||
metaclasses The code from the 1.5 metaclasses paper on the web.
|
||||
|
||||
parser Example using the parser module.
|
||||
|
||||
pdist Old, unfinished code messing with CVS, RCS and remote
|
||||
files.
|
||||
|
||||
scripts Some useful Python scripts that I put in my bin
|
||||
directory. No optional built-in modules needed.
|
||||
|
||||
sockets Examples for the new built-in module 'socket'.
|
||||
|
||||
xml Some XML demos.
|
||||
|
||||
zlib Some demos for the zlib module (see also the standard
|
||||
library module gzip.py).
|
|
@ -0,0 +1,11 @@
|
|||
CGI Examples
|
||||
------------
|
||||
|
||||
Here are some example CGI programs. For a larger example, see
|
||||
../../Tools/faqwiz/.
|
||||
|
||||
cgi0.sh -- A shell script to test your server is configured for CGI
|
||||
cgi1.py -- A Python script to test your server is configured for CGI
|
||||
cgi2.py -- A Python script showing how to parse a form
|
||||
cgi3.py -- A Python script for driving an arbitrary CGI application
|
||||
wiki.py -- Sample CGI application: a minimal Wiki implementation
|
|
@ -0,0 +1,8 @@
|
|||
#! /bin/sh
|
||||
|
||||
# If you can't get this to work, your web server isn't set up right
|
||||
|
||||
echo Content-type: text/plain
|
||||
echo
|
||||
echo Hello world
|
||||
echo This is cgi0.sh
|
|
@ -0,0 +1,14 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
"""CGI test 1 - check server setup."""
|
||||
|
||||
# Until you get this to work, your web server isn't set up right or
|
||||
# your Python isn't set up right.
|
||||
|
||||
# If cgi0.sh works but cgi1.py doesn't, check the #! line and the file
|
||||
# permissions. The docs for the cgi.py module have debugging tips.
|
||||
|
||||
print "Content-type: text/html"
|
||||
print
|
||||
print "<h1>Hello world</h1>"
|
||||
print "<p>This is cgi1.py"
|
|
@ -0,0 +1,22 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
"""CGI test 2 - basic use of cgi module."""
|
||||
|
||||
import cgitb; cgitb.enable()
|
||||
|
||||
import cgi
|
||||
|
||||
def main():
|
||||
form = cgi.FieldStorage()
|
||||
print "Content-type: text/html"
|
||||
print
|
||||
if not form:
|
||||
print "<h1>No Form Keys</h1>"
|
||||
else:
|
||||
print "<h1>Form Keys</h1>"
|
||||
for key in form.keys():
|
||||
value = form[key].value
|
||||
print "<p>", cgi.escape(key), ":", cgi.escape(value)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -0,0 +1,10 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
"""CGI test 3 (persistent data)."""
|
||||
|
||||
import cgitb; cgitb.enable()
|
||||
|
||||
from wiki import main
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -0,0 +1,123 @@
|
|||
"""Wiki main program. Imported and run by cgi3.py."""
|
||||
|
||||
import os, re, cgi, sys, tempfile
|
||||
escape = cgi.escape
|
||||
|
||||
def main():
|
||||
form = cgi.FieldStorage()
|
||||
print "Content-type: text/html"
|
||||
print
|
||||
cmd = form.getvalue("cmd", "view")
|
||||
page = form.getvalue("page", "FrontPage")
|
||||
wiki = WikiPage(page)
|
||||
method = getattr(wiki, 'cmd_' + cmd, None) or wiki.cmd_view
|
||||
method(form)
|
||||
|
||||
class WikiPage:
|
||||
|
||||
homedir = tempfile.gettempdir()
|
||||
scripturl = os.path.basename(sys.argv[0])
|
||||
|
||||
def __init__(self, name):
|
||||
if not self.iswikiword(name):
|
||||
raise ValueError, "page name is not a wiki word"
|
||||
self.name = name
|
||||
self.load()
|
||||
|
||||
def cmd_view(self, form):
|
||||
print "<h1>", escape(self.splitwikiword(self.name)), "</h1>"
|
||||
print "<p>"
|
||||
for line in self.data.splitlines():
|
||||
line = line.rstrip()
|
||||
if not line:
|
||||
print "<p>"
|
||||
else:
|
||||
print self.formatline(line)
|
||||
print "<hr>"
|
||||
print "<p>", self.mklink("edit", self.name, "Edit this page") + ";"
|
||||
print self.mklink("view", "FrontPage", "go to front page") + "."
|
||||
|
||||
def formatline(self, line):
|
||||
words = []
|
||||
for word in re.split('(\W+)', line):
|
||||
if self.iswikiword(word):
|
||||
if os.path.isfile(self.mkfile(word)):
|
||||
word = self.mklink("view", word, word)
|
||||
else:
|
||||
word = self.mklink("new", word, word + "*")
|
||||
else:
|
||||
word = escape(word)
|
||||
words.append(word)
|
||||
return "".join(words)
|
||||
|
||||
def cmd_edit(self, form, label="Change"):
|
||||
print "<h1>", label, self.name, "</h1>"
|
||||
print '<form method="POST" action="%s">' % self.scripturl
|
||||
s = '<textarea cols="70" rows="20" name="text">%s</textarea>'
|
||||
print s % self.data
|
||||
print '<input type="hidden" name="cmd" value="create">'
|
||||
print '<input type="hidden" name="page" value="%s">' % self.name
|
||||
print '<br>'
|
||||
print '<input type="submit" value="%s Page">' % label
|
||||
print "</form>"
|
||||
|
||||
def cmd_create(self, form):
|
||||
self.data = form.getvalue("text", "").strip()
|
||||
error = self.store()
|
||||
if error:
|
||||
print "<h1>I'm sorry. That didn't work</h1>"
|
||||
print "<p>An error occurred while attempting to write the file:"
|
||||
print "<p>", escape(error)
|
||||
else:
|
||||
# Use a redirect directive, to avoid "reload page" problems
|
||||
print "<head>"
|
||||
s = '<meta http-equiv="refresh" content="1; URL=%s">'
|
||||
print s % (self.scripturl + "?cmd=view&page=" + self.name)
|
||||
print "<head>"
|
||||
print "<h1>OK</h1>"
|
||||
print "<p>If nothing happens, please click here:",
|
||||
print self.mklink("view", self.name, self.name)
|
||||
|
||||
def cmd_new(self, form):
|
||||
self.cmd_edit(form, label="Create")
|
||||
|
||||
def iswikiword(self, word):
|
||||
return re.match("[A-Z][a-z]+([A-Z][a-z]*)+", word)
|
||||
|
||||
def splitwikiword(self, word):
|
||||
chars = []
|
||||
for c in word:
|
||||
if chars and c.isupper():
|
||||
chars.append(' ')
|
||||
chars.append(c)
|
||||
return "".join(chars)
|
||||
|
||||
def mkfile(self, name=None):
|
||||
if name is None:
|
||||
name = self.name
|
||||
return os.path.join(self.homedir, name + ".txt")
|
||||
|
||||
def mklink(self, cmd, page, text):
|
||||
link = self.scripturl + "?cmd=" + cmd + "&page=" + page
|
||||
return '<a href="%s">%s</a>' % (link, text)
|
||||
|
||||
def load(self):
|
||||
try:
|
||||
f = open(self.mkfile())
|
||||
data = f.read().strip()
|
||||
f.close()
|
||||
except IOError:
|
||||
data = ""
|
||||
self.data = data
|
||||
|
||||
def store(self):
|
||||
data = self.data
|
||||
try:
|
||||
f = open(self.mkfile(), "w")
|
||||
f.write(data)
|
||||
if data and not data.endswith('\n'):
|
||||
f.write('\n')
|
||||
f.close()
|
||||
return ""
|
||||
except IOError, err:
|
||||
return "IOError: %s" % str(err)
|
|
@ -0,0 +1,320 @@
|
|||
# Complex numbers
|
||||
# ---------------
|
||||
|
||||
# [Now that Python has a complex data type built-in, this is not very
|
||||
# useful, but it's still a nice example class]
|
||||
|
||||
# This module represents complex numbers as instances of the class Complex.
|
||||
# A Complex instance z has two data attribues, z.re (the real part) and z.im
|
||||
# (the imaginary part). In fact, z.re and z.im can have any value -- all
|
||||
# arithmetic operators work regardless of the type of z.re and z.im (as long
|
||||
# as they support numerical operations).
|
||||
#
|
||||
# The following functions exist (Complex is actually a class):
|
||||
# Complex([re [,im]) -> creates a complex number from a real and an imaginary part
|
||||
# IsComplex(z) -> true iff z is a complex number (== has .re and .im attributes)
|
||||
# ToComplex(z) -> a complex number equal to z; z itself if IsComplex(z) is true
|
||||
# if z is a tuple(re, im) it will also be converted
|
||||
# PolarToComplex([r [,phi [,fullcircle]]]) ->
|
||||
# the complex number z for which r == z.radius() and phi == z.angle(fullcircle)
|
||||
# (r and phi default to 0)
|
||||
# exp(z) -> returns the complex exponential of z. Equivalent to pow(math.e,z).
|
||||
#
|
||||
# Complex numbers have the following methods:
|
||||
# z.abs() -> absolute value of z
|
||||
# z.radius() == z.abs()
|
||||
# z.angle([fullcircle]) -> angle from positive X axis; fullcircle gives units
|
||||
# z.phi([fullcircle]) == z.angle(fullcircle)
|
||||
#
|
||||
# These standard functions and unary operators accept complex arguments:
|
||||
# abs(z)
|
||||
# -z
|
||||
# +z
|
||||
# not z
|
||||
# repr(z) == `z`
|
||||
# str(z)
|
||||
# hash(z) -> a combination of hash(z.re) and hash(z.im) such that if z.im is zero
|
||||
# the result equals hash(z.re)
|
||||
# Note that hex(z) and oct(z) are not defined.
|
||||
#
|
||||
# These conversions accept complex arguments only if their imaginary part is zero:
|
||||
# int(z)
|
||||
# long(z)
|
||||
# float(z)
|
||||
#
|
||||
# The following operators accept two complex numbers, or one complex number
|
||||
# and one real number (int, long or float):
|
||||
# z1 + z2
|
||||
# z1 - z2
|
||||
# z1 * z2
|
||||
# z1 / z2
|
||||
# pow(z1, z2)
|
||||
# cmp(z1, z2)
|
||||
# Note that z1 % z2 and divmod(z1, z2) are not defined,
|
||||
# nor are shift and mask operations.
|
||||
#
|
||||
# The standard module math does not support complex numbers.
|
||||
# The cmath modules should be used instead.
|
||||
#
|
||||
# Idea:
|
||||
# add a class Polar(r, phi) and mixed-mode arithmetic which
|
||||
# chooses the most appropriate type for the result:
|
||||
# Complex for +,-,cmp
|
||||
# Polar for *,/,pow
|
||||
|
||||
import math
|
||||
import sys
|
||||
|
||||
twopi = math.pi*2.0
|
||||
halfpi = math.pi/2.0
|
||||
|
||||
def IsComplex(obj):
|
||||
return hasattr(obj, 're') and hasattr(obj, 'im')
|
||||
|
||||
def ToComplex(obj):
|
||||
if IsComplex(obj):
|
||||
return obj
|
||||
elif isinstance(obj, tuple):
|
||||
return Complex(*obj)
|
||||
else:
|
||||
return Complex(obj)
|
||||
|
||||
def PolarToComplex(r = 0, phi = 0, fullcircle = twopi):
|
||||
phi = phi * (twopi / fullcircle)
|
||||
return Complex(math.cos(phi)*r, math.sin(phi)*r)
|
||||
|
||||
def Re(obj):
|
||||
if IsComplex(obj):
|
||||
return obj.re
|
||||
return obj
|
||||
|
||||
def Im(obj):
|
||||
if IsComplex(obj):
|
||||
return obj.im
|
||||
return 0
|
||||
|
||||
class Complex:
|
||||
|
||||
def __init__(self, re=0, im=0):
|
||||
_re = 0
|
||||
_im = 0
|
||||
if IsComplex(re):
|
||||
_re = re.re
|
||||
_im = re.im
|
||||
else:
|
||||
_re = re
|
||||
if IsComplex(im):
|
||||
_re = _re - im.im
|
||||
_im = _im + im.re
|
||||
else:
|
||||
_im = _im + im
|
||||
# this class is immutable, so setting self.re directly is
|
||||
# not possible.
|
||||
self.__dict__['re'] = _re
|
||||
self.__dict__['im'] = _im
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
raise TypeError, 'Complex numbers are immutable'
|
||||
|
||||
def __hash__(self):
|
||||
if not self.im:
|
||||
return hash(self.re)
|
||||
return hash((self.re, self.im))
|
||||
|
||||
def __repr__(self):
|
||||
if not self.im:
|
||||
return 'Complex(%r)' % (self.re,)
|
||||
else:
|
||||
return 'Complex(%r, %r)' % (self.re, self.im)
|
||||
|
||||
def __str__(self):
|
||||
if not self.im:
|
||||
return repr(self.re)
|
||||
else:
|
||||
return 'Complex(%r, %r)' % (self.re, self.im)
|
||||
|
||||
def __neg__(self):
|
||||
return Complex(-self.re, -self.im)
|
||||
|
||||
def __pos__(self):
|
||||
return self
|
||||
|
||||
def __abs__(self):
|
||||
return math.hypot(self.re, self.im)
|
||||
|
||||
def __int__(self):
|
||||
if self.im:
|
||||
raise ValueError, "can't convert Complex with nonzero im to int"
|
||||
return int(self.re)
|
||||
|
||||
def __long__(self):
|
||||
if self.im:
|
||||
raise ValueError, "can't convert Complex with nonzero im to long"
|
||||
return long(self.re)
|
||||
|
||||
def __float__(self):
|
||||
if self.im:
|
||||
raise ValueError, "can't convert Complex with nonzero im to float"
|
||||
return float(self.re)
|
||||
|
||||
def __cmp__(self, other):
|
||||
other = ToComplex(other)
|
||||
return cmp((self.re, self.im), (other.re, other.im))
|
||||
|
||||
def __rcmp__(self, other):
|
||||
other = ToComplex(other)
|
||||
return cmp(other, self)
|
||||
|
||||
def __nonzero__(self):
|
||||
return not (self.re == self.im == 0)
|
||||
|
||||
abs = radius = __abs__
|
||||
|
||||
def angle(self, fullcircle = twopi):
|
||||
return (fullcircle/twopi) * ((halfpi - math.atan2(self.re, self.im)) % twopi)
|
||||
|
||||
phi = angle
|
||||
|
||||
def __add__(self, other):
|
||||
other = ToComplex(other)
|
||||
return Complex(self.re + other.re, self.im + other.im)
|
||||
|
||||
__radd__ = __add__
|
||||
|
||||
def __sub__(self, other):
|
||||
other = ToComplex(other)
|
||||
return Complex(self.re - other.re, self.im - other.im)
|
||||
|
||||
def __rsub__(self, other):
|
||||
other = ToComplex(other)
|
||||
return other - self
|
||||
|
||||
def __mul__(self, other):
|
||||
other = ToComplex(other)
|
||||
return Complex(self.re*other.re - self.im*other.im,
|
||||
self.re*other.im + self.im*other.re)
|
||||
|
||||
__rmul__ = __mul__
|
||||
|
||||
def __div__(self, other):
|
||||
other = ToComplex(other)
|
||||
d = float(other.re*other.re + other.im*other.im)
|
||||
if not d: raise ZeroDivisionError, 'Complex division'
|
||||
return Complex((self.re*other.re + self.im*other.im) / d,
|
||||
(self.im*other.re - self.re*other.im) / d)
|
||||
|
||||
def __rdiv__(self, other):
|
||||
other = ToComplex(other)
|
||||
return other / self
|
||||
|
||||
def __pow__(self, n, z=None):
|
||||
if z is not None:
|
||||
raise TypeError, 'Complex does not support ternary pow()'
|
||||
if IsComplex(n):
|
||||
if n.im:
|
||||
if self.im: raise TypeError, 'Complex to the Complex power'
|
||||
else: return exp(math.log(self.re)*n)
|
||||
n = n.re
|
||||
r = pow(self.abs(), n)
|
||||
phi = n*self.angle()
|
||||
return Complex(math.cos(phi)*r, math.sin(phi)*r)
|
||||
|
||||
def __rpow__(self, base):
|
||||
base = ToComplex(base)
|
||||
return pow(base, self)
|
||||
|
||||
def exp(z):
|
||||
r = math.exp(z.re)
|
||||
return Complex(math.cos(z.im)*r,math.sin(z.im)*r)
|
||||
|
||||
|
||||
def checkop(expr, a, b, value, fuzz = 1e-6):
|
||||
print ' ', a, 'and', b,
|
||||
try:
|
||||
result = eval(expr)
|
||||
except:
|
||||
result = sys.exc_type
|
||||
print '->', result
|
||||
if isinstance(result, str) or isinstance(value, str):
|
||||
ok = (result == value)
|
||||
else:
|
||||
ok = abs(result - value) <= fuzz
|
||||
if not ok:
|
||||
print '!!\t!!\t!! should be', value, 'diff', abs(result - value)
|
||||
|
||||
def test():
|
||||
print 'test constructors'
|
||||
constructor_test = (
|
||||
# "expect" is an array [re,im] "got" the Complex.
|
||||
( (0,0), Complex() ),
|
||||
( (0,0), Complex() ),
|
||||
( (1,0), Complex(1) ),
|
||||
( (0,1), Complex(0,1) ),
|
||||
( (1,2), Complex(Complex(1,2)) ),
|
||||
( (1,3), Complex(Complex(1,2),1) ),
|
||||
( (0,0), Complex(0,Complex(0,0)) ),
|
||||
( (3,4), Complex(3,Complex(4)) ),
|
||||
( (-1,3), Complex(1,Complex(3,2)) ),
|
||||
( (-7,6), Complex(Complex(1,2),Complex(4,8)) ) )
|
||||
cnt = [0,0]
|
||||
for t in constructor_test:
|
||||
cnt[0] += 1
|
||||
if ((t[0][0]!=t[1].re)or(t[0][1]!=t[1].im)):
|
||||
print " expected", t[0], "got", t[1]
|
||||
cnt[1] += 1
|
||||
print " ", cnt[1], "of", cnt[0], "tests failed"
|
||||
# test operators
|
||||
testsuite = {
|
||||
'a+b': [
|
||||
(1, 10, 11),
|
||||
(1, Complex(0,10), Complex(1,10)),
|
||||
(Complex(0,10), 1, Complex(1,10)),
|
||||
(Complex(0,10), Complex(1), Complex(1,10)),
|
||||
(Complex(1), Complex(0,10), Complex(1,10)),
|
||||
],
|
||||
'a-b': [
|
||||
(1, 10, -9),
|
||||
(1, Complex(0,10), Complex(1,-10)),
|
||||
(Complex(0,10), 1, Complex(-1,10)),
|
||||
(Complex(0,10), Complex(1), Complex(-1,10)),
|
||||
(Complex(1), Complex(0,10), Complex(1,-10)),
|
||||
],
|
||||
'a*b': [
|
||||
(1, 10, 10),
|
||||
(1, Complex(0,10), Complex(0, 10)),
|
||||
(Complex(0,10), 1, Complex(0,10)),
|
||||
(Complex(0,10), Complex(1), Complex(0,10)),
|
||||
(Complex(1), Complex(0,10), Complex(0,10)),
|
||||
],
|
||||
'a/b': [
|
||||
(1., 10, 0.1),
|
||||
(1, Complex(0,10), Complex(0, -0.1)),
|
||||
(Complex(0, 10), 1, Complex(0, 10)),
|
||||
(Complex(0, 10), Complex(1), Complex(0, 10)),
|
||||
(Complex(1), Complex(0,10), Complex(0, -0.1)),
|
||||
],
|
||||
'pow(a,b)': [
|
||||
(1, 10, 1),
|
||||
(1, Complex(0,10), 1),
|
||||
(Complex(0,10), 1, Complex(0,10)),
|
||||
(Complex(0,10), Complex(1), Complex(0,10)),
|
||||
(Complex(1), Complex(0,10), 1),
|
||||
(2, Complex(4,0), 16),
|
||||
],
|
||||
'cmp(a,b)': [
|
||||
(1, 10, -1),
|
||||
(1, Complex(0,10), 1),
|
||||
(Complex(0,10), 1, -1),
|
||||
(Complex(0,10), Complex(1), -1),
|
||||
(Complex(1), Complex(0,10), 1),
|
||||
],
|
||||
}
|
||||
for expr in sorted(testsuite):
|
||||
print expr + ':'
|
||||
t = (expr,)
|
||||
for item in testsuite[expr]:
|
||||
checkop(*(t+item))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test()
|
|
@ -0,0 +1,227 @@
|
|||
# Class Date supplies date objects that support date arithmetic.
|
||||
#
|
||||
# Date(month,day,year) returns a Date object. An instance prints as,
|
||||
# e.g., 'Mon 16 Aug 1993'.
|
||||
#
|
||||
# Addition, subtraction, comparison operators, min, max, and sorting
|
||||
# all work as expected for date objects: int+date or date+int returns
|
||||
# the date `int' days from `date'; date+date raises an exception;
|
||||
# date-int returns the date `int' days before `date'; date2-date1 returns
|
||||
# an integer, the number of days from date1 to date2; int-date raises an
|
||||
# exception; date1 < date2 is true iff date1 occurs before date2 (&
|
||||
# similarly for other comparisons); min(date1,date2) is the earlier of
|
||||
# the two dates and max(date1,date2) the later; and date objects can be
|
||||
# used as dictionary keys.
|
||||
#
|
||||
# Date objects support one visible method, date.weekday(). This returns
|
||||
# the day of the week the date falls on, as a string.
|
||||
#
|
||||
# Date objects also have 4 read-only data attributes:
|
||||
# .month in 1..12
|
||||
# .day in 1..31
|
||||
# .year int or long int
|
||||
# .ord the ordinal of the date relative to an arbitrary staring point
|
||||
#
|
||||
# The Dates module also supplies function today(), which returns the
|
||||
# current date as a date object.
|
||||
#
|
||||
# Those entranced by calendar trivia will be disappointed, as no attempt
|
||||
# has been made to accommodate the Julian (etc) system. On the other
|
||||
# hand, at least this package knows that 2000 is a leap year but 2100
|
||||
# isn't, and works fine for years with a hundred decimal digits <wink>.
|
||||
|
||||
# Tim Peters tim@ksr.com
|
||||
# not speaking for Kendall Square Research Corp
|
||||
|
||||
# Adapted to Python 1.1 (where some hacks to overcome coercion are unnecessary)
|
||||
# by Guido van Rossum
|
||||
|
||||
# Note that as of Python 2.3, a datetime module is included in the stardard
|
||||
# library.
|
||||
|
||||
# vi:set tabsize=8:
|
||||
|
||||
_MONTH_NAMES = [ 'January', 'February', 'March', 'April', 'May',
|
||||
'June', 'July', 'August', 'September', 'October',
|
||||
'November', 'December' ]
|
||||
|
||||
_DAY_NAMES = [ 'Friday', 'Saturday', 'Sunday', 'Monday',
|
||||
'Tuesday', 'Wednesday', 'Thursday' ]
|
||||
|
||||
_DAYS_IN_MONTH = [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ]
|
||||
|
||||
_DAYS_BEFORE_MONTH = []
|
||||
dbm = 0
|
||||
for dim in _DAYS_IN_MONTH:
|
||||
_DAYS_BEFORE_MONTH.append(dbm)
|
||||
dbm = dbm + dim
|
||||
del dbm, dim
|
||||
|
||||
_INT_TYPES = type(1), type(1L)
|
||||
|
||||
def _is_leap(year): # 1 if leap year, else 0
|
||||
if year % 4 != 0: return 0
|
||||
if year % 400 == 0: return 1
|
||||
return year % 100 != 0
|
||||
|
||||
def _days_in_year(year): # number of days in year
|
||||
return 365 + _is_leap(year)
|
||||
|
||||
def _days_before_year(year): # number of days before year
|
||||
return year*365L + (year+3)//4 - (year+99)//100 + (year+399)//400
|
||||
|
||||
def _days_in_month(month, year): # number of days in month of year
|
||||
if month == 2 and _is_leap(year): return 29
|
||||
return _DAYS_IN_MONTH[month-1]
|
||||
|
||||
def _days_before_month(month, year): # number of days in year before month
|
||||
return _DAYS_BEFORE_MONTH[month-1] + (month > 2 and _is_leap(year))
|
||||
|
||||
def _date2num(date): # compute ordinal of date.month,day,year
|
||||
return _days_before_year(date.year) + \
|
||||
_days_before_month(date.month, date.year) + \
|
||||
date.day
|
||||
|
||||
_DI400Y = _days_before_year(400) # number of days in 400 years
|
||||
|
||||
def _num2date(n): # return date with ordinal n
|
||||
if type(n) not in _INT_TYPES:
|
||||
raise TypeError, 'argument must be integer: %r' % type(n)
|
||||
|
||||
ans = Date(1,1,1) # arguments irrelevant; just getting a Date obj
|
||||
del ans.ord, ans.month, ans.day, ans.year # un-initialize it
|
||||
ans.ord = n
|
||||
|
||||
n400 = (n-1)//_DI400Y # # of 400-year blocks preceding
|
||||
year, n = 400 * n400, n - _DI400Y * n400
|
||||
more = n // 365
|
||||
dby = _days_before_year(more)
|
||||
if dby >= n:
|
||||
more = more - 1
|
||||
dby = dby - _days_in_year(more)
|
||||
year, n = year + more, int(n - dby)
|
||||
|
||||
try: year = int(year) # chop to int, if it fits
|
||||
except (ValueError, OverflowError): pass
|
||||
|
||||
month = min(n//29 + 1, 12)
|
||||
dbm = _days_before_month(month, year)
|
||||
if dbm >= n:
|
||||
month = month - 1
|
||||
dbm = dbm - _days_in_month(month, year)
|
||||
|
||||
ans.month, ans.day, ans.year = month, n-dbm, year
|
||||
return ans
|
||||
|
||||
def _num2day(n): # return weekday name of day with ordinal n
|
||||
return _DAY_NAMES[ int(n % 7) ]
|
||||
|
||||
|
||||
class Date:
|
||||
def __init__(self, month, day, year):
|
||||
if not 1 <= month <= 12:
|
||||
raise ValueError, 'month must be in 1..12: %r' % (month,)
|
||||
dim = _days_in_month(month, year)
|
||||
if not 1 <= day <= dim:
|
||||
raise ValueError, 'day must be in 1..%r: %r' % (dim, day)
|
||||
self.month, self.day, self.year = month, day, year
|
||||
self.ord = _date2num(self)
|
||||
|
||||
# don't allow setting existing attributes
|
||||
def __setattr__(self, name, value):
|
||||
if self.__dict__.has_key(name):
|
||||
raise AttributeError, 'read-only attribute ' + name
|
||||
self.__dict__[name] = value
|
||||
|
||||
def __cmp__(self, other):
|
||||
return cmp(self.ord, other.ord)
|
||||
|
||||
# define a hash function so dates can be used as dictionary keys
|
||||
def __hash__(self):
|
||||
return hash(self.ord)
|
||||
|
||||
# print as, e.g., Mon 16 Aug 1993
|
||||
def __repr__(self):
|
||||
return '%.3s %2d %.3s %r' % (
|
||||
self.weekday(),
|
||||
self.day,
|
||||
_MONTH_NAMES[self.month-1],
|
||||
self.year)
|
||||
|
||||
# Python 1.1 coerces neither int+date nor date+int
|
||||
def __add__(self, n):
|
||||
if type(n) not in _INT_TYPES:
|
||||
raise TypeError, 'can\'t add %r to date' % type(n)
|
||||
return _num2date(self.ord + n)
|
||||
__radd__ = __add__ # handle int+date
|
||||
|
||||
# Python 1.1 coerces neither date-int nor date-date
|
||||
def __sub__(self, other):
|
||||
if type(other) in _INT_TYPES: # date-int
|
||||
return _num2date(self.ord - other)
|
||||
else:
|
||||
return self.ord - other.ord # date-date
|
||||
|
||||
# complain about int-date
|
||||
def __rsub__(self, other):
|
||||
raise TypeError, 'Can\'t subtract date from integer'
|
||||
|
||||
def weekday(self):
|
||||
return _num2day(self.ord)
|
||||
|
||||
def today():
|
||||
import time
|
||||
local = time.localtime(time.time())
|
||||
return Date(local[1], local[2], local[0])
|
||||
|
||||
class DateTestError(Exception):
|
||||
pass
|
||||
|
||||
def test(firstyear, lastyear):
|
||||
a = Date(9,30,1913)
|
||||
b = Date(9,30,1914)
|
||||
if repr(a) != 'Tue 30 Sep 1913':
|
||||
raise DateTestError, '__repr__ failure'
|
||||
if (not a < b) or a == b or a > b or b != b:
|
||||
raise DateTestError, '__cmp__ failure'
|
||||
if a+365 != b or 365+a != b:
|
||||
raise DateTestError, '__add__ failure'
|
||||
if b-a != 365 or b-365 != a:
|
||||
raise DateTestError, '__sub__ failure'
|
||||
try:
|
||||
x = 1 - a
|
||||
raise DateTestError, 'int-date should have failed'
|
||||
except TypeError:
|
||||
pass
|
||||
try:
|
||||
x = a + b
|
||||
raise DateTestError, 'date+date should have failed'
|
||||
except TypeError:
|
||||
pass
|
||||
if a.weekday() != 'Tuesday':
|
||||
raise DateTestError, 'weekday() failure'
|
||||
if max(a,b) is not b or min(a,b) is not a:
|
||||
raise DateTestError, 'min/max failure'
|
||||
d = {a-1:b, b:a+1}
|
||||
if d[b-366] != b or d[a+(b-a)] != Date(10,1,1913):
|
||||
raise DateTestError, 'dictionary failure'
|
||||
|
||||
# verify date<->number conversions for first and last days for
|
||||
# all years in firstyear .. lastyear
|
||||
|
||||
lord = _days_before_year(firstyear)
|
||||
y = firstyear
|
||||
while y <= lastyear:
|
||||
ford = lord + 1
|
||||
lord = ford + _days_in_year(y) - 1
|
||||
fd, ld = Date(1,1,y), Date(12,31,y)
|
||||
if (fd.ord,ld.ord) != (ford,lord):
|
||||
raise DateTestError, ('date->num failed', y)
|
||||
fd, ld = _num2date(ford), _num2date(lord)
|
||||
if (1,1,y,12,31,y) != \
|
||||
(fd.month,fd.day,fd.year,ld.month,ld.day,ld.year):
|
||||
raise DateTestError, ('num->date failed', y)
|
||||
y = y + 1
|
||||
|
||||
if __name__ == '__main__':
|
||||
test(1850, 2150)
|
|
@ -0,0 +1,66 @@
|
|||
# A wrapper around the (optional) built-in class dbm, supporting keys
|
||||
# and values of almost any type instead of just string.
|
||||
# (Actually, this works only for keys and values that can be read back
|
||||
# correctly after being converted to a string.)
|
||||
|
||||
|
||||
class Dbm:
|
||||
|
||||
def __init__(self, filename, mode, perm):
|
||||
import dbm
|
||||
self.db = dbm.open(filename, mode, perm)
|
||||
|
||||
def __repr__(self):
|
||||
s = ''
|
||||
for key in self.keys():
|
||||
t = repr(key) + ': ' + repr(self[key])
|
||||
if s: t = ', ' + t
|
||||
s = s + t
|
||||
return '{' + s + '}'
|
||||
|
||||
def __len__(self):
|
||||
return len(self.db)
|
||||
|
||||
def __getitem__(self, key):
|
||||
return eval(self.db[repr(key)])
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self.db[repr(key)] = repr(value)
|
||||
|
||||
def __delitem__(self, key):
|
||||
del self.db[repr(key)]
|
||||
|
||||
def keys(self):
|
||||
res = []
|
||||
for key in self.db.keys():
|
||||
res.append(eval(key))
|
||||
return res
|
||||
|
||||
def has_key(self, key):
|
||||
return self.db.has_key(repr(key))
|
||||
|
||||
|
||||
def test():
|
||||
d = Dbm('@dbm', 'rw', 0600)
|
||||
print d
|
||||
while 1:
|
||||
try:
|
||||
key = input('key: ')
|
||||
if d.has_key(key):
|
||||
value = d[key]
|
||||
print 'currently:', value
|
||||
value = input('value: ')
|
||||
if value is None:
|
||||
del d[key]
|
||||
else:
|
||||
d[key] = value
|
||||
except KeyboardInterrupt:
|
||||
print ''
|
||||
print d
|
||||
except EOFError:
|
||||
print '[eof]'
|
||||
break
|
||||
print d
|
||||
|
||||
|
||||
test()
|
|
@ -0,0 +1,12 @@
|
|||
Examples of classes that implement special operators (see reference manual):
|
||||
|
||||
Complex.py Complex numbers
|
||||
Dates.py Date manipulation package by Tim Peters
|
||||
Dbm.py Wrapper around built-in dbm, supporting arbitrary values
|
||||
Range.py Example of a generator: re-implement built-in range()
|
||||
Rev.py Yield the reverse of a sequence
|
||||
Vec.py A simple vector class
|
||||
bitvec.py A bit-vector class by Jan-Hein B\"uhrman
|
||||
|
||||
(For straightforward examples of basic class features, such as use of
|
||||
methods and inheritance, see the library code.)
|
|
@ -0,0 +1,93 @@
|
|||
"""Example of a generator: re-implement the built-in range function
|
||||
without actually constructing the list of values.
|
||||
|
||||
OldStyleRange is coded in the way required to work in a 'for' loop before
|
||||
iterators were introduced into the language; using __getitem__ and __len__ .
|
||||
|
||||
"""
|
||||
def handleargs(arglist):
|
||||
"""Take list of arguments and extract/create proper start, stop, and step
|
||||
values and return in a tuple"""
|
||||
try:
|
||||
if len(arglist) == 1:
|
||||
return 0, int(arglist[0]), 1
|
||||
elif len(arglist) == 2:
|
||||
return int(arglist[0]), int(arglist[1]), 1
|
||||
elif len(arglist) == 3:
|
||||
if arglist[2] == 0:
|
||||
raise ValueError("step argument must not be zero")
|
||||
return tuple(int(x) for x in arglist)
|
||||
else:
|
||||
raise TypeError("range() accepts 1-3 arguments, given", len(arglist))
|
||||
except TypeError:
|
||||
raise TypeError("range() arguments must be numbers or strings "
|
||||
"representing numbers")
|
||||
|
||||
def genrange(*a):
|
||||
"""Function to implement 'range' as a generator"""
|
||||
start, stop, step = handleargs(a)
|
||||
value = start
|
||||
while value < stop:
|
||||
yield value
|
||||
value += step
|
||||
|
||||
class oldrange:
|
||||
"""Class implementing a range object.
|
||||
To the user the instances feel like immutable sequences
|
||||
(and you can't concatenate or slice them)
|
||||
|
||||
Done using the old way (pre-iterators; __len__ and __getitem__) to have an
|
||||
object be used by a 'for' loop.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, *a):
|
||||
""" Initialize start, stop, and step values along with calculating the
|
||||
nubmer of values (what __len__ will return) in the range"""
|
||||
self.start, self.stop, self.step = handleargs(a)
|
||||
self.len = max(0, (self.stop - self.start) // self.step)
|
||||
|
||||
def __repr__(self):
|
||||
"""implement repr(x) which is also used by print"""
|
||||
return 'range(%r, %r, %r)' % (self.start, self.stop, self.step)
|
||||
|
||||
def __len__(self):
|
||||
"""implement len(x)"""
|
||||
return self.len
|
||||
|
||||
def __getitem__(self, i):
|
||||
"""implement x[i]"""
|
||||
if 0 <= i <= self.len:
|
||||
return self.start + self.step * i
|
||||
else:
|
||||
raise IndexError, 'range[i] index out of range'
|
||||
|
||||
|
||||
def test():
|
||||
import time, __builtin__
|
||||
#Just a quick sanity check
|
||||
correct_result = __builtin__.range(5, 100, 3)
|
||||
oldrange_result = list(oldrange(5, 100, 3))
|
||||
genrange_result = list(genrange(5, 100, 3))
|
||||
if genrange_result != correct_result or oldrange_result != correct_result:
|
||||
raise Exception("error in implementation:\ncorrect = %s"
|
||||
"\nold-style = %s\ngenerator = %s" %
|
||||
(correct_result, oldrange_result, genrange_result))
|
||||
print "Timings for range(1000):"
|
||||
t1 = time.time()
|
||||
for i in oldrange(1000):
|
||||
pass
|
||||
t2 = time.time()
|
||||
for i in genrange(1000):
|
||||
pass
|
||||
t3 = time.time()
|
||||
for i in __builtin__.range(1000):
|
||||
pass
|
||||
t4 = time.time()
|
||||
print t2-t1, 'sec (old-style class)'
|
||||
print t3-t2, 'sec (generator)'
|
||||
print t4-t3, 'sec (built-in)'
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test()
|
|
@ -0,0 +1,95 @@
|
|||
'''
|
||||
A class which presents the reverse of a sequence without duplicating it.
|
||||
From: "Steven D. Majewski" <sdm7g@elvis.med.virginia.edu>
|
||||
|
||||
It works on mutable or inmutable sequences.
|
||||
|
||||
>>> chars = list(Rev('Hello World!'))
|
||||
>>> print ''.join(chars)
|
||||
!dlroW olleH
|
||||
|
||||
The .forw is so you can use anonymous sequences in __init__, and still
|
||||
keep a reference the forward sequence. )
|
||||
If you give it a non-anonymous mutable sequence, the reverse sequence
|
||||
will track the updated values. ( but not reassignment! - another
|
||||
good reason to use anonymous values in creating the sequence to avoid
|
||||
confusion. Maybe it should be change to copy input sequence to break
|
||||
the connection completely ? )
|
||||
|
||||
>>> nnn = range(3)
|
||||
>>> rnn = Rev(nnn)
|
||||
>>> for n in rnn: print n
|
||||
...
|
||||
2
|
||||
1
|
||||
0
|
||||
>>> for n in range(4, 6): nnn.append(n) # update nnn
|
||||
...
|
||||
>>> for n in rnn: print n # prints reversed updated values
|
||||
...
|
||||
5
|
||||
4
|
||||
2
|
||||
1
|
||||
0
|
||||
>>> nnn = nnn[1:-1]
|
||||
>>> nnn
|
||||
[1, 2, 4]
|
||||
>>> for n in rnn: print n # prints reversed values of old nnn
|
||||
...
|
||||
5
|
||||
4
|
||||
2
|
||||
1
|
||||
0
|
||||
|
||||
#
|
||||
>>> WH = Rev('Hello World!')
|
||||
>>> print WH.forw, WH.back
|
||||
Hello World! !dlroW olleH
|
||||
>>> nnn = Rev(range(1, 10))
|
||||
>>> print nnn.forw
|
||||
[1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||
>>> print nnn.back
|
||||
[9, 8, 7, 6, 5, 4, 3, 2, 1]
|
||||
|
||||
>>> rrr = Rev(nnn)
|
||||
>>> rrr
|
||||
<1, 2, 3, 4, 5, 6, 7, 8, 9>
|
||||
|
||||
'''
|
||||
|
||||
class Rev:
|
||||
def __init__(self, seq):
|
||||
self.forw = seq
|
||||
self.back = self
|
||||
|
||||
def __len__(self):
|
||||
return len(self.forw)
|
||||
|
||||
def __getitem__(self, j):
|
||||
return self.forw[-(j + 1)]
|
||||
|
||||
def __repr__(self):
|
||||
seq = self.forw
|
||||
if isinstance(seq, list):
|
||||
wrap = '[]'
|
||||
sep = ', '
|
||||
elif isinstance(seq, tuple):
|
||||
wrap = '()'
|
||||
sep = ', '
|
||||
elif isinstance(seq, str):
|
||||
wrap = ''
|
||||
sep = ''
|
||||
else:
|
||||
wrap = '<>'
|
||||
sep = ', '
|
||||
outstrs = [str(item) for item in self.back]
|
||||
return wrap[:1] + sep.join(outstrs) + wrap[-1:]
|
||||
|
||||
def _test():
|
||||
import doctest, Rev
|
||||
return doctest.testmod(Rev)
|
||||
|
||||
if __name__ == "__main__":
|
||||
_test()
|
|
@ -0,0 +1,68 @@
|
|||
class Vec:
|
||||
""" A simple vector class
|
||||
|
||||
Instances of the Vec class can be constructed from numbers
|
||||
|
||||
>>> a = Vec(1, 2, 3)
|
||||
>>> b = Vec(3, 2, 1)
|
||||
|
||||
added
|
||||
>>> a + b
|
||||
Vec(4, 4, 4)
|
||||
|
||||
subtracted
|
||||
>>> a - b
|
||||
Vec(-2, 0, 2)
|
||||
|
||||
and multiplied by a scalar on the left
|
||||
>>> 3.0 * a
|
||||
Vec(3.0, 6.0, 9.0)
|
||||
|
||||
or on the right
|
||||
>>> a * 3.0
|
||||
Vec(3.0, 6.0, 9.0)
|
||||
"""
|
||||
def __init__(self, *v):
|
||||
self.v = list(v)
|
||||
|
||||
@classmethod
|
||||
def fromlist(cls, v):
|
||||
if not isinstance(v, list):
|
||||
raise TypeError
|
||||
inst = cls()
|
||||
inst.v = v
|
||||
return inst
|
||||
|
||||
def __repr__(self):
|
||||
args = ', '.join(repr(x) for x in self.v)
|
||||
return 'Vec({0})'.format(args)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.v)
|
||||
|
||||
def __getitem__(self, i):
|
||||
return self.v[i]
|
||||
|
||||
def __add__(self, other):
|
||||
# Element-wise addition
|
||||
v = [x + y for x, y in zip(self.v, other.v)]
|
||||
return Vec.fromlist(v)
|
||||
|
||||
def __sub__(self, other):
|
||||
# Element-wise subtraction
|
||||
v = [x - y for x, y in zip(self.v, other.v)]
|
||||
return Vec.fromlist(v)
|
||||
|
||||
def __mul__(self, scalar):
|
||||
# Multiply by scalar
|
||||
v = [x * scalar for x in self.v]
|
||||
return Vec.fromlist(v)
|
||||
|
||||
__rmul__ = __mul__
|
||||
|
||||
|
||||
def test():
|
||||
import doctest
|
||||
doctest.testmod()
|
||||
|
||||
test()
|
|
@ -0,0 +1,333 @@
|
|||
#
|
||||
# this is a rather strict implementation of a bit vector class
|
||||
# it is accessed the same way as an array of python-ints, except
|
||||
# the value must be 0 or 1
|
||||
#
|
||||
|
||||
import sys; rprt = sys.stderr.write #for debugging
|
||||
|
||||
class error(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def _check_value(value):
|
||||
if type(value) != type(0) or not 0 <= value < 2:
|
||||
raise error, 'bitvec() items must have int value 0 or 1'
|
||||
|
||||
|
||||
import math
|
||||
|
||||
def _compute_len(param):
|
||||
mant, l = math.frexp(float(param))
|
||||
bitmask = 1L << l
|
||||
if bitmask <= param:
|
||||
raise RuntimeError('(param, l) = %r' % ((param, l),))
|
||||
while l:
|
||||
bitmask = bitmask >> 1
|
||||
if param & bitmask:
|
||||
break
|
||||
l = l - 1
|
||||
return l
|
||||
|
||||
|
||||
def _check_key(len, key):
|
||||
if type(key) != type(0):
|
||||
raise TypeError, 'sequence subscript not int'
|
||||
if key < 0:
|
||||
key = key + len
|
||||
if not 0 <= key < len:
|
||||
raise IndexError, 'list index out of range'
|
||||
return key
|
||||
|
||||
def _check_slice(len, i, j):
|
||||
#the type is ok, Python already checked that
|
||||
i, j = max(i, 0), min(len, j)
|
||||
if i > j:
|
||||
i = j
|
||||
return i, j
|
||||
|
||||
|
||||
class BitVec:
|
||||
|
||||
def __init__(self, *params):
|
||||
self._data = 0L
|
||||
self._len = 0
|
||||
if not len(params):
|
||||
pass
|
||||
elif len(params) == 1:
|
||||
param, = params
|
||||
if type(param) == type([]):
|
||||
value = 0L
|
||||
bit_mask = 1L
|
||||
for item in param:
|
||||
# strict check
|
||||
#_check_value(item)
|
||||
if item:
|
||||
value = value | bit_mask
|
||||
bit_mask = bit_mask << 1
|
||||
self._data = value
|
||||
self._len = len(param)
|
||||
elif type(param) == type(0L):
|
||||
if param < 0:
|
||||
raise error, 'bitvec() can\'t handle negative longs'
|
||||
self._data = param
|
||||
self._len = _compute_len(param)
|
||||
else:
|
||||
raise error, 'bitvec() requires array or long parameter'
|
||||
elif len(params) == 2:
|
||||
param, length = params
|
||||
if type(param) == type(0L):
|
||||
if param < 0:
|
||||
raise error, \
|
||||
'can\'t handle negative longs'
|
||||
self._data = param
|
||||
if type(length) != type(0):
|
||||
raise error, 'bitvec()\'s 2nd parameter must be int'
|
||||
computed_length = _compute_len(param)
|
||||
if computed_length > length:
|
||||
print 'warning: bitvec() value is longer than the length indicates, truncating value'
|
||||
self._data = self._data & \
|
||||
((1L << length) - 1)
|
||||
self._len = length
|
||||
else:
|
||||
raise error, 'bitvec() requires array or long parameter'
|
||||
else:
|
||||
raise error, 'bitvec() requires 0 -- 2 parameter(s)'
|
||||
|
||||
|
||||
def append(self, item):
|
||||
#_check_value(item)
|
||||
#self[self._len:self._len] = [item]
|
||||
self[self._len:self._len] = \
|
||||
BitVec(long(not not item), 1)
|
||||
|
||||
|
||||
def count(self, value):
|
||||
#_check_value(value)
|
||||
if value:
|
||||
data = self._data
|
||||
else:
|
||||
data = (~self)._data
|
||||
count = 0
|
||||
while data:
|
||||
data, count = data >> 1, count + (data & 1 != 0)
|
||||
return count
|
||||
|
||||
|
||||
def index(self, value):
|
||||
#_check_value(value):
|
||||
if value:
|
||||
data = self._data
|
||||
else:
|
||||
data = (~self)._data
|
||||
index = 0
|
||||
if not data:
|
||||
raise ValueError, 'list.index(x): x not in list'
|
||||
while not (data & 1):
|
||||
data, index = data >> 1, index + 1
|
||||
return index
|
||||
|
||||
|
||||
def insert(self, index, item):
|
||||
#_check_value(item)
|
||||
#self[index:index] = [item]
|
||||
self[index:index] = BitVec(long(not not item), 1)
|
||||
|
||||
|
||||
def remove(self, value):
|
||||
del self[self.index(value)]
|
||||
|
||||
|
||||
def reverse(self):
|
||||
#ouch, this one is expensive!
|
||||
#for i in self._len>>1: self[i], self[l-i] = self[l-i], self[i]
|
||||
data, result = self._data, 0L
|
||||
for i in range(self._len):
|
||||
if not data:
|
||||
result = result << (self._len - i)
|
||||
break
|
||||
result, data = (result << 1) | (data & 1), data >> 1
|
||||
self._data = result
|
||||
|
||||
|
||||
def sort(self):
|
||||
c = self.count(1)
|
||||
self._data = ((1L << c) - 1) << (self._len - c)
|
||||
|
||||
|
||||
def copy(self):
|
||||
return BitVec(self._data, self._len)
|
||||
|
||||
|
||||
def seq(self):
|
||||
result = []
|
||||
for i in self:
|
||||
result.append(i)
|
||||
return result
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
##rprt('<bitvec class instance object>.' + '__repr__()\n')
|
||||
return 'bitvec(%r, %r)' % (self._data, self._len)
|
||||
|
||||
def __cmp__(self, other, *rest):
|
||||
#rprt('%r.__cmp__%r\n' % (self, (other,) + rest))
|
||||
if type(other) != type(self):
|
||||
other = apply(bitvec, (other, ) + rest)
|
||||
#expensive solution... recursive binary, with slicing
|
||||
length = self._len
|
||||
if length == 0 or other._len == 0:
|
||||
return cmp(length, other._len)
|
||||
if length != other._len:
|
||||
min_length = min(length, other._len)
|
||||
return cmp(self[:min_length], other[:min_length]) or \
|
||||
cmp(self[min_length:], other[min_length:])
|
||||
#the lengths are the same now...
|
||||
if self._data == other._data:
|
||||
return 0
|
||||
if length == 1:
|
||||
return cmp(self[0], other[0])
|
||||
else:
|
||||
length = length >> 1
|
||||
return cmp(self[:length], other[:length]) or \
|
||||
cmp(self[length:], other[length:])
|
||||
|
||||
|
||||
def __len__(self):
|
||||
#rprt('%r.__len__()\n' % (self,))
|
||||
return self._len
|
||||
|
||||
def __getitem__(self, key):
|
||||
#rprt('%r.__getitem__(%r)\n' % (self, key))
|
||||
key = _check_key(self._len, key)
|
||||
return self._data & (1L << key) != 0
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
#rprt('%r.__setitem__(%r, %r)\n' % (self, key, value))
|
||||
key = _check_key(self._len, key)
|
||||
#_check_value(value)
|
||||
if value:
|
||||
self._data = self._data | (1L << key)
|
||||
else:
|
||||
self._data = self._data & ~(1L << key)
|
||||
|
||||
def __delitem__(self, key):
|
||||
#rprt('%r.__delitem__(%r)\n' % (self, key))
|
||||
key = _check_key(self._len, key)
|
||||
#el cheapo solution...
|
||||
self._data = self[:key]._data | self[key+1:]._data >> key
|
||||
self._len = self._len - 1
|
||||
|
||||
def __getslice__(self, i, j):
|
||||
#rprt('%r.__getslice__(%r, %r)\n' % (self, i, j))
|
||||
i, j = _check_slice(self._len, i, j)
|
||||
if i >= j:
|
||||
return BitVec(0L, 0)
|
||||
if i:
|
||||
ndata = self._data >> i
|
||||
else:
|
||||
ndata = self._data
|
||||
nlength = j - i
|
||||
if j != self._len:
|
||||
#we'll have to invent faster variants here
|
||||
#e.g. mod_2exp
|
||||
ndata = ndata & ((1L << nlength) - 1)
|
||||
return BitVec(ndata, nlength)
|
||||
|
||||
def __setslice__(self, i, j, sequence, *rest):
|
||||
#rprt('%s.__setslice__%r\n' % (self, (i, j, sequence) + rest))
|
||||
i, j = _check_slice(self._len, i, j)
|
||||
if type(sequence) != type(self):
|
||||
sequence = apply(bitvec, (sequence, ) + rest)
|
||||
#sequence is now of our own type
|
||||
ls_part = self[:i]
|
||||
ms_part = self[j:]
|
||||
self._data = ls_part._data | \
|
||||
((sequence._data | \
|
||||
(ms_part._data << sequence._len)) << ls_part._len)
|
||||
self._len = self._len - j + i + sequence._len
|
||||
|
||||
def __delslice__(self, i, j):
|
||||
#rprt('%r.__delslice__(%r, %r)\n' % (self, i, j))
|
||||
i, j = _check_slice(self._len, i, j)
|
||||
if i == 0 and j == self._len:
|
||||
self._data, self._len = 0L, 0
|
||||
elif i < j:
|
||||
self._data = self[:i]._data | (self[j:]._data >> i)
|
||||
self._len = self._len - j + i
|
||||
|
||||
def __add__(self, other):
|
||||
#rprt('%r.__add__(%r)\n' % (self, other))
|
||||
retval = self.copy()
|
||||
retval[self._len:self._len] = other
|
||||
return retval
|
||||
|
||||
def __mul__(self, multiplier):
|
||||
#rprt('%r.__mul__(%r)\n' % (self, multiplier))
|
||||
if type(multiplier) != type(0):
|
||||
raise TypeError, 'sequence subscript not int'
|
||||
if multiplier <= 0:
|
||||
return BitVec(0L, 0)
|
||||
elif multiplier == 1:
|
||||
return self.copy()
|
||||
#handle special cases all 0 or all 1...
|
||||
if self._data == 0L:
|
||||
return BitVec(0L, self._len * multiplier)
|
||||
elif (~self)._data == 0L:
|
||||
return ~BitVec(0L, self._len * multiplier)
|
||||
#otherwise el cheapo again...
|
||||
retval = BitVec(0L, 0)
|
||||
while multiplier:
|
||||
retval, multiplier = retval + self, multiplier - 1
|
||||
return retval
|
||||
|
||||
def __and__(self, otherseq, *rest):
|
||||
#rprt('%r.__and__%r\n' % (self, (otherseq,) + rest))
|
||||
if type(otherseq) != type(self):
|
||||
otherseq = apply(bitvec, (otherseq, ) + rest)
|
||||
#sequence is now of our own type
|
||||
return BitVec(self._data & otherseq._data, \
|
||||
min(self._len, otherseq._len))
|
||||
|
||||
|
||||
def __xor__(self, otherseq, *rest):
|
||||
#rprt('%r.__xor__%r\n' % (self, (otherseq,) + rest))
|
||||
if type(otherseq) != type(self):
|
||||
otherseq = apply(bitvec, (otherseq, ) + rest)
|
||||
#sequence is now of our own type
|
||||
return BitVec(self._data ^ otherseq._data, \
|
||||
max(self._len, otherseq._len))
|
||||
|
||||
|
||||
def __or__(self, otherseq, *rest):
|
||||
#rprt('%r.__or__%r\n' % (self, (otherseq,) + rest))
|
||||
if type(otherseq) != type(self):
|
||||
otherseq = apply(bitvec, (otherseq, ) + rest)
|
||||
#sequence is now of our own type
|
||||
return BitVec(self._data | otherseq._data, \
|
||||
max(self._len, otherseq._len))
|
||||
|
||||
|
||||
def __invert__(self):
|
||||
#rprt('%r.__invert__()\n' % (self,))
|
||||
return BitVec(~self._data & ((1L << self._len) - 1), \
|
||||
self._len)
|
||||
|
||||
def __coerce__(self, otherseq, *rest):
|
||||
#needed for *some* of the arithmetic operations
|
||||
#rprt('%r.__coerce__%r\n' % (self, (otherseq,) + rest))
|
||||
if type(otherseq) != type(self):
|
||||
otherseq = apply(bitvec, (otherseq, ) + rest)
|
||||
return self, otherseq
|
||||
|
||||
def __int__(self):
|
||||
return int(self._data)
|
||||
|
||||
def __long__(self):
|
||||
return long(self._data)
|
||||
|
||||
def __float__(self):
|
||||
return float(self._data)
|
||||
|
||||
|
||||
bitvec = BitVec
|
|
@ -0,0 +1,60 @@
|
|||
Subject: Re: What language would you use?
|
||||
From: Tom Christiansen <tchrist@mox.perl.com>
|
||||
Date: 6 Nov 1994 15:14:51 GMT
|
||||
Newsgroups: comp.lang.python,comp.lang.tcl,comp.lang.scheme,comp.lang.misc,comp.lang.perl
|
||||
Message-Id: <39irtb$3t4@csnews.cs.Colorado.EDU>
|
||||
References: <39b7ha$j9v@zeno.nscf.org> <39hhjp$lgn@csnews.cs.Colorado.EDU> <39hvsu$dus@mathserv.mps.ohio-state.edu>
|
||||
|
||||
[...]
|
||||
If you're really into benchmarks, I'd love it if someone were to code up
|
||||
the following problems in tcl, python, and scheme (and whatever else you'd
|
||||
like). Separate versions (one optimized for speed, one for beauty :-) are
|
||||
ok. Post your code so we can time it on our own systems.
|
||||
|
||||
0) Factorial Test (numerics and function calls)
|
||||
|
||||
(we did this already)
|
||||
|
||||
1) Regular Expressions Test
|
||||
|
||||
Read a file of (extended per egrep) regular expressions (one per line),
|
||||
and apply those to all files whose names are listed on the command line.
|
||||
Basically, an 'egrep -f' simulator. Test it with 20 "vt100" patterns
|
||||
against a five /etc/termcap files. Tests using more elaborate patters
|
||||
would also be interesting. Your code should not break if given hundreds
|
||||
of regular expressions or binary files to scan.
|
||||
|
||||
2) Sorting Test
|
||||
|
||||
Sort an input file that consists of lines like this
|
||||
|
||||
var1=23 other=14 ditto=23 fred=2
|
||||
|
||||
such that each output line is sorted WRT to the number. Order
|
||||
of output lines does not change. Resolve collisions using the
|
||||
variable name. e.g.
|
||||
|
||||
fred=2 other=14 ditto=23 var1=23
|
||||
|
||||
Lines may be up to several kilobytes in length and contain
|
||||
zillions of variables.
|
||||
|
||||
3) System Test
|
||||
|
||||
Given a list of directories, report any bogus symbolic links contained
|
||||
anywhere in those subtrees. A bogus symbolic link is one that cannot
|
||||
be resolved because it points to a nonexistent or otherwise
|
||||
unresolvable file. Do *not* use an external find executable.
|
||||
Directories may be very very deep. Print a warning immediately if the
|
||||
system you're running on doesn't support symbolic links.
|
||||
|
||||
|
||||
I'll post perl solutions if people post the others.
|
||||
|
||||
|
||||
--tom
|
||||
--
|
||||
Tom Christiansen Perl Consultant, Gamer, Hiker tchrist@mox.perl.com
|
||||
|
||||
"But Billy! A *small* allowance prepares you for a lifetime of small
|
||||
salaries and for your Social Security payments." --Family Circus
|
|
@ -0,0 +1,4 @@
|
|||
^def
|
||||
^class
|
||||
^import
|
||||
^from
|
|
@ -0,0 +1,47 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
# 1) Regular Expressions Test
|
||||
#
|
||||
# Read a file of (extended per egrep) regular expressions (one per line),
|
||||
# and apply those to all files whose names are listed on the command line.
|
||||
# Basically, an 'egrep -f' simulator. Test it with 20 "vt100" patterns
|
||||
# against a five /etc/termcap files. Tests using more elaborate patters
|
||||
# would also be interesting. Your code should not break if given hundreds
|
||||
# of regular expressions or binary files to scan.
|
||||
|
||||
# This implementation:
|
||||
# - combines all patterns into a single one using ( ... | ... | ... )
|
||||
# - reads patterns from stdin, scans files given as command line arguments
|
||||
# - produces output in the format <file>:<lineno>:<line>
|
||||
# - is only about 2.5 times as slow as egrep (though I couldn't run
|
||||
# Tom's test -- this system, a vanilla SGI, only has /etc/terminfo)
|
||||
|
||||
import string
|
||||
import sys
|
||||
import re
|
||||
|
||||
def main():
|
||||
pats = map(chomp, sys.stdin.readlines())
|
||||
bigpat = '(' + '|'.join(pats) + ')'
|
||||
prog = re.compile(bigpat)
|
||||
|
||||
for file in sys.argv[1:]:
|
||||
try:
|
||||
fp = open(file, 'r')
|
||||
except IOError, msg:
|
||||
print "%s: %s" % (file, msg)
|
||||
continue
|
||||
lineno = 0
|
||||
while 1:
|
||||
line = fp.readline()
|
||||
if not line:
|
||||
break
|
||||
lineno = lineno + 1
|
||||
if prog.search(line):
|
||||
print "%s:%s:%s" % (file, lineno, line),
|
||||
|
||||
def chomp(s):
|
||||
return s.rstrip('\n')
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -0,0 +1,45 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
# 2) Sorting Test
|
||||
#
|
||||
# Sort an input file that consists of lines like this
|
||||
#
|
||||
# var1=23 other=14 ditto=23 fred=2
|
||||
#
|
||||
# such that each output line is sorted WRT to the number. Order
|
||||
# of output lines does not change. Resolve collisions using the
|
||||
# variable name. e.g.
|
||||
#
|
||||
# fred=2 other=14 ditto=23 var1=23
|
||||
#
|
||||
# Lines may be up to several kilobytes in length and contain
|
||||
# zillions of variables.
|
||||
|
||||
# This implementation:
|
||||
# - Reads stdin, writes stdout
|
||||
# - Uses any amount of whitespace to separate fields
|
||||
# - Allows signed numbers
|
||||
# - Treats illegally formatted fields as field=0
|
||||
# - Outputs the sorted fields with exactly one space between them
|
||||
# - Handles blank input lines correctly
|
||||
|
||||
import re
|
||||
import sys
|
||||
|
||||
def main():
|
||||
prog = re.compile('^(.*)=([-+]?[0-9]+)')
|
||||
def makekey(item, prog=prog):
|
||||
match = prog.match(item)
|
||||
if match:
|
||||
var, num = match.groups()
|
||||
return int(num), var
|
||||
else:
|
||||
# Bad input -- pretend it's a var with value 0
|
||||
return 0, item
|
||||
for line in sys.stdin:
|
||||
items = sorted(makekey(item) for item in line.split())
|
||||
for num, var in items:
|
||||
print "%s=%s" % (var, num),
|
||||
print
|
||||
|
||||
main()
|
|
@ -0,0 +1,74 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
# 3) System Test
|
||||
#
|
||||
# Given a list of directories, report any bogus symbolic links contained
|
||||
# anywhere in those subtrees. A bogus symbolic link is one that cannot
|
||||
# be resolved because it points to a nonexistent or otherwise
|
||||
# unresolvable file. Do *not* use an external find executable.
|
||||
# Directories may be very very deep. Print a warning immediately if the
|
||||
# system you're running on doesn't support symbolic links.
|
||||
|
||||
# This implementation:
|
||||
# - takes one optional argument, using the current directory as default
|
||||
# - uses chdir to increase performance
|
||||
# - sorts the names per directory
|
||||
# - prints output lines of the form "path1 -> path2" as it goes
|
||||
# - prints error messages about directories it can't list or chdir into
|
||||
|
||||
import os
|
||||
import sys
|
||||
from stat import *
|
||||
|
||||
def main():
|
||||
try:
|
||||
# Note: can't test for presence of lstat -- it's always there
|
||||
dummy = os.readlink
|
||||
except AttributeError:
|
||||
print "This system doesn't have symbolic links"
|
||||
sys.exit(0)
|
||||
if sys.argv[1:]:
|
||||
prefix = sys.argv[1]
|
||||
else:
|
||||
prefix = ''
|
||||
if prefix:
|
||||
os.chdir(prefix)
|
||||
if prefix[-1:] != '/': prefix = prefix + '/'
|
||||
reportboguslinks(prefix)
|
||||
else:
|
||||
reportboguslinks('')
|
||||
|
||||
def reportboguslinks(prefix):
|
||||
try:
|
||||
names = os.listdir('.')
|
||||
except os.error, msg:
|
||||
print "%s%s: can't list: %s" % (prefix, '.', msg)
|
||||
return
|
||||
names.sort()
|
||||
for name in names:
|
||||
if name == os.curdir or name == os.pardir:
|
||||
continue
|
||||
try:
|
||||
mode = os.lstat(name)[ST_MODE]
|
||||
except os.error:
|
||||
print "%s%s: can't stat: %s" % (prefix, name, msg)
|
||||
continue
|
||||
if S_ISLNK(mode):
|
||||
try:
|
||||
os.stat(name)
|
||||
except os.error:
|
||||
print "%s%s -> %s" % \
|
||||
(prefix, name, os.readlink(name))
|
||||
elif S_ISDIR(mode):
|
||||
try:
|
||||
os.chdir(name)
|
||||
except os.error, msg:
|
||||
print "%s%s: can't chdir: %s" % \
|
||||
(prefix, name, msg)
|
||||
continue
|
||||
try:
|
||||
reportboguslinks(prefix + name + '/')
|
||||
finally:
|
||||
os.chdir('..')
|
||||
|
||||
main()
|
|
@ -0,0 +1,10 @@
|
|||
This is the Python version of the MD5 test program from the MD5
|
||||
Internet Draft (Rivest and Dusse, The MD5 Message-Digest Algorithm, 10
|
||||
July 1991). The file "foo" contains the string "abc" with no trailing
|
||||
newline.
|
||||
|
||||
When called without arguments, it acts as a filter. When called with
|
||||
"-x", it executes a self-test, and the output should literally match
|
||||
the output given in the RFC.
|
||||
|
||||
Code by Jan-Hein B\"uhrman after the original in C.
|
|
@ -0,0 +1 @@
|
|||
abc
|
|
@ -0,0 +1,123 @@
|
|||
import string
|
||||
import md5
|
||||
from sys import argv
|
||||
|
||||
def MDPrint(str):
|
||||
outstr = ''
|
||||
for i in str:
|
||||
o = ord(i)
|
||||
outstr = (outstr
|
||||
+ string.hexdigits[(o >> 4) & 0xF]
|
||||
+ string.hexdigits[o & 0xF])
|
||||
print outstr,
|
||||
|
||||
|
||||
from time import time
|
||||
|
||||
def makestr(start, end):
|
||||
result = ''
|
||||
for i in range(start, end + 1):
|
||||
result = result + chr(i)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def MDTimeTrial():
|
||||
TEST_BLOCK_SIZE = 1000
|
||||
TEST_BLOCKS = 10000
|
||||
|
||||
TEST_BYTES = TEST_BLOCK_SIZE * TEST_BLOCKS
|
||||
|
||||
# initialize test data, need temporary string filler
|
||||
|
||||
filsiz = 1 << 8
|
||||
filler = makestr(0, filsiz-1)
|
||||
data = filler * (TEST_BLOCK_SIZE // filsiz)
|
||||
data = data + filler[:(TEST_BLOCK_SIZE % filsiz)]
|
||||
|
||||
del filsiz, filler
|
||||
|
||||
|
||||
# start timer
|
||||
print 'MD5 time trial. Processing', TEST_BYTES, 'characters...'
|
||||
t1 = time()
|
||||
|
||||
mdContext = md5.new()
|
||||
|
||||
for i in range(TEST_BLOCKS):
|
||||
mdContext.update(data)
|
||||
|
||||
str = mdContext.digest()
|
||||
t2 = time()
|
||||
|
||||
MDPrint(str)
|
||||
print 'is digest of test input.'
|
||||
print 'Seconds to process test input:', t2 - t1
|
||||
print 'Characters processed per second:', TEST_BYTES / (t2 - t1)
|
||||
|
||||
|
||||
def MDString(str):
|
||||
MDPrint(md5.new(str).digest())
|
||||
print '"' + str + '"'
|
||||
|
||||
|
||||
def MDFile(filename):
|
||||
f = open(filename, 'rb')
|
||||
mdContext = md5.new()
|
||||
|
||||
while 1:
|
||||
data = f.read(1024)
|
||||
if not data:
|
||||
break
|
||||
mdContext.update(data)
|
||||
|
||||
MDPrint(mdContext.digest())
|
||||
print filename
|
||||
|
||||
|
||||
import sys
|
||||
|
||||
def MDFilter():
|
||||
mdContext = md5.new()
|
||||
|
||||
while 1:
|
||||
data = sys.stdin.read(16)
|
||||
if not data:
|
||||
break
|
||||
mdContext.update(data)
|
||||
|
||||
MDPrint(mdContext.digest())
|
||||
print
|
||||
|
||||
|
||||
def MDTestSuite():
|
||||
print 'MD5 test suite results:'
|
||||
MDString('')
|
||||
MDString('a')
|
||||
MDString('abc')
|
||||
MDString('message digest')
|
||||
MDString(makestr(ord('a'), ord('z')))
|
||||
MDString(makestr(ord('A'), ord('Z'))
|
||||
+ makestr(ord('a'), ord('z'))
|
||||
+ makestr(ord('0'), ord('9')))
|
||||
MDString((makestr(ord('1'), ord('9')) + '0') * 8)
|
||||
|
||||
# Contents of file foo are "abc"
|
||||
MDFile('foo')
|
||||
|
||||
|
||||
# I don't wanna use getopt(), since I want to use the same i/f...
|
||||
def main():
|
||||
if len(argv) == 1:
|
||||
MDFilter()
|
||||
for arg in argv[1:]:
|
||||
if arg[:2] == '-s':
|
||||
MDString(arg[2:])
|
||||
elif arg == '-t':
|
||||
MDTimeTrial()
|
||||
elif arg == '-x':
|
||||
MDTestSuite()
|
||||
else:
|
||||
MDFile(arg)
|
||||
|
||||
main()
|
|
@ -0,0 +1,113 @@
|
|||
"""Support Eiffel-style preconditions and postconditions.
|
||||
|
||||
For example,
|
||||
|
||||
class C:
|
||||
def m1(self, arg):
|
||||
require arg > 0
|
||||
return whatever
|
||||
ensure Result > arg
|
||||
|
||||
can be written (clumsily, I agree) as:
|
||||
|
||||
class C(Eiffel):
|
||||
def m1(self, arg):
|
||||
return whatever
|
||||
def m1_pre(self, arg):
|
||||
assert arg > 0
|
||||
def m1_post(self, Result, arg):
|
||||
assert Result > arg
|
||||
|
||||
Pre- and post-conditions for a method, being implemented as methods
|
||||
themselves, are inherited independently from the method. This gives
|
||||
much of the same effect of Eiffel, where pre- and post-conditions are
|
||||
inherited when a method is overridden by a derived class. However,
|
||||
when a derived class in Python needs to extend a pre- or
|
||||
post-condition, it must manually merge the base class' pre- or
|
||||
post-condition with that defined in the derived class', for example:
|
||||
|
||||
class D(C):
|
||||
def m1(self, arg):
|
||||
return arg**2
|
||||
def m1_post(self, Result, arg):
|
||||
C.m1_post(self, Result, arg)
|
||||
assert Result < 100
|
||||
|
||||
This gives derived classes more freedom but also more responsibility
|
||||
than in Eiffel, where the compiler automatically takes care of this.
|
||||
|
||||
In Eiffel, pre-conditions combine using contravariance, meaning a
|
||||
derived class can only make a pre-condition weaker; in Python, this is
|
||||
up to the derived class. For example, a derived class that takes away
|
||||
the requirement that arg > 0 could write:
|
||||
|
||||
def m1_pre(self, arg):
|
||||
pass
|
||||
|
||||
but one could equally write a derived class that makes a stronger
|
||||
requirement:
|
||||
|
||||
def m1_pre(self, arg):
|
||||
require arg > 50
|
||||
|
||||
It would be easy to modify the classes shown here so that pre- and
|
||||
post-conditions can be disabled (separately, on a per-class basis).
|
||||
|
||||
A different design would have the pre- or post-condition testing
|
||||
functions return true for success and false for failure. This would
|
||||
make it possible to implement automatic combination of inherited
|
||||
and new pre-/post-conditions. All this is left as an exercise to the
|
||||
reader.
|
||||
|
||||
"""
|
||||
|
||||
from Meta import MetaClass, MetaHelper, MetaMethodWrapper
|
||||
|
||||
class EiffelMethodWrapper(MetaMethodWrapper):
|
||||
|
||||
def __init__(self, func, inst):
|
||||
MetaMethodWrapper.__init__(self, func, inst)
|
||||
# Note that the following causes recursive wrappers around
|
||||
# the pre-/post-condition testing methods. These are harmless
|
||||
# but inefficient; to avoid them, the lookup must be done
|
||||
# using the class.
|
||||
try:
|
||||
self.pre = getattr(inst, self.__name__ + "_pre")
|
||||
except AttributeError:
|
||||
self.pre = None
|
||||
try:
|
||||
self.post = getattr(inst, self.__name__ + "_post")
|
||||
except AttributeError:
|
||||
self.post = None
|
||||
|
||||
def __call__(self, *args, **kw):
|
||||
if self.pre:
|
||||
apply(self.pre, args, kw)
|
||||
Result = apply(self.func, (self.inst,) + args, kw)
|
||||
if self.post:
|
||||
apply(self.post, (Result,) + args, kw)
|
||||
return Result
|
||||
|
||||
class EiffelHelper(MetaHelper):
|
||||
__methodwrapper__ = EiffelMethodWrapper
|
||||
|
||||
class EiffelMetaClass(MetaClass):
|
||||
__helper__ = EiffelHelper
|
||||
|
||||
Eiffel = EiffelMetaClass('Eiffel', (), {})
|
||||
|
||||
|
||||
def _test():
|
||||
class C(Eiffel):
|
||||
def m1(self, arg):
|
||||
return arg+1
|
||||
def m1_pre(self, arg):
|
||||
assert arg > 0, "precondition for m1 failed"
|
||||
def m1_post(self, Result, arg):
|
||||
assert Result > arg
|
||||
x = C()
|
||||
x.m1(12)
|
||||
## x.m1(-1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
_test()
|
|
@ -0,0 +1,169 @@
|
|||
"""Enumeration metaclass.
|
||||
|
||||
XXX This is very much a work in progress.
|
||||
|
||||
"""
|
||||
|
||||
import string
|
||||
|
||||
class EnumMetaClass:
|
||||
"""Metaclass for enumeration.
|
||||
|
||||
To define your own enumeration, do something like
|
||||
|
||||
class Color(Enum):
|
||||
red = 1
|
||||
green = 2
|
||||
blue = 3
|
||||
|
||||
Now, Color.red, Color.green and Color.blue behave totally
|
||||
different: they are enumerated values, not integers.
|
||||
|
||||
Enumerations cannot be instantiated; however they can be
|
||||
subclassed.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, name, bases, dict):
|
||||
"""Constructor -- create an enumeration.
|
||||
|
||||
Called at the end of the class statement. The arguments are
|
||||
the name of the new class, a tuple containing the base
|
||||
classes, and a dictionary containing everything that was
|
||||
entered in the class' namespace during execution of the class
|
||||
statement. In the above example, it would be {'red': 1,
|
||||
'green': 2, 'blue': 3}.
|
||||
|
||||
"""
|
||||
for base in bases:
|
||||
if base.__class__ is not EnumMetaClass:
|
||||
raise TypeError, "Enumeration base class must be enumeration"
|
||||
bases = filter(lambda x: x is not Enum, bases)
|
||||
self.__name__ = name
|
||||
self.__bases__ = bases
|
||||
self.__dict = {}
|
||||
for key, value in dict.items():
|
||||
self.__dict[key] = EnumInstance(name, key, value)
|
||||
|
||||
def __getattr__(self, name):
|
||||
"""Return an enumeration value.
|
||||
|
||||
For example, Color.red returns the value corresponding to red.
|
||||
|
||||
XXX Perhaps the values should be created in the constructor?
|
||||
|
||||
This looks in the class dictionary and if it is not found
|
||||
there asks the base classes.
|
||||
|
||||
The special attribute __members__ returns the list of names
|
||||
defined in this class (it does not merge in the names defined
|
||||
in base classes).
|
||||
|
||||
"""
|
||||
if name == '__members__':
|
||||
return self.__dict.keys()
|
||||
|
||||
try:
|
||||
return self.__dict[name]
|
||||
except KeyError:
|
||||
for base in self.__bases__:
|
||||
try:
|
||||
return getattr(base, name)
|
||||
except AttributeError:
|
||||
continue
|
||||
|
||||
raise AttributeError, name
|
||||
|
||||
def __repr__(self):
|
||||
s = self.__name__
|
||||
if self.__bases__:
|
||||
s = s + '(' + string.join(map(lambda x: x.__name__,
|
||||
self.__bases__), ", ") + ')'
|
||||
if self.__dict:
|
||||
list = []
|
||||
for key, value in self.__dict.items():
|
||||
list.append("%s: %s" % (key, int(value)))
|
||||
s = "%s: {%s}" % (s, string.join(list, ", "))
|
||||
return s
|
||||
|
||||
|
||||
class EnumInstance:
|
||||
"""Class to represent an enumeration value.
|
||||
|
||||
EnumInstance('Color', 'red', 12) prints as 'Color.red' and behaves
|
||||
like the integer 12 when compared, but doesn't support arithmetic.
|
||||
|
||||
XXX Should it record the actual enumeration rather than just its
|
||||
name?
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, classname, enumname, value):
|
||||
self.__classname = classname
|
||||
self.__enumname = enumname
|
||||
self.__value = value
|
||||
|
||||
def __int__(self):
|
||||
return self.__value
|
||||
|
||||
def __repr__(self):
|
||||
return "EnumInstance(%r, %r, %r)" % (self.__classname,
|
||||
self.__enumname,
|
||||
self.__value)
|
||||
|
||||
def __str__(self):
|
||||
return "%s.%s" % (self.__classname, self.__enumname)
|
||||
|
||||
def __cmp__(self, other):
|
||||
return cmp(self.__value, int(other))
|
||||
|
||||
|
||||
# Create the base class for enumerations.
|
||||
# It is an empty enumeration.
|
||||
Enum = EnumMetaClass("Enum", (), {})
|
||||
|
||||
|
||||
def _test():
|
||||
|
||||
class Color(Enum):
|
||||
red = 1
|
||||
green = 2
|
||||
blue = 3
|
||||
|
||||
print Color.red
|
||||
print dir(Color)
|
||||
|
||||
print Color.red == Color.red
|
||||
print Color.red == Color.blue
|
||||
print Color.red == 1
|
||||
print Color.red == 2
|
||||
|
||||
class ExtendedColor(Color):
|
||||
white = 0
|
||||
orange = 4
|
||||
yellow = 5
|
||||
purple = 6
|
||||
black = 7
|
||||
|
||||
print ExtendedColor.orange
|
||||
print ExtendedColor.red
|
||||
|
||||
print Color.red == ExtendedColor.red
|
||||
|
||||
class OtherColor(Enum):
|
||||
white = 4
|
||||
blue = 5
|
||||
|
||||
class MergedColor(Color, OtherColor):
|
||||
pass
|
||||
|
||||
print MergedColor.red
|
||||
print MergedColor.white
|
||||
|
||||
print Color
|
||||
print ExtendedColor
|
||||
print OtherColor
|
||||
print MergedColor
|
||||
|
||||
if __name__ == '__main__':
|
||||
_test()
|
|
@ -0,0 +1,118 @@
|
|||
"""Generic metaclass.
|
||||
|
||||
XXX This is very much a work in progress.
|
||||
|
||||
"""
|
||||
|
||||
import types
|
||||
|
||||
class MetaMethodWrapper:
|
||||
|
||||
def __init__(self, func, inst):
|
||||
self.func = func
|
||||
self.inst = inst
|
||||
self.__name__ = self.func.__name__
|
||||
|
||||
def __call__(self, *args, **kw):
|
||||
return apply(self.func, (self.inst,) + args, kw)
|
||||
|
||||
class MetaHelper:
|
||||
|
||||
__methodwrapper__ = MetaMethodWrapper # For derived helpers to override
|
||||
|
||||
def __helperinit__(self, formalclass):
|
||||
self.__formalclass__ = formalclass
|
||||
|
||||
def __getattr__(self, name):
|
||||
# Invoked for any attr not in the instance's __dict__
|
||||
try:
|
||||
raw = self.__formalclass__.__getattr__(name)
|
||||
except AttributeError:
|
||||
try:
|
||||
ga = self.__formalclass__.__getattr__('__usergetattr__')
|
||||
except (KeyError, AttributeError):
|
||||
raise AttributeError, name
|
||||
return ga(self, name)
|
||||
if type(raw) != types.FunctionType:
|
||||
return raw
|
||||
return self.__methodwrapper__(raw, self)
|
||||
|
||||
class MetaClass:
|
||||
|
||||
"""A generic metaclass.
|
||||
|
||||
This can be subclassed to implement various kinds of meta-behavior.
|
||||
|
||||
"""
|
||||
|
||||
__helper__ = MetaHelper # For derived metaclasses to override
|
||||
|
||||
__inited = 0
|
||||
|
||||
def __init__(self, name, bases, dict):
|
||||
try:
|
||||
ga = dict['__getattr__']
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
dict['__usergetattr__'] = ga
|
||||
del dict['__getattr__']
|
||||
self.__name__ = name
|
||||
self.__bases__ = bases
|
||||
self.__realdict__ = dict
|
||||
self.__inited = 1
|
||||
|
||||
def __getattr__(self, name):
|
||||
try:
|
||||
return self.__realdict__[name]
|
||||
except KeyError:
|
||||
for base in self.__bases__:
|
||||
try:
|
||||
return base.__getattr__(name)
|
||||
except AttributeError:
|
||||
pass
|
||||
raise AttributeError, name
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
if not self.__inited:
|
||||
self.__dict__[name] = value
|
||||
else:
|
||||
self.__realdict__[name] = value
|
||||
|
||||
def __call__(self, *args, **kw):
|
||||
inst = self.__helper__()
|
||||
inst.__helperinit__(self)
|
||||
try:
|
||||
init = inst.__getattr__('__init__')
|
||||
except AttributeError:
|
||||
init = lambda: None
|
||||
apply(init, args, kw)
|
||||
return inst
|
||||
|
||||
|
||||
Meta = MetaClass('Meta', (), {})
|
||||
|
||||
|
||||
def _test():
|
||||
class C(Meta):
|
||||
def __init__(self, *args):
|
||||
print "__init__, args =", args
|
||||
def m1(self, x):
|
||||
print "m1(x=%r)" % (x,)
|
||||
print C
|
||||
x = C()
|
||||
print x
|
||||
x.m1(12)
|
||||
class D(C):
|
||||
def __getattr__(self, name):
|
||||
if name[:2] == '__': raise AttributeError, name
|
||||
return "getattr:%s" % name
|
||||
x = D()
|
||||
print x.foo
|
||||
print x._foo
|
||||
## print x.__foo
|
||||
## print x.__foo__
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
_test()
|
|
@ -0,0 +1,45 @@
|
|||
import types
|
||||
|
||||
class Tracing:
|
||||
def __init__(self, name, bases, namespace):
|
||||
"""Create a new class."""
|
||||
self.__name__ = name
|
||||
self.__bases__ = bases
|
||||
self.__namespace__ = namespace
|
||||
def __call__(self):
|
||||
"""Create a new instance."""
|
||||
return Instance(self)
|
||||
|
||||
class Instance:
|
||||
def __init__(self, klass):
|
||||
self.__klass__ = klass
|
||||
def __getattr__(self, name):
|
||||
try:
|
||||
value = self.__klass__.__namespace__[name]
|
||||
except KeyError:
|
||||
raise AttributeError, name
|
||||
if type(value) is not types.FunctionType:
|
||||
return value
|
||||
return BoundMethod(value, self)
|
||||
|
||||
class BoundMethod:
|
||||
def __init__(self, function, instance):
|
||||
self.function = function
|
||||
self.instance = instance
|
||||
def __call__(self, *args):
|
||||
print "calling", self.function, "for", self.instance, "with", args
|
||||
return apply(self.function, (self.instance,) + args)
|
||||
|
||||
Trace = Tracing('Trace', (), {})
|
||||
|
||||
class MyTracedClass(Trace):
|
||||
def method1(self, a):
|
||||
self.a = a
|
||||
def method2(self):
|
||||
return self.a
|
||||
|
||||
aninstance = MyTracedClass()
|
||||
|
||||
aninstance.method1(10)
|
||||
|
||||
print aninstance.method2()
|
|
@ -0,0 +1,256 @@
|
|||
"""Synchronization metaclass.
|
||||
|
||||
This metaclass makes it possible to declare synchronized methods.
|
||||
|
||||
"""
|
||||
|
||||
import thread
|
||||
|
||||
# First we need to define a reentrant lock.
|
||||
# This is generally useful and should probably be in a standard Python
|
||||
# library module. For now, we in-line it.
|
||||
|
||||
class Lock:
|
||||
|
||||
"""Reentrant lock.
|
||||
|
||||
This is a mutex-like object which can be acquired by the same
|
||||
thread more than once. It keeps a reference count of the number
|
||||
of times it has been acquired by the same thread. Each acquire()
|
||||
call must be matched by a release() call and only the last
|
||||
release() call actually releases the lock for acquisition by
|
||||
another thread.
|
||||
|
||||
The implementation uses two locks internally:
|
||||
|
||||
__mutex is a short term lock used to protect the instance variables
|
||||
__wait is the lock for which other threads wait
|
||||
|
||||
A thread intending to acquire both locks should acquire __wait
|
||||
first.
|
||||
|
||||
The implementation uses two other instance variables, protected by
|
||||
locking __mutex:
|
||||
|
||||
__tid is the thread ID of the thread that currently has the lock
|
||||
__count is the number of times the current thread has acquired it
|
||||
|
||||
When the lock is released, __tid is None and __count is zero.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""Constructor. Initialize all instance variables."""
|
||||
self.__mutex = thread.allocate_lock()
|
||||
self.__wait = thread.allocate_lock()
|
||||
self.__tid = None
|
||||
self.__count = 0
|
||||
|
||||
def acquire(self, flag=1):
|
||||
"""Acquire the lock.
|
||||
|
||||
If the optional flag argument is false, returns immediately
|
||||
when it cannot acquire the __wait lock without blocking (it
|
||||
may still block for a little while in order to acquire the
|
||||
__mutex lock).
|
||||
|
||||
The return value is only relevant when the flag argument is
|
||||
false; it is 1 if the lock is acquired, 0 if not.
|
||||
|
||||
"""
|
||||
self.__mutex.acquire()
|
||||
try:
|
||||
if self.__tid == thread.get_ident():
|
||||
self.__count = self.__count + 1
|
||||
return 1
|
||||
finally:
|
||||
self.__mutex.release()
|
||||
locked = self.__wait.acquire(flag)
|
||||
if not flag and not locked:
|
||||
return 0
|
||||
try:
|
||||
self.__mutex.acquire()
|
||||
assert self.__tid == None
|
||||
assert self.__count == 0
|
||||
self.__tid = thread.get_ident()
|
||||
self.__count = 1
|
||||
return 1
|
||||
finally:
|
||||
self.__mutex.release()
|
||||
|
||||
def release(self):
|
||||
"""Release the lock.
|
||||
|
||||
If this thread doesn't currently have the lock, an assertion
|
||||
error is raised.
|
||||
|
||||
Only allow another thread to acquire the lock when the count
|
||||
reaches zero after decrementing it.
|
||||
|
||||
"""
|
||||
self.__mutex.acquire()
|
||||
try:
|
||||
assert self.__tid == thread.get_ident()
|
||||
assert self.__count > 0
|
||||
self.__count = self.__count - 1
|
||||
if self.__count == 0:
|
||||
self.__tid = None
|
||||
self.__wait.release()
|
||||
finally:
|
||||
self.__mutex.release()
|
||||
|
||||
|
||||
def _testLock():
|
||||
|
||||
done = []
|
||||
|
||||
def f2(lock, done=done):
|
||||
lock.acquire()
|
||||
print "f2 running in thread %d\n" % thread.get_ident(),
|
||||
lock.release()
|
||||
done.append(1)
|
||||
|
||||
def f1(lock, f2=f2, done=done):
|
||||
lock.acquire()
|
||||
print "f1 running in thread %d\n" % thread.get_ident(),
|
||||
try:
|
||||
f2(lock)
|
||||
finally:
|
||||
lock.release()
|
||||
done.append(1)
|
||||
|
||||
lock = Lock()
|
||||
lock.acquire()
|
||||
f1(lock) # Adds 2 to done
|
||||
lock.release()
|
||||
|
||||
lock.acquire()
|
||||
|
||||
thread.start_new_thread(f1, (lock,)) # Adds 2
|
||||
thread.start_new_thread(f1, (lock, f1)) # Adds 3
|
||||
thread.start_new_thread(f2, (lock,)) # Adds 1
|
||||
thread.start_new_thread(f2, (lock,)) # Adds 1
|
||||
|
||||
lock.release()
|
||||
import time
|
||||
while len(done) < 9:
|
||||
print len(done)
|
||||
time.sleep(0.001)
|
||||
print len(done)
|
||||
|
||||
|
||||
# Now, the Locking metaclass is a piece of cake.
|
||||
# As an example feature, methods whose name begins with exactly one
|
||||
# underscore are not synchronized.
|
||||
|
||||
from Meta import MetaClass, MetaHelper, MetaMethodWrapper
|
||||
|
||||
class LockingMethodWrapper(MetaMethodWrapper):
|
||||
def __call__(self, *args, **kw):
|
||||
if self.__name__[:1] == '_' and self.__name__[1:] != '_':
|
||||
return apply(self.func, (self.inst,) + args, kw)
|
||||
self.inst.__lock__.acquire()
|
||||
try:
|
||||
return apply(self.func, (self.inst,) + args, kw)
|
||||
finally:
|
||||
self.inst.__lock__.release()
|
||||
|
||||
class LockingHelper(MetaHelper):
|
||||
__methodwrapper__ = LockingMethodWrapper
|
||||
def __helperinit__(self, formalclass):
|
||||
MetaHelper.__helperinit__(self, formalclass)
|
||||
self.__lock__ = Lock()
|
||||
|
||||
class LockingMetaClass(MetaClass):
|
||||
__helper__ = LockingHelper
|
||||
|
||||
Locking = LockingMetaClass('Locking', (), {})
|
||||
|
||||
def _test():
|
||||
# For kicks, take away the Locking base class and see it die
|
||||
class Buffer(Locking):
|
||||
def __init__(self, initialsize):
|
||||
assert initialsize > 0
|
||||
self.size = initialsize
|
||||
self.buffer = [None]*self.size
|
||||
self.first = self.last = 0
|
||||
def put(self, item):
|
||||
# Do we need to grow the buffer?
|
||||
if (self.last+1) % self.size != self.first:
|
||||
# Insert the new item
|
||||
self.buffer[self.last] = item
|
||||
self.last = (self.last+1) % self.size
|
||||
return
|
||||
# Double the buffer size
|
||||
# First normalize it so that first==0 and last==size-1
|
||||
print "buffer =", self.buffer
|
||||
print "first = %d, last = %d, size = %d" % (
|
||||
self.first, self.last, self.size)
|
||||
if self.first <= self.last:
|
||||
temp = self.buffer[self.first:self.last]
|
||||
else:
|
||||
temp = self.buffer[self.first:] + self.buffer[:self.last]
|
||||
print "temp =", temp
|
||||
self.buffer = temp + [None]*(self.size+1)
|
||||
self.first = 0
|
||||
self.last = self.size-1
|
||||
self.size = self.size*2
|
||||
print "Buffer size doubled to", self.size
|
||||
print "new buffer =", self.buffer
|
||||
print "first = %d, last = %d, size = %d" % (
|
||||
self.first, self.last, self.size)
|
||||
self.put(item) # Recursive call to test the locking
|
||||
def get(self):
|
||||
# Is the buffer empty?
|
||||
if self.first == self.last:
|
||||
raise EOFError # Avoid defining a new exception
|
||||
item = self.buffer[self.first]
|
||||
self.first = (self.first+1) % self.size
|
||||
return item
|
||||
|
||||
def producer(buffer, wait, n=1000):
|
||||
import time
|
||||
i = 0
|
||||
while i < n:
|
||||
print "put", i
|
||||
buffer.put(i)
|
||||
i = i+1
|
||||
print "Producer: done producing", n, "items"
|
||||
wait.release()
|
||||
|
||||
def consumer(buffer, wait, n=1000):
|
||||
import time
|
||||
i = 0
|
||||
tout = 0.001
|
||||
while i < n:
|
||||
try:
|
||||
x = buffer.get()
|
||||
if x != i:
|
||||
raise AssertionError, \
|
||||
"get() returned %s, expected %s" % (x, i)
|
||||
print "got", i
|
||||
i = i+1
|
||||
tout = 0.001
|
||||
except EOFError:
|
||||
time.sleep(tout)
|
||||
tout = tout*2
|
||||
print "Consumer: done consuming", n, "items"
|
||||
wait.release()
|
||||
|
||||
pwait = thread.allocate_lock()
|
||||
pwait.acquire()
|
||||
cwait = thread.allocate_lock()
|
||||
cwait.acquire()
|
||||
buffer = Buffer(1)
|
||||
n = 1000
|
||||
thread.start_new_thread(consumer, (buffer, cwait, n))
|
||||
thread.start_new_thread(producer, (buffer, pwait, n))
|
||||
pwait.acquire()
|
||||
print "Producer done"
|
||||
cwait.acquire()
|
||||
print "All done"
|
||||
print "buffer size ==", len(buffer.buffer)
|
||||
|
||||
if __name__ == '__main__':
|
||||
_testLock()
|
||||
_test()
|
|
@ -0,0 +1,144 @@
|
|||
"""Tracing metaclass.
|
||||
|
||||
XXX This is very much a work in progress.
|
||||
|
||||
"""
|
||||
|
||||
import types, sys
|
||||
|
||||
class TraceMetaClass:
|
||||
"""Metaclass for tracing.
|
||||
|
||||
Classes defined using this metaclass have an automatic tracing
|
||||
feature -- by setting the __trace_output__ instance (or class)
|
||||
variable to a file object, trace messages about all calls are
|
||||
written to the file. The trace formatting can be changed by
|
||||
defining a suitable __trace_call__ method.
|
||||
|
||||
"""
|
||||
|
||||
__inited = 0
|
||||
|
||||
def __init__(self, name, bases, dict):
|
||||
self.__name__ = name
|
||||
self.__bases__ = bases
|
||||
self.__dict = dict
|
||||
# XXX Can't define __dict__, alas
|
||||
self.__inited = 1
|
||||
|
||||
def __getattr__(self, name):
|
||||
try:
|
||||
return self.__dict[name]
|
||||
except KeyError:
|
||||
for base in self.__bases__:
|
||||
try:
|
||||
return base.__getattr__(name)
|
||||
except AttributeError:
|
||||
pass
|
||||
raise AttributeError, name
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
if not self.__inited:
|
||||
self.__dict__[name] = value
|
||||
else:
|
||||
self.__dict[name] = value
|
||||
|
||||
def __call__(self, *args, **kw):
|
||||
inst = TracingInstance()
|
||||
inst.__meta_init__(self)
|
||||
try:
|
||||
init = inst.__getattr__('__init__')
|
||||
except AttributeError:
|
||||
init = lambda: None
|
||||
apply(init, args, kw)
|
||||
return inst
|
||||
|
||||
__trace_output__ = None
|
||||
|
||||
class TracingInstance:
|
||||
"""Helper class to represent an instance of a tracing class."""
|
||||
|
||||
def __trace_call__(self, fp, fmt, *args):
|
||||
fp.write((fmt+'\n') % args)
|
||||
|
||||
def __meta_init__(self, klass):
|
||||
self.__class = klass
|
||||
|
||||
def __getattr__(self, name):
|
||||
# Invoked for any attr not in the instance's __dict__
|
||||
try:
|
||||
raw = self.__class.__getattr__(name)
|
||||
except AttributeError:
|
||||
raise AttributeError, name
|
||||
if type(raw) != types.FunctionType:
|
||||
return raw
|
||||
# It's a function
|
||||
fullname = self.__class.__name__ + "." + name
|
||||
if not self.__trace_output__ or name == '__trace_call__':
|
||||
return NotTracingWrapper(fullname, raw, self)
|
||||
else:
|
||||
return TracingWrapper(fullname, raw, self)
|
||||
|
||||
class NotTracingWrapper:
|
||||
def __init__(self, name, func, inst):
|
||||
self.__name__ = name
|
||||
self.func = func
|
||||
self.inst = inst
|
||||
def __call__(self, *args, **kw):
|
||||
return apply(self.func, (self.inst,) + args, kw)
|
||||
|
||||
class TracingWrapper(NotTracingWrapper):
|
||||
def __call__(self, *args, **kw):
|
||||
self.inst.__trace_call__(self.inst.__trace_output__,
|
||||
"calling %s, inst=%s, args=%s, kw=%s",
|
||||
self.__name__, self.inst, args, kw)
|
||||
try:
|
||||
rv = apply(self.func, (self.inst,) + args, kw)
|
||||
except:
|
||||
t, v, tb = sys.exc_info()
|
||||
self.inst.__trace_call__(self.inst.__trace_output__,
|
||||
"returning from %s with exception %s: %s",
|
||||
self.__name__, t, v)
|
||||
raise t, v, tb
|
||||
else:
|
||||
self.inst.__trace_call__(self.inst.__trace_output__,
|
||||
"returning from %s with value %s",
|
||||
self.__name__, rv)
|
||||
return rv
|
||||
|
||||
Traced = TraceMetaClass('Traced', (), {'__trace_output__': None})
|
||||
|
||||
|
||||
def _test():
|
||||
global C, D
|
||||
class C(Traced):
|
||||
def __init__(self, x=0): self.x = x
|
||||
def m1(self, x): self.x = x
|
||||
def m2(self, y): return self.x + y
|
||||
__trace_output__ = sys.stdout
|
||||
class D(C):
|
||||
def m2(self, y): print "D.m2(%r)" % (y,); return C.m2(self, y)
|
||||
__trace_output__ = None
|
||||
x = C(4321)
|
||||
print x
|
||||
print x.x
|
||||
print x.m1(100)
|
||||
print x.m1(10)
|
||||
print x.m2(33)
|
||||
print x.m1(5)
|
||||
print x.m2(4000)
|
||||
print x.x
|
||||
|
||||
print C.__init__
|
||||
print C.m2
|
||||
print D.__init__
|
||||
print D.m2
|
||||
|
||||
y = D()
|
||||
print y
|
||||
print y.m1(10)
|
||||
print y.m2(100)
|
||||
print y.x
|
||||
|
||||
if __name__ == '__main__':
|
||||
_test()
|
|
@ -0,0 +1,605 @@
|
|||
<HTML>
|
||||
|
||||
<HEAD>
|
||||
<TITLE>Metaclasses in Python 1.5</TITLE>
|
||||
</HEAD>
|
||||
|
||||
<BODY BGCOLOR="FFFFFF">
|
||||
|
||||
<H1>Metaclasses in Python 1.5</H1>
|
||||
<H2>(A.k.a. The Killer Joke :-)</H2>
|
||||
|
||||
<HR>
|
||||
|
||||
(<i>Postscript:</i> reading this essay is probably not the best way to
|
||||
understand the metaclass hook described here. See a <A
|
||||
HREF="meta-vladimir.txt">message posted by Vladimir Marangozov</A>
|
||||
which may give a gentler introduction to the matter. You may also
|
||||
want to search Deja News for messages with "metaclass" in the subject
|
||||
posted to comp.lang.python in July and August 1998.)
|
||||
|
||||
<HR>
|
||||
|
||||
<P>In previous Python releases (and still in 1.5), there is something
|
||||
called the ``Don Beaudry hook'', after its inventor and champion.
|
||||
This allows C extensions to provide alternate class behavior, thereby
|
||||
allowing the Python class syntax to be used to define other class-like
|
||||
entities. Don Beaudry has used this in his infamous <A
|
||||
HREF="http://maigret.cog.brown.edu/pyutil/">MESS</A> package; Jim
|
||||
Fulton has used it in his <A
|
||||
HREF="http://www.digicool.com/releases/ExtensionClass/">Extension
|
||||
Classes</A> package. (It has also been referred to as the ``Don
|
||||
Beaudry <i>hack</i>,'' but that's a misnomer. There's nothing hackish
|
||||
about it -- in fact, it is rather elegant and deep, even though
|
||||
there's something dark to it.)
|
||||
|
||||
<P>(On first reading, you may want to skip directly to the examples in
|
||||
the section "Writing Metaclasses in Python" below, unless you want
|
||||
your head to explode.)
|
||||
|
||||
<P>
|
||||
|
||||
<HR>
|
||||
|
||||
<P>Documentation of the Don Beaudry hook has purposefully been kept
|
||||
minimal, since it is a feature of incredible power, and is easily
|
||||
abused. Basically, it checks whether the <b>type of the base
|
||||
class</b> is callable, and if so, it is called to create the new
|
||||
class.
|
||||
|
||||
<P>Note the two indirection levels. Take a simple example:
|
||||
|
||||
<PRE>
|
||||
class B:
|
||||
pass
|
||||
|
||||
class C(B):
|
||||
pass
|
||||
</PRE>
|
||||
|
||||
Take a look at the second class definition, and try to fathom ``the
|
||||
type of the base class is callable.''
|
||||
|
||||
<P>(Types are not classes, by the way. See questions 4.2, 4.19 and in
|
||||
particular 6.22 in the <A
|
||||
HREF="http://www.python.org/cgi-bin/faqw.py" >Python FAQ</A>
|
||||
for more on this topic.)
|
||||
|
||||
<P>
|
||||
|
||||
<UL>
|
||||
|
||||
<LI>The <b>base class</b> is B; this one's easy.<P>
|
||||
|
||||
<LI>Since B is a class, its type is ``class''; so the <b>type of the
|
||||
base class</b> is the type ``class''. This is also known as
|
||||
types.ClassType, assuming the standard module <code>types</code> has
|
||||
been imported.<P>
|
||||
|
||||
<LI>Now is the type ``class'' <b>callable</b>? No, because types (in
|
||||
core Python) are never callable. Classes are callable (calling a
|
||||
class creates a new instance) but types aren't.<P>
|
||||
|
||||
</UL>
|
||||
|
||||
<P>So our conclusion is that in our example, the type of the base
|
||||
class (of C) is not callable. So the Don Beaudry hook does not apply,
|
||||
and the default class creation mechanism is used (which is also used
|
||||
when there is no base class). In fact, the Don Beaudry hook never
|
||||
applies when using only core Python, since the type of a core object
|
||||
is never callable.
|
||||
|
||||
<P>So what do Don and Jim do in order to use Don's hook? Write an
|
||||
extension that defines at least two new Python object types. The
|
||||
first would be the type for ``class-like'' objects usable as a base
|
||||
class, to trigger Don's hook. This type must be made callable.
|
||||
That's why we need a second type. Whether an object is callable
|
||||
depends on its type. So whether a type object is callable depends on
|
||||
<i>its</i> type, which is a <i>meta-type</i>. (In core Python there
|
||||
is only one meta-type, the type ``type'' (types.TypeType), which is
|
||||
the type of all type objects, even itself.) A new meta-type must
|
||||
be defined that makes the type of the class-like objects callable.
|
||||
(Normally, a third type would also be needed, the new ``instance''
|
||||
type, but this is not an absolute requirement -- the new class type
|
||||
could return an object of some existing type when invoked to create an
|
||||
instance.)
|
||||
|
||||
<P>Still confused? Here's a simple device due to Don himself to
|
||||
explain metaclasses. Take a simple class definition; assume B is a
|
||||
special class that triggers Don's hook:
|
||||
|
||||
<PRE>
|
||||
class C(B):
|
||||
a = 1
|
||||
b = 2
|
||||
</PRE>
|
||||
|
||||
This can be though of as equivalent to:
|
||||
|
||||
<PRE>
|
||||
C = type(B)('C', (B,), {'a': 1, 'b': 2})
|
||||
</PRE>
|
||||
|
||||
If that's too dense for you, here's the same thing written out using
|
||||
temporary variables:
|
||||
|
||||
<PRE>
|
||||
creator = type(B) # The type of the base class
|
||||
name = 'C' # The name of the new class
|
||||
bases = (B,) # A tuple containing the base class(es)
|
||||
namespace = {'a': 1, 'b': 2} # The namespace of the class statement
|
||||
C = creator(name, bases, namespace)
|
||||
</PRE>
|
||||
|
||||
This is analogous to what happens without the Don Beaudry hook, except
|
||||
that in that case the creator function is set to the default class
|
||||
creator.
|
||||
|
||||
<P>In either case, the creator is called with three arguments. The
|
||||
first one, <i>name</i>, is the name of the new class (as given at the
|
||||
top of the class statement). The <i>bases</i> argument is a tuple of
|
||||
base classes (a singleton tuple if there's only one base class, like
|
||||
the example). Finally, <i>namespace</i> is a dictionary containing
|
||||
the local variables collected during execution of the class statement.
|
||||
|
||||
<P>Note that the contents of the namespace dictionary is simply
|
||||
whatever names were defined in the class statement. A little-known
|
||||
fact is that when Python executes a class statement, it enters a new
|
||||
local namespace, and all assignments and function definitions take
|
||||
place in this namespace. Thus, after executing the following class
|
||||
statement:
|
||||
|
||||
<PRE>
|
||||
class C:
|
||||
a = 1
|
||||
def f(s): pass
|
||||
</PRE>
|
||||
|
||||
the class namespace's contents would be {'a': 1, 'f': <function f
|
||||
...>}.
|
||||
|
||||
<P>But enough already about writing Python metaclasses in C; read the
|
||||
documentation of <A
|
||||
HREF="http://maigret.cog.brown.edu/pyutil/">MESS</A> or <A
|
||||
HREF="http://www.digicool.com/papers/ExtensionClass.html" >Extension
|
||||
Classes</A> for more information.
|
||||
|
||||
<P>
|
||||
|
||||
<HR>
|
||||
|
||||
<H2>Writing Metaclasses in Python</H2>
|
||||
|
||||
<P>In Python 1.5, the requirement to write a C extension in order to
|
||||
write metaclasses has been dropped (though you can still do
|
||||
it, of course). In addition to the check ``is the type of the base
|
||||
class callable,'' there's a check ``does the base class have a
|
||||
__class__ attribute.'' If so, it is assumed that the __class__
|
||||
attribute refers to a class.
|
||||
|
||||
<P>Let's repeat our simple example from above:
|
||||
|
||||
<PRE>
|
||||
class C(B):
|
||||
a = 1
|
||||
b = 2
|
||||
</PRE>
|
||||
|
||||
Assuming B has a __class__ attribute, this translates into:
|
||||
|
||||
<PRE>
|
||||
C = B.__class__('C', (B,), {'a': 1, 'b': 2})
|
||||
</PRE>
|
||||
|
||||
This is exactly the same as before except that instead of type(B),
|
||||
B.__class__ is invoked. If you have read <A HREF=
|
||||
"http://www.python.org/cgi-bin/faqw.py?req=show&file=faq06.022.htp"
|
||||
>FAQ question 6.22</A> you will understand that while there is a big
|
||||
technical difference between type(B) and B.__class__, they play the
|
||||
same role at different abstraction levels. And perhaps at some point
|
||||
in the future they will really be the same thing (at which point you
|
||||
would be able to derive subclasses from built-in types).
|
||||
|
||||
<P>At this point it may be worth mentioning that C.__class__ is the
|
||||
same object as B.__class__, i.e., C's metaclass is the same as B's
|
||||
metaclass. In other words, subclassing an existing class creates a
|
||||
new (meta)inststance of the base class's metaclass.
|
||||
|
||||
<P>Going back to the example, the class B.__class__ is instantiated,
|
||||
passing its constructor the same three arguments that are passed to
|
||||
the default class constructor or to an extension's metaclass:
|
||||
<i>name</i>, <i>bases</i>, and <i>namespace</i>.
|
||||
|
||||
<P>It is easy to be confused by what exactly happens when using a
|
||||
metaclass, because we lose the absolute distinction between classes
|
||||
and instances: a class is an instance of a metaclass (a
|
||||
``metainstance''), but technically (i.e. in the eyes of the python
|
||||
runtime system), the metaclass is just a class, and the metainstance
|
||||
is just an instance. At the end of the class statement, the metaclass
|
||||
whose metainstance is used as a base class is instantiated, yielding a
|
||||
second metainstance (of the same metaclass). This metainstance is
|
||||
then used as a (normal, non-meta) class; instantiation of the class
|
||||
means calling the metainstance, and this will return a real instance.
|
||||
And what class is that an instance of? Conceptually, it is of course
|
||||
an instance of our metainstance; but in most cases the Python runtime
|
||||
system will see it as an instance of a a helper class used by the
|
||||
metaclass to implement its (non-meta) instances...
|
||||
|
||||
<P>Hopefully an example will make things clearer. Let's presume we
|
||||
have a metaclass MetaClass1. It's helper class (for non-meta
|
||||
instances) is callled HelperClass1. We now (manually) instantiate
|
||||
MetaClass1 once to get an empty special base class:
|
||||
|
||||
<PRE>
|
||||
BaseClass1 = MetaClass1("BaseClass1", (), {})
|
||||
</PRE>
|
||||
|
||||
We can now use BaseClass1 as a base class in a class statement:
|
||||
|
||||
<PRE>
|
||||
class MySpecialClass(BaseClass1):
|
||||
i = 1
|
||||
def f(s): pass
|
||||
</PRE>
|
||||
|
||||
At this point, MySpecialClass is defined; it is a metainstance of
|
||||
MetaClass1 just like BaseClass1, and in fact the expression
|
||||
``BaseClass1.__class__ == MySpecialClass.__class__ == MetaClass1''
|
||||
yields true.
|
||||
|
||||
<P>We are now ready to create instances of MySpecialClass. Let's
|
||||
assume that no constructor arguments are required:
|
||||
|
||||
<PRE>
|
||||
x = MySpecialClass()
|
||||
y = MySpecialClass()
|
||||
print x.__class__, y.__class__
|
||||
</PRE>
|
||||
|
||||
The print statement shows that x and y are instances of HelperClass1.
|
||||
How did this happen? MySpecialClass is an instance of MetaClass1
|
||||
(``meta'' is irrelevant here); when an instance is called, its
|
||||
__call__ method is invoked, and presumably the __call__ method defined
|
||||
by MetaClass1 returns an instance of HelperClass1.
|
||||
|
||||
<P>Now let's see how we could use metaclasses -- what can we do
|
||||
with metaclasses that we can't easily do without them? Here's one
|
||||
idea: a metaclass could automatically insert trace calls for all
|
||||
method calls. Let's first develop a simplified example, without
|
||||
support for inheritance or other ``advanced'' Python features (we'll
|
||||
add those later).
|
||||
|
||||
<PRE>
|
||||
import types
|
||||
|
||||
class Tracing:
|
||||
def __init__(self, name, bases, namespace):
|
||||
"""Create a new class."""
|
||||
self.__name__ = name
|
||||
self.__bases__ = bases
|
||||
self.__namespace__ = namespace
|
||||
def __call__(self):
|
||||
"""Create a new instance."""
|
||||
return Instance(self)
|
||||
|
||||
class Instance:
|
||||
def __init__(self, klass):
|
||||
self.__klass__ = klass
|
||||
def __getattr__(self, name):
|
||||
try:
|
||||
value = self.__klass__.__namespace__[name]
|
||||
except KeyError:
|
||||
raise AttributeError, name
|
||||
if type(value) is not types.FunctionType:
|
||||
return value
|
||||
return BoundMethod(value, self)
|
||||
|
||||
class BoundMethod:
|
||||
def __init__(self, function, instance):
|
||||
self.function = function
|
||||
self.instance = instance
|
||||
def __call__(self, *args):
|
||||
print "calling", self.function, "for", self.instance, "with", args
|
||||
return apply(self.function, (self.instance,) + args)
|
||||
|
||||
Trace = Tracing('Trace', (), {})
|
||||
|
||||
class MyTracedClass(Trace):
|
||||
def method1(self, a):
|
||||
self.a = a
|
||||
def method2(self):
|
||||
return self.a
|
||||
|
||||
aninstance = MyTracedClass()
|
||||
|
||||
aninstance.method1(10)
|
||||
|
||||
print "the answer is %d" % aninstance.method2()
|
||||
</PRE>
|
||||
|
||||
Confused already? The intention is to read this from top down. The
|
||||
Tracing class is the metaclass we're defining. Its structure is
|
||||
really simple.
|
||||
|
||||
<P>
|
||||
|
||||
<UL>
|
||||
|
||||
<LI>The __init__ method is invoked when a new Tracing instance is
|
||||
created, e.g. the definition of class MyTracedClass later in the
|
||||
example. It simply saves the class name, base classes and namespace
|
||||
as instance variables.<P>
|
||||
|
||||
<LI>The __call__ method is invoked when a Tracing instance is called,
|
||||
e.g. the creation of aninstance later in the example. It returns an
|
||||
instance of the class Instance, which is defined next.<P>
|
||||
|
||||
</UL>
|
||||
|
||||
<P>The class Instance is the class used for all instances of classes
|
||||
built using the Tracing metaclass, e.g. aninstance. It has two
|
||||
methods:
|
||||
|
||||
<P>
|
||||
|
||||
<UL>
|
||||
|
||||
<LI>The __init__ method is invoked from the Tracing.__call__ method
|
||||
above to initialize a new instance. It saves the class reference as
|
||||
an instance variable. It uses a funny name because the user's
|
||||
instance variables (e.g. self.a later in the example) live in the same
|
||||
namespace.<P>
|
||||
|
||||
<LI>The __getattr__ method is invoked whenever the user code
|
||||
references an attribute of the instance that is not an instance
|
||||
variable (nor a class variable; but except for __init__ and
|
||||
__getattr__ there are no class variables). It will be called, for
|
||||
example, when aninstance.method1 is referenced in the example, with
|
||||
self set to aninstance and name set to the string "method1".<P>
|
||||
|
||||
</UL>
|
||||
|
||||
<P>The __getattr__ method looks the name up in the __namespace__
|
||||
dictionary. If it isn't found, it raises an AttributeError exception.
|
||||
(In a more realistic example, it would first have to look through the
|
||||
base classes as well.) If it is found, there are two possibilities:
|
||||
it's either a function or it isn't. If it's not a function, it is
|
||||
assumed to be a class variable, and its value is returned. If it's a
|
||||
function, we have to ``wrap'' it in instance of yet another helper
|
||||
class, BoundMethod.
|
||||
|
||||
<P>The BoundMethod class is needed to implement a familiar feature:
|
||||
when a method is defined, it has an initial argument, self, which is
|
||||
automatically bound to the relevant instance when it is called. For
|
||||
example, aninstance.method1(10) is equivalent to method1(aninstance,
|
||||
10). In the example if this call, first a temporary BoundMethod
|
||||
instance is created with the following constructor call: temp =
|
||||
BoundMethod(method1, aninstance); then this instance is called as
|
||||
temp(10). After the call, the temporary instance is discarded.
|
||||
|
||||
<P>
|
||||
|
||||
<UL>
|
||||
|
||||
<LI>The __init__ method is invoked for the constructor call
|
||||
BoundMethod(method1, aninstance). It simply saves away its
|
||||
arguments.<P>
|
||||
|
||||
<LI>The __call__ method is invoked when the bound method instance is
|
||||
called, as in temp(10). It needs to call method1(aninstance, 10).
|
||||
However, even though self.function is now method1 and self.instance is
|
||||
aninstance, it can't call self.function(self.instance, args) directly,
|
||||
because it should work regardless of the number of arguments passed.
|
||||
(For simplicity, support for keyword arguments has been omitted.)<P>
|
||||
|
||||
</UL>
|
||||
|
||||
<P>In order to be able to support arbitrary argument lists, the
|
||||
__call__ method first constructs a new argument tuple. Conveniently,
|
||||
because of the notation *args in __call__'s own argument list, the
|
||||
arguments to __call__ (except for self) are placed in the tuple args.
|
||||
To construct the desired argument list, we concatenate a singleton
|
||||
tuple containing the instance with the args tuple: (self.instance,) +
|
||||
args. (Note the trailing comma used to construct the singleton
|
||||
tuple.) In our example, the resulting argument tuple is (aninstance,
|
||||
10).
|
||||
|
||||
<P>The intrinsic function apply() takes a function and an argument
|
||||
tuple and calls the function for it. In our example, we are calling
|
||||
apply(method1, (aninstance, 10)) which is equivalent to calling
|
||||
method(aninstance, 10).
|
||||
|
||||
<P>From here on, things should come together quite easily. The output
|
||||
of the example code is something like this:
|
||||
|
||||
<PRE>
|
||||
calling <function method1 at ae8d8> for <Instance instance at 95ab0> with (10,)
|
||||
calling <function method2 at ae900> for <Instance instance at 95ab0> with ()
|
||||
the answer is 10
|
||||
</PRE>
|
||||
|
||||
<P>That was about the shortest meaningful example that I could come up
|
||||
with. A real tracing metaclass (for example, <A
|
||||
HREF="#Trace">Trace.py</A> discussed below) needs to be more
|
||||
complicated in two dimensions.
|
||||
|
||||
<P>First, it needs to support more advanced Python features such as
|
||||
class variables, inheritance, __init__ methods, and keyword arguments.
|
||||
|
||||
<P>Second, it needs to provide a more flexible way to handle the
|
||||
actual tracing information; perhaps it should be possible to write
|
||||
your own tracing function that gets called, perhaps it should be
|
||||
possible to enable and disable tracing on a per-class or per-instance
|
||||
basis, and perhaps a filter so that only interesting calls are traced;
|
||||
it should also be able to trace the return value of the call (or the
|
||||
exception it raised if an error occurs). Even the Trace.py example
|
||||
doesn't support all these features yet.
|
||||
|
||||
<P>
|
||||
|
||||
<HR>
|
||||
|
||||
<H1>Real-life Examples</H1>
|
||||
|
||||
<P>Have a look at some very preliminary examples that I coded up to
|
||||
teach myself how to write metaclasses:
|
||||
|
||||
<DL>
|
||||
|
||||
<DT><A HREF="Enum.py">Enum.py</A>
|
||||
|
||||
<DD>This (ab)uses the class syntax as an elegant way to define
|
||||
enumerated types. The resulting classes are never instantiated --
|
||||
rather, their class attributes are the enumerated values. For
|
||||
example:
|
||||
|
||||
<PRE>
|
||||
class Color(Enum):
|
||||
red = 1
|
||||
green = 2
|
||||
blue = 3
|
||||
print Color.red
|
||||
</PRE>
|
||||
|
||||
will print the string ``Color.red'', while ``Color.red==1'' is true,
|
||||
and ``Color.red + 1'' raise a TypeError exception.
|
||||
|
||||
<P>
|
||||
|
||||
<DT><A NAME=Trace></A><A HREF="Trace.py">Trace.py</A>
|
||||
|
||||
<DD>The resulting classes work much like standard
|
||||
classes, but by setting a special class or instance attribute
|
||||
__trace_output__ to point to a file, all calls to the class's methods
|
||||
are traced. It was a bit of a struggle to get this right. This
|
||||
should probably redone using the generic metaclass below.
|
||||
|
||||
<P>
|
||||
|
||||
<DT><A HREF="Meta.py">Meta.py</A>
|
||||
|
||||
<DD>A generic metaclass. This is an attempt at finding out how much
|
||||
standard class behavior can be mimicked by a metaclass. The
|
||||
preliminary answer appears to be that everything's fine as long as the
|
||||
class (or its clients) don't look at the instance's __class__
|
||||
attribute, nor at the class's __dict__ attribute. The use of
|
||||
__getattr__ internally makes the classic implementation of __getattr__
|
||||
hooks tough; we provide a similar hook _getattr_ instead.
|
||||
(__setattr__ and __delattr__ are not affected.)
|
||||
(XXX Hm. Could detect presence of __getattr__ and rename it.)
|
||||
|
||||
<P>
|
||||
|
||||
<DT><A HREF="Eiffel.py">Eiffel.py</A>
|
||||
|
||||
<DD>Uses the above generic metaclass to implement Eiffel style
|
||||
pre-conditions and post-conditions.
|
||||
|
||||
<P>
|
||||
|
||||
<DT><A HREF="Synch.py">Synch.py</A>
|
||||
|
||||
<DD>Uses the above generic metaclass to implement synchronized
|
||||
methods.
|
||||
|
||||
<P>
|
||||
|
||||
<DT><A HREF="Simple.py">Simple.py</A>
|
||||
|
||||
<DD>The example module used above.
|
||||
|
||||
<P>
|
||||
|
||||
</DL>
|
||||
|
||||
<P>A pattern seems to be emerging: almost all these uses of
|
||||
metaclasses (except for Enum, which is probably more cute than useful)
|
||||
mostly work by placing wrappers around method calls. An obvious
|
||||
problem with that is that it's not easy to combine the features of
|
||||
different metaclasses, while this would actually be quite useful: for
|
||||
example, I wouldn't mind getting a trace from the test run of the
|
||||
Synch module, and it would be interesting to add preconditions to it
|
||||
as well. This needs more research. Perhaps a metaclass could be
|
||||
provided that allows stackable wrappers...
|
||||
|
||||
<P>
|
||||
|
||||
<HR>
|
||||
|
||||
<H2>Things You Could Do With Metaclasses</H2>
|
||||
|
||||
<P>There are lots of things you could do with metaclasses. Most of
|
||||
these can also be done with creative use of __getattr__, but
|
||||
metaclasses make it easier to modify the attribute lookup behavior of
|
||||
classes. Here's a partial list.
|
||||
|
||||
<P>
|
||||
|
||||
<UL>
|
||||
|
||||
<LI>Enforce different inheritance semantics, e.g. automatically call
|
||||
base class methods when a derived class overrides<P>
|
||||
|
||||
<LI>Implement class methods (e.g. if the first argument is not named
|
||||
'self')<P>
|
||||
|
||||
<LI>Implement that each instance is initialized with <b>copies</b> of
|
||||
all class variables<P>
|
||||
|
||||
<LI>Implement a different way to store instance variables (e.g. in a
|
||||
list kept outside the instance but indexed by the instance's id())<P>
|
||||
|
||||
<LI>Automatically wrap or trap all or certain methods
|
||||
|
||||
<UL>
|
||||
|
||||
<LI>for tracing
|
||||
|
||||
<LI>for precondition and postcondition checking
|
||||
|
||||
<LI>for synchronized methods
|
||||
|
||||
<LI>for automatic value caching
|
||||
|
||||
</UL>
|
||||
<P>
|
||||
|
||||
<LI>When an attribute is a parameterless function, call it on
|
||||
reference (to mimic it being an instance variable); same on assignment<P>
|
||||
|
||||
<LI>Instrumentation: see how many times various attributes are used<P>
|
||||
|
||||
<LI>Different semantics for __setattr__ and __getattr__ (e.g. disable
|
||||
them when they are being used recursively)<P>
|
||||
|
||||
<LI>Abuse class syntax for other things<P>
|
||||
|
||||
<LI>Experiment with automatic type checking<P>
|
||||
|
||||
<LI>Delegation (or acquisition)<P>
|
||||
|
||||
<LI>Dynamic inheritance patterns<P>
|
||||
|
||||
<LI>Automatic caching of methods<P>
|
||||
|
||||
</UL>
|
||||
|
||||
<P>
|
||||
|
||||
<HR>
|
||||
|
||||
<H4>Credits</H4>
|
||||
|
||||
<P>Many thanks to David Ascher and Donald Beaudry for their comments
|
||||
on earlier draft of this paper. Also thanks to Matt Conway and Tommy
|
||||
Burnette for putting a seed for the idea of metaclasses in my
|
||||
mind, nearly three years ago, even though at the time my response was
|
||||
``you can do that with __getattr__ hooks...'' :-)
|
||||
|
||||
<P>
|
||||
|
||||
<HR>
|
||||
|
||||
</BODY>
|
||||
|
||||
</HTML>
|
|
@ -0,0 +1,256 @@
|
|||
Subject: Re: The metaclass saga using Python
|
||||
From: Vladimir Marangozov <Vladimir.Marangozov@imag.fr>
|
||||
To: tim_one@email.msn.com (Tim Peters)
|
||||
Cc: python-list@cwi.nl
|
||||
Date: Wed, 5 Aug 1998 15:59:06 +0200 (DFT)
|
||||
|
||||
[Tim]
|
||||
>
|
||||
> building-on-examples-tends-to-prevent-abstract-thrashing-ly y'rs - tim
|
||||
>
|
||||
|
||||
OK, I stand corrected. I understand that anybody's interpretation of
|
||||
the meta-class concept is likely to be difficult to digest by others.
|
||||
|
||||
Here's another try, expressing the same thing, but using the Python
|
||||
programming model, examples and, perhaps, more popular terms.
|
||||
|
||||
1. Classes.
|
||||
|
||||
This is pure Python of today. Sorry about the tutorial, but it is
|
||||
meant to illustrate the second part, which is the one we're
|
||||
interested in and which will follow the same development scenario.
|
||||
Besides, newbies are likely to understand that the discussion is
|
||||
affordable even for them :-)
|
||||
|
||||
a) Class definition
|
||||
|
||||
A class is meant to define the common properties of a set of objects.
|
||||
A class is a "package" of properties. The assembly of properties
|
||||
in a class package is sometimes called a class structure (which isn't
|
||||
always appropriate).
|
||||
|
||||
>>> class A:
|
||||
attr1 = "Hello" # an attribute of A
|
||||
def method1(self, *args): pass # method1 of A
|
||||
def method2(self, *args): pass # method2 of A
|
||||
>>>
|
||||
|
||||
So far, we defined the structure of the class A. The class A is
|
||||
of type <class>. We can check this by asking Python: "what is A?"
|
||||
|
||||
>>> A # What is A?
|
||||
<class __main__.A at 2023e360>
|
||||
|
||||
b) Class instantiation
|
||||
|
||||
Creating an object with the properties defined in the class A is
|
||||
called instantiation of the class A. After an instantiation of A, we
|
||||
obtain a new object, called an instance, which has the properties
|
||||
packaged in the class A.
|
||||
|
||||
>>> a = A() # 'a' is the 1st instance of A
|
||||
>>> a # What is 'a'?
|
||||
<__main__.A instance at 2022b9d0>
|
||||
|
||||
>>> b = A() # 'b' is another instance of A
|
||||
>>> b # What is 'b'?
|
||||
<__main__.A instance at 2022b9c0>
|
||||
|
||||
The objects, 'a' and 'b', are of type <instance> and they both have
|
||||
the same properties. Note, that 'a' and 'b' are different objects.
|
||||
(their adresses differ). This is a bit hard to see, so let's ask Python:
|
||||
|
||||
>>> a == b # Is 'a' the same object as 'b'?
|
||||
0 # No.
|
||||
|
||||
Instance objects have one more special property, indicating the class
|
||||
they are an instance of. This property is named __class__.
|
||||
|
||||
>>> a.__class__ # What is the class of 'a'?
|
||||
<class __main__.A at 2023e360> # 'a' is an instance of A
|
||||
>>> b.__class__ # What is the class of 'b'?
|
||||
<class __main__.A at 2023e360> # 'b' is an instance of A
|
||||
>>> a.__class__ == b.__class__ # Is it really the same class A?
|
||||
1 # Yes.
|
||||
|
||||
c) Class inheritance (class composition and specialization)
|
||||
|
||||
Classes can be defined in terms of other existing classes (and only
|
||||
classes! -- don't bug me on this now). Thus, we can compose property
|
||||
packages and create new ones. We reuse the property set defined
|
||||
in a class by defining a new class, which "inherits" from the former.
|
||||
In other words, a class B which inherits from the class A, inherits
|
||||
the properties defined in A, or, B inherits the structure of A.
|
||||
|
||||
In the same time, at the definition of the new class B, we can enrich
|
||||
the inherited set of properties by adding new ones and/or modify some
|
||||
of the inherited properties.
|
||||
|
||||
>>> class B(A): # B inherits A's properties
|
||||
attr2 = "World" # additional attr2
|
||||
def method2(self, arg1): pass # method2 is redefined
|
||||
def method3(self, *args): pass # additional method3
|
||||
|
||||
>>> B # What is B?
|
||||
<class __main__.B at 2023e500>
|
||||
>>> B == A # Is B the same class as A?
|
||||
0 # No.
|
||||
|
||||
Classes define one special property, indicating whether a class
|
||||
inherits the properties of another class. This property is called
|
||||
__bases__ and it contains a list (a tuple) of the classes the new
|
||||
class inherits from. The classes from which a class is inheriting the
|
||||
properties are called superclasses (in Python, we call them also --
|
||||
base classes).
|
||||
|
||||
>>> A.__bases__ # Does A have any superclasses?
|
||||
() # No.
|
||||
>>> B.__bases__ # Does B have any superclasses?
|
||||
(<class __main__.A at 2023e360>,) # Yes. It has one superclass.
|
||||
>>> B.__bases__[0] == A # Is it really the class A?
|
||||
1 # Yes, it is.
|
||||
|
||||
--------
|
||||
|
||||
Congratulations on getting this far! This was the hard part.
|
||||
Now, let's continue with the easy one.
|
||||
|
||||
--------
|
||||
|
||||
2. Meta-classes
|
||||
|
||||
You have to admit, that an anonymous group of Python wizards are
|
||||
not satisfied with the property packaging facilities presented above.
|
||||
They say, that the Real-World bugs them with problems that cannot be
|
||||
modelled successfully with classes. Or, that the way classes are
|
||||
implemented in Python and the way classes and instances behave at
|
||||
runtime isn't always appropriate for reproducing the Real-World's
|
||||
behavior in a way that satisfies them.
|
||||
|
||||
Hence, what they want is the following:
|
||||
|
||||
a) leave objects as they are (instances of classes)
|
||||
b) leave classes as they are (property packages and object creators)
|
||||
|
||||
BUT, at the same time:
|
||||
|
||||
c) consider classes as being instances of mysterious objects.
|
||||
d) label mysterious objects "meta-classes".
|
||||
|
||||
Easy, eh?
|
||||
|
||||
You may ask: "Why on earth do they want to do that?".
|
||||
They answer: "Poor soul... Go and see how cruel the Real-World is!".
|
||||
You - fuzzy: "OK, will do!"
|
||||
|
||||
And here we go for another round of what I said in section 1 -- Classes.
|
||||
|
||||
However, be warned! The features we're going to talk about aren't fully
|
||||
implemented yet, because the Real-World don't let wizards to evaluate
|
||||
precisely how cruel it is, so the features are still highly-experimental.
|
||||
|
||||
a) Meta-class definition
|
||||
|
||||
A meta-class is meant to define the common properties of a set of
|
||||
classes. A meta-class is a "package" of properties. The assembly
|
||||
of properties in a meta-class package is sometimes called a meta-class
|
||||
structure (which isn't always appropriate).
|
||||
|
||||
In Python, a meta-class definition would have looked like this:
|
||||
|
||||
>>> metaclass M:
|
||||
attr1 = "Hello" # an attribute of M
|
||||
def method1(self, *args): pass # method1 of M
|
||||
def method2(self, *args): pass # method2 of M
|
||||
>>>
|
||||
|
||||
So far, we defined the structure of the meta-class M. The meta-class
|
||||
M is of type <metaclass>. We cannot check this by asking Python, but
|
||||
if we could, it would have answered:
|
||||
|
||||
>>> M # What is M?
|
||||
<metaclass __main__.M at 2023e4e0>
|
||||
|
||||
b) Meta-class instantiation
|
||||
|
||||
Creating an object with the properties defined in the meta-class M is
|
||||
called instantiation of the meta-class M. After an instantiation of M,
|
||||
we obtain a new object, called an class, but now it is called also
|
||||
a meta-instance, which has the properties packaged in the meta-class M.
|
||||
|
||||
In Python, instantiating a meta-class would have looked like this:
|
||||
|
||||
>>> A = M() # 'A' is the 1st instance of M
|
||||
>>> A # What is 'A'?
|
||||
<class __main__.A at 2022b9d0>
|
||||
|
||||
>>> B = M() # 'B' is another instance of M
|
||||
>>> B # What is 'B'?
|
||||
<class __main__.B at 2022b9c0>
|
||||
|
||||
The metaclass-instances, A and B, are of type <class> and they both
|
||||
have the same properties. Note, that A and B are different objects.
|
||||
(their adresses differ). This is a bit hard to see, but if it was
|
||||
possible to ask Python, it would have answered:
|
||||
|
||||
>>> A == B # Is A the same class as B?
|
||||
0 # No.
|
||||
|
||||
Class objects have one more special property, indicating the meta-class
|
||||
they are an instance of. This property is named __metaclass__.
|
||||
|
||||
>>> A.__metaclass__ # What is the meta-class of A?
|
||||
<metaclass __main__.M at 2023e4e0> # A is an instance of M
|
||||
>>> A.__metaclass__ # What is the meta-class of B?
|
||||
<metaclass __main__.M at 2023e4e0> # B is an instance of M
|
||||
>>> A.__metaclass__ == B.__metaclass__ # Is it the same meta-class M?
|
||||
1 # Yes.
|
||||
|
||||
c) Meta-class inheritance (meta-class composition and specialization)
|
||||
|
||||
Meta-classes can be defined in terms of other existing meta-classes
|
||||
(and only meta-classes!). Thus, we can compose property packages and
|
||||
create new ones. We reuse the property set defined in a meta-class by
|
||||
defining a new meta-class, which "inherits" from the former.
|
||||
In other words, a meta-class N which inherits from the meta-class M,
|
||||
inherits the properties defined in M, or, N inherits the structure of M.
|
||||
|
||||
In the same time, at the definition of the new meta-class N, we can
|
||||
enrich the inherited set of properties by adding new ones and/or modify
|
||||
some of the inherited properties.
|
||||
|
||||
>>> metaclass N(M): # N inherits M's properties
|
||||
attr2 = "World" # additional attr2
|
||||
def method2(self, arg1): pass # method2 is redefined
|
||||
def method3(self, *args): pass # additional method3
|
||||
|
||||
>>> N # What is N?
|
||||
<metaclass __main__.N at 2023e500>
|
||||
>>> N == M # Is N the same meta-class as M?
|
||||
0 # No.
|
||||
|
||||
Meta-classes define one special property, indicating whether a
|
||||
meta-class inherits the properties of another meta-class. This property
|
||||
is called __metabases__ and it contains a list (a tuple) of the
|
||||
meta-classes the new meta-class inherits from. The meta-classes from
|
||||
which a meta-class is inheriting the properties are called
|
||||
super-meta-classes (in Python, we call them also -- super meta-bases).
|
||||
|
||||
>>> M.__metabases__ # Does M have any supermetaclasses?
|
||||
() # No.
|
||||
>>> N.__metabases__ # Does N have any supermetaclasses?
|
||||
(<metaclass __main__.M at 2023e360>,) # Yes. It has a supermetaclass.
|
||||
>>> N.__metabases__[0] == M # Is it really the meta-class M?
|
||||
1 # Yes, it is.
|
||||
|
||||
--------
|
||||
|
||||
Triple congratulations on getting this far!
|
||||
Now you know everything about meta-classes and the Real-World!
|
||||
|
||||
<unless-wizards-want-meta-classes-be-instances-of-mysterious-objects!>
|
||||
|
||||
--
|
||||
Vladimir MARANGOZOV | Vladimir.Marangozov@inrialpes.fr
|
||||
http://sirac.inrialpes.fr/~marangoz | tel:(+33-4)76615277 fax:76615252
|
|
@ -0,0 +1,141 @@
|
|||
"""Support Eiffel-style preconditions and postconditions."""
|
||||
|
||||
from types import FunctionType as function
|
||||
|
||||
class EiffelBaseMetaClass(type):
|
||||
|
||||
def __new__(meta, name, bases, dict):
|
||||
meta.convert_methods(dict)
|
||||
return super(EiffelBaseMetaClass, meta).__new__(meta, name, bases,
|
||||
dict)
|
||||
|
||||
@classmethod
|
||||
def convert_methods(cls, dict):
|
||||
"""Replace functions in dict with EiffelMethod wrappers.
|
||||
|
||||
The dict is modified in place.
|
||||
|
||||
If a method ends in _pre or _post, it is removed from the dict
|
||||
regardless of whether there is a corresponding method.
|
||||
"""
|
||||
# find methods with pre or post conditions
|
||||
methods = []
|
||||
for k, v in dict.iteritems():
|
||||
if k.endswith('_pre') or k.endswith('_post'):
|
||||
assert isinstance(v, function)
|
||||
elif isinstance(v, function):
|
||||
methods.append(k)
|
||||
for m in methods:
|
||||
pre = dict.get("%s_pre" % m)
|
||||
post = dict.get("%s_post" % m)
|
||||
if pre or post:
|
||||
dict[k] = cls.make_eiffel_method(dict[m], pre, post)
|
||||
|
||||
class EiffelMetaClass1(EiffelBaseMetaClass):
|
||||
# an implementation of the "eiffel" meta class that uses nested functions
|
||||
|
||||
@staticmethod
|
||||
def make_eiffel_method(func, pre, post):
|
||||
def method(self, *args, **kwargs):
|
||||
if pre:
|
||||
pre(self, *args, **kwargs)
|
||||
x = func(self, *args, **kwargs)
|
||||
if post:
|
||||
post(self, x, *args, **kwargs)
|
||||
return x
|
||||
|
||||
if func.__doc__:
|
||||
method.__doc__ = func.__doc__
|
||||
|
||||
return method
|
||||
|
||||
class EiffelMethodWrapper:
|
||||
|
||||
def __init__(self, inst, descr):
|
||||
self._inst = inst
|
||||
self._descr = descr
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
return self._descr.callmethod(self._inst, args, kwargs)
|
||||
|
||||
class EiffelDescriptor(object):
|
||||
|
||||
def __init__(self, func, pre, post):
|
||||
self._func = func
|
||||
self._pre = pre
|
||||
self._post = post
|
||||
|
||||
self.__name__ = func.__name__
|
||||
self.__doc__ = func.__doc__
|
||||
|
||||
def __get__(self, obj, cls):
|
||||
return EiffelMethodWrapper(obj, self)
|
||||
|
||||
def callmethod(self, inst, args, kwargs):
|
||||
if self._pre:
|
||||
self._pre(inst, *args, **kwargs)
|
||||
x = self._func(inst, *args, **kwargs)
|
||||
if self._post:
|
||||
self._post(inst, x, *args, **kwargs)
|
||||
return x
|
||||
|
||||
class EiffelMetaClass2(EiffelBaseMetaClass):
|
||||
# an implementation of the "eiffel" meta class that uses descriptors
|
||||
|
||||
make_eiffel_method = EiffelDescriptor
|
||||
|
||||
def _test(metaclass):
|
||||
class Eiffel:
|
||||
__metaclass__ = metaclass
|
||||
|
||||
class Test(Eiffel):
|
||||
|
||||
def m(self, arg):
|
||||
"""Make it a little larger"""
|
||||
return arg + 1
|
||||
|
||||
def m2(self, arg):
|
||||
"""Make it a little larger"""
|
||||
return arg + 1
|
||||
|
||||
def m2_pre(self, arg):
|
||||
assert arg > 0
|
||||
|
||||
def m2_post(self, result, arg):
|
||||
assert result > arg
|
||||
|
||||
class Sub(Test):
|
||||
def m2(self, arg):
|
||||
return arg**2
|
||||
def m2_post(self, Result, arg):
|
||||
super(Sub, self).m2_post(Result, arg)
|
||||
assert Result < 100
|
||||
|
||||
t = Test()
|
||||
t.m(1)
|
||||
t.m2(1)
|
||||
try:
|
||||
t.m2(0)
|
||||
except AssertionError:
|
||||
pass
|
||||
else:
|
||||
assert False
|
||||
|
||||
s = Sub()
|
||||
try:
|
||||
s.m2(1)
|
||||
except AssertionError:
|
||||
pass # result == arg
|
||||
else:
|
||||
assert False
|
||||
try:
|
||||
s.m2(10)
|
||||
except AssertionError:
|
||||
pass # result == 100
|
||||
else:
|
||||
assert False
|
||||
s.m2(5)
|
||||
|
||||
if __name__ == "__main__":
|
||||
_test(EiffelMetaClass1)
|
||||
_test(EiffelMetaClass2)
|
|
@ -0,0 +1,177 @@
|
|||
"""Enumeration metaclass."""
|
||||
|
||||
class EnumMetaclass(type):
|
||||
"""Metaclass for enumeration.
|
||||
|
||||
To define your own enumeration, do something like
|
||||
|
||||
class Color(Enum):
|
||||
red = 1
|
||||
green = 2
|
||||
blue = 3
|
||||
|
||||
Now, Color.red, Color.green and Color.blue behave totally
|
||||
different: they are enumerated values, not integers.
|
||||
|
||||
Enumerations cannot be instantiated; however they can be
|
||||
subclassed.
|
||||
"""
|
||||
|
||||
def __init__(cls, name, bases, dict):
|
||||
super(EnumMetaclass, cls).__init__(name, bases, dict)
|
||||
cls._members = []
|
||||
for attr in dict.keys():
|
||||
if not (attr.startswith('__') and attr.endswith('__')):
|
||||
enumval = EnumInstance(name, attr, dict[attr])
|
||||
setattr(cls, attr, enumval)
|
||||
cls._members.append(attr)
|
||||
|
||||
def __getattr__(cls, name):
|
||||
if name == "__members__":
|
||||
return cls._members
|
||||
raise AttributeError, name
|
||||
|
||||
def __repr__(cls):
|
||||
s1 = s2 = ""
|
||||
enumbases = [base.__name__ for base in cls.__bases__
|
||||
if isinstance(base, EnumMetaclass) and not base is Enum]
|
||||
if enumbases:
|
||||
s1 = "(%s)" % ", ".join(enumbases)
|
||||
enumvalues = ["%s: %d" % (val, getattr(cls, val))
|
||||
for val in cls._members]
|
||||
if enumvalues:
|
||||
s2 = ": {%s}" % ", ".join(enumvalues)
|
||||
return "%s%s%s" % (cls.__name__, s1, s2)
|
||||
|
||||
class FullEnumMetaclass(EnumMetaclass):
|
||||
"""Metaclass for full enumerations.
|
||||
|
||||
A full enumeration displays all the values defined in base classes.
|
||||
"""
|
||||
|
||||
def __init__(cls, name, bases, dict):
|
||||
super(FullEnumMetaclass, cls).__init__(name, bases, dict)
|
||||
for obj in cls.__mro__:
|
||||
if isinstance(obj, EnumMetaclass):
|
||||
for attr in obj._members:
|
||||
# XXX inefficient
|
||||
if not attr in cls._members:
|
||||
cls._members.append(attr)
|
||||
|
||||
class EnumInstance(int):
|
||||
"""Class to represent an enumeration value.
|
||||
|
||||
EnumInstance('Color', 'red', 12) prints as 'Color.red' and behaves
|
||||
like the integer 12 when compared, but doesn't support arithmetic.
|
||||
|
||||
XXX Should it record the actual enumeration rather than just its
|
||||
name?
|
||||
"""
|
||||
|
||||
def __new__(cls, classname, enumname, value):
|
||||
return int.__new__(cls, value)
|
||||
|
||||
def __init__(self, classname, enumname, value):
|
||||
self.__classname = classname
|
||||
self.__enumname = enumname
|
||||
|
||||
def __repr__(self):
|
||||
return "EnumInstance(%s, %s, %d)" % (self.__classname, self.__enumname,
|
||||
self)
|
||||
|
||||
def __str__(self):
|
||||
return "%s.%s" % (self.__classname, self.__enumname)
|
||||
|
||||
class Enum:
|
||||
__metaclass__ = EnumMetaclass
|
||||
|
||||
class FullEnum:
|
||||
__metaclass__ = FullEnumMetaclass
|
||||
|
||||
def _test():
|
||||
|
||||
class Color(Enum):
|
||||
red = 1
|
||||
green = 2
|
||||
blue = 3
|
||||
|
||||
print Color.red
|
||||
|
||||
print repr(Color.red)
|
||||
print Color.red == Color.red
|
||||
print Color.red == Color.blue
|
||||
print Color.red == 1
|
||||
print Color.red == 2
|
||||
|
||||
class ExtendedColor(Color):
|
||||
white = 0
|
||||
orange = 4
|
||||
yellow = 5
|
||||
purple = 6
|
||||
black = 7
|
||||
|
||||
print ExtendedColor.orange
|
||||
print ExtendedColor.red
|
||||
|
||||
print Color.red == ExtendedColor.red
|
||||
|
||||
class OtherColor(Enum):
|
||||
white = 4
|
||||
blue = 5
|
||||
|
||||
class MergedColor(Color, OtherColor):
|
||||
pass
|
||||
|
||||
print MergedColor.red
|
||||
print MergedColor.white
|
||||
|
||||
print Color
|
||||
print ExtendedColor
|
||||
print OtherColor
|
||||
print MergedColor
|
||||
|
||||
def _test2():
|
||||
|
||||
class Color(FullEnum):
|
||||
red = 1
|
||||
green = 2
|
||||
blue = 3
|
||||
|
||||
print Color.red
|
||||
|
||||
print repr(Color.red)
|
||||
print Color.red == Color.red
|
||||
print Color.red == Color.blue
|
||||
print Color.red == 1
|
||||
print Color.red == 2
|
||||
|
||||
class ExtendedColor(Color):
|
||||
white = 0
|
||||
orange = 4
|
||||
yellow = 5
|
||||
purple = 6
|
||||
black = 7
|
||||
|
||||
print ExtendedColor.orange
|
||||
print ExtendedColor.red
|
||||
|
||||
print Color.red == ExtendedColor.red
|
||||
|
||||
class OtherColor(FullEnum):
|
||||
white = 4
|
||||
blue = 5
|
||||
|
||||
class MergedColor(Color, OtherColor):
|
||||
pass
|
||||
|
||||
print MergedColor.red
|
||||
print MergedColor.white
|
||||
|
||||
print Color
|
||||
print ExtendedColor
|
||||
print OtherColor
|
||||
print MergedColor
|
||||
|
||||
if __name__ == '__main__':
|
||||
_test()
|
||||
_test2()
|
|
@ -0,0 +1,6 @@
|
|||
Demo/parser
|
||||
Doc/libparser.tex
|
||||
Lib/AST.py
|
||||
Lib/symbol.py
|
||||
Lib/token.py
|
||||
Modules/parsermodule.c
|
|
@ -0,0 +1,32 @@
|
|||
These files are from the large example of using the `parser' module. Refer
|
||||
to the Python Library Reference for more information.
|
||||
|
||||
It also contains examples for the AST parser.
|
||||
|
||||
Files:
|
||||
------
|
||||
|
||||
FILES -- list of files associated with the parser module.
|
||||
|
||||
README -- this file.
|
||||
|
||||
docstring.py -- sample source file containing only a module docstring.
|
||||
|
||||
example.py -- module that uses the `parser' module to extract
|
||||
information from the parse tree of Python source
|
||||
code.
|
||||
|
||||
simple.py -- sample source containing a "short form" definition.
|
||||
|
||||
source.py -- sample source code used to demonstrate ability to
|
||||
handle nested constructs easily using the functions
|
||||
and classes in example.py.
|
||||
|
||||
test_parser.py program to put the parser module through its paces.
|
||||
|
||||
test_unparse.py tests for the unparse module
|
||||
|
||||
unparse.py AST (2.7) based example to recreate source code
|
||||
from an AST.
|
||||
|
||||
Enjoy!
|
|
@ -0,0 +1,2 @@
|
|||
"""Some documentation.
|
||||
"""
|
|
@ -0,0 +1,190 @@
|
|||
"""Simple code to extract class & function docstrings from a module.
|
||||
|
||||
This code is used as an example in the library reference manual in the
|
||||
section on using the parser module. Refer to the manual for a thorough
|
||||
discussion of the operation of this code.
|
||||
"""
|
||||
|
||||
import os
|
||||
import parser
|
||||
import symbol
|
||||
import token
|
||||
import types
|
||||
|
||||
from types import ListType, TupleType
|
||||
|
||||
|
||||
def get_docs(fileName):
|
||||
"""Retrieve information from the parse tree of a source file.
|
||||
|
||||
fileName
|
||||
Name of the file to read Python source code from.
|
||||
"""
|
||||
source = open(fileName).read()
|
||||
basename = os.path.basename(os.path.splitext(fileName)[0])
|
||||
ast = parser.suite(source)
|
||||
return ModuleInfo(ast.totuple(), basename)
|
||||
|
||||
|
||||
class SuiteInfoBase:
|
||||
_docstring = ''
|
||||
_name = ''
|
||||
|
||||
def __init__(self, tree = None):
|
||||
self._class_info = {}
|
||||
self._function_info = {}
|
||||
if tree:
|
||||
self._extract_info(tree)
|
||||
|
||||
def _extract_info(self, tree):
|
||||
# extract docstring
|
||||
if len(tree) == 2:
|
||||
found, vars = match(DOCSTRING_STMT_PATTERN[1], tree[1])
|
||||
else:
|
||||
found, vars = match(DOCSTRING_STMT_PATTERN, tree[3])
|
||||
if found:
|
||||
self._docstring = eval(vars['docstring'])
|
||||
# discover inner definitions
|
||||
for node in tree[1:]:
|
||||
found, vars = match(COMPOUND_STMT_PATTERN, node)
|
||||
if found:
|
||||
cstmt = vars['compound']
|
||||
if cstmt[0] == symbol.funcdef:
|
||||
name = cstmt[2][1]
|
||||
self._function_info[name] = FunctionInfo(cstmt)
|
||||
elif cstmt[0] == symbol.classdef:
|
||||
name = cstmt[2][1]
|
||||
self._class_info[name] = ClassInfo(cstmt)
|
||||
|
||||
def get_docstring(self):
|
||||
return self._docstring
|
||||
|
||||
def get_name(self):
|
||||
return self._name
|
||||
|
||||
def get_class_names(self):
|
||||
return self._class_info.keys()
|
||||
|
||||
def get_class_info(self, name):
|
||||
return self._class_info[name]
|
||||
|
||||
def __getitem__(self, name):
|
||||
try:
|
||||
return self._class_info[name]
|
||||
except KeyError:
|
||||
return self._function_info[name]
|
||||
|
||||
|
||||
class SuiteFuncInfo:
|
||||
# Mixin class providing access to function names and info.
|
||||
|
||||
def get_function_names(self):
|
||||
return self._function_info.keys()
|
||||
|
||||
def get_function_info(self, name):
|
||||
return self._function_info[name]
|
||||
|
||||
|
||||
class FunctionInfo(SuiteInfoBase, SuiteFuncInfo):
|
||||
def __init__(self, tree = None):
|
||||
self._name = tree[2][1]
|
||||
SuiteInfoBase.__init__(self, tree and tree[-1] or None)
|
||||
|
||||
|
||||
class ClassInfo(SuiteInfoBase):
|
||||
def __init__(self, tree = None):
|
||||
self._name = tree[2][1]
|
||||
SuiteInfoBase.__init__(self, tree and tree[-1] or None)
|
||||
|
||||
def get_method_names(self):
|
||||
return self._function_info.keys()
|
||||
|
||||
def get_method_info(self, name):
|
||||
return self._function_info[name]
|
||||
|
||||
|
||||
class ModuleInfo(SuiteInfoBase, SuiteFuncInfo):
|
||||
def __init__(self, tree = None, name = "<string>"):
|
||||
self._name = name
|
||||
SuiteInfoBase.__init__(self, tree)
|
||||
if tree:
|
||||
found, vars = match(DOCSTRING_STMT_PATTERN, tree[1])
|
||||
if found:
|
||||
self._docstring = vars["docstring"]
|
||||
|
||||
|
||||
def match(pattern, data, vars=None):
|
||||
"""Match `data' to `pattern', with variable extraction.
|
||||
|
||||
pattern
|
||||
Pattern to match against, possibly containing variables.
|
||||
|
||||
data
|
||||
Data to be checked and against which variables are extracted.
|
||||
|
||||
vars
|
||||
Dictionary of variables which have already been found. If not
|
||||
provided, an empty dictionary is created.
|
||||
|
||||
The `pattern' value may contain variables of the form ['varname'] which
|
||||
are allowed to match anything. The value that is matched is returned as
|
||||
part of a dictionary which maps 'varname' to the matched value. 'varname'
|
||||
is not required to be a string object, but using strings makes patterns
|
||||
and the code which uses them more readable.
|
||||
|
||||
This function returns two values: a boolean indicating whether a match
|
||||
was found and a dictionary mapping variable names to their associated
|
||||
values.
|
||||
"""
|
||||
if vars is None:
|
||||
vars = {}
|
||||
if type(pattern) is ListType: # 'variables' are ['varname']
|
||||
vars[pattern[0]] = data
|
||||
return 1, vars
|
||||
if type(pattern) is not TupleType:
|
||||
return (pattern == data), vars
|
||||
if len(data) != len(pattern):
|
||||
return 0, vars
|
||||
for pattern, data in map(None, pattern, data):
|
||||
same, vars = match(pattern, data, vars)
|
||||
if not same:
|
||||
break
|
||||
return same, vars
|
||||
|
||||
|
||||
# This pattern identifies compound statements, allowing them to be readily
|
||||
# differentiated from simple statements.
|
||||
#
|
||||
COMPOUND_STMT_PATTERN = (
|
||||
symbol.stmt,
|
||||
(symbol.compound_stmt, ['compound'])
|
||||
)
|
||||
|
||||
|
||||
# This pattern will match a 'stmt' node which *might* represent a docstring;
|
||||
# docstrings require that the statement which provides the docstring be the
|
||||
# first statement in the class or function, which this pattern does not check.
|
||||
#
|
||||
DOCSTRING_STMT_PATTERN = (
|
||||
symbol.stmt,
|
||||
(symbol.simple_stmt,
|
||||
(symbol.small_stmt,
|
||||
(symbol.expr_stmt,
|
||||
(symbol.testlist,
|
||||
(symbol.test,
|
||||
(symbol.and_test,
|
||||
(symbol.not_test,
|
||||
(symbol.comparison,
|
||||
(symbol.expr,
|
||||
(symbol.xor_expr,
|
||||
(symbol.and_expr,
|
||||
(symbol.shift_expr,
|
||||
(symbol.arith_expr,
|
||||
(symbol.term,
|
||||
(symbol.factor,
|
||||
(symbol.power,
|
||||
(symbol.atom,
|
||||
(token.STRING, ['docstring'])
|
||||
)))))))))))))))),
|
||||
(token.NEWLINE, '')
|
||||
))
|
|
@ -0,0 +1 @@
|
|||
def f(): "maybe a docstring"
|
|
@ -0,0 +1,27 @@
|
|||
"""Exmaple file to be parsed for the parsermodule example.
|
||||
|
||||
The classes and functions in this module exist only to exhibit the ability
|
||||
of the handling information extraction from nested definitions using parse
|
||||
trees. They shouldn't interest you otherwise!
|
||||
"""
|
||||
|
||||
class Simple:
|
||||
"This class does very little."
|
||||
|
||||
def method(self):
|
||||
"This method does almost nothing."
|
||||
return 1
|
||||
|
||||
class Nested:
|
||||
"This is a nested class."
|
||||
|
||||
def nested_method(self):
|
||||
"Method of Nested class."
|
||||
def nested_function():
|
||||
"Function in method of Nested class."
|
||||
pass
|
||||
return nested_function
|
||||
|
||||
def function():
|
||||
"This function lives at the module level."
|
||||
return 0
|
|
@ -0,0 +1,48 @@
|
|||
#! /usr/bin/env python
|
||||
# (Force the script to use the latest build.)
|
||||
#
|
||||
# test_parser.py
|
||||
|
||||
import parser, traceback
|
||||
|
||||
_numFailed = 0
|
||||
|
||||
def testChunk(t, fileName):
|
||||
global _numFailed
|
||||
print '----', fileName,
|
||||
try:
|
||||
st = parser.suite(t)
|
||||
tup = parser.st2tuple(st)
|
||||
# this discards the first ST; a huge memory savings when running
|
||||
# against a large source file like Tkinter.py.
|
||||
st = None
|
||||
new = parser.tuple2st(tup)
|
||||
except parser.ParserError, err:
|
||||
print
|
||||
print 'parser module raised exception on input file', fileName + ':'
|
||||
traceback.print_exc()
|
||||
_numFailed = _numFailed + 1
|
||||
else:
|
||||
if tup != parser.st2tuple(new):
|
||||
print
|
||||
print 'parser module failed on input file', fileName
|
||||
_numFailed = _numFailed + 1
|
||||
else:
|
||||
print 'o.k.'
|
||||
|
||||
def testFile(fileName):
|
||||
t = open(fileName).read()
|
||||
testChunk(t, fileName)
|
||||
|
||||
def test():
|
||||
import sys
|
||||
args = sys.argv[1:]
|
||||
if not args:
|
||||
import glob
|
||||
args = glob.glob("*.py")
|
||||
args.sort()
|
||||
map(testFile, args)
|
||||
sys.exit(_numFailed != 0)
|
||||
|
||||
if __name__ == '__main__':
|
||||
test()
|
|
@ -0,0 +1,213 @@
|
|||
import unittest
|
||||
from test import test_support
|
||||
import cStringIO
|
||||
import sys
|
||||
import os
|
||||
import tokenize
|
||||
import ast
|
||||
import unparse
|
||||
|
||||
def read_pyfile(filename):
|
||||
"""Read and return the contents of a Python source file (as a
|
||||
string), taking into account the file encoding."""
|
||||
with open(filename, "r") as pyfile:
|
||||
source = pyfile.read()
|
||||
return source
|
||||
|
||||
for_else = """\
|
||||
def f():
|
||||
for x in range(10):
|
||||
break
|
||||
else:
|
||||
y = 2
|
||||
z = 3
|
||||
"""
|
||||
|
||||
while_else = """\
|
||||
def g():
|
||||
while True:
|
||||
break
|
||||
else:
|
||||
y = 2
|
||||
z = 3
|
||||
"""
|
||||
|
||||
relative_import = """\
|
||||
from . import fred
|
||||
from .. import barney
|
||||
from .australia import shrimp as prawns
|
||||
"""
|
||||
|
||||
class_decorator = """\
|
||||
@f1(arg)
|
||||
@f2
|
||||
class Foo: pass
|
||||
"""
|
||||
|
||||
elif1 = """\
|
||||
if cond1:
|
||||
suite1
|
||||
elif cond2:
|
||||
suite2
|
||||
else:
|
||||
suite3
|
||||
"""
|
||||
|
||||
elif2 = """\
|
||||
if cond1:
|
||||
suite1
|
||||
elif cond2:
|
||||
suite2
|
||||
"""
|
||||
|
||||
try_except_finally = """\
|
||||
try:
|
||||
suite1
|
||||
except ex1:
|
||||
suite2
|
||||
except ex2:
|
||||
suite3
|
||||
else:
|
||||
suite4
|
||||
finally:
|
||||
suite5
|
||||
"""
|
||||
|
||||
class ASTTestCase(unittest.TestCase):
|
||||
def assertASTEqual(self, ast1, ast2):
|
||||
dump1 = ast.dump(ast1)
|
||||
dump2 = ast.dump(ast2)
|
||||
self.assertEqual(ast.dump(ast1), ast.dump(ast2))
|
||||
|
||||
def check_roundtrip(self, code1, filename="internal"):
|
||||
ast1 = compile(code1, filename, "exec", ast.PyCF_ONLY_AST)
|
||||
unparse_buffer = cStringIO.StringIO()
|
||||
unparse.Unparser(ast1, unparse_buffer)
|
||||
code2 = unparse_buffer.getvalue()
|
||||
ast2 = compile(code2, filename, "exec", ast.PyCF_ONLY_AST)
|
||||
self.assertASTEqual(ast1, ast2)
|
||||
|
||||
class UnparseTestCase(ASTTestCase):
|
||||
# Tests for specific bugs found in earlier versions of unparse
|
||||
|
||||
def test_del_statement(self):
|
||||
self.check_roundtrip("del x, y, z")
|
||||
|
||||
def test_shifts(self):
|
||||
self.check_roundtrip("45 << 2")
|
||||
self.check_roundtrip("13 >> 7")
|
||||
|
||||
def test_for_else(self):
|
||||
self.check_roundtrip(for_else)
|
||||
|
||||
def test_while_else(self):
|
||||
self.check_roundtrip(while_else)
|
||||
|
||||
def test_unary_parens(self):
|
||||
self.check_roundtrip("(-1)**7")
|
||||
self.check_roundtrip("(-1.)**8")
|
||||
self.check_roundtrip("(-1j)**6")
|
||||
self.check_roundtrip("not True or False")
|
||||
self.check_roundtrip("True or not False")
|
||||
|
||||
def test_integer_parens(self):
|
||||
self.check_roundtrip("3 .__abs__()")
|
||||
|
||||
def test_huge_float(self):
|
||||
self.check_roundtrip("1e1000")
|
||||
self.check_roundtrip("-1e1000")
|
||||
self.check_roundtrip("1e1000j")
|
||||
self.check_roundtrip("-1e1000j")
|
||||
|
||||
def test_min_int(self):
|
||||
self.check_roundtrip(str(-sys.maxint-1))
|
||||
self.check_roundtrip("-(%s)" % (sys.maxint + 1))
|
||||
|
||||
def test_imaginary_literals(self):
|
||||
self.check_roundtrip("7j")
|
||||
self.check_roundtrip("-7j")
|
||||
self.check_roundtrip("-(7j)")
|
||||
self.check_roundtrip("0j")
|
||||
self.check_roundtrip("-0j")
|
||||
self.check_roundtrip("-(0j)")
|
||||
|
||||
def test_negative_zero(self):
|
||||
self.check_roundtrip("-0")
|
||||
self.check_roundtrip("-(0)")
|
||||
self.check_roundtrip("-0b0")
|
||||
self.check_roundtrip("-(0b0)")
|
||||
self.check_roundtrip("-0o0")
|
||||
self.check_roundtrip("-(0o0)")
|
||||
self.check_roundtrip("-0x0")
|
||||
self.check_roundtrip("-(0x0)")
|
||||
|
||||
def test_lambda_parentheses(self):
|
||||
self.check_roundtrip("(lambda: int)()")
|
||||
|
||||
def test_chained_comparisons(self):
|
||||
self.check_roundtrip("1 < 4 <= 5")
|
||||
self.check_roundtrip("a is b is c is not d")
|
||||
|
||||
def test_function_arguments(self):
|
||||
self.check_roundtrip("def f(): pass")
|
||||
self.check_roundtrip("def f(a): pass")
|
||||
self.check_roundtrip("def f(b = 2): pass")
|
||||
self.check_roundtrip("def f(a, b): pass")
|
||||
self.check_roundtrip("def f(a, b = 2): pass")
|
||||
self.check_roundtrip("def f(a = 5, b = 2): pass")
|
||||
self.check_roundtrip("def f(*args, **kwargs): pass")
|
||||
|
||||
def test_relative_import(self):
|
||||
self.check_roundtrip(relative_import)
|
||||
|
||||
def test_bytes(self):
|
||||
self.check_roundtrip("b'123'")
|
||||
|
||||
def test_set_literal(self):
|
||||
self.check_roundtrip("{'a', 'b', 'c'}")
|
||||
|
||||
def test_set_comprehension(self):
|
||||
self.check_roundtrip("{x for x in range(5)}")
|
||||
|
||||
def test_dict_comprehension(self):
|
||||
self.check_roundtrip("{x: x*x for x in range(10)}")
|
||||
|
||||
def test_class_decorators(self):
|
||||
self.check_roundtrip(class_decorator)
|
||||
|
||||
def test_elifs(self):
|
||||
self.check_roundtrip(elif1)
|
||||
self.check_roundtrip(elif2)
|
||||
|
||||
def test_try_except_finally(self):
|
||||
self.check_roundtrip(try_except_finally)
|
||||
|
||||
class DirectoryTestCase(ASTTestCase):
|
||||
"""Test roundtrip behaviour on all files in Lib and Lib/test."""
|
||||
|
||||
# test directories, relative to the root of the distribution
|
||||
test_directories = 'Lib', os.path.join('Lib', 'test')
|
||||
|
||||
def test_files(self):
|
||||
# get names of files to test
|
||||
dist_dir = os.path.join(os.path.dirname(__file__), os.pardir, os.pardir)
|
||||
|
||||
names = []
|
||||
for d in self.test_directories:
|
||||
test_dir = os.path.join(dist_dir, d)
|
||||
for n in os.listdir(test_dir):
|
||||
if n.endswith('.py') and not n.startswith('bad'):
|
||||
names.append(os.path.join(test_dir, n))
|
||||
|
||||
for filename in names:
|
||||
if test_support.verbose:
|
||||
print('Testing %s' % filename)
|
||||
source = read_pyfile(filename)
|
||||
self.check_roundtrip(source)
|
||||
|
||||
|
||||
def test_main():
|
||||
test_support.run_unittest(UnparseTestCase, DirectoryTestCase)
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_main()
|
|
@ -0,0 +1,606 @@
|
|||
"Usage: unparse.py <path to source file>"
|
||||
import sys
|
||||
import ast
|
||||
import cStringIO
|
||||
import os
|
||||
|
||||
# Large float and imaginary literals get turned into infinities in the AST.
|
||||
# We unparse those infinities to INFSTR.
|
||||
INFSTR = "1e" + repr(sys.float_info.max_10_exp + 1)
|
||||
|
||||
def interleave(inter, f, seq):
|
||||
"""Call f on each item in seq, calling inter() in between.
|
||||
"""
|
||||
seq = iter(seq)
|
||||
try:
|
||||
f(next(seq))
|
||||
except StopIteration:
|
||||
pass
|
||||
else:
|
||||
for x in seq:
|
||||
inter()
|
||||
f(x)
|
||||
|
||||
class Unparser:
|
||||
"""Methods in this class recursively traverse an AST and
|
||||
output source code for the abstract syntax; original formatting
|
||||
is disregarded. """
|
||||
|
||||
def __init__(self, tree, file = sys.stdout):
|
||||
"""Unparser(tree, file=sys.stdout) -> None.
|
||||
Print the source for tree to file."""
|
||||
self.f = file
|
||||
self.future_imports = []
|
||||
self._indent = 0
|
||||
self.dispatch(tree)
|
||||
self.f.write("")
|
||||
self.f.flush()
|
||||
|
||||
def fill(self, text = ""):
|
||||
"Indent a piece of text, according to the current indentation level"
|
||||
self.f.write("\n"+" "*self._indent + text)
|
||||
|
||||
def write(self, text):
|
||||
"Append a piece of text to the current line."
|
||||
self.f.write(text)
|
||||
|
||||
def enter(self):
|
||||
"Print ':', and increase the indentation."
|
||||
self.write(":")
|
||||
self._indent += 1
|
||||
|
||||
def leave(self):
|
||||
"Decrease the indentation level."
|
||||
self._indent -= 1
|
||||
|
||||
def dispatch(self, tree):
|
||||
"Dispatcher function, dispatching tree type T to method _T."
|
||||
if isinstance(tree, list):
|
||||
for t in tree:
|
||||
self.dispatch(t)
|
||||
return
|
||||
meth = getattr(self, "_"+tree.__class__.__name__)
|
||||
meth(tree)
|
||||
|
||||
|
||||
############### Unparsing methods ######################
|
||||
# There should be one method per concrete grammar type #
|
||||
# Constructors should be grouped by sum type. Ideally, #
|
||||
# this would follow the order in the grammar, but #
|
||||
# currently doesn't. #
|
||||
########################################################
|
||||
|
||||
def _Module(self, tree):
|
||||
for stmt in tree.body:
|
||||
self.dispatch(stmt)
|
||||
|
||||
# stmt
|
||||
def _Expr(self, tree):
|
||||
self.fill()
|
||||
self.dispatch(tree.value)
|
||||
|
||||
def _Import(self, t):
|
||||
self.fill("import ")
|
||||
interleave(lambda: self.write(", "), self.dispatch, t.names)
|
||||
|
||||
def _ImportFrom(self, t):
|
||||
# A from __future__ import may affect unparsing, so record it.
|
||||
if t.module and t.module == '__future__':
|
||||
self.future_imports.extend(n.name for n in t.names)
|
||||
|
||||
self.fill("from ")
|
||||
self.write("." * t.level)
|
||||
if t.module:
|
||||
self.write(t.module)
|
||||
self.write(" import ")
|
||||
interleave(lambda: self.write(", "), self.dispatch, t.names)
|
||||
|
||||
def _Assign(self, t):
|
||||
self.fill()
|
||||
for target in t.targets:
|
||||
self.dispatch(target)
|
||||
self.write(" = ")
|
||||
self.dispatch(t.value)
|
||||
|
||||
def _AugAssign(self, t):
|
||||
self.fill()
|
||||
self.dispatch(t.target)
|
||||
self.write(" "+self.binop[t.op.__class__.__name__]+"= ")
|
||||
self.dispatch(t.value)
|
||||
|
||||
def _Return(self, t):
|
||||
self.fill("return")
|
||||
if t.value:
|
||||
self.write(" ")
|
||||
self.dispatch(t.value)
|
||||
|
||||
def _Pass(self, t):
|
||||
self.fill("pass")
|
||||
|
||||
def _Break(self, t):
|
||||
self.fill("break")
|
||||
|
||||
def _Continue(self, t):
|
||||
self.fill("continue")
|
||||
|
||||
def _Delete(self, t):
|
||||
self.fill("del ")
|
||||
interleave(lambda: self.write(", "), self.dispatch, t.targets)
|
||||
|
||||
def _Assert(self, t):
|
||||
self.fill("assert ")
|
||||
self.dispatch(t.test)
|
||||
if t.msg:
|
||||
self.write(", ")
|
||||
self.dispatch(t.msg)
|
||||
|
||||
def _Exec(self, t):
|
||||
self.fill("exec ")
|
||||
self.dispatch(t.body)
|
||||
if t.globals:
|
||||
self.write(" in ")
|
||||
self.dispatch(t.globals)
|
||||
if t.locals:
|
||||
self.write(", ")
|
||||
self.dispatch(t.locals)
|
||||
|
||||
def _Print(self, t):
|
||||
self.fill("print ")
|
||||
do_comma = False
|
||||
if t.dest:
|
||||
self.write(">>")
|
||||
self.dispatch(t.dest)
|
||||
do_comma = True
|
||||
for e in t.values:
|
||||
if do_comma:self.write(", ")
|
||||
else:do_comma=True
|
||||
self.dispatch(e)
|
||||
if not t.nl:
|
||||
self.write(",")
|
||||
|
||||
def _Global(self, t):
|
||||
self.fill("global ")
|
||||
interleave(lambda: self.write(", "), self.write, t.names)
|
||||
|
||||
def _Yield(self, t):
|
||||
self.write("(")
|
||||
self.write("yield")
|
||||
if t.value:
|
||||
self.write(" ")
|
||||
self.dispatch(t.value)
|
||||
self.write(")")
|
||||
|
||||
def _Raise(self, t):
|
||||
self.fill('raise ')
|
||||
if t.type:
|
||||
self.dispatch(t.type)
|
||||
if t.inst:
|
||||
self.write(", ")
|
||||
self.dispatch(t.inst)
|
||||
if t.tback:
|
||||
self.write(", ")
|
||||
self.dispatch(t.tback)
|
||||
|
||||
def _TryExcept(self, t):
|
||||
self.fill("try")
|
||||
self.enter()
|
||||
self.dispatch(t.body)
|
||||
self.leave()
|
||||
|
||||
for ex in t.handlers:
|
||||
self.dispatch(ex)
|
||||
if t.orelse:
|
||||
self.fill("else")
|
||||
self.enter()
|
||||
self.dispatch(t.orelse)
|
||||
self.leave()
|
||||
|
||||
def _TryFinally(self, t):
|
||||
if len(t.body) == 1 and isinstance(t.body[0], ast.TryExcept):
|
||||
# try-except-finally
|
||||
self.dispatch(t.body)
|
||||
else:
|
||||
self.fill("try")
|
||||
self.enter()
|
||||
self.dispatch(t.body)
|
||||
self.leave()
|
||||
|
||||
self.fill("finally")
|
||||
self.enter()
|
||||
self.dispatch(t.finalbody)
|
||||
self.leave()
|
||||
|
||||
def _ExceptHandler(self, t):
|
||||
self.fill("except")
|
||||
if t.type:
|
||||
self.write(" ")
|
||||
self.dispatch(t.type)
|
||||
if t.name:
|
||||
self.write(" as ")
|
||||
self.dispatch(t.name)
|
||||
self.enter()
|
||||
self.dispatch(t.body)
|
||||
self.leave()
|
||||
|
||||
def _ClassDef(self, t):
|
||||
self.write("\n")
|
||||
for deco in t.decorator_list:
|
||||
self.fill("@")
|
||||
self.dispatch(deco)
|
||||
self.fill("class "+t.name)
|
||||
if t.bases:
|
||||
self.write("(")
|
||||
for a in t.bases:
|
||||
self.dispatch(a)
|
||||
self.write(", ")
|
||||
self.write(")")
|
||||
self.enter()
|
||||
self.dispatch(t.body)
|
||||
self.leave()
|
||||
|
||||
def _FunctionDef(self, t):
|
||||
self.write("\n")
|
||||
for deco in t.decorator_list:
|
||||
self.fill("@")
|
||||
self.dispatch(deco)
|
||||
self.fill("def "+t.name + "(")
|
||||
self.dispatch(t.args)
|
||||
self.write(")")
|
||||
self.enter()
|
||||
self.dispatch(t.body)
|
||||
self.leave()
|
||||
|
||||
def _For(self, t):
|
||||
self.fill("for ")
|
||||
self.dispatch(t.target)
|
||||
self.write(" in ")
|
||||
self.dispatch(t.iter)
|
||||
self.enter()
|
||||
self.dispatch(t.body)
|
||||
self.leave()
|
||||
if t.orelse:
|
||||
self.fill("else")
|
||||
self.enter()
|
||||
self.dispatch(t.orelse)
|
||||
self.leave()
|
||||
|
||||
def _If(self, t):
|
||||
self.fill("if ")
|
||||
self.dispatch(t.test)
|
||||
self.enter()
|
||||
self.dispatch(t.body)
|
||||
self.leave()
|
||||
# collapse nested ifs into equivalent elifs.
|
||||
while (t.orelse and len(t.orelse) == 1 and
|
||||
isinstance(t.orelse[0], ast.If)):
|
||||
t = t.orelse[0]
|
||||
self.fill("elif ")
|
||||
self.dispatch(t.test)
|
||||
self.enter()
|
||||
self.dispatch(t.body)
|
||||
self.leave()
|
||||
# final else
|
||||
if t.orelse:
|
||||
self.fill("else")
|
||||
self.enter()
|
||||
self.dispatch(t.orelse)
|
||||
self.leave()
|
||||
|
||||
def _While(self, t):
|
||||
self.fill("while ")
|
||||
self.dispatch(t.test)
|
||||
self.enter()
|
||||
self.dispatch(t.body)
|
||||
self.leave()
|
||||
if t.orelse:
|
||||
self.fill("else")
|
||||
self.enter()
|
||||
self.dispatch(t.orelse)
|
||||
self.leave()
|
||||
|
||||
def _With(self, t):
|
||||
self.fill("with ")
|
||||
self.dispatch(t.context_expr)
|
||||
if t.optional_vars:
|
||||
self.write(" as ")
|
||||
self.dispatch(t.optional_vars)
|
||||
self.enter()
|
||||
self.dispatch(t.body)
|
||||
self.leave()
|
||||
|
||||
# expr
|
||||
def _Str(self, tree):
|
||||
# if from __future__ import unicode_literals is in effect,
|
||||
# then we want to output string literals using a 'b' prefix
|
||||
# and unicode literals with no prefix.
|
||||
if "unicode_literals" not in self.future_imports:
|
||||
self.write(repr(tree.s))
|
||||
elif isinstance(tree.s, str):
|
||||
self.write("b" + repr(tree.s))
|
||||
elif isinstance(tree.s, unicode):
|
||||
self.write(repr(tree.s).lstrip("u"))
|
||||
else:
|
||||
assert False, "shouldn't get here"
|
||||
|
||||
def _Name(self, t):
|
||||
self.write(t.id)
|
||||
|
||||
def _Repr(self, t):
|
||||
self.write("`")
|
||||
self.dispatch(t.value)
|
||||
self.write("`")
|
||||
|
||||
def _Num(self, t):
|
||||
repr_n = repr(t.n)
|
||||
# Parenthesize negative numbers, to avoid turning (-1)**2 into -1**2.
|
||||
if repr_n.startswith("-"):
|
||||
self.write("(")
|
||||
# Substitute overflowing decimal literal for AST infinities.
|
||||
self.write(repr_n.replace("inf", INFSTR))
|
||||
if repr_n.startswith("-"):
|
||||
self.write(")")
|
||||
|
||||
def _List(self, t):
|
||||
self.write("[")
|
||||
interleave(lambda: self.write(", "), self.dispatch, t.elts)
|
||||
self.write("]")
|
||||
|
||||
def _ListComp(self, t):
|
||||
self.write("[")
|
||||
self.dispatch(t.elt)
|
||||
for gen in t.generators:
|
||||
self.dispatch(gen)
|
||||
self.write("]")
|
||||
|
||||
def _GeneratorExp(self, t):
|
||||
self.write("(")
|
||||
self.dispatch(t.elt)
|
||||
for gen in t.generators:
|
||||
self.dispatch(gen)
|
||||
self.write(")")
|
||||
|
||||
def _SetComp(self, t):
|
||||
self.write("{")
|
||||
self.dispatch(t.elt)
|
||||
for gen in t.generators:
|
||||
self.dispatch(gen)
|
||||
self.write("}")
|
||||
|
||||
def _DictComp(self, t):
|
||||
self.write("{")
|
||||
self.dispatch(t.key)
|
||||
self.write(": ")
|
||||
self.dispatch(t.value)
|
||||
for gen in t.generators:
|
||||
self.dispatch(gen)
|
||||
self.write("}")
|
||||
|
||||
def _comprehension(self, t):
|
||||
self.write(" for ")
|
||||
self.dispatch(t.target)
|
||||
self.write(" in ")
|
||||
self.dispatch(t.iter)
|
||||
for if_clause in t.ifs:
|
||||
self.write(" if ")
|
||||
self.dispatch(if_clause)
|
||||
|
||||
def _IfExp(self, t):
|
||||
self.write("(")
|
||||
self.dispatch(t.body)
|
||||
self.write(" if ")
|
||||
self.dispatch(t.test)
|
||||
self.write(" else ")
|
||||
self.dispatch(t.orelse)
|
||||
self.write(")")
|
||||
|
||||
def _Set(self, t):
|
||||
assert(t.elts) # should be at least one element
|
||||
self.write("{")
|
||||
interleave(lambda: self.write(", "), self.dispatch, t.elts)
|
||||
self.write("}")
|
||||
|
||||
def _Dict(self, t):
|
||||
self.write("{")
|
||||
def write_pair(pair):
|
||||
(k, v) = pair
|
||||
self.dispatch(k)
|
||||
self.write(": ")
|
||||
self.dispatch(v)
|
||||
interleave(lambda: self.write(", "), write_pair, zip(t.keys, t.values))
|
||||
self.write("}")
|
||||
|
||||
def _Tuple(self, t):
|
||||
self.write("(")
|
||||
if len(t.elts) == 1:
|
||||
(elt,) = t.elts
|
||||
self.dispatch(elt)
|
||||
self.write(",")
|
||||
else:
|
||||
interleave(lambda: self.write(", "), self.dispatch, t.elts)
|
||||
self.write(")")
|
||||
|
||||
unop = {"Invert":"~", "Not": "not", "UAdd":"+", "USub":"-"}
|
||||
def _UnaryOp(self, t):
|
||||
self.write("(")
|
||||
self.write(self.unop[t.op.__class__.__name__])
|
||||
self.write(" ")
|
||||
# If we're applying unary minus to a number, parenthesize the number.
|
||||
# This is necessary: -2147483648 is different from -(2147483648) on
|
||||
# a 32-bit machine (the first is an int, the second a long), and
|
||||
# -7j is different from -(7j). (The first has real part 0.0, the second
|
||||
# has real part -0.0.)
|
||||
if isinstance(t.op, ast.USub) and isinstance(t.operand, ast.Num):
|
||||
self.write("(")
|
||||
self.dispatch(t.operand)
|
||||
self.write(")")
|
||||
else:
|
||||
self.dispatch(t.operand)
|
||||
self.write(")")
|
||||
|
||||
binop = { "Add":"+", "Sub":"-", "Mult":"*", "Div":"/", "Mod":"%",
|
||||
"LShift":"<<", "RShift":">>", "BitOr":"|", "BitXor":"^", "BitAnd":"&",
|
||||
"FloorDiv":"//", "Pow": "**"}
|
||||
def _BinOp(self, t):
|
||||
self.write("(")
|
||||
self.dispatch(t.left)
|
||||
self.write(" " + self.binop[t.op.__class__.__name__] + " ")
|
||||
self.dispatch(t.right)
|
||||
self.write(")")
|
||||
|
||||
cmpops = {"Eq":"==", "NotEq":"!=", "Lt":"<", "LtE":"<=", "Gt":">", "GtE":">=",
|
||||
"Is":"is", "IsNot":"is not", "In":"in", "NotIn":"not in"}
|
||||
def _Compare(self, t):
|
||||
self.write("(")
|
||||
self.dispatch(t.left)
|
||||
for o, e in zip(t.ops, t.comparators):
|
||||
self.write(" " + self.cmpops[o.__class__.__name__] + " ")
|
||||
self.dispatch(e)
|
||||
self.write(")")
|
||||
|
||||
boolops = {ast.And: 'and', ast.Or: 'or'}
|
||||
def _BoolOp(self, t):
|
||||
self.write("(")
|
||||
s = " %s " % self.boolops[t.op.__class__]
|
||||
interleave(lambda: self.write(s), self.dispatch, t.values)
|
||||
self.write(")")
|
||||
|
||||
def _Attribute(self,t):
|
||||
self.dispatch(t.value)
|
||||
# Special case: 3.__abs__() is a syntax error, so if t.value
|
||||
# is an integer literal then we need to either parenthesize
|
||||
# it or add an extra space to get 3 .__abs__().
|
||||
if isinstance(t.value, ast.Num) and isinstance(t.value.n, int):
|
||||
self.write(" ")
|
||||
self.write(".")
|
||||
self.write(t.attr)
|
||||
|
||||
def _Call(self, t):
|
||||
self.dispatch(t.func)
|
||||
self.write("(")
|
||||
comma = False
|
||||
for e in t.args:
|
||||
if comma: self.write(", ")
|
||||
else: comma = True
|
||||
self.dispatch(e)
|
||||
for e in t.keywords:
|
||||
if comma: self.write(", ")
|
||||
else: comma = True
|
||||
self.dispatch(e)
|
||||
if t.starargs:
|
||||
if comma: self.write(", ")
|
||||
else: comma = True
|
||||
self.write("*")
|
||||
self.dispatch(t.starargs)
|
||||
if t.kwargs:
|
||||
if comma: self.write(", ")
|
||||
else: comma = True
|
||||
self.write("**")
|
||||
self.dispatch(t.kwargs)
|
||||
self.write(")")
|
||||
|
||||
def _Subscript(self, t):
|
||||
self.dispatch(t.value)
|
||||
self.write("[")
|
||||
self.dispatch(t.slice)
|
||||
self.write("]")
|
||||
|
||||
# slice
|
||||
def _Ellipsis(self, t):
|
||||
self.write("...")
|
||||
|
||||
def _Index(self, t):
|
||||
self.dispatch(t.value)
|
||||
|
||||
def _Slice(self, t):
|
||||
if t.lower:
|
||||
self.dispatch(t.lower)
|
||||
self.write(":")
|
||||
if t.upper:
|
||||
self.dispatch(t.upper)
|
||||
if t.step:
|
||||
self.write(":")
|
||||
self.dispatch(t.step)
|
||||
|
||||
def _ExtSlice(self, t):
|
||||
interleave(lambda: self.write(', '), self.dispatch, t.dims)
|
||||
|
||||
# others
|
||||
def _arguments(self, t):
|
||||
first = True
|
||||
# normal arguments
|
||||
defaults = [None] * (len(t.args) - len(t.defaults)) + t.defaults
|
||||
for a,d in zip(t.args, defaults):
|
||||
if first:first = False
|
||||
else: self.write(", ")
|
||||
self.dispatch(a),
|
||||
if d:
|
||||
self.write("=")
|
||||
self.dispatch(d)
|
||||
|
||||
# varargs
|
||||
if t.vararg:
|
||||
if first:first = False
|
||||
else: self.write(", ")
|
||||
self.write("*")
|
||||
self.write(t.vararg)
|
||||
|
||||
# kwargs
|
||||
if t.kwarg:
|
||||
if first:first = False
|
||||
else: self.write(", ")
|
||||
self.write("**"+t.kwarg)
|
||||
|
||||
def _keyword(self, t):
|
||||
self.write(t.arg)
|
||||
self.write("=")
|
||||
self.dispatch(t.value)
|
||||
|
||||
def _Lambda(self, t):
|
||||
self.write("(")
|
||||
self.write("lambda ")
|
||||
self.dispatch(t.args)
|
||||
self.write(": ")
|
||||
self.dispatch(t.body)
|
||||
self.write(")")
|
||||
|
||||
def _alias(self, t):
|
||||
self.write(t.name)
|
||||
if t.asname:
|
||||
self.write(" as "+t.asname)
|
||||
|
||||
def roundtrip(filename, output=sys.stdout):
|
||||
with open(filename, "r") as pyfile:
|
||||
source = pyfile.read()
|
||||
tree = compile(source, filename, "exec", ast.PyCF_ONLY_AST)
|
||||
Unparser(tree, output)
|
||||
|
||||
|
||||
|
||||
def testdir(a):
|
||||
try:
|
||||
names = [n for n in os.listdir(a) if n.endswith('.py')]
|
||||
except OSError:
|
||||
sys.stderr.write("Directory not readable: %s" % a)
|
||||
else:
|
||||
for n in names:
|
||||
fullname = os.path.join(a, n)
|
||||
if os.path.isfile(fullname):
|
||||
output = cStringIO.StringIO()
|
||||
print 'Testing %s' % fullname
|
||||
try:
|
||||
roundtrip(fullname, output)
|
||||
except Exception as e:
|
||||
print ' Failed to compile, exception is %s' % repr(e)
|
||||
elif os.path.isdir(fullname):
|
||||
testdir(fullname)
|
||||
|
||||
def main(args):
|
||||
if args[0] == '--testdir':
|
||||
for a in args[1:]:
|
||||
testdir(a)
|
||||
else:
|
||||
for a in args:
|
||||
roundtrip(a)
|
||||
|
||||
if __name__=='__main__':
|
||||
main(sys.argv[1:])
|
|
@ -0,0 +1,301 @@
|
|||
"""File System Proxy.
|
||||
|
||||
Provide an OS-neutral view on a file system, locally or remotely.
|
||||
The functionality is geared towards implementing some sort of
|
||||
rdist-like utility between a Mac and a UNIX system.
|
||||
|
||||
The module defines three classes:
|
||||
|
||||
FSProxyLocal -- used for local access
|
||||
FSProxyServer -- used on the server side of remote access
|
||||
FSProxyClient -- used on the client side of remote access
|
||||
|
||||
The remote classes are instantiated with an IP address and an optional
|
||||
verbosity flag.
|
||||
"""
|
||||
|
||||
import server
|
||||
import client
|
||||
import md5
|
||||
import os
|
||||
import fnmatch
|
||||
from stat import *
|
||||
import time
|
||||
import fnmatch
|
||||
|
||||
maxnamelen = 255
|
||||
|
||||
skipnames = (os.curdir, os.pardir)
|
||||
|
||||
|
||||
class FSProxyLocal:
|
||||
|
||||
def __init__(self):
|
||||
self._dirstack = []
|
||||
self._ignore = ['*.pyc'] + self._readignore()
|
||||
|
||||
def _close(self):
|
||||
while self._dirstack:
|
||||
self.back()
|
||||
|
||||
def _readignore(self):
|
||||
file = self._hide('ignore')
|
||||
try:
|
||||
f = open(file)
|
||||
except IOError:
|
||||
file = self._hide('synctree.ignorefiles')
|
||||
try:
|
||||
f = open(file)
|
||||
except IOError:
|
||||
return []
|
||||
ignore = []
|
||||
while 1:
|
||||
line = f.readline()
|
||||
if not line: break
|
||||
if line[-1] == '\n': line = line[:-1]
|
||||
ignore.append(line)
|
||||
f.close()
|
||||
return ignore
|
||||
|
||||
def _hidden(self, name):
|
||||
return name[0] == '.'
|
||||
|
||||
def _hide(self, name):
|
||||
return '.%s' % name
|
||||
|
||||
def visible(self, name):
|
||||
if len(name) > maxnamelen: return 0
|
||||
if name[-1] == '~': return 0
|
||||
if name in skipnames: return 0
|
||||
if self._hidden(name): return 0
|
||||
head, tail = os.path.split(name)
|
||||
if head or not tail: return 0
|
||||
if os.path.islink(name): return 0
|
||||
if '\0' in open(name, 'rb').read(512): return 0
|
||||
for ign in self._ignore:
|
||||
if fnmatch.fnmatch(name, ign): return 0
|
||||
return 1
|
||||
|
||||
def check(self, name):
|
||||
if not self.visible(name):
|
||||
raise os.error, "protected name %s" % repr(name)
|
||||
|
||||
def checkfile(self, name):
|
||||
self.check(name)
|
||||
if not os.path.isfile(name):
|
||||
raise os.error, "not a plain file %s" % repr(name)
|
||||
|
||||
def pwd(self):
|
||||
return os.getcwd()
|
||||
|
||||
def cd(self, name):
|
||||
self.check(name)
|
||||
save = os.getcwd(), self._ignore
|
||||
os.chdir(name)
|
||||
self._dirstack.append(save)
|
||||
self._ignore = self._ignore + self._readignore()
|
||||
|
||||
def back(self):
|
||||
if not self._dirstack:
|
||||
raise os.error, "empty directory stack"
|
||||
dir, ignore = self._dirstack[-1]
|
||||
os.chdir(dir)
|
||||
del self._dirstack[-1]
|
||||
self._ignore = ignore
|
||||
|
||||
def _filter(self, files, pat = None):
|
||||
if pat:
|
||||
def keep(name, pat = pat):
|
||||
return fnmatch.fnmatch(name, pat)
|
||||
files = filter(keep, files)
|
||||
files = filter(self.visible, files)
|
||||
files.sort()
|
||||
return files
|
||||
|
||||
def list(self, pat = None):
|
||||
files = os.listdir(os.curdir)
|
||||
return self._filter(files, pat)
|
||||
|
||||
def listfiles(self, pat = None):
|
||||
files = os.listdir(os.curdir)
|
||||
files = filter(os.path.isfile, files)
|
||||
return self._filter(files, pat)
|
||||
|
||||
def listsubdirs(self, pat = None):
|
||||
files = os.listdir(os.curdir)
|
||||
files = filter(os.path.isdir, files)
|
||||
return self._filter(files, pat)
|
||||
|
||||
def exists(self, name):
|
||||
return self.visible(name) and os.path.exists(name)
|
||||
|
||||
def isdir(self, name):
|
||||
return self.visible(name) and os.path.isdir(name)
|
||||
|
||||
def islink(self, name):
|
||||
return self.visible(name) and os.path.islink(name)
|
||||
|
||||
def isfile(self, name):
|
||||
return self.visible(name) and os.path.isfile(name)
|
||||
|
||||
def sum(self, name):
|
||||
self.checkfile(name)
|
||||
BUFFERSIZE = 1024*8
|
||||
f = open(name)
|
||||
sum = md5.new()
|
||||
while 1:
|
||||
buffer = f.read(BUFFERSIZE)
|
||||
if not buffer:
|
||||
break
|
||||
sum.update(buffer)
|
||||
return sum.digest()
|
||||
|
||||
def size(self, name):
|
||||
self.checkfile(name)
|
||||
return os.stat(name)[ST_SIZE]
|
||||
|
||||
def mtime(self, name):
|
||||
self.checkfile(name)
|
||||
return time.localtime(os.stat(name)[ST_MTIME])
|
||||
|
||||
def stat(self, name):
|
||||
self.checkfile(name)
|
||||
size = os.stat(name)[ST_SIZE]
|
||||
mtime = time.localtime(os.stat(name)[ST_MTIME])
|
||||
return size, mtime
|
||||
|
||||
def info(self, name):
|
||||
sum = self.sum(name)
|
||||
size = os.stat(name)[ST_SIZE]
|
||||
mtime = time.localtime(os.stat(name)[ST_MTIME])
|
||||
return sum, size, mtime
|
||||
|
||||
def _list(self, function, list):
|
||||
if list is None:
|
||||
list = self.listfiles()
|
||||
res = []
|
||||
for name in list:
|
||||
try:
|
||||
res.append((name, function(name)))
|
||||
except (os.error, IOError):
|
||||
res.append((name, None))
|
||||
return res
|
||||
|
||||
def sumlist(self, list = None):
|
||||
return self._list(self.sum, list)
|
||||
|
||||
def statlist(self, list = None):
|
||||
return self._list(self.stat, list)
|
||||
|
||||
def mtimelist(self, list = None):
|
||||
return self._list(self.mtime, list)
|
||||
|
||||
def sizelist(self, list = None):
|
||||
return self._list(self.size, list)
|
||||
|
||||
def infolist(self, list = None):
|
||||
return self._list(self.info, list)
|
||||
|
||||
def _dict(self, function, list):
|
||||
if list is None:
|
||||
list = self.listfiles()
|
||||
dict = {}
|
||||
for name in list:
|
||||
try:
|
||||
dict[name] = function(name)
|
||||
except (os.error, IOError):
|
||||
pass
|
||||
return dict
|
||||
|
||||
def sumdict(self, list = None):
|
||||
return self.dict(self.sum, list)
|
||||
|
||||
def sizedict(self, list = None):
|
||||
return self.dict(self.size, list)
|
||||
|
||||
def mtimedict(self, list = None):
|
||||
return self.dict(self.mtime, list)
|
||||
|
||||
def statdict(self, list = None):
|
||||
return self.dict(self.stat, list)
|
||||
|
||||
def infodict(self, list = None):
|
||||
return self._dict(self.info, list)
|
||||
|
||||
def read(self, name, offset = 0, length = -1):
|
||||
self.checkfile(name)
|
||||
f = open(name)
|
||||
f.seek(offset)
|
||||
if length == 0:
|
||||
data = ''
|
||||
elif length < 0:
|
||||
data = f.read()
|
||||
else:
|
||||
data = f.read(length)
|
||||
f.close()
|
||||
return data
|
||||
|
||||
def create(self, name):
|
||||
self.check(name)
|
||||
if os.path.exists(name):
|
||||
self.checkfile(name)
|
||||
bname = name + '~'
|
||||
try:
|
||||
os.unlink(bname)
|
||||
except os.error:
|
||||
pass
|
||||
os.rename(name, bname)
|
||||
f = open(name, 'w')
|
||||
f.close()
|
||||
|
||||
def write(self, name, data, offset = 0):
|
||||
self.checkfile(name)
|
||||
f = open(name, 'r+')
|
||||
f.seek(offset)
|
||||
f.write(data)
|
||||
f.close()
|
||||
|
||||
def mkdir(self, name):
|
||||
self.check(name)
|
||||
os.mkdir(name, 0777)
|
||||
|
||||
def rmdir(self, name):
|
||||
self.check(name)
|
||||
os.rmdir(name)
|
||||
|
||||
|
||||
class FSProxyServer(FSProxyLocal, server.Server):
|
||||
|
||||
def __init__(self, address, verbose = server.VERBOSE):
|
||||
FSProxyLocal.__init__(self)
|
||||
server.Server.__init__(self, address, verbose)
|
||||
|
||||
def _close(self):
|
||||
server.Server._close(self)
|
||||
FSProxyLocal._close(self)
|
||||
|
||||
def _serve(self):
|
||||
server.Server._serve(self)
|
||||
# Retreat into start directory
|
||||
while self._dirstack: self.back()
|
||||
|
||||
|
||||
class FSProxyClient(client.Client):
|
||||
|
||||
def __init__(self, address, verbose = client.VERBOSE):
|
||||
client.Client.__init__(self, address, verbose)
|
||||
|
||||
|
||||
def test():
|
||||
import string
|
||||
import sys
|
||||
if sys.argv[1:]:
|
||||
port = string.atoi(sys.argv[1])
|
||||
else:
|
||||
port = 4127
|
||||
proxy = FSProxyServer(('', port))
|
||||
proxy._serverloop()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test()
|
|
@ -0,0 +1,198 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
"""RCS Proxy.
|
||||
|
||||
Provide a simplified interface on RCS files, locally or remotely.
|
||||
The functionality is geared towards implementing some sort of
|
||||
remote CVS like utility. It is modeled after the similar module
|
||||
FSProxy.
|
||||
|
||||
The module defines two classes:
|
||||
|
||||
RCSProxyLocal -- used for local access
|
||||
RCSProxyServer -- used on the server side of remote access
|
||||
|
||||
The corresponding client class, RCSProxyClient, is defined in module
|
||||
rcsclient.
|
||||
|
||||
The remote classes are instantiated with an IP address and an optional
|
||||
verbosity flag.
|
||||
"""
|
||||
|
||||
import server
|
||||
import md5
|
||||
import os
|
||||
import fnmatch
|
||||
import string
|
||||
import tempfile
|
||||
import rcslib
|
||||
|
||||
|
||||
class DirSupport:
|
||||
|
||||
def __init__(self):
|
||||
self._dirstack = []
|
||||
|
||||
def __del__(self):
|
||||
self._close()
|
||||
|
||||
def _close(self):
|
||||
while self._dirstack:
|
||||
self.back()
|
||||
|
||||
def pwd(self):
|
||||
return os.getcwd()
|
||||
|
||||
def cd(self, name):
|
||||
save = os.getcwd()
|
||||
os.chdir(name)
|
||||
self._dirstack.append(save)
|
||||
|
||||
def back(self):
|
||||
if not self._dirstack:
|
||||
raise os.error, "empty directory stack"
|
||||
dir = self._dirstack[-1]
|
||||
os.chdir(dir)
|
||||
del self._dirstack[-1]
|
||||
|
||||
def listsubdirs(self, pat = None):
|
||||
files = os.listdir(os.curdir)
|
||||
files = filter(os.path.isdir, files)
|
||||
return self._filter(files, pat)
|
||||
|
||||
def isdir(self, name):
|
||||
return os.path.isdir(name)
|
||||
|
||||
def mkdir(self, name):
|
||||
os.mkdir(name, 0777)
|
||||
|
||||
def rmdir(self, name):
|
||||
os.rmdir(name)
|
||||
|
||||
|
||||
class RCSProxyLocal(rcslib.RCS, DirSupport):
|
||||
|
||||
def __init__(self):
|
||||
rcslib.RCS.__init__(self)
|
||||
DirSupport.__init__(self)
|
||||
|
||||
def __del__(self):
|
||||
DirSupport.__del__(self)
|
||||
rcslib.RCS.__del__(self)
|
||||
|
||||
def sumlist(self, list = None):
|
||||
return self._list(self.sum, list)
|
||||
|
||||
def sumdict(self, list = None):
|
||||
return self._dict(self.sum, list)
|
||||
|
||||
def sum(self, name_rev):
|
||||
f = self._open(name_rev)
|
||||
BUFFERSIZE = 1024*8
|
||||
sum = md5.new()
|
||||
while 1:
|
||||
buffer = f.read(BUFFERSIZE)
|
||||
if not buffer:
|
||||
break
|
||||
sum.update(buffer)
|
||||
self._closepipe(f)
|
||||
return sum.digest()
|
||||
|
||||
def get(self, name_rev):
|
||||
f = self._open(name_rev)
|
||||
data = f.read()
|
||||
self._closepipe(f)
|
||||
return data
|
||||
|
||||
def put(self, name_rev, data, message=None):
|
||||
name, rev = self._unmangle(name_rev)
|
||||
f = open(name, 'w')
|
||||
f.write(data)
|
||||
f.close()
|
||||
self.checkin(name_rev, message)
|
||||
self._remove(name)
|
||||
|
||||
def _list(self, function, list = None):
|
||||
"""INTERNAL: apply FUNCTION to all files in LIST.
|
||||
|
||||
Return a list of the results.
|
||||
|
||||
The list defaults to all files in the directory if None.
|
||||
|
||||
"""
|
||||
if list is None:
|
||||
list = self.listfiles()
|
||||
res = []
|
||||
for name in list:
|
||||
try:
|
||||
res.append((name, function(name)))
|
||||
except (os.error, IOError):
|
||||
res.append((name, None))
|
||||
return res
|
||||
|
||||
def _dict(self, function, list = None):
|
||||
"""INTERNAL: apply FUNCTION to all files in LIST.
|
||||
|
||||
Return a dictionary mapping files to results.
|
||||
|
||||
The list defaults to all files in the directory if None.
|
||||
|
||||
"""
|
||||
if list is None:
|
||||
list = self.listfiles()
|
||||
dict = {}
|
||||
for name in list:
|
||||
try:
|
||||
dict[name] = function(name)
|
||||
except (os.error, IOError):
|
||||
pass
|
||||
return dict
|
||||
|
||||
|
||||
class RCSProxyServer(RCSProxyLocal, server.SecureServer):
|
||||
|
||||
def __init__(self, address, verbose = server.VERBOSE):
|
||||
RCSProxyLocal.__init__(self)
|
||||
server.SecureServer.__init__(self, address, verbose)
|
||||
|
||||
def _close(self):
|
||||
server.SecureServer._close(self)
|
||||
RCSProxyLocal._close(self)
|
||||
|
||||
def _serve(self):
|
||||
server.SecureServer._serve(self)
|
||||
# Retreat into start directory
|
||||
while self._dirstack: self.back()
|
||||
|
||||
|
||||
def test_server():
|
||||
import string
|
||||
import sys
|
||||
if sys.argv[1:]:
|
||||
port = string.atoi(sys.argv[1])
|
||||
else:
|
||||
port = 4127
|
||||
proxy = RCSProxyServer(('', port))
|
||||
proxy._serverloop()
|
||||
|
||||
|
||||
def test():
|
||||
import sys
|
||||
if not sys.argv[1:] or sys.argv[1] and sys.argv[1][0] in '0123456789':
|
||||
test_server()
|
||||
sys.exit(0)
|
||||
proxy = RCSProxyLocal()
|
||||
what = sys.argv[1]
|
||||
if hasattr(proxy, what):
|
||||
attr = getattr(proxy, what)
|
||||
if callable(attr):
|
||||
print apply(attr, tuple(sys.argv[2:]))
|
||||
else:
|
||||
print repr(attr)
|
||||
else:
|
||||
print "%s: no such attribute" % what
|
||||
sys.exit(2)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test()
|
|
@ -0,0 +1,121 @@
|
|||
Filesystem, RCS and CVS client and server classes
|
||||
=================================================
|
||||
|
||||
*** See the security warning at the end of this file! ***
|
||||
|
||||
This directory contains various modules and classes that support
|
||||
remote file system operations.
|
||||
|
||||
CVS stuff
|
||||
---------
|
||||
|
||||
rcvs Script to put in your bin directory
|
||||
rcvs.py Remote CVS client command line interface
|
||||
|
||||
cvslib.py CVS admin files classes (used by rrcs)
|
||||
cvslock.py CVS locking algorithms
|
||||
|
||||
RCS stuff
|
||||
---------
|
||||
|
||||
rrcs Script to put in your bin directory
|
||||
rrcs.py Remote RCS client command line interface
|
||||
|
||||
rcsclient.py Return an RCSProxyClient instance
|
||||
(has reasonable default server/port/directory)
|
||||
|
||||
RCSProxy.py RCS proxy and server classes (on top of rcslib.py)
|
||||
|
||||
rcslib.py Local-only RCS base class (affects stdout &
|
||||
local work files)
|
||||
|
||||
FSProxy stuff
|
||||
-------------
|
||||
|
||||
sumtree.py Old demo for FSProxy
|
||||
cmptree.py First FSProxy client (used to sync from the Mac)
|
||||
FSProxy.py Filesystem interface classes
|
||||
|
||||
Generic client/server stuff
|
||||
---------------------------
|
||||
|
||||
client.py Client class
|
||||
server.py Server class
|
||||
|
||||
security.py Security mix-in class (not very secure I think)
|
||||
|
||||
Other generic stuff
|
||||
-------------------
|
||||
|
||||
cmdfw.py CommandFrameWork class
|
||||
(used by rcvs, should be used by rrcs as well)
|
||||
|
||||
|
||||
Client/Server operation
|
||||
-----------------------
|
||||
|
||||
The Client and Server classes implement a simple-minded RPC protocol,
|
||||
using Python's pickle module to transfer arguments, return values and
|
||||
exceptions with the most generality. The Server class is instantiated
|
||||
with a port number on which it should listen for requests; the Client
|
||||
class is instantiated with a host name and a port number where it
|
||||
should connect to. Once a client is connected, a TCP connection is
|
||||
maintained between client and server.
|
||||
|
||||
The Server class currently handles only one connection at a time;
|
||||
however it could be rewritten to allow various modes of operations,
|
||||
using multiple threads or processes or the select() system call as
|
||||
desired to serve multiple clients simultaneously (when using select(),
|
||||
still handling one request at a time). This would not require
|
||||
rewriting of the Client class. It may also be possible to adapt the
|
||||
code to use UDP instead of TCP, but then both classes will have to be
|
||||
rewritten (and unless extensive acknowlegements and request serial
|
||||
numbers are used, the server should handle duplicate requests, so its
|
||||
semantics should be idempotent -- shrudder).
|
||||
|
||||
Even though the FSProxy and RCSProxy modules define client classes,
|
||||
the client class is fully generic -- what methods it supports is
|
||||
determined entirely by the server. The server class, however, must be
|
||||
derived from. This is generally done as follows:
|
||||
|
||||
from server import Server
|
||||
from client import Client
|
||||
|
||||
# Define a class that performs the operations locally
|
||||
class MyClassLocal:
|
||||
def __init__(self): ...
|
||||
def _close(self): ...
|
||||
|
||||
# Derive a server class using multiple inheritance
|
||||
class MyClassServer(MyClassLocal, Server):
|
||||
def __init__(self, address):
|
||||
# Must initialize MyClassLocal as well as Server
|
||||
MyClassLocal.__init__(self)
|
||||
Server.__init__(self, address)
|
||||
def _close(self):
|
||||
Server._close()
|
||||
MyClassLocal._close()
|
||||
|
||||
# A dummy client class
|
||||
class MyClassClient(Client): pass
|
||||
|
||||
Note that because MyClassLocal isn't used in the definition of
|
||||
MyClassClient, it would actually be better to place it in a separate
|
||||
module so the definition of MyClassLocal isn't executed when we only
|
||||
instantiate a client.
|
||||
|
||||
The modules client and server should probably be renamed to Client and
|
||||
Server in order to match the class names.
|
||||
|
||||
|
||||
*** Security warning: this version requires that you have a file
|
||||
$HOME/.python_keyfile at the server and client side containing two
|
||||
comma- separated numbers. The security system at the moment makes no
|
||||
guarantees of actuallng being secure -- however it requires that the
|
||||
key file exists and contains the same numbers at both ends for this to
|
||||
work. (You can specify an alternative keyfile in $PYTHON_KEYFILE).
|
||||
Have a look at the Security class in security.py for details;
|
||||
basically, if the key file contains (x, y), then the security server
|
||||
class chooses a random number z (the challenge) in the range
|
||||
10..100000 and the client must be able to produce pow(z, x, y)
|
||||
(i.e. z**x mod y).
|
|
@ -0,0 +1,157 @@
|
|||
"""RPC Client module."""
|
||||
|
||||
import sys
|
||||
import socket
|
||||
import pickle
|
||||
import __builtin__
|
||||
import os
|
||||
|
||||
|
||||
# Default verbosity (0 = silent, 1 = print connections, 2 = print requests too)
|
||||
VERBOSE = 1
|
||||
|
||||
|
||||
class Client:
|
||||
|
||||
"""RPC Client class. No need to derive a class -- it's fully generic."""
|
||||
|
||||
def __init__(self, address, verbose = VERBOSE):
|
||||
self._pre_init(address, verbose)
|
||||
self._post_init()
|
||||
|
||||
def _pre_init(self, address, verbose = VERBOSE):
|
||||
if type(address) == type(0):
|
||||
address = ('', address)
|
||||
self._address = address
|
||||
self._verbose = verbose
|
||||
if self._verbose: print "Connecting to %s ..." % repr(address)
|
||||
self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self._socket.connect(address)
|
||||
if self._verbose: print "Connected."
|
||||
self._lastid = 0 # Last id for which a reply has been received
|
||||
self._nextid = 1 # Id of next request
|
||||
self._replies = {} # Unprocessed replies
|
||||
self._rf = self._socket.makefile('r')
|
||||
self._wf = self._socket.makefile('w')
|
||||
|
||||
def _post_init(self):
|
||||
self._methods = self._call('.methods')
|
||||
|
||||
def __del__(self):
|
||||
self._close()
|
||||
|
||||
def _close(self):
|
||||
if self._rf: self._rf.close()
|
||||
self._rf = None
|
||||
if self._wf: self._wf.close()
|
||||
self._wf = None
|
||||
if self._socket: self._socket.close()
|
||||
self._socket = None
|
||||
|
||||
def __getattr__(self, name):
|
||||
if name in self._methods:
|
||||
method = _stub(self, name)
|
||||
setattr(self, name, method) # XXX circular reference
|
||||
return method
|
||||
raise AttributeError, name
|
||||
|
||||
def _setverbose(self, verbose):
|
||||
self._verbose = verbose
|
||||
|
||||
def _call(self, name, *args):
|
||||
return self._vcall(name, args)
|
||||
|
||||
def _vcall(self, name, args):
|
||||
return self._recv(self._vsend(name, args))
|
||||
|
||||
def _send(self, name, *args):
|
||||
return self._vsend(name, args)
|
||||
|
||||
def _send_noreply(self, name, *args):
|
||||
return self._vsend(name, args, 0)
|
||||
|
||||
def _vsend_noreply(self, name, args):
|
||||
return self._vsend(name, args, 0)
|
||||
|
||||
def _vsend(self, name, args, wantreply = 1):
|
||||
id = self._nextid
|
||||
self._nextid = id+1
|
||||
if not wantreply: id = -id
|
||||
request = (name, args, id)
|
||||
if self._verbose > 1: print "sending request: %s" % repr(request)
|
||||
wp = pickle.Pickler(self._wf)
|
||||
wp.dump(request)
|
||||
return id
|
||||
|
||||
def _recv(self, id):
|
||||
exception, value, rid = self._vrecv(id)
|
||||
if rid != id:
|
||||
raise RuntimeError, "request/reply id mismatch: %d/%d" % (id, rid)
|
||||
if exception is None:
|
||||
return value
|
||||
x = exception
|
||||
if hasattr(__builtin__, exception):
|
||||
x = getattr(__builtin__, exception)
|
||||
elif exception in ('posix.error', 'mac.error'):
|
||||
x = os.error
|
||||
if x == exception:
|
||||
exception = x
|
||||
raise exception, value
|
||||
|
||||
def _vrecv(self, id):
|
||||
self._flush()
|
||||
if self._replies.has_key(id):
|
||||
if self._verbose > 1: print "retrieving previous reply, id = %d" % id
|
||||
reply = self._replies[id]
|
||||
del self._replies[id]
|
||||
return reply
|
||||
aid = abs(id)
|
||||
while 1:
|
||||
if self._verbose > 1: print "waiting for reply, id = %d" % id
|
||||
rp = pickle.Unpickler(self._rf)
|
||||
reply = rp.load()
|
||||
del rp
|
||||
if self._verbose > 1: print "got reply: %s" % repr(reply)
|
||||
rid = reply[2]
|
||||
arid = abs(rid)
|
||||
if arid == aid:
|
||||
if self._verbose > 1: print "got it"
|
||||
return reply
|
||||
self._replies[rid] = reply
|
||||
if arid > aid:
|
||||
if self._verbose > 1: print "got higher id, assume all ok"
|
||||
return (None, None, id)
|
||||
|
||||
def _flush(self):
|
||||
self._wf.flush()
|
||||
|
||||
|
||||
from security import Security
|
||||
|
||||
|
||||
class SecureClient(Client, Security):
|
||||
|
||||
def __init__(self, *args):
|
||||
import string
|
||||
apply(self._pre_init, args)
|
||||
Security.__init__(self)
|
||||
self._wf.flush()
|
||||
line = self._rf.readline()
|
||||
challenge = string.atoi(string.strip(line))
|
||||
response = self._encode_challenge(challenge)
|
||||
line = repr(long(response))
|
||||
if line[-1] in 'Ll': line = line[:-1]
|
||||
self._wf.write(line + '\n')
|
||||
self._wf.flush()
|
||||
self._post_init()
|
||||
|
||||
class _stub:
|
||||
|
||||
"""Helper class for Client -- each instance serves as a method of the client."""
|
||||
|
||||
def __init__(self, client, name):
|
||||
self._client = client
|
||||
self._name = name
|
||||
|
||||
def __call__(self, *args):
|
||||
return self._client._vcall(self._name, args)
|
|
@ -0,0 +1,144 @@
|
|||
"Framework for command line interfaces like CVS. See class CmdFrameWork."
|
||||
|
||||
|
||||
class CommandFrameWork:
|
||||
|
||||
"""Framework class for command line interfaces like CVS.
|
||||
|
||||
The general command line structure is
|
||||
|
||||
command [flags] subcommand [subflags] [argument] ...
|
||||
|
||||
There's a class variable GlobalFlags which specifies the
|
||||
global flags options. Subcommands are defined by defining
|
||||
methods named do_<subcommand>. Flags for the subcommand are
|
||||
defined by defining class or instance variables named
|
||||
flags_<subcommand>. If there's no command, method default()
|
||||
is called. The __doc__ strings for the do_ methods are used
|
||||
for the usage message, printed after the general usage message
|
||||
which is the class variable UsageMessage. The class variable
|
||||
PostUsageMessage is printed after all the do_ methods' __doc__
|
||||
strings. The method's return value can be a suggested exit
|
||||
status. [XXX Need to rewrite this to clarify it.]
|
||||
|
||||
Common usage is to derive a class, instantiate it, and then call its
|
||||
run() method; by default this takes its arguments from sys.argv[1:].
|
||||
"""
|
||||
|
||||
UsageMessage = \
|
||||
"usage: (name)s [flags] subcommand [subflags] [argument] ..."
|
||||
|
||||
PostUsageMessage = None
|
||||
|
||||
GlobalFlags = ''
|
||||
|
||||
def __init__(self):
|
||||
"""Constructor, present for completeness."""
|
||||
pass
|
||||
|
||||
def run(self, args = None):
|
||||
"""Process flags, subcommand and options, then run it."""
|
||||
import getopt, sys
|
||||
if args is None: args = sys.argv[1:]
|
||||
try:
|
||||
opts, args = getopt.getopt(args, self.GlobalFlags)
|
||||
except getopt.error, msg:
|
||||
return self.usage(msg)
|
||||
self.options(opts)
|
||||
if not args:
|
||||
self.ready()
|
||||
return self.default()
|
||||
else:
|
||||
cmd = args[0]
|
||||
mname = 'do_' + cmd
|
||||
fname = 'flags_' + cmd
|
||||
try:
|
||||
method = getattr(self, mname)
|
||||
except AttributeError:
|
||||
return self.usage("command %r unknown" % (cmd,))
|
||||
try:
|
||||
flags = getattr(self, fname)
|
||||
except AttributeError:
|
||||
flags = ''
|
||||
try:
|
||||
opts, args = getopt.getopt(args[1:], flags)
|
||||
except getopt.error, msg:
|
||||
return self.usage(
|
||||
"subcommand %s: " % cmd + str(msg))
|
||||
self.ready()
|
||||
return method(opts, args)
|
||||
|
||||
def options(self, opts):
|
||||
"""Process the options retrieved by getopt.
|
||||
Override this if you have any options."""
|
||||
if opts:
|
||||
print "-"*40
|
||||
print "Options:"
|
||||
for o, a in opts:
|
||||
print 'option', o, 'value', repr(a)
|
||||
print "-"*40
|
||||
|
||||
def ready(self):
|
||||
"""Called just before calling the subcommand."""
|
||||
pass
|
||||
|
||||
def usage(self, msg = None):
|
||||
"""Print usage message. Return suitable exit code (2)."""
|
||||
if msg: print msg
|
||||
print self.UsageMessage % {'name': self.__class__.__name__}
|
||||
docstrings = {}
|
||||
c = self.__class__
|
||||
while 1:
|
||||
for name in dir(c):
|
||||
if name[:3] == 'do_':
|
||||
if docstrings.has_key(name):
|
||||
continue
|
||||
try:
|
||||
doc = getattr(c, name).__doc__
|
||||
except:
|
||||
doc = None
|
||||
if doc:
|
||||
docstrings[name] = doc
|
||||
if not c.__bases__:
|
||||
break
|
||||
c = c.__bases__[0]
|
||||
if docstrings:
|
||||
print "where subcommand can be:"
|
||||
names = docstrings.keys()
|
||||
names.sort()
|
||||
for name in names:
|
||||
print docstrings[name]
|
||||
if self.PostUsageMessage:
|
||||
print self.PostUsageMessage
|
||||
return 2
|
||||
|
||||
def default(self):
|
||||
"""Default method, called when no subcommand is given.
|
||||
You should always override this."""
|
||||
print "Nobody expects the Spanish Inquisition!"
|
||||
|
||||
|
||||
def test():
|
||||
"""Test script -- called when this module is run as a script."""
|
||||
import sys
|
||||
class Hello(CommandFrameWork):
|
||||
def do_hello(self, opts, args):
|
||||
"hello -- print 'hello world', needs no arguments"
|
||||
print "Hello, world"
|
||||
x = Hello()
|
||||
tests = [
|
||||
[],
|
||||
['hello'],
|
||||
['spam'],
|
||||
['-x'],
|
||||
['hello', '-x'],
|
||||
None,
|
||||
]
|
||||
for t in tests:
|
||||
print '-'*10, t, '-'*10
|
||||
sts = x.run(t)
|
||||
print "Exit status:", repr(sts)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test()
|
|
@ -0,0 +1,208 @@
|
|||
"""Compare local and remote dictionaries and transfer differing files -- like rdist."""
|
||||
|
||||
import sys
|
||||
from repr import repr
|
||||
import FSProxy
|
||||
import time
|
||||
import os
|
||||
|
||||
def main():
|
||||
pwd = os.getcwd()
|
||||
s = raw_input("chdir [%s] " % pwd)
|
||||
if s:
|
||||
os.chdir(s)
|
||||
pwd = os.getcwd()
|
||||
host = ask("host", 'voorn.cwi.nl')
|
||||
port = 4127
|
||||
verbose = 1
|
||||
mode = ''
|
||||
print """\
|
||||
Mode should be a string of characters, indicating what to do with differences.
|
||||
r - read different files to local file system
|
||||
w - write different files to remote file system
|
||||
c - create new files, either remote or local
|
||||
d - delete disappearing files, either remote or local
|
||||
"""
|
||||
s = raw_input("mode [%s] " % mode)
|
||||
if s: mode = s
|
||||
address = (host, port)
|
||||
t1 = time.time()
|
||||
local = FSProxy.FSProxyLocal()
|
||||
remote = FSProxy.FSProxyClient(address, verbose)
|
||||
compare(local, remote, mode)
|
||||
remote._close()
|
||||
local._close()
|
||||
t2 = time.time()
|
||||
dt = t2-t1
|
||||
mins, secs = divmod(dt, 60)
|
||||
print mins, "minutes and", round(secs), "seconds"
|
||||
raw_input("[Return to exit] ")
|
||||
|
||||
def ask(prompt, default):
|
||||
s = raw_input("%s [%s] " % (prompt, default))
|
||||
return s or default
|
||||
|
||||
def askint(prompt, default):
|
||||
s = raw_input("%s [%s] " % (prompt, str(default)))
|
||||
if s: return string.atoi(s)
|
||||
return default
|
||||
|
||||
def compare(local, remote, mode):
|
||||
print
|
||||
print "PWD =", repr(os.getcwd())
|
||||
sums_id = remote._send('sumlist')
|
||||
subdirs_id = remote._send('listsubdirs')
|
||||
remote._flush()
|
||||
print "calculating local sums ..."
|
||||
lsumdict = {}
|
||||
for name, info in local.sumlist():
|
||||
lsumdict[name] = info
|
||||
print "getting remote sums ..."
|
||||
sums = remote._recv(sums_id)
|
||||
print "got", len(sums)
|
||||
rsumdict = {}
|
||||
for name, rsum in sums:
|
||||
rsumdict[name] = rsum
|
||||
if not lsumdict.has_key(name):
|
||||
print repr(name), "only remote"
|
||||
if 'r' in mode and 'c' in mode:
|
||||
recvfile(local, remote, name)
|
||||
else:
|
||||
lsum = lsumdict[name]
|
||||
if lsum != rsum:
|
||||
print repr(name),
|
||||
rmtime = remote.mtime(name)
|
||||
lmtime = local.mtime(name)
|
||||
if rmtime > lmtime:
|
||||
print "remote newer",
|
||||
if 'r' in mode:
|
||||
recvfile(local, remote, name)
|
||||
elif lmtime > rmtime:
|
||||
print "local newer",
|
||||
if 'w' in mode:
|
||||
sendfile(local, remote, name)
|
||||
else:
|
||||
print "same mtime but different sum?!?!",
|
||||
print
|
||||
for name in lsumdict.keys():
|
||||
if not rsumdict.keys():
|
||||
print repr(name), "only locally",
|
||||
fl()
|
||||
if 'w' in mode and 'c' in mode:
|
||||
sendfile(local, remote, name)
|
||||
elif 'r' in mode and 'd' in mode:
|
||||
os.unlink(name)
|
||||
print "removed."
|
||||
print
|
||||
print "gettin subdirs ..."
|
||||
subdirs = remote._recv(subdirs_id)
|
||||
common = []
|
||||
for name in subdirs:
|
||||
if local.isdir(name):
|
||||
print "Common subdirectory", repr(name)
|
||||
common.append(name)
|
||||
else:
|
||||
print "Remote subdirectory", repr(name), "not found locally"
|
||||
if 'r' in mode and 'c' in mode:
|
||||
pr = "Create local subdirectory %s? [y] " % \
|
||||
repr(name)
|
||||
if 'y' in mode:
|
||||
ok = 'y'
|
||||
else:
|
||||
ok = ask(pr, "y")
|
||||
if ok[:1] in ('y', 'Y'):
|
||||
local.mkdir(name)
|
||||
print "Subdirectory %s made" % \
|
||||
repr(name)
|
||||
common.append(name)
|
||||
lsubdirs = local.listsubdirs()
|
||||
for name in lsubdirs:
|
||||
if name not in subdirs:
|
||||
print "Local subdirectory", repr(name), "not found remotely"
|
||||
for name in common:
|
||||
print "Entering subdirectory", repr(name)
|
||||
local.cd(name)
|
||||
remote.cd(name)
|
||||
compare(local, remote, mode)
|
||||
remote.back()
|
||||
local.back()
|
||||
|
||||
def sendfile(local, remote, name):
|
||||
try:
|
||||
remote.create(name)
|
||||
except (IOError, os.error), msg:
|
||||
print "cannot create:", msg
|
||||
return
|
||||
|
||||
print "sending ...",
|
||||
fl()
|
||||
|
||||
data = open(name).read()
|
||||
|
||||
t1 = time.time()
|
||||
|
||||
remote._send_noreply('write', name, data)
|
||||
remote._flush()
|
||||
|
||||
t2 = time.time()
|
||||
|
||||
dt = t2-t1
|
||||
print len(data), "bytes in", round(dt), "seconds",
|
||||
if dt:
|
||||
print "i.e.", round(len(data)/dt), "bytes/sec",
|
||||
print
|
||||
|
||||
def recvfile(local, remote, name):
|
||||
ok = 0
|
||||
try:
|
||||
rv = recvfile_real(local, remote, name)
|
||||
ok = 1
|
||||
return rv
|
||||
finally:
|
||||
if not ok:
|
||||
print "*** recvfile of %r failed, deleting" % (name,)
|
||||
local.delete(name)
|
||||
|
||||
def recvfile_real(local, remote, name):
|
||||
try:
|
||||
local.create(name)
|
||||
except (IOError, os.error), msg:
|
||||
print "cannot create:", msg
|
||||
return
|
||||
|
||||
print "receiving ...",
|
||||
fl()
|
||||
|
||||
f = open(name, 'w')
|
||||
t1 = time.time()
|
||||
|
||||
length = 4*1024
|
||||
offset = 0
|
||||
id = remote._send('read', name, offset, length)
|
||||
remote._flush()
|
||||
while 1:
|
||||
newoffset = offset + length
|
||||
newid = remote._send('read', name, newoffset, length)
|
||||
data = remote._recv(id)
|
||||
id = newid
|
||||
if not data: break
|
||||
f.seek(offset)
|
||||
f.write(data)
|
||||
offset = newoffset
|
||||
size = f.tell()
|
||||
|
||||
t2 = time.time()
|
||||
f.close()
|
||||
|
||||
dt = t2-t1
|
||||
print size, "bytes in", round(dt), "seconds",
|
||||
if dt:
|
||||
print "i.e.", size//dt, "bytes/sec",
|
||||
print
|
||||
remote._recv(id) # ignored
|
||||
|
||||
def fl():
|
||||
sys.stdout.flush()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -0,0 +1,364 @@
|
|||
"""Utilities for CVS administration."""
|
||||
|
||||
import string
|
||||
import os
|
||||
import time
|
||||
import md5
|
||||
import fnmatch
|
||||
|
||||
if not hasattr(time, 'timezone'):
|
||||
time.timezone = 0
|
||||
|
||||
class File:
|
||||
|
||||
"""Represent a file's status.
|
||||
|
||||
Instance variables:
|
||||
|
||||
file -- the filename (no slashes), None if uninitialized
|
||||
lseen -- true if the data for the local file is up to date
|
||||
eseen -- true if the data from the CVS/Entries entry is up to date
|
||||
(this implies that the entry must be written back)
|
||||
rseen -- true if the data for the remote file is up to date
|
||||
proxy -- RCSProxy instance used to contact the server, or None
|
||||
|
||||
Note that lseen and rseen don't necessary mean that a local
|
||||
or remote file *exists* -- they indicate that we've checked it.
|
||||
However, eseen means that this instance corresponds to an
|
||||
entry in the CVS/Entries file.
|
||||
|
||||
If lseen is true:
|
||||
|
||||
lsum -- checksum of the local file, None if no local file
|
||||
lctime -- ctime of the local file, None if no local file
|
||||
lmtime -- mtime of the local file, None if no local file
|
||||
|
||||
If eseen is true:
|
||||
|
||||
erev -- revision, None if this is a no revision (not '0')
|
||||
enew -- true if this is an uncommitted added file
|
||||
edeleted -- true if this is an uncommitted removed file
|
||||
ectime -- ctime of last local file corresponding to erev
|
||||
emtime -- mtime of last local file corresponding to erev
|
||||
extra -- 5th string from CVS/Entries file
|
||||
|
||||
If rseen is true:
|
||||
|
||||
rrev -- revision of head, None if non-existent
|
||||
rsum -- checksum of that revision, Non if non-existent
|
||||
|
||||
If eseen and rseen are both true:
|
||||
|
||||
esum -- checksum of revision erev, None if no revision
|
||||
|
||||
Note
|
||||
"""
|
||||
|
||||
def __init__(self, file = None):
|
||||
if file and '/' in file:
|
||||
raise ValueError, "no slash allowed in file"
|
||||
self.file = file
|
||||
self.lseen = self.eseen = self.rseen = 0
|
||||
self.proxy = None
|
||||
|
||||
def __cmp__(self, other):
|
||||
return cmp(self.file, other.file)
|
||||
|
||||
def getlocal(self):
|
||||
try:
|
||||
self.lmtime, self.lctime = os.stat(self.file)[-2:]
|
||||
except os.error:
|
||||
self.lmtime = self.lctime = self.lsum = None
|
||||
else:
|
||||
self.lsum = md5.new(open(self.file).read()).digest()
|
||||
self.lseen = 1
|
||||
|
||||
def getentry(self, line):
|
||||
words = string.splitfields(line, '/')
|
||||
if self.file and words[1] != self.file:
|
||||
raise ValueError, "file name mismatch"
|
||||
self.file = words[1]
|
||||
self.erev = words[2]
|
||||
self.edeleted = 0
|
||||
self.enew = 0
|
||||
self.ectime = self.emtime = None
|
||||
if self.erev[:1] == '-':
|
||||
self.edeleted = 1
|
||||
self.erev = self.erev[1:]
|
||||
if self.erev == '0':
|
||||
self.erev = None
|
||||
self.enew = 1
|
||||
else:
|
||||
dates = words[3]
|
||||
self.ectime = unctime(dates[:24])
|
||||
self.emtime = unctime(dates[25:])
|
||||
self.extra = words[4]
|
||||
if self.rseen:
|
||||
self.getesum()
|
||||
self.eseen = 1
|
||||
|
||||
def getremote(self, proxy = None):
|
||||
if proxy:
|
||||
self.proxy = proxy
|
||||
try:
|
||||
self.rrev = self.proxy.head(self.file)
|
||||
except (os.error, IOError):
|
||||
self.rrev = None
|
||||
if self.rrev:
|
||||
self.rsum = self.proxy.sum(self.file)
|
||||
else:
|
||||
self.rsum = None
|
||||
if self.eseen:
|
||||
self.getesum()
|
||||
self.rseen = 1
|
||||
|
||||
def getesum(self):
|
||||
if self.erev == self.rrev:
|
||||
self.esum = self.rsum
|
||||
elif self.erev:
|
||||
name = (self.file, self.erev)
|
||||
self.esum = self.proxy.sum(name)
|
||||
else:
|
||||
self.esum = None
|
||||
|
||||
def putentry(self):
|
||||
"""Return a line suitable for inclusion in CVS/Entries.
|
||||
|
||||
The returned line is terminated by a newline.
|
||||
If no entry should be written for this file,
|
||||
return "".
|
||||
"""
|
||||
if not self.eseen:
|
||||
return ""
|
||||
|
||||
rev = self.erev or '0'
|
||||
if self.edeleted:
|
||||
rev = '-' + rev
|
||||
if self.enew:
|
||||
dates = 'Initial ' + self.file
|
||||
else:
|
||||
dates = gmctime(self.ectime) + ' ' + \
|
||||
gmctime(self.emtime)
|
||||
return "/%s/%s/%s/%s/\n" % (
|
||||
self.file,
|
||||
rev,
|
||||
dates,
|
||||
self.extra)
|
||||
|
||||
def report(self):
|
||||
print '-'*50
|
||||
def r(key, repr=repr, self=self):
|
||||
try:
|
||||
value = repr(getattr(self, key))
|
||||
except AttributeError:
|
||||
value = "?"
|
||||
print "%-15s:" % key, value
|
||||
r("file")
|
||||
if self.lseen:
|
||||
r("lsum", hexify)
|
||||
r("lctime", gmctime)
|
||||
r("lmtime", gmctime)
|
||||
if self.eseen:
|
||||
r("erev")
|
||||
r("enew")
|
||||
r("edeleted")
|
||||
r("ectime", gmctime)
|
||||
r("emtime", gmctime)
|
||||
if self.rseen:
|
||||
r("rrev")
|
||||
r("rsum", hexify)
|
||||
if self.eseen:
|
||||
r("esum", hexify)
|
||||
|
||||
|
||||
class CVS:
|
||||
|
||||
"""Represent the contents of a CVS admin file (and more).
|
||||
|
||||
Class variables:
|
||||
|
||||
FileClass -- the class to be instantiated for entries
|
||||
(this should be derived from class File above)
|
||||
IgnoreList -- shell patterns for local files to be ignored
|
||||
|
||||
Instance variables:
|
||||
|
||||
entries -- a dictionary containing File instances keyed by
|
||||
their file name
|
||||
proxy -- an RCSProxy instance, or None
|
||||
"""
|
||||
|
||||
FileClass = File
|
||||
|
||||
IgnoreList = ['.*', '@*', ',*', '*~', '*.o', '*.a', '*.so', '*.pyc']
|
||||
|
||||
def __init__(self):
|
||||
self.entries = {}
|
||||
self.proxy = None
|
||||
|
||||
def setproxy(self, proxy):
|
||||
if proxy is self.proxy:
|
||||
return
|
||||
self.proxy = proxy
|
||||
for e in self.entries.values():
|
||||
e.rseen = 0
|
||||
|
||||
def getentries(self):
|
||||
"""Read the contents of CVS/Entries"""
|
||||
self.entries = {}
|
||||
f = self.cvsopen("Entries")
|
||||
while 1:
|
||||
line = f.readline()
|
||||
if not line: break
|
||||
e = self.FileClass()
|
||||
e.getentry(line)
|
||||
self.entries[e.file] = e
|
||||
f.close()
|
||||
|
||||
def putentries(self):
|
||||
"""Write CVS/Entries back"""
|
||||
f = self.cvsopen("Entries", 'w')
|
||||
for e in self.values():
|
||||
f.write(e.putentry())
|
||||
f.close()
|
||||
|
||||
def getlocalfiles(self):
|
||||
list = self.entries.keys()
|
||||
addlist = os.listdir(os.curdir)
|
||||
for name in addlist:
|
||||
if name in list:
|
||||
continue
|
||||
if not self.ignored(name):
|
||||
list.append(name)
|
||||
list.sort()
|
||||
for file in list:
|
||||
try:
|
||||
e = self.entries[file]
|
||||
except KeyError:
|
||||
e = self.entries[file] = self.FileClass(file)
|
||||
e.getlocal()
|
||||
|
||||
def getremotefiles(self, proxy = None):
|
||||
if proxy:
|
||||
self.proxy = proxy
|
||||
if not self.proxy:
|
||||
raise RuntimeError, "no RCS proxy"
|
||||
addlist = self.proxy.listfiles()
|
||||
for file in addlist:
|
||||
try:
|
||||
e = self.entries[file]
|
||||
except KeyError:
|
||||
e = self.entries[file] = self.FileClass(file)
|
||||
e.getremote(self.proxy)
|
||||
|
||||
def report(self):
|
||||
for e in self.values():
|
||||
e.report()
|
||||
print '-'*50
|
||||
|
||||
def keys(self):
|
||||
keys = self.entries.keys()
|
||||
keys.sort()
|
||||
return keys
|
||||
|
||||
def values(self):
|
||||
def value(key, self=self):
|
||||
return self.entries[key]
|
||||
return map(value, self.keys())
|
||||
|
||||
def items(self):
|
||||
def item(key, self=self):
|
||||
return (key, self.entries[key])
|
||||
return map(item, self.keys())
|
||||
|
||||
def cvsexists(self, file):
|
||||
file = os.path.join("CVS", file)
|
||||
return os.path.exists(file)
|
||||
|
||||
def cvsopen(self, file, mode = 'r'):
|
||||
file = os.path.join("CVS", file)
|
||||
if 'r' not in mode:
|
||||
self.backup(file)
|
||||
return open(file, mode)
|
||||
|
||||
def backup(self, file):
|
||||
if os.path.isfile(file):
|
||||
bfile = file + '~'
|
||||
try: os.unlink(bfile)
|
||||
except os.error: pass
|
||||
os.rename(file, bfile)
|
||||
|
||||
def ignored(self, file):
|
||||
if os.path.isdir(file): return True
|
||||
for pat in self.IgnoreList:
|
||||
if fnmatch.fnmatch(file, pat): return True
|
||||
return False
|
||||
|
||||
|
||||
# hexify and unhexify are useful to print MD5 checksums in hex format
|
||||
|
||||
hexify_format = '%02x' * 16
|
||||
def hexify(sum):
|
||||
"Return a hex representation of a 16-byte string (e.g. an MD5 digest)"
|
||||
if sum is None:
|
||||
return "None"
|
||||
return hexify_format % tuple(map(ord, sum))
|
||||
|
||||
def unhexify(hexsum):
|
||||
"Return the original from a hexified string"
|
||||
if hexsum == "None":
|
||||
return None
|
||||
sum = ''
|
||||
for i in range(0, len(hexsum), 2):
|
||||
sum = sum + chr(string.atoi(hexsum[i:i+2], 16))
|
||||
return sum
|
||||
|
||||
|
||||
unctime_monthmap = {}
|
||||
def unctime(date):
|
||||
if date == "None": return None
|
||||
if not unctime_monthmap:
|
||||
months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
|
||||
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
|
||||
i = 0
|
||||
for m in months:
|
||||
i = i+1
|
||||
unctime_monthmap[m] = i
|
||||
words = string.split(date) # Day Mon DD HH:MM:SS YEAR
|
||||
year = string.atoi(words[4])
|
||||
month = unctime_monthmap[words[1]]
|
||||
day = string.atoi(words[2])
|
||||
[hh, mm, ss] = map(string.atoi, string.splitfields(words[3], ':'))
|
||||
ss = ss - time.timezone
|
||||
return time.mktime((year, month, day, hh, mm, ss, 0, 0, 0))
|
||||
|
||||
def gmctime(t):
|
||||
if t is None: return "None"
|
||||
return time.asctime(time.gmtime(t))
|
||||
|
||||
def test_unctime():
|
||||
now = int(time.time())
|
||||
t = time.gmtime(now)
|
||||
at = time.asctime(t)
|
||||
print 'GMT', now, at
|
||||
print 'timezone', time.timezone
|
||||
print 'local', time.ctime(now)
|
||||
u = unctime(at)
|
||||
print 'unctime()', u
|
||||
gu = time.gmtime(u)
|
||||
print '->', gu
|
||||
print time.asctime(gu)
|
||||
|
||||
def test():
|
||||
x = CVS()
|
||||
x.getentries()
|
||||
x.getlocalfiles()
|
||||
## x.report()
|
||||
import rcsclient
|
||||
proxy = rcsclient.openrcsclient()
|
||||
x.getremotefiles(proxy)
|
||||
x.report()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test()
|
|
@ -0,0 +1,280 @@
|
|||
"""CVS locking algorithm.
|
||||
|
||||
CVS locking strategy
|
||||
====================
|
||||
|
||||
As reverse engineered from the CVS 1.3 sources (file lock.c):
|
||||
|
||||
- Locking is done on a per repository basis (but a process can hold
|
||||
write locks for multiple directories); all lock files are placed in
|
||||
the repository and have names beginning with "#cvs.".
|
||||
|
||||
- Before even attempting to lock, a file "#cvs.tfl.<pid>" is created
|
||||
(and removed again), to test that we can write the repository. [The
|
||||
algorithm can still be fooled (1) if the repository's mode is changed
|
||||
while attempting to lock; (2) if this file exists and is writable but
|
||||
the directory is not.]
|
||||
|
||||
- While creating the actual read/write lock files (which may exist for
|
||||
a long time), a "meta-lock" is held. The meta-lock is a directory
|
||||
named "#cvs.lock" in the repository. The meta-lock is also held while
|
||||
a write lock is held.
|
||||
|
||||
- To set a read lock:
|
||||
|
||||
- acquire the meta-lock
|
||||
- create the file "#cvs.rfl.<pid>"
|
||||
- release the meta-lock
|
||||
|
||||
- To set a write lock:
|
||||
|
||||
- acquire the meta-lock
|
||||
- check that there are no files called "#cvs.rfl.*"
|
||||
- if there are, release the meta-lock, sleep, try again
|
||||
- create the file "#cvs.wfl.<pid>"
|
||||
|
||||
- To release a write lock:
|
||||
|
||||
- remove the file "#cvs.wfl.<pid>"
|
||||
- rmdir the meta-lock
|
||||
|
||||
- To release a read lock:
|
||||
|
||||
- remove the file "#cvs.rfl.<pid>"
|
||||
|
||||
|
||||
Additional notes
|
||||
----------------
|
||||
|
||||
- A process should read-lock at most one repository at a time.
|
||||
|
||||
- A process may write-lock as many repositories as it wishes (to avoid
|
||||
deadlocks, I presume it should always lock them top-down in the
|
||||
directory hierarchy).
|
||||
|
||||
- A process should make sure it removes all its lock files and
|
||||
directories when it crashes.
|
||||
|
||||
- Limitation: one user id should not be committing files into the same
|
||||
repository at the same time.
|
||||
|
||||
|
||||
Turn this into Python code
|
||||
--------------------------
|
||||
|
||||
rl = ReadLock(repository, waittime)
|
||||
|
||||
wl = WriteLock(repository, waittime)
|
||||
|
||||
list = MultipleWriteLock([repository1, repository2, ...], waittime)
|
||||
|
||||
"""
|
||||
|
||||
|
||||
import os
|
||||
import time
|
||||
import stat
|
||||
import pwd
|
||||
|
||||
|
||||
# Default wait time
|
||||
DELAY = 10
|
||||
|
||||
|
||||
# XXX This should be the same on all Unix versions
|
||||
EEXIST = 17
|
||||
|
||||
|
||||
# Files used for locking (must match cvs.h in the CVS sources)
|
||||
CVSLCK = "#cvs.lck"
|
||||
CVSRFL = "#cvs.rfl."
|
||||
CVSWFL = "#cvs.wfl."
|
||||
|
||||
|
||||
class Error:
|
||||
|
||||
def __init__(self, msg):
|
||||
self.msg = msg
|
||||
|
||||
def __repr__(self):
|
||||
return repr(self.msg)
|
||||
|
||||
def __str__(self):
|
||||
return str(self.msg)
|
||||
|
||||
|
||||
class Locked(Error):
|
||||
pass
|
||||
|
||||
|
||||
class Lock:
|
||||
|
||||
def __init__(self, repository = ".", delay = DELAY):
|
||||
self.repository = repository
|
||||
self.delay = delay
|
||||
self.lockdir = None
|
||||
self.lockfile = None
|
||||
pid = repr(os.getpid())
|
||||
self.cvslck = self.join(CVSLCK)
|
||||
self.cvsrfl = self.join(CVSRFL + pid)
|
||||
self.cvswfl = self.join(CVSWFL + pid)
|
||||
|
||||
def __del__(self):
|
||||
print "__del__"
|
||||
self.unlock()
|
||||
|
||||
def setlockdir(self):
|
||||
while 1:
|
||||
try:
|
||||
self.lockdir = self.cvslck
|
||||
os.mkdir(self.cvslck, 0777)
|
||||
return
|
||||
except os.error, msg:
|
||||
self.lockdir = None
|
||||
if msg[0] == EEXIST:
|
||||
try:
|
||||
st = os.stat(self.cvslck)
|
||||
except os.error:
|
||||
continue
|
||||
self.sleep(st)
|
||||
continue
|
||||
raise Error("failed to lock %s: %s" % (
|
||||
self.repository, msg))
|
||||
|
||||
def unlock(self):
|
||||
self.unlockfile()
|
||||
self.unlockdir()
|
||||
|
||||
def unlockfile(self):
|
||||
if self.lockfile:
|
||||
print "unlink", self.lockfile
|
||||
try:
|
||||
os.unlink(self.lockfile)
|
||||
except os.error:
|
||||
pass
|
||||
self.lockfile = None
|
||||
|
||||
def unlockdir(self):
|
||||
if self.lockdir:
|
||||
print "rmdir", self.lockdir
|
||||
try:
|
||||
os.rmdir(self.lockdir)
|
||||
except os.error:
|
||||
pass
|
||||
self.lockdir = None
|
||||
|
||||
def sleep(self, st):
|
||||
sleep(st, self.repository, self.delay)
|
||||
|
||||
def join(self, name):
|
||||
return os.path.join(self.repository, name)
|
||||
|
||||
|
||||
def sleep(st, repository, delay):
|
||||
if delay <= 0:
|
||||
raise Locked(st)
|
||||
uid = st[stat.ST_UID]
|
||||
try:
|
||||
pwent = pwd.getpwuid(uid)
|
||||
user = pwent[0]
|
||||
except KeyError:
|
||||
user = "uid %d" % uid
|
||||
print "[%s]" % time.ctime(time.time())[11:19],
|
||||
print "Waiting for %s's lock in" % user, repository
|
||||
time.sleep(delay)
|
||||
|
||||
|
||||
class ReadLock(Lock):
|
||||
|
||||
def __init__(self, repository, delay = DELAY):
|
||||
Lock.__init__(self, repository, delay)
|
||||
ok = 0
|
||||
try:
|
||||
self.setlockdir()
|
||||
self.lockfile = self.cvsrfl
|
||||
fp = open(self.lockfile, 'w')
|
||||
fp.close()
|
||||
ok = 1
|
||||
finally:
|
||||
if not ok:
|
||||
self.unlockfile()
|
||||
self.unlockdir()
|
||||
|
||||
|
||||
class WriteLock(Lock):
|
||||
|
||||
def __init__(self, repository, delay = DELAY):
|
||||
Lock.__init__(self, repository, delay)
|
||||
self.setlockdir()
|
||||
while 1:
|
||||
uid = self.readers_exist()
|
||||
if not uid:
|
||||
break
|
||||
self.unlockdir()
|
||||
self.sleep(uid)
|
||||
self.lockfile = self.cvswfl
|
||||
fp = open(self.lockfile, 'w')
|
||||
fp.close()
|
||||
|
||||
def readers_exist(self):
|
||||
n = len(CVSRFL)
|
||||
for name in os.listdir(self.repository):
|
||||
if name[:n] == CVSRFL:
|
||||
try:
|
||||
st = os.stat(self.join(name))
|
||||
except os.error:
|
||||
continue
|
||||
return st
|
||||
return None
|
||||
|
||||
|
||||
def MultipleWriteLock(repositories, delay = DELAY):
|
||||
while 1:
|
||||
locks = []
|
||||
for r in repositories:
|
||||
try:
|
||||
locks.append(WriteLock(r, 0))
|
||||
except Locked, instance:
|
||||
del locks
|
||||
break
|
||||
else:
|
||||
break
|
||||
sleep(instance.msg, r, delay)
|
||||
return list
|
||||
|
||||
|
||||
def test():
|
||||
import sys
|
||||
if sys.argv[1:]:
|
||||
repository = sys.argv[1]
|
||||
else:
|
||||
repository = "."
|
||||
rl = None
|
||||
wl = None
|
||||
try:
|
||||
print "attempting write lock ..."
|
||||
wl = WriteLock(repository)
|
||||
print "got it."
|
||||
wl.unlock()
|
||||
print "attempting read lock ..."
|
||||
rl = ReadLock(repository)
|
||||
print "got it."
|
||||
rl.unlock()
|
||||
finally:
|
||||
print [1]
|
||||
sys.exc_traceback = None
|
||||
print [2]
|
||||
if rl:
|
||||
rl.unlock()
|
||||
print [3]
|
||||
if wl:
|
||||
wl.unlock()
|
||||
print [4]
|
||||
rl = None
|
||||
print [5]
|
||||
wl = None
|
||||
print [6]
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test()
|
|
@ -0,0 +1,19 @@
|
|||
import sys
|
||||
import string
|
||||
import rcvs
|
||||
|
||||
def main():
|
||||
while 1:
|
||||
try:
|
||||
line = raw_input('$ ')
|
||||
except EOFError:
|
||||
break
|
||||
words = string.split(line)
|
||||
if not words:
|
||||
continue
|
||||
if words[0] != 'rcvs':
|
||||
words.insert(0, 'rcvs')
|
||||
sys.argv = words
|
||||
rcvs.main()
|
||||
|
||||
main()
|
|
@ -0,0 +1,109 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
"""Turn a pile of RCS log output into ChangeLog file entries.
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
import string
|
||||
import re
|
||||
import getopt
|
||||
import time
|
||||
|
||||
def main():
|
||||
args = sys.argv[1:]
|
||||
opts, args = getopt.getopt(args, 'p:')
|
||||
prefix = ''
|
||||
for o, a in opts:
|
||||
if p == '-p': prefix = a
|
||||
|
||||
f = sys.stdin
|
||||
allrevs = []
|
||||
while 1:
|
||||
file = getnextfile(f)
|
||||
if not file: break
|
||||
revs = []
|
||||
while 1:
|
||||
rev = getnextrev(f, file)
|
||||
if not rev:
|
||||
break
|
||||
revs.append(rev)
|
||||
if revs:
|
||||
allrevs[len(allrevs):] = revs
|
||||
allrevs.sort()
|
||||
allrevs.reverse()
|
||||
for rev in allrevs:
|
||||
formatrev(rev, prefix)
|
||||
|
||||
parsedateprog = re.compile(
|
||||
'^date: ([0-9]+)/([0-9]+)/([0-9]+) ' +
|
||||
'([0-9]+):([0-9]+):([0-9]+); author: ([^ ;]+)')
|
||||
|
||||
authormap = {
|
||||
'guido': 'Guido van Rossum <guido@cnri.reston.va.us>',
|
||||
'jack': 'Jack Jansen <jack@cwi.nl>',
|
||||
'sjoerd': 'Sjoerd Mullender <sjoerd@cwi.nl>',
|
||||
}
|
||||
|
||||
def formatrev(rev, prefix):
|
||||
dateline, file, revline, log = rev
|
||||
if parsedateprog.match(dateline) >= 0:
|
||||
fields = parsedateprog.group(1, 2, 3, 4, 5, 6)
|
||||
author = parsedateprog.group(7)
|
||||
if authormap.has_key(author): author = authormap[author]
|
||||
tfields = map(string.atoi, fields) + [0, 0, 0]
|
||||
tfields[5] = tfields[5] - time.timezone
|
||||
t = time.mktime(tuple(tfields))
|
||||
print time.ctime(t), '', author
|
||||
words = string.split(log)
|
||||
words[:0] = ['*', prefix + file + ':']
|
||||
maxcol = 72-8
|
||||
col = maxcol
|
||||
for word in words:
|
||||
if col > 0 and col + len(word) >= maxcol:
|
||||
print
|
||||
print '\t' + word,
|
||||
col = -1
|
||||
else:
|
||||
print word,
|
||||
col = col + 1 + len(word)
|
||||
print
|
||||
print
|
||||
|
||||
startprog = re.compile("^Working file: (.*)$")
|
||||
|
||||
def getnextfile(f):
|
||||
while 1:
|
||||
line = f.readline()
|
||||
if not line: return None
|
||||
if startprog.match(line) >= 0:
|
||||
file = startprog.group(1)
|
||||
# Skip until first revision
|
||||
while 1:
|
||||
line = f.readline()
|
||||
if not line: return None
|
||||
if line[:10] == '='*10: return None
|
||||
if line[:10] == '-'*10: break
|
||||
## print "Skipped", line,
|
||||
return file
|
||||
## else:
|
||||
## print "Ignored", line,
|
||||
|
||||
def getnextrev(f, file):
|
||||
# This is called when we are positioned just after a '---' separator
|
||||
revline = f.readline()
|
||||
dateline = f.readline()
|
||||
log = ''
|
||||
while 1:
|
||||
line = f.readline()
|
||||
if not line: break
|
||||
if line[:10] == '='*10:
|
||||
# Ignore the *last* log entry for each file since it
|
||||
# is the revision since which we are logging.
|
||||
return None
|
||||
if line[:10] == '-'*10: break
|
||||
log = log + line
|
||||
return dateline, file, revline, log
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -0,0 +1,33 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- python -*-
|
||||
#
|
||||
# guido's version, from rcsbump,v 1.2 1995/06/22 21:27:27 bwarsaw Exp
|
||||
#
|
||||
# Python script for bumping up an RCS major revision number.
|
||||
|
||||
import sys
|
||||
import re
|
||||
import rcslib
|
||||
import string
|
||||
|
||||
WITHLOCK = 1
|
||||
majorrev_re = re.compile('^[0-9]+')
|
||||
|
||||
dir = rcslib.RCS()
|
||||
|
||||
if sys.argv[1:]:
|
||||
files = sys.argv[1:]
|
||||
else:
|
||||
files = dir.listfiles()
|
||||
|
||||
for file in files:
|
||||
# get the major revnumber of the file
|
||||
headbranch = dir.info(file)['head']
|
||||
majorrev_re.match(headbranch)
|
||||
majorrev = string.atoi(majorrev_re.group(0)) + 1
|
||||
|
||||
if not dir.islocked(file):
|
||||
dir.checkout(file, WITHLOCK)
|
||||
|
||||
msg = "Bumping major revision number (to %d)" % majorrev
|
||||
dir.checkin((file, "%s.0" % majorrev), msg, "-f")
|
|
@ -0,0 +1,71 @@
|
|||
"""Customize this file to change the default client etc.
|
||||
|
||||
(In general, it is probably be better to make local operation the
|
||||
default and to require something like an RCSSERVER environment
|
||||
variable to enable remote operation.)
|
||||
|
||||
"""
|
||||
|
||||
import string
|
||||
import os
|
||||
|
||||
# These defaults don't belong here -- they should be taken from the
|
||||
# environment or from a hidden file in the current directory
|
||||
|
||||
HOST = 'voorn.cwi.nl'
|
||||
PORT = 4127
|
||||
VERBOSE = 1
|
||||
LOCAL = 0
|
||||
|
||||
import client
|
||||
|
||||
|
||||
class RCSProxyClient(client.SecureClient):
|
||||
|
||||
def __init__(self, address, verbose = client.VERBOSE):
|
||||
client.SecureClient.__init__(self, address, verbose)
|
||||
|
||||
|
||||
def openrcsclient(opts = []):
|
||||
"open an RCSProxy client based on a list of options returned by getopt"
|
||||
import RCSProxy
|
||||
host = HOST
|
||||
port = PORT
|
||||
verbose = VERBOSE
|
||||
local = LOCAL
|
||||
directory = None
|
||||
for o, a in opts:
|
||||
if o == '-h':
|
||||
host = a
|
||||
if ':' in host:
|
||||
i = string.find(host, ':')
|
||||
host, p = host[:i], host[i+1:]
|
||||
if p:
|
||||
port = string.atoi(p)
|
||||
if o == '-p':
|
||||
port = string.atoi(a)
|
||||
if o == '-d':
|
||||
directory = a
|
||||
if o == '-v':
|
||||
verbose = verbose + 1
|
||||
if o == '-q':
|
||||
verbose = 0
|
||||
if o == '-L':
|
||||
local = 1
|
||||
if local:
|
||||
import RCSProxy
|
||||
x = RCSProxy.RCSProxyLocal()
|
||||
else:
|
||||
address = (host, port)
|
||||
x = RCSProxyClient(address, verbose)
|
||||
if not directory:
|
||||
try:
|
||||
directory = open(os.path.join("CVS", "Repository")).readline()
|
||||
except IOError:
|
||||
pass
|
||||
else:
|
||||
if directory[-1] == '\n':
|
||||
directory = directory[:-1]
|
||||
if directory:
|
||||
x.cd(directory)
|
||||
return x
|
|
@ -0,0 +1,334 @@
|
|||
"""RCS interface module.
|
||||
|
||||
Defines the class RCS, which represents a directory with rcs version
|
||||
files and (possibly) corresponding work files.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
import fnmatch
|
||||
import os
|
||||
import re
|
||||
import string
|
||||
import tempfile
|
||||
|
||||
|
||||
class RCS:
|
||||
|
||||
"""RCS interface class (local filesystem version).
|
||||
|
||||
An instance of this class represents a directory with rcs version
|
||||
files and (possible) corresponding work files.
|
||||
|
||||
Methods provide access to most rcs operations such as
|
||||
checkin/checkout, access to the rcs metadata (revisions, logs,
|
||||
branches etc.) as well as some filesystem operations such as
|
||||
listing all rcs version files.
|
||||
|
||||
XXX BUGS / PROBLEMS
|
||||
|
||||
- The instance always represents the current directory so it's not
|
||||
very useful to have more than one instance around simultaneously
|
||||
|
||||
"""
|
||||
|
||||
# Characters allowed in work file names
|
||||
okchars = string.ascii_letters + string.digits + '-_=+'
|
||||
|
||||
def __init__(self):
|
||||
"""Constructor."""
|
||||
pass
|
||||
|
||||
def __del__(self):
|
||||
"""Destructor."""
|
||||
pass
|
||||
|
||||
# --- Informational methods about a single file/revision ---
|
||||
|
||||
def log(self, name_rev, otherflags = ''):
|
||||
"""Return the full log text for NAME_REV as a string.
|
||||
|
||||
Optional OTHERFLAGS are passed to rlog.
|
||||
|
||||
"""
|
||||
f = self._open(name_rev, 'rlog ' + otherflags)
|
||||
data = f.read()
|
||||
status = self._closepipe(f)
|
||||
if status:
|
||||
data = data + "%s: %s" % status
|
||||
elif data[-1] == '\n':
|
||||
data = data[:-1]
|
||||
return data
|
||||
|
||||
def head(self, name_rev):
|
||||
"""Return the head revision for NAME_REV"""
|
||||
dict = self.info(name_rev)
|
||||
return dict['head']
|
||||
|
||||
def info(self, name_rev):
|
||||
"""Return a dictionary of info (from rlog -h) for NAME_REV
|
||||
|
||||
The dictionary's keys are the keywords that rlog prints
|
||||
(e.g. 'head' and its values are the corresponding data
|
||||
(e.g. '1.3').
|
||||
|
||||
XXX symbolic names and locks are not returned
|
||||
|
||||
"""
|
||||
f = self._open(name_rev, 'rlog -h')
|
||||
dict = {}
|
||||
while 1:
|
||||
line = f.readline()
|
||||
if not line: break
|
||||
if line[0] == '\t':
|
||||
# XXX could be a lock or symbolic name
|
||||
# Anything else?
|
||||
continue
|
||||
i = string.find(line, ':')
|
||||
if i > 0:
|
||||
key, value = line[:i], string.strip(line[i+1:])
|
||||
dict[key] = value
|
||||
status = self._closepipe(f)
|
||||
if status:
|
||||
raise IOError, status
|
||||
return dict
|
||||
|
||||
# --- Methods that change files ---
|
||||
|
||||
def lock(self, name_rev):
|
||||
"""Set an rcs lock on NAME_REV."""
|
||||
name, rev = self.checkfile(name_rev)
|
||||
cmd = "rcs -l%s %s" % (rev, name)
|
||||
return self._system(cmd)
|
||||
|
||||
def unlock(self, name_rev):
|
||||
"""Clear an rcs lock on NAME_REV."""
|
||||
name, rev = self.checkfile(name_rev)
|
||||
cmd = "rcs -u%s %s" % (rev, name)
|
||||
return self._system(cmd)
|
||||
|
||||
def checkout(self, name_rev, withlock=0, otherflags=""):
|
||||
"""Check out NAME_REV to its work file.
|
||||
|
||||
If optional WITHLOCK is set, check out locked, else unlocked.
|
||||
|
||||
The optional OTHERFLAGS is passed to co without
|
||||
interpretation.
|
||||
|
||||
Any output from co goes to directly to stdout.
|
||||
|
||||
"""
|
||||
name, rev = self.checkfile(name_rev)
|
||||
if withlock: lockflag = "-l"
|
||||
else: lockflag = "-u"
|
||||
cmd = 'co %s%s %s %s' % (lockflag, rev, otherflags, name)
|
||||
return self._system(cmd)
|
||||
|
||||
def checkin(self, name_rev, message=None, otherflags=""):
|
||||
"""Check in NAME_REV from its work file.
|
||||
|
||||
The optional MESSAGE argument becomes the checkin message
|
||||
(default "<none>" if None); or the file description if this is
|
||||
a new file.
|
||||
|
||||
The optional OTHERFLAGS argument is passed to ci without
|
||||
interpretation.
|
||||
|
||||
Any output from ci goes to directly to stdout.
|
||||
|
||||
"""
|
||||
name, rev = self._unmangle(name_rev)
|
||||
new = not self.isvalid(name)
|
||||
if not message: message = "<none>"
|
||||
if message and message[-1] != '\n':
|
||||
message = message + '\n'
|
||||
lockflag = "-u"
|
||||
if new:
|
||||
f = tempfile.NamedTemporaryFile()
|
||||
f.write(message)
|
||||
f.flush()
|
||||
cmd = 'ci %s%s -t%s %s %s' % \
|
||||
(lockflag, rev, f.name, otherflags, name)
|
||||
else:
|
||||
message = re.sub(r'([\"$`])', r'\\\1', message)
|
||||
cmd = 'ci %s%s -m"%s" %s %s' % \
|
||||
(lockflag, rev, message, otherflags, name)
|
||||
return self._system(cmd)
|
||||
|
||||
# --- Exported support methods ---
|
||||
|
||||
def listfiles(self, pat = None):
|
||||
"""Return a list of all version files matching optional PATTERN."""
|
||||
files = os.listdir(os.curdir)
|
||||
files = filter(self._isrcs, files)
|
||||
if os.path.isdir('RCS'):
|
||||
files2 = os.listdir('RCS')
|
||||
files2 = filter(self._isrcs, files2)
|
||||
files = files + files2
|
||||
files = map(self.realname, files)
|
||||
return self._filter(files, pat)
|
||||
|
||||
def isvalid(self, name):
|
||||
"""Test whether NAME has a version file associated."""
|
||||
namev = self.rcsname(name)
|
||||
return (os.path.isfile(namev) or
|
||||
os.path.isfile(os.path.join('RCS', namev)))
|
||||
|
||||
def rcsname(self, name):
|
||||
"""Return the pathname of the version file for NAME.
|
||||
|
||||
The argument can be a work file name or a version file name.
|
||||
If the version file does not exist, the name of the version
|
||||
file that would be created by "ci" is returned.
|
||||
|
||||
"""
|
||||
if self._isrcs(name): namev = name
|
||||
else: namev = name + ',v'
|
||||
if os.path.isfile(namev): return namev
|
||||
namev = os.path.join('RCS', os.path.basename(namev))
|
||||
if os.path.isfile(namev): return namev
|
||||
if os.path.isdir('RCS'):
|
||||
return os.path.join('RCS', namev)
|
||||
else:
|
||||
return namev
|
||||
|
||||
def realname(self, namev):
|
||||
"""Return the pathname of the work file for NAME.
|
||||
|
||||
The argument can be a work file name or a version file name.
|
||||
If the work file does not exist, the name of the work file
|
||||
that would be created by "co" is returned.
|
||||
|
||||
"""
|
||||
if self._isrcs(namev): name = namev[:-2]
|
||||
else: name = namev
|
||||
if os.path.isfile(name): return name
|
||||
name = os.path.basename(name)
|
||||
return name
|
||||
|
||||
def islocked(self, name_rev):
|
||||
"""Test whether FILE (which must have a version file) is locked.
|
||||
|
||||
XXX This does not tell you which revision number is locked and
|
||||
ignores any revision you may pass in (by virtue of using rlog
|
||||
-L -R).
|
||||
|
||||
"""
|
||||
f = self._open(name_rev, 'rlog -L -R')
|
||||
line = f.readline()
|
||||
status = self._closepipe(f)
|
||||
if status:
|
||||
raise IOError, status
|
||||
if not line: return None
|
||||
if line[-1] == '\n':
|
||||
line = line[:-1]
|
||||
return self.realname(name_rev) == self.realname(line)
|
||||
|
||||
def checkfile(self, name_rev):
|
||||
"""Normalize NAME_REV into a (NAME, REV) tuple.
|
||||
|
||||
Raise an exception if there is no corresponding version file.
|
||||
|
||||
"""
|
||||
name, rev = self._unmangle(name_rev)
|
||||
if not self.isvalid(name):
|
||||
raise os.error, 'not an rcs file %r' % (name,)
|
||||
return name, rev
|
||||
|
||||
# --- Internal methods ---
|
||||
|
||||
def _open(self, name_rev, cmd = 'co -p', rflag = '-r'):
|
||||
"""INTERNAL: open a read pipe to NAME_REV using optional COMMAND.
|
||||
|
||||
Optional FLAG is used to indicate the revision (default -r).
|
||||
|
||||
Default COMMAND is "co -p".
|
||||
|
||||
Return a file object connected by a pipe to the command's
|
||||
output.
|
||||
|
||||
"""
|
||||
name, rev = self.checkfile(name_rev)
|
||||
namev = self.rcsname(name)
|
||||
if rev:
|
||||
cmd = cmd + ' ' + rflag + rev
|
||||
return os.popen("%s %r" % (cmd, namev))
|
||||
|
||||
def _unmangle(self, name_rev):
|
||||
"""INTERNAL: Normalize NAME_REV argument to (NAME, REV) tuple.
|
||||
|
||||
Raise an exception if NAME contains invalid characters.
|
||||
|
||||
A NAME_REV argument is either NAME string (implying REV='') or
|
||||
a tuple of the form (NAME, REV).
|
||||
|
||||
"""
|
||||
if type(name_rev) == type(''):
|
||||
name_rev = name, rev = name_rev, ''
|
||||
else:
|
||||
name, rev = name_rev
|
||||
for c in rev:
|
||||
if c not in self.okchars:
|
||||
raise ValueError, "bad char in rev"
|
||||
return name_rev
|
||||
|
||||
def _closepipe(self, f):
|
||||
"""INTERNAL: Close PIPE and print its exit status if nonzero."""
|
||||
sts = f.close()
|
||||
if not sts: return None
|
||||
detail, reason = divmod(sts, 256)
|
||||
if reason == 0: return 'exit', detail # Exit status
|
||||
signal = reason&0x7F
|
||||
if signal == 0x7F:
|
||||
code = 'stopped'
|
||||
signal = detail
|
||||
else:
|
||||
code = 'killed'
|
||||
if reason&0x80:
|
||||
code = code + '(coredump)'
|
||||
return code, signal
|
||||
|
||||
def _system(self, cmd):
|
||||
"""INTERNAL: run COMMAND in a subshell.
|
||||
|
||||
Standard input for the command is taken from /dev/null.
|
||||
|
||||
Raise IOError when the exit status is not zero.
|
||||
|
||||
Return whatever the calling method should return; normally
|
||||
None.
|
||||
|
||||
A derived class may override this method and redefine it to
|
||||
capture stdout/stderr of the command and return it.
|
||||
|
||||
"""
|
||||
cmd = cmd + " </dev/null"
|
||||
sts = os.system(cmd)
|
||||
if sts: raise IOError, "command exit status %d" % sts
|
||||
|
||||
def _filter(self, files, pat = None):
|
||||
"""INTERNAL: Return a sorted copy of the given list of FILES.
|
||||
|
||||
If a second PATTERN argument is given, only files matching it
|
||||
are kept. No check for valid filenames is made.
|
||||
|
||||
"""
|
||||
if pat:
|
||||
def keep(name, pat = pat):
|
||||
return fnmatch.fnmatch(name, pat)
|
||||
files = filter(keep, files)
|
||||
else:
|
||||
files = files[:]
|
||||
files.sort()
|
||||
return files
|
||||
|
||||
def _remove(self, fn):
|
||||
"""INTERNAL: remove FILE without complaints."""
|
||||
try:
|
||||
os.unlink(fn)
|
||||
except os.error:
|
||||
pass
|
||||
|
||||
def _isrcs(self, name):
|
||||
"""INTERNAL: Test whether NAME ends in ',v'."""
|
||||
return name[-2:] == ',v'
|
|
@ -0,0 +1,8 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import addpack
|
||||
addpack.addpack('/home/guido/src/python/Demo/pdist')
|
||||
|
||||
import rcvs
|
||||
|
||||
rcvs.main()
|
|
@ -0,0 +1,477 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
"""Remote CVS -- command line interface"""
|
||||
|
||||
# XXX To do:
|
||||
#
|
||||
# Bugs:
|
||||
# - if the remote file is deleted, "rcvs update" will fail
|
||||
#
|
||||
# Functionality:
|
||||
# - cvs rm
|
||||
# - descend into directories (alraedy done for update)
|
||||
# - conflict resolution
|
||||
# - other relevant commands?
|
||||
# - branches
|
||||
#
|
||||
# - Finesses:
|
||||
# - retain file mode's x bits
|
||||
# - complain when "nothing known about filename"
|
||||
# - edit log message the way CVS lets you edit it
|
||||
# - cvs diff -rREVA -rREVB
|
||||
# - send mail the way CVS sends it
|
||||
#
|
||||
# Performance:
|
||||
# - cache remote checksums (for every revision ever seen!)
|
||||
# - translate symbolic revisions to numeric revisions
|
||||
#
|
||||
# Reliability:
|
||||
# - remote locking
|
||||
#
|
||||
# Security:
|
||||
# - Authenticated RPC?
|
||||
|
||||
|
||||
from cvslib import CVS, File
|
||||
import md5
|
||||
import os
|
||||
import string
|
||||
import sys
|
||||
from cmdfw import CommandFrameWork
|
||||
|
||||
|
||||
DEF_LOCAL = 1 # Default -l
|
||||
|
||||
|
||||
class MyFile(File):
|
||||
|
||||
def action(self):
|
||||
"""Return a code indicating the update status of this file.
|
||||
|
||||
The possible return values are:
|
||||
|
||||
'=' -- everything's fine
|
||||
'0' -- file doesn't exist anywhere
|
||||
'?' -- exists locally only
|
||||
'A' -- new locally
|
||||
'R' -- deleted locally
|
||||
'U' -- changed remotely, no changes locally
|
||||
(includes new remotely or deleted remotely)
|
||||
'M' -- changed locally, no changes remotely
|
||||
'C' -- conflict: changed locally as well as remotely
|
||||
(includes cases where the file has been added
|
||||
or removed locally and remotely)
|
||||
'D' -- deleted remotely
|
||||
'N' -- new remotely
|
||||
'r' -- get rid of entry
|
||||
'c' -- create entry
|
||||
'u' -- update entry
|
||||
|
||||
(and probably others :-)
|
||||
"""
|
||||
if not self.lseen:
|
||||
self.getlocal()
|
||||
if not self.rseen:
|
||||
self.getremote()
|
||||
if not self.eseen:
|
||||
if not self.lsum:
|
||||
if not self.rsum: return '0' # Never heard of
|
||||
else:
|
||||
return 'N' # New remotely
|
||||
else: # self.lsum
|
||||
if not self.rsum: return '?' # Local only
|
||||
# Local and remote, but no entry
|
||||
if self.lsum == self.rsum:
|
||||
return 'c' # Restore entry only
|
||||
else: return 'C' # Real conflict
|
||||
else: # self.eseen
|
||||
if not self.lsum:
|
||||
if self.edeleted:
|
||||
if self.rsum: return 'R' # Removed
|
||||
else: return 'r' # Get rid of entry
|
||||
else: # not self.edeleted
|
||||
if self.rsum:
|
||||
print "warning:",
|
||||
print self.file,
|
||||
print "was lost"
|
||||
return 'U'
|
||||
else: return 'r' # Get rid of entry
|
||||
else: # self.lsum
|
||||
if not self.rsum:
|
||||
if self.enew: return 'A' # New locally
|
||||
else: return 'D' # Deleted remotely
|
||||
else: # self.rsum
|
||||
if self.enew:
|
||||
if self.lsum == self.rsum:
|
||||
return 'u'
|
||||
else:
|
||||
return 'C'
|
||||
if self.lsum == self.esum:
|
||||
if self.esum == self.rsum:
|
||||
return '='
|
||||
else:
|
||||
return 'U'
|
||||
elif self.esum == self.rsum:
|
||||
return 'M'
|
||||
elif self.lsum == self.rsum:
|
||||
return 'u'
|
||||
else:
|
||||
return 'C'
|
||||
|
||||
def update(self):
|
||||
code = self.action()
|
||||
if code == '=': return
|
||||
print code, self.file
|
||||
if code in ('U', 'N'):
|
||||
self.get()
|
||||
elif code == 'C':
|
||||
print "%s: conflict resolution not yet implemented" % \
|
||||
self.file
|
||||
elif code == 'D':
|
||||
remove(self.file)
|
||||
self.eseen = 0
|
||||
elif code == 'r':
|
||||
self.eseen = 0
|
||||
elif code in ('c', 'u'):
|
||||
self.eseen = 1
|
||||
self.erev = self.rrev
|
||||
self.enew = 0
|
||||
self.edeleted = 0
|
||||
self.esum = self.rsum
|
||||
self.emtime, self.ectime = os.stat(self.file)[-2:]
|
||||
self.extra = ''
|
||||
|
||||
def commit(self, message = ""):
|
||||
code = self.action()
|
||||
if code in ('A', 'M'):
|
||||
self.put(message)
|
||||
return 1
|
||||
elif code == 'R':
|
||||
print "%s: committing removes not yet implemented" % \
|
||||
self.file
|
||||
elif code == 'C':
|
||||
print "%s: conflict resolution not yet implemented" % \
|
||||
self.file
|
||||
|
||||
def diff(self, opts = []):
|
||||
self.action() # To update lseen, rseen
|
||||
flags = ''
|
||||
rev = self.rrev
|
||||
# XXX should support two rev options too!
|
||||
for o, a in opts:
|
||||
if o == '-r':
|
||||
rev = a
|
||||
else:
|
||||
flags = flags + ' ' + o + a
|
||||
if rev == self.rrev and self.lsum == self.rsum:
|
||||
return
|
||||
flags = flags[1:]
|
||||
fn = self.file
|
||||
data = self.proxy.get((fn, rev))
|
||||
sum = md5.new(data).digest()
|
||||
if self.lsum == sum:
|
||||
return
|
||||
import tempfile
|
||||
tf = tempfile.NamedTemporaryFile()
|
||||
tf.write(data)
|
||||
tf.flush()
|
||||
print 'diff %s -r%s %s' % (flags, rev, fn)
|
||||
sts = os.system('diff %s %s %s' % (flags, tf.name, fn))
|
||||
if sts:
|
||||
print '='*70
|
||||
|
||||
def commitcheck(self):
|
||||
return self.action() != 'C'
|
||||
|
||||
def put(self, message = ""):
|
||||
print "Checking in", self.file, "..."
|
||||
data = open(self.file).read()
|
||||
if not self.enew:
|
||||
self.proxy.lock(self.file)
|
||||
messages = self.proxy.put(self.file, data, message)
|
||||
if messages:
|
||||
print messages
|
||||
self.setentry(self.proxy.head(self.file), self.lsum)
|
||||
|
||||
def get(self):
|
||||
data = self.proxy.get(self.file)
|
||||
f = open(self.file, 'w')
|
||||
f.write(data)
|
||||
f.close()
|
||||
self.setentry(self.rrev, self.rsum)
|
||||
|
||||
def log(self, otherflags):
|
||||
print self.proxy.log(self.file, otherflags)
|
||||
|
||||
def add(self):
|
||||
self.eseen = 0 # While we're hacking...
|
||||
self.esum = self.lsum
|
||||
self.emtime, self.ectime = 0, 0
|
||||
self.erev = ''
|
||||
self.enew = 1
|
||||
self.edeleted = 0
|
||||
self.eseen = 1 # Done
|
||||
self.extra = ''
|
||||
|
||||
def setentry(self, erev, esum):
|
||||
self.eseen = 0 # While we're hacking...
|
||||
self.esum = esum
|
||||
self.emtime, self.ectime = os.stat(self.file)[-2:]
|
||||
self.erev = erev
|
||||
self.enew = 0
|
||||
self.edeleted = 0
|
||||
self.eseen = 1 # Done
|
||||
self.extra = ''
|
||||
|
||||
|
||||
SENDMAIL = "/usr/lib/sendmail -t"
|
||||
MAILFORM = """To: %s
|
||||
Subject: CVS changes: %s
|
||||
|
||||
...Message from rcvs...
|
||||
|
||||
Committed files:
|
||||
%s
|
||||
|
||||
Log message:
|
||||
%s
|
||||
"""
|
||||
|
||||
|
||||
class RCVS(CVS):
|
||||
|
||||
FileClass = MyFile
|
||||
|
||||
def __init__(self):
|
||||
CVS.__init__(self)
|
||||
|
||||
def update(self, files):
|
||||
for e in self.whichentries(files, 1):
|
||||
e.update()
|
||||
|
||||
def commit(self, files, message = ""):
|
||||
list = self.whichentries(files)
|
||||
if not list: return
|
||||
ok = 1
|
||||
for e in list:
|
||||
if not e.commitcheck():
|
||||
ok = 0
|
||||
if not ok:
|
||||
print "correct above errors first"
|
||||
return
|
||||
if not message:
|
||||
message = raw_input("One-liner: ")
|
||||
committed = []
|
||||
for e in list:
|
||||
if e.commit(message):
|
||||
committed.append(e.file)
|
||||
self.mailinfo(committed, message)
|
||||
|
||||
def mailinfo(self, files, message = ""):
|
||||
towhom = "sjoerd@cwi.nl, jack@cwi.nl" # XXX
|
||||
mailtext = MAILFORM % (towhom, string.join(files),
|
||||
string.join(files), message)
|
||||
print '-'*70
|
||||
print mailtext
|
||||
print '-'*70
|
||||
ok = raw_input("OK to mail to %s? " % towhom)
|
||||
if string.lower(string.strip(ok)) in ('y', 'ye', 'yes'):
|
||||
p = os.popen(SENDMAIL, "w")
|
||||
p.write(mailtext)
|
||||
sts = p.close()
|
||||
if sts:
|
||||
print "Sendmail exit status %s" % str(sts)
|
||||
else:
|
||||
print "Mail sent."
|
||||
else:
|
||||
print "No mail sent."
|
||||
|
||||
def report(self, files):
|
||||
for e in self.whichentries(files):
|
||||
e.report()
|
||||
|
||||
def diff(self, files, opts):
|
||||
for e in self.whichentries(files):
|
||||
e.diff(opts)
|
||||
|
||||
def add(self, files):
|
||||
if not files:
|
||||
raise RuntimeError, "'cvs add' needs at least one file"
|
||||
list = []
|
||||
for e in self.whichentries(files, 1):
|
||||
e.add()
|
||||
|
||||
def rm(self, files):
|
||||
if not files:
|
||||
raise RuntimeError, "'cvs rm' needs at least one file"
|
||||
raise RuntimeError, "'cvs rm' not yet imlemented"
|
||||
|
||||
def log(self, files, opts):
|
||||
flags = ''
|
||||
for o, a in opts:
|
||||
flags = flags + ' ' + o + a
|
||||
for e in self.whichentries(files):
|
||||
e.log(flags)
|
||||
|
||||
def whichentries(self, files, localfilestoo = 0):
|
||||
if files:
|
||||
list = []
|
||||
for file in files:
|
||||
if self.entries.has_key(file):
|
||||
e = self.entries[file]
|
||||
else:
|
||||
e = self.FileClass(file)
|
||||
self.entries[file] = e
|
||||
list.append(e)
|
||||
else:
|
||||
list = self.entries.values()
|
||||
for file in self.proxy.listfiles():
|
||||
if self.entries.has_key(file):
|
||||
continue
|
||||
e = self.FileClass(file)
|
||||
self.entries[file] = e
|
||||
list.append(e)
|
||||
if localfilestoo:
|
||||
for file in os.listdir(os.curdir):
|
||||
if not self.entries.has_key(file) \
|
||||
and not self.ignored(file):
|
||||
e = self.FileClass(file)
|
||||
self.entries[file] = e
|
||||
list.append(e)
|
||||
list.sort()
|
||||
if self.proxy:
|
||||
for e in list:
|
||||
if e.proxy is None:
|
||||
e.proxy = self.proxy
|
||||
return list
|
||||
|
||||
|
||||
class rcvs(CommandFrameWork):
|
||||
|
||||
GlobalFlags = 'd:h:p:qvL'
|
||||
UsageMessage = \
|
||||
"usage: rcvs [-d directory] [-h host] [-p port] [-q] [-v] [subcommand arg ...]"
|
||||
PostUsageMessage = \
|
||||
"If no subcommand is given, the status of all files is listed"
|
||||
|
||||
def __init__(self):
|
||||
"""Constructor."""
|
||||
CommandFrameWork.__init__(self)
|
||||
self.proxy = None
|
||||
self.cvs = RCVS()
|
||||
|
||||
def close(self):
|
||||
if self.proxy:
|
||||
self.proxy._close()
|
||||
self.proxy = None
|
||||
|
||||
def recurse(self):
|
||||
self.close()
|
||||
names = os.listdir(os.curdir)
|
||||
for name in names:
|
||||
if name == os.curdir or name == os.pardir:
|
||||
continue
|
||||
if name == "CVS":
|
||||
continue
|
||||
if not os.path.isdir(name):
|
||||
continue
|
||||
if os.path.islink(name):
|
||||
continue
|
||||
print "--- entering subdirectory", name, "---"
|
||||
os.chdir(name)
|
||||
try:
|
||||
if os.path.isdir("CVS"):
|
||||
self.__class__().run()
|
||||
else:
|
||||
self.recurse()
|
||||
finally:
|
||||
os.chdir(os.pardir)
|
||||
print "--- left subdirectory", name, "---"
|
||||
|
||||
def options(self, opts):
|
||||
self.opts = opts
|
||||
|
||||
def ready(self):
|
||||
import rcsclient
|
||||
self.proxy = rcsclient.openrcsclient(self.opts)
|
||||
self.cvs.setproxy(self.proxy)
|
||||
self.cvs.getentries()
|
||||
|
||||
def default(self):
|
||||
self.cvs.report([])
|
||||
|
||||
def do_report(self, opts, files):
|
||||
self.cvs.report(files)
|
||||
|
||||
def do_update(self, opts, files):
|
||||
"""update [-l] [-R] [file] ..."""
|
||||
local = DEF_LOCAL
|
||||
for o, a in opts:
|
||||
if o == '-l': local = 1
|
||||
if o == '-R': local = 0
|
||||
self.cvs.update(files)
|
||||
self.cvs.putentries()
|
||||
if not local and not files:
|
||||
self.recurse()
|
||||
flags_update = '-lR'
|
||||
do_up = do_update
|
||||
flags_up = flags_update
|
||||
|
||||
def do_commit(self, opts, files):
|
||||
"""commit [-m message] [file] ..."""
|
||||
message = ""
|
||||
for o, a in opts:
|
||||
if o == '-m': message = a
|
||||
self.cvs.commit(files, message)
|
||||
self.cvs.putentries()
|
||||
flags_commit = 'm:'
|
||||
do_com = do_commit
|
||||
flags_com = flags_commit
|
||||
|
||||
def do_diff(self, opts, files):
|
||||
"""diff [difflags] [file] ..."""
|
||||
self.cvs.diff(files, opts)
|
||||
flags_diff = 'cbitwcefhnlr:sD:S:'
|
||||
do_dif = do_diff
|
||||
flags_dif = flags_diff
|
||||
|
||||
def do_add(self, opts, files):
|
||||
"""add file ..."""
|
||||
if not files:
|
||||
print "'rcvs add' requires at least one file"
|
||||
return
|
||||
self.cvs.add(files)
|
||||
self.cvs.putentries()
|
||||
|
||||
def do_remove(self, opts, files):
|
||||
"""remove file ..."""
|
||||
if not files:
|
||||
print "'rcvs remove' requires at least one file"
|
||||
return
|
||||
self.cvs.remove(files)
|
||||
self.cvs.putentries()
|
||||
do_rm = do_remove
|
||||
|
||||
def do_log(self, opts, files):
|
||||
"""log [rlog-options] [file] ..."""
|
||||
self.cvs.log(files, opts)
|
||||
flags_log = 'bhLNRtd:s:V:r:'
|
||||
|
||||
|
||||
def remove(fn):
|
||||
try:
|
||||
os.unlink(fn)
|
||||
except os.error:
|
||||
pass
|
||||
|
||||
|
||||
def main():
|
||||
r = rcvs()
|
||||
try:
|
||||
r.run()
|
||||
finally:
|
||||
r.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -0,0 +1,8 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import addpack
|
||||
addpack.addpack('/home/guido/src/python/Demo/pdist')
|
||||
|
||||
import rrcs
|
||||
|
||||
rrcs.main()
|
|
@ -0,0 +1,160 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
"Remote RCS -- command line interface"
|
||||
|
||||
import sys
|
||||
import os
|
||||
import getopt
|
||||
import string
|
||||
import md5
|
||||
import tempfile
|
||||
from rcsclient import openrcsclient
|
||||
|
||||
def main():
|
||||
sys.stdout = sys.stderr
|
||||
try:
|
||||
opts, rest = getopt.getopt(sys.argv[1:], 'h:p:d:qvL')
|
||||
if not rest:
|
||||
cmd = 'head'
|
||||
else:
|
||||
cmd, rest = rest[0], rest[1:]
|
||||
if not commands.has_key(cmd):
|
||||
raise getopt.error, "unknown command"
|
||||
coptset, func = commands[cmd]
|
||||
copts, files = getopt.getopt(rest, coptset)
|
||||
except getopt.error, msg:
|
||||
print msg
|
||||
print "usage: rrcs [options] command [options] [file] ..."
|
||||
print "where command can be:"
|
||||
print " ci|put # checkin the given files"
|
||||
print " co|get # checkout"
|
||||
print " info # print header info"
|
||||
print " head # print revision of head branch"
|
||||
print " list # list filename if valid"
|
||||
print " log # print full log"
|
||||
print " diff # diff rcs file and work file"
|
||||
print "if no files are given, all remote rcs files are assumed"
|
||||
sys.exit(2)
|
||||
x = openrcsclient(opts)
|
||||
if not files:
|
||||
files = x.listfiles()
|
||||
for fn in files:
|
||||
try:
|
||||
func(x, copts, fn)
|
||||
except (IOError, os.error), msg:
|
||||
print "%s: %s" % (fn, msg)
|
||||
|
||||
def checkin(x, copts, fn):
|
||||
f = open(fn)
|
||||
data = f.read()
|
||||
f.close()
|
||||
new = not x.isvalid(fn)
|
||||
if not new and same(x, copts, fn, data):
|
||||
print "%s: unchanged since last checkin" % fn
|
||||
return
|
||||
print "Checking in", fn, "..."
|
||||
message = asklogmessage(new)
|
||||
messages = x.put(fn, data, message)
|
||||
if messages:
|
||||
print messages
|
||||
|
||||
def checkout(x, copts, fn):
|
||||
data = x.get(fn)
|
||||
f = open(fn, 'w')
|
||||
f.write(data)
|
||||
f.close()
|
||||
|
||||
def lock(x, copts, fn):
|
||||
x.lock(fn)
|
||||
|
||||
def unlock(x, copts, fn):
|
||||
x.unlock(fn)
|
||||
|
||||
def info(x, copts, fn):
|
||||
dict = x.info(fn)
|
||||
keys = dict.keys()
|
||||
keys.sort()
|
||||
for key in keys:
|
||||
print key + ':', dict[key]
|
||||
print '='*70
|
||||
|
||||
def head(x, copts, fn):
|
||||
head = x.head(fn)
|
||||
print fn, head
|
||||
|
||||
def list(x, copts, fn):
|
||||
if x.isvalid(fn):
|
||||
print fn
|
||||
|
||||
def log(x, copts, fn):
|
||||
flags = ''
|
||||
for o, a in copts:
|
||||
flags = flags + ' ' + o + a
|
||||
flags = flags[1:]
|
||||
messages = x.log(fn, flags)
|
||||
print messages
|
||||
|
||||
def diff(x, copts, fn):
|
||||
if same(x, copts, fn):
|
||||
return
|
||||
flags = ''
|
||||
for o, a in copts:
|
||||
flags = flags + ' ' + o + a
|
||||
flags = flags[1:]
|
||||
data = x.get(fn)
|
||||
tf = tempfile.NamedTemporaryFile()
|
||||
tf.write(data)
|
||||
tf.flush()
|
||||
print 'diff %s -r%s %s' % (flags, x.head(fn), fn)
|
||||
sts = os.system('diff %s %s %s' % (flags, tf.name, fn))
|
||||
if sts:
|
||||
print '='*70
|
||||
|
||||
def same(x, copts, fn, data = None):
|
||||
if data is None:
|
||||
f = open(fn)
|
||||
data = f.read()
|
||||
f.close()
|
||||
lsum = md5.new(data).digest()
|
||||
rsum = x.sum(fn)
|
||||
return lsum == rsum
|
||||
|
||||
def asklogmessage(new):
|
||||
if new:
|
||||
print "enter description,",
|
||||
else:
|
||||
print "enter log message,",
|
||||
print "terminate with single '.' or end of file:"
|
||||
if new:
|
||||
print "NOTE: This is NOT the log message!"
|
||||
message = ""
|
||||
while 1:
|
||||
sys.stderr.write(">> ")
|
||||
sys.stderr.flush()
|
||||
line = sys.stdin.readline()
|
||||
if not line or line == '.\n': break
|
||||
message = message + line
|
||||
return message
|
||||
|
||||
def remove(fn):
|
||||
try:
|
||||
os.unlink(fn)
|
||||
except os.error:
|
||||
pass
|
||||
|
||||
commands = {
|
||||
'ci': ('', checkin),
|
||||
'put': ('', checkin),
|
||||
'co': ('', checkout),
|
||||
'get': ('', checkout),
|
||||
'info': ('', info),
|
||||
'head': ('', head),
|
||||
'list': ('', list),
|
||||
'lock': ('', lock),
|
||||
'unlock': ('', unlock),
|
||||
'log': ('bhLRtd:l:r:s:w:V:', log),
|
||||
'diff': ('c', diff),
|
||||
}
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -0,0 +1,33 @@
|
|||
class Security:
|
||||
|
||||
def __init__(self):
|
||||
import os
|
||||
env = os.environ
|
||||
if env.has_key('PYTHON_KEYFILE'):
|
||||
keyfile = env['PYTHON_KEYFILE']
|
||||
else:
|
||||
keyfile = '.python_keyfile'
|
||||
if env.has_key('HOME'):
|
||||
keyfile = os.path.join(env['HOME'], keyfile)
|
||||
if not os.path.exists(keyfile):
|
||||
import sys
|
||||
for dir in sys.path:
|
||||
kf = os.path.join(dir, keyfile)
|
||||
if os.path.exists(kf):
|
||||
keyfile = kf
|
||||
break
|
||||
try:
|
||||
self._key = eval(open(keyfile).readline())
|
||||
except IOError:
|
||||
raise IOError, "python keyfile %s: cannot open" % keyfile
|
||||
|
||||
def _generate_challenge(self):
|
||||
import random
|
||||
return random.randint(100, 100000)
|
||||
|
||||
def _compare_challenge_response(self, challenge, response):
|
||||
return self._encode_challenge(challenge) == response
|
||||
|
||||
def _encode_challenge(self, challenge):
|
||||
p, m = self._key
|
||||
return pow(long(challenge), p, m)
|
|
@ -0,0 +1,145 @@
|
|||
"""RPC Server module."""
|
||||
|
||||
import sys
|
||||
import socket
|
||||
import pickle
|
||||
from fnmatch import fnmatch
|
||||
from repr import repr
|
||||
|
||||
|
||||
# Default verbosity (0 = silent, 1 = print connections, 2 = print requests too)
|
||||
VERBOSE = 1
|
||||
|
||||
|
||||
class Server:
|
||||
|
||||
"""RPC Server class. Derive a class to implement a particular service."""
|
||||
|
||||
def __init__(self, address, verbose = VERBOSE):
|
||||
if type(address) == type(0):
|
||||
address = ('', address)
|
||||
self._address = address
|
||||
self._verbose = verbose
|
||||
self._socket = None
|
||||
self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self._socket.bind(address)
|
||||
self._socket.listen(1)
|
||||
self._listening = 1
|
||||
|
||||
def _setverbose(self, verbose):
|
||||
self._verbose = verbose
|
||||
|
||||
def __del__(self):
|
||||
self._close()
|
||||
|
||||
def _close(self):
|
||||
self._listening = 0
|
||||
if self._socket:
|
||||
self._socket.close()
|
||||
self._socket = None
|
||||
|
||||
def _serverloop(self):
|
||||
while self._listening:
|
||||
self._serve()
|
||||
|
||||
def _serve(self):
|
||||
if self._verbose: print "Wait for connection ..."
|
||||
conn, address = self._socket.accept()
|
||||
if self._verbose: print "Accepted connection from %s" % repr(address)
|
||||
if not self._verify(conn, address):
|
||||
print "*** Connection from %s refused" % repr(address)
|
||||
conn.close()
|
||||
return
|
||||
rf = conn.makefile('r')
|
||||
wf = conn.makefile('w')
|
||||
ok = 1
|
||||
while ok:
|
||||
wf.flush()
|
||||
if self._verbose > 1: print "Wait for next request ..."
|
||||
ok = self._dorequest(rf, wf)
|
||||
|
||||
_valid = ['192.16.201.*', '192.16.197.*', '132.151.1.*', '129.6.64.*']
|
||||
|
||||
def _verify(self, conn, address):
|
||||
host, port = address
|
||||
for pat in self._valid:
|
||||
if fnmatch(host, pat): return 1
|
||||
return 0
|
||||
|
||||
def _dorequest(self, rf, wf):
|
||||
rp = pickle.Unpickler(rf)
|
||||
try:
|
||||
request = rp.load()
|
||||
except EOFError:
|
||||
return 0
|
||||
if self._verbose > 1: print "Got request: %s" % repr(request)
|
||||
try:
|
||||
methodname, args, id = request
|
||||
if '.' in methodname:
|
||||
reply = (None, self._special(methodname, args), id)
|
||||
elif methodname[0] == '_':
|
||||
raise NameError, "illegal method name %s" % repr(methodname)
|
||||
else:
|
||||
method = getattr(self, methodname)
|
||||
reply = (None, apply(method, args), id)
|
||||
except:
|
||||
reply = (sys.exc_type, sys.exc_value, id)
|
||||
if id < 0 and reply[:2] == (None, None):
|
||||
if self._verbose > 1: print "Suppress reply"
|
||||
return 1
|
||||
if self._verbose > 1: print "Send reply: %s" % repr(reply)
|
||||
wp = pickle.Pickler(wf)
|
||||
wp.dump(reply)
|
||||
return 1
|
||||
|
||||
def _special(self, methodname, args):
|
||||
if methodname == '.methods':
|
||||
if not hasattr(self, '_methods'):
|
||||
self._methods = tuple(self._listmethods())
|
||||
return self._methods
|
||||
raise NameError, "unrecognized special method name %s" % repr(methodname)
|
||||
|
||||
def _listmethods(self, cl=None):
|
||||
if not cl: cl = self.__class__
|
||||
names = cl.__dict__.keys()
|
||||
names = filter(lambda x: x[0] != '_', names)
|
||||
names.sort()
|
||||
for base in cl.__bases__:
|
||||
basenames = self._listmethods(base)
|
||||
basenames = filter(lambda x, names=names: x not in names, basenames)
|
||||
names[len(names):] = basenames
|
||||
return names
|
||||
|
||||
|
||||
from security import Security
|
||||
|
||||
|
||||
class SecureServer(Server, Security):
|
||||
|
||||
def __init__(self, *args):
|
||||
apply(Server.__init__, (self,) + args)
|
||||
Security.__init__(self)
|
||||
|
||||
def _verify(self, conn, address):
|
||||
import string
|
||||
challenge = self._generate_challenge()
|
||||
conn.send("%d\n" % challenge)
|
||||
response = ""
|
||||
while "\n" not in response and len(response) < 100:
|
||||
data = conn.recv(100)
|
||||
if not data:
|
||||
break
|
||||
response = response + data
|
||||
try:
|
||||
response = string.atol(string.strip(response))
|
||||
except string.atol_error:
|
||||
if self._verbose > 0:
|
||||
print "Invalid response syntax", repr(response)
|
||||
return 0
|
||||
if not self._compare_challenge_response(challenge, response):
|
||||
if self._verbose > 0:
|
||||
print "Invalid response value", repr(response)
|
||||
return 0
|
||||
if self._verbose > 1:
|
||||
print "Response matches challenge. Go ahead!"
|
||||
return 1
|
|
@ -0,0 +1,24 @@
|
|||
import time
|
||||
import FSProxy
|
||||
|
||||
def main():
|
||||
t1 = time.time()
|
||||
#proxy = FSProxy.FSProxyClient(('voorn.cwi.nl', 4127))
|
||||
proxy = FSProxy.FSProxyLocal()
|
||||
sumtree(proxy)
|
||||
proxy._close()
|
||||
t2 = time.time()
|
||||
print t2-t1, "seconds"
|
||||
raw_input("[Return to exit] ")
|
||||
|
||||
def sumtree(proxy):
|
||||
print "PWD =", proxy.pwd()
|
||||
files = proxy.listfiles()
|
||||
proxy.infolist(files)
|
||||
subdirs = proxy.listsubdirs()
|
||||
for name in subdirs:
|
||||
proxy.cd(name)
|
||||
sumtree(proxy)
|
||||
proxy.back()
|
||||
|
||||
main()
|
|
@ -0,0 +1,22 @@
|
|||
This directory contains a collection of executable Python scripts.
|
||||
|
||||
See also the Tools/scripts directory!
|
||||
|
||||
beer.py Print the classic 'bottles of beer' list
|
||||
eqfix.py Fix .py files to use the correct equality test operator
|
||||
fact.py Factorize numbers
|
||||
find-uname.py Search for Unicode characters using regexps
|
||||
from.py Summarize mailbox
|
||||
lpwatch.py Watch BSD line printer queues
|
||||
makedir.py Like mkdir -p
|
||||
markov.py Markov chain simulation of words or characters
|
||||
mboxconvert.py Convert MH or MMDF mailboxes to unix mailbox format
|
||||
morse.py Produce morse code (audible or on AIFF file)
|
||||
newslist.py List all newsgroups on a NNTP server as HTML pages
|
||||
pi.py Print all digits of pi -- given enough time and memory
|
||||
pp.py Emulate some Perl command line options
|
||||
primes.py Print prime numbers
|
||||
queens.py Dijkstra's solution to Wirth's "N Queens problem"
|
||||
script.py Equivalent to BSD script(1) -- by Steen Lumholt
|
||||
unbirthday.py Print unbirthday count
|
||||
update.py Update a bunch of files according to a script.
|
|
@ -0,0 +1,20 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
# By GvR, demystified after a version by Fredrik Lundh.
|
||||
|
||||
import sys
|
||||
|
||||
n = 100
|
||||
if sys.argv[1:]:
|
||||
n = int(sys.argv[1])
|
||||
|
||||
def bottle(n):
|
||||
if n == 0: return "no more bottles of beer"
|
||||
if n == 1: return "one bottle of beer"
|
||||
return str(n) + " bottles of beer"
|
||||
|
||||
for i in range(n, 0, -1):
|
||||
print bottle(i), "on the wall,"
|
||||
print bottle(i) + "."
|
||||
print "Take one down, pass it around,"
|
||||
print bottle(i-1), "on the wall."
|
|
@ -0,0 +1,198 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
# Fix Python source files to use the new equality test operator, i.e.,
|
||||
# if x = y: ...
|
||||
# is changed to
|
||||
# if x == y: ...
|
||||
# The script correctly tokenizes the Python program to reliably
|
||||
# distinguish between assignments and equality tests.
|
||||
#
|
||||
# Command line arguments are files or directories to be processed.
|
||||
# Directories are searched recursively for files whose name looks
|
||||
# like a python module.
|
||||
# Symbolic links are always ignored (except as explicit directory
|
||||
# arguments). Of course, the original file is kept as a back-up
|
||||
# (with a "~" attached to its name).
|
||||
# It complains about binaries (files containing null bytes)
|
||||
# and about files that are ostensibly not Python files: if the first
|
||||
# line starts with '#!' and does not contain the string 'python'.
|
||||
#
|
||||
# Changes made are reported to stdout in a diff-like format.
|
||||
#
|
||||
# Undoubtedly you can do this using find and sed or perl, but this is
|
||||
# a nice example of Python code that recurses down a directory tree
|
||||
# and uses regular expressions. Also note several subtleties like
|
||||
# preserving the file's mode and avoiding to even write a temp file
|
||||
# when no changes are needed for a file.
|
||||
#
|
||||
# NB: by changing only the function fixline() you can turn this
|
||||
# into a program for a different change to Python programs...
|
||||
|
||||
import sys
|
||||
import re
|
||||
import os
|
||||
from stat import *
|
||||
import string
|
||||
|
||||
err = sys.stderr.write
|
||||
dbg = err
|
||||
rep = sys.stdout.write
|
||||
|
||||
def main():
|
||||
bad = 0
|
||||
if not sys.argv[1:]: # No arguments
|
||||
err('usage: ' + sys.argv[0] + ' file-or-directory ...\n')
|
||||
sys.exit(2)
|
||||
for arg in sys.argv[1:]:
|
||||
if os.path.isdir(arg):
|
||||
if recursedown(arg): bad = 1
|
||||
elif os.path.islink(arg):
|
||||
err(arg + ': will not process symbolic links\n')
|
||||
bad = 1
|
||||
else:
|
||||
if fix(arg): bad = 1
|
||||
sys.exit(bad)
|
||||
|
||||
ispythonprog = re.compile('^[a-zA-Z0-9_]+\.py$')
|
||||
def ispython(name):
|
||||
return ispythonprog.match(name) >= 0
|
||||
|
||||
def recursedown(dirname):
|
||||
dbg('recursedown(%r)\n' % (dirname,))
|
||||
bad = 0
|
||||
try:
|
||||
names = os.listdir(dirname)
|
||||
except os.error, msg:
|
||||
err('%s: cannot list directory: %r\n' % (dirname, msg))
|
||||
return 1
|
||||
names.sort()
|
||||
subdirs = []
|
||||
for name in names:
|
||||
if name in (os.curdir, os.pardir): continue
|
||||
fullname = os.path.join(dirname, name)
|
||||
if os.path.islink(fullname): pass
|
||||
elif os.path.isdir(fullname):
|
||||
subdirs.append(fullname)
|
||||
elif ispython(name):
|
||||
if fix(fullname): bad = 1
|
||||
for fullname in subdirs:
|
||||
if recursedown(fullname): bad = 1
|
||||
return bad
|
||||
|
||||
def fix(filename):
|
||||
## dbg('fix(%r)\n' % (dirname,))
|
||||
try:
|
||||
f = open(filename, 'r')
|
||||
except IOError, msg:
|
||||
err('%s: cannot open: %r\n' % (filename, msg))
|
||||
return 1
|
||||
head, tail = os.path.split(filename)
|
||||
tempname = os.path.join(head, '@' + tail)
|
||||
g = None
|
||||
# If we find a match, we rewind the file and start over but
|
||||
# now copy everything to a temp file.
|
||||
lineno = 0
|
||||
while 1:
|
||||
line = f.readline()
|
||||
if not line: break
|
||||
lineno = lineno + 1
|
||||
if g is None and '\0' in line:
|
||||
# Check for binary files
|
||||
err(filename + ': contains null bytes; not fixed\n')
|
||||
f.close()
|
||||
return 1
|
||||
if lineno == 1 and g is None and line[:2] == '#!':
|
||||
# Check for non-Python scripts
|
||||
words = string.split(line[2:])
|
||||
if words and re.search('[pP]ython', words[0]) < 0:
|
||||
msg = filename + ': ' + words[0]
|
||||
msg = msg + ' script; not fixed\n'
|
||||
err(msg)
|
||||
f.close()
|
||||
return 1
|
||||
while line[-2:] == '\\\n':
|
||||
nextline = f.readline()
|
||||
if not nextline: break
|
||||
line = line + nextline
|
||||
lineno = lineno + 1
|
||||
newline = fixline(line)
|
||||
if newline != line:
|
||||
if g is None:
|
||||
try:
|
||||
g = open(tempname, 'w')
|
||||
except IOError, msg:
|
||||
f.close()
|
||||
err('%s: cannot create: %r\n' % (tempname, msg))
|
||||
return 1
|
||||
f.seek(0)
|
||||
lineno = 0
|
||||
rep(filename + ':\n')
|
||||
continue # restart from the beginning
|
||||
rep(repr(lineno) + '\n')
|
||||
rep('< ' + line)
|
||||
rep('> ' + newline)
|
||||
if g is not None:
|
||||
g.write(newline)
|
||||
|
||||
# End of file
|
||||
f.close()
|
||||
if not g: return 0 # No changes
|
||||
|
||||
# Finishing touch -- move files
|
||||
|
||||
# First copy the file's mode to the temp file
|
||||
try:
|
||||
statbuf = os.stat(filename)
|
||||
os.chmod(tempname, statbuf[ST_MODE] & 07777)
|
||||
except os.error, msg:
|
||||
err('%s: warning: chmod failed (%r)\n' % (tempname, msg))
|
||||
# Then make a backup of the original file as filename~
|
||||
try:
|
||||
os.rename(filename, filename + '~')
|
||||
except os.error, msg:
|
||||
err('%s: warning: backup failed (%r)\n' % (filename, msg))
|
||||
# Now move the temp file to the original file
|
||||
try:
|
||||
os.rename(tempname, filename)
|
||||
except os.error, msg:
|
||||
err('%s: rename failed (%r)\n' % (filename, msg))
|
||||
return 1
|
||||
# Return succes
|
||||
return 0
|
||||
|
||||
|
||||
from tokenize import tokenprog
|
||||
|
||||
match = {'if':':', 'elif':':', 'while':':', 'return':'\n', \
|
||||
'(':')', '[':']', '{':'}', '`':'`'}
|
||||
|
||||
def fixline(line):
|
||||
# Quick check for easy case
|
||||
if '=' not in line: return line
|
||||
|
||||
i, n = 0, len(line)
|
||||
stack = []
|
||||
while i < n:
|
||||
j = tokenprog.match(line, i)
|
||||
if j < 0:
|
||||
# A bad token; forget about the rest of this line
|
||||
print '(Syntax error:)'
|
||||
print line,
|
||||
return line
|
||||
a, b = tokenprog.regs[3] # Location of the token proper
|
||||
token = line[a:b]
|
||||
i = i+j
|
||||
if stack and token == stack[-1]:
|
||||
del stack[-1]
|
||||
elif match.has_key(token):
|
||||
stack.append(match[token])
|
||||
elif token == '=' and stack:
|
||||
line = line[:a] + '==' + line[b:]
|
||||
i, n = a + len('=='), len(line)
|
||||
elif token == '==' and not stack:
|
||||
print '(Warning: \'==\' at top level:)'
|
||||
print line,
|
||||
return line
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -0,0 +1,49 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
# Factorize numbers.
|
||||
# The algorithm is not efficient, but easy to understand.
|
||||
# If there are large factors, it will take forever to find them,
|
||||
# because we try all odd numbers between 3 and sqrt(n)...
|
||||
|
||||
import sys
|
||||
from math import sqrt
|
||||
|
||||
def fact(n):
|
||||
if n < 1:
|
||||
raise ValueError('fact() argument should be >= 1')
|
||||
if n == 1:
|
||||
return [] # special case
|
||||
res = []
|
||||
# Treat even factors special, so we can use i += 2 later
|
||||
while n % 2 == 0:
|
||||
res.append(2)
|
||||
n //= 2
|
||||
# Try odd numbers up to sqrt(n)
|
||||
limit = sqrt(n+1)
|
||||
i = 3
|
||||
while i <= limit:
|
||||
if n % i == 0:
|
||||
res.append(i)
|
||||
n //= i
|
||||
limit = sqrt(n+1)
|
||||
else:
|
||||
i += 2
|
||||
if n != 1:
|
||||
res.append(n)
|
||||
return res
|
||||
|
||||
def main():
|
||||
if len(sys.argv) > 1:
|
||||
source = sys.argv[1:]
|
||||
else:
|
||||
source = iter(raw_input, '')
|
||||
for arg in source:
|
||||
try:
|
||||
n = int(arg)
|
||||
except ValueError:
|
||||
print arg, 'is not an integer'
|
||||
else:
|
||||
print n, fact(n)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -0,0 +1,40 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
For each argument on the command line, look for it in the set of all Unicode
|
||||
names. Arguments are treated as case-insensitive regular expressions, e.g.:
|
||||
|
||||
% find-uname 'small letter a$' 'horizontal line'
|
||||
*** small letter a$ matches ***
|
||||
LATIN SMALL LETTER A (97)
|
||||
COMBINING LATIN SMALL LETTER A (867)
|
||||
CYRILLIC SMALL LETTER A (1072)
|
||||
PARENTHESIZED LATIN SMALL LETTER A (9372)
|
||||
CIRCLED LATIN SMALL LETTER A (9424)
|
||||
FULLWIDTH LATIN SMALL LETTER A (65345)
|
||||
*** horizontal line matches ***
|
||||
HORIZONTAL LINE EXTENSION (9135)
|
||||
"""
|
||||
|
||||
import unicodedata
|
||||
import sys
|
||||
import re
|
||||
|
||||
def main(args):
|
||||
unicode_names = []
|
||||
for ix in range(sys.maxunicode+1):
|
||||
try:
|
||||
unicode_names.append((ix, unicodedata.name(unichr(ix))))
|
||||
except ValueError: # no name for the character
|
||||
pass
|
||||
for arg in args:
|
||||
pat = re.compile(arg, re.I)
|
||||
matches = [(y,x) for (x,y) in unicode_names
|
||||
if pat.search(y) is not None]
|
||||
if matches:
|
||||
print "***", arg, "matches", "***"
|
||||
for match in matches:
|
||||
print "%s (%d)" % match
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.argv[1:])
|
|
@ -0,0 +1,35 @@
|
|||
#! /usr/bin/env 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 sys, os
|
||||
|
||||
# Open mailbox file. Exits with exception when this fails.
|
||||
|
||||
try:
|
||||
mailbox = os.environ['MAIL']
|
||||
except (AttributeError, KeyError):
|
||||
sys.stderr.write('No environment variable $MAIL\n')
|
||||
sys.exit(2)
|
||||
|
||||
try:
|
||||
mail = open(mailbox)
|
||||
except IOError:
|
||||
sys.exit('Cannot open mailbox file: ' + mailbox)
|
||||
|
||||
while 1:
|
||||
line = mail.readline()
|
||||
if not line:
|
||||
break # EOF
|
||||
if line.startswith('From '):
|
||||
# Start of message found
|
||||
print line[:-1],
|
||||
while 1:
|
||||
line = mail.readline()
|
||||
if not line or line == '\n':
|
||||
break
|
||||
if line.startswith('Subject: '):
|
||||
print repr(line[9:-1]),
|
||||
print
|
|
@ -0,0 +1,102 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
# Watch line printer queue(s).
|
||||
# Intended for BSD 4.3 lpq.
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
|
||||
DEF_PRINTER = 'psc'
|
||||
DEF_DELAY = 10
|
||||
|
||||
def main():
|
||||
delay = DEF_DELAY # XXX Use getopt() later
|
||||
try:
|
||||
thisuser = os.environ['LOGNAME']
|
||||
except:
|
||||
thisuser = os.environ['USER']
|
||||
printers = sys.argv[1:]
|
||||
if printers:
|
||||
# Strip '-P' from printer names just in case
|
||||
# the user specified it...
|
||||
for i, name in enumerate(printers):
|
||||
if name[:2] == '-P':
|
||||
printers[i] = name[2:]
|
||||
else:
|
||||
if os.environ.has_key('PRINTER'):
|
||||
printers = [os.environ['PRINTER']]
|
||||
else:
|
||||
printers = [DEF_PRINTER]
|
||||
|
||||
clearhome = os.popen('clear', 'r').read()
|
||||
|
||||
while True:
|
||||
text = clearhome
|
||||
for name in printers:
|
||||
text += makestatus(name, thisuser) + '\n'
|
||||
print text
|
||||
time.sleep(delay)
|
||||
|
||||
def makestatus(name, thisuser):
|
||||
pipe = os.popen('lpq -P' + name + ' 2>&1', 'r')
|
||||
lines = []
|
||||
users = {}
|
||||
aheadbytes = 0
|
||||
aheadjobs = 0
|
||||
userseen = False
|
||||
totalbytes = 0
|
||||
totaljobs = 0
|
||||
for line in pipe:
|
||||
fields = line.split()
|
||||
n = len(fields)
|
||||
if len(fields) >= 6 and fields[n-1] == 'bytes':
|
||||
rank, user, job = fields[0:3]
|
||||
files = fields[3:-2]
|
||||
bytes = int(fields[n-2])
|
||||
if user == thisuser:
|
||||
userseen = True
|
||||
elif not userseen:
|
||||
aheadbytes += bytes
|
||||
aheadjobs += 1
|
||||
totalbytes += bytes
|
||||
totaljobs += 1
|
||||
ujobs, ubytes = users.get(user, (0, 0))
|
||||
ujobs += 1
|
||||
ubytes += bytes
|
||||
users[user] = ujobs, ubytes
|
||||
else:
|
||||
if fields and fields[0] != 'Rank':
|
||||
line = line.strip()
|
||||
if line == 'no entries':
|
||||
line = name + ': idle'
|
||||
elif line[-22:] == ' is ready and printing':
|
||||
line = name
|
||||
lines.append(line)
|
||||
|
||||
if totaljobs:
|
||||
line = '%d K' % ((totalbytes+1023) // 1024)
|
||||
if totaljobs != len(users):
|
||||
line += ' (%d jobs)' % totaljobs
|
||||
if len(users) == 1:
|
||||
line += ' for %s' % (users.keys()[0],)
|
||||
else:
|
||||
line += ' for %d users' % len(users)
|
||||
if userseen:
|
||||
if aheadjobs == 0:
|
||||
line += ' (%s first)' % thisuser
|
||||
else:
|
||||
line += ' (%d K before %s)' % (
|
||||
(aheadbytes+1023) // 1024, thisuser)
|
||||
lines.append(line)
|
||||
|
||||
sts = pipe.close()
|
||||
if sts:
|
||||
lines.append('lpq exit status %r' % (sts,))
|
||||
return ': '.join(lines)
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except KeyboardInterrupt:
|
||||
pass
|
|
@ -0,0 +1,21 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
# Like mkdir, but also make intermediate directories if necessary.
|
||||
# It is not an error if the given directory already exists (as long
|
||||
# as it is a directory).
|
||||
# Errors are not treated specially -- you just get a Python exception.
|
||||
|
||||
import sys, os
|
||||
|
||||
def main():
|
||||
for p in sys.argv[1:]:
|
||||
makedirs(p)
|
||||
|
||||
def makedirs(p):
|
||||
if p and not os.path.isdir(p):
|
||||
head, tail = os.path.split(p)
|
||||
makedirs(head)
|
||||
os.mkdir(p, 0777)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -0,0 +1,121 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
class Markov:
|
||||
def __init__(self, histsize, choice):
|
||||
self.histsize = histsize
|
||||
self.choice = choice
|
||||
self.trans = {}
|
||||
|
||||
def add(self, state, next):
|
||||
self.trans.setdefault(state, []).append(next)
|
||||
|
||||
def put(self, seq):
|
||||
n = self.histsize
|
||||
add = self.add
|
||||
add(None, seq[:0])
|
||||
for i in range(len(seq)):
|
||||
add(seq[max(0, i-n):i], seq[i:i+1])
|
||||
add(seq[len(seq)-n:], None)
|
||||
|
||||
def get(self):
|
||||
choice = self.choice
|
||||
trans = self.trans
|
||||
n = self.histsize
|
||||
seq = choice(trans[None])
|
||||
while True:
|
||||
subseq = seq[max(0, len(seq)-n):]
|
||||
options = trans[subseq]
|
||||
next = choice(options)
|
||||
if not next:
|
||||
break
|
||||
seq += next
|
||||
return seq
|
||||
|
||||
|
||||
def test():
|
||||
import sys, random, getopt
|
||||
args = sys.argv[1:]
|
||||
try:
|
||||
opts, args = getopt.getopt(args, '0123456789cdwq')
|
||||
except getopt.error:
|
||||
print 'Usage: %s [-#] [-cddqw] [file] ...' % sys.argv[0]
|
||||
print 'Options:'
|
||||
print '-#: 1-digit history size (default 2)'
|
||||
print '-c: characters (default)'
|
||||
print '-w: words'
|
||||
print '-d: more debugging output'
|
||||
print '-q: no debugging output'
|
||||
print 'Input files (default stdin) are split in paragraphs'
|
||||
print 'separated blank lines and each paragraph is split'
|
||||
print 'in words by whitespace, then reconcatenated with'
|
||||
print 'exactly one space separating words.'
|
||||
print 'Output consists of paragraphs separated by blank'
|
||||
print 'lines, where lines are no longer than 72 characters.'
|
||||
sys.exit(2)
|
||||
histsize = 2
|
||||
do_words = False
|
||||
debug = 1
|
||||
for o, a in opts:
|
||||
if '-0' <= o <= '-9': histsize = int(o[1:])
|
||||
if o == '-c': do_words = False
|
||||
if o == '-d': debug += 1
|
||||
if o == '-q': debug = 0
|
||||
if o == '-w': do_words = True
|
||||
if not args:
|
||||
args = ['-']
|
||||
|
||||
m = Markov(histsize, random.choice)
|
||||
try:
|
||||
for filename in args:
|
||||
if filename == '-':
|
||||
f = sys.stdin
|
||||
if f.isatty():
|
||||
print 'Sorry, need stdin from file'
|
||||
continue
|
||||
else:
|
||||
f = open(filename, 'r')
|
||||
if debug: print 'processing', filename, '...'
|
||||
text = f.read()
|
||||
f.close()
|
||||
paralist = text.split('\n\n')
|
||||
for para in paralist:
|
||||
if debug > 1: print 'feeding ...'
|
||||
words = para.split()
|
||||
if words:
|
||||
if do_words:
|
||||
data = tuple(words)
|
||||
else:
|
||||
data = ' '.join(words)
|
||||
m.put(data)
|
||||
except KeyboardInterrupt:
|
||||
print 'Interrupted -- continue with data read so far'
|
||||
if not m.trans:
|
||||
print 'No valid input files'
|
||||
return
|
||||
if debug: print 'done.'
|
||||
|
||||
if debug > 1:
|
||||
for key in m.trans.keys():
|
||||
if key is None or len(key) < histsize:
|
||||
print repr(key), m.trans[key]
|
||||
if histsize == 0: print repr(''), m.trans['']
|
||||
print
|
||||
while True:
|
||||
data = m.get()
|
||||
if do_words:
|
||||
words = data
|
||||
else:
|
||||
words = data.split()
|
||||
n = 0
|
||||
limit = 72
|
||||
for w in words:
|
||||
if n + len(w) > limit:
|
||||
print
|
||||
n = 0
|
||||
print w,
|
||||
n += len(w) + 1
|
||||
print
|
||||
print
|
||||
|
||||
if __name__ == "__main__":
|
||||
test()
|
|
@ -0,0 +1,124 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
# Convert MH directories (1 message per file) or MMDF mailboxes (4x^A
|
||||
# delimited) to unix mailbox (From ... delimited) on stdout.
|
||||
# If -f is given, files contain one message per file (e.g. MH messages)
|
||||
|
||||
import rfc822
|
||||
import sys
|
||||
import time
|
||||
import os
|
||||
import stat
|
||||
import getopt
|
||||
import re
|
||||
|
||||
def main():
|
||||
dofile = mmdf
|
||||
try:
|
||||
opts, args = getopt.getopt(sys.argv[1:], 'f')
|
||||
except getopt.error, msg:
|
||||
sys.stderr.write('%s\n' % msg)
|
||||
sys.exit(2)
|
||||
for o, a in opts:
|
||||
if o == '-f':
|
||||
dofile = message
|
||||
if not args:
|
||||
args = ['-']
|
||||
sts = 0
|
||||
for arg in args:
|
||||
if arg == '-' or arg == '':
|
||||
sts = dofile(sys.stdin) or sts
|
||||
elif os.path.isdir(arg):
|
||||
sts = mh(arg) or sts
|
||||
elif os.path.isfile(arg):
|
||||
try:
|
||||
f = open(arg)
|
||||
except IOError, msg:
|
||||
sys.stderr.write('%s: %s\n' % (arg, msg))
|
||||
sts = 1
|
||||
continue
|
||||
sts = dofile(f) or sts
|
||||
f.close()
|
||||
else:
|
||||
sys.stderr.write('%s: not found\n' % arg)
|
||||
sts = 1
|
||||
if sts:
|
||||
sys.exit(sts)
|
||||
|
||||
numeric = re.compile('[1-9][0-9]*')
|
||||
|
||||
def mh(dir):
|
||||
sts = 0
|
||||
msgs = os.listdir(dir)
|
||||
for msg in msgs:
|
||||
if numeric.match(msg) != len(msg):
|
||||
continue
|
||||
fn = os.path.join(dir, msg)
|
||||
try:
|
||||
f = open(fn)
|
||||
except IOError, msg:
|
||||
sys.stderr.write('%s: %s\n' % (fn, msg))
|
||||
sts = 1
|
||||
continue
|
||||
sts = message(f) or sts
|
||||
return sts
|
||||
|
||||
def mmdf(f):
|
||||
sts = 0
|
||||
while 1:
|
||||
line = f.readline()
|
||||
if not line:
|
||||
break
|
||||
if line == '\1\1\1\1\n':
|
||||
sts = message(f, line) or sts
|
||||
else:
|
||||
sys.stderr.write(
|
||||
'Bad line in MMFD mailbox: %r\n' % (line,))
|
||||
return sts
|
||||
|
||||
counter = 0 # for generating unique Message-ID headers
|
||||
|
||||
def message(f, delimiter = ''):
|
||||
sts = 0
|
||||
# Parse RFC822 header
|
||||
m = rfc822.Message(f)
|
||||
# Write unix header line
|
||||
fullname, email = m.getaddr('From')
|
||||
tt = m.getdate('Date')
|
||||
if tt:
|
||||
t = time.mktime(tt)
|
||||
else:
|
||||
sys.stderr.write(
|
||||
'Unparseable date: %r\n' % (m.getheader('Date'),))
|
||||
t = os.fstat(f.fileno())[stat.ST_MTIME]
|
||||
print 'From', email, time.ctime(t)
|
||||
# Copy RFC822 header
|
||||
for line in m.headers:
|
||||
print line,
|
||||
# Invent Message-ID header if none is present
|
||||
if not m.has_key('message-id'):
|
||||
global counter
|
||||
counter = counter + 1
|
||||
msgid = "<%s.%d>" % (hex(t), counter)
|
||||
sys.stderr.write("Adding Message-ID %s (From %s)\n" %
|
||||
(msgid, email))
|
||||
print "Message-ID:", msgid
|
||||
print
|
||||
# Copy body
|
||||
while 1:
|
||||
line = f.readline()
|
||||
if line == delimiter:
|
||||
break
|
||||
if not line:
|
||||
sys.stderr.write('Unexpected EOF in message\n')
|
||||
sts = 1
|
||||
break
|
||||
if line[:5] == 'From ':
|
||||
line = '>' + line
|
||||
print line,
|
||||
# Print trailing newline
|
||||
print
|
||||
return sts
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -0,0 +1,138 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
# DAH should be three DOTs.
|
||||
# Space between DOTs and DAHs should be one DOT.
|
||||
# Space between two letters should be one DAH.
|
||||
# Space between two words should be DOT DAH DAH.
|
||||
|
||||
import sys, math, audiodev
|
||||
|
||||
DOT = 30
|
||||
DAH = 3 * DOT
|
||||
OCTAVE = 2 # 1 == 441 Hz, 2 == 882 Hz, ...
|
||||
|
||||
morsetab = {
|
||||
'A': '.-', 'a': '.-',
|
||||
'B': '-...', 'b': '-...',
|
||||
'C': '-.-.', 'c': '-.-.',
|
||||
'D': '-..', 'd': '-..',
|
||||
'E': '.', 'e': '.',
|
||||
'F': '..-.', 'f': '..-.',
|
||||
'G': '--.', 'g': '--.',
|
||||
'H': '....', 'h': '....',
|
||||
'I': '..', 'i': '..',
|
||||
'J': '.---', 'j': '.---',
|
||||
'K': '-.-', 'k': '-.-',
|
||||
'L': '.-..', 'l': '.-..',
|
||||
'M': '--', 'm': '--',
|
||||
'N': '-.', 'n': '-.',
|
||||
'O': '---', 'o': '---',
|
||||
'P': '.--.', 'p': '.--.',
|
||||
'Q': '--.-', 'q': '--.-',
|
||||
'R': '.-.', 'r': '.-.',
|
||||
'S': '...', 's': '...',
|
||||
'T': '-', 't': '-',
|
||||
'U': '..-', 'u': '..-',
|
||||
'V': '...-', 'v': '...-',
|
||||
'W': '.--', 'w': '.--',
|
||||
'X': '-..-', 'x': '-..-',
|
||||
'Y': '-.--', 'y': '-.--',
|
||||
'Z': '--..', 'z': '--..',
|
||||
'0': '-----', ',': '--..--',
|
||||
'1': '.----', '.': '.-.-.-',
|
||||
'2': '..---', '?': '..--..',
|
||||
'3': '...--', ';': '-.-.-.',
|
||||
'4': '....-', ':': '---...',
|
||||
'5': '.....', "'": '.----.',
|
||||
'6': '-....', '-': '-....-',
|
||||
'7': '--...', '/': '-..-.',
|
||||
'8': '---..', '(': '-.--.-',
|
||||
'9': '----.', ')': '-.--.-',
|
||||
' ': ' ', '_': '..--.-',
|
||||
}
|
||||
|
||||
nowave = '\0' * 200
|
||||
|
||||
# If we play at 44.1 kHz (which we do), then if we produce one sine
|
||||
# wave in 100 samples, we get a tone of 441 Hz. If we produce two
|
||||
# sine waves in these 100 samples, we get a tone of 882 Hz. 882 Hz
|
||||
# appears to be a nice one for playing morse code.
|
||||
def mkwave(octave):
|
||||
sinewave = ''
|
||||
for i in range(100):
|
||||
val = int(math.sin(math.pi * i * octave / 50.0) * 30000)
|
||||
sinewave += chr((val >> 8) & 255) + chr(val & 255)
|
||||
return sinewave
|
||||
|
||||
defaultwave = mkwave(OCTAVE)
|
||||
|
||||
def main():
|
||||
import getopt
|
||||
try:
|
||||
opts, args = getopt.getopt(sys.argv[1:], 'o:p:')
|
||||
except getopt.error:
|
||||
sys.stderr.write('Usage ' + sys.argv[0] +
|
||||
' [ -o outfile ] [ -p octave ] [ words ] ...\n')
|
||||
sys.exit(1)
|
||||
dev = None
|
||||
wave = defaultwave
|
||||
for o, a in opts:
|
||||
if o == '-o':
|
||||
import aifc
|
||||
dev = aifc.open(a, 'w')
|
||||
dev.setframerate(44100)
|
||||
dev.setsampwidth(2)
|
||||
dev.setnchannels(1)
|
||||
if o == '-p':
|
||||
wave = mkwave(int(a))
|
||||
if not dev:
|
||||
import audiodev
|
||||
dev = audiodev.AudioDev()
|
||||
dev.setoutrate(44100)
|
||||
dev.setsampwidth(2)
|
||||
dev.setnchannels(1)
|
||||
dev.close = dev.stop
|
||||
dev.writeframesraw = dev.writeframes
|
||||
if args:
|
||||
source = [' '.join(args)]
|
||||
else:
|
||||
source = iter(sys.stdin.readline, '')
|
||||
for line in source:
|
||||
mline = morse(line)
|
||||
play(mline, dev, wave)
|
||||
if hasattr(dev, 'wait'):
|
||||
dev.wait()
|
||||
dev.close()
|
||||
|
||||
# Convert a string to morse code with \001 between the characters in
|
||||
# the string.
|
||||
def morse(line):
|
||||
res = ''
|
||||
for c in line:
|
||||
try:
|
||||
res += morsetab[c] + '\001'
|
||||
except KeyError:
|
||||
pass
|
||||
return res
|
||||
|
||||
# Play a line of morse code.
|
||||
def play(line, dev, wave):
|
||||
for c in line:
|
||||
if c == '.':
|
||||
sine(dev, DOT, wave)
|
||||
elif c == '-':
|
||||
sine(dev, DAH, wave)
|
||||
else: # space
|
||||
pause(dev, DAH + DOT)
|
||||
pause(dev, DOT)
|
||||
|
||||
def sine(dev, length, wave):
|
||||
for i in range(length):
|
||||
dev.writeframesraw(wave)
|
||||
|
||||
def pause(dev, length):
|
||||
for i in range(length):
|
||||
dev.writeframesraw(nowave)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -0,0 +1,59 @@
|
|||
NEWSLIST
|
||||
========
|
||||
A program to assist HTTP browsing of newsgroups
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
WWW browsers such as NCSA Mosaic allow the user to read newsgroup
|
||||
articles by specifying the group name in a URL eg 'news:comp.answers'.
|
||||
|
||||
To browse through many groups, though, (and there are several thousand
|
||||
of them) you really need a page or pages containing links to all the
|
||||
groups. There are some good ones out there, for example,
|
||||
|
||||
http://info.cern.ch/hypertext/DataSources/News/Groups/Overview.html
|
||||
|
||||
is the standard one at CERN, but it only shows the groups available there,
|
||||
which may be rather different from those available on your machine.
|
||||
|
||||
Newslist is a program which creates a hierarchy of pages for you based
|
||||
on the groups available from YOUR server. It is written in python - a
|
||||
splendid interpreted object-oriented language which I suggest you get
|
||||
right now from the directory /pub/python at ftp.cwi.nl, if you haven't
|
||||
already got it.
|
||||
|
||||
You should be able to see some sample output by looking at:
|
||||
http://pelican.cl.cam.ac.uk/newspage/root.html
|
||||
|
||||
Descriptions of newsgroups can be added from a file with one group
|
||||
per line. eg:
|
||||
|
||||
alt.foo Articles about foo
|
||||
comp.bar Programming in 'bar' and related languages
|
||||
|
||||
A suitable list detailing most groups can be found at ftp.uu.net in
|
||||
/uunet-info/newsgroups.gz.
|
||||
|
||||
Make sure you read the information at the beginning of the program source and
|
||||
configure the variables before running.
|
||||
|
||||
In addition to python, you need:
|
||||
|
||||
An NNTP-based news feed.
|
||||
A directory in which to put the pages.
|
||||
|
||||
The programming is not very beautiful, but it works! It comes with no
|
||||
warranty, express or implied, but with the hope that some others may
|
||||
find it useful.
|
||||
|
||||
Comments, improvements & suggestions welcomed.
|
||||
Quentin Stafford-Fraser
|
||||
|
||||
----------------------------------------------------------------------
|
||||
Quentin Stafford-Fraser
|
||||
http://pelican.cl.cam.ac.uk/people/qs101/me.html
|
||||
|
||||
Cambridge University Computer Lab Rank Xerox Cambridge EuroPARC
|
||||
qs101@cl.cam.ac.uk fraser@europarc.xerox.com
|
||||
Tel: +44 223 334411 Tel: +44 223 341521
|
||||
Fax: +44 223 334679 Fax: +44 223 341510
|
||||
----------------------------------------------------------------------
|
|
@ -0,0 +1,362 @@
|
|||
#! /usr/bin/env python
|
||||
#######################################################################
|
||||
# Newslist $Revision$
|
||||
#
|
||||
# Syntax:
|
||||
# newslist [ -a ]
|
||||
#
|
||||
# This is a program to create a directory full of HTML pages
|
||||
# which between them contain links to all the newsgroups available
|
||||
# on your server.
|
||||
#
|
||||
# The -a option causes a complete list of all groups to be read from
|
||||
# the server rather than just the ones which have appeared since last
|
||||
# execution. This recreates the local list from scratch. Use this on
|
||||
# the first invocation of the program, and from time to time thereafter.
|
||||
# When new groups are first created they may appear on your server as
|
||||
# empty groups. By default, empty groups are ignored by the -a option.
|
||||
# However, these new groups will not be created again, and so will not
|
||||
# appear in the server's list of 'new groups' at a later date. Hence it
|
||||
# won't appear until you do a '-a' after some articles have appeared.
|
||||
#
|
||||
# I should really keep a list of ignored empty groups and re-check them
|
||||
# for articles on every run, but I haven't got around to it yet.
|
||||
#
|
||||
# This assumes an NNTP news feed.
|
||||
#
|
||||
# Feel free to copy, distribute and modify this code for
|
||||
# non-commercial use. If you make any useful modifications, let me
|
||||
# know!
|
||||
#
|
||||
# (c) Quentin Stafford-Fraser 1994
|
||||
# fraser@europarc.xerox.com qs101@cl.cam.ac.uk
|
||||
# #
|
||||
#######################################################################
|
||||
import sys, nntplib, marshal, time, os
|
||||
|
||||
#######################################################################
|
||||
# Check these variables before running! #
|
||||
|
||||
# Top directory.
|
||||
# Filenames which don't start with / are taken as being relative to this.
|
||||
topdir = os.path.expanduser('~/newspage')
|
||||
|
||||
# The name of your NNTP host
|
||||
# eg.
|
||||
# newshost = 'nntp-serv.cl.cam.ac.uk'
|
||||
# or use following to get the name from the NNTPSERVER environment
|
||||
# variable:
|
||||
# newshost = os.environ['NNTPSERVER']
|
||||
newshost = 'news.example.com'
|
||||
|
||||
# The filename for a local cache of the newsgroup list
|
||||
treefile = 'grouptree'
|
||||
|
||||
# The filename for descriptions of newsgroups
|
||||
# I found a suitable one at ftp.uu.net in /uunet-info/newgroups.gz
|
||||
# You can set this to '' if you don't wish to use one.
|
||||
descfile = 'newsgroups'
|
||||
|
||||
# The directory in which HTML pages should be created
|
||||
# eg.
|
||||
# pagedir = '/usr/local/lib/html/newspage'
|
||||
# pagedir = 'pages'
|
||||
pagedir = topdir
|
||||
|
||||
# The html prefix which will refer to this directory
|
||||
# eg.
|
||||
# httppref = '/newspage/',
|
||||
# or leave blank for relative links between pages: (Recommended)
|
||||
# httppref = ''
|
||||
httppref = ''
|
||||
|
||||
# The name of the 'root' news page in this directory.
|
||||
# A .html suffix will be added.
|
||||
rootpage = 'root'
|
||||
|
||||
# Set skipempty to 0 if you wish to see links to empty groups as well.
|
||||
# Only affects the -a option.
|
||||
skipempty = 1
|
||||
|
||||
# pagelinkicon can contain html to put an icon after links to
|
||||
# further pages. This helps to make important links stand out.
|
||||
# Set to '' if not wanted, or '...' is quite a good one.
|
||||
pagelinkicon = '... <img src="http://pelican.cl.cam.ac.uk/icons/page.xbm"> '
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# Less important personal preferences:
|
||||
|
||||
# Sublistsize controls the maximum number of items the will appear as
|
||||
# an indented sub-list before the whole thing is moved onto a different
|
||||
# page. The smaller this is, the more pages you will have, but the
|
||||
# shorter each will be.
|
||||
sublistsize = 4
|
||||
|
||||
# That should be all. #
|
||||
#######################################################################
|
||||
|
||||
for dir in os.curdir, os.environ['HOME']:
|
||||
rcfile = os.path.join(dir, '.newslistrc.py')
|
||||
if os.path.exists(rcfile):
|
||||
print rcfile
|
||||
execfile(rcfile)
|
||||
break
|
||||
|
||||
from nntplib import NNTP
|
||||
from stat import *
|
||||
|
||||
rcsrev = '$Revision$'
|
||||
rcsrev = ' '.join(filter(lambda s: '$' not in s, rcsrev.split()))
|
||||
desc = {}
|
||||
|
||||
# Make (possibly) relative filenames into absolute ones
|
||||
treefile = os.path.join(topdir,treefile)
|
||||
descfile = os.path.join(topdir,descfile)
|
||||
page = os.path.join(topdir,pagedir)
|
||||
|
||||
# First the bits for creating trees ---------------------------
|
||||
|
||||
# Addtotree creates/augments a tree from a list of group names
|
||||
def addtotree(tree, groups):
|
||||
print 'Updating tree...'
|
||||
for i in groups:
|
||||
parts = i.split('.')
|
||||
makeleaf(tree, parts)
|
||||
|
||||
# Makeleaf makes a leaf and the branch leading to it if necessary
|
||||
def makeleaf(tree,path):
|
||||
j = path[0]
|
||||
l = len(path)
|
||||
|
||||
if j not in tree:
|
||||
tree[j] = {}
|
||||
if l == 1:
|
||||
tree[j]['.'] = '.'
|
||||
if l > 1:
|
||||
makeleaf(tree[j],path[1:])
|
||||
|
||||
# Then the bits for outputting trees as pages ----------------
|
||||
|
||||
# Createpage creates an HTML file named <root>.html containing links
|
||||
# to those groups beginning with <root>.
|
||||
|
||||
def createpage(root, tree, p):
|
||||
filename = os.path.join(pagedir, root+'.html')
|
||||
if root == rootpage:
|
||||
detail = ''
|
||||
else:
|
||||
detail = ' under ' + root
|
||||
with open(filename, 'w') as f:
|
||||
# f.write('Content-Type: text/html\n')
|
||||
f.write('<html>\n<head>\n')
|
||||
f.write('<title>Newsgroups available%s</title>\n' % detail)
|
||||
f.write('</head>\n<body>\n')
|
||||
f.write('<h1>Newsgroups available%s</h1>\n' % detail)
|
||||
f.write('<a href="%s%s.html">Back to top level</a><p>\n' %
|
||||
(httppref, rootpage))
|
||||
printtree(f, tree, 0, p)
|
||||
f.write('\n<p>')
|
||||
f.write("<i>This page automatically created by 'newslist' v. %s." %
|
||||
rcsrev)
|
||||
f.write(time.ctime(time.time()) + '</i>\n')
|
||||
f.write('</body>\n</html>\n')
|
||||
|
||||
# Printtree prints the groups as a bulleted list. Groups with
|
||||
# more than <sublistsize> subgroups will be put on a separate page.
|
||||
# Other sets of subgroups are just indented.
|
||||
|
||||
def printtree(f, tree, indent, p):
|
||||
l = len(tree)
|
||||
|
||||
if l > sublistsize and indent > 0:
|
||||
# Create a new page and a link to it
|
||||
f.write('<li><b><a href="%s%s.html">' % (httppref, p[1:]))
|
||||
f.write(p[1:] + '.*')
|
||||
f.write('</a></b>%s\n' % pagelinkicon)
|
||||
createpage(p[1:], tree, p)
|
||||
return
|
||||
|
||||
kl = tree.keys()
|
||||
|
||||
if l > 1:
|
||||
kl.sort()
|
||||
if indent > 0:
|
||||
# Create a sub-list
|
||||
f.write('<li>%s\n<ul>' % p[1:])
|
||||
else:
|
||||
# Create a main list
|
||||
f.write('<ul>')
|
||||
indent = indent + 1
|
||||
|
||||
for i in kl:
|
||||
if i == '.':
|
||||
# Output a newsgroup
|
||||
f.write('<li><a href="news:%s">%s</a> ' % (p[1:], p[1:]))
|
||||
if p[1:] in desc:
|
||||
f.write(' <i>%s</i>\n' % desc[p[1:]])
|
||||
else:
|
||||
f.write('\n')
|
||||
else:
|
||||
# Output a hierarchy
|
||||
printtree(f, tree[i], indent, p+'.'+i)
|
||||
|
||||
if l > 1:
|
||||
f.write('\n</ul>')
|
||||
|
||||
# Reading descriptions file ---------------------------------------
|
||||
|
||||
# This returns a dict mapping group name to its description
|
||||
|
||||
def readdesc(descfile):
|
||||
global desc
|
||||
desc = {}
|
||||
|
||||
if descfile == '':
|
||||
return
|
||||
|
||||
try:
|
||||
with open(descfile, 'r') as d:
|
||||
print 'Reading descriptions...'
|
||||
for l in d:
|
||||
bits = l.split()
|
||||
try:
|
||||
grp = bits[0]
|
||||
dsc = ' '.join(bits[1:])
|
||||
if len(dsc) > 1:
|
||||
desc[grp] = dsc
|
||||
except IndexError:
|
||||
pass
|
||||
except IOError:
|
||||
print 'Failed to open description file ' + descfile
|
||||
return
|
||||
|
||||
# Check that ouput directory exists, ------------------------------
|
||||
# and offer to create it if not
|
||||
|
||||
def checkopdir(pagedir):
|
||||
if not os.path.isdir(pagedir):
|
||||
print 'Directory %s does not exist.' % pagedir
|
||||
print 'Shall I create it for you? (y/n)'
|
||||
if sys.stdin.readline()[0] == 'y':
|
||||
try:
|
||||
os.mkdir(pagedir, 0777)
|
||||
except:
|
||||
print 'Sorry - failed!'
|
||||
sys.exit(1)
|
||||
else:
|
||||
print 'OK. Exiting.'
|
||||
sys.exit(1)
|
||||
|
||||
# Read and write current local tree ----------------------------------
|
||||
|
||||
def readlocallist(treefile):
|
||||
print 'Reading current local group list...'
|
||||
tree = {}
|
||||
try:
|
||||
treetime = time.localtime(os.stat(treefile)[ST_MTIME])
|
||||
except:
|
||||
print '\n*** Failed to open local group cache '+treefile
|
||||
print 'If this is the first time you have run newslist, then'
|
||||
print 'use the -a option to create it.'
|
||||
sys.exit(1)
|
||||
treedate = '%02d%02d%02d' % (treetime[0] % 100, treetime[1], treetime[2])
|
||||
try:
|
||||
with open(treefile, 'rb') as dump:
|
||||
tree = marshal.load(dump)
|
||||
except IOError:
|
||||
print 'Cannot open local group list ' + treefile
|
||||
return (tree, treedate)
|
||||
|
||||
def writelocallist(treefile, tree):
|
||||
try:
|
||||
with open(treefile, 'wb') as dump:
|
||||
groups = marshal.dump(tree, dump)
|
||||
print 'Saved list to %s\n' % treefile
|
||||
except:
|
||||
print 'Sorry - failed to write to local group cache', treefile
|
||||
print 'Does it (or its directory) have the correct permissions?'
|
||||
sys.exit(1)
|
||||
|
||||
# Return list of all groups on server -----------------------------
|
||||
|
||||
def getallgroups(server):
|
||||
print 'Getting list of all groups...'
|
||||
treedate = '010101'
|
||||
info = server.list()[1]
|
||||
groups = []
|
||||
print 'Processing...'
|
||||
if skipempty:
|
||||
print '\nIgnoring following empty groups:'
|
||||
for i in info:
|
||||
grpname = i[0].split()[0]
|
||||
if skipempty and int(i[1]) < int(i[2]):
|
||||
print grpname + ' ',
|
||||
else:
|
||||
groups.append(grpname)
|
||||
print '\n'
|
||||
if skipempty:
|
||||
print '(End of empty groups)'
|
||||
return groups
|
||||
|
||||
# Return list of new groups on server -----------------------------
|
||||
|
||||
def getnewgroups(server, treedate):
|
||||
print 'Getting list of new groups since start of %s...' % treedate,
|
||||
info = server.newgroups(treedate, '000001')[1]
|
||||
print 'got %d.' % len(info)
|
||||
print 'Processing...',
|
||||
groups = []
|
||||
for i in info:
|
||||
grpname = i.split()[0]
|
||||
groups.append(grpname)
|
||||
print 'Done'
|
||||
return groups
|
||||
|
||||
# Now the main program --------------------------------------------
|
||||
|
||||
def main():
|
||||
tree = {}
|
||||
|
||||
# Check that the output directory exists
|
||||
checkopdir(pagedir)
|
||||
|
||||
try:
|
||||
print 'Connecting to %s...' % newshost
|
||||
if sys.version[0] == '0':
|
||||
s = NNTP.init(newshost)
|
||||
else:
|
||||
s = NNTP(newshost)
|
||||
connected = True
|
||||
except (nntplib.error_temp, nntplib.error_perm), x:
|
||||
print 'Error connecting to host:', x
|
||||
print 'I\'ll try to use just the local list.'
|
||||
connected = False
|
||||
|
||||
# If -a is specified, read the full list of groups from server
|
||||
if connected and len(sys.argv) > 1 and sys.argv[1] == '-a':
|
||||
groups = getallgroups(s)
|
||||
|
||||
# Otherwise just read the local file and then add
|
||||
# groups created since local file last modified.
|
||||
else:
|
||||
|
||||
(tree, treedate) = readlocallist(treefile)
|
||||
if connected:
|
||||
groups = getnewgroups(s, treedate)
|
||||
|
||||
if connected:
|
||||
addtotree(tree, groups)
|
||||
writelocallist(treefile,tree)
|
||||
|
||||
# Read group descriptions
|
||||
readdesc(descfile)
|
||||
|
||||
print 'Creating pages...'
|
||||
createpage(rootpage, tree, '')
|
||||
print 'Done'
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
# That's all folks
|
||||
######################################################################
|
|
@ -0,0 +1,33 @@
|
|||
#! /usr/bin/env 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 = 2, 4, 1, 12, 4
|
||||
while True:
|
||||
# Next approximation
|
||||
p, q, k = k*k, 2*k+1, k+1
|
||||
a, b, a1, b1 = a1, b1, p*a+q*a1, p*b+q*b1
|
||||
# Print common digits
|
||||
d, d1 = a//b, a1//b1
|
||||
while d == d1:
|
||||
output(d)
|
||||
a, a1 = 10*(a%b), 10*(a1%b1)
|
||||
d, d1 = a//b, a1//b1
|
||||
|
||||
def output(d):
|
||||
# Use write() to avoid spaces between the digits
|
||||
sys.stdout.write(str(d))
|
||||
# Flush so the output is seen immediately
|
||||
sys.stdout.flush()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -0,0 +1,129 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
# Emulate some Perl command line options.
|
||||
# Usage: pp [-a] [-c] [-d] [-e scriptline] [-F fieldsep] [-n] [-p] [file] ...
|
||||
# Where the options mean the following:
|
||||
# -a : together with -n or -p, splits each line into list F
|
||||
# -c : check syntax only, do not execute any code
|
||||
# -d : run the script under the debugger, pdb
|
||||
# -e scriptline : gives one line of the Python script; may be repeated
|
||||
# -F fieldsep : sets the field separator for the -a option [not in Perl]
|
||||
# -n : runs the script for each line of input
|
||||
# -p : prints the line after the script has run
|
||||
# When no script lines have been passed, the first file argument
|
||||
# contains the script. With -n or -p, the remaining arguments are
|
||||
# read as input to the script, line by line. If a file is '-'
|
||||
# or missing, standard input is read.
|
||||
|
||||
# XXX To do:
|
||||
# - add -i extension option (change files in place)
|
||||
# - make a single loop over the files and lines (changes effect of 'break')?
|
||||
# - add an option to specify the record separator
|
||||
# - except for -n/-p, run directly from the file if at all possible
|
||||
|
||||
import sys
|
||||
import getopt
|
||||
|
||||
FS = ''
|
||||
SCRIPT = []
|
||||
AFLAG = 0
|
||||
CFLAG = 0
|
||||
DFLAG = 0
|
||||
NFLAG = 0
|
||||
PFLAG = 0
|
||||
|
||||
try:
|
||||
optlist, ARGS = getopt.getopt(sys.argv[1:], 'acde:F:np')
|
||||
except getopt.error, msg:
|
||||
sys.stderr.write('%s: %s\n' % (sys.argv[0], msg))
|
||||
sys.exit(2)
|
||||
|
||||
for option, optarg in optlist:
|
||||
if option == '-a':
|
||||
AFLAG = 1
|
||||
elif option == '-c':
|
||||
CFLAG = 1
|
||||
elif option == '-d':
|
||||
DFLAG = 1
|
||||
elif option == '-e':
|
||||
for line in optarg.split('\n'):
|
||||
SCRIPT.append(line)
|
||||
elif option == '-F':
|
||||
FS = optarg
|
||||
elif option == '-n':
|
||||
NFLAG = 1
|
||||
PFLAG = 0
|
||||
elif option == '-p':
|
||||
NFLAG = 1
|
||||
PFLAG = 1
|
||||
else:
|
||||
print option, 'not recognized???'
|
||||
|
||||
if not ARGS: ARGS.append('-')
|
||||
|
||||
if not SCRIPT:
|
||||
if ARGS[0] == '-':
|
||||
fp = sys.stdin
|
||||
else:
|
||||
fp = open(ARGS[0], 'r')
|
||||
while 1:
|
||||
line = fp.readline()
|
||||
if not line: break
|
||||
SCRIPT.append(line[:-1])
|
||||
del fp
|
||||
del ARGS[0]
|
||||
if not ARGS: ARGS.append('-')
|
||||
|
||||
if CFLAG:
|
||||
prologue = ['if 0:']
|
||||
epilogue = []
|
||||
elif NFLAG:
|
||||
# Note that it is on purpose that AFLAG and PFLAG are
|
||||
# tested dynamically each time through the loop
|
||||
prologue = [
|
||||
'LINECOUNT = 0',
|
||||
'for FILE in ARGS:',
|
||||
' \tif FILE == \'-\':',
|
||||
' \t \tFP = sys.stdin',
|
||||
' \telse:',
|
||||
' \t \tFP = open(FILE, \'r\')',
|
||||
' \tLINENO = 0',
|
||||
' \twhile 1:',
|
||||
' \t \tLINE = FP.readline()',
|
||||
' \t \tif not LINE: break',
|
||||
' \t \tLINENO = LINENO + 1',
|
||||
' \t \tLINECOUNT = LINECOUNT + 1',
|
||||
' \t \tL = LINE[:-1]',
|
||||
' \t \taflag = AFLAG',
|
||||
' \t \tif aflag:',
|
||||
' \t \t \tif FS: F = L.split(FS)',
|
||||
' \t \t \telse: F = L.split()'
|
||||
]
|
||||
epilogue = [
|
||||
' \t \tif not PFLAG: continue',
|
||||
' \t \tif aflag:',
|
||||
' \t \t \tif FS: print FS.join(F)',
|
||||
' \t \t \telse: print \' \'.join(F)',
|
||||
' \t \telse: print L',
|
||||
]
|
||||
else:
|
||||
prologue = ['if 1:']
|
||||
epilogue = []
|
||||
|
||||
# Note that we indent using tabs only, so that any indentation style
|
||||
# used in 'command' will come out right after re-indentation.
|
||||
|
||||
program = '\n'.join(prologue) + '\n'
|
||||
for line in SCRIPT:
|
||||
program += ' \t \t' + line + '\n'
|
||||
program += '\n'.join(epilogue) + '\n'
|
||||
|
||||
import tempfile
|
||||
fp = tempfile.NamedTemporaryFile()
|
||||
fp.write(program)
|
||||
fp.flush()
|
||||
if DFLAG:
|
||||
import pdb
|
||||
pdb.run('execfile(%r)' % (fp.name,))
|
||||
else:
|
||||
execfile(fp.name)
|
|
@ -0,0 +1,30 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
# Print prime numbers in a given range
|
||||
|
||||
def primes(min, max):
|
||||
if max >= 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 += 2
|
||||
|
||||
def main():
|
||||
import sys
|
||||
min, max = 2, 0x7fffffff
|
||||
if sys.argv[1:]:
|
||||
min = int(sys.argv[1])
|
||||
if sys.argv[2:]:
|
||||
max = int(sys.argv[2])
|
||||
primes(min, max)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -0,0 +1,85 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
"""N queens problem.
|
||||
|
||||
The (well-known) problem is due to Niklaus Wirth.
|
||||
|
||||
This solution is inspired by Dijkstra (Structured Programming). It is
|
||||
a classic recursive backtracking approach.
|
||||
|
||||
"""
|
||||
|
||||
N = 8 # Default; command line overrides
|
||||
|
||||
class Queens:
|
||||
|
||||
def __init__(self, n=N):
|
||||
self.n = n
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
n = self.n
|
||||
self.y = [None] * n # Where is the queen in column x
|
||||
self.row = [0] * n # Is row[y] safe?
|
||||
self.up = [0] * (2*n-1) # Is upward diagonal[x-y] safe?
|
||||
self.down = [0] * (2*n-1) # Is downward diagonal[x+y] safe?
|
||||
self.nfound = 0 # Instrumentation
|
||||
|
||||
def solve(self, x=0): # Recursive solver
|
||||
for y in range(self.n):
|
||||
if self.safe(x, y):
|
||||
self.place(x, y)
|
||||
if x+1 == self.n:
|
||||
self.display()
|
||||
else:
|
||||
self.solve(x+1)
|
||||
self.remove(x, y)
|
||||
|
||||
def safe(self, x, y):
|
||||
return not self.row[y] and not self.up[x-y] and not self.down[x+y]
|
||||
|
||||
def place(self, x, y):
|
||||
self.y[x] = y
|
||||
self.row[y] = 1
|
||||
self.up[x-y] = 1
|
||||
self.down[x+y] = 1
|
||||
|
||||
def remove(self, x, y):
|
||||
self.y[x] = None
|
||||
self.row[y] = 0
|
||||
self.up[x-y] = 0
|
||||
self.down[x+y] = 0
|
||||
|
||||
silent = 0 # If true, count solutions only
|
||||
|
||||
def display(self):
|
||||
self.nfound = self.nfound + 1
|
||||
if self.silent:
|
||||
return
|
||||
print '+-' + '--'*self.n + '+'
|
||||
for y in range(self.n-1, -1, -1):
|
||||
print '|',
|
||||
for x in range(self.n):
|
||||
if self.y[x] == y:
|
||||
print "Q",
|
||||
else:
|
||||
print ".",
|
||||
print '|'
|
||||
print '+-' + '--'*self.n + '+'
|
||||
|
||||
def main():
|
||||
import sys
|
||||
silent = 0
|
||||
n = N
|
||||
if sys.argv[1:2] == ['-n']:
|
||||
silent = 1
|
||||
del sys.argv[1]
|
||||
if sys.argv[1:]:
|
||||
n = int(sys.argv[1])
|
||||
q = Queens(n)
|
||||
q.silent = silent
|
||||
q.solve()
|
||||
print "Found", q.nfound, "solutions."
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -0,0 +1,42 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
# script.py -- Make typescript of terminal session.
|
||||
# Usage:
|
||||
# -a Append to typescript.
|
||||
# -p Use Python as shell.
|
||||
# Author: Steen Lumholt.
|
||||
|
||||
|
||||
import os, time, sys, getopt
|
||||
import pty
|
||||
|
||||
def read(fd):
|
||||
data = os.read(fd, 1024)
|
||||
script.write(data)
|
||||
return data
|
||||
|
||||
shell = 'sh'
|
||||
filename = 'typescript'
|
||||
mode = 'w'
|
||||
if os.environ.has_key('SHELL'):
|
||||
shell = os.environ['SHELL']
|
||||
|
||||
try:
|
||||
opts, args = getopt.getopt(sys.argv[1:], 'ap')
|
||||
except getopt.error, msg:
|
||||
print '%s: %s' % (sys.argv[0], msg)
|
||||
sys.exit(2)
|
||||
|
||||
for o, a in opts:
|
||||
if o == '-a':
|
||||
mode = 'a'
|
||||
elif o == '-p':
|
||||
shell = 'python'
|
||||
|
||||
script = open(filename, mode)
|
||||
|
||||
sys.stdout.write('Script started, file is %s\n' % filename)
|
||||
script.write('Script started on %s\n' % time.ctime(time.time()))
|
||||
pty.spawn(shell, read)
|
||||
script.write('Script done on %s\n' % time.ctime(time.time()))
|
||||
sys.stdout.write('Script done, file is %s\n' % filename)
|
|
@ -0,0 +1,104 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
# Calculate your unbirthday count (see Alice in Wonderland).
|
||||
# This is defined as the number of days from your birth until today
|
||||
# that weren't your birthday. (The day you were born is not counted).
|
||||
# Leap years make it interesting.
|
||||
|
||||
import sys
|
||||
import time
|
||||
import calendar
|
||||
|
||||
def main():
|
||||
if sys.argv[1:]:
|
||||
year = int(sys.argv[1])
|
||||
else:
|
||||
year = int(raw_input('In which year were you born? '))
|
||||
if 0 <= year < 100:
|
||||
print "I'll assume that by", year,
|
||||
year = year + 1900
|
||||
print 'you mean', year, 'and not the early Christian era'
|
||||
elif not (1850 <= year <= time.localtime()[0]):
|
||||
print "It's hard to believe you were born in", year
|
||||
return
|
||||
|
||||
if sys.argv[2:]:
|
||||
month = int(sys.argv[2])
|
||||
else:
|
||||
month = int(raw_input('And in which month? (1-12) '))
|
||||
if not (1 <= month <= 12):
|
||||
print 'There is no month numbered', month
|
||||
return
|
||||
|
||||
if sys.argv[3:]:
|
||||
day = int(sys.argv[3])
|
||||
else:
|
||||
day = int(raw_input('And on what day of that month? (1-31) '))
|
||||
if month == 2 and calendar.isleap(year):
|
||||
maxday = 29
|
||||
else:
|
||||
maxday = calendar.mdays[month]
|
||||
if not (1 <= day <= maxday):
|
||||
print 'There are no', day, 'days in that month!'
|
||||
return
|
||||
|
||||
bdaytuple = (year, month, day)
|
||||
bdaydate = mkdate(bdaytuple)
|
||||
print 'You were born on', format(bdaytuple)
|
||||
|
||||
todaytuple = time.localtime()[:3]
|
||||
todaydate = mkdate(todaytuple)
|
||||
print 'Today is', format(todaytuple)
|
||||
|
||||
if bdaytuple > todaytuple:
|
||||
print 'You are a time traveler. Go back to the future!'
|
||||
return
|
||||
|
||||
if bdaytuple == todaytuple:
|
||||
print 'You were born today. Have a nice life!'
|
||||
return
|
||||
|
||||
days = todaydate - bdaydate
|
||||
print 'You have lived', days, 'days'
|
||||
|
||||
age = 0
|
||||
for y in range(year, todaytuple[0] + 1):
|
||||
if bdaytuple < (y, month, day) <= todaytuple:
|
||||
age = age + 1
|
||||
|
||||
print 'You are', age, 'years old'
|
||||
|
||||
if todaytuple[1:] == bdaytuple[1:]:
|
||||
print 'Congratulations! Today is your', nth(age), 'birthday'
|
||||
print 'Yesterday was your',
|
||||
else:
|
||||
print 'Today is your',
|
||||
print nth(days - age), 'unbirthday'
|
||||
|
||||
def format((year, month, day)):
|
||||
return '%d %s %d' % (day, calendar.month_name[month], year)
|
||||
|
||||
def nth(n):
|
||||
if n == 1: return '1st'
|
||||
if n == 2: return '2nd'
|
||||
if n == 3: return '3rd'
|
||||
return '%dth' % n
|
||||
|
||||
def mkdate((year, month, day)):
|
||||
# January 1st, in 0 A.D. is arbitrarily defined to be day 1,
|
||||
# even though that day never actually existed and the calendar
|
||||
# was different then...
|
||||
days = year*365 # years, roughly
|
||||
days = days + (year+3)//4 # plus leap years, roughly
|
||||
days = days - (year+99)//100 # minus non-leap years every century
|
||||
days = days + (year+399)//400 # plus leap years every 4 centirues
|
||||
for i in range(1, month):
|
||||
if i == 2 and calendar.isleap(year):
|
||||
days = days + 29
|
||||
else:
|
||||
days = days + calendar.mdays[i]
|
||||
days = days + day
|
||||
return days
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -0,0 +1,92 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
# Update a bunch of files according to a script.
|
||||
# The input file contains lines of the form <filename>:<lineno>:<text>,
|
||||
# meaning that the given line of the given file is to be replaced
|
||||
# by the given text. This is useful for performing global substitutions
|
||||
# on grep output:
|
||||
|
||||
import os
|
||||
import sys
|
||||
import re
|
||||
|
||||
pat = '^([^: \t\n]+):([1-9][0-9]*):'
|
||||
prog = re.compile(pat)
|
||||
|
||||
class FileObj:
|
||||
def __init__(self, filename):
|
||||
self.filename = filename
|
||||
self.changed = 0
|
||||
try:
|
||||
self.lines = open(filename, 'r').readlines()
|
||||
except IOError, msg:
|
||||
print '*** Can\'t open "%s":' % filename, msg
|
||||
self.lines = None
|
||||
return
|
||||
print 'diffing', self.filename
|
||||
|
||||
def finish(self):
|
||||
if not self.changed:
|
||||
print 'no changes to', self.filename
|
||||
return
|
||||
try:
|
||||
os.rename(self.filename, self.filename + '~')
|
||||
fp = open(self.filename, 'w')
|
||||
except (os.error, IOError), msg:
|
||||
print '*** Can\'t rewrite "%s":' % self.filename, msg
|
||||
return
|
||||
print 'writing', self.filename
|
||||
for line in self.lines:
|
||||
fp.write(line)
|
||||
fp.close()
|
||||
self.changed = 0
|
||||
|
||||
def process(self, lineno, rest):
|
||||
if self.lines is None:
|
||||
print '(not processed): %s:%s:%s' % (
|
||||
self.filename, lineno, rest),
|
||||
return
|
||||
i = eval(lineno) - 1
|
||||
if not 0 <= i < len(self.lines):
|
||||
print '*** Line number out of range: %s:%s:%s' % (
|
||||
self.filename, lineno, rest),
|
||||
return
|
||||
if self.lines[i] == rest:
|
||||
print '(no change): %s:%s:%s' % (
|
||||
self.filename, lineno, rest),
|
||||
return
|
||||
if not self.changed:
|
||||
self.changed = 1
|
||||
print '%sc%s' % (lineno, lineno)
|
||||
print '<', self.lines[i],
|
||||
print '---'
|
||||
self.lines[i] = rest
|
||||
print '>', self.lines[i],
|
||||
|
||||
def main():
|
||||
if sys.argv[1:]:
|
||||
try:
|
||||
fp = open(sys.argv[1], 'r')
|
||||
except IOError, msg:
|
||||
print 'Can\'t open "%s":' % sys.argv[1], msg
|
||||
sys.exit(1)
|
||||
else:
|
||||
fp = sys.stdin
|
||||
curfile = None
|
||||
while 1:
|
||||
line = fp.readline()
|
||||
if not line:
|
||||
if curfile: curfile.finish()
|
||||
break
|
||||
n = prog.match(line)
|
||||
if n < 0:
|
||||
print 'Funny line:', line,
|
||||
continue
|
||||
filename, lineno = prog.group(1, 2)
|
||||
if not curfile or filename <> curfile.filename:
|
||||
if curfile: curfile.finish()
|
||||
curfile = FileObj(filename)
|
||||
curfile.process(lineno, line[n:])
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -0,0 +1,14 @@
|
|||
This directory contains some demonstrations of the socket module:
|
||||
|
||||
broadcast.py Broadcast the time to radio.py.
|
||||
echosvr.py About the simplest TCP server possible.
|
||||
finger.py Client for the 'finger' protocol.
|
||||
ftp.py A very simple ftp client.
|
||||
gopher.py A simple gopher client.
|
||||
mcast.py IPv4/v6 multicast example
|
||||
radio.py Receive time broadcasts from broadcast.py.
|
||||
telnet.py Client for the 'telnet' protocol.
|
||||
throughput.py Client and server to measure TCP throughput.
|
||||
unixclient.py Unix socket example, client side
|
||||
unixserver.py Unix socket example, server side
|
||||
udpecho.py Client and server for the UDP echo protocol.
|
|
@ -0,0 +1,15 @@
|
|||
# Send UDP broadcast packets
|
||||
|
||||
MYPORT = 50000
|
||||
|
||||
import sys, time
|
||||
from socket import *
|
||||
|
||||
s = socket(AF_INET, SOCK_DGRAM)
|
||||
s.bind(('', 0))
|
||||
s.setsockopt(SOL_SOCKET, SO_BROADCAST, 1)
|
||||
|
||||
while 1:
|
||||
data = repr(time.time()) + '\n'
|
||||
s.sendto(data, ('<broadcast>', MYPORT))
|
||||
time.sleep(2)
|
|
@ -0,0 +1,31 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
# Python implementation of an 'echo' tcp server: echo all data it receives.
|
||||
#
|
||||
# This is the simplest possible server, servicing a single request only.
|
||||
|
||||
import sys
|
||||
from socket import *
|
||||
|
||||
# The standard echo port isn't very useful, it requires root permissions!
|
||||
# ECHO_PORT = 7
|
||||
ECHO_PORT = 50000 + 7
|
||||
BUFSIZE = 1024
|
||||
|
||||
def main():
|
||||
if len(sys.argv) > 1:
|
||||
port = int(eval(sys.argv[1]))
|
||||
else:
|
||||
port = ECHO_PORT
|
||||
s = socket(AF_INET, SOCK_STREAM)
|
||||
s.bind(('', port))
|
||||
s.listen(1)
|
||||
conn, (remotehost, remoteport) = s.accept()
|
||||
print 'connected by', remotehost, remoteport
|
||||
while 1:
|
||||
data = conn.recv(BUFSIZE)
|
||||
if not data:
|
||||
break
|
||||
conn.send(data)
|
||||
|
||||
main()
|
|
@ -0,0 +1,58 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
# Python interface to the Internet finger daemon.
|
||||
#
|
||||
# Usage: finger [options] [user][@host] ...
|
||||
#
|
||||
# If no host is given, the finger daemon on the local host is contacted.
|
||||
# Options are passed uninterpreted to the finger daemon!
|
||||
|
||||
|
||||
import sys, string
|
||||
from socket import *
|
||||
|
||||
|
||||
# Hardcode the number of the finger port here.
|
||||
# It's not likely to change soon...
|
||||
#
|
||||
FINGER_PORT = 79
|
||||
|
||||
|
||||
# Function to do one remote finger invocation.
|
||||
# Output goes directly to stdout (although this can be changed).
|
||||
#
|
||||
def finger(host, args):
|
||||
s = socket(AF_INET, SOCK_STREAM)
|
||||
s.connect((host, FINGER_PORT))
|
||||
s.send(args + '\n')
|
||||
while 1:
|
||||
buf = s.recv(1024)
|
||||
if not buf: break
|
||||
sys.stdout.write(buf)
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
# Main function: argument parsing.
|
||||
#
|
||||
def main():
|
||||
options = ''
|
||||
i = 1
|
||||
while i < len(sys.argv) and sys.argv[i][:1] == '-':
|
||||
options = options + sys.argv[i] + ' '
|
||||
i = i+1
|
||||
args = sys.argv[i:]
|
||||
if not args:
|
||||
args = ['']
|
||||
for arg in args:
|
||||
if '@' in arg:
|
||||
at = string.index(arg, '@')
|
||||
host = arg[at+1:]
|
||||
arg = arg[:at]
|
||||
else:
|
||||
host = ''
|
||||
finger(host, options + arg)
|
||||
|
||||
|
||||
# Call the main function.
|
||||
#
|
||||
main()
|
|
@ -0,0 +1,146 @@
|
|||
# A simple FTP client.
|
||||
#
|
||||
# The information to write this program was gathered from RFC 959,
|
||||
# but this is not a complete implementation! Yet it shows how a simple
|
||||
# FTP client can be built, and you are welcome to extend it to suit
|
||||
# it to your needs...
|
||||
#
|
||||
# How it works (assuming you've read the RFC):
|
||||
#
|
||||
# User commands are passed uninterpreted to the server. However, the
|
||||
# user never needs to send a PORT command. Rather, the client opens a
|
||||
# port right away and sends the appropriate PORT command to the server.
|
||||
# When a response code 150 is received, this port is used to receive
|
||||
# the data (which is written to stdout in this version), and when the
|
||||
# data is exhausted, a new port is opened and a corresponding PORT
|
||||
# command sent. In order to avoid errors when reusing ports quickly
|
||||
# (and because there is no s.getsockname() method in Python yet) we
|
||||
# cycle through a number of ports in the 50000 range.
|
||||
|
||||
|
||||
import sys, posix, string
|
||||
from socket import *
|
||||
|
||||
|
||||
BUFSIZE = 1024
|
||||
|
||||
# Default port numbers used by the FTP protocol.
|
||||
#
|
||||
FTP_PORT = 21
|
||||
FTP_DATA_PORT = FTP_PORT - 1
|
||||
|
||||
# Change the data port to something not needing root permissions.
|
||||
#
|
||||
FTP_DATA_PORT = FTP_DATA_PORT + 50000
|
||||
|
||||
|
||||
# Main program (called at the end of this file).
|
||||
#
|
||||
def main():
|
||||
hostname = sys.argv[1]
|
||||
control(hostname)
|
||||
|
||||
|
||||
# Control process (user interface and user protocol interpreter).
|
||||
#
|
||||
def control(hostname):
|
||||
#
|
||||
# Create control connection
|
||||
#
|
||||
s = socket(AF_INET, SOCK_STREAM)
|
||||
s.connect((hostname, FTP_PORT))
|
||||
f = s.makefile('r') # Reading the replies is easier from a file...
|
||||
#
|
||||
# Control loop
|
||||
#
|
||||
r = None
|
||||
while 1:
|
||||
code = getreply(f)
|
||||
if code in ('221', 'EOF'): break
|
||||
if code == '150':
|
||||
getdata(r)
|
||||
code = getreply(f)
|
||||
r = None
|
||||
if not r:
|
||||
r = newdataport(s, f)
|
||||
cmd = getcommand()
|
||||
if not cmd: break
|
||||
s.send(cmd + '\r\n')
|
||||
|
||||
|
||||
# Create a new data port and send a PORT command to the server for it.
|
||||
# (Cycle through a number of ports to avoid problems with reusing
|
||||
# a port within a short time.)
|
||||
#
|
||||
nextport = 0
|
||||
#
|
||||
def newdataport(s, f):
|
||||
global nextport
|
||||
port = nextport + FTP_DATA_PORT
|
||||
nextport = (nextport+1) % 16
|
||||
r = socket(AF_INET, SOCK_STREAM)
|
||||
r.bind((gethostbyname(gethostname()), port))
|
||||
r.listen(1)
|
||||
sendportcmd(s, f, port)
|
||||
return r
|
||||
|
||||
|
||||
# Send an appropriate port command.
|
||||
#
|
||||
def sendportcmd(s, f, port):
|
||||
hostname = gethostname()
|
||||
hostaddr = gethostbyname(hostname)
|
||||
hbytes = string.splitfields(hostaddr, '.')
|
||||
pbytes = [repr(port//256), repr(port%256)]
|
||||
bytes = hbytes + pbytes
|
||||
cmd = 'PORT ' + string.joinfields(bytes, ',')
|
||||
s.send(cmd + '\r\n')
|
||||
code = getreply(f)
|
||||
|
||||
|
||||
# Process an ftp reply and return the 3-digit reply code (as a string).
|
||||
# The reply should be a line of text starting with a 3-digit number.
|
||||
# If the 4th char is '-', it is a multi-line reply and is
|
||||
# terminate by a line starting with the same 3-digit number.
|
||||
# Any text while receiving the reply is echoed to the file.
|
||||
#
|
||||
def getreply(f):
|
||||
line = f.readline()
|
||||
if not line: return 'EOF'
|
||||
print line,
|
||||
code = line[:3]
|
||||
if line[3:4] == '-':
|
||||
while 1:
|
||||
line = f.readline()
|
||||
if not line: break # Really an error
|
||||
print line,
|
||||
if line[:3] == code and line[3:4] != '-': break
|
||||
return code
|
||||
|
||||
|
||||
# Get the data from the data connection.
|
||||
#
|
||||
def getdata(r):
|
||||
print '(accepting data connection)'
|
||||
conn, host = r.accept()
|
||||
print '(data connection accepted)'
|
||||
while 1:
|
||||
data = conn.recv(BUFSIZE)
|
||||
if not data: break
|
||||
sys.stdout.write(data)
|
||||
print '(end of data connection)'
|
||||
|
||||
# Get a command from the user.
|
||||
#
|
||||
def getcommand():
|
||||
try:
|
||||
while 1:
|
||||
line = raw_input('ftp.py> ')
|
||||
if line: return line
|
||||
except EOFError:
|
||||
return ''
|
||||
|
||||
|
||||
# Call the main program.
|
||||
#
|
||||
main()
|
|
@ -0,0 +1,347 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
# A simple gopher client.
|
||||
#
|
||||
# Usage: gopher [ [selector] host [port] ]
|
||||
|
||||
import string
|
||||
import sys
|
||||
import os
|
||||
import socket
|
||||
|
||||
# Default selector, host and port
|
||||
DEF_SELECTOR = ''
|
||||
DEF_HOST = 'gopher.micro.umn.edu'
|
||||
DEF_PORT = 70
|
||||
|
||||
# Recognized file types
|
||||
T_TEXTFILE = '0'
|
||||
T_MENU = '1'
|
||||
T_CSO = '2'
|
||||
T_ERROR = '3'
|
||||
T_BINHEX = '4'
|
||||
T_DOS = '5'
|
||||
T_UUENCODE = '6'
|
||||
T_SEARCH = '7'
|
||||
T_TELNET = '8'
|
||||
T_BINARY = '9'
|
||||
T_REDUNDANT = '+'
|
||||
T_SOUND = 's'
|
||||
|
||||
# Dictionary mapping types to strings
|
||||
typename = {'0': '<TEXT>', '1': '<DIR>', '2': '<CSO>', '3': '<ERROR>', \
|
||||
'4': '<BINHEX>', '5': '<DOS>', '6': '<UUENCODE>', '7': '<SEARCH>', \
|
||||
'8': '<TELNET>', '9': '<BINARY>', '+': '<REDUNDANT>', 's': '<SOUND>'}
|
||||
|
||||
# Oft-used characters and strings
|
||||
CRLF = '\r\n'
|
||||
TAB = '\t'
|
||||
|
||||
# Open a TCP connection to a given host and port
|
||||
def open_socket(host, port):
|
||||
if not port:
|
||||
port = DEF_PORT
|
||||
elif type(port) == type(''):
|
||||
port = string.atoi(port)
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.connect((host, port))
|
||||
return s
|
||||
|
||||
# Send a selector to a given host and port, return a file with the reply
|
||||
def send_request(selector, host, port):
|
||||
s = open_socket(host, port)
|
||||
s.send(selector + CRLF)
|
||||
s.shutdown(1)
|
||||
return s.makefile('r')
|
||||
|
||||
# Get a menu in the form of a list of entries
|
||||
def get_menu(selector, host, port):
|
||||
f = send_request(selector, host, port)
|
||||
list = []
|
||||
while 1:
|
||||
line = f.readline()
|
||||
if not line:
|
||||
print '(Unexpected EOF from server)'
|
||||
break
|
||||
if line[-2:] == CRLF:
|
||||
line = line[:-2]
|
||||
elif line[-1:] in CRLF:
|
||||
line = line[:-1]
|
||||
if line == '.':
|
||||
break
|
||||
if not line:
|
||||
print '(Empty line from server)'
|
||||
continue
|
||||
typechar = line[0]
|
||||
parts = string.splitfields(line[1:], TAB)
|
||||
if len(parts) < 4:
|
||||
print '(Bad line from server: %r)' % (line,)
|
||||
continue
|
||||
if len(parts) > 4:
|
||||
print '(Extra info from server: %r)' % (parts[4:],)
|
||||
parts.insert(0, typechar)
|
||||
list.append(parts)
|
||||
f.close()
|
||||
return list
|
||||
|
||||
# Get a text file as a list of lines, with trailing CRLF stripped
|
||||
def get_textfile(selector, host, port):
|
||||
list = []
|
||||
get_alt_textfile(selector, host, port, list.append)
|
||||
return list
|
||||
|
||||
# Get a text file and pass each line to a function, with trailing CRLF stripped
|
||||
def get_alt_textfile(selector, host, port, func):
|
||||
f = send_request(selector, host, port)
|
||||
while 1:
|
||||
line = f.readline()
|
||||
if not line:
|
||||
print '(Unexpected EOF from server)'
|
||||
break
|
||||
if line[-2:] == CRLF:
|
||||
line = line[:-2]
|
||||
elif line[-1:] in CRLF:
|
||||
line = line[:-1]
|
||||
if line == '.':
|
||||
break
|
||||
if line[:2] == '..':
|
||||
line = line[1:]
|
||||
func(line)
|
||||
f.close()
|
||||
|
||||
# Get a binary file as one solid data block
|
||||
def get_binary(selector, host, port):
|
||||
f = send_request(selector, host, port)
|
||||
data = f.read()
|
||||
f.close()
|
||||
return data
|
||||
|
||||
# Get a binary file and pass each block to a function
|
||||
def get_alt_binary(selector, host, port, func, blocksize):
|
||||
f = send_request(selector, host, port)
|
||||
while 1:
|
||||
data = f.read(blocksize)
|
||||
if not data:
|
||||
break
|
||||
func(data)
|
||||
|
||||
# A *very* simple interactive browser
|
||||
|
||||
# Browser main command, has default arguments
|
||||
def browser(*args):
|
||||
selector = DEF_SELECTOR
|
||||
host = DEF_HOST
|
||||
port = DEF_PORT
|
||||
n = len(args)
|
||||
if n > 0 and args[0]:
|
||||
selector = args[0]
|
||||
if n > 1 and args[1]:
|
||||
host = args[1]
|
||||
if n > 2 and args[2]:
|
||||
port = args[2]
|
||||
if n > 3:
|
||||
raise RuntimeError, 'too many args'
|
||||
try:
|
||||
browse_menu(selector, host, port)
|
||||
except socket.error, msg:
|
||||
print 'Socket error:', msg
|
||||
sys.exit(1)
|
||||
except KeyboardInterrupt:
|
||||
print '\n[Goodbye]'
|
||||
|
||||
# Browse a menu
|
||||
def browse_menu(selector, host, port):
|
||||
list = get_menu(selector, host, port)
|
||||
while 1:
|
||||
print '----- MENU -----'
|
||||
print 'Selector:', repr(selector)
|
||||
print 'Host:', host, ' Port:', port
|
||||
print
|
||||
for i in range(len(list)):
|
||||
item = list[i]
|
||||
typechar, description = item[0], item[1]
|
||||
print string.rjust(repr(i+1), 3) + ':', description,
|
||||
if typename.has_key(typechar):
|
||||
print typename[typechar]
|
||||
else:
|
||||
print '<TYPE=' + repr(typechar) + '>'
|
||||
print
|
||||
while 1:
|
||||
try:
|
||||
str = raw_input('Choice [CR == up a level]: ')
|
||||
except EOFError:
|
||||
print
|
||||
return
|
||||
if not str:
|
||||
return
|
||||
try:
|
||||
choice = string.atoi(str)
|
||||
except string.atoi_error:
|
||||
print 'Choice must be a number; try again:'
|
||||
continue
|
||||
if not 0 < choice <= len(list):
|
||||
print 'Choice out of range; try again:'
|
||||
continue
|
||||
break
|
||||
item = list[choice-1]
|
||||
typechar = item[0]
|
||||
[i_selector, i_host, i_port] = item[2:5]
|
||||
if typebrowser.has_key(typechar):
|
||||
browserfunc = typebrowser[typechar]
|
||||
try:
|
||||
browserfunc(i_selector, i_host, i_port)
|
||||
except (IOError, socket.error):
|
||||
print '***', sys.exc_type, ':', sys.exc_value
|
||||
else:
|
||||
print 'Unsupported object type'
|
||||
|
||||
# Browse a text file
|
||||
def browse_textfile(selector, host, port):
|
||||
x = None
|
||||
try:
|
||||
p = os.popen('${PAGER-more}', 'w')
|
||||
x = SaveLines(p)
|
||||
get_alt_textfile(selector, host, port, x.writeln)
|
||||
except IOError, msg:
|
||||
print 'IOError:', msg
|
||||
if x:
|
||||
x.close()
|
||||
f = open_savefile()
|
||||
if not f:
|
||||
return
|
||||
x = SaveLines(f)
|
||||
try:
|
||||
get_alt_textfile(selector, host, port, x.writeln)
|
||||
print 'Done.'
|
||||
except IOError, msg:
|
||||
print 'IOError:', msg
|
||||
x.close()
|
||||
|
||||
# Browse a search index
|
||||
def browse_search(selector, host, port):
|
||||
while 1:
|
||||
print '----- SEARCH -----'
|
||||
print 'Selector:', repr(selector)
|
||||
print 'Host:', host, ' Port:', port
|
||||
print
|
||||
try:
|
||||
query = raw_input('Query [CR == up a level]: ')
|
||||
except EOFError:
|
||||
print
|
||||
break
|
||||
query = string.strip(query)
|
||||
if not query:
|
||||
break
|
||||
if '\t' in query:
|
||||
print 'Sorry, queries cannot contain tabs'
|
||||
continue
|
||||
browse_menu(selector + TAB + query, host, port)
|
||||
|
||||
# "Browse" telnet-based information, i.e. open a telnet session
|
||||
def browse_telnet(selector, host, port):
|
||||
if selector:
|
||||
print 'Log in as', repr(selector)
|
||||
if type(port) <> type(''):
|
||||
port = repr(port)
|
||||
sts = os.system('set -x; exec telnet ' + host + ' ' + port)
|
||||
if sts:
|
||||
print 'Exit status:', sts
|
||||
|
||||
# "Browse" a binary file, i.e. save it to a file
|
||||
def browse_binary(selector, host, port):
|
||||
f = open_savefile()
|
||||
if not f:
|
||||
return
|
||||
x = SaveWithProgress(f)
|
||||
get_alt_binary(selector, host, port, x.write, 8*1024)
|
||||
x.close()
|
||||
|
||||
# "Browse" a sound file, i.e. play it or save it
|
||||
def browse_sound(selector, host, port):
|
||||
browse_binary(selector, host, port)
|
||||
|
||||
# Dictionary mapping types to browser functions
|
||||
typebrowser = {'0': browse_textfile, '1': browse_menu, \
|
||||
'4': browse_binary, '5': browse_binary, '6': browse_textfile, \
|
||||
'7': browse_search, \
|
||||
'8': browse_telnet, '9': browse_binary, 's': browse_sound}
|
||||
|
||||
# Class used to save lines, appending a newline to each line
|
||||
class SaveLines:
|
||||
def __init__(self, f):
|
||||
self.f = f
|
||||
def writeln(self, line):
|
||||
self.f.write(line + '\n')
|
||||
def close(self):
|
||||
sts = self.f.close()
|
||||
if sts:
|
||||
print 'Exit status:', sts
|
||||
|
||||
# Class used to save data while showing progress
|
||||
class SaveWithProgress:
|
||||
def __init__(self, f):
|
||||
self.f = f
|
||||
def write(self, data):
|
||||
sys.stdout.write('#')
|
||||
sys.stdout.flush()
|
||||
self.f.write(data)
|
||||
def close(self):
|
||||
print
|
||||
sts = self.f.close()
|
||||
if sts:
|
||||
print 'Exit status:', sts
|
||||
|
||||
# Ask for and open a save file, or return None if not to save
|
||||
def open_savefile():
|
||||
try:
|
||||
savefile = raw_input( \
|
||||
'Save as file [CR == don\'t save; |pipeline or ~user/... OK]: ')
|
||||
except EOFError:
|
||||
print
|
||||
return None
|
||||
savefile = string.strip(savefile)
|
||||
if not savefile:
|
||||
return None
|
||||
if savefile[0] == '|':
|
||||
cmd = string.strip(savefile[1:])
|
||||
try:
|
||||
p = os.popen(cmd, 'w')
|
||||
except IOError, msg:
|
||||
print repr(cmd), ':', msg
|
||||
return None
|
||||
print 'Piping through', repr(cmd), '...'
|
||||
return p
|
||||
if savefile[0] == '~':
|
||||
savefile = os.path.expanduser(savefile)
|
||||
try:
|
||||
f = open(savefile, 'w')
|
||||
except IOError, msg:
|
||||
print repr(savefile), ':', msg
|
||||
return None
|
||||
print 'Saving to', repr(savefile), '...'
|
||||
return f
|
||||
|
||||
# Test program
|
||||
def test():
|
||||
if sys.argv[4:]:
|
||||
print 'usage: gopher [ [selector] host [port] ]'
|
||||
sys.exit(2)
|
||||
elif sys.argv[3:]:
|
||||
browser(sys.argv[1], sys.argv[2], sys.argv[3])
|
||||
elif sys.argv[2:]:
|
||||
try:
|
||||
port = string.atoi(sys.argv[2])
|
||||
selector = ''
|
||||
host = sys.argv[1]
|
||||
except string.atoi_error:
|
||||
selector = sys.argv[1]
|
||||
host = sys.argv[2]
|
||||
port = ''
|
||||
browser(selector, host, port)
|
||||
elif sys.argv[1:]:
|
||||
browser('', sys.argv[1])
|
||||
else:
|
||||
browser()
|
||||
|
||||
# Call the test program as a main program
|
||||
test()
|
|
@ -0,0 +1,80 @@
|
|||
#!/usr/bin/env python
|
||||
#
|
||||
# Send/receive UDP multicast packets.
|
||||
# Requires that your OS kernel supports IP multicast.
|
||||
#
|
||||
# Usage:
|
||||
# mcast -s (sender, IPv4)
|
||||
# mcast -s -6 (sender, IPv6)
|
||||
# mcast (receivers, IPv4)
|
||||
# mcast -6 (receivers, IPv6)
|
||||
|
||||
MYPORT = 8123
|
||||
MYGROUP_4 = '225.0.0.250'
|
||||
MYGROUP_6 = 'ff15:7079:7468:6f6e:6465:6d6f:6d63:6173'
|
||||
MYTTL = 1 # Increase to reach other networks
|
||||
|
||||
import time
|
||||
import struct
|
||||
import socket
|
||||
import sys
|
||||
|
||||
def main():
|
||||
group = MYGROUP_6 if "-6" in sys.argv[1:] else MYGROUP_4
|
||||
|
||||
if "-s" in sys.argv[1:]:
|
||||
sender(group)
|
||||
else:
|
||||
receiver(group)
|
||||
|
||||
|
||||
def sender(group):
|
||||
addrinfo = socket.getaddrinfo(group, None)[0]
|
||||
|
||||
s = socket.socket(addrinfo[0], socket.SOCK_DGRAM)
|
||||
|
||||
# Set Time-to-live (optional)
|
||||
ttl_bin = struct.pack('@i', MYTTL)
|
||||
if addrinfo[0] == socket.AF_INET: # IPv4
|
||||
s.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, ttl_bin)
|
||||
else:
|
||||
s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_HOPS, ttl_bin)
|
||||
|
||||
while True:
|
||||
data = repr(time.time())
|
||||
s.sendto(data + '\0', (addrinfo[4][0], MYPORT))
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
def receiver(group):
|
||||
# Look up multicast group address in name server and find out IP version
|
||||
addrinfo = socket.getaddrinfo(group, None)[0]
|
||||
|
||||
# Create a socket
|
||||
s = socket.socket(addrinfo[0], socket.SOCK_DGRAM)
|
||||
|
||||
# Allow multiple copies of this program on one machine
|
||||
# (not strictly needed)
|
||||
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
|
||||
# Bind it to the port
|
||||
s.bind(('', MYPORT))
|
||||
|
||||
group_bin = socket.inet_pton(addrinfo[0], addrinfo[4][0])
|
||||
# Join group
|
||||
if addrinfo[0] == socket.AF_INET: # IPv4
|
||||
mreq = group_bin + struct.pack('=I', socket.INADDR_ANY)
|
||||
s.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
|
||||
else:
|
||||
mreq = group_bin + struct.pack('@I', 0)
|
||||
s.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, mreq)
|
||||
|
||||
# Loop, printing any data we receive
|
||||
while True:
|
||||
data, sender = s.recvfrom(1500)
|
||||
while data[-1:] == '\0': data = data[:-1] # Strip trailing \0's
|
||||
print (str(sender) + ' ' + repr(data))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -0,0 +1,14 @@
|
|||
# Receive UDP packets transmitted by a broadcasting service
|
||||
|
||||
MYPORT = 50000
|
||||
|
||||
import sys
|
||||
from socket import *
|
||||
|
||||
s = socket(AF_INET, SOCK_DGRAM)
|
||||
s.bind(('', MYPORT))
|
||||
|
||||
while 1:
|
||||
data, wherefrom = s.recvfrom(1500, 0)
|
||||
sys.stderr.write(repr(wherefrom) + '\n')
|
||||
sys.stdout.write(data)
|
|
@ -0,0 +1,35 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
# Remote python client.
|
||||
# Execute Python commands remotely and send output back.
|
||||
|
||||
import sys
|
||||
import string
|
||||
from socket import *
|
||||
|
||||
PORT = 4127
|
||||
BUFSIZE = 1024
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 3:
|
||||
print "usage: rpython host command"
|
||||
sys.exit(2)
|
||||
host = sys.argv[1]
|
||||
port = PORT
|
||||
i = string.find(host, ':')
|
||||
if i >= 0:
|
||||
port = string.atoi(port[i+1:])
|
||||
host = host[:i]
|
||||
command = string.join(sys.argv[2:])
|
||||
s = socket(AF_INET, SOCK_STREAM)
|
||||
s.connect((host, port))
|
||||
s.send(command)
|
||||
s.shutdown(1)
|
||||
reply = ''
|
||||
while 1:
|
||||
data = s.recv(BUFSIZE)
|
||||
if not data: break
|
||||
reply = reply + data
|
||||
print reply,
|
||||
|
||||
main()
|
|
@ -0,0 +1,52 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
# Remote python server.
|
||||
# Execute Python commands remotely and send output back.
|
||||
# WARNING: This version has a gaping security hole -- it accepts requests
|
||||
# from any host on the Internet!
|
||||
|
||||
import sys
|
||||
from socket import *
|
||||
import StringIO
|
||||
import traceback
|
||||
|
||||
PORT = 4127
|
||||
BUFSIZE = 1024
|
||||
|
||||
def main():
|
||||
if len(sys.argv) > 1:
|
||||
port = int(eval(sys.argv[1]))
|
||||
else:
|
||||
port = PORT
|
||||
s = socket(AF_INET, SOCK_STREAM)
|
||||
s.bind(('', port))
|
||||
s.listen(1)
|
||||
while 1:
|
||||
conn, (remotehost, remoteport) = s.accept()
|
||||
print 'connected by', remotehost, remoteport
|
||||
request = ''
|
||||
while 1:
|
||||
data = conn.recv(BUFSIZE)
|
||||
if not data:
|
||||
break
|
||||
request = request + data
|
||||
reply = execute(request)
|
||||
conn.send(reply)
|
||||
conn.close()
|
||||
|
||||
def execute(request):
|
||||
stdout = sys.stdout
|
||||
stderr = sys.stderr
|
||||
sys.stdout = sys.stderr = fakefile = StringIO.StringIO()
|
||||
try:
|
||||
try:
|
||||
exec request in {}, {}
|
||||
except:
|
||||
print
|
||||
traceback.print_exc(100)
|
||||
finally:
|
||||
sys.stderr = stderr
|
||||
sys.stdout = stdout
|
||||
return fakefile.getvalue()
|
||||
|
||||
main()
|
|
@ -0,0 +1,109 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
# Minimal interface to the Internet telnet protocol.
|
||||
#
|
||||
# It refuses all telnet options and does not recognize any of the other
|
||||
# telnet commands, but can still be used to connect in line-by-line mode.
|
||||
# It's also useful to play with a number of other services,
|
||||
# like time, finger, smtp and even ftp.
|
||||
#
|
||||
# Usage: telnet host [port]
|
||||
#
|
||||
# The port may be a service name or a decimal port number;
|
||||
# it defaults to 'telnet'.
|
||||
|
||||
|
||||
import sys, posix, time
|
||||
from socket import *
|
||||
|
||||
BUFSIZE = 1024
|
||||
|
||||
# Telnet protocol characters
|
||||
|
||||
IAC = chr(255) # Interpret as command
|
||||
DONT = chr(254)
|
||||
DO = chr(253)
|
||||
WONT = chr(252)
|
||||
WILL = chr(251)
|
||||
|
||||
def main():
|
||||
host = sys.argv[1]
|
||||
try:
|
||||
hostaddr = gethostbyname(host)
|
||||
except error:
|
||||
sys.stderr.write(sys.argv[1] + ': bad host name\n')
|
||||
sys.exit(2)
|
||||
#
|
||||
if len(sys.argv) > 2:
|
||||
servname = sys.argv[2]
|
||||
else:
|
||||
servname = 'telnet'
|
||||
#
|
||||
if '0' <= servname[:1] <= '9':
|
||||
port = eval(servname)
|
||||
else:
|
||||
try:
|
||||
port = getservbyname(servname, 'tcp')
|
||||
except error:
|
||||
sys.stderr.write(servname + ': bad tcp service name\n')
|
||||
sys.exit(2)
|
||||
#
|
||||
s = socket(AF_INET, SOCK_STREAM)
|
||||
#
|
||||
try:
|
||||
s.connect((host, port))
|
||||
except error, msg:
|
||||
sys.stderr.write('connect failed: ' + repr(msg) + '\n')
|
||||
sys.exit(1)
|
||||
#
|
||||
pid = posix.fork()
|
||||
#
|
||||
if pid == 0:
|
||||
# child -- read stdin, write socket
|
||||
while 1:
|
||||
line = sys.stdin.readline()
|
||||
s.send(line)
|
||||
else:
|
||||
# parent -- read socket, write stdout
|
||||
iac = 0 # Interpret next char as command
|
||||
opt = '' # Interpret next char as option
|
||||
while 1:
|
||||
data = s.recv(BUFSIZE)
|
||||
if not data:
|
||||
# EOF; kill child and exit
|
||||
sys.stderr.write( '(Closed by remote host)\n')
|
||||
posix.kill(pid, 9)
|
||||
sys.exit(1)
|
||||
cleandata = ''
|
||||
for c in data:
|
||||
if opt:
|
||||
print ord(c)
|
||||
s.send(opt + c)
|
||||
opt = ''
|
||||
elif iac:
|
||||
iac = 0
|
||||
if c == IAC:
|
||||
cleandata = cleandata + c
|
||||
elif c in (DO, DONT):
|
||||
if c == DO: print '(DO)',
|
||||
else: print '(DONT)',
|
||||
opt = IAC + WONT
|
||||
elif c in (WILL, WONT):
|
||||
if c == WILL: print '(WILL)',
|
||||
else: print '(WONT)',
|
||||
opt = IAC + DONT
|
||||
else:
|
||||
print '(command)', ord(c)
|
||||
elif c == IAC:
|
||||
iac = 1
|
||||
print '(IAC)',
|
||||
else:
|
||||
cleandata = cleandata + c
|
||||
sys.stdout.write(cleandata)
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
try:
|
||||
main()
|
||||
except KeyboardInterrupt:
|
||||
pass
|
|
@ -0,0 +1,93 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
# Test network throughput.
|
||||
#
|
||||
# Usage:
|
||||
# 1) on host_A: throughput -s [port] # start a server
|
||||
# 2) on host_B: throughput -c count host_A [port] # start a client
|
||||
#
|
||||
# The server will service multiple clients until it is killed.
|
||||
#
|
||||
# The client performs one transfer of count*BUFSIZE bytes and
|
||||
# measures the time it takes (roundtrip!).
|
||||
|
||||
|
||||
import sys, time
|
||||
from socket import *
|
||||
|
||||
MY_PORT = 50000 + 42
|
||||
|
||||
BUFSIZE = 1024
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
usage()
|
||||
if sys.argv[1] == '-s':
|
||||
server()
|
||||
elif sys.argv[1] == '-c':
|
||||
client()
|
||||
else:
|
||||
usage()
|
||||
|
||||
|
||||
def usage():
|
||||
sys.stdout = sys.stderr
|
||||
print 'Usage: (on host_A) throughput -s [port]'
|
||||
print 'and then: (on host_B) throughput -c count host_A [port]'
|
||||
sys.exit(2)
|
||||
|
||||
|
||||
def server():
|
||||
if len(sys.argv) > 2:
|
||||
port = eval(sys.argv[2])
|
||||
else:
|
||||
port = MY_PORT
|
||||
s = socket(AF_INET, SOCK_STREAM)
|
||||
s.bind(('', port))
|
||||
s.listen(1)
|
||||
print 'Server ready...'
|
||||
while 1:
|
||||
conn, (host, remoteport) = s.accept()
|
||||
while 1:
|
||||
data = conn.recv(BUFSIZE)
|
||||
if not data:
|
||||
break
|
||||
del data
|
||||
conn.send('OK\n')
|
||||
conn.close()
|
||||
print 'Done with', host, 'port', remoteport
|
||||
|
||||
|
||||
def client():
|
||||
if len(sys.argv) < 4:
|
||||
usage()
|
||||
count = int(eval(sys.argv[2]))
|
||||
host = sys.argv[3]
|
||||
if len(sys.argv) > 4:
|
||||
port = eval(sys.argv[4])
|
||||
else:
|
||||
port = MY_PORT
|
||||
testdata = 'x' * (BUFSIZE-1) + '\n'
|
||||
t1 = time.time()
|
||||
s = socket(AF_INET, SOCK_STREAM)
|
||||
t2 = time.time()
|
||||
s.connect((host, port))
|
||||
t3 = time.time()
|
||||
i = 0
|
||||
while i < count:
|
||||
i = i+1
|
||||
s.send(testdata)
|
||||
s.shutdown(1) # Send EOF
|
||||
t4 = time.time()
|
||||
data = s.recv(BUFSIZE)
|
||||
t5 = time.time()
|
||||
print data
|
||||
print 'Raw timers:', t1, t2, t3, t4, t5
|
||||
print 'Intervals:', t2-t1, t3-t2, t4-t3, t5-t4
|
||||
print 'Total:', t5-t1
|
||||
print 'Throughput:', round((BUFSIZE*count*0.001) / (t5-t1), 3),
|
||||
print 'K/sec.'
|
||||
|
||||
|
||||
main()
|
|
@ -0,0 +1,63 @@
|
|||
#! /usr/bin/env python
|
||||
|
||||
# Client and server for udp (datagram) echo.
|
||||
#
|
||||
# Usage: udpecho -s [port] (to start a server)
|
||||
# or: udpecho -c host [port] <file (client)
|
||||
|
||||
import sys
|
||||
from socket import *
|
||||
|
||||
ECHO_PORT = 50000 + 7
|
||||
BUFSIZE = 1024
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 2:
|
||||
usage()
|
||||
if sys.argv[1] == '-s':
|
||||
server()
|
||||
elif sys.argv[1] == '-c':
|
||||
client()
|
||||
else:
|
||||
usage()
|
||||
|
||||
def usage():
|
||||
sys.stdout = sys.stderr
|
||||
print 'Usage: udpecho -s [port] (server)'
|
||||
print 'or: udpecho -c host [port] <file (client)'
|
||||
sys.exit(2)
|
||||
|
||||
def server():
|
||||
if len(sys.argv) > 2:
|
||||
port = eval(sys.argv[2])
|
||||
else:
|
||||
port = ECHO_PORT
|
||||
s = socket(AF_INET, SOCK_DGRAM)
|
||||
s.bind(('', port))
|
||||
print 'udp echo server ready'
|
||||
while 1:
|
||||
data, addr = s.recvfrom(BUFSIZE)
|
||||
print 'server received %r from %r' % (data, addr)
|
||||
s.sendto(data, addr)
|
||||
|
||||
def client():
|
||||
if len(sys.argv) < 3:
|
||||
usage()
|
||||
host = sys.argv[2]
|
||||
if len(sys.argv) > 3:
|
||||
port = eval(sys.argv[3])
|
||||
else:
|
||||
port = ECHO_PORT
|
||||
addr = host, port
|
||||
s = socket(AF_INET, SOCK_DGRAM)
|
||||
s.bind(('', 0))
|
||||
print 'udp echo client ready, reading stdin'
|
||||
while 1:
|
||||
line = sys.stdin.readline()
|
||||
if not line:
|
||||
break
|
||||
s.sendto(line, addr)
|
||||
data, fromaddr = s.recvfrom(BUFSIZE)
|
||||
print 'client received %r from %r' % (data, fromaddr)
|
||||
|
||||
main()
|
|
@ -0,0 +1,14 @@
|
|||
# Send UDP broadcast packets
|
||||
|
||||
MYPORT = 50000
|
||||
|
||||
import sys, time
|
||||
from socket import *
|
||||
|
||||
s = socket(AF_INET, SOCK_DGRAM)
|
||||
s.bind(('', 0))
|
||||
|
||||
while 1:
|
||||
data = repr(time.time()) + '\n'
|
||||
s.sendto(data, ('', MYPORT))
|
||||
time.sleep(2)
|
|
@ -0,0 +1,12 @@
|
|||
# Echo client demo using Unix sockets
|
||||
# Piet van Oostrum
|
||||
|
||||
from socket import *
|
||||
|
||||
FILE = 'unix-socket'
|
||||
s = socket(AF_UNIX, SOCK_STREAM)
|
||||
s.connect(FILE)
|
||||
s.send('Hello, world')
|
||||
data = s.recv(1024)
|
||||
s.close()
|
||||
print 'Received', repr(data)
|
|
@ -0,0 +1,24 @@
|
|||
# Echo server demo using Unix sockets (handles one connection only)
|
||||
# Piet van Oostrum
|
||||
|
||||
import os
|
||||
from socket import *
|
||||
|
||||
FILE = 'unix-socket'
|
||||
s = socket(AF_UNIX, SOCK_STREAM)
|
||||
s.bind(FILE)
|
||||
|
||||
print 'Sock name is: ['+s.getsockname()+']'
|
||||
|
||||
# Wait for a connection
|
||||
s.listen(1)
|
||||
conn, addr = s.accept()
|
||||
|
||||
while True:
|
||||
data = conn.recv(1024)
|
||||
if not data:
|
||||
break
|
||||
conn.send(data)
|
||||
|
||||
conn.close()
|
||||
os.unlink(FILE)
|
|
@ -0,0 +1,42 @@
|
|||
"""
|
||||
A simple demo that reads in an XML document and displays the number of
|
||||
elements and attributes as well as a tally of elements and attributes by name.
|
||||
"""
|
||||
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
|
||||
from xml.sax import make_parser, handler
|
||||
|
||||
class FancyCounter(handler.ContentHandler):
|
||||
|
||||
def __init__(self):
|
||||
self._elems = 0
|
||||
self._attrs = 0
|
||||
self._elem_types = defaultdict(int)
|
||||
self._attr_types = defaultdict(int)
|
||||
|
||||
def startElement(self, name, attrs):
|
||||
self._elems += 1
|
||||
self._attrs += len(attrs)
|
||||
self._elem_types[name] += 1
|
||||
|
||||
for name in attrs.keys():
|
||||
self._attr_types[name] += 1
|
||||
|
||||
def endDocument(self):
|
||||
print "There were", self._elems, "elements."
|
||||
print "There were", self._attrs, "attributes."
|
||||
|
||||
print "---ELEMENT TYPES"
|
||||
for pair in self._elem_types.items():
|
||||
print "%20s %d" % pair
|
||||
|
||||
print "---ATTRIBUTE TYPES"
|
||||
for pair in self._attr_types.items():
|
||||
print "%20s %d" % pair
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = make_parser()
|
||||
parser.setContentHandler(FancyCounter())
|
||||
parser.parse(sys.argv[1])
|
|
@ -0,0 +1,46 @@
|
|||
"""
|
||||
A simple demo that reads in an XML document and spits out an equivalent,
|
||||
but not necessarily identical, document.
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
||||
from xml.sax import saxutils, handler, make_parser
|
||||
|
||||
# --- The ContentHandler
|
||||
|
||||
class ContentGenerator(handler.ContentHandler):
|
||||
|
||||
def __init__(self, out=sys.stdout):
|
||||
handler.ContentHandler.__init__(self)
|
||||
self._out = out
|
||||
|
||||
# ContentHandler methods
|
||||
|
||||
def startDocument(self):
|
||||
self._out.write('<?xml version="1.0" encoding="iso-8859-1"?>\n')
|
||||
|
||||
def startElement(self, name, attrs):
|
||||
self._out.write('<' + name)
|
||||
for (name, value) in attrs.items():
|
||||
self._out.write(' %s="%s"' % (name, saxutils.escape(value)))
|
||||
self._out.write('>')
|
||||
|
||||
def endElement(self, name):
|
||||
self._out.write('</%s>' % name)
|
||||
|
||||
def characters(self, content):
|
||||
self._out.write(saxutils.escape(content))
|
||||
|
||||
def ignorableWhitespace(self, content):
|
||||
self._out.write(content)
|
||||
|
||||
def processingInstruction(self, target, data):
|
||||
self._out.write('<?%s %s?>' % (target, data))
|
||||
|
||||
# --- The main program
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = make_parser()
|
||||
parser.setContentHandler(ContentGenerator())
|
||||
parser.parse(sys.argv[1])
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue