utils.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. from __future__ import unicode_literals
  2. import inspect
  3. import os
  4. import signal
  5. import sys
  6. import threading
  7. import weakref
  8. from wcwidth import wcwidth
  9. from six.moves import range
  10. __all__ = (
  11. 'Event',
  12. 'DummyContext',
  13. 'get_cwidth',
  14. 'suspend_to_background_supported',
  15. 'is_conemu_ansi',
  16. 'is_windows',
  17. 'in_main_thread',
  18. 'take_using_weights',
  19. 'test_callable_args',
  20. )
  21. class Event(object):
  22. """
  23. Simple event to which event handlers can be attached. For instance::
  24. class Cls:
  25. def __init__(self):
  26. # Define event. The first parameter is the sender.
  27. self.event = Event(self)
  28. obj = Cls()
  29. def handler(sender):
  30. pass
  31. # Add event handler by using the += operator.
  32. obj.event += handler
  33. # Fire event.
  34. obj.event()
  35. """
  36. def __init__(self, sender, handler=None):
  37. self.sender = sender
  38. self._handlers = []
  39. if handler is not None:
  40. self += handler
  41. def __call__(self):
  42. " Fire event. "
  43. for handler in self._handlers:
  44. handler(self.sender)
  45. def fire(self):
  46. " Alias for just calling the event. "
  47. self()
  48. def __iadd__(self, handler):
  49. """
  50. Add another handler to this callback.
  51. (Handler should be a callable that takes exactly one parameter: the
  52. sender object.)
  53. """
  54. # Test handler.
  55. assert callable(handler)
  56. if not test_callable_args(handler, [None]):
  57. raise TypeError("%r doesn't take exactly one argument." % handler)
  58. # Add to list of event handlers.
  59. self._handlers.append(handler)
  60. return self
  61. def __isub__(self, handler):
  62. """
  63. Remove a handler from this callback.
  64. """
  65. self._handlers.remove(handler)
  66. return self
  67. # Cache of signatures. Improves the performance of `test_callable_args`.
  68. _signatures_cache = weakref.WeakKeyDictionary()
  69. def test_callable_args(func, args):
  70. """
  71. Return True when this function can be called with the given arguments.
  72. """
  73. assert isinstance(args, (list, tuple))
  74. signature = getattr(inspect, 'signature', None)
  75. if signature is not None:
  76. # For Python 3, use inspect.signature.
  77. try:
  78. sig = _signatures_cache[func]
  79. except KeyError:
  80. sig = signature(func)
  81. _signatures_cache[func] = sig
  82. try:
  83. sig.bind(*args)
  84. except TypeError:
  85. return False
  86. else:
  87. return True
  88. else:
  89. # For older Python versions, fall back to using getargspec.
  90. spec = inspect.getargspec(func)
  91. # Drop the 'self'
  92. def drop_self(spec):
  93. args, varargs, varkw, defaults = spec
  94. if args[0:1] == ['self']:
  95. args = args[1:]
  96. return inspect.ArgSpec(args, varargs, varkw, defaults)
  97. spec = drop_self(spec)
  98. # When taking *args, always return True.
  99. if spec.varargs is not None:
  100. return True
  101. # Test whether the given amount of args is between the min and max
  102. # accepted argument counts.
  103. return len(spec.args) - len(spec.defaults or []) <= len(args) <= len(spec.args)
  104. class DummyContext(object):
  105. """
  106. (contextlib.nested is not available on Py3)
  107. """
  108. def __enter__(self):
  109. pass
  110. def __exit__(self, *a):
  111. pass
  112. class _CharSizesCache(dict):
  113. """
  114. Cache for wcwidth sizes.
  115. """
  116. def __missing__(self, string):
  117. # Note: We use the `max(0, ...` because some non printable control
  118. # characters, like e.g. Ctrl-underscore get a -1 wcwidth value.
  119. # It can be possible that these characters end up in the input
  120. # text.
  121. if len(string) == 1:
  122. result = max(0, wcwidth(string))
  123. else:
  124. result = sum(max(0, wcwidth(c)) for c in string)
  125. # Cache for short strings.
  126. # (It's hard to tell what we can consider short...)
  127. if len(string) < 256:
  128. self[string] = result
  129. return result
  130. _CHAR_SIZES_CACHE = _CharSizesCache()
  131. def get_cwidth(string):
  132. """
  133. Return width of a string. Wrapper around ``wcwidth``.
  134. """
  135. return _CHAR_SIZES_CACHE[string]
  136. def suspend_to_background_supported():
  137. """
  138. Returns `True` when the Python implementation supports
  139. suspend-to-background. This is typically `False' on Windows systems.
  140. """
  141. return hasattr(signal, 'SIGTSTP')
  142. def is_windows():
  143. """
  144. True when we are using Windows.
  145. """
  146. return sys.platform.startswith('win') # E.g. 'win32', not 'darwin' or 'linux2'
  147. def is_conemu_ansi():
  148. """
  149. True when the ConEmu Windows console is used.
  150. """
  151. return is_windows() and os.environ.get('ConEmuANSI', 'OFF') == 'ON'
  152. def in_main_thread():
  153. """
  154. True when the current thread is the main thread.
  155. """
  156. return threading.current_thread().__class__.__name__ == '_MainThread'
  157. def take_using_weights(items, weights):
  158. """
  159. Generator that keeps yielding items from the items list, in proportion to
  160. their weight. For instance::
  161. # Getting the first 70 items from this generator should have yielded 10
  162. # times A, 20 times B and 40 times C, all distributed equally..
  163. take_using_weights(['A', 'B', 'C'], [5, 10, 20])
  164. :param items: List of items to take from.
  165. :param weights: Integers representing the weight. (Numbers have to be
  166. integers, not floats.)
  167. """
  168. assert isinstance(items, list)
  169. assert isinstance(weights, list)
  170. assert all(isinstance(i, int) for i in weights)
  171. assert len(items) == len(weights)
  172. assert len(items) > 0
  173. already_taken = [0 for i in items]
  174. item_count = len(items)
  175. max_weight = max(weights)
  176. i = 0
  177. while True:
  178. # Each iteration of this loop, we fill up until by (total_weight/max_weight).
  179. adding = True
  180. while adding:
  181. adding = False
  182. for item_i, item, weight in zip(range(item_count), items, weights):
  183. if already_taken[item_i] < i * weight / float(max_weight):
  184. yield item
  185. already_taken[item_i] += 1
  186. adding = True
  187. i += 1