import sys import logging import traceback from zymb import sched import readline class Readline(sched.Agent): """Readline: An Agent which attends to the readline interface. This agent WILL NOT WORK without a hacked-up Python readline module. The standard Python readline module is missing some APIs which are necessary for asynchronous operation. You can get my hacked-up source from . WARNING: Do not instantiate a Readline agent from a Python interactive shell which is using readline! The results will make you sad. As is common for agents, Readline does not actually do any work until you start it and call mainloop(). Also, it does not (by default) do anything with received data. Incoming data triggers an 'input' event, but there is no preregistered handler for this event. You can write your own, or install the basicinput() handler. See below. mainloop() -- main event loop. The procedure for invoking zymb is slightly complicated when you are using readline. Instead of calling zymb.sched.process in one of the usual ways, you should call the mainloop() function provided in this module. (The readline module's event hook is rigged to call zymb.sched.process for you.) Readline(ignoreempty=False) -- constructor. If *ignoreempty* is true, empty inputs (just hitting Enter) will not generate 'input' events. If it is false, they will generate 'input' events containing empty strings. You can only create one Readline agent, because it does not make sense for more than one readline to be running in a single process. Agent states and events: state 'start': Initial state. event 'input' (unicode): A line of input has been read. (Newline included.) event 'interrupt': The user hit ctrl-C. state 'end': The agent has shut down. Public methods: write(dat) -- print output. basicinput(str) -- a simple event handler for 'input'. getprompt() -- get the current readline prompt. setprompt(str) -- change the readline prompt. """ singleton = None quitnow = False received = [] interruption = None def __init__(self, ignoreempty=False): if (Readline.singleton): raise Exception, 'only one Readline agent may exist' sched.Agent.__init__(self) self.prompt = '> ' self.ignoreempty = ignoreempty self.incomplete = '' try: readline.set_erase_empty_line(1) readline.set_event_hook(event_hook) readline.set_startup_hook(startup_hook) except: print """ Hello, Pythoneer! This is the zymb.readlineagent module. I need some features which your standard Python readline module does not support. Go to for my hacked-up readline code. I apologize for the inconvenience. """ raise Readline.singleton = self def write(self, dat): """write(str) -> None Print output. Since readline takes over the terminal, it is unwise to print data directly, or write to sys.stdout -- that will confuse the cursor position and the editing line. Instead, you should call this method. The data you pass will be printed tidily, without interrupting or corrupting the user's typing. The edit line will be moved down if necessary. This method only prints data in full lines. If the string you pass does not end with a newline, the fractional line will be invisibly buffered until you do write a newline. """ Readline.received.append(dat) def getprompt(self): """getprompt() -> str Return the current readline prompt. The default prompt is '> '. """ return self.prompt def setprompt(self, st): """setprompt(str) -> None Change the readline prompt. The default prompt is '> '. """ self.prompt = st def basicinput(self, dat): """basicinput(dat) -> None A simple event handler for 'input'. This simply writes the received data to stdout: self.write(dat) This handler is not installed by default. You can enable it by calling: readlnag.addhandler('input', fileag.basicinput) """ self.write(dat) def event_hook(): """event_hook() -- internal readline callback. Do not call. """ try: res = sched.process(0) if (not res): Readline.quitnow = True readline.set_done(1) except Exception, ex: (typ, val, tb) = sys.exc_info() biglist = traceback.format_exception(typ, val, tb) print 'Exception during sched.process():', str(ex) print ''.join(biglist) if (Readline.received): ln = readline.get_line_buffer() if (ln != None): Readline.interruption = (ln, readline.get_point(), readline.get_mark()) readline.set_done(1) def startup_hook(): """startup_hook() -- internal readline callback. Do not call. """ if (Readline.interruption != None): readline.insert_text(Readline.interruption[0]) readline.set_point(Readline.interruption[1]) readline.set_mark(Readline.interruption[2]) Readline.interruption = None def mainloop(): """mainloop() -> None The procedure for invoking zymb is slightly complicated when you are using readline. Instead of calling zymb.sched.process in one of the usual ways, you have to set up readline, and then call raw_input in a loop. (The readline module's event hook is rigged to call zymb.sched.process for you.) This function does all of that. Simply call mainloop() as your top-level event loop. When the input stream is terminated (either by ctrl-C or because the Readline agent stopped), mainloop() will return. """ ag = Readline.singleton if (not ag or not ag.live): raise Exception, 'must create and start a Readline agent before calling mainloop' while (not Readline.quitnow): ln = None try: ln = raw_input(ag.prompt) ln = unicode(ln, sys.stdin.encoding) except EOFError: print '' pass except KeyboardInterrupt: print '' print '' ag.perform('interrupt') if (ln == '' and ag.ignoreempty): ln = None if (ln != None): if (Readline.interruption == None): ag.perform('input', ln+'\n') while (Readline.received): msg = Readline.received.pop(0) ag.incomplete = ag.incomplete + msg while 1: nlpos = ag.incomplete.find('\n') if (nlpos < 0): break line = ag.incomplete[:nlpos] ag.incomplete = ag.incomplete[nlpos+1:] print line