summaryrefslogtreecommitdiff
path: root/sys/src/cmd/python/Demo
diff options
context:
space:
mode:
authorcinap_lenrek <cinap_lenrek@localhost>2011-05-03 11:25:13 +0000
committercinap_lenrek <cinap_lenrek@localhost>2011-05-03 11:25:13 +0000
commit458120dd40db6b4df55a4e96b650e16798ef06a0 (patch)
tree8f82685be24fef97e715c6f5ca4c68d34d5074ee /sys/src/cmd/python/Demo
parent3a742c699f6806c1145aea5149bf15de15a0afd7 (diff)
add hg and python
Diffstat (limited to 'sys/src/cmd/python/Demo')
-rw-r--r--sys/src/cmd/python/Demo/README61
-rw-r--r--sys/src/cmd/python/Demo/cgi/README11
-rwxr-xr-xsys/src/cmd/python/Demo/cgi/cgi0.sh8
-rwxr-xr-xsys/src/cmd/python/Demo/cgi/cgi1.py14
-rwxr-xr-xsys/src/cmd/python/Demo/cgi/cgi2.py22
-rwxr-xr-xsys/src/cmd/python/Demo/cgi/cgi3.py10
-rw-r--r--sys/src/cmd/python/Demo/cgi/wiki.py123
-rwxr-xr-xsys/src/cmd/python/Demo/classes/Complex.py320
-rwxr-xr-xsys/src/cmd/python/Demo/classes/Dates.py222
-rwxr-xr-xsys/src/cmd/python/Demo/classes/Dbm.py66
-rw-r--r--sys/src/cmd/python/Demo/classes/README13
-rwxr-xr-xsys/src/cmd/python/Demo/classes/Range.py93
-rwxr-xr-xsys/src/cmd/python/Demo/classes/Rat.py310
-rwxr-xr-xsys/src/cmd/python/Demo/classes/Rev.py95
-rwxr-xr-xsys/src/cmd/python/Demo/classes/Vec.py54
-rwxr-xr-xsys/src/cmd/python/Demo/classes/bitvec.py332
-rw-r--r--sys/src/cmd/python/Demo/comparisons/README60
-rwxr-xr-xsys/src/cmd/python/Demo/comparisons/patterns4
-rwxr-xr-xsys/src/cmd/python/Demo/comparisons/regextest.py47
-rwxr-xr-xsys/src/cmd/python/Demo/comparisons/sortingtest.py51
-rwxr-xr-xsys/src/cmd/python/Demo/comparisons/systemtest.py74
-rw-r--r--sys/src/cmd/python/Demo/curses/README25
-rwxr-xr-xsys/src/cmd/python/Demo/curses/life.py216
-rw-r--r--sys/src/cmd/python/Demo/curses/ncurses.py273
-rwxr-xr-xsys/src/cmd/python/Demo/curses/rain.py94
-rwxr-xr-xsys/src/cmd/python/Demo/curses/repeat.py58
-rw-r--r--sys/src/cmd/python/Demo/curses/tclock.py147
-rw-r--r--sys/src/cmd/python/Demo/curses/xmas.py906
-rw-r--r--sys/src/cmd/python/Demo/embed/Makefile57
-rw-r--r--sys/src/cmd/python/Demo/embed/README19
-rw-r--r--sys/src/cmd/python/Demo/embed/demo.c65
-rw-r--r--sys/src/cmd/python/Demo/embed/importexc.c17
-rw-r--r--sys/src/cmd/python/Demo/embed/loop.c33
-rw-r--r--sys/src/cmd/python/Demo/imputil/importers.py248
-rw-r--r--sys/src/cmd/python/Demo/imputil/knee.py126
-rw-r--r--sys/src/cmd/python/Demo/md5test/README10
-rwxr-xr-xsys/src/cmd/python/Demo/md5test/foo1
-rwxr-xr-xsys/src/cmd/python/Demo/md5test/md5driver.py123
-rw-r--r--sys/src/cmd/python/Demo/metaclasses/Eiffel.py113
-rw-r--r--sys/src/cmd/python/Demo/metaclasses/Enum.py169
-rw-r--r--sys/src/cmd/python/Demo/metaclasses/Meta.py118
-rw-r--r--sys/src/cmd/python/Demo/metaclasses/Simple.py45
-rw-r--r--sys/src/cmd/python/Demo/metaclasses/Synch.py256
-rw-r--r--sys/src/cmd/python/Demo/metaclasses/Trace.py144
-rw-r--r--sys/src/cmd/python/Demo/metaclasses/index.html605
-rw-r--r--sys/src/cmd/python/Demo/metaclasses/meta-vladimir.txt256
-rw-r--r--sys/src/cmd/python/Demo/newmetaclasses/Eiffel.py141
-rw-r--r--sys/src/cmd/python/Demo/newmetaclasses/Enum.py177
-rw-r--r--sys/src/cmd/python/Demo/parser/FILES6
-rw-r--r--sys/src/cmd/python/Demo/parser/README31
-rw-r--r--sys/src/cmd/python/Demo/parser/docstring.py2
-rw-r--r--sys/src/cmd/python/Demo/parser/example.py190
-rw-r--r--sys/src/cmd/python/Demo/parser/simple.py1
-rw-r--r--sys/src/cmd/python/Demo/parser/source.py27
-rwxr-xr-xsys/src/cmd/python/Demo/parser/test_parser.py48
-rw-r--r--sys/src/cmd/python/Demo/parser/texipre.dat100
-rw-r--r--sys/src/cmd/python/Demo/parser/unparse.py519
-rwxr-xr-xsys/src/cmd/python/Demo/pdist/FSProxy.py322
-rwxr-xr-xsys/src/cmd/python/Demo/pdist/RCSProxy.py198
-rw-r--r--sys/src/cmd/python/Demo/pdist/README121
-rwxr-xr-xsys/src/cmd/python/Demo/pdist/client.py157
-rwxr-xr-xsys/src/cmd/python/Demo/pdist/cmdfw.py144
-rwxr-xr-xsys/src/cmd/python/Demo/pdist/cmptree.py208
-rwxr-xr-xsys/src/cmd/python/Demo/pdist/cvslib.py364
-rwxr-xr-xsys/src/cmd/python/Demo/pdist/cvslock.py280
-rwxr-xr-xsys/src/cmd/python/Demo/pdist/mac.py19
-rwxr-xr-xsys/src/cmd/python/Demo/pdist/makechangelog.py109
-rwxr-xr-xsys/src/cmd/python/Demo/pdist/rcsbump33
-rwxr-xr-xsys/src/cmd/python/Demo/pdist/rcsclient.py71
-rwxr-xr-xsys/src/cmd/python/Demo/pdist/rcslib.py334
-rwxr-xr-xsys/src/cmd/python/Demo/pdist/rcvs8
-rwxr-xr-xsys/src/cmd/python/Demo/pdist/rcvs.py477
-rwxr-xr-xsys/src/cmd/python/Demo/pdist/rrcs8
-rwxr-xr-xsys/src/cmd/python/Demo/pdist/rrcs.py160
-rwxr-xr-xsys/src/cmd/python/Demo/pdist/security.py33
-rwxr-xr-xsys/src/cmd/python/Demo/pdist/server.py145
-rwxr-xr-xsys/src/cmd/python/Demo/pdist/sumtree.py24
-rw-r--r--sys/src/cmd/python/Demo/pysvr/Makefile57
-rw-r--r--sys/src/cmd/python/Demo/pysvr/README9
-rw-r--r--sys/src/cmd/python/Demo/pysvr/pysvr.c370
-rwxr-xr-xsys/src/cmd/python/Demo/pysvr/pysvr.py124
-rw-r--r--sys/src/cmd/python/Demo/rpc/MANIFEST10
-rw-r--r--sys/src/cmd/python/Demo/rpc/README31
-rw-r--r--sys/src/cmd/python/Demo/rpc/T.py22
-rw-r--r--sys/src/cmd/python/Demo/rpc/mountclient.py202
-rw-r--r--sys/src/cmd/python/Demo/rpc/nfsclient.py201
-rw-r--r--sys/src/cmd/python/Demo/rpc/rnusersclient.py98
-rw-r--r--sys/src/cmd/python/Demo/rpc/rpc.py893
-rwxr-xr-xsys/src/cmd/python/Demo/rpc/test24
-rw-r--r--sys/src/cmd/python/Demo/rpc/xdr.py200
-rw-r--r--sys/src/cmd/python/Demo/scripts/README23
-rw-r--r--sys/src/cmd/python/Demo/scripts/beer.py14
-rwxr-xr-xsys/src/cmd/python/Demo/scripts/eqfix.py198
-rwxr-xr-xsys/src/cmd/python/Demo/scripts/fact.py49
-rw-r--r--sys/src/cmd/python/Demo/scripts/find-uname.py40
-rwxr-xr-xsys/src/cmd/python/Demo/scripts/from.py35
-rwxr-xr-xsys/src/cmd/python/Demo/scripts/ftpstats.py145
-rwxr-xr-xsys/src/cmd/python/Demo/scripts/lpwatch.py110
-rwxr-xr-xsys/src/cmd/python/Demo/scripts/makedir.py21
-rwxr-xr-xsys/src/cmd/python/Demo/scripts/markov.py117
-rwxr-xr-xsys/src/cmd/python/Demo/scripts/mboxconvert.py124
-rwxr-xr-xsys/src/cmd/python/Demo/scripts/mkrcs.py61
-rwxr-xr-xsys/src/cmd/python/Demo/scripts/morse.py149
-rwxr-xr-xsys/src/cmd/python/Demo/scripts/newslist.doc59
-rwxr-xr-xsys/src/cmd/python/Demo/scripts/newslist.py366
-rwxr-xr-xsys/src/cmd/python/Demo/scripts/pi.py34
-rwxr-xr-xsys/src/cmd/python/Demo/scripts/pp.py130
-rwxr-xr-xsys/src/cmd/python/Demo/scripts/primes.py27
-rwxr-xr-xsys/src/cmd/python/Demo/scripts/queens.py85
-rwxr-xr-xsys/src/cmd/python/Demo/scripts/script.py33
-rwxr-xr-xsys/src/cmd/python/Demo/scripts/unbirthday.py107
-rwxr-xr-xsys/src/cmd/python/Demo/scripts/update.py92
-rwxr-xr-xsys/src/cmd/python/Demo/scripts/wh.py2
-rw-r--r--sys/src/cmd/python/Demo/sockets/README21
-rwxr-xr-xsys/src/cmd/python/Demo/sockets/broadcast.py15
-rwxr-xr-xsys/src/cmd/python/Demo/sockets/echosvr.py31
-rwxr-xr-xsys/src/cmd/python/Demo/sockets/finger.py58
-rwxr-xr-xsys/src/cmd/python/Demo/sockets/ftp.py146
-rwxr-xr-xsys/src/cmd/python/Demo/sockets/gopher.py347
-rwxr-xr-xsys/src/cmd/python/Demo/sockets/mcast.py93
-rwxr-xr-xsys/src/cmd/python/Demo/sockets/radio.py14
-rwxr-xr-xsys/src/cmd/python/Demo/sockets/rpython.py35
-rwxr-xr-xsys/src/cmd/python/Demo/sockets/rpythond.py52
-rwxr-xr-xsys/src/cmd/python/Demo/sockets/telnet.py109
-rwxr-xr-xsys/src/cmd/python/Demo/sockets/throughput.py93
-rwxr-xr-xsys/src/cmd/python/Demo/sockets/udpecho.py63
-rw-r--r--sys/src/cmd/python/Demo/sockets/unicast.py14
-rw-r--r--sys/src/cmd/python/Demo/sockets/unixclient.py12
-rw-r--r--sys/src/cmd/python/Demo/sockets/unixserver.py24
-rw-r--r--sys/src/cmd/python/Demo/threads/Coroutine.py159
-rw-r--r--sys/src/cmd/python/Demo/threads/Generator.py84
-rw-r--r--sys/src/cmd/python/Demo/threads/README13
-rw-r--r--sys/src/cmd/python/Demo/threads/fcmp.py64
-rw-r--r--sys/src/cmd/python/Demo/threads/find.py155
-rw-r--r--sys/src/cmd/python/Demo/threads/squasher.py105
-rw-r--r--sys/src/cmd/python/Demo/threads/sync.py603
-rw-r--r--sys/src/cmd/python/Demo/threads/telnet.py114
-rw-r--r--sys/src/cmd/python/Demo/tix/INSTALL.txt89
-rw-r--r--sys/src/cmd/python/Demo/tix/README.txt19
-rwxr-xr-xsys/src/cmd/python/Demo/tix/bitmaps/about.xpm50
-rwxr-xr-xsys/src/cmd/python/Demo/tix/bitmaps/bold.xbm6
-rwxr-xr-xsys/src/cmd/python/Demo/tix/bitmaps/capital.xbm6
-rwxr-xr-xsys/src/cmd/python/Demo/tix/bitmaps/centerj.xbm6
-rwxr-xr-xsys/src/cmd/python/Demo/tix/bitmaps/combobox.xbm14
-rwxr-xr-xsys/src/cmd/python/Demo/tix/bitmaps/combobox.xpm49
-rwxr-xr-xsys/src/cmd/python/Demo/tix/bitmaps/drivea.xbm14
-rwxr-xr-xsys/src/cmd/python/Demo/tix/bitmaps/drivea.xpm43
-rwxr-xr-xsys/src/cmd/python/Demo/tix/bitmaps/exit.xpm48
-rwxr-xr-xsys/src/cmd/python/Demo/tix/bitmaps/filebox.xbm14
-rwxr-xr-xsys/src/cmd/python/Demo/tix/bitmaps/filebox.xpm49
-rwxr-xr-xsys/src/cmd/python/Demo/tix/bitmaps/italic.xbm6
-rwxr-xr-xsys/src/cmd/python/Demo/tix/bitmaps/justify.xbm6
-rwxr-xr-xsys/src/cmd/python/Demo/tix/bitmaps/leftj.xbm6
-rwxr-xr-xsys/src/cmd/python/Demo/tix/bitmaps/netw.xbm14
-rwxr-xr-xsys/src/cmd/python/Demo/tix/bitmaps/netw.xpm45
-rwxr-xr-xsys/src/cmd/python/Demo/tix/bitmaps/optmenu.xpm48
-rwxr-xr-xsys/src/cmd/python/Demo/tix/bitmaps/rightj.xbm6
-rwxr-xr-xsys/src/cmd/python/Demo/tix/bitmaps/select.xpm52
-rwxr-xr-xsys/src/cmd/python/Demo/tix/bitmaps/tix.gifbin0 -> 11042 bytes
-rwxr-xr-xsys/src/cmd/python/Demo/tix/bitmaps/underline.xbm6
-rw-r--r--sys/src/cmd/python/Demo/tix/grid.py28
-rwxr-xr-xsys/src/cmd/python/Demo/tix/samples/Balloon.py68
-rwxr-xr-xsys/src/cmd/python/Demo/tix/samples/BtnBox.py44
-rwxr-xr-xsys/src/cmd/python/Demo/tix/samples/CmpImg.py196
-rwxr-xr-xsys/src/cmd/python/Demo/tix/samples/ComboBox.py102
-rwxr-xr-xsys/src/cmd/python/Demo/tix/samples/Control.py122
-rwxr-xr-xsys/src/cmd/python/Demo/tix/samples/DirList.py131
-rwxr-xr-xsys/src/cmd/python/Demo/tix/samples/DirTree.py117
-rwxr-xr-xsys/src/cmd/python/Demo/tix/samples/NoteBook.py119
-rwxr-xr-xsys/src/cmd/python/Demo/tix/samples/OptMenu.py68
-rwxr-xr-xsys/src/cmd/python/Demo/tix/samples/PanedWin.py98
-rwxr-xr-xsys/src/cmd/python/Demo/tix/samples/PopMenu.py57
-rwxr-xr-xsys/src/cmd/python/Demo/tix/samples/SHList1.py131
-rwxr-xr-xsys/src/cmd/python/Demo/tix/samples/SHList2.py168
-rwxr-xr-xsys/src/cmd/python/Demo/tix/samples/Tree.py80
-rw-r--r--sys/src/cmd/python/Demo/tix/tixwidgets.py1003
-rw-r--r--sys/src/cmd/python/Demo/tkinter/README10
-rwxr-xr-xsys/src/cmd/python/Demo/tkinter/guido/AttrDialog.py452
-rwxr-xr-xsys/src/cmd/python/Demo/tkinter/guido/ManPage.py220
-rwxr-xr-xsys/src/cmd/python/Demo/tkinter/guido/MimeViewer.py143
-rwxr-xr-xsys/src/cmd/python/Demo/tkinter/guido/ShellWindow.py151
-rw-r--r--sys/src/cmd/python/Demo/tkinter/guido/brownian.py50
-rw-r--r--sys/src/cmd/python/Demo/tkinter/guido/canvasevents.py244
-rwxr-xr-xsys/src/cmd/python/Demo/tkinter/guido/dialog.py109
-rwxr-xr-xsys/src/cmd/python/Demo/tkinter/guido/electrons.py91
-rwxr-xr-xsys/src/cmd/python/Demo/tkinter/guido/hanoi.py154
-rwxr-xr-xsys/src/cmd/python/Demo/tkinter/guido/hello.py17
-rwxr-xr-xsys/src/cmd/python/Demo/tkinter/guido/imagedraw.py23
-rwxr-xr-xsys/src/cmd/python/Demo/tkinter/guido/imageview.py12
-rwxr-xr-xsys/src/cmd/python/Demo/tkinter/guido/kill.py98
-rwxr-xr-xsys/src/cmd/python/Demo/tkinter/guido/listtree.py37
-rwxr-xr-xsys/src/cmd/python/Demo/tkinter/guido/mbox.py285
-rw-r--r--sys/src/cmd/python/Demo/tkinter/guido/newmenubardemo.py47
-rw-r--r--sys/src/cmd/python/Demo/tkinter/guido/optionmenu.py27
-rw-r--r--sys/src/cmd/python/Demo/tkinter/guido/paint.py60
-rwxr-xr-xsys/src/cmd/python/Demo/tkinter/guido/rmt.py159
-rwxr-xr-xsys/src/cmd/python/Demo/tkinter/guido/solitaire.py637
-rw-r--r--sys/src/cmd/python/Demo/tkinter/guido/sortvisu.py634
-rw-r--r--sys/src/cmd/python/Demo/tkinter/guido/ss1.py845
-rwxr-xr-xsys/src/cmd/python/Demo/tkinter/guido/svkill.py128
-rw-r--r--sys/src/cmd/python/Demo/tkinter/guido/switch.py55
-rwxr-xr-xsys/src/cmd/python/Demo/tkinter/guido/tkman.py267
-rwxr-xr-xsys/src/cmd/python/Demo/tkinter/guido/wish.py27
-rw-r--r--sys/src/cmd/python/Demo/tkinter/matt/00-HELLO-WORLD.py27
-rw-r--r--sys/src/cmd/python/Demo/tkinter/matt/README30
-rw-r--r--sys/src/cmd/python/Demo/tkinter/matt/animation-simple.py35
-rw-r--r--sys/src/cmd/python/Demo/tkinter/matt/animation-w-velocity-ctrl.py44
-rw-r--r--sys/src/cmd/python/Demo/tkinter/matt/bind-w-mult-calls-p-type.py44
-rw-r--r--sys/src/cmd/python/Demo/tkinter/matt/canvas-demo-simple.py28
-rw-r--r--sys/src/cmd/python/Demo/tkinter/matt/canvas-gridding.py61
-rw-r--r--sys/src/cmd/python/Demo/tkinter/matt/canvas-moving-or-creating.py62
-rw-r--r--sys/src/cmd/python/Demo/tkinter/matt/canvas-moving-w-mouse.py55
-rw-r--r--sys/src/cmd/python/Demo/tkinter/matt/canvas-mult-item-sel.py78
-rw-r--r--sys/src/cmd/python/Demo/tkinter/matt/canvas-reading-tag-info.py49
-rw-r--r--sys/src/cmd/python/Demo/tkinter/matt/canvas-w-widget-draw-el.py36
-rw-r--r--sys/src/cmd/python/Demo/tkinter/matt/canvas-with-scrollbars.py60
-rw-r--r--sys/src/cmd/python/Demo/tkinter/matt/dialog-box.py64
-rw-r--r--sys/src/cmd/python/Demo/tkinter/matt/entry-simple.py24
-rw-r--r--sys/src/cmd/python/Demo/tkinter/matt/entry-with-shared-variable.py46
-rw-r--r--sys/src/cmd/python/Demo/tkinter/matt/killing-window-w-wm.py42
-rw-r--r--sys/src/cmd/python/Demo/tkinter/matt/menu-all-types-of-entries.py244
-rw-r--r--sys/src/cmd/python/Demo/tkinter/matt/menu-simple.py112
-rw-r--r--sys/src/cmd/python/Demo/tkinter/matt/not-what-you-might-think-1.py28
-rw-r--r--sys/src/cmd/python/Demo/tkinter/matt/not-what-you-might-think-2.py30
-rw-r--r--sys/src/cmd/python/Demo/tkinter/matt/packer-and-placer-together.py41
-rw-r--r--sys/src/cmd/python/Demo/tkinter/matt/packer-simple.py32
-rw-r--r--sys/src/cmd/python/Demo/tkinter/matt/placer-simple.py39
-rw-r--r--sys/src/cmd/python/Demo/tkinter/matt/pong-demo-1.py54
-rw-r--r--sys/src/cmd/python/Demo/tkinter/matt/printing-coords-of-items.py61
-rw-r--r--sys/src/cmd/python/Demo/tkinter/matt/radiobutton-simple.py62
-rw-r--r--sys/src/cmd/python/Demo/tkinter/matt/rubber-band-box-demo-1.py58
-rw-r--r--sys/src/cmd/python/Demo/tkinter/matt/rubber-line-demo-1.py51
-rw-r--r--sys/src/cmd/python/Demo/tkinter/matt/slider-demo-1.py36
-rw-r--r--sys/src/cmd/python/Demo/tkinter/matt/subclass-existing-widgets.py28
-rw-r--r--sys/src/cmd/python/Demo/tkinter/matt/two-radio-groups.py110
-rw-r--r--sys/src/cmd/python/Demo/tkinter/matt/window-creation-more.py35
-rw-r--r--sys/src/cmd/python/Demo/tkinter/matt/window-creation-simple.py31
-rw-r--r--sys/src/cmd/python/Demo/tkinter/matt/window-creation-w-location.py45
-rw-r--r--sys/src/cmd/python/Demo/xml/elem_count.py36
-rw-r--r--sys/src/cmd/python/Demo/xml/roundtrip.py45
-rw-r--r--sys/src/cmd/python/Demo/xml/rss2html.py91
-rwxr-xr-xsys/src/cmd/python/Demo/zlib/minigzip.py133
-rw-r--r--sys/src/cmd/python/Demo/zlib/zlibdemo.py48
-rw-r--r--sys/src/cmd/python/Demo/zlib/zlibdemo.py.gzbin0 -> 613 bytes
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': &lt;function f
+...&gt;}.
+
+<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 &lt;function method1 at ae8d8&gt; for &lt;Instance instance at 95ab0&gt; with (10,)
+calling &lt;function method2 at ae900&gt; for &lt;Instance instance at 95ab0&gt; 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
new file mode 100755
index 000000000..e7d51a086
--- /dev/null
+++ b/sys/src/cmd/python/Demo/tix/bitmaps/tix.gif
Binary files differ
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
new file mode 100644
index 000000000..bf50355e3
--- /dev/null
+++ b/sys/src/cmd/python/Demo/zlib/zlibdemo.py.gz
Binary files differ