From 69500d28402817fb70c104501b5c8f4f54650f4a Mon Sep 17 00:00:00 2001 From: Zac Medico Date: Mon, 22 Jun 2009 18:21:56 +0000 Subject: [PATCH] Bug #275047 - Split _emerge/__init__.py into smaller pieces (part 2). Thanks to Sebastian Mingramm (few) for this patch. svn path=/main/trunk/; revision=13667 --- pym/_emerge/JobStatusDisplay.py | 267 +++++++++++++++++++++++ pym/_emerge/UninstallFailure.py | 18 ++ pym/_emerge/__init__.py | 365 +------------------------------- pym/_emerge/getloadavg.py | 24 +++ pym/_emerge/stdout_spinner.py | 80 +++++++ 5 files changed, 393 insertions(+), 361 deletions(-) create mode 100644 pym/_emerge/JobStatusDisplay.py create mode 100644 pym/_emerge/UninstallFailure.py create mode 100644 pym/_emerge/getloadavg.py create mode 100644 pym/_emerge/stdout_spinner.py diff --git a/pym/_emerge/JobStatusDisplay.py b/pym/_emerge/JobStatusDisplay.py new file mode 100644 index 000000000..7b111da05 --- /dev/null +++ b/pym/_emerge/JobStatusDisplay.py @@ -0,0 +1,267 @@ +import formatter +import os +import sys +import time + +try: + from cStringIO import StringIO +except ImportError: + from StringIO import StringIO + +try: + import portage +except ImportError: + from os import path as osp + sys.path.insert(0, osp.join(osp.dirname(osp.dirname(osp.realpath(__file__))), "pym")) + import portage + +from portage.output import xtermTitle + +from _emerge.getloadavg import getloadavg + +class JobStatusDisplay(object): + + _bound_properties = ("curval", "failed", "running") + _jobs_column_width = 48 + + # Don't update the display unless at least this much + # time has passed, in units of seconds. + _min_display_latency = 2 + + _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, xterm_titles=True): + object.__setattr__(self, "out", out) + object.__setattr__(self, "quiet", quiet) + object.__setattr__(self, "xterm_titles", xterm_titles) + object.__setattr__(self, "maxval", 0) + object.__setattr__(self, "merges", 0) + object.__setattr__(self, "_changed", False) + object.__setattr__(self, "_displayed", False) + object.__setattr__(self, "_last_display_time", 0) + object.__setattr__(self, "width", 80) + self.reset() + + isatty = hasattr(out, "isatty") and out.isatty() + object.__setattr__(self, "_isatty", isatty) + if not isatty or not self._init_term(): + 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) + encoding = sys.getdefaultencoding() + for k, v in self._term_codes.items(): + if not isinstance(v, basestring): + self._term_codes[k] = v.decode(encoding, 'replace') + + def _init_term(self): + """ + Initialize term control codes. + @rtype: bool + @returns: True if term codes were successfully initialized, + False otherwise. + """ + + 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: + return False + + term_codes = {} + for k, capname in self._termcap_name_map.iteritems(): + code = tigetstr(capname) + if code is None: + code = self._default_term_codes[capname] + term_codes[k] = code + object.__setattr__(self, "_term_codes", term_codes) + return True + + 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.out.flush() + self._displayed = False + + def _display(self, line): + self.out.write(line) + self.out.flush() + self._displayed = True + + def _update(self, msg): + + out = self.out + if not self._isatty: + out.write(self._format_msg(msg) + self._term_codes['newline']) + self.out.flush() + self._displayed = True + return + + if self._displayed: + self._erase() + + self._display(self._format_msg(msg)) + + def displayMessage(self, msg): + + was_displayed = self._displayed + + if self._isatty and self._displayed: + self._erase() + + self.out.write(self._format_msg(msg) + self._term_codes['newline']) + self.out.flush() + self._displayed = False + + if was_displayed: + self._changed = True + self.display() + + 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.out.flush() + self._displayed = False + + def __setattr__(self, name, value): + old_value = getattr(self, name) + if value == old_value: + return + object.__setattr__(self, name, value) + if name in self._bound_properties: + self._property_change(name, old_value, value) + + def _property_change(self, name, old_value, new_value): + self._changed = True + self.display() + + def _load_avg_str(self): + try: + avg = getloadavg() + except OSError: + return 'unknown' + + max_avg = max(avg) + + if max_avg < 10: + digits = 2 + elif max_avg < 100: + digits = 1 + else: + digits = 0 + + return ", ".join(("%%.%df" % digits ) % x for x in avg) + + def display(self): + """ + Display status on stdout, but only if something has + changed since the last call. + """ + + if self.quiet: + return + + current_time = time.time() + time_delta = current_time - self._last_display_time + if self._displayed and \ + not self._changed: + if not self._isatty: + return + if time_delta < self._min_display_latency: + return + + self._last_display_time = current_time + self._changed = False + self._display_status() + + def _display_status(self): + # Don't use len(self._completed_tasks) here since that also + # can include uninstall tasks. + curval_str = str(self.curval) + maxval_str = str(self.maxval) + running_str = str(self.running) + failed_str = str(self.failed) + load_avg_str = self._load_avg_str() + + color_output = StringIO() + plain_output = StringIO() + style_file = portage.output.ConsoleStyleFile(color_output) + style_file.write_listener = plain_output + style_writer = portage.output.StyleWriter(file=style_file, maxcol=9999) + style_writer.style_listener = style_file.new_styles + f = formatter.AbstractFormatter(style_writer) + + number_style = "INFORM" + f.add_literal_data("Jobs: ") + f.push_style(number_style) + f.add_literal_data(curval_str) + f.pop_style() + f.add_literal_data(" of ") + f.push_style(number_style) + f.add_literal_data(maxval_str) + f.pop_style() + f.add_literal_data(" complete") + + if self.running: + f.add_literal_data(", ") + f.push_style(number_style) + f.add_literal_data(running_str) + f.pop_style() + f.add_literal_data(" running") + + if self.failed: + f.add_literal_data(", ") + f.push_style(number_style) + f.add_literal_data(failed_str) + f.pop_style() + f.add_literal_data(" failed") + + padding = self._jobs_column_width - len(plain_output.getvalue()) + if padding > 0: + f.add_literal_data(padding * " ") + + f.add_literal_data("Load avg: ") + f.add_literal_data(load_avg_str) + + # Truncate to fit width, to avoid making the terminal scroll if the + # line overflows (happens when the load average is large). + plain_output = plain_output.getvalue() + if self._isatty and len(plain_output) > self.width: + # Use plain_output here since it's easier to truncate + # properly than the color output which contains console + # color codes. + self._update(plain_output[:self.width]) + else: + self._update(color_output.getvalue()) + + if self.xterm_titles: + xtermTitle(" ".join(plain_output.split())) diff --git a/pym/_emerge/UninstallFailure.py b/pym/_emerge/UninstallFailure.py new file mode 100644 index 000000000..04866d6cc --- /dev/null +++ b/pym/_emerge/UninstallFailure.py @@ -0,0 +1,18 @@ +try: + import portage +except ImportError: + from os import path as osp + import sys + sys.path.insert(0, osp.join(osp.dirname(osp.dirname(osp.realpath(__file__))), "pym")) + import portage + +class UninstallFailure(portage.exception.PortageException): + """ + An instance of this class is raised by unmerge() when + an uninstallation fails. + """ + status = 1 + def __init__(self, *pargs): + portage.exception.PortageException.__init__(self, pargs) + if pargs: + self.status = pargs[0] diff --git a/pym/_emerge/__init__.py b/pym/_emerge/__init__.py index 0a2bc6d2c..3a3dac25e 100644 --- a/pym/_emerge/__init__.py +++ b/pym/_emerge/__init__.py @@ -83,86 +83,10 @@ from _emerge.UseFlagDisplay import UseFlagDisplay from _emerge.PollSelectAdapter import PollSelectAdapter from _emerge.SequentialTaskQueue import SequentialTaskQueue from _emerge.ProgressHandler import ProgressHandler - -try: - from cStringIO import StringIO -except ImportError: - from StringIO import StringIO - -class stdout_spinner(object): - scroll_msgs = [ - "Gentoo Rocks ("+platform.system()+")", - "Thank you for using Gentoo. :)", - "Are you actually trying to read this?", - "How many times have you stared at this?", - "We are generating the cache right now", - "You are paying too much attention.", - "A theory is better than its explanation.", - "Phasers locked on target, Captain.", - "Thrashing is just virtual crashing.", - "To be is to program.", - "Real Users hate Real Programmers.", - "When all else fails, read the instructions.", - "Functionality breeds Contempt.", - "The future lies ahead.", - "3.1415926535897932384626433832795028841971694", - "Sometimes insanity is the only alternative.", - "Inaccuracy saves a world of explanation.", - ] - - twirl_sequence = "/-\\|/-\\|/-\\|/-\\|\\-/|\\-/|\\-/|\\-/|" - - def __init__(self): - self.spinpos = 0 - self.update = self.update_twirl - self.scroll_sequence = self.scroll_msgs[ - int(time.time() * 100) % len(self.scroll_msgs)] - self.last_update = 0 - self.min_display_latency = 0.05 - - def _return_early(self): - """ - Flushing ouput to the tty too frequently wastes cpu time. Therefore, - each update* method should return without doing any output when this - method returns True. - """ - cur_time = time.time() - if cur_time - self.last_update < self.min_display_latency: - return True - self.last_update = cur_time - return False - - def update_basic(self): - self.spinpos = (self.spinpos + 1) % 500 - if self._return_early(): - return - if (self.spinpos % 100) == 0: - if self.spinpos == 0: - sys.stdout.write(". ") - else: - sys.stdout.write(".") - sys.stdout.flush() - - def update_scroll(self): - if self._return_early(): - return - if(self.spinpos >= len(self.scroll_sequence)): - sys.stdout.write(darkgreen(" \b\b\b" + self.scroll_sequence[ - len(self.scroll_sequence) - 1 - (self.spinpos % len(self.scroll_sequence))])) - else: - sys.stdout.write(green("\b " + self.scroll_sequence[self.spinpos])) - sys.stdout.flush() - self.spinpos = (self.spinpos + 1) % (2 * len(self.scroll_sequence)) - - def update_twirl(self): - self.spinpos = (self.spinpos + 1) % len(self.twirl_sequence) - if self._return_early(): - return - sys.stdout.write("\b\b " + self.twirl_sequence[self.spinpos]) - sys.stdout.flush() - - def update_quiet(self): - return +from _emerge.stdout_spinner import stdout_spinner +from _emerge.UninstallFailure import UninstallFailure +from _emerge.JobStatusDisplay import JobStatusDisplay +from _emerge.getloadavg import getloadavg def userquery(prompt, responses=None, colours=None): """Displays a prompt and a set of responses, then waits for a response @@ -6569,29 +6493,6 @@ def create_poll_instance(): return select.poll() return PollSelectAdapter() -getloadavg = getattr(os, "getloadavg", None) -if getloadavg is None: - def getloadavg(): - """ - Uses /proc/loadavg to emulate os.getloadavg(). - Raises OSError if the load average was unobtainable. - """ - try: - loadavg_str = open('/proc/loadavg').readline() - except IOError: - # getloadavg() is only supposed to raise OSError, so convert - raise OSError('unknown') - loadavg_split = loadavg_str.split() - if len(loadavg_split) < 3: - raise OSError('unknown') - loadavg_floats = [] - for i in xrange(3): - try: - loadavg_floats.append(float(loadavg_split[i])) - except ValueError: - raise OSError('unknown') - return tuple(loadavg_floats) - class PollScheduler(object): class _sched_iface_class(SlotObject): @@ -6879,253 +6780,6 @@ class TaskScheduler(object): def add(self, task): self._queue.add(task) -class JobStatusDisplay(object): - - _bound_properties = ("curval", "failed", "running") - _jobs_column_width = 48 - - # Don't update the display unless at least this much - # time has passed, in units of seconds. - _min_display_latency = 2 - - _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, xterm_titles=True): - object.__setattr__(self, "out", out) - object.__setattr__(self, "quiet", quiet) - object.__setattr__(self, "xterm_titles", xterm_titles) - object.__setattr__(self, "maxval", 0) - object.__setattr__(self, "merges", 0) - object.__setattr__(self, "_changed", False) - object.__setattr__(self, "_displayed", False) - object.__setattr__(self, "_last_display_time", 0) - object.__setattr__(self, "width", 80) - self.reset() - - isatty = hasattr(out, "isatty") and out.isatty() - object.__setattr__(self, "_isatty", isatty) - if not isatty or not self._init_term(): - 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) - encoding = sys.getdefaultencoding() - for k, v in self._term_codes.items(): - if not isinstance(v, basestring): - self._term_codes[k] = v.decode(encoding, 'replace') - - def _init_term(self): - """ - Initialize term control codes. - @rtype: bool - @returns: True if term codes were successfully initialized, - False otherwise. - """ - - 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: - return False - - term_codes = {} - for k, capname in self._termcap_name_map.iteritems(): - code = tigetstr(capname) - if code is None: - code = self._default_term_codes[capname] - term_codes[k] = code - object.__setattr__(self, "_term_codes", term_codes) - return True - - 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.out.flush() - self._displayed = False - - def _display(self, line): - self.out.write(line) - self.out.flush() - self._displayed = True - - def _update(self, msg): - - out = self.out - if not self._isatty: - out.write(self._format_msg(msg) + self._term_codes['newline']) - self.out.flush() - self._displayed = True - return - - if self._displayed: - self._erase() - - self._display(self._format_msg(msg)) - - def displayMessage(self, msg): - - was_displayed = self._displayed - - if self._isatty and self._displayed: - self._erase() - - self.out.write(self._format_msg(msg) + self._term_codes['newline']) - self.out.flush() - self._displayed = False - - if was_displayed: - self._changed = True - self.display() - - 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.out.flush() - self._displayed = False - - def __setattr__(self, name, value): - old_value = getattr(self, name) - if value == old_value: - return - object.__setattr__(self, name, value) - if name in self._bound_properties: - self._property_change(name, old_value, value) - - def _property_change(self, name, old_value, new_value): - self._changed = True - self.display() - - def _load_avg_str(self): - try: - avg = getloadavg() - except OSError: - return 'unknown' - - max_avg = max(avg) - - if max_avg < 10: - digits = 2 - elif max_avg < 100: - digits = 1 - else: - digits = 0 - - return ", ".join(("%%.%df" % digits ) % x for x in avg) - - def display(self): - """ - Display status on stdout, but only if something has - changed since the last call. - """ - - if self.quiet: - return - - current_time = time.time() - time_delta = current_time - self._last_display_time - if self._displayed and \ - not self._changed: - if not self._isatty: - return - if time_delta < self._min_display_latency: - return - - self._last_display_time = current_time - self._changed = False - self._display_status() - - def _display_status(self): - # Don't use len(self._completed_tasks) here since that also - # can include uninstall tasks. - curval_str = str(self.curval) - maxval_str = str(self.maxval) - running_str = str(self.running) - failed_str = str(self.failed) - load_avg_str = self._load_avg_str() - - color_output = StringIO() - plain_output = StringIO() - style_file = portage.output.ConsoleStyleFile(color_output) - style_file.write_listener = plain_output - style_writer = portage.output.StyleWriter(file=style_file, maxcol=9999) - style_writer.style_listener = style_file.new_styles - f = formatter.AbstractFormatter(style_writer) - - number_style = "INFORM" - f.add_literal_data("Jobs: ") - f.push_style(number_style) - f.add_literal_data(curval_str) - f.pop_style() - f.add_literal_data(" of ") - f.push_style(number_style) - f.add_literal_data(maxval_str) - f.pop_style() - f.add_literal_data(" complete") - - if self.running: - f.add_literal_data(", ") - f.push_style(number_style) - f.add_literal_data(running_str) - f.pop_style() - f.add_literal_data(" running") - - if self.failed: - f.add_literal_data(", ") - f.push_style(number_style) - f.add_literal_data(failed_str) - f.pop_style() - f.add_literal_data(" failed") - - padding = self._jobs_column_width - len(plain_output.getvalue()) - if padding > 0: - f.add_literal_data(padding * " ") - - f.add_literal_data("Load avg: ") - f.add_literal_data(load_avg_str) - - # Truncate to fit width, to avoid making the terminal scroll if the - # line overflows (happens when the load average is large). - plain_output = plain_output.getvalue() - if self._isatty and len(plain_output) > self.width: - # Use plain_output here since it's easier to truncate - # properly than the color output which contains console - # color codes. - self._update(plain_output[:self.width]) - else: - self._update(color_output.getvalue()) - - if self.xterm_titles: - xtermTitle(" ".join(plain_output.split())) - class Scheduler(PollScheduler): _opts_ignore_blockers = \ @@ -8875,17 +8529,6 @@ class MetadataRegen(PollScheduler): self._schedule() -class UninstallFailure(portage.exception.PortageException): - """ - An instance of this class is raised by unmerge() when - an uninstallation fails. - """ - status = 1 - def __init__(self, *pargs): - portage.exception.PortageException.__init__(self, pargs) - if pargs: - self.status = pargs[0] - def unmerge(root_config, myopts, unmerge_action, unmerge_files, ldpath_mtimes, autoclean=0, clean_world=1, clean_delay=1, ordered=0, raise_on_error=0, diff --git a/pym/_emerge/getloadavg.py b/pym/_emerge/getloadavg.py new file mode 100644 index 000000000..b5a66cce9 --- /dev/null +++ b/pym/_emerge/getloadavg.py @@ -0,0 +1,24 @@ +import os + +getloadavg = getattr(os, "getloadavg", None) +if getloadavg is None: + def getloadavg(): + """ + Uses /proc/loadavg to emulate os.getloadavg(). + Raises OSError if the load average was unobtainable. + """ + try: + loadavg_str = open('/proc/loadavg').readline() + except IOError: + # getloadavg() is only supposed to raise OSError, so convert + raise OSError('unknown') + loadavg_split = loadavg_str.split() + if len(loadavg_split) < 3: + raise OSError('unknown') + loadavg_floats = [] + for i in xrange(3): + try: + loadavg_floats.append(float(loadavg_split[i])) + except ValueError: + raise OSError('unknown') + return tuple(loadavg_floats) diff --git a/pym/_emerge/stdout_spinner.py b/pym/_emerge/stdout_spinner.py new file mode 100644 index 000000000..1f49a6850 --- /dev/null +++ b/pym/_emerge/stdout_spinner.py @@ -0,0 +1,80 @@ +import platform +import sys +import time + +from portage.output import darkgreen, green + +class stdout_spinner(object): + scroll_msgs = [ + "Gentoo Rocks ("+platform.system()+")", + "Thank you for using Gentoo. :)", + "Are you actually trying to read this?", + "How many times have you stared at this?", + "We are generating the cache right now", + "You are paying too much attention.", + "A theory is better than its explanation.", + "Phasers locked on target, Captain.", + "Thrashing is just virtual crashing.", + "To be is to program.", + "Real Users hate Real Programmers.", + "When all else fails, read the instructions.", + "Functionality breeds Contempt.", + "The future lies ahead.", + "3.1415926535897932384626433832795028841971694", + "Sometimes insanity is the only alternative.", + "Inaccuracy saves a world of explanation.", + ] + + twirl_sequence = "/-\\|/-\\|/-\\|/-\\|\\-/|\\-/|\\-/|\\-/|" + + def __init__(self): + self.spinpos = 0 + self.update = self.update_twirl + self.scroll_sequence = self.scroll_msgs[ + int(time.time() * 100) % len(self.scroll_msgs)] + self.last_update = 0 + self.min_display_latency = 0.05 + + def _return_early(self): + """ + Flushing ouput to the tty too frequently wastes cpu time. Therefore, + each update* method should return without doing any output when this + method returns True. + """ + cur_time = time.time() + if cur_time - self.last_update < self.min_display_latency: + return True + self.last_update = cur_time + return False + + def update_basic(self): + self.spinpos = (self.spinpos + 1) % 500 + if self._return_early(): + return + if (self.spinpos % 100) == 0: + if self.spinpos == 0: + sys.stdout.write(". ") + else: + sys.stdout.write(".") + sys.stdout.flush() + + def update_scroll(self): + if self._return_early(): + return + if(self.spinpos >= len(self.scroll_sequence)): + sys.stdout.write(darkgreen(" \b\b\b" + self.scroll_sequence[ + len(self.scroll_sequence) - 1 - (self.spinpos % len(self.scroll_sequence))])) + else: + sys.stdout.write(green("\b " + self.scroll_sequence[self.spinpos])) + sys.stdout.flush() + self.spinpos = (self.spinpos + 1) % (2 * len(self.scroll_sequence)) + + def update_twirl(self): + self.spinpos = (self.spinpos + 1) % len(self.twirl_sequence) + if self._return_early(): + return + sys.stdout.write("\b\b " + self.twirl_sequence[self.spinpos]) + sys.stdout.flush() + + def update_quiet(self): + return -- 2.26.2