# NOTE:
# At the moment this script borrows fairly heavily from newf, since that's 
# basically the bare minimum code required for a routed WSGI framework.

import cgi
import re

# limit exports
__all__ = [
    'HttpRequest', 'HttpResponse', 'WebApplication'
]

class HttpError(Exception):
    pass


class HttpRequest(object):
    """A single HTTP request"""
    def __init__(self, environ):
        self.method = environ['REQUEST_METHOD']
        
        # http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
        if self.method not in ('POST', 'GET', 'DELETE', 'PUT', 'HEAD', 'OPTIONS', 'TRACE'):
            raise HttpError, "Invalid request"
        
        self.POST = self.GET = {}
        
        if len(environ['QUERY_STRING']):
            self.GET = cgi.parse_qs(environ['QUERY_STRING'])
        
        if self.method == 'POST':
            self.POST = cgi.FieldStorage(fp=environ['wsgi.input'], environ=environ, keep_blank_values=1)
        
        # like PHP's $_REQUEST - but you should usually be more explicit
        self.REQUEST = self.GET.copy()
        self.REQUEST.update(self.POST)
    

class HttpResponse(object):
    """A single HTTP response"""
    status_codes = {
        200: "OK",
        201: "Created",
        202: "Accepted",
        301: "Moved Permanently",
        302: "Found",
        303: "See Other",
        304: "Not Modified",
        403: "Forbidden",
        404: "Not Found",
        405: "Method Not Allowed",
        410: "Gone",
        500: "Internal Server Error",
    }
    
    def __init__(self, content='', headers={}, status_code=200):
        self.status_code = status_code
        self.set_content(content)
        self._headers = headers
        self._headers['content-length'] = str(len(content))
        
        if not 'content-type' in self.headers:
            self._headers['content-type'] = 'text/html'
    
    def get_status(self):
        if self.status_code not in self.status_codes:
            self.status_code = 500
        return "%s %s" % (self.status_code, self.status_codes[self.status_code])
    
    def set_status(self, code):
        self.status_code = code
    
    def get_headers(self):
        return list(self._headers.iteritems())
    
    def set_headers(self, *args):
        """takes either a key/value or a dictionary"""
        if type(args[0]).__name__ == 'dict':
            self._headers.update(args[0])
        else:
            key, value = args
            self._headers[key] = value
    
    def get_content(self):
        return [self._content, '\n']
    
    def set_content(self, value):
        # http://www.python.org/dev/peps/pep-0333/#unicode-issues
        if isinstance(value, unicode):
            value = value.encode('utf-8')
        self._content = value
    
    content = property(get_content, set_content)
    status = property(get_status, set_status)
    headers = property(get_headers, set_headers)


class WebApplication(object):
    """The actual serving of Python code is done here"""
    def __init__(self, routes):
        # cache routes
        self.routes = [(re.compile(route_master(pair[0])), pair[1]) for pair in routes]
    
    def __call__(self, environ, start_response):
        request = HttpRequest(environ)
        response = None
        
        for pair in self.routes:
            route, view = pair
            matches = route.match(environ['PATH_INFO'])
            if matches:
                if hasattr(view, '__call__'):
                    to_call = view
                else:
                    # TODO: do this better
                    to_call = eval(view)
                try:
                    response = to_call(request, **matches.groupdict())
                except Exception, error:
                    # either the function failed, or it doesn't exist
                    # TODO: log missing functions
                    try:
                        # see if there's a user-defined error route
                        response = error500(request, error)
                    except NameError:
                        # custom 500 function missing, return generic
                        msg = '<h1>500 Error</h1><pre>%s</pre>' % error
                        response = HttpResponse(msg, status_code=500)
                    except Exception, e:
                        # custom 500 function there, but broken
                        msg = '<h1>500 Error</h1><pre>%s</pre>' % e
                        response = HttpResponse(msg, status_code=500)
                finally:
                    break
        
        if not isinstance(response, HttpResponse):
            try:
                response = error404(request)
            except Exception, e:
                response = HttpResponse('<h1>Page Not Found</h1>', status_code=404)
        start_response(response.status, response.get_headers())
        return response.content
    
    def dev_run(self, port=8000):
        from wsgiref.simple_server import make_server
        server = make_server('', port, self)
        print 'MNML now running on http://127.0.0.1:%s\n' % port
        server.serve_forever()
    

def route_master(route):
    """returns a compiled regular expression"""
    # chop off leading slash
    if route.startswith('/'):
        route = route[1:]
        
    trailing_slash = False
    # check end slash and remember to keep it
    if route.endswith('/'):
        route = route[:-1]
        trailing_slash = True
        
    # split into path components
    bits = route.split('/')
    
    # compiled match starts with a slash,
    #  so we make it a list so we can join later
    regex = ['']
    for path_component in bits:
        if path_component.startswith(':'):
            # it's a route, so compile
            name = path_component[1:]
            # accept only valid URL characters
            regex.append(r'(?P<%s>[-_a-zA-Z0-9+%%]+)' % name)
        else:
            # just a string/static path component
            regex.append(path_component)
            
    # stick the trailing slash back on
    if trailing_slash:
        regex.append('')
        
    # stitch it back together as a path
    return '^%s$' % '/'.join(regex)

if __name__ == '__main__':
    """run the basic server"""
    routes = (
        ('/', 'homepage'),
        ('/myview/:stuff/', 'other_thing')
    )
    
    def homepage(request):
        return HttpResponse('hello world!')
    
    def other_thing(request, stuff):
        return HttpResponse('found stuff: %s!' % stuff)
    
    web_app = WebApplication(routes)
    
    try:
        web_app.dev_run(8000)
    except KeyboardInterrupt:
        import sys
        print "Server stopping"
        sys.exit()

@@ -4896,24 +4896,155 @@
 ontent%0A    %0A
+    def run(self):%0A        from wsgiref.handlers import BaseHandler%0A        handler = BaseHandler()%0A        handler.run(self)%0A    %0A
     def dev_
@@ -5061,24 +5061,24 @@
 port=8000):%0A
-
         from
@@ -5229,16 +5229,33 @@
  %25 port%0A
+        try:%0A    
         
@@ -5273,24 +5273,111 @@
 e_forever()%0A
+        except:%0A            print 'MNML stopping...'%0A            server.socket.close()%0A
     %0A%0Adef ro

Add graceful exit from dev mode