from collections import namedtuple import sys from .compat import ON_WINDOWS Message = namedtuple("Message", "type data time name colour") class Printer(object): """ Printer is where Honcho's user-visible output is defined. A Printer instance receives typed messages and prints them to its output (usually STDOUT) in the Honcho format. """ def __init__(self, output=sys.stdout, time_format="%H:%M:%S", width=0, colour=True, prefix=True): self.output = output self.time_format = time_format self.width = width self.colour = colour self.prefix = prefix try: # We only want to print coloured messages if the given output supports # ANSI escape sequences. Usually, testing if it is a TTY is safe enough. self._colours_supported = self.output.isatty() except AttributeError: # If the given output does not implement isatty(), we assume that it # is not able to handle ANSI escape sequences. self._colours_supported = False def write(self, message): if message.type != 'line': raise RuntimeError('Printer can only process messages of type "line"') name = message.name if message.name is not None else "" name = name.ljust(self.width) if name: name += " " # When encountering data that cannot be interpreted as UTF-8 encoded # Unicode, Printer will replace the unrecognisable bytes with the # Unicode replacement character (U+FFFD). if isinstance(message.data, bytes): string = message.data.decode("utf-8", "replace") else: string = message.data for line in string.splitlines(): prefix = '' if self.prefix: time_formatted = message.time.strftime(self.time_format) prefix = '{time} {name}| '.format(time=time_formatted, name=name) if self.colour and self._colours_supported and message.colour: prefix = _colour_string(message.colour, prefix) self.output.write(prefix + line + "\n") def _ansi(code): return '\033[{0}m'.format(code) def _colour_string(colour, s): return '{0}{1}{2}{3}'.format(_ansi(0), _ansi(colour), s, _ansi(0)) if ON_WINDOWS: # The colorama package provides transparent support for ANSI colour codes # on Win32 platforms. We try and import and configure that, but fall back # to no colour if we fail. try: import colorama except ImportError: def _colour_string(colour, s): # noqa return s else: colorama.init()