#!/usr/bin/python -u

""" Embed Python code in HTML, parse and execute it. """

# Aquarius <aquarius@kryogenix.org>
# http://www.kryogenix.org/code/castalian/
# Some code and ideas taken from PMZ, http://pmz.sf.net/

# v1.0

import sys,cgi,string,re,traceback,os,Cookie,urllib
from UserDict import UserDict

# Classes

class server_obj:
    def __init__(self):
        pass
    def urlencode(self,s):
        return urllib.quote(s)

class QueryStringDict(UserDict):
    "A dictionary-a-like to be the QueryString"
    def has_keys(self,*keys):
        "has_key() for multiple keys"
        for key in keys:
            if not self.has_key(key): return 0
        return 1

class request_obj:
    def __init__(self):
        self.servervariables = os.environ
        
        self.querystring = QueryStringDict()
        try:
            qs = self.servervariables['QUERY_STRING']
            for key,value in cgi.parse_qsl(qs,1):
                # Write over any previous version of this
                self.querystring[key] = value
        except KeyError:
            self.querystring = QueryStringDict()
        
        self.form = QueryStringDict()
        formparse = cgi.FieldStorage()
        for f in formparse.keys():
            self.form[f] = formparse[f].value
        for q in self.querystring.keys():
            if self.form.has_key(q):
                del self.form[q]

        self.__cookies = {}
        if self.servervariables.has_key("HTTP_COOKIE"):
            c = Cookie.SmartCookie()
            c.load(self.servervariables["HTTP_COOKIE"])
            self.__cookies = c
        
    def cookies(self,name):
        if self.__cookies.has_key(name):
            return self.__cookies[name].value

class ResponseCookies(UserDict):
    def __setitem__(self,key,item):
        if not self.data.has_key(key):
            self.data[key] = {}
        self.data[key]["value"] = item

class response_obj:
    def __init__(self):
        self.headers_written = 0
        self.__content_type = "Content-type: text/html"
        self.__additional_headers = []
        self.cookies = ResponseCookies()
        
    def write(self,*args):
        s = []
        for i in args: s.append(str(i))
        toWrite = string.join(s)
        if string.strip(toWrite) == '': return
        if not self.headers_written:
            self.headers_written = 1
            sys.stdout.write(self.__content_type)
            sys.stdout.write('\n')
            C = Cookie.SmartCookie()
            for cookie in self.cookies.keys():
                C[cookie] = self.cookies[cookie]["value"]
                for k in self.cookies[cookie].keys():
                    if k <> "value":
                        C[cookie][k] = self.cookies[cookie][k]
            if self.cookies:
                sys.stdout.write(str(C) + '\n')
            for h in self.__additional_headers: 
                sys.stdout.write(h)
                sys.stdout.write('\n')
            sys.stdout.write('\n')
        s = []
        for i in args: s.append(str(i))
        sys.stdout.write(toWrite)
        sys.stdout.flush()
    
    def writeln(self,str):
        self.write('%s\r\n' % str)

    def content_type(self,ct):
        if self.headers_written:
            raise "Can't change content type once headers are written"
            return
        self.__content_type = "Content-type: %s" % ct
        
    def addheader(self,name,value):
        self.__additional_headers.append('%s: %s' % (name,value))
    
    def redirect(self,url):
        self.addheader('Status','302')
        self.addheader('Location',url)
        response.write("Redirect to ",url)
        response.end
    
    def end(self):
        os._exit(0)

def parse_cas(fn):
    # parse input file
    try:
        data = open(fn,'r').read()
    except:
        response.addheader('Status','404')
        response.write('File not found (%s)' % fn)
        return
    
    # parse the file for SSI include file statements, and replace them
    
    incFile = re.compile(r'<!--\s#include\sfile="(?P<filename>[^"]*)"\s-->')
    wholeFile = re.split(incFile,data)
    incFilePath = os.path.dirname(os.environ["PATH_TRANSLATED"])
    for idx in range(2,len(wholeFile),2):
        try:
            wholeFile[idx-1] = open(os.path.join(incFilePath,wholeFile[idx-1])).read()
        except:
            print "Content-type: text/html\n\n"
            print "Include file '%s' not found." % wholeFile[idx-1]
            sys.exit(1)
    
    data = string.join(wholeFile,'\n')

    # split file into HTML and Python
    reg = re.compile('(<\?cas=|<\?cas|\?>)')
    fields = reg.split(data)
    fields = filter(lambda x: len(x) <> 0, fields)
    
    in_code = in_var = 0
    line = 0
    for f in fields:
        if f == '<?cas': in_code = 1
        elif f == '<?cas=': in_var = 1
        elif f == '?>': in_code = in_var = 0
        else:
            # remove CDATA section
            f = re.sub('(<!\[CDATA\[)','',(f))
            f = re.sub(']]>$','',f)
            
            if in_code:
                try:
                    exec(f) in globals()
                except:
                    from StringIO import StringIO
                    err_type, err_value, err_traceback = sys.exc_info()
                    response.write("\n<br>Castalian error: %s (%s)<br>\n" % (err_type,err_value))
                    response.write("<pre>")
                    fp = StringIO()
                    x = traceback.print_exc(file=fp)
                    for tline in string.split(fp.getvalue(),'\012'):
                        if string.find(tline,'Traceback (innermost last):') <> -1:
                            pass
                        elif string.find(tline,'in parse_cas') <> -1:
                            pass
                        elif string.find(tline,'exec(f)') <> -1:
                            pass
                        else:
                            response.write(cgi.escape(tline) + '\n')
                    response.write("</pre><br>\n")
                    del(err_traceback)
                    return
            elif in_var:
                if locals().has_key(string.strip(f)):
                    response.write(locals()[string.strip(f)])
                elif globals().has_key(string.strip(f)):
                    response.write(globals()[string.strip(f)])
            else:
                response.write(f)
        line = line + string.count(f,'\012')

    return

# Create builtin objects
request = request_obj()
response = response_obj()
server = server_obj()

def Main(argv):
    fn = None
    try:
        fn = os.environ['PATH_TRANSLATED']
        os.environ["CASTALIAN_FILENAME"] = os.environ["PATH_INFO"]
    except:
        print "Content-type: text/plain\n\n"
        print """Your webserver does not seem to be supplying 
        PATH_TRANSLATED. Castalian won't work without it."""
        sys.exit(0)
    outfile = sys.stdout
    # Change directory to the directory containing the file
    (d,f) = os.path.split(fn)
    os.chdir(d)
    sys.path.append('.')
    parse_cas(fn)
    return 0

if __name__=='__main__':
    sys.exit(Main(sys.argv))