You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

278 lines
7.9 KiB

  1. # -*- coding: utf-8 -*-
  2. """
  3. Miscellaneous helper functions.
  4. The formatter for ANSI colored console output is heavily based on Pygments
  5. terminal colorizing code, originally by Georg Brandl.
  6. """
  7. from __future__ import (absolute_import, division, print_function,
  8. unicode_literals)
  9. import calendar
  10. import datetime
  11. import importlib
  12. import logging
  13. import numbers
  14. import sys
  15. try:
  16. from collections.abc import Iterable
  17. except ImportError:
  18. from collections import Iterable
  19. from .compat import as_text, is_python_version, string_types
  20. from .exceptions import TimeoutFormatError
  21. class _Colorizer(object):
  22. def __init__(self):
  23. esc = "\x1b["
  24. self.codes = {}
  25. self.codes[""] = ""
  26. self.codes["reset"] = esc + "39;49;00m"
  27. self.codes["bold"] = esc + "01m"
  28. self.codes["faint"] = esc + "02m"
  29. self.codes["standout"] = esc + "03m"
  30. self.codes["underline"] = esc + "04m"
  31. self.codes["blink"] = esc + "05m"
  32. self.codes["overline"] = esc + "06m"
  33. dark_colors = ["black", "darkred", "darkgreen", "brown", "darkblue",
  34. "purple", "teal", "lightgray"]
  35. light_colors = ["darkgray", "red", "green", "yellow", "blue",
  36. "fuchsia", "turquoise", "white"]
  37. x = 30
  38. for d, l in zip(dark_colors, light_colors):
  39. self.codes[d] = esc + "%im" % x
  40. self.codes[l] = esc + "%i;01m" % x
  41. x += 1
  42. del d, l, x
  43. self.codes["darkteal"] = self.codes["turquoise"]
  44. self.codes["darkyellow"] = self.codes["brown"]
  45. self.codes["fuscia"] = self.codes["fuchsia"]
  46. self.codes["white"] = self.codes["bold"]
  47. if hasattr(sys.stdout, "isatty"):
  48. self.notty = not sys.stdout.isatty()
  49. else:
  50. self.notty = True
  51. def reset_color(self):
  52. return self.codes["reset"]
  53. def colorize(self, color_key, text):
  54. if self.notty:
  55. return text
  56. else:
  57. return self.codes[color_key] + text + self.codes["reset"]
  58. def ansiformat(self, attr, text):
  59. """
  60. Format ``text`` with a color and/or some attributes::
  61. color normal color
  62. *color* bold color
  63. _color_ underlined color
  64. +color+ blinking color
  65. """
  66. result = []
  67. if attr[:1] == attr[-1:] == '+':
  68. result.append(self.codes['blink'])
  69. attr = attr[1:-1]
  70. if attr[:1] == attr[-1:] == '*':
  71. result.append(self.codes['bold'])
  72. attr = attr[1:-1]
  73. if attr[:1] == attr[-1:] == '_':
  74. result.append(self.codes['underline'])
  75. attr = attr[1:-1]
  76. result.append(self.codes[attr])
  77. result.append(text)
  78. result.append(self.codes['reset'])
  79. return ''.join(result)
  80. colorizer = _Colorizer()
  81. def make_colorizer(color):
  82. """Creates a function that colorizes text with the given color.
  83. For example:
  84. green = make_colorizer('darkgreen')
  85. red = make_colorizer('red')
  86. Then, you can use:
  87. print "It's either " + green('OK') + ' or ' + red('Oops')
  88. """
  89. def inner(text):
  90. return colorizer.colorize(color, text)
  91. return inner
  92. class ColorizingStreamHandler(logging.StreamHandler):
  93. levels = {
  94. logging.WARNING: make_colorizer('darkyellow'),
  95. logging.ERROR: make_colorizer('darkred'),
  96. logging.CRITICAL: make_colorizer('darkred'),
  97. }
  98. def __init__(self, exclude=None, *args, **kwargs):
  99. self.exclude = exclude
  100. if is_python_version((2, 6)):
  101. logging.StreamHandler.__init__(self, *args, **kwargs)
  102. else:
  103. super(ColorizingStreamHandler, self).__init__(*args, **kwargs)
  104. @property
  105. def is_tty(self):
  106. isatty = getattr(self.stream, 'isatty', None)
  107. return isatty and isatty()
  108. def format(self, record):
  109. message = logging.StreamHandler.format(self, record)
  110. if self.is_tty:
  111. colorize = self.levels.get(record.levelno, lambda x: x)
  112. # Don't colorize any traceback
  113. parts = message.split('\n', 1)
  114. parts[0] = " ".join([parts[0].split(" ", 1)[0], colorize(parts[0].split(" ", 1)[1])])
  115. message = '\n'.join(parts)
  116. return message
  117. def import_attribute(name):
  118. """Return an attribute from a dotted path name (e.g. "path.to.func")."""
  119. module_name, attribute = name.rsplit('.', 1)
  120. module = importlib.import_module(module_name)
  121. return getattr(module, attribute)
  122. def utcnow():
  123. return datetime.datetime.utcnow()
  124. _TIMESTAMP_FORMAT = '%Y-%m-%dT%H:%M:%S.%fZ'
  125. def utcformat(dt):
  126. return dt.strftime(as_text(_TIMESTAMP_FORMAT))
  127. def utcparse(string):
  128. try:
  129. return datetime.datetime.strptime(string, _TIMESTAMP_FORMAT)
  130. except ValueError:
  131. # This catches any jobs remain with old datetime format
  132. return datetime.datetime.strptime(string, '%Y-%m-%dT%H:%M:%SZ')
  133. def first(iterable, default=None, key=None):
  134. """
  135. Return first element of `iterable` that evaluates true, else return None
  136. (or an optional default value).
  137. >>> first([0, False, None, [], (), 42])
  138. 42
  139. >>> first([0, False, None, [], ()]) is None
  140. True
  141. >>> first([0, False, None, [], ()], default='ohai')
  142. 'ohai'
  143. >>> import re
  144. >>> m = first(re.match(regex, 'abc') for regex in ['b.*', 'a(.*)'])
  145. >>> m.group(1)
  146. 'bc'
  147. The optional `key` argument specifies a one-argument predicate function
  148. like that used for `filter()`. The `key` argument, if supplied, must be
  149. in keyword form. For example:
  150. >>> first([1, 1, 3, 4, 5], key=lambda x: x % 2 == 0)
  151. 4
  152. """
  153. if key is None:
  154. for el in iterable:
  155. if el:
  156. return el
  157. else:
  158. for el in iterable:
  159. if key(el):
  160. return el
  161. return default
  162. def is_nonstring_iterable(obj):
  163. """Returns whether the obj is an iterable, but not a string"""
  164. return isinstance(obj, Iterable) and not isinstance(obj, string_types)
  165. def ensure_list(obj):
  166. """
  167. When passed an iterable of objects, does nothing, otherwise, it returns
  168. a list with just that object in it.
  169. """
  170. return obj if is_nonstring_iterable(obj) else [obj]
  171. def current_timestamp():
  172. """Returns current UTC timestamp"""
  173. return calendar.timegm(datetime.datetime.utcnow().utctimetuple())
  174. def enum(name, *sequential, **named):
  175. values = dict(zip(sequential, range(len(sequential))), **named)
  176. # NOTE: Yes, we *really* want to cast using str() here.
  177. # On Python 2 type() requires a byte string (which is str() on Python 2).
  178. # On Python 3 it does not matter, so we'll use str(), which acts as
  179. # a no-op.
  180. return type(str(name), (), values)
  181. def backend_class(holder, default_name, override=None):
  182. """Get a backend class using its default attribute name or an override"""
  183. if override is None:
  184. return getattr(holder, default_name)
  185. elif isinstance(override, string_types):
  186. return import_attribute(override)
  187. else:
  188. return override
  189. def str_to_date(date_str):
  190. if date_str is None:
  191. return
  192. else:
  193. return utcparse(as_text(date_str))
  194. def parse_timeout(timeout):
  195. """Transfer all kinds of timeout format to an integer representing seconds"""
  196. if not isinstance(timeout, numbers.Integral) and timeout is not None:
  197. try:
  198. timeout = int(timeout)
  199. except ValueError:
  200. digit, unit = timeout[:-1], (timeout[-1:]).lower()
  201. unit_second = {'d': 86400, 'h': 3600, 'm': 60, 's': 1}
  202. try:
  203. timeout = int(digit) * unit_second[unit]
  204. except (ValueError, KeyError):
  205. raise TimeoutFormatError('Timeout must be an integer or a string representing an integer, or '
  206. 'a string with format: digits + unit, unit can be "d", "h", "m", "s", '
  207. 'such as "1h", "23m".')
  208. return timeout