From a19a216bc60160c162e616145ef091dd18ce4e61 Mon Sep 17 00:00:00 2001 From: Skip Montanaro Date: Tue, 16 Feb 2021 14:40:46 -0600 Subject: Python 0.9.1 as posted in alt.sources --- lib/Abstract.py | 61 ++++++++ lib/Buttons.py | 399 ++++++++++++++++++++++++++++++++++++++++++++++++ lib/CSplit.py | 70 +++++++++ lib/DEVICE.py | 423 +++++++++++++++++++++++++++++++++++++++++++++++++++ lib/GL.py | 365 ++++++++++++++++++++++++++++++++++++++++++++ lib/HVSplit.py | 56 +++++++ lib/Histogram.py | 36 +++++ lib/Sliders.py | 175 +++++++++++++++++++++ lib/Soundogram.py | 36 +++++ lib/Split.py | 116 ++++++++++++++ lib/StripChart.py | 68 +++++++++ lib/Tcl.py | 421 ++++++++++++++++++++++++++++++++++++++++++++++++++ lib/TclShell.py | 255 +++++++++++++++++++++++++++++++ lib/TclUtil.py | 377 +++++++++++++++++++++++++++++++++++++++++++++ lib/TestCSplit.py | 26 ++++ lib/TransParent.py | 96 ++++++++++++ lib/VUMeter.py | 47 ++++++ lib/WindowParent.py | 101 ++++++++++++ lib/adv.py | 366 ++++++++++++++++++++++++++++++++++++++++++++ lib/anywin.py | 14 ++ lib/auds.py | 106 +++++++++++++ lib/calendar.py | 213 ++++++++++++++++++++++++++ lib/clock.py | 202 ++++++++++++++++++++++++ lib/cmp.py | 61 ++++++++ lib/cmpcache.py | 68 +++++++++ lib/commands.py | 77 ++++++++++ lib/dircache.py | 36 +++++ lib/dircmp.py | 205 +++++++++++++++++++++++++ lib/dirwin.py | 29 ++++ lib/dis.py | 176 +++++++++++++++++++++ lib/dump.py | 63 ++++++++ lib/fact.py | 37 +++++ lib/filewin.py | 18 +++ lib/fnmatch.py | 35 +++++ lib/getopt.py | 47 ++++++ lib/glob.py | 44 ++++++ lib/grep.py | 32 ++++ lib/gwin.py | 122 +++++++++++++++ lib/lambda.py | 151 ++++++++++++++++++ lib/listwin.py | 47 ++++++ lib/localtime.py | 53 +++++++ lib/maccache.py | 61 ++++++++ lib/macglob.py | 46 ++++++ lib/macpath.py | 108 +++++++++++++ lib/macshell.py | 399 ++++++++++++++++++++++++++++++++++++++++++++++++ lib/minmax.py | 6 + lib/packmail.py | 48 ++++++ lib/panel.py | 281 ++++++++++++++++++++++++++++++++++ lib/panelparser.py | 128 ++++++++++++++++ lib/path.py | 125 +++++++++++++++ lib/poly.py | 55 +++++++ lib/rand.py | 12 ++ lib/rect.py | 88 +++++++++++ lib/selection.py | 72 +++++++++ lib/shutil.py | 70 +++++++++ lib/stat.py | 57 +++++++ lib/statcache.py | 86 +++++++++++ lib/stdwinevents.py | 46 ++++++ lib/stdwinsupport.py | 34 +++++ lib/string.py | 129 ++++++++++++++++ lib/sunaudio.py | 54 +++++++ lib/tablewin.py | 237 +++++++++++++++++++++++++++++ lib/tb.py | 220 +++++++++++++++++++++++++++ lib/testall.py | 416 ++++++++++++++++++++++++++++++++++++++++++++++++++ lib/textwin.py | 119 +++++++++++++++ lib/util.py | 30 ++++ lib/whrandom.py | 74 +++++++++ lib/zmod.py | 94 ++++++++++++ 68 files changed, 8425 insertions(+) create mode 100644 lib/Abstract.py create mode 100644 lib/Buttons.py create mode 100644 lib/CSplit.py create mode 100644 lib/DEVICE.py create mode 100644 lib/GL.py create mode 100644 lib/HVSplit.py create mode 100644 lib/Histogram.py create mode 100644 lib/Sliders.py create mode 100644 lib/Soundogram.py create mode 100644 lib/Split.py create mode 100644 lib/StripChart.py create mode 100644 lib/Tcl.py create mode 100644 lib/TclShell.py create mode 100644 lib/TclUtil.py create mode 100644 lib/TestCSplit.py create mode 100644 lib/TransParent.py create mode 100644 lib/VUMeter.py create mode 100644 lib/WindowParent.py create mode 100644 lib/adv.py create mode 100644 lib/anywin.py create mode 100644 lib/auds.py create mode 100644 lib/calendar.py create mode 100644 lib/clock.py create mode 100644 lib/cmp.py create mode 100644 lib/cmpcache.py create mode 100644 lib/commands.py create mode 100644 lib/dircache.py create mode 100644 lib/dircmp.py create mode 100644 lib/dirwin.py create mode 100644 lib/dis.py create mode 100644 lib/dump.py create mode 100644 lib/fact.py create mode 100644 lib/filewin.py create mode 100644 lib/fnmatch.py create mode 100644 lib/getopt.py create mode 100644 lib/glob.py create mode 100644 lib/grep.py create mode 100644 lib/gwin.py create mode 100644 lib/lambda.py create mode 100644 lib/listwin.py create mode 100644 lib/localtime.py create mode 100644 lib/maccache.py create mode 100644 lib/macglob.py create mode 100644 lib/macpath.py create mode 100644 lib/macshell.py create mode 100644 lib/minmax.py create mode 100644 lib/packmail.py create mode 100644 lib/panel.py create mode 100644 lib/panelparser.py create mode 100644 lib/path.py create mode 100644 lib/poly.py create mode 100644 lib/rand.py create mode 100644 lib/rect.py create mode 100644 lib/selection.py create mode 100644 lib/shutil.py create mode 100644 lib/stat.py create mode 100644 lib/statcache.py create mode 100644 lib/stdwinevents.py create mode 100644 lib/stdwinsupport.py create mode 100644 lib/string.py create mode 100644 lib/sunaudio.py create mode 100644 lib/tablewin.py create mode 100644 lib/tb.py create mode 100644 lib/testall.py create mode 100644 lib/textwin.py create mode 100644 lib/util.py create mode 100644 lib/whrandom.py create mode 100644 lib/zmod.py (limited to 'lib') diff --git a/lib/Abstract.py b/lib/Abstract.py new file mode 100644 index 0000000..385e6e5 --- /dev/null +++ b/lib/Abstract.py @@ -0,0 +1,61 @@ +# Abstract classes for parents and children. +# +# Do not use as base class -- this is for documentation only. +# +# Note that the tree must be built top down (create the parent, +# then add the children). +# +# Also note that the creation methods are not standardized -- +# these have extra parameters dependent on the widget type. +# For historical reasons, button creation methods are called +# define() while split creation methods are called create(). + +class AbstractParent(): + # + # Upcalls from child to parent + # + def addchild(self, child): unimpl() + def delchild(self, child): unimpl() + # + def need_mouse(self, child): unimpl() + def no_mouse(self, child): unimpl() + # + def need_timer(self, child): unimpl() + def no_timer(self, child): unimpl() + # + # XXX need_kbd, no_kbd; focus??? + # + def begindrawing(self): return unimpl() + def beginmeasuring(self): return unimpl() + # + def change(self, area): unimpl() + def scroll(self, (area, (dh, dv))): unimpl() + def settimer(self, itimer): unimpl() + +class AbstractChild(): + # + # Downcalls from parent to child + # + def destroy(self): unimpl() + # + def minsize(self, m): return unimpl() + def getbounds(self): return unimpl() + def setbounds(self, bounds): unimpl() + def draw(self, (d, area)): unimpl() + # + # Downcalls only made after certain upcalls + # + def mouse_down(self, detail): unimpl() + def mouse_move(self, detail): unimpl() + def mouse_up(self, detail): unimpl() + # + def timer(self): unimpl() + +# A "Split" is a child that manages one or more children. +# (This terminology is due to DEC SRC, except for CSplits.) +# A child of a split may be another split, a button, a slider, etc. +# Certain upcalls and downcalls can be handled transparently, but +# for others (e.g., all geometry related calls) this is not possible. + +class AbstractSplit() = AbstractChild(), AbstractParent(): + pass diff --git a/lib/Buttons.py b/lib/Buttons.py new file mode 100644 index 0000000..7c36021 --- /dev/null +++ b/lib/Buttons.py @@ -0,0 +1,399 @@ +# Module 'Buttons' + + +# Import module 'rect' renamed as '_rect' to avoid exporting it on +# 'from Buttons import *' +# +import rect +_rect = rect +del rect + + +# Field indices in mouse event detail +# +_HV = 0 +_CLICKS = 1 +_BUTTON = 2 +_MASK = 3 + + +# LabelAppearance provides defaults for all appearance methods. +# selected state not visible +# disabled --> crossed out +# hilited --> inverted +# +class LabelAppearance(): + # + # Initialization + # + def init_appearance(self): + self.bounds = _rect.empty + self.enabled = 1 + self.hilited = 0 + self.selected = 0 + self.text = '' + # + # Size enquiry + # + def minsize(self, m): + try: + self.text = self.text + except NameError: + self.text = '' + return m.textwidth(self.text) + 6, m.lineheight() + 6 + # + def getbounds(self): + return self.bounds + # + # Changing the parameters + # + def settext(self, text): + self.text = text + if self.bounds <> _rect.empty: + self.recalctextpos() + self.redraw() + # + def setbounds(self, bounds): + if self.bounds <> _rect.empty: + self.parent.change(self.bounds) + self.bounds = bounds + if self.bounds <> _rect.empty: + self.recalc() + self.parent.change(bounds) + # + # Changing the state bits + # + def enable(self, flag): + if flag <> self.enabled: + self.enabled = flag + if self.bounds <> _rect.empty: + self.flipenable(self.parent.begindrawing()) + # + def hilite(self, flag): + if flag <> self.hilited: + self.hilited = flag + if self.bounds <> _rect.empty: + self.fliphilite(self.parent.begindrawing()) + # + def select(self, flag): + if flag <> self.selected: + self.selected = flag + if self.bounds <> _rect.empty: + self.redraw() + # + # Recalculate the box bounds and text position. + # This can be overridden by buttons that draw different boxes + # or want their text in a different position. + # + def recalc(self): + if self.bounds <> _rect.empty: + self.recalcbounds() + self.recalctextpos() + # + def recalcbounds(self): + self.hilitebounds = _rect.inset(self.bounds, (3, 3)) + self.crossbounds = self.bounds + # + def recalctextpos(self): + (left, top), (right, bottom) = self.bounds + m = self.parent.beginmeasuring() + h = (left + right - m.textwidth(self.text)) / 2 + v = (top + bottom - m.lineheight()) / 2 + self.textpos = h, v + # + # Generic drawing interface. + # Do not override redraw() or draw() methods; override drawit() c.s. + # + def redraw(self): + if self.bounds <> _rect.empty: + self.draw(self.parent.begindrawing(), self.bounds) + # + def draw(self, (d, area)): + area = _rect.intersect(area, self.bounds) + if area = _rect.empty: + return + d.cliprect(area) + d.erase(self.bounds) + self.drawit(d) + d.noclip() + # + # The drawit() method is fairly generic but may be overridden. + # + def drawit(self, d): + self.drawpict(d) + if self.text: + d.text(self.textpos, self.text) + if not self.enabled: + self.flipenable(d) + if self.hilited: + self.fliphilite(d) + # + # Default drawing detail functions. + # Overriding these is normally sufficient to get different + # appearances. + # + def drawpict(self, d): + pass + # + def flipenable(self, d): + _xorcross(d, self.crossbounds) + # + def fliphilite(self, d): + d.invert(self.hilitebounds) + + +# ButtonAppearance displays a centered string in a box. +# selected --> bold border +# disabled --> crossed out +# hilited --> inverted +# +class ButtonAppearance() = LabelAppearance(): + # + def drawpict(self, d): + d.box(_rect.inset(self.bounds, (1, 1))) + if self.selected: + # Make a thicker box + d.box(self.bounds) + d.box(_rect.inset(self.bounds, (2, 2))) + d.box(_rect.inset(self.bounds, (3, 3))) + # + + +# CheckAppearance displays a small square box and a left-justified string. +# selected --> a cross appears in the box +# disabled --> whole button crossed out +# hilited --> box is inverted +# +class CheckAppearance() = LabelAppearance(): + # + def minsize(self, m): + width, height = m.textwidth(self.text) + 6, m.lineheight() + 6 + return width + height + m.textwidth(' '), height + # + def drawpict(self, d): + d.box(self.boxbounds) + if self.selected: _xorcross(d, self.boxbounds) + # + def recalcbounds(self): + LabelAppearance.recalcbounds(self) + (left, top), (right, bottom) = self.bounds + self.size = bottom - top - 4 + self.boxbounds = (left+2, top+2), (left+2+self.size, bottom-2) + self.hilitebounds = self.boxbounds + # + def recalctextpos(self): + m = self.parent.beginmeasuring() + (left, top), (right, bottom) = self.boxbounds + h = right + m.textwidth(' ') + v = top + (self.size - m.lineheight()) / 2 + self.textpos = h, v + # + + +# RadioAppearance displays a round indicator and a left-justified string. +# selected --> a dot appears in the indicator +# disabled --> whole button crossed out +# hilited --> indicator is inverted +# +class RadioAppearance() = CheckAppearance(): + # + def drawpict(self, d): + (left, top), (right, bottom) = self.boxbounds + radius = self.size / 2 + h, v = left + radius, top + radius + d.circle((h, v), radius) + if self.selected: + some = radius/3 + d.paint((h-some, v-some), (h+some, v+some)) + # + + +# NoReactivity ignores mouse events. +# +class NoReactivity(): + def init_reactivity(self): pass + + +# BaseReactivity defines hooks and asks for mouse events, +# but provides only dummy mouse event handlers. +# The trigger methods call the corresponding hooks set by the user. +# Hooks (and triggers) mean the following: +# down_hook called on some mouse-down events +# move_hook called on some mouse-move events +# up_hook called on mouse-up events +# on_hook called for buttons with on/off state, when it goes on +# hook called when a button 'fires' or a radiobutton goes on +# There are usually extra conditions, e.g., hooks are only called +# when the button is enabled, or active, or selected (on). +# +class BaseReactivity(): + # + def init_reactivity(self): + self.down_hook = self.move_hook = self.up_hook = \ + self.on_hook = self.off_hook = \ + self.hook = self.active = 0 + self.parent.need_mouse(self) + # + def mousetest(self, hv): + return _rect.pointinrect(hv, self.bounds) + # + def mouse_down(self, detail): + pass + # + def mouse_move(self, detail): + pass + # + def mouse_up(self, detail): + pass + # + def down_trigger(self): + if self.down_hook: self.down_hook(self) + # + def move_trigger(self): + if self.move_hook: self.move_hook(self) + # + def up_trigger(self): + if self.up_hook: self.up_hook(self) + # + def on_trigger(self): + if self.on_hook: self.on_hook(self) + # + def off_trigger(self): + if self.off_hook: self.off_hook(self) + # + def trigger(self): + if self.hook: self.hook(self) + + +# ToggleReactivity acts like a simple pushbutton. +# It toggles its hilite state on mouse down events. +# +class ToggleReactivity() = BaseReactivity(): + # + def mouse_down(self, detail): + if self.enabled and self.mousetest(detail[_HV]): + self.active = 1 + self.hilite(not self.hilited) + self.down_trigger() + # + def mouse_move(self, detail): + if self.active: + self.move_trigger() + # + def mouse_up(self, detail): + if self.active: + self.up_trigger() + self.active = 0 + # + def down_trigger(self): + if self.hilited: + self.on_trigger() + else: + self.off_trigger() + self.trigger() + # + + +# TriggerReactivity acts like a fancy pushbutton. +# It hilites itself while the mouse is down within its bounds. +# +class TriggerReactivity() = BaseReactivity(): + # + def mouse_down(self, detail): + if self.enabled and self.mousetest(detail[_HV]): + self.active = 1 + self.hilite(1) + self.down_trigger() + # + def mouse_move(self, detail): + if self.active: + self.hilite(self.mousetest(detail[_HV])) + if self.hilited: + self.move_trigger() + # + def mouse_up(self, detail): + if self.active: + self.hilite(self.mousetest(detail[_HV])) + if self.hilited: + self.up_trigger() + self.trigger() + self.active = 0 + self.hilite(0) + # + + +# CheckReactivity handles mouse events like TriggerReactivity, +# It overrides the up_trigger method to flip its selected state. +# +class CheckReactivity() = TriggerReactivity(): + # + def up_trigger(self): + self.select(not self.selected) + if self.selected: + self.on_trigger() + else: + self.off_trigger() + self.trigger() + + +# RadioReactivity turns itself on and the other buttons in its group +# off when its up_trigger method is called. +# +class RadioReactivity() = TriggerReactivity(): + # + def init_reactivity(self): + TriggerReactivity.init_reactivity(self) + self.group = [] + # + def up_trigger(self): + for b in self.group: + if b <> self: + if b.selected: + b.select(0) + b.off_trigger() + self.select(1) + self.on_trigger() + self.trigger() + + +# Auxiliary class for 'define' method. +# Call the initializers in the right order. +# +class Define(): + # + def define(self, parent): + self.parent = parent + parent.addchild(self) + self.init_appearance() + self.init_reactivity() + return self + # + def destroy(self): + self.parent = 0 + # + def definetext(self, (parent, text)): + self = self.define(parent) + self.settext(text) + return self + + +# Subroutine to cross out a rectangle. +# +def _xorcross(d, bounds): + ((left, top), (right, bottom)) = bounds + # This is s bit funny to make it look better + left = left + 2 + right = right - 2 + top = top + 2 + bottom = bottom - 3 + d.xorline(((left, top), (right, bottom))) + d.xorline((left, bottom), (right, top)) + + +# Ready-made button classes. +# +class Label() = NoReactivity(), LabelAppearance(), Define(): pass +class PushButton() = TriggerReactivity(), ButtonAppearance(), Define(): pass +class CheckButton() = CheckReactivity(), CheckAppearance(), Define(): pass +class RadioButton() = RadioReactivity(), RadioAppearance(), Define(): pass +class ToggleButton() = ToggleReactivity(), ButtonAppearance(), Define(): pass diff --git a/lib/CSplit.py b/lib/CSplit.py new file mode 100644 index 0000000..a28b1c8 --- /dev/null +++ b/lib/CSplit.py @@ -0,0 +1,70 @@ +# A CSplit is a Clock-shaped split: the children are grouped in a circle. +# The numbering is a little different from a real clock: the 12 o'clock +# position is called 0, not 12. This is a little easier since Python +# usually counts from zero. (BTW, there needn't be exactly 12 children.) + + +from math import pi, sin, cos +from Split import Split + +class CSplit() = Split(): + # + def minsize(self, m): + # Since things look best if the children are spaced evenly + # along the circle (and often all children have the same + # size anyway) we compute the max child size and assume + # this is each child's size. + width, height = 0, 0 + for child in self.children: + wi, he = child.minsize(m) + width = max(width, wi) + height = max(height, he) + # In approximation, the diameter of the circle we need is + # (diameter of box) * (#children) / pi. + # We approximate pi by 3 (so we slightly overestimate + # our minimal size requirements -- not so bad). + # Because the boxes stick out of the circle we add the + # box size to each dimension. + # Because we really deal with ellipses, do everything + # separate in each dimension. + n = len(self.children) + return width + (width*n + 2)/3, height + (height*n + 2)/3 + # + def getbounds(self): + return self.bounds + # + def setbounds(self, bounds): + self.bounds = bounds + # Place the children. This involves some math. + # Compute center positions for children as if they were + # ellipses with a diameter about 1/N times the + # circumference of the big ellipse. + # (There is some rounding involved to make it look + # reasonable for small and large N alike.) + # XXX One day Python will have automatic conversions... + n = len(self.children) + fn = float(n) + if n = 0: return + (left, top), (right, bottom) = bounds + width, height = right-left, bottom-top + child_width, child_height = width*3/(n+4), height*3/(n+4) + half_width, half_height = \ + float(width-child_width)/2.0, \ + float(height-child_height)/2.0 + center_h, center_v = center = (left+right)/2, (top+bottom)/2 + fch, fcv = float(center_h), float(center_v) + alpha = 2.0 * pi / fn + for i in range(n): + child = self.children[i] + fi = float(i) + fh, fv = \ + fch + half_width*sin(fi*alpha), \ + fcv - half_height*cos(fi*alpha) + left, top = \ + int(fh) - child_width/2, \ + int(fv) - child_height/2 + right, bottom = \ + left + child_width, \ + top + child_height + child.setbounds((left, top), (right, bottom)) + # diff --git a/lib/DEVICE.py b/lib/DEVICE.py new file mode 100644 index 0000000..4f2fadc --- /dev/null +++ b/lib/DEVICE.py @@ -0,0 +1,423 @@ +#/************************************************************************** +# * * +# * Copyright (C) 1984, Silicon Graphics, Inc. * +# * * +# * These coded instructions, statements, and computer programs contain * +# * unpublished proprietary information of Silicon Graphics, Inc., and * +# * are protected by Federal copyright law. They may not be disclosed * +# * to third parties or copied or duplicated in any form, in whole or * +# * in part, without the prior written consent of Silicon Graphics, Inc. * +# * * +# **************************************************************************/ +#/* file with device definitions (see /usr/include/device.h) */ + +NULLDEV = 0 +BUTOFFSET = 1 +VALOFFSET = 256 +TIMOFFSET = 515 +XKBDOFFSET = 143 +INOFFSET = 1024 +OUTOFFSET = 1033 +BUTCOUNT = 190 +VALCOUNT = 27 +TIMCOUNT = 4 +XKBDCOUNT = 28 +INCOUNT = 8 +OUTCOUNT = 8 +# +# +# +# +BUT0 = 1 +BUT1 = 2 +BUT2 = 3 +BUT3 = 4 +BUT4 = 5 +BUT5 = 6 +BUT6 = 7 +BUT7 = 8 +BUT8 = 9 +BUT9 = 10 +BUT10 = 11 +BUT11 = 12 +BUT12 = 13 +BUT13 = 14 +BUT14 = 15 +BUT15 = 16 +BUT16 = 17 +BUT17 = 18 +BUT18 = 19 +BUT19 = 20 +BUT20 = 21 +BUT21 = 22 +BUT22 = 23 +BUT23 = 24 +BUT24 = 25 +BUT25 = 26 +BUT26 = 27 +BUT27 = 28 +BUT28 = 29 +BUT29 = 30 +BUT30 = 31 +BUT31 = 32 +BUT32 = 33 +BUT33 = 34 +BUT34 = 35 +BUT35 = 36 +BUT36 = 37 +BUT37 = 38 +BUT38 = 39 +BUT39 = 40 +BUT40 = 41 +BUT41 = 42 +BUT42 = 43 +BUT43 = 44 +BUT44 = 45 +BUT45 = 46 +BUT46 = 47 +BUT47 = 48 +BUT48 = 49 +BUT49 = 50 +BUT50 = 51 +BUT51 = 52 +BUT52 = 53 +BUT53 = 54 +BUT54 = 55 +BUT55 = 56 +BUT56 = 57 +BUT57 = 58 +BUT58 = 59 +BUT59 = 60 +BUT60 = 61 +BUT61 = 62 +BUT62 = 63 +BUT63 = 64 +BUT64 = 65 +BUT65 = 66 +BUT66 = 67 +BUT67 = 68 +BUT68 = 69 +BUT69 = 70 +BUT70 = 71 +BUT71 = 72 +BUT72 = 73 +BUT73 = 74 +BUT74 = 75 +BUT75 = 76 +BUT76 = 77 +BUT77 = 78 +BUT78 = 79 +BUT79 = 80 +BUT80 = 81 +BUT81 = 82 +BUT82 = 83 +MAXKBDBUT = 83 +BUT100 = 101 +BUT101 = 102 +BUT102 = 103 +BUT110 = 111 +BUT111 = 112 +BUT112 = 113 +BUT113 = 114 +BUT114 = 115 +BUT115 = 116 +BUT116 = 117 +BUT117 = 118 +BUT118 = 119 +BUT119 = 120 +BUT120 = 121 +BUT121 = 122 +BUT122 = 123 +BUT123 = 124 +BUT124 = 125 +BUT125 = 126 +BUT126 = 127 +BUT127 = 128 +BUT128 = 129 +BUT129 = 130 +BUT130 = 131 +BUT131 = 132 +BUT132 = 133 +BUT133 = 134 +BUT134 = 135 +BUT135 = 136 +BUT136 = 137 +BUT137 = 138 +BUT138 = 139 +BUT139 = 140 +BUT140 = 141 +BUT141 = 142 +BUT142 = 143 +BUT143 = 144 +BUT144 = 145 +BUT145 = 146 +BUT146 = 147 +BUT147 = 148 +BUT148 = 149 +BUT149 = 150 +BUT150 = 151 +BUT151 = 152 +BUT152 = 153 +BUT153 = 154 +BUT154 = 155 +BUT155 = 156 +BUT156 = 157 +BUT157 = 158 +BUT158 = 159 +BUT159 = 160 +BUT160 = 161 +BUT161 = 162 +BUT162 = 163 +BUT163 = 164 +BUT164 = 165 +BUT165 = 166 +BUT166 = 167 +BUT167 = 168 +BUT168 = 169 +BUT181 = 182 +BUT182 = 183 +BUT183 = 184 +BUT184 = 185 +BUT185 = 186 +BUT186 = 187 +BUT187 = 188 +BUT188 = 189 +BUT189 = 190 +MOUSE1 = 101 +MOUSE2 = 102 +MOUSE3 = 103 +LEFTMOUSE = 103 +MIDDLEMOUSE = 102 +RIGHTMOUSE = 101 +LPENBUT = 104 +BPAD0 = 105 +BPAD1 = 106 +BPAD2 = 107 +BPAD3 = 108 +LPENVALID = 109 +SWBASE = 111 +SW0 = 111 +SW1 = 112 +SW2 = 113 +SW3 = 114 +SW4 = 115 +SW5 = 116 +SW6 = 117 +SW7 = 118 +SW8 = 119 +SW9 = 120 +SW10 = 121 +SW11 = 122 +SW12 = 123 +SW13 = 124 +SW14 = 125 +SW15 = 126 +SW16 = 127 +SW17 = 128 +SW18 = 129 +SW19 = 130 +SW20 = 131 +SW21 = 132 +SW22 = 133 +SW23 = 134 +SW24 = 135 +SW25 = 136 +SW26 = 137 +SW27 = 138 +SW28 = 139 +SW29 = 140 +SW30 = 141 +SW31 = 142 +SBBASE = 182 +SBPICK = 182 +SBBUT1 = 183 +SBBUT2 = 184 +SBBUT3 = 185 +SBBUT4 = 186 +SBBUT5 = 187 +SBBUT6 = 188 +SBBUT7 = 189 +SBBUT8 = 190 +AKEY = 11 +BKEY = 36 +CKEY = 28 +DKEY = 18 +EKEY = 17 +FKEY = 19 +GKEY = 26 +HKEY = 27 +IKEY = 40 +JKEY = 34 +KKEY = 35 +LKEY = 42 +MKEY = 44 +NKEY = 37 +OKEY = 41 +PKEY = 48 +QKEY = 10 +RKEY = 24 +SKEY = 12 +TKEY = 25 +UKEY = 33 +VKEY = 29 +WKEY = 16 +XKEY = 21 +YKEY = 32 +ZKEY = 20 +ZEROKEY = 46 +ONEKEY = 8 +TWOKEY = 14 +THREEKEY = 15 +FOURKEY = 22 +FIVEKEY = 23 +SIXKEY = 30 +SEVENKEY = 31 +EIGHTKEY = 38 +NINEKEY = 39 +BREAKKEY = 1 +SETUPKEY = 2 +CTRLKEY = 3 +LEFTCTRLKEY = CTRLKEY +CAPSLOCKKEY = 4 +RIGHTSHIFTKEY = 5 +LEFTSHIFTKEY = 6 +NOSCRLKEY = 13 +ESCKEY = 7 +TABKEY = 9 +RETKEY = 51 +SPACEKEY = 83 +LINEFEEDKEY = 60 +BACKSPACEKEY = 61 +DELKEY = 62 +SEMICOLONKEY = 43 +PERIODKEY = 52 +COMMAKEY = 45 +QUOTEKEY = 50 +ACCENTGRAVEKEY = 55 +MINUSKEY = 47 +VIRGULEKEY = 53 +BACKSLASHKEY = 57 +EQUALKEY = 54 +LEFTBRACKETKEY = 49 +RIGHTBRACKETKEY = 56 +LEFTARROWKEY = 73 +DOWNARROWKEY = 74 +RIGHTARROWKEY = 80 +UPARROWKEY = 81 +PAD0 = 59 +PAD1 = 58 +PAD2 = 64 +PAD3 = 65 +PAD4 = 63 +PAD5 = 69 +PAD6 = 70 +PAD7 = 67 +PAD8 = 68 +PAD9 = 75 +PADPF1 = 72 +PADPF2 = 71 +PADPF3 = 79 +PADPF4 = 78 +PADPERIOD = 66 +PADMINUS = 76 +PADCOMMA = 77 +PADENTER = 82 +LEFTALTKEY = 143 +RIGHTALTKEY = 144 +RIGHTCTRLKEY = 145 +F1KEY = 146 +F2KEY = 147 +F3KEY = 148 +F4KEY = 149 +F5KEY = 150 +F6KEY = 151 +F7KEY = 152 +F8KEY = 153 +F9KEY = 154 +F10KEY = 155 +F11KEY = 156 +F12KEY = 157 +PRINTSCREENKEY = 158 +SCROLLLOCKKEY = 159 +PAUSEKEY = 160 +INSERTKEY = 161 +HOMEKEY = 162 +PAGEUPKEY = 163 +ENDKEY = 164 +PAGEDOWNKEY = 165 +NUMLOCKKEY = 166 +PADVIRGULEKEY = 167 +PADASTERKEY = 168 +PADPLUSKEY = 169 +SGIRESERVED = 256 +DIAL0 = 257 +DIAL1 = 258 +DIAL2 = 259 +DIAL3 = 260 +DIAL4 = 261 +DIAL5 = 262 +DIAL6 = 263 +DIAL7 = 264 +DIAL8 = 265 +MOUSEX = 266 +MOUSEY = 267 +LPENX = 268 +LPENY = 269 +BPADX = 270 +BPADY = 271 +CURSORX = 272 +CURSORY = 273 +GHOSTX = 274 +GHOSTY = 275 +SBTX = 276 +SBTY = 277 +SBTZ = 278 +SBRX = 279 +SBRY = 280 +SBRZ = 281 +SBPERIOD = 282 +TIMER0 = 515 +TIMER1 = 516 +TIMER2 = 517 +TIMER3 = 518 +KEYBD = 513 +RAWKEYBD = 514 +VALMARK = 523 +GERROR = 524 +REDRAW = 528 +WMSEND = 529 +WMREPLY = 530 +WMGFCLOSE = 531 +WMTXCLOSE = 532 +MODECHANGE = 533 +INPUTCHANGE = 534 +QFULL = 535 +PIECECHANGE = 536 +WINCLOSE = 537 +QREADERROR = 538 +WINFREEZE = 539 +WINTHAW = 540 +REDRAWICONIC = 541 +WINQUIT = 542 +DEPTHCHANGE = 543 +KEYBDFNAMES = 544 +KEYBDFSTRINGS = 545 +WINSHUT = 546 +INPUT0 = 1024 +INPUT1 = 1025 +INPUT2 = 1026 +INPUT3 = 1027 +INPUT4 = 1028 +INPUT5 = 1029 +INPUT6 = 1030 +INPUT7 = 1032 +OUTPUT0 = 1033 +OUTPUT1 = 1034 +OUTPUT2 = 1035 +OUTPUT3 = 1036 +OUTPUT4 = 1037 +OUTPUT5 = 1038 +OUTPUT6 = 1039 +OUTPUT7 = 1040 +MAXSGIDEVICE = 20000 +MENUBUTTON = RIGHTMOUSE diff --git a/lib/GL.py b/lib/GL.py new file mode 100644 index 0000000..b733e51 --- /dev/null +++ b/lib/GL.py @@ -0,0 +1,365 @@ +# Constants defined in + +#************************************************************************** +#* * +#* Copyright (C) 1984, Silicon Graphics, Inc. * +#* * +#* These coded instructions, statements, and computer programs contain * +#* unpublished proprietary information of Silicon Graphics, Inc., and * +#* are protected by Federal copyright law. They may not be disclosed * +#* to third parties or copied or duplicated in any form, in whole or * +#* in part, without the prior written consent of Silicon Graphics, Inc. * +#* * +#************************************************************************** + +# Graphics Libary constants + +# Booleans +TRUE = 1 +FALSE = 0 + +# maximum X and Y screen coordinates +XMAXSCREEN = 1279 +YMAXSCREEN = 1023 +XMAXMEDIUM = 1023 # max for medium res monitor +YMAXMEDIUM = 767 +XMAX170 = 645 # max for RS-170 +YMAX170 = 484 +XMAXPAL = 779 # max for PAL +YMAXPAL = 574 + +# various hardware/software limits +ATTRIBSTACKDEPTH = 10 +VPSTACKDEPTH = 8 +MATRIXSTACKDEPTH = 32 +NAMESTACKDEPTH = 1025 +STARTTAG = -2 +ENDTAG = -3 +CPOSX_INVALID = -(2*XMAXSCREEN) + +# names for colors in color map loaded by greset +BLACK = 0 +RED = 1 +GREEN = 2 +YELLOW = 3 +BLUE = 4 +MAGENTA = 5 +CYAN = 6 +WHITE = 7 + +# popup colors +PUP_CLEAR = 0 +PUP_COLOR = 1 +PUP_BLACK = 2 +PUP_WHITE = 3 + +# defines for drawmode +NORMALDRAW = 0 +PUPDRAW = 1 +OVERDRAW = 2 +UNDERDRAW = 3 +CURSORDRAW = 4 + +# defines for defpattern +PATTERN_16 = 16 +PATTERN_32 = 32 +PATTERN_64 = 64 + +PATTERN_16_SIZE = 16 +PATTERN_32_SIZE = 64 +PATTERN_64_SIZE = 256 + +# defines for readsource +SRC_AUTO = 0 +SRC_FRONT = 1 +SRC_BACK = 2 +SRC_ZBUFFER = 3 +SRC_PUP = 4 +SRC_OVER = 5 +SRC_UNDER = 6 +SRC_FRAMEGRABBER = 7 + +# defines for blendfunction +BF_ZERO = 0 +BF_ONE = 1 +BF_DC = 2 +BF_SC = 2 +BF_MDC = 3 +BF_MSC = 3 +BF_SA = 4 +BF_MSA = 5 +BF_DA = 6 +BF_MDA = 7 + +# defines for zfunction +ZF_NEVER = 0 +ZF_LESS = 1 +ZF_EQUAL = 2 +ZF_LEQUAL = 3 +ZF_GREATER = 4 +ZF_NOTEQUAL = 5 +ZF_GEQUAL = 6 +ZF_ALWAYS = 7 + +# defines for zsource +ZSRC_DEPTH = 0 +ZSRC_COLOR = 1 + +# defines for pntsmooth +SMP_OFF = 0 +SMP_ON = 1 + +# defines for linesmooth +SML_OFF = 0 +SML_ON = 1 + +# defines for setpup +PUP_NONE = 0 +PUP_GREY = 1 + +# defines for glcompat +GLC_OLDPOLYGON = 0 +GLC_ZRANGEMAP = 1 + +# defines for curstype +C16X1 = 0 +C16X2 = 1 +C32X1 = 2 +C32X2 = 3 +CCROSS = 4 + +# defines for shademodel +FLAT = 0 +GOURAUD = 1 + +# defines for logicop +### LO_ZERO = 0x0 +### LO_AND = 0x1 +### LO_ANDR = 0x2 +### LO_SRC = 0x3 +### LO_ANDI = 0x4 +### LO_DST = 0x5 +### LO_XOR = 0x6 +### LO_OR = 0x7 +### LO_NOR = 0x8 +### LO_XNOR = 0x9 +### LO_NDST = 0xa +### LO_ORR = 0xb +### LO_NSRC = 0xc +### LO_ORI = 0xd +### LO_NAND = 0xe +### LO_ONE = 0xf + + +# +# START defines for getgdesc +# + +GD_XPMAX = 0 +GD_YPMAX = 1 +GD_XMMAX = 2 +GD_YMMAX = 3 +GD_ZMIN = 4 +GD_ZMAX = 5 +GD_BITS_NORM_SNG_RED = 6 +GD_BITS_NORM_SNG_GREEN = 7 +GD_BITS_NORM_SNG_BLUE = 8 +GD_BITS_NORM_DBL_RED = 9 +GD_BITS_NORM_DBL_GREEN = 10 +GD_BITS_NORM_DBL_BLUE = 11 +GD_BITS_NORM_SNG_CMODE = 12 +GD_BITS_NORM_DBL_CMODE = 13 +GD_BITS_NORM_SNG_MMAP = 14 +GD_BITS_NORM_DBL_MMAP = 15 +GD_BITS_NORM_ZBUFFER = 16 +GD_BITS_OVER_SNG_CMODE = 17 +GD_BITS_UNDR_SNG_CMODE = 18 +GD_BITS_PUP_SNG_CMODE = 19 +GD_BITS_NORM_SNG_ALPHA = 21 +GD_BITS_NORM_DBL_ALPHA = 22 +GD_BITS_CURSOR = 23 +GD_OVERUNDER_SHARED = 24 +GD_BLEND = 25 +GD_CIFRACT = 26 +GD_CROSSHAIR_CINDEX = 27 +GD_DITHER = 28 +GD_LINESMOOTH_CMODE = 30 +GD_LINESMOOTH_RGB = 31 +GD_LOGICOP = 33 +GD_NSCRNS = 35 +GD_NURBS_ORDER = 36 +GD_NBLINKS = 37 +GD_NVERTEX_POLY = 39 +GD_PATSIZE_64 = 40 +GD_PNTSMOOTH_CMODE = 41 +GD_PNTSMOOTH_RGB = 42 +GD_PUP_TO_OVERUNDER = 43 +GD_READSOURCE = 44 +GD_READSOURCE_ZBUFFER = 48 +GD_STEREO = 50 +GD_SUBPIXEL_LINE = 51 +GD_SUBPIXEL_PNT = 52 +GD_SUBPIXEL_POLY = 53 +GD_TRIMCURVE_ORDER = 54 +GD_WSYS = 55 +GD_ZDRAW_GEOM = 57 +GD_ZDRAW_PIXELS = 58 +GD_SCRNTYPE = 61 +GD_TEXTPORT = 62 +GD_NMMAPS = 63 +GD_FRAMEGRABBER = 64 +GD_TIMERHZ = 66 +GD_DBBOX = 67 +GD_AFUNCTION = 68 +GD_ALPHA_OVERUNDER = 69 +GD_BITS_ACBUF = 70 +GD_BITS_ACBUF_HW = 71 +GD_BITS_STENCIL = 72 +GD_CLIPPLANES = 73 +GD_FOGVERTEX = 74 +GD_LIGHTING_TWOSIDE = 76 +GD_POLYMODE = 77 +GD_POLYSMOOTH = 78 +GD_SCRBOX = 79 +GD_TEXTURE = 80 + +# return value for inquiries when there is no limit +GD_NOLIMIT = 2 + +# return values for GD_WSYS +GD_WSYS_NONE = 0 +GD_WSYS_4S = 1 + +# return values for GD_SCRNTYPE +GD_SCRNTYPE_WM = 0 +GD_SCRNTYPE_NOWM = 1 + +# +# END defines for getgdesc +# + + +# +# START NURBS interface definitions +# + +# NURBS Rendering Properties +N_PIXEL_TOLERANCE = 1 +N_CULLING = 2 +N_DISPLAY = 3 +N_ERRORCHECKING = 4 +N_SUBDIVISIONS = 5 +N_S_STEPS = 6 +N_T_STEPS = 7 +N_TILES = 8 + +N_SHADED = 1.0 + +# --------------------------------------------------------------------------- +# FLAGS FOR NURBS SURFACES AND CURVES +# +# Bit: 9876 5432 1 0 +# |tttt|nnnn|f|r| : r - 1 bit = 1 if rational coordinate exists +# : f - 1 bit = 1 if rational coordinate is before rest +# : = 0 if rational coordinate is after rest +# : nnnn - 4 bits for number of coordinates +# : tttt - 4 bits for type of data (color, position, etc.) +# +# NURBS data type +# N_T_ST 0 parametric space data +# N_T_XYZ 1 model space data +# +# rational or non-rational data and position in memory +# N_NONRATIONAL 0 non-rational data +# N_RATAFTER 1 rational data with rat coord after rest +# N_RATBEFORE 3 rational data with rat coord before rest +# +# N_MKFLAG(a,b,c) ((a<<6) | (b<<2) | c) +# +# --------------------------------------------------------------------------- +# +N_ST = 0x8 # N_MKFLAG( N_T_ST, 2, N_NONRATIONAL ) +N_STW = 0xd # N_MKFLAG( N_T_ST, 3, N_RATAFTER ) +N_WST = 0xf # N_MKFLAG( N_T_ST, 3, N_RATBEFORE ) +N_XYZ = 0x4c # N_MKFLAG( N_T_XYZ, 3, N_NONRATIONAL ) +N_XYZW = 0x51 # N_MKFLAG( N_T_XYZ, 4, N_RATAFTER ) +N_WXYZ = 0x53 # N_MKFLAG( N_T_XYZ, 4, N_RATBEFORE ) + +# +# END NURBS interface definitions +# + + +# +# START lighting model defines +# + +LMNULL = 0.0 + +# MATRIX modes +MSINGLE = 0 +MPROJECTION = 1 +MVIEWING = 2 + +# LIGHT constants +MAXLIGHTS = 8 +MAXRESTRICTIONS = 4 + +# MATERIAL properties +DEFMATERIAL = 0 +EMISSION = 1 +AMBIENT = 2 +DIFFUSE = 3 +SPECULAR = 4 +SHININESS = 5 +COLORINDEXES = 6 +ALPHA = 7 + +# LIGHT properties +DEFLIGHT = 100 +LCOLOR = 101 +POSITION = 102 + +# LIGHTINGMODEL properties +DEFLMODEL = 200 +LOCALVIEWER = 201 +ATTENUATION = 202 + +# TARGET constants +MATERIAL = 1000 +LIGHT0 = 1100 +LIGHT1 = 1101 +LIGHT2 = 1102 +LIGHT3 = 1103 +LIGHT4 = 1104 +LIGHT5 = 1105 +LIGHT6 = 1106 +LIGHT7 = 1107 +LMODEL = 1200 + +# lmcolor modes +LMC_COLOR = 0 +LMC_EMISSION = 1 +LMC_AMBIENT = 2 +LMC_DIFFUSE = 3 +LMC_SPECULAR = 4 +LMC_AD = 5 +LMC_NULL = 6 + +# +# END lighting model defines +# + + +# +# START distributed graphics library defines +# + +DGLSINK = 0 # sink connection +DGLLOCAL = 1 # local connection +DGLTSOCKET = 2 # tcp socket connection +DGL4DDN = 3 # 4DDN (DECnet) + +# +# END distributed graphics library defines +# diff --git a/lib/HVSplit.py b/lib/HVSplit.py new file mode 100644 index 0000000..d52af8e --- /dev/null +++ b/lib/HVSplit.py @@ -0,0 +1,56 @@ +# HVSplit contains generic code for HSplit and VSplit. +# HSplit and VSplit are specializations to either dimension. + +# XXX This does not yet stretch/shrink children if there is too much +# XXX or too little space in the split dimension. +# XXX (NB There is no interface to ask children for stretch preferences.) + +from Split import Split + +class HVSplit() = Split(): + # + def create(self, (parent, hv)): + # hv is 0 or 1 for HSplit or VSplit + self = Split.create(self, parent) + self.hv = hv + return self + # + def minsize(self, m): + hv, vh = self.hv, 1 - self.hv + size = [0, 0] + for c in self.children: + csize = c.minsize(m) + if csize[vh] > size[vh]: size[vh] = csize[vh] + size[hv] = size[hv] + csize[hv] + return size[0], size[1] + # + def getbounds(self): + return self.bounds + # + def setbounds(self, bounds): + self.bounds = bounds + hv, vh = self.hv, 1 - self.hv + mf = self.parent.beginmeasuring + size = self.minsize(mf()) + # XXX not yet used! Later for stretching + maxsize_hv = bounds[1][hv] - bounds[0][hv] + origin = [self.bounds[0][0], self.bounds[0][1]] + for c in self.children: + size = c.minsize(mf()) + corner = [0, 0] + corner[vh] = bounds[1][vh] + corner[hv] = origin[hv] + size[hv] + c.setbounds((origin[0], origin[1]), \ + (corner[0], corner[1])) + origin[hv] = corner[hv] + # XXX stretch + # XXX too-small + # + +class HSplit() = HVSplit(): + def create(self, parent): + return HVSplit.create(self, (parent, 0)) + +class VSplit() = HVSplit(): + def create(self, parent): + return HVSplit.create(self, (parent, 1)) diff --git a/lib/Histogram.py b/lib/Histogram.py new file mode 100644 index 0000000..3f6da76 --- /dev/null +++ b/lib/Histogram.py @@ -0,0 +1,36 @@ +# Module 'Histogram' + +from Buttons import * + +# A Histogram displays a histogram of numeric data. +# +class HistogramAppearance() = LabelAppearance(), Define(): + # + def define(self, parent): + Define.define(self, (parent, '')) + self.ydata = [] + self.scale = (0, 100) + return self + # + def setdata(self, (ydata, scale)): + self.ydata = ydata + self.scale = scale # (min, max) + self.parent.change(self.bounds) + # + def drawpict(self, d): + (left, top), (right, bottom) = self.bounds + min, max = self.scale + size = max-min + width, height = right-left, bottom-top + ydata = self.ydata + npoints = len(ydata) + v1 = top + height # constant + h1 = left # changed in loop + for i in range(npoints): + h0 = h1 + v0 = top + height - (ydata[i]-min)*height/size + h1 = left + (i+1) * width/npoints + d.paint((h0, v0), (h1, v1)) + # + +class Histogram() = NoReactivity(), HistogramAppearance(): pass diff --git a/lib/Sliders.py b/lib/Sliders.py new file mode 100644 index 0000000..e44b466 --- /dev/null +++ b/lib/Sliders.py @@ -0,0 +1,175 @@ +# Module 'Sliders' + + +import stdwin +from stdwinevents import * +import rect +from Buttons import * +from HVSplit import HSplit + + +# Field indices in event detail +# +_HV = 0 +_CLICKS = 1 +_BUTTON = 2 +_MASK = 3 + + +# DragSlider is the simplest possible slider. +# It looks like a button but dragging the mouse left or right +# changes the controlled value. +# It does not support any of the triggers or hooks defined by Buttons, +# but defines its own setval_trigger and setval_hook. +# +class DragSliderReactivity() = BaseReactivity(): + # + def mouse_down(self, detail): + h, v = hv = detail[_HV] + if self.enabled and self.mousetest(hv): + self.anchor = h + self.oldval = self.val + self.active = 1 + # + def mouse_move(self, detail): + if self.active: + h, v = detail[_HV] + self.setval(self.oldval + (h - self.anchor)) + # + def mouse_up(self, detail): + if self.active: + h, v = detail[_HV] + self.setval(self.oldval + (h - self.anchor)) + self.active = 0 + # + +class DragSliderAppearance() = ButtonAppearance(): + # + # INVARIANTS maintained by the setval method: + # + # self.min <= self.val <= self.max + # self.text = self.pretext + `self.val` + self.postext + # + # (Notice that unlike Python ranges, the end point belongs + # to the range.) + # + def init_appearance(self): + ButtonAppearance.init_appearance(self) + self.min = 0 + self.val = 0 + self.max = 100 + self.hook = 0 + self.pretext = self.postext = '' + self.recalctext() + # + # The 'get*' and 'set*' methods belong to the generic slider interface + # + def getval(self): return self.val + # + def sethook(self, hook): + self.hook = hook + # + def setminvalmax(self, (min, val, max)): + self.min = min + self.max = max + self.setval(val) + # + def settexts(self, (pretext, postext)): + self.pretext = pretext + self.postext = postext + self.recalctext() + # + def setval(self, val): + val = min(self.max, max(self.min, val)) + if val <> self.val: + self.val = val + self.recalctext() + self.trigger() + # + def trigger(self): + if self.hook: + self.hook(self) + # + def recalctext(self): + self.settext(self.pretext + `self.val` + self.postext) + # + +class DragSlider() = DragSliderReactivity(), DragSliderAppearance(), Define(): + def definetext(self, (parent, text)): + raise RuntimeError, 'DragSlider.definetext() not supported' + + +# Auxiliary class for PushButton incorporated in ComplexSlider +# +class _StepButton() = PushButton(): + def define(self, parent): + self = PushButton.define(self, parent) + self.step = 0 + return self + def setstep(self, step): + self.step = step + def definetextstep(self, (parent, text, step)): + self = self.definetext(parent, text) + self.setstep(step) + return self + def init_reactivity(self): + PushButton.init_reactivity(self) + self.parent.need_timer(self) + def step_trigger(self): + self.parent.setval(self.parent.getval() + self.step) + def down_trigger(self): + self.step_trigger() + self.parent.settimer(5) + def timer(self): + if self.hilited: + self.step_trigger() + if self.active: + self.parent.settimer(1) + + +# A complex slider is an HSplit initialized to three buttons: +# one to step down, a dragslider, and one to step up. +# +class ComplexSlider() = HSplit(): + # + # Override Slider define() method + # + def define(self, parent): + self = self.create(parent) # HSplit + # + self.downbutton = _StepButton().definetextstep(self, '-', -1) + self.dragbutton = DragSlider().define(self) + self.upbutton = _StepButton().definetextstep(self, '+', 1) + # + return self + # + # Override HSplit methods + # + def minsize(self, m): + w1, h1 = self.downbutton.minsize(m) + w2, h2 = self.dragbutton.minsize(m) + w3, h3 = self.upbutton.minsize(m) + height = max(h1, h2, h3) + w1 = max(w1, height) + w3 = max(w3, height) + return w1+w2+w3, height + # + def setbounds(self, bounds): + (left, top), (right, bottom) = self.bounds = bounds + size = bottom - top + self.downbutton.setbounds((left, top), (left+size, bottom)) + self.dragbutton.setbounds((left+size, top), \ + (right-size, bottom)) + self.upbutton.setbounds((right-size, top), (right, bottom)) + # + # Pass other Slider methods on to dragbutton + # + def getval(self): return self.dragbutton.getval() + def sethook(self, hook): self.dragbutton.sethook(hook) + def setminvalmax(self, args): self.dragbutton.setminvalmax(args) + def settexts(self, args): self.dragbutton.settexts(args) + def setval(self, val): self.dragbutton.setval(val) + def enable(self, flag): + self.downbutton.enable(flag) + self.dragbutton.enable(flag) + self.upbutton.enable(flag) diff --git a/lib/Soundogram.py b/lib/Soundogram.py new file mode 100644 index 0000000..38dd5ef --- /dev/null +++ b/lib/Soundogram.py @@ -0,0 +1,36 @@ +# Module 'Soundogram' + +import audio +from Histogram import Histogram + +class Soundogram() = Histogram(): + # + def define(self, (win, chunk)): + width, height = corner = win.getwinsize() + bounds = (0, 0), corner + self.chunk = chunk + self.step = (len(chunk)-1)/(width/2+1) + 1 + ydata = _make_ydata(chunk, self.step) + return Histogram.define(self, (win, bounds, ydata, (0, 128))) + # + def setchunk(self, chunk): + self.chunk = chunk + self.recompute() + # + def recompute(self): + (left, top), (right, bottom) = self.bounds + width = right - left + self.step = (len(chunk)-1)/width + 1 + ydata = _make_ydata(chunk, self.step) + self.setdata(ydata, (0, 128)) + # + + +def _make_ydata(chunk, step): + ydata = [] + for i in range(0, len(chunk), step): + piece = audio.chr2num(chunk[i:i+step]) + mi, ma = min(piece), max(piece) + y = max(abs(mi), abs(ma)) + ydata.append(y) + return ydata diff --git a/lib/Split.py b/lib/Split.py new file mode 100644 index 0000000..8c0a8dc --- /dev/null +++ b/lib/Split.py @@ -0,0 +1,116 @@ +# Generic Split implementation. +# Use as a base class for other splits. +# Derived classes should at least implement the methods that call +# unimpl() below: minsize(), getbounds() and setbounds(). + +Error = 'Split.Error' # Exception + +import rect +from util import remove + +class Split(): + # + # Calls from creator + # NB derived classes may add parameters to create() + # + def create(self, parent): + parent.addchild(self) + self.parent = parent + self.children = [] + self.mouse_interest = [] + self.timer_interest = [] + self.mouse_focus = 0 + return self + # + # Downcalls from parent to child + # + def destroy(self): + self.parent = None + for child in self.children: + child.destroy() + del self.children[:] + del self.mouse_interest[:] + del self.timer_interest[:] + self.mouse_focus = None + # + def minsize(self, m): return unimpl() + def getbounds(self): return unimpl() + def setbounds(self, bounds): unimpl() + # + def draw(self, d_detail): + # (Could avoid calls to children outside the area) + for child in self.children: + child.draw(d_detail) + # + # Downcalls only made after certain upcalls + # + def mouse_down(self, detail): + if self.mouse_focus: + self.mouse_focus.mouse_down(detail) + p = detail[0] + for child in self.mouse_interest: + if rect.pointinrect(p, child.getbounds()): + self.mouse_focus = child + child.mouse_down(detail) + def mouse_move(self, detail): + if self.mouse_focus: + self.mouse_focus.mouse_move(detail) + def mouse_up(self, detail): + if self.mouse_focus: + self.mouse_focus.mouse_up(detail) + self.mouse_focus = 0 + # + def timer(self): + for child in self.timer_interest: + child.timer() + # + # Upcalls from child to parent + # + def addchild(self, child): + if child in self.children: + raise Error, 'addchild: child already inlist' + self.children.append(child) + def delchild(self, child): + if child not in self.children: + raise Error, 'delchild: child not in list' + remove(child, self.children) + if child in self.mouse_interest: + remove(child, self.mouse_interest) + if child in self.timer_interest: + remove(child, self.timer_interest) + if child = self.mouse_focus: + self.mouse_focus = 0 + # + def need_mouse(self, child): + if child not in self.mouse_interest: + self.mouse_interest.append(child) + self.parent.need_mouse(self) + def no_mouse(self, child): + if child in self.mouse_interest: + remove(child, self.mouse_interest) + if not self.mouse_interest: + self.parent.no_mouse(self) + # + def need_timer(self, child): + if child not in self.timer_interest: + self.timer_interest.append(child) + self.parent.need_timer(self) + def no_timer(self, child): + if child in self.timer_interest: + remove(child, self.timer_interest) + if not self.timer_interest: + self.parent.no_timer(self) + # + # The rest are transparent: + # + def begindrawing(self): + return self.parent.begindrawing() + def beginmeasuring(self): + return self.parent.beginmeasuring() + # + def change(self, area): + self.parent.change(area) + def scroll(self, area_vector): + self.parent.scroll(area_vector) + def settimer(self, itimer): + self.parent.settimer(itimer) diff --git a/lib/StripChart.py b/lib/StripChart.py new file mode 100644 index 0000000..fc707cb --- /dev/null +++ b/lib/StripChart.py @@ -0,0 +1,68 @@ +# Module 'StripChart' + +import rect +from Buttons import LabelAppearance, NoReactivity + +# A StripChart doesn't really look like a label but it needs a base class. +# LabelAppearance allows it to be disabled and hilited. + +class StripChart() = LabelAppearance(), NoReactivity(): + # + def define(self, (parent, scale)): + self.parent = parent + parent.addchild(self) + self.init_appearance() + self.init_reactivity() + self.ydata = [] + self.scale = scale + self.resetbounds() + return self + # + def setbounds(self, bounds): + LabelAppearance.setbounds(self, bounds) + self.resetbounds() + # + def resetbounds(self): + (left, top), (right, bottom) = self.bounds + self.width = right-left + self.height = bottom-top + excess = len(self.ydata) - self.width + if excess > 0: + del self.ydata[:excess] + elif excess < 0: + while len(self.ydata) < self.width: + self.ydata.insert(0, 0) + # + def append(self, y): + self.ydata.append(y) + excess = len(self.ydata) - self.width + if excess > 0: + del self.ydata[:excess] + if self.bounds <> rect.empty: + self.parent.scroll(self.bounds, (-excess, 0)) + if self.bounds <> rect.empty: + (left, top), (right, bottom) = self.bounds + i = len(self.ydata) + area = (left+i-1, top), (left+i, bottom) + self.draw(self.parent.begindrawing(), area) + # + def draw(self, (d, area)): + area = rect.intersect(area, self.bounds) + if area = rect.empty: + print 'mt' + return + d.cliprect(area) + d.erase(self.bounds) + (a_left, a_top), (a_right, a_bottom) = area + (left, top), (right, bottom) = self.bounds + height = bottom - top + i1 = a_left - left + i2 = a_right - left + for i in range(max(0, i1), min(len(self.ydata), i2)): + split = bottom-self.ydata[i]*height/self.scale + d.paint((left+i, split), (left+i+1, bottom)) + if not self.enabled: + self.flipenable(d) + if self.hilited: + self.fliphilite(d) + d.noclip() diff --git a/lib/Tcl.py b/lib/Tcl.py new file mode 100644 index 0000000..9d45e37 --- /dev/null +++ b/lib/Tcl.py @@ -0,0 +1,421 @@ +# An emulator for John Ousterhout's 'Tcl' language in Python (wow!). +# Currently only the most basic commands are implemented. +# +# Design choices: +# +# - Names used for functions are not exactly those used by C Tcl. +# In Python, names without 'Tcl_' prefix are acceptable because +# names are less global than in C (and often they are prefixed +# with a module name anyway). Parameter conventions also differ. +# +# - The Tcl Interpreter type is implemented using a Python class. +# Almost all functions with an Interpreter as first parameter are +# methods of this class. +# Applications can create derived classes to add additional commands +# or to override specific internal functions. +# +# - Tcl errors are mapped to Python exceptions. +# (I bet Ousterhout would have done the same in a language with +# a proper exception mechanism). +# +# - Tcl expressions are evaluated by Python's built-in function eval(). +# This makes Python Tcl scripts incompatible with C Tcl scripts, +# but is the only sensible solution for a quick-and-dirty version. +# It also makes an escape to Python possible. +# +# - The Backslash function interprets \, since it +# can return a string instead of a character. + + +from TclUtil import * + + +# Exceptions used to signify 'break' and 'continue' + +TclBreak = 'TclBreak' +TclContinue = 'TclContinue' +TclReturn = 'TclReturn' + + +class CmdBuf(): + # + def Create(buffer): + buffer.string = '' + return buffer + # + def Assemble(buffer, str): + buffer.string = buffer.string + str + if buffer.string[-1:] = '\n': + i, end = 0, len(buffer.string) + try: + while i < end: + list, i = FindNextCommand( \ + buffer.string, i, end, 0) + except TclMatchingError: + return '' + except TclSyntaxError: + pass # Let Eval() return the error + ret = buffer.string + buffer.string = '' + return ret + else: + return '' + + +class _Frame(): + def Create(frame): + frame.locals = {} + return frame + +class _Proc(): + # + def Create(proc, (interp, args, body)): + proc.interp = interp + proc.args = SplitList(args) # Do this once here + proc.body = body + return proc + # + def Call(proc, argv): + if len(argv) <> len(proc.args)+1: + raise TclRuntimeError, \ + 'wrong # args to proc "' + \ + argv[0] + '"' + # XXX No defaults or variable length 'args' yet + frame = _Frame().Create() + for i in range(len(proc.args)): + frame.locals[proc.args[i]] = argv[i+1] + proc.interp.stack.append(frame) + try: + value = proc.interp.Eval(proc.body) + except TclReturn, value: + pass + del proc.interp.stack[-1:] + return value + + +import regexp +_expand_prog = regexp.compile('([^[$\\]+|\n)*') +del regexp + +class Interpreter(): + # + def Create(interp): + interp.globals = {} + interp.commands = {} + interp.stack = [] + interp.commands['break'] = interp.BreakCmd + interp.commands['concat'] = interp.ConcatCmd + interp.commands['continue'] = interp.ContinueCmd + interp.commands['echo'] = interp.EchoCmd + interp.commands['eval'] = interp.EvalCmd + interp.commands['expr'] = interp.ExprCmd + interp.commands['for'] = interp.ForCmd + interp.commands['glob'] = interp.GlobCmd + interp.commands['global'] = interp.GlobalCmd + interp.commands['if'] = interp.IfCmd + interp.commands['index'] = interp.IndexCmd + interp.commands['list'] = interp.ListCmd + interp.commands['proc'] = interp.ProcCmd + interp.commands['rename'] = interp.RenameCmd + interp.commands['return'] = interp.ReturnCmd + interp.commands['set'] = interp.SetCmd + return interp + # + def Delete(interp): + # + # Only break circular references here; + # most things will be garbage-collected. + # + for name in interp.commands.keys(): + del interp.commands[name] + # + def CreateCommand(interp, (name, proc)): + interp.commands[name] = proc + # + def DeleteCommand(interp, (name)): + del interp.commands[name] + # + # Local variables are maintained on the stack. + # A local variable with value "None" is a dummy + # meaning that the corresponding global variable + # should be used. + # + def GetVar(interp, varName): + dict = interp.globals + if interp.stack: + d = interp.stack[-1:][0].locals + if d.has_key(varName) and d[varName] = None: + pass + else: + dict = d + if not dict.has_key(varName): + raise TclRuntimeError, \ + 'Variable "' + varName + '" not found' + return dict[varName] + # + def SetVar(interp, (varName, newValue)): + dict = interp.globals + if interp.stack: + d = interp.stack[-1:][0].locals + if d.has_key(varName) and d[varName] = None: + pass + else: + dict = d + dict[varName] = newValue + # + def Expand(interp, (str, i, end)): + if end <= i: return '' + if str[i] = '{' and str[end-1] = '}': + return str[i+1:end-1] + if str[i] = '"' and str[end-1] = '"': + i, end = i+1, end-1 + result = '' + while i < end: + c = str[i] + if c = '\\': + x, i = Backslash(str, i, end) + result = result + x + elif c = '[': + j = BalanceBrackets(str, i, end) + x = interp.EvalBasic(str, i+1, j-1, 1) + result = result + x + i = j + elif c = '$': + i = i+1 + j = FindVarName(str, i, end) + name = str[i:j] + i = j + if not name: + result = result + '$' + else: + if name[:1] = '{' and name[-1:] = '}': + name = name[1:-1] + result = result + interp.GetVar(name) + else: + j = _expand_prog.exec(str, i) + j = min(j, end) + result = result + str[i:j] + i = j + return result + # + def EvalBasic(interp, (str, i, end, bracketed)): + result = '' + while i < end: + indexargv, i = FindNextCommand( \ + str, i, end, bracketed) + if indexargv: + argv = [] + for x, y in indexargv: + arg = interp.Expand(str, x, y) + argv.append(arg) + name = argv[0] + if not interp.commands.has_key(name): + raise TclRuntimeError, \ + 'Command "' + name + \ + '" not found' + result = interp.commands[name](argv) + return result + # + def Eval(interp, str): + return interp.EvalBasic(str, 0, len(str), 0) + # + def ExprBasic(interp, (str, begin, end)): + expr = interp.Expand(str, begin, end) + i = SkipSpaces(expr, 0, len(expr)) + expr = expr[i:] + try: + return eval(expr, {}) + except (NameError, TypeError, RuntimeError, EOFError), msg: + import sys + raise TclRuntimeError, sys.exc_type + ': ' + msg + # + def Expr(interp, str): + return interp.ExprBasic(str, 0, len(str)) + # + # The rest are command implementations + # + def BreakCmd(interp, argv): + if len(argv) <> 1: + raise TclRuntimeError, 'usage: break' + raise TclBreak + # + def ConcatCmd(interp, argv): + if len(argv) < 2: + raise TclRuntimeError, 'usage: concat arg ...' + return Concat(argv[1:]) + # + def ContinueCmd(interp, argv): + if len(argv) <> 1: + raise TclRuntimeError, 'usage: continue' + raise TclContinue + # + def EchoCmd(interp, argv): + for arg in argv[1:]: print arg, + print + return '' + # + def EvalCmd(interp, argv): + if len(argv) < 2: + raise TclRuntimeError, 'usage: eval arg [arg ...]' + str = Concat(argv[1:]) + return interp.Eval(str) + # + def ExprCmd(interp, argv): + if len(argv) <> 2: + raise TclRuntimeError, 'usage: expr expression' + expr = argv[1] + result = interp.Expr(expr) + if type(result) <> type(''): result = `result` + return result + # + def ForCmd(interp, argv): + if len(argv) <> 5: + raise TclRuntimeError, \ + 'usage: for start test next body' + x = interp.Eval(argv[1]) + while interp.Expr(argv[2]): + try: + x = interp.Eval(argv[4]) + except TclBreak: + break + except TclContinue: + pass + x = interp.Eval(argv[3]) + return '' + # + def GlobCmd(interp, argv): + import macglob + if len(argv) < 2: + raise TclRuntimeError, 'usage: glob pattern ...' + list = [] + for pat in argv[1:]: + list = list + macglob.glob(pat) + if not list: + raise TclRuntimeError, 'no match for glob pattern(s)' + return BuildList(list) + # + def GlobalCmd(interp, argv): + if len(argv) < 2: + raise TclRuntimeError, 'usage: global varname ...' + if not interp.stack: + raise TclRuntimeError, 'global used outside proc' + dict = interp.stack[-1:][0].locals + for name in argv[1:]: + dict[name] = None + return '' + # + def IfCmd(interp, argv): + argv = argv[:] + if len(argv) > 2 and argv[2] = 'then': del argv[2] + if len(argv) > 3 and argv[3] = 'else': del argv[3] + if not 3 <= len(argv) <= 4: + raise TclRuntimeError, \ + 'usage: if test [then] trueBody [else] falseBody' + if interp.Expr(argv[1]): + return interp.Eval(argv[2]) + if len(argv) > 3: + return interp.Eval(argv[3]) + return '' + # + def IndexCmd(interp, argv): + if len(argv) <> 3: + raise TclRuntimeError, 'usage: index value index' + import string + try: + index = string.atoi(argv[2]) + if index < 0: raise string.atoi_error + except string.atoi_error: + raise TclRuntimeError, 'bad index: ' + argv[2] + list = SplitList(argv[1]) + if index >= len(list): return '' + return list[index] + # + def ListCmd(interp, argv): + if len(argv) < 2: + raise TclRuntimeError, 'usage: list arg ...' + return BuildList(argv[1:]) + # + def ProcCmd(interp, argv): + if len(argv) <> 4: + raise TclRuntimeError, 'usage: proc name args body' + x = _Proc().Create(interp, argv[2], argv[3]) + interp.CreateCommand(argv[1], x.Call) + return '' + # + def RenameCmd(interp, argv): + if len(argv) <> 3: + raise TclRuntimeError, 'usage: rename oldName newName' + oldName, newName = argv[1], argv[2] + if not interp.commands.has_key(oldName): + raise TclRuntimeError, \ + 'command "' + oldName + '" not found' + if newName: interp.commands[newName] = interp.commands[oldName] + del interp.commands[oldName] + return '' + # + def ReturnCmd(interp, argv): + if not 1 <= len(argv) <= 2: + raise TclRuntimeError, 'usage: return [arg]' + if len(argv) = 1: raise TclReturn, '' + raise TclReturn, argv[1] + # + def SetCmd(interp, argv): + n = len(argv) + if not 2 <= n <= 3: + raise TclRuntimeError, 'usage: set varname [newvalue]' + if n = 2: return interp.GetVar(argv[1]) + interp.SetVar(argv[1], argv[2]) + return '' + + +# The rest are just demos: + +def MainLoop(interp): + buffer = CmdBuf().Create() + if not interp.globals.has_key('ps1'): interp.globals['ps1'] = '% ' + if not interp.globals.has_key('ps2'): interp.globals['ps2'] = '' + psname = 'ps1' + while 1: + try: + line = raw_input(interp.globals[psname]) + except (EOFError, KeyboardInterrupt): + print + break + line = buffer.Assemble(line + '\n') + if not line: + psname = 'ps2' + else: + psname = 'ps1' + try: + x = interp.Eval(line) + if x <> '': print 'Result:', `x` + except (TclRuntimeError, TclSyntaxError, \ + TclMatchingError), msg: + print 'Error:', msg + except (TclBreak, TclContinue): + print 'Error: break or continue outside loop' + except TclReturn, value: + # Return outside proc returns to main loop + if value: print value + + +the_interpreter = Interpreter().Create() + +def main(): + MainLoop(the_interpreter) + + +# XXX To do: +# for proc: "args" and default arguments +# More commands: +# case +# uplevel +# info +# string +# list operations +# error, catch +# print +# scan, format +# source +# history? +# others? diff --git a/lib/TclShell.py b/lib/TclShell.py new file mode 100644 index 0000000..78d8dc4 --- /dev/null +++ b/lib/TclShell.py @@ -0,0 +1,255 @@ +# Tcl-based shell (for the Macintosh) + +import TclUtil +import Tcl +from Tcl import Interpreter, TclRuntimeError +import mac +import macpath +from macpath import isfile, isdir, exists + +UsageError = TclRuntimeError + +class ShellInterpreter() = Interpreter(): + # + def ResetVariables(interp): + interp.globals['ps1'] = '$ ' + interp.globals['ps2'] = '> ' + interp.globals['home'] = mac.getcwd() + # + def DefineCommands(interp): + interp.commands['cd'] = interp.CdCmd + interp.commands['grep'] = interp.GrepCmd + interp.commands['ls'] = interp.LsCmd + interp.commands['mkdir'] = interp.MkdirCmd + interp.commands['mv'] = interp.MvCmd + interp.commands['pg'] = interp.PgCmd + interp.commands['pwd'] = interp.PwdCmd + interp.commands['rm'] = interp.RmCmd + interp.commands['rmdir'] = interp.RmdirCmd + interp.commands['sync'] = interp.SyncCmd + # + def Reset(interp): + interp.ResetVariables() + interp.DefineCommands() + # + def Create(interp): + interp = Interpreter.Create(interp) # initialize base class + interp.Reset() + return interp + # + # Command-implementing functions + # + def CdCmd(interp, argv): + if len(argv) > 2: + raise UsageError, 'usage: cd [dirname]' + if len(argv) = 2: + chdirto(argv[1]) + else: + chdirto(interp.globals['home']) + return '' + # + def GrepCmd(interp, argv): + if len(argv) < 3: + raise UsageError, 'usage: grep regexp file ...' + import regexp + try: + prog = regexp.compile(argv[1]) + except regexp.error, msg: + raise TclRuntimeError, \ + ('grep', argv[1], ': bad regexp :', msg) + for file in argv[2:]: + grepfile(prog, file) + return '' + # + def LsCmd(interp, argv): + if len(argv) < 2: + lsdir(':') + else: + for dirname in argv[1:]: + lsdir(dirname) + return '' + # + def MkdirCmd(interp, argv): + if len(argv) < 2: + raise UsageError, 'usage: mkdir name ...' + for name in argv[1:]: + makedir(name) + return '' + # + def MvCmd(interp, argv): + if len(argv) <> 3: + raise UsageError, 'usage: mv src dst' + src, dst = argv[1], argv[2] + if not exists(src): + raise TclRuntimeError, \ + ('mv', src, dst, ': source does not exist') + if exists(dst): + raise TclRuntimeError, \ + ('mv', src, dst, ': destination already exists') + try: + mac.rename(src, dst) + except mac.error, msg: + raise TclRuntimeError, \ + (src, dst, ': rename failed :', msg) + return '' + # + def PgCmd(interp, argv): + if len(argv) < 2: + raise UsageError, 'usage: page file ...' + for name in argv[1:]: + pagefile(name) + return '' + # + def PwdCmd(interp, argv): + if len(argv) > 1: + raise UsageError, 'usage: pwd' + else: + return mac.getcwd() + # + def RmCmd(interp, argv): + if len(argv) < 2: + raise UsageError, 'usage: rm file ...' + for name in argv[1:]: + remove(name) + return '' + # + def RmdirCmd(interp, argv): + if len(argv) < 2: + raise UsageError, 'usage: rmdir dir ...' + for name in argv[1:]: + rmdir(name) + return '' + # + def SyncCmd(interp, argv): + if len(argv) > 1: + raise UsageError, 'usage: sync' + try: + mac.sync() + except mac.error, msg: + raise TclRuntimeError, ('sync failed :', msg) + # + +def chdirto(dirname): + try: + mac.chdir(dirname) + except mac.error, msg: + raise TclRuntimeError, (dirname, ': chdir failed :', msg) + +def grepfile(prog, file): + try: + fp = open(file, 'r') + except RuntimeError, msg: + raise TclRuntimeError, (file, ': open failed :', msg) + lineno = 0 + while 1: + line = fp.readline() + if not line: break + lineno = lineno+1 + if prog.exec(line): + print file+'('+`lineno`+'):', line, + +def lsdir(dirname): + if not isdir(dirname): + print dirname, ': no such directory' + return + names = mac.listdir(dirname) + lsfiles(names, dirname) + +def lsfiles(names, dirname): + names = names[:] # Make a copy so we can modify it + for i in range(len(names)): + name = names[i] + if isdir(macpath.cat(dirname, name)): + names[i] = ':' + name + ':' + columnize(names) + +def makedir(name): + if exists(name): + print name, ': already exists' + return + try: + mac.mkdir(name, 0777) + except mac.error, msg: + raise TclRuntimeError, (name, ': mkdir failed :', msg) + +def pagefile(name): + import string + if not isfile(name): + print name, ': no such file' + return + LINES = 24 - 1 + # For THINK C 3.0, make the path absolute: + # if not macpath.isabs(name): + # name = macpath.cat(mac.getcwd(), name) + try: + fp = open(name, 'r') + except RuntimeError, msg: + raise TclRuntimeError, (name, ': open failed :', msg) + line = fp.readline() + while line: + for i in range(LINES): + print line, + line = fp.readline() + if not line: break + if line: + try: + more = raw_input('[more]') + except (EOFError, KeyboardInterrupt): + print + break + if string.strip(more)[:1] in ('q', 'Q'): + break + +def remove(name): + if not isfile(name): + print name, ': no such file' + return + try: + mac.unlink(name) + except mac.error, msg: + raise TclRuntimeError, (name, ': unlink failed :', msg) + +def rmdir(name): + if not isdir(name): + raise TclRuntimeError, (name, ': no such directory') + try: + mac.rmdir(name) + except mac.error, msg: + raise TclRuntimeError, (name, ': rmdir failed :', msg) + +def printlist(list): + for word in list: + print word, + +def columnize(list): + import string + COLUMNS = 80-1 + n = len(list) + colwidth = maxwidth(list) + ncols = (COLUMNS + 1) / (colwidth + 1) + if ncols < 1: ncols = 1 + nrows = (n + ncols - 1) / ncols + for irow in range(nrows): + line = '' + for icol in range(ncols): + i = irow + nrows*icol + if 0 <= i < n: + word = list[i] + if i+nrows < n: + word = string.ljust(word, colwidth) + if icol > 0: + word = ' ' + word + line = line + word + print line + +def maxwidth(list): + width = 0 + for word in list: + if len(word) > width: + width = len(word) + return width + +the_interpreter = ShellInterpreter().Create() + +def main(): + Tcl.MainLoop(the_interpreter) diff --git a/lib/TclUtil.py b/lib/TclUtil.py new file mode 100644 index 0000000..0990530 --- /dev/null +++ b/lib/TclUtil.py @@ -0,0 +1,377 @@ +# Utilities used by 'Tcl' emulator. + + +# Many functions in this file parse specific constructs from strings. +# In order to limit the number of slice operations (the strings can +# be very large), they always receive indices into the string that +# indicate the slice of the string that should be considered. +# The return value is in general another index, pointing to the first +# character in the string beyond the recognized construct. +# Errors are reported as exceptions (TclSyntaxError, TclMatchingError). +# A few functions have multiple return values. + + +# For efficiency, the Tcl "tokenizing" routines used pre-compiled +# regular expressions. This is less readable but should be much faster +# than scanning the string a character at a time. +# +# The global variables +# containing the compiled regexp's are named _foo_prog where foo is +# an indication of the function that uses them. +# +# The patterns always +# have the form * so they always match at the start of the +# search buffer---maybe with the empty string. This makes it possible +# to use the expression "_foo_prog.exec(str, i)[0][1]" to find the first +# character beyond the matched string. Note that this may be beyond the +# end variable -- where this matters, "min(i, end)" is used. + +# Constructs that cannot +# be recognized by a finite automaton (like matching braces) are scanned +# by a hybrid technique where the regular expression excludes the +# braces. +# +# Many regular expressions contain an expression that matches +# a Tcl backslash sequence as a subpart: +# \\\\C?M?(.|\n) +# +# This is a bit hard to +# read because the backslash contained in it must be doubled twice: +# once to get past Python's backslash mechanism, once to get past that +# of regular expressions. It uses (.|\n) to match absolutely +# *every character*, becase the MULTILINE regular expression package does +# not accept '\n' as a match for '.'. +# +# There is also a simplification in the pattern for backslashes: +# *any* single character following a backslash is escaped, +# so hex and octal +# excapes are not scanned fully. The forms \Cx, \Mx and \CMx are +# scanned correctly, as these may hide a special character. +# (This does not invalidate the recognition of strings, although the +# match is effectuated in a different way than by the Backslash function.) + +import regexp + + +# Exceptions raised for various error conditions. + +TclAssertError = 'Tcl assert error' +TclSyntaxError = 'Tcl syntax error' +TclRuntimeError = 'Tcl runtime error' +TclMatchingError = 'Tcl matching error' + + +# Find a variable name. +# A variable name is either a (possiblly empty) sequence of letters, +# digits and underscores, or anything enclosed in matching braces. +# Return the index past the end of the name. + +_varname_prog = regexp.compile('[a-zA-Z0-9_]*') + +def FindVarName(str, i, end): + if i < end and str[i] = '{': return BalanceBraces(str, i, end) + i = _varname_prog.exec(str, i)[0][1] + return min(i, end) + + +# Split a list into its elements. +# Return a list of elements (strings). + +def SplitList(str): + i, end = 0, len(str) + list = [] + while 1: + i = SkipSpaces(str, i, end) + if i >= end: break + j = i + i = FindNextElement(str, i, end) + if str[j] = '{' and str[i-1] = '}': + element = str[j+1:i-1] + else: + element = Collapse(str[j:i]) + list.append(element) + return list + + +# Find the next element from a list. + +_element_prog = regexp.compile('([^ \t\n\\]+|\\\\C?M?(.|\n))*') + +def FindNextElement(str, i, end): + if i < end and str[i] = '{': + i = BalanceBraces(str, i, end) + if i < end and str[i] not in ' \t\n': + raise TclSyntaxError, 'Garbage after } in list' + return i + i = _element_prog.exec(str, i)[0][1] + return min(i, end) + + +# Copy a string, expanding all backslash sequences. + +_collapse_prog = regexp.compile('(\n|[^\\]+)*') + +def Collapse(str): + if '\\' not in str: return str + i, end = 0, len(str) + result = '' + while i < end: + j = _collapse_prog.exec(str, i)[0][1] + j = min(j, end) + result = result + str[i:j] + if j >= end: break + c = str[j] + if c <> '\\': raise TclAssertError, 'collapse error' + x, i = Backslash(str, j, end) + result = result + x + return result + + +# Find the next full command. +# Return a list of begin, end indices of words in the string, +# and an index pointing just after the terminating newline or +# semicolon. +# Initial spaces are skipped. +# If the command begins with '#', it is considered empty and +# characters until '\n' are skipped. + +_eol_prog = regexp.compile('[^\n]*') + +def FindNextCommand(str, i, end, bracketed): + i = SkipSpaces(str, i, end) + if i >= end: return [], end + if str[i] = '#': + i = _eol_prog.exec(str, i) + i = min(i, end) + if i < end and str[i] = '\n': i = i+1 + return [], i + if bracketed: terminators = [';'] + else: terminators = [';', '\n'] + list = [] + while i < end: + j = FindNextWord(str, i, end) + word = str[i:j] + if word in terminators: + i = j + break + if word <> '\n': list.append(i, j) + i = SkipSpaces(str, j, end) + return list, i + + +# Find the next word of a command. +# Semicolon and newline terminate words but also count as a word +# themselves. +# The start index must point to the start of the word. + +_word_prog = regexp.compile('([^ \t\n;[\\]+|\\\\C?M?(.|\n))*') + +def FindNextWord(str, i, end): + if i >= end: return end + if str[i] in '{"': + if str[i] = '{': i = BalanceBraces(str, i, end) + else: i = BalanceQuotes(str, i, end) + if i >= end or str[i] in ' \t\n;': return min(i, end) + raise TclSyntaxError, 'Garbage after } or "' + begin = i + while i < end: + i = _word_prog.exec(str, i)[0][1] + if i >= end: + i = end + break + c = str[i] + if c in ' \t': break + if c in ';\n': + if i = begin: i = i+1 + break + if c = '[': i = BalanceBrackets(str, i, end) + else: raise TclAssertError, 'word error' + return i + + +# Parse balanced brackets from str[i:end]. +# str[i] must be '['. +# Returns end such that str[i:end] ends with ']' +# and contains balanced braces and brackets. + +_brackets_prog = regexp.compile('([^][{\\]+|\n|\\\\C?M?(.|\n))*') + +def BalanceBrackets(str, i, end): + if i >= end or str[i] <> '[': + raise TclAssertError, 'BalanceBrackets' + nesting = 0 + while i < end: + i = _brackets_prog.exec(str, i)[0][1] + if i >= end: break + c = str[i] + if c = '{': i = BalanceBraces(str, i, end) + else: + i = i+1 + if c = '[': nesting = nesting + 1 + elif c = ']': + nesting = nesting - 1 + if nesting = 0: return i + else: raise TclAssertError, 'brackets error' + raise TclMatchingError, 'Unmatched bracket ([)' + + +# Parse balanced braces from str[i:end]. +# str[i] must be '{'. +# Returns end such that str[i:end] ends with '}' +# and contains balanced braces. + +_braces_prog = regexp.compile('([^{}\\]+|\n|\\\\C?M?(.|\n))*') + +def BalanceBraces(str, i, end): + if i >= end or str[i] <> '{': + raise TclAssertError, 'BalanceBraces' + nesting = 0 + while i < end: + i = _braces_prog.exec(str, i)[0][1] + if i >= end: break + c = str[i] + i = i+1 + if c = '{': nesting = nesting + 1 + elif c = '}': + nesting = nesting - 1 + if nesting = 0: return i + else: raise TclAssertError, 'braces error' + raise TclMatchingError, 'Unmatched brace ({)' + + +# Parse double quotes from str[i:end]. +# str[i] must be '"'. +# Returns end such that str[i:end] ends with an unescaped '"'. + +_quotes_prog = regexp.compile('([^"\\]+|\n|\\\\C?M?(.|\n))*') + +def BalanceQuotes(str, i, end): + if i >= end or str[i] <> '"': + raise TclAssertError, 'BalanceQuotes' + i = _quotes_prog.exec(str, i+1)[0][1] + if i < end and str[i] = '"': return i+1 + raise TclMatchingError, 'Unmatched quote (")' + + +# Static data used by Backslash() + +_bstab = {} +_bstab['n'] = '\n' +_bstab['r'] = '\r' +_bstab['t'] = '\t' +_bstab['b'] = '\b' +_bstab['e'] = '\033' +_bstab['\n'] = '' +for c in ' {}[]$";\\': _bstab[c] = c +del c + +# Backslash interpretation. +# First character must be a backslash. +# Return a pair (, ). +# Unrecognized or incomplete backslash sequences are not errors; +# this takes only the backslash itself off the string. + +def Backslash(str, i, end): + if i >= end or str[i] <> '\\': + raise TclAssertError, 'Backslash' + i = i+1 + if i = end: return '\\', i + c = str[i] + i = i+1 + if _bstab.has_key(c): return _bstab[c], i + if c = 'C': + if i = end: return '\\', i-1 + c = str[i] + i = i+1 + if c = 'M': + if i = end: return '\\', i-2 + c = str[i] + i = i+1 + x = ord(c) % 040 + 0200 + else: + x = ord(c) % 040 + return chr(x), i + elif c = 'M': + if i = end: return '\\', i-1 + c = str[i] + i = i+1 + x = ord(c) + if x < 0200: x = x + 0200 + return chr(x), i + elif c and c in '0123456789': + x = ord(c) - ord('0') + end = min(end, i+2) + while i < end: + c = str[i] + if c not in '0123456789': break + i = i+1 + x = x*8 + ord(c) - ord('0') + return ord(x), i + else: + # Not something that we recognize + return '\\', i-1 + + +# Skip over spaces and tabs (but not newlines). + +_spaces_prog = regexp.compile('[ \t]*') + +def SkipSpaces(str, i, end): + i = _spaces_prog.exec(str, i)[0][1] + return min(i, end) + + +# Concatenate the elements of a list with intervening spaces. + +def Concat(argv): + result = '' + sep = '' + for arg in argv: + result = result + (sep + arg) + sep = ' ' + return result + + +# Concatenate list elements, adding braces etc. to make them parseable +# again with SplitList. + +def BuildList(argv): + result = '' + sep = '' + for arg in argv: + arg = AddBraces(arg) + result = result + (sep + arg) + sep = ' ' + return result + + +# Add braces around a string if necessary to make it parseable by SplitList. + +def AddBraces(str): + # Special case for empty string + if str = '': return '{}' + # See if it contains balanced braces + res = '{' + str + '}' + if TryNextElement(res): + # See if it would survive unquoted + # XXX should escape [] and $ as well??? + if TryNextElement(str) and Collapse(str) = str: return str + # No -- return with added braces + return res + # Unbalanced braces. Add backslashes before suspect characters + res = '' + for c in str: + if c in '$\\[]{} ;': c = '\\' + c + elif c = '\n': c = '\\n' + elif c = '\t': c = '\\t' + res = res + c + return res + + +def TryNextElement(str): + end = len(str) + try: + i = FindNextElement(str, 0, end) + return i = end + except (TclSyntaxError, TclMatchingError): + return 0 diff --git a/lib/TestCSplit.py b/lib/TestCSplit.py new file mode 100644 index 0000000..b638f81 --- /dev/null +++ b/lib/TestCSplit.py @@ -0,0 +1,26 @@ +# TestCSplit + +import stdwin +from stdwinevents import WE_CLOSE +from WindowParent import WindowParent +from Buttons import PushButton + +def main(n): + from CSplit import CSplit + + the_window = WindowParent().create('TestCSplit', (0, 0)) + the_csplit = CSplit().create(the_window) + + for i in range(n): + the_child = PushButton().define(the_csplit) + the_child.settext(`(i+n-1)%n+1`) + + the_window.realize() + + while 1: + the_event = stdwin.getevent() + if the_event[0] = WE_CLOSE: break + the_window.dispatch(the_event) + the_window.destroy() + +main(12) diff --git a/lib/TransParent.py b/lib/TransParent.py new file mode 100644 index 0000000..a07b4c4 --- /dev/null +++ b/lib/TransParent.py @@ -0,0 +1,96 @@ +# A class that sits transparently between a parent and one child. +# First create the parent, then this thing, then the child. +# Use this as a base class for objects that are almost transparent. +# Don't use as a base class for parents with multiple children. + +Error = 'TransParent.Error' # Exception + +class ManageOneChild(): + # + # Upcalls shared with other single-child parents + # + def addchild(self, child): + if self.child: + raise Error, 'addchild: one child only' + if not child: + raise Error, 'addchild: bad child' + self.child = child + # + def delchild(self, child): + if not self.child: + raise Error, 'delchild: no child' + if child <> self.child: + raise Error, 'delchild: not my child' + self.child = 0 + +class TransParent() = ManageOneChild(): + # + # Calls from creator + # NB derived classes may add parameters to create() + # + def create(self, parent): + parent.addchild(self) + self.parent = parent + self.child = 0 # No child yet + # + # Downcalls from parent to child + # + def destroy(self): + del self.parent + if self.child: self.child.destroy() + del self.child + # + def minsize(self, m): + if not self.child: + return 0, 0 + else: + return self.child.minsize(m) + def getbounds(self, bounds): + if not self.child: + raise Error, 'getbounds w/o child' + else: + return self.child.getbounds() + def setbounds(self, bounds): + if not self.child: + raise Error, 'setbounds w/o child' + else: + self.child.setbounds(bounds) + def draw(self, args): + if self.child: + self.child.draw(args) + # + # Downcalls only made after certain upcalls + # + def mouse_down(self, detail): + if self.child: self.child.mouse_down(detail) + def mouse_move(self, detail): + if self.child: self.child.mouse_move(detail) + def mouse_up(self, detail): + if self.child: self.child.mouse_up(detail) + # + def timer(self): + if self.child: self.child.timer() + # + # Upcalls from child to parent + # + def need_mouse(self, child): + self.parent.need_mouse(self) + def no_mouse(self, child): + self.parent.no_mouse(self) + # + def need_timer(self, child): + self.parent.need_timer(self) + def no_timer(self, child): + self.parent.no_timer(self) + # + def begindrawing(self): + return self.parent.begindrawing() + def beginmeasuring(self): + return self.parent.beginmeasuring() + # + def change(self, area): + self.parent.change(area) + def scroll(self, args): + self.parent.scroll(args) + def settimer(self, itimer): + self.parent.settimer(itimer) diff --git a/lib/VUMeter.py b/lib/VUMeter.py new file mode 100644 index 0000000..c862452 --- /dev/null +++ b/lib/VUMeter.py @@ -0,0 +1,47 @@ +# Module 'VUMeter' + +import audio +from StripChart import StripChart + +K = 1024 +Rates = [0, 32*K, 16*K, 8*K] + +class VUMeter() = StripChart(): + # + # Override define() and timer() methods + # + def define(self, parent): + self = StripChart.define(self, (parent, 128)) + self.parent.need_timer(self) + self.sampling = 0 + self.rate = 3 + self.enable(0) + return self + # + def timer(self): + if self.sampling: + chunk = audio.wait_recording() + self.sampling = 0 + nums = audio.chr2num(chunk) + ampl = max(abs(min(nums)), abs(max(nums))) + self.append(ampl) + if self.enabled and not self.sampling: + audio.setrate(self.rate) + size = Rates[self.rate]/10 + size = size/48*48 + audio.start_recording(size) + self.sampling = 1 + if self.sampling: + self.parent.settimer(1) + # + # New methods: start() and stop() + # + def stop(self): + if self.sampling: + chunk = audio.stop_recording() + self.sampling = 0 + self.enable(0) + # + def start(self): + self.enable(1) + self.timer() diff --git a/lib/WindowParent.py b/lib/WindowParent.py new file mode 100644 index 0000000..1e18930 --- /dev/null +++ b/lib/WindowParent.py @@ -0,0 +1,101 @@ +# A 'WindowParent' is the only module that uses real stdwin functionality. +# It is the root of the tree. +# It should have exactly one child when realized. + +import stdwin +from stdwinevents import * + +from TransParent import ManageOneChild + +Error = 'WindowParent.Error' # Exception + +class WindowParent() = ManageOneChild(): + # + def create(self, (title, size)): + self.title = title + self.size = size # (width, height) + self._reset() + return self + # + def _reset(self): + self.child = 0 + self.win = 0 + self.itimer = 0 + self.do_mouse = 0 + self.do_timer = 0 + # + def destroy(self): + if self.child: self.child.destroy() + self._reset() + # + def need_mouse(self, child): self.do_mouse = 1 + def no_mouse(self, child): self.do_mouse = 0 + # + def need_timer(self, child): self.do_timer = 1 + def no_timer(self, child): self.do_timer = 0 + # + def realize(self): + if self.win: + raise Error, 'realize(): called twice' + if not self.child: + raise Error, 'realize(): no child' + size = self.child.minsize(self.beginmeasuring()) + self.size = max(self.size[0], size[0]), \ + max(self.size[1], size[1]) + #stdwin.setdefwinsize(self.size) + # XXX Compensate stdwin bug: + stdwin.setdefwinsize(self.size[0]+4, self.size[1]+2) + self.win = stdwin.open(self.title) + if self.itimer: + self.win.settimer(self.itimer) + bounds = (0, 0), self.win.getwinsize() + self.child.setbounds(bounds) + # + def beginmeasuring(self): + # Return something with which a child can measure text + if self.win: + return self.win.begindrawing() + else: + return stdwin + # + def begindrawing(self): + if self.win: + return self.win.begindrawing() + else: + raise Error, 'begindrawing(): not realized yet' + # + def change(self, area): + if self.win: + self.win.change(area) + # + def scroll(self, args): + if self.win: + self.win.scroll(args) + # + def settimer(self, itimer): + if self.win: + self.win.settimer(itimer) + else: + self.itimer = itimer + # + # Only call dispatch if we have a child + # + def dispatch(self, (type, win, detail)): + if win <> self.win: + return + elif type = WE_DRAW: + d = self.win.begindrawing() + self.child.draw(d, detail) + elif type = WE_MOUSE_DOWN: + if self.do_mouse: self.child.mouse_down(detail) + elif type = WE_MOUSE_MOVE: + if self.do_mouse: self.child.mouse_move(detail) + elif type = WE_MOUSE_UP: + if self.do_mouse: self.child.mouse_up(detail) + elif type = WE_TIMER: + if self.do_timer: self.child.timer() + elif type = WE_SIZE: + self.win.change((0, 0), (10000, 10000)) # XXX + bounds = (0, 0), self.win.getwinsize() + self.child.setbounds(bounds) + # diff --git a/lib/adv.py b/lib/adv.py new file mode 100644 index 0000000..f32f91b --- /dev/null +++ b/lib/adv.py @@ -0,0 +1,366 @@ +#! /ufs/guido/bin/sgi/python + +# Module 'adv' -- text-oriented adventure game. + + +# Name a constant that may once appear in the language... + +def return_nil(): return +nil = return_nil() + + +# Copy of string.split() (to avoid loading all of string.py) + +whitespace = ' \t\n' +def split(s): + res = [] + i, n = 0, len(s) + while i < n: + while i < n and s[i] in whitespace: i = i+1 + if i = n: break + j = i + while j < n and s[j] not in whitespace: j = j+1 + res.append(s[i:j]) + i = j + return res + + +# Constants to name directions + +N = 'north' +S = 'south' +W = 'west' +E = 'east' +U = 'up' +D = 'down' +NW = 'nw' +NE = 'ne' +SW = 'sw' +SE = 'se' + + +# Constants to name other commands + +INVENT = 'invent' +LOOK = 'look' +BACK = 'back' +HELP = 'help' +GET = 'get' +PUT = 'put' + + +# Aliases recognized by the parser + +alias = {} +alias['n'] = N +alias['s'] = S +alias['e'] = E +alias['w'] = W +alias['u'] = U +alias['d'] = D +alias['i'] = INVENT +alias['l'] = LOOK +alias['b'] = BACK +alias['take'] = GET +alias['drop'] = PUT + + +# Normalize a command, in place: truncate words to 6 chars, and expand aliases. + +def normalize(cmd): + for i in range(len(cmd)): + word = cmd[i][:6] + if alias.has_key(word): + word = alias[word] + cmd[i] = word + + +# The Object class describes objects that the player can carry around. + +class Object(): + def init(this, name): + this.name = name + return this + def describe(this): + print 'A', this.name + '.' + def get(this, (player, room)): + del room.objects[this.name] + player.objects[this.name] = this + def put(this, (player, room)): + del player.objects[this.name] + room.objects[this.name] = this + + +# The Player class embodies first person control. + +class Player(): + # Set initial state, except current room. + def init(self, initial_room): + self.blind = 0 + self.here = initial_room + self.prev = nil + self.objects = {} + return self + # Read and execute commands forever. + def play(self): + self.here.casualdescribe() + while 1: + self.move() + # Read and execute one command. + def move(self): + next = self.here.parser() + if next and next <> self.here: + self.prev = self.here + self.here = next + if not self.blind: + self.here.casualdescribe() + # Print inventory. + def inventory(self): + if not self.objects: + print 'You aren\'t carrying anything.' + return + print 'You are carrying:' + for key in self.objects.keys(): + self.objects[key].describe() + # Go back to previous room. + def back(self): + if self.prev: return self.prev + print 'You can\'t go back now.' + # Get an object from the room. + def get(self, name): + if self.here.objects.has_key(name): + self.here.objects[name].get(self, self.here) + else: + print 'I see no', name, 'here.' + # Put an object in the room. + def put(self, name): + if self.objects.has_key(name): + self.objects[name].put(self, self.here) + else: + print 'You have no', name, 'with you.' + + +# The Room class describes a generic room. +# Rooms with special properties are defined by derived classes +# that override certain operations. + +class Room(): + # Initialize a featureless room. + def init(here, name): + here.seen = 0 + here.name = name + here.exits = {} + here.objects = {} + here.description = [] + return here + # Add an object to the room. Used during initialization. + def add(here, obj): + here.objects[obj.name] = obj + # Print a casual description. + def casualdescribe(here): + if here.seen: + print here.name + '.' + here.listobjects() + return + here.seen = 1 + here.describe() + # Print a full description, including all exits and objects seen. + def describe(here): + if not here.description: + print here.name + '.' + here.listexits() + else: + for line in here.description: print line + here.listobjects() + # List exits. + def listexits(here): + there = here.exits.keys() + if there: + if len(there) = 1: + print 'There is an exit leading', + else: + print 'There are exits leading', + for name in there[:-2]: + print name + ',', + print there[len(there)-2], 'and', + print there[len(there)-1] + '.' + # List objects + def listobjects(here): + if here.objects: + print 'I see:' + for key in here.objects.keys(): + here.objects[key].describe() + # Default parser. Returns next room (possibly the same) or nil. + def parser(here): + cmd = here.getcmd(here.prompt()) + return here.decide(cmd) + # Return default prompt string. + def prompt(here): return '> ' + # Default input routine. Returns a non-empty list of words. + def getcmd(here, prompt): + # Loop until non-empty command gotten + # EOFError and KeyboardInterrupt may be caught elsewhere + while 1: + line = raw_input(prompt) + cmd = split(line) + if cmd: + normalize(cmd) + return cmd + # Default decision routine. Override for room-specific commands. + def decide(here, cmd): + key, args = cmd[0], cmd[1:] + if not args: + if key = N: return here.north() + if key = S: return here.south() + if key = E: return here.east() + if key = W: return here.west() + if key = U: return here.up() + if key = D: return here.down() + if key = NW: return here.nw() + if key = NE: return here.ne() + if key = SW: return here.sw() + if key = SE: return here.se() + if key = LOOK: return here.look() + if key = INVENT: return here.inventory() + if key = BACK: return here.back() + if here.objects.has_key(key): + print 'What do you want to do with the', key+'?' + else: + print 'Huh?' + return + if key = GET: + for arg in args: + player.get(arg) + return + if key = PUT: + for arg in args: + player.put(arg) + # Standard commands. + def look(here): + here.describe() + def inventory(here): + player.inventory() + def back(here): + return player.back() + # Standard exits. + def north(here): return here.take_exit(N) + def south(here): return here.take_exit(S) + def west(here): return here.take_exit(W) + def east(here): return here.take_exit(E) + def up(here): return here.take_exit(U) + def down(here): return here.take_exit(D) + def nw(here): return here.take_exit(NW) + def ne(here): return here.take_exit(NE) + def sw(here): return here.take_exit(SW) + def se(here): return here.take_exit(SE) + # Subroutine for standard exits. + def take_exit(here, key): + if here.exits.has_key(key): + return here.exits[key] + print 'You cannot go in that direction.' + return here + + +# Create the objects we know about. +# Object names begin with 'o_'. + +o_lamp = Object().init('lamp') +o_python = Object().init('python') + + +# Subroutine to connect two rooms. + +def connect(rm1, rm2, dir1, dir2): + if dir1: + rm1.exits[dir1] = rm2 + if dir2: + rm2.exits[dir2] = rm1 + + +# Create the rooms and connect them together. +# Room names begin with 'r_'. + +r_front = Room().init('Front of building') +r_initial = r_front +r_front.description = [ \ + 'You are standing in front of a large, desolate building.', \ + 'Huge neon letters spell "CWI". The "I" is blinking.', \ + 'There are entrances north and west from where you are standing.', \ + ] + +r_entrance = Room().init('Entrance') +r_entrance.description = [ \ + 'You are standing in a small entrance room.', \ + 'On the east side is a window to a reception room.', \ + 'South is a door leading outside the building.', \ + 'North is a large hall.' \ + ] + +r_hall_s = Room().init('South of hall') +r_hall_s.description = [ \ + 'You are standing at the south side of a very large hall.', \ + 'There are doors leading west, southwest, south and southeast,', \ + 'and a corridor leads east.', \ + 'The hall continues to the north.' \ + ] + +r_hall_n = Room().init('North of hall') +r_hall_n.description = [ \ + 'You are stanting at the north side of a very large hall.', \ + 'There are corridors leading west, northwest, northeast,', \ + 'an elevator door north, and a door leading outside east.', \ + 'There are stairs leading up, and the hall continues to the south.' \ + ] + +r_reception = Room().init('Reception') + +r_mail = Room().init('Mail room') + +connect(r_front, r_entrance, N, S) +connect(r_entrance, r_hall_s, N, SW) +connect(r_hall_s, r_hall_n, N, S) +connect(r_hall_s, r_reception, S, N) +connect(r_hall_s, r_mail, SE, N) +connect(r_reception, r_mail, E, W) + +r_aud_front = Room().init('Front of Auditorium') +r_aud_back = Room().init('Back of Auditorium') +r_aud_tech = Room().init('Technician\'s room in Auditorium') +r_aud_proj = Room().init('Projection room in Auditorium') + +connect(r_aud_front, r_hall_s, E, W) +connect(r_aud_front, r_aud_back, S, N) +connect(r_aud_back, r_aud_proj, SE, N) +connect(r_aud_front, r_aud_tech, W, E) + +r_floor1 = Room().init('First floor') +r_floor2 = Room().init('Second floor') +r_floor3 = Room().init('Third floor') + +connect(r_hall_n, r_floor1, U, D) +connect(r_floor1, r_floor2, U, D) +connect(r_floor2, r_floor3, U, D) + + +# Drop objects here and there + +r_aud_proj.add(o_python) +r_reception.add(o_lamp) + +# Create an uninitialized player object. +# It is initialized by main(), but must be created here (as global) +# since some Room methods reference it. (Though maybe they shouldn't?) + +player = Player() + + +# Play the game from the beginning. + +def main(): + x = player.init(r_initial) + try: + player.play() + except (EOFError, KeyboardInterrupt): + pass + +main() diff --git a/lib/anywin.py b/lib/anywin.py new file mode 100644 index 0000000..6de3605 --- /dev/null +++ b/lib/anywin.py @@ -0,0 +1,14 @@ +# Module 'anywin' +# Open a file or directory in a window + +import dirwin +import filewin +import path + +def open(name): + print 'opening', name, '...' + if path.isdir(name): + w = dirwin.open(name) + else: + w = filewin.open(name) + return w diff --git a/lib/auds.py b/lib/auds.py new file mode 100644 index 0000000..53db6aa --- /dev/null +++ b/lib/auds.py @@ -0,0 +1,106 @@ +import audio + +RATE = 8192 + +# Initialize the audio stuff +audio.setrate(3) +audio.setoutgain(100) # for speaker + +play = audio.write + +def samp(n): + savegain = audio.getoutgain() + try: + audio.setoutgain(0) + x = raw_input('Hit Enter to sample ' + `n` + ' seconds: ') + return audio.read(n*RATE) + finally: + audio.setoutgain(savegain) + +def echo(s, delay, gain): + return s[:delay] + audio.add(s[delay:], audio.amplify(s, gain, gain)) + +def save(s, file): + f = open(file, 'w') + f.write(s) + +def load(file): + return loadfp(open(file, 'r')) + +def loadfp(fp): + s = '' + while 1: + buf = fp.read(16*1024) + if not buf: break + s = s + buf + return s + +def unbias(s): + if not s: return s + a = audio.chr2num(s) + sum = 0 + for i in a: sum = sum + i + bias = (sum + len(a)/2) / len(a) + print 'Bias value:', bias + if bias: + for i in range(len(a)): + a[i] = a[i] - bias + s = audio.num2chr(a) + return s + +# Stretch by a/b. +# Think of this as converting the sampling rate from a samples/sec +# to b samples/sec. Or, if the input is a bytes long, the output +# will be b bytes long. +# +def stretch(s, a, b): + y = audio.chr2num(s) + m = len(y) + out = [] + n = m * b / a + # i, j will walk through y and out (step 1) + # ib, ja are i*b, j*a and are kept as close together as possible + i, ib = 0, 0 + j, ja = 0, 0 + for j in range(n): + ja = ja+a + while ib < ja: + i = i+1 + ib = ib+b + if i >= m: + break + if ib = ja: + out.append(y[i]) + else: + out.append((y[i]*(ja-(ib-b)) + y[i-1]*(ib-ja)) / b) + return audio.num2chr(out) + +def sinus(freq): # return a 1-second sine wave + from math import sin, pi + factor = 2.0*pi*float(freq)/float(RATE) + list = range(RATE) + for i in list: + list[i] = int(sin(float(i) * factor) * 127.0) + return audio.num2chr(list) + +def softclip(s): + if '\177' not in s and '\200' not in s: + return s + num = audio.chr2num(s) + extremes = (-128, 127) + for i in range(1, len(num)-1): + if num[i] in extremes: + num[i] = (num[i-1] + num[i+1]) / 2 + return audio.num2chr(num) + +def demo(): + gday = load('gday')[1000:6000] + save(gday, 'gday0') + gg = [gday] + for i in range(1, 10): + for g in gg: play(g) + g = stretch(gday, 10, 10-i) + save(g, 'gday' + `i`) + gg.append(g) + while 1: + for g in gg: play(g) diff --git a/lib/calendar.py b/lib/calendar.py new file mode 100644 index 0000000..97ca86f --- /dev/null +++ b/lib/calendar.py @@ -0,0 +1,213 @@ +# module calendar + +############################## +# Calendar support functions # +############################## + +# This is based on UNIX ctime() et al. (also Standard C and POSIX) +# Subtle but crucial differences: +# - the order of the elements of a 'struct tm' differs, to ease sorting +# - months numbers are 1-12, not 0-11; month arrays have a dummy element 0 +# - Monday is the first day of the week (numbered 0) + +# These are really parameters of the 'time' module: +epoch = 1970 # Time began on January 1 of this year (00:00:00 UCT) +day_0 = 3 # The epoch begins on a Thursday (Monday = 0) + +# Return 1 for leap years, 0 for non-leap years +def isleap(year): + return year % 4 = 0 and (year % 100 <> 0 or year % 400 = 0) + +# Constants for months referenced later +January = 1 +February = 2 + +# Number of days per month (except for February in leap years) +mdays = (0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) + +# Exception raised for bad input (with string parameter for details) +error = 'calendar error' + +# Turn seconds since epoch into calendar time +def gmtime(secs): + if secs < 0: raise error, 'negative input to gmtime()' + mins, secs = divmod(secs, 60) + hours, mins = divmod(mins, 60) + days, hours = divmod(hours, 24) + wday = (days + day_0) % 7 + year = epoch + # XXX Most of the following loop can be replaced by one division + while 1: + yd = 365 + isleap(year) + if days < yd: break + days = days - yd + year = year + 1 + yday = days + month = January + while 1: + md = mdays[month] + (month = February and isleap(year)) + if days < md: break + days = days - md + month = month + 1 + return year, month, days + 1, hours, mins, secs, yday, wday + # XXX Week number also? + +# Return number of leap years in range [y1, y2) +# Assume y1 <= y2 and no funny (non-leap century) years +def leapdays(y1, y2): + return (y2+3)/4 - (y1+3)/4 + +# Inverse of gmtime(): +# Turn UCT calendar time (less yday, wday) into seconds since epoch +def mktime(year, month, day, hours, mins, secs): + days = day - 1 + for m in range(January, month): days = days + mdays[m] + if isleap(year) and month > February: days = days+1 + days = days + (year-epoch)*365 + leapdays(epoch, year) + return ((days*24 + hours)*60 + mins)*60 + secs + +# Full and abbreviated names of weekdays +day_name = ('Monday', 'Tuesday', 'Wednesday', 'Thursday') +day_name = day_name + ('Friday', 'Saturday', 'Sunday') +day_abbr = ('Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun') + +# Full and abbreviated of months (1-based arrays!!!) +month_name = ('', 'January', 'February', 'March', 'April') +month_name = month_name + ('May', 'June', 'July', 'August') +month_name = month_name + ('September', 'October', 'November', 'December') +month_abbr = (' ', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun') +month_abbr = month_abbr + ('Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec') + +# Zero-fill string to two positions (helper for asctime()) +def dd(s): + while len(s) < 2: s = '0' + s + return s + +# Blank-fill string to two positions (helper for asctime()) +def zd(s): + while len(s) < 2: s = ' ' + s + return s + +# Turn calendar time as returned by gmtime() into a string +# (the yday parameter is for compatibility with gmtime()) +def asctime(year, month, day, hours, mins, secs, yday, wday): + s = day_abbr[wday] + ' ' + month_abbr[month] + ' ' + zd(`day`) + s = s + ' ' + dd(`hours`) + ':' + dd(`mins`) + ':' + dd(`secs`) + return s + ' ' + `year` + +# Localization: Minutes West from Greenwich +# timezone = -2*60 # Middle-European time with DST on +timezone = 5*60 # EST (sigh -- THINK time() doesn't return UCT) + +# Local time ignores DST issues for now -- adjust 'timezone' to fake it +def localtime(secs): + return gmtime(secs - timezone*60) + +# UNIX-style ctime (except it doesn't append '\n'!) +def ctime(secs): + return asctime(localtime(secs)) + +###################### +# Non-UNIX additions # +###################### + +# Calendar printing etc. + +# Return weekday (0-6 ~ Mon-Sun) for year (1970-...), month (1-12), day (1-31) +def weekday(year, month, day): + secs = mktime(year, month, day, 0, 0, 0) + days = secs / (24*60*60) + return (days + day_0) % 7 + +# Return weekday (0-6 ~ Mon-Sun) and number of days (28-31) for year, month +def monthrange(year, month): + day1 = weekday(year, month, 1) + ndays = mdays[month] + (month = February and isleap(year)) + return day1, ndays + +# Return a matrix representing a month's calendar +# Each row represents a week; days outside this month are zero +def _monthcalendar(year, month): + day1, ndays = monthrange(year, month) + rows = [] + r7 = range(7) + day = 1 - day1 + while day <= ndays: + row = [0, 0, 0, 0, 0, 0, 0] + for i in r7: + if 1 <= day <= ndays: row[i] = day + day = day + 1 + rows.append(row) + return rows + +# Caching interface to _monthcalendar +mc_cache = {} +def monthcalendar(year, month): + key = `year` + month_abbr[month] + try: + return mc_cache[key] + except RuntimeError: + mc_cache[key] = ret = _monthcalendar(year, month) + return ret + +# Center a string in a field +def center(str, width): + n = width - len(str) + if n < 0: return str + return ' '*(n/2) + str + ' '*(n-n/2) + +# XXX The following code knows that print separates items with space! + +# Print a single week (no newline) +def prweek(week, width): + for day in week: + if day = 0: print ' '*width, + else: + if width > 2: print ' '*(width-3), + if day < 10: print '', + print day, + +# Return a header for a week +def weekheader(width): + str = '' + for i in range(7): + if str: str = str + ' ' + str = str + day_abbr[i%7][:width] + return str + +# Print a month's calendar +def prmonth(year, month): + print weekheader(3) + for week in monthcalendar(year, month): + prweek(week, 3) + print + +# Spacing between month columns +spacing = ' ' + +# 3-column formatting for year calendars +def format3c(a, b, c): + print center(a, 20), spacing, center(b, 20), spacing, center(c, 20) + +# Print a year's calendar +def prcal(year): + header = weekheader(2) + format3c('', `year`, '') + for q in range(January, January+12, 3): + print + format3c(month_name[q], month_name[q+1], month_name[q+2]) + format3c(header, header, header) + data = [] + height = 0 + for month in range(q, q+3): + cal = monthcalendar(year, month) + if len(cal) > height: height = len(cal) + data.append(cal) + for i in range(height): + for cal in data: + if i >= len(cal): + print ' '*20, + else: + prweek(cal[i], 2) + print spacing, + print diff --git a/lib/clock.py b/lib/clock.py new file mode 100644 index 0000000..ec5301e --- /dev/null +++ b/lib/clock.py @@ -0,0 +1,202 @@ +# 'klok' -- A simple alarm clock + +# The alarm can be set at 5 minute intervals on a 12 hour basis. +# It is controlled with the mouse: +# - Click and drag around the circle to set the alarm. +# - Click far outside the circle to clear the alarm. +# - Click near the center to set the alarm at the last time set. +# The alarm time is indicated by a small triangle just outside the circle, +# and also by a digital time at the bottom. +# The indicators disappear when the alarm is not set. +# When the alarm goes off, it beeps every minute for five minutes, +# and the clock turns into inverse video. +# Click or activate the window to turn the ringing off. + +import stdwin +from stdwinevents import WE_MOUSE_DOWN, WE_MOUSE_MOVE, WE_MOUSE_UP, \ + WE_TIMER, WE_DRAW, WE_SIZE, WE_CLOSE, WE_ACTIVATE +import time +from math import sin, cos, atan2, pi, sqrt + +DEFWIDTH, DEFHEIGHT = 200, 200 + +mouse_events = (WE_MOUSE_DOWN, WE_MOUSE_MOVE, WE_MOUSE_UP) +origin = 0, 0 +faraway = 2000, 2000 +everywhere = origin, faraway + +class struct(): pass # A class to declare featureless objects + +G = struct() # Global variables (most set in setdimensions()) +G.tzdiff = 5*3600 # THINK computes UCT from local time assuming EST! + +A = struct() # Globals used by the alarm +A.set = 1 # True when alarm is set +A.time = 11*60 + 40 # Time when alarm must go off +A.ring = 0 # True when alarm is ringing + +def main(): + try: + realmain() + except KeyboardInterrupt: + print 'KeyboardInterrupt' + finally: + G.w = 0 + +def realmain(): + setdimensions(DEFWIDTH, DEFHEIGHT) + stdwin.setdefwinsize(G.farcorner) + G.w = stdwin.open('klok') + settimer() + while 1: + type, window, detail = stdwin.getevent() + if type = WE_DRAW: + drawproc(detail) + elif type = WE_TIMER: + settimer() + drawproc(everywhere) + elif type in mouse_events: + mouseclick(type, detail) + elif type = WE_ACTIVATE: + if A.ring: + # Turn the ringing off + A.ring = 0 + G.w.begindrawing().invert(G.mainarea) + elif type = WE_SIZE: + G.w.change(everywhere) + width, height = G.w.getwinsize() + height = height - stdwin.lineheight() + setdimensions(width, height) + elif type = WE_CLOSE: + break + +def setdimensions(width, height): + if width < height: size = width + else: size = height + halfwidth = width/2 + halfheight = height/2 + G.center = halfwidth, halfheight + G.radius = size*45/100 + G.width = width + G.height = height + G.corner = width, height + G.mainarea = origin, G.corner + G.lineheight = stdwin.lineheight() + G.farcorner = width, height + G.lineheight + G.statusarea = (0, height), G.farcorner + G.fullarea = origin, G.farcorner + +def settimer(): + now = getlocaltime() + G.times = calctime(now) + delay = 61 - now % 60 + G.w.settimer(10 * delay) + minutes = (now/60) % 720 + if A.ring: + # Is it time to stop the alarm ringing? + since = (minutes - A.time + 720) % 720 + if since >= 5: + # Stop it now + A.ring = 0 + else: + # Ring again, once every minute + stdwin.fleep() + elif A.set and minutes = A.time: + # Start the alarm ringing + A.ring = 1 + stdwin.fleep() + +def drawproc(area): + hours, minutes, seconds = G.times + d = G.w.begindrawing() + d.cliprect(area) + d.erase(everywhere) + d.circle(G.center, G.radius) + d.line(G.center, calcpoint(hours*30 + minutes/2, 0.6)) + d.line(G.center, calcpoint(minutes*6, 1.0)) + str = dd(hours) + ':' + dd(minutes) + p = (G.width - d.textwidth(str))/2, G.height * 3 / 4 + d.text(p, str) + if A.set: + drawalarm(d) + drawalarmtime(d) + if A.ring: + d.invert(G.mainarea) + +def mouseclick(type, detail): + d = G.w.begindrawing() + if A.ring: + # First turn the ringing off + A.ring = 0 + d.invert(G.mainarea) + h, v = detail[0] + ch, cv = G.center + x, y = h-ch, cv-v + dist = sqrt(x*x + y*y) / float(G.radius) + if dist > 1.2: + if A.set: + drawalarm(d) + erasealarmtime(d) + A.set = 0 + elif dist < 0.8: + if not A.set: + A.set = 1 + drawalarm(d) + drawalarmtime(d) + else: + # Convert to half-degrees (range 0..720) + alpha = atan2(y, x) + hdeg = alpha*360.0/pi + hdeg = 180.0 - hdeg + hdeg = (hdeg + 720.0) % 720.0 + atime = 5*int(hdeg/5.0 + 0.5) + if atime <> A.time or not A.set: + if A.set: + drawalarm(d) + erasealarmtime(d) + A.set = 1 + A.time = atime + drawalarm(d) + drawalarmtime(d) + +def drawalarm(d): + p1 = calcpoint(float(A.time)/2.0, 1.02) + p2 = calcpoint(float(A.time)/2.0 - 4.0, 1.1) + p3 = calcpoint(float(A.time)/2.0 + 4.0, 1.1) + d.xorline(p1, p2) + d.xorline(p2, p3) + d.xorline(p3, p1) + +def erasealarmtime(d): + d.erase(G.statusarea) + +def drawalarmtime(d): + # A.time is in the range 0..720 with origin at 12 o'clock + # Convert to hours (0..12) and minutes (12*(0..60)) + hh = A.time/60 + mm = A.time%60 + str = 'Alarm@' + dd(hh) + ':' + dd(mm) + p1 = (G.width - d.textwidth(str))/2, G.height + d.text(p1, str) + +def calctime(now): + seconds = now % 60 + minutes = (now/60) % 60 + hours = (now/3600) % 12 + return hours, minutes, seconds + +def calcpoint(degrees, size): + alpha = pi/2.0 - float(degrees) * pi/180.0 + x, y = cos(alpha), sin(alpha) + h, v = G.center + r = float(G.radius) + return h + int(x*size*r), v - int(y*size*r) + +def dd(n): + s = `n` + return '0'*(2-len(s)) + s + +def getlocaltime(): + return time.time() - G.tzdiff + +#main() diff --git a/lib/cmp.py b/lib/cmp.py new file mode 100644 index 0000000..8dc315e --- /dev/null +++ b/lib/cmp.py @@ -0,0 +1,61 @@ +# Module 'cmp' + +# Efficiently compare files, boolean outcome only (equal / not equal). + +# Tricks (used in this order): +# - Files with identical type, size & mtime are assumed to be clones +# - Files with different type or size cannot be identical +# - We keep a cache of outcomes of earlier comparisons +# - We don't fork a process to run 'cmp' but read the files ourselves + +import posix + +cache = {} + +def cmp(f1, f2): # Compare two files, use the cache if possible. + # Return 1 for identical files, 0 for different. + # Raise exceptions if either file could not be statted, read, etc. + s1, s2 = sig(posix.stat(f1)), sig(posix.stat(f2)) + if s1[0] <> 8 or s2[0] <> 8: + # Either is a not a plain file -- always report as different + return 0 + if s1 = s2: + # type, size & mtime match -- report same + return 1 + if s1[:2] <> s2[:2]: # Types or sizes differ, don't bother + # types or sizes differ -- report different + return 0 + # same type and size -- look in the cache + key = f1 + ' ' + f2 + try: + cs1, cs2, outcome = cache[key] + # cache hit + if s1 = cs1 and s2 = cs2: + # cached signatures match + return outcome + # stale cached signature(s) + except RuntimeError: + # cache miss + pass + # really compare + outcome = do_cmp(f1, f2) + cache[key] = s1, s2, outcome + return outcome + +def sig(st): # Return signature (i.e., type, size, mtime) from raw stat data + # 0-5: st_mode, st_ino, st_dev, st_nlink, st_uid, st_gid + # 6-9: st_size, st_atime, st_mtime, st_ctime + type = st[0] / 4096 + size = st[6] + mtime = st[8] + return type, size, mtime + +def do_cmp(f1, f2): # Compare two files, really + bufsize = 8096 # Could be tuned + fp1 = open(f1, 'r') + fp2 = open(f2, 'r') + while 1: + b1 = fp1.read(bufsize) + b2 = fp2.read(bufsize) + if b1 <> b2: return 0 + if not b1: return 1 diff --git a/lib/cmpcache.py b/lib/cmpcache.py new file mode 100644 index 0000000..4aea356 --- /dev/null +++ b/lib/cmpcache.py @@ -0,0 +1,68 @@ +# Module 'cmpcache' +# +# Efficiently compare files, boolean outcome only (equal / not equal). +# +# Tricks (used in this order): +# - Use the statcache module to avoid statting files more than once +# - Files with identical type, size & mtime are assumed to be clones +# - Files with different type or size cannot be identical +# - We keep a cache of outcomes of earlier comparisons +# - We don't fork a process to run 'cmp' but read the files ourselves + +import posix +from stat import * +import statcache + + +# The cache. +# +cache = {} + + +# Compare two files, use the cache if possible. +# May raise posix.error if a stat or open of either fails. +# +def cmp(f1, f2): + # Return 1 for identical files, 0 for different. + # Raise exceptions if either file could not be statted, read, etc. + s1, s2 = sig(statcache.stat(f1)), sig(statcache.stat(f2)) + if not S_ISREG(s1[0]) or not S_ISREG(s2[0]): + # Either is a not a plain file -- always report as different + return 0 + if s1 = s2: + # type, size & mtime match -- report same + return 1 + if s1[:2] <> s2[:2]: # Types or sizes differ, don't bother + # types or sizes differ -- report different + return 0 + # same type and size -- look in the cache + key = f1 + ' ' + f2 + if cache.has_key(key): + cs1, cs2, outcome = cache[key] + # cache hit + if s1 = cs1 and s2 = cs2: + # cached signatures match + return outcome + # stale cached signature(s) + # really compare + outcome = do_cmp(f1, f2) + cache[key] = s1, s2, outcome + return outcome + +# Return signature (i.e., type, size, mtime) from raw stat data. +# +def sig(st): + return S_IFMT(st[ST_MODE]), st[ST_SIZE], st[ST_MTIME] + +# Compare two files, really. +# +def do_cmp(f1, f2): + #print ' cmp', f1, f2 # XXX remove when debugged + bufsize = 8096 # Could be tuned + fp1 = open(f1, 'r') + fp2 = open(f2, 'r') + while 1: + b1 = fp1.read(bufsize) + b2 = fp2.read(bufsize) + if b1 <> b2: return 0 + if not b1: return 1 diff --git a/lib/commands.py b/lib/commands.py new file mode 100644 index 0000000..3cb6e69 --- /dev/null +++ b/lib/commands.py @@ -0,0 +1,77 @@ +# Module 'commands' +# +# Various tools for executing commands and looking at their output and status. + +import rand +import posix +import stat +import path + + +# Get 'ls -l' status for an object into a string +# +def getstatus(file): + return getoutput('ls -ld' + mkarg(file)) + + +# Get the output from a shell command into a string. +# The exit status is ignored; a trailing newline is stripped. +# Assume the command will work with ' >tempfile 2>&1' appended. +# XXX This should use posix.popen() instead, should it exist. +# +def getoutput(cmd): + return getstatusoutput(cmd)[1] + + +# Ditto but preserving the exit status. +# Returns a pair (sts, output) +# +def getstatusoutput(cmd): + tmp = '/usr/tmp/wdiff' + `rand.rand()` + sts = -1 + try: + sts = posix.system(cmd + ' >' + tmp + ' 2>&1') + text = readfile(tmp) + finally: + altsts = posix.system('rm -f ' + tmp) + if text[-1:] = '\n': text = text[:-1] + return sts, text + + +# Return a string containing a file's contents. +# +def readfile(fn): + st = posix.stat(fn) + size = st[stat.ST_SIZE] + if not size: return '' + try: + fp = open(fn, 'r') + except: + raise posix.error, 'readfile(' + fn + '): open failed' + try: + return fp.read(size) + except: + raise posix.error, 'readfile(' + fn + '): read failed' + + +# Make command argument from directory and pathname (prefix space, add quotes). +# +def mk2arg(head, x): + return mkarg(path.cat(head, x)) + + +# Make a shell command argument from a string. +# Two strategies: enclose in single quotes if it contains none; +# otherwis, enclose in double quotes and prefix quotable characters +# with backslash. +# +def mkarg(x): + if '\'' not in x: + return ' \'' + x + '\'' + s = ' "' + for c in x: + if c in '\\$"': + s = s + '\\' + s = s + c + s = s + '"' + return s diff --git a/lib/dircache.py b/lib/dircache.py new file mode 100644 index 0000000..c14db3e --- /dev/null +++ b/lib/dircache.py @@ -0,0 +1,36 @@ +# Module 'dircache' +# +# Return a sorted list of the files in a POSIX directory, using a cache +# to avoid reading the directory more often than necessary. +# Also contains a subroutine to append slashes to directories. + +import posix +import path + +cache = {} + +def listdir(path): # List directory contents, using cache + try: + cached_mtime, list = cache[path] + del cache[path] + except RuntimeError: + cached_mtime, list = -1, [] + try: + mtime = posix.stat(path)[8] + except posix.error: + return [] + if mtime <> cached_mtime: + try: + list = posix.listdir(path) + except posix.error: + return [] + list.sort() + cache[path] = mtime, list + return list + +opendir = listdir # XXX backward compatibility + +def annotate(head, list): # Add '/' suffixes to directories + for i in range(len(list)): + if path.isdir(path.cat(head, list[i])): + list[i] = list[i] + '/' diff --git a/lib/dircmp.py b/lib/dircmp.py new file mode 100644 index 0000000..31e712c --- /dev/null +++ b/lib/dircmp.py @@ -0,0 +1,205 @@ +# Module 'dirmp' +# +# Defines a class to build directory diff tools on. + +import posix + +import path + +import dircache +import cmpcache +import statcache +from stat import * + +# Directory comparison class. +# +class dircmp(): + # + def new(dd, (a, b)): # Initialize + dd.a = a + dd.b = b + # Properties that caller may change before callingdd. run(): + dd.hide = ['.', '..'] # Names never to be shown + dd.ignore = ['RCS', 'tags'] # Names ignored in comparison + # + return dd + # + def run(dd): # Compare everything except common subdirectories + dd.a_list = filter(dircache.listdir(dd.a), dd.hide) + dd.b_list = filter(dircache.listdir(dd.b), dd.hide) + dd.a_list.sort() + dd.b_list.sort() + dd.phase1() + dd.phase2() + dd.phase3() + # + def phase1(dd): # Compute common names + dd.a_only = [] + dd.common = [] + for x in dd.a_list: + if x in dd.b_list: + dd.common.append(x) + else: + dd.a_only.append(x) + # + dd.b_only = [] + for x in dd.b_list: + if x not in dd.common: + dd.b_only.append(x) + # + def phase2(dd): # Distinguish files, directories, funnies + dd.common_dirs = [] + dd.common_files = [] + dd.common_funny = [] + # + for x in dd.common: + a_path = path.cat(dd.a, x) + b_path = path.cat(dd.b, x) + # + ok = 1 + try: + a_stat = statcache.stat(a_path) + except posix.error, why: + # print 'Can\'t stat', a_path, ':', why[1] + ok = 0 + try: + b_stat = statcache.stat(b_path) + except posix.error, why: + # print 'Can\'t stat', b_path, ':', why[1] + ok = 0 + # + if ok: + a_type = S_IFMT(a_stat[ST_MODE]) + b_type = S_IFMT(b_stat[ST_MODE]) + if a_type <> b_type: + dd.common_funny.append(x) + elif S_ISDIR(a_type): + dd.common_dirs.append(x) + elif S_ISREG(a_type): + dd.common_files.append(x) + else: + dd.common_funny.append(x) + else: + dd.common_funny.append(x) + # + def phase3(dd): # Find out differences between common files + xx = cmpfiles(dd.a, dd.b, dd.common_files) + dd.same_files, dd.diff_files, dd.funny_files = xx + # + def phase4(dd): # Find out differences between common subdirectories + # A new dircmp object is created for each common subdirectory, + # these are stored in a dictionary indexed by filename. + # The hide and ignore properties are inherited from the parent + dd.subdirs = {} + for x in dd.common_dirs: + a_x = path.cat(dd.a, x) + b_x = path.cat(dd.b, x) + dd.subdirs[x] = newdd = dircmp().new(a_x, b_x) + newdd.hide = dd.hide + newdd.ignore = dd.ignore + newdd.run() + # + def phase4_closure(dd): # Recursively call phase4() on subdirectories + dd.phase4() + for x in dd.subdirs.keys(): + dd.subdirs[x].phase4_closure() + # + def report(dd): # Print a report on the differences between a and b + # Assume that phases 1 to 3 have been executed + # Output format is purposely lousy + print 'diff', dd.a, dd.b + if dd.a_only: + print 'Only in', dd.a, ':', dd.a_only + if dd.b_only: + print 'Only in', dd.b, ':', dd.b_only + if dd.same_files: + print 'Identical files :', dd.same_files + if dd.diff_files: + print 'Differing files :', dd.diff_files + if dd.funny_files: + print 'Trouble with common files :', dd.funny_files + if dd.common_dirs: + print 'Common subdirectories :', dd.common_dirs + if dd.common_funny: + print 'Common funny cases :', dd.common_funny + # + def report_closure(dd): # Print reports on dd and on subdirs + # If phase 4 hasn't been done, no subdir reports are printed + dd.report() + try: + x = dd.subdirs + except NameError: + return # No subdirectories computed + for x in dd.subdirs.keys(): + print + dd.subdirs[x].report_closure() + # + def report_phase4_closure(dd): # Report and do phase 4 recursively + dd.report() + dd.phase4() + for x in dd.subdirs.keys(): + print + dd.subdirs[x].report_phase4_closure() + + +# Compare common files in two directories. +# Return: +# - files that compare equal +# - files that compare different +# - funny cases (can't stat etc.) +# +def cmpfiles(a, b, common): + res = ([], [], []) + for x in common: + res[cmp(path.cat(a, x), path.cat(b, x))].append(x) + return res + + +# Compare two files. +# Return: +# 0 for equal +# 1 for different +# 2 for funny cases (can't stat, etc.) +# +def cmp(a, b): + try: + if cmpcache.cmp(a, b): return 0 + return 1 + except posix.error: + return 2 + + +# Remove a list item. +# NB: This modifies the list argument. +# +def remove(list, item): + for i in range(len(list)): + if list[i] = item: + del list[i] + break + + +# Return a copy with items that occur in skip removed. +# +def filter(list, skip): + result = [] + for item in list: + if item not in skip: result.append(item) + return result + + +# Demonstration and testing. +# +def demo(): + import sys + import getopt + options, args = getopt.getopt(sys.argv[1:], 'r') + if len(args) <> 2: raise getopt.error, 'need exactly two args' + dd = dircmp().new(args[0], args[1]) + dd.run() + if ('-r', '') in options: + dd.report_phase4_closure() + else: + dd.report() + +# demo() diff --git a/lib/dirwin.py b/lib/dirwin.py new file mode 100644 index 0000000..76e0b5e --- /dev/null +++ b/lib/dirwin.py @@ -0,0 +1,29 @@ +# Module 'dirwin' + +# Directory windows, a subclass of listwin + +import gwin +import listwin +import anywin +import path +import dircache + +def action(w, string, i, detail): + (h, v), clicks, button, mask = detail + if clicks = 2: + name = path.cat(w.name, string) + try: + w2 = anywin.open(name) + w2.parent = w + except posix.error, why: + stdwin.message('Can\'t open ' + name + ': ' + why[1]) + +def open(name): + name = path.cat(name, '') + list = dircache.opendir(name)[:] + list.sort() + dircache.annotate(name, list) + w = listwin.open(name, list) + w.name = name + w.action = action + return w diff --git a/lib/dis.py b/lib/dis.py new file mode 100644 index 0000000..d13b08f --- /dev/null +++ b/lib/dis.py @@ -0,0 +1,176 @@ +# Disassembler + +import sys +import string + +def dis(): + tb = sys.last_traceback + while tb.tb_next: tb = tb.tb_next + distb(tb) + +def distb(tb): + disassemble(tb.tb_frame.f_code, tb.tb_lasti) + +def disco(co): + disassemble(co, -1) + +def disassemble(co, lasti): + code = co.co_code + labels = findlabels(code) + n = len(code) + i = 0 + while i < n: + c = code[i] + op = ord(c) + if op = SET_LINENO and i > 0: print # Extra blank line + if i = lasti: print '-->', + else: print ' ', + if i in labels: print '>>', + else: print ' ', + print string.rjust(`i`, 4), + print string.ljust(opname[op], 15), + i = i+1 + if op >= HAVE_ARGUMENT: + oparg = ord(code[i]) + ord(code[i+1])*256 + i = i+2 + print string.rjust(`oparg`, 5), + if op in hasconst: + print '(' + `co.co_consts[oparg]` + ')', + elif op in hasname: + print '(' + co.co_names[oparg] + ')', + elif op in hasjrel: + print '(to ' + `i + oparg` + ')', + print + +def findlabels(code): + labels = [] + n = len(code) + i = 0 + while i < n: + c = code[i] + op = ord(c) + i = i+1 + if op >= HAVE_ARGUMENT: + oparg = ord(code[i]) + ord(code[i+1])*256 + i = i+2 + label = -1 + if op in hasjrel: + label = i+oparg + elif op in hasjabs: + label = oparg + if label >= 0: + if label not in labels: + labels.append(label) + return labels + +hasconst = [] +hasname = [] +hasjrel = [] +hasjabs = [] + +opname = range(256) +for op in opname: opname[op] = '<' + `op` + '>' + +def def_op(name, op): + opname[op] = name + +def name_op(name, op): + opname[op] = name + hasname.append(op) + +def jrel_op(name, op): + opname[op] = name + hasjrel.append(op) + +def jabs_op(name, op): + opname[op] = name + hasjabs.append(op) + +# Instruction opcodes for compiled code + +def_op('STOP_CODE', 0) +def_op('POP_TOP', 1) +def_op('ROT_TWO', 2) +def_op('ROT_THREE', 3) +def_op('DUP_TOP', 4) + +def_op('UNARY_POSITIVE', 10) +def_op('UNARY_NEGATIVE', 11) +def_op('UNARY_NOT', 12) +def_op('UNARY_CONVERT', 13) +def_op('UNARY_CALL', 14) + +def_op('BINARY_MULTIPLY', 20) +def_op('BINARY_DIVIDE', 21) +def_op('BINARY_MODULO', 22) +def_op('BINARY_ADD', 23) +def_op('BINARY_SUBTRACT', 24) +def_op('BINARY_SUBSCR', 25) +def_op('BINARY_CALL', 26) + +def_op('SLICE+0', 30) +def_op('SLICE+1', 31) +def_op('SLICE+2', 32) +def_op('SLICE+3', 33) + +def_op('STORE_SLICE+0', 40) +def_op('STORE_SLICE+1', 41) +def_op('STORE_SLICE+2', 42) +def_op('STORE_SLICE+3', 43) + +def_op('DELETE_SLICE+0', 50) +def_op('DELETE_SLICE+1', 51) +def_op('DELETE_SLICE+2', 52) +def_op('DELETE_SLICE+3', 53) + +def_op('STORE_SUBSCR', 60) +def_op('DELETE_SUBSCR', 61) + +def_op('PRINT_EXPR', 70) +def_op('PRINT_ITEM', 71) +def_op('PRINT_NEWLINE', 72) + +def_op('BREAK_LOOP', 80) +def_op('RAISE_EXCEPTION', 81) +def_op('LOAD_LOCALS', 82) +def_op('RETURN_VALUE', 83) +def_op('REQUIRE_ARGS', 84) +def_op('REFUSE_ARGS', 85) +def_op('BUILD_FUNCTION', 86) +def_op('POP_BLOCK', 87) +def_op('END_FINALLY', 88) +def_op('BUILD_CLASS', 89) + +HAVE_ARGUMENT = 90 # Opcodes from here have an argument: + +name_op('STORE_NAME', 90) # Index in name list +name_op('DELETE_NAME', 91) # "" +def_op('UNPACK_TUPLE', 92) # Number of tuple items +def_op('UNPACK_LIST', 93) # Number of list items +# unused: 94 +name_op('STORE_ATTR', 95) # Index in name list +name_op('DELETE_ATTR', 96) # "" + +def_op('LOAD_CONST', 100) # Index in const list +hasconst.append(100) +name_op('LOAD_NAME', 101) # Index in name list +def_op('BUILD_TUPLE', 102) # Number of tuple items +def_op('BUILD_LIST', 103) # Number of list items +def_op('BUILD_MAP', 104) # Always zero for now +name_op('LOAD_ATTR', 105) # Index in name list +def_op('COMPARE_OP', 106) # Comparison operator +name_op('IMPORT_NAME', 107) # Index in name list +name_op('IMPORT_FROM', 108) # Index in name list + +jrel_op('JUMP_FORWARD', 110) # Number of bytes to skip +jrel_op('JUMP_IF_FALSE', 111) # "" +jrel_op('JUMP_IF_TRUE', 112) # "" +jabs_op('JUMP_ABSOLUTE', 113) # Target byte offset from beginning of code +jrel_op('FOR_LOOP', 114) # Number of bytes to skip + +jrel_op('SETUP_LOOP', 120) # Distance to target address +jrel_op('SETUP_EXCEPT', 121) # "" +jrel_op('SETUP_FINALLY', 122) # "" + +def_op('SET_LINENO', 127) # Current line number +SET_LINENO = 127 diff --git a/lib/dump.py b/lib/dump.py new file mode 100644 index 0000000..0510092 --- /dev/null +++ b/lib/dump.py @@ -0,0 +1,63 @@ +# Module 'dump' +# +# Print python code that reconstructs a variable. +# This only works in certain cases. +# +# It works fine for: +# - ints and floats (except NaNs and other weird things) +# - strings +# - compounds and lists, provided it works for all their elements +# - imported modules, provided their name is the module name +# +# It works for top-level dictionaries but not for dictionaries +# contained in other objects (could be made to work with some hassle +# though). +# +# It does not work for functions (all sorts), classes, class objects, +# windows, files etc. +# +# Finally, objects referenced by more than one name or contained in more +# than one other object lose their sharing property (this is bad for +# strings used as exception identifiers, for instance). + +# Dump a whole symbol table +# +def dumpsymtab(dict): + for key in dict.keys(): + dumpvar(key, dict[key]) + +# Dump a single variable +# +def dumpvar(name, x): + import sys + t = type(x) + if t = type({}): + print name, '= {}' + for key in x.keys(): + item = x[key] + if not printable(item): + print '#', + print name, '[', `key`, '] =', `item` + elif t in (type(''), type(0), type(0.0), type([]), type(())): + if not printable(x): + print '#', + print name, '=', `x` + elif t = type(sys): + print 'import', name, '#', x + else: + print '#', name, '=', x + +# check if a value is printable in a way that can be read back with input() +# +def printable(x): + t = type(x) + if t in (type(''), type(0), type(0.0)): + return 1 + if t in (type([]), type(())): + for item in x: + if not printable(item): + return 0 + return 1 + if x = {}: + return 1 + return 0 diff --git a/lib/fact.py b/lib/fact.py new file mode 100644 index 0000000..ba961c4 --- /dev/null +++ b/lib/fact.py @@ -0,0 +1,37 @@ +# Factorize numbers -- slow, could use a table of all primes <= 2*16 + +import sys +import math + +error = 'fact.error' # exception + +def fact(n): + if n < 1: raise error # fact() argument should be >= 1 + if n = 1: return [] # special case + res = [] + _fact(n, 2, res) + return res + +def _fact(n, lowest, res): + highest = int(math.sqrt(float(n+1))) + for i in range(lowest, highest+1): + if n%i = 0: + res.append(i) + _fact(n/i, i, res) + break + else: + res.append(n) + +def main(): + if len(sys.argv) > 1: + for arg in sys.argv[1:]: + n = eval(arg) + print n, fact(n) + else: + try: + while 1: + print fact(input()) + except EOFError: + pass + +main() diff --git a/lib/filewin.py b/lib/filewin.py new file mode 100644 index 0000000..ad35ea4 --- /dev/null +++ b/lib/filewin.py @@ -0,0 +1,18 @@ +# Module 'filewin' +# File windows, a subclass of textwin (which is a subclass of gwin) + +import textwin +from util import readfile + + +# FILE WINDOW + +def open_readonly(fn): # Open a file window + w = textwin.open_readonly(fn, readfile(fn)) + w.fn = fn + return w + +def open(fn): # Open a file window + w = textwin.open(fn, readfile(fn)) + w.fn = fn + return w diff --git a/lib/fnmatch.py b/lib/fnmatch.py new file mode 100644 index 0000000..8f9e318 --- /dev/null +++ b/lib/fnmatch.py @@ -0,0 +1,35 @@ +# module 'fnmatch' -- filename matching with shell patterns + +# XXX [] patterns are not supported (but recognized) + +def fnmatch(name, pat): + if '*' in pat or '?' in pat or '[' in pat: + return fnmatch1(name, pat) + return name = pat + +def fnmatch1(name, pat): + for i in range(len(pat)): + c = pat[i] + if c = '*': + restpat = pat[i+1:] + if '*' in restpat or '?' in restpat or '[' in restpat: + for i in range(i, len(name)): + if fnmatch1(name[i:], restpat): + return 1 + return 0 + else: + return name[len(name)-len(restpat):] = restpat + elif c = '?': + if len(name) <= i : return 0 + elif c = '[': + return 0 # XXX + else: + if name[i:i+1] <> c: + return 0 + return 1 + +def fnmatchlist(names, pat): + res = [] + for name in names: + if fnmatch(name, pat): res.append(name) + return res diff --git a/lib/getopt.py b/lib/getopt.py new file mode 100644 index 0000000..f343c22 --- /dev/null +++ b/lib/getopt.py @@ -0,0 +1,47 @@ +# module getopt -- Standard command line processing. + +# Function getopt.getopt() has a different interface but provides the +# same functionality as the Unix getopt() function. + +# It has two arguments: the first should be argv[1:] (it doesn't want +# the script name), the second the string of option letters as passed +# to Unix getopt() (i.e., a string of allowable option letters, with +# options requiring an argument followed by a colon). + +# It raises the exception getopt.error with a string argument if it +# detects an error. + +# It returns two items: +# (1) a list of pairs (option, option_argument) giving the options in +# the order in which they were specified. (I'd use a dictionary +# but applications may depend on option order or multiple +# occurrences.) Boolean options have '' as option_argument. +# (2) the list of remaining arguments (may be empty). + +error = 'getopt error' + +def getopt(args, options): + list = [] + while args and args[0][0] = '-' and args[0] <> '-': + if args[0] = '--': + args = args[1:] + break + optstring, args = args[0][1:], args[1:] + while optstring <> '': + opt, optstring = optstring[0], optstring[1:] + if classify(opt, options): # May raise exception as well + if optstring = '': + if not args: + raise error, 'option -' + opt + ' requires argument' + optstring, args = args[0], args[1:] + optarg, optstring = optstring, '' + else: + optarg = '' + list.append('-' + opt, optarg) + return list, args + +def classify(opt, options): # Helper to check type of option + for i in range(len(options)): + if opt = options[i] <> ':': + return options[i+1:i+2] = ':' + raise error, 'option -' + opt + ' not recognized' diff --git a/lib/glob.py b/lib/glob.py new file mode 100644 index 0000000..b711094 --- /dev/null +++ b/lib/glob.py @@ -0,0 +1,44 @@ +# Module 'glob' -- filename globbing. + +import posix +import path +import fnmatch + +def glob(pathname): + if not has_magic(pathname): return [pathname] + dirname, basename = path.split(pathname) + if dirname[-1:] = '/' and dirname <> '/': + dirname = dirname[:-1] + if has_magic(dirname): + list = glob(dirname) + else: + list = [dirname] + if not has_magic(basename): + result = [] + for dirname in list: + if basename or path.isdir(dirname): + name = path.cat(dirname, basename) + if path.exists(name): + result.append(name) + else: + result = [] + for dirname in list: + sublist = glob1(dirname, basename) + for name in sublist: + result.append(path.cat(dirname, name)) + return result + +def glob1(dirname, pattern): + if not dirname: dirname = '.' + try: + names = posix.listdir(dirname) + except posix.error: + return [] + result = [] + for name in names: + if name[0] <> '.' or pattern[0] = '.': + if fnmatch.fnmatch(name, pattern): result.append(name) + return result + +def has_magic(s): + return '*' in s or '?' in s or '[' in s diff --git a/lib/grep.py b/lib/grep.py new file mode 100644 index 0000000..66eb08d --- /dev/null +++ b/lib/grep.py @@ -0,0 +1,32 @@ +# 'grep' + +import regexp +import string + +def grep(expr, filename): + prog = regexp.compile(expr) + fp = open(filename, 'r') + lineno = 0 + while 1: + line = fp.readline() + if not line: break + lineno = lineno + 1 + res = prog.exec(line) + if res: + #print res + start, end = res[0] + if line[-1:] = '\n': line = line[:-1] + prefix = string.rjust(`lineno`, 3) + ': ' + print prefix + line + if 0: + line = line[:start] + if '\t' not in line: + prefix = ' ' * (len(prefix) + start) + else: + prefix = ' ' * len(prefix) + for c in line: + if c <> '\t': c = ' ' + prefix = prefix + c + if start = end: prefix = prefix + '\\' + else: prefix = prefix + '^'*(end-start) + print prefix diff --git a/lib/gwin.py b/lib/gwin.py new file mode 100644 index 0000000..f6291e1 --- /dev/null +++ b/lib/gwin.py @@ -0,0 +1,122 @@ +# Module 'gwin' +# Generic stdwin windows + +# This is used as a base class from which to derive other window types. +# The mainloop() function here is an event dispatcher for all window types. + +import stdwin +from stdwinevents import * + +# XXX Old version of stdwinevents, should go +import stdwinsupport +S = stdwinsupport # Shorthand + +windows = [] # List of open windows + + +# Open a window + +def open(title): # Open a generic window + w = stdwin.open(title) + stdwin.setdefwinsize(0, 0) + # Set default event handlers + w.draw = nop + w.char = nop + w.mdown = nop + w.mmove = nop + w.mup = nop + w.m2down = m2down + w.m2up = m2up + w.size = nop + w.move = nop + w.activate = w.deactivate = nop + w.timer = nop + # default command handlers + w.close = close + w.tab = tab + w.enter = enter + w.backspace = backspace + w.arrow = arrow + w.kleft = w.kup = w.kright = w.kdown = nop + windows.append(w) + return w + + +# Generic event dispatching + +def mainloop(): # Handle events until no windows left + while windows: + treatevent(stdwin.getevent()) + +def treatevent(e): # Handle a stdwin event + type, w, detail = e + if type = S.we_draw: + w.draw(w, detail) + elif type = S.we_menu: + m, item = detail + m.action[item](w, m, item) + elif type = S.we_command: + treatcommand(w, detail) + elif type = S.we_char: + w.char(w, detail) + elif type = S.we_mouse_down: + if detail[1] > 1: w.m2down(w, detail) + else: w.mdown(w, detail) + elif type = S.we_mouse_move: + w.mmove(w, detail) + elif type = S.we_mouse_up: + if detail[1] > 1: w.m2up(w, detail) + else: w.mup(w, detail) + elif type = S.we_size: + w.size(w, w.getwinsize()) + elif type = S.we_activate: + w.activate(w) + elif type = S.we_deactivate: + w.deactivate(w) + elif type = S.we_move: + w.move(w) + elif type = S.we_timer: + w.timer(w) + elif type = WE_CLOSE: + w.close(w) + +def treatcommand(w, type): # Handle a we_command event + if type = S.wc_close: + w.close(w) + elif type = S.wc_return: + w.enter(w) + elif type = S.wc_tab: + w.tab(w) + elif type = S.wc_backspace: + w.backspace(w) + elif type in (S.wc_left, S.wc_up, S.wc_right, S.wc_down): + w.arrow(w, type) + + +# Methods + +def close(w): # Close method + for i in range(len(windows)): + if windows[i] is w: + del windows[i] + break + +def arrow(w, detail): # Arrow key method + if detail = S.wc_left: + w.kleft(w) + elif detail = S.wc_up: + w.kup(w) + elif detail = S.wc_right: + w.kright(w) + elif detail = S.wc_down: + w.kdown(w) + + +# Trivial methods + +def tab(w): w.char(w, '\t') +def enter(w): w.char(w, '\n') # 'return' is a Python reserved word +def backspace(w): w.char(w, '\b') +def m2down(w, detail): w.mdown(w, detail) +def m2up(w, detail): w.mup(w, detail) +def nop(args): pass diff --git a/lib/lambda.py b/lib/lambda.py new file mode 100644 index 0000000..7d62b83 --- /dev/null +++ b/lib/lambda.py @@ -0,0 +1,151 @@ +# A bit of Lambda Calculus illustrated in Python. +# +# This does not use Python's built-in 'eval' or 'exec' functions! + + +# Currying +# +# From a function with 2 args, f(x, y), and a value for the 1st arg, +# we can create a new function with one argument fx(y) = f(x, y). +# This is called "Currying" (after the idea's inventor, a Mr. Curry). +# +# To implement this we create a class member, of which fx is a method; +# f and x are stored as data attributes of the member. + +class _CurryClass(): + def new(self, (f, x)): + self.f = f + self.x = x + return self + def fx(self, y): + return self.f(self.x, y) + +def CURRY(f, x): + # NB: f is not "simple": it has 2 arguments + return _CurryClass().new(f, x).fx + + +# Numbers in Lambda Calculus +# +# In the lambda calculus, natural numbers are represented by +# higher-order functions Ntimes(f, x) that yield f applied N +# times to x, e.g., Twice(f, x) = f(f(x)). +# As far as I understand, there is no difference in real lambda +# calculus between +# lambda f x : f(f(x)) +# and +# lambda f : f o f # 'o' means function composition +# but in Python we have to write the first as Twice(f, x) and +# the second as twice(f). + +def Never(f, x): return x +def Once(f, x): return f(x) +def Twice(f, x): return f(f(x)) +def Thrice(f, x): return f(f(f(x))) +def Fourfold(f, x): return f(f(f(f(x)))) +# (etc.) + +def never(f): return CURRY(Never, f) +def once(f): return CURRY(Once, f) +def twice(f): return CURRY(Twice, f) +def thrice(f): return CURRY(Thrice, f) +def fourfold(f): return CURRY(Fourfold, f) +# (etc.) + + +# NB: actually 'ntimes' is less concrete than 'Ntimes', as +# 'ntimes' returns a function, while 'Ntimes' returns a value +# similar to what f(x) returns. 'Ntimes' can be derived +# from 'ntimes', as follows: +# def Ntimes(f, x): return ntimes(f)(x) +# but this doesn't help us since 'ntimes' can only be defined +# using Ntimes (or a trick like the one used by CURRY). + + +# Arithmetic in Lambda Calculus +# +# We can perform simple arithmetic on the un-curried versions, e.g., +# Successor(Twice) = Thrice (2+1) +# Sum(Thrice, Twice) = Fivefold (3+2) +# Product(Thrice, Twice) = Sixfold (3*2) +# Power(Thrice, Twice) = Ninefold (3**2) +# +# First we define versions that need f and x arguments. +# They have funny argument forms so the final functions can +# use CURRY, which only works on functions of exactly 2 arguments. + +def SUCCESSOR(Ntimes, (f, x)): return f(Ntimes(f, x)) +def SUCCESSOR(Ntimes, (f, x)): return Ntimes(f, f(x)) # Same effect +def SUM(Ntimes, (Mtimes, (f, x))): return Ntimes(f, Mtimes(f, x)) +def PRODUCT(Ntimes, (Mtimes, (f, x))): return Ntimes(CURRY(Mtimes, f), x) +def POWER(Ntimes, (Mtimes, (f, x))): + return Mtimes(CURRY(CURRY, Ntimes), f)(x) + +def Successor(Ntimes): return CURRY(SUCCESSOR, Ntimes) +def Sum(Ntimes, Mtimes): return CURRY(CURRY(SUM, Ntimes), Mtimes) +def Product(Ntimes, Mtimes): return CURRY(CURRY(PRODUCT, Ntimes), Mtimes) +def Power(Ntimes, Mtimes): return CURRY(CURRY(POWER, Ntimes), Mtimes) + + +# Sorry, I don't have a clue on how to do subtraction or division... + + +# References +# +# All I know about lambda calculus is from Roger Penrose's +# The Emperor's New Mind, Chapter 2. + + +# P.S.: Here is a Lambda function in Python. +# It uses 'exec' and expects two strings to describe the arguments +# and the function expression. Example: +# lambda('x', 'x+1') +# defines the successor function. + +def lambda(args, expr): + if '\n' in args or '\n' in expr: + raise RuntimeError, 'lambda: no cheating!' + stmt = 'def func(' + args + '): return ' + expr + '\n' + print 'lambda:', stmt, + exec(stmt) + return func + + +# P.P.S.S.: Here is a way to construct Ntimes and ntimes directly. +# Example: +# GenericNtimes(4) +# is equivalent to Fourfold. + +class _GenericNtimesClass(): + def new(self, n): + self.n = n + return self + def Ntimes(self, (f, x)): + n = self.n + while n > 0: x, n = f(x), n-1 + return x + +def GenericNtimes(n): + return _GenericNtimesClass().new(n).Ntimes + + +# To construct any 'ntimes' function from the corresponding 'Ntimes', +# we use a trick as used by CURRY. For example, +# Ntimes2ntimes(Fourfold) +# yields a function equivalent to fourfold. + +class _Ntimes2ntimesClass(): + def new(self, Ntimes): + self.Ntimes = Ntimes + return self + def ntimes(self, f): + return CURRY(self.Ntimes, f) + +def Ntimes2ntimes(Ntimes): return _Ntimes2ntimesClass().new(Ntimes).ntimes + + +# This allows us to construct generic 'ntimes' functions. Example: +# generic_ntimes(3) +# is the same as thrice. + +def generic_ntimes(n): return Ntimes2ntimes(GenericNtimes(n)) diff --git a/lib/listwin.py b/lib/listwin.py new file mode 100644 index 0000000..aac127c --- /dev/null +++ b/lib/listwin.py @@ -0,0 +1,47 @@ +# Module 'listwin' +# List windows, a subclass of gwin + +import gwin +import stdwin + +def maxlinewidth(a): # Compute maximum textwidth of lines in a sequence + max = 0 + for line in a: + width = stdwin.textwidth(line) + if width > max: max = width + return max + +def action(w, string, i, detail): # Default item selection method + pass + +def mup(w, detail): # Mouse up method + (h, v), clicks, button, mask = detail + i = divmod(v, w.lineheight)[0] + if 0 <= i < len(w.data): + w.action(w, w.data[i], i, detail) + +def draw(w, ((left, top), (right, bottom))): # Text window draw method + data = w.data + d = w.begindrawing() + lh = w.lineheight + itop = top/lh + ibot = (bottom-1)/lh + 1 + if itop < 0: itop = 0 + if ibot > len(data): ibot = len(data) + for i in range(itop, ibot): d.text((0, i*lh), data[i]) + +def open(title, data): # Display a list of texts in a window + lineheight = stdwin.lineheight() + h, v = maxlinewidth(data), len(data)*lineheight + h0, v0 = h + stdwin.textwidth(' '), v + lineheight + if h0 > stdwin.textwidth(' ')*80: h0 = 0 + if v0 > stdwin.lineheight()*24: v0 = 0 + stdwin.setdefwinsize(h0, v0) + w = gwin.open(title) + w.setdocsize(h, v) + w.lineheight = lineheight + w.data = data + w.draw = draw + w.action = action + w.mup = mup + return w diff --git a/lib/localtime.py b/lib/localtime.py new file mode 100644 index 0000000..5ce6529 --- /dev/null +++ b/lib/localtime.py @@ -0,0 +1,53 @@ +# module localtime -- Time conversions + +import posix + +epoch = 1970 # 1 jan 00:00:00, UCT +day0 = 4 # day 0 was a thursday + +day_names = ('Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat') + +month_names = ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun') +month_names = month_names + ('Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec') + +month_sizes = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) + +def isleap(year): + return year % 4 = 0 and (year % 100 <> 0 or year % 400 = 0) + +def gmtime(secs): # decode time into UCT + mins, secs = divmod(secs, 60) + hours, mins = divmod(mins, 60) + days, hours = divmod(hours, 24) + wday = (day0 + days) % 7 + year = epoch + lp = isleap(year) + dpy = 365 + lp + while days >= dpy: + days = days - dpy + year = year + 1 + lp = isleap(year) + dpy = 365 + lp + yday = days + month = 0 + dpm = month_sizes[month] + (lp and month = 1) + while days >= dpm: + days = days - dpm + month = month + 1 + dpm = month_sizes[month] + (lp and month = 1) + return (year, month, days+1, hours, mins, secs, yday, wday) + +def dd(x): + s = `x` + while len(s) < 2: s = '0' + s + return s + +def zd(x): + s = `x` + while len(s) < 2: s = ' ' + s + return s + +def format(year, month, days, hours, mins, secs, yday, wday): + s = day_names[wday] + ' ' + zd(days) + ' ' + month_names[month] + ' ' + s = s + dd(hours) + ':' + dd(mins) + ':' + dd(secs) + return s diff --git a/lib/maccache.py b/lib/maccache.py new file mode 100644 index 0000000..329e514 --- /dev/null +++ b/lib/maccache.py @@ -0,0 +1,61 @@ +# Module 'maccache' +# +# Maintain a cache of listdir(), isdir(), isfile() or exists() outcomes. + +import mac +import macpath + + +# The cache. +# Keys are absolute pathnames; +# values are 0 (nothing), 1 (file) or [...] (dir). +# +cache = {} + + +# Current working directory. +# +cwd = mac.getcwd() + + +# Constants. +# +NONE = 0 +FILE = 1 +LISTTYPE = type([]) + +def _stat(name): + name = macpath.cat(cwd, name) + if cache.has_key(name): + return cache[name] + if macpath.isfile(name): + cache[name] = FILE + return FILE + try: + list = mac.listdir(name) + except: + cache[name] = NONE + return NONE + cache[name] = list + if name[-1:] = ':': cache[name[:-1]] = list + else: cache[name+':'] = list + return list + +def isdir(name): + st = _stat(name) + return type(st) = LISTTYPE + +def isfile(name): + st = _stat(name) + return st = FILE + +def exists(name): + st = _stat(name) + return st <> NONE + +def listdir(name): + st = _stat(name) + if type(st) = LISTTYPE: + return st + else: + raise RuntimeError, 'list non-directory' diff --git a/lib/macglob.py b/lib/macglob.py new file mode 100644 index 0000000..cad2c79 --- /dev/null +++ b/lib/macglob.py @@ -0,0 +1,46 @@ +# Module 'macglob' -- version of 'glob' for the Macintosh. + +# XXX At least one bug is left: a pattern like '*:' is treated +# XXX as a relative pathname (and returns as if it was ':*:'). + +import mac +import macpath +import fnmatch + +def glob(pathname): + if not has_magic(pathname): return [pathname] + dirname, basename = macpath.split(pathname) + if has_magic(dirname): + if dirname[-1:] = ':': dirname = dirname[:-1] + list = glob(dirname) + else: + list = [dirname] + if not has_magic(basename): + result = [] + for dirname in list: + if basename or macpath.isdir(dirname): + name = macpath.cat(dirname, basename) + if macpath.exists(name): + result.append(name) + else: + result = [] + for dirname in list: + sublist = glob1(dirname, basename) + for name in sublist: + result.append(macpath.cat(dirname, name)) + return result + +def glob1(dirname, pattern): + if not dirname: dirname = ':' + try: + names = mac.listdir(dirname) + except mac.error: + return [] + result = [] + for name in names: + if name[0] <> '.' or pattern[0] = '.': + if fnmatch.fnmatch(name, pattern): result.append(name) + return result + +def has_magic(s): + return '*' in s or '?' in s or '[' in s diff --git a/lib/macpath.py b/lib/macpath.py new file mode 100644 index 0000000..99ba6b8 --- /dev/null +++ b/lib/macpath.py @@ -0,0 +1,108 @@ +# module 'macpath' -- pathname (or -related) operations for the Macintosh + +import mac + +from stat import * + + +# Return true if a path is absolute. +# On the Mac, relative paths begin with a colon, +# but as a special case, paths with no colons at all are also relative. +# Anything else is absolute (the string up to the first colon is the +# volume name). + +def isabs(s): + return ':' in s and s[0] <> ':' + + +# Concatenate two pathnames. +# The result is equivalent to what the second pathname would refer to +# if the first pathname were the current directory. + +def cat(s, t): + if (not s) or isabs(t): return t + if t[:1] = ':': t = t[1:] + if ':' not in s: + s = ':' + s + if s[-1:] <> ':': + s = s + ':' + return s + t + + +# Split a pathname in two parts: the directory leading up to the final bit, +# and the basename (the filename, without colons, in that directory). +# The result (s, t) is such that cat(s, t) yields the original argument. + +def split(s): + if ':' not in s: return '', s + colon = 0 + for i in range(len(s)): + if s[i] = ':': colon = i+1 + return s[:colon], s[colon:] + + +# Normalize a pathname: get rid of '::' sequences by backing up, +# e.g., 'foo:bar::bletch' becomes 'foo:bletch'. +# Raise the exception norm_error below if backing up is impossible, +# e.g., for '::foo'. + +norm_error = 'macpath.norm_error: path cannot be normalized' + +def norm(s): + import string + if ':' not in s: + return ':' + s + f = string.splitfields(s, ':') + pre = [] + post = [] + if not f[0]: + pre = f[:1] + f = f[1:] + if not f[len(f)-1]: + post = f[-1:] + f = f[:-1] + res = [] + for seg in f: + if seg: + res.append(seg) + else: + if not res: raise norm_error, 'path starts with ::' + del res[len(res)-1] + if not (pre or res): + raise norm_error, 'path starts with volume::' + if pre: res = pre + res + if post: res = res + post + s = res[0] + for seg in res[1:]: + s = s + ':' + seg + return s + + +# Return true if the pathname refers to an existing directory. + +def isdir(s): + try: + st = mac.stat(s) + except mac.error: + return 0 + return S_ISDIR(st[ST_MODE]) + + +# Return true if the pathname refers to an existing regular file. + +def isfile(s): + try: + st = mac.stat(s) + except mac.error: + return 0 + return S_ISREG(st[ST_MODE]) + + +# Return true if the pathname refers to an existing file or directory. + +def exists(s): + try: + st = mac.stat(s) + except mac.error: + return 0 + return 1 diff --git a/lib/macshell.py b/lib/macshell.py new file mode 100644 index 0000000..c96d65b --- /dev/null +++ b/lib/macshell.py @@ -0,0 +1,399 @@ +# Macintosh 'shell' +# vi:set tabsize=4: + +# XXX string quoting in arguments +# XXX directory stack +# XXX $macros? +# XXX ^C during any command +# XXX 'sh' builtin command? +# XXX why does open require absolute path? (need to fix chdir.c) + + +import mac + +import macpath +import string +import glob +from macpath import isfile, isdir, exists +import TclUtil # For splitting/quoting mechanisms + +class Struct(): pass +G = Struct() + +def reset(): + G.debug = 0 + G.ps1 = '$ ' + G.homedir = mac.getcwd() + G.commands = mkcmdtab() + G.aliases = {} + +def mkcmdtab(): + tab = {} + tab['alias'] = do_alias + tab['cd'] = do_cd + tab['debug'] = do_debug + tab['grep'] = do_grep + tab['help'] = do_help + tab['ls'] = do_ls + tab['mkdir'] = do_mkdir + tab['mv'] = do_mv + tab['page'] = do_page + tab['pwd'] = do_pwd + tab['reset'] = do_reset + tab['rm'] = do_rm + tab['rmdir'] = do_rmdir + tab['sync'] = do_sync + tab['unalias'] = do_unalias + return tab + +def main(): + while 1: + try: + line = raw_input(G.ps1) + except EOFError: + print '[EOF]' + break + except KeyboardInterrupt: + print '[Intr]' + line = '' + if G.debug: + print 'line:', `line` + words = TclUtil.SplitList(line) + if G.debug: + print 'words:', words + if words and words[0][0] <> '#': + run(words) + +def run(words): + expandaliases(words) + cmd = words[0] + args = words[1:] + if G.commands.has_key(cmd): + if args: + try: + args = expandgloblist(args) + except glob_error, msg: + print cmd, ': glob error :', msg + return + G.commands[cmd](args) + return + if hasglobchar(cmd): + if args: + print cmd, ': cannot glob pattern with arguments' + return + try: + words = expandglobword(cmd) + except glob_error, msg: + print cmd, ': glob error :', msg + return + if len(words) > 1: + columnize(words) + return + cmd = words[0] + print cmd + if isfile(cmd): + if args: + print cmd, ': file command expects no arguments' + return + do_page([cmd]) + elif isdir(cmd): + if args: + print cmd, ': directory command expects no arguments' + return + do_cd([cmd]) + else: + print cmd, ': no such command, file or directory' + +glob_error = 'glob error' + +def expandgloblist(words): + res = [] + for word in words: + if hasglobchar(word): + res = res + expandglobword(word) + else: + res.append(word) + return res + +def expandglobword(word): + names = glob.globlist(mac.listdir(':'), word) + if not names: raise glob_error, 'no match for pattern ' + word + return names + +def hasglobchar(word): + return '*' in word or '?' in word + +def expandaliases(words): + seen = [] + cmd = words[0] + while cmd not in seen and G.aliases.has_key(cmd): + seen.append(cmd) + words[:1] = G.aliases[cmd] + cmd = words[0] + +def do_alias(args): + if not args: + listaliases() + elif len(args) = 1: + listalias(args[0]) + else: + defalias(args[0], args[1:]) + +def listaliases(): + names = G.aliases.keys() + names.sort() + for name in names: listalias(name) + +def listalias(name): + if not G.aliases.has_key(name): + print name, ': no such alias' + return + print 'alias', name, + printlist(G.aliases[name]) + print + +def defalias(name, expansion): + G.aliases[name] = expansion + +def do_cd(args): + if len(args) > 1: + print 'usage: cd [dirname]' + elif args: + chdirto(args[0]) + else: + chdirto(G.homedir) + +def chdirto(dirname): + try: + mac.chdir(dirname) + except mac.error, msg: + print dirname, ':', msg + return + +def do_debug(args): + G.debug = (not G.debug) + +def do_grep(args): + if len(args) < 2: + print 'usage: grep regexp file ...' + return + import regexp + try: + prog = regexp.compile(args[0]) + except regexp.error, msg: + print 'regexp.compile error for', args[0], ':', msg + return + for file in args[1:]: + grepfile(prog, file) + +def grepfile(prog, file): + try: + fp = open(file, 'r') + except RuntimeError, msg: + print file, ': cannot open :', msg + return + lineno = 0 + while 1: + line = fp.readline() + if not line: break + lineno = lineno+1 + if prog.exec(line): + print file+'('+`lineno`+'):', line, + +def do_help(args): + if args: + print 'usage: help' + return + names = G.commands.keys() + names.sort() + columnize(names) + +def do_ls(args): + if not args: + lsdir(':') + else: + for dirname in args: + lsdir(dirname) + +def lsdir(dirname): + if not isdir(dirname): + print dirname, ': no such directory' + return + names = mac.listdir(dirname) + lsfiles(names, dirname) + +def lsfiles(names, dirname): + names = names[:] # Make a copy so we can modify it + for i in range(len(names)): + name = names[i] + if G.debug: print i, name + if isdir(macpath.cat(dirname, name)): + names[i] = ':' + name + ':' + columnize(names) + +def columnize(list): + COLUMNS = 80-1 + n = len(list) + colwidth = maxwidth(list) + ncols = (COLUMNS + 1) / (colwidth + 1) + if ncols < 1: ncols = 1 + nrows = (n + ncols - 1) / ncols + for irow in range(nrows): + line = '' + for icol in range(ncols): + i = irow + nrows*icol + if 0 <= i < n: + word = list[i] + if i+nrows < n: + word = string.ljust(word, colwidth) + if icol > 0: + word = ' ' + word + line = line + word + print line + +def maxwidth(list): + width = 0 + for word in list: + if len(word) > width: + width = len(word) + return width + +def do_mv(args): + if len(args) <> 2: + print 'usage: mv src dst' + return + src, dst = args[0], args[1] + if not exists(src): + print src, ': source does not exist' + return + if exists(dst): + print src, ': destination already exists' + return + try: + mac.rename(src, dst) + except mac.error, msg: + print src, dst, ': rename failed:', msg + +def do_mkdir(args): + if not args: + print 'usage: mkdir name ...' + return + for name in args: + makedir(name) + +def makedir(name): + if exists(name): + print name, ': already exists' + return + try: + mac.mkdir(name, 0777) + except mac.error, msg: + print name, ': mkdir failed:', msg + +def do_page(args): + if not args: + print 'usage: page file ...' + return + for name in args: + pagefile(name) + +def pagefile(name): + if not isfile(name): + print name, ': no such file' + return + LINES = 24 - 1 + # For THINK C 3.0, make the path absolute: + # if not macpath.isabs(name): + # name = macpath.cat(mac.getcwd(), name) + try: + fp = open(name, 'r') + except: + print name, ': cannot open' + return + line = fp.readline() + while line: + for i in range(LINES): + print line, + line = fp.readline() + if not line: break + if line: + try: + more = raw_input('[more]') + except (EOFError, KeyboardInterrupt): + print + break + if string.strip(more)[:1] in ('q', 'Q'): + break + +def do_pwd(args): + if args: + print 'usage: pwd' + else: + print mac.getcwd() + +def do_reset(args): + if args: + print 'usage: reset' + else: + reset() + +def do_rm(args): + if not args: + print 'usage: rm file ...' + return + for name in args: + remove(name) + +def remove(name): + if not isfile(name): + print name, ': no such file' + return + try: + mac.unlink(name) + except mac.error, msg: + print name, ': unlink failed:', msg + +def do_rmdir(args): + if not args: + print 'usage: rmdir dir ...' + return + for name in args: + rmdir(name) + +def rmdir(name): + if not isdir(name): + print name, ': no such directory' + return + try: + mac.rmdir(name) + except mac.error, msg: + print name, ': rmdir failed:', msg + +def do_sync(args): + if args: + print 'usage: sync' + return + try: + mac.sync() + except mac.error, msg: + print 'sync failed:', msg + +def do_unalias(args): + if not args: + print 'usage: unalias name ...' + return + for name in args: + unalias(name) + +def unalias(name): + if not G.aliases.has_key(name): + print name, ': no such alias' + return + del G.aliases[name] + +def printlist(list): + for word in list: + print word, + +reset() +main() diff --git a/lib/minmax.py b/lib/minmax.py new file mode 100644 index 0000000..38e20d1 --- /dev/null +++ b/lib/minmax.py @@ -0,0 +1,6 @@ +# Module 'minmax' +# These are now built in functions. +# For compatibility, we export the builtins + +min = min +max = max diff --git a/lib/packmail.py b/lib/packmail.py new file mode 100644 index 0000000..40b5b7a --- /dev/null +++ b/lib/packmail.py @@ -0,0 +1,48 @@ +# Module 'packmail' -- create a shell script out of some files. + +import mac +import macpath +from stat import ST_MTIME + +# Pack one file +def pack(outfp, file, name): + fp = open(file, 'r') + outfp.write('sed "s/^X//" >' + name + ' <<"!"\n') + while 1: + line = fp.readline() + if not line: break + if line[-1:] <> '\n': + line = line + '\n' + outfp.write('X' + line) + outfp.write('!\n') + +# Pack some files from a directory +def packsome(outfp, dirname, names): + for name in names: + print name + file = macpath.cat(dirname, name) + pack(outfp, file, name) + +# Pack all files from a directory +def packall(outfp, dirname): + names = mac.listdir(dirname) + names.sort() + packsome(outfp, dirname, names) + +# Pack all files from a directory that are not older than a give one +def packnotolder(outfp, dirname, oldest): + names = mac.listdir(dirname) + oldest = macpath.cat(dirname, oldest) + st = mac.stat(oldest) + mtime = st[ST_MTIME] + todo = [] + for name in names: + print name, '...', + st = mac.stat(macpath.cat(dirname, name)) + if st[ST_MTIME] >= mtime: + print 'Yes.' + todo.append(name) + else: + print 'No.' + todo.sort() + packsome(outfp, dirname, todo) diff --git a/lib/panel.py b/lib/panel.py new file mode 100644 index 0000000..8b0b45c --- /dev/null +++ b/lib/panel.py @@ -0,0 +1,281 @@ +# Module 'panel' +# +# Support for the Panel library. +# Uses built-in module 'pnl'. +# Applciations should use 'panel.function' instead of 'pnl.function'; +# most 'pnl' functions are transparently exported by 'panel', +# but dopanel() is overridden and you have to use this version +# if you want to use callbacks. + + +import pnl + + +debug = 0 + + +# Test if an object is a list. +# +def is_list(x): + return type(x) = type([]) + + +# Reverse a list. +# +def reverse(list): + res = [] + for item in list: + res.insert(0, item) + return res + + +# Get an attribute of a list, which may itself be another list. +# Don't use 'prop' for name. +# +def getattrlist(list, name): + for item in list: + if item and is_list(item) and item[0] = name: + return item[1:] + return [] + + +# Get a property of a list, which may itself be another list. +# +def getproplist(list, name): + for item in list: + if item and is_list(item) and item[0] = 'prop': + if len(item) > 1 and item[1] = name: + return item[2:] + return [] + + +# Test if an actuator description contains the property 'end-of-group' +# +def is_endgroup(list): + x = getproplist(list, 'end-of-group') + return (x and x[0] = '#t') + + +# Neatly display an actuator definition given as S-expression +# the prefix string is printed before each line. +# +def show_actuator(prefix, a): + for item in a: + if not is_list(item): + print prefix, item + elif item and item[0] = 'al': + print prefix, 'Subactuator list:' + for a in item[1:]: + show_actuator(prefix + ' ', a) + elif len(item) = 2: + print prefix, item[0], '=>', item[1] + elif len(item) = 3 and item[0] = 'prop': + print prefix, 'Prop', item[1], '=>', + print item[2] + else: + print prefix, '?', item + + +# Neatly display a panel. +# +def show_panel(prefix, p): + for item in p: + if not is_list(item): + print prefix, item + elif item and item[0] = 'al': + print prefix, 'Actuator list:' + for a in item[1:]: + show_actuator(prefix + ' ', a) + elif len(item) = 2: + print prefix, item[0], '=>', item[1] + elif len(item) = 3 and item[0] = 'prop': + print prefix, 'Prop', item[1], '=>', + print item[2] + else: + print prefix, '?', item + + +# Exception raised by build_actuator or build_panel. +# +panel_error = 'panel error' + + +# Dummy callback used to initialize the callbacks. +# +def dummy_callback(arg): + pass + + +# Assign attributes to members of the target. +# Attribute names in exclist are ignored. +# The member name is the attribute name prefixed with the prefix. +# +def assign_members(target, attrlist, exclist, prefix): + for item in attrlist: + if is_list(item) and len(item) = 2 and item[0] not in exclist: + name, value = item[0], item[1] + ok = 1 + if value[0] in '-0123456789': + value = eval(value) + elif value[0] = '"': + value = value[1:-1] + elif value = 'move-then-resize': + # Strange default set by Panel Editor... + ok = 0 + else: + print 'unknown value', value, 'for', name + ok = 0 + if ok: + lhs = 'target.' + prefix + name + stmt = lhs + '=' + `value` + if debug: print 'exec', stmt + try: + exec(stmt + '\n') + except KeyboardInterrupt: # Don't catch this! + raise KeyboardInterrupt + except: + print 'assign failed:', stmt + + +# Build a real actuator from an actuator descriptior. +# Return a pair (actuator, name). +# +def build_actuator(descr): + namelist = getattrlist(descr, 'name') + if namelist: + # Assume it is a string + actuatorname = namelist[0][1:-1] + else: + actuatorname = '' + type = descr[0] + if type[:4] = 'pnl_': type = type[4:] + act = pnl.mkact(type) + act.downfunc = act.activefunc = act.upfunc = dummy_callback + # + assign_members(act, descr[1:], ['al', 'data', 'name'], '') + # + # Treat actuator-specific data + # + datalist = getattrlist(descr, 'data') + prefix = '' + if type[-4:] = 'puck': + prefix = 'puck_' + elif type = 'mouse': + prefix = 'mouse_' + assign_members(act, datalist, [], prefix) + # + return act, actuatorname + + +# Build all sub-actuators and add them to the super-actuator. +# The super-actuator must already have been added to the panel. +# Sub-actuators with defined names are added as members to the panel +# so they can be referenced as p.name. +# +# Note: I have no idea how panel.endgroup() works when applied +# to a sub-actuator. +# +def build_subactuators(panel, super_act, al): + # + # This is nearly the same loop as below in build_panel(), + # except a call is made to addsubact() instead of addact(). + # + for a in al: + act, name = build_actuator(a) + act.addsubact(super_act) + if name: + stmt = 'panel.' + name + ' = act' + if debug: print 'exec', stmt + exec(stmt + '\n') + if is_endgroup(a): + panel.endgroup() + sub_al = getattrlist(a, 'al') + if sub_al: + build_subactuators(panel, act, sub_al) + # + # Fix the actuator to which whe just added subactuators. + # This can't hurt (I hope) and is needed for the scroll actuator. + # + super_act.fixact() + + +# Build a real panel from a panel definition. +# Return a panel object p, where for each named actuator a, p.name is a +# reference to a. +# +def build_panel(descr): + # + # Sanity check + # + if (not descr) or descr[0] <> 'panel': + raise panel_error, 'panel description must start with "panel"' + # + if debug: show_panel('', descr) + # + # Create an empty panel + # + panel = pnl.mkpanel() + # + # Assign panel attributes + # + assign_members(panel, descr[1:], ['al'], '') + # + # Look for actuator list + # + al = getattrlist(descr, 'al') + # + # The order in which actuators are created is important + # because of the endgroup() operator. + # Unfortunately the Panel Editor outputs the actuator list + # in reverse order, so we reverse it here. + # + al = reverse(al) + # + for a in al: + act, name = build_actuator(a) + act.addact(panel) + if name: + stmt = 'panel.' + name + ' = act' + exec(stmt + '\n') + if is_endgroup(a): + panel.endgroup() + sub_al = getattrlist(a, 'al') + if sub_al: + build_subactuators(panel, act, sub_al) + # + return panel + + +# Wrapper around pnl.dopanel() which calls call-back functions. +# +def my_dopanel(): + # Extract only the first 4 elements to allow for future expansion + a, down, active, up = pnl.dopanel()[:4] + if down: + down.downfunc(down) + if active: + active.activefunc(active) + if up: + up.upfunc(up) + return a + + +# Create one or more panels from a description file (S-expressions) +# generated by the Panel Editor. +# +def defpanellist(file): + import panelparser + descrlist = panelparser.parse_file(open(file, 'r')) + panellist = [] + for descr in descrlist: + panellist.append(build_panel(descr)) + return panellist + + +# Import everything from built-in method pnl, so the user can always +# use panel.foo() instead of pnl.foo(). +# This gives *no* performance penalty once this module is imported. +# +from pnl import * # for export + +dopanel = my_dopanel # override pnl.dopanel diff --git a/lib/panelparser.py b/lib/panelparser.py new file mode 100644 index 0000000..5eb2f2b --- /dev/null +++ b/lib/panelparser.py @@ -0,0 +1,128 @@ +# Module 'parser' +# +# Parse S-expressions output by the Panel Editor +# (which is written in Scheme so it can't help writing S-expressions). +# +# See notes at end of file. + + +whitespace = ' \t\n' +operators = '()\'' +separators = operators + whitespace + ';' + '"' + + +# Tokenize a string. +# Return a list of tokens (strings). +# +def tokenize_string(s): + tokens = [] + while s: + c = s[:1] + if c in whitespace: + s = s[1:] + elif c = ';': + s = '' + elif c = '"': + n = len(s) + i = 1 + while i < n: + c = s[i] + i = i+1 + if c = '"': break + if c = '\\': i = i+1 + tokens.append(s[:i]) + s = s[i:] + elif c in operators: + tokens.append(c) + s = s[1:] + else: + n = len(s) + i = 1 + while i < n: + if s[i] in separators: break + i = i+1 + tokens.append(s[:i]) + s = s[i:] + return tokens + + +# Tokenize a whole file (given as file object, not as file name). +# Return a list of tokens (strings). +# +def tokenize_file(fp): + tokens = [] + while 1: + line = fp.readline() + if not line: break + tokens = tokens + tokenize_string(line) + return tokens + + +# Exception raised by parse_exr. +# +syntax_error = 'syntax error' + + +# Parse an S-expression. +# Input is a list of tokens as returned by tokenize_*(). +# Return a pair (expr, tokens) +# where expr is a list representing the s-expression, +# and tokens contains the remaining tokens. +# May raise syntax_error. +# +def parse_expr(tokens): + if (not tokens) or tokens[0] <> '(': + raise syntax_error, 'expected "("' + tokens = tokens[1:] + expr = [] + while 1: + if not tokens: + raise syntax_error, 'missing ")"' + if tokens[0] = ')': + return expr, tokens[1:] + elif tokens[0] = '(': + subexpr, tokens = parse_expr(tokens) + expr.append(subexpr) + else: + expr.append(tokens[0]) + tokens = tokens[1:] + + +# Parse a file (given as file object, not as file name). +# Return a list of parsed S-expressions found at the top level. +# +def parse_file(fp): + tokens = tokenize_file(fp) + exprlist = [] + while tokens: + expr, tokens = parse_expr(tokens) + exprlist.append(expr) + return exprlist + + +# EXAMPLE: +# +# The input +# '(hip (hop hur-ray))' +# +# passed to tokenize_string() returns the token list +# ['(', 'hip', '(', 'hop', 'hur-ray', ')', ')'] +# +# When this is passed to parse_expr() it returns the expression +# ['hip', ['hop', 'hur-ray']] +# plus an empty token list (because there are no tokens left. +# +# When a file containing the example is passed to parse_file() it returns +# a list whose only element is the output of parse_expr() above: +# [['hip', ['hop', 'hur-ray']]] + + +# TOKENIZING: +# +# Comments start with semicolon (;) and continue till the end of the line. +# +# Tokens are separated by whitespace, except the following characters +# always form a separate token (outside strings): +# ( ) ' +# Strings are enclosed in double quotes (") and backslash (\) is used +# as escape character in strings. diff --git a/lib/path.py b/lib/path.py new file mode 100644 index 0000000..be2340f --- /dev/null +++ b/lib/path.py @@ -0,0 +1,125 @@ +# Module 'path' -- common operations on POSIX pathnames + +import posix +import stat + + +# Intelligent pathname concatenation. +# Inserts a '/' unless the first part is empty or already ends in '/'. +# Ignores the first part altogether if the second part is absolute +# (begins with '/'). +# +def cat(a, b): + if b[:1] = '/': return b + if a = '' or a[-1:] = '/': return a + b + return a + '/' + b + + +# Split a path in head (empty or ending in '/') and tail (no '/'). +# The tail will be empty if the path ends in '/'. +# +def split(p): + head, tail = '', '' + for c in p: + tail = tail + c + if c = '/': + head, tail = head + tail, '' + return head, tail + + +# Return the tail (basename) part of a path. +# +def basename(p): + return split(p)[1] + + +# Return the longest prefix of all list elements. +# +def commonprefix(m): + if not m: return '' + prefix = m[0] + for item in m: + for i in range(len(prefix)): + if prefix[:i+1] <> item[:i+1]: + prefix = prefix[:i] + if i = 0: return '' + break + return prefix + + +# Does a file/directory exist? +# +def exists(path): + try: + st = posix.stat(path) + except posix.error: + return 0 + return 1 + + +# Is a path a posix directory? +# +def isdir(path): + try: + st = posix.stat(path) + except posix.error: + return 0 + return stat.S_ISDIR(st[stat.ST_MODE]) + + +# Is a path a symbolic link? +# This will always return false on systems where posix.lstat doesn't exist. +# +def islink(path): + try: + st = posix.lstat(path) + except (posix.error, NameError): + return 0 + return stat.S_ISLNK(st[stat.ST_MODE]) + + +_mounts = [] + +def _getmounts(): + import commands, string + mounts = [] + data = commands.getoutput('/etc/mount') + lines = string.splitfields(data, '\n') + for line in lines: + words = string.split(line) + if len(words) >= 3 and words[1] = 'on': + mounts.append(words[2]) + return mounts + + +# Is a path a mount point? +# This only works for normalized, absolute paths, +# and only if the mount table as printed by /etc/mount is correct. +# Sorry. +# +def ismount(path): + if not _mounts: + _mounts[:] = _getmounts() + return path in _mounts + + +# Directory tree walk. +# For each directory under top (including top itself), +# func(arg, dirname, filenames) is called, where dirname +# is the name of the directory and filenames is the list of +# files (and subdirectories etc.) in the directory. +# func may modify the filenames list, to implement a filter, +# or to impose a different order of visiting. +# +def walk(top, func, arg): + try: + names = posix.listdir(top) + except posix.error: + return + func(arg, top, names) + exceptions = ('.', '..') + for name in names: + if name not in exceptions: + name = cat(top, name) + if isdir(name): + walk(name, func, arg) diff --git a/lib/poly.py b/lib/poly.py new file mode 100644 index 0000000..a7c24ec --- /dev/null +++ b/lib/poly.py @@ -0,0 +1,55 @@ +# module 'poly' -- Polynomials + +# A polynomial is represented by a list of coefficients, e.g., +# [1, 10, 5] represents 1*x**0 + 10*x**1 + 5*x**2 (or 1 + 10x + 5x**2). +# There is no way to suppress internal zeros; trailing zeros are +# taken out by normalize(). + +def normalize(p): # Strip unnecessary zero coefficients + n = len(p) + while p: + if p[n-1]: return p[:n] + n = n-1 + return [] + +def plus(a, b): + if len(a) < len(b): a, b = b, a # make sure a is the longest + res = a[:] # make a copy + for i in range(len(b)): + res[i] = res[i] + b[i] + return normalize(res) + +def minus(a, b): + if len(a) < len(b): a, b = b, a # make sure a is the longest + res = a[:] # make a copy + for i in range(len(b)): + res[i] = res[i] - b[i] + return normalize(res) + +def one(power, coeff): # Representation of coeff * x**power + res = [] + for i in range(power): res.append(0) + return res + [coeff] + +def times(a, b): + res = [] + for i in range(len(a)): + for j in range(len(b)): + res = plus(res, one(i+j, a[i]*b[j])) + return res + +def power(a, n): # Raise polynomial a to the positive integral power n + if n = 0: return [1] + if n = 1: return a + if n/2*2 = n: + b = power(a, n/2) + return times(b, b) + return times(power(a, n-1), a) + +def der(a): # First derivative + res = a[1:] + for i in range(len(res)): + res[i] = res[i] * (i+1) + return res + +# Computing a primitive function would require rational arithmetic... diff --git a/lib/rand.py b/lib/rand.py new file mode 100644 index 0000000..0ca1a53 --- /dev/null +++ b/lib/rand.py @@ -0,0 +1,12 @@ +# Module 'rand' + +import whrandom + +def srand(seed): + whrandom.seed(seed%256, seed/256%256, seed/65536%256) + +def rand(): + return int(whrandom.random() * 32768.0) % 32768 + +def choice(seq): + return seq[rand() % len(seq)] diff --git a/lib/rect.py b/lib/rect.py new file mode 100644 index 0000000..8f61e11 --- /dev/null +++ b/lib/rect.py @@ -0,0 +1,88 @@ +# Module 'rect'. +# +# Operations on rectangles. +# There is some normalization: all results return the object 'empty' +# if their result would contain no points. + + +# Exception. +# +error = 'rect.error' + + +# The empty rectangle. +# +empty = (0, 0), (0, 0) + + +# Check if a rectangle is empty. +# +def is_empty((left, top), (right, bottom)): + return left >= right or top >= bottom + + +# Compute the intersection or two or more rectangles. +# This works with a list or tuple argument. +# +def intersect(list): + if not list: raise error, 'intersect called with empty list' + if is_empty(list[0]): return empty + (left, top), (right, bottom) = list[0] + for rect in list[1:]: + if is_empty(rect): + return empty + (l, t), (r, b) = rect + if left < l: left = l + if top < t: top = t + if right > r: right = r + if bottom > b: bottom = b + if is_empty((left, top), (right, bottom)): + return empty + return (left, top), (right, bottom) + + +# Compute the smallest rectangle containing all given rectangles. +# This works with a list or tuple argument. +# +def union(list): + (left, top), (right, bottom) = empty + for (l, t), (r, b) in list[1:]: + if not is_empty((l, t), (r, b)): + if l < left: left = l + if t < top: top = t + if r > right: right = r + if b > bottom: bottom = b + res = (left, top), (right, bottom) + if is_empty(res): + return empty + return res + + +# Check if a point is in a rectangle. +# +def pointinrect((h, v), ((left, top), (right, bottom))): + return left <= h < right and top <= v < bottom + + +# Return a rectangle that is dh, dv inside another +# +def inset(((left, top), (right, bottom)), (dh, dv)): + left = left + dh + top = top + dv + right = right - dh + bottom = bottom - dv + r = (left, top), (right, bottom) + if is_empty(r): + return empty + else: + return r + + +# Conversions between rectangles and 'geometry tuples', +# given as origin (h, v) and dimensions (width, height). +# +def rect2geom((left, top), (right, bottom)): + return (left, top), (right-left, bottom-top) + +def geom2rect((h, v), (width, height)): + return (h, v), (h+width, v+height) diff --git a/lib/selection.py b/lib/selection.py new file mode 100644 index 0000000..22a0ea9 --- /dev/null +++ b/lib/selection.py @@ -0,0 +1,72 @@ +# DAWKINS' BLIND WATCHMAKER -- "METHINKS IT IS LIKE A WEASEL" EXAMPLE +# +# Start with a random phrase. Successively: +# Breed a next generation by copying with small mutations. +# Choose the member of the next generation that most closely resembles +# the target phrase to start breeding the next generation. +# How many generations will it take before the target is reached? + +from whrandom import random + +# Parameters determining the problem space +Target = 'methinks it is like a weasel' +Alphabet = ' abcdefghijklmnopqrstuvwxyz' + +# Parameters to play with +GenerationSize = 100 +MutationChance = 1.0/28.0 + +IndexSet = range(len(Target)) # Used to speed up loops + +def choice(sequence): + n = len(sequence) + i = int(random() * float(n)) % n + return sequence[i] + +def random_phrase(): + phrase = '' + for i in IndexSet: + phrase = phrase + choice(Alphabet) + return phrase + +def mutate_phrase(phrase): + mutant = phrase + for i in range(int(0.5 + MutationChance*float(len(phrase)))): + i = choice(IndexSet) + c = choice(Alphabet) + mutant = mutant[:i] + c + mutant[i+1:] + #print `mutant` + return mutant + +def matching_factor(phrase): + factor = 0 + for i in IndexSet: + if phrase[i] = Target[i]: factor = factor + 1 + return factor + +def breed_generation(phrase): + generation = [phrase] + while len(generation) < GenerationSize: + generation.append(mutate_phrase(phrase)) + return generation + +def select_best(generation): + factor, selected = -1, '' + for phrase in generation: + f = matching_factor(phrase) + if f > factor: + factor, selected = f, phrase + return selected + +def main(): + gen = 0 + phrase = random_phrase() + print gen, `phrase` + while phrase <> Target: + next_generation = breed_generation(phrase) + #print next_generation + phrase = select_best(next_generation) + gen = gen+1 + print gen, `phrase` + +main() diff --git a/lib/shutil.py b/lib/shutil.py new file mode 100644 index 0000000..c01d889 --- /dev/null +++ b/lib/shutil.py @@ -0,0 +1,70 @@ +# Module 'shutil' -- utility functions usable in a shell-like program + +import posix +import path + +MODEBITS = 010000 # Lower 12 mode bits +# Change this to 01000 (9 mode bits) to avoid copying setuid etc. + +# Copy data from src to dst +# +def copyfile(src, dst): + fsrc = open(src, 'r') + fdst = open(dst, 'w') + while 1: + buf = fsrc.read(16*1024) + if not buf: break + fdst.write(buf) + +# Copy mode bits from src to dst +# +def copymode(src, dst): + st = posix.stat(src) + mode = divmod(st[0], MODEBITS)[1] + posix.chmod(dst, mode) + +# Copy all stat info (mode bits, atime and mtime) from src to dst +# +def copystat(src, dst): + st = posix.stat(src) + mode = divmod(st[0], MODEBITS)[1] + posix.chmod(dst, mode) + posix.utimes(dst, st[7:9]) + +# Copy data and mode bits ("cp src dst") +# +def copy(src, dst): + copyfile(src, dst) + copymode(src, dst) + +# Copy data and all stat info ("cp -p src dst") +# +def copy2(src, dst): + copyfile(src, dst) + copystat(src, dst) + +# Recursively copy a directory tree. +# The destination must not already exist. +# +def copytree(src, dst): + names = posix.listdir(src) + posix.mkdir(dst, 0777) + dot_dotdot = '.', '..' + for name in names: + if name not in dot_dotdot: + srcname = path.cat(src, name) + dstname = path.cat(dst, name) + #print 'Copying', srcname, 'to', dstname + try: + #if path.islink(srcname): + # linkto = posix.readlink(srcname) + # posix.symlink(linkto, dstname) + #elif path.isdir(srcname): + if path.isdir(srcname): + copytree(srcname, dstname) + else: + copy2(srcname, dstname) + # XXX What about devices, sockets etc.? + except posix.error, why: + print 'Could not copy', srcname, 'to', dstname, + print '(', why[1], ')' diff --git a/lib/stat.py b/lib/stat.py new file mode 100644 index 0000000..83855b6 --- /dev/null +++ b/lib/stat.py @@ -0,0 +1,57 @@ +# Module 'stat' + +# Defines constants and functions for interpreting stat/lstat struct +# as returned by posix.stat() and posix.lstat() (if it exists). + +# XXX This module may have to be adapted for UNIXoid systems whose +# deviates from AT&T or BSD UNIX; their S_IF* constants +# may differ. + +# Suggested usage: from stat import * + +# Tuple indices for stat struct members + +ST_MODE = 0 +ST_INO = 1 +ST_DEV = 2 +ST_NLINK = 3 +ST_UID = 4 +ST_GID = 5 +ST_SIZE = 6 +ST_ATIME = 7 +ST_MTIME = 8 +ST_CTIME = 9 + +def S_IMODE(mode): + return mode%4096 +def S_IFMT(mode): + return mode - mode%4096 + +S_IFDIR = 0040000 +S_IFCHR = 0020000 +S_IFBLK = 0060000 +S_IFREG = 0100000 +S_IFIFO = 0010000 +S_IFLNK = 0120000 +S_IFSOCK = 0140000 + +def S_ISDIR(mode): + return S_IFMT(mode) = S_IFDIR + +def S_ISCHR(mode): + return S_IFMT(mode) = S_IFCHR + +def S_ISBLK(mode): + return S_IFMT(mode) = S_IFBLK + +def S_ISREG(mode): + return S_IFMT(mode) = S_IFREG + +def S_ISFIFO(mode): + return S_IFMT(mode) = S_IFIFO + +def S_ISLNK(mode): + return S_IFMT(mode) = S_IFLNK + +def S_ISSOCK(mode): + return S_IFMT(mode) = S_IFSOCK diff --git a/lib/statcache.py b/lib/statcache.py new file mode 100644 index 0000000..0eaf810 --- /dev/null +++ b/lib/statcache.py @@ -0,0 +1,86 @@ +# Module 'statcache' +# +# Maintain a cache of file stats. +# There are functions to reset the cache or to selectively remove items. + +import posix +from stat import * + +# The cache. +# Keys are pathnames, values are `posix.stat' outcomes. +# +cache = {} + + +# Stat a file, possibly out of the cache. +# +def stat(path): + if cache.has_key(path): + return cache[path] + cache[path] = ret = posix.stat(path) + return ret + + +# Reset the cache completely. +# Hack: to reset a global variable, we import this module. +# +def reset(): + import statcache + # Check that we really imported the same module + if cache is not statcache.cache: + raise 'sorry, statcache identity crisis' + statcache.cache = {} + + +# Remove a given item from the cache, if it exists. +# +def forget(path): + if cache.has_key(path): + del cache[path] + + +# Remove all pathnames with a given prefix. +# +def forget_prefix(prefix): + n = len(prefix) + for path in cache.keys(): + if path[:n] = prefix: + del cache[path] + + +# Forget about a directory and all entries in it, but not about +# entries in subdirectories. +# +def forget_dir(prefix): + if prefix[-1:] = '/' and prefix <> '/': + prefix = prefix[:-1] + forget(prefix) + if prefix[-1:] <> '/': + prefix = prefix + '/' + n = len(prefix) + for path in cache.keys(): + if path[:n] = prefix: + rest = path[n:] + if rest[-1:] = '/': rest = rest[:-1] + if '/' not in rest: + del cache[path] + + +# Remove all pathnames except with a given prefix. +# Normally used with prefix = '/' after a chdir(). +# +def forget_except_prefix(prefix): + n = len(prefix) + for path in cache.keys(): + if path[:n] <> prefix: + del cache[path] + + +# Check for directory. +# +def isdir(path): + try: + st = stat(path) + except posix.error: + return 0 + return S_ISDIR(st[ST_MODE]) diff --git a/lib/stdwinevents.py b/lib/stdwinevents.py new file mode 100644 index 0000000..fc1c1e7 --- /dev/null +++ b/lib/stdwinevents.py @@ -0,0 +1,46 @@ +# Module 'stdwinevents' -- Constants for stdwin event types +# +# Suggested usage: +# from stdwinevents import * + +# The function stdwin.getevent() returns a tuple containing: +# (type, window, detail) +# where detail may be or a value depending on type, see below: + +# Values for type: + +WE_NULL = 0 # not reported -- means 'no event' internally +WE_ACTIVATE = 1 # detail is +WE_CHAR = 2 # detail is the character +WE_COMMAND = 3 # detail is one of the WC_* constants below +WE_MOUSE_DOWN = 4 # detail is ((h, v), clicks, button, mask) +WE_MOUSE_MOVE = 5 # ditto +WE_MOUSE_UP = 6 # ditto +WE_MENU = 7 # detail is (menu, item) +WE_SIZE = 8 # detail is (width, height) [???] +WE_MOVE = 9 # not reported -- reserved for future use +WE_DRAW = 10 # detail is ((left, top), (right, bottom)) +WE_TIMER = 11 # detail is +WE_DEACTIVATE = 12 # detail is +WE_EXTERN = 13 # detail is +WE_KEY = 14 # detail is ??? +WE_LOST_SEL = 15 # detail is selection number +WE_CLOSE = 16 # detail is + +# Values for detail when type is WE_COMMAND: + +WC_CLOSE = 1 # user hit close box +WC_LEFT = 2 # left arrow key +WC_RIGHT = 3 # right arrow key +WC_UP = 4 # up arrow key +WC_DOWN = 5 # down arrow key +WC_CANCEL = 6 # not reported -- turned into KeyboardInterrupt +WC_BACKSPACE = 7 # backspace key +WC_TAB = 8 # tab key +WC_RETURN = 9 # return or enter key + +# Selection numbers + +WS_CLIPBOARD = 0 +WS_PRIMARY = 1 +WS_SECONDARY = 2 diff --git a/lib/stdwinsupport.py b/lib/stdwinsupport.py new file mode 100644 index 0000000..1a28072 --- /dev/null +++ b/lib/stdwinsupport.py @@ -0,0 +1,34 @@ +# Module 'stdwinsupport' + +import stdwin + +eventnames = ['null', 'activate', 'char', 'command'] +eventnames = eventnames + ['mouse_down', 'mouse_move', 'mouse_up', 'menu'] +eventnames = eventnames + ['size', 'move', 'draw', 'timer', 'deactivate'] + +we_null = 0 +we_activate = 1 +we_char = 2 +we_command = 3 +we_mouse_down = 4 +we_mouse_move = 5 +we_mouse_up = 6 +we_menu = 7 +we_size = 8 +we_move = 9 +we_draw = 10 +we_timer = 11 +we_deactivate = 12 + +commandnames = ['?', 'close', 'left', 'right', 'up', 'down', 'cancel'] +commandnames = commandnames + ['backspace', 'tab', 'return'] + +wc_close = 1 +wc_left = 2 +wc_right = 3 +wc_up = 4 +wc_down = 5 +wc_cancel = 6 # not reported -- turned into KeyboardInterrupt +wc_backspace = 7 +wc_tab = 8 +wc_return = 9 diff --git a/lib/string.py b/lib/string.py new file mode 100644 index 0000000..5262013 --- /dev/null +++ b/lib/string.py @@ -0,0 +1,129 @@ +# module 'string' -- A collection of string operations + +# XXX Some of these operations are incredibly slow and should be built in + +# Some strings for ctype-style character classification +whitespace = ' \t\n' +lowercase = 'abcdefghijklmnopqrstuvwxyz' +uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' +letters = lowercase + uppercase +digits = '0123456789' +hexdigits = digits + 'abcdef' + 'ABCDEF' +octdigits = '01234567' + +# Case conversion helpers +_caseswap = {} +for i in range(26): + _caseswap[lowercase[i]] = uppercase[i] + _caseswap[uppercase[i]] = lowercase[i] +del i + +# convert UPPER CASE letters to lower case +def lower(s): + res = '' + for c in s: + if 'A' <= c <= 'Z': c = _caseswap[c] + res = res + c + return res + +# Convert lower case letters to UPPER CASE +def upper(s): + res = '' + for c in s: + if 'a' <= c <= 'z': c = _caseswap[c] + res = res + c + return res + +# Swap lower case letters and UPPER CASE +def swapcase(s): + res = '' + for c in s: + if 'a' <= c <= 'z' or 'A' <= c <= 'Z': c = _caseswap[c] + res = res + c + return res + +# Strip leading and trailing tabs and spaces +def strip(s): + i, j = 0, len(s) + while i < j and s[i] in whitespace: i = i+1 + while i < j and s[j-1] in whitespace: j = j-1 + return s[i:j] + +# Split a string into a list of space/tab-separated words +# NB: split(s) is NOT the same as splitfields(s, ' ')! +def split(s): + res = [] + i, n = 0, len(s) + while i < n: + while i < n and s[i] in whitespace: i = i+1 + if i = n: break + j = i + while j < n and s[j] not in whitespace: j = j+1 + res.append(s[i:j]) + i = j + return res + +# Split a list into fields separated by a given string +# NB: splitfields(s, ' ') is NOT the same as split(s)! +def splitfields(s, sep): + res = [] + ns = len(s) + nsep = len(sep) + i = j = 0 + while j+nsep <= ns: + if s[j:j+nsep] = sep: + res.append(s[i:j]) + i = j = j + nsep + else: + j = j + 1 + res.append(s[i:]) + return res + +# Find substring +index_error = 'substring not found in string.index' +def index(s, sub): + n = len(sub) + for i in range(len(s) + 1 - n): + if sub = s[i:i+n]: return i + raise index_error, (s, sub) + +# Convert string to integer +atoi_error = 'non-numeric argument to string.atoi' +def atoi(str): + s = str + if s[:1] in '+-': s = s[1:] + if not s: raise atoi_error, str + for c in s: + if c not in digits: raise atoi_error, str + return eval(str) + +# Left-justify a string +def ljust(s, width): + n = len(s) + if n >= width: return s + return s + ' '*(width-n) + +# Right-justify a string +def rjust(s, width): + n = len(s) + if n >= width: return s + return ' '*(width-n) + s + +# Center a string +def center(s, width): + n = len(s) + if n >= width: return s + return ' '*((width-n)/2) + s + ' '*(width -(width-n)/2) + +# Zero-fill a number, e.g., (12, 3) --> '012' and (-3, 3) --> '-03' +# Decadent feature: the argument may be a string or a number +# (Use of this is deprecated; it should be a string as with ljust c.s.) +def zfill(x, width): + if type(x) = type(''): s = x + else: s = `x` + n = len(s) + if n >= width: return s + sign = '' + if s[0] = '-': + sign, s = '-', s[1:] + return sign + '0'*(width-n) + s diff --git a/lib/sunaudio.py b/lib/sunaudio.py new file mode 100644 index 0000000..ed1b6e4 --- /dev/null +++ b/lib/sunaudio.py @@ -0,0 +1,54 @@ +# Module 'sunaudio' -- interpret sun audio headers + +import audio + +MAGIC = '.snd' + +error = 'sunaudio sound header conversion error' + + +# convert a 4-char value to integer + +def c2i(data): + if type(data) <> type('') or len(data) <> 4: + raise error, 'c2i: bad arg (not string[4])' + bytes = audio.chr2num(data) + for i in (1, 2, 3): + if bytes[i] < 0: + bytes[i] = bytes[i] + 256 + return ((bytes[0]*256 + bytes[1])*256 + bytes[2])*256 + bytes[3] + + +# read a sound header from an open file + +def gethdr(fp): + if fp.read(4) <> MAGIC: + raise error, 'gethdr: bad magic word' + hdr_size = c2i(fp.read(4)) + data_size = c2i(fp.read(4)) + encoding = c2i(fp.read(4)) + sample_rate = c2i(fp.read(4)) + channels = c2i(fp.read(4)) + excess = hdr_size - 24 + if excess < 0: + raise error, 'gethdr: bad hdr_size' + if excess > 0: + info = fp.read(excess) + else: + info = '' + return (data_size, encoding, sample_rate, channels, info) + + +# read and print the sound header of a named file + +def printhdr(file): + hdr = gethdr(open(file, 'r')) + data_size, encoding, sample_rate, channels, info = hdr + while info[-1:] = '\0': + info = info[:-1] + print 'File name: ', file + print 'Data size: ', data_size + print 'Encoding: ', encoding + print 'Sample rate:', sample_rate + print 'Channels: ', channels + print 'Info: ', `info` diff --git a/lib/tablewin.py b/lib/tablewin.py new file mode 100644 index 0000000..df06476 --- /dev/null +++ b/lib/tablewin.py @@ -0,0 +1,237 @@ +# Module 'tablewin' + +# Display a table, with per-item actions: + +# A1 | A2 | A3 | .... | AN +# B1 | B2 | B3 | .... | BN +# C1 | C2 | C3 | .... | CN +# .. | .. | .. | .... | .. +# Z1 | Z2 | Z3 | .... | ZN + +# Not all columns need to have the same length. +# The data structure is a list of columns; +# each column is a list of items. +# Each item is a pair of a string and an action procedure. +# The first item may be a column title. + +import stdwin +import gwin + +def open(title, data): # Public function to open a table window + # + # Set geometry parameters (one day, these may be changeable) + # + margin = stdwin.textwidth(' ') + lineheight = stdwin.lineheight() + # + # Geometry calculations + # + colstarts = [0] + totwidth = 0 + maxrows = 0 + for coldata in data: + # Height calculations + rows = len(coldata) + if rows > maxrows: maxrows = rows + # Width calculations + width = colwidth(coldata) + margin + totwidth = totwidth + width + colstarts.append(totwidth) + # + # Calculate document and window height + # + docwidth, docheight = totwidth, maxrows*lineheight + winwidth, winheight = docwidth, docheight + if winwidth > stdwin.textwidth('n')*100: winwidth = 0 + if winheight > stdwin.lineheight()*30: winheight = 0 + # + # Create the window + # + stdwin.setdefwinsize(winwidth, winheight) + w = gwin.open(title) + # + # Set properties and override methods + # + w.data = data + w.margin = margin + w.lineheight = lineheight + w.colstarts = colstarts + w.totwidth = totwidth + w.maxrows = maxrows + w.selection = (-1, -1) + w.lastselection = (-1, -1) + w.selshown = 0 + w.setdocsize(docwidth, docheight) + w.draw = draw + w.mup = mup + w.arrow = arrow + # + # Return + # + return w + +def update(w, data): # Change the data + # + # Hide selection + # + hidesel(w, w.begindrawing()) + # + # Get old geometry parameters + # + margin = w.margin + lineheight = w.lineheight + # + # Geometry calculations + # + colstarts = [0] + totwidth = 0 + maxrows = 0 + for coldata in data: + # Height calculations + rows = len(coldata) + if rows > maxrows: maxrows = rows + # Width calculations + width = colwidth(coldata) + margin + totwidth = totwidth + width + colstarts.append(totwidth) + # + # Calculate document and window height + # + docwidth, docheight = totwidth, maxrows*lineheight + # + # Set changed properties and change window size + # + w.data = data + w.colstarts = colstarts + w.totwidth = totwidth + w.maxrows = maxrows + w.change((0, 0), (10000, 10000)) + w.setdocsize(docwidth, docheight) + w.change((0, 0), (docwidth, docheight)) + # + # Show selection, or forget it if out of range + # + showsel(w, w.begindrawing()) + if not w.selshown: w.selection = (-1, -1) + +def colwidth(coldata): # Subroutine to calculate column width + maxwidth = 0 + for string, action in coldata: + width = stdwin.textwidth(string) + if width > maxwidth: maxwidth = width + return maxwidth + +def draw(w, ((left, top), (right, bottom))): # Draw method + ileft = whichcol(w, left) + iright = whichcol(w, right-1) + 1 + if iright > len(w.data): iright = len(w.data) + itop = divmod(top, w.lineheight)[0] + if itop < 0: itop = 0 + ibottom, remainder = divmod(bottom, w.lineheight) + if remainder: ibottom = ibottom + 1 + d = w.begindrawing() + if ileft <= w.selection[0] < iright: + if itop <= w.selection[1] < ibottom: + hidesel(w, d) + d.erase((left, top), (right, bottom)) + for i in range(ileft, iright): + col = w.data[i] + jbottom = len(col) + if ibottom < jbottom: jbottom = ibottom + h = w.colstarts[i] + v = itop * w.lineheight + for j in range(itop, jbottom): + string, action = col[j] + d.text((h, v), string) + v = v + w.lineheight + showsel(w, d) + +def mup(w, detail): # Mouse up method + (h, v), nclicks, button, mask = detail + icol = whichcol(w, h) + if 0 <= icol < len(w.data): + irow = divmod(v, w.lineheight)[0] + col = w.data[icol] + if 0 <= irow < len(col): + string, action = col[irow] + action(w, string, (icol, irow), detail) + +def whichcol(w, h): # Return column number (may be >= len(w.data)) + for icol in range(0, len(w.data)): + if h < w.colstarts[icol+1]: + return icol + return len(w.data) + +def arrow(w, type): + import stdwinsupport + S = stdwinsupport + if type = S.wc_left: + incr = -1, 0 + elif type = S.wc_up: + incr = 0, -1 + elif type = S.wc_right: + incr = 1, 0 + elif type = S.wc_down: + incr = 0, 1 + else: + return + icol, irow = w.lastselection + icol = icol + incr[0] + if icol < 0: icol = len(w.data)-1 + if icol >= len(w.data): icol = 0 + if 0 <= icol < len(w.data): + irow = irow + incr[1] + if irow < 0: irow = len(w.data[icol]) - 1 + if irow >= len(w.data[icol]): irow = 0 + else: + irow = 0 + if 0 <= icol < len(w.data) and 0 <= irow < len(w.data[icol]): + w.lastselection = icol, irow + string, action = w.data[icol][irow] + detail = (0, 0), 1, 1, 1 + action(w, string, (icol, irow), detail) + + +# Selection management +# TO DO: allow multiple selected entries + +def select(w, selection): # Public function to set the item selection + d = w.begindrawing() + hidesel(w, d) + w.selection = selection + showsel(w, d) + if w.selshown: lastselection = selection + +def hidesel(w, d): # Hide the selection, if shown + if w.selshown: invertsel(w, d) + +def showsel(w, d): # Show the selection, if hidden + if not w.selshown: invertsel(w, d) + +def invertsel(w, d): # Invert the selection, if valid + icol, irow = w.selection + if 0 <= icol < len(w.data) and 0 <= irow < len(w.data[icol]): + left = w.colstarts[icol] + right = w.colstarts[icol+1] + top = irow * w.lineheight + bottom = (irow+1) * w.lineheight + d.invert((left, top), (right, bottom)) + w.selshown = (not w.selshown) + + +# Demonstration + +def demo_action(w, string, (icol, irow), detail): # Action function for demo + select(w, (irow, icol)) + +def demo(): # Demonstration + da = demo_action # shorthand + col0 = [('a1', da), ('bbb1', da), ('c1', da)] + col1 = [('a2', da), ('bbb2', da)] + col2 = [('a3', da), ('b3', da), ('c3', da), ('d4', da), ('d5', da)] + col3 = [] + for i in range(1, 31): col3.append('xxx' + `i`, da) + data = [col0, col1, col2, col3] + w = open('tablewin.demo', data) + gwin.mainloop() + return w diff --git a/lib/tb.py b/lib/tb.py new file mode 100644 index 0000000..9f2c92c --- /dev/null +++ b/lib/tb.py @@ -0,0 +1,220 @@ +# Print tracebacks, with a dump of local variables. +# Also an interactive stack trace browser. + +import sys +try: + import mac + os = mac +except NameError: + import posix + os = posix +from stat import * +import string + +def br(): browser(sys.last_traceback) + +def tb(): printtb(sys.last_traceback) + +def browser(tb): + if not tb: + print 'No traceback.' + return + tblist = [] + while tb: + tblist.append(tb) + tb = tb.tb_next + ptr = len(tblist)-1 + tb = tblist[ptr] + while 1: + if tb <> tblist[ptr]: + tb = tblist[ptr] + print `ptr` + ':', + printtbheader(tb) + try: + line = raw_input('TB: ') + except KeyboardInterrupt: + print '\n[Interrupted]' + break + except EOFError: + print '\n[EOF]' + break + cmd = string.strip(line) + if cmd: + if cmd = 'quit': + break + elif cmd = 'list': + browserlist(tb) + elif cmd = 'up': + if ptr-1 >= 0: ptr = ptr-1 + else: print 'Bottom of stack.' + elif cmd = 'down': + if ptr+1 < len(tblist): ptr = ptr+1 + else: print 'Top of stack.' + elif cmd = 'locals': + printsymbols(tb.tb_frame.f_locals) + elif cmd = 'globals': + printsymbols(tb.tb_frame.f_globals) + elif cmd in ('?', 'help'): + browserhelp() + else: + browserexec(tb, cmd) + +def browserlist(tb): + filename = tb.tb_frame.f_code.co_filename + lineno = tb.tb_lineno + last = lineno + first = max(1, last-10) + for i in range(first, last+1): + if i = lineno: prefix = '***' + string.rjust(`i`, 4) + ':' + else: prefix = string.rjust(`i`, 7) + ':' + line = readfileline(filename, i) + if line[-1:] = '\n': line = line[:-1] + print prefix + line + +def browserexec(tb, cmd): + locals = tb.tb_frame.f_locals + globals = tb.tb_frame.f_globals + try: + exec(cmd+'\n', globals, locals) + except: + print '*** Exception:', + print sys.exc_type, + if sys.exc_value <> None: + print ':', sys.exc_value, + print + print 'Type help to get help.' + +def browserhelp(): + print + print ' This is the traceback browser. Commands are:' + print ' up : move one level up in the call stack' + print ' down : move one level down in the call stack' + print ' locals : print all local variables at this level' + print ' globals : print all global variables at this level' + print ' list : list source code around the failure' + print ' help : print help (what you are reading now)' + print ' quit : back to command interpreter' + print ' Typing any other 1-line statement will execute it' + print ' using the current level\'s symbol tables' + print + +def printtb(tb): + while tb: + print1tb(tb) + tb = tb.tb_next + +def print1tb(tb): + printtbheader(tb) + if tb.tb_frame.f_locals is not tb.tb_frame.f_globals: + printsymbols(tb.tb_frame.f_locals) + +def printtbheader(tb): + filename = tb.tb_frame.f_code.co_filename + lineno = tb.tb_lineno + info = '"' + filename + '"(' + `lineno` + ')' + line = readfileline(filename, lineno) + if line: + info = info + ': ' + string.strip(line) + print info + +def printsymbols(d): + keys = d.keys() + keys.sort() + for name in keys: + print ' ' + string.ljust(name, 12) + ':', + printobject(d[name], 4) + print + +def printobject(v, maxlevel): + if v = None: + print 'None', + elif type(v) in (type(0), type(0.0)): + print v, + elif type(v) = type(''): + if len(v) > 20: + print `v[:17] + '...'`, + else: + print `v`, + elif type(v) = type(()): + print '(', + printlist(v, maxlevel) + print ')', + elif type(v) = type([]): + print '[', + printlist(v, maxlevel) + print ']', + elif type(v) = type({}): + print '{', + printdict(v, maxlevel) + print '}', + else: + print v, + +def printlist(v, maxlevel): + n = len(v) + if n = 0: return + if maxlevel <= 0: + print '...', + return + for i in range(min(6, n)): + printobject(v[i], maxlevel-1) + if i+1 < n: print ',', + if n > 6: print '...', + +def printdict(v, maxlevel): + keys = v.keys() + n = len(keys) + if n = 0: return + if maxlevel <= 0: + print '...', + return + keys.sort() + for i in range(min(6, n)): + key = keys[i] + print `key` + ':', + printobject(v[key], maxlevel-1) + if i+1 < n: print ',', + if n > 6: print '...', + +_filecache = {} + +def readfileline(filename, lineno): + try: + stat = os.stat(filename) + except os.error, msg: + print 'Cannot stat', filename, '--', msg + return '' + cache_ok = 0 + if _filecache.has_key(filename): + cached_stat, lines = _filecache[filename] + if stat[ST_SIZE] = cached_stat[ST_SIZE] and \ + stat[ST_MTIME] = cached_stat[ST_MTIME]: + cache_ok = 1 + else: + print 'Stale cache entry for', filename + del _filecache[filename] + if not cache_ok: + lines = readfilelines(filename) + if not lines: + return '' + _filecache[filename] = stat, lines + if 0 <= lineno-1 < len(lines): + return lines[lineno-1] + else: + print 'Line number out of range, last line is', len(lines) + return '' + +def readfilelines(filename): + try: + fp = open(filename, 'r') + except: + print 'Cannot open', filename + return [] + lines = [] + while 1: + line = fp.readline() + if not line: break + lines.append(line) + if not lines: + print 'Empty file', filename + return lines diff --git a/lib/testall.py b/lib/testall.py new file mode 100644 index 0000000..d9fd7e5 --- /dev/null +++ b/lib/testall.py @@ -0,0 +1,416 @@ +# Module 'testall' +# +# Python test set, should exercise: +# - all lexical and grammatical constructs +# - all opcodes from "opcode.h" +# - all operations on all object types +# - all builtin functions +# Ideally also: +# - all possible exception situations (Thank God we've got 'try') +# - all boundary cases + + +TestFailed = 'testall -- test failed' # Exception + + +######################################################### +# Part 1. Test all lexical and grammatical constructs. +# This just tests whether the parser accepts them all. +######################################################### + +print '1. Parser' + +print '1.1 Tokens' + +print '1.1.1 Backslashes' + +# Backslash means line continuation: +x = 1 \ ++ 1 +if x <> 2: raise TestFailed, 'backslash for line continuation' + +# Backslash does not means continuation in comments :\ +x = 0 +if x <> 0: raise TestFailed, 'backslash ending comment' + +print '1.1.2 Number formats' + +if 0xff <> 255: raise TestFailed, 'hex number' +if 0377 <> 255: raise TestFailed, 'octal number' +x = 3.14 +x = 0.314 +x = 3e14 +x = 3E14 +x = 3e-14 + +print '1.2 Grammar' + +print 'single_input' # NEWLINE | simple_stmt | compound_stmt NEWLINE +# XXX can't test in a script -- this rule is only used when interactive + +print 'file_input' # (NEWLINE | stmt)* ENDMARKER +# Being tested as this very moment this very module + +print 'expr_input' # testlist NEWLINE +# XXX Hard to test -- used only in calls to input() + +print 'eval_input' # testlist ENDMARKER +x = eval('1, 0 or 1') + +print 'funcdef' # 'def' NAME parameters ':' suite +### parameters: '(' [fplist] ')' +### fplist: fpdef (',' fpdef)* +### fpdef: NAME | '(' fplist ')' +def f1(): pass +def f2(one_argument): pass +def f3(two, arguments): pass +def f4(two, (compound, (arguments))): pass + +### stmt: simple_stmt | compound_stmt +### simple_stmt: expr_stmt | print_stmt | pass_stmt | del_stmt | flow_stmt | import_stmt +# Tested below + +print 'expr_stmt' # (exprlist '=')* exprlist NEWLINE +1 +1, 2, 3 +x = 1 +x = 1, 2, 3 +x = y = z = 1, 2, 3 +x, y, z = 1, 2, 3 +abc = a, b, c = x, y, z = xyz = 1, 2, (3, 4) +# NB these variables are deleted below + +print 'print_stmt' # 'print' (test ',')* [test] NEWLINE +print 1, 2, 3 +print 1, 2, 3, +print +print 0 or 1, 0 or 1, +print 0 or 1 + +print 'del_stmt' # 'del' exprlist NEWLINE +del abc +del x, y, (z, xyz) + +print 'pass_stmt' # 'pass' NEWLINE +pass + +print 'flow_stmt' # break_stmt | return_stmt | raise_stmt +# Tested below + +print 'break_stmt' # 'break' NEWLINE +while 1: break + +print 'return_stmt' # 'return' [testlist] NEWLINE +def g1(): return +def g2(): return 1 +g1() +x = g2() + +print 'raise_stmt' # 'raise' expr [',' expr] NEWLINE +try: raise RuntimeError, 'just testing' +except RuntimeError: pass +try: raise KeyboardInterrupt +except KeyboardInterrupt: pass + +print 'import_stmt' # 'import' NAME (',' NAME)* NEWLINE | 'from' NAME 'import' ('*' | NAME (',' NAME)*) NEWLINE +[1] +import sys +[2] +import time, math +[3] +from time import sleep +[4] +from math import * +[5] +from sys import modules, path +[6] + +### compound_stmt: if_stmt | while_stmt | for_stmt | try_stmt | funcdef | classdef +# Tested below + +print 'if_stmt' # 'if' test ':' suite ('elif' test ':' suite)* ['else' ':' suite] +if 1: pass +if 1: pass +else: pass +if 0: pass +elif 0: pass +if 0: pass +elif 0: pass +elif 0: pass +elif 0: pass +else: pass + +print 'while_stmt' # 'while' test ':' suite ['else' ':' suite] +while 0: pass +while 0: pass +else: pass + +print 'for_stmt' # 'for' exprlist 'in' exprlist ':' suite ['else' ':' suite] +[1] +for i in 1, 2, 3: pass +[2] +for i, j, k in (): pass +else: pass +[3] + +print 'try_stmt' # 'try' ':' suite (except_clause ':' suite)* ['finally' ':' suite] +### except_clause: 'except' [expr [',' expr]] +try: pass +try: 1/0 +except RuntimeError: pass +try: 1/0 +except EOFError: pass +except TypeError, msg: pass +except RuntimeError, msg: pass +except: pass +try: pass +finally: pass +try: 1/0 +except: pass +finally: pass + +print 'suite' # simple_stmt | NEWLINE INDENT NEWLINE* (stmt NEWLINE*)+ DEDENT +if 1: pass +if 1: + pass +if 1: + # + # + # + pass + pass + # + pass + # + +print 'test' # and_test ('or' and_test)* +### and_test: not_test ('and' not_test)* +### not_test: 'not' not_test | comparison +### comparison: expr (comp_op expr)* +### comp_op: '<'|'>'|'='|'>' '='|'<' '='|'<' '>'|'in'|'not' 'in'|'is'|'is' 'not' +if 1: pass +if 1 = 1: pass +if 1 < 1 > 1 = 1 >= 1 <= 1 <> 1 in 1 not in 1 is 1 is not 1: pass +if not 1 = 1 = 1: pass +if not 1 = 1 and 1 and 1: pass +if 1 and 1 or 1 and 1 and 1 or not 1 = 1 = 1 and 1: pass + +print 'expr' # term (('+'|'-') term)* +x = 1 +x = 1 + 1 +x = 1 - 1 - 1 +x = 1 - 1 + 1 - 1 + 1 + +print 'term' # factor (('*'|'/'|'%') factor)* +x = 1 * 1 +x = 1 / 1 +x = 1 % 1 +x = 1 / 1 * 1 % 1 + +print 'factor' # ('+'|'-') factor | atom trailer* +### trailer: '(' [testlist] ')' | '[' subscript ']' | '.' NAME +### subscript: expr | [expr] ':' [expr] +x = +1 +x = -1 +x = 1 +c = sys.path[0] +x = time.time() +x = sys.modules['time'].time() +a = '01234' +c = a[0] +c = a[0:5] +c = a[:5] +c = a[0:] +c = a[:] +c = a[-5:] +c = a[:-1] +c = a[-4:-3] + +print 'atom' # '(' [testlist] ')' | '[' [testlist] ']' | '{' '}' | '`' testlist '`' | NAME | NUMBER | STRING +x = (1) +x = (1 or 2 or 3) +x = (1 or 2 or 3, 2, 3) +x = [] +x = [1] +x = [1 or 2 or 3] +x = [1 or 2 or 3, 2, 3] +x = [] +x = {} +x = `x` +x = x +x = 'x' +x = 123 + +### exprlist: expr (',' expr)* [','] +### testlist: test (',' test)* [','] +# These have been exercised enough above + +print 'classdef' # 'class' NAME parameters ['=' baselist] ':' suite +### baselist: atom arguments (',' atom arguments)* +### arguments: '(' [testlist] ')' +class B(): pass +class C1() = B(): pass +class C2() = B(): pass +class D() = C1(), C2(), B(): pass +class C(): + def meth1(self): pass + def meth2(self, arg): pass + def meth3(self, (a1, a2)): pass + + +######################################################### +# Part 2. Test all opcodes from "opcode.h" +######################################################### + +print '2. Opcodes' +print 'XXX Not yet fully implemented' + +print '2.1 try inside for loop' +n = 0 +for i in range(10): + n = n+i + try: 1/0 + except NameError: pass + except RuntimeError: pass + except TypeError: pass + finally: pass + try: pass + except: pass + try: pass + finally: pass + n = n+i +if n <> 90: + raise TestFailed, 'try inside for' + + +######################################################### +# Part 3. Test all operations on all object types +######################################################### + +print '3. Object types' +print 'XXX Not yet implemented' + + +######################################################### +# Part 4. Test all built-in functions +######################################################### + +print '4. Built-in functions' + +print 'abs' +if abs(0) <> 0: raise TestFailed, 'abs(0)' +if abs(1234) <> 1234: raise TestFailed, 'abs(1234)' +if abs(-1234) <> 1234: raise TestFailed, 'abs(-1234)' +if abs(0.0) <> 0.0: raise TestFailed, 'abs(0.0)' +if abs(3.14) <> 3.14: raise TestFailed, 'abs(3.14)' +if abs(-3.14) <> 3.14: raise TestFailed, 'abs(-3.14)' + +print 'dir' +if 'x' not in dir(): raise TestFailed, 'dir()' +if 'modules' not in dir(sys): raise TestFailed, 'dir(sys)' + +print 'divmod' +if divmod(12, 7) <> (1, 5): raise TestFailed, 'divmod(12, 7)' +if divmod(-12, 7) <> (-2, 2): raise TestFailed, 'divmod(-12, 7)' +if divmod(12, -7) <> (-2, -2): raise TestFailed, 'divmod(12, -7)' +if divmod(-12, -7) <> (1, -5): raise TestFailed, 'divmod(-12, -7)' + +print 'eval' +if eval('1+1') <> 2: raise TestFailed, 'eval(\'1+1\')' + +print 'exec' +exec('z=1+1\n') +if z <> 2: raise TestFailed, 'exec(\'z=1+1\'\\n)' + +print 'float' +if float(3.14) <> 3.14: raise TestFailed, 'float(3.14)' +if float(314) <> 314.0: raise TestFailed, 'float(314)' + +print 'input' +# Can't test in a script + +print 'int' +if int(100) <> 100: raise TestFailed, 'int(100)' +if int(3.14) <> 3: raise TestFailed, 'int(3.14)' + +print 'len' +if len('123') <> 3: raise TestFailed, 'len(\'123\')' +if len(()) <> 0: raise TestFailed, 'len(())' +if len((1, 2, 3, 4)) <> 4: raise TestFailed, 'len((1, 2, 3, 4))' +if len([1, 2, 3, 4]) <> 4: raise TestFailed, 'len([1, 2, 3, 4])' +if len({}) <> 0: raise TestFailed, 'len({})' + +print 'min' +if min('123123') <> '1': raise TestFailed, 'min(\'123123\')' +if min(1, 2, 3) <> 1: raise TestFailed, 'min(1, 2, 3)' +if min((1, 2, 3, 1, 2, 3)) <> 1: raise TestFailed, 'min((1, 2, 3, 1, 2, 3))' +if min([1, 2, 3, 1, 2, 3]) <> 1: raise TestFailed, 'min([1, 2, 3, 1, 2, 3])' + +print 'max' +if max('123123') <> '3': raise TestFailed, 'max(\'123123\')' +if max(1, 2, 3) <> 3: raise TestFailed, 'max(1, 2, 3)' +if max((1, 2, 3, 1, 2, 3)) <> 3: raise TestFailed, 'max((1, 2, 3, 1, 2, 3))' +if max([1, 2, 3, 1, 2, 3]) <> 3: raise TestFailed, 'max([1, 2, 3, 1, 2, 3])' + +print 'open' +print 'NB! This test creates a file named "@test" in the current directory.' +fp = open('@test', 'w') +fp.write('The quick brown fox jumps over the lazy dog') +fp.write('.\n') +fp.write('Dear John\n') +fp.write('XXX'*100) +fp.write('YYY'*100) +fp.close() +del fp +fp = open('@test', 'r') +if fp.readline() <> 'The quick brown fox jumps over the lazy dog.\n': + raise TestFailed, 'readline()' +if fp.readline(4) <> 'Dear': raise TestFailed, 'readline(4) # short' +if fp.readline(100) <> ' John\n': raise TestFailed, 'readline(100)' +if fp.read(300) <> 'XXX'*100: raise TestFailed, 'read(300)' +if fp.read(1000) <> 'YYY'*100: raise TestFailed, 'read(1000) # truncate' +fp.close() +del fp + +print 'range' +if range(3) <> [0, 1, 2]: raise TestFailed, 'range(3)' +if range(1, 5) <> [1, 2, 3, 4]: raise TestFailed, 'range(1, 5)' +if range(0) <> []: raise TestFailed, 'range(0)' +if range(-3) <> []: raise TestFailed, 'range(-3)' +if range(1, 10, 3) <> [1, 4, 7]: raise TestFailed, 'range(1, 10, 3)' +if range(5, -5, -3) <> [5, 2, -1, -4]: raise TestFailed, 'range(5, -5, -3)' + +print 'raw_input' +savestdin = sys.stdin +try: + sys.stdin = open('@test', 'r') + if raw_input() <> 'The quick brown fox jumps over the lazy dog.': + raise TestFailed, 'raw_input()' + if raw_input('testing\n') <> 'Dear John': + raise TestFailed, 'raw_input(\'testing\\n\')' +finally: + sys.stdin = savestdin + +print 'reload' +import string +reload(string) + +print 'type' +if type('') <> type('123') or type('') = type(()): + raise TestFailed, 'type()' + + +print 'Passed all tests.' + +try: + import mac + unlink = mac.unlink +except NameError: + try: + import posix + unlink = posix.unlink + except NameError: + pass + +unlink('@test') +print 'Unlinked @test' diff --git a/lib/textwin.py b/lib/textwin.py new file mode 100644 index 0000000..d12342f --- /dev/null +++ b/lib/textwin.py @@ -0,0 +1,119 @@ +# Module 'textwin' + +# Text windows, a subclass of gwin + +import stdwin +import stdwinsupport +import gwin + +S = stdwinsupport # Shorthand + + +def fixsize(w): + docwidth, docheight = w.text.getrect()[1] + winheight = w.getwinsize()[1] + if winheight > docheight: docheight = winheight + w.setdocsize(0, docheight) + fixeditmenu(w) + +def cut(w, m, id): + s = w.text.getfocustext() + if s: + stdwin.setcutbuffer(0, s) + w.text.replace('') + fixsize(w) + +def copy(w, m, id): + s = w.text.getfocustext() + if s: + stdwin.setcutbuffer(0, s) + fixeditmenu(w) + +def paste(w, m, id): + w.text.replace(stdwin.getcutbuffer(0)) + fixsize(w) + +def addeditmenu(w): + m = w.editmenu = w.menucreate('Edit') + m.action = [] + m.additem('Cut', 'X') + m.action.append(cut) + m.additem('Copy', 'C') + m.action.append(copy) + m.additem('Paste', 'V') + m.action.append(paste) + +def fixeditmenu(w): + m = w.editmenu + f = w.text.getfocus() + can_copy = (f[0] < f[1]) + m.enable(1, can_copy) + if not w.readonly: + m.enable(0, can_copy) + m.enable(2, (stdwin.getcutbuffer(0) <> '')) + +def draw(w, area): # Draw method + w.text.draw(area) + +def size(w, newsize): # Size method + w.text.move((0, 0), newsize) + fixsize(w) + +def close(w): # Close method + del w.text # Break circular ref + gwin.close(w) + +def char(w, c): # Char method + w.text.replace(c) + fixsize(w) + +def backspace(w): # Backspace method + void = w.text.event(S.we_command, w, S.wc_backspace) + fixsize(w) + +def arrow(w, detail): # Arrow method + w.text.arrow(detail) + fixeditmenu(w) + +def mdown(w, detail): # Mouse down method + void = w.text.event(S.we_mouse_down, w, detail) + fixeditmenu(w) + +def mmove(w, detail): # Mouse move method + void = w.text.event(S.we_mouse_move, w, detail) + +def mup(w, detail): # Mouse up method + void = w.text.event(S.we_mouse_up, w, detail) + fixeditmenu(w) + +def activate(w): # Activate method + fixeditmenu(w) + +def open(title, str): # Display a string in a window + w = gwin.open(title) + w.readonly = 0 + w.text = w.textcreate((0, 0), w.getwinsize()) + w.text.replace(str) + w.text.setfocus(0, 0) + addeditmenu(w) + fixsize(w) + w.draw = draw + w.size = size + w.close = close + w.mdown = mdown + w.mmove = mmove + w.mup = mup + w.char = char + w.backspace = backspace + w.arrow = arrow + w.activate = activate + return w + +def open_readonly(title, str): # Same with char input disabled + w = open(title, str) + w.readonly = 1 + w.char = w.backspace = gwin.nop + # Disable Cut and Paste menu item; leave Copy alone + w.editmenu.enable(0, 0) + w.editmenu.enable(2, 0) + return w diff --git a/lib/util.py b/lib/util.py new file mode 100644 index 0000000..0b7778f --- /dev/null +++ b/lib/util.py @@ -0,0 +1,30 @@ +# Module 'util' -- some useful functions that don't fit elsewhere + + +# Remove an item from a list. +# No complaints if it isn't in the list at all. +# If it occurs more than once, remove the first occurrence. +# +def remove(item, list): + for i in range(len(list)): + if list[i] = item: + del list[i] + break + + +# Return a string containing a file's contents. +# +def readfile(fn): + return readopenfile(open(fn, 'r')) + + +# Read an open file until EOF. +# +def readopenfile(fp): + BUFSIZE = 512*8 + data = '' + while 1: + buf = fp.read(BUFSIZE) + if not buf: break + data = data + buf + return data diff --git a/lib/whrandom.py b/lib/whrandom.py new file mode 100644 index 0000000..ee8dc67 --- /dev/null +++ b/lib/whrandom.py @@ -0,0 +1,74 @@ +# WICHMANN-HILL RANDOM NUMBER GENERATOR +# +# Wichmann, B. A. & Hill, I. D. (1982) +# Algorithm AS 183: +# An efficient and portable pseudo-random number generator +# Applied Statistics 31 (1982) 188-190 +# +# see also: +# Correction to Algorithm AS 183 +# Applied Statistics 33 (1984) 123 +# +# McLeod, A. I. (1985) +# A remark on Algorithm AS 183 +# Applied Statistics 34 (1985),198-200 +# +# +# USE: +# whrandom.random() yields double precision random numbers +# uniformly distributed between 0 and 1. +# +# whrandom.seed() must be called before whrandom.random() +# to seed the generator + + +# Translated by Guido van Rossum from C source provided by +# Adrian Baddeley. + + +# The seed +# +_seed = [0, 0, 0] + + +# Set the seed +# +def seed(x, y, z): + _seed[:] = [x, y, z] + + +# Return the next random number in the range [0.0 .. 1.0) +# +def random(): + from math import floor # floor() function + # + [x, y, z] = _seed + x = 171 * (x % 177) - 2 * (x/177) + y = 172 * (y % 176) - 35 * (y/176) + z = 170 * (z % 178) - 63 * (z/178) + # + if x < 0: x = x + 30269 + if y < 0: y = y + 30307 + if z < 0: z = z + 30323 + # + _seed[:] = [x, y, z] + # + term = float(x)/30269.0 + float(y)/30307.0 + float(z)/30323.0 + rand = term - floor(term) + # + if rand >= 1.0: rand = 0.0 # floor() inaccuracy? + # + return rand + + +# Initialize from the current time +# +def init(): + import time + t = time.time() + seed(t%256, t/256%256, t/65536%256) + + +# Make sure the generator is preset to a nonzero value +# +init() diff --git a/lib/zmod.py b/lib/zmod.py new file mode 100644 index 0000000..3b38df9 --- /dev/null +++ b/lib/zmod.py @@ -0,0 +1,94 @@ +# module 'zmod' + +# Compute properties of mathematical "fields" formed by taking +# Z/n (the whole numbers modulo some whole number n) and an +# irreducible polynomial (i.e., a polynomial with only complex zeros), +# e.g., Z/5 and X**2 + 2. +# +# The field is formed by taking all possible linear combinations of +# a set of d base vectors (where d is the degree of the polynomial). +# +# Note that this procedure doesn't yield a field for all combinations +# of n and p: it may well be that some numbers have more than one +# inverse and others have none. This is what we check. +# +# Remember that a field is a ring where each element has an inverse. +# A ring has commutative addition and multiplication, a zero and a one: +# 0*x = x*0 = 0, 0+x = x+0 = x, 1*x = x*1 = x. Also, the distributive +# property holds: a*(b+c) = a*b + b*c. +# (XXX I forget if this is an axiom or follows from the rules.) + +import poly + + +# Example N and polynomial + +N = 5 +P = poly.plus(poly.one(0, 2), poly.one(2, 1)) # 2 + x**2 + + +# Return x modulo y. Returns >= 0 even if x < 0. + +def mod(x, y): + return divmod(x, y)[1] + + +# Normalize a polynomial modulo n and modulo p. + +def norm(a, n, p): + a = poly.modulo(a, p) + a = a[:] + for i in range(len(a)): a[i] = mod(a[i], n) + a = poly.normalize(a) + return a + + +# Make a list of all n^d elements of the proposed field. + +def make_all(mat): + all = [] + for row in mat: + for a in row: + all.append(a) + return all + +def make_elements(n, d): + if d = 0: return [poly.one(0, 0)] + sub = make_elements(n, d-1) + all = [] + for a in sub: + for i in range(n): + all.append(poly.plus(a, poly.one(d-1, i))) + return all + +def make_inv(all, n, p): + x = poly.one(1, 1) + inv = [] + for a in all: + inv.append(norm(poly.times(a, x), n, p)) + return inv + +def checkfield(n, p): + all = make_elements(n, len(p)-1) + inv = make_inv(all, n, p) + all1 = all[:] + inv1 = inv[:] + all1.sort() + inv1.sort() + if all1 = inv1: print 'BINGO!' + else: + print 'Sorry:', n, p + print all + print inv + +def rj(s, width): + if type(s) <> type(''): s = `s` + n = len(s) + if n >= width: return s + return ' '*(width - n) + s + +def lj(s, width): + if type(s) <> type(''): s = `s` + n = len(s) + if n >= width: return s + return s + ' '*(width - n) -- cgit v1.2.3