##// END OF EJS Templates
Add missing import.
walter.doerwald -
Show More
@@ -1,390 +1,390 b''
1 1 """
2 2 ``astyle`` provides classes for adding style (foreground and background color;
3 3 bold; blink; etc.) to terminal and curses output.
4 4 """
5 5
6 6
7 import os
7 import sys, os
8 8
9 9 try:
10 10 import curses
11 11 except ImportError:
12 12 curses = None
13 13
14 14
15 15 COLOR_BLACK = 0
16 16 COLOR_RED = 1
17 17 COLOR_GREEN = 2
18 18 COLOR_YELLOW = 3
19 19 COLOR_BLUE = 4
20 20 COLOR_MAGENTA = 5
21 21 COLOR_CYAN = 6
22 22 COLOR_WHITE = 7
23 23
24 24 A_BLINK = 1<<0 # Blinking text
25 25 A_BOLD = 1<<1 # Extra bright or bold text
26 26 A_DIM = 1<<2 # Half bright text
27 27 A_REVERSE = 1<<3 # Reverse-video text
28 28 A_STANDOUT = 1<<4 # The best highlighting mode available
29 29 A_UNDERLINE = 1<<5 # Underlined text
30 30
31 31
32 32 class Style(object):
33 33 """
34 34 Store foreground color, background color and attribute (bold, underlined
35 35 etc.).
36 36 """
37 37 __slots__ = ("fg", "bg", "attrs")
38 38
39 39 COLORNAMES = {
40 40 "black": COLOR_BLACK,
41 41 "red": COLOR_RED,
42 42 "green": COLOR_GREEN,
43 43 "yellow": COLOR_YELLOW,
44 44 "blue": COLOR_BLUE,
45 45 "magenta": COLOR_MAGENTA,
46 46 "cyan": COLOR_CYAN,
47 47 "white": COLOR_WHITE,
48 48 }
49 49 ATTRNAMES = {
50 50 "blink": A_BLINK,
51 51 "bold": A_BOLD,
52 52 "dim": A_DIM,
53 53 "reverse": A_REVERSE,
54 54 "standout": A_STANDOUT,
55 55 "underline": A_UNDERLINE,
56 56 }
57 57
58 58 def __init__(self, fg, bg, attrs=0):
59 59 """
60 60 Create a ``Style`` object with ``fg`` as the foreground color,
61 61 ``bg`` as the background color and ``attrs`` as the attributes.
62 62
63 63 Examples:
64 64
65 65 >>> Style(COLOR_RED, COLOR_BLACK)
66 66 >>> Style(COLOR_YELLOW, COLOR_BLUE, A_BOLD|A_UNDERLINE)
67 67 """
68 68 self.fg = fg
69 69 self.bg = bg
70 70 self.attrs = attrs
71 71
72 72 def __call__(self, *args):
73 73 text = Text()
74 74 for arg in args:
75 75 if isinstance(arg, Text):
76 76 text.extend(arg)
77 77 else:
78 78 text.append((self, arg))
79 79 return text
80 80
81 81 def __eq__(self, other):
82 82 return self.fg == other.fg and self.bg == other.bg and self.attrs == other.attrs
83 83
84 84 def __neq__(self, other):
85 85 return self.fg != other.fg or self.bg != other.bg or self.attrs != other.attrs
86 86
87 87 def __repr__(self):
88 88 color2name = ("black", "red", "green", "yellow", "blue", "magenta", "cyan", "white")
89 89 attrs2name = ("blink", "bold", "dim", "reverse", "standout", "underline")
90 90
91 91 return "<%s fg=%s bg=%s attrs=%s>" % (
92 92 self.__class__.__name__, color2name[self.fg], color2name[self.bg],
93 93 "|".join([attrs2name[b] for b in xrange(6) if self.attrs&(1<<b)]) or 0)
94 94
95 95 def fromstr(cls, value):
96 96 """
97 97 Create a ``Style`` object from a string. The format looks like this:
98 98 ``"red:black:bold|blink"``.
99 99 """
100 100 # defaults
101 101 fg = COLOR_WHITE
102 102 bg = COLOR_BLACK
103 103 attrs = 0
104 104
105 105 parts = value.split(":")
106 106 if len(parts) > 0:
107 107 fg = cls.COLORNAMES[parts[0].lower()]
108 108 if len(parts) > 1:
109 109 bg = cls.COLORNAMES[parts[1].lower()]
110 110 if len(parts) > 2:
111 111 for strattr in parts[2].split("|"):
112 112 attrs |= cls.ATTRNAMES[strattr.lower()]
113 113 return cls(fg, bg, attrs)
114 114 fromstr = classmethod(fromstr)
115 115
116 116 def fromenv(cls, name, default):
117 117 """
118 118 Create a ``Style`` from an environment variable named ``name``
119 119 (using ``default`` if the environment variable doesn't exist).
120 120 """
121 121 return cls.fromstr(os.environ.get(name, default))
122 122 fromenv = classmethod(fromenv)
123 123
124 124
125 125 def switchstyle(s1, s2):
126 126 """
127 127 Return the ANSI escape sequence needed to switch from style ``s1`` to
128 128 style ``s2``.
129 129 """
130 130 attrmask = (A_BLINK|A_BOLD|A_UNDERLINE|A_REVERSE)
131 131 a1 = s1.attrs & attrmask
132 132 a2 = s2.attrs & attrmask
133 133
134 134 args = []
135 135 if s1 != s2:
136 136 # do we have to get rid of the bold/underline/blink bit?
137 137 # (can only be done by a reset)
138 138 # use reset when our target color is the default color
139 139 # (this is shorter than 37;40)
140 140 if (a1 & ~a2 or s2==style_default):
141 141 args.append("0")
142 142 s1 = style_default
143 143 a1 = 0
144 144
145 145 # now we know that old and new color have the same boldness,
146 146 # or the new color is bold and the old isn't,
147 147 # i.e. we only might have to switch bold on, not off
148 148 if not (a1 & A_BOLD) and (a2 & A_BOLD):
149 149 args.append("1")
150 150
151 151 # Fix underline
152 152 if not (a1 & A_UNDERLINE) and (a2 & A_UNDERLINE):
153 153 args.append("4")
154 154
155 155 # Fix blink
156 156 if not (a1 & A_BLINK) and (a2 & A_BLINK):
157 157 args.append("5")
158 158
159 159 # Fix reverse
160 160 if not (a1 & A_REVERSE) and (a2 & A_REVERSE):
161 161 args.append("7")
162 162
163 163 # Fix foreground color
164 164 if s1.fg != s2.fg:
165 165 args.append("3%d" % s2.fg)
166 166
167 167 # Finally fix the background color
168 168 if s1.bg != s2.bg:
169 169 args.append("4%d" % s2.bg)
170 170
171 171 if args:
172 172 return "\033[%sm" % ";".join(args)
173 173 return ""
174 174
175 175
176 176 class Text(list):
177 177 """
178 178 A colored string. A ``Text`` object is a sequence, the sequence
179 179 items will be ``(style, string)`` tuples.
180 180 """
181 181
182 182 def __init__(self, *args):
183 183 list.__init__(self)
184 184 self.append(*args)
185 185
186 186 def __repr__(self):
187 187 return "%s.%s(%s)" % (
188 188 self.__class__.__module__, self.__class__.__name__,
189 189 list.__repr__(self)[1:-1])
190 190
191 191 def append(self, *args):
192 192 for arg in args:
193 193 if isinstance(arg, Text):
194 194 self.extend(arg)
195 195 elif isinstance(arg, tuple): # must be (style, string)
196 196 list.append(self, arg)
197 197 elif isinstance(arg, unicode):
198 198 list.append(self, (style_default, arg))
199 199 else:
200 200 list.append(self, (style_default, str(arg)))
201 201
202 202 def insert(self, index, *args):
203 203 self[index:index] = Text(*args)
204 204
205 205 def __add__(self, other):
206 206 new = Text()
207 207 new.append(self)
208 208 new.append(other)
209 209 return new
210 210
211 211 def __iadd__(self, other):
212 212 self.append(other)
213 213 return self
214 214
215 215 def format(self, styled=True):
216 216 """
217 217 This generator yields the strings that will make up the final
218 218 colorized string.
219 219 """
220 220 if styled:
221 221 oldstyle = style_default
222 222 for (style, string) in self:
223 223 if not isinstance(style, (int, long)):
224 224 switch = switchstyle(oldstyle, style)
225 225 if switch:
226 226 yield switch
227 227 if string:
228 228 yield string
229 229 oldstyle = style
230 230 switch = switchstyle(oldstyle, style_default)
231 231 if switch:
232 232 yield switch
233 233 else:
234 234 for (style, string) in self:
235 235 if not isinstance(style, (int, long)):
236 236 yield string
237 237
238 238 def string(self, styled=True):
239 239 """
240 240 Return the resulting string (with escape sequences, if ``styled``
241 241 is true).
242 242 """
243 243 return "".join(self.format(styled))
244 244
245 245 def __str__(self):
246 246 """
247 247 Return ``self`` as a string (without ANSI escape sequences).
248 248 """
249 249 return self.string(False)
250 250
251 251 def write(self, stream, styled=True):
252 252 """
253 253 Write ``self`` to the output stream ``stream`` (with escape sequences,
254 254 if ``styled`` is true).
255 255 """
256 256 for part in self.format(styled):
257 257 stream.write(part)
258 258
259 259 def __xrepr__(self, mode="default"):
260 260 yield (-1, True)
261 261 for info in self:
262 262 yield info
263 263
264 264
265 265 def streamstyle(stream, styled=None):
266 266 """
267 267 If ``styled`` is ``None``, return whether ``stream`` refers to a terminal.
268 268 If this can't be determined (either because ``stream`` doesn't refer to a
269 269 real OS file, or because you're on Windows) return ``False``. If ``styled``
270 270 is not ``None`` ``styled`` will be returned unchanged.
271 271 """
272 272 if styled is None:
273 273 try:
274 274 styled = os.isatty(stream.fileno())
275 275 except (KeyboardInterrupt, SystemExit):
276 276 raise
277 277 except Exception:
278 278 styled = False
279 279 return styled
280 280
281 281
282 282 def write(stream, styled, *texts):
283 283 """
284 284 Write ``texts`` to ``stream``.
285 285 """
286 286 text = Text(*texts)
287 287 text.write(stream, streamstyle(stream, styled))
288 288
289 289
290 290 def writeln(stream, styled, *texts):
291 291 """
292 292 Write ``texts`` to ``stream`` and finish with a line feed.
293 293 """
294 294 write(stream, styled, *texts)
295 295 stream.write("\n")
296 296
297 297
298 298 class Stream(object):
299 299 """
300 300 Stream wrapper that adds color output.
301 301 """
302 302 def __init__(self, stream, styled=None):
303 303 self.stream = stream
304 304 self.styled = streamstyle(stream, styled)
305 305
306 306 def write(self, *texts):
307 307 write(self.stream, self.styled, *texts)
308 308
309 309 def writeln(self, *texts):
310 310 writeln(self.stream, self.styled, *texts)
311 311
312 312 def __getattr__(self, name):
313 313 return getattr(self.stream, name)
314 314
315 315
316 316 class stdout(object):
317 317 """
318 318 Stream wrapper for ``sys.stdout`` that adds color output.
319 319 """
320 320 def write(self, *texts):
321 321 write(sys.stdout, None, *texts)
322 322
323 323 def writeln(self, *texts):
324 324 writeln(sys.stdout, None, *texts)
325 325
326 326 def __getattr__(self, name):
327 327 return getattr(sys.stdout, name)
328 328 stdout = stdout()
329 329
330 330
331 331 class stderr(object):
332 332 """
333 333 Stream wrapper for ``sys.stderr`` that adds color output.
334 334 """
335 335 def write(self, *texts):
336 336 write(sys.stderr, None, *texts)
337 337
338 338 def writeln(self, *texts):
339 339 writeln(sys.stderr, None, *texts)
340 340
341 341 def __getattr__(self, name):
342 342 return getattr(sys.stdout, name)
343 343 stderr = stderr()
344 344
345 345
346 346 if curses is not None:
347 347 # This is probably just range(8)
348 348 COLOR2CURSES = [
349 349 COLOR_BLACK,
350 350 COLOR_RED,
351 351 COLOR_GREEN,
352 352 COLOR_YELLOW,
353 353 COLOR_BLUE,
354 354 COLOR_MAGENTA,
355 355 COLOR_CYAN,
356 356 COLOR_WHITE,
357 357 ]
358 358
359 359 A2CURSES = {
360 360 A_BLINK: curses.A_BLINK,
361 361 A_BOLD: curses.A_BOLD,
362 362 A_DIM: curses.A_DIM,
363 363 A_REVERSE: curses.A_REVERSE,
364 364 A_STANDOUT: curses.A_STANDOUT,
365 365 A_UNDERLINE: curses.A_UNDERLINE,
366 366 }
367 367
368 368
369 369 # default style
370 370 style_default = Style.fromstr("white:black")
371 371
372 372 # Styles for datatypes
373 373 style_type_none = Style.fromstr("magenta:black")
374 374 style_type_bool = Style.fromstr("magenta:black")
375 375 style_type_number = Style.fromstr("yellow:black")
376 376 style_type_datetime = Style.fromstr("magenta:black")
377 377
378 378 # Style for URLs and file/directory names
379 379 style_url = Style.fromstr("green:black")
380 380 style_dir = Style.fromstr("cyan:black")
381 381 style_file = Style.fromstr("green:black")
382 382
383 383 # Style for ellipsis (when an output has been shortened
384 384 style_ellisis = Style.fromstr("red:black")
385 385
386 386 # Style for displaying exceptions
387 387 style_error = Style.fromstr("red:black")
388 388
389 389 # Style for displaying non-existing attributes
390 390 style_nodata = Style.fromstr("red:black")
General Comments 0
You need to be logged in to leave comments. Login now