# -*- coding: utf-8 -*-
"""Class to trap stdout and stderr and log them separately.
"""

#*****************************************************************************
#       Copyright (C) 2001-2004 Fernando Perez <fperez@colorado.edu>
#
#  Distributed under the terms of the BSD License.  The full license is in
#  the file COPYING, distributed as part of this software.
#*****************************************************************************

import exceptions
import sys
from cStringIO import StringIO

class OutputTrapError(exceptions.Exception):
    """Exception for OutputTrap class."""

    def __init__(self,args=None):
        exceptions.Exception.__init__(self)
        self.args = args

class OutputTrap:

    """Class to trap standard output and standard error. They get logged in
    StringIO objects which are available as <instance>.out and
    <instance>.err. The class also offers summary methods which format this
    data a bit.

    A word of caution: because it blocks messages, using this class can make
    debugging very tricky. If you are having bizarre problems silently, try
    turning your output traps off for a while. You can call the constructor
    with the parameter debug=1 for these cases. This turns actual trapping
    off, but you can keep the rest of your code unchanged (this has already
    been a life saver).

    Example:

    # config: trapper with a line of dots as log separator (final '\\n' needed)
    config = OutputTrap('Config','Out ','Err ','.'*80+'\\n')

    # start trapping output
    config.trap_all()

    # now all output is logged ...
    # do stuff...

    # output back to normal:
    config.release_all()

    # print all that got logged:
    print config.summary()

    # print individual raw data:
    print config.out.getvalue()
    print config.err.getvalue()
    """

    def __init__(self,name='Generic Output Trap',
                 out_head='Standard Output. ',err_head='Standard Error. ',
                 sum_sep='\n',debug=0,trap_out=0,trap_err=0,
                 quiet_out=0,quiet_err=0):
        self.name = name
        self.out_head = out_head
        self.err_head = err_head
        self.sum_sep = sum_sep
        self.out = StringIO()
        self.err = StringIO()
        self.out_save = None
        self.err_save = None
        self.debug = debug
        self.quiet_out = quiet_out
        self.quiet_err = quiet_err
        if trap_out:
            self.trap_out()
        if trap_err:
            self.trap_err()

    def trap_out(self):
        """Trap and log stdout."""
        if sys.stdout is self.out:
            raise OutputTrapError,'You are already trapping stdout.'
        if not self.debug:
            self._out_save = sys.stdout
            sys.stdout = self.out

    def release_out(self):
        """Release stdout."""
        if not self.debug:
            if not sys.stdout is self.out:
                raise OutputTrapError,'You are not trapping stdout.'
            sys.stdout = self._out_save
            self.out_save = None

    def summary_out(self):
        """Return as a string the log from stdout."""
        out = self.out.getvalue()
        if out:
            if self.quiet_out:
                return out
            else:
                return self.out_head + 'Log by '+ self.name + ':\n' + out
        else:
            return ''

    def flush_out(self):
        """Flush the stdout log. All data held in the log is lost."""

        self.out.close()
        self.out = StringIO()

    def trap_err(self):
        """Trap and log stderr."""
        if sys.stderr is self.err:
            raise OutputTrapError,'You are already trapping stderr.'
        if not self.debug:
            self._err_save = sys.stderr
            sys.stderr = self.err

    def release_err(self):
        """Release stderr."""
        if not self.debug:
            if not sys.stderr is self.err:
                raise OutputTrapError,'You are not trapping stderr.'
            sys.stderr = self._err_save
            self.err_save = None

    def summary_err(self):
        """Return as a string the log from stderr."""
        err = self.err.getvalue()
        if err:
            if self.quiet_err:
                return err
            else:
                return self.err_head + 'Log by '+ self.name + ':\n' + err
        else:
            return ''

    def flush_err(self):
        """Flush the stdout log. All data held in the log is lost."""

        self.err.close()
        self.err = StringIO()

    def trap_all(self):
        """Trap and log both stdout and stderr.

        Cacthes and discards OutputTrapError exceptions raised."""
        try:
            self.trap_out()
        except OutputTrapError:
            pass
        try:
            self.trap_err()
        except OutputTrapError:
            pass

    def release_all(self):
        """Release both stdout and stderr.

        Cacthes and discards OutputTrapError exceptions raised."""
        try:
            self.release_out()
        except OutputTrapError:
            pass
        try:
            self.release_err()
        except OutputTrapError:
            pass
        
    def summary_all(self):
        """Return as a string the log from stdout and stderr, prepending a separator
        to each (defined in __init__ as sum_sep)."""
        sum = ''
        sout = self.summary_out()
        if sout:
            sum += self.sum_sep + sout
        serr = self.summary_err()
        if serr:
            sum += '\n'+self.sum_sep + serr
        return sum

    def flush_all(self):
        """Flush stdout and stderr"""
        self.flush_out()
        self.flush_err()

    # a few shorthands
    trap = trap_all
    release = release_all
    summary = summary_all
    flush = flush_all
# end OutputTrap


#****************************************************************************
# Module testing. Incomplete, I'm lazy...

def _test_all():

    """Module testing functions, activated when the module is called as a
    script (not imported)."""

    # Put tests for this module in here.
    # Define them as nested functions so they don't clobber the
    # pydoc-generated docs

    def _test_():
        name = ''
        print '#'*50+'\nRunning test for ' + name
        # ...
        print 'Finished test for '+ name +'\n'+'#'*50

    def _test_OutputTrap():
        trap = OutputTrap(name = 'Test Trap', sum_sep = '.'*50+'\n',
                          out_head = 'SOut. ', err_head = 'SErr. ')

        name = 'OutputTrap class'
        print '#'*50+'\nRunning test for ' + name
        print 'Trapping out'
        trap.trap_out()
        print >>sys.stdout, '>>stdout. stdout is trapped.'
        print >>sys.stderr, '>>stderr. stdout is trapped.'
        trap.release_out()
        print trap.summary_out()

        print 'Trapping err'
        trap.trap_err()
        print >>sys.stdout, '>>stdout. stderr is trapped.'
        print >>sys.stderr, '>>stderr. stderr is trapped.'
        trap.release_err()
        print trap.summary_err()

        print 'Trapping all (no flushing)'
        trap.trap_all()
        print >>sys.stdout, '>>stdout. stdout/err is trapped.'
        print >>sys.stderr, '>>stderr. stdout/err is trapped.'
        trap.release_all()
        print trap.summary_all()

        print 'Trapping all (flushing first)'
        trap.flush()
        trap.trap_all()
        print >>sys.stdout, '>>stdout. stdout/err is trapped.'
        print >>sys.stderr, '>>stderr. stdout/err is trapped.'
        trap.release_all()
        print trap.summary_all()
        print 'Finished test for '+ name +'\n'+'#'*50

    # call the actual tests here:
    _test_OutputTrap()


if __name__=="__main__":
    # _test_all() # XXX BROKEN.
    pass

#************************ end of file <OutputTrap.py> ************************
