Add support to JobStatusDisplay() to update the current line on the terminal,
authorZac Medico <zmedico@gentoo.org>
Sat, 19 Jul 2008 03:18:21 +0000 (03:18 -0000)
committerZac Medico <zmedico@gentoo.org>
Sat, 19 Jul 2008 03:18:21 +0000 (03:18 -0000)
which in the future can be used to erase the line and update it with new
information such as the latest load average measurement. This uses curses
to obtain the simple control codes that are needed, and has fallback codes
in case the curses module is unavailable or raises an error. When stdout
is not a tty then the line erasing behavior is automatically disabled and
display updates are simply show on a new line.

svn path=/main/trunk/; revision=11128

pym/_emerge/__init__.py

index bfc5767e9b4cc9eb9eb02329b0c96497b5ef4a61..6ebdb6ff77167a5a7ea99dbe75a883e5680c0243 100644 (file)
@@ -8388,19 +8388,107 @@ class JobStatusDisplay(object):
        _bound_properties = ("curval", "running")
        _jobs_column_width = 45
 
-       def __init__(self, quiet=False):
+       _default_term_codes = {
+               'cr'  : '\r',
+               'el'  : '\x1b[K',
+               'nel' : '\n',
+       }
+
+       _termcap_name_map = {
+               'carriage_return' : 'cr',
+               'clr_eol'         : 'el',
+               'newline'         : 'nel',
+       }
+
+       def __init__(self, out=sys.stdout, quiet=False):
+               object.__setattr__(self, "out", out)
                object.__setattr__(self, "quiet", quiet)
                object.__setattr__(self, "maxval", 0)
                object.__setattr__(self, "merges", 0)
                object.__setattr__(self, "_changed", False)
+               object.__setattr__(self, "_displayed", False)
                self.reset()
 
+               isatty = hasattr(out, "isatty") and out.isatty()
+               object.__setattr__(self, "_isatty", isatty)
+               if isatty:
+                       self._init_term()
+               else:
+                       term_codes = {}
+                       for k, capname in self._termcap_name_map.iteritems():
+                               term_codes[k] = self._default_term_codes[capname]
+                       object.__setattr__(self, "_term_codes", term_codes)
+
+       def _init_term(self):
+               """
+               Initialize term control codes.
+               """
+
+               term_type = os.environ.get("TERM", "vt100")
+               tigetstr = None
+
+               try:
+                       import curses
+                       try:
+                               curses.setupterm(term_type, self.out.fileno())
+                               tigetstr = curses.tigetstr
+                       except curses.error:
+                               pass
+               except ImportError:
+                       pass
+
+               if tigetstr is None:
+                       def tigetstr(capname):
+                               return self._default_term_codes[capname]
+
+               term_codes = {}
+               for k, capname in self._termcap_name_map.iteritems():
+                       term_codes[k] = tigetstr(capname)
+               object.__setattr__(self, "_term_codes", term_codes)
+
+       def _format_msg(self, msg):
+               return ">>> %s" % msg
+
+       def _erase(self):
+               self.out.write(
+                       self._term_codes['carriage_return'] + \
+                       self._term_codes['clr_eol'])
+               self._displayed = False
+
+       def _display(self, line):
+               self.out.write(line)
+               self._displayed = True
+
+       def _update(self, msg):
+
+               out = self.out
+               if not self._isatty:
+                       out.write(self._format_msg(msg) + self._term_codes['newline'])
+                       return
+
+               if self._displayed:
+                       self._erase()
+
+               self._display(self._format_msg(msg))
+
+       def displayMessage(self, msg):
+
+               if self._isatty and self._displayed:
+                       self._erase()
+
+               self.out.write(self._format_msg(msg) + self._term_codes['newline'])
+               self._displayed = False
+
        def reset(self):
                self.maxval = 0
                self.merges = 0
                for name in self._bound_properties:
                        object.__setattr__(self, name, 0)
 
+               if self._displayed:
+                       self.out.write(self._term_codes['newline'])
+                       self._displayed = False
+
        def __setattr__(self, name, value):
                old_value = getattr(self, name)
                if value == old_value:
@@ -8482,8 +8570,7 @@ class JobStatusDisplay(object):
                f.add_literal_data("Load average: ")
                f.add_literal_data(load_avg_str)
 
-               portage.writemsg_stdout(">>> %s\n" % \
-                       (color_output.getvalue(),), noiselevel=-1)
+               self._update(color_output.getvalue())
                xtermTitle(plain_output.getvalue())
 
 class Scheduler(PollScheduler):
@@ -9374,8 +9461,7 @@ class Scheduler(PollScheduler):
                @param msg: a brief status message (no newlines allowed)
                """
 
-               # TODO: Let self._status_display handle this.
-               portage.util.writemsg_level(">>> %s\n" % msg, noiselevel=-1)
+               self._status_display.displayMessage(msg)
 
        def _save_resume_list(self):
                """