Source

twoq / twoq / core.py

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
# -*- coding: utf-8 -*-
'''twoq queuing mixins'''

from threading import local
from collections import deque
from contextlib import contextmanager

from stuf.utils import OrderedDict
from stuf.six import binaries, texts, b, u
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', 'current_mode', '_savepoints', '_start',
]


class ThingsMixin(local):

    '''things management mixin'''

    def __init__(self, incoming, outgoing, **kw):
        '''
        init

        @param incoming: incoming things
        @param outgoing: outgoing things
        '''
        super(ThingsMixin, self).__init__()
        # incoming things
        self.incoming = incoming
        # outgoing things
        self.outgoing = outgoing
        # preferred mode
        self.current_mode = self._RW
        #######################################################################
        ## context defaults ###################################################
        #######################################################################
        # preferred context
        self._context = getattr(self, self._DEFAULT_CONTEXT)
        # default context settings
        self._CONFIG = {}
        # 1. default incoming things
        self._INQ = self._INVAR
        # 2. default work things
        self._WORKQ = self._WORKVAR
        # 3. default utility things
        self._UTILQ = self._UTILVAR
        # 4. default outgoing things
        self._OUTQ = self._OUTVAR
        # clear outgoing things before extending/appending to them?
        self._clearout = True
        #######################################################################
        ## snapshotting defaults ##############################################
        #######################################################################
        # number of savepoints to keep (default: 5)
        maxlen = kw.pop('savepoints', 5)
        # create stack for savepoint things
        self._savepoints = deque(maxlen=maxlen) if maxlen is not None else None
        # savepoint of original incoming things
        if self._savepoints is not None:
            self._original()
        #######################################################################
        ## callable defaults ##################################################
        #######################################################################
        # current callable (default: identity)
        self._call = lambda x: x
        # current alternate callable (default: identity)
        self._alt = lambda x: x
        # iterable export wrapper (default: `list`)
        self._wrapper = list
        # postition arguments (default: `tuple`)
        self._args = ()
        # keyword arguments (default: `dict`)
        self._kw = {}

    ###########################################################################
    ## mode things ############################################################
    ###########################################################################

    # read/write mode
    _RW = 'read/write'
    # read-only mode
    _RO = 'read-only'

    def rw(self):
        '''switch to read/write mode'''
        self.current_mode = self._RW
        return self._clearu().unswap()

    ###########################################################################
    ## context things #EE######################################################
    ###########################################################################

    # 1. incoming things
    _INCFG = 'inq'
    _INVAR = 'incoming'
    # 2. utility things
    _UTILCFG = 'utilq'
    _UTILVAR = '_util'
    # 3. work things
    _WORKCFG = 'workq'
    _WORKVAR = '_work'
    # 4. outgoing things
    _OUTCFG = 'outq'
    _OUTVAR = 'outgoing'

    def swap(self, **kw):
        '''swap context'''
        # savepoint
        savepoint = kw.pop('savepoint', True)
        if savepoint:
            self._savepoint()
        # keep context-specific settings between context swaps
        self._CONFIG = kw if kw.get('hard', False) else {}
        # set context
        self._context = kw.get('context', getattr(self, self._DEFAULT_CONTEXT))
        # clear out outgoing things before extending them?
        self._clearout = kw.get('clearout', True)
        # 1. incoming things
        self._INQ = kw.get(self._INCFG, self._INVAR)
        # 2. work things
        self._WORKQ = kw.get(self._WORKCFG, self._WORKVAR)
        # 3. utility things
        self._UTILQ = kw.get(self._UTILCFG, self._UTILVAR)
        # 4. outgoing things
        self._OUTQ = kw.get(self._OUTCFG, self._OUTVAR)
        return self

    def reswap(self):
        '''swap for preferred context'''
        return self.swap(savepoint=False, **self._CONFIG)

    def unswap(self):
        '''swap for current default context'''
        return self.swap(savepoint=False)

    @contextmanager
    def ctx1(self, **kw):
        '''swap for one-armed context'''
        q = kw.pop(self._WORKCFG, self._INVAR)
        self.swap(workq=q, utilq=q, context=self.ctx1, **kw)
        yield
        self.reswap()

    ###########################################################################
    ## savepoint for things ##################################################
    ###########################################################################

    def _original(self):
        '''preserve original incoming things'''
        self._savepoint()
        # preserve from savepoint stack
        self._start = self._savepoints.pop()
        return self

    def undo(self, index=0, everything=False):
        '''
        revert to previous savepoint

        @param index: index of savepoint (default: 0)
        @param everything: undo everything and return things to original state
        '''
        if everything:
            self.clear()._clearsp()
            self.incoming = self._start
            self._original()
            return self
        self.clear()
        if not index:
            self.incoming = self._savepoints.pop()
        else:
            self._savepoints.reverse()
            self.incoming = self._savepoints[index]
            self._savepoints.reverse()
        return self._savepoint()

    ###########################################################################
    ## rotate things ##########################################################
    ###########################################################################

    def reup(self):
        '''put incoming things in incoming things as one incoming thing'''
        with self.ctx2(savepoint=False):
            return self._append(list(self._iterable))

    def sync(self):
        '''shift outgoing things to incoming things'''
        with self.autoctx(inq=self._OUTVAR, outq=self._INVAR, savepoint=False):
            return self._xtend(self._iterable)

    def syncout(self):
        '''shift incoming things to outgoing things'''
        with self.autoctx(savepoint=False):
            return self._xtend(self._iterable)

    ###########################################################################
    ## extend incoming things #################################################
    ###########################################################################

    def extend(self, things):
        '''
        extend after incoming things

        @param thing: some things
        '''
        with self.ctx1():
            return self._xtend(things)

    def extendleft(self, things):
        '''
        extend before incoming things

        @param thing: some things
        '''
        with self.ctx1():
            return self._xtendleft(things)

    def outextend(self, things):
        '''
        extend right side of outgoing things

        @param thing: some things
        '''
        with self.ctx1(workq=self._OUTVAR):
            return self._xtend(things)

    ###########################################################################
    ## append incoming things #################################################
    ###########################################################################

    def append(self, thing):
        '''
        append after incoming things

        @param thing: some thing
        '''
        with self.ctx1():
            return self._append(thing)

    def prepend(self, thing):
        '''
        append before incoming things

        @param thing: some thing
        '''
        with self.ctx1():
            return self._appendleft(thing)

    ###########################################################################
    ## call things ############################################################
    ###########################################################################

    def args(self, *args, **kw):
        '''set arguments for current or alternative callable'''
        # set position arguments
        self._args = args
        # set keyword arguemnts
        self._kw = kw
        return self

    def tap(self, call, alt=None, factory=False):
        '''
        set current callable

        @param call: a callable
        @param alt: an alternative callable (default: None)
        @param factor: call is a factory? (default: False)
        '''
        # reset postition arguments
        self._args = ()
        # reset keyword arguments
        self._kw = {}
        # set factory for building current callable
        if factory:
            def factory(*args, **kw):
                return call(*args, **kw)
            self._call = factory
        else:
            # set current callable
            self._call = call
        # set alternative callable
        self._alt = alt if alt is not None else lambda x: x
        return self

    def untap(self):
        '''clear current callable'''
        # reset postition arguments
        self._args = ()
        # reset keyword arguments
        self._kw = {}
        # reset current callable (default is identity)
        self._call = lambda x: x
        # reset alternative callable
        self._alt = lambda x: x
        return self

    ###########################################################################
    ## know things ############################################################
    ###########################################################################

    @staticmethod
    def _repr(*args):
        '''queue representation'''
        return (
            '<{0}.{1}<<{2}>>([IN: {3}({4}) => WORK: {5}({6}) => UTIL: {7}({8})'
            ' => OUT: {9}: ({10})]) at {11}>'
        ).format(*args)

    @property
    def balanced(self):
        '''queues are balanced?'''
        return self.countout() == self.__len__()

    ###########################################################################
    ## clear things ###########################################################
    ###########################################################################

    def _clearsp(self):
        '''clear savepoints'''
        self._savepoints.clear()
        return self

    def clear(self):
        '''clear anything'''
        return self.untap().unwrap().clearout().clearin()._clearw()._clearu()


class ResultsMixin(local):

    '''result of things mixin'''

    ###########################################################################
    ## outgoing things export #################################################
    ###########################################################################

    def peek(self):
        '''results from read-only context'''
        self.ro()
        out = self._wrapper(self._util)
        results = out[0] if len(out) == 1 else out
        self.rw()
        return results

    def results(self):
        '''yield outgoing things, clearing outgoing things as it iterates'''
        return self.__iter__()

    ###########################################################################
    ## wrap outgoing things ###################################################
    ###########################################################################

    def wrap(self, wrapper):
        '''
        wrapper for outgoing things

        @param wrapper: an iterator class
        '''
        self._wrapper = wrapper
        return self

    def tuple_wrap(self):
        '''set wrapper to `tuple`'''
        return self.wrap(tuple)

    def set_wrap(self):
        '''set wrapper to `set`'''
        return self.wrap(set)

    def byte_wrap(self, encoding='ISO-8859-1', join=b('')):
        '''set wrapper to `bytes` with given `encoding`'''
        return self.wrap(lambda x: n2b(
            join.join(binaries(i) for i in x), encoding)
        )

    def deque_wrap(self):
        '''set wrapper to `deque`'''
        return self.wrap(deque)

    def dict_wrap(self):
        '''set wrapper to `dict`'''
        return self.wrap(dict)

    def frozenset_wrap(self):
        '''set wrapper to `frozenset`'''
        return self.wrap(frozenset)

    def frozenstuf_wrap(self):
        '''set wrapper to `frozenstuf`'''
        return self.wrap(frozenstuf)

    def ordereddict_wrap(self):
        '''set wrapper to `OrderedDict`'''
        return self.wrap(OrderedDict)

    def orderedstuf_wrap(self):
        '''set wrapper to `orderedstuf`'''
        return self.wrap(orderedstuf)

    def stuf_wrap(self):
        '''set wrapper to `stuf`'''
        return self.wrap(stuf)

    def unicode_wrap(self, encoding='utf-8', join=u('')):
        '''set wrapper to `unicode` with given `encoding`'''
        return self.wrap(
            lambda x: n2u(join.join(texts(i) for i in x), encoding)
        )

    def list_wrap(self):
        '''clear current wrapper'''
        return self.wrap(list)

    unwrap = list_wrap