diff options
author | cinap_lenrek <cinap_lenrek@localhost> | 2011-05-03 11:25:13 +0000 |
---|---|---|
committer | cinap_lenrek <cinap_lenrek@localhost> | 2011-05-03 11:25:13 +0000 |
commit | 458120dd40db6b4df55a4e96b650e16798ef06a0 (patch) | |
tree | 8f82685be24fef97e715c6f5ca4c68d34d5074ee /sys/src/cmd/python/Demo | |
parent | 3a742c699f6806c1145aea5149bf15de15a0afd7 (diff) |
add hg and python
Diffstat (limited to 'sys/src/cmd/python/Demo')
244 files changed, 27602 insertions, 0 deletions
diff --git a/sys/src/cmd/python/Demo/README b/sys/src/cmd/python/Demo/README new file mode 100644 index 000000000..9d150d682 --- /dev/null +++ b/sys/src/cmd/python/Demo/README @@ -0,0 +1,61 @@ +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. + +curses A set of curses demos. + +embed An example of embedding Python in another application + (see also pysvr). + +imputil Demonstration subclasses of imputil.Importer. + +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. + +pysvr An example of embedding Python in a threaded + application. + +rpc A set of classes for building clients and servers for + Sun RPC. + +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'. + +threads Demos that use the 'thread' module. (Currently these + only run on SGIs, but this may change in the future.) + +tix Demos using the Tix widget set addition to Tkinter. + +tkinter Demos using the Tk interface (including Matt Conway's + excellent set of demos). + +xml Some XML demos. + +zlib Some demos for the zlib module (see also the standard + library module gzip.py). diff --git a/sys/src/cmd/python/Demo/cgi/README b/sys/src/cmd/python/Demo/cgi/README new file mode 100644 index 000000000..e50d2d051 --- /dev/null +++ b/sys/src/cmd/python/Demo/cgi/README @@ -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 diff --git a/sys/src/cmd/python/Demo/cgi/cgi0.sh b/sys/src/cmd/python/Demo/cgi/cgi0.sh new file mode 100755 index 000000000..5cefcd37c --- /dev/null +++ b/sys/src/cmd/python/Demo/cgi/cgi0.sh @@ -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 diff --git a/sys/src/cmd/python/Demo/cgi/cgi1.py b/sys/src/cmd/python/Demo/cgi/cgi1.py new file mode 100755 index 000000000..9d25c7db1 --- /dev/null +++ b/sys/src/cmd/python/Demo/cgi/cgi1.py @@ -0,0 +1,14 @@ +#!/usr/local/bin/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" diff --git a/sys/src/cmd/python/Demo/cgi/cgi2.py b/sys/src/cmd/python/Demo/cgi/cgi2.py new file mode 100755 index 000000000..d956f6538 --- /dev/null +++ b/sys/src/cmd/python/Demo/cgi/cgi2.py @@ -0,0 +1,22 @@ +#!/usr/local/bin/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() diff --git a/sys/src/cmd/python/Demo/cgi/cgi3.py b/sys/src/cmd/python/Demo/cgi/cgi3.py new file mode 100755 index 000000000..a3421b5b2 --- /dev/null +++ b/sys/src/cmd/python/Demo/cgi/cgi3.py @@ -0,0 +1,10 @@ +#!/usr/local/bin/python + +"""CGI test 3 (persistent data).""" + +import cgitb; cgitb.enable() + +from wiki import main + +if __name__ == "__main__": + main() diff --git a/sys/src/cmd/python/Demo/cgi/wiki.py b/sys/src/cmd/python/Demo/cgi/wiki.py new file mode 100644 index 000000000..ee094a8ef --- /dev/null +++ b/sys/src/cmd/python/Demo/cgi/wiki.py @@ -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) diff --git a/sys/src/cmd/python/Demo/classes/Complex.py b/sys/src/cmd/python/Demo/classes/Complex.py new file mode 100755 index 000000000..2b306ad5e --- /dev/null +++ b/sys/src/cmd/python/Demo/classes/Complex.py @@ -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() diff --git a/sys/src/cmd/python/Demo/classes/Dates.py b/sys/src/cmd/python/Demo/classes/Dates.py new file mode 100755 index 000000000..6494b6a4c --- /dev/null +++ b/sys/src/cmd/python/Demo/classes/Dates.py @@ -0,0 +1,222 @@ +# 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]) + +DateTestError = 'DateTestError' +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 diff --git a/sys/src/cmd/python/Demo/classes/Dbm.py b/sys/src/cmd/python/Demo/classes/Dbm.py new file mode 100755 index 000000000..482806a4e --- /dev/null +++ b/sys/src/cmd/python/Demo/classes/Dbm.py @@ -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 == None: + del d[key] + else: + d[key] = value + except KeyboardInterrupt: + print '' + print d + except EOFError: + print '[eof]' + break + print d + + +test() diff --git a/sys/src/cmd/python/Demo/classes/README b/sys/src/cmd/python/Demo/classes/README new file mode 100644 index 000000000..1d41f6af3 --- /dev/null +++ b/sys/src/cmd/python/Demo/classes/README @@ -0,0 +1,13 @@ +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() +Rat.py Rational numbers +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.) diff --git a/sys/src/cmd/python/Demo/classes/Range.py b/sys/src/cmd/python/Demo/classes/Range.py new file mode 100755 index 000000000..3f1daaead --- /dev/null +++ b/sys/src/cmd/python/Demo/classes/Range.py @@ -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() diff --git a/sys/src/cmd/python/Demo/classes/Rat.py b/sys/src/cmd/python/Demo/classes/Rat.py new file mode 100755 index 000000000..55543b6d2 --- /dev/null +++ b/sys/src/cmd/python/Demo/classes/Rat.py @@ -0,0 +1,310 @@ +'''\ +This module implements rational numbers. + +The entry point of this module is the function + rat(numerator, denominator) +If either numerator or denominator is of an integral or rational type, +the result is a rational number, else, the result is the simplest of +the types float and complex which can hold numerator/denominator. +If denominator is omitted, it defaults to 1. +Rational numbers can be used in calculations with any other numeric +type. The result of the calculation will be rational if possible. + +There is also a test function with calling sequence + test() +The documentation string of the test function contains the expected +output. +''' + +# Contributed by Sjoerd Mullender + +from types import * + +def gcd(a, b): + '''Calculate the Greatest Common Divisor.''' + while b: + a, b = b, a%b + return a + +def rat(num, den = 1): + # must check complex before float + if isinstance(num, complex) or isinstance(den, complex): + # numerator or denominator is complex: return a complex + return complex(num) / complex(den) + if isinstance(num, float) or isinstance(den, float): + # numerator or denominator is float: return a float + return float(num) / float(den) + # otherwise return a rational + return Rat(num, den) + +class Rat: + '''This class implements rational numbers.''' + + def __init__(self, num, den = 1): + if den == 0: + raise ZeroDivisionError, 'rat(x, 0)' + + # normalize + + # must check complex before float + if (isinstance(num, complex) or + isinstance(den, complex)): + # numerator or denominator is complex: + # normalized form has denominator == 1+0j + self.__num = complex(num) / complex(den) + self.__den = complex(1) + return + if isinstance(num, float) or isinstance(den, float): + # numerator or denominator is float: + # normalized form has denominator == 1.0 + self.__num = float(num) / float(den) + self.__den = 1.0 + return + if (isinstance(num, self.__class__) or + isinstance(den, self.__class__)): + # numerator or denominator is rational + new = num / den + if not isinstance(new, self.__class__): + self.__num = new + if isinstance(new, complex): + self.__den = complex(1) + else: + self.__den = 1.0 + else: + self.__num = new.__num + self.__den = new.__den + else: + # make sure numerator and denominator don't + # have common factors + # this also makes sure that denominator > 0 + g = gcd(num, den) + self.__num = num / g + self.__den = den / g + # try making numerator and denominator of IntType if they fit + try: + numi = int(self.__num) + deni = int(self.__den) + except (OverflowError, TypeError): + pass + else: + if self.__num == numi and self.__den == deni: + self.__num = numi + self.__den = deni + + def __repr__(self): + return 'Rat(%s,%s)' % (self.__num, self.__den) + + def __str__(self): + if self.__den == 1: + return str(self.__num) + else: + return '(%s/%s)' % (str(self.__num), str(self.__den)) + + # a + b + def __add__(a, b): + try: + return rat(a.__num * b.__den + b.__num * a.__den, + a.__den * b.__den) + except OverflowError: + return rat(long(a.__num) * long(b.__den) + + long(b.__num) * long(a.__den), + long(a.__den) * long(b.__den)) + + def __radd__(b, a): + return Rat(a) + b + + # a - b + def __sub__(a, b): + try: + return rat(a.__num * b.__den - b.__num * a.__den, + a.__den * b.__den) + except OverflowError: + return rat(long(a.__num) * long(b.__den) - + long(b.__num) * long(a.__den), + long(a.__den) * long(b.__den)) + + def __rsub__(b, a): + return Rat(a) - b + + # a * b + def __mul__(a, b): + try: + return rat(a.__num * b.__num, a.__den * b.__den) + except OverflowError: + return rat(long(a.__num) * long(b.__num), + long(a.__den) * long(b.__den)) + + def __rmul__(b, a): + return Rat(a) * b + + # a / b + def __div__(a, b): + try: + return rat(a.__num * b.__den, a.__den * b.__num) + except OverflowError: + return rat(long(a.__num) * long(b.__den), + long(a.__den) * long(b.__num)) + + def __rdiv__(b, a): + return Rat(a) / b + + # a % b + def __mod__(a, b): + div = a / b + try: + div = int(div) + except OverflowError: + div = long(div) + return a - b * div + + def __rmod__(b, a): + return Rat(a) % b + + # a ** b + def __pow__(a, b): + if b.__den != 1: + if isinstance(a.__num, complex): + a = complex(a) + else: + a = float(a) + if isinstance(b.__num, complex): + b = complex(b) + else: + b = float(b) + return a ** b + try: + return rat(a.__num ** b.__num, a.__den ** b.__num) + except OverflowError: + return rat(long(a.__num) ** b.__num, + long(a.__den) ** b.__num) + + def __rpow__(b, a): + return Rat(a) ** b + + # -a + def __neg__(a): + try: + return rat(-a.__num, a.__den) + except OverflowError: + # a.__num == sys.maxint + return rat(-long(a.__num), a.__den) + + # abs(a) + def __abs__(a): + return rat(abs(a.__num), a.__den) + + # int(a) + def __int__(a): + return int(a.__num / a.__den) + + # long(a) + def __long__(a): + return long(a.__num) / long(a.__den) + + # float(a) + def __float__(a): + return float(a.__num) / float(a.__den) + + # complex(a) + def __complex__(a): + return complex(a.__num) / complex(a.__den) + + # cmp(a,b) + def __cmp__(a, b): + diff = Rat(a - b) + if diff.__num < 0: + return -1 + elif diff.__num > 0: + return 1 + else: + return 0 + + def __rcmp__(b, a): + return cmp(Rat(a), b) + + # a != 0 + def __nonzero__(a): + return a.__num != 0 + + # coercion + def __coerce__(a, b): + return a, Rat(b) + +def test(): + '''\ + Test function for rat module. + + The expected output is (module some differences in floating + precission): + -1 + -1 + 0 0L 0.1 (0.1+0j) + [Rat(1,2), Rat(-3,10), Rat(1,25), Rat(1,4)] + [Rat(-3,10), Rat(1,25), Rat(1,4), Rat(1,2)] + 0 + (11/10) + (11/10) + 1.1 + OK + 2 1.5 (3/2) (1.5+1.5j) (15707963/5000000) + 2 2 2.0 (2+0j) + + 4 0 4 1 4 0 + 3.5 0.5 3.0 1.33333333333 2.82842712475 1 + (7/2) (1/2) 3 (4/3) 2.82842712475 1 + (3.5+1.5j) (0.5-1.5j) (3+3j) (0.666666666667-0.666666666667j) (1.43248815986+2.43884761145j) 1 + 1.5 1 1.5 (1.5+0j) + + 3.5 -0.5 3.0 0.75 2.25 -1 + 3.0 0.0 2.25 1.0 1.83711730709 0 + 3.0 0.0 2.25 1.0 1.83711730709 1 + (3+1.5j) -1.5j (2.25+2.25j) (0.5-0.5j) (1.50768393746+1.04970907623j) -1 + (3/2) 1 1.5 (1.5+0j) + + (7/2) (-1/2) 3 (3/4) (9/4) -1 + 3.0 0.0 2.25 1.0 1.83711730709 -1 + 3 0 (9/4) 1 1.83711730709 0 + (3+1.5j) -1.5j (2.25+2.25j) (0.5-0.5j) (1.50768393746+1.04970907623j) -1 + (1.5+1.5j) (1.5+1.5j) + + (3.5+1.5j) (-0.5+1.5j) (3+3j) (0.75+0.75j) 4.5j -1 + (3+1.5j) 1.5j (2.25+2.25j) (1+1j) (1.18235814075+2.85446505899j) 1 + (3+1.5j) 1.5j (2.25+2.25j) (1+1j) (1.18235814075+2.85446505899j) 1 + (3+3j) 0j 4.5j (1+0j) (-0.638110484918+0.705394566962j) 0 + ''' + print rat(-1L, 1) + print rat(1, -1) + a = rat(1, 10) + print int(a), long(a), float(a), complex(a) + b = rat(2, 5) + l = [a+b, a-b, a*b, a/b] + print l + l.sort() + print l + print rat(0, 1) + print a+1 + print a+1L + print a+1.0 + try: + print rat(1, 0) + raise SystemError, 'should have been ZeroDivisionError' + except ZeroDivisionError: + print 'OK' + print rat(2), rat(1.5), rat(3, 2), rat(1.5+1.5j), rat(31415926,10000000) + list = [2, 1.5, rat(3,2), 1.5+1.5j] + for i in list: + print i, + if not isinstance(i, complex): + print int(i), float(i), + print complex(i) + print + for j in list: + print i + j, i - j, i * j, i / j, i ** j, + if not (isinstance(i, complex) or + isinstance(j, complex)): + print cmp(i, j) + print + + +if __name__ == '__main__': + test() diff --git a/sys/src/cmd/python/Demo/classes/Rev.py b/sys/src/cmd/python/Demo/classes/Rev.py new file mode 100755 index 000000000..7fd78e03e --- /dev/null +++ b/sys/src/cmd/python/Demo/classes/Rev.py @@ -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() diff --git a/sys/src/cmd/python/Demo/classes/Vec.py b/sys/src/cmd/python/Demo/classes/Vec.py new file mode 100755 index 000000000..56cb83939 --- /dev/null +++ b/sys/src/cmd/python/Demo/classes/Vec.py @@ -0,0 +1,54 @@ +# A simple vector class + + +def vec(*v): + return Vec(*v) + + +class Vec: + + def __init__(self, *v): + self.v = list(v) + + def fromlist(self, v): + if not isinstance(v, list): + raise TypeError + self.v = v[:] + return self + + def __repr__(self): + return 'vec(' + repr(self.v)[1:-1] + ')' + + def __len__(self): + return len(self.v) + + def __getitem__(self, i): + return self.v[i] + + def __add__(self, other): + # Element-wise addition + v = map(lambda x, y: x+y, self, other) + return Vec().fromlist(v) + + def __sub__(self, other): + # Element-wise subtraction + v = map(lambda x, y: x-y, self, other) + return Vec().fromlist(v) + + def __mul__(self, scalar): + # Multiply by scalar + v = map(lambda x: x*scalar, self.v) + return Vec().fromlist(v) + + + +def test(): + a = vec(1, 2, 3) + b = vec(3, 2, 1) + print a + print b + print a+b + print a-b + print a*3.0 + +test() diff --git a/sys/src/cmd/python/Demo/classes/bitvec.py b/sys/src/cmd/python/Demo/classes/bitvec.py new file mode 100755 index 000000000..2894a56ae --- /dev/null +++ b/sys/src/cmd/python/Demo/classes/bitvec.py @@ -0,0 +1,332 @@ +# +# 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 + +error = 'bitvec.error' + + +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 'FATAL', '(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 diff --git a/sys/src/cmd/python/Demo/comparisons/README b/sys/src/cmd/python/Demo/comparisons/README new file mode 100644 index 000000000..111667c26 --- /dev/null +++ b/sys/src/cmd/python/Demo/comparisons/README @@ -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 diff --git a/sys/src/cmd/python/Demo/comparisons/patterns b/sys/src/cmd/python/Demo/comparisons/patterns new file mode 100755 index 000000000..f4da846af --- /dev/null +++ b/sys/src/cmd/python/Demo/comparisons/patterns @@ -0,0 +1,4 @@ +^def +^class +^import +^from diff --git a/sys/src/cmd/python/Demo/comparisons/regextest.py b/sys/src/cmd/python/Demo/comparisons/regextest.py new file mode 100755 index 000000000..b27d741d7 --- /dev/null +++ b/sys/src/cmd/python/Demo/comparisons/regextest.py @@ -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() diff --git a/sys/src/cmd/python/Demo/comparisons/sortingtest.py b/sys/src/cmd/python/Demo/comparisons/sortingtest.py new file mode 100755 index 000000000..cabf6260d --- /dev/null +++ b/sys/src/cmd/python/Demo/comparisons/sortingtest.py @@ -0,0 +1,51 @@ +#! /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 string +import sys + +def main(): + prog = re.compile('^(.*)=([-+]?[0-9]+)') + def makekey(item, prog=prog): + match = prog.match(item) + if match: + var, num = match.group(1, 2) + return string.atoi(num), var + else: + # Bad input -- pretend it's a var with value 0 + return 0, item + while 1: + line = sys.stdin.readline() + if not line: + break + items = line.split() + items = map(makekey, items) + items.sort() + for num, var in items: + print "%s=%s" % (var, num), + print + +main() diff --git a/sys/src/cmd/python/Demo/comparisons/systemtest.py b/sys/src/cmd/python/Demo/comparisons/systemtest.py new file mode 100755 index 000000000..bbc313ba1 --- /dev/null +++ b/sys/src/cmd/python/Demo/comparisons/systemtest.py @@ -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() diff --git a/sys/src/cmd/python/Demo/curses/README b/sys/src/cmd/python/Demo/curses/README new file mode 100644 index 000000000..2d1c4b1c2 --- /dev/null +++ b/sys/src/cmd/python/Demo/curses/README @@ -0,0 +1,25 @@ +This is a collection of demos and tests for the curses module. + +ncurses demos +============= + +These demos are converted from the C versions in the ncurses +distribution, and were contributed by Thomas Gellekum <tg@FreeBSD.org> +I didn't strive for a `pythonic' style, but bluntly copied the +originals. I won't attempt to `beautify' the program anytime soon, but +I wouldn't mind someone else making an effort in that direction, of +course. + +ncurses.py -- currently only a panels demo +rain.py -- raindrops keep falling on my desktop +tclock.py -- ASCII clock, by Howard Jones +xmas.py -- I'm dreaming of an ASCII christmas + +Please submit bugfixes and new contributions to the Python bug tracker. + + +Other demos +=========== + +life.py -- Simple game of Life +repeat.py -- Repeatedly execute a shell command (like watch(1)) diff --git a/sys/src/cmd/python/Demo/curses/life.py b/sys/src/cmd/python/Demo/curses/life.py new file mode 100755 index 000000000..a5bbed21e --- /dev/null +++ b/sys/src/cmd/python/Demo/curses/life.py @@ -0,0 +1,216 @@ +#!/usr/bin/env python +# life.py -- A curses-based version of Conway's Game of Life. +# Contributed by AMK +# +# An empty board will be displayed, and the following commands are available: +# E : Erase the board +# R : Fill the board randomly +# S : Step for a single generation +# C : Update continuously until a key is struck +# Q : Quit +# Cursor keys : Move the cursor around the board +# Space or Enter : Toggle the contents of the cursor's position +# +# TODO : +# Support the mouse +# Use colour if available +# Make board updates faster +# + +import random, string, traceback +import curses + +class LifeBoard: + """Encapsulates a Life board + + Attributes: + X,Y : horizontal and vertical size of the board + state : dictionary mapping (x,y) to 0 or 1 + + Methods: + display(update_board) -- If update_board is true, compute the + next generation. Then display the state + of the board and refresh the screen. + erase() -- clear the entire board + makeRandom() -- fill the board randomly + set(y,x) -- set the given cell to Live; doesn't refresh the screen + toggle(y,x) -- change the given cell from live to dead, or vice + versa, and refresh the screen display + + """ + def __init__(self, scr, char=ord('*')): + """Create a new LifeBoard instance. + + scr -- curses screen object to use for display + char -- character used to render live cells (default: '*') + """ + self.state = {} + self.scr = scr + Y, X = self.scr.getmaxyx() + self.X, self.Y = X-2, Y-2-1 + self.char = char + self.scr.clear() + + # Draw a border around the board + border_line = '+'+(self.X*'-')+'+' + self.scr.addstr(0, 0, border_line) + self.scr.addstr(self.Y+1,0, border_line) + for y in range(0, self.Y): + self.scr.addstr(1+y, 0, '|') + self.scr.addstr(1+y, self.X+1, '|') + self.scr.refresh() + + def set(self, y, x): + """Set a cell to the live state""" + if x<0 or self.X<=x or y<0 or self.Y<=y: + raise ValueError, "Coordinates out of range %i,%i"% (y,x) + self.state[x,y] = 1 + + def toggle(self, y, x): + """Toggle a cell's state between live and dead""" + if x<0 or self.X<=x or y<0 or self.Y<=y: + raise ValueError, "Coordinates out of range %i,%i"% (y,x) + if self.state.has_key( (x,y) ): + del self.state[x,y] + self.scr.addch(y+1, x+1, ' ') + else: + self.state[x,y] = 1 + self.scr.addch(y+1, x+1, self.char) + self.scr.refresh() + + def erase(self): + """Clear the entire board and update the board display""" + self.state = {} + self.display(update_board=False) + + def display(self, update_board=True): + """Display the whole board, optionally computing one generation""" + M,N = self.X, self.Y + if not update_board: + for i in range(0, M): + for j in range(0, N): + if self.state.has_key( (i,j) ): + self.scr.addch(j+1, i+1, self.char) + else: + self.scr.addch(j+1, i+1, ' ') + self.scr.refresh() + return + + d = {} + self.boring = 1 + for i in range(0, M): + L = range( max(0, i-1), min(M, i+2) ) + for j in range(0, N): + s = 0 + live = self.state.has_key( (i,j) ) + for k in range( max(0, j-1), min(N, j+2) ): + for l in L: + if self.state.has_key( (l,k) ): + s += 1 + s -= live + if s == 3: + # Birth + d[i,j] = 1 + self.scr.addch(j+1, i+1, self.char) + if not live: self.boring = 0 + elif s == 2 and live: d[i,j] = 1 # Survival + elif live: + # Death + self.scr.addch(j+1, i+1, ' ') + self.boring = 0 + self.state = d + self.scr.refresh() + + def makeRandom(self): + "Fill the board with a random pattern" + self.state = {} + for i in range(0, self.X): + for j in range(0, self.Y): + if random.random() > 0.5: + self.set(j,i) + + +def erase_menu(stdscr, menu_y): + "Clear the space where the menu resides" + stdscr.move(menu_y, 0) + stdscr.clrtoeol() + stdscr.move(menu_y+1, 0) + stdscr.clrtoeol() + +def display_menu(stdscr, menu_y): + "Display the menu of possible keystroke commands" + erase_menu(stdscr, menu_y) + stdscr.addstr(menu_y, 4, + 'Use the cursor keys to move, and space or Enter to toggle a cell.') + stdscr.addstr(menu_y+1, 4, + 'E)rase the board, R)andom fill, S)tep once or C)ontinuously, Q)uit') + +def keyloop(stdscr): + # Clear the screen and display the menu of keys + stdscr.clear() + stdscr_y, stdscr_x = stdscr.getmaxyx() + menu_y = (stdscr_y-3)-1 + display_menu(stdscr, menu_y) + + # Allocate a subwindow for the Life board and create the board object + subwin = stdscr.subwin(stdscr_y-3, stdscr_x, 0, 0) + board = LifeBoard(subwin, char=ord('*')) + board.display(update_board=False) + + # xpos, ypos are the cursor's position + xpos, ypos = board.X/2, board.Y/2 + + # Main loop: + while (1): + stdscr.move(1+ypos, 1+xpos) # Move the cursor + c = stdscr.getch() # Get a keystroke + if 0<c<256: + c = chr(c) + if c in ' \n': + board.toggle(ypos, xpos) + elif c in 'Cc': + erase_menu(stdscr, menu_y) + stdscr.addstr(menu_y, 6, ' Hit any key to stop continuously ' + 'updating the screen.') + stdscr.refresh() + # Activate nodelay mode; getch() will return -1 + # if no keystroke is available, instead of waiting. + stdscr.nodelay(1) + while (1): + c = stdscr.getch() + if c != -1: + break + stdscr.addstr(0,0, '/') + stdscr.refresh() + board.display() + stdscr.addstr(0,0, '+') + stdscr.refresh() + + stdscr.nodelay(0) # Disable nodelay mode + display_menu(stdscr, menu_y) + + elif c in 'Ee': + board.erase() + elif c in 'Qq': + break + elif c in 'Rr': + board.makeRandom() + board.display(update_board=False) + elif c in 'Ss': + board.display() + else: pass # Ignore incorrect keys + elif c == curses.KEY_UP and ypos>0: ypos -= 1 + elif c == curses.KEY_DOWN and ypos<board.Y-1: ypos += 1 + elif c == curses.KEY_LEFT and xpos>0: xpos -= 1 + elif c == curses.KEY_RIGHT and xpos<board.X-1: xpos += 1 + else: + # Ignore incorrect keys + pass + + +def main(stdscr): + keyloop(stdscr) # Enter the main loop + + +if __name__ == '__main__': + curses.wrapper(main) diff --git a/sys/src/cmd/python/Demo/curses/ncurses.py b/sys/src/cmd/python/Demo/curses/ncurses.py new file mode 100644 index 000000000..5c6f4b6ee --- /dev/null +++ b/sys/src/cmd/python/Demo/curses/ncurses.py @@ -0,0 +1,273 @@ +#!/usr/bin/env python +# +# $Id: ncurses.py 36559 2004-07-18 05:56:09Z tim_one $ +# +# (n)curses exerciser in Python, an interactive test for the curses +# module. Currently, only the panel demos are ported. + +import curses +from curses import panel + +def wGetchar(win = None): + if win == None: win = stdscr + return win.getch() + +def Getchar(): + wGetchar() + +# +# Panels tester +# +def wait_a_while(): + if nap_msec == 1: + Getchar() + else: + curses.napms(nap_msec) + +def saywhat(text): + stdscr.move(curses.LINES - 1, 0) + stdscr.clrtoeol() + stdscr.addstr(text) + +def mkpanel(color, rows, cols, tly, tlx): + win = curses.newwin(rows, cols, tly, tlx) + pan = panel.new_panel(win) + if curses.has_colors(): + if color == curses.COLOR_BLUE: + fg = curses.COLOR_WHITE + else: + fg = curses.COLOR_BLACK + bg = color + curses.init_pair(color, fg, bg) + win.bkgdset(ord(' '), curses.color_pair(color)) + else: + win.bkgdset(ord(' '), curses.A_BOLD) + + return pan + +def pflush(): + panel.update_panels() + curses.doupdate() + +def fill_panel(pan): + win = pan.window() + num = pan.userptr()[1] + + win.move(1, 1) + win.addstr("-pan%c-" % num) + win.clrtoeol() + win.box() + + maxy, maxx = win.getmaxyx() + for y in range(2, maxy - 1): + for x in range(1, maxx - 1): + win.move(y, x) + win.addch(num) + +def demo_panels(win): + global stdscr, nap_msec, mod + stdscr = win + nap_msec = 1 + mod = ["test", "TEST", "(**)", "*()*", "<-->", "LAST"] + + stdscr.refresh() + + for y in range(0, curses.LINES - 1): + for x in range(0, curses.COLS): + stdscr.addstr("%d" % ((y + x) % 10)) + for y in range(0, 1): + p1 = mkpanel(curses.COLOR_RED, + curses.LINES / 2 - 2, + curses.COLS / 8 + 1, + 0, + 0) + p1.set_userptr("p1") + + p2 = mkpanel(curses.COLOR_GREEN, + curses.LINES / 2 + 1, + curses.COLS / 7, + curses.LINES / 4, + curses.COLS / 10) + p2.set_userptr("p2") + + p3 = mkpanel(curses.COLOR_YELLOW, + curses.LINES / 4, + curses.COLS / 10, + curses.LINES / 2, + curses.COLS / 9) + p3.set_userptr("p3") + + p4 = mkpanel(curses.COLOR_BLUE, + curses.LINES / 2 - 2, + curses.COLS / 8, + curses.LINES / 2 - 2, + curses.COLS / 3) + p4.set_userptr("p4") + + p5 = mkpanel(curses.COLOR_MAGENTA, + curses.LINES / 2 - 2, + curses.COLS / 8, + curses.LINES / 2, + curses.COLS / 2 - 2) + p5.set_userptr("p5") + + fill_panel(p1) + fill_panel(p2) + fill_panel(p3) + fill_panel(p4) + fill_panel(p5) + p4.hide() + p5.hide() + pflush() + saywhat("press any key to continue") + wait_a_while() + + saywhat("h3 s1 s2 s4 s5;press any key to continue") + p1.move(0, 0) + p3.hide() + p1.show() + p2.show() + p4.show() + p5.show() + pflush() + wait_a_while() + + saywhat("s1; press any key to continue") + p1.show() + pflush() + wait_a_while() + + saywhat("s2; press any key to continue") + p2.show() + pflush() + wait_a_while() + + saywhat("m2; press any key to continue") + p2.move(curses.LINES / 3 + 1, curses.COLS / 8) + pflush() + wait_a_while() + + saywhat("s3; press any key to continue") + p3.show() + pflush() + wait_a_while() + + saywhat("m3; press any key to continue") + p3.move(curses.LINES / 4 + 1, curses.COLS / 15) + pflush() + wait_a_while() + + saywhat("b3; press any key to continue") + p3.bottom() + pflush() + wait_a_while() + + saywhat("s4; press any key to continue") + p4.show() + pflush() + wait_a_while() + + saywhat("s5; press any key to continue") + p5.show() + pflush() + wait_a_while() + + saywhat("t3; press any key to continue") + p3.top() + pflush() + wait_a_while() + + saywhat("t1; press any key to continue") + p1.show() + pflush() + wait_a_while() + + saywhat("t2; press any key to continue") + p2.show() + pflush() + wait_a_while() + + saywhat("t3; press any key to continue") + p3.show() + pflush() + wait_a_while() + + saywhat("t4; press any key to continue") + p4.show() + pflush() + wait_a_while() + + for itmp in range(0, 6): + w4 = p4.window() + w5 = p5.window() + + saywhat("m4; press any key to continue") + w4.move(curses.LINES / 8, 1) + w4.addstr(mod[itmp]) + p4.move(curses.LINES / 6, itmp * curses.COLS / 8) + w5.move(curses.LINES / 6, 1) + w5.addstr(mod[itmp]) + pflush() + wait_a_while() + + saywhat("m5; press any key to continue") + w4.move(curses.LINES / 6, 1) + w4.addstr(mod[itmp]) + p5.move(curses.LINES / 3 - 1, itmp * 10 + 6) + w5.move(curses.LINES / 8, 1) + w5.addstr(mod[itmp]) + pflush() + wait_a_while() + + saywhat("m4; press any key to continue") + p4.move(curses.LINES / 6, (itmp + 1) * curses.COLS / 8) + pflush() + wait_a_while() + + saywhat("t5; press any key to continue") + p5.top() + pflush() + wait_a_while() + + saywhat("t2; press any key to continue") + p2.top() + pflush() + wait_a_while() + + saywhat("t1; press any key to continue") + p1.top() + pflush() + wait_a_while() + + saywhat("d2; press any key to continue") + del p2 + pflush() + wait_a_while() + + saywhat("h3; press any key to continue") + p3.hide() + pflush() + wait_a_while() + + saywhat("d1; press any key to continue") + del p1 + pflush() + wait_a_while() + + saywhat("d4; press any key to continue") + del p4 + pflush() + wait_a_while() + + saywhat("d5; press any key to continue") + del p5 + pflush() + wait_a_while() + if nap_msec == 1: + break + nap_msec = 100 + +# +# one fine day there'll be the menu at this place +# +curses.wrapper(demo_panels) diff --git a/sys/src/cmd/python/Demo/curses/rain.py b/sys/src/cmd/python/Demo/curses/rain.py new file mode 100755 index 000000000..c0a5fc122 --- /dev/null +++ b/sys/src/cmd/python/Demo/curses/rain.py @@ -0,0 +1,94 @@ +#!/bin/python +# +# $Id: rain.py 46625 2006-06-03 23:02:15Z andrew.kuchling $ +# +# somebody should probably check the randrange()s... + +import curses +from random import randrange + +def next_j(j): + if j == 0: + j = 4 + else: + j -= 1 + + if curses.has_colors(): + z = randrange(0, 3) + color = curses.color_pair(z) + if z: + color = color | curses.A_BOLD + stdscr.attrset(color) + + return j + +def main(win): + # we know that the first argument from curses.wrapper() is stdscr. + # Initialize it globally for convenience. + global stdscr + stdscr = win + + if curses.has_colors(): + bg = curses.COLOR_BLACK + curses.init_pair(1, curses.COLOR_BLUE, bg) + curses.init_pair(2, curses.COLOR_CYAN, bg) + + curses.nl() + curses.noecho() + # XXX curs_set() always returns ERR + # curses.curs_set(0) + stdscr.timeout(0) + + c = curses.COLS - 4 + r = curses.LINES - 4 + xpos = [0] * c + ypos = [0] * r + for j in range(4, -1, -1): + xpos[j] = randrange(0, c) + 2 + ypos[j] = randrange(0, r) + 2 + + j = 0 + while True: + x = randrange(0, c) + 2 + y = randrange(0, r) + 2 + + stdscr.addch(y, x, ord('.')) + + stdscr.addch(ypos[j], xpos[j], ord('o')) + + j = next_j(j) + stdscr.addch(ypos[j], xpos[j], ord('O')) + + j = next_j(j) + stdscr.addch( ypos[j] - 1, xpos[j], ord('-')) + stdscr.addstr(ypos[j], xpos[j] - 1, "|.|") + stdscr.addch( ypos[j] + 1, xpos[j], ord('-')) + + j = next_j(j) + stdscr.addch( ypos[j] - 2, xpos[j], ord('-')) + stdscr.addstr(ypos[j] - 1, xpos[j] - 1, "/ \\") + stdscr.addstr(ypos[j], xpos[j] - 2, "| O |") + stdscr.addstr(ypos[j] + 1, xpos[j] - 1, "\\ /") + stdscr.addch( ypos[j] + 2, xpos[j], ord('-')) + + j = next_j(j) + stdscr.addch( ypos[j] - 2, xpos[j], ord(' ')) + stdscr.addstr(ypos[j] - 1, xpos[j] - 1, " ") + stdscr.addstr(ypos[j], xpos[j] - 2, " ") + stdscr.addstr(ypos[j] + 1, xpos[j] - 1, " ") + stdscr.addch( ypos[j] + 2, xpos[j], ord(' ')) + + xpos[j] = x + ypos[j] = y + + ch = stdscr.getch() + if ch == ord('q') or ch == ord('Q'): + return + elif ch == ord('s'): + stdscr.nodelay(0) + elif ch == ord(' '): + stdscr.nodelay(1) + + curses.napms(50) + +curses.wrapper(main) diff --git a/sys/src/cmd/python/Demo/curses/repeat.py b/sys/src/cmd/python/Demo/curses/repeat.py new file mode 100755 index 000000000..fa7daac17 --- /dev/null +++ b/sys/src/cmd/python/Demo/curses/repeat.py @@ -0,0 +1,58 @@ +#! /usr/bin/env python + +"""repeat <shell-command> + +This simple program repeatedly (at 1-second intervals) executes the +shell command given on the command line and displays the output (or as +much of it as fits on the screen). It uses curses to paint each new +output on top of the old output, so that if nothing changes, the +screen doesn't change. This is handy to watch for changes in e.g. a +directory or process listing. + +To end, hit Control-C. +""" + +# Author: Guido van Rossum + +# Disclaimer: there's a Linux program named 'watch' that does the same +# thing. Honestly, I didn't know of its existence when I wrote this! + +# To do: add features until it has the same functionality as watch(1); +# then compare code size and development time. + +import os +import sys +import time +import curses + +def main(): + if not sys.argv[1:]: + print __doc__ + sys.exit(0) + cmd = " ".join(sys.argv[1:]) + p = os.popen(cmd, "r") + text = p.read() + sts = p.close() + if sts: + print >>sys.stderr, "Exit code:", sts + sys.exit(sts) + w = curses.initscr() + try: + while True: + w.erase() + try: + w.addstr(text) + except curses.error: + pass + w.refresh() + time.sleep(1) + p = os.popen(cmd, "r") + text = p.read() + sts = p.close() + if sts: + print >>sys.stderr, "Exit code:", sts + sys.exit(sts) + finally: + curses.endwin() + +main() diff --git a/sys/src/cmd/python/Demo/curses/tclock.py b/sys/src/cmd/python/Demo/curses/tclock.py new file mode 100644 index 000000000..11c59f1a6 --- /dev/null +++ b/sys/src/cmd/python/Demo/curses/tclock.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python +# +# $Id: tclock.py 46626 2006-06-03 23:07:21Z andrew.kuchling $ +# +# From tclock.c, Copyright Howard Jones <ha.jones@ic.ac.uk>, September 1994. + +from math import * +import curses, time + +ASPECT = 2.2 + +def sign(_x): + if _x < 0: return -1 + return 1 + +def A2XY(angle, radius): + return (int(round(ASPECT * radius * sin(angle))), + int(round(radius * cos(angle)))) + +def plot(x, y, col): + stdscr.addch(y, x, col) + +# draw a diagonal line using Bresenham's algorithm +def dline(pair, from_x, from_y, x2, y2, ch): + if curses.has_colors(): + stdscr.attrset(curses.color_pair(pair)) + + dx = x2 - from_x + dy = y2 - from_y + + ax = abs(dx * 2) + ay = abs(dy * 2) + + sx = sign(dx) + sy = sign(dy) + + x = from_x + y = from_y + + if ax > ay: + d = ay - ax // 2 + + while True: + plot(x, y, ch) + if x == x2: + return + + if d >= 0: + y += sy + d -= ax + x += sx + d += ay + else: + d = ax - ay // 2 + + while True: + plot(x, y, ch) + if y == y2: + return + + if d >= 0: + x += sx + d -= ay + y += sy + d += ax + +def main(win): + global stdscr + stdscr = win + + lastbeep = -1 + my_bg = curses.COLOR_BLACK + + stdscr.nodelay(1) + stdscr.timeout(0) +# curses.curs_set(0) + if curses.has_colors(): + curses.init_pair(1, curses.COLOR_RED, my_bg) + curses.init_pair(2, curses.COLOR_MAGENTA, my_bg) + curses.init_pair(3, curses.COLOR_GREEN, my_bg) + + cx = (curses.COLS - 1) // 2 + cy = curses.LINES // 2 + ch = min( cy-1, int(cx // ASPECT) - 1) + mradius = (3 * ch) // 4 + hradius = ch // 2 + sradius = 5 * ch // 6 + + for i in range(0, 12): + sangle = (i + 1) * 2.0 * pi / 12.0 + sdx, sdy = A2XY(sangle, sradius) + + stdscr.addstr(cy - sdy, cx + sdx, "%d" % (i + 1)) + + stdscr.addstr(0, 0, + "ASCII Clock by Howard Jones <ha.jones@ic.ac.uk>, 1994") + + sradius = max(sradius-4, 8) + + while True: + curses.napms(1000) + + tim = time.time() + t = time.localtime(tim) + + hours = t[3] + t[4] / 60.0 + if hours > 12.0: + hours -= 12.0 + + mangle = t[4] * 2 * pi / 60.0 + mdx, mdy = A2XY(mangle, mradius) + + hangle = hours * 2 * pi / 12.0 + hdx, hdy = A2XY(hangle, hradius) + + sangle = t[5] * 2 * pi / 60.0 + sdx, sdy = A2XY(sangle, sradius) + + dline(3, cx, cy, cx + mdx, cy - mdy, ord('#')) + + stdscr.attrset(curses.A_REVERSE) + dline(2, cx, cy, cx + hdx, cy - hdy, ord('.')) + stdscr.attroff(curses.A_REVERSE) + + if curses.has_colors(): + stdscr.attrset(curses.color_pair(1)) + + plot(cx + sdx, cy - sdy, ord('O')) + + if curses.has_colors(): + stdscr.attrset(curses.color_pair(0)) + + stdscr.addstr(curses.LINES - 2, 0, time.ctime(tim)) + stdscr.refresh() + if (t[5] % 5) == 0 and t[5] != lastbeep: + lastbeep = t[5] + curses.beep() + + ch = stdscr.getch() + if ch == ord('q'): + return 0 + + plot(cx + sdx, cy - sdy, ord(' ')) + dline(0, cx, cy, cx + hdx, cy - hdy, ord(' ')) + dline(0, cx, cy, cx + mdx, cy - mdy, ord(' ')) + +curses.wrapper(main) diff --git a/sys/src/cmd/python/Demo/curses/xmas.py b/sys/src/cmd/python/Demo/curses/xmas.py new file mode 100644 index 000000000..fa4a4f65b --- /dev/null +++ b/sys/src/cmd/python/Demo/curses/xmas.py @@ -0,0 +1,906 @@ +# asciixmas +# December 1989 Larry Bartz Indianapolis, IN +# +# $Id: xmas.py 46623 2006-06-03 22:59:23Z andrew.kuchling $ +# +# I'm dreaming of an ascii character-based monochrome Christmas, +# Just like the ones I used to know! +# Via a full duplex communications channel, +# At 9600 bits per second, +# Even though it's kinda slow. +# +# I'm dreaming of an ascii character-based monochrome Christmas, +# With ev'ry C program I write! +# May your screen be merry and bright! +# And may all your Christmases be amber or green, +# (for reduced eyestrain and improved visibility)! +# +# +# Notes on the Python version: +# I used a couple of `try...except curses.error' to get around some functions +# returning ERR. The errors come from using wrapping functions to fill +# windows to the last character cell. The C version doesn't have this problem, +# it simply ignores any return values. +# + +import curses +import sys + +FROMWHO = "Thomas Gellekum <tg@FreeBSD.org>" + +def set_color(win, color): + if curses.has_colors(): + n = color + 1 + curses.init_pair(n, color, my_bg) + win.attroff(curses.A_COLOR) + win.attron(curses.color_pair(n)) + +def unset_color(win): + if curses.has_colors(): + win.attrset(curses.color_pair(0)) + +def look_out(msecs): + curses.napms(msecs) + if stdscr.getch() != -1: + curses.beep() + sys.exit(0) + +def boxit(): + for y in range(0, 20): + stdscr.addch(y, 7, ord('|')) + + for x in range(8, 80): + stdscr.addch(19, x, ord('_')) + + for x in range(0, 80): + stdscr.addch(22, x, ord('_')) + + return + +def seas(): + stdscr.addch(4, 1, ord('S')) + stdscr.addch(6, 1, ord('E')) + stdscr.addch(8, 1, ord('A')) + stdscr.addch(10, 1, ord('S')) + stdscr.addch(12, 1, ord('O')) + stdscr.addch(14, 1, ord('N')) + stdscr.addch(16, 1, ord("'")) + stdscr.addch(18, 1, ord('S')) + + return + +def greet(): + stdscr.addch(3, 5, ord('G')) + stdscr.addch(5, 5, ord('R')) + stdscr.addch(7, 5, ord('E')) + stdscr.addch(9, 5, ord('E')) + stdscr.addch(11, 5, ord('T')) + stdscr.addch(13, 5, ord('I')) + stdscr.addch(15, 5, ord('N')) + stdscr.addch(17, 5, ord('G')) + stdscr.addch(19, 5, ord('S')) + + return + +def fromwho(): + stdscr.addstr(21, 13, FROMWHO) + return + +def tree(): + set_color(treescrn, curses.COLOR_GREEN) + treescrn.addch(1, 11, ord('/')) + treescrn.addch(2, 11, ord('/')) + treescrn.addch(3, 10, ord('/')) + treescrn.addch(4, 9, ord('/')) + treescrn.addch(5, 9, ord('/')) + treescrn.addch(6, 8, ord('/')) + treescrn.addch(7, 7, ord('/')) + treescrn.addch(8, 6, ord('/')) + treescrn.addch(9, 6, ord('/')) + treescrn.addch(10, 5, ord('/')) + treescrn.addch(11, 3, ord('/')) + treescrn.addch(12, 2, ord('/')) + + treescrn.addch(1, 13, ord('\\')) + treescrn.addch(2, 13, ord('\\')) + treescrn.addch(3, 14, ord('\\')) + treescrn.addch(4, 15, ord('\\')) + treescrn.addch(5, 15, ord('\\')) + treescrn.addch(6, 16, ord('\\')) + treescrn.addch(7, 17, ord('\\')) + treescrn.addch(8, 18, ord('\\')) + treescrn.addch(9, 18, ord('\\')) + treescrn.addch(10, 19, ord('\\')) + treescrn.addch(11, 21, ord('\\')) + treescrn.addch(12, 22, ord('\\')) + + treescrn.addch(4, 10, ord('_')) + treescrn.addch(4, 14, ord('_')) + treescrn.addch(8, 7, ord('_')) + treescrn.addch(8, 17, ord('_')) + + treescrn.addstr(13, 0, "//////////// \\\\\\\\\\\\\\\\\\\\\\\\") + + treescrn.addstr(14, 11, "| |") + treescrn.addstr(15, 11, "|_|") + + unset_color(treescrn) + treescrn.refresh() + w_del_msg.refresh() + + return + +def balls(): + treescrn.overlay(treescrn2) + + set_color(treescrn2, curses.COLOR_BLUE) + treescrn2.addch(3, 9, ord('@')) + treescrn2.addch(3, 15, ord('@')) + treescrn2.addch(4, 8, ord('@')) + treescrn2.addch(4, 16, ord('@')) + treescrn2.addch(5, 7, ord('@')) + treescrn2.addch(5, 17, ord('@')) + treescrn2.addch(7, 6, ord('@')) + treescrn2.addch(7, 18, ord('@')) + treescrn2.addch(8, 5, ord('@')) + treescrn2.addch(8, 19, ord('@')) + treescrn2.addch(10, 4, ord('@')) + treescrn2.addch(10, 20, ord('@')) + treescrn2.addch(11, 2, ord('@')) + treescrn2.addch(11, 22, ord('@')) + treescrn2.addch(12, 1, ord('@')) + treescrn2.addch(12, 23, ord('@')) + + unset_color(treescrn2) + treescrn2.refresh() + w_del_msg.refresh() + return + +def star(): + treescrn2.attrset(curses.A_BOLD | curses.A_BLINK) + set_color(treescrn2, curses.COLOR_YELLOW) + + treescrn2.addch(0, 12, ord('*')) + treescrn2.standend() + + unset_color(treescrn2) + treescrn2.refresh() + w_del_msg.refresh() + return + +def strng1(): + treescrn2.attrset(curses.A_BOLD | curses.A_BLINK) + set_color(treescrn2, curses.COLOR_WHITE) + + treescrn2.addch(3, 13, ord('\'')) + treescrn2.addch(3, 12, ord(':')) + treescrn2.addch(3, 11, ord('.')) + + treescrn2.attroff(curses.A_BOLD | curses.A_BLINK) + unset_color(treescrn2) + + treescrn2.refresh() + w_del_msg.refresh() + return + +def strng2(): + treescrn2.attrset(curses.A_BOLD | curses.A_BLINK) + set_color(treescrn2, curses.COLOR_WHITE) + + treescrn2.addch(5, 14, ord('\'')) + treescrn2.addch(5, 13, ord(':')) + treescrn2.addch(5, 12, ord('.')) + treescrn2.addch(5, 11, ord(',')) + treescrn2.addch(6, 10, ord('\'')) + treescrn2.addch(6, 9, ord(':')) + + treescrn2.attroff(curses.A_BOLD | curses.A_BLINK) + unset_color(treescrn2) + + treescrn2.refresh() + w_del_msg.refresh() + return + +def strng3(): + treescrn2.attrset(curses.A_BOLD | curses.A_BLINK) + set_color(treescrn2, curses.COLOR_WHITE) + + treescrn2.addch(7, 16, ord('\'')) + treescrn2.addch(7, 15, ord(':')) + treescrn2.addch(7, 14, ord('.')) + treescrn2.addch(7, 13, ord(',')) + treescrn2.addch(8, 12, ord('\'')) + treescrn2.addch(8, 11, ord(':')) + treescrn2.addch(8, 10, ord('.')) + treescrn2.addch(8, 9, ord(',')) + + treescrn2.attroff(curses.A_BOLD | curses.A_BLINK) + unset_color(treescrn2) + + treescrn2.refresh() + w_del_msg.refresh() + return + +def strng4(): + treescrn2.attrset(curses.A_BOLD | curses.A_BLINK) + set_color(treescrn2, curses.COLOR_WHITE) + + treescrn2.addch(9, 17, ord('\'')) + treescrn2.addch(9, 16, ord(':')) + treescrn2.addch(9, 15, ord('.')) + treescrn2.addch(9, 14, ord(',')) + treescrn2.addch(10, 13, ord('\'')) + treescrn2.addch(10, 12, ord(':')) + treescrn2.addch(10, 11, ord('.')) + treescrn2.addch(10, 10, ord(',')) + treescrn2.addch(11, 9, ord('\'')) + treescrn2.addch(11, 8, ord(':')) + treescrn2.addch(11, 7, ord('.')) + treescrn2.addch(11, 6, ord(',')) + treescrn2.addch(12, 5, ord('\'')) + + treescrn2.attroff(curses.A_BOLD | curses.A_BLINK) + unset_color(treescrn2) + + treescrn2.refresh() + w_del_msg.refresh() + return + +def strng5(): + treescrn2.attrset(curses.A_BOLD | curses.A_BLINK) + set_color(treescrn2, curses.COLOR_WHITE) + + treescrn2.addch(11, 19, ord('\'')) + treescrn2.addch(11, 18, ord(':')) + treescrn2.addch(11, 17, ord('.')) + treescrn2.addch(11, 16, ord(',')) + treescrn2.addch(12, 15, ord('\'')) + treescrn2.addch(12, 14, ord(':')) + treescrn2.addch(12, 13, ord('.')) + treescrn2.addch(12, 12, ord(',')) + + treescrn2.attroff(curses.A_BOLD | curses.A_BLINK) + unset_color(treescrn2) + + # save a fully lit tree + treescrn2.overlay(treescrn) + + treescrn2.refresh() + w_del_msg.refresh() + return + +def blinkit(): + treescrn8.touchwin() + + for cycle in range(5): + if cycle == 0: + treescrn3.overlay(treescrn8) + treescrn8.refresh() + w_del_msg.refresh() + break + elif cycle == 1: + treescrn4.overlay(treescrn8) + treescrn8.refresh() + w_del_msg.refresh() + break + elif cycle == 2: + treescrn5.overlay(treescrn8) + treescrn8.refresh() + w_del_msg.refresh() + break + elif cycle == 3: + treescrn6.overlay(treescrn8) + treescrn8.refresh() + w_del_msg.refresh() + break + elif cycle == 4: + treescrn7.overlay(treescrn8) + treescrn8.refresh() + w_del_msg.refresh() + break + + treescrn8.touchwin() + + # ALL ON + treescrn.overlay(treescrn8) + treescrn8.refresh() + w_del_msg.refresh() + + return + +def deer_step(win, y, x): + win.mvwin(y, x) + win.refresh() + w_del_msg.refresh() + look_out(5) + +def reindeer(): + y_pos = 0 + + for x_pos in range(70, 62, -1): + if x_pos < 66: y_pos = 1 + for looper in range(0, 4): + dotdeer0.addch(y_pos, x_pos, ord('.')) + dotdeer0.refresh() + w_del_msg.refresh() + dotdeer0.erase() + dotdeer0.refresh() + w_del_msg.refresh() + look_out(50) + + y_pos = 2 + + for x_pos in range(x_pos - 1, 50, -1): + for looper in range(0, 4): + if x_pos < 56: + y_pos = 3 + + try: + stardeer0.addch(y_pos, x_pos, ord('*')) + except curses.error: + pass + stardeer0.refresh() + w_del_msg.refresh() + stardeer0.erase() + stardeer0.refresh() + w_del_msg.refresh() + else: + dotdeer0.addch(y_pos, x_pos, ord('*')) + dotdeer0.refresh() + w_del_msg.refresh() + dotdeer0.erase() + dotdeer0.refresh() + w_del_msg.refresh() + + x_pos = 58 + + for y_pos in range(2, 5): + lildeer0.touchwin() + lildeer0.refresh() + w_del_msg.refresh() + + for looper in range(0, 4): + deer_step(lildeer3, y_pos, x_pos) + deer_step(lildeer2, y_pos, x_pos) + deer_step(lildeer1, y_pos, x_pos) + deer_step(lildeer2, y_pos, x_pos) + deer_step(lildeer3, y_pos, x_pos) + + lildeer0.touchwin() + lildeer0.refresh() + w_del_msg.refresh() + + x_pos -= 2 + + x_pos = 35 + + for y_pos in range(5, 10): + + middeer0.touchwin() + middeer0.refresh() + w_del_msg.refresh() + + for looper in range(2): + deer_step(middeer3, y_pos, x_pos) + deer_step(middeer2, y_pos, x_pos) + deer_step(middeer1, y_pos, x_pos) + deer_step(middeer2, y_pos, x_pos) + deer_step(middeer3, y_pos, x_pos) + + middeer0.touchwin() + middeer0.refresh() + w_del_msg.refresh() + + x_pos -= 3 + + look_out(300) + + y_pos = 1 + + for x_pos in range(8, 16): + deer_step(bigdeer4, y_pos, x_pos) + deer_step(bigdeer3, y_pos, x_pos) + deer_step(bigdeer2, y_pos, x_pos) + deer_step(bigdeer1, y_pos, x_pos) + deer_step(bigdeer2, y_pos, x_pos) + deer_step(bigdeer3, y_pos, x_pos) + deer_step(bigdeer4, y_pos, x_pos) + deer_step(bigdeer0, y_pos, x_pos) + + x_pos -= 1 + + for looper in range(0, 6): + deer_step(lookdeer4, y_pos, x_pos) + deer_step(lookdeer3, y_pos, x_pos) + deer_step(lookdeer2, y_pos, x_pos) + deer_step(lookdeer1, y_pos, x_pos) + deer_step(lookdeer2, y_pos, x_pos) + deer_step(lookdeer3, y_pos, x_pos) + deer_step(lookdeer4, y_pos, x_pos) + + deer_step(lookdeer0, y_pos, x_pos) + + for y_pos in range(y_pos, 10): + for looper in range(0, 2): + deer_step(bigdeer4, y_pos, x_pos) + deer_step(bigdeer3, y_pos, x_pos) + deer_step(bigdeer2, y_pos, x_pos) + deer_step(bigdeer1, y_pos, x_pos) + deer_step(bigdeer2, y_pos, x_pos) + deer_step(bigdeer3, y_pos, x_pos) + deer_step(bigdeer4, y_pos, x_pos) + deer_step(bigdeer0, y_pos, x_pos) + + y_pos -= 1 + + deer_step(lookdeer3, y_pos, x_pos) + return + +def main(win): + global stdscr + stdscr = win + + global my_bg, y_pos, x_pos + global treescrn, treescrn2, treescrn3, treescrn4 + global treescrn5, treescrn6, treescrn7, treescrn8 + global dotdeer0, stardeer0 + global lildeer0, lildeer1, lildeer2, lildeer3 + global middeer0, middeer1, middeer2, middeer3 + global bigdeer0, bigdeer1, bigdeer2, bigdeer3, bigdeer4 + global lookdeer0, lookdeer1, lookdeer2, lookdeer3, lookdeer4 + global w_holiday, w_del_msg + + my_bg = curses.COLOR_BLACK + # curses.curs_set(0) + + treescrn = curses.newwin(16, 27, 3, 53) + treescrn2 = curses.newwin(16, 27, 3, 53) + treescrn3 = curses.newwin(16, 27, 3, 53) + treescrn4 = curses.newwin(16, 27, 3, 53) + treescrn5 = curses.newwin(16, 27, 3, 53) + treescrn6 = curses.newwin(16, 27, 3, 53) + treescrn7 = curses.newwin(16, 27, 3, 53) + treescrn8 = curses.newwin(16, 27, 3, 53) + + dotdeer0 = curses.newwin(3, 71, 0, 8) + + stardeer0 = curses.newwin(4, 56, 0, 8) + + lildeer0 = curses.newwin(7, 53, 0, 8) + lildeer1 = curses.newwin(2, 4, 0, 0) + lildeer2 = curses.newwin(2, 4, 0, 0) + lildeer3 = curses.newwin(2, 4, 0, 0) + + middeer0 = curses.newwin(15, 42, 0, 8) + middeer1 = curses.newwin(3, 7, 0, 0) + middeer2 = curses.newwin(3, 7, 0, 0) + middeer3 = curses.newwin(3, 7, 0, 0) + + bigdeer0 = curses.newwin(10, 23, 0, 0) + bigdeer1 = curses.newwin(10, 23, 0, 0) + bigdeer2 = curses.newwin(10, 23, 0, 0) + bigdeer3 = curses.newwin(10, 23, 0, 0) + bigdeer4 = curses.newwin(10, 23, 0, 0) + + lookdeer0 = curses.newwin(10, 25, 0, 0) + lookdeer1 = curses.newwin(10, 25, 0, 0) + lookdeer2 = curses.newwin(10, 25, 0, 0) + lookdeer3 = curses.newwin(10, 25, 0, 0) + lookdeer4 = curses.newwin(10, 25, 0, 0) + + w_holiday = curses.newwin(1, 27, 3, 27) + + w_del_msg = curses.newwin(1, 20, 23, 60) + + try: + w_del_msg.addstr(0, 0, "Hit any key to quit") + except curses.error: + pass + + try: + w_holiday.addstr(0, 0, "H A P P Y H O L I D A Y S") + except curses.error: + pass + + # set up the windows for our various reindeer + lildeer1.addch(0, 0, ord('V')) + lildeer1.addch(1, 0, ord('@')) + lildeer1.addch(1, 1, ord('<')) + lildeer1.addch(1, 2, ord('>')) + try: + lildeer1.addch(1, 3, ord('~')) + except curses.error: + pass + + lildeer2.addch(0, 0, ord('V')) + lildeer2.addch(1, 0, ord('@')) + lildeer2.addch(1, 1, ord('|')) + lildeer2.addch(1, 2, ord('|')) + try: + lildeer2.addch(1, 3, ord('~')) + except curses.error: + pass + + lildeer3.addch(0, 0, ord('V')) + lildeer3.addch(1, 0, ord('@')) + lildeer3.addch(1, 1, ord('>')) + lildeer3.addch(1, 2, ord('<')) + try: + lildeer2.addch(1, 3, ord('~')) # XXX + except curses.error: + pass + + middeer1.addch(0, 2, ord('y')) + middeer1.addch(0, 3, ord('y')) + middeer1.addch(1, 2, ord('0')) + middeer1.addch(1, 3, ord('(')) + middeer1.addch(1, 4, ord('=')) + middeer1.addch(1, 5, ord(')')) + middeer1.addch(1, 6, ord('~')) + middeer1.addch(2, 3, ord('\\')) + middeer1.addch(2, 5, ord('/')) + + middeer2.addch(0, 2, ord('y')) + middeer2.addch(0, 3, ord('y')) + middeer2.addch(1, 2, ord('0')) + middeer2.addch(1, 3, ord('(')) + middeer2.addch(1, 4, ord('=')) + middeer2.addch(1, 5, ord(')')) + middeer2.addch(1, 6, ord('~')) + middeer2.addch(2, 3, ord('|')) + middeer2.addch(2, 5, ord('|')) + + middeer3.addch(0, 2, ord('y')) + middeer3.addch(0, 3, ord('y')) + middeer3.addch(1, 2, ord('0')) + middeer3.addch(1, 3, ord('(')) + middeer3.addch(1, 4, ord('=')) + middeer3.addch(1, 5, ord(')')) + middeer3.addch(1, 6, ord('~')) + middeer3.addch(2, 3, ord('/')) + middeer3.addch(2, 5, ord('\\')) + + bigdeer1.addch(0, 17, ord('\\')) + bigdeer1.addch(0, 18, ord('/')) + bigdeer1.addch(0, 19, ord('\\')) + bigdeer1.addch(0, 20, ord('/')) + bigdeer1.addch(1, 18, ord('\\')) + bigdeer1.addch(1, 20, ord('/')) + bigdeer1.addch(2, 19, ord('|')) + bigdeer1.addch(2, 20, ord('_')) + bigdeer1.addch(3, 18, ord('/')) + bigdeer1.addch(3, 19, ord('^')) + bigdeer1.addch(3, 20, ord('0')) + bigdeer1.addch(3, 21, ord('\\')) + bigdeer1.addch(4, 17, ord('/')) + bigdeer1.addch(4, 18, ord('/')) + bigdeer1.addch(4, 19, ord('\\')) + bigdeer1.addch(4, 22, ord('\\')) + bigdeer1.addstr(5, 7, "^~~~~~~~~// ~~U") + bigdeer1.addstr(6, 7, "( \\_____( /") # )) + bigdeer1.addstr(7, 8, "( ) /") + bigdeer1.addstr(8, 9, "\\\\ /") + bigdeer1.addstr(9, 11, "\\>/>") + + bigdeer2.addch(0, 17, ord('\\')) + bigdeer2.addch(0, 18, ord('/')) + bigdeer2.addch(0, 19, ord('\\')) + bigdeer2.addch(0, 20, ord('/')) + bigdeer2.addch(1, 18, ord('\\')) + bigdeer2.addch(1, 20, ord('/')) + bigdeer2.addch(2, 19, ord('|')) + bigdeer2.addch(2, 20, ord('_')) + bigdeer2.addch(3, 18, ord('/')) + bigdeer2.addch(3, 19, ord('^')) + bigdeer2.addch(3, 20, ord('0')) + bigdeer2.addch(3, 21, ord('\\')) + bigdeer2.addch(4, 17, ord('/')) + bigdeer2.addch(4, 18, ord('/')) + bigdeer2.addch(4, 19, ord('\\')) + bigdeer2.addch(4, 22, ord('\\')) + bigdeer2.addstr(5, 7, "^~~~~~~~~// ~~U") + bigdeer2.addstr(6, 7, "(( )____( /") # )) + bigdeer2.addstr(7, 7, "( / |") + bigdeer2.addstr(8, 8, "\\/ |") + bigdeer2.addstr(9, 9, "|> |>") + + bigdeer3.addch(0, 17, ord('\\')) + bigdeer3.addch(0, 18, ord('/')) + bigdeer3.addch(0, 19, ord('\\')) + bigdeer3.addch(0, 20, ord('/')) + bigdeer3.addch(1, 18, ord('\\')) + bigdeer3.addch(1, 20, ord('/')) + bigdeer3.addch(2, 19, ord('|')) + bigdeer3.addch(2, 20, ord('_')) + bigdeer3.addch(3, 18, ord('/')) + bigdeer3.addch(3, 19, ord('^')) + bigdeer3.addch(3, 20, ord('0')) + bigdeer3.addch(3, 21, ord('\\')) + bigdeer3.addch(4, 17, ord('/')) + bigdeer3.addch(4, 18, ord('/')) + bigdeer3.addch(4, 19, ord('\\')) + bigdeer3.addch(4, 22, ord('\\')) + bigdeer3.addstr(5, 7, "^~~~~~~~~// ~~U") + bigdeer3.addstr(6, 6, "( ()_____( /") # )) + bigdeer3.addstr(7, 6, "/ / /") + bigdeer3.addstr(8, 5, "|/ \\") + bigdeer3.addstr(9, 5, "/> \\>") + + bigdeer4.addch(0, 17, ord('\\')) + bigdeer4.addch(0, 18, ord('/')) + bigdeer4.addch(0, 19, ord('\\')) + bigdeer4.addch(0, 20, ord('/')) + bigdeer4.addch(1, 18, ord('\\')) + bigdeer4.addch(1, 20, ord('/')) + bigdeer4.addch(2, 19, ord('|')) + bigdeer4.addch(2, 20, ord('_')) + bigdeer4.addch(3, 18, ord('/')) + bigdeer4.addch(3, 19, ord('^')) + bigdeer4.addch(3, 20, ord('0')) + bigdeer4.addch(3, 21, ord('\\')) + bigdeer4.addch(4, 17, ord('/')) + bigdeer4.addch(4, 18, ord('/')) + bigdeer4.addch(4, 19, ord('\\')) + bigdeer4.addch(4, 22, ord('\\')) + bigdeer4.addstr(5, 7, "^~~~~~~~~// ~~U") + bigdeer4.addstr(6, 6, "( )______( /") # ) + bigdeer4.addstr(7, 5, "(/ \\") # ) + bigdeer4.addstr(8, 0, "v___= ----^") + + lookdeer1.addstr(0, 16, "\\/ \\/") + lookdeer1.addstr(1, 17, "\\Y/ \\Y/") + lookdeer1.addstr(2, 19, "\\=/") + lookdeer1.addstr(3, 17, "^\\o o/^") + lookdeer1.addstr(4, 17, "//( )") + lookdeer1.addstr(5, 7, "^~~~~~~~~// \\O/") + lookdeer1.addstr(6, 7, "( \\_____( /") # )) + lookdeer1.addstr(7, 8, "( ) /") + lookdeer1.addstr(8, 9, "\\\\ /") + lookdeer1.addstr(9, 11, "\\>/>") + + lookdeer2.addstr(0, 16, "\\/ \\/") + lookdeer2.addstr(1, 17, "\\Y/ \\Y/") + lookdeer2.addstr(2, 19, "\\=/") + lookdeer2.addstr(3, 17, "^\\o o/^") + lookdeer2.addstr(4, 17, "//( )") + lookdeer2.addstr(5, 7, "^~~~~~~~~// \\O/") + lookdeer2.addstr(6, 7, "(( )____( /") # )) + lookdeer2.addstr(7, 7, "( / |") + lookdeer2.addstr(8, 8, "\\/ |") + lookdeer2.addstr(9, 9, "|> |>") + + lookdeer3.addstr(0, 16, "\\/ \\/") + lookdeer3.addstr(1, 17, "\\Y/ \\Y/") + lookdeer3.addstr(2, 19, "\\=/") + lookdeer3.addstr(3, 17, "^\\o o/^") + lookdeer3.addstr(4, 17, "//( )") + lookdeer3.addstr(5, 7, "^~~~~~~~~// \\O/") + lookdeer3.addstr(6, 6, "( ()_____( /") # )) + lookdeer3.addstr(7, 6, "/ / /") + lookdeer3.addstr(8, 5, "|/ \\") + lookdeer3.addstr(9, 5, "/> \\>") + + lookdeer4.addstr(0, 16, "\\/ \\/") + lookdeer4.addstr(1, 17, "\\Y/ \\Y/") + lookdeer4.addstr(2, 19, "\\=/") + lookdeer4.addstr(3, 17, "^\\o o/^") + lookdeer4.addstr(4, 17, "//( )") + lookdeer4.addstr(5, 7, "^~~~~~~~~// \\O/") + lookdeer4.addstr(6, 6, "( )______( /") # ) + lookdeer4.addstr(7, 5, "(/ \\") # ) + lookdeer4.addstr(8, 0, "v___= ----^") + + ############################################### + curses.cbreak() + stdscr.nodelay(1) + + while 1: + stdscr.clear() + treescrn.erase() + w_del_msg.touchwin() + treescrn.touchwin() + treescrn2.erase() + treescrn2.touchwin() + treescrn8.erase() + treescrn8.touchwin() + stdscr.refresh() + look_out(150) + boxit() + stdscr.refresh() + look_out(150) + seas() + stdscr.refresh() + greet() + stdscr.refresh() + look_out(150) + fromwho() + stdscr.refresh() + look_out(150) + tree() + look_out(150) + balls() + look_out(150) + star() + look_out(150) + strng1() + strng2() + strng3() + strng4() + strng5() + + # set up the windows for our blinking trees + # + # treescrn3 + treescrn.overlay(treescrn3) + + # balls + treescrn3.addch(4, 18, ord(' ')) + treescrn3.addch(7, 6, ord(' ')) + treescrn3.addch(8, 19, ord(' ')) + treescrn3.addch(11, 22, ord(' ')) + + # star + treescrn3.addch(0, 12, ord('*')) + + # strng1 + treescrn3.addch(3, 11, ord(' ')) + + # strng2 + treescrn3.addch(5, 13, ord(' ')) + treescrn3.addch(6, 10, ord(' ')) + + # strng3 + treescrn3.addch(7, 16, ord(' ')) + treescrn3.addch(7, 14, ord(' ')) + + # strng4 + treescrn3.addch(10, 13, ord(' ')) + treescrn3.addch(10, 10, ord(' ')) + treescrn3.addch(11, 8, ord(' ')) + + # strng5 + treescrn3.addch(11, 18, ord(' ')) + treescrn3.addch(12, 13, ord(' ')) + + # treescrn4 + treescrn.overlay(treescrn4) + + # balls + treescrn4.addch(3, 9, ord(' ')) + treescrn4.addch(4, 16, ord(' ')) + treescrn4.addch(7, 6, ord(' ')) + treescrn4.addch(8, 19, ord(' ')) + treescrn4.addch(11, 2, ord(' ')) + treescrn4.addch(12, 23, ord(' ')) + + # star + treescrn4.standout() + treescrn4.addch(0, 12, ord('*')) + treescrn4.standend() + + # strng1 + treescrn4.addch(3, 13, ord(' ')) + + # strng2 + + # strng3 + treescrn4.addch(7, 15, ord(' ')) + treescrn4.addch(8, 11, ord(' ')) + + # strng4 + treescrn4.addch(9, 16, ord(' ')) + treescrn4.addch(10, 12, ord(' ')) + treescrn4.addch(11, 8, ord(' ')) + + # strng5 + treescrn4.addch(11, 18, ord(' ')) + treescrn4.addch(12, 14, ord(' ')) + + # treescrn5 + treescrn.overlay(treescrn5) + + # balls + treescrn5.addch(3, 15, ord(' ')) + treescrn5.addch(10, 20, ord(' ')) + treescrn5.addch(12, 1, ord(' ')) + + # star + treescrn5.addch(0, 12, ord(' ')) + + # strng1 + treescrn5.addch(3, 11, ord(' ')) + + # strng2 + treescrn5.addch(5, 12, ord(' ')) + + # strng3 + treescrn5.addch(7, 14, ord(' ')) + treescrn5.addch(8, 10, ord(' ')) + + # strng4 + treescrn5.addch(9, 15, ord(' ')) + treescrn5.addch(10, 11, ord(' ')) + treescrn5.addch(11, 7, ord(' ')) + + # strng5 + treescrn5.addch(11, 17, ord(' ')) + treescrn5.addch(12, 13, ord(' ')) + + # treescrn6 + treescrn.overlay(treescrn6) + + # balls + treescrn6.addch(6, 7, ord(' ')) + treescrn6.addch(7, 18, ord(' ')) + treescrn6.addch(10, 4, ord(' ')) + treescrn6.addch(11, 23, ord(' ')) + + # star + treescrn6.standout() + treescrn6.addch(0, 12, ord('*')) + treescrn6.standend() + + # strng1 + + # strng2 + treescrn6.addch(5, 11, ord(' ')) + + # strng3 + treescrn6.addch(7, 13, ord(' ')) + treescrn6.addch(8, 9, ord(' ')) + + # strng4 + treescrn6.addch(9, 14, ord(' ')) + treescrn6.addch(10, 10, ord(' ')) + treescrn6.addch(11, 6, ord(' ')) + + # strng5 + treescrn6.addch(11, 16, ord(' ')) + treescrn6.addch(12, 12, ord(' ')) + + # treescrn7 + + treescrn.overlay(treescrn7) + + # balls + treescrn7.addch(3, 15, ord(' ')) + treescrn7.addch(6, 7, ord(' ')) + treescrn7.addch(7, 18, ord(' ')) + treescrn7.addch(10, 4, ord(' ')) + treescrn7.addch(11, 22, ord(' ')) + + # star + treescrn7.addch(0, 12, ord('*')) + + # strng1 + treescrn7.addch(3, 12, ord(' ')) + + # strng2 + treescrn7.addch(5, 13, ord(' ')) + treescrn7.addch(6, 9, ord(' ')) + + # strng3 + treescrn7.addch(7, 15, ord(' ')) + treescrn7.addch(8, 11, ord(' ')) + + # strng4 + treescrn7.addch(9, 16, ord(' ')) + treescrn7.addch(10, 12, ord(' ')) + treescrn7.addch(11, 8, ord(' ')) + + # strng5 + treescrn7.addch(11, 18, ord(' ')) + treescrn7.addch(12, 14, ord(' ')) + + look_out(150) + reindeer() + + w_holiday.touchwin() + w_holiday.refresh() + w_del_msg.refresh() + + look_out(500) + for i in range(0, 20): + blinkit() + +curses.wrapper(main) diff --git a/sys/src/cmd/python/Demo/embed/Makefile b/sys/src/cmd/python/Demo/embed/Makefile new file mode 100644 index 000000000..ac935f16c --- /dev/null +++ b/sys/src/cmd/python/Demo/embed/Makefile @@ -0,0 +1,57 @@ +# Makefile for embedded Python use demo. +# (This version tailored for my Red Hat Linux 6.1 setup; +# edit lines marked with XXX.) + +# XXX The compiler you are using +CC= gcc + +# XXX Top of the build tree and source tree +blddir= ../.. +srcdir= ../.. + +# Python version +VERSION= 2.5 + +# Compiler flags +OPT= -g +INCLUDES= -I$(srcdir)/Include -I$(blddir) +CFLAGS= $(OPT) +CPPFLAGS= $(INCLUDES) + +# The Python library +LIBPYTHON= $(blddir)/libpython$(VERSION).a + +# XXX edit LIBS (in particular) to match $(blddir)/Modules/Makefile +LIBS= -lnsl -ldl -lreadline -ltermcap -lieee -lpthread -lutil +LDFLAGS= -Xlinker -export-dynamic +SYSLIBS= -lm +MODLIBS= +ALLLIBS= $(LIBPYTHON) $(MODLIBS) $(LIBS) $(SYSLIBS) + +# Build the demo applications +all: demo loop importexc +demo: demo.o + $(CC) $(LDFLAGS) demo.o $(ALLLIBS) -o demo + +loop: loop.o + $(CC) $(LDFLAGS) loop.o $(ALLLIBS) -o loop + +importexc: importexc.o + $(CC) $(LDFLAGS) importexc.o $(ALLLIBS) -o importexc + +# Administrative targets + +test: demo + ./demo + +COMMAND="print 'hello world'" +looptest: loop + ./loop $(COMMAND) + +clean: + -rm -f *.o core + +clobber: clean + -rm -f *~ @* '#'* demo loop importexc + +realclean: clobber diff --git a/sys/src/cmd/python/Demo/embed/README b/sys/src/cmd/python/Demo/embed/README new file mode 100644 index 000000000..a0f7af843 --- /dev/null +++ b/sys/src/cmd/python/Demo/embed/README @@ -0,0 +1,19 @@ +This directory show how to embed the Python interpreter in your own +application. The file demo.c shows you all that is needed in your C +code. + +To build it, you may have to edit the Makefile: + +1) set blddir to the directory where you built Python, if it isn't in +the source directory (../..) + +2) change the variables that together define the list of libraries +(MODLIBS, LIBS, SYSLIBS) to link with, to match their definitions in +$(blddir)/Modules/Makefile + +An additional test program, loop.c, is used to experiment with memory +leakage caused by repeated initialization and finalization of the +interpreter. It can be build by saying "make loop" and tested with +"make looptest". Command line usage is "./loop <python-command>", +e.g. "./loop 'print 2+2'" should spit out an endless number of lines +containing the number 4. diff --git a/sys/src/cmd/python/Demo/embed/demo.c b/sys/src/cmd/python/Demo/embed/demo.c new file mode 100644 index 000000000..6005f1396 --- /dev/null +++ b/sys/src/cmd/python/Demo/embed/demo.c @@ -0,0 +1,65 @@ +/* Example of embedding Python in another program */ + +#include "Python.h" + +void initxyzzy(void); /* Forward */ + +main(int argc, char **argv) +{ + /* Pass argv[0] to the Python interpreter */ + Py_SetProgramName(argv[0]); + + /* Initialize the Python interpreter. Required. */ + Py_Initialize(); + + /* Add a static module */ + initxyzzy(); + + /* Define sys.argv. It is up to the application if you + want this; you can also let it undefined (since the Python + code is generally not a main program it has no business + touching sys.argv...) */ + PySys_SetArgv(argc, argv); + + /* Do some application specific code */ + printf("Hello, brave new world\n\n"); + + /* Execute some Python statements (in module __main__) */ + PyRun_SimpleString("import sys\n"); + PyRun_SimpleString("print sys.builtin_module_names\n"); + PyRun_SimpleString("print sys.modules.keys()\n"); + PyRun_SimpleString("print sys.executable\n"); + PyRun_SimpleString("print sys.argv\n"); + + /* Note that you can call any public function of the Python + interpreter here, e.g. call_object(). */ + + /* Some more application specific code */ + printf("\nGoodbye, cruel world\n"); + + /* Exit, cleaning up the interpreter */ + Py_Exit(0); + /*NOTREACHED*/ +} + +/* A static module */ + +/* 'self' is not used */ +static PyObject * +xyzzy_foo(PyObject *self, PyObject* args) +{ + return PyInt_FromLong(42L); +} + +static PyMethodDef xyzzy_methods[] = { + {"foo", xyzzy_foo, METH_NOARGS, + "Return the meaning of everything."}, + {NULL, NULL} /* sentinel */ +}; + +void +initxyzzy(void) +{ + PyImport_AddModule("xyzzy"); + Py_InitModule("xyzzy", xyzzy_methods); +} diff --git a/sys/src/cmd/python/Demo/embed/importexc.c b/sys/src/cmd/python/Demo/embed/importexc.c new file mode 100644 index 000000000..375ce1b68 --- /dev/null +++ b/sys/src/cmd/python/Demo/embed/importexc.c @@ -0,0 +1,17 @@ +#include <Python.h> + +char* cmd = "import exceptions"; + +int main() +{ + Py_Initialize(); + PyEval_InitThreads(); + PyRun_SimpleString(cmd); + Py_EndInterpreter(PyThreadState_Get()); + + Py_NewInterpreter(); + PyRun_SimpleString(cmd); + Py_Finalize(); + + return 0; +} diff --git a/sys/src/cmd/python/Demo/embed/loop.c b/sys/src/cmd/python/Demo/embed/loop.c new file mode 100644 index 000000000..d5af82986 --- /dev/null +++ b/sys/src/cmd/python/Demo/embed/loop.c @@ -0,0 +1,33 @@ +/* Simple program that repeatedly calls Py_Initialize(), does something, and + then calls Py_Finalize(). This should help finding leaks related to + initialization. */ + +#include "Python.h" + +main(int argc, char **argv) +{ + int count = -1; + char *command; + + if (argc < 2 || argc > 3) { + fprintf(stderr, "usage: loop <python-command> [count]\n"); + exit(2); + } + command = argv[1]; + + if (argc == 3) { + count = atoi(argv[2]); + } + + Py_SetProgramName(argv[0]); + + /* uncomment this if you don't want to load site.py */ + /* Py_NoSiteFlag = 1; */ + + while (count == -1 || --count >= 0 ) { + Py_Initialize(); + PyRun_SimpleString(command); + Py_Finalize(); + } + return 0; +} diff --git a/sys/src/cmd/python/Demo/imputil/importers.py b/sys/src/cmd/python/Demo/imputil/importers.py new file mode 100644 index 000000000..864ff02f5 --- /dev/null +++ b/sys/src/cmd/python/Demo/imputil/importers.py @@ -0,0 +1,248 @@ +# +# importers.py +# +# Demonstration subclasses of imputil.Importer +# + +# There should be consideration for the imports below if it is desirable +# to have "all" modules be imported through the imputil system. + +# these are C extensions +import sys +import imp +import struct +import marshal + +# these are .py modules +import imputil +import os + +###################################################################### + +_TupleType = type(()) +_StringType = type('') + +###################################################################### + +# byte-compiled file suffic character +_suffix_char = __debug__ and 'c' or 'o' + +# byte-compiled file suffix +_suffix = '.py' + _suffix_char + +# the C_EXTENSION suffixes +_c_suffixes = filter(lambda x: x[2] == imp.C_EXTENSION, imp.get_suffixes()) + +def _timestamp(pathname): + "Return the file modification time as a Long." + try: + s = os.stat(pathname) + except OSError: + return None + return long(s[8]) + +def _fs_import(dir, modname, fqname): + "Fetch a module from the filesystem." + + pathname = os.path.join(dir, modname) + if os.path.isdir(pathname): + values = { '__pkgdir__' : pathname, '__path__' : [ pathname ] } + ispkg = 1 + pathname = os.path.join(pathname, '__init__') + else: + values = { } + ispkg = 0 + + # look for dynload modules + for desc in _c_suffixes: + file = pathname + desc[0] + try: + fp = open(file, desc[1]) + except IOError: + pass + else: + module = imp.load_module(fqname, fp, file, desc) + values['__file__'] = file + return 0, module, values + + t_py = _timestamp(pathname + '.py') + t_pyc = _timestamp(pathname + _suffix) + if t_py is None and t_pyc is None: + return None + code = None + if t_py is None or (t_pyc is not None and t_pyc >= t_py): + file = pathname + _suffix + f = open(file, 'rb') + if f.read(4) == imp.get_magic(): + t = struct.unpack('<I', f.read(4))[0] + if t == t_py: + code = marshal.load(f) + f.close() + if code is None: + file = pathname + '.py' + code = _compile(file, t_py) + + values['__file__'] = file + return ispkg, code, values + +###################################################################### +# +# Simple function-based importer +# +class FuncImporter(imputil.Importer): + "Importer subclass to delegate to a function rather than method overrides." + def __init__(self, func): + self.func = func + def get_code(self, parent, modname, fqname): + return self.func(parent, modname, fqname) + +def install_with(func): + FuncImporter(func).install() + + +###################################################################### +# +# Base class for archive-based importing +# +class PackageArchiveImporter(imputil.Importer): + """Importer subclass to import from (file) archives. + + This Importer handles imports of the style <archive>.<subfile>, where + <archive> can be located using a subclass-specific mechanism and the + <subfile> is found in the archive using a subclass-specific mechanism. + + This class defines two hooks for subclasses: one to locate an archive + (and possibly return some context for future subfile lookups), and one + to locate subfiles. + """ + + def get_code(self, parent, modname, fqname): + if parent: + # the Importer._finish_import logic ensures that we handle imports + # under the top level module (package / archive). + assert parent.__importer__ == self + + # if a parent "package" is provided, then we are importing a + # sub-file from the archive. + result = self.get_subfile(parent.__archive__, modname) + if result is None: + return None + if isinstance(result, _TupleType): + assert len(result) == 2 + return (0,) + result + return 0, result, {} + + # no parent was provided, so the archive should exist somewhere on the + # default "path". + archive = self.get_archive(modname) + if archive is None: + return None + return 1, "", {'__archive__':archive} + + def get_archive(self, modname): + """Get an archive of modules. + + This method should locate an archive and return a value which can be + used by get_subfile to load modules from it. The value may be a simple + pathname, an open file, or a complex object that caches information + for future imports. + + Return None if the archive was not found. + """ + raise RuntimeError, "get_archive not implemented" + + def get_subfile(self, archive, modname): + """Get code from a subfile in the specified archive. + + Given the specified archive (as returned by get_archive()), locate + and return a code object for the specified module name. + + A 2-tuple may be returned, consisting of a code object and a dict + of name/values to place into the target module. + + Return None if the subfile was not found. + """ + raise RuntimeError, "get_subfile not implemented" + + +class PackageArchive(PackageArchiveImporter): + "PackageArchiveImporter subclass that refers to a specific archive." + + def __init__(self, modname, archive_pathname): + self.__modname = modname + self.__path = archive_pathname + + def get_archive(self, modname): + if modname == self.__modname: + return self.__path + return None + + # get_subfile is passed the full pathname of the archive + + +###################################################################### +# +# Emulate the standard directory-based import mechanism +# +class DirectoryImporter(imputil.Importer): + "Importer subclass to emulate the standard importer." + + def __init__(self, dir): + self.dir = dir + + def get_code(self, parent, modname, fqname): + if parent: + dir = parent.__pkgdir__ + else: + dir = self.dir + + # Return the module (and other info) if found in the specified + # directory. Otherwise, return None. + return _fs_import(dir, modname, fqname) + + def __repr__(self): + return '<%s.%s for "%s" at 0x%x>' % (self.__class__.__module__, + self.__class__.__name__, + self.dir, + id(self)) + + +###################################################################### +# +# Emulate the standard path-style import mechanism +# +class PathImporter(imputil.Importer): + def __init__(self, path=sys.path): + self.path = path + + def get_code(self, parent, modname, fqname): + if parent: + # we are looking for a module inside of a specific package + return _fs_import(parent.__pkgdir__, modname, fqname) + + # scan sys.path, looking for the requested module + for dir in self.path: + if isinstance(dir, _StringType): + result = _fs_import(dir, modname, fqname) + if result: + return result + + # not found + return None + +###################################################################### + +def _test_dir(): + "Debug/test function to create DirectoryImporters from sys.path." + imputil.ImportManager().install() + path = sys.path[:] + path.reverse() + for d in path: + sys.path.insert(0, DirectoryImporter(d)) + sys.path.insert(0, imputil.BuiltinImporter()) + +def _test_revamp(): + "Debug/test function for the revamped import system." + imputil.ImportManager().install() + sys.path.insert(0, PathImporter()) + sys.path.insert(0, imputil.BuiltinImporter()) diff --git a/sys/src/cmd/python/Demo/imputil/knee.py b/sys/src/cmd/python/Demo/imputil/knee.py new file mode 100644 index 000000000..64764da04 --- /dev/null +++ b/sys/src/cmd/python/Demo/imputil/knee.py @@ -0,0 +1,126 @@ +"""An Python re-implementation of hierarchical module import. + +This code is intended to be read, not executed. However, it does work +-- all you need to do to enable it is "import knee". + +(The name is a pun on the klunkier predecessor of this module, "ni".) + +""" + +import sys, imp, __builtin__ + + +# Replacement for __import__() +def import_hook(name, globals=None, locals=None, fromlist=None): + parent = determine_parent(globals) + q, tail = find_head_package(parent, name) + m = load_tail(q, tail) + if not fromlist: + return q + if hasattr(m, "__path__"): + ensure_fromlist(m, fromlist) + return m + +def determine_parent(globals): + if not globals or not globals.has_key("__name__"): + return None + pname = globals['__name__'] + if globals.has_key("__path__"): + parent = sys.modules[pname] + assert globals is parent.__dict__ + return parent + if '.' in pname: + i = pname.rfind('.') + pname = pname[:i] + parent = sys.modules[pname] + assert parent.__name__ == pname + return parent + return None + +def find_head_package(parent, name): + if '.' in name: + i = name.find('.') + head = name[:i] + tail = name[i+1:] + else: + head = name + tail = "" + if parent: + qname = "%s.%s" % (parent.__name__, head) + else: + qname = head + q = import_module(head, qname, parent) + if q: return q, tail + if parent: + qname = head + parent = None + q = import_module(head, qname, parent) + if q: return q, tail + raise ImportError, "No module named " + qname + +def load_tail(q, tail): + m = q + while tail: + i = tail.find('.') + if i < 0: i = len(tail) + head, tail = tail[:i], tail[i+1:] + mname = "%s.%s" % (m.__name__, head) + m = import_module(head, mname, m) + if not m: + raise ImportError, "No module named " + mname + return m + +def ensure_fromlist(m, fromlist, recursive=0): + for sub in fromlist: + if sub == "*": + if not recursive: + try: + all = m.__all__ + except AttributeError: + pass + else: + ensure_fromlist(m, all, 1) + continue + if sub != "*" and not hasattr(m, sub): + subname = "%s.%s" % (m.__name__, sub) + submod = import_module(sub, subname, m) + if not submod: + raise ImportError, "No module named " + subname + +def import_module(partname, fqname, parent): + try: + return sys.modules[fqname] + except KeyError: + pass + try: + fp, pathname, stuff = imp.find_module(partname, + parent and parent.__path__) + except ImportError: + return None + try: + m = imp.load_module(fqname, fp, pathname, stuff) + finally: + if fp: fp.close() + if parent: + setattr(parent, partname, m) + return m + + +# Replacement for reload() +def reload_hook(module): + name = module.__name__ + if '.' not in name: + return import_module(name, name, None) + i = name.rfind('.') + pname = name[:i] + parent = sys.modules[pname] + return import_module(name[i+1:], name, parent) + + +# Save the original hooks +original_import = __builtin__.__import__ +original_reload = __builtin__.reload + +# Now install our hooks +__builtin__.__import__ = import_hook +__builtin__.reload = reload_hook diff --git a/sys/src/cmd/python/Demo/md5test/README b/sys/src/cmd/python/Demo/md5test/README new file mode 100644 index 000000000..be7621e46 --- /dev/null +++ b/sys/src/cmd/python/Demo/md5test/README @@ -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. diff --git a/sys/src/cmd/python/Demo/md5test/foo b/sys/src/cmd/python/Demo/md5test/foo new file mode 100755 index 000000000..f2ba8f84a --- /dev/null +++ b/sys/src/cmd/python/Demo/md5test/foo @@ -0,0 +1 @@ +abc
\ No newline at end of file diff --git a/sys/src/cmd/python/Demo/md5test/md5driver.py b/sys/src/cmd/python/Demo/md5test/md5driver.py new file mode 100755 index 000000000..4c7cfcb89 --- /dev/null +++ b/sys/src/cmd/python/Demo/md5test/md5driver.py @@ -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() diff --git a/sys/src/cmd/python/Demo/metaclasses/Eiffel.py b/sys/src/cmd/python/Demo/metaclasses/Eiffel.py new file mode 100644 index 000000000..24fac148d --- /dev/null +++ b/sys/src/cmd/python/Demo/metaclasses/Eiffel.py @@ -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() diff --git a/sys/src/cmd/python/Demo/metaclasses/Enum.py b/sys/src/cmd/python/Demo/metaclasses/Enum.py new file mode 100644 index 000000000..df1d8143e --- /dev/null +++ b/sys/src/cmd/python/Demo/metaclasses/Enum.py @@ -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() diff --git a/sys/src/cmd/python/Demo/metaclasses/Meta.py b/sys/src/cmd/python/Demo/metaclasses/Meta.py new file mode 100644 index 000000000..580f5821c --- /dev/null +++ b/sys/src/cmd/python/Demo/metaclasses/Meta.py @@ -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() diff --git a/sys/src/cmd/python/Demo/metaclasses/Simple.py b/sys/src/cmd/python/Demo/metaclasses/Simple.py new file mode 100644 index 000000000..03ed2592e --- /dev/null +++ b/sys/src/cmd/python/Demo/metaclasses/Simple.py @@ -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() diff --git a/sys/src/cmd/python/Demo/metaclasses/Synch.py b/sys/src/cmd/python/Demo/metaclasses/Synch.py new file mode 100644 index 000000000..80e52d9fd --- /dev/null +++ b/sys/src/cmd/python/Demo/metaclasses/Synch.py @@ -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() diff --git a/sys/src/cmd/python/Demo/metaclasses/Trace.py b/sys/src/cmd/python/Demo/metaclasses/Trace.py new file mode 100644 index 000000000..69b9fab7d --- /dev/null +++ b/sys/src/cmd/python/Demo/metaclasses/Trace.py @@ -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() diff --git a/sys/src/cmd/python/Demo/metaclasses/index.html b/sys/src/cmd/python/Demo/metaclasses/index.html new file mode 100644 index 000000000..eee473a81 --- /dev/null +++ b/sys/src/cmd/python/Demo/metaclasses/index.html @@ -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> diff --git a/sys/src/cmd/python/Demo/metaclasses/meta-vladimir.txt b/sys/src/cmd/python/Demo/metaclasses/meta-vladimir.txt new file mode 100644 index 000000000..36406bb46 --- /dev/null +++ b/sys/src/cmd/python/Demo/metaclasses/meta-vladimir.txt @@ -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 diff --git a/sys/src/cmd/python/Demo/newmetaclasses/Eiffel.py b/sys/src/cmd/python/Demo/newmetaclasses/Eiffel.py new file mode 100644 index 000000000..04f991585 --- /dev/null +++ b/sys/src/cmd/python/Demo/newmetaclasses/Eiffel.py @@ -0,0 +1,141 @@ +"""Support Eiffel-style preconditions and postconditions.""" + +from new import 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) diff --git a/sys/src/cmd/python/Demo/newmetaclasses/Enum.py b/sys/src/cmd/python/Demo/newmetaclasses/Enum.py new file mode 100644 index 000000000..2a5823b56 --- /dev/null +++ b/sys/src/cmd/python/Demo/newmetaclasses/Enum.py @@ -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() diff --git a/sys/src/cmd/python/Demo/parser/FILES b/sys/src/cmd/python/Demo/parser/FILES new file mode 100644 index 000000000..1ff59a31a --- /dev/null +++ b/sys/src/cmd/python/Demo/parser/FILES @@ -0,0 +1,6 @@ +Demo/parser +Doc/libparser.tex +Lib/AST.py +Lib/symbol.py +Lib/token.py +Modules/parsermodule.c diff --git a/sys/src/cmd/python/Demo/parser/README b/sys/src/cmd/python/Demo/parser/README new file mode 100644 index 000000000..a576d33b0 --- /dev/null +++ b/sys/src/cmd/python/Demo/parser/README @@ -0,0 +1,31 @@ +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. + + example.py -- module that uses the `parser' module to extract + information from the parse tree of Python source + code. + + docstring.py -- sample source file containing only a module docstring. + + 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. + + unparse.py AST (2.5) based example to recreate source code + from an AST. This is incomplete; contributions + are welcome. + +Enjoy! diff --git a/sys/src/cmd/python/Demo/parser/docstring.py b/sys/src/cmd/python/Demo/parser/docstring.py new file mode 100644 index 000000000..45a261b61 --- /dev/null +++ b/sys/src/cmd/python/Demo/parser/docstring.py @@ -0,0 +1,2 @@ +"""Some documentation. +""" diff --git a/sys/src/cmd/python/Demo/parser/example.py b/sys/src/cmd/python/Demo/parser/example.py new file mode 100644 index 000000000..2aa9ec285 --- /dev/null +++ b/sys/src/cmd/python/Demo/parser/example.py @@ -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, '') + )) diff --git a/sys/src/cmd/python/Demo/parser/simple.py b/sys/src/cmd/python/Demo/parser/simple.py new file mode 100644 index 000000000..184e2fe5d --- /dev/null +++ b/sys/src/cmd/python/Demo/parser/simple.py @@ -0,0 +1 @@ +def f(): "maybe a docstring" diff --git a/sys/src/cmd/python/Demo/parser/source.py b/sys/src/cmd/python/Demo/parser/source.py new file mode 100644 index 000000000..b90062851 --- /dev/null +++ b/sys/src/cmd/python/Demo/parser/source.py @@ -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 diff --git a/sys/src/cmd/python/Demo/parser/test_parser.py b/sys/src/cmd/python/Demo/parser/test_parser.py new file mode 100755 index 000000000..be39bca7f --- /dev/null +++ b/sys/src/cmd/python/Demo/parser/test_parser.py @@ -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: + ast = parser.suite(t) + tup = parser.ast2tuple(ast) + # this discards the first AST; a huge memory savings when running + # against a large source file like Tkinter.py. + ast = None + new = parser.tuple2ast(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.ast2tuple(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() diff --git a/sys/src/cmd/python/Demo/parser/texipre.dat b/sys/src/cmd/python/Demo/parser/texipre.dat new file mode 100644 index 000000000..8ad03a617 --- /dev/null +++ b/sys/src/cmd/python/Demo/parser/texipre.dat @@ -0,0 +1,100 @@ +\input texinfo @c -*-texinfo-*- +@c %**start of header +@setfilename parser.info +@settitle Python Parser Module Reference +@setchapternewpage odd +@footnotestyle end +@c %**end of header + +@ifinfo +This file describes the interfaces +published by the optional @code{parser} module and gives examples of +how they may be used. It contains the same text as the chapter on the +@code{parser} module in the @cite{Python Library Reference}, but is +presented as a separate document. + +Copyright 1995-1996 by Fred L. Drake, Jr., Reston, Virginia, USA, and +Virginia Polytechnic Institute and State University, Blacksburg, +Virginia, USA. Portions of the software copyright 1991-1995 by +Stichting Mathematisch Centrum, Amsterdam, The Netherlands. Copying is +permitted under the terms associated with the main Python distribution, +with the additional restriction that this additional notice be included +and maintained on all distributed copies. + + All Rights Reserved + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that the names of Fred L. Drake, Jr. and +Virginia Polytechnic Institute and State University not be used in +advertising or publicity pertaining to distribution of the software +without specific, written prior permission. + +FRED L. DRAKE, JR. AND VIRGINIA POLYTECHNIC INSTITUTE AND STATE +UNIVERSITY DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, +INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO +EVENT SHALL FRED L. DRAKE, JR. OR VIRGINIA POLYTECHNIC INSTITUTE AND +STATE UNIVERSITY BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL +DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR +PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. +@end ifinfo + +@titlepage +@title Python Parser Module Reference +@author Fred L. Drake, Jr. + +@c The following two commands start the copyright page. +@page +@vskip 0pt plus 1filll +Copyright 1995-1996 by Fred L. Drake, Jr., Reston, Virginia, USA, and +Virginia Polytechnic Institute and State University, Blacksburg, +Virginia, USA. Portions of the software copyright 1991-1995 by +Stichting Mathematisch Centrum, Amsterdam, The Netherlands. Copying is +permitted under the terms associated with the main Python distribution, +with the additional restriction that this additional notice be included +and maintained on all distributed copies. + +@center All Rights Reserved + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that the names of Fred L. Drake, Jr. and +Virginia Polytechnic Institute and State University not be used in +advertising or publicity pertaining to distribution of the software +without specific, written prior permission. + +FRED L. DRAKE, JR. AND VIRGINIA POLYTECHNIC INSTITUTE AND STATE +UNIVERSITY DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, +INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO +EVENT SHALL FRED L. DRAKE, JR. OR VIRGINIA POLYTECHNIC INSTITUTE AND +STATE UNIVERSITY BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL +DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR +PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. +@end titlepage + + +@node Top, Overview, (dir), (dir) +@top The Python Parser Module + +@ifinfo +This file describes the interfaces +published by the optional @code{parser} module and gives examples of +how they may be used. It contains the same text as the chapter on the +@code{parser} module in the @cite{Python Library Reference}, but is +presented as a separate document. + +This version corresponds to Python version 1.4 (1 Sept. 1996). + +@end ifinfo + +@c placeholder for the master menu -- patched by texinfo-all-menus-update +@menu +@end menu diff --git a/sys/src/cmd/python/Demo/parser/unparse.py b/sys/src/cmd/python/Demo/parser/unparse.py new file mode 100644 index 000000000..f4dd90c95 --- /dev/null +++ b/sys/src/cmd/python/Demo/parser/unparse.py @@ -0,0 +1,519 @@ +"Usage: unparse.py <path to source file>" +import sys +import _ast +import cStringIO +import os + +class Unparser: + """Methods in this class recursively traverse an AST and + output source code for the abstract syntax; original formatting + is disregarged. """ + + def __init__(self, tree, file = sys.stdout): + """Unparser(tree, file=sys.stdout) -> None. + Print the source for tree to file.""" + self.f = file + self._indent = 0 + self.dispatch(tree) + print >>self.f,"" + 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 ") + first = True + for a in t.names: + if first: + first = False + else: + self.write(", ") + self.write(a.name) + if a.asname: + self.write(" as "+a.asname) + + def _ImportFrom(self, t): + self.fill("from ") + self.write(t.module) + self.write(" import ") + for i, a in enumerate(t.names): + if i == 0: + self.write(", ") + self.write(a.name) + if a.asname: + self.write(" as "+a.asname) + # XXX(jpe) what is level for? + + 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.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 ") + 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") + for i, n in enumerate(t.names): + if i != 0: + self.write(",") + self.write(" " + n) + + def _Yield(self, t): + self.fill("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): + 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.dispatch(t.type) + if t.name: + self.write(", ") + self.dispatch(t.name) + self.enter() + self.dispatch(t.body) + self.leave() + + def _ClassDef(self, t): + self.write("\n") + 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.decorators: + 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() + # XXX elif? + self.dispatch(t.body) + self.leave() + 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): + self.write(repr(tree.s)) + + def _Name(self, t): + self.write(t.id) + + def _Repr(self, t): + self.write("`") + self.dispatch(t.value) + self.write("`") + + def _Num(self, t): + self.write(repr(t.n)) + + def _List(self, t): + self.write("[") + for e in t.elts: + self.dispatch(e) + self.write(", ") + 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 _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.dispatch(t.body) + self.write(" if ") + self.dispatch(t.test) + if t.orelse: + self.write(" else ") + self.dispatch(t.orelse) + + def _Dict(self, t): + self.write("{") + for k,v in zip(t.keys, t.values): + self.dispatch(k) + self.write(" : ") + self.dispatch(v) + self.write(", ") + self.write("}") + + def _Tuple(self, t): + if not t.elts: + self.write("()") + return + self.write("(") + for e in t.elts: + self.dispatch(e) + self.write(", ") + self.write(")") + + unop = {"Invert":"~", "Not": "not", "UAdd":"+", "USub":"-"} + def _UnaryOp(self, t): + self.write(self.unop[t.op.__class__.__name__]) + self.write("(") + 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("(") + self.dispatch(t.values[0]) + for v in t.values[1:]: + self.write(" %s " % self.boolops[t.op.__class__]) + self.dispatch(v) + self.write(")") + + def _Attribute(self,t): + self.dispatch(t.value) + 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): + for i, d in enumerate(t.dims): + if i != 0: + self.write(': ') + self.dispatch(d) + + # others + def _arguments(self, t): + first = True + nonDef = len(t.args)-len(t.defaults) + for a in t.args[0:nonDef]: + if first:first = False + else: self.write(", ") + self.dispatch(a) + for a,d in zip(t.args[nonDef:], t.defaults): + if first:first = False + else: self.write(", ") + self.dispatch(a), + self.write("=") + self.dispatch(d) + if t.vararg: + if first:first = False + else: self.write(", ") + self.write("*"+t.vararg) + 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("lambda ") + self.dispatch(t.args) + self.write(": ") + self.dispatch(t.body) + +def roundtrip(filename, output=sys.stdout): + source = open(filename).read() + tree = compile(source, filename, "exec", 0x400) + Unparser(tree, output) + + + +def testdir(a): + try: + names = [n for n in os.listdir(a) if n.endswith('.py')] + except OSError: + print >> sys.stderr, "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, 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:]) diff --git a/sys/src/cmd/python/Demo/pdist/FSProxy.py b/sys/src/cmd/python/Demo/pdist/FSProxy.py new file mode 100755 index 000000000..a1ab635c0 --- /dev/null +++ b/sys/src/cmd/python/Demo/pdist/FSProxy.py @@ -0,0 +1,322 @@ +"""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 + +if os.name == 'mac': + import macfs + maxnamelen = 31 +else: + macfs = None + 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): + if os.name == 'mac': + return name[0] == '(' and name[-1] == ')' + else: + return name[0] == '.' + + def _hide(self, name): + if os.name == 'mac': + return '(%s)' % name + else: + 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 macfs: + if os.path.exists(name) and not os.path.isdir(name): + try: + fs = macfs.FSSpec(name) + c, t = fs.GetCreatorType() + if t != 'TEXT': return 0 + except macfs.error, msg: + print "***", name, msg + return 0 + else: + 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() diff --git a/sys/src/cmd/python/Demo/pdist/RCSProxy.py b/sys/src/cmd/python/Demo/pdist/RCSProxy.py new file mode 100755 index 000000000..87c65ccf0 --- /dev/null +++ b/sys/src/cmd/python/Demo/pdist/RCSProxy.py @@ -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() diff --git a/sys/src/cmd/python/Demo/pdist/README b/sys/src/cmd/python/Demo/pdist/README new file mode 100644 index 000000000..b3fac2412 --- /dev/null +++ b/sys/src/cmd/python/Demo/pdist/README @@ -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). diff --git a/sys/src/cmd/python/Demo/pdist/client.py b/sys/src/cmd/python/Demo/pdist/client.py new file mode 100755 index 000000000..3e97d8469 --- /dev/null +++ b/sys/src/cmd/python/Demo/pdist/client.py @@ -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) diff --git a/sys/src/cmd/python/Demo/pdist/cmdfw.py b/sys/src/cmd/python/Demo/pdist/cmdfw.py new file mode 100755 index 000000000..e2edd0a86 --- /dev/null +++ b/sys/src/cmd/python/Demo/pdist/cmdfw.py @@ -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() diff --git a/sys/src/cmd/python/Demo/pdist/cmptree.py b/sys/src/cmd/python/Demo/pdist/cmptree.py new file mode 100755 index 000000000..f6c611f69 --- /dev/null +++ b/sys/src/cmd/python/Demo/pdist/cmptree.py @@ -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.", int(size/dt), "bytes/sec", + print + remote._recv(id) # ignored + +def fl(): + sys.stdout.flush() + +if __name__ == '__main__': + main() diff --git a/sys/src/cmd/python/Demo/pdist/cvslib.py b/sys/src/cmd/python/Demo/pdist/cvslib.py new file mode 100755 index 000000000..ebcc69747 --- /dev/null +++ b/sys/src/cmd/python/Demo/pdist/cvslib.py @@ -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() diff --git a/sys/src/cmd/python/Demo/pdist/cvslock.py b/sys/src/cmd/python/Demo/pdist/cvslock.py new file mode 100755 index 000000000..8f6d008cb --- /dev/null +++ b/sys/src/cmd/python/Demo/pdist/cvslock.py @@ -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() diff --git a/sys/src/cmd/python/Demo/pdist/mac.py b/sys/src/cmd/python/Demo/pdist/mac.py new file mode 100755 index 000000000..107113c18 --- /dev/null +++ b/sys/src/cmd/python/Demo/pdist/mac.py @@ -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() diff --git a/sys/src/cmd/python/Demo/pdist/makechangelog.py b/sys/src/cmd/python/Demo/pdist/makechangelog.py new file mode 100755 index 000000000..1ffa5880b --- /dev/null +++ b/sys/src/cmd/python/Demo/pdist/makechangelog.py @@ -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() diff --git a/sys/src/cmd/python/Demo/pdist/rcsbump b/sys/src/cmd/python/Demo/pdist/rcsbump new file mode 100755 index 000000000..4fa078e74 --- /dev/null +++ b/sys/src/cmd/python/Demo/pdist/rcsbump @@ -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") diff --git a/sys/src/cmd/python/Demo/pdist/rcsclient.py b/sys/src/cmd/python/Demo/pdist/rcsclient.py new file mode 100755 index 000000000..d8cb004b2 --- /dev/null +++ b/sys/src/cmd/python/Demo/pdist/rcsclient.py @@ -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 diff --git a/sys/src/cmd/python/Demo/pdist/rcslib.py b/sys/src/cmd/python/Demo/pdist/rcslib.py new file mode 100755 index 000000000..3e6386924 --- /dev/null +++ b/sys/src/cmd/python/Demo/pdist/rcslib.py @@ -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' diff --git a/sys/src/cmd/python/Demo/pdist/rcvs b/sys/src/cmd/python/Demo/pdist/rcvs new file mode 100755 index 000000000..f82a27a20 --- /dev/null +++ b/sys/src/cmd/python/Demo/pdist/rcvs @@ -0,0 +1,8 @@ +#!/usr/bin/env python + +import addpack +addpack.addpack('/home/guido/src/python/Demo/pdist') + +import rcvs + +rcvs.main() diff --git a/sys/src/cmd/python/Demo/pdist/rcvs.py b/sys/src/cmd/python/Demo/pdist/rcvs.py new file mode 100755 index 000000000..8b8bae63b --- /dev/null +++ b/sys/src/cmd/python/Demo/pdist/rcvs.py @@ -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() diff --git a/sys/src/cmd/python/Demo/pdist/rrcs b/sys/src/cmd/python/Demo/pdist/rrcs new file mode 100755 index 000000000..31fc2c510 --- /dev/null +++ b/sys/src/cmd/python/Demo/pdist/rrcs @@ -0,0 +1,8 @@ +#!/usr/bin/env python + +import addpack +addpack.addpack('/home/guido/src/python/Demo/pdist') + +import rrcs + +rrcs.main() diff --git a/sys/src/cmd/python/Demo/pdist/rrcs.py b/sys/src/cmd/python/Demo/pdist/rrcs.py new file mode 100755 index 000000000..4d23e6c1e --- /dev/null +++ b/sys/src/cmd/python/Demo/pdist/rrcs.py @@ -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() diff --git a/sys/src/cmd/python/Demo/pdist/security.py b/sys/src/cmd/python/Demo/pdist/security.py new file mode 100755 index 000000000..b63081ee3 --- /dev/null +++ b/sys/src/cmd/python/Demo/pdist/security.py @@ -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) diff --git a/sys/src/cmd/python/Demo/pdist/server.py b/sys/src/cmd/python/Demo/pdist/server.py new file mode 100755 index 000000000..e692eea7f --- /dev/null +++ b/sys/src/cmd/python/Demo/pdist/server.py @@ -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 diff --git a/sys/src/cmd/python/Demo/pdist/sumtree.py b/sys/src/cmd/python/Demo/pdist/sumtree.py new file mode 100755 index 000000000..9291a56b9 --- /dev/null +++ b/sys/src/cmd/python/Demo/pdist/sumtree.py @@ -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() diff --git a/sys/src/cmd/python/Demo/pysvr/Makefile b/sys/src/cmd/python/Demo/pysvr/Makefile new file mode 100644 index 000000000..b4b9f3e11 --- /dev/null +++ b/sys/src/cmd/python/Demo/pysvr/Makefile @@ -0,0 +1,57 @@ +# Makefile for 'pysvr' application embedding Python. +# Tailored for Python 1.5a3 or later. +# Some details are specific for Solaris or CNRI. +# Also see ## comments for tailoring. + +# Which C compiler +CC=gcc +##PURIFY=/usr/local/pure/purify +LINKCC=$(PURIFY) $(CC) + +# Optimization preferences +OPT=-g + +# Which Python version we're using +VER=2.2 + +# Expressions using the above definitions +PYVER=python$(VER) + +# Use these defs when compiling against installed Python +##INST=/usr/local +##PYC=$(INST)/lib/$(PYVER)/config +##PYINCL=-I$(INST)/include/$(PYVER) -I$(PYC) +##PYLIBS=$(PYC)/lib$(PYVER).a + +# Use these defs when compiling against built Python +PLAT=linux +PYINCL=-I../../Include -I../../$(PLAT) +PYLIBS=../../$(PLAT)/lib$(PYVER).a + +# Libraries to link with -- very installation dependent +# (See LIBS= in Modules/Makefile in build tree) +RLLIBS=-lreadline -ltermcap +OTHERLIBS=-lnsl -lpthread -ldl -lm -ldb -lutil + +# Compilation and link flags -- no need to change normally +CFLAGS=$(OPT) +CPPFLAGS=$(PYINCL) +LIBS=$(PYLIBS) $(RLLIBS) $(OTHERLIBS) + +# Default port for the pysvr application +PORT=4000 + +# Default target +all: pysvr + +# Target to build pysvr +pysvr: pysvr.o $(PYOBJS) $(PYLIBS) + $(LINKCC) pysvr.o $(LIBS) -o pysvr + +# Target to build and run pysvr +run: pysvr + pysvr $(PORT) + +# Target to clean up the directory +clean: + -rm -f pysvr *.o *~ core diff --git a/sys/src/cmd/python/Demo/pysvr/README b/sys/src/cmd/python/Demo/pysvr/README new file mode 100644 index 000000000..5e64e38bb --- /dev/null +++ b/sys/src/cmd/python/Demo/pysvr/README @@ -0,0 +1,9 @@ +This is an example of a multi-threaded C application embedding a +Python interpreter. + +The particular application is a multi-threaded telnet-like server that +provides you with a Python prompt (instead of a shell prompt). + +The file pysvr.py is a prototype in Python. + +THIS APPLICATION IS NOT SECURE -- ONLY USE IT FOR TESTING! diff --git a/sys/src/cmd/python/Demo/pysvr/pysvr.c b/sys/src/cmd/python/Demo/pysvr/pysvr.c new file mode 100644 index 000000000..cced6da4b --- /dev/null +++ b/sys/src/cmd/python/Demo/pysvr/pysvr.c @@ -0,0 +1,370 @@ +/* A multi-threaded telnet-like server that gives a Python prompt. + +Usage: pysvr [port] + +For security reasons, it only accepts requests from the current host. +This can still be insecure, but restricts violations from people who +can log in on your machine. Use with caution! + +*/ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <errno.h> + +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> + +#include <pthread.h> +#include <getopt.h> + +/* XXX Umpfh. + Python.h defines a typedef destructor, which conflicts with pthread.h. + So Python.h must be included after pthread.h. */ + +#include "Python.h" + +extern int Py_VerboseFlag; + +#ifndef PORT +#define PORT 4000 +#endif + +struct workorder { + int conn; + struct sockaddr_in addr; +}; + +/* Forward */ +static void init_python(void); +static void usage(void); +static void oprogname(void); +static void main_thread(int); +static void create_thread(int, struct sockaddr_in *); +static void *service_thread(struct workorder *); +static void run_interpreter(FILE *, FILE *); +static int run_command(char *, PyObject *); +static void ps(void); + +static char *progname = "pysvr"; + +static PyThreadState *gtstate; + +main(int argc, char **argv) +{ + int port = PORT; + int c; + + if (argc > 0 && argv[0] != NULL && argv[0][0] != '\0') + progname = argv[0]; + + while ((c = getopt(argc, argv, "v")) != EOF) { + switch (c) { + case 'v': + Py_VerboseFlag++; + break; + default: + usage(); + } + } + + if (optind < argc) { + if (optind+1 < argc) { + oprogname(); + fprintf(stderr, "too many arguments\n"); + usage(); + } + port = atoi(argv[optind]); + if (port <= 0) { + fprintf(stderr, "bad port (%s)\n", argv[optind]); + usage(); + } + } + + main_thread(port); + + fprintf(stderr, "Bye.\n"); + + exit(0); +} + +static char usage_line[] = "usage: %s [port]\n"; + +static void +usage(void) +{ + fprintf(stderr, usage_line, progname); + exit(2); +} + +static void +main_thread(int port) +{ + int sock, conn, size, i; + struct sockaddr_in addr, clientaddr; + + sock = socket(PF_INET, SOCK_STREAM, 0); + if (sock < 0) { + oprogname(); + perror("can't create socket"); + exit(1); + } + +#ifdef SO_REUSEADDR + i = 1; + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *) &i, sizeof i); +#endif + + memset((char *)&addr, '\0', sizeof addr); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = 0L; + if (bind(sock, (struct sockaddr *)&addr, sizeof addr) < 0) { + oprogname(); + perror("can't bind socket to address"); + exit(1); + } + + if (listen(sock, 5) < 0) { + oprogname(); + perror("can't listen on socket"); + exit(1); + } + + fprintf(stderr, "Listening on port %d...\n", port); + + for (i = 0; ; i++) { + size = sizeof clientaddr; + memset((char *) &clientaddr, '\0', size); + conn = accept(sock, (struct sockaddr *) &clientaddr, &size); + if (conn < 0) { + oprogname(); + perror("can't accept connection from socket"); + exit(1); + } + + size = sizeof addr; + memset((char *) &addr, '\0', size); + if (getsockname(conn, (struct sockaddr *)&addr, &size) < 0) { + oprogname(); + perror("can't get socket name of connection"); + exit(1); + } + if (clientaddr.sin_addr.s_addr != addr.sin_addr.s_addr) { + oprogname(); + perror("connection from non-local host refused"); + fprintf(stderr, "(addr=%lx, clientaddr=%lx)\n", + ntohl(addr.sin_addr.s_addr), + ntohl(clientaddr.sin_addr.s_addr)); + close(conn); + continue; + } + if (i == 4) { + close(conn); + break; + } + create_thread(conn, &clientaddr); + } + + close(sock); + + if (gtstate) { + PyEval_AcquireThread(gtstate); + gtstate = NULL; + Py_Finalize(); + /* And a second time, just because we can. */ + Py_Finalize(); /* This should be harmless. */ + } + exit(0); +} + +static void +create_thread(int conn, struct sockaddr_in *addr) +{ + struct workorder *work; + pthread_t tdata; + + work = malloc(sizeof(struct workorder)); + if (work == NULL) { + oprogname(); + fprintf(stderr, "out of memory for thread.\n"); + close(conn); + return; + } + work->conn = conn; + work->addr = *addr; + + init_python(); + + if (pthread_create(&tdata, NULL, (void *)service_thread, work) < 0) { + oprogname(); + perror("can't create new thread"); + close(conn); + return; + } + + if (pthread_detach(tdata) < 0) { + oprogname(); + perror("can't detach from thread"); + } +} + +static PyThreadState *the_tstate; +static PyInterpreterState *the_interp; +static PyObject *the_builtins; + +static void +init_python(void) +{ + if (gtstate) + return; + Py_Initialize(); /* Initialize the interpreter */ + PyEval_InitThreads(); /* Create (and acquire) the interpreter lock */ + gtstate = PyEval_SaveThread(); /* Release the thread state */ +} + +static void * +service_thread(struct workorder *work) +{ + FILE *input, *output; + + fprintf(stderr, "Start thread for connection %d.\n", work->conn); + + ps(); + + input = fdopen(work->conn, "r"); + if (input == NULL) { + oprogname(); + perror("can't create input stream"); + goto done; + } + + output = fdopen(work->conn, "w"); + if (output == NULL) { + oprogname(); + perror("can't create output stream"); + fclose(input); + goto done; + } + + setvbuf(input, NULL, _IONBF, 0); + setvbuf(output, NULL, _IONBF, 0); + + run_interpreter(input, output); + + fclose(input); + fclose(output); + + done: + fprintf(stderr, "End thread for connection %d.\n", work->conn); + close(work->conn); + free(work); +} + +static void +oprogname(void) +{ + int save = errno; + fprintf(stderr, "%s: ", progname); + errno = save; +} + +static void +run_interpreter(FILE *input, FILE *output) +{ + PyThreadState *tstate; + PyObject *new_stdin, *new_stdout; + PyObject *mainmod, *globals; + char buffer[1000]; + char *p, *q; + int n, end; + + PyEval_AcquireLock(); + tstate = Py_NewInterpreter(); + if (tstate == NULL) { + fprintf(output, "Sorry -- can't create an interpreter\n"); + return; + } + + mainmod = PyImport_AddModule("__main__"); + globals = PyModule_GetDict(mainmod); + Py_INCREF(globals); + + new_stdin = PyFile_FromFile(input, "<socket-in>", "r", NULL); + new_stdout = PyFile_FromFile(output, "<socket-out>", "w", NULL); + + PySys_SetObject("stdin", new_stdin); + PySys_SetObject("stdout", new_stdout); + PySys_SetObject("stderr", new_stdout); + + for (n = 1; !PyErr_Occurred(); n++) { + Py_BEGIN_ALLOW_THREADS + fprintf(output, "%d> ", n); + p = fgets(buffer, sizeof buffer, input); + Py_END_ALLOW_THREADS + + if (p == NULL) + break; + if (p[0] == '\377' && p[1] == '\354') + break; + + q = strrchr(p, '\r'); + if (q && q[1] == '\n' && q[2] == '\0') { + *q++ = '\n'; + *q++ = '\0'; + } + + while (*p && isspace(*p)) + p++; + if (p[0] == '#' || p[0] == '\0') + continue; + + end = run_command(buffer, globals); + if (end < 0) + PyErr_Print(); + + if (end) + break; + } + + Py_XDECREF(globals); + Py_XDECREF(new_stdin); + Py_XDECREF(new_stdout); + + Py_EndInterpreter(tstate); + PyEval_ReleaseLock(); + + fprintf(output, "Goodbye!\n"); +} + +static int +run_command(char *buffer, PyObject *globals) +{ + PyObject *m, *d, *v; + fprintf(stderr, "run_command: %s", buffer); + if (strchr(buffer, '\n') == NULL) + fprintf(stderr, "\n"); + v = PyRun_String(buffer, Py_single_input, globals, globals); + if (v == NULL) { + if (PyErr_Occurred() == PyExc_SystemExit) { + PyErr_Clear(); + return 1; + } + PyErr_Print(); + return 0; + } + Py_DECREF(v); + return 0; +} + +static void +ps(void) +{ + char buffer[100]; + PyOS_snprintf(buffer, sizeof(buffer), + "ps -l -p %d </dev/null | sed 1d\n", getpid()); + system(buffer); +} diff --git a/sys/src/cmd/python/Demo/pysvr/pysvr.py b/sys/src/cmd/python/Demo/pysvr/pysvr.py new file mode 100755 index 000000000..dd0abdc77 --- /dev/null +++ b/sys/src/cmd/python/Demo/pysvr/pysvr.py @@ -0,0 +1,124 @@ +#! /usr/bin/env python + +"""A multi-threaded telnet-like server that gives a Python prompt. + +This is really a prototype for the same thing in C. + +Usage: pysvr.py [port] + +For security reasons, it only accepts requests from the current host. +This can still be insecure, but restricts violations from people who +can log in on your machine. Use with caution! + +""" + +import sys, os, string, getopt, thread, socket, traceback + +PORT = 4000 # Default port + +def main(): + try: + opts, args = getopt.getopt(sys.argv[1:], "") + if len(args) > 1: + raise getopt.error, "Too many arguments." + except getopt.error, msg: + usage(msg) + for o, a in opts: + pass + if args: + try: + port = string.atoi(args[0]) + except ValueError, msg: + usage(msg) + else: + port = PORT + main_thread(port) + +def usage(msg=None): + sys.stdout = sys.stderr + if msg: + print msg + print "\n", __doc__, + sys.exit(2) + +def main_thread(port): + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.bind(("", port)) + sock.listen(5) + print "Listening on port", port, "..." + while 1: + (conn, addr) = sock.accept() + if addr[0] != conn.getsockname()[0]: + conn.close() + print "Refusing connection from non-local host", addr[0], "." + continue + thread.start_new_thread(service_thread, (conn, addr)) + del conn, addr + +def service_thread(conn, addr): + (caddr, cport) = addr + print "Thread %s has connection from %s.\n" % (str(thread.get_ident()), + caddr), + stdin = conn.makefile("r") + stdout = conn.makefile("w", 0) + run_interpreter(stdin, stdout) + print "Thread %s is done.\n" % str(thread.get_ident()), + +def run_interpreter(stdin, stdout): + globals = {} + try: + str(sys.ps1) + except: + sys.ps1 = ">>> " + source = "" + while 1: + stdout.write(sys.ps1) + line = stdin.readline() + if line[:2] == '\377\354': + line = "" + if not line and not source: + break + if line[-2:] == '\r\n': + line = line[:-2] + '\n' + source = source + line + try: + code = compile_command(source) + except SyntaxError, err: + source = "" + traceback.print_exception(SyntaxError, err, None, file=stdout) + continue + if not code: + continue + source = "" + try: + run_command(code, stdin, stdout, globals) + except SystemExit, how: + if how: + try: + how = str(how) + except: + how = "" + stdout.write("Exit %s\n" % how) + break + stdout.write("\nGoodbye.\n") + +def run_command(code, stdin, stdout, globals): + save = sys.stdin, sys.stdout, sys.stderr + try: + sys.stdout = sys.stderr = stdout + sys.stdin = stdin + try: + exec code in globals + except SystemExit, how: + raise SystemExit, how, sys.exc_info()[2] + except: + type, value, tb = sys.exc_info() + if tb: tb = tb.tb_next + traceback.print_exception(type, value, tb) + del tb + finally: + sys.stdin, sys.stdout, sys.stderr = save + +from code import compile_command + +main() diff --git a/sys/src/cmd/python/Demo/rpc/MANIFEST b/sys/src/cmd/python/Demo/rpc/MANIFEST new file mode 100644 index 000000000..e65f3ebee --- /dev/null +++ b/sys/src/cmd/python/Demo/rpc/MANIFEST @@ -0,0 +1,10 @@ + File Name Archive # Description +----------------------------------------------------------- + MANIFEST 1 This shipping list + README 1 + T.py 1 + mountclient.py 1 + nfsclient.py 1 + rpc.py 1 + test 1 + xdr.py 1 diff --git a/sys/src/cmd/python/Demo/rpc/README b/sys/src/cmd/python/Demo/rpc/README new file mode 100644 index 000000000..97948a338 --- /dev/null +++ b/sys/src/cmd/python/Demo/rpc/README @@ -0,0 +1,31 @@ +This is a Python interface to Sun RPC, designed and implemented mostly +by reading the Internet RFCs about the subject. + +*** NOTE: xdr.py has evolved into the standard module xdrlib.py *** + +There are two library modules, xdr.py and rpc.py, and several example +clients: mountclient.py, nfsclient.py, and rnusersclient.py, +implementing the NFS Mount protocol, (part of) the NFS protocol, and +the "rnusers" protocol (used by rusers(1)), respectively. The latter +demonstrates the use of broadcast via the Port mapper's CALLIT +procedure. + +There is also a way to create servers in Python. + +To test the nfs client, run it from the shell with something like this: + + python -c 'import nfsclient; nfsclient.test()' [hostname [filesystemname]] + +When called without a filesystemname, it lists the filesystems at the +host; default host is the local machine. + +Other clients are tested similarly. + +For hostname, use e.g. wuarchive.wustl.edu or gatekeeper.dec.com (two +hosts that are known to export NFS filesystems with little restrictions). + +There are now two different RPC compilers: + +1) Wim Lewis rpcgen.py found on http://www.omnigroup.com/~wiml/soft/stale-index.html#python. + +2) Peter Åstrands rpcgen.py, which is part of "pynfs" (http://www.cendio.se/~peter/pynfs/). diff --git a/sys/src/cmd/python/Demo/rpc/T.py b/sys/src/cmd/python/Demo/rpc/T.py new file mode 100644 index 000000000..33255073e --- /dev/null +++ b/sys/src/cmd/python/Demo/rpc/T.py @@ -0,0 +1,22 @@ +# Simple interface to report execution times of program fragments. +# Call TSTART() to reset the timer, TSTOP(...) to report times. + +import sys, os, time + +def TSTART(): + global t0, t1 + u, s, cu, cs = os.times() + t0 = u+cu, s+cs, time.time() + +def TSTOP(*label): + global t0, t1 + u, s, cu, cs = os.times() + t1 = u+cu, s+cs, time.time() + tt = [] + for i in range(3): + tt.append(t1[i] - t0[i]) + [u, s, r] = tt + msg = '' + for x in label: msg = msg + (x + ' ') + msg = msg + '%r user, %r sys, %r real\n' % (u, s, r) + sys.stderr.write(msg) diff --git a/sys/src/cmd/python/Demo/rpc/mountclient.py b/sys/src/cmd/python/Demo/rpc/mountclient.py new file mode 100644 index 000000000..318a9d99b --- /dev/null +++ b/sys/src/cmd/python/Demo/rpc/mountclient.py @@ -0,0 +1,202 @@ +# Mount RPC client -- RFC 1094 (NFS), Appendix A + +# This module demonstrates how to write your own RPC client in Python. +# When this example was written, there was no RPC compiler for +# Python. Without such a compiler, you must first create classes +# derived from Packer and Unpacker to handle the data types for the +# server you want to interface to. You then write the client class. +# If you want to support both the TCP and the UDP version of a +# protocol, use multiple inheritance as shown below. + + +import rpc +from rpc import Packer, Unpacker, TCPClient, UDPClient + + +# Program number and version for the mount protocol +MOUNTPROG = 100005 +MOUNTVERS = 1 + +# Size of the 'fhandle' opaque structure +FHSIZE = 32 + + +# Packer derived class for Mount protocol clients. +# The only thing we need to pack beyond basic types is an 'fhandle' + +class MountPacker(Packer): + + def pack_fhandle(self, fhandle): + self.pack_fopaque(FHSIZE, fhandle) + + +# Unpacker derived class for Mount protocol clients. +# The important types we need to unpack are fhandle, fhstatus, +# mountlist and exportlist; mountstruct, exportstruct and groups are +# used to unpack components of mountlist and exportlist and the +# corresponding functions are passed as function argument to the +# generic unpack_list function. + +class MountUnpacker(Unpacker): + + def unpack_fhandle(self): + return self.unpack_fopaque(FHSIZE) + + def unpack_fhstatus(self): + status = self.unpack_uint() + if status == 0: + fh = self.unpack_fhandle() + else: + fh = None + return status, fh + + def unpack_mountlist(self): + return self.unpack_list(self.unpack_mountstruct) + + def unpack_mountstruct(self): + hostname = self.unpack_string() + directory = self.unpack_string() + return (hostname, directory) + + def unpack_exportlist(self): + return self.unpack_list(self.unpack_exportstruct) + + def unpack_exportstruct(self): + filesys = self.unpack_string() + groups = self.unpack_groups() + return (filesys, groups) + + def unpack_groups(self): + return self.unpack_list(self.unpack_string) + + +# These are the procedures specific to the Mount client class. +# Think of this as a derived class of either TCPClient or UDPClient. + +class PartialMountClient: + + # This method is called by Client.__init__ to initialize + # self.packer and self.unpacker + def addpackers(self): + self.packer = MountPacker() + self.unpacker = MountUnpacker('') + + # This method is called by Client.__init__ to bind the socket + # to a particular network interface and port. We use the + # default network interface, but if we're running as root, + # we want to bind to a reserved port + def bindsocket(self): + import os + try: + uid = os.getuid() + except AttributeError: + uid = 1 + if uid == 0: + port = rpc.bindresvport(self.sock, '') + # 'port' is not used + else: + self.sock.bind(('', 0)) + + # This function is called to cough up a suitable + # authentication object for a call to procedure 'proc'. + def mkcred(self): + if self.cred == None: + self.cred = rpc.AUTH_UNIX, rpc.make_auth_unix_default() + return self.cred + + # The methods Mnt, Dump etc. each implement one Remote + # Procedure Call. This is done by calling self.make_call() + # with as arguments: + # + # - the procedure number + # - the arguments (or None) + # - the "packer" function for the arguments (or None) + # - the "unpacker" function for the return value (or None) + # + # The packer and unpacker function, if not None, *must* be + # methods of self.packer and self.unpacker, respectively. + # A value of None means that there are no arguments or is no + # return value, respectively. + # + # The return value from make_call() is the return value from + # the remote procedure call, as unpacked by the "unpacker" + # function, or None if the unpacker function is None. + # + # (Even if you expect a result of None, you should still + # return the return value from make_call(), since this may be + # needed by a broadcasting version of the class.) + # + # If the call fails, make_call() raises an exception + # (this includes time-outs and invalid results). + # + # Note that (at least with the UDP protocol) there is no + # guarantee that a call is executed at most once. When you do + # get a reply, you know it has been executed at least once; + # when you don't get a reply, you know nothing. + + def Mnt(self, directory): + return self.make_call(1, directory, \ + self.packer.pack_string, \ + self.unpacker.unpack_fhstatus) + + def Dump(self): + return self.make_call(2, None, \ + None, self.unpacker.unpack_mountlist) + + def Umnt(self, directory): + return self.make_call(3, directory, \ + self.packer.pack_string, None) + + def Umntall(self): + return self.make_call(4, None, None, None) + + def Export(self): + return self.make_call(5, None, \ + None, self.unpacker.unpack_exportlist) + + +# We turn the partial Mount client into a full one for either protocol +# by use of multiple inheritance. (In general, when class C has base +# classes B1...Bn, if x is an instance of class C, methods of x are +# searched first in C, then in B1, then in B2, ..., finally in Bn.) + +class TCPMountClient(PartialMountClient, TCPClient): + + def __init__(self, host): + TCPClient.__init__(self, host, MOUNTPROG, MOUNTVERS) + + +class UDPMountClient(PartialMountClient, UDPClient): + + def __init__(self, host): + UDPClient.__init__(self, host, MOUNTPROG, MOUNTVERS) + + +# A little test program for the Mount client. This takes a host as +# command line argument (default the local machine), prints its export +# list, and attempts to mount and unmount each exported files system. +# An optional first argument of -t or -u specifies the protocol to use +# (TCP or UDP), default is UDP. + +def test(): + import sys + if sys.argv[1:] and sys.argv[1] == '-t': + C = TCPMountClient + del sys.argv[1] + elif sys.argv[1:] and sys.argv[1] == '-u': + C = UDPMountClient + del sys.argv[1] + else: + C = UDPMountClient + if sys.argv[1:]: host = sys.argv[1] + else: host = '' + mcl = C(host) + list = mcl.Export() + for item in list: + print item + try: + mcl.Mnt(item[0]) + except: + print 'Sorry' + continue + mcl.Umnt(item[0]) diff --git a/sys/src/cmd/python/Demo/rpc/nfsclient.py b/sys/src/cmd/python/Demo/rpc/nfsclient.py new file mode 100644 index 000000000..c4387b4a0 --- /dev/null +++ b/sys/src/cmd/python/Demo/rpc/nfsclient.py @@ -0,0 +1,201 @@ +# NFS RPC client -- RFC 1094 + +# XXX This is not yet complete. +# XXX Only GETATTR, SETTTR, LOOKUP and READDIR are supported. + +# (See mountclient.py for some hints on how to write RPC clients in +# Python in general) + +import rpc +from rpc import UDPClient, TCPClient +from mountclient import FHSIZE, MountPacker, MountUnpacker + +NFS_PROGRAM = 100003 +NFS_VERSION = 2 + +# enum stat +NFS_OK = 0 +# (...many error values...) + +# enum ftype +NFNON = 0 +NFREG = 1 +NFDIR = 2 +NFBLK = 3 +NFCHR = 4 +NFLNK = 5 + + +class NFSPacker(MountPacker): + + def pack_sattrargs(self, sa): + file, attributes = sa + self.pack_fhandle(file) + self.pack_sattr(attributes) + + def pack_sattr(self, sa): + mode, uid, gid, size, atime, mtime = sa + self.pack_uint(mode) + self.pack_uint(uid) + self.pack_uint(gid) + self.pack_uint(size) + self.pack_timeval(atime) + self.pack_timeval(mtime) + + def pack_diropargs(self, da): + dir, name = da + self.pack_fhandle(dir) + self.pack_string(name) + + def pack_readdirargs(self, ra): + dir, cookie, count = ra + self.pack_fhandle(dir) + self.pack_uint(cookie) + self.pack_uint(count) + + def pack_timeval(self, tv): + secs, usecs = tv + self.pack_uint(secs) + self.pack_uint(usecs) + + +class NFSUnpacker(MountUnpacker): + + def unpack_readdirres(self): + status = self.unpack_enum() + if status == NFS_OK: + entries = self.unpack_list(self.unpack_entry) + eof = self.unpack_bool() + rest = (entries, eof) + else: + rest = None + return (status, rest) + + def unpack_entry(self): + fileid = self.unpack_uint() + name = self.unpack_string() + cookie = self.unpack_uint() + return (fileid, name, cookie) + + def unpack_diropres(self): + status = self.unpack_enum() + if status == NFS_OK: + fh = self.unpack_fhandle() + fa = self.unpack_fattr() + rest = (fh, fa) + else: + rest = None + return (status, rest) + + def unpack_attrstat(self): + status = self.unpack_enum() + if status == NFS_OK: + attributes = self.unpack_fattr() + else: + attributes = None + return status, attributes + + def unpack_fattr(self): + type = self.unpack_enum() + mode = self.unpack_uint() + nlink = self.unpack_uint() + uid = self.unpack_uint() + gid = self.unpack_uint() + size = self.unpack_uint() + blocksize = self.unpack_uint() + rdev = self.unpack_uint() + blocks = self.unpack_uint() + fsid = self.unpack_uint() + fileid = self.unpack_uint() + atime = self.unpack_timeval() + mtime = self.unpack_timeval() + ctime = self.unpack_timeval() + return (type, mode, nlink, uid, gid, size, blocksize, \ + rdev, blocks, fsid, fileid, atime, mtime, ctime) + + def unpack_timeval(self): + secs = self.unpack_uint() + usecs = self.unpack_uint() + return (secs, usecs) + + +class NFSClient(UDPClient): + + def __init__(self, host): + UDPClient.__init__(self, host, NFS_PROGRAM, NFS_VERSION) + + def addpackers(self): + self.packer = NFSPacker() + self.unpacker = NFSUnpacker('') + + def mkcred(self): + if self.cred == None: + self.cred = rpc.AUTH_UNIX, rpc.make_auth_unix_default() + return self.cred + + def Getattr(self, fh): + return self.make_call(1, fh, \ + self.packer.pack_fhandle, \ + self.unpacker.unpack_attrstat) + + def Setattr(self, sa): + return self.make_call(2, sa, \ + self.packer.pack_sattrargs, \ + self.unpacker.unpack_attrstat) + + # Root() is obsolete + + def Lookup(self, da): + return self.make_call(4, da, \ + self.packer.pack_diropargs, \ + self.unpacker.unpack_diropres) + + # ... + + def Readdir(self, ra): + return self.make_call(16, ra, \ + self.packer.pack_readdirargs, \ + self.unpacker.unpack_readdirres) + + # Shorthand to get the entire contents of a directory + def Listdir(self, dir): + list = [] + ra = (dir, 0, 2000) + while 1: + (status, rest) = self.Readdir(ra) + if status <> NFS_OK: + break + entries, eof = rest + last_cookie = None + for fileid, name, cookie in entries: + list.append((fileid, name)) + last_cookie = cookie + if eof or last_cookie == None: + break + ra = (ra[0], last_cookie, ra[2]) + return list + + +def test(): + import sys + if sys.argv[1:]: host = sys.argv[1] + else: host = '' + if sys.argv[2:]: filesys = sys.argv[2] + else: filesys = None + from mountclient import UDPMountClient, TCPMountClient + mcl = TCPMountClient(host) + if filesys == None: + list = mcl.Export() + for item in list: + print item + return + sf = mcl.Mnt(filesys) + print sf + fh = sf[1] + if fh: + ncl = NFSClient(host) + as = ncl.Getattr(fh) + print as + list = ncl.Listdir(fh) + for item in list: print item + mcl.Umnt(filesys) diff --git a/sys/src/cmd/python/Demo/rpc/rnusersclient.py b/sys/src/cmd/python/Demo/rpc/rnusersclient.py new file mode 100644 index 000000000..1f3a882c5 --- /dev/null +++ b/sys/src/cmd/python/Demo/rpc/rnusersclient.py @@ -0,0 +1,98 @@ +# Remote nusers client interface + +import rpc +from rpc import Packer, Unpacker, UDPClient, BroadcastUDPClient + + +class RnusersPacker(Packer): + def pack_utmp(self, ui): + ut_line, ut_name, ut_host, ut_time = utmp + self.pack_string(ut_line) + self.pack_string(ut_name) + self.pack_string(ut_host) + self.pack_int(ut_time) + def pack_utmpidle(self, ui): + ui_itmp, ui_idle = ui + self.pack_utmp(ui_utmp) + self.pack_uint(ui_idle) + def pack_utmpidlearr(self, list): + self.pack_array(list, self.pack_itmpidle) + + +class RnusersUnpacker(Unpacker): + def unpack_utmp(self): + ut_line = self.unpack_string() + ut_name = self.unpack_string() + ut_host = self.unpack_string() + ut_time = self.unpack_int() + return ut_line, ut_name, ut_host, ut_time + def unpack_utmpidle(self): + ui_utmp = self.unpack_utmp() + ui_idle = self.unpack_uint() + return ui_utmp, ui_idle + def unpack_utmpidlearr(self): + return self.unpack_array(self.unpack_utmpidle) + + +class PartialRnusersClient: + + def addpackers(self): + self.packer = RnusersPacker() + self.unpacker = RnusersUnpacker('') + + def Num(self): + return self.make_call(1, None, None, self.unpacker.unpack_int) + + def Names(self): + return self.make_call(2, None, \ + None, self.unpacker.unpack_utmpidlearr) + + def Allnames(self): + return self.make_call(3, None, \ + None, self.unpacker.unpack_utmpidlearr) + + +class RnusersClient(PartialRnusersClient, UDPClient): + + def __init__(self, host): + UDPClient.__init__(self, host, 100002, 2) + + +class BroadcastRnusersClient(PartialRnusersClient, BroadcastUDPClient): + + def __init__(self, bcastaddr): + BroadcastUDPClient.__init__(self, bcastaddr, 100002, 2) + + +def test(): + import sys + if not sys.argv[1:]: + testbcast() + return + else: + host = sys.argv[1] + c = RnusersClient(host) + list = c.Names() + for (line, name, host, time), idle in list: + line = strip0(line) + name = strip0(name) + host = strip0(host) + print "%r %r %r %s %s" % (name, host, line, time, idle) + +def testbcast(): + c = BroadcastRnusersClient('<broadcast>') + def listit(list, fromaddr): + host, port = fromaddr + print host + '\t:', + for (line, name, host, time), idle in list: + print strip0(name), + print + c.set_reply_handler(listit) + all = c.Names() + print 'Total Count:', len(all) + +def strip0(s): + while s and s[-1] == '\0': s = s[:-1] + return s + +test() diff --git a/sys/src/cmd/python/Demo/rpc/rpc.py b/sys/src/cmd/python/Demo/rpc/rpc.py new file mode 100644 index 000000000..141fe09a5 --- /dev/null +++ b/sys/src/cmd/python/Demo/rpc/rpc.py @@ -0,0 +1,893 @@ +# Sun RPC version 2 -- RFC1057. + +# XXX There should be separate exceptions for the various reasons why +# XXX an RPC can fail, rather than using RuntimeError for everything + +# XXX Need to use class based exceptions rather than string exceptions + +# XXX The UDP version of the protocol resends requests when it does +# XXX not receive a timely reply -- use only for idempotent calls! + +# XXX There is no provision for call timeout on TCP connections + +import xdr +import socket +import os + +RPCVERSION = 2 + +CALL = 0 +REPLY = 1 + +AUTH_NULL = 0 +AUTH_UNIX = 1 +AUTH_SHORT = 2 +AUTH_DES = 3 + +MSG_ACCEPTED = 0 +MSG_DENIED = 1 + +SUCCESS = 0 # RPC executed successfully +PROG_UNAVAIL = 1 # remote hasn't exported program +PROG_MISMATCH = 2 # remote can't support version # +PROC_UNAVAIL = 3 # program can't support procedure +GARBAGE_ARGS = 4 # procedure can't decode params + +RPC_MISMATCH = 0 # RPC version number != 2 +AUTH_ERROR = 1 # remote can't authenticate caller + +AUTH_BADCRED = 1 # bad credentials (seal broken) +AUTH_REJECTEDCRED = 2 # client must begin new session +AUTH_BADVERF = 3 # bad verifier (seal broken) +AUTH_REJECTEDVERF = 4 # verifier expired or replayed +AUTH_TOOWEAK = 5 # rejected for security reasons + + +class Packer(xdr.Packer): + + def pack_auth(self, auth): + flavor, stuff = auth + self.pack_enum(flavor) + self.pack_opaque(stuff) + + def pack_auth_unix(self, stamp, machinename, uid, gid, gids): + self.pack_uint(stamp) + self.pack_string(machinename) + self.pack_uint(uid) + self.pack_uint(gid) + self.pack_uint(len(gids)) + for i in gids: + self.pack_uint(i) + + def pack_callheader(self, xid, prog, vers, proc, cred, verf): + self.pack_uint(xid) + self.pack_enum(CALL) + self.pack_uint(RPCVERSION) + self.pack_uint(prog) + self.pack_uint(vers) + self.pack_uint(proc) + self.pack_auth(cred) + self.pack_auth(verf) + # Caller must add procedure-specific part of call + + def pack_replyheader(self, xid, verf): + self.pack_uint(xid) + self.pack_enum(REPLY) + self.pack_uint(MSG_ACCEPTED) + self.pack_auth(verf) + self.pack_enum(SUCCESS) + # Caller must add procedure-specific part of reply + + +# Exceptions +BadRPCFormat = 'rpc.BadRPCFormat' +BadRPCVersion = 'rpc.BadRPCVersion' +GarbageArgs = 'rpc.GarbageArgs' + +class Unpacker(xdr.Unpacker): + + def unpack_auth(self): + flavor = self.unpack_enum() + stuff = self.unpack_opaque() + return (flavor, stuff) + + def unpack_callheader(self): + xid = self.unpack_uint() + temp = self.unpack_enum() + if temp != CALL: + raise BadRPCFormat, 'no CALL but %r' % (temp,) + temp = self.unpack_uint() + if temp != RPCVERSION: + raise BadRPCVersion, 'bad RPC version %r' % (temp,) + prog = self.unpack_uint() + vers = self.unpack_uint() + proc = self.unpack_uint() + cred = self.unpack_auth() + verf = self.unpack_auth() + return xid, prog, vers, proc, cred, verf + # Caller must add procedure-specific part of call + + def unpack_replyheader(self): + xid = self.unpack_uint() + mtype = self.unpack_enum() + if mtype != REPLY: + raise RuntimeError, 'no REPLY but %r' % (mtype,) + stat = self.unpack_enum() + if stat == MSG_DENIED: + stat = self.unpack_enum() + if stat == RPC_MISMATCH: + low = self.unpack_uint() + high = self.unpack_uint() + raise RuntimeError, \ + 'MSG_DENIED: RPC_MISMATCH: %r' % ((low, high),) + if stat == AUTH_ERROR: + stat = self.unpack_uint() + raise RuntimeError, \ + 'MSG_DENIED: AUTH_ERROR: %r' % (stat,) + raise RuntimeError, 'MSG_DENIED: %r' % (stat,) + if stat != MSG_ACCEPTED: + raise RuntimeError, \ + 'Neither MSG_DENIED nor MSG_ACCEPTED: %r' % (stat,) + verf = self.unpack_auth() + stat = self.unpack_enum() + if stat == PROG_UNAVAIL: + raise RuntimeError, 'call failed: PROG_UNAVAIL' + if stat == PROG_MISMATCH: + low = self.unpack_uint() + high = self.unpack_uint() + raise RuntimeError, \ + 'call failed: PROG_MISMATCH: %r' % ((low, high),) + if stat == PROC_UNAVAIL: + raise RuntimeError, 'call failed: PROC_UNAVAIL' + if stat == GARBAGE_ARGS: + raise RuntimeError, 'call failed: GARBAGE_ARGS' + if stat != SUCCESS: + raise RuntimeError, 'call failed: %r' % (stat,) + return xid, verf + # Caller must get procedure-specific part of reply + + +# Subroutines to create opaque authentication objects + +def make_auth_null(): + return '' + +def make_auth_unix(seed, host, uid, gid, groups): + p = Packer() + p.pack_auth_unix(seed, host, uid, gid, groups) + return p.get_buf() + +def make_auth_unix_default(): + try: + from os import getuid, getgid + uid = getuid() + gid = getgid() + except ImportError: + uid = gid = 0 + import time + return make_auth_unix(int(time.time()-unix_epoch()), \ + socket.gethostname(), uid, gid, []) + +_unix_epoch = -1 +def unix_epoch(): + """Very painful calculation of when the Unix Epoch is. + + This is defined as the return value of time.time() on Jan 1st, + 1970, 00:00:00 GMT. + + On a Unix system, this should always return 0.0. On a Mac, the + calculations are needed -- and hard because of integer overflow + and other limitations. + + """ + global _unix_epoch + if _unix_epoch >= 0: return _unix_epoch + import time + now = time.time() + localt = time.localtime(now) # (y, m, d, hh, mm, ss, ..., ..., ...) + gmt = time.gmtime(now) + offset = time.mktime(localt) - time.mktime(gmt) + y, m, d, hh, mm, ss = 1970, 1, 1, 0, 0, 0 + offset, ss = divmod(ss + offset, 60) + offset, mm = divmod(mm + offset, 60) + offset, hh = divmod(hh + offset, 24) + d = d + offset + _unix_epoch = time.mktime((y, m, d, hh, mm, ss, 0, 0, 0)) + print "Unix epoch:", time.ctime(_unix_epoch) + return _unix_epoch + + +# Common base class for clients + +class Client: + + def __init__(self, host, prog, vers, port): + self.host = host + self.prog = prog + self.vers = vers + self.port = port + self.makesocket() # Assigns to self.sock + self.bindsocket() + self.connsocket() + self.lastxid = 0 # XXX should be more random? + self.addpackers() + self.cred = None + self.verf = None + + def close(self): + self.sock.close() + + def makesocket(self): + # This MUST be overridden + raise RuntimeError, 'makesocket not defined' + + def connsocket(self): + # Override this if you don't want/need a connection + self.sock.connect((self.host, self.port)) + + def bindsocket(self): + # Override this to bind to a different port (e.g. reserved) + self.sock.bind(('', 0)) + + def addpackers(self): + # Override this to use derived classes from Packer/Unpacker + self.packer = Packer() + self.unpacker = Unpacker('') + + def make_call(self, proc, args, pack_func, unpack_func): + # Don't normally override this (but see Broadcast) + if pack_func is None and args is not None: + raise TypeError, 'non-null args with null pack_func' + self.start_call(proc) + if pack_func: + pack_func(args) + self.do_call() + if unpack_func: + result = unpack_func() + else: + result = None + self.unpacker.done() + return result + + def start_call(self, proc): + # Don't override this + self.lastxid = xid = self.lastxid + 1 + cred = self.mkcred() + verf = self.mkverf() + p = self.packer + p.reset() + p.pack_callheader(xid, self.prog, self.vers, proc, cred, verf) + + def do_call(self): + # This MUST be overridden + raise RuntimeError, 'do_call not defined' + + def mkcred(self): + # Override this to use more powerful credentials + if self.cred == None: + self.cred = (AUTH_NULL, make_auth_null()) + return self.cred + + def mkverf(self): + # Override this to use a more powerful verifier + if self.verf == None: + self.verf = (AUTH_NULL, make_auth_null()) + return self.verf + + def call_0(self): # Procedure 0 is always like this + return self.make_call(0, None, None, None) + + +# Record-Marking standard support + +def sendfrag(sock, last, frag): + x = len(frag) + if last: x = x | 0x80000000L + header = (chr(int(x>>24 & 0xff)) + chr(int(x>>16 & 0xff)) + \ + chr(int(x>>8 & 0xff)) + chr(int(x & 0xff))) + sock.send(header + frag) + +def sendrecord(sock, record): + sendfrag(sock, 1, record) + +def recvfrag(sock): + header = sock.recv(4) + if len(header) < 4: + raise EOFError + x = long(ord(header[0]))<<24 | ord(header[1])<<16 | \ + ord(header[2])<<8 | ord(header[3]) + last = ((x & 0x80000000) != 0) + n = int(x & 0x7fffffff) + frag = '' + while n > 0: + buf = sock.recv(n) + if not buf: raise EOFError + n = n - len(buf) + frag = frag + buf + return last, frag + +def recvrecord(sock): + record = '' + last = 0 + while not last: + last, frag = recvfrag(sock) + record = record + frag + return record + + +# Try to bind to a reserved port (must be root) + +last_resv_port_tried = None +def bindresvport(sock, host): + global last_resv_port_tried + FIRST, LAST = 600, 1024 # Range of ports to try + if last_resv_port_tried == None: + import os + last_resv_port_tried = FIRST + os.getpid() % (LAST-FIRST) + for i in range(last_resv_port_tried, LAST) + \ + range(FIRST, last_resv_port_tried): + last_resv_port_tried = i + try: + sock.bind((host, i)) + return last_resv_port_tried + except socket.error, (errno, msg): + if errno != 114: + raise socket.error, (errno, msg) + raise RuntimeError, 'can\'t assign reserved port' + + +# Client using TCP to a specific port + +class RawTCPClient(Client): + + def makesocket(self): + self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + def do_call(self): + call = self.packer.get_buf() + sendrecord(self.sock, call) + reply = recvrecord(self.sock) + u = self.unpacker + u.reset(reply) + xid, verf = u.unpack_replyheader() + if xid != self.lastxid: + # Can't really happen since this is TCP... + raise RuntimeError, 'wrong xid in reply %r instead of %r' % ( + xid, self.lastxid) + + +# Client using UDP to a specific port + +class RawUDPClient(Client): + + def makesocket(self): + self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + + def do_call(self): + call = self.packer.get_buf() + self.sock.send(call) + try: + from select import select + except ImportError: + print 'WARNING: select not found, RPC may hang' + select = None + BUFSIZE = 8192 # Max UDP buffer size + timeout = 1 + count = 5 + while 1: + r, w, x = [self.sock], [], [] + if select: + r, w, x = select(r, w, x, timeout) + if self.sock not in r: + count = count - 1 + if count < 0: raise RuntimeError, 'timeout' + if timeout < 25: timeout = timeout *2 +## print 'RESEND', timeout, count + self.sock.send(call) + continue + reply = self.sock.recv(BUFSIZE) + u = self.unpacker + u.reset(reply) + xid, verf = u.unpack_replyheader() + if xid != self.lastxid: +## print 'BAD xid' + continue + break + + +# Client using UDP broadcast to a specific port + +class RawBroadcastUDPClient(RawUDPClient): + + def __init__(self, bcastaddr, prog, vers, port): + RawUDPClient.__init__(self, bcastaddr, prog, vers, port) + self.reply_handler = None + self.timeout = 30 + + def connsocket(self): + # Don't connect -- use sendto + self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) + + def set_reply_handler(self, reply_handler): + self.reply_handler = reply_handler + + def set_timeout(self, timeout): + self.timeout = timeout # Use None for infinite timeout + + def make_call(self, proc, args, pack_func, unpack_func): + if pack_func is None and args is not None: + raise TypeError, 'non-null args with null pack_func' + self.start_call(proc) + if pack_func: + pack_func(args) + call = self.packer.get_buf() + self.sock.sendto(call, (self.host, self.port)) + try: + from select import select + except ImportError: + print 'WARNING: select not found, broadcast will hang' + select = None + BUFSIZE = 8192 # Max UDP buffer size (for reply) + replies = [] + if unpack_func is None: + def dummy(): pass + unpack_func = dummy + while 1: + r, w, x = [self.sock], [], [] + if select: + if self.timeout is None: + r, w, x = select(r, w, x) + else: + r, w, x = select(r, w, x, self.timeout) + if self.sock not in r: + break + reply, fromaddr = self.sock.recvfrom(BUFSIZE) + u = self.unpacker + u.reset(reply) + xid, verf = u.unpack_replyheader() + if xid != self.lastxid: +## print 'BAD xid' + continue + reply = unpack_func() + self.unpacker.done() + replies.append((reply, fromaddr)) + if self.reply_handler: + self.reply_handler(reply, fromaddr) + return replies + + +# Port mapper interface + +# Program number, version and (fixed!) port number +PMAP_PROG = 100000 +PMAP_VERS = 2 +PMAP_PORT = 111 + +# Procedure numbers +PMAPPROC_NULL = 0 # (void) -> void +PMAPPROC_SET = 1 # (mapping) -> bool +PMAPPROC_UNSET = 2 # (mapping) -> bool +PMAPPROC_GETPORT = 3 # (mapping) -> unsigned int +PMAPPROC_DUMP = 4 # (void) -> pmaplist +PMAPPROC_CALLIT = 5 # (call_args) -> call_result + +# A mapping is (prog, vers, prot, port) and prot is one of: + +IPPROTO_TCP = 6 +IPPROTO_UDP = 17 + +# A pmaplist is a variable-length list of mappings, as follows: +# either (1, mapping, pmaplist) or (0). + +# A call_args is (prog, vers, proc, args) where args is opaque; +# a call_result is (port, res) where res is opaque. + + +class PortMapperPacker(Packer): + + def pack_mapping(self, mapping): + prog, vers, prot, port = mapping + self.pack_uint(prog) + self.pack_uint(vers) + self.pack_uint(prot) + self.pack_uint(port) + + def pack_pmaplist(self, list): + self.pack_list(list, self.pack_mapping) + + def pack_call_args(self, ca): + prog, vers, proc, args = ca + self.pack_uint(prog) + self.pack_uint(vers) + self.pack_uint(proc) + self.pack_opaque(args) + + +class PortMapperUnpacker(Unpacker): + + def unpack_mapping(self): + prog = self.unpack_uint() + vers = self.unpack_uint() + prot = self.unpack_uint() + port = self.unpack_uint() + return prog, vers, prot, port + + def unpack_pmaplist(self): + return self.unpack_list(self.unpack_mapping) + + def unpack_call_result(self): + port = self.unpack_uint() + res = self.unpack_opaque() + return port, res + + +class PartialPortMapperClient: + + def addpackers(self): + self.packer = PortMapperPacker() + self.unpacker = PortMapperUnpacker('') + + def Set(self, mapping): + return self.make_call(PMAPPROC_SET, mapping, \ + self.packer.pack_mapping, \ + self.unpacker.unpack_uint) + + def Unset(self, mapping): + return self.make_call(PMAPPROC_UNSET, mapping, \ + self.packer.pack_mapping, \ + self.unpacker.unpack_uint) + + def Getport(self, mapping): + return self.make_call(PMAPPROC_GETPORT, mapping, \ + self.packer.pack_mapping, \ + self.unpacker.unpack_uint) + + def Dump(self): + return self.make_call(PMAPPROC_DUMP, None, \ + None, \ + self.unpacker.unpack_pmaplist) + + def Callit(self, ca): + return self.make_call(PMAPPROC_CALLIT, ca, \ + self.packer.pack_call_args, \ + self.unpacker.unpack_call_result) + + +class TCPPortMapperClient(PartialPortMapperClient, RawTCPClient): + + def __init__(self, host): + RawTCPClient.__init__(self, \ + host, PMAP_PROG, PMAP_VERS, PMAP_PORT) + + +class UDPPortMapperClient(PartialPortMapperClient, RawUDPClient): + + def __init__(self, host): + RawUDPClient.__init__(self, \ + host, PMAP_PROG, PMAP_VERS, PMAP_PORT) + + +class BroadcastUDPPortMapperClient(PartialPortMapperClient, \ + RawBroadcastUDPClient): + + def __init__(self, bcastaddr): + RawBroadcastUDPClient.__init__(self, \ + bcastaddr, PMAP_PROG, PMAP_VERS, PMAP_PORT) + + +# Generic clients that find their server through the Port mapper + +class TCPClient(RawTCPClient): + + def __init__(self, host, prog, vers): + pmap = TCPPortMapperClient(host) + port = pmap.Getport((prog, vers, IPPROTO_TCP, 0)) + pmap.close() + if port == 0: + raise RuntimeError, 'program not registered' + RawTCPClient.__init__(self, host, prog, vers, port) + + +class UDPClient(RawUDPClient): + + def __init__(self, host, prog, vers): + pmap = UDPPortMapperClient(host) + port = pmap.Getport((prog, vers, IPPROTO_UDP, 0)) + pmap.close() + if port == 0: + raise RuntimeError, 'program not registered' + RawUDPClient.__init__(self, host, prog, vers, port) + + +class BroadcastUDPClient(Client): + + def __init__(self, bcastaddr, prog, vers): + self.pmap = BroadcastUDPPortMapperClient(bcastaddr) + self.pmap.set_reply_handler(self.my_reply_handler) + self.prog = prog + self.vers = vers + self.user_reply_handler = None + self.addpackers() + + def close(self): + self.pmap.close() + + def set_reply_handler(self, reply_handler): + self.user_reply_handler = reply_handler + + def set_timeout(self, timeout): + self.pmap.set_timeout(timeout) + + def my_reply_handler(self, reply, fromaddr): + port, res = reply + self.unpacker.reset(res) + result = self.unpack_func() + self.unpacker.done() + self.replies.append((result, fromaddr)) + if self.user_reply_handler is not None: + self.user_reply_handler(result, fromaddr) + + def make_call(self, proc, args, pack_func, unpack_func): + self.packer.reset() + if pack_func: + pack_func(args) + if unpack_func is None: + def dummy(): pass + self.unpack_func = dummy + else: + self.unpack_func = unpack_func + self.replies = [] + packed_args = self.packer.get_buf() + dummy_replies = self.pmap.Callit( \ + (self.prog, self.vers, proc, packed_args)) + return self.replies + + +# Server classes + +# These are not symmetric to the Client classes +# XXX No attempt is made to provide authorization hooks yet + +class Server: + + def __init__(self, host, prog, vers, port): + self.host = host # Should normally be '' for default interface + self.prog = prog + self.vers = vers + self.port = port # Should normally be 0 for random port + self.makesocket() # Assigns to self.sock and self.prot + self.bindsocket() + self.host, self.port = self.sock.getsockname() + self.addpackers() + + def register(self): + mapping = self.prog, self.vers, self.prot, self.port + p = TCPPortMapperClient(self.host) + if not p.Set(mapping): + raise RuntimeError, 'register failed' + + def unregister(self): + mapping = self.prog, self.vers, self.prot, self.port + p = TCPPortMapperClient(self.host) + if not p.Unset(mapping): + raise RuntimeError, 'unregister failed' + + def handle(self, call): + # Don't use unpack_header but parse the header piecewise + # XXX I have no idea if I am using the right error responses! + self.unpacker.reset(call) + self.packer.reset() + xid = self.unpacker.unpack_uint() + self.packer.pack_uint(xid) + temp = self.unpacker.unpack_enum() + if temp != CALL: + return None # Not worthy of a reply + self.packer.pack_uint(REPLY) + temp = self.unpacker.unpack_uint() + if temp != RPCVERSION: + self.packer.pack_uint(MSG_DENIED) + self.packer.pack_uint(RPC_MISMATCH) + self.packer.pack_uint(RPCVERSION) + self.packer.pack_uint(RPCVERSION) + return self.packer.get_buf() + self.packer.pack_uint(MSG_ACCEPTED) + self.packer.pack_auth((AUTH_NULL, make_auth_null())) + prog = self.unpacker.unpack_uint() + if prog != self.prog: + self.packer.pack_uint(PROG_UNAVAIL) + return self.packer.get_buf() + vers = self.unpacker.unpack_uint() + if vers != self.vers: + self.packer.pack_uint(PROG_MISMATCH) + self.packer.pack_uint(self.vers) + self.packer.pack_uint(self.vers) + return self.packer.get_buf() + proc = self.unpacker.unpack_uint() + methname = 'handle_' + repr(proc) + try: + meth = getattr(self, methname) + except AttributeError: + self.packer.pack_uint(PROC_UNAVAIL) + return self.packer.get_buf() + cred = self.unpacker.unpack_auth() + verf = self.unpacker.unpack_auth() + try: + meth() # Unpack args, call turn_around(), pack reply + except (EOFError, GarbageArgs): + # Too few or too many arguments + self.packer.reset() + self.packer.pack_uint(xid) + self.packer.pack_uint(REPLY) + self.packer.pack_uint(MSG_ACCEPTED) + self.packer.pack_auth((AUTH_NULL, make_auth_null())) + self.packer.pack_uint(GARBAGE_ARGS) + return self.packer.get_buf() + + def turn_around(self): + try: + self.unpacker.done() + except RuntimeError: + raise GarbageArgs + self.packer.pack_uint(SUCCESS) + + def handle_0(self): # Handle NULL message + self.turn_around() + + def makesocket(self): + # This MUST be overridden + raise RuntimeError, 'makesocket not defined' + + def bindsocket(self): + # Override this to bind to a different port (e.g. reserved) + self.sock.bind((self.host, self.port)) + + def addpackers(self): + # Override this to use derived classes from Packer/Unpacker + self.packer = Packer() + self.unpacker = Unpacker('') + + +class TCPServer(Server): + + def makesocket(self): + self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.prot = IPPROTO_TCP + + def loop(self): + self.sock.listen(0) + while 1: + self.session(self.sock.accept()) + + def session(self, connection): + sock, (host, port) = connection + while 1: + try: + call = recvrecord(sock) + except EOFError: + break + except socket.error, msg: + print 'socket error:', msg + break + reply = self.handle(call) + if reply is not None: + sendrecord(sock, reply) + + def forkingloop(self): + # Like loop but uses forksession() + self.sock.listen(0) + while 1: + self.forksession(self.sock.accept()) + + def forksession(self, connection): + # Like session but forks off a subprocess + import os + # Wait for deceased children + try: + while 1: + pid, sts = os.waitpid(0, 1) + except os.error: + pass + pid = None + try: + pid = os.fork() + if pid: # Parent + connection[0].close() + return + # Child + self.session(connection) + finally: + # Make sure we don't fall through in the parent + if pid == 0: + os._exit(0) + + +class UDPServer(Server): + + def makesocket(self): + self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self.prot = IPPROTO_UDP + + def loop(self): + while 1: + self.session() + + def session(self): + call, host_port = self.sock.recvfrom(8192) + reply = self.handle(call) + if reply != None: + self.sock.sendto(reply, host_port) + + +# Simple test program -- dump local portmapper status + +def test(): + pmap = UDPPortMapperClient('') + list = pmap.Dump() + list.sort() + for prog, vers, prot, port in list: + print prog, vers, + if prot == IPPROTO_TCP: print 'tcp', + elif prot == IPPROTO_UDP: print 'udp', + else: print prot, + print port + + +# Test program for broadcast operation -- dump everybody's portmapper status + +def testbcast(): + import sys + if sys.argv[1:]: + bcastaddr = sys.argv[1] + else: + bcastaddr = '<broadcast>' + def rh(reply, fromaddr): + host, port = fromaddr + print host + '\t' + repr(reply) + pmap = BroadcastUDPPortMapperClient(bcastaddr) + pmap.set_reply_handler(rh) + pmap.set_timeout(5) + replies = pmap.Getport((100002, 1, IPPROTO_UDP, 0)) + + +# Test program for server, with corresponding client +# On machine A: python -c 'import rpc; rpc.testsvr()' +# On machine B: python -c 'import rpc; rpc.testclt()' A +# (A may be == B) + +def testsvr(): + # Simple test class -- proc 1 doubles its string argument as reply + class S(UDPServer): + def handle_1(self): + arg = self.unpacker.unpack_string() + self.turn_around() + print 'RPC function 1 called, arg', repr(arg) + self.packer.pack_string(arg + arg) + # + s = S('', 0x20000000, 1, 0) + try: + s.unregister() + except RuntimeError, msg: + print 'RuntimeError:', msg, '(ignored)' + s.register() + print 'Service started...' + try: + s.loop() + finally: + s.unregister() + print 'Service interrupted.' + + +def testclt(): + import sys + if sys.argv[1:]: host = sys.argv[1] + else: host = '' + # Client for above server + class C(UDPClient): + def call_1(self, arg): + return self.make_call(1, arg, \ + self.packer.pack_string, \ + self.unpacker.unpack_string) + c = C(host, 0x20000000, 1) + print 'making call...' + reply = c.call_1('hello, world, ') + print 'call returned', repr(reply) diff --git a/sys/src/cmd/python/Demo/rpc/test b/sys/src/cmd/python/Demo/rpc/test new file mode 100755 index 000000000..ba220f24a --- /dev/null +++ b/sys/src/cmd/python/Demo/rpc/test @@ -0,0 +1,24 @@ +: ${PYTHON=python} +: ${SERVER=charon.cwi.nl} + +set -xe + +$PYTHON -c 'from rpc import test; test()' +$PYTHON -c 'from rpc import test; test()' ${SERVER} + +$PYTHON -c 'from rpc import testsvr; testsvr()' & +PID=$! +sleep 2 +$PYTHON -c 'from rpc import testclt; testclt()' +kill -2 $PID + +$PYTHON -c 'from mountclient import test; test()' +$PYTHON -c 'from mountclient import test; test()' gatekeeper.dec.com + +$PYTHON -c 'from nfsclient import test; test()' +$PYTHON -c 'from nfsclient import test; test()' gatekeeper.dec.com +$PYTHON -c 'from nfsclient import test; test()' gatekeeper.dec.com /archive + +$PYTHON -c 'from rnusersclient import test; test()' '' + +$PYTHON -c 'from rpc import testbcast; testbcast()' diff --git a/sys/src/cmd/python/Demo/rpc/xdr.py b/sys/src/cmd/python/Demo/rpc/xdr.py new file mode 100644 index 000000000..df5cbafd0 --- /dev/null +++ b/sys/src/cmd/python/Demo/rpc/xdr.py @@ -0,0 +1,200 @@ +# Implement (a subset of) Sun XDR -- RFC1014. + + +try: + import struct +except ImportError: + struct = None + + +Long = type(0L) + + +class Packer: + + def __init__(self): + self.reset() + + def reset(self): + self.buf = '' + + def get_buf(self): + return self.buf + + def pack_uint(self, x): + self.buf = self.buf + \ + (chr(int(x>>24 & 0xff)) + chr(int(x>>16 & 0xff)) + \ + chr(int(x>>8 & 0xff)) + chr(int(x & 0xff))) + if struct and struct.pack('l', 1) == '\0\0\0\1': + def pack_uint(self, x): + if type(x) == Long: + x = int((x + 0x80000000L) % 0x100000000L \ + - 0x80000000L) + self.buf = self.buf + struct.pack('l', x) + + pack_int = pack_uint + + pack_enum = pack_int + + def pack_bool(self, x): + if x: self.buf = self.buf + '\0\0\0\1' + else: self.buf = self.buf + '\0\0\0\0' + + def pack_uhyper(self, x): + self.pack_uint(int(x>>32 & 0xffffffff)) + self.pack_uint(int(x & 0xffffffff)) + + pack_hyper = pack_uhyper + + def pack_float(self, x): + # XXX + self.buf = self.buf + struct.pack('f', x) + + def pack_double(self, x): + # XXX + self.buf = self.buf + struct.pack('d', x) + + def pack_fstring(self, n, s): + if n < 0: + raise ValueError, 'fstring size must be nonnegative' + n = ((n+3)/4)*4 + data = s[:n] + data = data + (n - len(data)) * '\0' + self.buf = self.buf + data + + pack_fopaque = pack_fstring + + def pack_string(self, s): + n = len(s) + self.pack_uint(n) + self.pack_fstring(n, s) + + pack_opaque = pack_string + + def pack_list(self, list, pack_item): + for item in list: + self.pack_uint(1) + pack_item(item) + self.pack_uint(0) + + def pack_farray(self, n, list, pack_item): + if len(list) <> n: + raise ValueError, 'wrong array size' + for item in list: + pack_item(item) + + def pack_array(self, list, pack_item): + n = len(list) + self.pack_uint(n) + self.pack_farray(n, list, pack_item) + + +class Unpacker: + + def __init__(self, data): + self.reset(data) + + def reset(self, data): + self.buf = data + self.pos = 0 + + def done(self): + if self.pos < len(self.buf): + raise RuntimeError, 'unextracted data remains' + + def unpack_uint(self): + i = self.pos + self.pos = j = i+4 + data = self.buf[i:j] + if len(data) < 4: + raise EOFError + x = long(ord(data[0]))<<24 | ord(data[1])<<16 | \ + ord(data[2])<<8 | ord(data[3]) + # Return a Python long only if the value is not representable + # as a nonnegative Python int + if x < 0x80000000L: x = int(x) + return x + if struct and struct.unpack('l', '\0\0\0\1') == 1: + def unpack_uint(self): + i = self.pos + self.pos = j = i+4 + data = self.buf[i:j] + if len(data) < 4: + raise EOFError + return struct.unpack('l', data) + + def unpack_int(self): + x = self.unpack_uint() + if x >= 0x80000000L: x = x - 0x100000000L + return int(x) + + unpack_enum = unpack_int + + unpack_bool = unpack_int + + def unpack_uhyper(self): + hi = self.unpack_uint() + lo = self.unpack_uint() + return long(hi)<<32 | lo + + def unpack_hyper(self): + x = self.unpack_uhyper() + if x >= 0x8000000000000000L: x = x - 0x10000000000000000L + return x + + def unpack_float(self): + # XXX + i = self.pos + self.pos = j = i+4 + data = self.buf[i:j] + if len(data) < 4: + raise EOFError + return struct.unpack('f', data)[0] + + def unpack_double(self): + # XXX + i = self.pos + self.pos = j = i+8 + data = self.buf[i:j] + if len(data) < 8: + raise EOFError + return struct.unpack('d', data)[0] + + def unpack_fstring(self, n): + if n < 0: + raise ValueError, 'fstring size must be nonnegative' + i = self.pos + j = i + (n+3)/4*4 + if j > len(self.buf): + raise EOFError + self.pos = j + return self.buf[i:i+n] + + unpack_fopaque = unpack_fstring + + def unpack_string(self): + n = self.unpack_uint() + return self.unpack_fstring(n) + + unpack_opaque = unpack_string + + def unpack_list(self, unpack_item): + list = [] + while 1: + x = self.unpack_uint() + if x == 0: break + if x <> 1: + raise RuntimeError, '0 or 1 expected, got %r' % (x, ) + item = unpack_item() + list.append(item) + return list + + def unpack_farray(self, n, unpack_item): + list = [] + for i in range(n): + list.append(unpack_item()) + return list + + def unpack_array(self, unpack_item): + n = self.unpack_uint() + return self.unpack_farray(n, unpack_item) diff --git a/sys/src/cmd/python/Demo/scripts/README b/sys/src/cmd/python/Demo/scripts/README new file mode 100644 index 000000000..d8434e8d4 --- /dev/null +++ b/sys/src/cmd/python/Demo/scripts/README @@ -0,0 +1,23 @@ +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 +ftpstats.py Summarize ftp daemon log file +lpwatch.py Watch BSD line printer queues +makedir.py Like mkdir -p +markov.py Markov chain simulation of words or characters +mboxconvvert.py Convert MH or MMDF mailboxes to unix mailbox format +mkrcs.py Fix symlinks named RCS into parallel tree +morse.py Produce morse code (audible or on AIFF file) +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. diff --git a/sys/src/cmd/python/Demo/scripts/beer.py b/sys/src/cmd/python/Demo/scripts/beer.py new file mode 100644 index 000000000..1b9ac8bad --- /dev/null +++ b/sys/src/cmd/python/Demo/scripts/beer.py @@ -0,0 +1,14 @@ +#! /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): + print bottle(n-i), "on the wall," + print bottle(n-i) + "." + print "Take one down, pass it around," + print bottle(n-i-1), "on the wall." diff --git a/sys/src/cmd/python/Demo/scripts/eqfix.py b/sys/src/cmd/python/Demo/scripts/eqfix.py new file mode 100755 index 000000000..35c43aa04 --- /dev/null +++ b/sys/src/cmd/python/Demo/scripts/eqfix.py @@ -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() diff --git a/sys/src/cmd/python/Demo/scripts/fact.py b/sys/src/cmd/python/Demo/scripts/fact.py new file mode 100755 index 000000000..03cab8bb8 --- /dev/null +++ b/sys/src/cmd/python/Demo/scripts/fact.py @@ -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 + +error = 'fact.error' # exception + +def fact(n): + if n < 1: raise error # fact() argument should be >= 1 + if n == 1: return [] # special case + res = [] + # Treat even factors special, so we can use i = i+2 later + while n%2 == 0: + res.append(2) + n = n/2 + # Try odd numbers up to sqrt(n) + limit = sqrt(float(n+1)) + i = 3 + while i <= limit: + if n%i == 0: + res.append(i) + n = n/i + limit = sqrt(n+1) + else: + i = i+2 + if n != 1: + res.append(n) + return res + +def main(): + if len(sys.argv) > 1: + for arg in sys.argv[1:]: + n = eval(arg) + print n, fact(n) + else: + try: + while 1: + n = input() + print n, fact(n) + except EOFError: + pass + +if __name__ == "__main__": + main() diff --git a/sys/src/cmd/python/Demo/scripts/find-uname.py b/sys/src/cmd/python/Demo/scripts/find-uname.py new file mode 100644 index 000000000..b76b9f0fe --- /dev/null +++ b/sys/src/cmd/python/Demo/scripts/find-uname.py @@ -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 = [(x,y) for (x,y) in unicode_names + if pat.search(y) is not None] + if matches: + print "***", arg, "matches", "***" + for (x,y) in matches: + print "%s (%d)" % (y,x) + +if __name__ == "__main__": + main(sys.argv[1:]) diff --git a/sys/src/cmd/python/Demo/scripts/from.py b/sys/src/cmd/python/Demo/scripts/from.py new file mode 100755 index 000000000..3c04fcd49 --- /dev/null +++ b/sys/src/cmd/python/Demo/scripts/from.py @@ -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 diff --git a/sys/src/cmd/python/Demo/scripts/ftpstats.py b/sys/src/cmd/python/Demo/scripts/ftpstats.py new file mode 100755 index 000000000..5c1599e66 --- /dev/null +++ b/sys/src/cmd/python/Demo/scripts/ftpstats.py @@ -0,0 +1,145 @@ +#! /usr/bin/env python + +# Extract statistics from ftp daemon log. + +# Usage: +# ftpstats [-m maxitems] [-s search] [file] +# -m maxitems: restrict number of items in "top-N" lists, default 25. +# -s string: restrict statistics to lines containing this string. +# Default file is /usr/adm/ftpd; a "-" means read standard input. + +# The script must be run on the host where the ftp daemon runs. +# (At CWI this is currently buizerd.) + +import os +import sys +import re +import string +import getopt + +pat = '^([a-zA-Z0-9 :]*)!(.*)!(.*)!([<>].*)!([0-9]+)!([0-9]+)$' +prog = re.compile(pat) + +def main(): + maxitems = 25 + search = None + try: + opts, args = getopt.getopt(sys.argv[1:], 'm:s:') + except getopt.error, msg: + print msg + print 'usage: ftpstats [-m maxitems] [file]' + sys.exit(2) + for o, a in opts: + if o == '-m': + maxitems = string.atoi(a) + if o == '-s': + search = a + file = '/usr/adm/ftpd' + if args: file = args[0] + if file == '-': + f = sys.stdin + else: + try: + f = open(file, 'r') + except IOError, msg: + print file, ':', msg + sys.exit(1) + bydate = {} + bytime = {} + byfile = {} + bydir = {} + byhost = {} + byuser = {} + bytype = {} + lineno = 0 + try: + while 1: + line = f.readline() + if not line: break + lineno = lineno + 1 + if search and string.find(line, search) < 0: + continue + if prog.match(line) < 0: + print 'Bad line', lineno, ':', repr(line) + continue + items = prog.group(1, 2, 3, 4, 5, 6) + (logtime, loguser, loghost, logfile, logbytes, + logxxx2) = items +## print logtime +## print '-->', loguser +## print '--> -->', loghost +## print '--> --> -->', logfile +## print '--> --> --> -->', logbytes +## print '--> --> --> --> -->', logxxx2 +## for i in logtime, loghost, logbytes, logxxx2: +## if '!' in i: print '???', i + add(bydate, logtime[-4:] + ' ' + logtime[:6], items) + add(bytime, logtime[7:9] + ':00-59', items) + direction, logfile = logfile[0], logfile[1:] + # The real path probably starts at the last //... + while 1: + i = string.find(logfile, '//') + if i < 0: break + logfile = logfile[i+1:] + add(byfile, logfile + ' ' + direction, items) + logdir = os.path.dirname(logfile) +## logdir = os.path.normpath(logdir) + '/.' + while 1: + add(bydir, logdir + ' ' + direction, items) + dirhead = os.path.dirname(logdir) + if dirhead == logdir: break + logdir = dirhead + add(byhost, loghost, items) + add(byuser, loguser, items) + add(bytype, direction, items) + except KeyboardInterrupt: + print 'Interrupted at line', lineno + show(bytype, 'by transfer direction', maxitems) + show(bydir, 'by directory', maxitems) + show(byfile, 'by file', maxitems) + show(byhost, 'by host', maxitems) + show(byuser, 'by user', maxitems) + showbar(bydate, 'by date') + showbar(bytime, 'by time of day') + +def showbar(dict, title): + n = len(title) + print '='*((70-n)/2), title, '='*((71-n)/2) + list = [] + keys = dict.keys() + keys.sort() + for key in keys: + n = len(str(key)) + list.append((len(dict[key]), key)) + maxkeylength = 0 + maxcount = 0 + for count, key in list: + maxkeylength = max(maxkeylength, len(key)) + maxcount = max(maxcount, count) + maxbarlength = 72 - maxkeylength - 7 + for count, key in list: + barlength = int(round(maxbarlength*float(count)/maxcount)) + bar = '*'*barlength + print '%5d %-*s %s' % (count, maxkeylength, key, bar) + +def show(dict, title, maxitems): + if len(dict) > maxitems: + title = title + ' (first %d)'%maxitems + n = len(title) + print '='*((70-n)/2), title, '='*((71-n)/2) + list = [] + keys = dict.keys() + for key in keys: + list.append((-len(dict[key]), key)) + list.sort() + for count, key in list[:maxitems]: + print '%5d %s' % (-count, key) + +def add(dict, key, item): + if dict.has_key(key): + dict[key].append(item) + else: + dict[key] = [item] + +if __name__ == "__main__": + main() diff --git a/sys/src/cmd/python/Demo/scripts/lpwatch.py b/sys/src/cmd/python/Demo/scripts/lpwatch.py new file mode 100755 index 000000000..8887dee7d --- /dev/null +++ b/sys/src/cmd/python/Demo/scripts/lpwatch.py @@ -0,0 +1,110 @@ +#! /usr/bin/env python + +# Watch line printer queue(s). +# Intended for BSD 4.3 lpq. + +import posix +import sys +import time +import string + +DEF_PRINTER = 'psc' +DEF_DELAY = 10 + +def main(): + delay = DEF_DELAY # XXX Use getopt() later + try: + thisuser = posix.environ['LOGNAME'] + except: + thisuser = posix.environ['USER'] + printers = sys.argv[1:] + if printers: + # Strip '-P' from printer names just in case + # the user specified it... + for i in range(len(printers)): + if printers[i][:2] == '-P': + printers[i] = printers[i][2:] + else: + if posix.environ.has_key('PRINTER'): + printers = [posix.environ['PRINTER']] + else: + printers = [DEF_PRINTER] + # + clearhome = posix.popen('clear', 'r').read() + # + while 1: + text = clearhome + for name in printers: + text = text + makestatus(name, thisuser) + '\n' + print text + time.sleep(delay) + +def makestatus(name, thisuser): + pipe = posix.popen('lpq -P' + name + ' 2>&1', 'r') + lines = [] + users = {} + aheadbytes = 0 + aheadjobs = 0 + userseen = 0 + totalbytes = 0 + totaljobs = 0 + while 1: + line = pipe.readline() + if not line: break + fields = string.split(line) + n = len(fields) + if len(fields) >= 6 and fields[n-1] == 'bytes': + rank = fields[0] + user = fields[1] + job = fields[2] + files = fields[3:-2] + bytes = eval(fields[n-2]) + if user == thisuser: + userseen = 1 + elif not userseen: + aheadbytes = aheadbytes + bytes + aheadjobs = aheadjobs + 1 + totalbytes = totalbytes + bytes + totaljobs = totaljobs + 1 + if users.has_key(user): + ujobs, ubytes = users[user] + else: + ujobs, ubytes = 0, 0 + ujobs = ujobs + 1 + ubytes = ubytes + bytes + users[user] = ujobs, ubytes + else: + if fields and fields[0] <> 'Rank': + line = string.strip(line) + 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 = line + ' (%d jobs)' % totaljobs + if len(users) == 1: + line = line + ' for %s' % (users.keys()[0],) + else: + line = line + ' for %d users' % len(users) + if userseen: + if aheadjobs == 0: + line = line + ' (%s first)' % thisuser + else: + line = 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 string.joinfields(lines, ': ') + +if __name__ == "__main__": + try: + main() + except KeyboardInterrupt: + pass diff --git a/sys/src/cmd/python/Demo/scripts/makedir.py b/sys/src/cmd/python/Demo/scripts/makedir.py new file mode 100755 index 000000000..f70facd08 --- /dev/null +++ b/sys/src/cmd/python/Demo/scripts/makedir.py @@ -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() diff --git a/sys/src/cmd/python/Demo/scripts/markov.py b/sys/src/cmd/python/Demo/scripts/markov.py new file mode 100755 index 000000000..bddec5693 --- /dev/null +++ b/sys/src/cmd/python/Demo/scripts/markov.py @@ -0,0 +1,117 @@ +#! /usr/bin/env python + +class Markov: + def __init__(self, histsize, choice): + self.histsize = histsize + self.choice = choice + self.trans = {} + def add(self, state, next): + if not self.trans.has_key(state): + self.trans[state] = [next] + else: + self.trans[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 1: + subseq = seq[max(0, len(seq)-n):] + options = trans[subseq] + next = choice(options) + if not next: break + seq = seq + next + return seq + +def test(): + import sys, string, random, getopt + args = sys.argv[1:] + try: + opts, args = getopt.getopt(args, '0123456789cdw') + except getopt.error: + print 'Usage: markov [-#] [-cddqw] [file] ...' + 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.' + histsize = 2 + do_words = 0 + debug = 1 + for o, a in opts: + if '-0' <= o <= '-9': histsize = eval(o[1:]) + if o == '-c': do_words = 0 + if o == '-d': debug = debug + 1 + if o == '-q': debug = 0 + if o == '-w': do_words = 1 + 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 = string.splitfields(text, '\n\n') + for para in paralist: + if debug > 1: print 'feeding ...' + words = string.split(para) + if words: + if do_words: data = tuple(words) + else: data = string.joinfields(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 1: + data = m.get() + if do_words: words = data + else: words = string.split(data) + n = 0 + limit = 72 + for w in words: + if n + len(w) > limit: + print + n = 0 + print w, + n = n + len(w) + 1 + print + print + +def tuple(list): + if len(list) == 0: return () + if len(list) == 1: return (list[0],) + i = len(list)/2 + return tuple(list[:i]) + tuple(list[i:]) + +if __name__ == "__main__": + test() diff --git a/sys/src/cmd/python/Demo/scripts/mboxconvert.py b/sys/src/cmd/python/Demo/scripts/mboxconvert.py new file mode 100755 index 000000000..8c462f3b1 --- /dev/null +++ b/sys/src/cmd/python/Demo/scripts/mboxconvert.py @@ -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() diff --git a/sys/src/cmd/python/Demo/scripts/mkrcs.py b/sys/src/cmd/python/Demo/scripts/mkrcs.py new file mode 100755 index 000000000..cacdda0a5 --- /dev/null +++ b/sys/src/cmd/python/Demo/scripts/mkrcs.py @@ -0,0 +1,61 @@ +#! /usr/bin/env python + +# A rather specialized script to make sure that a symbolic link named +# RCS exists pointing to a real RCS directory in a parallel tree +# referenced as RCStree in an ancestor directory. +# (I use this because I like my RCS files to reside on a physically +# different machine). + +import os + +def main(): + rcstree = 'RCStree' + rcs = 'RCS' + if os.path.islink(rcs): + print '%r is a symlink to %r' % (rcs, os.readlink(rcs)) + return + if os.path.isdir(rcs): + print '%r is an ordinary directory' % (rcs,) + return + if os.path.exists(rcs): + print '%r is a file?!?!' % (rcs,) + return + # + p = os.getcwd() + up = '' + down = '' + # Invariants: + # (1) join(p, down) is the current directory + # (2) up is the same directory as p + # Ergo: + # (3) join(up, down) is the current directory + #print 'p =', repr(p) + while not os.path.isdir(os.path.join(p, rcstree)): + head, tail = os.path.split(p) + #print 'head = %r; tail = %r' % (head, tail) + if not tail: + print 'Sorry, no ancestor dir contains %r' % (rcstree,) + return + p = head + up = os.path.join(os.pardir, up) + down = os.path.join(tail, down) + #print 'p = %r; up = %r; down = %r' % (p, up, down) + there = os.path.join(up, rcstree) + there = os.path.join(there, down) + there = os.path.join(there, rcs) + if os.path.isdir(there): + print '%r already exists' % (there, ) + else: + print 'making %r' % (there,) + makedirs(there) + print 'making symlink %r -> %r' % (rcs, there) + os.symlink(there, rcs) + +def makedirs(p): + if not os.path.isdir(p): + head, tail = os.path.split(p) + makedirs(head) + os.mkdir(p, 0777) + +if __name__ == "__main__": + main() diff --git a/sys/src/cmd/python/Demo/scripts/morse.py b/sys/src/cmd/python/Demo/scripts/morse.py new file mode 100755 index 000000000..3da49da9e --- /dev/null +++ b/sys/src/cmd/python/Demo/scripts/morse.py @@ -0,0 +1,149 @@ +# 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': '----.', + ',': '--..--', + '.': '.-.-.-', + '?': '..--..', + ';': '-.-.-.', + ':': '---...', + "'": '.----.', + '-': '-....-', + '/': '-..-.', + '(': '-.--.-', + ')': '-.--.-', + '_': '..--.-', + ' ': ' ' +} + +# 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): + global sinewave, nowave + sinewave = '' + for i in range(100): + val = int(math.sin(math.pi * float(i) * octave / 50.0) * 30000) + sinewave = sinewave + chr((val >> 8) & 255) + chr(val & 255) + nowave = '\0' * 200 + +mkwave(OCTAVE) + +def main(): + import getopt, string + try: + opts, args = getopt.getopt(sys.argv[1:], 'o:p:') + except getopt.error: + sys.stderr.write('Usage ' + sys.argv[0] + + ' [ -o outfile ] [ args ] ...\n') + sys.exit(1) + dev = None + 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': + mkwave(string.atoi(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: + line = string.join(args) + else: + line = sys.stdin.readline() + while line: + mline = morse(line) + play(mline, dev) + if hasattr(dev, 'wait'): + dev.wait() + if not args: + line = sys.stdin.readline() + else: + line = '' + 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 = res + morsetab[c] + '\001' + except KeyError: + pass + return res + +# Play a line of morse code. +def play(line, dev): + for c in line: + if c == '.': + sine(dev, DOT) + elif c == '-': + sine(dev, DAH) + else: # space + pause(dev, DAH + DOT) + pause(dev, DOT) + +def sine(dev, length): + for i in range(length): + dev.writeframesraw(sinewave) + +def pause(dev, length): + for i in range(length): + dev.writeframesraw(nowave) + +if __name__ == '__main__' or sys.argv[0] == __name__: + main() diff --git a/sys/src/cmd/python/Demo/scripts/newslist.doc b/sys/src/cmd/python/Demo/scripts/newslist.doc new file mode 100755 index 000000000..87fd9ba27 --- /dev/null +++ b/sys/src/cmd/python/Demo/scripts/newslist.doc @@ -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 + ---------------------------------------------------------------------- diff --git a/sys/src/cmd/python/Demo/scripts/newslist.py b/sys/src/cmd/python/Demo/scripts/newslist.py new file mode 100755 index 000000000..35ba48a48 --- /dev/null +++ b/sys/src/cmd/python/Demo/scripts/newslist.py @@ -0,0 +1,366 @@ +#! /usr/bin/env python +####################################################################### +# Newslist $Revision: 37320 $ +# +# 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, string, marshal, time, os, posix, string + +####################################################################### +# Check these variables before running! # + +# Top directory. +# Filenames which don't start with / are taken as being relative to this. +topdir='/anfs/qsbigdisc/web/html/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 = posix.environ['NNTPSERVER'] +newshost = 'nntp-serv.cl.cam.ac.uk' + +# 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: 37320 $' +rcsrev = string.join(filter(lambda s: '$' not in s, string.split(rcsrev))) +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 = string.splitfields(i,'.') + 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 not tree.has_key(j): + 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 + f = open(filename,'w') + # f.write('Content-Type: text/html\n') + f.write('<TITLE>Newsgroups available' + detail + '</TITLE>\n') + f.write('<H1>Newsgroups available' + detail +'</H1>\n') + f.write('<A HREF="'+httppref+rootpage+'.html">Back to top level</A><P>\n') + printtree(f,tree,0,p) + f.write('<I>This page automatically created by \'newslist\' v. '+rcsrev+'.') + f.write(time.ctime(time.time()) + '</I><P>') + f.close() + +# 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): + global desc + l = len(tree) + + if l > sublistsize and indent>0: + # Create a new page and a link to it + f.write('<LI><B><A HREF="'+httppref+p[1:]+'.html">') + f.write(p[1:]+'.*') + f.write('</A></B>'+pagelinkicon+'\n') + createpage(p[1:], tree, p) + return + + kl = tree.keys() + + if l > 1: + kl.sort() + if indent > 0: + # Create a sub-list + f.write('<LI>'+p[1:]+'\n<UL>') + 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:' + p[1:] + '">'+ p[1:] + '</A> ') + if desc.has_key(p[1:]): + f.write(' <I>'+desc[p[1:]]+'</I>\n') + 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 an array mapping group name to its description + +def readdesc(descfile): + global desc + + desc = {} + + if descfile == '': + return + + try: + d = open(descfile, 'r') + print 'Reading descriptions...' + except (IOError): + print 'Failed to open description file ' + descfile + return + l = d.readline() + while l != '': + bits = string.split(l) + try: + grp = bits[0] + dsc = string.join(bits[1:]) + if len(dsc)>1: + desc[grp] = dsc + except (IndexError): + pass + l = d.readline() + +# Check that ouput directory exists, ------------------------------ +# and offer to create it if not + +def checkopdir(pagedir): + if not os.path.isdir(pagedir): + print 'Directory '+pagedir+' does not exist.' + 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: + dump = open(treefile,'r') + tree = marshal.load(dump) + dump.close() + except (IOError): + print 'Cannot open local group list ' + treefile + return (tree, treedate) + +def writelocallist(treefile, tree): + try: + dump = open(treefile,'w') + groups = marshal.dump(tree,dump) + dump.close() + print 'Saved list to '+treefile+'\n' + 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 = string.split(i[0])[0] + if skipempty and string.atoi(i[1]) < string.atoi(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 '+treedate+'...', + info = server.newgroups(treedate,'000001')[1] + print 'got %d.' % len(info) + print 'Processing...', + groups = [] + for i in info: + grpname = string.split(i)[0] + groups.append(grpname) + print 'Done' + return groups + +# Now the main program -------------------------------------------- + +def main(): + global desc + + tree={} + + # Check that the output directory exists + checkopdir(pagedir); + + try: + print 'Connecting to '+newshost+'...' + if sys.version[0] == '0': + s = NNTP.init(newshost) + else: + s = NNTP(newshost) + connected = 1 + 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 = 0 + + # 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 +###################################################################### diff --git a/sys/src/cmd/python/Demo/scripts/pi.py b/sys/src/cmd/python/Demo/scripts/pi.py new file mode 100755 index 000000000..9b242451a --- /dev/null +++ b/sys/src/cmd/python/Demo/scripts/pi.py @@ -0,0 +1,34 @@ +#! /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 = 2L, 4L, 1L, 12L, 4L + while 1: + # Next approximation + p, q, k = k*k, 2L*k+1L, k+1L + a, b, a1, b1 = a1, b1, p*a+q*a1, p*b+q*b1 + # Print common digits + d, d1 = a/b, a1/b1 + while d == d1: + output(d) + a, a1 = 10L*(a%b), 10L*(a1%b1) + d, d1 = a/b, a1/b1 + +def output(d): + # Use write() to avoid spaces between the digits + # Use str() to avoid the 'L' + sys.stdout.write(str(d)) + # Flush so the output is seen immediately + sys.stdout.flush() + +if __name__ == "__main__": + main() diff --git a/sys/src/cmd/python/Demo/scripts/pp.py b/sys/src/cmd/python/Demo/scripts/pp.py new file mode 100755 index 000000000..0491fa9bf --- /dev/null +++ b/sys/src/cmd/python/Demo/scripts/pp.py @@ -0,0 +1,130 @@ +#! /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 string +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(sys.argv[0] + ': ' + msg + '\n') + 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 string.splitfields(optarg, '\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 = string.splitfields(L, FS)', \ + ' \t \t \telse: F = string.split(L)' \ + ] + epilogue = [ \ + ' \t \tif not PFLAG: continue', \ + ' \t \tif aflag:', \ + ' \t \t \tif FS: print string.joinfields(F, FS)', \ + ' \t \t \telse: print string.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 = string.joinfields(prologue, '\n') + '\n' +for line in SCRIPT: + program = program + (' \t \t' + line + '\n') +program = program + (string.joinfields(epilogue, '\n') + '\n') + +import tempfile +fp = tempfile.NamedTemporaryFile() +fp.write(program) +fp.flush() +if DFLAG: + import pdb + pdb.run('execfile(%r)' % (tfn,)) +else: + execfile(tfn) diff --git a/sys/src/cmd/python/Demo/scripts/primes.py b/sys/src/cmd/python/Demo/scripts/primes.py new file mode 100755 index 000000000..5935a3c84 --- /dev/null +++ b/sys/src/cmd/python/Demo/scripts/primes.py @@ -0,0 +1,27 @@ +#! /usr/bin/env python + +# Print prime numbers in a given range + +def main(): + import sys + min, max = 2, 0x7fffffff + if sys.argv[1:]: + min = int(eval(sys.argv[1])) + if sys.argv[2:]: + max = int(eval(sys.argv[2])) + primes(min, max) + +def primes(min, max): + if 2 >= min: print 2 + primes = [2] + i = 3 + while i <= max: + for p in primes: + if i%p == 0 or p*p > i: break + if i%p <> 0: + primes.append(i) + if i >= min: print i + i = i+2 + +if __name__ == "__main__": + main() diff --git a/sys/src/cmd/python/Demo/scripts/queens.py b/sys/src/cmd/python/Demo/scripts/queens.py new file mode 100755 index 000000000..74756be7d --- /dev/null +++ b/sys/src/cmd/python/Demo/scripts/queens.py @@ -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 set, 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() diff --git a/sys/src/cmd/python/Demo/scripts/script.py b/sys/src/cmd/python/Demo/scripts/script.py new file mode 100755 index 000000000..6eaa7aec2 --- /dev/null +++ b/sys/src/cmd/python/Demo/scripts/script.py @@ -0,0 +1,33 @@ +#! /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 +import pty + +def read(fd): + data = os.read(fd, 1024) + file.write(data) + return data + +shell = 'sh' +filename = 'typescript' +mode = 'w' +if os.environ.has_key('SHELL'): + shell = os.environ['SHELL'] +if '-a' in sys.argv: + mode = 'a' +if '-p' in sys.argv: + shell = 'python' + +file = open(filename, mode) + +sys.stdout.write('Script started, file is %s\n' % filename) +file.write('Script started on %s\n' % time.ctime(time.time())) +pty.spawn(shell, read) +file.write('Script done on %s\n' % time.ctime(time.time())) +sys.stdout.write('Script done, file is %s\n' % filename) diff --git a/sys/src/cmd/python/Demo/scripts/unbirthday.py b/sys/src/cmd/python/Demo/scripts/unbirthday.py new file mode 100755 index 000000000..2d0b8e5f4 --- /dev/null +++ b/sys/src/cmd/python/Demo/scripts/unbirthday.py @@ -0,0 +1,107 @@ +#! /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(): + # Note that the range checks below also check for bad types, + # e.g. 3.14 or (). However syntactically invalid replies + # will raise an exception. + 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<=2002): + 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)): + # Januari 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() diff --git a/sys/src/cmd/python/Demo/scripts/update.py b/sys/src/cmd/python/Demo/scripts/update.py new file mode 100755 index 000000000..c9360260e --- /dev/null +++ b/sys/src/cmd/python/Demo/scripts/update.py @@ -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() diff --git a/sys/src/cmd/python/Demo/scripts/wh.py b/sys/src/cmd/python/Demo/scripts/wh.py new file mode 100755 index 000000000..b9b09efa6 --- /dev/null +++ b/sys/src/cmd/python/Demo/scripts/wh.py @@ -0,0 +1,2 @@ +# This is here so I can use 'wh' instead of 'which' in '~/bin/generic_python' +import which diff --git a/sys/src/cmd/python/Demo/sockets/README b/sys/src/cmd/python/Demo/sockets/README new file mode 100644 index 000000000..f5405abd0 --- /dev/null +++ b/sys/src/cmd/python/Demo/sockets/README @@ -0,0 +1,21 @@ +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. +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. + +The following file is only relevant on SGI machines (or other systems +that support multicast): + +mcast.py A Python translation of + /usr/people/4Dgifts/examples/network/mcast.c + (Note that IN.py is in ../../lib/sgi.) + diff --git a/sys/src/cmd/python/Demo/sockets/broadcast.py b/sys/src/cmd/python/Demo/sockets/broadcast.py new file mode 100755 index 000000000..6d2b1e813 --- /dev/null +++ b/sys/src/cmd/python/Demo/sockets/broadcast.py @@ -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) diff --git a/sys/src/cmd/python/Demo/sockets/echosvr.py b/sys/src/cmd/python/Demo/sockets/echosvr.py new file mode 100755 index 000000000..f8a9623d7 --- /dev/null +++ b/sys/src/cmd/python/Demo/sockets/echosvr.py @@ -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() diff --git a/sys/src/cmd/python/Demo/sockets/finger.py b/sys/src/cmd/python/Demo/sockets/finger.py new file mode 100755 index 000000000..e8b9ed2b0 --- /dev/null +++ b/sys/src/cmd/python/Demo/sockets/finger.py @@ -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() diff --git a/sys/src/cmd/python/Demo/sockets/ftp.py b/sys/src/cmd/python/Demo/sockets/ftp.py new file mode 100755 index 000000000..6e9282a59 --- /dev/null +++ b/sys/src/cmd/python/Demo/sockets/ftp.py @@ -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() diff --git a/sys/src/cmd/python/Demo/sockets/gopher.py b/sys/src/cmd/python/Demo/sockets/gopher.py new file mode 100755 index 000000000..cd7665936 --- /dev/null +++ b/sys/src/cmd/python/Demo/sockets/gopher.py @@ -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() diff --git a/sys/src/cmd/python/Demo/sockets/mcast.py b/sys/src/cmd/python/Demo/sockets/mcast.py new file mode 100755 index 000000000..1abd30563 --- /dev/null +++ b/sys/src/cmd/python/Demo/sockets/mcast.py @@ -0,0 +1,93 @@ +# Send/receive UDP multicast packets. +# Requires that your OS kernel supports IP multicast. +# This is built-in on SGI, still optional for most other vendors. +# +# Usage: +# mcast -s (sender) +# mcast -b (sender, using broadcast instead multicast) +# mcast (receivers) + +MYPORT = 8123 +MYGROUP = '225.0.0.250' + +import sys +import time +import struct +from socket import * + + +# Main program +def main(): + flags = sys.argv[1:] + # + if flags: + sender(flags[0]) + else: + receiver() + + +# Sender subroutine (only one per local area network) +def sender(flag): + s = socket(AF_INET, SOCK_DGRAM) + if flag == '-b': + s.setsockopt(SOL_SOCKET, SO_BROADCAST, 1) + mygroup = '<broadcast>' + else: + mygroup = MYGROUP + ttl = struct.pack('b', 1) # Time-to-live + s.setsockopt(IPPROTO_IP, IP_MULTICAST_TTL, ttl) + while 1: + data = repr(time.time()) +## data = data + (1400 - len(data)) * '\0' + s.sendto(data, (mygroup, MYPORT)) + time.sleep(1) + + +# Receiver subroutine (as many as you like) +def receiver(): + # Open and initialize the socket + s = openmcastsock(MYGROUP, MYPORT) + # + # Loop, printing any data we receive + while 1: + data, sender = s.recvfrom(1500) + while data[-1:] == '\0': data = data[:-1] # Strip trailing \0's + print sender, ':', repr(data) + + +# Open a UDP socket, bind it to a port and select a multicast group +def openmcastsock(group, port): + # Import modules used only here + import string + import struct + # + # Create a socket + s = socket(AF_INET, SOCK_DGRAM) + # + # Allow multiple copies of this program on one machine + # (not strictly needed) + s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) + # + # Bind it to the port + s.bind(('', port)) + # + # Look up multicast group address in name server + # (doesn't hurt if it is already in ddd.ddd.ddd.ddd format) + group = gethostbyname(group) + # + # Construct binary group address + bytes = map(int, string.split(group, ".")) + grpaddr = 0 + for byte in bytes: grpaddr = (grpaddr << 8) | byte + # + # Construct struct mreq from grpaddr and ifaddr + ifaddr = INADDR_ANY + mreq = struct.pack('ll', htonl(grpaddr), htonl(ifaddr)) + # + # Add group membership + s.setsockopt(IPPROTO_IP, IP_ADD_MEMBERSHIP, mreq) + # + return s + + +main() diff --git a/sys/src/cmd/python/Demo/sockets/radio.py b/sys/src/cmd/python/Demo/sockets/radio.py new file mode 100755 index 000000000..fa4ce75eb --- /dev/null +++ b/sys/src/cmd/python/Demo/sockets/radio.py @@ -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) diff --git a/sys/src/cmd/python/Demo/sockets/rpython.py b/sys/src/cmd/python/Demo/sockets/rpython.py new file mode 100755 index 000000000..8333d3989 --- /dev/null +++ b/sys/src/cmd/python/Demo/sockets/rpython.py @@ -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() diff --git a/sys/src/cmd/python/Demo/sockets/rpythond.py b/sys/src/cmd/python/Demo/sockets/rpythond.py new file mode 100755 index 000000000..81397d683 --- /dev/null +++ b/sys/src/cmd/python/Demo/sockets/rpythond.py @@ -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() diff --git a/sys/src/cmd/python/Demo/sockets/telnet.py b/sys/src/cmd/python/Demo/sockets/telnet.py new file mode 100755 index 000000000..d50c37f2c --- /dev/null +++ b/sys/src/cmd/python/Demo/sockets/telnet.py @@ -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 diff --git a/sys/src/cmd/python/Demo/sockets/throughput.py b/sys/src/cmd/python/Demo/sockets/throughput.py new file mode 100755 index 000000000..b8df1f369 --- /dev/null +++ b/sys/src/cmd/python/Demo/sockets/throughput.py @@ -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() diff --git a/sys/src/cmd/python/Demo/sockets/udpecho.py b/sys/src/cmd/python/Demo/sockets/udpecho.py new file mode 100755 index 000000000..5181c8283 --- /dev/null +++ b/sys/src/cmd/python/Demo/sockets/udpecho.py @@ -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() diff --git a/sys/src/cmd/python/Demo/sockets/unicast.py b/sys/src/cmd/python/Demo/sockets/unicast.py new file mode 100644 index 000000000..dd15e3c7b --- /dev/null +++ b/sys/src/cmd/python/Demo/sockets/unicast.py @@ -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) diff --git a/sys/src/cmd/python/Demo/sockets/unixclient.py b/sys/src/cmd/python/Demo/sockets/unixclient.py new file mode 100644 index 000000000..fdbcc7aec --- /dev/null +++ b/sys/src/cmd/python/Demo/sockets/unixclient.py @@ -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) diff --git a/sys/src/cmd/python/Demo/sockets/unixserver.py b/sys/src/cmd/python/Demo/sockets/unixserver.py new file mode 100644 index 000000000..b73f857b1 --- /dev/null +++ b/sys/src/cmd/python/Demo/sockets/unixserver.py @@ -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) diff --git a/sys/src/cmd/python/Demo/threads/Coroutine.py b/sys/src/cmd/python/Demo/threads/Coroutine.py new file mode 100644 index 000000000..4cc65f7bf --- /dev/null +++ b/sys/src/cmd/python/Demo/threads/Coroutine.py @@ -0,0 +1,159 @@ +# Coroutine implementation using Python threads. +# +# Combines ideas from Guido's Generator module, and from the coroutine +# features of Icon and Simula 67. +# +# To run a collection of functions as coroutines, you need to create +# a Coroutine object to control them: +# co = Coroutine() +# and then 'create' a subsidiary object for each function in the +# collection: +# cof1 = co.create(f1 [, arg1, arg2, ...]) # [] means optional, +# cof2 = co.create(f2 [, arg1, arg2, ...]) #... not list +# cof3 = co.create(f3 [, arg1, arg2, ...]) +# etc. The functions need not be distinct; 'create'ing the same +# function multiple times gives you independent instances of the +# function. +# +# To start the coroutines running, use co.tran on one of the create'd +# functions; e.g., co.tran(cof2). The routine that first executes +# co.tran is called the "main coroutine". It's special in several +# respects: it existed before you created the Coroutine object; if any of +# the create'd coroutines exits (does a return, or suffers an unhandled +# exception), EarlyExit error is raised in the main coroutine; and the +# co.detach() method transfers control directly to the main coroutine +# (you can't use co.tran() for this because the main coroutine doesn't +# have a name ...). +# +# Coroutine objects support these methods: +# +# handle = .create(func [, arg1, arg2, ...]) +# Creates a coroutine for an invocation of func(arg1, arg2, ...), +# and returns a handle ("name") for the coroutine so created. The +# handle can be used as the target in a subsequent .tran(). +# +# .tran(target, data=None) +# Transfer control to the create'd coroutine "target", optionally +# passing it an arbitrary piece of data. To the coroutine A that does +# the .tran, .tran acts like an ordinary function call: another +# coroutine B can .tran back to it later, and if it does A's .tran +# returns the 'data' argument passed to B's tran. E.g., +# +# in coroutine coA in coroutine coC in coroutine coB +# x = co.tran(coC) co.tran(coB) co.tran(coA,12) +# print x # 12 +# +# The data-passing feature is taken from Icon, and greatly cuts +# the need to use global variables for inter-coroutine communication. +# +# .back( data=None ) +# The same as .tran(invoker, data=None), where 'invoker' is the +# coroutine that most recently .tran'ed control to the coroutine +# doing the .back. This is akin to Icon's "&source". +# +# .detach( data=None ) +# The same as .tran(main, data=None), where 'main' is the +# (unnameable!) coroutine that started it all. 'main' has all the +# rights of any other coroutine: upon receiving control, it can +# .tran to an arbitrary coroutine of its choosing, go .back to +# the .detach'er, or .kill the whole thing. +# +# .kill() +# Destroy all the coroutines, and return control to the main +# coroutine. None of the create'ed coroutines can be resumed after a +# .kill(). An EarlyExit exception does a .kill() automatically. It's +# a good idea to .kill() coroutines you're done with, since the +# current implementation consumes a thread for each coroutine that +# may be resumed. + +import thread +import sync + +class _CoEvent: + def __init__(self, func): + self.f = func + self.e = sync.event() + + def __repr__(self): + if self.f is None: + return 'main coroutine' + else: + return 'coroutine for func ' + self.f.func_name + + def __hash__(self): + return id(self) + + def __cmp__(x,y): + return cmp(id(x), id(y)) + + def resume(self): + self.e.post() + + def wait(self): + self.e.wait() + self.e.clear() + +Killed = 'Coroutine.Killed' +EarlyExit = 'Coroutine.EarlyExit' + +class Coroutine: + def __init__(self): + self.active = self.main = _CoEvent(None) + self.invokedby = {self.main: None} + self.killed = 0 + self.value = None + self.terminated_by = None + + def create(self, func, *args): + me = _CoEvent(func) + self.invokedby[me] = None + thread.start_new_thread(self._start, (me,) + args) + return me + + def _start(self, me, *args): + me.wait() + if not self.killed: + try: + try: + apply(me.f, args) + except Killed: + pass + finally: + if not self.killed: + self.terminated_by = me + self.kill() + + def kill(self): + if self.killed: + raise TypeError, 'kill() called on dead coroutines' + self.killed = 1 + for coroutine in self.invokedby.keys(): + coroutine.resume() + + def back(self, data=None): + return self.tran( self.invokedby[self.active], data ) + + def detach(self, data=None): + return self.tran( self.main, data ) + + def tran(self, target, data=None): + if not self.invokedby.has_key(target): + raise TypeError, '.tran target %r is not an active coroutine' % (target,) + if self.killed: + raise TypeError, '.tran target %r is killed' % (target,) + self.value = data + me = self.active + self.invokedby[target] = me + self.active = target + target.resume() + + me.wait() + if self.killed: + if self.main is not me: + raise Killed + if self.terminated_by is not None: + raise EarlyExit, '%r terminated early' % (self.terminated_by,) + + return self.value + +# end of module diff --git a/sys/src/cmd/python/Demo/threads/Generator.py b/sys/src/cmd/python/Demo/threads/Generator.py new file mode 100644 index 000000000..a2713af1a --- /dev/null +++ b/sys/src/cmd/python/Demo/threads/Generator.py @@ -0,0 +1,84 @@ +# Generator implementation using threads + +import thread + +Killed = 'Generator.Killed' + +class Generator: + # Constructor + def __init__(self, func, args): + self.getlock = thread.allocate_lock() + self.putlock = thread.allocate_lock() + self.getlock.acquire() + self.putlock.acquire() + self.func = func + self.args = args + self.done = 0 + self.killed = 0 + thread.start_new_thread(self._start, ()) + # Internal routine + def _start(self): + try: + self.putlock.acquire() + if not self.killed: + try: + apply(self.func, (self,) + self.args) + except Killed: + pass + finally: + if not self.killed: + self.done = 1 + self.getlock.release() + # Called by producer for each value; raise Killed if no more needed + def put(self, value): + if self.killed: + raise TypeError, 'put() called on killed generator' + self.value = value + self.getlock.release() # Resume consumer thread + self.putlock.acquire() # Wait for next get() call + if self.killed: + raise Killed + # Called by producer to get next value; raise EOFError if no more + def get(self): + if self.killed: + raise TypeError, 'get() called on killed generator' + self.putlock.release() # Resume producer thread + self.getlock.acquire() # Wait for value to appear + if self.done: + raise EOFError # Say there are no more values + return self.value + # Called by consumer if no more values wanted + def kill(self): + if self.killed: + raise TypeError, 'kill() called on killed generator' + self.killed = 1 + self.putlock.release() + # Clone constructor + def clone(self): + return Generator(self.func, self.args) + +def pi(g): + k, a, b, a1, b1 = 2L, 4L, 1L, 12L, 4L + while 1: + # Next approximation + p, q, k = k*k, 2L*k+1L, k+1L + a, b, a1, b1 = a1, b1, p*a+q*a1, p*b+q*b1 + # Print common digits + d, d1 = a/b, a1/b1 + while d == d1: + g.put(int(d)) + a, a1 = 10L*(a%b), 10L*(a1%b1) + d, d1 = a/b, a1/b1 + +def test(): + g = Generator(pi, ()) + g.kill() + g = Generator(pi, ()) + for i in range(10): print g.get(), + print + h = g.clone() + g.kill() + while 1: + print h.get(), + +test() diff --git a/sys/src/cmd/python/Demo/threads/README b/sys/src/cmd/python/Demo/threads/README new file mode 100644 index 000000000..fee6aad32 --- /dev/null +++ b/sys/src/cmd/python/Demo/threads/README @@ -0,0 +1,13 @@ +This directory contains some demonstrations of the thread module. + +These are mostly "proof of concept" type applications: + +Generator.py Generator class implemented with threads. +find.py Parallelized "find(1)" (looks for directories). +sync.py Condition variables primitives by Tim Peters. +telnet.py Version of ../sockets/telnet.py using threads. +wpi.py Version of ../scripts/pi.py using threads (needs stdwin). + +Coroutine.py Coroutines using threads, by Tim Peters (22 May 94) +fcmp.py Example of above, by Tim +squasher.py Another example of above, also by Tim diff --git a/sys/src/cmd/python/Demo/threads/fcmp.py b/sys/src/cmd/python/Demo/threads/fcmp.py new file mode 100644 index 000000000..27af76d54 --- /dev/null +++ b/sys/src/cmd/python/Demo/threads/fcmp.py @@ -0,0 +1,64 @@ +# Coroutine example: controlling multiple instances of a single function + +from Coroutine import * + +# fringe visits a nested list in inorder, and detaches for each non-list +# element; raises EarlyExit after the list is exhausted +def fringe(co, list): + for x in list: + if type(x) is type([]): + fringe(co, x) + else: + co.back(x) + +def printinorder(list): + co = Coroutine() + f = co.create(fringe, co, list) + try: + while 1: + print co.tran(f), + except EarlyExit: + pass + print + +printinorder([1,2,3]) # 1 2 3 +printinorder([[[[1,[2]]],3]]) # ditto +x = [0, 1, [2, [3]], [4,5], [[[6]]] ] +printinorder(x) # 0 1 2 3 4 5 6 + +# fcmp lexicographically compares the fringes of two nested lists +def fcmp(l1, l2): + co1 = Coroutine(); f1 = co1.create(fringe, co1, l1) + co2 = Coroutine(); f2 = co2.create(fringe, co2, l2) + while 1: + try: + v1 = co1.tran(f1) + except EarlyExit: + try: + v2 = co2.tran(f2) + except EarlyExit: + return 0 + co2.kill() + return -1 + try: + v2 = co2.tran(f2) + except EarlyExit: + co1.kill() + return 1 + if v1 != v2: + co1.kill(); co2.kill() + return cmp(v1,v2) + +print fcmp(range(7), x) # 0; fringes are equal +print fcmp(range(6), x) # -1; 1st list ends early +print fcmp(x, range(6)) # 1; 2nd list ends early +print fcmp(range(8), x) # 1; 2nd list ends early +print fcmp(x, range(8)) # -1; 1st list ends early +print fcmp([1,[[2],8]], + [[[1],2],8]) # 0 +print fcmp([1,[[3],8]], + [[[1],2],8]) # 1 +print fcmp([1,[[2],8]], + [[[1],2],9]) # -1 + +# end of example diff --git a/sys/src/cmd/python/Demo/threads/find.py b/sys/src/cmd/python/Demo/threads/find.py new file mode 100644 index 000000000..7d5edc1c5 --- /dev/null +++ b/sys/src/cmd/python/Demo/threads/find.py @@ -0,0 +1,155 @@ +# A parallelized "find(1)" using the thread module. + +# This demonstrates the use of a work queue and worker threads. +# It really does do more stats/sec when using multiple threads, +# although the improvement is only about 20-30 percent. +# (That was 8 years ago. In 2002, on Linux, I can't measure +# a speedup. :-( ) + +# I'm too lazy to write a command line parser for the full find(1) +# command line syntax, so the predicate it searches for is wired-in, +# see function selector() below. (It currently searches for files with +# world write permission.) + +# Usage: parfind.py [-w nworkers] [directory] ... +# Default nworkers is 4 + + +import sys +import getopt +import string +import time +import os +from stat import * +import thread + + +# Work queue class. Usage: +# wq = WorkQ() +# wq.addwork(func, (arg1, arg2, ...)) # one or more calls +# wq.run(nworkers) +# The work is done when wq.run() completes. +# The function calls executed by the workers may add more work. +# Don't use keyboard interrupts! + +class WorkQ: + + # Invariants: + + # - busy and work are only modified when mutex is locked + # - len(work) is the number of jobs ready to be taken + # - busy is the number of jobs being done + # - todo is locked iff there is no work and somebody is busy + + def __init__(self): + self.mutex = thread.allocate() + self.todo = thread.allocate() + self.todo.acquire() + self.work = [] + self.busy = 0 + + def addwork(self, func, args): + job = (func, args) + self.mutex.acquire() + self.work.append(job) + self.mutex.release() + if len(self.work) == 1: + self.todo.release() + + def _getwork(self): + self.todo.acquire() + self.mutex.acquire() + if self.busy == 0 and len(self.work) == 0: + self.mutex.release() + self.todo.release() + return None + job = self.work[0] + del self.work[0] + self.busy = self.busy + 1 + self.mutex.release() + if len(self.work) > 0: + self.todo.release() + return job + + def _donework(self): + self.mutex.acquire() + self.busy = self.busy - 1 + if self.busy == 0 and len(self.work) == 0: + self.todo.release() + self.mutex.release() + + def _worker(self): + time.sleep(0.00001) # Let other threads run + while 1: + job = self._getwork() + if not job: + break + func, args = job + apply(func, args) + self._donework() + + def run(self, nworkers): + if not self.work: + return # Nothing to do + for i in range(nworkers-1): + thread.start_new(self._worker, ()) + self._worker() + self.todo.acquire() + + +# Main program + +def main(): + nworkers = 4 + opts, args = getopt.getopt(sys.argv[1:], '-w:') + for opt, arg in opts: + if opt == '-w': + nworkers = string.atoi(arg) + if not args: + args = [os.curdir] + + wq = WorkQ() + for dir in args: + wq.addwork(find, (dir, selector, wq)) + + t1 = time.time() + wq.run(nworkers) + t2 = time.time() + + sys.stderr.write('Total time %r sec.\n' % (t2-t1)) + + +# The predicate -- defines what files we look for. +# Feel free to change this to suit your purpose + +def selector(dir, name, fullname, stat): + # Look for world writable files that are not symlinks + return (stat[ST_MODE] & 0002) != 0 and not S_ISLNK(stat[ST_MODE]) + + +# The find procedure -- calls wq.addwork() for subdirectories + +def find(dir, pred, wq): + try: + names = os.listdir(dir) + except os.error, msg: + print repr(dir), ':', msg + return + for name in names: + if name not in (os.curdir, os.pardir): + fullname = os.path.join(dir, name) + try: + stat = os.lstat(fullname) + except os.error, msg: + print repr(fullname), ':', msg + continue + if pred(dir, name, fullname, stat): + print fullname + if S_ISDIR(stat[ST_MODE]): + if not os.path.ismount(fullname): + wq.addwork(find, (fullname, pred, wq)) + + +# Call the main program + +main() diff --git a/sys/src/cmd/python/Demo/threads/squasher.py b/sys/src/cmd/python/Demo/threads/squasher.py new file mode 100644 index 000000000..0d59cb839 --- /dev/null +++ b/sys/src/cmd/python/Demo/threads/squasher.py @@ -0,0 +1,105 @@ +# Coroutine example: general coroutine transfers +# +# The program is a variation of a Simula 67 program due to Dahl & Hoare, +# (Dahl/Dijkstra/Hoare, Structured Programming; Academic Press, 1972) +# who in turn credit the original example to Conway. +# +# We have a number of input lines, terminated by a 0 byte. The problem +# is to squash them together into output lines containing 72 characters +# each. A semicolon must be added between input lines. Runs of blanks +# and tabs in input lines must be squashed into single blanks. +# Occurrences of "**" in input lines must be replaced by "^". +# +# Here's a test case: + +test = """\ + d = sqrt(b**2 - 4*a*c) +twoa = 2*a + L = -b/twoa + R = d/twoa + A1 = L + R + A2 = L - R\0 +""" + +# The program should print: + +# d = sqrt(b^2 - 4*a*c);twoa = 2*a; L = -b/twoa; R = d/twoa; A1 = L + R; +#A2 = L - R +#done + +# getline: delivers the next input line to its invoker +# disassembler: grabs input lines from getline, and delivers them one +# character at a time to squasher, also inserting a semicolon into +# the stream between lines +# squasher: grabs characters from disassembler and passes them on to +# assembler, first replacing "**" with "^" and squashing runs of +# whitespace +# assembler: grabs characters from squasher and packs them into lines +# with 72 character each, delivering each such line to putline; +# when it sees a null byte, passes the last line to putline and +# then kills all the coroutines +# putline: grabs lines from assembler, and just prints them + +from Coroutine import * + +def getline(text): + for line in string.splitfields(text, '\n'): + co.tran(codisassembler, line) + +def disassembler(): + while 1: + card = co.tran(cogetline) + for i in range(len(card)): + co.tran(cosquasher, card[i]) + co.tran(cosquasher, ';') + +def squasher(): + while 1: + ch = co.tran(codisassembler) + if ch == '*': + ch2 = co.tran(codisassembler) + if ch2 == '*': + ch = '^' + else: + co.tran(coassembler, ch) + ch = ch2 + if ch in ' \t': + while 1: + ch2 = co.tran(codisassembler) + if ch2 not in ' \t': + break + co.tran(coassembler, ' ') + ch = ch2 + co.tran(coassembler, ch) + +def assembler(): + line = '' + while 1: + ch = co.tran(cosquasher) + if ch == '\0': + break + if len(line) == 72: + co.tran(coputline, line) + line = '' + line = line + ch + line = line + ' ' * (72 - len(line)) + co.tran(coputline, line) + co.kill() + +def putline(): + while 1: + line = co.tran(coassembler) + print line + +import string +co = Coroutine() +cogetline = co.create(getline, test) +coputline = co.create(putline) +coassembler = co.create(assembler) +codisassembler = co.create(disassembler) +cosquasher = co.create(squasher) + +co.tran(coputline) +print 'done' + +# end of example diff --git a/sys/src/cmd/python/Demo/threads/sync.py b/sys/src/cmd/python/Demo/threads/sync.py new file mode 100644 index 000000000..843767ac7 --- /dev/null +++ b/sys/src/cmd/python/Demo/threads/sync.py @@ -0,0 +1,603 @@ +# Defines classes that provide synchronization objects. Note that use of +# this module requires that your Python support threads. +# +# condition(lock=None) # a POSIX-like condition-variable object +# barrier(n) # an n-thread barrier +# event() # an event object +# semaphore(n=1) # a semaphore object, with initial count n +# mrsw() # a multiple-reader single-writer lock +# +# CONDITIONS +# +# A condition object is created via +# import this_module +# your_condition_object = this_module.condition(lock=None) +# +# As explained below, a condition object has a lock associated with it, +# used in the protocol to protect condition data. You can specify a +# lock to use in the constructor, else the constructor will allocate +# an anonymous lock for you. Specifying a lock explicitly can be useful +# when more than one condition keys off the same set of shared data. +# +# Methods: +# .acquire() +# acquire the lock associated with the condition +# .release() +# release the lock associated with the condition +# .wait() +# block the thread until such time as some other thread does a +# .signal or .broadcast on the same condition, and release the +# lock associated with the condition. The lock associated with +# the condition MUST be in the acquired state at the time +# .wait is invoked. +# .signal() +# wake up exactly one thread (if any) that previously did a .wait +# on the condition; that thread will awaken with the lock associated +# with the condition in the acquired state. If no threads are +# .wait'ing, this is a nop. If more than one thread is .wait'ing on +# the condition, any of them may be awakened. +# .broadcast() +# wake up all threads (if any) that are .wait'ing on the condition; +# the threads are woken up serially, each with the lock in the +# acquired state, so should .release() as soon as possible. If no +# threads are .wait'ing, this is a nop. +# +# Note that if a thread does a .wait *while* a signal/broadcast is +# in progress, it's guaranteeed to block until a subsequent +# signal/broadcast. +# +# Secret feature: `broadcast' actually takes an integer argument, +# and will wake up exactly that many waiting threads (or the total +# number waiting, if that's less). Use of this is dubious, though, +# and probably won't be supported if this form of condition is +# reimplemented in C. +# +# DIFFERENCES FROM POSIX +# +# + A separate mutex is not needed to guard condition data. Instead, a +# condition object can (must) be .acquire'ed and .release'ed directly. +# This eliminates a common error in using POSIX conditions. +# +# + Because of implementation difficulties, a POSIX `signal' wakes up +# _at least_ one .wait'ing thread. Race conditions make it difficult +# to stop that. This implementation guarantees to wake up only one, +# but you probably shouldn't rely on that. +# +# PROTOCOL +# +# Condition objects are used to block threads until "some condition" is +# true. E.g., a thread may wish to wait until a producer pumps out data +# for it to consume, or a server may wish to wait until someone requests +# its services, or perhaps a whole bunch of threads want to wait until a +# preceding pass over the data is complete. Early models for conditions +# relied on some other thread figuring out when a blocked thread's +# condition was true, and made the other thread responsible both for +# waking up the blocked thread and guaranteeing that it woke up with all +# data in a correct state. This proved to be very delicate in practice, +# and gave conditions a bad name in some circles. +# +# The POSIX model addresses these problems by making a thread responsible +# for ensuring that its own state is correct when it wakes, and relies +# on a rigid protocol to make this easy; so long as you stick to the +# protocol, POSIX conditions are easy to "get right": +# +# A) The thread that's waiting for some arbitrarily-complex condition +# (ACC) to become true does: +# +# condition.acquire() +# while not (code to evaluate the ACC): +# condition.wait() +# # That blocks the thread, *and* releases the lock. When a +# # condition.signal() happens, it will wake up some thread that +# # did a .wait, *and* acquire the lock again before .wait +# # returns. +# # +# # Because the lock is acquired at this point, the state used +# # in evaluating the ACC is frozen, so it's safe to go back & +# # reevaluate the ACC. +# +# # At this point, ACC is true, and the thread has the condition +# # locked. +# # So code here can safely muck with the shared state that +# # went into evaluating the ACC -- if it wants to. +# # When done mucking with the shared state, do +# condition.release() +# +# B) Threads that are mucking with shared state that may affect the +# ACC do: +# +# condition.acquire() +# # muck with shared state +# condition.release() +# if it's possible that ACC is true now: +# condition.signal() # or .broadcast() +# +# Note: You may prefer to put the "if" clause before the release(). +# That's fine, but do note that anyone waiting on the signal will +# stay blocked until the release() is done (since acquiring the +# condition is part of what .wait() does before it returns). +# +# TRICK OF THE TRADE +# +# With simpler forms of conditions, it can be impossible to know when +# a thread that's supposed to do a .wait has actually done it. But +# because this form of condition releases a lock as _part_ of doing a +# wait, the state of that lock can be used to guarantee it. +# +# E.g., suppose thread A spawns thread B and later wants to wait for B to +# complete: +# +# In A: In B: +# +# B_done = condition() ... do work ... +# B_done.acquire() B_done.acquire(); B_done.release() +# spawn B B_done.signal() +# ... some time later ... ... and B exits ... +# B_done.wait() +# +# Because B_done was in the acquire'd state at the time B was spawned, +# B's attempt to acquire B_done can't succeed until A has done its +# B_done.wait() (which releases B_done). So B's B_done.signal() is +# guaranteed to be seen by the .wait(). Without the lock trick, B +# may signal before A .waits, and then A would wait forever. +# +# BARRIERS +# +# A barrier object is created via +# import this_module +# your_barrier = this_module.barrier(num_threads) +# +# Methods: +# .enter() +# the thread blocks until num_threads threads in all have done +# .enter(). Then the num_threads threads that .enter'ed resume, +# and the barrier resets to capture the next num_threads threads +# that .enter it. +# +# EVENTS +# +# An event object is created via +# import this_module +# your_event = this_module.event() +# +# An event has two states, `posted' and `cleared'. An event is +# created in the cleared state. +# +# Methods: +# +# .post() +# Put the event in the posted state, and resume all threads +# .wait'ing on the event (if any). +# +# .clear() +# Put the event in the cleared state. +# +# .is_posted() +# Returns 0 if the event is in the cleared state, or 1 if the event +# is in the posted state. +# +# .wait() +# If the event is in the posted state, returns immediately. +# If the event is in the cleared state, blocks the calling thread +# until the event is .post'ed by another thread. +# +# Note that an event, once posted, remains posted until explicitly +# cleared. Relative to conditions, this is both the strength & weakness +# of events. It's a strength because the .post'ing thread doesn't have to +# worry about whether the threads it's trying to communicate with have +# already done a .wait (a condition .signal is seen only by threads that +# do a .wait _prior_ to the .signal; a .signal does not persist). But +# it's a weakness because .clear'ing an event is error-prone: it's easy +# to mistakenly .clear an event before all the threads you intended to +# see the event get around to .wait'ing on it. But so long as you don't +# need to .clear an event, events are easy to use safely. +# +# SEMAPHORES +# +# A semaphore object is created via +# import this_module +# your_semaphore = this_module.semaphore(count=1) +# +# A semaphore has an integer count associated with it. The initial value +# of the count is specified by the optional argument (which defaults to +# 1) passed to the semaphore constructor. +# +# Methods: +# +# .p() +# If the semaphore's count is greater than 0, decrements the count +# by 1 and returns. +# Else if the semaphore's count is 0, blocks the calling thread +# until a subsequent .v() increases the count. When that happens, +# the count will be decremented by 1 and the calling thread resumed. +# +# .v() +# Increments the semaphore's count by 1, and wakes up a thread (if +# any) blocked by a .p(). It's an (detected) error for a .v() to +# increase the semaphore's count to a value larger than the initial +# count. +# +# MULTIPLE-READER SINGLE-WRITER LOCKS +# +# A mrsw lock is created via +# import this_module +# your_mrsw_lock = this_module.mrsw() +# +# This kind of lock is often useful with complex shared data structures. +# The object lets any number of "readers" proceed, so long as no thread +# wishes to "write". When a (one or more) thread declares its intention +# to "write" (e.g., to update a shared structure), all current readers +# are allowed to finish, and then a writer gets exclusive access; all +# other readers & writers are blocked until the current writer completes. +# Finally, if some thread is waiting to write and another is waiting to +# read, the writer takes precedence. +# +# Methods: +# +# .read_in() +# If no thread is writing or waiting to write, returns immediately. +# Else blocks until no thread is writing or waiting to write. So +# long as some thread has completed a .read_in but not a .read_out, +# writers are blocked. +# +# .read_out() +# Use sometime after a .read_in to declare that the thread is done +# reading. When all threads complete reading, a writer can proceed. +# +# .write_in() +# If no thread is writing (has completed a .write_in, but hasn't yet +# done a .write_out) or reading (similarly), returns immediately. +# Else blocks the calling thread, and threads waiting to read, until +# the current writer completes writing or all the current readers +# complete reading; if then more than one thread is waiting to +# write, one of them is allowed to proceed, but which one is not +# specified. +# +# .write_out() +# Use sometime after a .write_in to declare that the thread is done +# writing. Then if some other thread is waiting to write, it's +# allowed to proceed. Else all threads (if any) waiting to read are +# allowed to proceed. +# +# .write_to_read() +# Use instead of a .write_in to declare that the thread is done +# writing but wants to continue reading without other writers +# intervening. If there are other threads waiting to write, they +# are allowed to proceed only if the current thread calls +# .read_out; threads waiting to read are only allowed to proceed +# if there are are no threads waiting to write. (This is a +# weakness of the interface!) + +import thread + +class condition: + def __init__(self, lock=None): + # the lock actually used by .acquire() and .release() + if lock is None: + self.mutex = thread.allocate_lock() + else: + if hasattr(lock, 'acquire') and \ + hasattr(lock, 'release'): + self.mutex = lock + else: + raise TypeError, 'condition constructor requires ' \ + 'a lock argument' + + # lock used to block threads until a signal + self.checkout = thread.allocate_lock() + self.checkout.acquire() + + # internal critical-section lock, & the data it protects + self.idlock = thread.allocate_lock() + self.id = 0 + self.waiting = 0 # num waiters subject to current release + self.pending = 0 # num waiters awaiting next signal + self.torelease = 0 # num waiters to release + self.releasing = 0 # 1 iff release is in progress + + def acquire(self): + self.mutex.acquire() + + def release(self): + self.mutex.release() + + def wait(self): + mutex, checkout, idlock = self.mutex, self.checkout, self.idlock + if not mutex.locked(): + raise ValueError, \ + "condition must be .acquire'd when .wait() invoked" + + idlock.acquire() + myid = self.id + self.pending = self.pending + 1 + idlock.release() + + mutex.release() + + while 1: + checkout.acquire(); idlock.acquire() + if myid < self.id: + break + checkout.release(); idlock.release() + + self.waiting = self.waiting - 1 + self.torelease = self.torelease - 1 + if self.torelease: + checkout.release() + else: + self.releasing = 0 + if self.waiting == self.pending == 0: + self.id = 0 + idlock.release() + mutex.acquire() + + def signal(self): + self.broadcast(1) + + def broadcast(self, num = -1): + if num < -1: + raise ValueError, '.broadcast called with num %r' % (num,) + if num == 0: + return + self.idlock.acquire() + if self.pending: + self.waiting = self.waiting + self.pending + self.pending = 0 + self.id = self.id + 1 + if num == -1: + self.torelease = self.waiting + else: + self.torelease = min( self.waiting, + self.torelease + num ) + if self.torelease and not self.releasing: + self.releasing = 1 + self.checkout.release() + self.idlock.release() + +class barrier: + def __init__(self, n): + self.n = n + self.togo = n + self.full = condition() + + def enter(self): + full = self.full + full.acquire() + self.togo = self.togo - 1 + if self.togo: + full.wait() + else: + self.togo = self.n + full.broadcast() + full.release() + +class event: + def __init__(self): + self.state = 0 + self.posted = condition() + + def post(self): + self.posted.acquire() + self.state = 1 + self.posted.broadcast() + self.posted.release() + + def clear(self): + self.posted.acquire() + self.state = 0 + self.posted.release() + + def is_posted(self): + self.posted.acquire() + answer = self.state + self.posted.release() + return answer + + def wait(self): + self.posted.acquire() + if not self.state: + self.posted.wait() + self.posted.release() + +class semaphore: + def __init__(self, count=1): + if count <= 0: + raise ValueError, 'semaphore count %d; must be >= 1' % count + self.count = count + self.maxcount = count + self.nonzero = condition() + + def p(self): + self.nonzero.acquire() + while self.count == 0: + self.nonzero.wait() + self.count = self.count - 1 + self.nonzero.release() + + def v(self): + self.nonzero.acquire() + if self.count == self.maxcount: + raise ValueError, '.v() tried to raise semaphore count above ' \ + 'initial value %r' % self.maxcount + self.count = self.count + 1 + self.nonzero.signal() + self.nonzero.release() + +class mrsw: + def __init__(self): + # critical-section lock & the data it protects + self.rwOK = thread.allocate_lock() + self.nr = 0 # number readers actively reading (not just waiting) + self.nw = 0 # number writers either waiting to write or writing + self.writing = 0 # 1 iff some thread is writing + + # conditions + self.readOK = condition(self.rwOK) # OK to unblock readers + self.writeOK = condition(self.rwOK) # OK to unblock writers + + def read_in(self): + self.rwOK.acquire() + while self.nw: + self.readOK.wait() + self.nr = self.nr + 1 + self.rwOK.release() + + def read_out(self): + self.rwOK.acquire() + if self.nr <= 0: + raise ValueError, \ + '.read_out() invoked without an active reader' + self.nr = self.nr - 1 + if self.nr == 0: + self.writeOK.signal() + self.rwOK.release() + + def write_in(self): + self.rwOK.acquire() + self.nw = self.nw + 1 + while self.writing or self.nr: + self.writeOK.wait() + self.writing = 1 + self.rwOK.release() + + def write_out(self): + self.rwOK.acquire() + if not self.writing: + raise ValueError, \ + '.write_out() invoked without an active writer' + self.writing = 0 + self.nw = self.nw - 1 + if self.nw: + self.writeOK.signal() + else: + self.readOK.broadcast() + self.rwOK.release() + + def write_to_read(self): + self.rwOK.acquire() + if not self.writing: + raise ValueError, \ + '.write_to_read() invoked without an active writer' + self.writing = 0 + self.nw = self.nw - 1 + self.nr = self.nr + 1 + if not self.nw: + self.readOK.broadcast() + self.rwOK.release() + +# The rest of the file is a test case, that runs a number of parallelized +# quicksorts in parallel. If it works, you'll get about 600 lines of +# tracing output, with a line like +# test passed! 209 threads created in all +# as the last line. The content and order of preceding lines will +# vary across runs. + +def _new_thread(func, *args): + global TID + tid.acquire(); id = TID = TID+1; tid.release() + io.acquire(); alive.append(id); \ + print 'starting thread', id, '--', len(alive), 'alive'; \ + io.release() + thread.start_new_thread( func, (id,) + args ) + +def _qsort(tid, a, l, r, finished): + # sort a[l:r]; post finished when done + io.acquire(); print 'thread', tid, 'qsort', l, r; io.release() + if r-l > 1: + pivot = a[l] + j = l+1 # make a[l:j] <= pivot, and a[j:r] > pivot + for i in range(j, r): + if a[i] <= pivot: + a[j], a[i] = a[i], a[j] + j = j + 1 + a[l], a[j-1] = a[j-1], pivot + + l_subarray_sorted = event() + r_subarray_sorted = event() + _new_thread(_qsort, a, l, j-1, l_subarray_sorted) + _new_thread(_qsort, a, j, r, r_subarray_sorted) + l_subarray_sorted.wait() + r_subarray_sorted.wait() + + io.acquire(); print 'thread', tid, 'qsort done'; \ + alive.remove(tid); io.release() + finished.post() + +def _randarray(tid, a, finished): + io.acquire(); print 'thread', tid, 'randomizing array'; \ + io.release() + for i in range(1, len(a)): + wh.acquire(); j = randint(0,i); wh.release() + a[i], a[j] = a[j], a[i] + io.acquire(); print 'thread', tid, 'randomizing done'; \ + alive.remove(tid); io.release() + finished.post() + +def _check_sort(a): + if a != range(len(a)): + raise ValueError, ('a not sorted', a) + +def _run_one_sort(tid, a, bar, done): + # randomize a, and quicksort it + # for variety, all the threads running this enter a barrier + # at the end, and post `done' after the barrier exits + io.acquire(); print 'thread', tid, 'randomizing', a; \ + io.release() + finished = event() + _new_thread(_randarray, a, finished) + finished.wait() + + io.acquire(); print 'thread', tid, 'sorting', a; io.release() + finished.clear() + _new_thread(_qsort, a, 0, len(a), finished) + finished.wait() + _check_sort(a) + + io.acquire(); print 'thread', tid, 'entering barrier'; \ + io.release() + bar.enter() + io.acquire(); print 'thread', tid, 'leaving barrier'; \ + io.release() + io.acquire(); alive.remove(tid); io.release() + bar.enter() # make sure they've all removed themselves from alive + ## before 'done' is posted + bar.enter() # just to be cruel + done.post() + +def test(): + global TID, tid, io, wh, randint, alive + import random + randint = random.randint + + TID = 0 # thread ID (1, 2, ...) + tid = thread.allocate_lock() # for changing TID + io = thread.allocate_lock() # for printing, and 'alive' + wh = thread.allocate_lock() # for calls to random + alive = [] # IDs of active threads + + NSORTS = 5 + arrays = [] + for i in range(NSORTS): + arrays.append( range( (i+1)*10 ) ) + + bar = barrier(NSORTS) + finished = event() + for i in range(NSORTS): + _new_thread(_run_one_sort, arrays[i], bar, finished) + finished.wait() + + print 'all threads done, and checking results ...' + if alive: + raise ValueError, ('threads still alive at end', alive) + for i in range(NSORTS): + a = arrays[i] + if len(a) != (i+1)*10: + raise ValueError, ('length of array', i, 'screwed up') + _check_sort(a) + + print 'test passed!', TID, 'threads created in all' + +if __name__ == '__main__': + test() + +# end of module diff --git a/sys/src/cmd/python/Demo/threads/telnet.py b/sys/src/cmd/python/Demo/threads/telnet.py new file mode 100644 index 000000000..707a35386 --- /dev/null +++ b/sys/src/cmd/python/Demo/threads/telnet.py @@ -0,0 +1,114 @@ +# Minimal interface to the Internet telnet protocol. +# +# *** modified to use threads *** +# +# 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, os, time +from socket import * +import thread + +BUFSIZE = 8*1024 + +# Telnet protocol characters + +IAC = chr(255) # Interpret as command +DONT = chr(254) +DO = chr(253) +WONT = chr(252) +WILL = chr(251) + +def main(): + if len(sys.argv) < 2: + sys.stderr.write('usage: telnet hostname [port]\n') + sys.exit(2) + 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: %r\n' % (msg,)) + sys.exit(1) + # + thread.start_new(child, (s,)) + parent(s) + +def parent(s): + # read socket, write stdout + iac = 0 # Interpret next char as command + opt = '' # Interpret next char as option + while 1: + data, dummy = s.recvfrom(BUFSIZE) + if not data: + # EOF -- exit + sys.stderr.write( '(Closed by remote host)\n') + sys.exit(1) + cleandata = '' + for c in data: + if opt: + print ord(c) +## print '(replying: %r)' % (opt+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() +## print 'Out:', repr(cleandata) + +def child(s): + # read stdin, write socket + while 1: + line = sys.stdin.readline() +## print 'Got:', repr(line) + if not line: break + s.send(line) + +main() diff --git a/sys/src/cmd/python/Demo/tix/INSTALL.txt b/sys/src/cmd/python/Demo/tix/INSTALL.txt new file mode 100644 index 000000000..d150b0ce6 --- /dev/null +++ b/sys/src/cmd/python/Demo/tix/INSTALL.txt @@ -0,0 +1,89 @@ +$Id: INSTALL.txt 24230 2001-11-11 14:07:37Z loewis $ + +Installing Tix.py +---------------- + +0) To use Tix.py, you need Tcl/Tk (V8.3.3), Tix (V8.1.1) and Python (V2.1.1). + Tix.py has been written and tested on a Intel Pentium running RH Linux 5.2 + and Mandrake Linux 7.0 and Windows with the above mentioned packages. + + Older versions, e.g. Tix 4.1 and Tk 8.0, might also work. + + There is nothing OS-specific in Tix.py itself so it should work on + any machine with Tix and Python installed. You can get Tcl and Tk + from http://dev.scriptics.com and Tix from http://tix.sourceforge.net. + +1) Build and install Tcl/Tk 8.3. Build and install Tix 8.1. + Ensure that Tix is properly installed by running tixwish and executing + the demo programs. Under Unix, use the --enable-shared configure option + for all three. We recommend tcl8.3.3 for this release of Tix.py. + +2a) If you have a distribution like ActiveState with a tcl subdirectory + of $PYTHONHOME, which contains the directories tcl8.3 and tk8.3, + make a directory tix8.1 as well. Recursively copy the files from + <tix>/library to $PYTHONHOME/lib/tix8.1, and copy the dynamic library + (tix8183.dll or libtix8.1.8.3.so) to the same place as the tcl dynamic + libraries ($PYTHONHOME/Dlls or lib/python-2.1/lib-dynload). In this + case you are all installed, and you can skip to the end. + +2b) Modify Modules/Setup.dist and setup.py to change the version of the + tix library from tix4.1.8.0 to tix8.1.8.3 + These modified files can be used for Tkinter with or without Tix. + +3) The default is to build dynamically, and use the Tcl 'package require'. + To build statically, modify the Modules/Setup file to link in the Tix + library according to the comments in the file. On Linux this looks like: + +# *** Always uncomment this (leave the leading underscore in!): +_tkinter _tkinter.c tkappinit.c -DWITH_APPINIT \ +# *** Uncomment and edit to reflect where your Tcl/Tk libraries are: + -L/usr/local/lib \ +# *** Uncomment and edit to reflect where your Tcl/Tk headers are: + -I/usr/local/include \ +# *** Uncomment and edit to reflect where your X11 header files are: + -I/usr/X11R6/include \ +# *** Or uncomment this for Solaris: +# -I/usr/openwin/include \ +# *** Uncomment and edit for BLT extension only: +# -DWITH_BLT -I/usr/local/blt/blt8.0-unoff/include -lBLT8.0 \ +# *** Uncomment and edit for PIL (TkImaging) extension only: +# (See http://www.pythonware.com/products/pil/ for more info) +# -DWITH_PIL -I../Extensions/Imaging/libImaging tkImaging.c \ +# *** Uncomment and edit for TOGL extension only: +# -DWITH_TOGL togl.c \ +# *** Uncomment and edit for Tix extension only: + -DWITH_TIX -ltix8.1.8.3 \ +# *** Uncomment and edit to reflect your Tcl/Tk versions: + -ltk8.3 -ltcl8.3 \ +# *** Uncomment and edit to reflect where your X11 libraries are: + -L/usr/X11R6/lib \ +# *** Or uncomment this for Solaris: +# -L/usr/openwin/lib \ +# *** Uncomment these for TOGL extension only: +# -lGL -lGLU -lXext -lXmu \ +# *** Uncomment for AIX: +# -lld \ +# *** Always uncomment this; X11 libraries to link with: + -lX11 + +4) Rebuild Python and reinstall. + +You should now have a working Tix implementation in Python. To see if all +is as it should be, run the 'tixwidgets.py' script in the Demo/tix directory. +Under X windows, do + /usr/local/bin/python Demo/tix/tixwidgets.py + +If this does not work, you may need to tell python where to find +the Tcl, Tk and Tix library files. This is done by setting the +TCL_LIBRARY, TK_LIBRARY and TIX_LIBRARY environment variables. Try this: + + env TCL_LIBRARY=/usr/local/lib/tcl8.3 \ + TK_LIBRARY=/usr/local/lib/tk8.3 \ + TIX_LIBRARY=/usr/local/lib/tix8.1 \ + /usr/local/bin/python Demo/tix/tixwidgets.py + + +If you find any bugs or have suggestions for improvement, please report them +via http://tix.sourceforge.net + + diff --git a/sys/src/cmd/python/Demo/tix/README.txt b/sys/src/cmd/python/Demo/tix/README.txt new file mode 100644 index 000000000..e0196ac3c --- /dev/null +++ b/sys/src/cmd/python/Demo/tix/README.txt @@ -0,0 +1,19 @@ +About Tix.py +----------- + +Tix.py is based on an idea of Jean-Marc Lugrin (lugrin@ms.com) who wrote +pytix (another Python-Tix marriage). Tix widgets are an attractive and +useful extension to Tk. See http://tix.sourceforge.net +for more details about Tix and how to get it. + +Features: + 1) It is almost complete. + 2) Tix widgets are represented by classes in Python. Sub-widgets + are members of the mega-widget class. For example, if a + particular TixWidget (e.g. ScrolledText) has an embedded widget + (Text in this case), it is possible to call the methods of the + child directly. + 3) The members of the class are created automatically. In the case + of widgets like ButtonBox, the members are added dynamically. + + diff --git a/sys/src/cmd/python/Demo/tix/bitmaps/about.xpm b/sys/src/cmd/python/Demo/tix/bitmaps/about.xpm new file mode 100755 index 000000000..33ffcc06e --- /dev/null +++ b/sys/src/cmd/python/Demo/tix/bitmaps/about.xpm @@ -0,0 +1,50 @@ +/* XPM */ +static char * about_xpm[] = { +"50 40 7 1", +" s None c None", +". c black", +"X c white", +"o c gray70", +"O c navy", +"+ c red", +"@ c yellow", +" ", +" ", +" ", +" ................................. ", +" ..XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXoo. ", +" .XooooooooooooooooooooooooooooooXo. ", +" .XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXooXo. ", +" ..oooooooooooooooooooooooooooooooXo. ", +" ...............................XoXo. ", +" .OOOOOOOOOOOOOOOOOOOOOOOOOOOOO.XoXo. ", +" .OOOOOOOOOOOOOOOOOOOOOOOOOOOOO.XoXo. ", +" .OOOOOOOOOOOOOOOOOOOOOOOOOOOOO.XoXo. ", +" .OOOOOOOOOOOOOOOOOOOOOOOOOOOOO.XoXo. ", +" .OOOOOOOOOOOOOOOOOOOOOOOOOOOOO.XoXo.++++ ", +" .OOOOOOOOOOOOOOOOOOOOOOOOOOOOO.XoXo+++ ", +" .OOOOOOOOOOOOOOOOOOOOOOOOOOOOO.Xo+++++ ", +" .OOOOOOOOOOOOOOOOOOOOOOOOOOOOO.Xo++++++ ", +" .OOOOOOOOOOOOOOOOOOOOOOOOOOOOO.Xo+++ + ", +" .OOOOO@@@@@OOOOOOOOOOOOOOOOOOO.Xo++. ", +" .OOOOOOO@OOOOO@OOOOOOOOOOOOOOO.XoXo. ", +" .OOOOOOO@OOOOOOOOOOOOOOOOOOOOO.XoXo. ", +" .OOOOOOO@OOOO@@OOO@OOO@OOOOOOO.XoXo. ", +" .OOOOOOO@OOOOO@OOOO@O@OOOOOOOO.XoXo. ", +" .OOOOOOO@OOOOO@OOOOO@OOOOOOOOO.XoXo. ", +" .OOOOOOO@OOOOO@OOOOO@OOOOOOOOO.XoXo. ", +" .OOOOOOO@OOOOO@OOOO@O@OOOOOOOO.XoXo. ", +" .OOOOOOO@OOOO@@@OO@OOO@OOOOOOO.XoXo. ", +" .OOOOOOOOOOOOOOOOOOOOOOOOOOOOO.XoXo. ", +" .OOOOOOOOOOOOOOOOOOOOOOOOOOOOO.XoXo. ", +" .OOOOOOOOOOOOOOOOOOOOOOOOOOOOO.XoXo. ", +" .OOOOOOOOOOOOOOOOOOOOOOOOOOOOO.XoXo. ", +" .OOOOOOOOOOOOOOOOOOOOOOOOOOOOO.XoXo. ", +" .OOOOOOOOOOOOOOOOOOOOOOOOOOOOO.XoXo. ", +" .OOOOOOOOOOOOOOOOOOOOOOOOOOOOO.Xo.. ", +" .OOOOOOOOOOOOOOOOOOOOOOOOOOOOO.Xo ", +" OOOOOOOOOOOOOOOOOOOOOOOOOOOOO.X. ", +" ............................. ", +" ", +" ", +" "}; diff --git a/sys/src/cmd/python/Demo/tix/bitmaps/bold.xbm b/sys/src/cmd/python/Demo/tix/bitmaps/bold.xbm new file mode 100755 index 000000000..ebff8d117 --- /dev/null +++ b/sys/src/cmd/python/Demo/tix/bitmaps/bold.xbm @@ -0,0 +1,6 @@ +#define bold_width 16 +#define bold_height 16 +static unsigned char bold_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0xfc, 0x07, 0xfc, 0x0f, 0x18, 0x1c, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x1c, 0xf8, 0x0f, 0xf8, 0x0f, 0x18, 0x18, 0x18, 0x30, + 0x18, 0x30, 0x18, 0x38, 0xfc, 0x3f, 0xfc, 0x1f}; diff --git a/sys/src/cmd/python/Demo/tix/bitmaps/capital.xbm b/sys/src/cmd/python/Demo/tix/bitmaps/capital.xbm new file mode 100755 index 000000000..fb4e0703b --- /dev/null +++ b/sys/src/cmd/python/Demo/tix/bitmaps/capital.xbm @@ -0,0 +1,6 @@ +#define capital_width 16 +#define capital_height 16 +static unsigned char capital_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x08, 0x30, 0x0c, 0x30, 0x06, + 0x30, 0x03, 0xb0, 0x01, 0xf0, 0x00, 0xf0, 0x00, 0xf0, 0x01, 0xb0, 0x03, + 0x30, 0x07, 0x30, 0x0e, 0x30, 0x1c, 0x00, 0x00}; diff --git a/sys/src/cmd/python/Demo/tix/bitmaps/centerj.xbm b/sys/src/cmd/python/Demo/tix/bitmaps/centerj.xbm new file mode 100755 index 000000000..9d2c06483 --- /dev/null +++ b/sys/src/cmd/python/Demo/tix/bitmaps/centerj.xbm @@ -0,0 +1,6 @@ +#define centerj_width 16 +#define centerj_height 16 +static unsigned char centerj_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x3e, 0x00, 0x00, 0xc0, 0x0d, + 0x00, 0x00, 0x58, 0x77, 0x00, 0x00, 0xb0, 0x3b, 0x00, 0x00, 0xdc, 0xf7, + 0x00, 0x00, 0xf0, 0x3e, 0x00, 0x00, 0xd8, 0x7e}; diff --git a/sys/src/cmd/python/Demo/tix/bitmaps/combobox.xbm b/sys/src/cmd/python/Demo/tix/bitmaps/combobox.xbm new file mode 100755 index 000000000..f5947f57b --- /dev/null +++ b/sys/src/cmd/python/Demo/tix/bitmaps/combobox.xbm @@ -0,0 +1,14 @@ +#define combobox_width 32 +#define combobox_height 32 +static unsigned char combobox_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xfc, 0xff, 0xff, 0x3e, 0x04, 0x00, 0x80, 0x2a, 0x04, 0x00, 0x80, 0x2a, + 0x04, 0x00, 0x80, 0x2a, 0x04, 0x00, 0x80, 0x2b, 0xfc, 0xff, 0xff, 0x3e, + 0x08, 0x00, 0x00, 0x20, 0x08, 0x00, 0x00, 0x3e, 0x08, 0x00, 0x00, 0x2a, + 0x28, 0x49, 0x00, 0x2a, 0x08, 0x00, 0x00, 0x3e, 0x08, 0x00, 0x00, 0x22, + 0x08, 0x00, 0x00, 0x22, 0x28, 0x49, 0x12, 0x22, 0x08, 0x00, 0x00, 0x22, + 0x08, 0x00, 0x00, 0x22, 0x08, 0x00, 0x00, 0x22, 0x28, 0x49, 0x02, 0x22, + 0x08, 0x00, 0x00, 0x3e, 0x08, 0x00, 0x00, 0x2a, 0x08, 0x00, 0x00, 0x2a, + 0xf8, 0xff, 0xff, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; diff --git a/sys/src/cmd/python/Demo/tix/bitmaps/combobox.xpm b/sys/src/cmd/python/Demo/tix/bitmaps/combobox.xpm new file mode 100755 index 000000000..d0234ab8e --- /dev/null +++ b/sys/src/cmd/python/Demo/tix/bitmaps/combobox.xpm @@ -0,0 +1,49 @@ +/* XPM */ +static char * combobox_xpm[] = { +"50 40 6 1", +" s None c None", +". c black", +"X c white", +"o c #FFFF80808080", +"O c gray70", +"+ c #808000008080", +" ", +" ", +" ", +" .................................... XXXXXXX ", +" .ooooooooooooooooooooooooooooooooooX X . . ", +" .ooooooooooooooooooooooooooooooooooX X . . ", +" .oooo.oooooooooooooooooooooooooooooX X . . ", +" .oo.o..oo.o.oo.o.ooooooooooooooooooX X . . ", +" .o..o.o.o.oo.oo.oo.ooooooooooooooooX X ... . ", +" .oo.oo.oo.o.oo.ooo.ooooooooooooooooX X . . ", +" .ooooooooooooooooooooooooooooooooooX X . ", +" .XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX X...... ", +" ", +" ", +" ", +" XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX ", +" X............................................ ", +" X.OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOX.OOOOX. ", +" X.O+OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOX.OX OX. ", +" X.O++OOO+OO+++OOOOOOOOOOOOOOOOOOOOOOOX.X ..X. ", +" X.O+O+O+OOO+O+OOOOOOOOOOOOOOOOOOOOOOOX.OOOOX. ", +" X.O++OOO+OO+++OOOOOOOOOOOOOOOOOOOOOOOX.OOOOX. ", +" X.OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOX.XXXXX. ", +" X.O.....X..........................OOX.X .X. ", +" X.OX...XXX.X.XX.XX.................OOX.X .X. ", +" X.OX.X..X..X.XX..XX.X..............OOX.X .X. ", +" X.O.X...X..X.X...X..X..............OOX.X .X. ", +" X.OOOOOOOOOOOOOOOOOOOOOOOO+OOOOOOOOOOX.X .X. ", +" X.OOOOOOOOO+OOO+OOOOO+OOOO+OOOOOOOOOOX.X .X. ", +" X.O+++OO+OO+O+OO++O++OO+OO+OOOOOOOOOOX.X...X. ", +" X.OO+OO++OO+O+OO+OOO+OO+O++OOOOOOOOOOX.OOOOX. ", +" X.OOOOOOOO+OOOOO++OO+OOOOOOOOOOOOOOOOX.OOOOX. ", +" X.OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOX.X .X. ", +" X.OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOX.O .OX. ", +" X.OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOX.OOOOX. ", +" X.XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.XXXXX. ", +" X............................................ ", +" ", +" ", +" "}; diff --git a/sys/src/cmd/python/Demo/tix/bitmaps/drivea.xbm b/sys/src/cmd/python/Demo/tix/bitmaps/drivea.xbm new file mode 100755 index 000000000..83c636c67 --- /dev/null +++ b/sys/src/cmd/python/Demo/tix/bitmaps/drivea.xbm @@ -0,0 +1,14 @@ +#define drivea_width 32 +#define drivea_height 32 +static unsigned char drivea_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xf8, 0xff, 0xff, 0x1f, 0x08, 0x00, 0x00, 0x18, 0xa8, 0xaa, 0xaa, 0x1a, + 0x48, 0x55, 0xd5, 0x1d, 0xa8, 0xaa, 0xaa, 0x1b, 0x48, 0x55, 0x55, 0x1d, + 0xa8, 0xfa, 0xaf, 0x1a, 0xc8, 0xff, 0xff, 0x1d, 0xa8, 0xfa, 0xaf, 0x1a, + 0x48, 0x55, 0x55, 0x1d, 0xa8, 0xaa, 0xaa, 0x1a, 0x48, 0x55, 0x55, 0x1d, + 0xa8, 0xaa, 0xaa, 0x1a, 0xf8, 0xff, 0xff, 0x1f, 0xf8, 0xff, 0xff, 0x1f, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; diff --git a/sys/src/cmd/python/Demo/tix/bitmaps/drivea.xpm b/sys/src/cmd/python/Demo/tix/bitmaps/drivea.xpm new file mode 100755 index 000000000..4d274b995 --- /dev/null +++ b/sys/src/cmd/python/Demo/tix/bitmaps/drivea.xpm @@ -0,0 +1,43 @@ +/* XPM */ +static char * drivea_xpm[] = { +/* width height ncolors chars_per_pixel */ +"32 32 5 1", +/* colors */ +" s None c None", +". c #000000000000", +"X c white", +"o c #c000c000c000", +"O c #800080008000", +/* pixels */ +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" .......................... ", +" .XXXXXXXXXXXXXXXXXXXXXXXo. ", +" .XooooooooooooooooooooooO. ", +" .Xooooooooooooooooo..oooO. ", +" .Xooooooooooooooooo..oooO. ", +" .XooooooooooooooooooooooO. ", +" .Xoooooooo.......oooooooO. ", +" .Xoo...................oO. ", +" .Xoooooooo.......oooooooO. ", +" .XooooooooooooooooooooooO. ", +" .XooooooooooooooooooooooO. ", +" .XooooooooooooooooooooooO. ", +" .XooooooooooooooooooooooO. ", +" .oOOOOOOOOOOOOOOOOOOOOOOO. ", +" .......................... ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" "}; diff --git a/sys/src/cmd/python/Demo/tix/bitmaps/exit.xpm b/sys/src/cmd/python/Demo/tix/bitmaps/exit.xpm new file mode 100755 index 000000000..505a07bdf --- /dev/null +++ b/sys/src/cmd/python/Demo/tix/bitmaps/exit.xpm @@ -0,0 +1,48 @@ +/* XPM */ +static char * exit_xpm[] = { +"50 40 5 1", +" s None c None", +". c black", +"X c white", +"o c #000080800000", +"O c yellow", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ....................................... ", +" .XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX. ", +" .XoooooooooooooooooooooooooooooooooooX. ", +" .XoooooooooooooooooooooooooooooooooooX. ", +" .XoooooooooooooooooooooooOoooooooooooX. ", +" .XoooooooooooooooooooooooOOooooooooooX. ", +" .XoooooooooooooooooooooooOOOoooooooooX. ", +" .XoooooOOOOOOOOOOOOOOOOOOOOOOooooooooX. ", +" .XoooooOOOOOOOOOOOOOOOOOOOOOOOoooooooX. ", +" .XoooooOOOOOOOOOOOOOOOOOOOOOOOOooooooX. ", +" .XoooooOOOOOOOOOOOOOOOOOOOOOOOOOoooooX. ", +" .XoooooOOOOOOOOOOOOOOOOOOOOOOOOooooooX. ", +" .XoooooOOOOOOOOOOOOOOOOOOOOOOOoooooooX. ", +" .XoooooOOOOOOOOOOOOOOOOOOOOOOooooooooX. ", +" .XoooooooooooooooooooooooOOOoooooooooX. ", +" .XoooooooooooooooooooooooOOooooooooooX. ", +" .XoooooooooooooooooooooooOoooooooooooX. ", +" .XoooooooooooooooooooooooooooooooooooX. ", +" .XoooooooooooooooooooooooooooooooooooX. ", +" .XoooooooooooooooooooooooooooooooooooX. ", +" .XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX. ", +" ....................................... ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" "}; diff --git a/sys/src/cmd/python/Demo/tix/bitmaps/filebox.xbm b/sys/src/cmd/python/Demo/tix/bitmaps/filebox.xbm new file mode 100755 index 000000000..c8f7ac255 --- /dev/null +++ b/sys/src/cmd/python/Demo/tix/bitmaps/filebox.xbm @@ -0,0 +1,14 @@ +#define filebox_width 32 +#define filebox_height 32 +static unsigned char filebox_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xfc, 0xff, 0xff, 0x3f, 0x04, 0x00, 0x00, 0x20, + 0xe4, 0xff, 0xff, 0x27, 0x24, 0x00, 0x00, 0x24, 0x24, 0x00, 0x00, 0x24, + 0xe4, 0xff, 0xff, 0x27, 0x04, 0x00, 0x00, 0x20, 0xe4, 0x7f, 0xfe, 0x27, + 0x24, 0x50, 0x02, 0x25, 0x24, 0x40, 0x02, 0x24, 0x24, 0x50, 0x02, 0x25, + 0x24, 0x40, 0x02, 0x24, 0x24, 0x50, 0x02, 0x25, 0x24, 0x40, 0x02, 0x24, + 0x24, 0x50, 0x02, 0x25, 0xe4, 0x7f, 0xfe, 0x27, 0x04, 0x00, 0x00, 0x20, + 0xe4, 0xff, 0xff, 0x27, 0x24, 0x00, 0x00, 0x24, 0x24, 0x00, 0x00, 0x24, + 0xe4, 0xff, 0xff, 0x27, 0x04, 0x00, 0x00, 0x20, 0xfc, 0xff, 0xff, 0x3f, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; diff --git a/sys/src/cmd/python/Demo/tix/bitmaps/filebox.xpm b/sys/src/cmd/python/Demo/tix/bitmaps/filebox.xpm new file mode 100755 index 000000000..7377ee60e --- /dev/null +++ b/sys/src/cmd/python/Demo/tix/bitmaps/filebox.xpm @@ -0,0 +1,49 @@ +/* XPM */ +static char * filebox_xpm[] = { +"50 40 6 1", +" s None c None", +". c white", +"X c gray80", +"o c black", +"O c #FFFF80808080", +"+ c gray70", +" ", +" ", +" ", +" ............................................ ", +" .XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXo ", +" .XXooXooXoXooXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXo ", +" .XXooXooXoXooXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXo ", +" .XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXo ", +" .XXooooooooooooooooooooooooooooooooooooo.XXo ", +" .XXoOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO.XXo ", +" .XXoOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO.XXo ", +" .XX......................................XXo ", +" .XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXo ", +" .XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXo ", +" .XXoooooooooooooooo.XXXXoooooooooooooooo.XXo ", +" .XXo+++++++++++++++.XXXXo+++++++++++++++.XXo ", +" .XXo+++++++++++++++.XXXXo+++++++++++++++.XXo ", +" .XXo+++++++++++++++.XXXXo+++++++++++++++.XXo ", +" .XXo+++++++++++++++.XXXXo+++++++++++++++.XXo ", +" .XXo+++++++++++++++.XXXXo+++++++++++++++.XXo ", +" .XXo+++++++++++++++.XXXXo+++++++++++++++.XXo ", +" .XXo+++++++++++++++.XXXXo+++++++++++++++.XXo ", +" .XXo+++++++++++++++.XXXXo+++++++++++++++.XXo ", +" .XXo+++++++++++++++.XXXXo+++++++++++++++.XXo ", +" .XXo+++++++++++++++.XXXXo+++++++++++++++.XXo ", +" .XXo+++++++++++++++.XXXXo+++++++++++++++.XXo ", +" .XX.................XXXX.................XXo ", +" .XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXo ", +" .XXooXooXoXooXoXXXXXXXXXXXXXXXXXXXXXXXXXXXXo ", +" .XXooXooXoXooXoXooXXXXXXXXXXXXXXXXXXXXXXXXXo ", +" .XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXo ", +" .XXoooooooooooooooooooooooooooooooooooooo.Xo ", +" .XXoOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO.Xo ", +" .XXoOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO.Xo ", +" .XX.......................................Xo ", +" .XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXo ", +" .ooooooooooooooooooooooooooooooooooooooooooo ", +" ", +" ", +" "}; diff --git a/sys/src/cmd/python/Demo/tix/bitmaps/italic.xbm b/sys/src/cmd/python/Demo/tix/bitmaps/italic.xbm new file mode 100755 index 000000000..169c3cb75 --- /dev/null +++ b/sys/src/cmd/python/Demo/tix/bitmaps/italic.xbm @@ -0,0 +1,6 @@ +#define italic_width 16 +#define italic_height 16 +static unsigned char italic_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x80, 0x3f, 0x00, 0x06, 0x00, 0x06, + 0x00, 0x03, 0x00, 0x03, 0x80, 0x01, 0x80, 0x01, 0xc0, 0x00, 0xc0, 0x00, + 0x60, 0x00, 0x60, 0x00, 0xfc, 0x01, 0xfc, 0x01}; diff --git a/sys/src/cmd/python/Demo/tix/bitmaps/justify.xbm b/sys/src/cmd/python/Demo/tix/bitmaps/justify.xbm new file mode 100755 index 000000000..bba660ace --- /dev/null +++ b/sys/src/cmd/python/Demo/tix/bitmaps/justify.xbm @@ -0,0 +1,6 @@ +#define justify_width 16 +#define justify_height 16 +static unsigned char justify_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xec, 0xdb, 0x00, 0x00, 0x7c, 0xdb, + 0x00, 0x00, 0xbc, 0xf7, 0x00, 0x00, 0xdc, 0xde, 0x00, 0x00, 0x6c, 0xdf, + 0x00, 0x00, 0x6c, 0xef, 0x00, 0x00, 0xdc, 0xdf}; diff --git a/sys/src/cmd/python/Demo/tix/bitmaps/leftj.xbm b/sys/src/cmd/python/Demo/tix/bitmaps/leftj.xbm new file mode 100755 index 000000000..5f8e006f4 --- /dev/null +++ b/sys/src/cmd/python/Demo/tix/bitmaps/leftj.xbm @@ -0,0 +1,6 @@ +#define leftj_width 16 +#define leftj_height 16 +static unsigned char leftj_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcc, 0x6d, 0x00, 0x00, 0xdc, 0x01, + 0x00, 0x00, 0xec, 0x0e, 0x00, 0x00, 0xfc, 0x7e, 0x00, 0x00, 0xdc, 0x03, + 0x00, 0x00, 0x6c, 0x3b, 0x00, 0x00, 0x6c, 0x1f}; diff --git a/sys/src/cmd/python/Demo/tix/bitmaps/netw.xbm b/sys/src/cmd/python/Demo/tix/bitmaps/netw.xbm new file mode 100755 index 000000000..a684d65d4 --- /dev/null +++ b/sys/src/cmd/python/Demo/tix/bitmaps/netw.xbm @@ -0,0 +1,14 @@ +#define netw_width 32 +#define netw_height 32 +static unsigned char netw_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x7f, 0x00, 0x00, 0x02, 0x40, + 0x00, 0x00, 0xfa, 0x5f, 0x00, 0x00, 0x0a, 0x50, 0x00, 0x00, 0x0a, 0x52, + 0x00, 0x00, 0x0a, 0x52, 0x00, 0x00, 0x8a, 0x51, 0x00, 0x00, 0x0a, 0x50, + 0x00, 0x00, 0x4a, 0x50, 0x00, 0x00, 0x0a, 0x50, 0x00, 0x00, 0x0a, 0x50, + 0x00, 0x00, 0xfa, 0x5f, 0x00, 0x00, 0x02, 0x40, 0xfe, 0x7f, 0x52, 0x55, + 0x02, 0x40, 0xaa, 0x6a, 0xfa, 0x5f, 0xfe, 0x7f, 0x0a, 0x50, 0xfe, 0x7f, + 0x0a, 0x52, 0x80, 0x00, 0x0a, 0x52, 0x80, 0x00, 0x8a, 0x51, 0x80, 0x00, + 0x0a, 0x50, 0x80, 0x00, 0x4a, 0x50, 0x80, 0x00, 0x0a, 0x50, 0xe0, 0x03, + 0x0a, 0x50, 0x20, 0x02, 0xfa, 0xdf, 0x3f, 0x03, 0x02, 0x40, 0xa0, 0x02, + 0x52, 0x55, 0xe0, 0x03, 0xaa, 0x6a, 0x00, 0x00, 0xfe, 0x7f, 0x00, 0x00, + 0xfe, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; diff --git a/sys/src/cmd/python/Demo/tix/bitmaps/netw.xpm b/sys/src/cmd/python/Demo/tix/bitmaps/netw.xpm new file mode 100755 index 000000000..fff6593bc --- /dev/null +++ b/sys/src/cmd/python/Demo/tix/bitmaps/netw.xpm @@ -0,0 +1,45 @@ +/* XPM */ +static char * netw_xpm[] = { +/* width height ncolors chars_per_pixel */ +"32 32 7 1", +/* colors */ +" s None c None", +". c #000000000000", +"X c white", +"o c #c000c000c000", +"O c #404040", +"+ c blue", +"@ c red", +/* pixels */ +" ", +" .............. ", +" .XXXXXXXXXXXX. ", +" .XooooooooooO. ", +" .Xo.......XoO. ", +" .Xo.++++o+XoO. ", +" .Xo.++++o+XoO. ", +" .Xo.++oo++XoO. ", +" .Xo.++++++XoO. ", +" .Xo.+o++++XoO. ", +" .Xo.++++++XoO. ", +" .Xo.XXXXXXXoO. ", +" .XooooooooooO. ", +" .Xo@ooo....oO. ", +" .............. .XooooooooooO. ", +" .XXXXXXXXXXXX. .XooooooooooO. ", +" .XooooooooooO. .OOOOOOOOOOOO. ", +" .Xo.......XoO. .............. ", +" .Xo.++++o+XoO. @ ", +" .Xo.++++o+XoO. @ ", +" .Xo.++oo++XoO. @ ", +" .Xo.++++++XoO. @ ", +" .Xo.+o++++XoO. @ ", +" .Xo.++++++XoO. ..... ", +" .Xo.XXXXXXXoO. .XXX. ", +" .XooooooooooO.@@@@@@.X O. ", +" .Xo@ooo....oO. .OOO. ", +" .XooooooooooO. ..... ", +" .XooooooooooO. ", +" .OOOOOOOOOOOO. ", +" .............. ", +" "}; diff --git a/sys/src/cmd/python/Demo/tix/bitmaps/optmenu.xpm b/sys/src/cmd/python/Demo/tix/bitmaps/optmenu.xpm new file mode 100755 index 000000000..63bab8129 --- /dev/null +++ b/sys/src/cmd/python/Demo/tix/bitmaps/optmenu.xpm @@ -0,0 +1,48 @@ +/* XPM */ +static char * optmenu_xpm[] = { +"50 40 5 1", +" s None c None", +". c white", +"X c gray80", +"o c gray50", +"O c black", +" ", +" ", +" .............................. ", +" .XXXXXXXXXXXXXXXXXXXXXXXXXXXXo ", +" .XXXXXXXXXXXXXXXXXXXXXXXXXXXXo ", +" .XXXXXXXXXXXXXXXXXXXXXXXXXXXXo ", +" .XXXOXOXXOXXOXXXXOOXXXXXXXXXXo ", +" .XXXOXOXXOXOXXXOXXOXXXXXXXXXXo ", +" .XXXXOXXOXXOXXXOXXXOXXXXXXXXXo ", +" .XXXXOXXXOXXOOXXOXOXXXXXXXXXXo ", +" .XXXXXXXXXXXXXXXXXXXXXXXXXXXXo ", +" .XXXXXXXXXXXXXXXXXXXXXXXXXXXXo.............o ", +" .............................o o ", +" ..XXXOXXXXXOXXXXXXXXOXXXXXXXOo o ", +" ..XXOXOXOXXOXOXXXOXXOXXXXXXXOo ...... o ", +" ..XXXOXXXOXXOXXXOXXXOXXXXXXXOo . o o ", +" ..XXOXXXOXXXOXOXXOXXOXXXXXXXOo . o o ", +" ..XXXXXXXXXXXXXXXXXXXXXXXXXXOo .ooooo o ", +" .OOOOOOOOOOOOOOOOOOOOOOOOOOOOo o ", +" .XXXXXXXXXXXXXXXXXXXXXXXXXXXXo o ", +" .XXXXXXXXXXXXXXXXXXXXXXXXXXXXooooooooooooooo ", +" .XXXXOXXXXXOXXXXXXXXXXXXXXXXXo ", +" .XXXOXXXXXXXXXOXXXXXXXXXXXXXXo ", +" .XXXXOXXOXXOXOXOXXXXXXXXXXXXXo ", +" .XXXXXOXXOXOXXXXXXXXXXXXXXXXXo ", +" .XXXXXXXXXXXXXOXXXXXXXXXXXXXXo ", +" .XXXXXXXXXXXXXXXXXXXXXXXXXXXXo ", +" .XXXXXXXXXXXXXXXXXXXXXXXXXXXXo ", +" .XXXOXOXXXXXXXOXOXXXXXOXXXXXXo ", +" .XXXXXOXOXOXXOXXXXXOXXOXXXXXXo ", +" .XXXXOXXOXOXOXXXOXOXOXXOXXXXXo ", +" .XXXOXXXXOXXOXXXOXXOXXXXOXXXXo ", +" .XXXXXXXXXXXXXXXXXXXXXXXXXXXXo ", +" .XXXXXXXXXXXXXXXXXXXXXXXXXXXXo ", +" .XXXXXXXXXXXXXXXXXXXXXXXXXXXXo ", +" oooooooooooooooooooooooooooooo ", +" ", +" ", +" ", +" "}; diff --git a/sys/src/cmd/python/Demo/tix/bitmaps/rightj.xbm b/sys/src/cmd/python/Demo/tix/bitmaps/rightj.xbm new file mode 100755 index 000000000..1d438e009 --- /dev/null +++ b/sys/src/cmd/python/Demo/tix/bitmaps/rightj.xbm @@ -0,0 +1,6 @@ +#define rightj_width 16 +#define rightj_height 16 +static unsigned char rightj_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xdb, 0x00, 0x00, 0x70, 0xdb, + 0x00, 0x00, 0x00, 0xef, 0x00, 0x00, 0xd8, 0xde, 0x00, 0x00, 0xc0, 0xdd, + 0x00, 0x00, 0xa0, 0xef, 0x00, 0x00, 0xd8, 0xde}; diff --git a/sys/src/cmd/python/Demo/tix/bitmaps/select.xpm b/sys/src/cmd/python/Demo/tix/bitmaps/select.xpm new file mode 100755 index 000000000..392e5a083 --- /dev/null +++ b/sys/src/cmd/python/Demo/tix/bitmaps/select.xpm @@ -0,0 +1,52 @@ +/* XPM */ +static char * select_xpm[] = { +"50 40 9 1", +" s None c None", +". c black", +"X c gray95", +"o c gray50", +"O c gray70", +"+ c navy", +"@ c #000080800000", +"# c #808000000000", +"$ c white", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" .............................................. ", +" .XXXXXXXXXXooooooooooooXXXXXXXXXXXoXXXXXXXXXX. ", +" .X ooOOOOOOOOOOXX oX o. ", +" .X ooOOOOOOOOOOXX oX o. ", +" .X ++++ ooOOOOOOOOOOXX ... oX @ o. ", +" .X +++++ ooOOOOOOOOOOXX . . oX @@@ o. ", +" .X +++ + ooOOOOOOOOOOXX . . oX @ @ o. ", +" .X + + ooOO#####OOOXX . . oX @ @ o. ", +" .X + + ooOO#OOO##OOXX . oX @ @ o. ", +" .X + + ooO##OOOO##OXX . oX @ @ o. ", +" .X ++ ++ ooO###OOO#OOXX . oX @ @ o. ", +" .X +++++++ ooO#######OOXX . oX @ @ o. ", +" .X + + ooO##O#OO#OOXX . oX @ @ o. ", +" .X + ++ ooO##OOOOO#OXX . . oX @ @ o. ", +" .X + + ooOO#OOOOO#OXX . . oX @ @@ o. ", +" .X + ++ ooOO#OOOOO#OXX .... oX @@@@@ o. ", +" .X ooOO######OOXX oX o. ", +" .X ooOOOOOOOOOOXX $oX o. ", +" .XoooooooooooXXXXXXXXXXXoooooooooooXooooooooo. ", +" .............................................. ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" "}; diff --git a/sys/src/cmd/python/Demo/tix/bitmaps/tix.gif b/sys/src/cmd/python/Demo/tix/bitmaps/tix.gif Binary files differnew file mode 100755 index 000000000..e7d51a086 --- /dev/null +++ b/sys/src/cmd/python/Demo/tix/bitmaps/tix.gif diff --git a/sys/src/cmd/python/Demo/tix/bitmaps/underline.xbm b/sys/src/cmd/python/Demo/tix/bitmaps/underline.xbm new file mode 100755 index 000000000..f07bb4605 --- /dev/null +++ b/sys/src/cmd/python/Demo/tix/bitmaps/underline.xbm @@ -0,0 +1,6 @@ +#define underline_width 16 +#define underline_height 16 +static unsigned char underline_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x1c, 0x38, 0x1c, + 0x30, 0x0c, 0x30, 0x0c, 0x30, 0x0c, 0x30, 0x0c, 0x30, 0x0c, 0x70, 0x0e, + 0xf0, 0x0f, 0xe0, 0x07, 0x00, 0x00, 0xf8, 0x1f}; diff --git a/sys/src/cmd/python/Demo/tix/grid.py b/sys/src/cmd/python/Demo/tix/grid.py new file mode 100644 index 000000000..07ca87f8e --- /dev/null +++ b/sys/src/cmd/python/Demo/tix/grid.py @@ -0,0 +1,28 @@ +### +import Tix as tk +from pprint import pprint + +r= tk.Tk() +r.title("test") + +l=tk.Label(r, name="a_label") +l.pack() + +class MyGrid(tk.Grid): + def __init__(self, *args, **kwargs): + kwargs['editnotify']= self.editnotify + tk.Grid.__init__(self, *args, **kwargs) + def editnotify(self, x, y): + return True + +g = MyGrid(r, name="a_grid", +selectunit="cell") +g.pack(fill=tk.BOTH) +for x in xrange(5): + for y in xrange(5): + g.set(x,y,text=str((x,y))) + +c = tk.Button(r, text="Close", command=r.destroy) +c.pack() + +tk.mainloop() diff --git a/sys/src/cmd/python/Demo/tix/samples/Balloon.py b/sys/src/cmd/python/Demo/tix/samples/Balloon.py new file mode 100755 index 000000000..e67d899fe --- /dev/null +++ b/sys/src/cmd/python/Demo/tix/samples/Balloon.py @@ -0,0 +1,68 @@ +# -*-mode: python; fill-column: 75; tab-width: 8; coding: iso-latin-1-unix -*- +# +# $Id: Balloon.py 36560 2004-07-18 06:16:08Z tim_one $ +# +# Tix Demostration Program +# +# This sample program is structured in such a way so that it can be +# executed from the Tix demo program "tixwidgets.py": it must have a +# procedure called "RunSample". It should also have the "if" statment +# at the end of this file so that it can be run as a standalone +# program. + +# This file demonstrates the use of the tixBalloon widget, which provides +# a interesting way to give help tips about elements in your user interface. +# Your can display the help message in a "balloon" and a status bar widget. +# + +import Tix + +TCL_ALL_EVENTS = 0 + +def RunSample (root): + balloon = DemoBalloon(root) + balloon.mainloop() + balloon.destroy() + +class DemoBalloon: + def __init__(self, w): + self.root = w + self.exit = -1 + + z = w.winfo_toplevel() + z.wm_protocol("WM_DELETE_WINDOW", lambda self=self: self.quitcmd()) + + status = Tix.Label(w, width=40, relief=Tix.SUNKEN, bd=1) + status.pack(side=Tix.BOTTOM, fill=Tix.Y, padx=2, pady=1) + + # Create two mysterious widgets that need balloon help + button1 = Tix.Button(w, text='Something Unexpected', + command=self.quitcmd) + button2 = Tix.Button(w, text='Something Else Unexpected') + button2['command'] = lambda w=button2: w.destroy() + button1.pack(side=Tix.TOP, expand=1) + button2.pack(side=Tix.TOP, expand=1) + + # Create the balloon widget and associate it with the widgets that we want + # to provide tips for: + b = Tix.Balloon(w, statusbar=status) + + b.bind_widget(button1, balloonmsg='Close Window', + statusmsg='Press this button to close this window') + b.bind_widget(button2, balloonmsg='Self-destruct button', + statusmsg='Press this button and it will destroy itself') + + def quitcmd (self): + self.exit = 0 + + def mainloop(self): + foundEvent = 1 + while self.exit < 0 and foundEvent > 0: + foundEvent = self.root.tk.dooneevent(TCL_ALL_EVENTS) + + def destroy (self): + self.root.destroy() + +if __name__ == '__main__': + root = Tix.Tk() + RunSample(root) diff --git a/sys/src/cmd/python/Demo/tix/samples/BtnBox.py b/sys/src/cmd/python/Demo/tix/samples/BtnBox.py new file mode 100755 index 000000000..7b274971d --- /dev/null +++ b/sys/src/cmd/python/Demo/tix/samples/BtnBox.py @@ -0,0 +1,44 @@ +# -*-mode: python; fill-column: 75; tab-width: 8; coding: iso-latin-1-unix -*- +# +# $Id: BtnBox.py 36560 2004-07-18 06:16:08Z tim_one $ +# +# Tix Demostration Program +# +# This sample program is structured in such a way so that it can be +# executed from the Tix demo program "tixwidgets.py": it must have a +# procedure called "RunSample". It should also have the "if" statment +# at the end of this file so that it can be run as a standalone +# program. + +# This file demonstrates the use of the tixButtonBox widget, which is a +# group of TK buttons. You can use it to manage the buttons in a dialog box, +# for example. +# + +import Tix + +def RunSample(w): + # Create the label on the top of the dialog box + # + top = Tix.Label(w, padx=20, pady=10, bd=1, relief=Tix.RAISED, + anchor=Tix.CENTER, text='This dialog box is\n a demonstration of the\n tixButtonBox widget') + + # Create the button box and add a few buttons in it. Set the + # -width of all the buttons to the same value so that they + # appear in the same size. + # + # Note that the -text, -underline, -command and -width options are all + # standard options of the button widgets. + # + box = Tix.ButtonBox(w, orientation=Tix.HORIZONTAL) + box.add('ok', text='OK', underline=0, width=5, + command=lambda w=w: w.destroy()) + box.add('close', text='Cancel', underline=0, width=5, + command=lambda w=w: w.destroy()) + box.pack(side=Tix.BOTTOM, fill=Tix.X) + top.pack(side=Tix.TOP, fill=Tix.BOTH, expand=1) + +if __name__ == '__main__': + root = Tix.Tk() + RunSample(root) + root.mainloop() diff --git a/sys/src/cmd/python/Demo/tix/samples/CmpImg.py b/sys/src/cmd/python/Demo/tix/samples/CmpImg.py new file mode 100755 index 000000000..943dab063 --- /dev/null +++ b/sys/src/cmd/python/Demo/tix/samples/CmpImg.py @@ -0,0 +1,196 @@ +# -*-mode: python; fill-column: 75; tab-width: 8; coding: iso-latin-1-unix -*- +# +# $Id: CmpImg.py 36560 2004-07-18 06:16:08Z tim_one $ +# +# Tix Demostration Program +# +# This sample program is structured in such a way so that it can be +# executed from the Tix demo program "tixwidgets.py": it must have a +# procedure called "RunSample". It should also have the "if" statment +# at the end of this file so that it can be run as a standalone +# program. + +# This file demonstrates the use of the compound images: it uses compound +# images to display a text string together with a pixmap inside +# buttons +# + +import Tix + +network_pixmap = """/* XPM */ +static char * netw_xpm[] = { +/* width height ncolors chars_per_pixel */ +"32 32 7 1", +/* colors */ +" s None c None", +". c #000000000000", +"X c white", +"o c #c000c000c000", +"O c #404040", +"+ c blue", +"@ c red", +/* pixels */ +" ", +" .............. ", +" .XXXXXXXXXXXX. ", +" .XooooooooooO. ", +" .Xo.......XoO. ", +" .Xo.++++o+XoO. ", +" .Xo.++++o+XoO. ", +" .Xo.++oo++XoO. ", +" .Xo.++++++XoO. ", +" .Xo.+o++++XoO. ", +" .Xo.++++++XoO. ", +" .Xo.XXXXXXXoO. ", +" .XooooooooooO. ", +" .Xo@ooo....oO. ", +" .............. .XooooooooooO. ", +" .XXXXXXXXXXXX. .XooooooooooO. ", +" .XooooooooooO. .OOOOOOOOOOOO. ", +" .Xo.......XoO. .............. ", +" .Xo.++++o+XoO. @ ", +" .Xo.++++o+XoO. @ ", +" .Xo.++oo++XoO. @ ", +" .Xo.++++++XoO. @ ", +" .Xo.+o++++XoO. @ ", +" .Xo.++++++XoO. ..... ", +" .Xo.XXXXXXXoO. .XXX. ", +" .XooooooooooO.@@@@@@.X O. ", +" .Xo@ooo....oO. .OOO. ", +" .XooooooooooO. ..... ", +" .XooooooooooO. ", +" .OOOOOOOOOOOO. ", +" .............. ", +" "}; +""" + +hard_disk_pixmap = """/* XPM */ +static char * drivea_xpm[] = { +/* width height ncolors chars_per_pixel */ +"32 32 5 1", +/* colors */ +" s None c None", +". c #000000000000", +"X c white", +"o c #c000c000c000", +"O c #800080008000", +/* pixels */ +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" .......................... ", +" .XXXXXXXXXXXXXXXXXXXXXXXo. ", +" .XooooooooooooooooooooooO. ", +" .Xooooooooooooooooo..oooO. ", +" .Xooooooooooooooooo..oooO. ", +" .XooooooooooooooooooooooO. ", +" .Xoooooooo.......oooooooO. ", +" .Xoo...................oO. ", +" .Xoooooooo.......oooooooO. ", +" .XooooooooooooooooooooooO. ", +" .XooooooooooooooooooooooO. ", +" .XooooooooooooooooooooooO. ", +" .XooooooooooooooooooooooO. ", +" .oOOOOOOOOOOOOOOOOOOOOOOO. ", +" .......................... ", +" ", +" ", +" ", +" ", +" ", +" ", +" ", +" "}; +""" + +network_bitmap = """ +#define netw_width 32 +#define netw_height 32 +static unsigned char netw_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x7f, 0x00, 0x00, 0x02, 0x40, + 0x00, 0x00, 0xfa, 0x5f, 0x00, 0x00, 0x0a, 0x50, 0x00, 0x00, 0x0a, 0x52, + 0x00, 0x00, 0x0a, 0x52, 0x00, 0x00, 0x8a, 0x51, 0x00, 0x00, 0x0a, 0x50, + 0x00, 0x00, 0x4a, 0x50, 0x00, 0x00, 0x0a, 0x50, 0x00, 0x00, 0x0a, 0x50, + 0x00, 0x00, 0xfa, 0x5f, 0x00, 0x00, 0x02, 0x40, 0xfe, 0x7f, 0x52, 0x55, + 0x02, 0x40, 0xaa, 0x6a, 0xfa, 0x5f, 0xfe, 0x7f, 0x0a, 0x50, 0xfe, 0x7f, + 0x0a, 0x52, 0x80, 0x00, 0x0a, 0x52, 0x80, 0x00, 0x8a, 0x51, 0x80, 0x00, + 0x0a, 0x50, 0x80, 0x00, 0x4a, 0x50, 0x80, 0x00, 0x0a, 0x50, 0xe0, 0x03, + 0x0a, 0x50, 0x20, 0x02, 0xfa, 0xdf, 0x3f, 0x03, 0x02, 0x40, 0xa0, 0x02, + 0x52, 0x55, 0xe0, 0x03, 0xaa, 0x6a, 0x00, 0x00, 0xfe, 0x7f, 0x00, 0x00, + 0xfe, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; +""" + +hard_disk_bitmap = """ +#define drivea_width 32 +#define drivea_height 32 +static unsigned char drivea_bits[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xf8, 0xff, 0xff, 0x1f, 0x08, 0x00, 0x00, 0x18, 0xa8, 0xaa, 0xaa, 0x1a, + 0x48, 0x55, 0xd5, 0x1d, 0xa8, 0xaa, 0xaa, 0x1b, 0x48, 0x55, 0x55, 0x1d, + 0xa8, 0xfa, 0xaf, 0x1a, 0xc8, 0xff, 0xff, 0x1d, 0xa8, 0xfa, 0xaf, 0x1a, + 0x48, 0x55, 0x55, 0x1d, 0xa8, 0xaa, 0xaa, 0x1a, 0x48, 0x55, 0x55, 0x1d, + 0xa8, 0xaa, 0xaa, 0x1a, 0xf8, 0xff, 0xff, 0x1f, 0xf8, 0xff, 0xff, 0x1f, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; +""" + +def RunSample(w): + w.img0 = Tix.Image('pixmap', data=network_pixmap) + if not w.img0: + w.img0 = Tix.Image('bitmap', data=network_bitmap) + w.img1 = Tix.Image('pixmap', data=hard_disk_pixmap) + if not w.img0: + w.img1 = Tix.Image('bitmap', data=hard_disk_bitmap) + + hdd = Tix.Button(w, padx=4, pady=1, width=120) + net = Tix.Button(w, padx=4, pady=1, width=120) + + # Create the first image: we create a line, then put a string, + # a space and a image into this line, from left to right. + # The result: we have a one-line image that consists of three + # individual items + # + # The tk.calls should be methods in Tix ... + w.hdd_img = Tix.Image('compound', window=hdd) + w.hdd_img.tk.call(str(w.hdd_img), 'add', 'line') + w.hdd_img.tk.call(str(w.hdd_img), 'add', 'text', '-text', 'Hard Disk', + '-underline', '0') + w.hdd_img.tk.call(str(w.hdd_img), 'add', 'space', '-width', '7') + w.hdd_img.tk.call(str(w.hdd_img), 'add', 'image', '-image', w.img1) + + # Put this image into the first button + # + hdd['image'] = w.hdd_img + + # Next button + w.net_img = Tix.Image('compound', window=net) + w.net_img.tk.call(str(w.net_img), 'add', 'line') + w.net_img.tk.call(str(w.net_img), 'add', 'text', '-text', 'Network', + '-underline', '0') + w.net_img.tk.call(str(w.net_img), 'add', 'space', '-width', '7') + w.net_img.tk.call(str(w.net_img), 'add', 'image', '-image', w.img0) + + # Put this image into the first button + # + net['image'] = w.net_img + + close = Tix.Button(w, pady=1, text='Close', + command=lambda w=w: w.destroy()) + + hdd.pack(side=Tix.LEFT, padx=10, pady=10, fill=Tix.Y, expand=1) + net.pack(side=Tix.LEFT, padx=10, pady=10, fill=Tix.Y, expand=1) + close.pack(side=Tix.LEFT, padx=10, pady=10, fill=Tix.Y, expand=1) + +if __name__ == '__main__': + root = Tix.Tk() + RunSample(root) + root.mainloop() diff --git a/sys/src/cmd/python/Demo/tix/samples/ComboBox.py b/sys/src/cmd/python/Demo/tix/samples/ComboBox.py new file mode 100755 index 000000000..9e052e367 --- /dev/null +++ b/sys/src/cmd/python/Demo/tix/samples/ComboBox.py @@ -0,0 +1,102 @@ +# -*-mode: python; fill-column: 75; tab-width: 8; coding: iso-latin-1-unix -*- +# +# $Id: ComboBox.py 36560 2004-07-18 06:16:08Z tim_one $ +# +# Tix Demostration Program +# +# This sample program is structured in such a way so that it can be +# executed from the Tix demo program "tixwidgets.py": it must have a +# procedure called "RunSample". It should also have the "if" statment +# at the end of this file so that it can be run as a standalone +# program. + +# This file demonstrates the use of the tixComboBox widget, which is close +# to the MS Window Combo Box control. +# +import Tix + +def RunSample(w): + global demo_month, demo_year + + top = Tix.Frame(w, bd=1, relief=Tix.RAISED) + + demo_month = Tix.StringVar() + demo_year = Tix.StringVar() + + # $w.top.a is a drop-down combo box. It is not editable -- who wants + # to invent new months? + # + # [Hint] The -options switch sets the options of the subwidgets. + # [Hint] We set the label.width subwidget option of both comboboxes to + # be 10 so that their labels appear to be aligned. + # + a = Tix.ComboBox(top, label="Month: ", dropdown=1, + command=select_month, editable=0, variable=demo_month, + options='listbox.height 6 label.width 10 label.anchor e') + + # $w.top.b is a non-drop-down combo box. It is not editable: we provide + # four choices for the user, but he can enter an alternative year if he + # wants to. + # + # [Hint] Use the padY and anchor options of the label subwidget to + # align the label with the entry subwidget. + # [Hint] Notice that you should use padY (the NAME of the option) and not + # pady (the SWITCH of the option). + # + b = Tix.ComboBox(top, label="Year: ", dropdown=0, + command=select_year, editable=1, variable=demo_year, + options='listbox.height 4 label.padY 5 label.width 10 label.anchor ne') + + a.pack(side=Tix.TOP, anchor=Tix.W) + b.pack(side=Tix.TOP, anchor=Tix.W) + + a.insert(Tix.END, 'January') + a.insert(Tix.END, 'February') + a.insert(Tix.END, 'March') + a.insert(Tix.END, 'April') + a.insert(Tix.END, 'May') + a.insert(Tix.END, 'June') + a.insert(Tix.END, 'July') + a.insert(Tix.END, 'August') + a.insert(Tix.END, 'September') + a.insert(Tix.END, 'October') + a.insert(Tix.END, 'November') + a.insert(Tix.END, 'December') + + b.insert(Tix.END, '1992') + b.insert(Tix.END, '1993') + b.insert(Tix.END, '1994') + b.insert(Tix.END, '1995') + b.insert(Tix.END, '1996') + + # Use "tixSetSilent" to set the values of the combo box if you + # don't want your -command procedures (cbx:select_month and + # cbx:select_year) to be called. + # + a.set_silent('January') + b.set_silent('1995') + + box = Tix.ButtonBox(w, orientation=Tix.HORIZONTAL) + box.add('ok', text='Ok', underline=0, width=6, + command=lambda w=w: ok_command(w)) + box.add('cancel', text='Cancel', underline=0, width=6, + command=lambda w=w: w.destroy()) + box.pack(side=Tix.BOTTOM, fill=Tix.X) + top.pack(side=Tix.TOP, fill=Tix.BOTH, expand=1) + +def select_month(event=None): + # tixDemo:Status "Month = %s" % demo_month.get() + pass + +def select_year(event=None): + # tixDemo:Status "Year = %s" % demo_year.get() + pass + +def ok_command(w): + # tixDemo:Status "Month = %s, Year= %s" % (demo_month.get(), demo_year.get()) + w.destroy() + +if __name__ == '__main__': + root = Tix.Tk() + RunSample(root) + root.mainloop() diff --git a/sys/src/cmd/python/Demo/tix/samples/Control.py b/sys/src/cmd/python/Demo/tix/samples/Control.py new file mode 100755 index 000000000..5d846326d --- /dev/null +++ b/sys/src/cmd/python/Demo/tix/samples/Control.py @@ -0,0 +1,122 @@ +# -*-mode: python; fill-column: 75; tab-width: 8; coding: iso-latin-1-unix -*- +# +# $Id: Control.py 36560 2004-07-18 06:16:08Z tim_one $ +# +# Tix Demostration Program +# +# This sample program is structured in such a way so that it can be +# executed from the Tix demo program "tixwidgets.py": it must have a +# procedure called "RunSample". It should also have the "if" statment +# at the end of this file so that it can be run as a standalone +# program. + +# This file demonstrates the use of the tixControl widget -- it is an +# entry widget with up/down arrow buttons. You can use the arrow buttons +# to adjust the value inside the entry widget. +# +# This example program uses three Control widgets. One lets you select +# integer values; one lets you select floating point values and the last +# one lets you select a few names. + +import Tix + +TCL_ALL_EVENTS = 0 + +def RunSample (root): + control = DemoControl(root) + control.mainloop() + control.destroy() + +class DemoControl: + def __init__(self, w): + self.root = w + self.exit = -1 + + global demo_maker, demo_thrust, demo_num_engines + + demo_maker = Tix.StringVar() + demo_thrust = Tix.DoubleVar() + demo_num_engines = Tix.IntVar() + demo_maker.set('P&W') + demo_thrust.set(20000.0) + demo_num_engines.set(2) + + top = Tix.Frame(w, bd=1, relief=Tix.RAISED) + + # $w.top.a allows only integer values + # + # [Hint] The -options switch sets the options of the subwidgets. + # [Hint] We set the label.width subwidget option of the Controls to + # be 16 so that their labels appear to be aligned. + # + a = Tix.Control(top, label='Number of Engines: ', integer=1, + variable=demo_num_engines, min=1, max=4, + options='entry.width 10 label.width 20 label.anchor e') + + b = Tix.Control(top, label='Thrust: ', integer=0, + min='10000.0', max='60000.0', step=500, + variable=demo_thrust, + options='entry.width 10 label.width 20 label.anchor e') + + c = Tix.Control(top, label='Engine Maker: ', value='P&W', + variable=demo_maker, + options='entry.width 10 label.width 20 label.anchor e') + + # We can't define these in the init because the widget 'c' doesn't + # exist yet and we need to reference it + c['incrcmd'] = lambda w=c: adjust_maker(w, 1) + c['decrcmd'] = lambda w=c: adjust_maker(w, -1) + c['validatecmd'] = lambda w=c: validate_maker(w) + + a.pack(side=Tix.TOP, anchor=Tix.W) + b.pack(side=Tix.TOP, anchor=Tix.W) + c.pack(side=Tix.TOP, anchor=Tix.W) + + box = Tix.ButtonBox(w, orientation=Tix.HORIZONTAL) + box.add('ok', text='Ok', underline=0, width=6, + command=self.okcmd) + box.add('cancel', text='Cancel', underline=0, width=6, + command=self.quitcmd) + box.pack(side=Tix.BOTTOM, fill=Tix.X) + top.pack(side=Tix.TOP, fill=Tix.BOTH, expand=1) + + def okcmd (self): + # tixDemo:Status "Selected %d of %s engines each of thrust %d", (demo_num_engines.get(), demo_maker.get(), demo_thrust.get()) + self.quitcmd() + + def quitcmd (self): + self.exit = 0 + + def mainloop(self): + while self.exit < 0: + self.root.tk.dooneevent(TCL_ALL_EVENTS) + + def destroy (self): + self.root.destroy() + +maker_list = ['P&W', 'GE', 'Rolls Royce'] + +def adjust_maker(w, inc): + i = maker_list.index(demo_maker.get()) + i = i + inc + if i >= len(maker_list): + i = 0 + elif i < 0: + i = len(maker_list) - 1 + + # In Tcl/Tix we should return the string maker_list[i]. We can't + # do that in Tkinter so we set the global variable. (This works). + demo_maker.set(maker_list[i]) + +def validate_maker(w): + try: + i = maker_list.index(demo_maker.get()) + except ValueError: + # Works here though. Why ? Beats me. + return maker_list[0] + # Works here though. Why ? Beats me. + return maker_list[i] + +if __name__ == '__main__': + root = Tix.Tk() + RunSample(root) diff --git a/sys/src/cmd/python/Demo/tix/samples/DirList.py b/sys/src/cmd/python/Demo/tix/samples/DirList.py new file mode 100755 index 000000000..f88b120b6 --- /dev/null +++ b/sys/src/cmd/python/Demo/tix/samples/DirList.py @@ -0,0 +1,131 @@ +# -*-mode: python; fill-column: 75; tab-width: 8; coding: iso-latin-1-unix -*- +# +# $Id: DirList.py 36560 2004-07-18 06:16:08Z tim_one $ +# +# Tix Demostration Program +# +# This sample program is structured in such a way so that it can be +# executed from the Tix demo program "tixwidgets.py": it must have a +# procedure called "RunSample". It should also have the "if" statment +# at the end of this file so that it can be run as a standalone +# program using tixwish. + +# This file demonstrates the use of the tixDirList widget -- you can +# use it for the user to select a directory. For example, an installation +# program can use the tixDirList widget to ask the user to select the +# installation directory for an application. +# + +import Tix, os, copy +from Tkconstants import * + +TCL_ALL_EVENTS = 0 + +def RunSample (root): + dirlist = DemoDirList(root) + dirlist.mainloop() + dirlist.destroy() + +class DemoDirList: + def __init__(self, w): + self.root = w + self.exit = -1 + + z = w.winfo_toplevel() + z.wm_protocol("WM_DELETE_WINDOW", lambda self=self: self.quitcmd()) + + # Create the tixDirList and the tixLabelEntry widgets on the on the top + # of the dialog box + + # bg = root.tk.eval('tix option get bg') + # adding bg=bg crashes Windows pythonw tk8.3.3 Python 2.1.0 + + top = Tix.Frame( w, relief=RAISED, bd=1) + + # Create the DirList widget. By default it will show the current + # directory + # + # + top.dir = Tix.DirList(top) + top.dir.hlist['width'] = 40 + + # When the user presses the ".." button, the selected directory + # is "transferred" into the entry widget + # + top.btn = Tix.Button(top, text = " >> ", pady = 0) + + # We use a LabelEntry to hold the installation directory. The user + # can choose from the DirList widget, or he can type in the directory + # manually + # + top.ent = Tix.LabelEntry(top, label="Installation Directory:", + labelside = 'top', + options = ''' + entry.width 40 + label.anchor w + ''') + + font = self.root.tk.eval('tix option get fixed_font') + # font = self.root.master.tix_option_get('fixed_font') + top.ent.entry['font'] = font + + self.dlist_dir = copy.copy(os.curdir) + # This should work setting the entry's textvariable + top.ent.entry['textvariable'] = self.dlist_dir + top.btn['command'] = lambda dir=top.dir, ent=top.ent, self=self: \ + self.copy_name(dir,ent) + + # top.ent.entry.insert(0,'tix'+repr(self)) + top.ent.entry.bind('<Return>', lambda self=self: self.okcmd () ) + + top.pack( expand='yes', fill='both', side=TOP) + top.dir.pack( expand=1, fill=BOTH, padx=4, pady=4, side=LEFT) + top.btn.pack( anchor='s', padx=4, pady=4, side=LEFT) + top.ent.pack( expand=1, fill=X, anchor='s', padx=4, pady=4, side=LEFT) + + # Use a ButtonBox to hold the buttons. + # + box = Tix.ButtonBox (w, orientation='horizontal') + box.add ('ok', text='Ok', underline=0, width=6, + command = lambda self=self: self.okcmd () ) + box.add ('cancel', text='Cancel', underline=0, width=6, + command = lambda self=self: self.quitcmd () ) + + box.pack( anchor='s', fill='x', side=BOTTOM) + + def copy_name (self, dir, ent): + # This should work as it is the entry's textvariable + self.dlist_dir = dir.cget('value') + # but it isn't so I'll do it manually + ent.entry.delete(0,'end') + ent.entry.insert(0, self.dlist_dir) + + def okcmd (self): + # tixDemo:Status "You have selected the directory" + self.dlist_dir + self.quitcmd() + + def quitcmd (self): + self.exit = 0 + + def mainloop(self): + while self.exit < 0: + self.root.tk.dooneevent(TCL_ALL_EVENTS) + + def destroy (self): + self.root.destroy() + +# This "if" statement makes it possible to run this script file inside or +# outside of the main demo program "tixwidgets.py". +# +if __name__== '__main__' : + import tkMessageBox, traceback + + try: + root=Tix.Tk() + RunSample(root) + except: + t, v, tb = sys.exc_info() + text = "Error running the demo script:\n" + for line in traceback.format_exception(t,v,tb): + text = text + line + '\n' + d = tkMessageBox.showerror ( 'Tix Demo Error', text) diff --git a/sys/src/cmd/python/Demo/tix/samples/DirTree.py b/sys/src/cmd/python/Demo/tix/samples/DirTree.py new file mode 100755 index 000000000..d25524dc9 --- /dev/null +++ b/sys/src/cmd/python/Demo/tix/samples/DirTree.py @@ -0,0 +1,117 @@ +# -*-mode: python; fill-column: 75; tab-width: 8; coding: iso-latin-1-unix -*- +# +# $Id: DirTree.py 36560 2004-07-18 06:16:08Z tim_one $ +# +# Tix Demostration Program +# +# This sample program is structured in such a way so that it can be +# executed from the Tix demo program "tixwidgets.py": it must have a +# procedure called "RunSample". It should also have the "if" statment +# at the end of this file so that it can be run as a standalone +# program using tixwish. + +# This file demonstrates the use of the tixDirTree widget -- you can +# use it for the user to select a directory. For example, an installation +# program can use the tixDirTree widget to ask the user to select the +# installation directory for an application. +# + +import Tix, os, copy +from Tkconstants import * + +TCL_ALL_EVENTS = 0 + +def RunSample (root): + dirtree = DemoDirTree(root) + dirtree.mainloop() + dirtree.destroy() + +class DemoDirTree: + def __init__(self, w): + self.root = w + self.exit = -1 + + z = w.winfo_toplevel() + z.wm_protocol("WM_DELETE_WINDOW", lambda self=self: self.quitcmd()) + + # Create the tixDirTree and the tixLabelEntry widgets on the on the top + # of the dialog box + + # bg = root.tk.eval('tix option get bg') + # adding bg=bg crashes Windows pythonw tk8.3.3 Python 2.1.0 + + top = Tix.Frame( w, relief=RAISED, bd=1) + + # Create the DirTree widget. By default it will show the current + # directory + # + # + top.dir = Tix.DirTree(top) + top.dir.hlist['width'] = 40 + + # When the user presses the ".." button, the selected directory + # is "transferred" into the entry widget + # + top.btn = Tix.Button(top, text = " >> ", pady = 0) + + # We use a LabelEntry to hold the installation directory. The user + # can choose from the DirTree widget, or he can type in the directory + # manually + # + top.ent = Tix.LabelEntry(top, label="Installation Directory:", + labelside = 'top', + options = ''' + entry.width 40 + label.anchor w + ''') + + self.dlist_dir = copy.copy(os.curdir) + top.ent.entry['textvariable'] = self.dlist_dir + top.btn['command'] = lambda dir=top.dir, ent=top.ent, self=self: \ + self.copy_name(dir,ent) + + top.ent.entry.bind('<Return>', lambda self=self: self.okcmd () ) + + top.pack( expand='yes', fill='both', side=TOP) + top.dir.pack( expand=1, fill=BOTH, padx=4, pady=4, side=LEFT) + top.btn.pack( anchor='s', padx=4, pady=4, side=LEFT) + top.ent.pack( expand=1, fill=X, anchor='s', padx=4, pady=4, side=LEFT) + + # Use a ButtonBox to hold the buttons. + # + box = Tix.ButtonBox (w, orientation='horizontal') + box.add ('ok', text='Ok', underline=0, width=6, + command = lambda self=self: self.okcmd () ) + box.add ('cancel', text='Cancel', underline=0, width=6, + command = lambda self=self: self.quitcmd () ) + + box.pack( anchor='s', fill='x', side=BOTTOM) + + def copy_name (self, dir, ent): + # This should work as it is the entry's textvariable + self.dlist_dir = dir.cget('value') + # but it isn't so I'll do it manually + ent.entry.delete(0,'end') + ent.entry.insert(0, self.dlist_dir) + + def okcmd (self): + # tixDemo:Status "You have selected the directory" + self.dlist_dir + self.quitcmd() + + def quitcmd (self): + # tixDemo:Status "You have selected the directory" + self.dlist_dir + self.exit = 0 + + def mainloop(self): + while self.exit < 0: + self.root.tk.dooneevent(TCL_ALL_EVENTS) + + def destroy (self): + self.root.destroy() + +# This "if" statement makes it possible to run this script file inside or +# outside of the main demo program "tixwidgets.py". +# +if __name__== '__main__' : + root=Tix.Tk() + RunSample(root) diff --git a/sys/src/cmd/python/Demo/tix/samples/NoteBook.py b/sys/src/cmd/python/Demo/tix/samples/NoteBook.py new file mode 100755 index 000000000..adab91a4c --- /dev/null +++ b/sys/src/cmd/python/Demo/tix/samples/NoteBook.py @@ -0,0 +1,119 @@ +# -*-mode: python; fill-column: 75; tab-width: 8; coding: iso-latin-1-unix -*- +# +# $Id: NoteBook.py 36560 2004-07-18 06:16:08Z tim_one $ +# +# Tix Demostration Program +# +# This sample program is structured in such a way so that it can be +# executed from the Tix demo program "tixwidgets.py": it must have a +# procedure called "RunSample". It should also have the "if" statment +# at the end of this file so that it can be run as a standalone +# program. + +# This file demonstrates the use of the tixNoteBook widget, which allows +# you to lay out your interface using a "notebook" metaphore +# +import Tix + +def RunSample(w): + global root + root = w + + # We use these options to set the sizes of the subwidgets inside the + # notebook, so that they are well-aligned on the screen. + prefix = Tix.OptionName(w) + if prefix: + prefix = '*'+prefix + else: + prefix = '' + w.option_add(prefix+'*TixControl*entry.width', 10) + w.option_add(prefix+'*TixControl*label.width', 18) + w.option_add(prefix+'*TixControl*label.anchor', Tix.E) + w.option_add(prefix+'*TixNoteBook*tagPadX', 8) + + # Create the notebook widget and set its backpagecolor to gray. + # Note that the -backpagecolor option belongs to the "nbframe" + # subwidget. + nb = Tix.NoteBook(w, name='nb', ipadx=6, ipady=6) + nb['bg'] = 'gray' + nb.nbframe['backpagecolor'] = 'gray' + + # Create the two tabs on the notebook. The -underline option + # puts a underline on the first character of the labels of the tabs. + # Keyboard accelerators will be defined automatically according + # to the underlined character. + nb.add('hard_disk', label="Hard Disk", underline=0) + nb.add('network', label="Network", underline=0) + + nb.pack(expand=1, fill=Tix.BOTH, padx=5, pady=5 ,side=Tix.TOP) + + #---------------------------------------- + # Create the first page + #---------------------------------------- + # Create two frames: one for the common buttons, one for the + # other widgets + # + tab=nb.hard_disk + f = Tix.Frame(tab) + common = Tix.Frame(tab) + + f.pack(side=Tix.LEFT, padx=2, pady=2, fill=Tix.BOTH, expand=1) + common.pack(side=Tix.RIGHT, padx=2, fill=Tix.Y) + + a = Tix.Control(f, value=12, label='Access time: ') + w = Tix.Control(f, value=400, label='Write Throughput: ') + r = Tix.Control(f, value=400, label='Read Throughput: ') + c = Tix.Control(f, value=1021, label='Capacity: ') + + a.pack(side=Tix.TOP, padx=20, pady=2) + w.pack(side=Tix.TOP, padx=20, pady=2) + r.pack(side=Tix.TOP, padx=20, pady=2) + c.pack(side=Tix.TOP, padx=20, pady=2) + + # Create the common buttons + createCommonButtons(common) + + #---------------------------------------- + # Create the second page + #---------------------------------------- + + tab = nb.network + + f = Tix.Frame(tab) + common = Tix.Frame(tab) + + f.pack(side=Tix.LEFT, padx=2, pady=2, fill=Tix.BOTH, expand=1) + common.pack(side=Tix.RIGHT, padx=2, fill=Tix.Y) + + a = Tix.Control(f, value=12, label='Access time: ') + w = Tix.Control(f, value=400, label='Write Throughput: ') + r = Tix.Control(f, value=400, label='Read Throughput: ') + c = Tix.Control(f, value=1021, label='Capacity: ') + u = Tix.Control(f, value=10, label='Users: ') + + a.pack(side=Tix.TOP, padx=20, pady=2) + w.pack(side=Tix.TOP, padx=20, pady=2) + r.pack(side=Tix.TOP, padx=20, pady=2) + c.pack(side=Tix.TOP, padx=20, pady=2) + u.pack(side=Tix.TOP, padx=20, pady=2) + + createCommonButtons(common) + +def doDestroy(): + global root + root.destroy() + +def createCommonButtons(master): + ok = Tix.Button(master, name='ok', text='OK', width=6, + command=doDestroy) + cancel = Tix.Button(master, name='cancel', + text='Cancel', width=6, + command=doDestroy) + + ok.pack(side=Tix.TOP, padx=2, pady=2) + cancel.pack(side=Tix.TOP, padx=2, pady=2) + +if __name__ == '__main__': + root = Tix.Tk() + RunSample(root) + root.mainloop() diff --git a/sys/src/cmd/python/Demo/tix/samples/OptMenu.py b/sys/src/cmd/python/Demo/tix/samples/OptMenu.py new file mode 100755 index 000000000..c9faca7aa --- /dev/null +++ b/sys/src/cmd/python/Demo/tix/samples/OptMenu.py @@ -0,0 +1,68 @@ +# -*-mode: python; fill-column: 75; tab-width: 8; coding: iso-latin-1-unix -*- +# +# $Id: OptMenu.py 36560 2004-07-18 06:16:08Z tim_one $ +# +# Tix Demostration Program +# +# This sample program is structured in such a way so that it can be +# executed from the Tix demo program "tixwidgets.py": it must have a +# procedure called "RunSample". It should also have the "if" statment +# at the end of this file so that it can be run as a standalone +# program. + +# This file demonstrates the use of the tixOptionMenu widget -- you can +# use it for the user to choose from a fixed set of options +# +import Tix + +options = {'text':'Plain Text', 'post':'PostScript', 'html':'HTML', + 'tex':'LaTeX', 'rtf':'Rich Text Format'} + +def RunSample(w): + global demo_opt_from, demo_opt_to + + demo_opt_from = Tix.StringVar() + demo_opt_to = Tix.StringVar() + + top = Tix.Frame(w, bd=1, relief=Tix.RAISED) + + from_file = Tix.OptionMenu(top, label="From File Format : ", + variable=demo_opt_from, + options = 'label.width 19 label.anchor e menubutton.width 15') + + to_file = Tix.OptionMenu(top, label="To File Format : ", + variable=demo_opt_to, + options='label.width 19 label.anchor e menubutton.width 15') + + # Add the available options to the two OptionMenu widgets + # + # [Hint] You have to add the options first before you set the + # global variables "demo_opt_from" and "demo_opt_to". Otherwise + # the OptionMenu widget will complain about "unknown options"! + # + for opt in options.keys(): + from_file.add_command(opt, label=options[opt]) + to_file.add_command(opt, label=options[opt]) + + demo_opt_from.set('html') + demo_opt_to.set('post') + + from_file.pack(side=Tix.TOP, anchor=Tix.W, pady=3, padx=6) + to_file.pack(side=Tix.TOP, anchor=Tix.W, pady=3, padx=6) + + box = Tix.ButtonBox(w, orientation=Tix.HORIZONTAL) + box.add('ok', text='Ok', underline=0, width=6, + command=lambda w=w: ok_command(w)) + box.add('cancel', text='Cancel', underline=0, width=6, + command=lambda w=w: w.destroy()) + box.pack(side=Tix.BOTTOM, fill=Tix.X) + top.pack(side=Tix.TOP, fill=Tix.BOTH, expand=1) + +def ok_command(w): + # tixDemo:Status "Convert file from %s to %s" % ( demo_opt_from.get(), demo_opt_to.get()) + w.destroy() + +if __name__ == '__main__': + root = Tix.Tk() + RunSample(root) + root.mainloop() diff --git a/sys/src/cmd/python/Demo/tix/samples/PanedWin.py b/sys/src/cmd/python/Demo/tix/samples/PanedWin.py new file mode 100755 index 000000000..6f5f94720 --- /dev/null +++ b/sys/src/cmd/python/Demo/tix/samples/PanedWin.py @@ -0,0 +1,98 @@ +# -*-mode: python; fill-column: 75; tab-width: 8; coding: iso-latin-1-unix -*- +# +# $Id: PanedWin.py 36560 2004-07-18 06:16:08Z tim_one $ +# +# Tix Demostration Program +# +# This sample program is structured in such a way so that it can be +# executed from the Tix demo program "tixwidgets.py": it must have a +# procedure called "RunSample". It should also have the "if" statment +# at the end of this file so that it can be run as a standalone +# program. + +# This file demonstrates the use of the tixPanedWindow widget. This program +# is a dummy news reader: the user can adjust the sizes of the list +# of artical names and the size of the text widget that shows the body +# of the article. + +import Tix + +TCL_ALL_EVENTS = 0 + +def RunSample (root): + panedwin = DemoPanedwin(root) + panedwin.mainloop() + panedwin.destroy() + +class DemoPanedwin: + def __init__(self, w): + self.root = w + self.exit = -1 + + z = w.winfo_toplevel() + z.wm_protocol("WM_DELETE_WINDOW", lambda self=self: self.quitcmd()) + + group = Tix.LabelEntry(w, label='Newsgroup:', options='entry.width 25') + group.entry.insert(0,'comp.lang.python') + pane = Tix.PanedWindow(w, orientation='vertical') + + p1 = pane.add('list', min=70, size=100) + p2 = pane.add('text', min=70) + list = Tix.ScrolledListBox(p1) + list.listbox['width'] = 80 + list.listbox['height'] = 5 + text = Tix.ScrolledText(p2) + text.text['width'] = 80 + text.text['height'] = 20 + + list.listbox.insert(Tix.END, " 12324 Re: Tkinter is good for your health") + list.listbox.insert(Tix.END, "+ 12325 Re: Tkinter is good for your health") + list.listbox.insert(Tix.END, "+ 12326 Re: Tix is even better for your health (Was: Tkinter is good...)") + list.listbox.insert(Tix.END, " 12327 Re: Tix is even better for your health (Was: Tkinter is good...)") + list.listbox.insert(Tix.END, "+ 12328 Re: Tix is even better for your health (Was: Tkinter is good...)") + list.listbox.insert(Tix.END, " 12329 Re: Tix is even better for your health (Was: Tkinter is good...)") + list.listbox.insert(Tix.END, "+ 12330 Re: Tix is even better for your health (Was: Tkinter is good...)") + + text.text['bg'] = list.listbox['bg'] + text.text['wrap'] = 'none' + text.text.insert(Tix.END, """ + Mon, 19 Jun 1995 11:39:52 comp.lang.python Thread 34 of 220 + Lines 353 A new way to put text and bitmaps together iNo responses + ioi@blue.seas.upenn.edu Ioi K. Lam at University of Pennsylvania + + Hi, + + I have implemented a new image type called "compound". It allows you + to glue together a bunch of bitmaps, images and text strings together + to form a bigger image. Then you can use this image with widgets that + support the -image option. For example, you can display a text string string + together with a bitmap, at the same time, inside a TK button widget. + """) + text.text['state'] = 'disabled' + + list.pack(expand=1, fill=Tix.BOTH, padx=4, pady=6) + text.pack(expand=1, fill=Tix.BOTH, padx=4, pady=6) + + group.pack(side=Tix.TOP, padx=3, pady=3, fill=Tix.BOTH) + pane.pack(side=Tix.TOP, padx=3, pady=3, fill=Tix.BOTH, expand=1) + + box = Tix.ButtonBox(w, orientation=Tix.HORIZONTAL) + box.add('ok', text='Ok', underline=0, width=6, + command=self.quitcmd) + box.add('cancel', text='Cancel', underline=0, width=6, + command=self.quitcmd) + box.pack(side=Tix.BOTTOM, fill=Tix.X) + + def quitcmd (self): + self.exit = 0 + + def mainloop(self): + while self.exit < 0: + self.root.tk.dooneevent(TCL_ALL_EVENTS) + + def destroy (self): + self.root.destroy() + +if __name__ == '__main__': + root = Tix.Tk() + RunSample(root) diff --git a/sys/src/cmd/python/Demo/tix/samples/PopMenu.py b/sys/src/cmd/python/Demo/tix/samples/PopMenu.py new file mode 100755 index 000000000..db3d7ab53 --- /dev/null +++ b/sys/src/cmd/python/Demo/tix/samples/PopMenu.py @@ -0,0 +1,57 @@ +# -*-mode: python; fill-column: 75; tab-width: 8; coding: iso-latin-1-unix -*- +# +# $Id: PopMenu.py 36560 2004-07-18 06:16:08Z tim_one $ +# +# Tix Demostration Program +# +# This sample program is structured in such a way so that it can be +# executed from the Tix demo program "tixwidgets.py": it must have a +# procedure called "RunSample". It should also have the "if" statment +# at the end of this file so that it can be run as a standalone +# program using tixwish. + +# This file demonstrates the use of the tixPopupMenu widget. +# +import Tix + +def RunSample(w): + # We create the frame and the button, then we'll bind the PopupMenu + # to both widgets. The result is, when you press the right mouse + # button over $w.top or $w.top.but, the PopupMenu will come up. + # + top = Tix.Frame(w, relief=Tix.RAISED, bd=1) + but = Tix.Button(top, text='Press the right mouse button over this button or its surrounding area') + but.pack(expand=1, fill=Tix.BOTH, padx=50, pady=50) + + p = Tix.PopupMenu(top, title='Popup Test') + p.bind_widget(top) + p.bind_widget(but) + + # Set the entries inside the PopupMenu widget. + # [Hint] You have to manipulate the "menu" subwidget. + # $w.top.p itself is NOT a menu widget. + # [Hint] Watch carefully how the sub-menu is created + # + p.menu.add_command(label='Desktop', underline=0) + p.menu.add_command(label='Select', underline=0) + p.menu.add_command(label='Find', underline=0) + p.menu.add_command(label='System', underline=1) + p.menu.add_command(label='Help', underline=0) + m1 = Tix.Menu(p.menu) + m1.add_command(label='Hello') + p.menu.add_cascade(label='More', menu=m1) + + but.pack(side=Tix.TOP, padx=40, pady=50) + + box = Tix.ButtonBox(w, orientation=Tix.HORIZONTAL) + box.add('ok', text='Ok', underline=0, width=6, + command=lambda w=w: w.destroy()) + box.add('cancel', text='Cancel', underline=0, width=6, + command=lambda w=w: w.destroy()) + box.pack(side=Tix.BOTTOM, fill=Tix.X) + top.pack(side=Tix.TOP, fill=Tix.BOTH, expand=1) + +if __name__ == '__main__': + root = Tix.Tk() + RunSample(root) + root.mainloop() diff --git a/sys/src/cmd/python/Demo/tix/samples/SHList1.py b/sys/src/cmd/python/Demo/tix/samples/SHList1.py new file mode 100755 index 000000000..22e44aaf4 --- /dev/null +++ b/sys/src/cmd/python/Demo/tix/samples/SHList1.py @@ -0,0 +1,131 @@ +# -*-mode: python; fill-column: 75; tab-width: 8; coding: iso-latin-1-unix -*- +# +# $Id: SHList1.py 36560 2004-07-18 06:16:08Z tim_one $ +# +# Tix Demostration Program +# +# This sample program is structured in such a way so that it can be +# executed from the Tix demo program "tixwidgets.py": it must have a +# procedure called "RunSample". It should also have the "if" statment +# at the end of this file so that it can be run as a standalone +# program using tixwish. + +# This file demonstrates the use of the tixScrolledHList widget. +# + +import Tix + +TCL_ALL_EVENTS = 0 + +def RunSample (root): + shlist = DemoSHList(root) + shlist.mainloop() + shlist.destroy() + +class DemoSHList: + def __init__(self, w): + self.root = w + self.exit = -1 + + z = w.winfo_toplevel() + z.wm_protocol("WM_DELETE_WINDOW", lambda self=self: self.quitcmd()) + + # We create the frame and the ScrolledHList widget + # at the top of the dialog box + # + top = Tix.Frame( w, relief=Tix.RAISED, bd=1) + + # Put a simple hierachy into the HList (two levels). Use colors and + # separator widgets (frames) to make the list look fancy + # + top.a = Tix.ScrolledHList(top) + top.a.pack( expand=1, fill=Tix.BOTH, padx=10, pady=10, side=Tix.TOP) + + # This is our little relational database + # + bosses = [ + ('jeff', 'Jeff Waxman'), + ('john', 'John Lee'), + ('peter', 'Peter Kenson') + ] + + employees = [ + ('alex', 'john', 'Alex Kellman'), + ('alan', 'john', 'Alan Adams'), + ('andy', 'peter', 'Andreas Crawford'), + ('doug', 'jeff', 'Douglas Bloom'), + ('jon', 'peter', 'Jon Baraki'), + ('chris', 'jeff', 'Chris Geoffrey'), + ('chuck', 'jeff', 'Chuck McLean') + ] + + hlist=top.a.hlist + + # Let configure the appearance of the HList subwidget + # + hlist.config( separator='.', width=25, drawbranch=0, indent=10) + + count=0 + for boss,name in bosses : + if count : + f=Tix.Frame(hlist, name='sep%d' % count, height=2, width=150, + bd=2, relief=Tix.SUNKEN ) + + hlist.add_child( itemtype=Tix.WINDOW, + window=f, state=Tix.DISABLED ) + + hlist.add(boss, itemtype=Tix.TEXT, text=name) + count = count+1 + + + for person,boss,name in employees : + # '.' is the separator character we chose above + # + key= boss + '.' + person + # ^^^^ ^^^^^^ + # parent entryPath / child's name + + hlist.add( key, text=name ) + + # [Hint] Make sure the keys (e.g. 'boss.person') you choose + # are unique names. If you cannot be sure of this (because of + # the structure of your database, e.g.) you can use the + # "add_child" command instead: + # + # hlist.addchild( boss, text=name) + # ^^^^ + # parent entryPath + + + # Use a ButtonBox to hold the buttons. + # + box= Tix.ButtonBox(top, orientation=Tix.HORIZONTAL ) + box.add( 'ok', text='Ok', underline=0, width=6, + command = self.okcmd) + + box.add( 'cancel', text='Cancel', underline=0, width=6, + command = self.quitcmd) + + box.pack( side=Tix.BOTTOM, fill=Tix.X) + top.pack( side=Tix.TOP, fill=Tix.BOTH, expand=1 ) + + def okcmd (self): + self.quitcmd() + + def quitcmd (self): + self.exit = 0 + + def mainloop(self): + while self.exit < 0: + self.root.tk.dooneevent(TCL_ALL_EVENTS) + + def destroy (self): + self.root.destroy() + + +# This "if" statement makes it possible to run this script file inside or +# outside of the main demo program "tixwidgets.py". +# +if __name__== '__main__' : + root=Tix.Tk() + RunSample(root) diff --git a/sys/src/cmd/python/Demo/tix/samples/SHList2.py b/sys/src/cmd/python/Demo/tix/samples/SHList2.py new file mode 100755 index 000000000..1492242b3 --- /dev/null +++ b/sys/src/cmd/python/Demo/tix/samples/SHList2.py @@ -0,0 +1,168 @@ +# -*-mode: python; fill-column: 75; tab-width: 8; coding: iso-latin-1-unix -*- +# +# $Id: SHList2.py 36560 2004-07-18 06:16:08Z tim_one $ +# +# Tix Demostration Program +# +# This sample program is structured in such a way so that it can be +# executed from the Tix demo program "tixwidget": it must have a +# procedure called "RunSample". It should also have the "if" statment +# at the end of this file so that it can be run as a standalone +# program using tixwish. + +# This file demonstrates how to use multiple columns and multiple styles +# in the tixHList widget +# +# In a tixHList widget, you can have one ore more columns. +# + +import Tix + +TCL_ALL_EVENTS = 0 + +def RunSample (root): + shlist = DemoSHList(root) + shlist.mainloop() + shlist.destroy() + +class DemoSHList: + def __init__(self, w): + self.root = w + self.exit = -1 + + z = w.winfo_toplevel() + z.wm_protocol("WM_DELETE_WINDOW", lambda self=self: self.quitcmd()) + + # We create the frame and the ScrolledHList widget + # at the top of the dialog box + # + top = Tix.Frame( w, relief=Tix.RAISED, bd=1) + + # Put a simple hierachy into the HList (two levels). Use colors and + # separator widgets (frames) to make the list look fancy + # + top.a = Tix.ScrolledHList(top, options='hlist.columns 3 hlist.header 1' ) + top.a.pack( expand=1, fill=Tix.BOTH, padx=10, pady=10, side=Tix.TOP) + + hlist=top.a.hlist + + # Create the title for the HList widget + # >> Notice that we have set the hlist.header subwidget option to true + # so that the header is displayed + # + + boldfont=hlist.tk.call('tix','option','get','bold_font') + + # First some styles for the headers + style={} + style['header'] = Tix.DisplayStyle(Tix.TEXT, refwindow=hlist, + anchor=Tix.CENTER, padx=8, pady=2, font = boldfont ) + + hlist.header_create(0, itemtype=Tix.TEXT, text='Name', + style=style['header']) + hlist.header_create(1, itemtype=Tix.TEXT, text='Position', + style=style['header']) + + # Notice that we use 3 columns in the hlist widget. This way when the user + # expands the windows wide, the right side of the header doesn't look + # chopped off. The following line ensures that the 3 column header is + # not shown unless the hlist window is wider than its contents. + # + hlist.column_width(2,0) + + # This is our little relational database + # + boss = ('doe', 'John Doe', 'Director') + + managers = [ + ('jeff', 'Jeff Waxman', 'Manager'), + ('john', 'John Lee', 'Manager'), + ('peter', 'Peter Kenson', 'Manager') + ] + + employees = [ + ('alex', 'john', 'Alex Kellman', 'Clerk'), + ('alan', 'john', 'Alan Adams', 'Clerk'), + ('andy', 'peter', 'Andreas Crawford', 'Salesman'), + ('doug', 'jeff', 'Douglas Bloom', 'Clerk'), + ('jon', 'peter', 'Jon Baraki', 'Salesman'), + ('chris', 'jeff', 'Chris Geoffrey', 'Clerk'), + ('chuck', 'jeff', 'Chuck McLean', 'Cleaner') + ] + + style['mgr_name'] = Tix.DisplayStyle(Tix.TEXT, refwindow=hlist) + + style['mgr_posn'] = Tix.DisplayStyle(Tix.TEXT, padx=8, refwindow=hlist) + + style['empl_name'] = Tix.DisplayStyle(Tix.TEXT, refwindow=hlist) + + style['empl_posn'] = Tix.DisplayStyle(Tix.TEXT, padx=8, refwindow=hlist) + + # Let configure the appearance of the HList subwidget + # + hlist.config(separator='.', width=25, drawbranch=0, indent=10) + hlist.column_width(0, chars=20) + + # Create the boss + # + hlist.add ('.', itemtype=Tix.TEXT, text=boss[1], + style=style['mgr_name']) + hlist.item_create('.', 1, itemtype=Tix.TEXT, text=boss[2], + style=style['mgr_posn']) + + # Create the managers + # + + for key,name,posn in managers : + e= '.'+ key + hlist.add(e, itemtype=Tix.TEXT, text=name, + style=style['mgr_name']) + hlist.item_create(e, 1, itemtype=Tix.TEXT, text=posn, + style=style['mgr_posn']) + + + for key,mgr,name,posn in employees : + # "." is the separator character we chose above + + entrypath = '.' + mgr + '.' + key + + # ^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^ + # parent entryPath / child's name + + hlist.add(entrypath, text=name, style=style['empl_name']) + hlist.item_create(entrypath, 1, itemtype=Tix.TEXT, + text = posn, style = style['empl_posn'] ) + + + # Use a ButtonBox to hold the buttons. + # + box= Tix.ButtonBox(top, orientation=Tix.HORIZONTAL ) + box.add( 'ok', text='Ok', underline=0, width=6, + command = self.okcmd ) + + box.add( 'cancel', text='Cancel', underline=0, width=6, + command = self.quitcmd ) + + box.pack( side=Tix.BOTTOM, fill=Tix.X) + top.pack( side=Tix.TOP, fill=Tix.BOTH, expand=1 ) + + def okcmd (self): + self.quitcmd() + + def quitcmd (self): + self.exit = 0 + + def mainloop(self): + while self.exit < 0: + self.root.tk.dooneevent(TCL_ALL_EVENTS) + + def destroy (self): + self.root.destroy() + + +# This "if" statement makes it possible to run this script file inside or +# outside of the main demo program "tixwidgets.py". +# +if __name__== '__main__' : + root=Tix.Tk() + RunSample(root) diff --git a/sys/src/cmd/python/Demo/tix/samples/Tree.py b/sys/src/cmd/python/Demo/tix/samples/Tree.py new file mode 100755 index 000000000..5b66daab7 --- /dev/null +++ b/sys/src/cmd/python/Demo/tix/samples/Tree.py @@ -0,0 +1,80 @@ +# -*-mode: python; fill-column: 75; tab-width: 8; coding: iso-latin-1-unix -*- +# +# $Id: Tree.py 36560 2004-07-18 06:16:08Z tim_one $ +# +# Tix Demostration Program +# +# This sample program is structured in such a way so that it can be +# executed from the Tix demo program "tixwidgets.py": it must have a +# procedure called "RunSample". It should also have the "if" statment +# at the end of this file so that it can be run as a standalone +# program. + +# This file demonstrates how to use the TixTree widget to display +# dynamic hierachical data (the files in the Unix file system) +# + +import Tix, os + +def RunSample(w): + top = Tix.Frame(w, relief=Tix.RAISED, bd=1) + tree = Tix.Tree(top, options='separator "/"') + tree.pack(expand=1, fill=Tix.BOTH, padx=10, pady=10, side=Tix.LEFT) + tree['opencmd'] = lambda dir=None, w=tree: opendir(w, dir) + + # The / directory is added in the "open" mode. The user can open it + # and then browse its subdirectories ... + adddir(tree, "/") + + box = Tix.ButtonBox(w, orientation=Tix.HORIZONTAL) + box.add('ok', text='Ok', underline=0, command=w.destroy, width=6) + box.add('cancel', text='Cancel', underline=0, command=w.destroy, width=6) + box.pack(side=Tix.BOTTOM, fill=Tix.X) + top.pack(side=Tix.TOP, fill=Tix.BOTH, expand=1) + +def adddir(tree, dir): + if dir == '/': + text = '/' + else: + text = os.path.basename(dir) + tree.hlist.add(dir, itemtype=Tix.IMAGETEXT, text=text, + image=tree.tk.call('tix', 'getimage', 'folder')) + try: + os.listdir(dir) + tree.setmode(dir, 'open') + except os.error: + # No read permission ? + pass + +# This function is called whenever the user presses the (+) indicator or +# double clicks on a directory whose mode is "open". It loads the files +# inside that directory into the Tree widget. +# +# Note we didn't specify the closecmd option for the Tree widget, so it +# performs the default action when the user presses the (-) indicator or +# double clicks on a directory whose mode is "close": hide all of its child +# entries +def opendir(tree, dir): + entries = tree.hlist.info_children(dir) + if entries: + # We have already loaded this directory. Let's just + # show all the child entries + # + # Note: since we load the directory only once, it will not be + # refreshed if the you add or remove files from this + # directory. + # + for entry in entries: + tree.hlist.show_entry(entry) + files = os.listdir(dir) + for file in files: + if os.path.isdir(dir + '/' + file): + adddir(tree, dir + '/' + file) + else: + tree.hlist.add(dir + '/' + file, itemtype=Tix.IMAGETEXT, text=file, + image=tree.tk.call('tix', 'getimage', 'file')) + +if __name__ == '__main__': + root = Tix.Tk() + RunSample(root) + root.mainloop() diff --git a/sys/src/cmd/python/Demo/tix/tixwidgets.py b/sys/src/cmd/python/Demo/tix/tixwidgets.py new file mode 100644 index 000000000..0355e0b37 --- /dev/null +++ b/sys/src/cmd/python/Demo/tix/tixwidgets.py @@ -0,0 +1,1003 @@ +# -*-mode: python; fill-column: 75; tab-width: 8; coding: iso-latin-1-unix -*- +# +# $Id: tixwidgets.py 36560 2004-07-18 06:16:08Z tim_one $ +# +# tixwidgets.py -- +# +# For Tix, see http://tix.sourceforge.net +# +# This is a demo program of some of the Tix widgets available in Python. +# If you have installed Python & Tix properly, you can execute this as +# +# % python tixwidgets.py +# + +import os, os.path, sys, Tix +from Tkconstants import * +import traceback, tkMessageBox + +TCL_DONT_WAIT = 1<<1 +TCL_WINDOW_EVENTS = 1<<2 +TCL_FILE_EVENTS = 1<<3 +TCL_TIMER_EVENTS = 1<<4 +TCL_IDLE_EVENTS = 1<<5 +TCL_ALL_EVENTS = 0 + +class Demo: + def __init__(self, top): + self.root = top + self.exit = -1 + + self.dir = None # script directory + self.balloon = None # balloon widget + self.useBalloons = Tix.StringVar() + self.useBalloons.set('0') + self.statusbar = None # status bar widget + self.welmsg = None # Msg widget + self.welfont = '' # font name + self.welsize = '' # font size + + progname = sys.argv[0] + dirname = os.path.dirname(progname) + if dirname and dirname != os.curdir: + self.dir = dirname + index = -1 + for i in range(len(sys.path)): + p = sys.path[i] + if p in ("", os.curdir): + index = i + if index >= 0: + sys.path[index] = dirname + else: + sys.path.insert(0, dirname) + else: + self.dir = os.getcwd() + sys.path.insert(0, self.dir+'/samples') + + def MkMainMenu(self): + top = self.root + w = Tix.Frame(top, bd=2, relief=RAISED) + file = Tix.Menubutton(w, text='File', underline=0, takefocus=0) + help = Tix.Menubutton(w, text='Help', underline=0, takefocus=0) + file.pack(side=LEFT) + help.pack(side=RIGHT) + fm = Tix.Menu(file, tearoff=0) + file['menu'] = fm + hm = Tix.Menu(help, tearoff=0) + help['menu'] = hm + + fm.add_command(label='Exit', underline=1, + command = lambda self=self: self.quitcmd () ) + hm.add_checkbutton(label='BalloonHelp', underline=0, command=ToggleHelp, + variable=self.useBalloons) + # The trace variable option doesn't seem to work, instead I use 'command' + #apply(w.tk.call, ('trace', 'variable', self.useBalloons, 'w', + # ToggleHelp)) + + return w + + def MkMainNotebook(self): + top = self.root + w = Tix.NoteBook(top, ipadx=5, ipady=5, options=""" + tagPadX 6 + tagPadY 4 + borderWidth 2 + """) + # This may be required if there is no *Background option + top['bg'] = w['bg'] + + w.add('wel', label='Welcome', underline=0, + createcmd=lambda w=w, name='wel': MkWelcome(w, name)) + w.add('cho', label='Choosers', underline=0, + createcmd=lambda w=w, name='cho': MkChoosers(w, name)) + w.add('scr', label='Scrolled Widgets', underline=0, + createcmd=lambda w=w, name='scr': MkScroll(w, name)) + w.add('mgr', label='Manager Widgets', underline=0, + createcmd=lambda w=w, name='mgr': MkManager(w, name)) + w.add('dir', label='Directory List', underline=0, + createcmd=lambda w=w, name='dir': MkDirList(w, name)) + w.add('exp', label='Run Sample Programs', underline=0, + createcmd=lambda w=w, name='exp': MkSample(w, name)) + return w + + def MkMainStatus(self): + global demo + top = self.root + + w = Tix.Frame(top, relief=Tix.RAISED, bd=1) + demo.statusbar = Tix.Label(w, relief=Tix.SUNKEN, bd=1) + demo.statusbar.form(padx=3, pady=3, left=0, right='%70') + return w + + def build(self): + root = self.root + z = root.winfo_toplevel() + z.wm_title('Tix Widget Demonstration') + if z.winfo_screenwidth() <= 800: + z.geometry('790x590+10+10') + else: + z.geometry('890x640+10+10') + demo.balloon = Tix.Balloon(root) + frame1 = self.MkMainMenu() + frame2 = self.MkMainNotebook() + frame3 = self.MkMainStatus() + frame1.pack(side=TOP, fill=X) + frame3.pack(side=BOTTOM, fill=X) + frame2.pack(side=TOP, expand=1, fill=BOTH, padx=4, pady=4) + demo.balloon['statusbar'] = demo.statusbar + z.wm_protocol("WM_DELETE_WINDOW", lambda self=self: self.quitcmd()) + + # To show Tcl errors - uncomment this to see the listbox bug. + # Tkinter defines a Tcl tkerror procedure that in effect + # silences all background Tcl error reporting. + # root.tk.eval('if {[info commands tkerror] != ""} {rename tkerror pytkerror}') + def quitcmd (self): + """Quit our mainloop. It is up to you to call root.destroy() after.""" + self.exit = 0 + + def loop(self): + """This is an explict replacement for _tkinter mainloop() + It lets you catch keyboard interrupts easier, and avoids + the 20 msec. dead sleep() which burns a constant CPU.""" + while self.exit < 0: + # There are 2 whiles here. The outer one lets you continue + # after a ^C interrupt. + try: + # This is the replacement for _tkinter mainloop() + # It blocks waiting for the next Tcl event using select. + while self.exit < 0: + self.root.tk.dooneevent(TCL_ALL_EVENTS) + except SystemExit: + # Tkinter uses SystemExit to exit + #print 'Exit' + self.exit = 1 + return + except KeyboardInterrupt: + if tkMessageBox.askquestion ('Interrupt', 'Really Quit?') == 'yes': + # self.tk.eval('exit') + self.exit = 1 + return + continue + except: + # Otherwise it's some other error - be nice and say why + t, v, tb = sys.exc_info() + text = "" + for line in traceback.format_exception(t,v,tb): + text += line + '\n' + try: tkMessageBox.showerror ('Error', text) + except: pass + self.exit = 1 + raise SystemExit, 1 + + def destroy (self): + self.root.destroy() + +def RunMain(root): + global demo + + demo = Demo(root) + + demo.build() + demo.loop() + demo.destroy() + +# Tabs +def MkWelcome(nb, name): + w = nb.page(name) + bar = MkWelcomeBar(w) + text = MkWelcomeText(w) + bar.pack(side=TOP, fill=X, padx=2, pady=2) + text.pack(side=TOP, fill=BOTH, expand=1) + +def MkWelcomeBar(top): + global demo + + w = Tix.Frame(top, bd=2, relief=Tix.GROOVE) + b1 = Tix.ComboBox(w, command=lambda w=top: MainTextFont(w)) + b2 = Tix.ComboBox(w, command=lambda w=top: MainTextFont(w)) + b1.entry['width'] = 15 + b1.slistbox.listbox['height'] = 3 + b2.entry['width'] = 4 + b2.slistbox.listbox['height'] = 3 + + demo.welfont = b1 + demo.welsize = b2 + + b1.insert(Tix.END, 'Courier') + b1.insert(Tix.END, 'Helvetica') + b1.insert(Tix.END, 'Lucida') + b1.insert(Tix.END, 'Times Roman') + + b2.insert(Tix.END, '8') + b2.insert(Tix.END, '10') + b2.insert(Tix.END, '12') + b2.insert(Tix.END, '14') + b2.insert(Tix.END, '18') + + b1.pick(1) + b2.pick(3) + + b1.pack(side=Tix.LEFT, padx=4, pady=4) + b2.pack(side=Tix.LEFT, padx=4, pady=4) + + demo.balloon.bind_widget(b1, msg='Choose\na font', + statusmsg='Choose a font for this page') + demo.balloon.bind_widget(b2, msg='Point size', + statusmsg='Choose the font size for this page') + return w + +def MkWelcomeText(top): + global demo + + w = Tix.ScrolledWindow(top, scrollbar='auto') + win = w.window + text = 'Welcome to TIX in Python' + title = Tix.Label(win, + bd=0, width=30, anchor=Tix.N, text=text) + msg = Tix.Message(win, + bd=0, width=400, anchor=Tix.N, + text='Tix is a set of mega-widgets based on TK. This program \ +demonstrates the widgets in the Tix widget set. You can choose the pages \ +in this window to look at the corresponding widgets. \n\n\ +To quit this program, choose the "File | Exit" command.\n\n\ +For more information, see http://tix.sourceforge.net.') + title.pack(expand=1, fill=Tix.BOTH, padx=10, pady=10) + msg.pack(expand=1, fill=Tix.BOTH, padx=10, pady=10) + demo.welmsg = msg + return w + +def MainTextFont(w): + global demo + + if not demo.welmsg: + return + font = demo.welfont['value'] + point = demo.welsize['value'] + if font == 'Times Roman': + font = 'times' + fontstr = '%s %s' % (font, point) + demo.welmsg['font'] = fontstr + +def ToggleHelp(): + if demo.useBalloons.get() == '1': + demo.balloon['state'] = 'both' + else: + demo.balloon['state'] = 'none' + +def MkChoosers(nb, name): + w = nb.page(name) + options = "label.padX 4" + + til = Tix.LabelFrame(w, label='Chooser Widgets', options=options) + cbx = Tix.LabelFrame(w, label='tixComboBox', options=options) + ctl = Tix.LabelFrame(w, label='tixControl', options=options) + sel = Tix.LabelFrame(w, label='tixSelect', options=options) + opt = Tix.LabelFrame(w, label='tixOptionMenu', options=options) + fil = Tix.LabelFrame(w, label='tixFileEntry', options=options) + fbx = Tix.LabelFrame(w, label='tixFileSelectBox', options=options) + tbr = Tix.LabelFrame(w, label='Tool Bar', options=options) + + MkTitle(til.frame) + MkCombo(cbx.frame) + MkControl(ctl.frame) + MkSelect(sel.frame) + MkOptMenu(opt.frame) + MkFileEnt(fil.frame) + MkFileBox(fbx.frame) + MkToolBar(tbr.frame) + + # First column: comBox and selector + cbx.form(top=0, left=0, right='%33') + sel.form(left=0, right='&'+str(cbx), top=cbx) + opt.form(left=0, right='&'+str(cbx), top=sel, bottom=-1) + + # Second column: title .. etc + til.form(left=cbx, top=0,right='%66') + ctl.form(left=cbx, right='&'+str(til), top=til) + fil.form(left=cbx, right='&'+str(til), top=ctl) + tbr.form(left=cbx, right='&'+str(til), top=fil, bottom=-1) + + # + # Third column: file selection + fbx.form(right=-1, top=0, left='%66') + +def MkCombo(w): + options="label.width %d label.anchor %s entry.width %d" % (10, Tix.E, 14) + + static = Tix.ComboBox(w, label='Static', editable=0, options=options) + editable = Tix.ComboBox(w, label='Editable', editable=1, options=options) + history = Tix.ComboBox(w, label='History', editable=1, history=1, + anchor=Tix.E, options=options) + static.insert(Tix.END, 'January') + static.insert(Tix.END, 'February') + static.insert(Tix.END, 'March') + static.insert(Tix.END, 'April') + static.insert(Tix.END, 'May') + static.insert(Tix.END, 'June') + static.insert(Tix.END, 'July') + static.insert(Tix.END, 'August') + static.insert(Tix.END, 'September') + static.insert(Tix.END, 'October') + static.insert(Tix.END, 'November') + static.insert(Tix.END, 'December') + + editable.insert(Tix.END, 'Angola') + editable.insert(Tix.END, 'Bangladesh') + editable.insert(Tix.END, 'China') + editable.insert(Tix.END, 'Denmark') + editable.insert(Tix.END, 'Ecuador') + + history.insert(Tix.END, '/usr/bin/ksh') + history.insert(Tix.END, '/usr/local/lib/python') + history.insert(Tix.END, '/var/adm') + + static.pack(side=Tix.TOP, padx=5, pady=3) + editable.pack(side=Tix.TOP, padx=5, pady=3) + history.pack(side=Tix.TOP, padx=5, pady=3) + +states = ['Bengal', 'Delhi', 'Karnataka', 'Tamil Nadu'] + +def spin_cmd(w, inc): + idx = states.index(demo_spintxt.get()) + inc + if idx < 0: + idx = len(states) - 1 + elif idx >= len(states): + idx = 0 +# following doesn't work. +# return states[idx] + demo_spintxt.set(states[idx]) # this works + +def spin_validate(w): + global states, demo_spintxt + + try: + i = states.index(demo_spintxt.get()) + except ValueError: + return states[0] + return states[i] + # why this procedure works as opposed to the previous one beats me. + +def MkControl(w): + global demo_spintxt + + options="label.width %d label.anchor %s entry.width %d" % (10, Tix.E, 13) + + demo_spintxt = Tix.StringVar() + demo_spintxt.set(states[0]) + simple = Tix.Control(w, label='Numbers', options=options) + spintxt = Tix.Control(w, label='States', variable=demo_spintxt, + options=options) + spintxt['incrcmd'] = lambda w=spintxt: spin_cmd(w, 1) + spintxt['decrcmd'] = lambda w=spintxt: spin_cmd(w, -1) + spintxt['validatecmd'] = lambda w=spintxt: spin_validate(w) + + simple.pack(side=Tix.TOP, padx=5, pady=3) + spintxt.pack(side=Tix.TOP, padx=5, pady=3) + +def MkSelect(w): + options = "label.anchor %s" % Tix.CENTER + + sel1 = Tix.Select(w, label='Mere Mortals', allowzero=1, radio=1, + orientation=Tix.VERTICAL, + labelside=Tix.TOP, + options=options) + sel2 = Tix.Select(w, label='Geeks', allowzero=1, radio=0, + orientation=Tix.VERTICAL, + labelside= Tix.TOP, + options=options) + + sel1.add('eat', text='Eat') + sel1.add('work', text='Work') + sel1.add('play', text='Play') + sel1.add('party', text='Party') + sel1.add('sleep', text='Sleep') + + sel2.add('eat', text='Eat') + sel2.add('prog1', text='Program') + sel2.add('prog2', text='Program') + sel2.add('prog3', text='Program') + sel2.add('sleep', text='Sleep') + + sel1.pack(side=Tix.LEFT, padx=5, pady=3, fill=Tix.X) + sel2.pack(side=Tix.LEFT, padx=5, pady=3, fill=Tix.X) + +def MkOptMenu(w): + options='menubutton.width 15 label.anchor %s' % Tix.E + + m = Tix.OptionMenu(w, label='File Format : ', options=options) + m.add_command('text', label='Plain Text') + m.add_command('post', label='PostScript') + m.add_command('format', label='Formatted Text') + m.add_command('html', label='HTML') + m.add_command('sep') + m.add_command('tex', label='LaTeX') + m.add_command('rtf', label='Rich Text Format') + + m.pack(fill=Tix.X, padx=5, pady=3) + +def MkFileEnt(w): + msg = Tix.Message(w, + relief=Tix.FLAT, width=240, anchor=Tix.N, + text='Press the "open file" icon button and a TixFileSelectDialog will popup.') + ent = Tix.FileEntry(w, label='Select a file : ') + msg.pack(side=Tix.TOP, expand=1, fill=Tix.BOTH, padx=3, pady=3) + ent.pack(side=Tix.TOP, fill=Tix.X, padx=3, pady=3) + +def MkFileBox(w): + """The FileSelectBox is a Motif-style box with various enhancements. + For example, you can adjust the size of the two listboxes + and your past selections are recorded. + """ + msg = Tix.Message(w, + relief=Tix.FLAT, width=240, anchor=Tix.N, + text='The Tix FileSelectBox is a Motif-style box with various enhancements. For example, you can adjust the size of the two listboxes and your past selections are recorded.') + box = Tix.FileSelectBox(w) + msg.pack(side=Tix.TOP, expand=1, fill=Tix.BOTH, padx=3, pady=3) + box.pack(side=Tix.TOP, fill=Tix.X, padx=3, pady=3) + +def MkToolBar(w): + """The Select widget is also good for arranging buttons in a tool bar. + """ + global demo + + options='frame.borderWidth 1' + + msg = Tix.Message(w, + relief=Tix.FLAT, width=240, anchor=Tix.N, + text='The Select widget is also good for arranging buttons in a tool bar.') + bar = Tix.Frame(w, bd=2, relief=Tix.RAISED) + font = Tix.Select(w, allowzero=1, radio=0, label='', options=options) + para = Tix.Select(w, allowzero=0, radio=1, label='', options=options) + + font.add('bold', bitmap='@' + demo.dir + '/bitmaps/bold.xbm') + font.add('italic', bitmap='@' + demo.dir + '/bitmaps/italic.xbm') + font.add('underline', bitmap='@' + demo.dir + '/bitmaps/underline.xbm') + font.add('capital', bitmap='@' + demo.dir + '/bitmaps/capital.xbm') + + para.add('left', bitmap='@' + demo.dir + '/bitmaps/leftj.xbm') + para.add('right', bitmap='@' + demo.dir + '/bitmaps/rightj.xbm') + para.add('center', bitmap='@' + demo.dir + '/bitmaps/centerj.xbm') + para.add('justify', bitmap='@' + demo.dir + '/bitmaps/justify.xbm') + + msg.pack(side=Tix.TOP, expand=1, fill=Tix.BOTH, padx=3, pady=3) + bar.pack(side=Tix.TOP, fill=Tix.X, padx=3, pady=3) + font.pack({'in':bar}, side=Tix.LEFT, padx=3, pady=3) + para.pack({'in':bar}, side=Tix.LEFT, padx=3, pady=3) + +def MkTitle(w): + msg = Tix.Message(w, + relief=Tix.FLAT, width=240, anchor=Tix.N, + text='There are many types of "chooser" widgets that allow the user to input different types of information') + msg.pack(side=Tix.TOP, expand=1, fill=Tix.BOTH, padx=3, pady=3) + +def MkScroll(nb, name): + w = nb.page(name) + options='label.padX 4' + + sls = Tix.LabelFrame(w, label='Tix.ScrolledListBox', options=options) + swn = Tix.LabelFrame(w, label='Tix.ScrolledWindow', options=options) + stx = Tix.LabelFrame(w, label='Tix.ScrolledText', options=options) + + MkSList(sls.frame) + MkSWindow(swn.frame) + MkSText(stx.frame) + + sls.form(top=0, left=0, right='%33', bottom=-1) + swn.form(top=0, left=sls, right='%66', bottom=-1) + stx.form(top=0, left=swn, right=-1, bottom=-1) + + +def MkSList(w): + """This TixScrolledListBox is configured so that it uses scrollbars + only when it is necessary. Use the handles to resize the listbox and + watch the scrollbars automatically appear and disappear. """ + top = Tix.Frame(w, width=300, height=330) + bot = Tix.Frame(w) + msg = Tix.Message(top, + relief=Tix.FLAT, width=200, anchor=Tix.N, + text='This TixScrolledListBox is configured so that it uses scrollbars only when it is necessary. Use the handles to resize the listbox and watch the scrollbars automatically appear and disappear.') + + list = Tix.ScrolledListBox(top, scrollbar='auto') + list.place(x=50, y=150, width=120, height=80) + list.listbox.insert(Tix.END, 'Alabama') + list.listbox.insert(Tix.END, 'California') + list.listbox.insert(Tix.END, 'Montana') + list.listbox.insert(Tix.END, 'New Jersey') + list.listbox.insert(Tix.END, 'New York') + list.listbox.insert(Tix.END, 'Pennsylvania') + list.listbox.insert(Tix.END, 'Washington') + + rh = Tix.ResizeHandle(top, bg='black', + relief=Tix.RAISED, + handlesize=8, gridded=1, minwidth=50, minheight=30) + btn = Tix.Button(bot, text='Reset', command=lambda w=rh, x=list: SList_reset(w,x)) + top.propagate(0) + msg.pack(fill=Tix.X) + btn.pack(anchor=Tix.CENTER) + top.pack(expand=1, fill=Tix.BOTH) + bot.pack(fill=Tix.BOTH) + list.bind('<Map>', func=lambda arg=0, rh=rh, list=list: + list.tk.call('tixDoWhenIdle', str(rh), 'attachwidget', str(list))) + +def SList_reset(rh, list): + list.place(x=50, y=150, width=120, height=80) + list.update() + rh.attach_widget(list) + +def MkSWindow(w): + """The ScrolledWindow widget allows you to scroll any kind of Tk + widget. It is more versatile than a scrolled canvas widget. + """ + global demo + + text = 'The Tix ScrolledWindow widget allows you to scroll any kind of Tk widget. It is more versatile than a scrolled canvas widget.' + + file = os.path.join(demo.dir, 'bitmaps', 'tix.gif') + if not os.path.isfile(file): + text += ' (Image missing)' + + top = Tix.Frame(w, width=330, height=330) + bot = Tix.Frame(w) + msg = Tix.Message(top, + relief=Tix.FLAT, width=200, anchor=Tix.N, + text=text) + + win = Tix.ScrolledWindow(top, scrollbar='auto') + + image1 = win.window.image_create('photo', file=file) + lbl = Tix.Label(win.window, image=image1) + lbl.pack(expand=1, fill=Tix.BOTH) + + win.place(x=30, y=150, width=190, height=120) + + rh = Tix.ResizeHandle(top, bg='black', + relief=Tix.RAISED, + handlesize=8, gridded=1, minwidth=50, minheight=30) + btn = Tix.Button(bot, text='Reset', command=lambda w=rh, x=win: SWindow_reset(w,x)) + top.propagate(0) + msg.pack(fill=Tix.X) + btn.pack(anchor=Tix.CENTER) + top.pack(expand=1, fill=Tix.BOTH) + bot.pack(fill=Tix.BOTH) + + win.bind('<Map>', func=lambda arg=0, rh=rh, win=win: + win.tk.call('tixDoWhenIdle', str(rh), 'attachwidget', str(win))) + +def SWindow_reset(rh, win): + win.place(x=30, y=150, width=190, height=120) + win.update() + rh.attach_widget(win) + +def MkSText(w): + """The TixScrolledWindow widget allows you to scroll any kind of Tk + widget. It is more versatile than a scrolled canvas widget.""" + top = Tix.Frame(w, width=330, height=330) + bot = Tix.Frame(w) + msg = Tix.Message(top, + relief=Tix.FLAT, width=200, anchor=Tix.N, + text='The Tix ScrolledWindow widget allows you to scroll any kind of Tk widget. It is more versatile than a scrolled canvas widget.') + + win = Tix.ScrolledText(top, scrollbar='auto') + win.text['wrap'] = 'none' + win.text.insert(Tix.END, '''When -scrollbar is set to "auto", the +scrollbars are shown only when needed. +Additional modifiers can be used to force a +scrollbar to be shown or hidden. For example, +"auto -y" means the horizontal scrollbar +should be shown when needed but the vertical +scrollbar should always be hidden; +"auto +x" means the vertical scrollbar +should be shown when needed but the horizontal +scrollbar should always be shown, and so on.''' +) + win.place(x=30, y=150, width=190, height=100) + + rh = Tix.ResizeHandle(top, bg='black', + relief=Tix.RAISED, + handlesize=8, gridded=1, minwidth=50, minheight=30) + btn = Tix.Button(bot, text='Reset', command=lambda w=rh, x=win: SText_reset(w,x)) + top.propagate(0) + msg.pack(fill=Tix.X) + btn.pack(anchor=Tix.CENTER) + top.pack(expand=1, fill=Tix.BOTH) + bot.pack(fill=Tix.BOTH) + win.bind('<Map>', func=lambda arg=0, rh=rh, win=win: + win.tk.call('tixDoWhenIdle', str(rh), 'attachwidget', str(win))) + +def SText_reset(rh, win): + win.place(x=30, y=150, width=190, height=120) + win.update() + rh.attach_widget(win) + +def MkManager(nb, name): + w = nb.page(name) + options='label.padX 4' + + pane = Tix.LabelFrame(w, label='Tix.PanedWindow', options=options) + note = Tix.LabelFrame(w, label='Tix.NoteBook', options=options) + + MkPanedWindow(pane.frame) + MkNoteBook(note.frame) + + pane.form(top=0, left=0, right=note, bottom=-1) + note.form(top=0, right=-1, bottom=-1) + +def MkPanedWindow(w): + """The PanedWindow widget allows the user to interactively manipulate + the sizes of several panes. The panes can be arranged either vertically + or horizontally. + """ + msg = Tix.Message(w, + relief=Tix.FLAT, width=240, anchor=Tix.N, + text='The PanedWindow widget allows the user to interactively manipulate the sizes of several panes. The panes can be arranged either vertically or horizontally.') + group = Tix.LabelEntry(w, label='Newsgroup:', options='entry.width 25') + group.entry.insert(0,'comp.lang.python') + pane = Tix.PanedWindow(w, orientation='vertical') + + p1 = pane.add('list', min=70, size=100) + p2 = pane.add('text', min=70) + list = Tix.ScrolledListBox(p1) + text = Tix.ScrolledText(p2) + + list.listbox.insert(Tix.END, " 12324 Re: Tkinter is good for your health") + list.listbox.insert(Tix.END, "+ 12325 Re: Tkinter is good for your health") + list.listbox.insert(Tix.END, "+ 12326 Re: Tix is even better for your health (Was: Tkinter is good...)") + list.listbox.insert(Tix.END, " 12327 Re: Tix is even better for your health (Was: Tkinter is good...)") + list.listbox.insert(Tix.END, "+ 12328 Re: Tix is even better for your health (Was: Tkinter is good...)") + list.listbox.insert(Tix.END, " 12329 Re: Tix is even better for your health (Was: Tkinter is good...)") + list.listbox.insert(Tix.END, "+ 12330 Re: Tix is even better for your health (Was: Tkinter is good...)") + + text.text['bg'] = list.listbox['bg'] + text.text['wrap'] = 'none' + text.text.insert(Tix.END, """ +Mon, 19 Jun 1995 11:39:52 comp.lang.python Thread 34 of 220 +Lines 353 A new way to put text and bitmaps together iNo responses +ioi@blue.seas.upenn.edu Ioi K. Lam at University of Pennsylvania + +Hi, + +I have implemented a new image type called "compound". It allows you +to glue together a bunch of bitmaps, images and text strings together +to form a bigger image. Then you can use this image with widgets that +support the -image option. For example, you can display a text string string +together with a bitmap, at the same time, inside a TK button widget. +""") + list.pack(expand=1, fill=Tix.BOTH, padx=4, pady=6) + text.pack(expand=1, fill=Tix.BOTH, padx=4, pady=6) + + msg.pack(side=Tix.TOP, padx=3, pady=3, fill=Tix.BOTH) + group.pack(side=Tix.TOP, padx=3, pady=3, fill=Tix.BOTH) + pane.pack(side=Tix.TOP, padx=3, pady=3, fill=Tix.BOTH, expand=1) + +def MkNoteBook(w): + msg = Tix.Message(w, + relief=Tix.FLAT, width=240, anchor=Tix.N, + text='The NoteBook widget allows you to layout a complex interface into individual pages.') + # prefix = Tix.OptionName(w) + # if not prefix: prefix = '' + # w.option_add('*' + prefix + '*TixNoteBook*tagPadX', 8) + options = "entry.width %d label.width %d label.anchor %s" % (10, 18, Tix.E) + + nb = Tix.NoteBook(w, ipadx=6, ipady=6, options=options) + nb.add('hard_disk', label="Hard Disk", underline=0) + nb.add('network', label="Network", underline=0) + + # Frame for the buttons that are present on all pages + common = Tix.Frame(nb.hard_disk) + common.pack(side=Tix.RIGHT, padx=2, pady=2, fill=Tix.Y) + CreateCommonButtons(common) + + # Widgets belonging only to this page + a = Tix.Control(nb.hard_disk, value=12, label='Access Time: ') + w = Tix.Control(nb.hard_disk, value=400, label='Write Throughput: ') + r = Tix.Control(nb.hard_disk, value=400, label='Read Throughput: ') + c = Tix.Control(nb.hard_disk, value=1021, label='Capacity: ') + a.pack(side=Tix.TOP, padx=20, pady=2) + w.pack(side=Tix.TOP, padx=20, pady=2) + r.pack(side=Tix.TOP, padx=20, pady=2) + c.pack(side=Tix.TOP, padx=20, pady=2) + + common = Tix.Frame(nb.network) + common.pack(side=Tix.RIGHT, padx=2, pady=2, fill=Tix.Y) + CreateCommonButtons(common) + + a = Tix.Control(nb.network, value=12, label='Access Time: ') + w = Tix.Control(nb.network, value=400, label='Write Throughput: ') + r = Tix.Control(nb.network, value=400, label='Read Throughput: ') + c = Tix.Control(nb.network, value=1021, label='Capacity: ') + u = Tix.Control(nb.network, value=10, label='Users: ') + a.pack(side=Tix.TOP, padx=20, pady=2) + w.pack(side=Tix.TOP, padx=20, pady=2) + r.pack(side=Tix.TOP, padx=20, pady=2) + c.pack(side=Tix.TOP, padx=20, pady=2) + u.pack(side=Tix.TOP, padx=20, pady=2) + + msg.pack(side=Tix.TOP, padx=3, pady=3, fill=Tix.BOTH) + nb.pack(side=Tix.TOP, padx=5, pady=5, fill=Tix.BOTH, expand=1) + +def CreateCommonButtons(f): + ok = Tix.Button(f, text='OK', width = 6) + cancel = Tix.Button(f, text='Cancel', width = 6) + ok.pack(side=Tix.TOP, padx=2, pady=2) + cancel.pack(side=Tix.TOP, padx=2, pady=2) + +def MkDirList(nb, name): + w = nb.page(name) + options = "label.padX 4" + + dir = Tix.LabelFrame(w, label='Tix.DirList', options=options) + fsbox = Tix.LabelFrame(w, label='Tix.ExFileSelectBox', options=options) + MkDirListWidget(dir.frame) + MkExFileWidget(fsbox.frame) + dir.form(top=0, left=0, right='%40', bottom=-1) + fsbox.form(top=0, left='%40', right=-1, bottom=-1) + +def MkDirListWidget(w): + """The TixDirList widget gives a graphical representation of the file + system directory and makes it easy for the user to choose and access + directories. + """ + msg = Tix.Message(w, + relief=Tix.FLAT, width=240, anchor=Tix.N, + text='The Tix DirList widget gives a graphical representation of the file system directory and makes it easy for the user to choose and access directories.') + dirlist = Tix.DirList(w, options='hlist.padY 1 hlist.width 25 hlist.height 16') + msg.pack(side=Tix.TOP, expand=1, fill=Tix.BOTH, padx=3, pady=3) + dirlist.pack(side=Tix.TOP, padx=3, pady=3) + +def MkExFileWidget(w): + """The TixExFileSelectBox widget is more user friendly than the Motif + style FileSelectBox. """ + msg = Tix.Message(w, + relief=Tix.FLAT, width=240, anchor=Tix.N, + text='The Tix ExFileSelectBox widget is more user friendly than the Motif style FileSelectBox.') + # There's a bug in the ComboBoxes - the scrolledlistbox is destroyed + box = Tix.ExFileSelectBox(w, bd=2, relief=Tix.RAISED) + msg.pack(side=Tix.TOP, expand=1, fill=Tix.BOTH, padx=3, pady=3) + box.pack(side=Tix.TOP, padx=3, pady=3) + +### +### List of all the demos we want to show off +comments = {'widget' : 'Widget Demos', 'image' : 'Image Demos'} +samples = {'Balloon' : 'Balloon', + 'Button Box' : 'BtnBox', + 'Combo Box' : 'ComboBox', + 'Compound Image' : 'CmpImg', + 'Directory List' : 'DirList', + 'Directory Tree' : 'DirTree', + 'Control' : 'Control', + 'Notebook' : 'NoteBook', + 'Option Menu' : 'OptMenu', + 'Paned Window' : 'PanedWin', + 'Popup Menu' : 'PopMenu', + 'ScrolledHList (1)' : 'SHList1', + 'ScrolledHList (2)' : 'SHList2', + 'Tree (dynamic)' : 'Tree' +} + +# There are still a lot of demos to be translated: +## set root { +## {d "File Selectors" file } +## {d "Hierachical ListBox" hlist } +## {d "Tabular ListBox" tlist {c tixTList}} +## {d "Grid Widget" grid {c tixGrid}} +## {d "Manager Widgets" manager } +## {d "Scrolled Widgets" scroll } +## {d "Miscellaneous Widgets" misc } +## {d "Image Types" image } +## } +## +## set image { +## {d "Compound Image" cmpimg } +## {d "XPM Image" xpm {i pixmap}} +## } +## +## set cmpimg { +##done {f "In Buttons" CmpImg.tcl } +## {f "In NoteBook" CmpImg2.tcl } +## {f "Notebook Color Tabs" CmpImg4.tcl } +## {f "Icons" CmpImg3.tcl } +## } +## +## set xpm { +## {f "In Button" Xpm.tcl {i pixmap}} +## {f "In Menu" Xpm1.tcl {i pixmap}} +## } +## +## set file { +##added {f DirList DirList.tcl } +##added {f DirTree DirTree.tcl } +## {f DirSelectDialog DirDlg.tcl } +## {f ExFileSelectDialog EFileDlg.tcl } +## {f FileSelectDialog FileDlg.tcl } +## {f FileEntry FileEnt.tcl } +## } +## +## set hlist { +## {f HList HList1.tcl } +## {f CheckList ChkList.tcl {c tixCheckList}} +##done {f "ScrolledHList (1)" SHList.tcl } +##done {f "ScrolledHList (2)" SHList2.tcl } +##done {f Tree Tree.tcl } +##done {f "Tree (Dynamic)" DynTree.tcl {v win}} +## } +## +## set tlist { +## {f "ScrolledTList (1)" STList1.tcl {c tixTList}} +## {f "ScrolledTList (2)" STList2.tcl {c tixTList}} +## } +## global tcl_platform +## # This demo hangs windows +## if {$tcl_platform(platform) != "windows"} { +##na lappend tlist {f "TList File Viewer" STList3.tcl {c tixTList}} +## } +## +## set grid { +##na {f "Simple Grid" SGrid0.tcl {c tixGrid}} +##na {f "ScrolledGrid" SGrid1.tcl {c tixGrid}} +##na {f "Editable Grid" EditGrid.tcl {c tixGrid}} +## } +## +## set scroll { +## {f ScrolledListBox SListBox.tcl } +## {f ScrolledText SText.tcl } +## {f ScrolledWindow SWindow.tcl } +##na {f "Canvas Object View" CObjView.tcl {c tixCObjView}} +## } +## +## set manager { +## {f ListNoteBook ListNBK.tcl } +##done {f NoteBook NoteBook.tcl } +##done {f PanedWindow PanedWin.tcl } +## } +## +## set misc { +##done {f Balloon Balloon.tcl } +##done {f ButtonBox BtnBox.tcl } +##done {f ComboBox ComboBox.tcl } +##done {f Control Control.tcl } +## {f LabelEntry LabEntry.tcl } +## {f LabelFrame LabFrame.tcl } +## {f Meter Meter.tcl {c tixMeter}} +##done {f OptionMenu OptMenu.tcl } +##done {f PopupMenu PopMenu.tcl } +## {f Select Select.tcl } +## {f StdButtonBox StdBBox.tcl } +## } +## + +stypes = {} +stypes['widget'] = ['Balloon', 'Button Box', 'Combo Box', 'Control', + 'Directory List', 'Directory Tree', + 'Notebook', 'Option Menu', 'Popup Menu', 'Paned Window', + 'ScrolledHList (1)', 'ScrolledHList (2)', 'Tree (dynamic)'] +stypes['image'] = ['Compound Image'] + +def MkSample(nb, name): + w = nb.page(name) + options = "label.padX 4" + + pane = Tix.PanedWindow(w, orientation='horizontal') + pane.pack(side=Tix.TOP, expand=1, fill=Tix.BOTH) + f1 = pane.add('list', expand='1') + f2 = pane.add('text', expand='5') + f1['relief'] = 'flat' + f2['relief'] = 'flat' + + lab = Tix.LabelFrame(f1, label='Select a sample program:') + lab.pack(side=Tix.TOP, expand=1, fill=Tix.BOTH, padx=5, pady=5) + lab1 = Tix.LabelFrame(f2, label='Source:') + lab1.pack(side=Tix.TOP, expand=1, fill=Tix.BOTH, padx=5, pady=5) + + slb = Tix.Tree(lab.frame, options='hlist.width 20') + slb.pack(side=Tix.TOP, expand=1, fill=Tix.BOTH, padx=5) + + stext = Tix.ScrolledText(lab1.frame, name='stext') + font = root.tk.eval('tix option get fixed_font') + stext.text.config(font=font) + + frame = Tix.Frame(lab1.frame, name='frame') + + run = Tix.Button(frame, text='Run ...', name='run') + view = Tix.Button(frame, text='View Source ...', name='view') + run.pack(side=Tix.LEFT, expand=0, fill=Tix.NONE) + view.pack(side=Tix.LEFT, expand=0, fill=Tix.NONE) + + stext.text['bg'] = slb.hlist['bg'] + stext.text['state'] = 'disabled' + stext.text['wrap'] = 'none' + stext.text['width'] = 80 + + frame.pack(side=Tix.BOTTOM, expand=0, fill=Tix.X, padx=7) + stext.pack(side=Tix.TOP, expand=0, fill=Tix.BOTH, padx=7) + + slb.hlist['separator'] = '.' + slb.hlist['width'] = 25 + slb.hlist['drawbranch'] = 0 + slb.hlist['indent'] = 10 + slb.hlist['wideselect'] = 1 + slb.hlist['command'] = lambda args=0, w=w,slb=slb,stext=stext,run=run,view=view: Sample_Action(w, slb, stext, run, view, 'run') + slb.hlist['browsecmd'] = lambda args=0, w=w,slb=slb,stext=stext,run=run,view=view: Sample_Action(w, slb, stext, run, view, 'browse') + + run['command'] = lambda args=0, w=w,slb=slb,stext=stext,run=run,view=view: Sample_Action(w, slb, stext, run, view, 'run') + view['command'] = lambda args=0, w=w,slb=slb,stext=stext,run=run,view=view: Sample_Action(w, slb, stext, run, view, 'view') + + for type in ['widget', 'image']: + if type != 'widget': + x = Tix.Frame(slb.hlist, bd=2, height=2, width=150, + relief=Tix.SUNKEN, bg=slb.hlist['bg']) + slb.hlist.add_child(itemtype=Tix.WINDOW, window=x, state='disabled') + x = slb.hlist.add_child(itemtype=Tix.TEXT, state='disabled', + text=comments[type]) + for key in stypes[type]: + slb.hlist.add_child(x, itemtype=Tix.TEXT, data=key, + text=key) + slb.hlist.selection_clear() + + run['state'] = 'disabled' + view['state'] = 'disabled' + +def Sample_Action(w, slb, stext, run, view, action): + global demo + + hlist = slb.hlist + anchor = hlist.info_anchor() + if not anchor: + run['state'] = 'disabled' + view['state'] = 'disabled' + elif not hlist.info_parent(anchor): + # a comment + return + + run['state'] = 'normal' + view['state'] = 'normal' + key = hlist.info_data(anchor) + title = key + prog = samples[key] + + if action == 'run': + exec('import ' + prog) + w = Tix.Toplevel() + w.title(title) + rtn = eval(prog + '.RunSample') + rtn(w) + elif action == 'view': + w = Tix.Toplevel() + w.title('Source view: ' + title) + LoadFile(w, demo.dir + '/samples/' + prog + '.py') + elif action == 'browse': + ReadFile(stext.text, demo.dir + '/samples/' + prog + '.py') + +def LoadFile(w, fname): + global root + b = Tix.Button(w, text='Close', command=w.destroy) + t = Tix.ScrolledText(w) + # b.form(left=0, bottom=0, padx=4, pady=4) + # t.form(left=0, bottom=b, right='-0', top=0) + t.pack() + b.pack() + + font = root.tk.eval('tix option get fixed_font') + t.text.config(font=font) + t.text['bd'] = 2 + t.text['wrap'] = 'none' + + ReadFile(t.text, fname) + +def ReadFile(w, fname): + old_state = w['state'] + w['state'] = 'normal' + w.delete('0.0', Tix.END) + + try: + f = open(fname) + lines = f.readlines() + for s in lines: + w.insert(Tix.END, s) + f.close() + finally: +# w.see('1.0') + w['state'] = old_state + +if __name__ == '__main__': + root = Tix.Tk() + RunMain(root) diff --git a/sys/src/cmd/python/Demo/tkinter/README b/sys/src/cmd/python/Demo/tkinter/README new file mode 100644 index 000000000..f245d163c --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/README @@ -0,0 +1,10 @@ +Several collections of example code for Tkinter. + +See the toplevel README for an explanation of the difference between +Tkinter and _tkinter, how to enable the Python Tk interface, and where +to get Matt Conway's lifesaver document. + +Subdirectories: + +guido my original example set (fairly random collection) +matt Matt Conway's examples, to go with his lifesaver document diff --git a/sys/src/cmd/python/Demo/tkinter/guido/AttrDialog.py b/sys/src/cmd/python/Demo/tkinter/guido/AttrDialog.py new file mode 100755 index 000000000..86333adc7 --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/guido/AttrDialog.py @@ -0,0 +1,452 @@ + +# The options of a widget are described by the following attributes +# of the Pack and Widget dialogs: +# +# Dialog.current: {name: value} +# -- changes during Widget's lifetime +# +# Dialog.options: {name: (default, klass)} +# -- depends on widget class only +# +# Dialog.classes: {klass: (v0, v1, v2, ...) | 'boolean' | 'other'} +# -- totally static, though different between PackDialog and WidgetDialog +# (but even that could be unified) + +from Tkinter import * + +class Option: + + varclass = StringVar # May be overridden + + def __init__(self, dialog, option): + self.dialog = dialog + self.option = option + self.master = dialog.top + self.default, self.klass = dialog.options[option] + self.var = self.varclass(self.master) + self.frame = Frame(self.master) + self.frame.pack(fill=X) + self.label = Label(self.frame, text=(option + ":")) + self.label.pack(side=LEFT) + self.update() + self.addoption() + + def refresh(self): + self.dialog.refresh() + self.update() + + def update(self): + try: + self.current = self.dialog.current[self.option] + except KeyError: + self.current = self.default + self.var.set(self.current) + + def set(self, e=None): # Should be overridden + pass + +class BooleanOption(Option): + + varclass = BooleanVar + + def addoption(self): + self.button = Checkbutton(self.frame, + text='on/off', + onvalue=1, + offvalue=0, + variable=self.var, + relief=RAISED, + borderwidth=2, + command=self.set) + self.button.pack(side=RIGHT) + +class EnumOption(Option): + + def addoption(self): + self.button = Menubutton(self.frame, + textvariable=self.var, + relief=RAISED, borderwidth=2) + self.button.pack(side=RIGHT) + self.menu = Menu(self.button) + self.button['menu'] = self.menu + for v in self.dialog.classes[self.klass]: + self.menu.add_radiobutton( + label=v, + variable=self.var, + value=v, + command=self.set) + +class StringOption(Option): + + def addoption(self): + self.entry = Entry(self.frame, + textvariable=self.var, + width=10, + relief=SUNKEN, + borderwidth=2) + self.entry.pack(side=RIGHT, fill=X, expand=1) + self.entry.bind('<Return>', self.set) + +class ReadonlyOption(Option): + + def addoption(self): + self.label = Label(self.frame, textvariable=self.var, + anchor=E) + self.label.pack(side=RIGHT) + +class Dialog: + + def __init__(self, master): + self.master = master + self.fixclasses() + self.refresh() + self.top = Toplevel(self.master) + self.top.title(self.__class__.__name__) + self.top.minsize(1, 1) + self.addchoices() + + def refresh(self): pass # Must override + + def fixclasses(self): pass # May override + + def addchoices(self): + self.choices = {} + list = [] + for k, dc in self.options.items(): + list.append((k, dc)) + list.sort() + for k, (d, c) in list: + try: + cl = self.classes[c] + except KeyError: + cl = 'unknown' + if type(cl) == TupleType: + cl = self.enumoption + elif cl == 'boolean': + cl = self.booleanoption + elif cl == 'readonly': + cl = self.readonlyoption + else: + cl = self.stringoption + self.choices[k] = cl(self, k) + + # Must override: + options = {} + classes = {} + + # May override: + booleanoption = BooleanOption + stringoption = StringOption + enumoption = EnumOption + readonlyoption = ReadonlyOption + +class PackDialog(Dialog): + + def __init__(self, widget): + self.widget = widget + Dialog.__init__(self, widget) + + def refresh(self): + self.current = self.widget.info() + self.current['.class'] = self.widget.winfo_class() + self.current['.name'] = self.widget._w + + class packoption: # Mix-in class + def set(self, e=None): + self.current = self.var.get() + try: + apply(self.dialog.widget.pack, (), + {self.option: self.current}) + except TclError, msg: + print msg + self.refresh() + + class booleanoption(packoption, BooleanOption): pass + class enumoption(packoption, EnumOption): pass + class stringoption(packoption, StringOption): pass + class readonlyoption(packoption, ReadonlyOption): pass + + options = { + '.class': (None, 'Class'), + '.name': (None, 'Name'), + 'after': (None, 'Widget'), + 'anchor': ('center', 'Anchor'), + 'before': (None, 'Widget'), + 'expand': ('no', 'Boolean'), + 'fill': ('none', 'Fill'), + 'in': (None, 'Widget'), + 'ipadx': (0, 'Pad'), + 'ipady': (0, 'Pad'), + 'padx': (0, 'Pad'), + 'pady': (0, 'Pad'), + 'side': ('top', 'Side'), + } + + classes = { + 'Anchor': (N, NE, E, SE, S, SW, W, NW, CENTER), + 'Boolean': 'boolean', + 'Class': 'readonly', + 'Expand': 'boolean', + 'Fill': (NONE, X, Y, BOTH), + 'Name': 'readonly', + 'Pad': 'pixel', + 'Side': (TOP, RIGHT, BOTTOM, LEFT), + 'Widget': 'readonly', + } + +class RemotePackDialog(PackDialog): + + def __init__(self, master, app, widget): + self.master = master + self.app = app + self.widget = widget + self.refresh() + self.top = Toplevel(self.master) + self.top.title(self.app + ' PackDialog') + self.top.minsize(1, 1) + self.addchoices() + + def refresh(self): + try: + words = self.master.tk.splitlist( + self.master.send(self.app, + 'pack', + 'info', + self.widget)) + except TclError, msg: + print msg + return + dict = {} + for i in range(0, len(words), 2): + key = words[i][1:] + value = words[i+1] + dict[key] = value + dict['.class'] = self.master.send(self.app, + 'winfo', + 'class', + self.widget) + dict['.name'] = self.widget + self.current = dict + + class remotepackoption: # Mix-in class + def set(self, e=None): + self.current = self.var.get() + try: + self.dialog.master.send( + self.dialog.app, + 'pack', + 'config', + self.dialog.widget, + '-'+self.option, + self.dialog.master.tk.merge( + self.current)) + except TclError, msg: + print msg + self.refresh() + + class booleanoption(remotepackoption, BooleanOption): pass + class enumoption(remotepackoption, EnumOption): pass + class stringoption(remotepackoption, StringOption): pass + class readonlyoption(remotepackoption, ReadonlyOption): pass + +class WidgetDialog(Dialog): + + def __init__(self, widget): + self.widget = widget + self.klass = widget.winfo_class() + Dialog.__init__(self, widget) + + def fixclasses(self): + if self.addclasses.has_key(self.klass): + classes = {} + for c in (self.classes, + self.addclasses[self.klass]): + for k in c.keys(): + classes[k] = c[k] + self.classes = classes + + def refresh(self): + self.configuration = self.widget.config() + self.update() + self.current['.class'] = self.widget.winfo_class() + self.current['.name'] = self.widget._w + + def update(self): + self.current = {} + self.options = {} + for k, v in self.configuration.items(): + if len(v) > 4: + self.current[k] = v[4] + self.options[k] = v[3], v[2] # default, klass + self.options['.class'] = (None, 'Class') + self.options['.name'] = (None, 'Name') + + class widgetoption: # Mix-in class + def set(self, e=None): + self.current = self.var.get() + try: + self.dialog.widget[self.option] = self.current + except TclError, msg: + print msg + self.refresh() + + class booleanoption(widgetoption, BooleanOption): pass + class enumoption(widgetoption, EnumOption): pass + class stringoption(widgetoption, StringOption): pass + class readonlyoption(widgetoption, ReadonlyOption): pass + + # Universal classes + classes = { + 'Anchor': (N, NE, E, SE, S, SW, W, NW, CENTER), + 'Aspect': 'integer', + 'Background': 'color', + 'Bitmap': 'bitmap', + 'BorderWidth': 'pixel', + 'Class': 'readonly', + 'CloseEnough': 'double', + 'Command': 'command', + 'Confine': 'boolean', + 'Cursor': 'cursor', + 'CursorWidth': 'pixel', + 'DisabledForeground': 'color', + 'ExportSelection': 'boolean', + 'Font': 'font', + 'Foreground': 'color', + 'From': 'integer', + 'Geometry': 'geometry', + 'Height': 'pixel', + 'InsertWidth': 'time', + 'Justify': (LEFT, CENTER, RIGHT), + 'Label': 'string', + 'Length': 'pixel', + 'MenuName': 'widget', + 'Name': 'readonly', + 'OffTime': 'time', + 'OnTime': 'time', + 'Orient': (HORIZONTAL, VERTICAL), + 'Pad': 'pixel', + 'Relief': (RAISED, SUNKEN, FLAT, RIDGE, GROOVE), + 'RepeatDelay': 'time', + 'RepeatInterval': 'time', + 'ScrollCommand': 'command', + 'ScrollIncrement': 'pixel', + 'ScrollRegion': 'rectangle', + 'ShowValue': 'boolean', + 'SetGrid': 'boolean', + 'Sliderforeground': 'color', + 'SliderLength': 'pixel', + 'Text': 'string', + 'TickInterval': 'integer', + 'To': 'integer', + 'Underline': 'index', + 'Variable': 'variable', + 'Value': 'string', + 'Width': 'pixel', + 'Wrap': (NONE, CHAR, WORD), + } + + # Classes that (may) differ per widget type + _tristate = {'State': (NORMAL, ACTIVE, DISABLED)} + _bistate = {'State': (NORMAL, DISABLED)} + addclasses = { + 'Button': _tristate, + 'Radiobutton': _tristate, + 'Checkbutton': _tristate, + 'Entry': _bistate, + 'Text': _bistate, + 'Menubutton': _tristate, + 'Slider': _bistate, + } + +class RemoteWidgetDialog(WidgetDialog): + + def __init__(self, master, app, widget): + self.app = app + self.widget = widget + self.klass = master.send(self.app, + 'winfo', + 'class', + self.widget) + Dialog.__init__(self, master) + + def refresh(self): + try: + items = self.master.tk.splitlist( + self.master.send(self.app, + self.widget, + 'config')) + except TclError, msg: + print msg + return + dict = {} + for item in items: + words = self.master.tk.splitlist(item) + key = words[0][1:] + value = (key,) + words[1:] + dict[key] = value + self.configuration = dict + self.update() + self.current['.class'] = self.klass + self.current['.name'] = self.widget + + class remotewidgetoption: # Mix-in class + def set(self, e=None): + self.current = self.var.get() + try: + self.dialog.master.send( + self.dialog.app, + self.dialog.widget, + 'config', + '-'+self.option, + self.current) + except TclError, msg: + print msg + self.refresh() + + class booleanoption(remotewidgetoption, BooleanOption): pass + class enumoption(remotewidgetoption, EnumOption): pass + class stringoption(remotewidgetoption, StringOption): pass + class readonlyoption(remotewidgetoption, ReadonlyOption): pass + +def test(): + import sys + root = Tk() + root.minsize(1, 1) + if sys.argv[1:]: + remotetest(root, sys.argv[1]) + else: + frame = Frame(root, name='frame') + frame.pack(expand=1, fill=BOTH) + button = Button(frame, name='button', text='button') + button.pack(expand=1) + canvas = Canvas(frame, name='canvas') + canvas.pack() + fpd = PackDialog(frame) + fwd = WidgetDialog(frame) + bpd = PackDialog(button) + bwd = WidgetDialog(button) + cpd = PackDialog(canvas) + cwd = WidgetDialog(canvas) + root.mainloop() + +def remotetest(root, app): + from listtree import listtree + list = listtree(root, app) + list.bind('<Any-Double-1>', opendialogs) + list.app = app # Pass it on to handler + +def opendialogs(e): + import string + list = e.widget + sel = list.curselection() + for i in sel: + item = list.get(i) + widget = string.split(item)[0] + RemoteWidgetDialog(list, list.app, widget) + if widget == '.': continue + try: + RemotePackDialog(list, list.app, widget) + except TclError, msg: + print msg + +test() diff --git a/sys/src/cmd/python/Demo/tkinter/guido/ManPage.py b/sys/src/cmd/python/Demo/tkinter/guido/ManPage.py new file mode 100755 index 000000000..de3117b35 --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/guido/ManPage.py @@ -0,0 +1,220 @@ +# Widget to display a man page + +import re +from Tkinter import * +from Tkinter import _tkinter +from ScrolledText import ScrolledText + +# XXX These fonts may have to be changed to match your system +BOLDFONT = '*-Courier-Bold-R-Normal-*-120-*' +ITALICFONT = '*-Courier-Medium-O-Normal-*-120-*' + +# XXX Recognizing footers is system dependent +# (This one works for IRIX 5.2 and Solaris 2.2) +footerprog = re.compile( + '^ Page [1-9][0-9]*[ \t]+\|^.*Last change:.*[1-9][0-9]*\n') +emptyprog = re.compile('^[ \t]*\n') +ulprog = re.compile('^[ \t]*[Xv!_][Xv!_ \t]*\n') + +# Basic Man Page class -- does not disable editing +class EditableManPage(ScrolledText): + + # Initialize instance + def __init__(self, master=None, **cnf): + # Initialize base class + apply(ScrolledText.__init__, (self, master), cnf) + + # Define tags for formatting styles + self.tag_config('X', underline=1) + self.tag_config('!', font=BOLDFONT) + self.tag_config('_', font=ITALICFONT) + + # Set state to idle + self.fp = None + self.lineno = 0 + + # Test whether we are busy parsing a file + def busy(self): + return self.fp != None + + # Ensure we're not busy + def kill(self): + if self.busy(): + self._endparser() + + # Parse a file, in the background + def asyncparsefile(self, fp): + self._startparser(fp) + self.tk.createfilehandler(fp, _tkinter.READABLE, + self._filehandler) + + parsefile = asyncparsefile # Alias + + # I/O handler used by background parsing + def _filehandler(self, fp, mask): + nextline = self.fp.readline() + if not nextline: + self._endparser() + return + self._parseline(nextline) + + # Parse a file, now (cannot be aborted) + def syncparsefile(self, fp): + from select import select + def avail(fp=fp, tout=0.0, select=select): + return select([fp], [], [], tout)[0] + height = self.getint(self['height']) + self._startparser(fp) + while 1: + nextline = fp.readline() + if not nextline: + break + self._parseline(nextline) + self._endparser() + + # Initialize parsing from a particular file -- must not be busy + def _startparser(self, fp): + if self.busy(): + raise RuntimeError, 'startparser: still busy' + fp.fileno() # Test for file-ness + self.fp = fp + self.lineno = 0 + self.ok = 0 + self.empty = 0 + self.buffer = None + savestate = self['state'] + self['state'] = NORMAL + self.delete('1.0', END) + self['state'] = savestate + + # End parsing -- must be busy, need not be at EOF + def _endparser(self): + if not self.busy(): + raise RuntimeError, 'endparser: not busy' + if self.buffer: + self._parseline('') + try: + self.tk.deletefilehandler(self.fp) + except TclError, msg: + pass + self.fp.close() + self.fp = None + del self.ok, self.empty, self.buffer + + # Parse a single line + def _parseline(self, nextline): + if not self.buffer: + # Save this line -- we need one line read-ahead + self.buffer = nextline + return + if emptyprog.match(self.buffer) >= 0: + # Buffered line was empty -- set a flag + self.empty = 1 + self.buffer = nextline + return + textline = self.buffer + if ulprog.match(nextline) >= 0: + # Next line is properties for buffered line + propline = nextline + self.buffer = None + else: + # Next line is read-ahead + propline = None + self.buffer = nextline + if not self.ok: + # First non blank line after footer must be header + # -- skip that too + self.ok = 1 + self.empty = 0 + return + if footerprog.match(textline) >= 0: + # Footer -- start skipping until next non-blank line + self.ok = 0 + self.empty = 0 + return + savestate = self['state'] + self['state'] = NORMAL + if TkVersion >= 4.0: + self.mark_set('insert', 'end-1c') + else: + self.mark_set('insert', END) + if self.empty: + # One or more previous lines were empty + # -- insert one blank line in the text + self._insert_prop('\n') + self.lineno = self.lineno + 1 + self.empty = 0 + if not propline: + # No properties + self._insert_prop(textline) + else: + # Search for properties + p = '' + j = 0 + for i in range(min(len(propline), len(textline))): + if propline[i] != p: + if j < i: + self._insert_prop(textline[j:i], p) + j = i + p = propline[i] + self._insert_prop(textline[j:]) + self.lineno = self.lineno + 1 + self['state'] = savestate + + # Insert a string at the end, with at most one property (tag) + def _insert_prop(self, str, prop = ' '): + here = self.index(AtInsert()) + self.insert(AtInsert(), str) + if TkVersion <= 4.0: + tags = self.tag_names(here) + for tag in tags: + self.tag_remove(tag, here, AtInsert()) + if prop != ' ': + self.tag_add(prop, here, AtInsert()) + +# Readonly Man Page class -- disables editing, otherwise the same +class ReadonlyManPage(EditableManPage): + + # Initialize instance + def __init__(self, master=None, **cnf): + cnf['state'] = DISABLED + apply(EditableManPage.__init__, (self, master), cnf) + +# Alias +ManPage = ReadonlyManPage + +# Test program. +# usage: ManPage [manpage]; or ManPage [-f] file +# -f means that the file is nroff -man output run through ul -i +def test(): + import os + import sys + # XXX This directory may be different on your system + MANDIR = '/usr/local/man/mann' + DEFAULTPAGE = 'Tcl' + formatted = 0 + if sys.argv[1:] and sys.argv[1] == '-f': + formatted = 1 + del sys.argv[1] + if sys.argv[1:]: + name = sys.argv[1] + else: + name = DEFAULTPAGE + if not formatted: + if name[-2:-1] != '.': + name = name + '.n' + name = os.path.join(MANDIR, name) + root = Tk() + root.minsize(1, 1) + manpage = ManPage(root, relief=SUNKEN, borderwidth=2) + manpage.pack(expand=1, fill=BOTH) + if formatted: + fp = open(name, 'r') + else: + fp = os.popen('nroff -man %s | ul -i' % name, 'r') + manpage.parsefile(fp) + root.mainloop() + +# Run the test program when called as a script +if __name__ == '__main__': + test() diff --git a/sys/src/cmd/python/Demo/tkinter/guido/MimeViewer.py b/sys/src/cmd/python/Demo/tkinter/guido/MimeViewer.py new file mode 100755 index 000000000..749442589 --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/guido/MimeViewer.py @@ -0,0 +1,143 @@ +#! /usr/bin/env python + +# View a single MIME multipart message. +# Display each part as a box. + +import string +from types import * +from Tkinter import * +from ScrolledText import ScrolledText + +class MimeViewer: + def __init__(self, parent, title, msg): + self.title = title + self.msg = msg + self.frame = Frame(parent, {'relief': 'raised', 'bd': 2}) + self.frame.packing = {'expand': 0, 'fill': 'both'} + self.button = Checkbutton(self.frame, + {'text': title, + 'command': self.toggle}) + self.button.pack({'anchor': 'w'}) + headertext = msg.getheadertext( + lambda x: x != 'received' and x[:5] != 'x400-') + height = countlines(headertext, 4) + if height: + self.htext = ScrolledText(self.frame, + {'height': height, + 'width': 80, + 'wrap': 'none', + 'relief': 'raised', + 'bd': 2}) + self.htext.packing = {'expand': 1, 'fill': 'both', + 'after': self.button} + self.htext.insert('end', headertext) + else: + self.htext = Frame(self.frame, + {'relief': 'raised', 'bd': 2}) + self.htext.packing = {'side': 'top', + 'ipady': 2, + 'fill': 'x', + 'after': self.button} + body = msg.getbody() + if type(body) == StringType: + self.pad = None + height = countlines(body, 10) + if height: + self.btext = ScrolledText(self.frame, + {'height': height, + 'width': 80, + 'wrap': 'none', + 'relief': 'raised', + 'bd': 2}) + self.btext.packing = {'expand': 1, + 'fill': 'both'} + self.btext.insert('end', body) + else: + self.btext = None + self.parts = None + else: + self.pad = Frame(self.frame, + {'relief': 'flat', 'bd': 2}) + self.pad.packing = {'side': 'left', 'ipadx': 10, + 'fill': 'y', 'after': self.htext} + self.parts = [] + for i in range(len(body)): + p = MimeViewer(self.frame, + '%s.%d' % (title, i+1), + body[i]) + self.parts.append(p) + self.btext = None + self.collapsed = 1 + def pack(self): + self.frame.pack(self.frame.packing) + def destroy(self): + self.frame.destroy() + def show(self): + if self.collapsed: + self.button.invoke() + def toggle(self): + if self.collapsed: + self.explode() + else: + self.collapse() + def collapse(self): + self.collapsed = 1 + for comp in self.htext, self.btext, self.pad: + if comp: + comp.forget() + if self.parts: + for part in self.parts: + part.frame.forget() + self.frame.pack({'expand': 0}) + def explode(self): + self.collapsed = 0 + for comp in self.htext, self.btext, self.pad: + if comp: comp.pack(comp.packing) + if self.parts: + for part in self.parts: + part.pack() + self.frame.pack({'expand': 1}) + +def countlines(str, limit): + i = 0 + n = 0 + while n < limit: + i = string.find(str, '\n', i) + if i < 0: break + n = n+1 + i = i+1 + return n + +def main(): + import sys + import getopt + import mhlib + opts, args = getopt.getopt(sys.argv[1:], '') + for o, a in opts: + pass + message = None + folder = 'inbox' + for arg in args: + if arg[:1] == '+': + folder = arg[1:] + else: + message = string.atoi(arg) + + mh = mhlib.MH() + f = mh.openfolder(folder) + if not message: + message = f.getcurrent() + m = f.openmessage(message) + + root = Tk() + tk = root.tk + + top = MimeViewer(root, '+%s/%d' % (folder, message), m) + top.pack() + top.show() + + root.minsize(1, 1) + + tk.mainloop() + +if __name__ == '__main__': main() diff --git a/sys/src/cmd/python/Demo/tkinter/guido/ShellWindow.py b/sys/src/cmd/python/Demo/tkinter/guido/ShellWindow.py new file mode 100755 index 000000000..609101bc8 --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/guido/ShellWindow.py @@ -0,0 +1,151 @@ +import os +import sys +import string +from Tkinter import * +from ScrolledText import ScrolledText +from Dialog import Dialog +import signal + +BUFSIZE = 512 + +class ShellWindow(ScrolledText): + + def __init__(self, master=None, shell=None, **cnf): + if not shell: + try: + shell = os.environ['SHELL'] + except KeyError: + shell = '/bin/sh' + shell = shell + ' -i' + args = string.split(shell) + shell = args[0] + + apply(ScrolledText.__init__, (self, master), cnf) + self.pos = '1.0' + self.bind('<Return>', self.inputhandler) + self.bind('<Control-c>', self.sigint) + self.bind('<Control-t>', self.sigterm) + self.bind('<Control-k>', self.sigkill) + self.bind('<Control-d>', self.sendeof) + + self.pid, self.fromchild, self.tochild = spawn(shell, args) + self.tk.createfilehandler(self.fromchild, READABLE, + self.outputhandler) + + def outputhandler(self, file, mask): + data = os.read(file, BUFSIZE) + if not data: + self.tk.deletefilehandler(file) + pid, sts = os.waitpid(self.pid, 0) + print 'pid', pid, 'status', sts + self.pid = None + detail = sts>>8 + cause = sts & 0xff + if cause == 0: + msg = "exit status %d" % detail + else: + msg = "killed by signal %d" % (cause & 0x7f) + if cause & 0x80: + msg = msg + " -- core dumped" + Dialog(self.master, + text=msg, + title="Exit status", + bitmap='warning', + default=0, + strings=('OK',)) + return + self.insert(END, data) + self.pos = self.index("end - 1 char") + self.yview_pickplace(END) + + def inputhandler(self, *args): + if not self.pid: + self.no_process() + return "break" + self.insert(END, "\n") + line = self.get(self.pos, "end - 1 char") + self.pos = self.index(END) + os.write(self.tochild, line) + return "break" + + def sendeof(self, *args): + if not self.pid: + self.no_process() + return "break" + os.close(self.tochild) + return "break" + + def sendsig(self, sig): + if not self.pid: + self.no_process() + return "break" + os.kill(self.pid, sig) + return "break" + + def sigint(self, *args): + return self.sendsig(signal.SIGINT) + + def sigquit(self, *args): + return self.sendsig(signal.SIGQUIT) + + def sigterm(self, *args): + return self.sendsig(signal.SIGTERM) + + def sigkill(self, *args): + return self.sendsig(signal.SIGKILL) + + def no_process(self): + Dialog(self.master, + text="No active process", + title="No process", + bitmap='error', + default=0, + strings=('OK',)) + +MAXFD = 100 # Max number of file descriptors (os.getdtablesize()???) + +def spawn(prog, args): + p2cread, p2cwrite = os.pipe() + c2pread, c2pwrite = os.pipe() + pid = os.fork() + if pid == 0: + # Child + for i in 0, 1, 2: + try: + os.close(i) + except os.error: + pass + if os.dup(p2cread) <> 0: + sys.stderr.write('popen2: bad read dup\n') + if os.dup(c2pwrite) <> 1: + sys.stderr.write('popen2: bad write dup\n') + if os.dup(c2pwrite) <> 2: + sys.stderr.write('popen2: bad write dup\n') + for i in range(3, MAXFD): + try: + os.close(i) + except: + pass + try: + os.execvp(prog, args) + finally: + sys.stderr.write('execvp failed\n') + os._exit(1) + os.close(p2cread) + os.close(c2pwrite) + return pid, c2pread, p2cwrite + +def test(): + shell = string.join(sys.argv[1:]) + root = Tk() + root.minsize(1, 1) + if shell: + w = ShellWindow(root, shell=shell) + else: + w = ShellWindow(root) + w.pack(expand=1, fill=BOTH) + w.focus_set() + w.tk.mainloop() + +if __name__ == '__main__': + test() diff --git a/sys/src/cmd/python/Demo/tkinter/guido/brownian.py b/sys/src/cmd/python/Demo/tkinter/guido/brownian.py new file mode 100644 index 000000000..8007f141c --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/guido/brownian.py @@ -0,0 +1,50 @@ +# Brownian motion -- an example of a multi-threaded Tkinter program. + +from Tkinter import * +import random +import threading +import time +import sys + +WIDTH = 400 +HEIGHT = 300 +SIGMA = 10 +BUZZ = 2 +RADIUS = 2 +LAMBDA = 10 +FILL = 'red' + +stop = 0 # Set when main loop exits + +def particle(canvas): + r = RADIUS + x = random.gauss(WIDTH/2.0, SIGMA) + y = random.gauss(HEIGHT/2.0, SIGMA) + p = canvas.create_oval(x-r, y-r, x+r, y+r, fill=FILL) + while not stop: + dx = random.gauss(0, BUZZ) + dy = random.gauss(0, BUZZ) + dt = random.expovariate(LAMBDA) + try: + canvas.move(p, dx, dy) + except TclError: + break + time.sleep(dt) + +def main(): + global stop + root = Tk() + canvas = Canvas(root, width=WIDTH, height=HEIGHT) + canvas.pack(fill='both', expand=1) + np = 30 + if sys.argv[1:]: + np = int(sys.argv[1]) + for i in range(np): + t = threading.Thread(target=particle, args=(canvas,)) + t.start() + try: + root.mainloop() + finally: + stop = 1 + +main() diff --git a/sys/src/cmd/python/Demo/tkinter/guido/canvasevents.py b/sys/src/cmd/python/Demo/tkinter/guido/canvasevents.py new file mode 100644 index 000000000..74ed76f61 --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/guido/canvasevents.py @@ -0,0 +1,244 @@ +#! /usr/bin/env python + +from Tkinter import * +from Canvas import Oval, Group, CanvasText + + +# Fix a bug in Canvas.Group as distributed in Python 1.4. The +# distributed bind() method is broken. This is what should be used: + +class Group(Group): + def bind(self, sequence=None, command=None): + return self.canvas.tag_bind(self.id, sequence, command) + +class Object: + + """Base class for composite graphical objects. + + Objects belong to a canvas, and can be moved around on the canvas. + They also belong to at most one ``pile'' of objects, and can be + transferred between piles (or removed from their pile). + + Objects have a canonical ``x, y'' position which is moved when the + object is moved. Where the object is relative to this position + depends on the object; for simple objects, it may be their center. + + Objects have mouse sensitivity. They can be clicked, dragged and + double-clicked. The behavior may actually determined by the pile + they are in. + + All instance attributes are public since the derived class may + need them. + + """ + + def __init__(self, canvas, x=0, y=0, fill='red', text='object'): + self.canvas = canvas + self.x = x + self.y = y + self.pile = None + self.group = Group(self.canvas) + self.createitems(fill, text) + + def __str__(self): + return str(self.group) + + def createitems(self, fill, text): + self.__oval = Oval(self.canvas, + self.x-20, self.y-10, self.x+20, self.y+10, + fill=fill, width=3) + self.group.addtag_withtag(self.__oval) + self.__text = CanvasText(self.canvas, + self.x, self.y, text=text) + self.group.addtag_withtag(self.__text) + + def moveby(self, dx, dy): + if dx == dy == 0: + return + self.group.move(dx, dy) + self.x = self.x + dx + self.y = self.y + dy + + def moveto(self, x, y): + self.moveby(x - self.x, y - self.y) + + def transfer(self, pile): + if self.pile: + self.pile.delete(self) + self.pile = None + self.pile = pile + if self.pile: + self.pile.add(self) + + def tkraise(self): + self.group.tkraise() + + +class Bottom(Object): + + """An object to serve as the bottom of a pile.""" + + def createitems(self, *args): + self.__oval = Oval(self.canvas, + self.x-20, self.y-10, self.x+20, self.y+10, + fill='gray', outline='') + self.group.addtag_withtag(self.__oval) + + +class Pile: + + """A group of graphical objects.""" + + def __init__(self, canvas, x, y, tag=None): + self.canvas = canvas + self.x = x + self.y = y + self.objects = [] + self.bottom = Bottom(self.canvas, self.x, self.y) + self.group = Group(self.canvas, tag=tag) + self.group.addtag_withtag(self.bottom.group) + self.bindhandlers() + + def bindhandlers(self): + self.group.bind('<1>', self.clickhandler) + self.group.bind('<Double-1>', self.doubleclickhandler) + + def add(self, object): + self.objects.append(object) + self.group.addtag_withtag(object.group) + self.position(object) + + def delete(self, object): + object.group.dtag(self.group) + self.objects.remove(object) + + def position(self, object): + object.tkraise() + i = self.objects.index(object) + object.moveto(self.x + i*4, self.y + i*8) + + def clickhandler(self, event): + pass + + def doubleclickhandler(self, event): + pass + + +class MovingPile(Pile): + + def bindhandlers(self): + Pile.bindhandlers(self) + self.group.bind('<B1-Motion>', self.motionhandler) + self.group.bind('<ButtonRelease-1>', self.releasehandler) + + movethis = None + + def clickhandler(self, event): + tags = self.canvas.gettags('current') + for i in range(len(self.objects)): + o = self.objects[i] + if o.group.tag in tags: + break + else: + self.movethis = None + return + self.movethis = self.objects[i:] + for o in self.movethis: + o.tkraise() + self.lastx = event.x + self.lasty = event.y + + doubleclickhandler = clickhandler + + def motionhandler(self, event): + if not self.movethis: + return + dx = event.x - self.lastx + dy = event.y - self.lasty + self.lastx = event.x + self.lasty = event.y + for o in self.movethis: + o.moveby(dx, dy) + + def releasehandler(self, event): + objects = self.movethis + if not objects: + return + self.movethis = None + self.finishmove(objects) + + def finishmove(self, objects): + for o in objects: + self.position(o) + + +class Pile1(MovingPile): + + x = 50 + y = 50 + tag = 'p1' + + def __init__(self, demo): + self.demo = demo + MovingPile.__init__(self, self.demo.canvas, self.x, self.y, self.tag) + + def doubleclickhandler(self, event): + try: + o = self.objects[-1] + except IndexError: + return + o.transfer(self.other()) + MovingPile.doubleclickhandler(self, event) + + def other(self): + return self.demo.p2 + + def finishmove(self, objects): + o = objects[0] + p = self.other() + x, y = o.x, o.y + if (x-p.x)**2 + (y-p.y)**2 < (x-self.x)**2 + (y-self.y)**2: + for o in objects: + o.transfer(p) + else: + MovingPile.finishmove(self, objects) + +class Pile2(Pile1): + + x = 150 + y = 50 + tag = 'p2' + + def other(self): + return self.demo.p1 + + +class Demo: + + def __init__(self, master): + self.master = master + self.canvas = Canvas(master, + width=200, height=200, + background='yellow', + relief=SUNKEN, borderwidth=2) + self.canvas.pack(expand=1, fill=BOTH) + self.p1 = Pile1(self) + self.p2 = Pile2(self) + o1 = Object(self.canvas, fill='red', text='o1') + o2 = Object(self.canvas, fill='green', text='o2') + o3 = Object(self.canvas, fill='light blue', text='o3') + o1.transfer(self.p1) + o2.transfer(self.p1) + o3.transfer(self.p2) + + +# Main function, run when invoked as a stand-alone Python program. + +def main(): + root = Tk() + demo = Demo(root) + root.protocol('WM_DELETE_WINDOW', root.quit) + root.mainloop() + +if __name__ == '__main__': + main() diff --git a/sys/src/cmd/python/Demo/tkinter/guido/dialog.py b/sys/src/cmd/python/Demo/tkinter/guido/dialog.py new file mode 100755 index 000000000..50d84b9a3 --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/guido/dialog.py @@ -0,0 +1,109 @@ +#! /usr/bin/env python + +# A Python function that generates dialog boxes with a text message, +# optional bitmap, and any number of buttons. +# Cf. Ousterhout, Tcl and the Tk Toolkit, Figs. 27.2-3, pp. 269-270. + +from Tkinter import * +import sys + + +def dialog(master, title, text, bitmap, default, *args): + + # 1. Create the top-level window and divide it into top + # and bottom parts. + + w = Toplevel(master, class_='Dialog') + w.title(title) + w.iconname('Dialog') + + top = Frame(w, relief=RAISED, borderwidth=1) + top.pack(side=TOP, fill=BOTH) + bot = Frame(w, relief=RAISED, borderwidth=1) + bot.pack(side=BOTTOM, fill=BOTH) + + # 2. Fill the top part with the bitmap and message. + + msg = Message(top, width='3i', text=text, + font='-Adobe-Times-Medium-R-Normal-*-180-*') + msg.pack(side=RIGHT, expand=1, fill=BOTH, padx='3m', pady='3m') + if bitmap: + bm = Label(top, bitmap=bitmap) + bm.pack(side=LEFT, padx='3m', pady='3m') + + # 3. Create a row of buttons at the bottom of the dialog. + + var = IntVar() + buttons = [] + i = 0 + for but in args: + b = Button(bot, text=but, command=lambda v=var,i=i: v.set(i)) + buttons.append(b) + if i == default: + bd = Frame(bot, relief=SUNKEN, borderwidth=1) + bd.pack(side=LEFT, expand=1, padx='3m', pady='2m') + b.lift() + b.pack (in_=bd, side=LEFT, + padx='2m', pady='2m', ipadx='2m', ipady='1m') + else: + b.pack (side=LEFT, expand=1, + padx='3m', pady='3m', ipadx='2m', ipady='1m') + i = i+1 + + # 4. Set up a binding for <Return>, if there's a default, + # set a grab, and claim the focus too. + + if default >= 0: + w.bind('<Return>', + lambda e, b=buttons[default], v=var, i=default: + (b.flash(), + v.set(i))) + + oldFocus = w.focus_get() + w.grab_set() + w.focus_set() + + # 5. Wait for the user to respond, then restore the focus + # and return the index of the selected button. + + w.waitvar(var) + w.destroy() + if oldFocus: oldFocus.focus_set() + return var.get() + +# The rest is the test program. + +def go(): + i = dialog(mainWidget, + 'Not Responding', + "The file server isn't responding right now; " + "I'll keep trying.", + '', + -1, + 'OK') + print 'pressed button', i + i = dialog(mainWidget, + 'File Modified', + 'File "tcl.h" has been modified since ' + 'the last time it was saved. ' + 'Do you want to save it before exiting the application?', + 'warning', + 0, + 'Save File', + 'Discard Changes', + 'Return To Editor') + print 'pressed button', i + +def test(): + import sys + global mainWidget + mainWidget = Frame() + Pack.config(mainWidget) + start = Button(mainWidget, text='Press Here To Start', command=go) + start.pack() + endit = Button(mainWidget, text="Exit", command=sys.exit) + endit.pack(fill=BOTH) + mainWidget.mainloop() + +if __name__ == '__main__': + test() diff --git a/sys/src/cmd/python/Demo/tkinter/guido/electrons.py b/sys/src/cmd/python/Demo/tkinter/guido/electrons.py new file mode 100755 index 000000000..fdc558f88 --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/guido/electrons.py @@ -0,0 +1,91 @@ +#! /usr/bin/env python + +# Simulate "electrons" migrating across the screen. +# An optional bitmap file in can be in the background. +# +# Usage: electrons [n [bitmapfile]] +# +# n is the number of electrons to animate; default is 30. +# +# The bitmap file can be any X11 bitmap file (look in +# /usr/include/X11/bitmaps for samples); it is displayed as the +# background of the animation. Default is no bitmap. + +from Tkinter import * +import random + + +# The graphical interface +class Electrons: + + # Create our objects + def __init__(self, n, bitmap = None): + self.n = n + self.tk = tk = Tk() + self.canvas = c = Canvas(tk) + c.pack() + width, height = tk.getint(c['width']), tk.getint(c['height']) + + # Add background bitmap + if bitmap: + self.bitmap = c.create_bitmap(width/2, height/2, + bitmap=bitmap, + foreground='blue') + + self.pieces = [] + x1, y1, x2, y2 = 10,70,14,74 + for i in range(n): + p = c.create_oval(x1, y1, x2, y2, fill='red') + self.pieces.append(p) + y1, y2 = y1 +2, y2 + 2 + self.tk.update() + + def random_move(self, n): + c = self.canvas + for p in self.pieces: + x = random.choice(range(-2,4)) + y = random.choice(range(-3,4)) + c.move(p, x, y) + self.tk.update() + + # Run -- allow 500 movemens + def run(self): + try: + for i in range(500): + self.random_move(self.n) + except TclError: + try: + self.tk.destroy() + except TclError: + pass + + +# Main program +def main(): + import sys, string + + # First argument is number of electrons, default 30 + if sys.argv[1:]: + n = string.atoi(sys.argv[1]) + else: + n = 30 + + # Second argument is bitmap file, default none + if sys.argv[2:]: + bitmap = sys.argv[2] + # Reverse meaning of leading '@' compared to Tk + if bitmap[0] == '@': bitmap = bitmap[1:] + else: bitmap = '@' + bitmap + else: + bitmap = None + + # Create the graphical objects... + h = Electrons(n, bitmap) + + # ...and run! + h.run() + + +# Call main when run as script +if __name__ == '__main__': + main() diff --git a/sys/src/cmd/python/Demo/tkinter/guido/hanoi.py b/sys/src/cmd/python/Demo/tkinter/guido/hanoi.py new file mode 100755 index 000000000..078c24611 --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/guido/hanoi.py @@ -0,0 +1,154 @@ +# Animated Towers of Hanoi using Tk with optional bitmap file in +# background. +# +# Usage: tkhanoi [n [bitmapfile]] +# +# n is the number of pieces to animate; default is 4, maximum 15. +# +# The bitmap file can be any X11 bitmap file (look in +# /usr/include/X11/bitmaps for samples); it is displayed as the +# background of the animation. Default is no bitmap. + +# This uses Steen Lumholt's Tk interface +from Tkinter import * + + +# Basic Towers-of-Hanoi algorithm: move n pieces from a to b, using c +# as temporary. For each move, call report() +def hanoi(n, a, b, c, report): + if n <= 0: return + hanoi(n-1, a, c, b, report) + report(n, a, b) + hanoi(n-1, c, b, a, report) + + +# The graphical interface +class Tkhanoi: + + # Create our objects + def __init__(self, n, bitmap = None): + self.n = n + self.tk = tk = Tk() + self.canvas = c = Canvas(tk) + c.pack() + width, height = tk.getint(c['width']), tk.getint(c['height']) + + # Add background bitmap + if bitmap: + self.bitmap = c.create_bitmap(width/2, height/2, + bitmap=bitmap, + foreground='blue') + + # Generate pegs + pegwidth = 10 + pegheight = height/2 + pegdist = width/3 + x1, y1 = (pegdist-pegwidth)/2, height*1/3 + x2, y2 = x1+pegwidth, y1+pegheight + self.pegs = [] + p = c.create_rectangle(x1, y1, x2, y2, fill='black') + self.pegs.append(p) + x1, x2 = x1+pegdist, x2+pegdist + p = c.create_rectangle(x1, y1, x2, y2, fill='black') + self.pegs.append(p) + x1, x2 = x1+pegdist, x2+pegdist + p = c.create_rectangle(x1, y1, x2, y2, fill='black') + self.pegs.append(p) + self.tk.update() + + # Generate pieces + pieceheight = pegheight/16 + maxpiecewidth = pegdist*2/3 + minpiecewidth = 2*pegwidth + self.pegstate = [[], [], []] + self.pieces = {} + x1, y1 = (pegdist-maxpiecewidth)/2, y2-pieceheight-2 + x2, y2 = x1+maxpiecewidth, y1+pieceheight + dx = (maxpiecewidth-minpiecewidth) / (2*max(1, n-1)) + for i in range(n, 0, -1): + p = c.create_rectangle(x1, y1, x2, y2, fill='red') + self.pieces[i] = p + self.pegstate[0].append(i) + x1, x2 = x1 + dx, x2-dx + y1, y2 = y1 - pieceheight-2, y2-pieceheight-2 + self.tk.update() + self.tk.after(25) + + # Run -- never returns + def run(self): + while 1: + hanoi(self.n, 0, 1, 2, self.report) + hanoi(self.n, 1, 2, 0, self.report) + hanoi(self.n, 2, 0, 1, self.report) + hanoi(self.n, 0, 2, 1, self.report) + hanoi(self.n, 2, 1, 0, self.report) + hanoi(self.n, 1, 0, 2, self.report) + + # Reporting callback for the actual hanoi function + def report(self, i, a, b): + if self.pegstate[a][-1] != i: raise RuntimeError # Assertion + del self.pegstate[a][-1] + p = self.pieces[i] + c = self.canvas + + # Lift the piece above peg a + ax1, ay1, ax2, ay2 = c.bbox(self.pegs[a]) + while 1: + x1, y1, x2, y2 = c.bbox(p) + if y2 < ay1: break + c.move(p, 0, -1) + self.tk.update() + + # Move it towards peg b + bx1, by1, bx2, by2 = c.bbox(self.pegs[b]) + newcenter = (bx1+bx2)/2 + while 1: + x1, y1, x2, y2 = c.bbox(p) + center = (x1+x2)/2 + if center == newcenter: break + if center > newcenter: c.move(p, -1, 0) + else: c.move(p, 1, 0) + self.tk.update() + + # Move it down on top of the previous piece + pieceheight = y2-y1 + newbottom = by2 - pieceheight*len(self.pegstate[b]) - 2 + while 1: + x1, y1, x2, y2 = c.bbox(p) + if y2 >= newbottom: break + c.move(p, 0, 1) + self.tk.update() + + # Update peg state + self.pegstate[b].append(i) + + +# Main program +def main(): + import sys, string + + # First argument is number of pegs, default 4 + if sys.argv[1:]: + n = string.atoi(sys.argv[1]) + else: + n = 4 + + # Second argument is bitmap file, default none + if sys.argv[2:]: + bitmap = sys.argv[2] + # Reverse meaning of leading '@' compared to Tk + if bitmap[0] == '@': bitmap = bitmap[1:] + else: bitmap = '@' + bitmap + else: + bitmap = None + + # Create the graphical objects... + h = Tkhanoi(n, bitmap) + + # ...and run! + h.run() + + +# Call main when run as script +if __name__ == '__main__': + main() diff --git a/sys/src/cmd/python/Demo/tkinter/guido/hello.py b/sys/src/cmd/python/Demo/tkinter/guido/hello.py new file mode 100755 index 000000000..358a7eceb --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/guido/hello.py @@ -0,0 +1,17 @@ +# Display hello, world in a button; clicking it quits the program + +import sys +from Tkinter import * + +def main(): + root = Tk() + button = Button(root) + button['text'] = 'Hello, world' + button['command'] = quit_callback # See below + button.pack() + root.mainloop() + +def quit_callback(): + sys.exit(0) + +main() diff --git a/sys/src/cmd/python/Demo/tkinter/guido/imagedraw.py b/sys/src/cmd/python/Demo/tkinter/guido/imagedraw.py new file mode 100755 index 000000000..d3dba4565 --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/guido/imagedraw.py @@ -0,0 +1,23 @@ +"""Draw on top of an image""" + +from Tkinter import * +import sys + +def main(): + filename = sys.argv[1] + root = Tk() + img = PhotoImage(file=filename) + w, h = img.width(), img.height() + canv = Canvas(root, width=w, height=h) + canv.create_image(0, 0, anchor=NW, image=img) + canv.pack() + canv.bind('<Button-1>', blob) + root.mainloop() + +def blob(event): + x, y = event.x, event.y + canv = event.widget + r = 5 + canv.create_oval(x-r, y-r, x+r, y+r, fill='red', outline="") + +main() diff --git a/sys/src/cmd/python/Demo/tkinter/guido/imageview.py b/sys/src/cmd/python/Demo/tkinter/guido/imageview.py new file mode 100755 index 000000000..d6efed0b2 --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/guido/imageview.py @@ -0,0 +1,12 @@ +from Tkinter import * +import sys + +def main(): + filename = sys.argv[1] + root = Tk() + img = PhotoImage(file=filename) + label = Label(root, image=img) + label.pack() + root.mainloop() + +main() diff --git a/sys/src/cmd/python/Demo/tkinter/guido/kill.py b/sys/src/cmd/python/Demo/tkinter/guido/kill.py new file mode 100755 index 000000000..e7df26121 --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/guido/kill.py @@ -0,0 +1,98 @@ +#! /usr/bin/env python +# Tkinter interface to Linux `kill' command. + +from Tkinter import * +from string import splitfields +from string import split +import commands +import os + +class BarButton(Menubutton): + def __init__(self, master=None, **cnf): + apply(Menubutton.__init__, (self, master), cnf) + self.pack(side=LEFT) + self.menu = Menu(self, name='menu') + self['menu'] = self.menu + +class Kill(Frame): + # List of (name, option, pid_column) + format_list = [('Default', '', 0), + ('Long', '-l', 2), + ('User', '-u', 1), + ('Jobs', '-j', 1), + ('Signal', '-s', 1), + ('Memory', '-m', 0), + ('VM', '-v', 0), + ('Hex', '-X', 0)] + def kill(self, selected): + c = self.format_list[self.format.get()][2] + pid = split(selected)[c] + os.system('kill -9 ' + pid) + self.do_update() + def do_update(self): + name, option, column = self.format_list[self.format.get()] + s = commands.getoutput('ps -w ' + option) + list = splitfields(s, '\n') + self.header.set(list[0]) + del list[0] + y = self.frame.vscroll.get()[0] + self.frame.list.delete(0, AtEnd()) + for line in list: + self.frame.list.insert(0, line) + self.frame.list.yview(int(y)) + def do_motion(self, e): + e.widget.select_clear(0, END) + e.widget.select_set(e.widget.nearest(e.y)) + def do_leave(self, e): + e.widget.select_clear(0, END) + def do_1(self, e): + self.kill(e.widget.get(e.widget.nearest(e.y))) + def __init__(self, master=None, **cnf): + Frame.__init__(self, master, cnf) + self.pack(expand=1, fill=BOTH) + self.bar = Frame(self, name='bar', relief=RAISED, + borderwidth=2) + self.bar.pack(fill=X) + self.bar.file = BarButton(self.bar, text='File') + self.bar.file.menu.add_command( + label='Quit', command=self.quit) + self.bar.view = BarButton(self.bar, text='View') + self.format = IntVar(self) + self.format.set(2) + for num in range(len(self.format_list)): + self.bar.view.menu.add_radiobutton( + label=self.format_list[num][0], + command=self.do_update, + variable=self.format, + value=num) + #self.bar.view.menu.add_separator() + #XXX ... + self.bar.tk_menuBar(self.bar.file, self.bar.view) + self.frame = Frame(self, relief=RAISED, borderwidth=2) + self.frame.pack(expand=1, fill=BOTH) + self.header = StringVar(self) + self.frame.label = Label(self.frame, relief=FLAT, anchor=NW, + borderwidth=0, + textvariable=self.header) + self.frame.label.pack(fill=X) + self.frame.vscroll = Scrollbar(self.frame, orient=VERTICAL) + self.frame.list = Listbox(self.frame, relief=SUNKEN, + selectbackground='#eed5b7', + selectborderwidth=0, + yscroll=self.frame.vscroll.set) + self.frame.vscroll['command'] = self.frame.list.yview + self.frame.vscroll.pack(side=RIGHT, fill=Y) + self.frame.list.pack(expand=1, fill=BOTH) + self.update = Button(self, text="Update", + command=self.do_update) + self.update.pack(expand=1, fill=X) + self.frame.list.bind('<Motion>', self.do_motion) + self.frame.list.bind('<Leave>', self.do_leave) + self.frame.list.bind('<1>', self.do_1) + self.do_update() + +if __name__ == '__main__': + kill = Kill(None, borderwidth=5) + kill.winfo_toplevel().title('Tkinter Process Killer') + kill.winfo_toplevel().minsize(1, 1) + kill.mainloop() diff --git a/sys/src/cmd/python/Demo/tkinter/guido/listtree.py b/sys/src/cmd/python/Demo/tkinter/guido/listtree.py new file mode 100755 index 000000000..d28ce49ef --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/guido/listtree.py @@ -0,0 +1,37 @@ +# List a remote app's widget tree (names and classes only) + +import sys +import string + +from Tkinter import * + +def listtree(master, app): + list = Listbox(master, name='list') + list.pack(expand=1, fill=BOTH) + listnodes(list, app, '.', 0) + return list + +def listnodes(list, app, widget, level): + klass = list.send(app, 'winfo', 'class', widget) +## i = string.rindex(widget, '.') +## list.insert(END, '%s%s (%s)' % ((level-1)*'. ', widget[i:], klass)) + list.insert(END, '%s (%s)' % (widget, klass)) + children = list.tk.splitlist( + list.send(app, 'winfo', 'children', widget)) + for c in children: + listnodes(list, app, c, level+1) + +def main(): + if not sys.argv[1:]: + sys.stderr.write('Usage: listtree appname\n') + sys.exit(2) + app = sys.argv[1] + tk = Tk() + tk.minsize(1, 1) + f = Frame(tk, name='f') + f.pack(expand=1, fill=BOTH) + list = listtree(f, app) + tk.mainloop() + +if __name__ == '__main__': + main() diff --git a/sys/src/cmd/python/Demo/tkinter/guido/mbox.py b/sys/src/cmd/python/Demo/tkinter/guido/mbox.py new file mode 100755 index 000000000..3c36d8899 --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/guido/mbox.py @@ -0,0 +1,285 @@ +#! /usr/bin/env python + +# Scan MH folder, display results in window + +import os +import sys +import re +import getopt +import string +import mhlib + +from Tkinter import * + +from dialog import dialog + +mailbox = os.environ['HOME'] + '/Mail' + +def main(): + global root, tk, top, mid, bot + global folderbox, foldermenu, scanbox, scanmenu, viewer + global folder, seq + global mh, mhf + + # Parse command line options + + folder = 'inbox' + seq = 'all' + try: + opts, args = getopt.getopt(sys.argv[1:], '') + except getopt.error, msg: + print msg + sys.exit(2) + for arg in args: + if arg[:1] == '+': + folder = arg[1:] + else: + seq = arg + + # Initialize MH + + mh = mhlib.MH() + mhf = mh.openfolder(folder) + + # Build widget hierarchy + + root = Tk() + tk = root.tk + + top = Frame(root) + top.pack({'expand': 1, 'fill': 'both'}) + + # Build right part: folder list + + right = Frame(top) + right.pack({'fill': 'y', 'side': 'right'}) + + folderbar = Scrollbar(right, {'relief': 'sunken', 'bd': 2}) + folderbar.pack({'fill': 'y', 'side': 'right'}) + + folderbox = Listbox(right, {'exportselection': 0}) + folderbox.pack({'expand': 1, 'fill': 'both', 'side': 'left'}) + + foldermenu = Menu(root) + foldermenu.add('command', + {'label': 'Open Folder', + 'command': open_folder}) + foldermenu.add('separator') + foldermenu.add('command', + {'label': 'Quit', + 'command': 'exit'}) + foldermenu.bind('<ButtonRelease-3>', folder_unpost) + + folderbox['yscrollcommand'] = (folderbar, 'set') + folderbar['command'] = (folderbox, 'yview') + folderbox.bind('<Double-1>', open_folder, 1) + folderbox.bind('<3>', folder_post) + + # Build left part: scan list + + left = Frame(top) + left.pack({'expand': 1, 'fill': 'both', 'side': 'left'}) + + scanbar = Scrollbar(left, {'relief': 'sunken', 'bd': 2}) + scanbar.pack({'fill': 'y', 'side': 'right'}) + + scanbox = Listbox(left, {'font': 'fixed'}) + scanbox.pack({'expand': 1, 'fill': 'both', 'side': 'left'}) + + scanmenu = Menu(root) + scanmenu.add('command', + {'label': 'Open Message', + 'command': open_message}) + scanmenu.add('command', + {'label': 'Remove Message', + 'command': remove_message}) + scanmenu.add('command', + {'label': 'Refile Message', + 'command': refile_message}) + scanmenu.add('separator') + scanmenu.add('command', + {'label': 'Quit', + 'command': 'exit'}) + scanmenu.bind('<ButtonRelease-3>', scan_unpost) + + scanbox['yscrollcommand'] = (scanbar, 'set') + scanbar['command'] = (scanbox, 'yview') + scanbox.bind('<Double-1>', open_message) + scanbox.bind('<3>', scan_post) + + # Separator between middle and bottom part + + rule2 = Frame(root, {'bg': 'black'}) + rule2.pack({'fill': 'x'}) + + # Build bottom part: current message + + bot = Frame(root) + bot.pack({'expand': 1, 'fill': 'both'}) + # + viewer = None + + # Window manager commands + + root.minsize(800, 1) # Make window resizable + + # Fill folderbox with text + + setfolders() + + # Fill scanbox with text + + rescan() + + # Enter mainloop + + root.mainloop() + +def folder_post(e): + x, y = e.x_root, e.y_root + foldermenu.post(x - 10, y - 10) + foldermenu.grab_set() + +def folder_unpost(e): + tk.call('update', 'idletasks') + foldermenu.grab_release() + foldermenu.unpost() + foldermenu.invoke('active') + +def scan_post(e): + x, y = e.x_root, e.y_root + scanmenu.post(x - 10, y - 10) + scanmenu.grab_set() + +def scan_unpost(e): + tk.call('update', 'idletasks') + scanmenu.grab_release() + scanmenu.unpost() + scanmenu.invoke('active') + +scanparser = re.compile('^ *([0-9]+)') + +def open_folder(e=None): + global folder, mhf + sel = folderbox.curselection() + if len(sel) != 1: + if len(sel) > 1: + msg = "Please open one folder at a time" + else: + msg = "Please select a folder to open" + dialog(root, "Can't Open Folder", msg, "", 0, "OK") + return + i = sel[0] + folder = folderbox.get(i) + mhf = mh.openfolder(folder) + rescan() + +def open_message(e=None): + global viewer + sel = scanbox.curselection() + if len(sel) != 1: + if len(sel) > 1: + msg = "Please open one message at a time" + else: + msg = "Please select a message to open" + dialog(root, "Can't Open Message", msg, "", 0, "OK") + return + cursor = scanbox['cursor'] + scanbox['cursor'] = 'watch' + tk.call('update', 'idletasks') + i = sel[0] + line = scanbox.get(i) + if scanparser.match(line) >= 0: + num = string.atoi(scanparser.group(1)) + m = mhf.openmessage(num) + if viewer: viewer.destroy() + from MimeViewer import MimeViewer + viewer = MimeViewer(bot, '+%s/%d' % (folder, num), m) + viewer.pack() + viewer.show() + scanbox['cursor'] = cursor + +def interestingheader(header): + return header != 'received' + +def remove_message(e=None): + itop = scanbox.nearest(0) + sel = scanbox.curselection() + if not sel: + dialog(root, "No Message To Remove", + "Please select a message to remove", "", 0, "OK") + return + todo = [] + for i in sel: + line = scanbox.get(i) + if scanparser.match(line) >= 0: + todo.append(string.atoi(scanparser.group(1))) + mhf.removemessages(todo) + rescan() + fixfocus(min(todo), itop) + +lastrefile = '' +tofolder = None +def refile_message(e=None): + global lastrefile, tofolder + itop = scanbox.nearest(0) + sel = scanbox.curselection() + if not sel: + dialog(root, "No Message To Refile", + "Please select a message to refile", "", 0, "OK") + return + foldersel = folderbox.curselection() + if len(foldersel) != 1: + if not foldersel: + msg = "Please select a folder to refile to" + else: + msg = "Please select exactly one folder to refile to" + dialog(root, "No Folder To Refile", msg, "", 0, "OK") + return + refileto = folderbox.get(foldersel[0]) + todo = [] + for i in sel: + line = scanbox.get(i) + if scanparser.match(line) >= 0: + todo.append(string.atoi(scanparser.group(1))) + if lastrefile != refileto or not tofolder: + lastrefile = refileto + tofolder = None + tofolder = mh.openfolder(lastrefile) + mhf.refilemessages(todo, tofolder) + rescan() + fixfocus(min(todo), itop) + +def fixfocus(near, itop): + n = scanbox.size() + for i in range(n): + line = scanbox.get(repr(i)) + if scanparser.match(line) >= 0: + num = string.atoi(scanparser.group(1)) + if num >= near: + break + else: + i = 'end' + scanbox.select_from(i) + scanbox.yview(itop) + +def setfolders(): + folderbox.delete(0, 'end') + for fn in mh.listallfolders(): + folderbox.insert('end', fn) + +def rescan(): + global viewer + if viewer: + viewer.destroy() + viewer = None + scanbox.delete(0, 'end') + for line in scanfolder(folder, seq): + scanbox.insert('end', line) + +def scanfolder(folder = 'inbox', sequence = 'all'): + return map( + lambda line: line[:-1], + os.popen('scan +%s %s' % (folder, sequence), 'r').readlines()) + +main() diff --git a/sys/src/cmd/python/Demo/tkinter/guido/newmenubardemo.py b/sys/src/cmd/python/Demo/tkinter/guido/newmenubardemo.py new file mode 100644 index 000000000..57bf13c44 --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/guido/newmenubardemo.py @@ -0,0 +1,47 @@ +#! /usr/bin/env python + +"""Play with the new Tk 8.0 toplevel menu option.""" + +from Tkinter import * + +class App: + + def __init__(self, master): + self.master = master + + self.menubar = Menu(self.master) + + self.filemenu = Menu(self.menubar) + + self.filemenu.add_command(label="New") + self.filemenu.add_command(label="Open...") + self.filemenu.add_command(label="Close") + self.filemenu.add_separator() + self.filemenu.add_command(label="Quit", command=self.master.quit) + + self.editmenu = Menu(self.menubar) + + self.editmenu.add_command(label="Cut") + self.editmenu.add_command(label="Copy") + self.editmenu.add_command(label="Paste") + + self.helpmenu = Menu(self.menubar, name='help') + + self.helpmenu.add_command(label="About...") + + self.menubar.add_cascade(label="File", menu=self.filemenu) + self.menubar.add_cascade(label="Edit", menu=self.editmenu) + self.menubar.add_cascade(label="Help", menu=self.helpmenu) + + self.top = Toplevel(menu=self.menubar) + + # Rest of app goes here... + +def main(): + root = Tk() + root.withdraw() + app = App(root) + root.mainloop() + +if __name__ == '__main__': + main() diff --git a/sys/src/cmd/python/Demo/tkinter/guido/optionmenu.py b/sys/src/cmd/python/Demo/tkinter/guido/optionmenu.py new file mode 100644 index 000000000..be9d3ac2a --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/guido/optionmenu.py @@ -0,0 +1,27 @@ +# option menu sample (Fredrik Lundh, September 1997) + +from Tkinter import * + +root = Tk() + +# +# standard usage + +var1 = StringVar() +var1.set("One") # default selection + +menu1 = OptionMenu(root, var1, "One", "Two", "Three") +menu1.pack() + +# +# initialize from a sequence + +CHOICES = "Aah", "Bee", "Cee", "Dee", "Eff" + +var2 = StringVar() +var2.set(CHOICES[0]) + +menu2 = apply(OptionMenu, (root, var2) + tuple(CHOICES)) +menu2.pack() + +root.mainloop() diff --git a/sys/src/cmd/python/Demo/tkinter/guido/paint.py b/sys/src/cmd/python/Demo/tkinter/guido/paint.py new file mode 100644 index 000000000..d46e20b9a --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/guido/paint.py @@ -0,0 +1,60 @@ +""""Paint program by Dave Michell. + +Subject: tkinter "paint" example +From: Dave Mitchell <davem@magnet.com> +To: python-list@cwi.nl +Date: Fri, 23 Jan 1998 12:18:05 -0500 (EST) + + Not too long ago (last week maybe?) someone posted a request +for an example of a paint program using Tkinter. Try as I might +I can't seem to find it in the archive, so i'll just post mine +here and hope that the person who requested it sees this! + + All this does is put up a canvas and draw a smooth black line +whenever you have the mouse button down, but hopefully it will +be enough to start with.. It would be easy enough to add some +options like other shapes or colors... + + yours, + dave mitchell + davem@magnet.com +""" + +from Tkinter import * + +"""paint.py: not exactly a paint program.. just a smooth line drawing demo.""" + +b1 = "up" +xold, yold = None, None + +def main(): + root = Tk() + drawing_area = Canvas(root) + drawing_area.pack() + drawing_area.bind("<Motion>", motion) + drawing_area.bind("<ButtonPress-1>", b1down) + drawing_area.bind("<ButtonRelease-1>", b1up) + root.mainloop() + +def b1down(event): + global b1 + b1 = "down" # you only want to draw when the button is down + # because "Motion" events happen -all the time- + +def b1up(event): + global b1, xold, yold + b1 = "up" + xold = None # reset the line when you let go of the button + yold = None + +def motion(event): + if b1 == "down": + global xold, yold + if xold != None and yold != None: + event.widget.create_line(xold,yold,event.x,event.y,smooth=TRUE) + # here's where you draw it. smooth. neat. + xold = event.x + yold = event.y + +if __name__ == "__main__": + main() diff --git a/sys/src/cmd/python/Demo/tkinter/guido/rmt.py b/sys/src/cmd/python/Demo/tkinter/guido/rmt.py new file mode 100755 index 000000000..440650c9c --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/guido/rmt.py @@ -0,0 +1,159 @@ +#! /usr/bin/env python + +# A Python program implementing rmt, an application for remotely +# controlling other Tk applications. +# Cf. Ousterhout, Tcl and the Tk Toolkit, Figs. 27.5-8, pp. 273-276. + +# Note that because of forward references in the original, we +# sometimes delay bindings until after the corresponding procedure is +# defined. We also introduce names for some unnamed code blocks in +# the original because of restrictions on lambda forms in Python. + +# XXX This should be written in a more Python-like style!!! + +from Tkinter import * +import sys + +# 1. Create basic application structure: menu bar on top of +# text widget, scrollbar on right. + +root = Tk() +tk = root.tk +mBar = Frame(root, relief=RAISED, borderwidth=2) +mBar.pack(fill=X) + +f = Frame(root) +f.pack(expand=1, fill=BOTH) +s = Scrollbar(f, relief=FLAT) +s.pack(side=RIGHT, fill=Y) +t = Text(f, relief=RAISED, borderwidth=2, yscrollcommand=s.set, setgrid=1) +t.pack(side=LEFT, fill=BOTH, expand=1) +t.tag_config('bold', font='-Adobe-Courier-Bold-R-Normal-*-120-*') +s['command'] = t.yview + +root.title('Tk Remote Controller') +root.iconname('Tk Remote') + +# 2. Create menu button and menus. + +file = Menubutton(mBar, text='File', underline=0) +file.pack(side=LEFT) +file_m = Menu(file) +file['menu'] = file_m +file_m_apps = Menu(file_m, tearoff=0) +file_m.add_cascade(label='Select Application', underline=0, + menu=file_m_apps) +file_m.add_command(label='Quit', underline=0, command=sys.exit) + +# 3. Create bindings for text widget to allow commands to be +# entered and information to be selected. New characters +# can only be added at the end of the text (can't ever move +# insertion point). + +def single1(e): + x = e.x + y = e.y + t.setvar('tk_priv(selectMode)', 'char') + t.mark_set('anchor', At(x, y)) + # Should focus W +t.bind('<1>', single1) + +def double1(e): + x = e.x + y = e.y + t.setvar('tk_priv(selectMode)', 'word') + t.tk_textSelectTo(At(x, y)) +t.bind('<Double-1>', double1) + +def triple1(e): + x = e.x + y = e.y + t.setvar('tk_priv(selectMode)', 'line') + t.tk_textSelectTo(At(x, y)) +t.bind('<Triple-1>', triple1) + +def returnkey(e): + t.insert(AtInsert(), '\n') + invoke() +t.bind('<Return>', returnkey) + +def controlv(e): + t.insert(AtInsert(), t.selection_get()) + t.yview_pickplace(AtInsert()) + if t.index(AtInsert())[-2:] == '.0': + invoke() +t.bind('<Control-v>', controlv) + +# 4. Procedure to backspace over one character, as long as +# the character isn't part of the prompt. + +def backspace(e): + if t.index('promptEnd') != t.index('insert - 1 char'): + t.delete('insert - 1 char', AtInsert()) + t.yview_pickplace(AtInsert()) +t.bind('<BackSpace>', backspace) +t.bind('<Control-h>', backspace) +t.bind('<Delete>', backspace) + + +# 5. Procedure that's invoked when return is typed: if +# there's not yet a complete command (e.g. braces are open) +# then do nothing. Otherwise, execute command (locally or +# remotely), output the result or error message, and issue +# a new prompt. + +def invoke(): + cmd = t.get('promptEnd + 1 char', AtInsert()) + if t.getboolean(tk.call('info', 'complete', cmd)): # XXX + if app == root.winfo_name(): + msg = tk.call('eval', cmd) # XXX + else: + msg = t.send(app, cmd) + if msg: + t.insert(AtInsert(), msg + '\n') + prompt() + t.yview_pickplace(AtInsert()) + +def prompt(): + t.insert(AtInsert(), app + ': ') + t.mark_set('promptEnd', 'insert - 1 char') + t.tag_add('bold', 'insert linestart', 'promptEnd') + +# 6. Procedure to select a new application. Also changes +# the prompt on the current command line to reflect the new +# name. + +def newApp(appName): + global app + app = appName + t.delete('promptEnd linestart', 'promptEnd') + t.insert('promptEnd', appName + ':') + t.tag_add('bold', 'promptEnd linestart', 'promptEnd') + +def fillAppsMenu(): + file_m_apps.add('command') + file_m_apps.delete(0, 'last') + names = root.winfo_interps() + names = map(None, names) # convert tuple to list + names.sort() + for name in names: + try: + root.send(name, 'winfo name .') + except TclError: + # Inoperative window -- ignore it + pass + else: + file_m_apps.add_command( + label=name, + command=lambda name=name: newApp(name)) + +file_m_apps['postcommand'] = fillAppsMenu +mBar.tk_menuBar(file) + +# 7. Miscellaneous initialization. + +app = root.winfo_name() +prompt() +t.focus() + +root.mainloop() diff --git a/sys/src/cmd/python/Demo/tkinter/guido/solitaire.py b/sys/src/cmd/python/Demo/tkinter/guido/solitaire.py new file mode 100755 index 000000000..50a8b2643 --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/guido/solitaire.py @@ -0,0 +1,637 @@ +#! /usr/bin/env python + +"""Solitaire game, much like the one that comes with MS Windows. + +Limitations: + +- No cute graphical images for the playing cards faces or backs. +- No scoring or timer. +- No undo. +- No option to turn 3 cards at a time. +- No keyboard shortcuts. +- Less fancy animation when you win. +- The determination of which stack you drag to is more relaxed. + +Apology: + +I'm not much of a card player, so my terminology in these comments may +at times be a little unusual. If you have suggestions, please let me +know! + +""" + +# Imports + +import math +import random + +from Tkinter import * +from Canvas import Rectangle, CanvasText, Group, Window + + +# Fix a bug in Canvas.Group as distributed in Python 1.4. The +# distributed bind() method is broken. Rather than asking you to fix +# the source, we fix it here by deriving a subclass: + +class Group(Group): + def bind(self, sequence=None, command=None): + return self.canvas.tag_bind(self.id, sequence, command) + + +# Constants determining the size and lay-out of cards and stacks. We +# work in a "grid" where each card/stack is surrounded by MARGIN +# pixels of space on each side, so adjacent stacks are separated by +# 2*MARGIN pixels. OFFSET is the offset used for displaying the +# face down cards in the row stacks. + +CARDWIDTH = 100 +CARDHEIGHT = 150 +MARGIN = 10 +XSPACING = CARDWIDTH + 2*MARGIN +YSPACING = CARDHEIGHT + 4*MARGIN +OFFSET = 5 + +# The background color, green to look like a playing table. The +# standard green is way too bright, and dark green is way to dark, so +# we use something in between. (There are a few more colors that +# could be customized, but they are less controversial.) + +BACKGROUND = '#070' + + +# Suits and colors. The values of the symbolic suit names are the +# strings used to display them (you change these and VALNAMES to +# internationalize the game). The COLOR dictionary maps suit names to +# colors (red and black) which must be Tk color names. The keys() of +# the COLOR dictionary conveniently provides us with a list of all +# suits (in arbitrary order). + +HEARTS = 'Heart' +DIAMONDS = 'Diamond' +CLUBS = 'Club' +SPADES = 'Spade' + +RED = 'red' +BLACK = 'black' + +COLOR = {} +for s in (HEARTS, DIAMONDS): + COLOR[s] = RED +for s in (CLUBS, SPADES): + COLOR[s] = BLACK + +ALLSUITS = COLOR.keys() +NSUITS = len(ALLSUITS) + + +# Card values are 1-13. We also define symbolic names for the picture +# cards. ALLVALUES is a list of all card values. + +ACE = 1 +JACK = 11 +QUEEN = 12 +KING = 13 +ALLVALUES = range(1, 14) # (one more than the highest value) +NVALUES = len(ALLVALUES) + + +# VALNAMES is a list that maps a card value to string. It contains a +# dummy element at index 0 so it can be indexed directly with the card +# value. + +VALNAMES = ["", "A"] + map(str, range(2, 11)) + ["J", "Q", "K"] + + +# Solitaire constants. The only one I can think of is the number of +# row stacks. + +NROWS = 7 + + +# The rest of the program consists of class definitions. These are +# further described in their documentation strings. + + +class Card: + + """A playing card. + + A card doesn't record to which stack it belongs; only the stack + records this (it turns out that we always know this from the + context, and this saves a ``double update'' with potential for + inconsistencies). + + Public methods: + + moveto(x, y) -- move the card to an absolute position + moveby(dx, dy) -- move the card by a relative offset + tkraise() -- raise the card to the top of its stack + showface(), showback() -- turn the card face up or down & raise it + + Public read-only instance variables: + + suit, value, color -- the card's suit, value and color + face_shown -- true when the card is shown face up, else false + + Semi-public read-only instance variables (XXX should be made + private): + + group -- the Canvas.Group representing the card + x, y -- the position of the card's top left corner + + Private instance variables: + + __back, __rect, __text -- the canvas items making up the card + + (To show the card face up, the text item is placed in front of + rect and the back is placed behind it. To show it face down, this + is reversed. The card is created face down.) + + """ + + def __init__(self, suit, value, canvas): + """Card constructor. + + Arguments are the card's suit and value, and the canvas widget. + + The card is created at position (0, 0), with its face down + (adding it to a stack will position it according to that + stack's rules). + + """ + self.suit = suit + self.value = value + self.color = COLOR[suit] + self.face_shown = 0 + + self.x = self.y = 0 + self.group = Group(canvas) + + text = "%s %s" % (VALNAMES[value], suit) + self.__text = CanvasText(canvas, CARDWIDTH/2, 0, + anchor=N, fill=self.color, text=text) + self.group.addtag_withtag(self.__text) + + self.__rect = Rectangle(canvas, 0, 0, CARDWIDTH, CARDHEIGHT, + outline='black', fill='white') + self.group.addtag_withtag(self.__rect) + + self.__back = Rectangle(canvas, MARGIN, MARGIN, + CARDWIDTH-MARGIN, CARDHEIGHT-MARGIN, + outline='black', fill='blue') + self.group.addtag_withtag(self.__back) + + def __repr__(self): + """Return a string for debug print statements.""" + return "Card(%r, %r)" % (self.suit, self.value) + + def moveto(self, x, y): + """Move the card to absolute position (x, y).""" + self.moveby(x - self.x, y - self.y) + + def moveby(self, dx, dy): + """Move the card by (dx, dy).""" + self.x = self.x + dx + self.y = self.y + dy + self.group.move(dx, dy) + + def tkraise(self): + """Raise the card above all other objects in its canvas.""" + self.group.tkraise() + + def showface(self): + """Turn the card's face up.""" + self.tkraise() + self.__rect.tkraise() + self.__text.tkraise() + self.face_shown = 1 + + def showback(self): + """Turn the card's face down.""" + self.tkraise() + self.__rect.tkraise() + self.__back.tkraise() + self.face_shown = 0 + + +class Stack: + + """A generic stack of cards. + + This is used as a base class for all other stacks (e.g. the deck, + the suit stacks, and the row stacks). + + Public methods: + + add(card) -- add a card to the stack + delete(card) -- delete a card from the stack + showtop() -- show the top card (if any) face up + deal() -- delete and return the top card, or None if empty + + Method that subclasses may override: + + position(card) -- move the card to its proper (x, y) position + + The default position() method places all cards at the stack's + own (x, y) position. + + userclickhandler(), userdoubleclickhandler() -- called to do + subclass specific things on single and double clicks + + The default user (single) click handler shows the top card + face up. The default user double click handler calls the user + single click handler. + + usermovehandler(cards) -- called to complete a subpile move + + The default user move handler moves all moved cards back to + their original position (by calling the position() method). + + Private methods: + + clickhandler(event), doubleclickhandler(event), + motionhandler(event), releasehandler(event) -- event handlers + + The default event handlers turn the top card of the stack with + its face up on a (single or double) click, and also support + moving a subpile around. + + startmoving(event) -- begin a move operation + finishmoving() -- finish a move operation + + """ + + def __init__(self, x, y, game=None): + """Stack constructor. + + Arguments are the stack's nominal x and y position (the top + left corner of the first card placed in the stack), and the + game object (which is used to get the canvas; subclasses use + the game object to find other stacks). + + """ + self.x = x + self.y = y + self.game = game + self.cards = [] + self.group = Group(self.game.canvas) + self.group.bind('<1>', self.clickhandler) + self.group.bind('<Double-1>', self.doubleclickhandler) + self.group.bind('<B1-Motion>', self.motionhandler) + self.group.bind('<ButtonRelease-1>', self.releasehandler) + self.makebottom() + + def makebottom(self): + pass + + def __repr__(self): + """Return a string for debug print statements.""" + return "%s(%d, %d)" % (self.__class__.__name__, self.x, self.y) + + # Public methods + + def add(self, card): + self.cards.append(card) + card.tkraise() + self.position(card) + self.group.addtag_withtag(card.group) + + def delete(self, card): + self.cards.remove(card) + card.group.dtag(self.group) + + def showtop(self): + if self.cards: + self.cards[-1].showface() + + def deal(self): + if not self.cards: + return None + card = self.cards[-1] + self.delete(card) + return card + + # Subclass overridable methods + + def position(self, card): + card.moveto(self.x, self.y) + + def userclickhandler(self): + self.showtop() + + def userdoubleclickhandler(self): + self.userclickhandler() + + def usermovehandler(self, cards): + for card in cards: + self.position(card) + + # Event handlers + + def clickhandler(self, event): + self.finishmoving() # In case we lost an event + self.userclickhandler() + self.startmoving(event) + + def motionhandler(self, event): + self.keepmoving(event) + + def releasehandler(self, event): + self.keepmoving(event) + self.finishmoving() + + def doubleclickhandler(self, event): + self.finishmoving() # In case we lost an event + self.userdoubleclickhandler() + self.startmoving(event) + + # Move internals + + moving = None + + def startmoving(self, event): + self.moving = None + tags = self.game.canvas.gettags('current') + for i in range(len(self.cards)): + card = self.cards[i] + if card.group.tag in tags: + break + else: + return + if not card.face_shown: + return + self.moving = self.cards[i:] + self.lastx = event.x + self.lasty = event.y + for card in self.moving: + card.tkraise() + + def keepmoving(self, event): + if not self.moving: + return + dx = event.x - self.lastx + dy = event.y - self.lasty + self.lastx = event.x + self.lasty = event.y + if dx or dy: + for card in self.moving: + card.moveby(dx, dy) + + def finishmoving(self): + cards = self.moving + self.moving = None + if cards: + self.usermovehandler(cards) + + +class Deck(Stack): + + """The deck is a stack with support for shuffling. + + New methods: + + fill() -- create the playing cards + shuffle() -- shuffle the playing cards + + A single click moves the top card to the game's open deck and + moves it face up; if we're out of cards, it moves the open deck + back to the deck. + + """ + + def makebottom(self): + bottom = Rectangle(self.game.canvas, + self.x, self.y, + self.x+CARDWIDTH, self.y+CARDHEIGHT, + outline='black', fill=BACKGROUND) + self.group.addtag_withtag(bottom) + + def fill(self): + for suit in ALLSUITS: + for value in ALLVALUES: + self.add(Card(suit, value, self.game.canvas)) + + def shuffle(self): + n = len(self.cards) + newcards = [] + for i in randperm(n): + newcards.append(self.cards[i]) + self.cards = newcards + + def userclickhandler(self): + opendeck = self.game.opendeck + card = self.deal() + if not card: + while 1: + card = opendeck.deal() + if not card: + break + self.add(card) + card.showback() + else: + self.game.opendeck.add(card) + card.showface() + + +def randperm(n): + """Function returning a random permutation of range(n).""" + r = range(n) + x = [] + while r: + i = random.choice(r) + x.append(i) + r.remove(i) + return x + + +class OpenStack(Stack): + + def acceptable(self, cards): + return 0 + + def usermovehandler(self, cards): + card = cards[0] + stack = self.game.closeststack(card) + if not stack or stack is self or not stack.acceptable(cards): + Stack.usermovehandler(self, cards) + else: + for card in cards: + self.delete(card) + stack.add(card) + self.game.wincheck() + + def userdoubleclickhandler(self): + if not self.cards: + return + card = self.cards[-1] + if not card.face_shown: + self.userclickhandler() + return + for s in self.game.suits: + if s.acceptable([card]): + self.delete(card) + s.add(card) + self.game.wincheck() + break + + +class SuitStack(OpenStack): + + def makebottom(self): + bottom = Rectangle(self.game.canvas, + self.x, self.y, + self.x+CARDWIDTH, self.y+CARDHEIGHT, + outline='black', fill='') + + def userclickhandler(self): + pass + + def userdoubleclickhandler(self): + pass + + def acceptable(self, cards): + if len(cards) != 1: + return 0 + card = cards[0] + if not self.cards: + return card.value == ACE + topcard = self.cards[-1] + return card.suit == topcard.suit and card.value == topcard.value + 1 + + +class RowStack(OpenStack): + + def acceptable(self, cards): + card = cards[0] + if not self.cards: + return card.value == KING + topcard = self.cards[-1] + if not topcard.face_shown: + return 0 + return card.color != topcard.color and card.value == topcard.value - 1 + + def position(self, card): + y = self.y + for c in self.cards: + if c == card: + break + if c.face_shown: + y = y + 2*MARGIN + else: + y = y + OFFSET + card.moveto(self.x, y) + + +class Solitaire: + + def __init__(self, master): + self.master = master + + self.canvas = Canvas(self.master, + background=BACKGROUND, + highlightthickness=0, + width=NROWS*XSPACING, + height=3*YSPACING + 20 + MARGIN) + self.canvas.pack(fill=BOTH, expand=TRUE) + + self.dealbutton = Button(self.canvas, + text="Deal", + highlightthickness=0, + background=BACKGROUND, + activebackground="green", + command=self.deal) + Window(self.canvas, MARGIN, 3*YSPACING + 20, + window=self.dealbutton, anchor=SW) + + x = MARGIN + y = MARGIN + + self.deck = Deck(x, y, self) + + x = x + XSPACING + self.opendeck = OpenStack(x, y, self) + + x = x + XSPACING + self.suits = [] + for i in range(NSUITS): + x = x + XSPACING + self.suits.append(SuitStack(x, y, self)) + + x = MARGIN + y = y + YSPACING + + self.rows = [] + for i in range(NROWS): + self.rows.append(RowStack(x, y, self)) + x = x + XSPACING + + self.openstacks = [self.opendeck] + self.suits + self.rows + + self.deck.fill() + self.deal() + + def wincheck(self): + for s in self.suits: + if len(s.cards) != NVALUES: + return + self.win() + self.deal() + + def win(self): + """Stupid animation when you win.""" + cards = [] + for s in self.openstacks: + cards = cards + s.cards + while cards: + card = random.choice(cards) + cards.remove(card) + self.animatedmoveto(card, self.deck) + + def animatedmoveto(self, card, dest): + for i in range(10, 0, -1): + dx, dy = (dest.x-card.x)/i, (dest.y-card.y)/i + card.moveby(dx, dy) + self.master.update_idletasks() + + def closeststack(self, card): + closest = None + cdist = 999999999 + # Since we only compare distances, + # we don't bother to take the square root. + for stack in self.openstacks: + dist = (stack.x - card.x)**2 + (stack.y - card.y)**2 + if dist < cdist: + closest = stack + cdist = dist + return closest + + def deal(self): + self.reset() + self.deck.shuffle() + for i in range(NROWS): + for r in self.rows[i:]: + card = self.deck.deal() + r.add(card) + for r in self.rows: + r.showtop() + + def reset(self): + for stack in self.openstacks: + while 1: + card = stack.deal() + if not card: + break + self.deck.add(card) + card.showback() + + +# Main function, run when invoked as a stand-alone Python program. + +def main(): + root = Tk() + game = Solitaire(root) + root.protocol('WM_DELETE_WINDOW', root.quit) + root.mainloop() + +if __name__ == '__main__': + main() diff --git a/sys/src/cmd/python/Demo/tkinter/guido/sortvisu.py b/sys/src/cmd/python/Demo/tkinter/guido/sortvisu.py new file mode 100644 index 000000000..f18f2c116 --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/guido/sortvisu.py @@ -0,0 +1,634 @@ +#! /usr/bin/env python + +"""Sorting algorithms visualizer using Tkinter. + +This module is comprised of three ``components'': + +- an array visualizer with methods that implement basic sorting +operations (compare, swap) as well as methods for ``annotating'' the +sorting algorithm (e.g. to show the pivot element); + +- a number of sorting algorithms (currently quicksort, insertion sort, +selection sort and bubble sort, as well as a randomization function), +all using the array visualizer for its basic operations and with calls +to its annotation methods; + +- and a ``driver'' class which can be used as a Grail applet or as a +stand-alone application. + +""" + + +from Tkinter import * +from Canvas import Line, Rectangle +import random + + +XGRID = 10 +YGRID = 10 +WIDTH = 6 + + +class Array: + + def __init__(self, master, data=None): + self.master = master + self.frame = Frame(self.master) + self.frame.pack(fill=X) + self.label = Label(self.frame) + self.label.pack() + self.canvas = Canvas(self.frame) + self.canvas.pack() + self.report = Label(self.frame) + self.report.pack() + self.left = Line(self.canvas, 0, 0, 0, 0) + self.right = Line(self.canvas, 0, 0, 0, 0) + self.pivot = Line(self.canvas, 0, 0, 0, 0) + self.items = [] + self.size = self.maxvalue = 0 + if data: + self.setdata(data) + + def setdata(self, data): + olditems = self.items + self.items = [] + for item in olditems: + item.delete() + self.size = len(data) + self.maxvalue = max(data) + self.canvas.config(width=(self.size+1)*XGRID, + height=(self.maxvalue+1)*YGRID) + for i in range(self.size): + self.items.append(ArrayItem(self, i, data[i])) + self.reset("Sort demo, size %d" % self.size) + + speed = "normal" + + def setspeed(self, speed): + self.speed = speed + + def destroy(self): + self.frame.destroy() + + in_mainloop = 0 + stop_mainloop = 0 + + def cancel(self): + self.stop_mainloop = 1 + if self.in_mainloop: + self.master.quit() + + def step(self): + if self.in_mainloop: + self.master.quit() + + Cancelled = "Array.Cancelled" # Exception + + def wait(self, msecs): + if self.speed == "fastest": + msecs = 0 + elif self.speed == "fast": + msecs = msecs/10 + elif self.speed == "single-step": + msecs = 1000000000 + if not self.stop_mainloop: + self.master.update() + id = self.master.after(msecs, self.master.quit) + self.in_mainloop = 1 + self.master.mainloop() + self.master.after_cancel(id) + self.in_mainloop = 0 + if self.stop_mainloop: + self.stop_mainloop = 0 + self.message("Cancelled") + raise Array.Cancelled + + def getsize(self): + return self.size + + def show_partition(self, first, last): + for i in range(self.size): + item = self.items[i] + if first <= i < last: + item.item.config(fill='red') + else: + item.item.config(fill='orange') + self.hide_left_right_pivot() + + def hide_partition(self): + for i in range(self.size): + item = self.items[i] + item.item.config(fill='red') + self.hide_left_right_pivot() + + def show_left(self, left): + if not 0 <= left < self.size: + self.hide_left() + return + x1, y1, x2, y2 = self.items[left].position() +## top, bot = HIRO + self.left.coords([(x1-2, 0), (x1-2, 9999)]) + self.master.update() + + def show_right(self, right): + if not 0 <= right < self.size: + self.hide_right() + return + x1, y1, x2, y2 = self.items[right].position() + self.right.coords(((x2+2, 0), (x2+2, 9999))) + self.master.update() + + def hide_left_right_pivot(self): + self.hide_left() + self.hide_right() + self.hide_pivot() + + def hide_left(self): + self.left.coords(((0, 0), (0, 0))) + + def hide_right(self): + self.right.coords(((0, 0), (0, 0))) + + def show_pivot(self, pivot): + x1, y1, x2, y2 = self.items[pivot].position() + self.pivot.coords(((0, y1-2), (9999, y1-2))) + + def hide_pivot(self): + self.pivot.coords(((0, 0), (0, 0))) + + def swap(self, i, j): + if i == j: return + self.countswap() + item = self.items[i] + other = self.items[j] + self.items[i], self.items[j] = other, item + item.swapwith(other) + + def compare(self, i, j): + self.countcompare() + item = self.items[i] + other = self.items[j] + return item.compareto(other) + + def reset(self, msg): + self.ncompares = 0 + self.nswaps = 0 + self.message(msg) + self.updatereport() + self.hide_partition() + + def message(self, msg): + self.label.config(text=msg) + + def countswap(self): + self.nswaps = self.nswaps + 1 + self.updatereport() + + def countcompare(self): + self.ncompares = self.ncompares + 1 + self.updatereport() + + def updatereport(self): + text = "%d cmps, %d swaps" % (self.ncompares, self.nswaps) + self.report.config(text=text) + + +class ArrayItem: + + def __init__(self, array, index, value): + self.array = array + self.index = index + self.value = value + x1, y1, x2, y2 = self.position() + self.item = Rectangle(array.canvas, x1, y1, x2, y2, + fill='red', outline='black', width=1) + self.item.bind('<Button-1>', self.mouse_down) + self.item.bind('<Button1-Motion>', self.mouse_move) + self.item.bind('<ButtonRelease-1>', self.mouse_up) + + def delete(self): + item = self.item + self.array = None + self.item = None + item.delete() + + def mouse_down(self, event): + self.lastx = event.x + self.lasty = event.y + self.origx = event.x + self.origy = event.y + self.item.tkraise() + + def mouse_move(self, event): + self.item.move(event.x - self.lastx, event.y - self.lasty) + self.lastx = event.x + self.lasty = event.y + + def mouse_up(self, event): + i = self.nearestindex(event.x) + if i >= self.array.getsize(): + i = self.array.getsize() - 1 + if i < 0: + i = 0 + other = self.array.items[i] + here = self.index + self.array.items[here], self.array.items[i] = other, self + self.index = i + x1, y1, x2, y2 = self.position() + self.item.coords(((x1, y1), (x2, y2))) + other.setindex(here) + + def setindex(self, index): + nsteps = steps(self.index, index) + if not nsteps: return + if self.array.speed == "fastest": + nsteps = 0 + oldpts = self.position() + self.index = index + newpts = self.position() + trajectory = interpolate(oldpts, newpts, nsteps) + self.item.tkraise() + for pts in trajectory: + self.item.coords((pts[:2], pts[2:])) + self.array.wait(50) + + def swapwith(self, other): + nsteps = steps(self.index, other.index) + if not nsteps: return + if self.array.speed == "fastest": + nsteps = 0 + myoldpts = self.position() + otheroldpts = other.position() + self.index, other.index = other.index, self.index + mynewpts = self.position() + othernewpts = other.position() + myfill = self.item['fill'] + otherfill = other.item['fill'] + self.item.config(fill='green') + other.item.config(fill='yellow') + self.array.master.update() + if self.array.speed == "single-step": + self.item.coords((mynewpts[:2], mynewpts[2:])) + other.item.coords((othernewpts[:2], othernewpts[2:])) + self.array.master.update() + self.item.config(fill=myfill) + other.item.config(fill=otherfill) + self.array.wait(0) + return + mytrajectory = interpolate(myoldpts, mynewpts, nsteps) + othertrajectory = interpolate(otheroldpts, othernewpts, nsteps) + if self.value > other.value: + self.item.tkraise() + other.item.tkraise() + else: + other.item.tkraise() + self.item.tkraise() + try: + for i in range(len(mytrajectory)): + mypts = mytrajectory[i] + otherpts = othertrajectory[i] + self.item.coords((mypts[:2], mypts[2:])) + other.item.coords((otherpts[:2], otherpts[2:])) + self.array.wait(50) + finally: + mypts = mytrajectory[-1] + otherpts = othertrajectory[-1] + self.item.coords((mypts[:2], mypts[2:])) + other.item.coords((otherpts[:2], otherpts[2:])) + self.item.config(fill=myfill) + other.item.config(fill=otherfill) + + def compareto(self, other): + myfill = self.item['fill'] + otherfill = other.item['fill'] + outcome = cmp(self.value, other.value) + if outcome < 0: + myflash = 'white' + otherflash = 'black' + elif outcome > 0: + myflash = 'black' + otherflash = 'white' + else: + myflash = otherflash = 'grey' + try: + self.item.config(fill=myflash) + other.item.config(fill=otherflash) + self.array.wait(500) + finally: + self.item.config(fill=myfill) + other.item.config(fill=otherfill) + return outcome + + def position(self): + x1 = (self.index+1)*XGRID - WIDTH/2 + x2 = x1+WIDTH + y2 = (self.array.maxvalue+1)*YGRID + y1 = y2 - (self.value)*YGRID + return x1, y1, x2, y2 + + def nearestindex(self, x): + return int(round(float(x)/XGRID)) - 1 + + +# Subroutines that don't need an object + +def steps(here, there): + nsteps = abs(here - there) + if nsteps <= 3: + nsteps = nsteps * 3 + elif nsteps <= 5: + nsteps = nsteps * 2 + elif nsteps > 10: + nsteps = 10 + return nsteps + +def interpolate(oldpts, newpts, n): + if len(oldpts) != len(newpts): + raise ValueError, "can't interpolate arrays of different length" + pts = [0]*len(oldpts) + res = [tuple(oldpts)] + for i in range(1, n): + for k in range(len(pts)): + pts[k] = oldpts[k] + (newpts[k] - oldpts[k])*i/n + res.append(tuple(pts)) + res.append(tuple(newpts)) + return res + + +# Various (un)sorting algorithms + +def uniform(array): + size = array.getsize() + array.setdata([(size+1)/2] * size) + array.reset("Uniform data, size %d" % size) + +def distinct(array): + size = array.getsize() + array.setdata(range(1, size+1)) + array.reset("Distinct data, size %d" % size) + +def randomize(array): + array.reset("Randomizing") + n = array.getsize() + for i in range(n): + j = random.randint(0, n-1) + array.swap(i, j) + array.message("Randomized") + +def insertionsort(array): + size = array.getsize() + array.reset("Insertion sort") + for i in range(1, size): + j = i-1 + while j >= 0: + if array.compare(j, j+1) <= 0: + break + array.swap(j, j+1) + j = j-1 + array.message("Sorted") + +def selectionsort(array): + size = array.getsize() + array.reset("Selection sort") + try: + for i in range(size): + array.show_partition(i, size) + for j in range(i+1, size): + if array.compare(i, j) > 0: + array.swap(i, j) + array.message("Sorted") + finally: + array.hide_partition() + +def bubblesort(array): + size = array.getsize() + array.reset("Bubble sort") + for i in range(size): + for j in range(1, size): + if array.compare(j-1, j) > 0: + array.swap(j-1, j) + array.message("Sorted") + +def quicksort(array): + size = array.getsize() + array.reset("Quicksort") + try: + stack = [(0, size)] + while stack: + first, last = stack[-1] + del stack[-1] + array.show_partition(first, last) + if last-first < 5: + array.message("Insertion sort") + for i in range(first+1, last): + j = i-1 + while j >= first: + if array.compare(j, j+1) <= 0: + break + array.swap(j, j+1) + j = j-1 + continue + array.message("Choosing pivot") + j, i, k = first, (first+last)/2, last-1 + if array.compare(k, i) < 0: + array.swap(k, i) + if array.compare(k, j) < 0: + array.swap(k, j) + if array.compare(j, i) < 0: + array.swap(j, i) + pivot = j + array.show_pivot(pivot) + array.message("Pivot at left of partition") + array.wait(1000) + left = first + right = last + while 1: + array.message("Sweep right pointer") + right = right-1 + array.show_right(right) + while right > first and array.compare(right, pivot) >= 0: + right = right-1 + array.show_right(right) + array.message("Sweep left pointer") + left = left+1 + array.show_left(left) + while left < last and array.compare(left, pivot) <= 0: + left = left+1 + array.show_left(left) + if left > right: + array.message("End of partition") + break + array.message("Swap items") + array.swap(left, right) + array.message("Swap pivot back") + array.swap(pivot, right) + n1 = right-first + n2 = last-left + if n1 > 1: stack.append((first, right)) + if n2 > 1: stack.append((left, last)) + array.message("Sorted") + finally: + array.hide_partition() + +def demosort(array): + while 1: + for alg in [quicksort, insertionsort, selectionsort, bubblesort]: + randomize(array) + alg(array) + + +# Sort demo class -- usable as a Grail applet + +class SortDemo: + + def __init__(self, master, size=15): + self.master = master + self.size = size + self.busy = 0 + self.array = Array(self.master) + + self.botframe = Frame(master) + self.botframe.pack(side=BOTTOM) + self.botleftframe = Frame(self.botframe) + self.botleftframe.pack(side=LEFT, fill=Y) + self.botrightframe = Frame(self.botframe) + self.botrightframe.pack(side=RIGHT, fill=Y) + + self.b_qsort = Button(self.botleftframe, + text="Quicksort", command=self.c_qsort) + self.b_qsort.pack(fill=X) + self.b_isort = Button(self.botleftframe, + text="Insertion sort", command=self.c_isort) + self.b_isort.pack(fill=X) + self.b_ssort = Button(self.botleftframe, + text="Selection sort", command=self.c_ssort) + self.b_ssort.pack(fill=X) + self.b_bsort = Button(self.botleftframe, + text="Bubble sort", command=self.c_bsort) + self.b_bsort.pack(fill=X) + + # Terrible hack to overcome limitation of OptionMenu... + class MyIntVar(IntVar): + def __init__(self, master, demo): + self.demo = demo + IntVar.__init__(self, master) + def set(self, value): + IntVar.set(self, value) + if str(value) != '0': + self.demo.resize(value) + + self.v_size = MyIntVar(self.master, self) + self.v_size.set(size) + sizes = [1, 2, 3, 4] + range(5, 55, 5) + if self.size not in sizes: + sizes.append(self.size) + sizes.sort() + self.m_size = apply(OptionMenu, + (self.botleftframe, self.v_size) + tuple(sizes)) + self.m_size.pack(fill=X) + + self.v_speed = StringVar(self.master) + self.v_speed.set("normal") + self.m_speed = OptionMenu(self.botleftframe, self.v_speed, + "single-step", "normal", "fast", "fastest") + self.m_speed.pack(fill=X) + + self.b_step = Button(self.botleftframe, + text="Step", command=self.c_step) + self.b_step.pack(fill=X) + + self.b_randomize = Button(self.botrightframe, + text="Randomize", command=self.c_randomize) + self.b_randomize.pack(fill=X) + self.b_uniform = Button(self.botrightframe, + text="Uniform", command=self.c_uniform) + self.b_uniform.pack(fill=X) + self.b_distinct = Button(self.botrightframe, + text="Distinct", command=self.c_distinct) + self.b_distinct.pack(fill=X) + self.b_demo = Button(self.botrightframe, + text="Demo", command=self.c_demo) + self.b_demo.pack(fill=X) + self.b_cancel = Button(self.botrightframe, + text="Cancel", command=self.c_cancel) + self.b_cancel.pack(fill=X) + self.b_cancel.config(state=DISABLED) + self.b_quit = Button(self.botrightframe, + text="Quit", command=self.c_quit) + self.b_quit.pack(fill=X) + + def resize(self, newsize): + if self.busy: + self.master.bell() + return + self.size = newsize + self.array.setdata(range(1, self.size+1)) + + def c_qsort(self): + self.run(quicksort) + + def c_isort(self): + self.run(insertionsort) + + def c_ssort(self): + self.run(selectionsort) + + def c_bsort(self): + self.run(bubblesort) + + def c_demo(self): + self.run(demosort) + + def c_randomize(self): + self.run(randomize) + + def c_uniform(self): + self.run(uniform) + + def c_distinct(self): + self.run(distinct) + + def run(self, func): + if self.busy: + self.master.bell() + return + self.busy = 1 + self.array.setspeed(self.v_speed.get()) + self.b_cancel.config(state=NORMAL) + try: + func(self.array) + except Array.Cancelled: + pass + self.b_cancel.config(state=DISABLED) + self.busy = 0 + + def c_cancel(self): + if not self.busy: + self.master.bell() + return + self.array.cancel() + + def c_step(self): + if not self.busy: + self.master.bell() + return + self.v_speed.set("single-step") + self.array.setspeed("single-step") + self.array.step() + + def c_quit(self): + if self.busy: + self.array.cancel() + self.master.after_idle(self.master.quit) + + +# Main program -- for stand-alone operation outside Grail + +def main(): + root = Tk() + demo = SortDemo(root) + root.protocol('WM_DELETE_WINDOW', demo.c_quit) + root.mainloop() + +if __name__ == '__main__': + main() diff --git a/sys/src/cmd/python/Demo/tkinter/guido/ss1.py b/sys/src/cmd/python/Demo/tkinter/guido/ss1.py new file mode 100644 index 000000000..89354753e --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/guido/ss1.py @@ -0,0 +1,845 @@ +"""SS1 -- a spreadsheet.""" + +import os +import re +import sys +import cgi +import rexec +from xml.parsers import expat + +LEFT, CENTER, RIGHT = "LEFT", "CENTER", "RIGHT" + +def ljust(x, n): + return x.ljust(n) +def center(x, n): + return x.center(n) +def rjust(x, n): + return x.rjust(n) +align2action = {LEFT: ljust, CENTER: center, RIGHT: rjust} + +align2xml = {LEFT: "left", CENTER: "center", RIGHT: "right"} +xml2align = {"left": LEFT, "center": CENTER, "right": RIGHT} + +align2anchor = {LEFT: "w", CENTER: "center", RIGHT: "e"} + +def sum(seq): + total = 0 + for x in seq: + if x is not None: + total += x + return total + +class Sheet: + + def __init__(self): + self.cells = {} # {(x, y): cell, ...} + self.rexec = rexec.RExec() + m = self.rexec.add_module('__main__') + m.cell = self.cellvalue + m.cells = self.multicellvalue + m.sum = sum + + def cellvalue(self, x, y): + cell = self.getcell(x, y) + if hasattr(cell, 'recalc'): + return cell.recalc(self.rexec) + else: + return cell + + def multicellvalue(self, x1, y1, x2, y2): + if x1 > x2: + x1, x2 = x2, x1 + if y1 > y2: + y1, y2 = y2, y1 + seq = [] + for y in range(y1, y2+1): + for x in range(x1, x2+1): + seq.append(self.cellvalue(x, y)) + return seq + + def getcell(self, x, y): + return self.cells.get((x, y)) + + def setcell(self, x, y, cell): + assert x > 0 and y > 0 + assert isinstance(cell, BaseCell) + self.cells[x, y] = cell + + def clearcell(self, x, y): + try: + del self.cells[x, y] + except KeyError: + pass + + def clearcells(self, x1, y1, x2, y2): + for xy in self.selectcells(x1, y1, x2, y2): + del self.cells[xy] + + def clearrows(self, y1, y2): + self.clearcells(0, y1, sys.maxint, y2) + + def clearcolumns(self, x1, x2): + self.clearcells(x1, 0, x2, sys.maxint) + + def selectcells(self, x1, y1, x2, y2): + if x1 > x2: + x1, x2 = x2, x1 + if y1 > y2: + y1, y2 = y2, y1 + return [(x, y) for x, y in self.cells + if x1 <= x <= x2 and y1 <= y <= y2] + + def movecells(self, x1, y1, x2, y2, dx, dy): + if dx == 0 and dy == 0: + return + if x1 > x2: + x1, x2 = x2, x1 + if y1 > y2: + y1, y2 = y2, y1 + assert x1+dx > 0 and y1+dy > 0 + new = {} + for x, y in self.cells: + cell = self.cells[x, y] + if hasattr(cell, 'renumber'): + cell = cell.renumber(x1, y1, x2, y2, dx, dy) + if x1 <= x <= x2 and y1 <= y <= y2: + x += dx + y += dy + new[x, y] = cell + self.cells = new + + def insertrows(self, y, n): + assert n > 0 + self.movecells(0, y, sys.maxint, sys.maxint, 0, n) + + def deleterows(self, y1, y2): + if y1 > y2: + y1, y2 = y2, y1 + self.clearrows(y1, y2) + self.movecells(0, y2+1, sys.maxint, sys.maxint, 0, y1-y2-1) + + def insertcolumns(self, x, n): + assert n > 0 + self.movecells(x, 0, sys.maxint, sys.maxint, n, 0) + + def deletecolumns(self, x1, x2): + if x1 > x2: + x1, x2 = x2, x1 + self.clearcells(x1, x2) + self.movecells(x2+1, 0, sys.maxint, sys.maxint, x1-x2-1, 0) + + def getsize(self): + maxx = maxy = 0 + for x, y in self.cells: + maxx = max(maxx, x) + maxy = max(maxy, y) + return maxx, maxy + + def reset(self): + for cell in self.cells.itervalues(): + if hasattr(cell, 'reset'): + cell.reset() + + def recalc(self): + self.reset() + for cell in self.cells.itervalues(): + if hasattr(cell, 'recalc'): + cell.recalc(self.rexec) + + def display(self): + maxx, maxy = self.getsize() + width, height = maxx+1, maxy+1 + colwidth = [1] * width + full = {} + # Add column heading labels in row 0 + for x in range(1, width): + full[x, 0] = text, alignment = colnum2name(x), RIGHT + colwidth[x] = max(colwidth[x], len(text)) + # Add row labels in column 0 + for y in range(1, height): + full[0, y] = text, alignment = str(y), RIGHT + colwidth[0] = max(colwidth[0], len(text)) + # Add sheet cells in columns with x>0 and y>0 + for (x, y), cell in self.cells.iteritems(): + if x <= 0 or y <= 0: + continue + if hasattr(cell, 'recalc'): + cell.recalc(self.rexec) + if hasattr(cell, 'format'): + text, alignment = cell.format() + assert isinstance(text, str) + assert alignment in (LEFT, CENTER, RIGHT) + else: + text = str(cell) + if isinstance(cell, str): + alignment = LEFT + else: + alignment = RIGHT + full[x, y] = (text, alignment) + colwidth[x] = max(colwidth[x], len(text)) + # Calculate the horizontal separator line (dashes and dots) + sep = "" + for x in range(width): + if sep: + sep += "+" + sep += "-"*colwidth[x] + # Now print The full grid + for y in range(height): + line = "" + for x in range(width): + text, alignment = full.get((x, y)) or ("", LEFT) + text = align2action[alignment](text, colwidth[x]) + if line: + line += '|' + line += text + print line + if y == 0: + print sep + + def xml(self): + out = ['<spreadsheet>'] + for (x, y), cell in self.cells.iteritems(): + if hasattr(cell, 'xml'): + cellxml = cell.xml() + else: + cellxml = '<value>%s</value>' % cgi.escape(cell) + out.append('<cell row="%s" col="%s">\n %s\n</cell>' % + (y, x, cellxml)) + out.append('</spreadsheet>') + return '\n'.join(out) + + def save(self, filename): + text = self.xml() + f = open(filename, "w") + f.write(text) + if text and not text.endswith('\n'): + f.write('\n') + f.close() + + def load(self, filename): + f = open(filename, 'r') + SheetParser(self).parsefile(f) + f.close() + +class SheetParser: + + def __init__(self, sheet): + self.sheet = sheet + + def parsefile(self, f): + parser = expat.ParserCreate() + parser.StartElementHandler = self.startelement + parser.EndElementHandler = self.endelement + parser.CharacterDataHandler = self.data + parser.ParseFile(f) + + def startelement(self, tag, attrs): + method = getattr(self, 'start_'+tag, None) + if method: + for key, value in attrs.iteritems(): + attrs[key] = str(value) # XXX Convert Unicode to 8-bit + method(attrs) + self.texts = [] + + def data(self, text): + text = str(text) # XXX Convert Unicode to 8-bit + self.texts.append(text) + + def endelement(self, tag): + method = getattr(self, 'end_'+tag, None) + if method: + method("".join(self.texts)) + + def start_cell(self, attrs): + self.y = int(attrs.get("row")) + self.x = int(attrs.get("col")) + + def start_value(self, attrs): + self.fmt = attrs.get('format') + self.alignment = xml2align.get(attrs.get('align')) + + start_formula = start_value + + def end_int(self, text): + try: + self.value = int(text) + except: + self.value = None + + def end_long(self, text): + try: + self.value = long(text) + except: + self.value = None + + def end_double(self, text): + try: + self.value = float(text) + except: + self.value = None + + def end_complex(self, text): + try: + self.value = complex(text) + except: + self.value = None + + def end_string(self, text): + try: + self.value = text + except: + self.value = None + + def end_value(self, text): + if isinstance(self.value, BaseCell): + self.cell = self.value + elif isinstance(self.value, str): + self.cell = StringCell(self.value, + self.fmt or "%s", + self.alignment or LEFT) + else: + self.cell = NumericCell(self.value, + self.fmt or "%s", + self.alignment or RIGHT) + + def end_formula(self, text): + self.cell = FormulaCell(text, + self.fmt or "%s", + self.alignment or RIGHT) + + def end_cell(self, text): + self.sheet.setcell(self.x, self.y, self.cell) + +class BaseCell: + __init__ = None # Must provide + """Abstract base class for sheet cells. + + Subclasses may but needn't provide the following APIs: + + cell.reset() -- prepare for recalculation + cell.recalc(rexec) -> value -- recalculate formula + cell.format() -> (value, alignment) -- return formatted value + cell.xml() -> string -- return XML + """ + +class NumericCell(BaseCell): + + def __init__(self, value, fmt="%s", alignment=RIGHT): + assert isinstance(value, (int, long, float, complex)) + assert alignment in (LEFT, CENTER, RIGHT) + self.value = value + self.fmt = fmt + self.alignment = alignment + + def recalc(self, rexec): + return self.value + + def format(self): + try: + text = self.fmt % self.value + except: + text = str(self.value) + return text, self.alignment + + def xml(self): + method = getattr(self, '_xml_' + type(self.value).__name__) + return '<value align="%s" format="%s">%s</value>' % ( + align2xml[self.alignment], + self.fmt, + method()) + + def _xml_int(self): + if -2**31 <= self.value < 2**31: + return '<int>%s</int>' % self.value + else: + return self._xml_long() + + def _xml_long(self): + return '<long>%s</long>' % self.value + + def _xml_float(self): + return '<double>%s</double>' % repr(self.value) + + def _xml_complex(self): + return '<complex>%s</double>' % repr(self.value) + +class StringCell(BaseCell): + + def __init__(self, text, fmt="%s", alignment=LEFT): + assert isinstance(text, (str, unicode)) + assert alignment in (LEFT, CENTER, RIGHT) + self.text = text + self.fmt = fmt + self.alignment = alignment + + def recalc(self, rexec): + return self.text + + def format(self): + return self.text, self.alignment + + def xml(self): + s = '<value align="%s" format="%s"><string>%s</string></value>' + return s % ( + align2xml[self.alignment], + self.fmt, + cgi.escape(self.text)) + +class FormulaCell(BaseCell): + + def __init__(self, formula, fmt="%s", alignment=RIGHT): + assert alignment in (LEFT, CENTER, RIGHT) + self.formula = formula + self.translated = translate(self.formula) + self.fmt = fmt + self.alignment = alignment + self.reset() + + def reset(self): + self.value = None + + def recalc(self, rexec): + if self.value is None: + try: + # A hack to evaluate expressions using true division + rexec.r_exec("from __future__ import division\n" + + "__value__ = eval(%s)" % repr(self.translated)) + self.value = rexec.r_eval("__value__") + except: + exc = sys.exc_info()[0] + if hasattr(exc, "__name__"): + self.value = exc.__name__ + else: + self.value = str(exc) + return self.value + + def format(self): + try: + text = self.fmt % self.value + except: + text = str(self.value) + return text, self.alignment + + def xml(self): + return '<formula align="%s" format="%s">%s</formula>' % ( + align2xml[self.alignment], + self.fmt, + self.formula) + + def renumber(self, x1, y1, x2, y2, dx, dy): + out = [] + for part in re.split('(\w+)', self.formula): + m = re.match('^([A-Z]+)([1-9][0-9]*)$', part) + if m is not None: + sx, sy = m.groups() + x = colname2num(sx) + y = int(sy) + if x1 <= x <= x2 and y1 <= y <= y2: + part = cellname(x+dx, y+dy) + out.append(part) + return FormulaCell("".join(out), self.fmt, self.alignment) + +def translate(formula): + """Translate a formula containing fancy cell names to valid Python code. + + Examples: + B4 -> cell(2, 4) + B4:Z100 -> cells(2, 4, 26, 100) + """ + out = [] + for part in re.split(r"(\w+(?::\w+)?)", formula): + m = re.match(r"^([A-Z]+)([1-9][0-9]*)(?::([A-Z]+)([1-9][0-9]*))?$", part) + if m is None: + out.append(part) + else: + x1, y1, x2, y2 = m.groups() + x1 = colname2num(x1) + if x2 is None: + s = "cell(%s, %s)" % (x1, y1) + else: + x2 = colname2num(x2) + s = "cells(%s, %s, %s, %s)" % (x1, y1, x2, y2) + out.append(s) + return "".join(out) + +def cellname(x, y): + "Translate a cell coordinate to a fancy cell name (e.g. (1, 1)->'A1')." + assert x > 0 # Column 0 has an empty name, so can't use that + return colnum2name(x) + str(y) + +def colname2num(s): + "Translate a column name to number (e.g. 'A'->1, 'Z'->26, 'AA'->27)." + s = s.upper() + n = 0 + for c in s: + assert 'A' <= c <= 'Z' + n = n*26 + ord(c) - ord('A') + 1 + return n + +def colnum2name(n): + "Translate a column number to name (e.g. 1->'A', etc.)." + assert n > 0 + s = "" + while n: + n, m = divmod(n-1, 26) + s = chr(m+ord('A')) + s + return s + +import Tkinter as Tk + +class SheetGUI: + + """Beginnings of a GUI for a spreadsheet. + + TO DO: + - clear multiple cells + - Insert, clear, remove rows or columns + - Show new contents while typing + - Scroll bars + - Grow grid when window is grown + - Proper menus + - Undo, redo + - Cut, copy and paste + - Formatting and alignment + """ + + def __init__(self, filename="sheet1.xml", rows=10, columns=5): + """Constructor. + + Load the sheet from the filename argument. + Set up the Tk widget tree. + """ + # Create and load the sheet + self.filename = filename + self.sheet = Sheet() + if os.path.isfile(filename): + self.sheet.load(filename) + # Calculate the needed grid size + maxx, maxy = self.sheet.getsize() + rows = max(rows, maxy) + columns = max(columns, maxx) + # Create the widgets + self.root = Tk.Tk() + self.root.wm_title("Spreadsheet: %s" % self.filename) + self.beacon = Tk.Label(self.root, text="A1", + font=('helvetica', 16, 'bold')) + self.entry = Tk.Entry(self.root) + self.savebutton = Tk.Button(self.root, text="Save", + command=self.save) + self.cellgrid = Tk.Frame(self.root) + # Configure the widget lay-out + self.cellgrid.pack(side="bottom", expand=1, fill="both") + self.beacon.pack(side="left") + self.savebutton.pack(side="right") + self.entry.pack(side="left", expand=1, fill="x") + # Bind some events + self.entry.bind("<Return>", self.return_event) + self.entry.bind("<Shift-Return>", self.shift_return_event) + self.entry.bind("<Tab>", self.tab_event) + self.entry.bind("<Shift-Tab>", self.shift_tab_event) + self.entry.bind("<Delete>", self.delete_event) + self.entry.bind("<Escape>", self.escape_event) + # Now create the cell grid + self.makegrid(rows, columns) + # Select the top-left cell + self.currentxy = None + self.cornerxy = None + self.setcurrent(1, 1) + # Copy the sheet cells to the GUI cells + self.sync() + + def delete_event(self, event): + if self.cornerxy != self.currentxy and self.cornerxy is not None: + self.sheet.clearcells(*(self.currentxy + self.cornerxy)) + else: + self.sheet.clearcell(*self.currentxy) + self.sync() + self.entry.delete(0, 'end') + return "break" + + def escape_event(self, event): + x, y = self.currentxy + self.load_entry(x, y) + + def load_entry(self, x, y): + cell = self.sheet.getcell(x, y) + if cell is None: + text = "" + elif isinstance(cell, FormulaCell): + text = '=' + cell.formula + else: + text, alignment = cell.format() + self.entry.delete(0, 'end') + self.entry.insert(0, text) + self.entry.selection_range(0, 'end') + + def makegrid(self, rows, columns): + """Helper to create the grid of GUI cells. + + The edge (x==0 or y==0) is filled with labels; the rest is real cells. + """ + self.rows = rows + self.columns = columns + self.gridcells = {} + # Create the top left corner cell (which selects all) + cell = Tk.Label(self.cellgrid, relief='raised') + cell.grid_configure(column=0, row=0, sticky='NSWE') + cell.bind("<ButtonPress-1>", self.selectall) + # Create the top row of labels, and confiure the grid columns + for x in range(1, columns+1): + self.cellgrid.grid_columnconfigure(x, minsize=64) + cell = Tk.Label(self.cellgrid, text=colnum2name(x), relief='raised') + cell.grid_configure(column=x, row=0, sticky='WE') + self.gridcells[x, 0] = cell + cell.__x = x + cell.__y = 0 + cell.bind("<ButtonPress-1>", self.selectcolumn) + cell.bind("<B1-Motion>", self.extendcolumn) + cell.bind("<ButtonRelease-1>", self.extendcolumn) + cell.bind("<Shift-Button-1>", self.extendcolumn) + # Create the leftmost column of labels + for y in range(1, rows+1): + cell = Tk.Label(self.cellgrid, text=str(y), relief='raised') + cell.grid_configure(column=0, row=y, sticky='WE') + self.gridcells[0, y] = cell + cell.__x = 0 + cell.__y = y + cell.bind("<ButtonPress-1>", self.selectrow) + cell.bind("<B1-Motion>", self.extendrow) + cell.bind("<ButtonRelease-1>", self.extendrow) + cell.bind("<Shift-Button-1>", self.extendrow) + # Create the real cells + for x in range(1, columns+1): + for y in range(1, rows+1): + cell = Tk.Label(self.cellgrid, relief='sunken', + bg='white', fg='black') + cell.grid_configure(column=x, row=y, sticky='NSWE') + self.gridcells[x, y] = cell + cell.__x = x + cell.__y = y + # Bind mouse events + cell.bind("<ButtonPress-1>", self.press) + cell.bind("<B1-Motion>", self.motion) + cell.bind("<ButtonRelease-1>", self.release) + cell.bind("<Shift-Button-1>", self.release) + + def selectall(self, event): + self.setcurrent(1, 1) + self.setcorner(sys.maxint, sys.maxint) + + def selectcolumn(self, event): + x, y = self.whichxy(event) + self.setcurrent(x, 1) + self.setcorner(x, sys.maxint) + + def extendcolumn(self, event): + x, y = self.whichxy(event) + if x > 0: + self.setcurrent(self.currentxy[0], 1) + self.setcorner(x, sys.maxint) + + def selectrow(self, event): + x, y = self.whichxy(event) + self.setcurrent(1, y) + self.setcorner(sys.maxint, y) + + def extendrow(self, event): + x, y = self.whichxy(event) + if y > 0: + self.setcurrent(1, self.currentxy[1]) + self.setcorner(sys.maxint, y) + + def press(self, event): + x, y = self.whichxy(event) + if x > 0 and y > 0: + self.setcurrent(x, y) + + def motion(self, event): + x, y = self.whichxy(event) + if x > 0 and y > 0: + self.setcorner(x, y) + + release = motion + + def whichxy(self, event): + w = self.cellgrid.winfo_containing(event.x_root, event.y_root) + if w is not None and isinstance(w, Tk.Label): + try: + return w.__x, w.__y + except AttributeError: + pass + return 0, 0 + + def save(self): + self.sheet.save(self.filename) + + def setcurrent(self, x, y): + "Make (x, y) the current cell." + if self.currentxy is not None: + self.change_cell() + self.clearfocus() + self.beacon['text'] = cellname(x, y) + self.load_entry(x, y) + self.entry.focus_set() + self.currentxy = x, y + self.cornerxy = None + gridcell = self.gridcells.get(self.currentxy) + if gridcell is not None: + gridcell['bg'] = 'yellow' + + def setcorner(self, x, y): + if self.currentxy is None or self.currentxy == (x, y): + self.setcurrent(x, y) + return + self.clearfocus() + self.cornerxy = x, y + x1, y1 = self.currentxy + x2, y2 = self.cornerxy or self.currentxy + if x1 > x2: + x1, x2 = x2, x1 + if y1 > y2: + y1, y2 = y2, y1 + for (x, y), cell in self.gridcells.iteritems(): + if x1 <= x <= x2 and y1 <= y <= y2: + cell['bg'] = 'lightBlue' + gridcell = self.gridcells.get(self.currentxy) + if gridcell is not None: + gridcell['bg'] = 'yellow' + self.setbeacon(x1, y1, x2, y2) + + def setbeacon(self, x1, y1, x2, y2): + if x1 == y1 == 1 and x2 == y2 == sys.maxint: + name = ":" + elif (x1, x2) == (1, sys.maxint): + if y1 == y2: + name = "%d" % y1 + else: + name = "%d:%d" % (y1, y2) + elif (y1, y2) == (1, sys.maxint): + if x1 == x2: + name = "%s" % colnum2name(x1) + else: + name = "%s:%s" % (colnum2name(x1), colnum2name(x2)) + else: + name1 = cellname(*self.currentxy) + name2 = cellname(*self.cornerxy) + name = "%s:%s" % (name1, name2) + self.beacon['text'] = name + + + def clearfocus(self): + if self.currentxy is not None: + x1, y1 = self.currentxy + x2, y2 = self.cornerxy or self.currentxy + if x1 > x2: + x1, x2 = x2, x1 + if y1 > y2: + y1, y2 = y2, y1 + for (x, y), cell in self.gridcells.iteritems(): + if x1 <= x <= x2 and y1 <= y <= y2: + cell['bg'] = 'white' + + def return_event(self, event): + "Callback for the Return key." + self.change_cell() + x, y = self.currentxy + self.setcurrent(x, y+1) + return "break" + + def shift_return_event(self, event): + "Callback for the Return key with Shift modifier." + self.change_cell() + x, y = self.currentxy + self.setcurrent(x, max(1, y-1)) + return "break" + + def tab_event(self, event): + "Callback for the Tab key." + self.change_cell() + x, y = self.currentxy + self.setcurrent(x+1, y) + return "break" + + def shift_tab_event(self, event): + "Callback for the Tab key with Shift modifier." + self.change_cell() + x, y = self.currentxy + self.setcurrent(max(1, x-1), y) + return "break" + + def change_cell(self): + "Set the current cell from the entry widget." + x, y = self.currentxy + text = self.entry.get() + cell = None + if text.startswith('='): + cell = FormulaCell(text[1:]) + else: + for cls in int, long, float, complex: + try: + value = cls(text) + except: + continue + else: + cell = NumericCell(value) + break + if cell is None and text: + cell = StringCell(text) + if cell is None: + self.sheet.clearcell(x, y) + else: + self.sheet.setcell(x, y, cell) + self.sync() + + def sync(self): + "Fill the GUI cells from the sheet cells." + self.sheet.recalc() + for (x, y), gridcell in self.gridcells.iteritems(): + if x == 0 or y == 0: + continue + cell = self.sheet.getcell(x, y) + if cell is None: + gridcell['text'] = "" + else: + if hasattr(cell, 'format'): + text, alignment = cell.format() + else: + text, alignment = str(cell), LEFT + gridcell['text'] = text + gridcell['anchor'] = align2anchor[alignment] + + +def test_basic(): + "Basic non-gui self-test." + import os + a = Sheet() + for x in range(1, 11): + for y in range(1, 11): + if x == 1: + cell = NumericCell(y) + elif y == 1: + cell = NumericCell(x) + else: + c1 = cellname(x, 1) + c2 = cellname(1, y) + formula = "%s*%s" % (c1, c2) + cell = FormulaCell(formula) + a.setcell(x, y, cell) +## if os.path.isfile("sheet1.xml"): +## print "Loading from sheet1.xml" +## a.load("sheet1.xml") + a.display() + a.save("sheet1.xml") + +def test_gui(): + "GUI test." + if sys.argv[1:]: + filename = sys.argv[1] + else: + filename = "sheet1.xml" + g = SheetGUI(filename) + g.root.mainloop() + +if __name__ == '__main__': + #test_basic() + test_gui() diff --git a/sys/src/cmd/python/Demo/tkinter/guido/svkill.py b/sys/src/cmd/python/Demo/tkinter/guido/svkill.py new file mode 100755 index 000000000..69f7f3b16 --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/guido/svkill.py @@ -0,0 +1,128 @@ +#! /usr/bin/env python + +# Tkinter interface to SYSV `ps' and `kill' commands. + +from Tkinter import * + +if TkVersion < 4.0: + raise ImportError, "This version of svkill requires Tk 4.0 or later" + +from string import splitfields +from string import split +import commands +import os + +user = os.environ['LOGNAME'] + +class BarButton(Menubutton): + def __init__(self, master=None, **cnf): + apply(Menubutton.__init__, (self, master), cnf) + self.pack(side=LEFT) + self.menu = Menu(self, name='menu') + self['menu'] = self.menu + +class Kill(Frame): + # List of (name, option, pid_column) + view_list = [ + ('Default', ''), + ('Every (-e)', '-e'), + ('Non process group leaders (-d)', '-d'), + ('Non leaders with tty (-a)', '-a'), + ('For this user (-u %s)' % user, '-u %s' % user), + ] + format_list = [ + ('Default', '', 0), + ('Long (-l)', '-l', 3), + ('Full (-f)', '-f', 1), + ('Full Long (-f -l)', '-l -f', 3), + ('Session and group ID (-j)', '-j', 0), + ('Scheduler properties (-c)', '-c', 0), + ] + def kill(self, selected): + c = self.format_list[self.format.get()][2] + pid = split(selected)[c] + os.system('kill -9 ' + pid) + self.do_update() + def do_update(self): + format = self.format_list[self.format.get()][1] + view = self.view_list[self.view.get()][1] + s = commands.getoutput('ps %s %s' % (view, format)) + list = splitfields(s, '\n') + self.header.set(list[0] + ' ') + del list[0] + self.frame.list.delete(0, AtEnd()) + for line in list: + self.frame.list.insert(0, line) + def do_motion(self, e): + e.widget.select_clear('0', 'end') + e.widget.select_set(e.widget.nearest(e.y)) + def do_leave(self, e): + e.widget.select_clear('0', 'end') + def do_1(self, e): + self.kill(e.widget.get(e.widget.nearest(e.y))) + def __init__(self, master=None, **cnf): + apply(Frame.__init__, (self, master), cnf) + self.pack(expand=1, fill=BOTH) + self.bar = Frame(self, name='bar', relief=RAISED, + borderwidth=2) + self.bar.pack(fill=X) + self.bar.file = BarButton(self.bar, text='File') + self.bar.file.menu.add_command( + label='Quit', command=self.quit) + self.bar.view = BarButton(self.bar, text='View') + self.bar.format = BarButton(self.bar, text='Format') + self.view = IntVar(self) + self.view.set(0) + self.format = IntVar(self) + self.format.set(0) + for num in range(len(self.view_list)): + label, option = self.view_list[num] + self.bar.view.menu.add_radiobutton( + label=label, + command=self.do_update, + variable=self.view, + value=num) + for num in range(len(self.format_list)): + label, option, col = self.format_list[num] + self.bar.format.menu.add_radiobutton( + label=label, + command=self.do_update, + variable=self.format, + value=num) + self.bar.tk_menuBar(self.bar.file, + self.bar.view, + self.bar.format) + self.frame = Frame(self, relief=RAISED, borderwidth=2) + self.frame.pack(expand=1, fill=BOTH) + self.header = StringVar(self) + self.frame.label = Label( + self.frame, relief=FLAT, anchor=NW, borderwidth=0, + font='*-Courier-Bold-R-Normal-*-120-*', + textvariable=self.header) + self.frame.label.pack(fill=Y, anchor=W) + self.frame.vscroll = Scrollbar(self.frame, orient=VERTICAL) + self.frame.list = Listbox( + self.frame, + relief=SUNKEN, + font='*-Courier-Medium-R-Normal-*-120-*', + width=40, height=10, + selectbackground='#eed5b7', + selectborderwidth=0, + selectmode=BROWSE, + yscroll=self.frame.vscroll.set) + self.frame.vscroll['command'] = self.frame.list.yview + self.frame.vscroll.pack(side=RIGHT, fill=Y) + self.frame.list.pack(expand=1, fill=BOTH) + self.update = Button(self, text='Update', + command=self.do_update) + self.update.pack(fill=X) + self.frame.list.bind('<Motion>', self.do_motion) + self.frame.list.bind('<Leave>', self.do_leave) + self.frame.list.bind('<1>', self.do_1) + self.do_update() + +if __name__ == '__main__': + kill = Kill(None, borderwidth=5) + kill.winfo_toplevel().title('Tkinter Process Killer (SYSV)') + kill.winfo_toplevel().minsize(1, 1) + kill.mainloop() diff --git a/sys/src/cmd/python/Demo/tkinter/guido/switch.py b/sys/src/cmd/python/Demo/tkinter/guido/switch.py new file mode 100644 index 000000000..3b58f7ce4 --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/guido/switch.py @@ -0,0 +1,55 @@ +# Show how to do switchable panels. + +from Tkinter import * + +class App: + + def __init__(self, top=None, master=None): + if top is None: + if master is None: + top = Tk() + else: + top = Toplevel(master) + self.top = top + self.buttonframe = Frame(top) + self.buttonframe.pack() + self.panelframe = Frame(top, borderwidth=2, relief=GROOVE) + self.panelframe.pack(expand=1, fill=BOTH) + self.panels = {} + self.curpanel = None + + def addpanel(self, name, klass): + button = Button(self.buttonframe, text=name, + command=lambda self=self, name=name: self.show(name)) + button.pack(side=LEFT) + frame = Frame(self.panelframe) + instance = klass(frame) + self.panels[name] = (button, frame, instance) + if self.curpanel is None: + self.show(name) + + def show(self, name): + (button, frame, instance) = self.panels[name] + if self.curpanel: + self.curpanel.pack_forget() + self.curpanel = frame + frame.pack(expand=1, fill="both") + +class LabelPanel: + def __init__(self, frame): + self.label = Label(frame, text="Hello world") + self.label.pack() + +class ButtonPanel: + def __init__(self, frame): + self.button = Button(frame, text="Press me") + self.button.pack() + +def main(): + app = App() + app.addpanel("label", LabelPanel) + app.addpanel("button", ButtonPanel) + app.top.mainloop() + +if __name__ == '__main__': + main() diff --git a/sys/src/cmd/python/Demo/tkinter/guido/tkman.py b/sys/src/cmd/python/Demo/tkinter/guido/tkman.py new file mode 100755 index 000000000..6b0b64118 --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/guido/tkman.py @@ -0,0 +1,267 @@ +#! /usr/bin/env python + +# Tk man page browser -- currently only shows the Tcl/Tk man pages + +import sys +import os +import string +import re +from Tkinter import * +from ManPage import ManPage + +MANNDIRLIST = ['/depot/sundry/man/mann','/usr/local/man/mann'] +MAN3DIRLIST = ['/depot/sundry/man/man3','/usr/local/man/man3'] + +foundmanndir = 0 +for dir in MANNDIRLIST: + if os.path.exists(dir): + MANNDIR = dir + foundmanndir = 1 + +foundman3dir = 0 +for dir in MAN3DIRLIST: + if os.path.exists(dir): + MAN3DIR = dir + foundman3dir = 1 + +if not foundmanndir or not foundman3dir: + sys.stderr.write('\n') + if not foundmanndir: + msg = """\ +Failed to find mann directory. +Please add the correct entry to the MANNDIRLIST +at the top of %s script.""" % \ +sys.argv[0] + sys.stderr.write("%s\n\n" % msg) + if not foundman3dir: + msg = """\ +Failed to find man3 directory. +Please add the correct entry to the MAN3DIRLIST +at the top of %s script.""" % \ +sys.argv[0] + sys.stderr.write("%s\n\n" % msg) + sys.exit(1) + +del foundmanndir +del foundman3dir + +def listmanpages(mandir): + files = os.listdir(mandir) + names = [] + for file in files: + if file[-2:-1] == '.' and (file[-1] in 'ln123456789'): + names.append(file[:-2]) + names.sort() + return names + +class SelectionBox: + + def __init__(self, master=None): + self.choices = [] + + self.frame = Frame(master, name="frame") + self.frame.pack(expand=1, fill=BOTH) + self.master = self.frame.master + self.subframe = Frame(self.frame, name="subframe") + self.subframe.pack(expand=0, fill=BOTH) + self.leftsubframe = Frame(self.subframe, name='leftsubframe') + self.leftsubframe.pack(side=LEFT, expand=1, fill=BOTH) + self.rightsubframe = Frame(self.subframe, name='rightsubframe') + self.rightsubframe.pack(side=RIGHT, expand=1, fill=BOTH) + self.chaptervar = StringVar(master) + self.chapter = Menubutton(self.rightsubframe, name='chapter', + text='Directory', relief=RAISED, + borderwidth=2) + self.chapter.pack(side=TOP) + self.chaptermenu = Menu(self.chapter, name='chaptermenu') + self.chaptermenu.add_radiobutton(label='C functions', + value=MAN3DIR, + variable=self.chaptervar, + command=self.newchapter) + self.chaptermenu.add_radiobutton(label='Tcl/Tk functions', + value=MANNDIR, + variable=self.chaptervar, + command=self.newchapter) + self.chapter['menu'] = self.chaptermenu + self.listbox = Listbox(self.rightsubframe, name='listbox', + relief=SUNKEN, borderwidth=2, + width=20, height=5) + self.listbox.pack(expand=1, fill=BOTH) + self.l1 = Button(self.leftsubframe, name='l1', + text='Display manual page named:', + command=self.entry_cb) + self.l1.pack(side=TOP) + self.entry = Entry(self.leftsubframe, name='entry', + relief=SUNKEN, borderwidth=2, + width=20) + self.entry.pack(expand=0, fill=X) + self.l2frame = Frame(self.leftsubframe, name='l2frame') + self.l2frame.pack(expand=0, fill=NONE) + self.l2 = Button(self.l2frame, name='l2', + text='Search regexp:', + command=self.search_cb) + self.l2.pack(side=LEFT) + self.casevar = BooleanVar() + self.casesense = Checkbutton(self.l2frame, name='casesense', + text='Case sensitive', + variable=self.casevar, + relief=FLAT) + self.casesense.pack(side=LEFT) + self.search = Entry(self.leftsubframe, name='search', + relief=SUNKEN, borderwidth=2, + width=20) + self.search.pack(expand=0, fill=X) + self.title = Label(self.leftsubframe, name='title', + text='(none)') + self.title.pack(side=BOTTOM) + self.text = ManPage(self.frame, name='text', + relief=SUNKEN, borderwidth=2, + wrap=NONE, width=72, + selectbackground='pink') + self.text.pack(expand=1, fill=BOTH) + + self.entry.bind('<Return>', self.entry_cb) + self.search.bind('<Return>', self.search_cb) + self.listbox.bind('<Double-1>', self.listbox_cb) + + self.entry.bind('<Tab>', self.entry_tab) + self.search.bind('<Tab>', self.search_tab) + self.text.bind('<Tab>', self.text_tab) + + self.entry.focus_set() + + self.chaptervar.set(MANNDIR) + self.newchapter() + + def newchapter(self): + mandir = self.chaptervar.get() + self.choices = [] + self.addlist(listmanpages(mandir)) + + def addchoice(self, choice): + if choice not in self.choices: + self.choices.append(choice) + self.choices.sort() + self.update() + + def addlist(self, list): + self.choices[len(self.choices):] = list + self.choices.sort() + self.update() + + def entry_cb(self, *e): + self.update() + + def listbox_cb(self, e): + selection = self.listbox.curselection() + if selection and len(selection) == 1: + name = self.listbox.get(selection[0]) + self.show_page(name) + + def search_cb(self, *e): + self.search_string(self.search.get()) + + def entry_tab(self, e): + self.search.focus_set() + + def search_tab(self, e): + self.entry.focus_set() + + def text_tab(self, e): + self.entry.focus_set() + + def updatelist(self): + key = self.entry.get() + ok = filter(lambda name, key=key, n=len(key): name[:n]==key, + self.choices) + if not ok: + self.frame.bell() + self.listbox.delete(0, AtEnd()) + exactmatch = 0 + for item in ok: + if item == key: exactmatch = 1 + self.listbox.insert(AtEnd(), item) + if exactmatch: + return key + n = self.listbox.size() + if n == 1: + return self.listbox.get(0) + # Else return None, meaning not a unique selection + + def update(self): + name = self.updatelist() + if name: + self.show_page(name) + self.entry.delete(0, AtEnd()) + self.updatelist() + + def show_page(self, name): + file = '%s/%s.?' % (self.chaptervar.get(), name) + fp = os.popen('nroff -man %s | ul -i' % file, 'r') + self.text.kill() + self.title['text'] = name + self.text.parsefile(fp) + + def search_string(self, search): + if not search: + self.frame.bell() + print 'Empty search string' + return + if not self.casevar.get(): + map = re.IGNORECASE + else: + map = None + try: + if map: + prog = re.compile(search, map) + else: + prog = re.compile(search) + except re.error, msg: + self.frame.bell() + print 'Regex error:', msg + return + here = self.text.index(AtInsert()) + lineno = string.atoi(here[:string.find(here, '.')]) + end = self.text.index(AtEnd()) + endlineno = string.atoi(end[:string.find(end, '.')]) + wraplineno = lineno + found = 0 + while 1: + lineno = lineno + 1 + if lineno > endlineno: + if wraplineno <= 0: + break + endlineno = wraplineno + lineno = 0 + wraplineno = 0 + line = self.text.get('%d.0 linestart' % lineno, + '%d.0 lineend' % lineno) + i = prog.search(line) + if i >= 0: + found = 1 + n = max(1, len(prog.group(0))) + try: + self.text.tag_remove('sel', + AtSelFirst(), + AtSelLast()) + except TclError: + pass + self.text.tag_add('sel', + '%d.%d' % (lineno, i), + '%d.%d' % (lineno, i+n)) + self.text.mark_set(AtInsert(), + '%d.%d' % (lineno, i)) + self.text.yview_pickplace(AtInsert()) + break + if not found: + self.frame.bell() + +def main(): + root = Tk() + sb = SelectionBox(root) + if sys.argv[1:]: + sb.show_page(sys.argv[1]) + root.minsize(1, 1) + root.mainloop() + +main() diff --git a/sys/src/cmd/python/Demo/tkinter/guido/wish.py b/sys/src/cmd/python/Demo/tkinter/guido/wish.py new file mode 100755 index 000000000..0a61ad88d --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/guido/wish.py @@ -0,0 +1,27 @@ +# This is about all it requires to write a wish shell in Python! + +import _tkinter +import os + +tk = _tkinter.create(os.environ['DISPLAY'], 'wish', 'Tk', 1) +tk.call('update') + +cmd = '' + +while 1: + if cmd: prompt = '' + else: prompt = '% ' + try: + line = raw_input(prompt) + except EOFError: + break + cmd = cmd + (line + '\n') + if tk.getboolean(tk.call('info', 'complete', cmd)): + tk.record(line) + try: + result = tk.call('eval', cmd) + except _tkinter.TclError, msg: + print 'TclError:', msg + else: + if result: print result + cmd = '' diff --git a/sys/src/cmd/python/Demo/tkinter/matt/00-HELLO-WORLD.py b/sys/src/cmd/python/Demo/tkinter/matt/00-HELLO-WORLD.py new file mode 100644 index 000000000..1c3151b11 --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/matt/00-HELLO-WORLD.py @@ -0,0 +1,27 @@ +from Tkinter import * + +# note that there is no explicit call to start Tk. +# Tkinter is smart enough to start the system if it's not already going. + +class Test(Frame): + def printit(self): + print "hi" + + def createWidgets(self): + self.QUIT = Button(self, text='QUIT', foreground='red', + command=self.quit) + + self.QUIT.pack(side=LEFT, fill=BOTH) + + # a hello button + self.hi_there = Button(self, text='Hello', + command=self.printit) + self.hi_there.pack(side=LEFT) + + def __init__(self, master=None): + Frame.__init__(self, master) + Pack.config(self) + self.createWidgets() + +test = Test() +test.mainloop() diff --git a/sys/src/cmd/python/Demo/tkinter/matt/README b/sys/src/cmd/python/Demo/tkinter/matt/README new file mode 100644 index 000000000..eb9d30246 --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/matt/README @@ -0,0 +1,30 @@ +This directory contains some ad-hoc examples of Tkinter widget +creation. The files named + + *-simple.py + +are the ones to start with if you're looking for a bare-bones usage of +a widget. The other files are meant to show common usage patters that +are a tad more involved. + +If you have a suggestion for an example program, please send mail to + + conway@virginia.edu + +and I'll include it. + + +matt + +TODO +------- +The X selection +Dialog Boxes +More canvas examples +Message widgets +Text Editors +Scrollbars +Listboxes + + + diff --git a/sys/src/cmd/python/Demo/tkinter/matt/animation-simple.py b/sys/src/cmd/python/Demo/tkinter/matt/animation-simple.py new file mode 100644 index 000000000..b52e1dc3a --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/matt/animation-simple.py @@ -0,0 +1,35 @@ +from Tkinter import * + +# This program shows how to use the "after" function to make animation. + +class Test(Frame): + def printit(self): + print "hi" + + def createWidgets(self): + self.QUIT = Button(self, text='QUIT', foreground='red', + command=self.quit) + self.QUIT.pack(side=LEFT, fill=BOTH) + + self.draw = Canvas(self, width="5i", height="5i") + + # all of these work.. + self.draw.create_rectangle(0, 0, 10, 10, tags="thing", fill="blue") + self.draw.pack(side=LEFT) + + def moveThing(self, *args): + # move 1/10 of an inch every 1/10 sec (1" per second, smoothly) + self.draw.move("thing", "0.01i", "0.01i") + self.after(10, self.moveThing) + + + def __init__(self, master=None): + Frame.__init__(self, master) + Pack.config(self) + self.createWidgets() + self.after(10, self.moveThing) + + +test = Test() + +test.mainloop() diff --git a/sys/src/cmd/python/Demo/tkinter/matt/animation-w-velocity-ctrl.py b/sys/src/cmd/python/Demo/tkinter/matt/animation-w-velocity-ctrl.py new file mode 100644 index 000000000..e676338fe --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/matt/animation-w-velocity-ctrl.py @@ -0,0 +1,44 @@ +from Tkinter import * + +# this is the same as simple-demo-1.py, but uses +# subclassing. +# note that there is no explicit call to start Tk. +# Tkinter is smart enough to start the system if it's not already going. + + +class Test(Frame): + def printit(self): + print "hi" + + def createWidgets(self): + self.QUIT = Button(self, text='QUIT', foreground='red', + command=self.quit) + self.QUIT.pack(side=BOTTOM, fill=BOTH) + + self.draw = Canvas(self, width="5i", height="5i") + + self.speed = Scale(self, orient=HORIZONTAL, from_=-100, to=100) + + self.speed.pack(side=BOTTOM, fill=X) + + # all of these work.. + self.draw.create_rectangle(0, 0, 10, 10, tags="thing", fill="blue") + self.draw.pack(side=LEFT) + + def moveThing(self, *args): + velocity = self.speed.get() + str = float(velocity) / 1000.0 + str = "%ri" % (str,) + self.draw.move("thing", str, str) + self.after(10, self.moveThing) + + def __init__(self, master=None): + Frame.__init__(self, master) + Pack.config(self) + self.createWidgets() + self.after(10, self.moveThing) + + +test = Test() + +test.mainloop() diff --git a/sys/src/cmd/python/Demo/tkinter/matt/bind-w-mult-calls-p-type.py b/sys/src/cmd/python/Demo/tkinter/matt/bind-w-mult-calls-p-type.py new file mode 100644 index 000000000..f3220da50 --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/matt/bind-w-mult-calls-p-type.py @@ -0,0 +1,44 @@ +from Tkinter import * +import string + +# This program shows how to use a simple type-in box + +class App(Frame): + def __init__(self, master=None): + Frame.__init__(self, master) + self.pack() + + self.entrythingy = Entry() + self.entrythingy.pack() + + # and here we get a callback when the user hits return. we could + # make the key that triggers the callback anything we wanted to. + # other typical options might be <Key-Tab> or <Key> (for anything) + self.entrythingy.bind('<Key-Return>', self.print_contents) + + # Note that here is where we bind a completely different callback to + # the same event. We pass "+" here to indicate that we wish to ADD + # this callback to the list associated with this event type. + # Not specifying "+" would simply override whatever callback was + # defined on this event. + self.entrythingy.bind('<Key-Return>', self.print_something_else, "+") + + def print_contents(self, event): + print "hi. contents of entry is now ---->", self.entrythingy.get() + + + def print_something_else(self, event): + print "hi. Now doing something completely different" + + +root = App() +root.master.title("Foo") +root.mainloop() + + + +# secret tip for experts: if you pass *any* non-false value as +# the third parameter to bind(), Tkinter.py will accumulate +# callbacks instead of overwriting. I use "+" here because that's +# the Tk notation for getting this sort of behavior. The perfect GUI +# interface would use a less obscure notation. diff --git a/sys/src/cmd/python/Demo/tkinter/matt/canvas-demo-simple.py b/sys/src/cmd/python/Demo/tkinter/matt/canvas-demo-simple.py new file mode 100644 index 000000000..a01679a66 --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/matt/canvas-demo-simple.py @@ -0,0 +1,28 @@ +from Tkinter import * + +# this program creates a canvas and puts a single polygon on the canvas + +class Test(Frame): + def printit(self): + print "hi" + + def createWidgets(self): + self.QUIT = Button(self, text='QUIT', foreground='red', + command=self.quit) + self.QUIT.pack(side=BOTTOM, fill=BOTH) + + self.draw = Canvas(self, width="5i", height="5i") + + # see the other demos for other ways of specifying coords for a polygon + self.draw.create_rectangle(0, 0, "3i", "3i", fill="black") + + self.draw.pack(side=LEFT) + + def __init__(self, master=None): + Frame.__init__(self, master) + Pack.config(self) + self.createWidgets() + +test = Test() + +test.mainloop() diff --git a/sys/src/cmd/python/Demo/tkinter/matt/canvas-gridding.py b/sys/src/cmd/python/Demo/tkinter/matt/canvas-gridding.py new file mode 100644 index 000000000..3c52b91af --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/matt/canvas-gridding.py @@ -0,0 +1,61 @@ +from Tkinter import * + +# this is the same as simple-demo-1.py, but uses +# subclassing. +# note that there is no explicit call to start Tk. +# Tkinter is smart enough to start the system if it's not already going. + +class Test(Frame): + def printit(self): + print "hi" + + def createWidgets(self): + self.QUIT = Button(self, text='QUIT', + background='red', + foreground='white', + height=3, + command=self.quit) + self.QUIT.pack(side=BOTTOM, fill=BOTH) + + self.canvasObject = Canvas(self, width="5i", height="5i") + self.canvasObject.pack(side=LEFT) + + def mouseDown(self, event): + # canvas x and y take the screen coords from the event and translate + # them into the coordinate system of the canvas object + self.startx = self.canvasObject.canvasx(event.x, self.griddingSize) + self.starty = self.canvasObject.canvasy(event.y, self.griddingSize) + + def mouseMotion(self, event): + # canvas x and y take the screen coords from the event and translate + # them into the coordinate system of the canvas object + x = self.canvasObject.canvasx(event.x, self.griddingSize) + y = self.canvasObject.canvasy(event.y, self.griddingSize) + + if (self.startx != event.x) and (self.starty != event.y) : + self.canvasObject.delete(self.rubberbandBox) + self.rubberbandBox = self.canvasObject.create_rectangle( + self.startx, self.starty, x, y) + # this flushes the output, making sure that + # the rectangle makes it to the screen + # before the next event is handled + self.update_idletasks() + + def __init__(self, master=None): + Frame.__init__(self, master) + Pack.config(self) + self.createWidgets() + + # this is a "tagOrId" for the rectangle we draw on the canvas + self.rubberbandBox = None + + # this is the size of the gridding squares + self.griddingSize = 50 + + Widget.bind(self.canvasObject, "<Button-1>", self.mouseDown) + Widget.bind(self.canvasObject, "<Button1-Motion>", self.mouseMotion) + + +test = Test() + +test.mainloop() diff --git a/sys/src/cmd/python/Demo/tkinter/matt/canvas-moving-or-creating.py b/sys/src/cmd/python/Demo/tkinter/matt/canvas-moving-or-creating.py new file mode 100644 index 000000000..5327c0827 --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/matt/canvas-moving-or-creating.py @@ -0,0 +1,62 @@ +from Tkinter import * + +# this file demonstrates a more sophisticated movement -- +# move dots or create new ones if you click outside the dots + +class Test(Frame): + ################################################################### + ###### Event callbacks for THE CANVAS (not the stuff drawn on it) + ################################################################### + def mouseDown(self, event): + # see if we're inside a dot. If we are, it + # gets tagged as CURRENT for free by tk. + if not event.widget.find_withtag(CURRENT): + # there is no dot here, so we can make one, + # and bind some interesting behavior to it. + # ------ + # create a dot, and mark it as CURRENT + fred = self.draw.create_oval( + event.x - 10, event.y -10, event.x +10, event.y + 10, + fill="green", tags=CURRENT) + + self.draw.tag_bind(fred, "<Any-Enter>", self.mouseEnter) + self.draw.tag_bind(fred, "<Any-Leave>", self.mouseLeave) + + self.lastx = event.x + self.lasty = event.y + + def mouseMove(self, event): + self.draw.move(CURRENT, event.x - self.lastx, event.y - self.lasty) + self.lastx = event.x + self.lasty = event.y + + ################################################################### + ###### Event callbacks for canvas ITEMS (stuff drawn on the canvas) + ################################################################### + def mouseEnter(self, event): + # the CURRENT tag is applied to the object the cursor is over. + # this happens automatically. + self.draw.itemconfig(CURRENT, fill="red") + + def mouseLeave(self, event): + # the CURRENT tag is applied to the object the cursor is over. + # this happens automatically. + self.draw.itemconfig(CURRENT, fill="blue") + + def createWidgets(self): + self.QUIT = Button(self, text='QUIT', foreground='red', + command=self.quit) + self.QUIT.pack(side=LEFT, fill=BOTH) + self.draw = Canvas(self, width="5i", height="5i") + self.draw.pack(side=LEFT) + + Widget.bind(self.draw, "<1>", self.mouseDown) + Widget.bind(self.draw, "<B1-Motion>", self.mouseMove) + + def __init__(self, master=None): + Frame.__init__(self, master) + Pack.config(self) + self.createWidgets() + +test = Test() +test.mainloop() diff --git a/sys/src/cmd/python/Demo/tkinter/matt/canvas-moving-w-mouse.py b/sys/src/cmd/python/Demo/tkinter/matt/canvas-moving-w-mouse.py new file mode 100644 index 000000000..81785d86a --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/matt/canvas-moving-w-mouse.py @@ -0,0 +1,55 @@ +from Tkinter import * + +# this file demonstrates the movement of a single canvas item under mouse control + +class Test(Frame): + ################################################################### + ###### Event callbacks for THE CANVAS (not the stuff drawn on it) + ################################################################### + def mouseDown(self, event): + # remember where the mouse went down + self.lastx = event.x + self.lasty = event.y + + def mouseMove(self, event): + # whatever the mouse is over gets tagged as CURRENT for free by tk. + self.draw.move(CURRENT, event.x - self.lastx, event.y - self.lasty) + self.lastx = event.x + self.lasty = event.y + + ################################################################### + ###### Event callbacks for canvas ITEMS (stuff drawn on the canvas) + ################################################################### + def mouseEnter(self, event): + # the CURRENT tag is applied to the object the cursor is over. + # this happens automatically. + self.draw.itemconfig(CURRENT, fill="red") + + def mouseLeave(self, event): + # the CURRENT tag is applied to the object the cursor is over. + # this happens automatically. + self.draw.itemconfig(CURRENT, fill="blue") + + def createWidgets(self): + self.QUIT = Button(self, text='QUIT', foreground='red', + command=self.quit) + self.QUIT.pack(side=LEFT, fill=BOTH) + self.draw = Canvas(self, width="5i", height="5i") + self.draw.pack(side=LEFT) + + fred = self.draw.create_oval(0, 0, 20, 20, + fill="green", tags="selected") + + self.draw.tag_bind(fred, "<Any-Enter>", self.mouseEnter) + self.draw.tag_bind(fred, "<Any-Leave>", self.mouseLeave) + + Widget.bind(self.draw, "<1>", self.mouseDown) + Widget.bind(self.draw, "<B1-Motion>", self.mouseMove) + + def __init__(self, master=None): + Frame.__init__(self, master) + Pack.config(self) + self.createWidgets() + +test = Test() +test.mainloop() diff --git a/sys/src/cmd/python/Demo/tkinter/matt/canvas-mult-item-sel.py b/sys/src/cmd/python/Demo/tkinter/matt/canvas-mult-item-sel.py new file mode 100644 index 000000000..a4f267cc1 --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/matt/canvas-mult-item-sel.py @@ -0,0 +1,78 @@ +from Tkinter import * + +# allows moving dots with multiple selection. + +SELECTED_COLOR = "red" +UNSELECTED_COLOR = "blue" + +class Test(Frame): + ################################################################### + ###### Event callbacks for THE CANVAS (not the stuff drawn on it) + ################################################################### + def mouseDown(self, event): + # see if we're inside a dot. If we are, it + # gets tagged as CURRENT for free by tk. + + if not event.widget.find_withtag(CURRENT): + # we clicked outside of all dots on the canvas. unselect all. + + # re-color everything back to an unselected color + self.draw.itemconfig("selected", fill=UNSELECTED_COLOR) + # unselect everything + self.draw.dtag("selected") + else: + # mark as "selected" the thing the cursor is under + self.draw.addtag("selected", "withtag", CURRENT) + # color it as selected + self.draw.itemconfig("selected", fill=SELECTED_COLOR) + + self.lastx = event.x + self.lasty = event.y + + + def mouseMove(self, event): + self.draw.move("selected", event.x - self.lastx, event.y - self.lasty) + self.lastx = event.x + self.lasty = event.y + + def makeNewDot(self): + # create a dot, and mark it as current + fred = self.draw.create_oval(0, 0, 20, 20, + fill=SELECTED_COLOR, tags=CURRENT) + # and make it selected + self.draw.addtag("selected", "withtag", CURRENT) + + def createWidgets(self): + self.QUIT = Button(self, text='QUIT', foreground='red', + command=self.quit) + + ################ + # make the canvas and bind some behavior to it + ################ + self.draw = Canvas(self, width="5i", height="5i") + Widget.bind(self.draw, "<1>", self.mouseDown) + Widget.bind(self.draw, "<B1-Motion>", self.mouseMove) + + # and other things..... + self.button = Button(self, text="make a new dot", foreground="blue", + command=self.makeNewDot) + + message = ("%s dots are selected and can be dragged.\n" + "%s are not selected.\n" + "Click in a dot to select it.\n" + "Click on empty space to deselect all dots." + ) % (SELECTED_COLOR, UNSELECTED_COLOR) + self.label = Message(self, width="5i", text=message) + + self.QUIT.pack(side=BOTTOM, fill=BOTH) + self.label.pack(side=BOTTOM, fill=X, expand=1) + self.button.pack(side=BOTTOM, fill=X) + self.draw.pack(side=LEFT) + + def __init__(self, master=None): + Frame.__init__(self, master) + Pack.config(self) + self.createWidgets() + +test = Test() +test.mainloop() diff --git a/sys/src/cmd/python/Demo/tkinter/matt/canvas-reading-tag-info.py b/sys/src/cmd/python/Demo/tkinter/matt/canvas-reading-tag-info.py new file mode 100644 index 000000000..f57ea180a --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/matt/canvas-reading-tag-info.py @@ -0,0 +1,49 @@ +from Tkinter import * + + +class Test(Frame): + def printit(self): + print "hi" + + def createWidgets(self): + self.QUIT = Button(self, text='QUIT', foreground='red', + command=self.quit) + self.QUIT.pack(side=BOTTOM, fill=BOTH) + + self.drawing = Canvas(self, width="5i", height="5i") + + # make a shape + pgon = self.drawing.create_polygon( + 10, 10, 110, 10, 110, 110, 10 , 110, + fill="red", tags=("weee", "foo", "groo")) + + # this is how you query an object for its attributes + # config options FOR CANVAS ITEMS always come back in tuples of length 5. + # 0 attribute name + # 1 BLANK + # 2 BLANK + # 3 default value + # 4 current value + # the blank spots are for consistency with the config command that + # is used for widgets. (remember, this is for ITEMS drawn + # on a canvas widget, not widgets) + option_value = self.drawing.itemconfig(pgon, "stipple") + print "pgon's current stipple value is -->", option_value[4], "<--" + option_value = self.drawing.itemconfig(pgon, "fill") + print "pgon's current fill value is -->", option_value[4], "<--" + print " when he is usually colored -->", option_value[3], "<--" + + ## here we print out all the tags associated with this object + option_value = self.drawing.itemconfig(pgon, "tags") + print "pgon's tags are", option_value[4] + + self.drawing.pack(side=LEFT) + + def __init__(self, master=None): + Frame.__init__(self, master) + Pack.config(self) + self.createWidgets() + +test = Test() + +test.mainloop() diff --git a/sys/src/cmd/python/Demo/tkinter/matt/canvas-w-widget-draw-el.py b/sys/src/cmd/python/Demo/tkinter/matt/canvas-w-widget-draw-el.py new file mode 100644 index 000000000..5b26210c0 --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/matt/canvas-w-widget-draw-el.py @@ -0,0 +1,36 @@ +from Tkinter import * + +# this file demonstrates the creation of widgets as part of a canvas object + +class Test(Frame): + def printhi(self): + print "hi" + + def createWidgets(self): + self.QUIT = Button(self, text='QUIT', foreground='red', + command=self.quit) + self.QUIT.pack(side=BOTTOM, fill=BOTH) + + self.draw = Canvas(self, width="5i", height="5i") + + self.button = Button(self, text="this is a button", + command=self.printhi) + + # note here the coords are given in pixels (form the + # upper right and corner of the window, as usual for X) + # but might just have well been given in inches or points or + # whatever...use the "anchor" option to control what point of the + # widget (in this case the button) gets mapped to the given x, y. + # you can specify corners, edges, center, etc... + self.draw.create_window(300, 300, window=self.button) + + self.draw.pack(side=LEFT) + + def __init__(self, master=None): + Frame.__init__(self, master) + Pack.config(self) + self.createWidgets() + +test = Test() + +test.mainloop() diff --git a/sys/src/cmd/python/Demo/tkinter/matt/canvas-with-scrollbars.py b/sys/src/cmd/python/Demo/tkinter/matt/canvas-with-scrollbars.py new file mode 100644 index 000000000..81ef25a88 --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/matt/canvas-with-scrollbars.py @@ -0,0 +1,60 @@ +from Tkinter import * + +# This example program creates a scroling canvas, and demonstrates +# how to tie scrollbars and canvses together. The mechanism +# is analogus for listboxes and other widgets with +# "xscroll" and "yscroll" configuration options. + +class Test(Frame): + def printit(self): + print "hi" + + def createWidgets(self): + self.question = Label(self, text="Can Find The BLUE Square??????") + self.question.pack() + + self.QUIT = Button(self, text='QUIT', background='red', + height=3, command=self.quit) + self.QUIT.pack(side=BOTTOM, fill=BOTH) + spacer = Frame(self, height="0.25i") + spacer.pack(side=BOTTOM) + + # notice that the scroll region (20" x 20") is larger than + # displayed size of the widget (5" x 5") + self.draw = Canvas(self, width="5i", height="5i", + background="white", + scrollregion=(0, 0, "20i", "20i")) + + self.draw.scrollX = Scrollbar(self, orient=HORIZONTAL) + self.draw.scrollY = Scrollbar(self, orient=VERTICAL) + + # now tie the three together. This is standard boilerplate text + self.draw['xscrollcommand'] = self.draw.scrollX.set + self.draw['yscrollcommand'] = self.draw.scrollY.set + self.draw.scrollX['command'] = self.draw.xview + self.draw.scrollY['command'] = self.draw.yview + + # draw something. Note that the first square + # is visible, but you need to scroll to see the second one. + self.draw.create_rectangle(0, 0, "3.5i", "3.5i", fill="black") + self.draw.create_rectangle("10i", "10i", "13.5i", "13.5i", fill="blue") + + # pack 'em up + self.draw.scrollX.pack(side=BOTTOM, fill=X) + self.draw.scrollY.pack(side=RIGHT, fill=Y) + self.draw.pack(side=LEFT) + + + def scrollCanvasX(self, *args): + print "scrolling", args + print self.draw.scrollX.get() + + + def __init__(self, master=None): + Frame.__init__(self, master) + Pack.config(self) + self.createWidgets() + +test = Test() + +test.mainloop() diff --git a/sys/src/cmd/python/Demo/tkinter/matt/dialog-box.py b/sys/src/cmd/python/Demo/tkinter/matt/dialog-box.py new file mode 100644 index 000000000..dea8f3900 --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/matt/dialog-box.py @@ -0,0 +1,64 @@ +from Tkinter import * +from Dialog import Dialog + +# this shows how to create a new window with a button in it +# that can create new windows + +class Test(Frame): + def printit(self): + print "hi" + + def makeWindow(self): + """Create a top-level dialog with some buttons. + + This uses the Dialog class, which is a wrapper around the Tcl/Tk + tk_dialog script. The function returns 0 if the user clicks 'yes' + or 1 if the user clicks 'no'. + """ + # the parameters to this call are as follows: + d = Dialog( + self, ## name of a toplevel window + title="fred the dialog box",## title on the window + text="click on a choice", ## message to appear in window + bitmap="info", ## bitmap (if any) to appear; + ## if none, use "" + # legal values here are: + # string what it looks like + # ---------------------------------------------- + # error a circle with a slash through it + # grey25 grey square + # grey50 darker grey square + # hourglass use for "wait.." + # info a large, lower case "i" + # questhead a human head with a "?" in it + # question a large "?" + # warning a large "!" + # @fname X bitmap where fname is the path to the file + # + default=0, # the index of the default button choice. + # hitting return selects this + strings=("yes", "no")) + # values of the 'strings' key are the labels for the + # buttons that appear left to right in the dialog box + return d.num + + + def createWidgets(self): + self.QUIT = Button(self, text='QUIT', foreground='red', + command=self.quit) + self.QUIT.pack(side=LEFT, fill=BOTH) + + # a hello button + self.hi_there = Button(self, text='Make a New Window', + command=self.makeWindow) + self.hi_there.pack(side=LEFT) + + + def __init__(self, master=None): + Frame.__init__(self, master) + Pack.config(self) + self.windownum = 0 + self.createWidgets() + +test = Test() +test.mainloop() diff --git a/sys/src/cmd/python/Demo/tkinter/matt/entry-simple.py b/sys/src/cmd/python/Demo/tkinter/matt/entry-simple.py new file mode 100644 index 000000000..5146e6fd9 --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/matt/entry-simple.py @@ -0,0 +1,24 @@ +from Tkinter import * +import string + +# This program shows how to use a simple type-in box + +class App(Frame): + def __init__(self, master=None): + Frame.__init__(self, master) + self.pack() + + self.entrythingy = Entry() + self.entrythingy.pack() + + # and here we get a callback when the user hits return. we could + # make the key that triggers the callback anything we wanted to. + # other typical options might be <Key-Tab> or <Key> (for anything) + self.entrythingy.bind('<Key-Return>', self.print_contents) + + def print_contents(self, event): + print "hi. contents of entry is now ---->", self.entrythingy.get() + +root = App() +root.master.title("Foo") +root.mainloop() diff --git a/sys/src/cmd/python/Demo/tkinter/matt/entry-with-shared-variable.py b/sys/src/cmd/python/Demo/tkinter/matt/entry-with-shared-variable.py new file mode 100644 index 000000000..2b76162bd --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/matt/entry-with-shared-variable.py @@ -0,0 +1,46 @@ +from Tkinter import * +import string + +# This program shows how to make a typein box shadow a program variable. + +class App(Frame): + def __init__(self, master=None): + Frame.__init__(self, master) + self.pack() + + self.entrythingy = Entry(self) + self.entrythingy.pack() + + self.button = Button(self, text="Uppercase The Entry", + command=self.upper) + self.button.pack() + + # here we have the text in the entry widget tied to a variable. + # changes in the variable are echoed in the widget and vice versa. + # Very handy. + # there are other Variable types. See Tkinter.py for all + # the other variable types that can be shadowed + self.contents = StringVar() + self.contents.set("this is a variable") + self.entrythingy.config(textvariable=self.contents) + + # and here we get a callback when the user hits return. we could + # make the key that triggers the callback anything we wanted to. + # other typical options might be <Key-Tab> or <Key> (for anything) + self.entrythingy.bind('<Key-Return>', self.print_contents) + + def upper(self): + # notice here, we don't actually refer to the entry box. + # we just operate on the string variable and we + # because it's being looked at by the entry widget, changing + # the variable changes the entry widget display automatically. + # the strange get/set operators are clunky, true... + str = string.upper(self.contents.get()) + self.contents.set(str) + + def print_contents(self, event): + print "hi. contents of entry is now ---->", self.contents.get() + +root = App() +root.master.title("Foo") +root.mainloop() diff --git a/sys/src/cmd/python/Demo/tkinter/matt/killing-window-w-wm.py b/sys/src/cmd/python/Demo/tkinter/matt/killing-window-w-wm.py new file mode 100644 index 000000000..6a0e2fe62 --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/matt/killing-window-w-wm.py @@ -0,0 +1,42 @@ +from Tkinter import * + +# This file shows how to trap the killing of a window +# when the user uses window manager menus (typ. upper left hand corner +# menu in the decoration border). + + +### ******* this isn't really called -- read the comments +def my_delete_callback(): + print "whoops -- tried to delete me!" + +class Test(Frame): + def deathHandler(self, event): + print self, "is now getting nuked. performing some save here...." + + def createWidgets(self): + # a hello button + self.hi_there = Button(self, text='Hello') + self.hi_there.pack(side=LEFT) + + def __init__(self, master=None): + Frame.__init__(self, master) + Pack.config(self) + self.createWidgets() + + ### + ### PREVENT WM kills from happening + ### + + # the docs would have you do this: + +# self.master.protocol("WM_DELETE_WINDOW", my_delete_callback) + + # unfortunately, some window managers will not send this request to a window. + # the "protocol" function seems incapable of trapping these "aggressive" window kills. + # this line of code catches everything, tho. The window is deleted, but you have a chance + # of cleaning up first. + self.bind_all("<Destroy>", self.deathHandler) + + +test = Test() +test.mainloop() diff --git a/sys/src/cmd/python/Demo/tkinter/matt/menu-all-types-of-entries.py b/sys/src/cmd/python/Demo/tkinter/matt/menu-all-types-of-entries.py new file mode 100644 index 000000000..f4afe4a8b --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/matt/menu-all-types-of-entries.py @@ -0,0 +1,244 @@ +from Tkinter import * + +# some vocabulary to keep from getting confused. This terminology +# is something I cooked up for this file, but follows the man pages +# pretty closely +# +# +# +# This is a MENUBUTTON +# V +# +-------------+ +# | | +# +# +------------++------------++------------+ +# | || || | +# | File || Edit || Options | <-------- the MENUBAR +# | || || | +# +------------++------------++------------+ +# | New... | +# | Open... | +# | Print | +# | | <-------- This is a MENU. The lines of text in the menu are +# | | MENU ENTRIES +# | +---------------+ +# | Open Files > | file1 | +# | | file2 | +# | | another file | <------ this cascading part is also a MENU +# +----------------| | +# | | +# | | +# | | +# +---------------+ + + + +# some miscellaneous callbacks +def new_file(): + print "opening new file" + +def open_file(): + print "opening OLD file" + +def print_something(): + print "picked a menu item" + + + +anchovies = 0 + +def print_anchovies(): + global anchovies + anchovies = not anchovies + print "anchovies?", anchovies + +def makeCommandMenu(): + # make menu button + Command_button = Menubutton(mBar, text='Simple Button Commands', + underline=0) + Command_button.pack(side=LEFT, padx="2m") + + # make the pulldown part of the File menu. The parameter passed is the master. + # we attach it to the button as a python attribute called "menu" by convention. + # hopefully this isn't too confusing... + Command_button.menu = Menu(Command_button) + + # just to be cute, let's disable the undo option: + Command_button.menu.add_command(label="Undo") + # undo is the 0th entry... + Command_button.menu.entryconfig(0, state=DISABLED) + + Command_button.menu.add_command(label='New...', underline=0, + command=new_file) + Command_button.menu.add_command(label='Open...', underline=0, + command=open_file) + Command_button.menu.add_command(label='Different Font', underline=0, + font='-*-helvetica-*-r-*-*-*-180-*-*-*-*-*-*', + command=print_something) + + # we can make bitmaps be menu entries too. File format is X11 bitmap. + # if you use XV, save it under X11 bitmap format. duh-uh.,.. + Command_button.menu.add_command( + bitmap="info") + #bitmap='@/home/mjc4y/dilbert/project.status.is.doomed.last.panel.bm') + + # this is just a line + Command_button.menu.add('separator') + + # change the color + Command_button.menu.add_command(label='Quit', underline=0, + background='red', + activebackground='green', + command=Command_button.quit) + + # set up a pointer from the file menubutton back to the file menu + Command_button['menu'] = Command_button.menu + + return Command_button + + + +def makeCascadeMenu(): + # make menu button + Cascade_button = Menubutton(mBar, text='Cascading Menus', underline=0) + Cascade_button.pack(side=LEFT, padx="2m") + + # the primary pulldown + Cascade_button.menu = Menu(Cascade_button) + + # this is the menu that cascades from the primary pulldown.... + Cascade_button.menu.choices = Menu(Cascade_button.menu) + + # ...and this is a menu that cascades from that. + Cascade_button.menu.choices.wierdones = Menu(Cascade_button.menu.choices) + + # then you define the menus from the deepest level on up. + Cascade_button.menu.choices.wierdones.add_command(label='avacado') + Cascade_button.menu.choices.wierdones.add_command(label='belgian endive') + Cascade_button.menu.choices.wierdones.add_command(label='beefaroni') + + # definition of the menu one level up... + Cascade_button.menu.choices.add_command(label='Chocolate') + Cascade_button.menu.choices.add_command(label='Vanilla') + Cascade_button.menu.choices.add_command(label='TuttiFruiti') + Cascade_button.menu.choices.add_command(label='WopBopaLoopBapABopBamBoom') + Cascade_button.menu.choices.add_command(label='Rocky Road') + Cascade_button.menu.choices.add_command(label='BubbleGum') + Cascade_button.menu.choices.add_cascade( + label='Wierd Flavors', + menu=Cascade_button.menu.choices.wierdones) + + # and finally, the definition for the top level + Cascade_button.menu.add_cascade(label='more choices', + menu=Cascade_button.menu.choices) + + Cascade_button['menu'] = Cascade_button.menu + + return Cascade_button + +def makeCheckbuttonMenu(): + global fred + # make menu button + Checkbutton_button = Menubutton(mBar, text='Checkbutton Menus', + underline=0) + Checkbutton_button.pack(side=LEFT, padx='2m') + + # the primary pulldown + Checkbutton_button.menu = Menu(Checkbutton_button) + + # and all the check buttons. Note that the "variable" "onvalue" and "offvalue" options + # are not supported correctly at present. You have to do all your application + # work through the calback. + Checkbutton_button.menu.add_checkbutton(label='Pepperoni') + Checkbutton_button.menu.add_checkbutton(label='Sausage') + Checkbutton_button.menu.add_checkbutton(label='Extra Cheese') + + # so here's a callback + Checkbutton_button.menu.add_checkbutton(label='Anchovy', + command=print_anchovies) + + # and start with anchovies selected to be on. Do this by + # calling invoke on this menu option. To refer to the "anchovy" menu + # entry we need to know it's index. To do this, we use the index method + # which takes arguments of several forms: + # + # argument what it does + # ----------------------------------- + # a number -- this is useless. + # "last" -- last option in the menu + # "none" -- used with the activate command. see the man page on menus + # "active" -- the currently active menu option. A menu option is made active + # with the 'activate' method + # "@number" -- where 'number' is an integer and is treated like a y coordinate in pixels + # string pattern -- this is the option used below, and attempts to match "labels" using the + # rules of Tcl_StringMatch + Checkbutton_button.menu.invoke(Checkbutton_button.menu.index('Anchovy')) + + # set up a pointer from the file menubutton back to the file menu + Checkbutton_button['menu'] = Checkbutton_button.menu + + return Checkbutton_button + + +def makeRadiobuttonMenu(): + # make menu button + Radiobutton_button = Menubutton(mBar, text='Radiobutton Menus', + underline=0) + Radiobutton_button.pack(side=LEFT, padx='2m') + + # the primary pulldown + Radiobutton_button.menu = Menu(Radiobutton_button) + + # and all the Radio buttons. Note that the "variable" "onvalue" and "offvalue" options + # are not supported correctly at present. You have to do all your application + # work through the calback. + Radiobutton_button.menu.add_radiobutton(label='Republican') + Radiobutton_button.menu.add_radiobutton(label='Democrat') + Radiobutton_button.menu.add_radiobutton(label='Libertarian') + Radiobutton_button.menu.add_radiobutton(label='Commie') + Radiobutton_button.menu.add_radiobutton(label='Facist') + Radiobutton_button.menu.add_radiobutton(label='Labor Party') + Radiobutton_button.menu.add_radiobutton(label='Torie') + Radiobutton_button.menu.add_radiobutton(label='Independent') + Radiobutton_button.menu.add_radiobutton(label='Anarchist') + Radiobutton_button.menu.add_radiobutton(label='No Opinion') + + # set up a pointer from the file menubutton back to the file menu + Radiobutton_button['menu'] = Radiobutton_button.menu + + return Radiobutton_button + + +def makeDisabledMenu(): + Dummy_button = Menubutton(mBar, text='Dead Menu', underline=0) + Dummy_button.pack(side=LEFT, padx='2m') + + # this is the standard way of turning off a whole menu + Dummy_button["state"] = DISABLED + return Dummy_button + + +################################################# +#### Main starts here ... +root = Tk() + + +# make a menu bar +mBar = Frame(root, relief=RAISED, borderwidth=2) +mBar.pack(fill=X) + +Command_button = makeCommandMenu() +Cascade_button = makeCascadeMenu() +Checkbutton_button = makeCheckbuttonMenu() +Radiobutton_button = makeRadiobuttonMenu() +NoMenu = makeDisabledMenu() + +# finally, install the buttons in the menu bar. +# This allows for scanning from one menubutton to the next. +mBar.tk_menuBar(Command_button, Cascade_button, Checkbutton_button, Radiobutton_button, NoMenu) + + +root.title('menu demo') +root.iconname('menu demo') + +root.mainloop() diff --git a/sys/src/cmd/python/Demo/tkinter/matt/menu-simple.py b/sys/src/cmd/python/Demo/tkinter/matt/menu-simple.py new file mode 100644 index 000000000..46b53642e --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/matt/menu-simple.py @@ -0,0 +1,112 @@ +from Tkinter import * + +# some vocabulary to keep from getting confused. This terminology +# is something I cooked up for this file, but follows the man pages +# pretty closely +# +# +# +# This is a MENUBUTTON +# V +# +-------------+ +# | | +# +# +------------++------------++------------+ +# | || || | +# | File || Edit || Options | <-------- the MENUBAR +# | || || | +# +------------++------------++------------+ +# | New... | +# | Open... | +# | Print | +# | | <------ This is a MENU. The lines of text in the menu are +# | | MENU ENTRIES +# | +---------------+ +# | Open Files > | file1 | +# | | file2 | +# | | another file | <------ this cascading part is also a MENU +# +----------------| | +# | | +# | | +# | | +# +---------------+ + + + +def new_file(): + print "opening new file" + + +def open_file(): + print "opening OLD file" + + +def makeFileMenu(): + # make menu button : "File" + File_button = Menubutton(mBar, text='File', underline=0) + File_button.pack(side=LEFT, padx="1m") + File_button.menu = Menu(File_button) + + # add an item. The first param is a menu entry type, + # must be one of: "cascade", "checkbutton", "command", "radiobutton", "seperator" + # see menu-demo-2.py for examples of use + File_button.menu.add_command(label='New...', underline=0, + command=new_file) + + + File_button.menu.add_command(label='Open...', underline=0, + command=open_file) + + File_button.menu.add_command(label='Quit', underline=0, + command='exit') + + # set up a pointer from the file menubutton back to the file menu + File_button['menu'] = File_button.menu + + return File_button + + + +def makeEditMenu(): + Edit_button = Menubutton(mBar, text='Edit', underline=0) + Edit_button.pack(side=LEFT, padx="1m") + Edit_button.menu = Menu(Edit_button) + + # just to be cute, let's disable the undo option: + Edit_button.menu.add('command', label="Undo") + # Since the tear-off bar is the 0th entry, + # undo is the 1st entry... + Edit_button.menu.entryconfig(1, state=DISABLED) + + # and these are just for show. No "command" callbacks attached. + Edit_button.menu.add_command(label="Cut") + Edit_button.menu.add_command(label="Copy") + Edit_button.menu.add_command(label="Paste") + + # set up a pointer from the file menubutton back to the file menu + Edit_button['menu'] = Edit_button.menu + + return Edit_button + + +################################################# + +#### Main starts here ... +root = Tk() + + +# make a menu bar +mBar = Frame(root, relief=RAISED, borderwidth=2) +mBar.pack(fill=X) + +File_button = makeFileMenu() +Edit_button = makeEditMenu() + +# finally, install the buttons in the menu bar. +# This allows for scanning from one menubutton to the next. +mBar.tk_menuBar(File_button, Edit_button) + +root.title('menu demo') +root.iconname('packer') + +root.mainloop() diff --git a/sys/src/cmd/python/Demo/tkinter/matt/not-what-you-might-think-1.py b/sys/src/cmd/python/Demo/tkinter/matt/not-what-you-might-think-1.py new file mode 100644 index 000000000..7b20f02b3 --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/matt/not-what-you-might-think-1.py @@ -0,0 +1,28 @@ +from Tkinter import * + + +class Test(Frame): + def createWidgets(self): + + self.Gpanel = Frame(self, width='1i', height='1i', + background='green') + self.Gpanel.pack(side=LEFT) + + # a QUIT button + self.Gpanel.QUIT = Button(self.Gpanel, text='QUIT', + foreground='red', + command=self.quit) + self.Gpanel.QUIT.pack(side=LEFT) + + + def __init__(self, master=None): + Frame.__init__(self, master) + Pack.config(self) + self.createWidgets() + +test = Test() + +test.master.title('packer demo') +test.master.iconname('packer') + +test.mainloop() diff --git a/sys/src/cmd/python/Demo/tkinter/matt/not-what-you-might-think-2.py b/sys/src/cmd/python/Demo/tkinter/matt/not-what-you-might-think-2.py new file mode 100644 index 000000000..9ee197cf9 --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/matt/not-what-you-might-think-2.py @@ -0,0 +1,30 @@ +from Tkinter import * + + +class Test(Frame): + def createWidgets(self): + + self.Gpanel = Frame(self, width='1i', height='1i', + background='green') + + # this line turns off the recalculation of geometry by masters. + self.Gpanel.propagate(0) + + self.Gpanel.pack(side=LEFT) + + # a QUIT button + self.Gpanel.QUIT = Button(self.Gpanel, text='QUIT', foreground='red', + command=self.quit) + self.Gpanel.QUIT.pack(side=LEFT) + + def __init__(self, master=None): + Frame.__init__(self, master) + Pack.config(self) + self.createWidgets() + +test = Test() + +test.master.title('packer demo') +test.master.iconname('packer') + +test.mainloop() diff --git a/sys/src/cmd/python/Demo/tkinter/matt/packer-and-placer-together.py b/sys/src/cmd/python/Demo/tkinter/matt/packer-and-placer-together.py new file mode 100644 index 000000000..184d56bc1 --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/matt/packer-and-placer-together.py @@ -0,0 +1,41 @@ +from Tkinter import * + +# This is a program that tests the placer geom manager in conjunction with +# the packer. The background (green) is packed, while the widget inside is placed + + +def do_motion(event): + app.button.place(x=event.x, y=event.y) + +def dothis(): + print 'calling me!' + +def createWidgets(top): + # make a frame. Note that the widget is 200 x 200 + # and the window containing is 400x400. We do this + # simply to show that this is possible. The rest of the + # area is inaccesssible. + f = Frame(top, width=200, height=200, background='green') + + # note that we use a different manager here. + # This way, the top level frame widget resizes when the + # application window does. + f.pack(fill=BOTH, expand=1) + + # now make a button + f.button = Button(f, foreground='red', text='amazing', command=dothis) + + # and place it so that the nw corner is + # 1/2 way along the top X edge of its' parent + f.button.place(relx=0.5, rely=0.0, anchor=NW) + + # allow the user to move the button SUIT-style. + f.bind('<Control-Shift-Motion>', do_motion) + + return f + +root = Tk() +app = createWidgets(root) +root.geometry("400x400") +root.maxsize(1000, 1000) +root.mainloop() diff --git a/sys/src/cmd/python/Demo/tkinter/matt/packer-simple.py b/sys/src/cmd/python/Demo/tkinter/matt/packer-simple.py new file mode 100644 index 000000000..f55f1bee7 --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/matt/packer-simple.py @@ -0,0 +1,32 @@ +from Tkinter import * + + +class Test(Frame): + def printit(self): + print self.hi_there["command"] + + def createWidgets(self): + # a hello button + self.QUIT = Button(self, text='QUIT', foreground='red', + command=self.quit) + self.QUIT.pack(side=LEFT, fill=BOTH) + + self.hi_there = Button(self, text='Hello', + command=self.printit) + self.hi_there.pack(side=LEFT) + + # note how Packer defaults to side=TOP + + self.guy2 = Button(self, text='button 2') + self.guy2.pack() + + self.guy3 = Button(self, text='button 3') + self.guy3.pack() + + def __init__(self, master=None): + Frame.__init__(self, master) + Pack.config(self) + self.createWidgets() + +test = Test() +test.mainloop() diff --git a/sys/src/cmd/python/Demo/tkinter/matt/placer-simple.py b/sys/src/cmd/python/Demo/tkinter/matt/placer-simple.py new file mode 100644 index 000000000..30d9e9e90 --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/matt/placer-simple.py @@ -0,0 +1,39 @@ +from Tkinter import * + +# This is a program that tests the placer geom manager + +def do_motion(event): + app.button.place(x=event.x, y=event.y) + +def dothis(): + print 'calling me!' + +def createWidgets(top): + # make a frame. Note that the widget is 200 x 200 + # and the window containing is 400x400. We do this + # simply to show that this is possible. The rest of the + # area is inaccesssible. + f = Frame(top, width=200, height=200, background='green') + + # place it so the upper left hand corner of + # the frame is in the upper left corner of + # the parent + f.place(relx=0.0, rely=0.0) + + # now make a button + f.button = Button(f, foreground='red', text='amazing', command=dothis) + + # and place it so that the nw corner is + # 1/2 way along the top X edge of its' parent + f.button.place(relx=0.5, rely=0.0, anchor=NW) + + # allow the user to move the button SUIT-style. + f.bind('<Control-Shift-Motion>', do_motion) + + return f + +root = Tk() +app = createWidgets(root) +root.geometry("400x400") +root.maxsize(1000, 1000) +root.mainloop() diff --git a/sys/src/cmd/python/Demo/tkinter/matt/pong-demo-1.py b/sys/src/cmd/python/Demo/tkinter/matt/pong-demo-1.py new file mode 100644 index 000000000..7fcf800b6 --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/matt/pong-demo-1.py @@ -0,0 +1,54 @@ +from Tkinter import * + +import string + + +class Pong(Frame): + def createWidgets(self): + self.QUIT = Button(self, text='QUIT', foreground='red', + command=self.quit) + self.QUIT.pack(side=LEFT, fill=BOTH) + + ## The playing field + self.draw = Canvas(self, width="5i", height="5i") + + ## The speed control for the ball + self.speed = Scale(self, orient=HORIZONTAL, label="ball speed", + from_=-100, to=100) + + self.speed.pack(side=BOTTOM, fill=X) + + # The ball + self.ball = self.draw.create_oval("0i", "0i", "0.10i", "0.10i", + fill="red") + self.x = 0.05 + self.y = 0.05 + self.velocity_x = 0.3 + self.velocity_y = 0.5 + + self.draw.pack(side=LEFT) + + def moveBall(self, *args): + if (self.x > 5.0) or (self.x < 0.0): + self.velocity_x = -1.0 * self.velocity_x + if (self.y > 5.0) or (self.y < 0.0): + self.velocity_y = -1.0 * self.velocity_y + + deltax = (self.velocity_x * self.speed.get() / 100.0) + deltay = (self.velocity_y * self.speed.get() / 100.0) + self.x = self.x + deltax + self.y = self.y + deltay + + self.draw.move(self.ball, "%ri" % deltax, "%ri" % deltay) + self.after(10, self.moveBall) + + def __init__(self, master=None): + Frame.__init__(self, master) + Pack.config(self) + self.createWidgets() + self.after(10, self.moveBall) + + +game = Pong() + +game.mainloop() diff --git a/sys/src/cmd/python/Demo/tkinter/matt/printing-coords-of-items.py b/sys/src/cmd/python/Demo/tkinter/matt/printing-coords-of-items.py new file mode 100644 index 000000000..a37733d03 --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/matt/printing-coords-of-items.py @@ -0,0 +1,61 @@ +from Tkinter import * + +# this file demonstrates the creation of widgets as part of a canvas object + +class Test(Frame): + ################################################################### + ###### Event callbacks for THE CANVAS (not the stuff drawn on it) + ################################################################### + def mouseDown(self, event): + # see if we're inside a dot. If we are, it + # gets tagged as CURRENT for free by tk. + + if not event.widget.find_withtag(CURRENT): + # there is no dot here, so we can make one, + # and bind some interesting behavior to it. + # ------ + # create a dot, and mark it as current + fred = self.draw.create_oval( + event.x - 10, event.y -10, event.x +10, event.y + 10, + fill="green") + self.draw.tag_bind(fred, "<Enter>", self.mouseEnter) + self.draw.tag_bind(fred, "<Leave>", self.mouseLeave) + self.lastx = event.x + self.lasty = event.y + + def mouseMove(self, event): + self.draw.move(CURRENT, event.x - self.lastx, event.y - self.lasty) + self.lastx = event.x + self.lasty = event.y + + ################################################################### + ###### Event callbacks for canvas ITEMS (stuff drawn on the canvas) + ################################################################### + def mouseEnter(self, event): + # the "current" tag is applied to the object the cursor is over. + # this happens automatically. + self.draw.itemconfig(CURRENT, fill="red") + print self.draw.coords(CURRENT) + + def mouseLeave(self, event): + # the "current" tag is applied to the object the cursor is over. + # this happens automatically. + self.draw.itemconfig(CURRENT, fill="blue") + + def createWidgets(self): + self.QUIT = Button(self, text='QUIT', foreground='red', + command=self.quit) + self.QUIT.pack(side=LEFT, fill=BOTH) + self.draw = Canvas(self, width="5i", height="5i") + self.draw.pack(side=LEFT) + + Widget.bind(self.draw, "<1>", self.mouseDown) + Widget.bind(self.draw, "<B1-Motion>", self.mouseMove) + + def __init__(self, master=None): + Frame.__init__(self, master) + Pack.config(self) + self.createWidgets() + +test = Test() +test.mainloop() diff --git a/sys/src/cmd/python/Demo/tkinter/matt/radiobutton-simple.py b/sys/src/cmd/python/Demo/tkinter/matt/radiobutton-simple.py new file mode 100644 index 000000000..e9d6afee1 --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/matt/radiobutton-simple.py @@ -0,0 +1,62 @@ +from Tkinter import * + +# This is a demo program that shows how to +# create radio buttons and how to get other widgets to +# share the information in a radio button. +# +# There are other ways of doing this too, but +# the "variable" option of radiobuttons seems to be the easiest. +# +# note how each button has a value it sets the variable to as it gets hit. + + +class Test(Frame): + def printit(self): + print "hi" + + def createWidgets(self): + + self.flavor = StringVar() + self.flavor.set("chocolate") + + self.radioframe = Frame(self) + self.radioframe.pack() + + # 'text' is the label + # 'variable' is the name of the variable that all these radio buttons share + # 'value' is the value this variable takes on when the radio button is selected + # 'anchor' makes the text appear left justified (default is centered. ick) + self.radioframe.choc = Radiobutton( + self.radioframe, text="Chocolate Flavor", + variable=self.flavor, value="chocolate", + anchor=W) + self.radioframe.choc.pack(fill=X) + + self.radioframe.straw = Radiobutton( + self.radioframe, text="Strawberry Flavor", + variable=self.flavor, value="strawberry", + anchor=W) + self.radioframe.straw.pack(fill=X) + + self.radioframe.lemon = Radiobutton( + self.radioframe, text="Lemon Flavor", + variable=self.flavor, value="lemon", + anchor=W) + self.radioframe.lemon.pack(fill=X) + + # this is a text entry that lets you type in the name of a flavor too. + self.entry = Entry(self, textvariable=self.flavor) + self.entry.pack(fill=X) + self.QUIT = Button(self, text='QUIT', foreground='red', + command=self.quit) + self.QUIT.pack(side=BOTTOM, fill=BOTH) + + + def __init__(self, master=None): + Frame.__init__(self, master) + Pack.config(self) + self.createWidgets() + +test = Test() + +test.mainloop() diff --git a/sys/src/cmd/python/Demo/tkinter/matt/rubber-band-box-demo-1.py b/sys/src/cmd/python/Demo/tkinter/matt/rubber-band-box-demo-1.py new file mode 100644 index 000000000..b00518e00 --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/matt/rubber-band-box-demo-1.py @@ -0,0 +1,58 @@ +from Tkinter import * + +class Test(Frame): + def printit(self): + print "hi" + + def createWidgets(self): + self.QUIT = Button(self, text='QUIT', + background='red', + foreground='white', + height=3, + command=self.quit) + self.QUIT.pack(side=BOTTOM, fill=BOTH) + + self.canvasObject = Canvas(self, width="5i", height="5i") + self.canvasObject.pack(side=LEFT) + + def mouseDown(self, event): + # canvas x and y take the screen coords from the event and translate + # them into the coordinate system of the canvas object + self.startx = self.canvasObject.canvasx(event.x) + self.starty = self.canvasObject.canvasy(event.y) + + def mouseMotion(self, event): + # canvas x and y take the screen coords from the event and translate + # them into the coordinate system of the canvas object + x = self.canvasObject.canvasx(event.x) + y = self.canvasObject.canvasy(event.y) + + if (self.startx != event.x) and (self.starty != event.y) : + self.canvasObject.delete(self.rubberbandBox) + self.rubberbandBox = self.canvasObject.create_rectangle( + self.startx, self.starty, x, y) + # this flushes the output, making sure that + # the rectangle makes it to the screen + # before the next event is handled + self.update_idletasks() + + def mouseUp(self, event): + self.canvasObject.delete(self.rubberbandBox) + + def __init__(self, master=None): + Frame.__init__(self, master) + Pack.config(self) + self.createWidgets() + + # this is a "tagOrId" for the rectangle we draw on the canvas + self.rubberbandBox = None + + # and the bindings that make it work.. + Widget.bind(self.canvasObject, "<Button-1>", self.mouseDown) + Widget.bind(self.canvasObject, "<Button1-Motion>", self.mouseMotion) + Widget.bind(self.canvasObject, "<Button1-ButtonRelease>", self.mouseUp) + + +test = Test() + +test.mainloop() diff --git a/sys/src/cmd/python/Demo/tkinter/matt/rubber-line-demo-1.py b/sys/src/cmd/python/Demo/tkinter/matt/rubber-line-demo-1.py new file mode 100644 index 000000000..59b8bd992 --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/matt/rubber-line-demo-1.py @@ -0,0 +1,51 @@ +from Tkinter import * + +class Test(Frame): + def printit(self): + print "hi" + + def createWidgets(self): + self.QUIT = Button(self, text='QUIT', + background='red', + foreground='white', + height=3, + command=self.quit) + self.QUIT.pack(side=BOTTOM, fill=BOTH) + + self.canvasObject = Canvas(self, width="5i", height="5i") + self.canvasObject.pack(side=LEFT) + + def mouseDown(self, event): + # canvas x and y take the screen coords from the event and translate + # them into the coordinate system of the canvas object + self.startx = self.canvasObject.canvasx(event.x) + self.starty = self.canvasObject.canvasy(event.y) + + def mouseMotion(self, event): + # canvas x and y take the screen coords from the event and translate + # them into the coordinate system of the canvas object + x = self.canvasObject.canvasx(event.x) + y = self.canvasObject.canvasy(event.y) + + if (self.startx != event.x) and (self.starty != event.y) : + self.canvasObject.delete(self.rubberbandLine) + self.rubberbandLine = self.canvasObject.create_line( + self.startx, self.starty, x, y) + # this flushes the output, making sure that + # the rectangle makes it to the screen + # before the next event is handled + self.update_idletasks() + + def __init__(self, master=None): + Frame.__init__(self, master) + Pack.config(self) + self.createWidgets() + # this is a "tagOrId" for the rectangle we draw on the canvas + self.rubberbandLine = None + Widget.bind(self.canvasObject, "<Button-1>", self.mouseDown) + Widget.bind(self.canvasObject, "<Button1-Motion>", self.mouseMotion) + + +test = Test() + +test.mainloop() diff --git a/sys/src/cmd/python/Demo/tkinter/matt/slider-demo-1.py b/sys/src/cmd/python/Demo/tkinter/matt/slider-demo-1.py new file mode 100644 index 000000000..db6114b1a --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/matt/slider-demo-1.py @@ -0,0 +1,36 @@ +from Tkinter import * + +# shows how to make a slider, set and get its value under program control + + +class Test(Frame): + def print_value(self, val): + print "slider now at", val + + def reset(self): + self.slider.set(0) + + def createWidgets(self): + self.slider = Scale(self, from_=0, to=100, + orient=HORIZONTAL, + length="3i", + label="happy slider", + command=self.print_value) + + self.reset = Button(self, text='reset slider', + command=self.reset) + + self.QUIT = Button(self, text='QUIT', foreground='red', + command=self.quit) + + self.slider.pack(side=LEFT) + self.reset.pack(side=LEFT) + self.QUIT.pack(side=LEFT, fill=BOTH) + + def __init__(self, master=None): + Frame.__init__(self, master) + Pack.config(self) + self.createWidgets() + +test = Test() +test.mainloop() diff --git a/sys/src/cmd/python/Demo/tkinter/matt/subclass-existing-widgets.py b/sys/src/cmd/python/Demo/tkinter/matt/subclass-existing-widgets.py new file mode 100644 index 000000000..0e08f9206 --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/matt/subclass-existing-widgets.py @@ -0,0 +1,28 @@ +from Tkinter import * + +# This is a program that makes a simple two button application + + +class New_Button(Button): + def callback(self): + print self.counter + self.counter = self.counter + 1 + +def createWidgets(top): + f = Frame(top) + f.pack() + f.QUIT = Button(f, text='QUIT', foreground='red', command=top.quit) + + f.QUIT.pack(side=LEFT, fill=BOTH) + + # a hello button + f.hi_there = New_Button(f, text='Hello') + # we do this on a different line because we need to reference f.hi_there + f.hi_there.config(command=f.hi_there.callback) + f.hi_there.pack(side=LEFT) + f.hi_there.counter = 43 + + +root = Tk() +createWidgets(root) +root.mainloop() diff --git a/sys/src/cmd/python/Demo/tkinter/matt/two-radio-groups.py b/sys/src/cmd/python/Demo/tkinter/matt/two-radio-groups.py new file mode 100644 index 000000000..9fd8f4f07 --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/matt/two-radio-groups.py @@ -0,0 +1,110 @@ +from Tkinter import * + +# The way to think about this is that each radio button menu +# controls a different variable -- clicking on one of the +# mutually exclusive choices in a radiobutton assigns some value +# to an application variable you provide. When you define a +# radiobutton menu choice, you have the option of specifying the +# name of a varaible and value to assign to that variable when +# that choice is selected. This clever mechanism relieves you, +# the programmer, from having to write a dumb callback that +# probably wouldn't have done anything more than an assignment +# anyway. The Tkinter options for this follow their Tk +# counterparts: +# {"variable" : my_flavor_variable, "value" : "strawberry"} +# where my_flavor_variable is an instance of one of the +# subclasses of Variable, provided in Tkinter.py (there is +# StringVar(), IntVar(), DoubleVar() and BooleanVar() to choose +# from) + + + +def makePoliticalParties(var): + # make menu button + Radiobutton_button = Menubutton(mBar, text='Political Party', + underline=0) + Radiobutton_button.pack(side=LEFT, padx='2m') + + # the primary pulldown + Radiobutton_button.menu = Menu(Radiobutton_button) + + Radiobutton_button.menu.add_radiobutton(label='Republican', + variable=var, value=1) + + Radiobutton_button.menu.add('radiobutton', {'label': 'Democrat', + 'variable' : var, + 'value' : 2}) + + Radiobutton_button.menu.add('radiobutton', {'label': 'Libertarian', + 'variable' : var, + 'value' : 3}) + + var.set(2) + + # set up a pointer from the file menubutton back to the file menu + Radiobutton_button['menu'] = Radiobutton_button.menu + + return Radiobutton_button + + +def makeFlavors(var): + # make menu button + Radiobutton_button = Menubutton(mBar, text='Flavors', + underline=0) + Radiobutton_button.pack(side=LEFT, padx='2m') + + # the primary pulldown + Radiobutton_button.menu = Menu(Radiobutton_button) + + Radiobutton_button.menu.add_radiobutton(label='Strawberry', + variable=var, value='Strawberry') + + Radiobutton_button.menu.add_radiobutton(label='Chocolate', + variable=var, value='Chocolate') + + Radiobutton_button.menu.add_radiobutton(label='Rocky Road', + variable=var, value='Rocky Road') + + # choose a default + var.set("Chocolate") + + # set up a pointer from the file menubutton back to the file menu + Radiobutton_button['menu'] = Radiobutton_button.menu + + return Radiobutton_button + + +def printStuff(): + print "party is", party.get() + print "flavor is", flavor.get() + print + +################################################# +#### Main starts here ... +root = Tk() + + +# make a menu bar +mBar = Frame(root, relief=RAISED, borderwidth=2) +mBar.pack(fill=X) + +# make two application variables, +# one to control each radio button set +party = IntVar() +flavor = StringVar() + +Radiobutton_button = makePoliticalParties(party) +Radiobutton_button2 = makeFlavors(flavor) + +# finally, install the buttons in the menu bar. +# This allows for scanning from one menubutton to the next. +mBar.tk_menuBar(Radiobutton_button, Radiobutton_button2) + +b = Button(root, text="print party and flavor", foreground="red", + command=printStuff) +b.pack(side=TOP) + +root.title('menu demo') +root.iconname('menu demo') + +root.mainloop() diff --git a/sys/src/cmd/python/Demo/tkinter/matt/window-creation-more.py b/sys/src/cmd/python/Demo/tkinter/matt/window-creation-more.py new file mode 100644 index 000000000..eb0eb6fc1 --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/matt/window-creation-more.py @@ -0,0 +1,35 @@ +from Tkinter import * + +# this shows how to create a new window with a button in it +# that can create new windows + +class Test(Frame): + def printit(self): + print "hi" + + def makeWindow(self): + fred = Toplevel() + fred.label = Button(fred, + text="This is window number %d." % self.windownum, + command=self.makeWindow) + fred.label.pack() + self.windownum = self.windownum + 1 + + def createWidgets(self): + self.QUIT = Button(self, text='QUIT', foreground='red', + command=self.quit) + self.QUIT.pack(side=LEFT, fill=BOTH) + + # a hello button + self.hi_there = Button(self, text='Make a New Window', + command=self.makeWindow) + self.hi_there.pack(side=LEFT) + + def __init__(self, master=None): + Frame.__init__(self, master) + Pack.config(self) + self.windownum = 0 + self.createWidgets() + +test = Test() +test.mainloop() diff --git a/sys/src/cmd/python/Demo/tkinter/matt/window-creation-simple.py b/sys/src/cmd/python/Demo/tkinter/matt/window-creation-simple.py new file mode 100644 index 000000000..c990ed9bf --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/matt/window-creation-simple.py @@ -0,0 +1,31 @@ +from Tkinter import * + +# this shows how to spawn off new windows at a button press + +class Test(Frame): + def printit(self): + print "hi" + + def makeWindow(self): + fred = Toplevel() + fred.label = Label(fred, text="Here's a new window") + fred.label.pack() + + def createWidgets(self): + self.QUIT = Button(self, text='QUIT', foreground='red', + command=self.quit) + + self.QUIT.pack(side=LEFT, fill=BOTH) + + # a hello button + self.hi_there = Button(self, text='Make a New Window', + command=self.makeWindow) + self.hi_there.pack(side=LEFT) + + def __init__(self, master=None): + Frame.__init__(self, master) + Pack.config(self) + self.createWidgets() + +test = Test() +test.mainloop() diff --git a/sys/src/cmd/python/Demo/tkinter/matt/window-creation-w-location.py b/sys/src/cmd/python/Demo/tkinter/matt/window-creation-w-location.py new file mode 100644 index 000000000..3f2b5b069 --- /dev/null +++ b/sys/src/cmd/python/Demo/tkinter/matt/window-creation-w-location.py @@ -0,0 +1,45 @@ +from Tkinter import * + +import sys +##sys.path.append("/users/mjc4y/projects/python/tkinter/utils") +##from TkinterUtils import * + +# this shows how to create a new window with a button in it that +# can create new windows + +class QuitButton(Button): + def __init__(self, master, *args, **kwargs): + if not kwargs.has_key("text"): + kwargs["text"] = "QUIT" + if not kwargs.has_key("command"): + kwargs["command"] = master.quit + apply(Button.__init__, (self, master) + args, kwargs) + +class Test(Frame): + def makeWindow(self, *args): + fred = Toplevel() + + fred.label = Canvas (fred, width="2i", height="2i") + + fred.label.create_line("0", "0", "2i", "2i") + fred.label.create_line("0", "2i", "2i", "0") + fred.label.pack() + + ##centerWindow(fred, self.master) + + def createWidgets(self): + self.QUIT = QuitButton(self) + self.QUIT.pack(side=LEFT, fill=BOTH) + + self.makeWindow = Button(self, text='Make a New Window', + width=50, height=20, + command=self.makeWindow) + self.makeWindow.pack(side=LEFT) + + def __init__(self, master=None): + Frame.__init__(self, master) + Pack.config(self) + self.createWidgets() + +test = Test() +test.mainloop() diff --git a/sys/src/cmd/python/Demo/xml/elem_count.py b/sys/src/cmd/python/Demo/xml/elem_count.py new file mode 100644 index 000000000..7b53189c7 --- /dev/null +++ b/sys/src/cmd/python/Demo/xml/elem_count.py @@ -0,0 +1,36 @@ +import sys + +from xml.sax import make_parser, handler + +class FancyCounter(handler.ContentHandler): + + def __init__(self): + self._elems = 0 + self._attrs = 0 + self._elem_types = {} + self._attr_types = {} + + def startElement(self, name, attrs): + self._elems = self._elems + 1 + self._attrs = self._attrs + len(attrs) + self._elem_types[name] = self._elem_types.get(name, 0) + 1 + + for name in attrs.keys(): + self._attr_types[name] = self._attr_types.get(name, 0) + 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 + + +parser = make_parser() +parser.setContentHandler(FancyCounter()) +parser.parse(sys.argv[1]) diff --git a/sys/src/cmd/python/Demo/xml/roundtrip.py b/sys/src/cmd/python/Demo/xml/roundtrip.py new file mode 100644 index 000000000..8d7d4374c --- /dev/null +++ b/sys/src/cmd/python/Demo/xml/roundtrip.py @@ -0,0 +1,45 @@ +""" +A simple demo that reads in an XML document and spits out an equivalent, +but not necessarily identical, document. +""" + +import sys, string + +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 + +parser = make_parser() +parser.setContentHandler(ContentGenerator()) +parser.parse(sys.argv[1]) diff --git a/sys/src/cmd/python/Demo/xml/rss2html.py b/sys/src/cmd/python/Demo/xml/rss2html.py new file mode 100644 index 000000000..15c989195 --- /dev/null +++ b/sys/src/cmd/python/Demo/xml/rss2html.py @@ -0,0 +1,91 @@ +import sys + +from xml.sax import make_parser, handler + +# --- Templates + +top = \ +""" +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> +<HTML> +<HEAD> + <TITLE>%s</TITLE> +</HEAD> + +<BODY> +<H1>%s</H1> +""" + +bottom = \ +""" +</ul> + +<HR> +<ADDRESS> +Converted to HTML by sax_rss2html.py. +</ADDRESS> + +</BODY> +</HTML> +""" + +# --- The ContentHandler + +class RSSHandler(handler.ContentHandler): + + def __init__(self, out = sys.stdout): + handler.ContentHandler.__init__(self) + self._out = out + + self._text = "" + self._parent = None + self._list_started = 0 + self._title = None + self._link = None + self._descr = "" + + # ContentHandler methods + + def startElement(self, name, attrs): + if name == "channel" or name == "image" or name == "item": + self._parent = name + + self._text = "" + + def endElement(self, name): + if self._parent == "channel": + if name == "title": + self._out.write(top % (self._text, self._text)) + elif name == "description": + self._out.write("<p>%s</p>\n" % self._text) + + elif self._parent == "item": + if name == "title": + self._title = self._text + elif name == "link": + self._link = self._text + elif name == "description": + self._descr = self._text + elif name == "item": + if not self._list_started: + self._out.write("<ul>\n") + self._list_started = 1 + + self._out.write(' <li><a href="%s">%s</a> %s\n' % + (self._link, self._title, self._descr)) + + self._title = None + self._link = None + self._descr = "" + + if name == "rss": + self._out.write(bottom) + + def characters(self, content): + self._text = self._text + content + +# --- Main program + +parser = make_parser() +parser.setContentHandler(RSSHandler()) +parser.parse(sys.argv[1]) diff --git a/sys/src/cmd/python/Demo/zlib/minigzip.py b/sys/src/cmd/python/Demo/zlib/minigzip.py new file mode 100755 index 000000000..87fed4ae5 --- /dev/null +++ b/sys/src/cmd/python/Demo/zlib/minigzip.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python +# Demo program for zlib; it compresses or decompresses files, but *doesn't* +# delete the original. This doesn't support all of gzip's options. +# +# The 'gzip' module in the standard library provides a more complete +# implementation of gzip-format files. + +import zlib, sys, os + +FTEXT, FHCRC, FEXTRA, FNAME, FCOMMENT = 1, 2, 4, 8, 16 + +def write32(output, value): + output.write(chr(value & 255)) ; value=value // 256 + output.write(chr(value & 255)) ; value=value // 256 + output.write(chr(value & 255)) ; value=value // 256 + output.write(chr(value & 255)) + +def read32(input): + v = ord(input.read(1)) + v += (ord(input.read(1)) << 8 ) + v += (ord(input.read(1)) << 16) + v += (ord(input.read(1)) << 24) + return v + +def compress (filename, input, output): + output.write('\037\213\010') # Write the header, ... + output.write(chr(FNAME)) # ... flag byte ... + + statval = os.stat(filename) # ... modification time ... + mtime = statval[8] + write32(output, mtime) + output.write('\002') # ... slowest compression alg. ... + output.write('\377') # ... OS (=unknown) ... + output.write(filename+'\000') # ... original filename ... + + crcval = zlib.crc32("") + compobj = zlib.compressobj(9, zlib.DEFLATED, -zlib.MAX_WBITS, + zlib.DEF_MEM_LEVEL, 0) + while True: + data = input.read(1024) + if data == "": + break + crcval = zlib.crc32(data, crcval) + output.write(compobj.compress(data)) + output.write(compobj.flush()) + write32(output, crcval) # ... the CRC ... + write32(output, statval[6]) # and the file size. + +def decompress (input, output): + magic = input.read(2) + if magic != '\037\213': + print 'Not a gzipped file' + sys.exit(0) + if ord(input.read(1)) != 8: + print 'Unknown compression method' + sys.exit(0) + flag = ord(input.read(1)) + input.read(4+1+1) # Discard modification time, + # extra flags, and OS byte. + if flag & FEXTRA: + # Read & discard the extra field, if present + xlen = ord(input.read(1)) + xlen += 256*ord(input.read(1)) + input.read(xlen) + if flag & FNAME: + # Read and discard a null-terminated string containing the filename + while True: + s = input.read(1) + if s == '\0': break + if flag & FCOMMENT: + # Read and discard a null-terminated string containing a comment + while True: + s=input.read(1) + if s=='\0': break + if flag & FHCRC: + input.read(2) # Read & discard the 16-bit header CRC + + decompobj = zlib.decompressobj(-zlib.MAX_WBITS) + crcval = zlib.crc32("") + length = 0 + while True: + data=input.read(1024) + if data == "": + break + decompdata = decompobj.decompress(data) + output.write(decompdata) + length += len(decompdata) + crcval = zlib.crc32(decompdata, crcval) + + decompdata = decompobj.flush() + output.write(decompdata) + length += len(decompdata) + crcval = zlib.crc32(decompdata, crcval) + + # We've read to the end of the file, so we have to rewind in order + # to reread the 8 bytes containing the CRC and the file size. The + # decompressor is smart and knows when to stop, so feeding it + # extra data is harmless. + input.seek(-8, 2) + crc32 = read32(input) + isize = read32(input) + if crc32 != crcval: + print 'CRC check failed.' + if isize != length: + print 'Incorrect length of data produced' + +def main(): + if len(sys.argv)!=2: + print 'Usage: minigzip.py <filename>' + print ' The file will be compressed or decompressed.' + sys.exit(0) + + filename = sys.argv[1] + if filename.endswith('.gz'): + compressing = False + outputname = filename[:-3] + else: + compressing = True + outputname = filename + '.gz' + + input = open(filename, 'rb') + output = open(outputname, 'wb') + + if compressing: + compress(filename, input, output) + else: + decompress(input, output) + + input.close() + output.close() + +if __name__ == '__main__': + main() diff --git a/sys/src/cmd/python/Demo/zlib/zlibdemo.py b/sys/src/cmd/python/Demo/zlib/zlibdemo.py new file mode 100644 index 000000000..b449c199b --- /dev/null +++ b/sys/src/cmd/python/Demo/zlib/zlibdemo.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python + +# Takes an optional filename, defaulting to this file itself. +# Reads the file and compresses the content using level 1 and level 9 +# compression, printing a summary of the results. + +import zlib, sys + +def main(): + if len(sys.argv) > 1: + filename = sys.argv[1] + else: + filename = sys.argv[0] + print 'Reading', filename + + f = open(filename, 'rb') # Get the data to compress + s = f.read() + f.close() + + # First, we'll compress the string in one step + comptext = zlib.compress(s, 1) + decomp = zlib.decompress(comptext) + + print '1-step compression: (level 1)' + print ' Original:', len(s), 'Compressed:', len(comptext), + print 'Uncompressed:', len(decomp) + + # Now, let's compress the string in stages; set chunk to work in smaller steps + + chunk = 256 + compressor = zlib.compressobj(9) + decompressor = zlib.decompressobj() + comptext = decomp = '' + for i in range(0, len(s), chunk): + comptext = comptext+compressor.compress(s[i:i+chunk]) + # Don't forget to call flush()!! + comptext = comptext + compressor.flush() + + for i in range(0, len(comptext), chunk): + decomp = decomp + decompressor.decompress(comptext[i:i+chunk]) + decomp=decomp+decompressor.flush() + + print 'Progressive compression (level 9):' + print ' Original:', len(s), 'Compressed:', len(comptext), + print 'Uncompressed:', len(decomp) + +if __name__ == '__main__': + main() diff --git a/sys/src/cmd/python/Demo/zlib/zlibdemo.py.gz b/sys/src/cmd/python/Demo/zlib/zlibdemo.py.gz Binary files differnew file mode 100644 index 000000000..bf50355e3 --- /dev/null +++ b/sys/src/cmd/python/Demo/zlib/zlibdemo.py.gz |