1 """General-purpose types and utilities.
2
3 Copyright 2007-2014 Research Foundation State University of New York
4 This file is part of QUB Express.
5
6 QUB Express is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 QUB Express is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License,
17 named LICENSE.txt, in the QUB Express program directory. If not, see
18 <http://www.gnu.org/licenses/>.
19
20 """
21
22 import collections
23 import heapq
24 import json
25 import pprint
26 import traceback
27 import weakref
28 from math import *
29 from string import join
30 import re
31 import os
32 import sys
33
34 from contextlib import contextmanager
35
36
37
38 ShowDropped = False
39
40
41
42
43 UNSET_VALUE = 86738.82583
46 """
47 Usage:
48
49 >>> myEvent = Event() # a, b
50 >>> callback1 = lambda a, b: dostuff(a, b)
51 >>> callback2 = lambda a, b: otherstuff(a, b)
52 >>> myEvent += callback1
53 >>> myEvent += callback2
54 >>> myEvent(a, b) # calls both callbacks
55 >>> myEvent -= callback1
56 >>> myEvent -= callback2
57 >>> myEvent(a, b) # no callbacks; does nothing
58
59 Note: this is a great source of circular references; see gc.collect()
60 """
61
79
81 """Prints and returns its argument(s). Handy for testing callbacks."""
82 if len(xx) == 1:
83 pprint.pprint(xx[0])
84
85 return xx[0]
86 else:
87 for x in xx:
88 print x,
89 print
90 return xx
91
93 """
94 Usage:
95
96 >>> myEvent = Event() # a, b
97 >>> callback1 = lambda a, b: dostuff(a, b)
98 >>> callback2 = lambda a, b: otherstuff(a, b)
99 >>> callback3 = self.callback3
100 >>> myEvent += callback1
101 >>> myEvent += callback2
102 >>> myEvent(a, b) # calls both callbacks
103 >>> myEvent -= callback1
104 >>> myEvent -= callback2
105 >>> myEvent(a, b) # no callbacks; does nothing
106
107 note: keep a ref somewhere, even to self.whatever, or it will be unlinked; see L{Reffer}
108 """
109 __slots__ = ['_listeners', '_names', '_stacks', 'name', 'enabled', '__weakref__']
110
118 dead = []
119 if self.enabled:
120 for l in self._listeners:
121 listener = l()
122 if not (listener is None):
123 try:
124 listener(*args, **kw)
125 except:
126 traceback.print_stack()
127 traceback.print_exc()
128 else:
129 dead.append(l)
130 for l in dead:
131 i = self._listeners.index(l)
132 n = self._names[i]
133 if ShowDropped:
134 print "%s%sdropped callback%s%s" % (self.name, self.name and ' ', n and ': ', n)
135 for line in self._stacks[i]:
136 sys.stdout.write(" > %s"%line)
137 self._listeners.pop(i)
138 self._names.pop(i)
139 self._stacks.pop(i)
154 dead = []
155 found = None
156 for l in self._listeners:
157 listener = l()
158 if listener == lsub:
159 found = l
160 dead.append(l)
161 elif listener is None:
162 dead.append(l)
163 for l in dead:
164 i = self._listeners.index(l)
165 if ShowDropped and (l != found):
166 n = self._names[i]
167 print "%s%sdropped callback%s%s" % (self.name, self.name and ' ', n and ': ', n)
168 for line in self._stacks[i]:
169 sys.stdout.write(" > %s"%line)
170 self._listeners.pop(i)
171 self._names.pop(i)
172 self._stacks.pop(i)
173 if not found:
174 traceback.print_stack()
175 print 'Warning: in unsubscribe (operator -=) of WeakEvent%s: caller was not subscribed.' % (self.name and (' %s' % self.name))
176 return self
179
183 """Like WeakEvent, except there's at most one listener, and it can return a value."""
184 __slots__ = ['name', '_listener', '_listener_name', 'enabled', '__weakref__']
204 - def assign(self, listener, name=""):
209
210
211
212
213
214
215 -class Reffer(object):
216 """
217 Holds references to keep objects alive.
218 Intended in combo with WeakEvent for bound method references e.g. self.onEvent,
219 since self.onEvent creates a temporary instance that would otherwise be destroyed.
220
221 Usage:
222
223 >>> self.ref = Reffer()
224 >>> self.evt = WeakEvent()
225 >>> self.evt += self.ref(self.onEvt)
226 >>> ...
227 >>> self.evt -= self.ref(self.onEvt) # hashes the same each time
228 """
229 __slots__ = ["r"]
238
240 """Prints to stdout when it is finally freed, presumably at the same time as its owner.
241
242 Usage:
243
244 >>> self.death_rattle = DeathRattle('Free at last')
245 """
250
252 """
253 An object whose fields are the constructor keyword arguments.
254 Requesting any other field returns None rather than raise NameError.
255
256 Example:
257
258 >>> x = Anon(stuff='x',n=5)
259 >>> print x.stuff, x.n, x.whatever
260 x, 5, None
261 """
267 dicts = []
268 for arg in args:
269 if isinstance(arg, str):
270 try:
271 dicts.append( dict(eval(arg)) )
272 except:
273 print '%s(): ignoring non-dict string argument: %s' % (self.__class__, arg)
274 elif isinstance(arg, dict):
275 dicts.append( arg )
276 else:
277 try:
278 dicts.append( arg.__dict__ )
279 except:
280 print '%s(): ignoring non-mapping argument: %s (%s)' % (self.__class__, arg, arg.__class__)
281 dicts.append(kw)
282 return dicts
283
285 try:
286 return self.__dict__[name]
287 except:
288 return None
290 return repr(self.__dict__)
294 def val2str(v):
295 if isinstance(v, Anon):
296 return v._str_indent(indent+" | ")
297 else:
298 return str(v)
299 return '\n'.join([str(self.__class__)] +
300 ['%s%16s: %s' % (indent,k,val2str(v)) for k,v in self.__dict__.iteritems()])
302 return self.__class__(**self.__dict__)
303
304
305 true = True
306 false = False
317 return json.dumps(eval(repr(self.__dict__)))
318
319
320
321
322
323
325 try:
326 return self.__dict__[name]
327 except:
328 j = self.__class__()
329 self.__dict__[name] = j
330 return j
342
345 """like builtin reduce(), but recursing by splitting in half. Doesn't seem to make a difference."""
346 if len(seq) < 1:
347 return None
348 elif len(seq) == 1:
349 return seq[0]
350 elif len(seq) == 2:
351 return func(seq[0], seq[1])
352 else:
353 half = len(seq)/2
354 return func(breduce(func, seq[:half]), breduce(func, seq[half:]))
355
357 """
358 Returns wrapped func that caches return values for specific args.
359 All args must be immutable (e.g. tuples not lists).
360
361 Example:
362
363 >>> def factorial(n):
364 >>> if n > 1:
365 >>> return n * fast_fac(n-1)
366 >>> else:
367 >>> return 1
368 >>> fast_fac = memoize(factorial)
369 """
370 results = {}
371 def wrapper(*args):
372 try:
373 return results[args]
374 except KeyError:
375 result = func(*args)
376 results[args] = result
377
378 return result
379 wrapper.func = func
380 wrapper.label = lbl
381 wrapper.results = results
382 wrapper.reset = results.clear
383 return wrapper
384
386 """
387 Returns wrapped func that caches most recent n return values for specific args.
388 All args must be immutable (e.g. tuples not lists).
389 """
390 results = {}
391 keys = [ [] ]
392 def wrapper(*args):
393 if args in results:
394 return results[args]
395 else:
396 if n == len(keys[0]):
397 del results[keys[0][0]]
398 keys[0] = keys[0][1:]
399 result = func(*args)
400 results[args] = result
401 keys[0].append(args)
402
403 return result
404 def reset():
405 results.clear()
406 keys[0] = []
407 wrapper.reset = reset
408 wrapper.results = results
409 return wrapper
410
412 """
413 Returns wrapped func that caches return values for specific args.
414 All args must be immutable (e.g. tuples not lists).
415
416 Keeps most recent k results such that sum(weigh(result) for result in results[:k]) <= max_weight.
417
418 Example:
419
420 >>> def factorial(n):
421 >>> if n > 1:
422 >>> return n * fast_fac(n-1)
423 >>> else:
424 >>> return 1
425 >>> fast_fac = memoize(factorial)
426 """
427 results = {}
428 order = []
429 next = [-1000000000]
430 total_weight = [0]
431 def wrapper(*args):
432 if args in results:
433 index, result = results[args]
434 for i in reversed(xrange(len(order))):
435 if order[i][0] == index:
436 results[args] = (next[0], result)
437 order[i] = (next[0], args)
438 next[0] += 1
439 heapq_sink(order, i)
440 break
441 return result
442 else:
443 result = func(*args)
444 weight = total_weight[0] + weigh(result)
445 while weight > max_weight:
446 rem_args = heapq.heappop(order)[1]
447 weight -= weigh(results[rem_args])
448 del results[rem_args]
449 total_weight[0] = weight
450 results[args] = (next[0], result)
451 heapq.heappush(order, (next[0], args))
452 next[0] += 1
453
454 return result
455 return wrapper
456
458 while (k > 0) and (heap[(k+1)/2-1] > heap[k]):
459 heap[k], heap[(k+1)/2-1] = heap[(k+1)/2-1], heap[k]
460
462 k2, N = (k+1)*2 - 1, len(heap)
463 if k2 >= N: return
464 if ((k2+1) < N) and (heap[k2] > heap[k2+1]):
465 k2 += 1
466 if heap[k] > heap[k2]:
467 heap[k], heap[k2] = heap[k2], heap[k]
468 heapq_sink(heap, k2)
469
471 """Remove item from heap"""
472
473 while index > 0:
474 up = (index + 1) / 2 - 1
475 heap[index] = heap[up]
476 index = up
477
478 heapq.heappop(heap)
479
480
481 -def bind(callback, *args, **kw):
482 """
483 Returns a function f which, when called, discards its own args and returns callback(*args, **kw).
484
485 Lambda can be useful to share one message handler among many sources, but there's a limitation:
486 >>> for i in xrange(N):
487 chk[i].OnToggle += self__ref(lambda chk: self.__onToggle(i))
488
489 Here, the lambda binds not the value of i, but the stack location whose contents are repeatedly changed by the for loop.
490 So all the lambdas are effectively doing self.__onToggle(N-1).
491
492 Instead:
493 >>> for i in xrange(N):
494 chk[i].OnToggle += self.__ref(bind(self.__onToggle, i))
495
496 This creates a unique stack frame for your unique i, so it does what you expect.
497 """
498
499 def act(*act_args, **act_kw):
500 return callback(*args, **kw)
501 return act
502
504 """Like bind, but it appends the callback's positional args after the bound positional args; returns callback(*(args+cb_args), **kw)."""
505 def act(*act_args, **act_kw):
506 use_kw = kw.copy()
507 for k,v in act_kw.iteritems():
508 use_kw[k] = v
509 return callback(*(args+act_args), **use_kw)
510 return act
511
513 """Like bind, but it prepends the callback's positional args before the bound positional args; returns callback(*(args+cb_args), **kw)."""
514 def act(*act_args, **act_kw):
515 use_kw = kw.copy()
516 for k,v in act_kw.iteritems():
517 use_kw[k] = v
518 return callback(*(act_args+args), **use_kw)
519 return act
520
524 """Returns (r,g,b) color coordinates corresponding to the hue, saturation and value. All coordinates are between 0 and 1."""
525 h, s, v = map(float, (h, s, v))
526 if h == 1.0: h = 0.0
527 h *= 360
528
529 if s == 0:
530 return v,v,v
531
532 sectorPos = h/60.0
533 sectorNumber = int(floor(sectorPos))
534 fractionalSector = sectorPos - sectorNumber
535
536 p = v * (1 - s)
537 q = v * (1 - (s * fractionalSector))
538 t = v * (1 - (s * (1 - fractionalSector)))
539
540 return {0 : (v, t, p),
541 1 : (q, v, p),
542 2 : (p, v, t),
543 3 : (p, q, v),
544 4 : (t, p, v),
545 5 : (v, p, q)} [sectorNumber]
546
548 """Returns the (hue, saturation, value) color coordinates corresponding to the RGB color. All coordinates are between 0 and 1."""
549 r, g, b = map(float, (r, g, b))
550 mn = min(r, g, b)
551 mx = max(r, g, b)
552 v = mx
553 delta = mx - mn
554 if (mx == 0.0) or (delta == 0.0):
555 return 0.0, 0.0, v
556 s = delta / mx
557 if r == mx:
558 h = ((g - b) / delta) % 6.0
559 elif g == mx:
560 h = 2.0 + (b - r) / delta
561 else:
562 h = 4.0 + (r - g) / delta
563 h *= 60.0
564 if h < 0: h += 360
565 return h/360.0, s, v
566
568 return (1-r, 1-g, 1-b)
569
571 c, m, y = RGBtoCMY(r, g, b)
572 k = min(c, m, y)
573 return ((c-k)/(1-k), (m-k)/(1-k), (y-k)/(1-k))
574
577 """
578 Returns r,g,b corresponding to x in [-1..1].
579 The scale moves smoothly around the color wheel from pink (-) to red (+),
580 with brightness proportional to abs(x).
581 """
582 y = sin(x*pi/2)
583 h = 0.44 * (1.0 - y)
584 v = abs(x)
585 return HSVtoRGB(h, saturation, v*(2.0 - v))
586
587
588
589 WITHALPHA = lambda tup: (len(tup) == 3) and tuple(list(tup)+[1]) or tup
590 NOALPHA = lambda tup: (len(tup) == 4) and tup[:3] or tup
591 SETALPHA = lambda tup, a: tuple(list(NOALPHA(tup)) + [a])
592 ADDALPHA = lambda tup, a: tuple(list(tup) + [a])
595 out = list(tup[:])
596 r,g,b = tup[:3]
597 h,s,v = RGBtoHSV(r, g, b)
598 out[:3] = HSVtoRGB((h+0.5) % 1.0, s, v)
599 return tuple(out)
600
601
602 COLOR = collections.defaultdict(lambda:(.5, .5, .5))
604 for i,c in enumerate([(0,0,0), (1,0,0), (0,0,1), (0,1,0), (0,1,1), (1,1,0), (1,0,1)]):
605 COLOR[i] = c
606 offset = len(COLOR)
607 for i in xrange(20):
608 COLOR[offset+i] = HSVtoRGB((i*2.0/20)%1.0, (40.0-i)/40, (20.0+i)/40)
609 _init_colors()
610 del _init_colors
611
612 COLOR_CLASS = lambda c: ('modelGTK.class[%i]' % c, COLOR[c])
613
614 rdname, rdbreak, rdbk = 'qubfast.txt', 'supporting', 'Expired.'
618
620 """Returns the list of all path components '/a/b/c' -> ['/', 'a', 'b', 'c']"""
621 l,r = os.path.split(s)
622 if (l == ''):
623 return [r]
624 elif (l == s):
625 return [s]
626 else:
627 lst = path_splits(l)
628 lst.append(r)
629 return lst
630
632 """Returns path unmodified if it already has an extension, otherwise appends ext."""
633 if os.path.splitext(path)[1]:
634 return path
635 else:
636 return path + ext
637
640 - def __init__(self, get=None, *def_args):
641 self.serial = 0
642 self.__get = get or self.get
643 self.__last_serial = -1
644 self.__last_args = None
645 self.__last_val = None
646 self.__def_args = def_args[:]
647 self.OnInvalidate = WeakEvent()
649 self.serial += 1
650 self.OnInvalidate(self)
652 if (self.serial != self.__last_serial) or (args != self.__last_args):
653 self.__last_val = self.__get(*args)
654 self.__last_serial = self.serial
655 self.__last_args = args
656 return self.__last_val
657 val = property(lambda self: self.get_val(*self.__def_args))
659 raise Exception('abstract')
660
663 """"Overrides" the base dict so keys are looked up first here, then if not found, in base dict."""
664 - def __init__(self, base=None, *args, **kw):
678 - def get(self, key):
683
710
732
762
766 """from Mark Dickinson (via stackoverflow.com): a more legible, flexible way to break out of nested loops, e.g.
767 with nested_break() as mylabel:
768 while True:
769 print "current state"
770 while True:
771 ok = raw_input("Is this ok? (y/n)")
772 if ok == "y" or ok == "Y": raise mylabel
773 if ok == "n" or ok == "N": break
774 print "more processing"
775 """
776 class NestedBreakException(Exception):
777 pass
778 try:
779 yield NestedBreakException
780 except NestedBreakException:
781 pass
782
786 """Takes as input an array of arrays, returns them transposed (so output[j][i] == arrays[i][j])."""
787 return [ [arrays[j][i] for j in xrange(len(arrays))]
788 for i in xrange(len(arrays[0])) ]
789
790
791 MAX_SAFENAME_LEN = 32
794 """Returns nm, modified to be a legal python identifier (up to MAX_SAFENAME_LEN (%i) chars).""" % MAX_SAFENAME_LEN
795 alnum = re.sub(r"[^a-zA-Z0-9_]", '_', nm)
796 safe = re.sub(r"(^[0-9])", r"_\1", alnum)
797 if safe == 'x': safe = '_x_'
798 return safe[:MAX_SAFENAME_LEN]
799
818
821 """ return the union of two lists """
822 return list(set(a) | set(b))
823
833
842
843 valid_identifier = re.compile(r"^[A-Za-z_][A-Za-z_0-9]*$")
844