# --- imports
# preinstalled python libraries
import io
import time
[docs]class StreamRedirector(io.IOBase):
    """
    Class for redirecting a stream to a callback-function.
    Whenever a new line is written into the underlying stream, the callback-function will be called with the input.
    Any write-operation applied to a StreamRedirector will still also be fully applied to the underlying stream.
    """
    def __init__(self, stream, callback, timestampFormat="[%x %X] "):
        """
        Initialisation of a StreamRedirector.
        Args:
            stream: A stream-like object to redirect from.
            callback: A callback-function that recieves data as a string (single parameter).
            timestampFormat: A string for formating a timestamp that will be prefixed to each data-string for the callback-function only.
                The format will be interpreted via time.strftime(timestampFormat); default is '[%x %X] '.
                If an empty string or any falsy expression is supplied, no prefix will be produced.
        """
        super(StreamRedirector, self).__init__()
        self._timestampFormat = timestampFormat
        self._buffer = ""
        self._stream = stream
        self._callback = callback
    @property
    def closed(self):
        """
        True if the underlying stream is closed.
        """
        return self._stream.closed
[docs]    def close(self):
        """
        Close the underlying stream and flush the current buffer.
        """
        self.flush()
        self._stream.close() 
[docs]    def flush(self):
        """
        Flush the underlying stream and the current buffer.
        """
        while "\n" in self._buffer:
            self._writeline()
        self._writeline()
        if not self.closed:
            self._stream.flush() 
[docs]    def read(self, *args):
        """
        Raise io.UnsupportedOperation as this stream-like object can not be read from.
        """
        raise io.UnsupportedOperation("read") 
[docs]    def writable(self):
        """
        Return True as this stream-like object is writable.
        """
        return True 
[docs]    def write(self, input):
        """
        Write to the underlying stream.
        If a new line is found also call the callback-function with every line.
        Args:
            input: An appropriate object for the underlying stream to write.
        """
        try:
            self._buffer += input
            while "\n" in self._buffer:
                self._writeline()
        except:
            pass
        return self._stream.write(input) 
    def _writeline(self):
        """
        Call the callback-function with a single line from the buffer.
        If the buffer has no line-endings the entire content of the buffer will be supplied as data to the callback-function.
        This also prepends the timestamp-prefix if needed.
        This is an internal function.
        """
        if self._buffer:
            splitted = self._buffer.split("\n", 1)
            data = splitted[0]
            if len(splitted) > 1:
                self._buffer = splitted[1]
            else:
                self._buffer = ""
            if self._timestampFormat:
                data = time.strftime(self._timestampFormat) + data
            self._callback(data)