"""
Bruces, templating engine.

Contains different functions for compiling and displaying templates.
"""

import os
import tokenize
from cgi import escape
from copy import copy
from StringIO import StringIO


class TemplateCompiler(object):
    """
    Class used to generate Python code from template source code.
    """
       
    def __init__(self, open_tag="{%", close_tag="%}"):
        """
        Initialize the compiler.

        open_tag -- the tag denoting the beginning of compilable code.
        close_tag -- the tag denoting the end of compilable code.
        """
        self.open_tag = open_tag
        self.close_tag = close_tag

    def compile(self, spec, indent=0):
        """
        Pre-compile a template to Python output.

        source -- the template source code.

        Returns a string containing executable Python code.
        """
        cursor = 0
        out = []

        source = str(spec) 
        source_len = len(source)

        while True: 
            start = source.find(self.open_tag, cursor)
            data = source[cursor:start]
            out.append(("  " * indent) + "__stream.write(%s)" % (repr(data),)) 

            end = source.find(self.close_tag, start)
            code = source[start + len(self.open_tag):end].strip()

            if end >= 0 and end < source_len:
                if code[0] == "=" and code[1] == "=":
                    out.append(("  " * indent) + "__stream.write(escape(unicode(%s)))" % (code[2:]),)
                elif code[0] == "=":
                    out.append(("  " * indent) + "__stream.write(%s)" % (code[1:],))
                elif code[0] == "+":
                    if isinstance(spec, TemplateFile):
                        filename = "%s/%s" % \
                            (os.path.dirname(spec.filename), eval(code[1:]),)
                    else:
                        filename = eval(code[1:])
                    tfile = TemplateFile(filename)
                    out.append(self.compile(tfile, indent))
                else:
                    tokens = list(tokenize.generate_tokens(StringIO(code).readline))
                    if tokens[0][1] in ["endexcept"]:
                        indent -= 1
                        code = code[3:] + ": pass"
                    if tokens[0][1] in ["endfor", "endif", "endwhile", "endwith", "endtry"]:
                        indent -= 1
                        code = ""
                    elif tokens[0][1] in ["else", "elif", "except"]:
                        indent -= 1

                    out.append(("  " * indent) + code)

                    if tokens[0][1] in ["for", "if", "while", "else", "elif", "with", "try", "except",
                                        "finally"]:
                        indent += 1
            else:
                break

            cursor = end + len(self.close_tag) 

        return "\n".join(out)

    def execute(self, __source, **kwargs):
        """
        Compile and display a template.

        source -- the template source code.
        """
        pre_source = self.compile(__source)
        try: 
            spec_source = "<template %s>" % (__source.filename,)
        except AttributeError:
            spec_source = "<template string>"

        try:
            spec = compile(pre_source, spec_source, "exec")
        except:
            raise
       
        data = copy(kwargs)
        self.stream = StringIO()
        data["__stream"] = self.stream
        try:
            eval(spec, globals(), data)
        except:
            raise

    def fetch(self, __source, **kwargs):
        data = self.execute(__source, **kwargs) 
        self.stream.seek(0)
        return self.stream.read()
        
    def display(self, __source, **kwargs):
        print self.fetch(__source, **kwargs) 

class TemplateFile(object):
    """
    Object representing a template file.
    """

    def __init__(self, filename):
        """
        Initialize a template file.

        filename -- the path to the template file.
        """
        self.filename = filename
        self.fp = open(self.filename, "r")

    def __str__(self):
        return self.fp.read()
