commit 71891f6f00d9e8134f1197f010a5f2ad7437f6ae
parent f4d2ca326122d3c9dfd677306e4bbfd9cff64afe
Author: Kris Maglione <kris@suckless.org>
Date:   Fri,  9 Jul 2010 17:48:46 -0400
[python] Rephrase async client request chains as coroutines.
Diffstat:
4 files changed, 155 insertions(+), 154 deletions(-)
diff --git a/alternative_wmiircs/python/pyxp/asyncclient.py b/alternative_wmiircs/python/pyxp/asyncclient.py
@@ -2,196 +2,194 @@ from pyxp import client, fcall
 from pyxp.client import *
 from functools import wraps
 
-def awithfile(*oargs, **okwargs):
-    def wrapper(fn):
-        @wraps(fn)
-        def next(self, path, *args, **kwargs):
-            def next(file, exc, tb):
-                fn(self, (file, exc, tb), *args, **kwargs)
-            self.aopen(path, next, *oargs, **okwargs)
-        return next
+def send(iter, val, default=None):
+    try:
+        return iter.send(val)
+    except StopIteration:
+        return default
+
+def awithfile(fn):
+    @wraps(fn)
+    def wrapper(self, path, *args, **kwargs):
+        gen = fn(self, *args, **kwargs)
+        callback, fail, mode = next(gen)
+        def cont(file):
+            send(gen, file)
+        self.aopen(path, cont, fail=fail or callback, mode=mode)
     return wrapper
 
-def wrap_callback(fn, file):
-    def callback(data, exc, tb):
-        file.close()
-        Client.respond(fn, data, exc, tb)
-    return callback
+def requestchain(fn):
+    @wraps(fn)
+    def wrapper(self, *args, **kwargs):
+        gen = fn(self, *args, **kwargs)
+        callback, fail = next(gen)
+
+        def cont(val):
+            data = gen.send(val)
+            if isinstance(data, fcall.Fcall):
+                self._dorpc(data, cont, fail or callback)
+            else:
+                Client.respond(callback, data)
+        cont(None)
+    return wrapper
 
 class Client(client.Client):
     ROOT_FID = 0
 
-    def _awalk(self, path, callback, fail=None):
-        path = self._splitpath(path)
-        ctxt = dict(path=path, fid=self._getfid(), ofid=ROOT_FID)
+    def _awalk(fn):
+        @wraps(fn)
+        @requestchain
+        def wrapper(self, *args, **kwargs):
+            gen = fn(self, *args, **kwargs)
+            path, callback, fail = next(gen)
+
+            path = self._splitpath(path)
+            fid  = self._getfid()
+            ofid = ROOT_FID
+
+            def fail_(resp, exc, tb):
+                if ofid != ROOT_FID:
+                    self._aclunk(fid)
+                self.respond(fail or callback, resp, exc, tb)
+            yield callback, fail_
 
-        def next(resp=None, exc=None, tb=None):
-            if exc and ctxt['ofid'] != ROOT_FID:
-                self._aclunk(ctxt['fid'])
-                ctxt['fid'] = None
+            while path:
+                wname = path[:fcall.MAX_WELEM]
+                path = path[fcall.MAX_WELEM:]
 
-            if not ctxt['path'] and resp or exc:
-                return self.respond(fail if exc and fail else callback,
-                                    ctxt['fid'], exc, tb)
+                resp = yield fcall.Twalk(fid=ofid, newfid=fid, wname=wname)
+                ofid = fid
 
-            wname = ctxt['path'][:fcall.MAX_WELEM]
-            ofid = ctxt['ofid']
-            ctxt['path'] = ctxt['path'][fcall.MAX_WELEM:]
-            if resp:
-                ctxt['ofid'] = ctxt['fid']
+            resp = fid
+            while resp is not None:
+                resp = yield send(gen, resp)
 
-            self._dorpc(fcall.Twalk(fid=ofid, newfid=ctxt['fid'], wname=wname),
-                        next)
-        next()
+        return wrapper
 
     _file = property(lambda self: File)
+
+    @_awalk
     def _aopen(self, path, mode, fcall, callback, fail=None, origpath=None):
         path = self._splitpath(path)
 
-        def next(fid, exc, tb):
-            def next(resp, exc, tb):
-                file = self._file(self, origpath or '/'.join(path), resp, fid, mode,
-                                  cleanup=lambda: self._clunk(fid))
-                self.respond(callback, file)
-            fcall.fid = fid
-            self._dorpc(fcall, next, fail or callback)
-        self._awalk(path, next, fail or callback)
+        fcall.fid = yield path, callback, fail
+        resp = yield fcall
+        yield self._file(self, origpath or '/'.join(path), resp, fcall.fid, mode,
+                         cleanup=lambda: self._clunk(fcall.fid))
 
     def aopen(self, path, callback=True, fail=None, mode=OREAD):
         assert callable(callback)
-        return self._aopen(path, mode, fcall.Topen(mode=mode),
-                           callback, fail)
+        self._aopen(path, mode, fcall.Topen(mode=mode), callback, fail)
 
     def acreate(self, path, callback=True, fail=None, mode=OREAD, perm=0):
         path = self._splitpath(path)
         name = path.pop()
 
-        if not callable(callback):
-            callback = lambda resp: resp and resp.close()
-
-        return self._aopen(path, mode, fcall.Tcreate(mode=mode, name=name, perm=perm),
-                           callback, fail, origpath='/'.join(path + [name]))
+        self._aopen(path, mode,
+                    fcall.Tcreate(mode=mode, name=name, perm=perm),
+                    callback if callable(callback) else lambda resp: resp and resp.close(),
+                    fail, origpath='/'.join(path + [name]))
 
+    @_awalk
     def aremove(self, path, callback=True, fail=None):
-        def next(fid):
-            self._dorpc(fcall.Tremove(fid=fid), callback, fail)
-        self._awalk(path, next, fail or callback)
+        yield fcall.Tremove(fid=(yield path, callback, fail))
 
+    @_awalk
     def astat(self, path, callback, fail=None):
-        def next(fid):
-            def next(resp):
-                self.respond(callback, resp.stat)
-            self._dorpc(fcall.Tstat(fid=fid), next, fail or callback)
-        self._awalk(self, next, fail or callback)
-
-    @awithfile()
-    def aread(self, (file, exc, tb), callback, *args, **kwargs):
-        if exc:
-            self.respond(callback, file, exc, tb)
-        else:
-            file.aread(wrap_callback(callback, file), *args, **kwargs)
-
-    @awithfile(mode=OWRITE)
-    def awrite(self, (file, exc, tb), data, callback=True, *args, **kwargs):
-        if exc:
-            self.respond(callback, file, exc, tb)
-        else:
-            file.awrite(data, wrap_callback(callback, file), *args, **kwargs)
-
-    @awithfile()
-    def areadlines(self, (file, exc, tb), fn):
-        def callback(resp):
-            if resp is None:
-                file.close()
-            if fn(resp) is False:
-                file.close()
-                return False
-        if exc:
-            callback(None)
-        else:
-            file.sreadlines(callback)
-
-class File(client.File):
-
-    def stat(self, callback):
-        def next(resp, exc, tb):
-            Client.respond(callback, resp.stat, exc, tb)
-        resp = self._dorpc(fcall.Tstat(), next, callback)
+        resp = yield fcall.Tstat(fid=(yield path, callback, fail))
+        yield resp.stat
 
+    @awithfile
     def aread(self, callback, fail=None, count=None, offset=None, buf=''):
-        ctxt = dict(res=[], count=self.iounit, offset=self.offset)
+        file = yield callback, fail, OREAD
+        file.aread(callback, fail, count, offset, buf)
 
-        if count is not None:
-            ctxt['count'] = count
-        if offset is not None:
-            ctxt['offset'] = offset
+    @awithfile
+    def awrite(self, data, callback=True, fail=None, offset=None):
+        file = yield callback, fail, OWRITE
+        file.awrite(data, callback, fail, offset)
 
-        def next(resp=None, exc=None, tb=None):
-            if resp and resp.data:
-                ctxt['res'].append(resp.data)
-                ctxt['offset'] += len(resp.data)
+    @awithfile
+    def areadlines(self, callback):
+        file = yield callback, fail, OREAD
+        file.areadlines(callback)
 
-            if ctxt['count'] == 0:
-                if offset is None:
-                    self.offset = ctxt['offset']
-                return Client.respond(callback, ''.join(ctxt['res']), exc, tb)
+class File(client.File):
 
-            n = min(ctxt['count'], self.iounit)
-            ctxt['count'] -= n
+    @requestchain
+    def stat(self, callback, fail=None):
+        yield callback, fail
+        resp = yield fcall.Tstat()
+        yield resp.stat
 
-            self._dorpc(fcall.Tread(offset=ctxt['offset'], count=n),
-                        next, fail or callback)
-        next()
+    @requestchain
+    def aread(self, callback, fail=None, count=None, offset=None, buf=''):
+        yield callback, fail
+
+        setoffset = offset is None
+        if count is None:
+            count = self.iounit
+        if offset is  None:
+            offset = self.offset
+
+        res = []
+        while count > 0:
+            n = min(count, self.iounit)
+            count -= n
+            resp = yield fcall.Tread(offset=offset, count=n)
+            res.append(resp.data)
+            offset += len(resp.data)
+            if len(resp.data) == 0:
+                break
+
+        if setoffset:
+            self.offset = offset
+        yield ''.join(res)
 
     def areadlines(self, callback):
-        ctxt = dict(last=None)
-        def next(data, exc, tb):
+        class ctxt:
+            last = None
+        def cont(data, exc, tb):
             res = True
             if data:
                 lines = data.split('\n')
-                if ctxt['last']:
-                    lines[0] = ctxt['last'] + lines[0]
+                if ctxt.last:
+                    lines[0] = ctxt.last + lines[0]
                 for i in range(0, len(lines) - 1):
                     res = callback(lines[i])
                     if res is False:
-                        break
-                ctxt['last'] = lines[-1]
-                if res is not False:
-                    self.aread(next)
+                        return
+                ctxt.last = lines[-1]
+                self.aread(cont)
             else:
-                if ctxt['last']:
-                    callback(ctxt['last'])
+                if ctxt.last:
+                    callback(ctxt.last)
                 callback(None)
-        self.aread(next)
+        self.aread(cont)
 
+    @requestchain
     def awrite(self, data, callback=True, fail=None, offset=None):
-        ctxt = dict(offset=self.offset, off=0)
-        if offset is not None:
-            ctxt['offset'] = offset
-
-        def next(resp=None, exc=None, tb=None):
-            if resp:
-                ctxt['off'] += resp.count
-                ctxt['offset'] += resp.count
-            if ctxt['off'] < len(data) or not (exc or resp):
-                n = min(len(data), self.iounit)
-
-                self._dorpc(fcall.Twrite(offset=ctxt['offset'],
-                                         data=data[ctxt['off']:ctxt['off']+n]),
-                            next, fail or callback)
-            else:
-                if offset is None:
-                    self.offset = ctxt['offset']
-                Client.respond(callback, ctxt['off'], exc, tb)
-        next()
-
+        yield callback, fail
+        setoffset = offset is None
+        if offset is None:
+            offset = self.offset
+
+        off = 0
+        while off < len(data):
+            n = min(len(data), self.iounit)
+            resp = yield fcall.Twrite(offset=offset, data=data[off:off+n])
+            off    += resp.count
+            offset += resp.count
+
+        if setoffset:
+            self.offset = offset
+        yield off
+
+    @requestchain
     def aremove(self, callback=True, fail=None):
-        def next(resp, exc, tb):
-            self.close()
-            if exc and fail:
-                Client.respond(fail, resp and True, exc, tb)
-            else:
-                Client.respond(callback, resp and True, exc, tb)
-        self._dorpc(fcall.Tremove(), next)
+        yield callback, fail
+        yield fcall.Tremove()
+        self.close()
+        yield True
 
 # vim:se sts=4 sw=4 et:
diff --git a/alternative_wmiircs/python/pyxp/client.py b/alternative_wmiircs/python/pyxp/client.py
@@ -45,8 +45,10 @@ class Client(object):
 
     @staticmethod
     def respond(callback, data, exc=None, tb=None):
-        if callable(callback):
+        if hasattr(callback, 'func_code'):
             callback(*(data, exc, tb)[0:callback.func_code.co_argcount])
+        elif callable(callback):
+            callback(data)
 
     def __enter__(self):
         return self
diff --git a/alternative_wmiircs/python/pyxp/fields.py b/alternative_wmiircs/python/pyxp/fields.py
@@ -23,18 +23,20 @@ class Int(Field):
     def encoder(cls, n):
         if n not in cls.encoders:
             exec ('def enc(n):\n' +
-                  '    assert n == n & 0x%s, "Arithmetic overflow"\n' % ('ff' * n) +
-                  '    return "".join((' + ','.join(
-                      'chr((n >> %d) & 0xff)' % (i * 8)
-                      for i in range(0, n)) + ',))\n')
+                  '    assert n == n & 0x%s, "Arithmetic overflow"\n' +
+                  '    return ''.join((%s,))'
+                 ) % ('ff' * n,
+                      ','.join('chr((n >> %d) & 0xff)' % (i * 8)
+                               for i in range(0, n)))
+
             cls.encoders[n] = enc
         return cls.encoders[n]
     @classmethod
     def decoder(cls, n):
         if n not in cls.decoders:
-            cls.decoders[n] = eval('lambda data, offset: ' + '|'.join(
-                'ord(data[offset + %d]) << %d' % (i, i * 8)
-                for i in range(0, n)))
+            cls.decoders[n] = eval('lambda data, offset: ' +
+                                   '|'.join('ord(data[offset + %d]) << %d' % (i, i * 8)
+                                            for i in range(0, n)))
         return cls.decoders[n]
 
     def __init__(self, size):
diff --git a/alternative_wmiircs/python/pyxp/messages.py b/alternative_wmiircs/python/pyxp/messages.py
@@ -51,9 +51,8 @@ class Message(object):
         vals = {}
         start = offset
         for field in cls.fields:
-            size, val = field.unmarshall(data, offset)
+            size, vals[field.name] = field.unmarshall(data, offset)
             offset += size
-            vals[field.name] = val
         return offset - start, cls(**vals)
     def marshall(self):
         res = []