1 # Copyright 1999-2009 Gentoo Foundation
2 # Distributed under the terms of the GNU General Public License v2
9 from io import StringIO
11 # Needed for python-2.6 with USE=build since
12 # io imports threading which imports thread
13 # which is unavailable.
14 from StringIO import StringIO
17 from portage import os
18 from portage import _encodings
19 from portage import _unicode_decode
20 from portage.output import xtermTitle
22 from _emerge.getloadavg import getloadavg
24 if sys.hexversion >= 0x3000000:
27 class JobStatusDisplay(object):
29 _bound_properties = ("curval", "failed", "running")
30 _jobs_column_width = 48
32 # Don't update the display unless at least this much
33 # time has passed, in units of seconds.
34 _min_display_latency = 2
36 _default_term_codes = {
43 'carriage_return' : 'cr',
48 def __init__(self, quiet=False, xterm_titles=True):
49 object.__setattr__(self, "quiet", quiet)
50 object.__setattr__(self, "xterm_titles", xterm_titles)
51 object.__setattr__(self, "maxval", 0)
52 object.__setattr__(self, "merges", 0)
53 object.__setattr__(self, "_changed", False)
54 object.__setattr__(self, "_displayed", False)
55 object.__setattr__(self, "_last_display_time", 0)
56 object.__setattr__(self, "width", 80)
59 isatty = hasattr(self.out, "isatty") and self.out.isatty()
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 if sys.hexversion < 0x3000000 and isinstance(s, unicode):
79 # avoid potential UnicodeEncodeError
80 s = s.encode(_encodings['stdio'], 'backslashreplace')
86 Initialize term control codes.
88 @returns: True if term codes were successfully initialized,
92 term_type = os.environ.get("TERM", "vt100")
98 curses.setupterm(term_type, self.out.fileno())
99 tigetstr = curses.tigetstr
109 for k, capname in self._termcap_name_map.items():
110 code = tigetstr(capname)
112 code = self._default_term_codes[capname]
114 object.__setattr__(self, "_term_codes", term_codes)
117 def _format_msg(self, msg):
118 return ">>> %s" % msg
122 self._term_codes['carriage_return'] + \
123 self._term_codes['clr_eol'])
124 self._displayed = False
126 def _display(self, line):
128 self._displayed = True
130 def _update(self, msg):
133 self._write(self._format_msg(msg) + self._term_codes['newline'])
134 self._displayed = True
140 self._display(self._format_msg(msg))
142 def displayMessage(self, msg):
144 was_displayed = self._displayed
146 if self._isatty and self._displayed:
149 self._write(self._format_msg(msg) + self._term_codes['newline'])
150 self._displayed = False
159 for name in self._bound_properties:
160 object.__setattr__(self, name, 0)
163 self._write(self._term_codes['newline'])
164 self._displayed = False
166 def __setattr__(self, name, value):
167 old_value = getattr(self, name)
168 if value == old_value:
170 object.__setattr__(self, name, value)
171 if name in self._bound_properties:
172 self._property_change(name, old_value, value)
174 def _property_change(self, name, old_value, new_value):
178 def _load_avg_str(self):
193 return ", ".join(("%%.%df" % digits ) % x for x in avg)
197 Display status on stdout, but only if something has
198 changed since the last call.
204 current_time = time.time()
205 time_delta = current_time - self._last_display_time
206 if self._displayed and \
210 if time_delta < self._min_display_latency:
213 self._last_display_time = current_time
214 self._changed = False
215 self._display_status()
217 def _display_status(self):
218 # Don't use len(self._completed_tasks) here since that also
219 # can include uninstall tasks.
220 curval_str = str(self.curval)
221 maxval_str = str(self.maxval)
222 running_str = str(self.running)
223 failed_str = str(self.failed)
224 load_avg_str = self._load_avg_str()
226 color_output = StringIO()
227 plain_output = StringIO()
228 style_file = portage.output.ConsoleStyleFile(color_output)
229 style_file.write_listener = plain_output
230 style_writer = portage.output.StyleWriter(file=style_file, maxcol=9999)
231 style_writer.style_listener = style_file.new_styles
232 f = formatter.AbstractFormatter(style_writer)
234 number_style = "INFORM"
235 f.add_literal_data(_unicode_decode("Jobs: "))
236 f.push_style(number_style)
237 f.add_literal_data(_unicode_decode(curval_str))
239 f.add_literal_data(_unicode_decode(" of "))
240 f.push_style(number_style)
241 f.add_literal_data(_unicode_decode(maxval_str))
243 f.add_literal_data(_unicode_decode(" complete"))
246 f.add_literal_data(_unicode_decode(", "))
247 f.push_style(number_style)
248 f.add_literal_data(_unicode_decode(running_str))
250 f.add_literal_data(_unicode_decode(" running"))
253 f.add_literal_data(_unicode_decode(", "))
254 f.push_style(number_style)
255 f.add_literal_data(_unicode_decode(failed_str))
257 f.add_literal_data(_unicode_decode(" failed"))
259 padding = self._jobs_column_width - len(plain_output.getvalue())
261 f.add_literal_data(padding * _unicode_decode(" "))
263 f.add_literal_data(_unicode_decode("Load avg: "))
264 f.add_literal_data(_unicode_decode(load_avg_str))
266 # Truncate to fit width, to avoid making the terminal scroll if the
267 # line overflows (happens when the load average is large).
268 plain_output = plain_output.getvalue()
269 if self._isatty and len(plain_output) > self.width:
270 # Use plain_output here since it's easier to truncate
271 # properly than the color output which contains console
273 self._update(plain_output[:self.width])
275 self._update(color_output.getvalue())
277 if self.xterm_titles:
278 xtermTitle(" ".join(plain_output.split()))