Source

consoleLog / src / consoleLog / consoleLog.py

# -*- coding: utf-8 -*-
# 
#  consoleLog.py
#  consoleLog
#  
#  Created by Lars Yencken on 2008-06-12.
#  Copyright 2008 Lars Yencken. All rights reserved.
# 

"""
A simple logging module which allows hierarchical logging without centralized
control.

>>> log = ConsoleLogger()
>>> log.start('ABC', nSteps=3)
ABC
>>> log.log('A')
├─ A
>>> log.log('B')
├─ B
>>> log.log('C')
└─ C
>>> log.finish()
"""

#----------------------------------------------------------------------------#

import sys
import codecs

_default_encoding = 'utf8'

#----------------------------------------------------------------------------#
class ConsoleLevel(object):
    """
    A context manager used to represent a level of nesting in console contexts
    """
    def __init__(self, logger, start_message):
        self.logger = logger
        self.start_message = start_message

    def __enter__(self):
        self.logger.start(self.start_message)

    def __exit__(self, exc_type, exc_value, traceback):
        self.logger.finish()

class LoggedFunction(object):
    """
    A higher-order function used to invoke consoleLog messages around the 
    execution of a particular function.
    """
    def __init__(self, logger, decoratee, start_message = None):
        self.logger = logger
        self.decoratee = decoratee
        self.start_message = start_message

    def __call__(self, *args, **kwargs):
        if self.start_message is None:
            self.logger.start('Calling '+self.decoratee.__name__)
        else:
            self.logger.start(self.start_message)
        # TODO: Find a way to put logger into the function's scope
        self.decoratee(*args, **kwargs)
        self.logger.finish()

class ConsoleLogger(object):
    """
    A console logging object, which can optionally be given an output stream
    which it prints to, defaulting to stdout.
    """
    def __init__(self, output=None, trim=78, encoding=_default_encoding):
        self.oStream = codecs.getwriter(encoding)(output or sys.stdout)
        self.logStack = []
        self.trim=trim

    def level(self, message):
        """
        Creates a context manager which represents a new level of nesting in
        the logging.
        """
        return ConsoleLevel(self, message)

    def with_log(self, arg):
        """
        Decorator which allows notification of calling and completion of the
        decorated function. Can be called with an optional string argument to 
        specify a custom 'start' message.
        """
        if callable(arg):
            return LoggedFunction(self, arg)
        else:
            return lambda decoratee: LoggedFunction(self, decoratee, arg)

    def start(self, message, nSteps=None):
        """
        Starts a new hierarchical logging section. Every call to start() must
        be later matched by a call to finish().
        """
        self.log(message)
        self.logStack.append(_SectionPadding(nSteps=nSteps))
        return

    def log(self, message, newLine=True):
        """
        Logs a message, with an optional newline. The most common case for
        not using the newline is for subsequent use of a progress bar.
        """
        if self.logStack:
            padding = []
            for levelLogger in self.logStack[:-1]:
                padding.append(levelLogger.getIntermediatePadding())
            padding.append(self.logStack[-1].getCurrentPadding())
        else:
            padding = u''
        oLine = u'%s%s' % (u''.join(padding), message)
        if self.trim and len(oLine) > self.trim:
            oLine = oLine[:self.trim - 3] + u'...'
        self.oStream.write(oLine)
        if newLine:
            print >> self.oStream
        else:
            self.oStream.flush()
        return

    def space(self):
        if self.logStack:
            padding = []
            for levelLogger in self.logStack:
                padding.append(levelLogger.getIntermediatePadding())
            padding = u''.join(padding)
        else:
            padding = u''
        print >> self.oStream, padding
        return

    def finish(self, message="Done"):
        """
        Ends a hierarchical logging section which was previously created using
        start().
        """
        padding = []
        for levelLogger in self.logStack[:-1]:
            padding.append(levelLogger.getIntermediatePadding())
        last = self.logStack.pop()
        padding.append(last.getLastPadding())
        if last.needsFinish():
            print >> self.oStream, u"%s%s" % (u''.join(padding), message)
        return

#----------------------------------------------------------------------------#

class _SectionPadding(object):
    """
    A manager for the amount of padding needed for any given log line, as well
    as any embellishments needed to indicate hierarchy.
    """
    def __init__(self, nSteps=None):
        """Private constructor used internally."""
        self.nSteps = nSteps
        self.currentStep = 0

    def getIntermediatePadding(self):
        if self.currentStep == self.nSteps:
            return u'   '
        else:
            return u'│  '

    def getCurrentPadding(self):
        self.currentStep += 1
        if self.currentStep == self.nSteps:
            return u'└─ '
        else:
            return u'├─ '

    def needsFinish(self):
        return self.currentStep != self.nSteps

    def getLastPadding(self):
        return u'└─ '

#----------------------------------------------------------------------------#
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.