diff --git a/pcl_expect.py b/pcl_expect.py new file mode 100644 index 0000000000000000000000000000000000000000..4265bf55718fb42d31891d216695b9ae4cc39437 --- /dev/null +++ b/pcl_expect.py @@ -0,0 +1,166 @@ +import sets +import select +import os + +# +# Global variables. The user are supposed to change these. +# + +# Default timeout, in seconds, as a floating point number. +timeout = 10.0 + +# If a timeout occurs and no timeout handler is specified, should an +# exception be raised, or should the code just continue (TCL-style)? +# Default is to raise an exception. +timeout_raises_exception = True + +# +# Internal code +# + +class Timeout(Exception): pass + +class expectable: + def __init__(self): + self.__eof_seen = False + + def fileno(self): + return self.__fileno + + def fill_buffer(self): + s = os.read(self.fileno(), 8192) + if s == "": + self.__eof_seen = True + else: + self.__buffer = self.__buffer + s + + def eof(self): + """Return True if end-of-file has been seen. + + There might still be pending data in the buffer, though. + """ + + def buffer(self): + return self.__buffer + + def remove_first(self, n): + self.__buffer = self.__buffer[n:] + +class impl: + def __init__(self, timeout = None): + self.__first = True + self.__inputs = sets.Set() + self.__readable = sets.Set() + self.__done = False + self.__timeout = timeout + self.__timeout_active = False + self.__acted = False + self.__all_inputs_seen = False + + def loop(self): + if self.__done: + return False + + if self.__first: + self.__first = False + if __expect_before(): + return False + return True + elif not self.__acted: + self.__all_inputs_seen = True + if __expect_after(): + return False + + # Nothing handled the timeout event. + if self.__timeout_active: + debug("Unhandled timeout") + if timeout_raises_exception: + raise Timeout() + else: + return False + + if not self.__all_inputs_seen: + t = 0 + elif self.__timeout == None: + t = timeout + else: + t = self.__timeout + if t > 0: + debug("Waiting for input") + (r, w, e) = select.select([x for x in self.__inputs], [], [], t) + self.__readable = sets.Set(r) + if len(self.__readable) == 0: + if self.__all_inputs_seen: + debug("Processing timeout event") + self.__timeout_active = True + else: + debug("Input available") + if __expect_before(): + return False + return True + + + def re(self, exp, regexp): + """Implement "when re foo, foo_re". + + EXP is an event source that adheres to the expectable API. + REGEXP is a regular expression to match. This can be a string + (that will be compiled using re.compile) or a compiled regexp + object. + + Return true if a match was found. The match attribute of EXP + will be set to the result of the match. The matching string + will have been removed from the buffer of EXP upon return. + """ + + if self.__acted: + return False + + if self.__done: + raise "foo! me bad!" + + if exp in self.__readable: + self.__readable.remove(exp) + exp.fill_buffer() + + # Doing this compilation again and again could be a problem. + # I rely on the cache in module re. I hope it exists... + if isinstance(regexp, types.StringTypes): + regexp = re.compile(regexp) + + match = regexp.search(exp.buffer()) + if match != None: + exp.match = match + exp.remove_first(match.end()) + self.__done = True + self.__acted = True + return True + else: + if not exp.eof(): + self.__inputs.add(exp) + return False + + def timeout(self): + """Implement "when timeout". + + Return true if a timeout has occured. + """ + + if self.__acted: + return False + + if self.__done: + raise "foo! me bad!" + + if self.__timeout_active: + self.__timeout_active = False + self.__done = True + self.__acted = True + return True + else: + return False + + def cont(self): + self.__done = False + self.__first = True + # Don't touch self.__acted