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