textformat.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. # -*- coding: utf-8 -*-
  2. '''
  3. ANSI escape code utilities, see
  4. http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-048.pdf
  5. '''
  6. from __future__ import absolute_import, print_function, unicode_literals
  7. # Import 3rd-party libs
  8. from salt.ext import six
  9. graph_prefix = '\x1b['
  10. graph_suffix = 'm'
  11. codes = {
  12. 'reset': '0',
  13. 'bold': '1',
  14. 'faint': '2',
  15. 'italic': '3',
  16. 'underline': '4',
  17. 'blink': '5',
  18. 'slow_blink': '5',
  19. 'fast_blink': '6',
  20. 'inverse': '7',
  21. 'conceal': '8',
  22. 'strike': '9',
  23. 'primary_font': '10',
  24. 'reset_font': '10',
  25. 'font_0': '10',
  26. 'font_1': '11',
  27. 'font_2': '12',
  28. 'font_3': '13',
  29. 'font_4': '14',
  30. 'font_5': '15',
  31. 'font_6': '16',
  32. 'font_7': '17',
  33. 'font_8': '18',
  34. 'font_9': '19',
  35. 'fraktur': '20',
  36. 'double_underline': '21',
  37. 'end_bold': '21',
  38. 'normal_intensity': '22',
  39. 'end_italic': '23',
  40. 'end_fraktur': '23',
  41. 'end_underline': '24', # single or double
  42. 'end_blink': '25',
  43. 'end_inverse': '27',
  44. 'end_conceal': '28',
  45. 'end_strike': '29',
  46. 'black': '30',
  47. 'red': '31',
  48. 'green': '32',
  49. 'yellow': '33',
  50. 'blue': '34',
  51. 'magenta': '35',
  52. 'cyan': '36',
  53. 'white': '37',
  54. 'extended': '38',
  55. 'default': '39',
  56. 'fg_black': '30',
  57. 'fg_red': '31',
  58. 'fg_green': '32',
  59. 'fg_yellow': '33',
  60. 'fg_blue': '34',
  61. 'fg_magenta': '35',
  62. 'fg_cyan': '36',
  63. 'fg_white': '37',
  64. 'fg_extended': '38',
  65. 'fg_default': '39',
  66. 'bg_black': '40',
  67. 'bg_red': '41',
  68. 'bg_green': '42',
  69. 'bg_yellow': '44',
  70. 'bg_blue': '44',
  71. 'bg_magenta': '45',
  72. 'bg_cyan': '46',
  73. 'bg_white': '47',
  74. 'bg_extended': '48',
  75. 'bg_default': '49',
  76. 'frame': '51',
  77. 'encircle': '52',
  78. 'overline': '53',
  79. 'end_frame': '54',
  80. 'end_encircle': '54',
  81. 'end_overline': '55',
  82. 'ideogram_underline': '60',
  83. 'right_line': '60',
  84. 'ideogram_double_underline': '61',
  85. 'right_double_line': '61',
  86. 'ideogram_overline': '62',
  87. 'left_line': '62',
  88. 'ideogram_double_overline': '63',
  89. 'left_double_line': '63',
  90. 'ideogram_stress': '64',
  91. 'reset_ideogram': '65'
  92. }
  93. class TextFormat(object):
  94. '''
  95. ANSI Select Graphic Rendition (SGR) code escape sequence.
  96. '''
  97. def __init__(self, *attrs, **kwargs):
  98. '''
  99. :param attrs: are the attribute names of any format codes in `codes`
  100. :param kwargs: may contain
  101. `x`, an integer in the range [0-255] that selects the corresponding
  102. color from the extended ANSI 256 color space for foreground text
  103. `rgb`, an iterable of 3 integers in the range [0-255] that select the
  104. corresponding colors from the extended ANSI 256^3 color space for
  105. foreground text
  106. `bg_x`, an integer in the range [0-255] that selects the corresponding
  107. color from the extended ANSI 256 color space for background text
  108. `bg_rgb`, an iterable of 3 integers in the range [0-255] that select
  109. the corresponding colors from the extended ANSI 256^3 color space for
  110. background text
  111. `reset`, prepend reset SGR code to sequence (default `True`)
  112. Examples:
  113. .. code-block:: python
  114. red_underlined = TextFormat('red', 'underline')
  115. nuanced_text = TextFormat(x=29, bg_x=71)
  116. magenta_on_green = TextFormat('magenta', 'bg_green')
  117. print(
  118. '{0}Can you read this?{1}'
  119. ).format(magenta_on_green, TextFormat('reset'))
  120. '''
  121. self.codes = [codes[attr.lower()] for attr in attrs if isinstance(attr, six.string_types)]
  122. if kwargs.get('reset', True):
  123. self.codes[:0] = [codes['reset']]
  124. def qualify_int(i):
  125. if isinstance(i, int):
  126. return i % 256 # set i to base element of its equivalence class
  127. def qualify_triple_int(t):
  128. if isinstance(t, (list, tuple)) and len(t) == 3:
  129. return qualify_int(t[0]), qualify_int(t[1]), qualify_int(t[2])
  130. if kwargs.get('x', None) is not None:
  131. self.codes.extend((codes['extended'], '5', qualify_int(kwargs['x'])))
  132. elif kwargs.get('rgb', None) is not None:
  133. self.codes.extend((codes['extended'], '2'))
  134. self.codes.extend(*qualify_triple_int(kwargs['rgb']))
  135. if kwargs.get('bg_x', None) is not None:
  136. self.codes.extend((codes['extended'], '5', qualify_int(kwargs['bg_x'])))
  137. elif kwargs.get('bg_rgb', None) is not None:
  138. self.codes.extend((codes['extended'], '2'))
  139. self.codes.extend(*qualify_triple_int(kwargs['bg_rgb']))
  140. self.sequence = '%s%s%s' % (graph_prefix, # pylint: disable=E1321
  141. ';'.join(self.codes),
  142. graph_suffix)
  143. def __call__(self, text, reset=True):
  144. '''
  145. Format :param text: by prefixing `self.sequence` and suffixing the
  146. reset sequence if :param reset: is `True`.
  147. Examples:
  148. .. code-block:: python
  149. green_blink_text = TextFormat('blink', 'green')
  150. 'The answer is: {0}'.format(green_blink_text(42))
  151. '''
  152. end = TextFormat('reset') if reset else ''
  153. return '%s%s%s' % (self.sequence, text, end) # pylint: disable=E1321
  154. def __str__(self):
  155. return self.sequence
  156. def __repr__(self):
  157. return self.sequence