# Copyright (C) 2010, 2012 Google Inc. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import logging import os import sys import time LOG_HANDLER_NAME = 'MeteredStreamLogHandler' class MeteredStream(object): """ This class implements a stream wrapper that has 'meters' as well as regular output. A 'meter' is a single line of text that can be erased and rewritten repeatedly, without producing multiple lines of output. It can be used to produce effects like progress bars. """ @staticmethod def _erasure(txt): num_chars = len(txt) return '\b' * num_chars + ' ' * num_chars + '\b' * num_chars @staticmethod def _ensure_newline(txt): return txt if txt.endswith('\n') else txt + '\n' def __init__(self, stream=None, verbose=False, logger=None, time_fn=None, pid=None, number_of_columns=None): self._stream = stream or sys.stderr self._verbose = verbose self._time_fn = time_fn or time.time self._pid = pid or os.getpid() self._isatty = self._stream.isatty() self._erasing = self._isatty and not verbose self._last_partial_line = '' self._last_write_time = 0.0 self._throttle_delay_in_secs = 0.066 if self._erasing else 10.0 self._number_of_columns = sys.maxint if self._isatty and number_of_columns: self._number_of_columns = number_of_columns self._logger = logger self._log_handler = None if self._logger: log_level = logging.DEBUG if verbose else logging.INFO self._log_handler = _LogHandler(self) self._log_handler.setLevel(log_level) self._logger.addHandler(self._log_handler) def __del__(self): self.cleanup() def cleanup(self): if self._logger: self._logger.removeHandler(self._log_handler) self._log_handler = None def write_throttled_update(self, txt): now = self._time_fn() if now - self._last_write_time >= self._throttle_delay_in_secs: self.write_update(txt, now) def write_update(self, txt, now=None): self.write(txt, now) if self._erasing: self._last_partial_line = txt[txt.rfind('\n') + 1:] def write(self, txt, now=None, pid=None): now = now or self._time_fn() pid = pid or self._pid self._last_write_time = now if self._last_partial_line: self._erase_last_partial_line() if self._verbose: now_tuple = time.localtime(now) msg = '%02d:%02d:%02d.%03d %d %s' % (now_tuple.tm_hour, now_tuple.tm_min, now_tuple.tm_sec, int((now * 1000) % 1000), pid, self._ensure_newline(txt)) elif self._isatty: msg = txt else: msg = self._ensure_newline(txt) self._stream.write(msg) def writeln(self, txt, now=None, pid=None): self.write(self._ensure_newline(txt), now, pid) def _erase_last_partial_line(self): num_chars = len(self._last_partial_line) self._stream.write(self._erasure(self._last_partial_line)) self._last_partial_line = '' def flush(self): if self._last_partial_line: self._stream.write('\n') self._last_partial_line = '' self._stream.flush() def number_of_columns(self): return self._number_of_columns class _LogHandler(logging.Handler): def __init__(self, meter): logging.Handler.__init__(self) self._meter = meter self.name = LOG_HANDLER_NAME def emit(self, record): self._meter.writeln(record.getMessage(), record.created, record.process)