# Released under BSD license, Go nuts.
# Oisin Mulvihill (oisin d-o-t mulvihill a-t gmail d-o-t com), 2010-11-05.
import Cheetah
from Cheetah.Template import Template
from Cheetah.ErrorCatchers import ListErrors

# The template with errors. The line numbers I show by hand
# should match up with the errors found and highlighted.
#
src = """
0
1  hello there $XYZ
2
3  $notpresent
4
5  $XYZ['notpresent']
6
7
8  A KeyError for an unkown key 'fish': $abc['fish']
9
10 
11 ${ 0/0 } A more general error.
12
""".strip()

# Data dict:
d = dict(XYZ=123, abc={})

# Example Output
#
# Rendered Template:
# ------------------
# 0
# 1  hello there 123
# 2
# ERROR on line 3, Cheetah.NameMapper.NotFound(cannot find 'notpresent'): 3  $notpresent
# 4
# ERROR on line 5, exceptions.TypeError(unsubscriptable object):  5  $XYZ['notpresent']
# 6
# 7
# ERROR on line 8, exceptions.KeyError('fish'):   8  A KeyError for an unkown key 'fish': $abc['fish']
# 9
# 10 
# ERROR on line 11, exceptions.ZeroDivisionError(integer division or modulo by zero):     11 ${ 0/0 } A more general error.
# 12
# 


def render_showing_errors(source, namespaces):
    """Given the Cheetah template and namespace to fill it will render the output.

    This will catch all exceptions and then highlight each line 
    on which the error occurred.

    :returns: The rendered template. If errors occured, each line causing 
    the problem will contain a message in the following form:

    Error on line XYZ, <exception class(<exception message>)>: <original line>

    For example:

    ERROR on line 3, Cheetah.NameMapper.NotFound(cannot find 'notpresent'): 3  $notpresent

    """
    import Cheetah
    from Cheetah.Template import Template
    from Cheetah.ErrorCatchers import ListErrors
    highlighted = []

    # Monkey patch my catch all exceptions error handler:
    class OM_ListAll(ListErrors):
        _exceptionsToCatch = (Exception,)
    
    # I have to do this as there doesn't appear to be a way
    # I can pass in my ListAllErrors. I've tried many different
    # approaches. The Cheetah internals are 'hairy' in the extreme.
    # This proved the easiest approach.
    Cheetah.ErrorCatchers.OM_ListAll = OM_ListAll

    class T(Template):
        def __init__(self, source, namespaces, *args, **kw):
            # add the errorCatcher to the template. Trying to programatically do
            # this proved a nightmare. I gave up and choose this simple approach.
            source = "#errorCatcher OM_ListAll\n%s" % source
            super(T,self).__init__(source, namespaces, errorCatcher='OM_ListAll', *args, **kw)

    t = T(source, namespaces)
    output = t.respond()

    # recover the list of errors:
    errors = t.errorCatcher().listErrors() 
    if errors:
        lines = output.split('\n')
        highlighted = []
        to_find = len(errors)-1 # include 0
        found = 0
        err = errors[0]

        # Hunt for the lines to highlight errors. The line numbers Cheetah gives us 
        # are not reliable. Therefore I need to search based on the code that caused
        # the problem. This will always be in the same place. The plus point is
        # the errors will follow each other in order. This means only one pass through
        # the original template source is needed.
        #
        for line_number, line in enumerate(lines):
            # Does this line contain the raw code that caused the problem?
            if line.find(err['rawCode']) != -1:
                ex = repr((err['exc_val'])).replace('<','')
                ex = ex.split()[0]
                line = "ERROR on line %s, %s(%s):\t%s" % (line_number, ex, str(err['exc_val']), line)
                found +=1
                if found <= to_find:
                    #print "next error_to_find: ", err['rawCode']
                    err = errors[found]

            highlighted.append(line)

        output = "\n".join(highlighted)

    return output            

if __name__ == "__main__":            
    result = render_showing_errors(src, d)

    print """
Rendered Template:
------------------
%s
    """ % (result)





