1 import sys, re
2
4 """
5 A class that can be used to portably generate formatted output to
6 a terminal.
7
8 `TerminalController` defines a set of instance variables whose
9 values are initialized to the control sequence necessary to
10 perform a given action. These can be simply included in normal
11 output to the terminal:
12
13 >>> term = TerminalController()
14 >>> print 'This is '+term.GREEN+'green'+term.NORMAL
15
16 Alternatively, the `render()` method can used, which replaces
17 '${action}' with the string required to perform 'action':
18
19 >>> term = TerminalController()
20 >>> print term.render('This is ${GREEN}green${NORMAL}')
21
22 If the terminal doesn't support a given action, then the value of
23 the corresponding instance variable will be set to ''. As a
24 result, the above code will still work on terminals that do not
25 support color, except that their output will not be colored.
26 Also, this means that you can test whether the terminal supports a
27 given action by simply testing the truth value of the
28 corresponding instance variable:
29
30 >>> term = TerminalController()
31 >>> if term.CLEAR_SCREEN:
32 ... print 'This terminal supports clearning the screen.'
33
34 Finally, if the width and height of the terminal are known, then
35 they will be stored in the `COLS` and `LINES` attributes.
36 """
37
38 BOL = ''
39 UP = ''
40 DOWN = ''
41 LEFT = ''
42 RIGHT = ''
43
44
45 CLEAR_SCREEN = ''
46 CLEAR_EOL = ''
47 CLEAR_BOL = ''
48 CLEAR_EOS = ''
49
50
51 BOLD = ''
52 BLINK = ''
53 DIM = ''
54 REVERSE = ''
55 NORMAL = ''
56
57
58 HIDE_CURSOR = ''
59 SHOW_CURSOR = ''
60
61
62 COLS = None
63 LINES = None
64
65
66 BLACK = BLUE = GREEN = CYAN = RED = MAGENTA = YELLOW = WHITE = ''
67
68
69 BG_BLACK = BG_BLUE = BG_GREEN = BG_CYAN = ''
70 BG_RED = BG_MAGENTA = BG_YELLOW = BG_WHITE = ''
71
72 _STRING_CAPABILITIES = """
73 BOL=cr UP=cuu1 DOWN=cud1 LEFT=cub1 RIGHT=cuf1
74 CLEAR_SCREEN=clear CLEAR_EOL=el CLEAR_BOL=el1 CLEAR_EOS=ed BOLD=bold
75 BLINK=blink DIM=dim REVERSE=rev UNDERLINE=smul NORMAL=sgr0
76 HIDE_CURSOR=cinvis SHOW_CURSOR=cnorm""".split()
77 _COLORS = """BLACK BLUE GREEN CYAN RED MAGENTA YELLOW WHITE""".split()
78 _ANSICOLORS = "BLACK RED GREEN YELLOW BLUE MAGENTA CYAN WHITE".split()
79
80 - def __init__(self, term_stream=sys.stdout):
81 """
82 Create a `TerminalController` and initialize its attributes
83 with appropriate values for the current terminal.
84 `term_stream` is the stream that will be used for terminal
85 output; if this stream is not a tty, then the terminal is
86 assumed to be a dumb terminal (i.e., have no capabilities).
87 """
88
89 try: import curses
90 except: return
91
92
93 if not term_stream.isatty(): return
94
95
96
97 try: curses.setupterm()
98 except: return
99
100
101 self.COLS = curses.tigetnum('cols')
102 self.LINES = curses.tigetnum('lines')
103
104
105 for capability in self._STRING_CAPABILITIES:
106 (attrib, cap_name) = capability.split('=')
107 setattr(self, attrib, self._tigetstr(cap_name) or '')
108
109
110 set_fg = self._tigetstr('setf')
111 if set_fg:
112 for i,color in zip(range(len(self._COLORS)), self._COLORS):
113 setattr(self, color, curses.tparm(set_fg, i) or '')
114 set_fg_ansi = self._tigetstr('setaf')
115 if set_fg_ansi:
116 for i,color in zip(range(len(self._ANSICOLORS)), self._ANSICOLORS):
117 setattr(self, color, curses.tparm(set_fg_ansi, i) or '')
118 set_bg = self._tigetstr('setb')
119 if set_bg:
120 for i,color in zip(range(len(self._COLORS)), self._COLORS):
121 setattr(self, 'BG_'+color, curses.tparm(set_bg, i) or '')
122 set_bg_ansi = self._tigetstr('setab')
123 if set_bg_ansi:
124 for i,color in zip(range(len(self._ANSICOLORS)), self._ANSICOLORS):
125 setattr(self, 'BG_'+color, curses.tparm(set_bg_ansi, i) or '')
126
128
129
130
131 import curses
132 cap = curses.tigetstr(cap_name) or ''
133 return re.sub(r'\$<\d+>[/*]?', '', cap)
134
136 """
137 Replace each $-substitutions in the given template string with
138 the corresponding terminal control string (if it's defined) or
139 '' (if it's not).
140 """
141 return re.sub(r'\$\$|\${\w+}', self._render_sub, template)
142
144 s = match.group()
145 if s == '$$': return s
146 else: return getattr(self, s[2:-1])
147
148
149
150
151
153 """
154 A 3-line progress bar, which looks like::
155
156 Header
157 20% [===========----------------------------------]
158 progress message
159
160 The progress bar is colored, if the terminal supports color
161 output; and adjusts to the width of the terminal.
162 """
163 BAR = '%3d%% ${GREEN}[${BOLD}%s%s${NORMAL}${GREEN}]${NORMAL}\n'
164 HEADER = '${BOLD}${CYAN}%s${NORMAL}\n\n'
165
167 self.term = term
168 if not (self.term.CLEAR_EOL and self.term.UP and self.term.BOL):
169 raise ValueError("Terminal isn't capable enough -- you "
170 "should use a simpler progress dispaly.")
171 self.width = self.term.COLS or 75
172 self.bar = term.render(self.BAR)
173 self.header = self.term.render(self.HEADER % header.center(self.width))
174 self.cleared = 1
175 self.update(0, '')
176
177 - def update(self, percent, message):
178 if self.cleared:
179 sys.stdout.write(self.header)
180 self.cleared = 0
181 n = int((self.width-10)*percent)
182 sys.stdout.write(
183 self.term.BOL + self.term.UP + self.term.CLEAR_EOL +
184 (self.bar % (100*percent, '='*n, '-'*(self.width-10-n))) +
185 self.term.CLEAR_EOL + message.center(self.width))
186
188 if not self.cleared:
189 sys.stdout.write(self.term.BOL + self.term.CLEAR_EOL +
190 self.term.UP + self.term.CLEAR_EOL +
191 self.term.UP + self.term.CLEAR_EOL)
192 self.cleared = 1
193