#!/usr/bin/python
#
#  PyWARD - Web App RESTful Dispatcher
#           Released by Paul Jimenez <pj@place.org> under the LGPL
#
#  Based on ideas from tarawa (http://www.mnot.net/tarawa)
#  with my own set of improvements/simplifications.
#
#  Note that this is (and is meant to be!) just a dispatch framework
#  to make it easier to write RESTful applications without
#   1) having to parse PATH_INFO yourself
#   2) forgetting to handle some method
#
#  History:
#    v1.0 - Fri Mar  4 10:35:15 CST 2005 - pj  Initial factorization from swami
# 

import os
import cgi
import string

def dispatchCGI(RootNode):
    ## parse cgi input
    ## put GET data (ie. 'query string' stuff) into getform
    ## and POST data into postform
    if os.environ['REQUEST_METHOD'].upper() == 'POST':
        postform = cgi.FieldStorage()
        # this is a cheat b/c most browsers don't PUT or DELETE too well
        action = postform.getfirst('action', 'POST').upper()
        os.environ['REQUEST_METHOD'] = 'GET'
    else:
        postform = {}
        action = 'GET'
    getform = cgi.FieldStorage()
    # my RESTful promise (also keeps people from causing trouble)
    if not action in ['GET', 'PUT', 'POST', 'DELETE']:
        action = 'GET'
    path = os.environ.get('PATH_INFO','/')
    dispatch(RootNode, action, path, getform, postform)



def dispatch(RootNode, action, path, getform, postform):
    ## FIXME:  this might do the wrong thing if there's a ?
    ##         in the wrong place in the query string
    # break up the path
    pieces = [ p for p in pathinfo[:pathinfo.index('?')].split('/') if p != '' ]
    # dispatch
    RootNode._dispatch(action, pieces, getform, postform)


class PywardHTTPRequestHandler(CGIServer.MyHTTPRequestHandler):

    RootNode = None

    def do_GET(self):
        path = self.path
        pieces = [ p for p in path[:path.index('?')].split('/') if p != '' ]
        self.RootNode._dispatch('GET', pieces, self.getvars, self.postvars)
        
    def do_POST(self):
        path = self.path
        pieces = [ p for p in path[:path.index('?')].split('/') if p != '' ]
	action = self.postvars.get('action', 'POST')
        # my RESTful promise (also keeps people from causing trouble)
        if not action in ['GET', 'PUT', 'POST', 'DELETE']:
            action = 'GET'
        self.RootNode._dispatch(action, pieces, self.getvars, self.postvars)


class RESTNode(object):

    # default way to map a child URI-space onto a RESTNode-descended class
    childClasses = {} 

    def makeChild(self, childname):
        # override this for dynamically-named resources
        try:
            return self.childClasses[childname]()
        except KeyError:
            return ErrorNotFound(childname)

    # these all get thier 'piece' as an arg to them
    def GET(self, query, body):
        pass

    POST = GET
    PUT = POST
    DELETE = PUT

    def _isAllowedTo(self, action):
        # authorization hook
        # action is always one of GET/PUT/POST/DELETE
        return 1

    # this is the internal dispatch mechanism
    def _dispatch(self, action, pieces, query, body):
        if len(pieces) < 1:
            # get the correct method
            if self._isAllowedTo(action):
                method = getattr(self, action)
            else:
                method = ErrorPermission(action, path).GET
            # call the method
            result = method(query, body)
            # make up blank headers if none specified
            if type(result) != type([]):
                result = ['', result]
            self._display(result[0], result[1])
        else:
            # find the correct child
            child = self.makeChild(pieces[0])
            # their turn to dispatch
            child._dispatch(action, pieces[1:], query, body)

    def _display(self, headers, bodytext):
        # make sure the results are displayable
        if headers.find("Content-type: ") < 0 :
            # supply a default Content-type if there's not one
            headers = 'Content-type: text/html\n' + headers
        # display the results
        print headers
        print '\n\n'
        print bodytext


class ErrorNotFound(RESTNode):

    def __init__(self, piece):
        self.piece = piece

    def GET(self, query, post):
        return ['Status: 404 Not Found', 
                "<h1>No Resource named %s Found.</h1>\n" % self.piece ]


class ErrorPermission(RESTNode):

    def __init__(self, action, path):
        self.args = (action, path)

    def GET(self, query, post):
        (action, path) = self.args
        return ['Status: 300',
                "<h1>Not allowed to %s %s. </h1>\n" % (action, path) ]

