1 # Copyright 1999-2009 Gentoo Foundation
2 # Distributed under the terms of the GNU General Public License v2
9 from portage import StringIO
10 from portage import os
11 from portage import _encodings
12 from portage import _unicode_decode
13 from portage import _unicode_encode
14 from portage.output import xtermTitle
16 from _emerge.getloadavg import getloadavg
18 if sys.hexversion >= 0x3000000:
21 class JobStatusDisplay(object):
23 _bound_properties = ("curval", "failed", "running")
25 # Don't update the display unless at least this much
26 # time has passed, in units of seconds.
27 _min_display_latency = 2
29 _default_term_codes = {
36 'carriage_return' : 'cr',
41 def __init__(self, quiet=False, xterm_titles=True):
42 object.__setattr__(self, "quiet", quiet)
43 object.__setattr__(self, "xterm_titles", xterm_titles)
44 object.__setattr__(self, "maxval", 0)
45 object.__setattr__(self, "merges", 0)
46 object.__setattr__(self, "_changed", False)
47 object.__setattr__(self, "_displayed", False)
48 object.__setattr__(self, "_last_display_time", 0)
50 width = portage.output.get_term_size()[1]
51 if width <= 0 or width > 80:
53 object.__setattr__(self, "width", width)
54 object.__setattr__(self, "_jobs_column_width", width - 32)
57 isatty = os.environ.get('TERM') != 'dumb' and \
58 hasattr(self.out, 'isatty') and \
60 object.__setattr__(self, "_isatty", isatty)
61 if not isatty or not self._init_term():
63 for k, capname in self._termcap_name_map.items():
64 term_codes[k] = self._default_term_codes[capname]
65 object.__setattr__(self, "_term_codes", term_codes)
66 encoding = sys.getdefaultencoding()
67 for k, v in self._term_codes.items():
68 if not isinstance(v, basestring):
69 self._term_codes[k] = v.decode(encoding, 'replace')
73 """Use a lazy reference to sys.stdout, in case the API consumer has
74 temporarily overridden stdout."""
78 # avoid potential UnicodeEncodeError
79 s = _unicode_encode(s,
80 encoding=_encodings['stdio'], errors='backslashreplace')
82 if sys.hexversion >= 0x3000000:
89 Initialize term control codes.
91 @returns: True if term codes were successfully initialized,
95 term_type = os.environ.get("TERM", "").strip()
103 curses.setupterm(term_type, self.out.fileno())
104 tigetstr = curses.tigetstr
114 for k, capname in self._termcap_name_map.items():
115 code = tigetstr(capname)
117 code = self._default_term_codes[capname]
119 object.__setattr__(self, "_term_codes", term_codes)
122 def _format_msg(self, msg):
123 return ">>> %s" % msg
127 self._term_codes['carriage_return'] + \
128 self._term_codes['clr_eol'])
129 self._displayed = False
131 def _display(self, line):
133 self._displayed = True
135 def _update(self, msg):
138 self._write(self._format_msg(msg) + self._term_codes['newline'])
139 self._displayed = True
145 self._display(self._format_msg(msg))
147 def displayMessage(self, msg):
149 was_displayed = self._displayed
151 if self._isatty and self._displayed:
154 self._write(self._format_msg(msg) + self._term_codes['newline'])
155 self._displayed = False
164 for name in self._bound_properties:
165 object.__setattr__(self, name, 0)
168 self._write(self._term_codes['newline'])
169 self._displayed = False
171 def __setattr__(self, name, value):
172 old_value = getattr(self, name)
173 if value == old_value:
175 object.__setattr__(self, name, value)
176 if name in self._bound_properties:
177 self._property_change(name, old_value, value)
179 def _property_change(self, name, old_value, new_value):
183 def _load_avg_str(self):
198 return ", ".join(("%%.%df" % digits ) % x for x in avg)
202 Display status on stdout, but only if something has
203 changed since the last call.
209 current_time = time.time()
210 time_delta = current_time - self._last_display_time
211 if self._displayed and \
215 if time_delta < self._min_display_latency:
218 self._last_display_time = current_time
219 self._changed = False
220 self._display_status()
222 def _display_status(self):
223 # Don't use len(self._completed_tasks) here since that also
224 # can include uninstall tasks.
225 curval_str = str(self.curval)
226 maxval_str = str(self.maxval)
227 running_str = str(self.running)
228 failed_str = str(self.failed)
229 load_avg_str = self._load_avg_str()
231 color_output = StringIO()
232 plain_output = StringIO()
233 style_file = portage.output.ConsoleStyleFile(color_output)
234 style_file.write_listener = plain_output
235 style_writer = portage.output.StyleWriter(file=style_file, maxcol=9999)
236 style_writer.style_listener = style_file.new_styles
237 f = formatter.AbstractFormatter(style_writer)
239 number_style = "INFORM"
240 f.add_literal_data(_unicode_decode("Jobs: "))
241 f.push_style(number_style)
242 f.add_literal_data(_unicode_decode(curval_str))
244 f.add_literal_data(_unicode_decode(" of "))
245 f.push_style(number_style)
246 f.add_literal_data(_unicode_decode(maxval_str))
248 f.add_literal_data(_unicode_decode(" complete"))
251 f.add_literal_data(_unicode_decode(", "))
252 f.push_style(number_style)
253 f.add_literal_data(_unicode_decode(running_str))
255 f.add_literal_data(_unicode_decode(" running"))
258 f.add_literal_data(_unicode_decode(", "))
259 f.push_style(number_style)
260 f.add_literal_data(_unicode_decode(failed_str))
262 f.add_literal_data(_unicode_decode(" failed"))
264 padding = self._jobs_column_width - len(plain_output.getvalue())
266 f.add_literal_data(padding * _unicode_decode(" "))
268 f.add_literal_data(_unicode_decode("Load avg: "))
269 f.add_literal_data(_unicode_decode(load_avg_str))
271 # Truncate to fit width, to avoid making the terminal scroll if the
272 # line overflows (happens when the load average is large).
273 plain_output = plain_output.getvalue()
274 if self._isatty and len(plain_output) > self.width:
275 # Use plain_output here since it's easier to truncate
276 # properly than the color output which contains console
278 self._update(plain_output[:self.width])
280 self._update(color_output.getvalue())
282 if self.xterm_titles:
283 xtermTitle(" ".join(plain_output.split()))