Commits

Lynn Rees committed f76fc19

- usability enhancements

Comments (0)

Files changed (7)

 
 setup(
     name='twoq',
-    version='0.4.13',
+    version='0.4.14',
     description='iterator chaining, underscored by a two-headed queue',
     long_description=open(join(getcwd(), 'README.rst'), 'r').read(),
     keywords='queue generator utility iterator functional programming',
 from twoq.active.queuing import twoq, manq, autoq
 
 __all__ = ('twoq', 'manq', 'autoq', 'port')
-__version__ = (0, 4, 13)
+__version__ = (0, 4, 14)

twoq/active/mixins.py

 # -*- coding: utf-8 -*-
 '''active twoq mixins'''
 
+from copy import copy
 from collections import deque
 from contextlib import contextmanager
 
 
     def __repr__(self):
         getr_, list_ = lambda x: getattr(self, x), list
-        return (
-            '<{}.{}([IN: {}({}) => WORK: {}({}) => UTIL: {}({}) => '
-            'OUT: {}: ({})]) at {}'
-        ).format(
+        return self._repr(
             self.__module__,
             clsname(self),
+            self.current_mode.upper(),
             self._INQ,
             list_(getr_(self._INQ)),
             self._WORKQ,
         )
 
     ###########################################################################
+    ## snapshots ##############################################################
+    ###########################################################################
+
+    def snapshot(self):
+        '''take snapshot of incoming'''
+        self._snapshots.append(copy(getattr(self, self._INQ)))
+        return self
+
+    ###########################################################################
     ## thing length ###########################################################
     ###########################################################################
 
         self.outgoing.clear()
         return self
 
+    def ssclear(self):
+        self._snapshots.clear()
+        return self
+
     ###########################################################################
     ## extend #################################################################
     ###########################################################################
 
     @contextmanager
     def ctx2(self, **kw):
-        '''swap to two-armed context'''
+        '''swap for two-armed context'''
+        self.snapshot()
         self.swap(
             outq=kw.get(self._OUTCFG, self._INVAR), context=self.ctx2(), **kw
         )
 
     @contextmanager
     def ctx3(self, **kw):
-        '''swap to three-armed context'''
+        '''swap for three-armed context'''
+        self.snapshot()
         self.swap(
             utilq=kw.get(self._WORKCFG, self._WORKVAR), context=self.ctx3, **kw
         )
 
     @contextmanager
     def ctx4(self, **kw):
-        '''swap to four-armed context'''
+        '''swap for four-armed context'''
+        self.snapshot()
         self.swap(context=self.ctx4, **kw)
         getr_ = lambda x: getattr(self, x)
         outq = getr_(self._OUTQ)
 
     @contextmanager
     def autoctx(self, **kw):
-        '''swap to auto-synchronizing context'''
+        '''swap for auto-synchronizing context'''
+        self.snapshot()
         self.swap(context=self.autoctx, **kw)
         getr_ = lambda x: getattr(self, x)
         outq = getr_(self._OUTQ)
         self.reswap()
 
     def ro(self):
-        '''swap to read-only context'''
+        '''switch to read-only mode'''
         with self.ctx3(outq=self._UTILVAR):
             self._xtend(self._iterable)
         with self.ctx1(hard=True, workq=self._UTILVAR):
+            self.current_mode = self._RO
             return self
 
 

twoq/filtering.py

 
     def deepmembers(self):
         '''collect object members from incoming things and their bases'''
-        _mf = self._mfilter
+        _mf, mro = self._mfilter, getmro
         _mz = lambda x: _mf(self._call, x)
-        def _memfilters(thing, mz=_mz, gc=getcls, ci=ichain):
-            return ci(imap(mz, ci([getmro((gc(thing))), [thing]])))
+        def memfilters(thing, mz=_mz, gc=getcls, ci=ichain, mro=mro): #@IgnorePep8
+            return ci(imap(mz, ci([mro((gc(thing))), [thing]])))
         with self._context():
             return self._xtend(
-                ichain(imap(_memfilters, self._iterable))
+                ichain(imap(memfilters, self._iterable))
             )
-            
+
     def extract(self):
         '''extract object members from incoming things'''
         with self._context():
                 lambda x: mfilter(call, x), self._iterable,
             )))
 
+    def mro(self):
+        '''extract ancestors of things by method resolution order'''
+        with self._context():
+            return self._extend(getmro(i) for i in self._iterable)
+
     def pick(self, *names):
         '''collect object attributes from incoming things by their `*names`'''
         with self._context():

twoq/lazy/mixins.py

         setr_(self._WORKQ, work1)
         util1, util2 = tee_(getr_(self._UTILQ))
         setr_(self._UTILQ, util1)
-        return (
-            '<{}.{}([IN: {}({}) => WORK: {}({}) => UTIL: {}({}) => '
-            'OUT: {}: ({})]) at {}>'
-        ).format(
+        return self._repr(
             self.__module__,
             clsname(self),
+            self.current_mode.upper(),
             self._INQ,
             list_(in2),
             self._WORKQ,
         )
 
     ###########################################################################
+    ## snapshots ##############################################################
+    ###########################################################################
+
+    def snapshot(self):
+        '''take snapshot of incoming'''
+        snapshot, self.incoming = tee(getattr(self, self._INQ))
+        self._snapshots.append(snapshot)
+        return self
+
+    ###########################################################################
     ## length #################################################################
     ###########################################################################
 
 
     @contextmanager
     def ctx2(self, **kw):
-        '''swap to two-armed context'''
+        '''swap for two-armed context'''
+        self.snapshot()
         self.swap(
             context=self.ctx2, outq=kw.get(self._OUTCFG, self._INVAR), **kw
         )._clearwork()
 
     @contextmanager
     def ctx3(self, **kw):
-        '''swap to three-armed context'''
+        '''swap for three-armed context'''
+        self.snapshot()
         self.swap(
             utilq=kw.get(self._WORKCFG, self._WORKVAR), context=self.ctx3, **kw
         )._clearwork()
 
     @contextmanager
     def ctx4(self, **kw):
-        '''swap to four-armed context'''
+        '''swap for four-armed context'''
         self.swap(context=self.ctx4, **kw)._clearwork()
         setr_ = lambda x, y: setattr(self, x, y)
         getr_ = lambda x: getattr(self, x)
 
     @contextmanager
     def autoctx(self, **kw):
-        '''swap to auto-synchronizing context'''
+        '''swap for auto-synchronizing context'''
         self.swap(context=self.autoctx, **kw)._clearwork()
         setr_ = lambda x, y: setattr(self, x, y)
         getr_ = lambda x: getattr(self, x)
         self.reswap()
 
     def ro(self):
-        '''swap to read-only context'''
+        '''switch to read-only mode'''
         with self.ctx3(outq=self._UTILVAR):
             self._xreplace(self._iterable)
         with self.ctx1(hard=True, workq=self._UTILVAR):
+            self.current_mode = self._RO
             return self
 
 
     '''manually balanced things mixin'''
 
     _default_context = 'ctx4'
-    
-    
+
+
 class EndMixin(ResultMixin):
 
     '''result things mixin'''
 from itertools import tee, repeat
 from contextlib import contextmanager
 
+from stuf.utils import OrderedDict
+from stuf.core import stuf, frozenstuf, orderedstuf
+
+from twoq.support import n2u, n2b
+
 SLOTS = [
     '_work', 'outgoing', '_util', 'incoming', '_call', '_alt', '_wrapper',
     '_args', '_kw', '_clearout', '_context', '_CONFIG', '_INQ', '_WORKQ',
-    '_UTILQ', '_OUTQ', '_iterator',
+    '_UTILQ', '_OUTQ', '_iterator', 'current_mode', '_snapshots',
 ]
 
 
     # 4. outgoing things
     _OUTCFG = 'outq'
     _OUTVAR = 'outgoing'
+    # read/write mode marker
+    _RW = 'read/write'
+    # read-only mode marker
+    _RO = 'read-only'
 
-    def __init__(self, incoming, outgoing):
+    def __init__(self, incoming, outgoing, **kw):
         '''
         init
 
         @param outgoing: outgoing things
         '''
         super(ThingsMixin, self).__init__()
+        # snapshots
+        self._snapshots = deque(maxlen=kw.pop('snapshots', 5))
         # incoming things
         self.incoming = incoming
         # outgoing things
         self._args = ()
         # reset keyword arguments
         self._kw = {}
+        # mode
+        self.current_mode = self._RW
         # set defaults
         self.unswap()
 
         '''if queues are balanced'''
         return self.outcount() == self.__len__()
 
+    @staticmethod
+    def _repr(*args):
+        return (
+            '<{0}.{1}<<{2}>>([IN: {3}({4}) => WORK: {5}({6}) => UTIL: {7}({8})'
+            ' => OUT: {9}: ({10})]) at {11}>'
+        ).format(*args)
+
     def clear(self):
         '''clear every thing'''
         self.detap().unwrap().dealt()
     @contextmanager
     def ctx1(self, **kw):
         '''swap to one-armed context'''
+        self.snapshot()
         q = kw.pop(self._WORKCFG, self._INVAR)
         self.swap(workq=q, utilq=q, context=self.ctx1, **kw)
         yield
         '''swap context to default context'''
         return self.swap()
 
-    def rw(self):
-        '''switch to read/write context'''
-        return self._uclear().unswap()
-
     def reswap(self):
         '''swap contexts to current preferred context'''
         return self.swap(**self._CONFIG)
 
     ###########################################################################
+    ## mode ###################################################################
+    ###########################################################################
+
+    def rw(self):
+        '''switch to read/write mode'''
+        self.current_mode = self._RW
+        return self._uclear().unswap()
+
+    ###########################################################################
+    ## snapshots ##############################################################
+    ###########################################################################
+
+    def undo(self):
+        '''revert to last snapshot'''
+        self.clear()
+        self.incoming = self._snapshots.pop()
+        self.snapshot()
+        return self
+
+    def revert(self, snapshot=0):
+        '''revert to specific snapshot'''
+        self.clear()
+        self.incoming = self._snapshots[snapshot]
+        self.snapshot()
+        return self
+
+    ###########################################################################
     ## current callable management ############################################
     ###########################################################################
 
 
     defactory = detap
 
-    def wrap(self, wrapper):
-        '''
-        wrapper for outgoing things
-
-        @param wrapper: an iterator
-        '''
-        self._wrapper = wrapper
-        return self
-
-    def unwrap(self):
-        '''clear current wrapper'''
-        self._wrapper = list
-        return self
-
     ###########################################################################
     ## things rotation ########################################################
     ###########################################################################
 
     '''result things mixin'''
 
+    def wrap(self, wrapper):
+        '''
+        wrapper for outgoing things
+
+        @param wrapper: an iterator
+        '''
+        self._wrapper = wrapper
+        return self
+
+    def unwrap(self):
+        '''clear current wrapper'''
+        return self.list()
+
+    def dict(self):
+        '''set wrapper to `d    ict`'''
+        self._wrap = dict
+        return self
+
+    def ordered_dict(self):
+        '''set wrapper to `OrderedDict`'''
+        self._wrap = OrderedDict
+
+    def list(self):
+        '''set wrapper to `list`'''
+        self._wrap = list
+        return self
+
+    def unicode(self, encoding='utf-8'):
+        '''set wrapper to `unicode` with given `encoding`'''
+        self._wrap = lambda x: n2u(x, encoding)
+        return self
+
+    def bytes(self, encoding='ISO-8859-1'):
+        '''set wrapper to `bytes` with given `encoding`'''
+        self._wrap = lambda x: n2b(x, encoding)
+        return self
+
+    def tuple(self):
+        '''set wrapper to `tuple`'''
+        self._wrap = tuple
+        return self
+
+    def set(self):
+        '''set wrapper to `set`'''
+        self._wrap = set
+        return self
+
+    def frozenset(self):
+        '''set wrapper to `frozenset`'''
+        self._wrap = frozenset
+        return self
+
+    def deque(self):
+        '''set wrapper to `deque`'''
+        self._wrap = deque
+        return self
+
+    def stuf(self):
+        '''set wrapper to `stuf`'''
+        self._wrap = stuf
+        return self
+
+    def frozenstuf(self):
+        '''set wrapper to `frozenstuf`'''
+        self._wrap = frozenstuf
+        return self
+
+    def orderedstuf(self):
+        '''set wrapper to `orderedstuf`'''
+        self._wrap = orderedstuf
+        return self
+
     def first(self):
         '''first incoming thing'''
         with self._context():
                 self_get = self.get
                 for elem in iterable:
                     self[elem] = self_get(elem, 0) + 1
+
+
+if six.PY3:
+
+    def n2b(n, encoding='ISO-8859-1'):
+        '''
+        the given native string as a byte string in the given encoding
+        '''
+        # In Python 3, the native string type is unicode
+        return n.encode(encoding)
+
+    def n2u(n, encoding='ISO-8859-1'):
+        '''
+        the given native string as a unicode string with the given encoding
+        '''
+        # In Python 3, the native string type is unicode
+        return n
+
+    def ton(n, encoding='ISO-8859-1'):
+        '''
+        the given string as a native string in the given encoding
+        '''
+        # In Python 3, the native string type is unicode
+        if isinstance(n, bytes):
+            return n.decode(encoding)
+        return n
+else:
+    import re
+
+    def n2b(n, encoding='ISO-8859-1'):
+        '''the given native string as a byte string in the given encoding'''
+        # In Python 2, the native string type is bytes. Assume it's already
+        # in the given encoding, which for ISO-8859-1 is almost always what
+        # was intended.
+        return n
+
+    def n2u(n, encoding='ISO-8859-1'):
+        '''
+        the given native string as a unicode string with the given encoding
+        '''
+        # In Python 2, the native string type is bytes.
+        # First, check for the special encoding 'escape'. The test suite uses
+        # this
+        # to signal that it wants to pass a string with embedded \unnnn
+        # escapes, but without having to prefix it with u'' for Python 2, but
+        # no prefix for Python 3.
+        if encoding == 'escape':
+            return unicode(re.sub(
+                r'\\u([0-9a-zA-Z]{4})',
+               lambda m: unichr(int(m.group(1), 16)),
+               n.decode('ISO-8859-1')
+            ))
+        # assume it's already in the given encoding, which for ISO-8859-1 is
+        # almost always what was intended.
+        return n.decode(encoding)
+
+    def ton(n, encoding='ISO-8859-1'):
+        '''return the given string as a native string in the given encoding'''
+        # in Python 2, the native string type is bytes.
+        if isinstance(n, unicode):
+            return n.encode(encoding)
+        return n