Commit 81b4e41c authored by Guido van Rossum's avatar Guido van Rossum

removing more stdwin users

parent 9ab06ee7
# Interface to the interactive CWI library catalog.
import sys
import stdwin
from stdwinevents import *
import select
import telnetlib
import vt100win
from form import Form
# Main program
def main():
vt = vt100win.VT100win()
#
host = 'biefstuk.cwi.nl'
tn = telnetlib.Telnet(host, 0)
#
try:
vt.send(tn.read_until('login: ', 10))
tn.write('cwilib\r')
#
vt.send(tn.read_until('Hit <RETURN> to continue...', 10))
tn.write('\r')
#
vt.send(tn.read_until('QUIT', 20))
except EOFError:
sys.stderr.write('Connection closed prematurely\n')
sys.exit(1)
#
define_screens(vt)
matches = vt.which_screens()
if 'menu' not in matches:
sys.stderr.write('Main menu does not appear\n')
sys.exit(1)
#
tn.write('\r\r')
vt.open('Progress -- CWI Library')
vt.set_debuglevel(0)
ui = UserInterface()
#
while 1:
try:
data = tn.read_very_eager()
except EOFError:
stdwin.message('Connection closed--goodbye')
break
if data:
print 'send...'
vt.send(data)
print 'send...done'
continue
event = stdwin.pollevent()
if event:
type, window, detail = event
if window == None and type == WE_LOST_SEL:
window = ui.queryform.window
event = type, window, detail
if type == WE_CLOSE:
break
if window in ui.windows:
ui.dispatch(type, window, detail)
elif window == vt.window:
if type == WE_NULL:
pass
elif type == WE_COMMAND:
if detail == WC_RETURN:
tn.write('\r')
elif detail == WC_BACKSPACE:
tn.write('\b')
elif detail == WC_TAB:
tn.write('\t')
elif detail == WC_UP:
tn.write('\033[A')
elif detail == WC_DOWN:
tn.write('\033[B')
elif detail == WC_RIGHT:
tn.write('\033[C')
elif detail == WC_LEFT:
tn.write('\033[D')
else:
print '*** Command:', detail
elif type == WE_CHAR:
tn.write(detail)
elif type == WE_DRAW:
vt.draw(detail)
elif type in (WE_ACTIVATE, WE_DEACTIVATE):
pass
else:
print '*** VT100 event:', type, detail
else:
print '*** Alien event:', type, window, detail
continue
rfd, wfd, xfd = select.select([tn, stdwin], [], [])
# Subroutine to define our screen recognition patterns
def define_screens(vt):
vt.define_screen('menu', {
'title': ('search', 0, 0, 80,
' SEARCH FUNCTIONS +OTHER FUNCTIONS '),
})
vt.define_screen('search', {
'title': ('search', 0, 0, 80, ' Search '),
})
vt.define_screen('shortlist', {'title': ('search', 0, 0, 80,
' Short-list')})
vt.define_screen('showrecord', {
'title': ('search', 0, 0, 80, ' Show record '),
})
vt.define_screen('timelimit', {
'limit': ('search', 12, 0, 80, ' TIME LIMIT '),
})
vt.define_screen('attention', {
'BASE': ('copy', 0, 0, 0, 'search'),
'title': ('search', 10, 0, 80, ' ATTENTION ')})
vt.define_screen('syntaxerror', {
'BASE': ('copy', 0, 0, 0, 'attention'),
'message': ('search', 12, 0, 80, ' Syntax error'),
})
vt.define_screen('emptyerror', {
'BASE': ('copy', 0, 0, 0, 'attention'),
'message': ('search', 12, 0, 80,
' Check your input. Search at least one term'),
})
vt.define_screen('unsortedwarning', {
'BASE': ('copy', 0, 0, 0, 'attention'),
'message': ('search', 12, 0, 80,
' Number of records exceeds sort limit'),
})
vt.define_screen('thereismore', {
'BASE': ('copy', 0, 0, 0, 'showrecord'),
'message': ('search', 15, 0, 80,
'There is more within this record. Use the arrow keys'),
})
vt.define_screen('nofurther', {
'BASE': ('copy', 0, 0, 0, 'showrecord'),
'message': ('search', 17, 0, 80, 'You cannot go further\.'),
})
vt.define_screen('nofurtherback', {
'BASE': ('copy', 0, 0, 0, 'showrecord'),
'message': ('search', 17, 0, 80,
'You cannot go further back'),
})
# Class to implement our user interface.
class UserInterface:
def __init__(self):
stdwin.setfont('7x14')
self.queryform = QueryForm()
self.listform = ListForm()
self.recordform = RecordForm()
self.forms = [self.queryform, self.listform, self.recordform]
define_query_fields(self.queryform)
self.windows = []
for form in self.forms:
if form.formheight > 0:
form.open()
self.windows.append(form.window)
def __del__(self):
self.close()
def close(self):
for form in self.forms:
form.close()
def dispatch(self, type, window, detail):
for form in self.forms:
if window == form.window:
form.dispatch(type, detail)
def define_query_fields(f):
f.define_field('name', 'Name auth./ed.', 1, 60)
f.define_field('title', 'Title', 4, 60)
f.define_field('shelfmark', 'Shelf mark', 1, 60)
f.define_field('class', 'Prim. classif.', 1, 60)
f.define_field('series', 'Series', 1, 60)
f.define_field('congress', 'Congr. pl./year', 1, 60)
f.define_field('type', 'Type', 1, 60)
class QueryForm(Form):
def __init__(self):
Form.__init__(self, 'Query form -- CWI Library')
def dispatch(self, type, detail):
if type == WE_COMMAND and detail == WC_RETURN:
print '*** SUBMIT ***'
else:
Form.dispatch(self, type, detail)
class ListForm(Form):
def __init__(self):
Form.__init__(self, 'Short list -- CWI Library')
class RecordForm(Form):
def __init__(self):
Form.__init__(self, 'Record detail -- CWI Library')
main()
# Fill-out form window
import stdwin
from stdwinevents import *
class Form:
def __init__(self, title):
self.title = title
self.window = None
self.fields = {}
self.fieldnames = []
self.formwidth = self.formheight = 0
self.focusname = None
self.tefocus = None
def define_field(self, name, label, lines, chars):
self.fieldnames.append(name)
lh = stdwin.lineheight()
cw = stdwin.textwidth('m')
left = 20*cw
top = self.formheight + 4
right = left + chars*cw
bottom = top + lines*lh
te = None
self.fields[name] = (label, left, top, right, bottom, te)
self.formheight = bottom + 2
self.formwidth = max(self.formwidth, right + 4)
def open(self):
if self.window: return
self.formwidth = max(100, self.formwidth)
self.formheight = max(50, self.formheight)
stdwin.setdefwinsize(self.formwidth, self.formheight)
stdwin.setdefscrollbars(0, 0)
self.window = stdwin.open(self.title)
self.window.setdocsize(self.formwidth, self.formheight)
for name in self.fieldnames:
label, left, top, right, bottom, te = \
self.fields[name]
rect = (left, top), (right, bottom)
te = self.window.textcreate(rect)
te.setactive(0)
te.setview(rect)
self.fields[name] = \
label, left, top, right, bottom, te
if self.fieldnames:
self.setfocus(self.fieldnames[0])
def setfocus(self, name):
if name <> self.focusname and self.tefocus:
self.tefocus.setactive(0)
self.focusname = name
if self.focusname:
self.tefocus = self.fields[self.focusname][-1]
self.tefocus.setactive(1)
else:
self.tefocus = None
def dispatch(self, type, detail):
event = type, self.window, detail
if type == WE_NULL:
pass
elif type == WE_DRAW:
self.draw(detail)
elif type == WE_MOUSE_DOWN:
x, y = detail[0]
for name in self.fieldnames:
label, left, top, right, bottom, te = \
self.fields[name]
if left <= x < right and \
top <= y < bottom:
self.setfocus(name)
break
else:
stdwin.fleep()
return
if self.tefocus:
(left, top), (right, bottom) = \
self.tefocus.getrect()
if x < left: x = left
if x >= right: x = right-1
if y < top: y = top
if y >= bottom:
y = bottom-1
x = right-1
event = type, self.window, ((x,y),)+detail[1:]
if not self.tefocus.event(event):
stdwin.fleep()
elif type in (WE_MOUSE_MOVE, WE_MOUSE_UP, WE_CHAR):
if not self.tefocus or not self.tefocus.event(event):
stdwin.fleep()
elif type == WE_MOUSE_UP:
button = detail[2]
if button == 2:
self.paste_selection()
else:
self.make_selection()
elif type == WE_COMMAND:
if detail in (WC_BACKSPACE, WC_UP, WC_DOWN,
WC_LEFT, WC_RIGHT):
if not self.tefocus or \
not self.tefocus.event(event):
stdwin.fleep()
elif detail == WC_RETURN:
print '*** Submit query'
elif detail == WC_TAB:
if not self.fields:
stdwin.fleep()
return
if not self.focusname:
i = 0
else:
i = self.fieldnames.index(
self.focusname)
i = (i+1) % len(self.fieldnames)
self.setfocus(self.fieldnames[i])
self.tefocus.setfocus(0, 0x7fff)
self.make_selection()
elif type in (WE_ACTIVATE, WE_DEACTIVATE):
pass
elif type == WE_LOST_SEL:
if self.tefocus:
a, b = self.tefocus.getfocus()
self.tefocus.setfocus(a, a)
else:
print 'Form.dispatch(%d, %s)' % (type, `detail`)
def draw(self, detail):
d = self.window.begindrawing()
d.cliprect(detail)
d.erase(detail)
self.drawform(d, detail)
d.noclip()
d.close()
# Stupid textedit objects can't draw with open draw object...
self.drawtextedit(detail)
def drawform(self, d, detail):
for name in self.fieldnames:
label, left, top, right, bottom, te = self.fields[name]
d.text((0, top), label)
d.box((left-3, top-2), (right+4, bottom+2))
def drawtextedit(self, detail):
for name in self.fieldnames:
label, left, top, right, bottom, te = self.fields[name]
te.draw(detail)
def make_selection(self):
s = self.tefocus.getfocustext()
if not s:
return
stdwin.rotatecutbuffers(1)
stdwin.setcutbuffer(0, s)
if not self.window.setselection(WS_PRIMARY, s):
stdwin.fleep()
def paste_selection(self):
if not self.tefocus:
stdwin.fleep()
return
s = stdwin.getselection(WS_PRIMARY)
if not s:
s = stdwin.getcutbuffer(0)
if not s:
stdwin.fleep()
return
self.tefocus.replace(s)
# VT100 terminal emulator.
# This is incomplete and slow, but will do for now...
# It shouldn't be difficult to extend it to be a more-or-less complete
# VT100 emulator. And little bit of profiling could go a long way...
from array import array
import regex
import string
# Tunable parameters
DEBUGLEVEL = 1
# Symbolic constants
ESC = '\033'
# VT100 emulation class
class VT100:
def __init__(self):
self.debuglevel = DEBUGLEVEL
# Unchangeable parameters (for now)
self.width = 80
self.height = 24
self.blankline = array('c', ' '*self.width)
self.blankattr = array('b', '\0'*self.width)
# Set mutable display state
self.reset()
# Set parser state
self.unfinished = ''
# Set screen recognition state
self.reset_recognizer()
def msg(self, msg, *args):
if self.debuglevel > 0:
print 'VT100:', msg%args
def set_debuglevel(self, debuglevel):
self.debuglevel = debuglevel
def reset(self):
self.lines = []
self.attrs = []
self.fill_bottom()
self.x = 0
self.y = 0
self.curattrs = []
def show(self):
lineno = 0
for line in self.lines:
lineno = lineno + 1
i = len(line)
while i > 0 and line[i-1] == ' ': i = i-1
print line[:i]
print 'CURSOR:', self.x, self.y
def fill_bottom(self):
while len(self.lines) < self.height:
self.lines.append(self.blankline[:])
self.attrs.append(self.blankattr[:])
def fill_top(self):
while len(self.lines) < self.height:
self.lines.insert(0, self.blankline[:])
self.attrs.insert(0, self.blankattr[:])
def clear_all(self):
self.lines = []
self.attrs = []
self.fill_bottom()
def clear_below(self):
del self.lines[self.y:]
del self.attrs[self.y:]
self.fill_bottom()
def clear_above(self):
del self.lines[:self.y]
del self.attrs[:self.y]
self.fill_top()
def send(self, buffer):
self.msg('send: unfinished=%s, buffer=%s',
`self.unfinished`, `buffer`)
self.unfinished = self.unfinished + buffer
i = 0
n = len(self.unfinished)
while i < n:
c = self.unfinished[i]
i = i+1
if c != ESC:
self.add_char(c)
continue
if i >= n:
i = i-1
break
c = self.unfinished[i]
i = i+1
if c == 'c':
self.reset()
continue
if c <> '[':
self.msg('unrecognized: ESC %s', `c`)
continue
argstr = ''
while i < n:
c = self.unfinished[i]
i = i+1
if c not in '0123456789;':
break
argstr = argstr + c
else:
i = i - len(argstr) - 2
break
## self.msg('found ESC [ %s %s' % (`argstr`, `c`))
args = string.splitfields(argstr, ';')
for j in range(len(args)):
s = args[j]
while s[:1] == '0': s = s[1:]
if s: args[j] = eval(s)
else: args[j] = 0
p1 = p2 = 0
if args: p1 = args[0]
if args[1:]: p2 = args[1]
if c in '@ABCDH':
if not p1: p1 = 1
if c in 'H':
if not p2: p2 = 1
if c == '@':
for j in range(p1):
self.add_char(' ')
elif c == 'A':
self.move_by(0, -p1)
elif c == 'B':
self.move_by(0, p1)
elif c == 'C':
self.move_by(p1, 0)
elif c == 'D':
self.move_by(-p1, 0)
elif c == 'H':
self.move_to(p2-1, p1-1)
elif c == 'J':
if p1 == 0: self.clear_above()
elif p1 == 1: self.clear_below()
elif p1 == 2: self.clear_all()
else: self.msg('weird ESC [ %d J', p1)
elif c == 'K':
if p1 == 0: self.erase_right()
elif p1 == 1: self.erase_left()
elif p1 == 2: self.erase_line()
else: self.msg('weird ESC [ %d K', p1)
elif c == 'm':
if p1 == 0:
self.curattrs = []
else:
if p1 not in self.curattrs:
self.curattrs.append(p1)
self.curattrs.sort()
else:
self.msg('unrecognized: ESC [ %s', `argstr+c`)
self.unfinished = self.unfinished[i:]
def add_char(self, c):
if c == '\r':
self.move_to(0, self.y)
return
if c in '\n\f\v':
self.move_to(self.x, self.y + 1)
if self.y >= self.height:
self.scroll_up(1)
self.move_to(self.x, self.height - 1)
return
if c == '\b':
self.move_by(-1, 0)
return
if c == '\a':
self.msg('BELL')
return
if c == '\t':
self.move_to((self.x+8)/8*8, self.y)
return
if c == '\0':
return
if c < ' ' or c > '~':
self.msg('ignored control char: %s', `c`)
return
if self.x >= self.width:
self.move_to(0, self.y + 1)
if self.y >= self.height:
self.scroll_up(1)
self.move_to(self.x, self.height - 1)
self.lines[self.y][self.x] = c
if self.curattrs:
self.attrs[self.y][self.x] = max(self.curattrs)
else:
self.attrs[self.y][self.x] = 0
self.move_by(1, 0)
def move_to(self, x, y):
self.x = min(max(0, x), self.width)
self.y = min(max(0, y), self.height)
def move_by(self, dx, dy):
self.move_to(self.x + dx, self.y + dy)
def scroll_up(self, nlines):
del self.lines[:max(0, nlines)]
del self.attrs[:max(0, nlines)]
self.fill_bottom()
def scroll_down(self, nlines):
del self.lines[-max(0, nlines):]
del self.attrs[-max(0, nlines):]
self.fill_top()
def erase_left(self):
x = min(self.width-1, x)
y = min(self.height-1, y)
self.lines[y][:x] = self.blankline[:x]
self.attrs[y][:x] = self.blankattr[:x]
def erase_right(self):
x = min(self.width-1, x)
y = min(self.height-1, y)
self.lines[y][x:] = self.blankline[x:]
self.attrs[y][x:] = self.blankattr[x:]
def erase_line(self):
self.lines[y][:] = self.blankline
self.attrs[y][:] = self.blankattr
# The following routines help automating the recognition of
# standard screens. A standard screen is characterized by
# a number of fields. A field is part of a line,
# characterized by a (lineno, begin, end) tuple;
# e.g. the first 10 characters of the second line are
# specified by the tuple (1, 0, 10). Fields can be:
# - regex: desired contents given by a regular expression,
# - extract: can be extracted,
# - cursor: screen is only valid if cursor in field,
# - copy: identical to another screen (position is ignored).
# A screen is defined as a dictionary full of fields. Screens
# also have names and are placed in a dictionary.
def reset_recognizer(self):
self.screens = {}
def define_screen(self, screenname, fields):
fieldscopy = {}
# Check if the fields make sense
for fieldname in fields.keys():
field = fields[fieldname]
ftype, lineno, begin, end, extra = field
if ftype in ('match', 'search'):
extra = regex.compile(extra)
elif ftype == 'extract':
extra = None
elif ftype == 'cursor':
extra = None
elif ftype == 'copy':
if not self.screens.has_key(extra):
raise ValueError, 'bad copy ref'
else:
raise ValueError, 'bad ftype: %s' % `ftype`
fieldscopy[fieldname] = (
ftype, lineno, begin, end, extra)
self.screens[screenname] = fieldscopy
def which_screens(self):
self.busy = []
self.okay = []
self.fail = []
for name in self.screens.keys():
ok = self.match_screen(name)
return self.okay[:]
def match_screen(self, name):
if name in self.busy: raise RuntimeError, 'recursive match'
if name in self.okay: return 1
if name in self.fail: return 0
self.busy.append(name)
fields = self.screens[name]
ok = 0
for key in fields.keys():
field = fields[key]
ftype, lineno, begin, end, extra = field
if ftype == 'copy':
if not self.match_screen(extra): break
elif ftype == 'search':
text = self.lines[lineno][begin:end].tostring()
if extra.search(text) < 0:
break
elif ftype == 'match':
text = self.lines[lineno][begin:end].tostring()
if extra.match(text) < 0:
break
elif ftype == 'cursor':
if self.x != lineno or not \
begin <= self.y < end:
break
else:
ok = 1
if ok:
self.okay.append(name)
else:
self.fail.append(name)
self.busy.remove(name)
return ok
def extract_field(self, screenname, fieldname):
ftype, lineno, begin, end, extra = \
self.screens[screenname][fieldname]
return stripright(self.lines[lineno][begin:end].tostring())
def extract_rect(self, left, top, right, bottom):
lines = []
for i in range(top, bottom):
lines.append(stripright(self.lines[i][left:right])
.tostring())
return lines
def stripright(line):
i = len(line)
while i > 0 and line[i-1] in string.whitespace: i = i-1
return line[:i]
# VT100 terminal emulator in a STDWIN window.
import stdwin
from stdwinevents import *
from vt100 import VT100
class VT100win(VT100):
def __init__(self):
VT100.__init__(self)
self.window = None
self.last_x = -1
self.last_y = -1
def __del__(self):
self.close()
def open(self, title):
stdwin.setfont('7x14')
self.charwidth = stdwin.textwidth('m')
self.lineheight = stdwin.lineheight()
self.docwidth = self.width * self.charwidth
self.docheight = self.height * self.lineheight
stdwin.setdefwinsize(self.docwidth + 2, self.docheight + 2)
stdwin.setdefscrollbars(0, 0)
self.window = stdwin.open(title)
self.window.setdocsize(self.docwidth + 2, self.docheight + 2)
def close(self):
if self.window:
self.window.close()
self.window = None
def show(self):
if not self.window: return
self.window.change(((-10, -10),
(self.docwidth+10, self.docheight+10)))
def draw(self, detail):
d = self.window.begindrawing()
fg = stdwin.getfgcolor()
red = stdwin.fetchcolor('red')
d.cliprect(detail)
d.erase(detail)
lh = self.lineheight
cw = self.charwidth
for y in range(self.height):
d.text((0, y*lh), self.lines[y].tostring())
if self.attrs[y] <> self.blankattr:
for x in range(len(self.attrs[y])):
if self.attrs[y][x] == 7:
p1 = x*cw, y*lh
p2 = (x+1)*cw, (y+1)*lh
d.invert((p1, p2))
x = self.x * cw
y = self.y * lh
d.setfgcolor(red)
d.invert((x, y), (x+cw, y+lh))
d.setfgcolor(fg)
d.close()
def move_to(self, x, y):
VT100.move_to(self, x, y)
if not self.window: return
if self.y != self.last_y:
self.window.change((0, self.last_y * self.lineheight),
(self.width*self.charwidth,
(self.last_y+1) * self.lineheight))
self.last_x = self.x
self.last_y = y
self.window.change((0, self.y * self.lineheight),
(self.width*self.charwidth,
(self.y+1) * self.lineheight))
def send(self, str):
VT100.send(self, str)
## self.show()
This directory contains a browser written in Python for "Info files"
as used by the Emacs documentation system. The browser requires that
Python is built with the "stdwin" option and runs under X11 or the
Mac window system.
Now you can read Info files even if you can't spare the memory, time or
disk space to run Emacs. (I have used this extensively on a Macintosh
with 1 Megabyte main memory and a 20 Meg harddisk.)
You can give this to someone with great fear of complex computer
systems, as long as they can use a mouse.
Another reason to use this is to encourage the use of Info for on-line
documentation of software that is not related to Emacs or GNU.
(In particular, I plan to redo the Python and STDWIN documentation
in texinfo.)
The main program is in file "ib.py"; this accepts a file name and a
node name as optional command line arguments, i.e., its usage is
python ib.py [file [node]]
Configuration:
- The pathname of the directory (or directories) containing
the standard Info files should be set by editing the
value assigned to INFOPATH in module ifile.py.
- The default font should be set by editing the value of FONT
in this module (ibrowse.py).
- For fastest I/O, you may look at BLOCKSIZE and a few other
constants in ifile.py.
: ${ARCH}=`arch`
exec /ufs/guido/bin/$ARCH/python ib.py ${1+"$@"}
#! /usr/bin/env python
# Call ibrowse (the info file browser) under UNIX.
import sys
import ibrowse
if len(sys.argv) > 1:
file = sys.argv[1]
if len(sys.argv) > 2:
if len(sys.argv) > 3:
sys.stdout = sys.stderr
print 'usage:', sys.argv[0], '[file [node]]'
sys.exit(2)
else:
node = sys.argv[2]
else:
node = ''
ibrowse.start('(' + file + ')' + node)
else:
ibrowse.main()
This diff is collapsed.
This diff is collapsed.
# Cache management for info file processing.
# The function get_node() is the standard interface;
# its signature is the same as ifile.get_node() but it uses
# the cache and supports indirect tag tables.
import string
import ifile
from ifile import NoSuchNode, NoSuchFile
import itags
# Special hack to save the cache when using reload().
# This can just be "cache = {}" in a production version.
#
try:
dummy = cache
del dummy
except NameError:
cache = {}
# Clear the entire cache.
#
def resetcache():
for key in cache.keys():
del cache[key]
# Clear the node info from the cache (the most voluminous data).
#
def resetnodecache():
for key in cache.keys():
tags, nodes = cache[key]
cache[key] = tags, {}
# Get a node.
#
def get_node(curfile, ref):
file, node = ifile.parse_ref(curfile, ref)
file = string.lower(file)
node = string.lower(node)
if node == '*':
# Don't cache whole file references;
# reading the data is faster than displaying it anyway.
return ifile.get_whole_file(file) # May raise NoSuchFile
if not cache.has_key(file):
cache[file] = get_tags(file), {} # May raise NoSuchFile
tags, nodes = cache[file]
if not nodes.has_key(node):
if not tags.has_key(node):
raise NoSuchNode, ref
file1, offset, line = tags[node]
if not file1:
file1 = file
file1, node1, header, menu, footnotes, text = \
ifile.get_file_node(file1, offset, node)
nodes[node] = file, node1, header, menu, footnotes, text
return nodes[node]
# Get the tag table for a file.
# Either construct one or get the one found in the file.
# Raise NoSuchFile if the file isn't found.
#
def get_tags(file):
f = ifile.try_open(file) # May raise NoSuchFile
tags = itags.get_tags(f)
if not tags:
###print 'Scanning file...'
f.seek(0)
tags = ifile.make_tags(f)
return tags
# Tools for info file processing.
# XXX Need to be more careful with reading ahead searching for nodes.
import regexp
import string
# Exported exceptions.
#
NoSuchFile = 'no such file'
NoSuchNode = 'no such node'
# The search path for info files; this is site-specific.
# Directory names should end in a partname delimiter,
# so they can simply be concatenated to a relative pathname.
#
#INFOPATH = ['', ':Info.Ibrowse:', ':Info:'] # Mac
INFOPATH = ['', '/usr/local/emacs/info/'] # X11 on UNIX
# Tunable constants.
#
BLOCKSIZE = 512 # Qty to align reads to, if possible
FUZZ = 2*BLOCKSIZE # Qty to back-up before searching for a node
CHUNKSIZE = 4*BLOCKSIZE # Qty to read at once when reading lots of data
# Regular expressions used.
# Note that it is essential that Python leaves unrecognized backslash
# escapes in a string so they can be seen by regexp.compile!
#
findheader = regexp.compile('\037\014?\n(.*\n)').match
findescape = regexp.compile('\037').match
parseheader = regexp.compile('[nN]ode:[ \t]*([^\t,\n]*)').match
findfirstline = regexp.compile('^.*\n').match
findnode = regexp.compile('[nN]ode:[ \t]*([^\t,\n]*)').match
findprev = regexp.compile('[pP]rev[ious]*:[ \t]*([^\t,\n]*)').match
findnext = regexp.compile('[nN]ext:[ \t]*([^\t,\n]*)').match
findup = regexp.compile('[uU]p:[ \t]*([^\t,\n]*)').match
findmenu = regexp.compile('^\* [mM]enu:').match
findmenuitem = regexp.compile( \
'^\* ([^:]+):[ \t]*(:|\([^\t]*\)[^\t,\n.]*|[^:(][^\t,\n.]*)').match
findfootnote = regexp.compile( \
'\*[nN]ote ([^:]+):[ \t]*(:|[^:][^\t,\n.]*)').match
parsenoderef = regexp.compile('^\((.*)\)(.*)$').match
# Get a node and all information pertaining to it.
# This doesn't work if there is an indirect tag table,
# and in general you are better off using icache.get_node() instead.
# Functions get_whole_file() and get_file_node() provide part
# functionality used by icache.
# Raise NoSuchFile or NoSuchNode as appropriate.
#
def get_node(curfile, ref):
file, node = parse_ref(curfile, ref)
if node == '*':
return get_whole_file(file)
else:
return get_file_node(file, 0, node)
#
def get_whole_file(file):
f = try_open(file) # May raise NoSuchFile
text = f.read()
header, menu, footnotes = ('', '', ''), [], []
return file, '*', header, menu, footnotes, text
#
def get_file_node(file, offset, node):
f = try_open(file) # May raise NoSuchFile
text = find_node(f, offset, node) # May raise NoSuchNode
node, header, menu, footnotes = analyze_node(text)
return file, node, header, menu, footnotes, text
# Parse a node reference into a file (possibly default) and node name.
# Possible reference formats are: "NODE", "(FILE)", "(FILE)NODE".
# Default file is the curfile argument; default node is Top.
# A node value of '*' is a special case: the whole file should
# be interpreted (by the caller!) as a single node.
#
def parse_ref(curfile, ref):
match = parsenoderef(ref)
if not match:
file, node = curfile, ref
else:
(a, b), (a1, b1), (a2, b2) = match
file, node = ref[a1:b1], ref[a2:b2]
if not file:
file = curfile # (Is this necessary?)
if not node:
node = 'Top'
return file, node
# Extract node name, links, menu and footnotes from the node text.
#
def analyze_node(text):
#
# Get node name and links from the header line
#
match = findfirstline(text)
if match:
(a, b) = match[0]
line = text[a:b]
else:
line = ''
node = get_it(text, findnode)
prev = get_it(text, findprev)
next = get_it(text, findnext)
up = get_it(text, findup)
#
# Get the menu items, if there is a menu
#
menu = []
match = findmenu(text)
if match:
(a, b) = match[0]
while 1:
match = findmenuitem(text, b)
if not match:
break
(a, b), (a1, b1), (a2, b2) = match
topic, ref = text[a1:b1], text[a2:b2]
if ref == ':':
ref = topic
menu.append((topic, ref))
#
# Get the footnotes
#
footnotes = []
b = 0
while 1:
match = findfootnote(text, b)
if not match:
break
(a, b), (a1, b1), (a2, b2) = match
topic, ref = text[a1:b1], text[a2:b2]
if ref == ':':
ref = topic
footnotes.append((topic, ref))
#
return node, (prev, next, up), menu, footnotes
#
def get_it(line, matcher):
match = matcher(line)
if not match:
return ''
else:
(a, b), (a1, b1) = match
return line[a1:b1]
# Find a node in an open file.
# The offset (from the tags table) is a hint about the node's position.
# Pass zero if there is no tags table.
# Raise NoSuchNode if the node isn't found.
# NB: This seeks around in the file.
#
def find_node(f, offset, node):
node = string.lower(node) # Just to be sure
#
# Position a little before the given offset,
# so we may find the node even if it has moved around
# in the file a little.
#
offset = max(0, ((offset-FUZZ) / BLOCKSIZE) * BLOCKSIZE)
f.seek(offset)
#
# Loop, hunting for a matching node header.
#
while 1:
buf = f.read(CHUNKSIZE)
if not buf:
break
i = 0
while 1:
match = findheader(buf, i)
if match:
(a,b), (a1,b1) = match
start = a1
line = buf[a1:b1]
i = b
match = parseheader(line)
if match:
(a,b), (a1,b1) = match
key = string.lower(line[a1:b1])
if key == node:
# Got it! Now read the rest.
return read_node(f, buf[start:])
elif findescape(buf, i):
next = f.read(CHUNKSIZE)
if not next:
break
buf = buf + next
else:
break
#
# If we get here, we didn't find it. Too bad.
#
raise NoSuchNode, node
# Finish off getting a node (subroutine for find_node()).
# The node begins at the start of buf and may end in buf;
# if it doesn't end there, read additional data from f.
#
def read_node(f, buf):
i = 0
match = findescape(buf, i)
while not match:
next = f.read(CHUNKSIZE)
if not next:
end = len(buf)
break
i = len(buf)
buf = buf + next
match = findescape(buf, i)
else:
# Got a match
(a, b) = match[0]
end = a
# Strip trailing newlines
while end > 0 and buf[end-1] == '\n':
end = end-1
buf = buf[:end]
return buf
# Read reverse starting at offset until the beginning of a node is found.
# Then return a buffer containing the beginning of the node,
# with f positioned just after the buffer.
# The buffer will contain at least the full header line of the node;
# the caller should finish off with read_node() if it is the right node.
# (It is also possible that the buffer extends beyond the node!)
# Return an empty string if there is no node before the given offset.
#
def backup_node(f, offset):
start = max(0, ((offset-CHUNKSIZE) / BLOCKSIZE) * BLOCKSIZE)
end = offset
while start < end:
f.seek(start)
buf = f.read(end-start)
i = 0
hit = -1
while 1:
match = findheader(buf, i)
if match:
(a,b), (a1,b1) = match
hit = a1
i = b
elif end < offset and findescape(buf, i):
next = f.read(min(offset-end, BLOCKSIZE))
if not next:
break
buf = buf + next
end = end + len(next)
else:
break
if hit >= 0:
return buf[hit:]
end = start
start = max(0, end - CHUNKSIZE)
return ''
# Make a tag table for the given file by scanning the file.
# The file must be open for reading, and positioned at the beginning
# (or wherever the hunt for tags must begin; it is read till the end).
#
def make_tags(f):
tags = {}
while 1:
offset = f.tell()
buf = f.read(CHUNKSIZE)
if not buf:
break
i = 0
while 1:
match = findheader(buf, i)
if match:
(a,b), (a1,b1) = match
start = offset+a1
line = buf[a1:b1]
i = b
match = parseheader(line)
if match:
(a,b), (a1,b1) = match
key = string.lower(line[a1:b1])
if tags.has_key(key):
print 'Duplicate node:',
print key
tags[key] = '', start, line
elif findescape(buf, i):
next = f.read(CHUNKSIZE)
if not next:
break
buf = buf + next
else:
break
return tags
# Try to open a file, return a file object if succeeds.
# Raise NoSuchFile if the file can't be opened.
# Should treat absolute pathnames special.
#
def try_open(file):
for dir in INFOPATH:
try:
return open(dir + file, 'r')
except IOError:
pass
raise NoSuchFile, file
# A little test for the speed of make_tags().
#
TESTFILE = 'texinfo-1'
def test_make_tags():
import time
f = try_open(TESTFILE)
t1 = time.time()
tags = make_tags(f)
t2 = time.time()
print 'Making tag table for', `TESTFILE`, 'took', t2-t1, 'sec.'
# Utility module for 'icache.py': interpret tag tables and indirect nodes.
# (This module is a bit chatty when confronted with the unexpected.)
import regexp
import string
import ifile
# Get the tag table of an open file, as a dictionary.
# Seeks around in the file; after reading, the position is undefined.
# Return an empty tag table if none is found.
#
def get_tags(f):
#
# First see if the last "node" is the end of tag table marker.
#
f.seek(0, 2) # Seek to EOF
end = f.tell()
buf = ifile.backup_node(f, end)
if not labelmatch(buf, 0, 'end tag table\n'):
return {} # No succes
#
# Next backup to the previous "node" -- the tag table itself.
#
###print 'Getting prebuilt tag table...'
end = f.tell() - len(buf)
buf = ifile.backup_node(f, end)
label = 'tag table:\n'
if not labelmatch(buf, 0, label):
print 'Weird: end tag table marker but no tag table?'
print 'Node begins:', `buf[:50]`
return {}
#
# Now read the whole tag table.
#
end = f.tell() - len(buf) # Do this first!
buf = ifile.read_node(f, buf)
#
# First check for an indirection table.
#
indirlist = []
if labelmatch(buf, len(label), '(indirect)\n'):
indirbuf = ifile.backup_node(f, end)
if not labelmatch(indirbuf, 0, 'indirect:\n'):
print 'Weird: promised indirection table not found'
print 'Node begins:', `indirbuf[:50]`
# Carry on. Things probably won't work though.
else:
indirbuf = ifile.read_node(f, indirbuf)
indirlist = parse_indirlist(indirbuf)
#
# Now parse the tag table.
#
findtag = regexp.compile('^(.*[nN]ode:[ \t]*(.*))\177([0-9]+)$').match
i = 0
tags = {}
while 1:
match = findtag(buf, i)
if not match:
break
(a,b), (a1,b1), (a2,b2), (a3,b3) = match
i = b
line = buf[a1:b1]
node = string.lower(buf[a2:b2])
offset = eval(buf[a3:b3]) # XXX What if it overflows?
if tags.has_key(node):
print 'Duplicate key in tag table:', `node`
file, offset = map_offset(offset, indirlist)
tags[node] = file, offset, line
#
return tags
# Return true if buf[i:] begins with a label, after lower case conversion.
# The label argument must be in lower case.
#
def labelmatch(buf, i, label):
return string.lower(buf[i:i+len(label)]) == label
# Parse the indirection list.
# Return a list of (filename, offset) pairs ready for use.
#
def parse_indirlist(buf):
list = []
findindir = regexp.compile('^(.+):[ \t]*([0-9]+)$').match
i = 0
while 1:
match = findindir(buf, i)
if not match:
break
(a,b), (a1,b1), (a2,b2) = match
file = buf[a1:b1]
offset = eval(buf[a2:b2]) # XXX What if this gets overflow?
list.append((file, offset))
i = b
return list
# Map an offset through the indirection list.
# Return (filename, new_offset).
# If the list is empty, return the given offset and an empty file name.
#
def map_offset(offset, indirlist):
if not indirlist:
return '', offset
#
# XXX This could be done more elegant.
#
filex, offx = indirlist[0]
for i in range(len(indirlist)):
file1, off1 = indirlist[i]
if i+1 >= len(indirlist):
file2, off2 = '', 0x7fffffff
else:
file2, off2 = indirlist[i+1]
if off1 <= offset < off2:
# Add offx+2 to compensate for extra header.
# No idea whether this is always correct.
return file1, offset-off1 + offx+2
#
# XXX Shouldn't get here.
#
print 'Oops, map_offset fell through'
return '', offset # Not likely to get good results
# Window interface to (some of) the CD player's vital audio functions
import cd
import stdwin
from stdwinevents import *
import mainloop
def main():
player = cd.open()
stdwin.setdefscrollbars(0, 0)
win = stdwin.open('CD')
win.player = player
win.dispatch = cddispatch
mainloop.register(win)
win.settimer(10)
mainloop.mainloop()
def cddispatch(type, win, detail):
if type == WE_NULL:
pass
elif type == WE_CLOSE:
mainloop.unregister(win)
win.close()
elif type == WE_DRAW:
draw(win)
elif type == WE_TIMER:
update(win)
elif type == WE_MOUSE_UP:
left, top, right, bottom, v1, v2 = getgeo(win)
h, v = detail[0]
if left < h < right:
if top < v < v1:
but1(win)
elif v1 < v < v2:
but2(win)
elif v2 < v < bottom:
but3(win)
else:
stdwin.fleep()
def but1(win):
update(win)
def but2(win):
state = win.player.getstatus()[0]
if state == cd.ready:
win.player.play(1, 1)
elif state in (cd.playing, cd.paused):
win.player.togglepause()
else:
stdwin.fleep()
update(win)
def but3(win):
win.player.stop()
update(win)
def update(win):
d = win.begindrawing()
drawstatus(win, d)
d.enddrawing()
win.settimer(10)
statedict = ['ERROR', 'NODISK', 'READY', 'PLAYING', 'PAUSED', 'STILL']
def draw(win):
left, top, right, bottom, v1, v2 = getgeo(win)
d = win.begindrawing()
drawstatus(win, d)
box(d, left, v1, right, v2, 'Play/Pause')
box(d, left, v2, right, bottom, 'Stop')
d.enddrawing()
def drawstatus(win, d):
left, top, right, bottom, v1, v2 = getgeo(win)
state, track, curtime, abstime, totaltime, first, last, \
scsi_audio, cur_block, dummy = win.player.getstatus()
if 0 <= state < len(statedict):
message = statedict[state]
else:
message = `status`
message = message + ' track ' + `track` + ' of ' + `last`
d.erase((left, top), (right, v1))
box(d, left, top, right, v1, message)
def box(d, left, top, right, bottom, label):
R = (left+1, top+1), (right-1, bottom-1)
width = d.textwidth(label)
height = d.lineheight()
h = (left + right - width) / 2
v = (top + bottom - height) / 2
d.box(R)
d.cliprect(R)
d.text((h, v), label)
d.noclip()
def getgeo(win):
(left, top), (right, bottom) = (0, 0), win.getwinsize()
v1 = top + (bottom - top) / 3
v2 = top + (bottom - top) * 2 / 3
return left, top, right, bottom, v1, v2
main()
# Display digits of pi in a window, calculating in a separate thread.
# Compare ../scripts/pi.py.
import sys
import time
import thread
import stdwin
from stdwinevents import *
ok = 1
digits = []
def worker():
k, a, b, a1, b1 = 2l, 4l, 1l, 12l, 4l
while ok:
# Next approximation
p, q, k = k*k, 2l*k+1l, k+1l
a, b, a1, b1 = a1, b1, p*a+q*a1, p*b+q*b1
# Print common digits
d, d1 = a/b, a1/b1
#print a, b, a1, b1
while d == d1:
digits.append(`int(d)`)
a, a1 = 10l*(a%b), 10l*(a1%b1)
d, d1 = a/b, a1/b1
def main():
global ok
digits_seen = 0
thread.start_new_thread(worker, ())
tw = stdwin.textwidth('0 ')
lh = stdwin.lineheight()
stdwin.setdefwinsize(20 * tw, 20 * lh)
stdwin.setdefscrollbars(0, 1)
win = stdwin.open('digits of pi')
options = win.menucreate('Options')
options.additem('Auto scroll')
autoscroll = 1
options.check(0, autoscroll)
while 1:
win.settimer(1)
type, w, detail = stdwin.getevent()
if type == WE_CLOSE:
ok = 0
sys.exit(0)
elif type == WE_DRAW:
(left, top), (right, bottom) = detail
digits_seen = len(digits)
d = win.begindrawing()
for i in range(digits_seen):
h = (i % 20) * tw
v = (i / 20) * lh
if top-lh < v < bottom:
d.text((h, v), digits[i])
d.close()
elif type == WE_TIMER:
n = len(digits)
if n > digits_seen:
win.settitle(`n` + ' digits of pi')
d = win.begindrawing()
for i in range(digits_seen, n):
h = (i % 20) * tw
v = (i / 20) * lh
d.text((h, v), digits[i])
d.close()
digits_seen = n
height = (v + 20*lh) / (20*lh) * (20*lh)
win.setdocsize(0, height)
if autoscroll:
win.show((0, v), (h+tw, v+lh))
elif type == WE_MENU:
menu, item = detail
if menu == options:
if item == 0:
autoscroll = (not autoscroll)
options.check(0, autoscroll)
main()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment