1"""Handles mapping between color names and ANSI codes and determining auto color codes.""" 2 3import sys 4try: 5 from collections import Mapping 6except ImportError: 7 from collections.abc import Mapping 8 9BASE_CODES = { 10 '/all': 0, 'b': 1, 'f': 2, 'i': 3, 'u': 4, 'flash': 5, 'outline': 6, 'negative': 7, 'invis': 8, 'strike': 9, 11 '/b': 22, '/f': 22, '/i': 23, '/u': 24, '/flash': 25, '/outline': 26, '/negative': 27, '/invis': 28, 12 '/strike': 29, '/fg': 39, '/bg': 49, 13 14 'black': 30, 'red': 31, 'green': 32, 'yellow': 33, 'blue': 34, 'magenta': 35, 'cyan': 36, 'white': 37, 15 16 'bgblack': 40, 'bgred': 41, 'bggreen': 42, 'bgyellow': 43, 'bgblue': 44, 'bgmagenta': 45, 'bgcyan': 46, 17 'bgwhite': 47, 18 19 'hiblack': 90, 'hired': 91, 'higreen': 92, 'hiyellow': 93, 'hiblue': 94, 'himagenta': 95, 'hicyan': 96, 20 'hiwhite': 97, 21 22 'hibgblack': 100, 'hibgred': 101, 'hibggreen': 102, 'hibgyellow': 103, 'hibgblue': 104, 'hibgmagenta': 105, 23 'hibgcyan': 106, 'hibgwhite': 107, 24 25 'autored': None, 'autoblack': None, 'automagenta': None, 'autowhite': None, 'autoblue': None, 'autoyellow': None, 26 'autogreen': None, 'autocyan': None, 27 28 'autobgred': None, 'autobgblack': None, 'autobgmagenta': None, 'autobgwhite': None, 'autobgblue': None, 29 'autobgyellow': None, 'autobggreen': None, 'autobgcyan': None, 30 31 '/black': 39, '/red': 39, '/green': 39, '/yellow': 39, '/blue': 39, '/magenta': 39, '/cyan': 39, '/white': 39, 32 '/hiblack': 39, '/hired': 39, '/higreen': 39, '/hiyellow': 39, '/hiblue': 39, '/himagenta': 39, '/hicyan': 39, 33 '/hiwhite': 39, 34 35 '/bgblack': 49, '/bgred': 49, '/bggreen': 49, '/bgyellow': 49, '/bgblue': 49, '/bgmagenta': 49, '/bgcyan': 49, 36 '/bgwhite': 49, '/hibgblack': 49, '/hibgred': 49, '/hibggreen': 49, '/hibgyellow': 49, '/hibgblue': 49, 37 '/hibgmagenta': 49, '/hibgcyan': 49, '/hibgwhite': 49, 38 39 '/autored': 39, '/autoblack': 39, '/automagenta': 39, '/autowhite': 39, '/autoblue': 39, '/autoyellow': 39, 40 '/autogreen': 39, '/autocyan': 39, 41 42 '/autobgred': 49, '/autobgblack': 49, '/autobgmagenta': 49, '/autobgwhite': 49, '/autobgblue': 49, 43 '/autobgyellow': 49, '/autobggreen': 49, '/autobgcyan': 49, 44} 45 46 47class ANSICodeMapping(Mapping): 48 """Read-only dictionary, resolves closing tags and automatic colors. Iterates only used color tags. 49 50 :cvar bool DISABLE_COLORS: Disable colors (strip color codes). 51 :cvar bool LIGHT_BACKGROUND: Use low intensity color codes. 52 """ 53 54 DISABLE_COLORS = False 55 LIGHT_BACKGROUND = False 56 57 def __init__(self, value_markup): 58 """Constructor. 59 60 :param str value_markup: String with {color} tags. 61 """ 62 self.whitelist = [k for k in BASE_CODES if '{' + k + '}' in value_markup] 63 64 def __getitem__(self, item): 65 """Return value for key or None if colors are disabled. 66 67 :param str item: Key. 68 69 :return: Color code integer. 70 :rtype: int 71 """ 72 if item not in self.whitelist: 73 raise KeyError(item) 74 if self.DISABLE_COLORS: 75 return None 76 return getattr(self, item, BASE_CODES[item]) 77 78 def __iter__(self): 79 """Iterate dictionary.""" 80 return iter(self.whitelist) 81 82 def __len__(self): 83 """Dictionary length.""" 84 return len(self.whitelist) 85 86 @classmethod 87 def disable_all_colors(cls): 88 """Disable all colors. Strips any color tags or codes.""" 89 cls.DISABLE_COLORS = True 90 91 @classmethod 92 def enable_all_colors(cls): 93 """Enable all colors. Strips any color tags or codes.""" 94 cls.DISABLE_COLORS = False 95 96 @classmethod 97 def disable_if_no_tty(cls): 98 """Disable all colors only if there is no TTY available. 99 100 :return: True if colors are disabled, False if stderr or stdout is a TTY. 101 :rtype: bool 102 """ 103 if sys.stdout.isatty() or sys.stderr.isatty(): 104 return False 105 cls.disable_all_colors() 106 return True 107 108 @classmethod 109 def set_dark_background(cls): 110 """Choose dark colors for all 'auto'-prefixed codes for readability on light backgrounds.""" 111 cls.LIGHT_BACKGROUND = False 112 113 @classmethod 114 def set_light_background(cls): 115 """Choose dark colors for all 'auto'-prefixed codes for readability on light backgrounds.""" 116 cls.LIGHT_BACKGROUND = True 117 118 @property 119 def autoblack(self): 120 """Return automatic black foreground color depending on background color.""" 121 return BASE_CODES['black' if ANSICodeMapping.LIGHT_BACKGROUND else 'hiblack'] 122 123 @property 124 def autored(self): 125 """Return automatic red foreground color depending on background color.""" 126 return BASE_CODES['red' if ANSICodeMapping.LIGHT_BACKGROUND else 'hired'] 127 128 @property 129 def autogreen(self): 130 """Return automatic green foreground color depending on background color.""" 131 return BASE_CODES['green' if ANSICodeMapping.LIGHT_BACKGROUND else 'higreen'] 132 133 @property 134 def autoyellow(self): 135 """Return automatic yellow foreground color depending on background color.""" 136 return BASE_CODES['yellow' if ANSICodeMapping.LIGHT_BACKGROUND else 'hiyellow'] 137 138 @property 139 def autoblue(self): 140 """Return automatic blue foreground color depending on background color.""" 141 return BASE_CODES['blue' if ANSICodeMapping.LIGHT_BACKGROUND else 'hiblue'] 142 143 @property 144 def automagenta(self): 145 """Return automatic magenta foreground color depending on background color.""" 146 return BASE_CODES['magenta' if ANSICodeMapping.LIGHT_BACKGROUND else 'himagenta'] 147 148 @property 149 def autocyan(self): 150 """Return automatic cyan foreground color depending on background color.""" 151 return BASE_CODES['cyan' if ANSICodeMapping.LIGHT_BACKGROUND else 'hicyan'] 152 153 @property 154 def autowhite(self): 155 """Return automatic white foreground color depending on background color.""" 156 return BASE_CODES['white' if ANSICodeMapping.LIGHT_BACKGROUND else 'hiwhite'] 157 158 @property 159 def autobgblack(self): 160 """Return automatic black background color depending on background color.""" 161 return BASE_CODES['bgblack' if ANSICodeMapping.LIGHT_BACKGROUND else 'hibgblack'] 162 163 @property 164 def autobgred(self): 165 """Return automatic red background color depending on background color.""" 166 return BASE_CODES['bgred' if ANSICodeMapping.LIGHT_BACKGROUND else 'hibgred'] 167 168 @property 169 def autobggreen(self): 170 """Return automatic green background color depending on background color.""" 171 return BASE_CODES['bggreen' if ANSICodeMapping.LIGHT_BACKGROUND else 'hibggreen'] 172 173 @property 174 def autobgyellow(self): 175 """Return automatic yellow background color depending on background color.""" 176 return BASE_CODES['bgyellow' if ANSICodeMapping.LIGHT_BACKGROUND else 'hibgyellow'] 177 178 @property 179 def autobgblue(self): 180 """Return automatic blue background color depending on background color.""" 181 return BASE_CODES['bgblue' if ANSICodeMapping.LIGHT_BACKGROUND else 'hibgblue'] 182 183 @property 184 def autobgmagenta(self): 185 """Return automatic magenta background color depending on background color.""" 186 return BASE_CODES['bgmagenta' if ANSICodeMapping.LIGHT_BACKGROUND else 'hibgmagenta'] 187 188 @property 189 def autobgcyan(self): 190 """Return automatic cyan background color depending on background color.""" 191 return BASE_CODES['bgcyan' if ANSICodeMapping.LIGHT_BACKGROUND else 'hibgcyan'] 192 193 @property 194 def autobgwhite(self): 195 """Return automatic white background color depending on background color.""" 196 return BASE_CODES['bgwhite' if ANSICodeMapping.LIGHT_BACKGROUND else 'hibgwhite'] 197 198 199def list_tags(): 200 """List the available tags. 201 202 :return: List of 4-item tuples: opening tag, closing tag, main ansi value, closing ansi value. 203 :rtype: list 204 """ 205 # Build reverse dictionary. Keys are closing tags, values are [closing ansi, opening tag, opening ansi]. 206 reverse_dict = dict() 207 for tag, ansi in sorted(BASE_CODES.items()): 208 if tag.startswith('/'): 209 reverse_dict[tag] = [ansi, None, None] 210 else: 211 reverse_dict['/' + tag][1:] = [tag, ansi] 212 213 # Collapse 214 four_item_tuples = [(v[1], k, v[2], v[0]) for k, v in reverse_dict.items()] 215 216 # Sort. 217 def sorter(four_item): 218 """Sort /all /fg /bg first, then b i u flash, then auto colors, then dark colors, finally light colors. 219 220 :param iter four_item: [opening tag, closing tag, main ansi value, closing ansi value] 221 222 :return Sorting weight. 223 :rtype: int 224 """ 225 if not four_item[2]: # /all /fg /bg 226 return four_item[3] - 200 227 if four_item[2] < 10 or four_item[0].startswith('auto'): # b f i u or auto colors 228 return four_item[2] - 100 229 return four_item[2] 230 four_item_tuples.sort(key=sorter) 231 232 return four_item_tuples 233