constance

Scripts for generating (an earlier obsolete version of) my personal web site
git clone https://code.djc.id.au/git/constance/
commit 106057d9f532f6edb726942b3dd2e92970d5248c
parent efbd6cce6fda52db11fb43f9352192493684f627
Author: Dan Callaghan <djc@djc.id.au>
Date:   Thu,  5 Jun 2008 22:08:17 +1000

WSGI app using Colubrid

committer: Dan Callaghan <djc@djc.id.au>

--HG--
extra : convert_revision : 34da204ef0b7e84c119cc8fb5a6168dce23042e2

Diffstat:
Aapp.py | 28++++++++++++++++++++++++++++
Dhandler.py | 9---------
Alib/colubrid/__init__.py | 25+++++++++++++++++++++++++
Alib/colubrid/application.py | 371+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/colubrid/const.py | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/colubrid/debug.py | 984+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/colubrid/exceptions.py | 145+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/colubrid/reloader.py | 92+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/colubrid/request.py | 191+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/colubrid/response.py | 142+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/colubrid/server.py | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Alib/colubrid/utils.py | 363+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
12 files changed, 2474 insertions(+), 9 deletions(-)
diff --git a/app.py b/app.py
@@ -0,0 +1,28 @@
+import os
+from genshi.template import TemplateLoader
+from colubrid import RegexApplication, HttpResponse, execute
+from colubrid.exceptions import PageNotFound, HttpFound
+from colubrid.server import StaticExports
+
+from blog import BASE_DIR, Entry
+
+template_loader = TemplateLoader(os.path.join(BASE_DIR, 'templates'), auto_reload=True)
+
+class BlogApplication(RegexApplication):
+
+	urls = [(r'^$', 'index'), 
+			(r'^([^/]+)/?$', 'post')]
+	charset = 'utf-8'
+
+	def index(self):
+		return HttpResponse('blah')
+	
+	def post(self, id):
+		rendered = template_loader.load('post.xml').generate(entry=Entry(id)).render('xhtml')
+		return HttpResponse(rendered, [('Content-Type', 'text/html')], 200)
+
+app = BlogApplication
+app = StaticExports(app, {'static': os.path.join(BASE_DIR, 'static')})
+
+if __name__ == '__main__':
+	execute(app)
diff --git a/handler.py b/handler.py
@@ -1,9 +0,0 @@
-import os
-from genshi.template import TemplateLoader
-
-from blog import BASE_DIR, Entry
-
-template_loader = TemplateLoader(os.path.join(BASE_DIR, 'templates'), auto_reload=True)
-
-def post(id):
-	print template_loader.load('post.xml').generate(entry=Entry(id)).render('xhtml')
diff --git a/lib/colubrid/__init__.py b/lib/colubrid/__init__.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+"""
+    Colubrid WSGI Toolkit
+    ---------------------
+"""
+__version__ = '0.10'
+__author__  = 'Armin Ronacher <armin.ronacher@active-4.com>'
+__license__ = 'BSD LICENSE'
+
+#from colubrid.application import *
+#from colubrid.request import *
+#from colubrid.response import *
+#from colubrid.server import *
+#from colubrid import application
+#from colubrid import request
+#from colubrid import response
+#from colubrid import server
+
+#__all__ = (application.__all__ + request.__all__ + response.__all__ +
+#           server.__all__)
+
+from colubrid.application import *
+from colubrid.request import Request
+from colubrid.response import HttpResponse
+from colubrid.server import execute
diff --git a/lib/colubrid/application.py b/lib/colubrid/application.py
@@ -0,0 +1,371 @@
+# -*- coding: utf-8 -*-
+"""
+    Colubrid Base Applications
+    ==========================
+
+    This file provides a list of colubrid application. Each of them inherits
+    form BaseApplication and implements a full WSGI compatible web application.
+    
+    If you like to add your own you _have_ to inherit from BaseApplication or
+    the Request object wont work properly.
+
+    Since colubrid 0.10 you don't have to use those base application objects.
+    If you're looking for an example check out the `customapp.py`
+    application in the example folder.
+"""
+
+from __future__ import generators
+from colubrid.request import Request, ResponseRequest, RoutesRequest
+from colubrid.response import HttpResponse
+from colubrid.utils import fix_slash
+from colubrid.exceptions import HttpException, PageNotFound
+import re
+
+__all__ = ('BaseApplication', 'RegexApplication', 'ResolveRegexApplication',
+           'PathApplication', 'ObjectApplication', 'WebpyApplication',
+           'RoutesApplication')
+
+
+class RegexCompilerClass(type):
+    """
+    Metaclass that automatically compiles regular expressions in
+    the 'urls' attribute.
+    """
+
+    def __new__(cls, name, bases, dct):
+        result = type.__new__(cls, name, bases, dct)
+        if type(bases[0]) == type:
+            return result
+        if not hasattr(result, 'urls'):
+            raise TypeError('Regex application without url definition.')
+        compiled_urls = []
+        for args in result.urls:
+            args = list(args)
+            args[0] = re.compile(args[0])
+            compiled_urls.append(tuple(args))
+        result.urls = compiled_urls
+        return result
+
+
+class RoutesMapperClass(type):
+    """
+    Metaclass that automatically creates a Routes mapper.
+    """
+
+    def __new__(cls, name, bases, dct):
+        result = type.__new__(cls, name, bases, dct)
+        if type(bases[0]) == type:
+            return result
+        if not hasattr(result, 'mapping'):
+            raise TypeError('Route application without mapping.')
+        from routes import Mapper
+        mapper = Mapper()
+        controllers = {}
+        controller_map = {}
+        for m in result.mapping:
+            name = m[0].split('/', 1)[0]
+            internal = str(id(m[1]))
+            controllers[internal] = m[1]
+            controller_map[m[1]] = internal
+            kwargs = {}
+            if len(m) >= 3 and not m[2] is None:
+                kwargs['requirements'] = m[2]
+            if len(m) == 4:
+                kwargs.update(m[3])
+            mapper.connect(name, m[0], controller=internal, **kwargs)
+        mapper.create_regs(controllers.keys())
+        result._routes_mapper = mapper
+        result._routes_controllers = controllers
+        result._controller_map = controller_map
+        return result
+
+
+class BaseApplication(object):
+    """
+    Base class for Colubrid applications.
+    """
+
+    def __init__(self, environ, start_response, request_class=Request):
+        charset = 'utf-8'
+        if hasattr(self, 'charset'):
+            charset = self.charset
+        self.request = request_class(environ, start_response, charset)
+    
+    def process_http_exception(self, exc):
+        """Default routine to process a HttpException."""
+        return HttpResponse(exc.get_error_page(), exc.get_headers(), exc.code)
+    
+    def process_request(self):
+        """Process a request. Must be overridden."""
+        raise NotImplementedError()
+    
+    def __iter__(self):
+        try:
+            response = self.process_request()
+            if isinstance(self.request, ResponseRequest):
+                response = self.request
+            else:
+                assert isinstance(response, HttpResponse), \
+                       'process_request() must return a HttpResponse instance'
+        except HttpException, exc:
+            response = self.process_http_exception(exc)
+        return response(self.request)
+
+
+class RegexApplication(BaseApplication):
+    """
+    Application that maps URLs based on regular expressions.
+    """
+    __metaclass__ = RegexCompilerClass
+
+    def process_request(self):
+        """Process a single request."""
+        path_info = self.request.environ.get('PATH_INFO', '/')[1:]
+        if hasattr(self, 'slash_append') and self.slash_append:
+            fix_slash(self.request.environ, True)
+        for url, module in self.urls:
+            matchobj = url.search(path_info)
+            if not matchobj is None:
+                args = matchobj.groups()
+                if module in (True, False):
+                    return fix_slash(self.request.environ, module)
+                elif not '.' in module:
+                    handler = getattr(self, module)
+                else:
+                    parts = module.split('.')
+                    mname, fname = '.'.join(parts[:-1]), parts[-1]
+                    package = __import__(mname, '', '', [''])
+                    handler = getattr(package, fname)
+                    args = list(args)
+                    args.insert(0, self.request)
+                    args = tuple(args)
+                return handler(*args)
+        raise PageNotFound()
+
+
+class ResolveRegexApplication(BaseApplication):
+    """
+    Application that ...
+    """
+    __metaclass__ = RegexCompilerClass
+    
+    def process_request(self):
+        """Process a single request."""
+        path_info = self.request.environ.get('PATH_INFO', '/')[1:]
+        if hasattr(self, 'slash_append') and self.slash_append:
+            fix_slash(self.request.environ, True)
+        for url, module in self.urls:
+            matchobj = url.search(path_info)
+            if not matchobj is None:
+                args = matchobj.groups()
+                new_args = []
+                for pos, value in enumerate(args):
+                    search = '$%d' % (pos + 1)
+                    if search in module:
+                        module = module.replace(search, value.replace('.', '_'))
+                    else:
+                        new_args.append(value)
+                args = tuple(new_args)
+                if not '.' in module:
+                    if not hasattr(self, module):
+                        raise PageNotFound
+                    handler = getattr(self, module)
+                else:
+                    parts = module.split('.')
+                    mname, fname = '.'.join(parts[:-1]), parts[-1]
+                    try:
+                        package = __import__(mname, '', '', [''])
+                        handler = getattr(package, fname)
+                    except (ImportError, AttributeError):
+                        raise PageNotFound
+                    args = list(args)
+                    args.insert(0, self.request)
+                    args = tuple(args)
+                if handler in (True, False):
+                    return fix_slash(self.request.environ, handler)
+                return handler(*args)
+        raise PageNotFound()
+
+
+class WebpyApplication(BaseApplication):
+    """
+    Application compatible with web.py.
+    """
+    __metaclass__ = RegexCompilerClass
+    
+    def process_request(self):
+        """Process a single request."""
+        path_info = self.request.environ.get('PATH_INFO', '/')[1:]
+        if hasattr(self, 'slash_append') and self.slash_append:
+            fix_slash(self.request.environ, True)
+        for url, cls in self.urls:
+            matchobj = url.search(path_info)
+            if not matchobj is None:
+                cls = cls()
+                cls.request = self.request
+                handler = getattr(cls, self.request.environ['REQUEST_METHOD'])
+                if handler in (True, False):
+                    return fix_slash(self.request.environ, handler)
+                return handler(*matchobj.groups())
+        raise PageNotFound()
+
+
+class PathApplication(BaseApplication):
+    """
+    Application that dispatches based on the first path element.
+    """
+
+    def process_request(self):
+        """Process a single request."""
+        path_info = self.request.environ.get('PATH_INFO', '/').strip('/')
+        parts = path_info.strip('/').split('/')
+        if not len(parts) or not parts[0]:
+            handler = 'show_index'
+            args = ()
+        else:
+            handler = 'show_%s' % parts[0]
+            args = tuple(parts[1:])
+        if hasattr(self, handler):
+            return getattr(self, handler)(*args)
+        fix_slash(self.request.environ, True)
+        raise PageNotFound()
+
+
+class ObjectApplication(BaseApplication):
+    """
+    A rather complex application type.
+    It uses python class structures to handler the user requests.
+    
+    an ObjectApplication might look like this:
+    
+        class HelloWorld(object):
+            def index(self):
+                self.request.write('Hello World!')
+            def name(self, name="Nobody"):
+                self.request.write('Hello %s!' % name)
+        
+        class AdminPanel(object):
+            def index(self):
+                pass
+            def login(self):
+                pass
+        
+        class DispatcherApplication(ObjectApplication):
+            root = HelloWorld
+            root.admin = AdminPanel
+            
+        app = DispatcherApplication
+    
+    Let's say that the application listens on localhost:
+    
+        http://localhost/               --> HelloWorld.index()
+        http://localhost/name/          --> HelloWorld.name('Nobody')
+        http://localhost/name/Max       --> HelloWorld.name('Max')
+        http://localhost/admin/         --> AdminPanel.index()
+        http://localhost/admin/login    --> AdminPanel.login()
+    """
+
+    def process_request(self):
+        """Process a single request."""
+        if not hasattr(self, 'root'):
+            raise AttributeError, 'ObjectApplication requires a root object.'
+        
+        path = self.request.environ.get('PATH_INFO', '').strip('/')
+        parts = path.split('/')
+
+        # Resolve the path
+        handler = self.root
+        args = []
+        for part in parts:
+            if part.startswith('_'):
+                raise PageNotFound
+            node = getattr(handler, part, None)
+            if node is None:
+                if part:
+                    args.append(part)
+            else:
+                handler = node
+
+        container = None
+
+        # Find handler and make first container check
+        import inspect
+        if inspect.ismethod(handler):
+            if handler.__name__ == 'index':
+                # the handler is called index so it's the leaf of
+                # itself. we don't want a slash, even if forced
+                container = False
+        else:
+            index = getattr(handler, 'index', None)
+            if not index is None:
+                if not hasattr(index, 'container'):
+                    container = True
+                handler = index
+            else:
+                raise PageNotFound()
+
+        # update with hardcoded container information
+        if container is None and hasattr(handler, 'container'):
+            container = handler.container
+        
+        # Check for handler arguments and update container
+        handler_args, varargs, _, defaults = inspect.getargspec(handler)
+        if defaults is None:
+            defaults = 0
+        else:
+            defaults = len(defaults)
+
+        max_len = len(handler_args) - 1
+        min_len = max_len - defaults
+        cur_len = len(args)
+        if varargs:
+            max_len = -1
+
+        # check if the number of arguments fits our handler
+        if max_len == -1:
+            if cur_len < min_len:
+                raise PageNotFound
+        elif min_len <= cur_len <= max_len:
+            if container is None:
+                container = cur_len < max_len
+        else:
+            raise PageNotFound()
+
+        if container is None:
+            container = False
+        fix_slash(self.request.environ, container)
+
+        # call handler
+        parent = handler.im_class()
+        if hasattr(self, 'request'):
+            parent.request = self.request
+        return handler(parent, *args)
+
+
+class RoutesApplication(BaseApplication):
+    """
+    Application that uses Routes (http://routes.groovie.org/) to
+    dispatch URLs.
+    """
+    __metaclass__ = RoutesMapperClass
+
+    def __init__(self, environ, start_response):
+        def create_request(e, s, c):
+            return RoutesRequest(self, e, s, c)
+        super(RoutesApplication, self).__init__(environ, start_response,
+                                                create_request)
+        path = self.request.environ.get('PATH_INFO') or '/'
+        match = self._routes_mapper.match(path)
+        if match is None:
+            raise PageNotFound()
+        
+        handler = self._routes_controllers[match['controller']]
+        app = handler.im_class()
+        app.request = self.request
+        
+        if match['action'] == 'index':
+            del match['action']
+        del match['controller']
+
+        # XXX: can't return from __init__
+        return handler(app, **match)
diff --git a/lib/colubrid/const.py b/lib/colubrid/const.py
@@ -0,0 +1,53 @@
+# -*- coding: utf-8 -*-
+"""
+    Colubrid Constants
+"""
+
+HTTP_STATUS_CODES = {
+    100:    'CONTINUE',
+    101:    'SWITCHING PROTOCOLS',
+    102:    'PROCESSING',
+    200:    'OK',
+    201:    'CREATED',
+    202:    'ACCEPTED',
+    203:    'NON-AUTHORITATIVE INFORMATION',
+    204:    'NO CONTENT',
+    205:    'RESET CONTENT',
+    206:    'PARTIAL CONTENT',
+    207:    'MULTI STATUS',
+    300:    'MULTIPLE CHOICES',
+    301:    'MOVED PERMANENTLY',
+    302:    'FOUND',
+    303:    'SEE OTHER',
+    304:    'NOT MODIFIED',
+    305:    'USE PROXY',
+    306:    'RESERVED',
+    307:    'TEMPORARY REDIRECT',
+    400:    'BAD REQUEST',
+    401:    'UNAUTHORIZED',
+    402:    'PAYMENT REQUIRED',
+    403:    'FORBIDDEN',
+    404:    'NOT FOUND',
+    405:    'METHOD NOT ALLOWED',
+    406:    'NOT ACCEPTABLE',
+    407:    'PROXY AUTHENTICATION REQUIRED',
+    408:    'REQUEST TIMEOUT',
+    409:    'CONFLICT',
+    410:    'GONE',
+    411:    'LENGTH REQUIRED',
+    412:    'PRECONDITION FAILED',
+    413:    'REQUEST ENTITY TOO LARGE',
+    414:    'REQUEST-URI TOO LONG',
+    415:    'UNSUPPORTED MEDIA TYPE',
+    416:    'REQUESTED RANGE NOT SATISFIABLE',
+    417:    'EXPECTATION FAILED',
+    500:    'INTERNAL SERVER ERROR',
+    501:    'NOT IMPLEMENTED',
+    502:    'BAD GATEWAY',
+    503:    'SERVICE UNAVAILABLE',
+    504:    'GATEWAY TIMEOUT',
+    505:    'HTTP VERSION NOT SUPPORTED',
+    506:    'VARIANT ALSO VARIES',
+    507:    'INSUFFICIENT STORAGE',
+    510:    'NOT EXTENDED'
+}
diff --git a/lib/colubrid/debug.py b/lib/colubrid/debug.py
@@ -0,0 +1,984 @@
+# -*- coding: utf-8 -*-
+"""
+    Colubrid Debugging Module
+    =========================
+
+    Adds debug support to colubrid applications.
+"""
+from __future__ import generators
+import os
+import sys
+import re
+import traceback
+import keyword
+import token
+import tokenize
+import string
+import pprint
+import inspect
+import threading
+import cgi
+from random import random
+from cStringIO import StringIO
+from xml.sax.saxutils import escape
+
+JAVASCRIPT = r'''
+function toggleBlock(handler) {
+    if (handler.nodeName == 'H3') {
+        var table = handler;
+        do {
+            table = table.nextSibling;
+            if (typeof table == 'undefined') {
+                return;
+            }
+        }
+        while (table.nodeName != 'TABLE');
+    }
+    
+    else if (handler.nodeName == 'DT') {
+        var parent = handler.parentNode;
+        var table = parent.getElementsByTagName('TABLE')[0];
+    }
+    
+    var lines = table.getElementsByTagName("TR");
+    for (var i = 0; i < lines.length; i++) {
+        var line = lines[i];
+        if (line.className == 'pre' || line.className == 'post') {
+            line.style.display = (line.style.display == 'none') ? '' : 'none';
+        }
+        else if (line.parentNode.parentNode.className == 'vars' ||
+                 line.parentNode.parentNode.className == 'exec_code') {
+            line.style.display = (line.style.display == 'none') ? '' : 'none';
+            var input = line.getElementsByTagName('TEXTAREA');
+            if (input.length) {
+                input[0].focus();
+            }
+        }
+    }
+}
+
+function initTB() {
+    var tb = document.getElementById('wsgi-traceback');
+    var handlers = tb.getElementsByTagName('H3');
+    for (var i = 0; i < handlers.length; i++) {
+        toggleBlock(handlers[i]);
+        handlers[i].setAttribute('onclick', 'toggleBlock(this)');
+    }
+    handlers = tb.getElementsByTagName('DT');
+    for (var i = 0; i < handlers.length; i++) {
+        toggleBlock(handlers[i]);
+        handlers[i].setAttribute('onclick', 'toggleBlock(this)');
+    }
+    var handlers = tb.getElementsByTagName('TEXTAREA');
+    for (var i = 0; i < handlers.length; i++) {
+        var hid = handlers[i].getAttribute('id');
+        if (hid && hid.substr(0, 6) == 'input-') {
+            var p = handlers[i].getAttribute('id').split('-');
+            handlers[i].onkeyup = makeEnter(p[1], p[2]);
+        }
+    }
+}
+
+AJAX_ACTIVEX = ['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.4.0'];
+
+function ajaxConnect() {
+    var con = null;
+    try {
+        con = new XMLHttpRequest();
+    }
+    catch (e) {
+        if (typeof AJAX_ACTIVEX == 'string') {
+            con = new ActiveXObject(AJAX_ACTIVEX);
+        }
+        else {
+            for (var i=0; i < AJAX_ACTIVEX.length; i++) {
+                var axid = AJAX_ACTIVEX[i];
+                try {
+                    con = new ActiveXObject(axid);
+                }
+                catch (e) {}
+                if (con) {
+                    AJAX_ACTIVEX = axid;
+                    break;
+                }
+            }
+        }
+    }
+    return con;
+}
+
+function execCode(traceback, frame) {
+    var input = document.getElementById('input-' + traceback + '-' +
+                                        frame);
+    var e = encodeURIComponent;
+    var data = 'tb=' + e(traceback) + '&' +
+               'frame=' + e(frame) + '&' +
+               'code=' + e(input.value);
+    writeToOutput(traceback, frame, '>>> ' + input.value);
+    var con = ajaxConnect();
+    con.onreadystatechange = function() {
+        if (con.readyState == 4 && con.status == 200) {
+            writeToOutput(traceback, frame, con.responseText);
+            input.focus();
+            input.value = '';
+        }
+    };
+    con.open('GET', '__traceback__?' + data);
+    con.send(data);
+}
+
+function makeEnter(traceback, frame) {
+    return function(e) {
+        var e = (e) ? e : window.event;
+        var code = (e.keyCode) ? e.keyCode : e.which;
+        if (code == 13) {
+            var input = document.getElementById('input-' + traceback +
+                                                '-' + frame);
+            if (input.className == 'big') {
+                if (input.value.substr(input.value.length - 2) != '\n\n') {
+                    return;
+                }
+                input.value = input.value.substr(0, input.value.length - 1);
+                input.className = 'small';
+            }
+            if (input.value == 'clear\n') {
+                clearOutput(traceback, frame);
+                input.value = '';
+            }
+            else {
+                execCode(traceback, frame);
+            }
+        }
+    }
+}
+
+function writeToOutput(traceback, frame, text) {
+    var output = document.getElementById('output-' + traceback + '-' +
+                                         frame);
+    if (text && text != '\n') {
+        var node = document.createTextNode(text);
+        output.appendChild(node);
+    }
+}
+
+function clearOutput(traceback, frame) {
+    var output = document.getElementById('output-' + traceback + '-' +
+                                         frame);
+    output.innerHTML = '';
+}
+
+function toggleExtend(traceback, frame) {
+    var input = document.getElementById('input-' + traceback + '-' +
+                                        frame);
+    input.className = (input.className == 'small') ? 'big' : 'small';
+    input.focus();
+}
+
+function change_tb() {
+    interactive = document.getElementById('interactive');
+    plain = document.getElementById('plain');
+    interactive.style.display = ((interactive.style.display == 'block') | (interactive.style.display == '')) ? 'none' : 'block';
+    plain.style.display = (plain.style.display == 'block') ? 'none' : 'block';
+}
+'''
+
+STYLESHEET = '''
+body {
+  font-size:0.9em;
+}
+
+* {
+  margin:0;
+  padding:0;
+}
+
+#wsgi-traceback {
+  margin: 1em;
+  border: 1px solid #5F9CC4;
+  background-color: #F6F6F6;
+}
+
+.footer {
+  margin: 1em;
+  text-align: right;
+  font-style: italic;
+}
+
+h1 {
+  background-color: #3F7CA4;
+  font-size:1.2em;
+  color:#FFFFFF;
+  padding:0.3em;
+  margin:0 0 0.2em 0;
+}
+
+h2 {
+  background-color:#5F9CC4;
+  font-size:1em;
+  color:#FFFFFF;
+  padding:0.3em;
+  margin:0.4em 0 0.2em 0;
+}
+
+h2.tb {
+  cursor:pointer;
+}
+
+h3 {
+  font-size:1em;
+  cursor:pointer;
+}
+
+h3.fn {
+  margin-top: 0.5em;
+}
+
+h3.fn:hover:before {
+  content: "\\21D2   ";
+}
+
+h3.indent {
+  margin:0 0.7em 0 0.7em;
+  font-weight:normal;
+}
+
+p.text {
+  padding:0.1em 0.5em 0.1em 0.5em;
+}
+
+p.important {
+  font-weight: bold;
+}
+
+div.frame {
+  margin:0 1em 0 1em;
+}
+
+table.code {
+  margin:0.5em 0.7em 0.3em 0.7em;
+  background-color:#E0E0E0;
+  width:100%;
+  font-size:0.9em;
+  border:1px solid #C9C9C9;
+  border-collapse:collapse;
+}
+
+table.code td.lineno {
+  width:42px;
+  text-align:right;
+  padding:0 5px 0 0;
+  color:#444444;
+  border-right:1px solid #888888;
+}
+
+table.code td.code {
+  background-color:#EFEFEF;
+  padding:0 0 0 5px;
+  white-space:pre;
+}
+
+table.code tr.cur td.code {
+  background-color: #FAFAFA;
+  padding: 1px 0 1px 5px;
+  white-space: pre;
+}
+
+pre.plain {
+  margin:0.5em 1em 1em 1em;
+  padding:0.5em;
+  border:1px solid #999999;
+  background-color: #FFFFFF;
+  line-height: 120%;
+  font-family: monospace;
+}
+
+table.exec_code {
+  width:100%;
+  margin:0 1em 0 1em;
+}
+
+table.exec_code td.input {
+  width:100%;
+}
+
+table.exec_code textarea.small {
+  width:100%;
+  height:1.5em;
+  border:1px solid #999999;
+}
+
+table.exec_code textarea.big {
+  width:100%;
+  height:5em;
+  border:1px solid #999999;
+}
+
+table.exec_code input {
+  height:1.5em;
+  border:1px solid #999999;
+  background-color:#FFFFFF;
+}
+
+table.exec_code td.extend {
+  width:70px;
+  padding:0 5px 0 5px;
+}
+
+table.exec_code td.output pre {
+  font-family: monospace;
+  white-space: pre-wrap;       /* css-3 should we be so lucky... */
+  white-space: -moz-pre-wrap;  /* Mozilla, since 1999 */
+  white-space: -pre-wrap;      /* Opera 4-6 ?? */
+  white-space: -o-pre-wrap;    /* Opera 7 ?? */
+  word-wrap: break-word;       /* Internet Explorer 5.5+ */
+  _white-space: pre;   /* IE only hack to re-specify in addition to word-wrap  */
+}
+
+table.vars {
+  margin:0 1.5em 0 1.5em;
+  border-collapse:collapse;
+  font-size: 0.9em;
+}
+
+table.vars td {
+  font-family: 'Bitstream Vera Sans Mono', 'Courier New', monospace;
+  padding: 0.3em;
+  border: 1px solid #ddd;
+  vertical-align: top;
+  background-color: white;
+}
+
+table.vars .name {
+  font-style: italic;
+}
+
+table.vars .value {
+  color: #555;
+}
+
+table.vars th {
+  padding: 0.2em;
+  border: 1px solid #ddd;
+  background-color: #f2f2f2;
+  text-align: left;
+}
+
+#plain {
+  display: none;
+}
+
+dl dt {
+    padding: 0.2em 0 0.2em 1em;
+    font-weight: bold;
+    cursor: pointer;
+    background-color: #ddd;
+}
+
+dl dt:hover {
+    background-color: #bbb; color: white;
+}
+
+dl dd {
+    padding: 0 0 0 2em;
+    background-color: #eee;
+}
+
+span.p-kw {
+  font-weight:bold;
+}
+
+span.p-cmt {
+  color:#8CBF83;
+}
+
+span.p-str {
+  color:#DEA39B;
+}
+
+span.p-num {
+  color:#D2A2D6;
+}
+
+span.p-op {
+    color:#0000AA;
+}
+'''
+
+
+def get_uid():
+    return str(random()).encode('base64')[3:11]
+
+
+def get_frame_info(tb, context_lines=7):
+    """
+    Return a dict of informations about a given traceback.
+    """
+    # line numbers / function / variables
+    lineno = tb.tb_lineno
+    function = tb.tb_frame.f_code.co_name
+    variables = tb.tb_frame.f_locals
+
+    # get filename
+    fn = tb.tb_frame.f_globals.get('__file__')
+    if not fn:
+        fn = os.path.realpath(inspect.getsourcefile(tb) or
+                              inspect.getfile(tb))
+    if fn[-4:] in ('.pyc', '.pyo'):
+        fn = fn[:-1]
+
+    # module name
+    modname = tb.tb_frame.f_globals.get('__name__')
+
+    # get loader
+    loader = tb.tb_frame.f_globals.get('__loader__')
+
+    # sourcecode
+    try:
+        if not loader is None:
+            source = loader.get_source(modname)
+        else:
+            source = file(fn).read()
+    except:
+        source = ''
+        pre_context, post_context = [], []
+        context_line, context_lineno = None, None
+    else:
+        parser = PythonParser(source)
+        parser.parse()
+        parsed_source = parser.get_html_output()
+        lbound = max(0, lineno - context_lines - 1)
+        ubound = lineno + context_lines
+        try:
+            context_line = parsed_source[lineno - 1]
+            pre_context = parsed_source[lbound:lineno - 1]
+            post_context = parsed_source[lineno:ubound]
+        except IndexError:
+            context_line = None
+            pre_context = post_context = [], []
+        context_lineno = lbound
+
+    return {
+        'tb':               tb,
+        'filename':         fn,
+        'loader':           loader,
+        'function':         function,
+        'lineno':           lineno,
+        'vars':             variables,
+        'pre_context':      pre_context,
+        'context_line':     context_line,
+        'post_context':     post_context,
+        'context_lineno':   context_lineno,
+        'source':           source
+    }
+
+
+def debug_info(request, context=None, evalex=True):
+    """
+    Return debug info for the request
+    """
+    if context is None:
+        context = Namespace()
+
+    req_vars = []
+    for item in dir(request):
+        attr = getattr(request, item)
+        if not (item.startswith("_") or inspect.isroutine(attr)):
+            req_vars.append((item, attr))
+    req_vars.sort()
+
+    context.req_vars = req_vars
+    return DebugRender(context, evalex).render()
+
+
+def get_current_thread():
+    return threading.currentThread()
+
+
+class Namespace(object):
+    def __init__(self, **kwds):
+        self.__dict__.update(kwds)
+
+
+class ThreadedStream(object):
+    _orig = None
+
+    def __init__(self):
+        self._buffer = {}
+
+    def install(cls, environ):
+        if cls._orig or not environ['wsgi.multithread']:
+            return
+        cls._orig = sys.stdout
+        sys.stdout = cls()
+    install = classmethod(install)
+
+    def can_interact(cls):
+        return not cls._orig is None
+    can_interact = classmethod(can_interact)
+
+    def push(self):
+        tid = get_current_thread()
+        self._buffer[tid] = StringIO()
+
+    def release(self):
+        tid = get_current_thread()
+        if tid in self._buffer:
+            result = self._buffer[tid].getvalue()
+            del self._buffer[tid]
+        else:
+            result = ''
+        return result
+
+    def write(self, d):
+        tid = get_current_thread()
+        if tid in self._buffer:
+            self._buffer[tid].write(d)
+        else:
+            self._orig.write(d)
+
+
+class EvalContext(object):
+
+    def __init__(self, frm):
+        self.locals = frm.f_locals
+        self.globals = frm.f_globals
+
+    def exec_expr(self, s):
+        sys.stdout.push()
+        try:
+            try:
+                code = compile(s, '<stdin>', 'single', 0, 1)
+                exec code in self.globals, self.locals
+            except:
+                etype, value, tb = sys.exc_info()
+                tb = tb.tb_next
+                msg = ''.join(traceback.format_exception(etype, value, tb))
+                sys.stdout.write(msg)
+        finally:
+            output = sys.stdout.release()
+        return output
+
+
+class PythonParser(object):
+    """
+    Simple python sourcecode highlighter.
+    Usage::
+
+        p = PythonParser(source)
+        p.parse()
+        for line in p.get_html_output():
+            print line
+    """
+
+    _KEYWORD = token.NT_OFFSET + 1
+    _TEXT    = token.NT_OFFSET + 2
+    _classes = {
+        token.NUMBER:       'num',
+        token.OP:           'op',
+        token.STRING:       'str',
+        tokenize.COMMENT:   'cmt',
+        token.NAME:         'id',
+        token.ERRORTOKEN:   'error',
+        _KEYWORD:           'kw',
+        _TEXT:              'txt',
+    }
+
+    def __init__(self, raw):
+        self.raw = raw.expandtabs(8).strip()
+        self.out = StringIO()
+
+    def parse(self):
+        self.lines = [0, 0]
+        pos = 0
+        while 1:
+            pos = string.find(self.raw, '\n', pos) + 1
+            if not pos: break
+            self.lines.append(pos)
+        self.lines.append(len(self.raw))
+
+        self.pos = 0
+        text = StringIO(self.raw)
+        try:
+            tokenize.tokenize(text.readline, self)
+        except tokenize.TokenError:
+            pass
+
+    def get_html_output(self):
+        """ Return line generator. """
+        def html_splitlines(lines):
+            # this cool function was taken from trac.
+            # http://projects.edgewall.com/trac/
+            open_tag_re = re.compile(r'<(\w+)(\s.*)?[^/]?>')
+            close_tag_re = re.compile(r'</(\w+)>')
+            open_tags = []
+            for line in lines:
+                for tag in open_tags:
+                    line = tag.group(0) + line
+                open_tags = []
+                for tag in open_tag_re.finditer(line):
+                    open_tags.append(tag)
+                open_tags.reverse()
+                for ctag in close_tag_re.finditer(line):
+                    for otag in open_tags:
+                        if otag.group(1) == ctag.group(1):
+                            open_tags.remove(otag)
+                            break
+                for tag in open_tags:
+                    line += '</%s>' % tag.group(1)
+                yield line
+                
+        return list(html_splitlines(self.out.getvalue().splitlines()))
+            
+
+    def __call__(self, toktype, toktext, (srow,scol), (erow,ecol), line):
+        oldpos = self.pos
+        newpos = self.lines[srow] + scol
+        self.pos = newpos + len(toktext)
+
+        if toktype in [token.NEWLINE, tokenize.NL]:
+            self.out.write('\n')
+            return
+
+        if newpos > oldpos:
+            self.out.write(self.raw[oldpos:newpos])
+
+        if toktype in [token.INDENT, token.DEDENT]:
+            self.pos = newpos
+            return
+
+        if token.LPAR <= toktype and toktype <= token.OP:
+            toktype = token.OP
+        elif toktype == token.NAME and keyword.iskeyword(toktext):
+            toktype = self._KEYWORD
+        clsname = self._classes.get(toktype, 'txt')
+
+        self.out.write('<span class="code-item p-%s">' % clsname)
+        self.out.write(escape(toktext))
+        self.out.write('</span>')
+
+
+class DebugRender(object):
+
+    def __init__(self, context, evalex):
+        self.c = context
+        self.evalex = evalex
+        
+    def render(self):
+        return '\n'.join([
+            self.header(),
+            self.traceback(),
+            self.request_information(),
+            self.footer()
+        ])
+        
+    def header(self):
+        data = [
+            '<script type="text/javascript">%s</script>' % JAVASCRIPT,
+            '<style type="text/css">%s</style>' % STYLESHEET,
+            '<div id="wsgi-traceback">'
+        ]
+        
+        if hasattr(self.c, 'exception_type'):
+            title = escape(self.c.exception_type)
+            exc = escape(self.c.exception_value)
+            data += [
+                '<h1>%s</h1>' % title,
+                '<p class="text important">%s</p>' % exc
+            ]
+
+        if hasattr(self.c, 'last_frame'):
+            data += [
+                '<p class="text important">%s in %s, line %s</p>' % (
+                self.c.last_frame['filename'], self.c.last_frame['function'],
+                self.c.last_frame['lineno'])
+            ]
+
+        return '\n'.join(data)
+
+    def render_code(self, frame):
+        def render_line(mode, lineno, code):
+            return ''.join([
+                '<tr class="%s">' % mode,
+                '<td class="lineno">%i</td>' % lineno,
+                '<td class="code">%s</td></tr>' % code
+            ])
+
+        tmp = ['<table class="code">']
+        lineno = frame['context_lineno']
+        if not lineno is None:
+            lineno += 1
+            for l in frame['pre_context']:
+                tmp.append(render_line('pre', lineno, l))
+                lineno += 1
+            tmp.append(render_line('cur', lineno, frame['context_line']))
+            lineno += 1
+            for l in frame['post_context']:
+                tmp.append(render_line('post', lineno, l))
+                lineno += 1
+        else:
+            tmp.append(render_line('cur', 1, 'Sourcecode not available'))
+        tmp.append('</table>')
+        
+        return '\n'.join(tmp)
+        
+    def var_table(self, var):
+        # simple data types
+        if isinstance(var, basestring) or isinstance(var, float)\
+           or isinstance(var, int) or isinstance(var, long):
+            return ('<table class="vars"><tr><td class="value">%r'
+                    '</td></tr></table>' % var)
+        
+        # dicts
+        if isinstance(var, dict) or hasattr(var, 'items'):
+            items = var.items()
+            items.sort()
+
+            # empty dict
+            if not items:
+                return ('<table class="vars"><tr><th>no data given'
+                        '</th></tr></table>')
+        
+            result = ['<table class="vars"><tr><th>Name'
+                      '</th><th>Value</th></tr>']
+            for key, value in items:
+                try:
+                    val = escape(pprint.pformat(value))
+                except:
+                    val = '?'
+                result.append('<tr><td class="name">%s</td><td class="value">%s'
+                              '</td></tr>' % (escape(repr(key)), val))
+            result.append('</table>')
+            return '\n'.join(result)
+
+        # lists
+        if isinstance(var, list):
+            # empty list
+            if not var:
+                return ('<table class="vars"><tr><th>no data given'
+                        '</th></tr></table>')
+
+            result = ['<table class="vars">']
+            for line in var:
+                try:
+                    val = escape(pprint.pformat(line))
+                except:
+                    val = '?'
+                result.append('<tr><td class="value">%s</td></tr>' % (val))
+            result.append('</table>')
+            return '\n'.join(result)
+        
+        # unknown things
+        try:
+            value = escape(repr(var))
+        except:
+            value = '?'
+        return '<table class="vars"><tr><th>%s</th></tr></table>' % value
+
+    def exec_code_table(self, uid):
+        return '''
+        <table class="exec_code">
+          <tr>
+            <td class="output" colspan="2"><pre id="output-%(tb_uid)s-%(frame_uid)s"></pre></td>
+           </tr>
+          <tr>
+            <td class="input">
+              <textarea class="small" id="input-%(tb_uid)s-%(frame_uid)s" value=""></textarea>
+            </td>
+            <td class="extend">
+              <input type="button" onclick="toggleExtend('%(tb_uid)s', '%(frame_uid)s')" value="extend">
+            </td>
+          </tr>
+        </table>
+        ''' % {
+            'target': '#',
+            'tb_uid': self.c.tb_uid,
+            'frame_uid': uid
+        }
+
+    def traceback(self):
+        if not hasattr(self.c, 'frames'):
+            return ''
+
+        result = ['<h2 onclick="change_tb()" class="tb">Traceback (click to switch to raw view)</h2>']
+        result.append('<div id="interactive"><p class="text">A problem occurred in your Python WSGI'
+        ' application. Here is the sequence of function calls leading up to'
+        ' the error, in the order they occurred. Click on a header to show'
+        ' context lines.</p>')
+        
+        for num, frame in enumerate(self.c.frames):
+            line = [
+                '<div class="frame" id="frame-%i">' % num,
+                '<h3 class="fn">%s in %s</h3>' % (frame['function'],
+                                                  frame['filename']),
+                self.render_code(frame),
+            ]
+                
+            if frame['vars']:
+                line.append('\n'.join([
+                    '<h3 class="indent">&rArr; local variables</h3>',
+                    self.var_table(frame['vars'])
+                ]))
+
+            if self.evalex and self.c.tb_uid:
+                line.append('\n'.join([
+                    '<h3 class="indent">&rArr; execute code</h3>',
+                    self.exec_code_table(frame['frame_uid'])
+                ]))
+            
+            line.append('</div>')
+            result.append(''.join(line))
+        result.append('\n'.join([
+            '</div>',
+            self.plain()
+        ]))
+        return '\n'.join(result)
+
+    def plain(self):
+        if not hasattr(self.c, 'plaintb'):
+            return ''
+        return '''
+        <div id="plain">
+        <p class="text">Here is the plain Python traceback for copy and paste:</p>
+        <pre class="plain">\n%s</pre>
+        </div>
+        ''' % self.c.plaintb
+        
+    def request_information(self):
+        result = [
+            '<h2>Request Data</h2>',
+            '<p class="text">The following list contains all important',
+            'request variables. Click on a header to expand the list.</p>'
+        ]
+
+        if not hasattr(self.c, 'frames'):
+            del result[0]
+        
+        for key, info in self.c.req_vars:
+            result.append('<dl><dt>%s</dt><dd>%s</dd></dl>' % (
+                escape(key), self.var_table(info)
+            ))
+        
+        return '\n'.join(result)
+        
+    def footer(self):
+        return '\n'.join([
+            '<script type="text/javascript">initTB();</script>',
+            '</div>',
+            '<div class="footer">Brought to you by '
+                '<span style="font-style: normal">DON\'T PANIC</span>, your friendly '
+                'Colubrid traceback interpreter system.</div>',
+            hasattr(self.c, 'plaintb')
+                and ('<!-- Plain traceback:\n\n%s-->' % self.c.plaintb)
+                or '',
+        ])
+        
+
+
+class DebuggedApplication(object):
+    """
+    Enables debugging support for a given application::
+
+        from colubrid.debug import DebuggedApplication
+        from myapp import app
+        app = DebuggedApplication(app)
+
+    Or for a whole package::
+        
+        app = DebuggedApplication("myapp:app")
+    """
+
+    def __init__(self, application, evalex=True):
+        self.evalex = bool(evalex)
+        if not isinstance(application, basestring):
+            self.application = application
+        else:
+            try:
+                self.module, self.handler = application.split(':', 1)
+            except ValueError:
+                self.module = application
+                self.handler = 'app'
+        self.tracebacks = {}
+    
+    def __call__(self, environ, start_response):
+        # exec code in open tracebacks
+        if self.evalex and environ.get('PATH_INFO', '').strip('/').endswith('__traceback__'):
+            parameters = cgi.parse_qs(environ['QUERY_STRING'])
+            try:
+                tb = self.tracebacks[parameters['tb'][0]]
+                frame = parameters['frame'][0]
+                context = tb[frame]
+                code = parameters['code'][0].replace('\r','')
+            except (IndexError, KeyError):
+                pass
+            else:
+                result = context.exec_expr(code)
+                start_response('200 OK', [('Content-Type', 'text/plain')])
+                yield result
+                return
+        appiter = None
+        try:
+            if hasattr(self, 'application'):
+                result = self.application(environ, start_response)
+            else:
+                module = __import__(self.module, '', '', [''])
+                app = getattr(module, self.handler)
+                result = app(environ, start_response)
+            appiter = iter(result)
+            for line in appiter:
+                yield line
+        except:
+            ThreadedStream.install(environ)
+            exc_info = sys.exc_info()
+            try:
+                headers = [('Content-Type', 'text/html')]
+                start_response('500 INTERNAL SERVER ERROR', headers)
+            except:
+                pass
+            debug_context = self.get_debug_context(exc_info)
+            yield debug_info(environ.get('colubrid.request'), debug_context, self.evalex)
+        
+        if hasattr(appiter, 'close'):
+            appiter.close()
+
+    def get_debug_context(self, exc_info):
+        exception_type, exception_value, tb = exc_info
+        # skip first internal frame
+        if not tb.tb_next is None:
+            tb = tb.tb_next
+        plaintb = ''.join(traceback.format_exception(*exc_info))
+
+        # load frames
+        frames = []
+        frame_map = {}
+        tb_uid = None
+        if ThreadedStream.can_interact():
+            tb_uid = get_uid()
+            frame_map = self.tracebacks[tb_uid] = {}
+
+        # walk through frames and collect informations
+        while tb is not None:
+            if tb_uid:
+                frame_uid = get_uid()
+                frame_map[frame_uid] = EvalContext(tb.tb_frame)
+            else:
+                frame_uid = None
+            frame = get_frame_info(tb)
+            frame['frame_uid'] = frame_uid
+            frames.append(frame)
+            tb = tb.tb_next
+
+        if exception_type.__module__ == "exceptions":
+            extypestr = exception_type.__name__
+        else:
+            extypestr = str(exception_type)
+
+        return Namespace(
+            exception_type =  extypestr,
+            exception_value = str(exception_value),
+            frames =          frames,
+            last_frame =      frames[-1],
+            plaintb =         plaintb,
+            tb_uid =          tb_uid,
+            frame_map =       frame_map
+        )
+
diff --git a/lib/colubrid/exceptions.py b/lib/colubrid/exceptions.py
@@ -0,0 +1,145 @@
+# -*- coding: utf-8 -*-
+"""
+    Colubrid Exceptions
+    ===================
+
+    Since paste covers that too this is only a "redirection module".
+    Because colubrid may change the error interface later it's
+    better to use the mapped names instead of the paste.httpexceptions
+    module.
+"""
+
+__all__ = ['PageNotFound', 'PageGone', 'AccessDenied', 'BadRequest',
+           'RequestTimeout', 'ServerError', 'HttpRedirect', 'HttpFound',
+           'HttpMoved']
+           
+
+ERROR_PAGE_TEMPLATE = """\
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+ <head>
+  <title>%(code)s %(title)s</title>
+  <style type="text/css">
+    body {
+        font-family: sans-serif;
+        margin: 2em;
+        padding: 0;
+    }
+    a, h1 {
+        color: #cc0000;
+    }
+    div.content {
+        margin: 1em 3em 2em 2em;
+    }
+    address {
+        border-top: 1px solid #ccc;
+        padding: 0.3em;
+    }
+  </style>
+ </head>
+ <body>
+<h1>%(title)s</h1>
+<div class="content">%(msg)s</div>
+<address>powered by colubrid %(version)s</address>
+</body></html>
+"""
+
+
+class HttpException(Exception):
+    """Base for HTTP exceptions. Not to be used directly."""
+    code = None
+    title = None
+    msg = ''
+    headers = None
+
+    def get_error_page(self):
+        from colubrid.utils import get_version
+        from cgi import escape
+        return ERROR_PAGE_TEMPLATE % {
+            'code':     self.code,
+            'title':    escape(self.title),
+            'msg':      escape(self.msg),
+            'version':  get_version()
+        }
+
+    def get_headers(self):
+        if not self.headers:
+            return []
+        return self.headers[:]
+
+    def __repr__(self):
+        return '<%s %d>' % (self.__class__.__name__, self.code)
+
+
+class HttpMove(HttpException):
+    """Automatically add a "Location:" header to the result."""
+    msg = 'The resource has been moved to %s.'
+    
+    def __init__(self, url):
+        self.headers = [('Location', url)]
+        if '%s' in self.msg:
+            self.msg = self.msg % url
+
+
+class PageNotFound(HttpException):
+    """HTTP 404."""
+    code = 404
+    title = 'Not Found'
+    msg = 'The resource could not be found.'
+
+
+class PageGone(HttpException):
+    """HTTP 410."""
+    code = 410
+    title = 'Gone'
+    msg = ('This resource is no longer available. '
+           'No forwarding address is given.')
+
+
+class AccessDenied(HttpException):
+    """HTTP 403."""
+    code = 403
+    title = 'Forbidden'
+    msg = 'Access was denied to this resource.'
+
+
+class BadRequest(HttpException):
+    """HTTP 400."""
+    code = 400
+    title = 'Bad Request'
+    msg = 'The server could not comply with the request since it is either '\
+          'malformed or wtherwise incorrect.'
+
+
+class RequestTimeout(HttpException):
+    """HTTP 408."""
+    code = 408
+    title = 'Request Timeout'
+    msg = 'There was a conflict when trying to complete your request.'
+
+
+class ServerError(HttpException):
+    """HTTP 500."""
+    code = 500
+    title = 'Internal Server Error'
+    msg = 'The server has either erred or is inapable of performing '\
+          'the requested operation.'
+
+
+class HttpRedirect(HttpMove):
+    """HTTP 307."""
+    code = 307
+    title = 'Temporary Redirect'
+
+
+class HttpFound(HttpMove):
+    """HTTP 302."""
+    code = 302
+    title = 'Found'
+    msg = 'The resource was found at %s.'
+
+
+class HttpMoved(HttpMove):
+    """HTTP 301."""
+    code = 301
+    title = 'Moved Permanently'
diff --git a/lib/colubrid/reloader.py b/lib/colubrid/reloader.py
@@ -0,0 +1,92 @@
+# -*- coding: utf-8 -*-
+"""
+    Reloader Module
+    ===============
+
+    Taken from django, which took it from cherrypy / paste
+"""
+# Portions copyright (c) 2004, CherryPy Team (team@cherrypy.org)
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+#     * Redistributions of source code must retain the above copyright notice,
+#       this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above copyright notice,
+#       this list of conditions and the following disclaimer in the documentation
+#       and/or other materials provided with the distribution.
+#     * Neither the name of the CherryPy Team nor the names of its contributors
+#       may be used to endorse or promote products derived from this software
+#       without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import os
+import sys
+import thread
+import time
+
+RUN_RELOADER = True
+
+def reloader_thread(watch):
+    """This thread watches Python and "watch" files and reloads the
+    application if something changes."""
+    mtimes = {}
+    win = sys.platform == 'win32'
+    
+    while RUN_RELOADER:
+        for filename in [getattr(m, '__file__', '') for m
+                         in sys.modules.values()] + watch:
+            if filename[-4:] in ('.pyo', '.pyc'):
+                filename = filename[:-1]
+            if not os.path.exists(filename):
+                continue
+            stat = os.stat(filename)
+            mtime = stat.st_mtime
+            if win:
+                mtime -= stat.st_ctime
+            if filename not in mtimes:
+                mtimes[filename] = mtime
+                continue
+            if mtime != mtimes[filename]:
+                sys.exit(3) # force reload
+        time.sleep(1)
+
+
+def restart_with_reloader():
+    """Spawn a new Python interpreter with the same arguments as this one,
+    but running the reloader thread."""
+    while True:
+        args = [sys.executable] + sys.argv
+        if sys.platform == 'win32':
+            args = ['"%s"' % arg for arg in args]
+        new_environ = os.environ.copy()
+        new_environ['RUN_MAIN'] = 'true'
+        exit_code = os.spawnve(os.P_WAIT, sys.executable, args, new_environ)
+        if exit_code != 3:
+            return exit_code
+
+
+def main(main_func, watch=[]):
+    """Call this to initialize the reloader."""
+    if os.environ.get('RUN_MAIN') == 'true':
+        thread.start_new_thread(main_func, ())
+        try:
+            reloader_thread(watch)
+        except KeyboardInterrupt:
+            pass
+    else:
+        try:
+            sys.exit(restart_with_reloader())
+        except KeyboardInterrupt:
+            pass
diff --git a/lib/colubrid/request.py b/lib/colubrid/request.py
@@ -0,0 +1,191 @@
+# -*- coding: utf-8 -*-
+"""
+    Colubrid Request Object
+    =======================
+"""
+
+from __future__ import generators
+from colubrid.utils import MultiDict, MergedMultiDict, FieldStorage,\
+                           get_full_url
+from colubrid.response import HttpResponse
+
+import posixpath
+import cgi
+import email
+from urllib import quote
+from email.Message import Message as MessageType
+from cStringIO import StringIO
+from Cookie import SimpleCookie
+
+
+__all__ = ('Request', 'ResponseRequest')
+
+
+class Request(object):
+    """
+    The central Request object. It stores all data coming from
+    the HTTP client.
+    """
+
+    def __init__(self, environ, start_response, charset='utf-8'):
+        self.charset = charset
+        self.start_response = start_response
+        self.environ = environ
+        self.environ['REQUEST_URI'] = get_full_url(self.environ)
+
+        # copy a reference to the request object
+        # into the environ so wsgi middlewares
+        # can communicate with it.
+        environ['colubrid.request'] = self
+
+        # get absolute path to script
+        root = self.environ.get('SCRIPT_NAME', '/')
+        if not root or not root.startswith('/'):
+            root = '/' + root
+        self.environ['SCRIPT_ROOT'] = root
+
+        # get the full application request
+        url = ''.join([
+            quote(self.environ['SCRIPT_NAME']),
+            quote(self.environ.get('PATH_INFO', ''))
+        ])
+        if not url.startswith('/'):
+            url = '/' + url
+        self.environ['APPLICATION_REQUEST'] = url
+
+    def read(self, *args):
+        if not hasattr(self, '_buffered_stream'):
+            self._buffered_stream = StringIO(self.data)
+        return self._buffered_stream.read(*args)
+
+    def readline(self, *args):
+        if not hasattr(self, '_buffered_stream'):
+            self._buffered_stream = StringIO(self.data)
+        return self._buffered_stream.readline(*args)
+
+    def readlines(self, *args):
+        while True:
+            line = self.readline(*args)
+            if not line:
+                raise StopIteration()
+            yield line
+
+    def _load_post_data(self):
+        self._data = ''
+        if self.environ['REQUEST_METHOD'] == 'POST':
+            maxlen = int(self.environ['CONTENT_LENGTH'])
+            self._data = self.environ['wsgi.input'].read(maxlen)
+            if self.environ.get('CONTENT_TYPE', '').startswith('multipart'):
+                lines = ['Content-Type: %s' %
+                         self.environ.get('CONTENT_TYPE', '')]
+                for key, value in self.environ.items():
+                    if key.startswith('HTTP_'):
+                        lines.append('%s: %s' % (key, value))
+                raw = '\r\n'.join(lines) + '\r\n\r\n' + self._data
+                msg = email.message_from_string(raw)
+                self._post = MultiDict()
+                self._files = MultiDict()
+                for sub in msg.get_payload():
+                    if not isinstance(sub, MessageType):
+                        continue
+                    name_dict = cgi.parse_header(sub['Content-Disposition'])[1]
+                    if 'filename' in name_dict:
+                        payload = sub.get_payload()
+                        filename = name_dict['filename']
+                        if isinstance(payload, list) or not filename.strip():
+                            continue
+                        filename = name_dict['filename']
+                        #XXX: fixes stupid ie bug but can cause problems
+                        filename = filename[filename.rfind('\\') + 1:]
+                        if 'Content-Type' in sub:
+                            content_type = sub['Content-Type']
+                        else:
+                            content_type = None
+                        s = FieldStorage(name_dict['name'], filename,
+                                         content_type, payload)
+                        self._files.appendlist(name_dict['name'], s)
+                    else:
+                        value = sub.get_payload()
+                        if not self.charset is None:
+                            value = value.decode(self.charset, 'ignore')
+                        self._post.appendlist(name_dict['name'], value)
+            else:
+                d = cgi.parse_qs(self._data, True)
+                if not self.charset is None:
+                    for key, value in d.iteritems():
+                        d[key] = [i.decode(self.charset, 'ignore')
+                                  for i in value]
+                self._post = MultiDict(d)
+                self._files = MultiDict()
+        else:
+            self._post = MultiDict()
+            self._files = MultiDict()
+
+    def args(self):
+        if not hasattr(self, '_get'):
+            query = cgi.parse_qs(self.environ.get('QUERY_STRING', ''), True)
+            if not self.charset is None:
+                for key, value in query.iteritems():
+                    query[key] = [i.decode(self.charset, 'ignore')
+                                  for i in value]
+            self._get = MultiDict(query)
+        return self._get
+
+    def form(self):
+        if not hasattr(self, '_post'):
+            self._load_post_data()
+        return self._post
+
+    def values(self):
+        if not hasattr(self, '_values'):
+            self._values = MergedMultiDict(self.args, self.form)
+        return self._values
+
+    def files(self):
+        if not hasattr(self, '_files'):
+            self._load_post_data()
+        return self._files
+
+    def cookies(self):
+        if not hasattr(self, '_cookie'):
+            self._cookie = SimpleCookie()
+            self._cookie.load(self.environ.get('HTTP_COOKIE', ''))
+        return self._cookie
+
+    def data(self):
+        if not hasattr(self, '_data'):
+            self._load_post_data()
+        return self._data
+
+    args = property(args, doc='url paramters')
+    form = property(form, doc='form data')
+    files = property(files, doc='submitted files')
+    values = property(values, doc='url parameters and form data')
+    cookies = property(cookies, doc='cookies')
+    data = property(data, doc='raw value of input stream')
+
+
+class ResponseRequest(Request, HttpResponse):
+    """
+    A Request that's a Response too. This way users can call
+    request.write() etc.
+    """
+
+    def __init__(self, environ, start_response, charset='utf-8'):
+        Request.__init__(self, environ, start_response, charset)
+        HttpResponse.__init__(self, [], [], 200)
+
+
+class RoutesRequest(Request):
+
+    def __init__(self, app, environ, start_response, charset='utf-8'):
+        super(RoutesRequest, self).__init__(environ, start_response, charset)
+        self.app = app
+
+    def link_to(self, __controller__, **kwargs):
+        controller = self.app._controller_map.get(__controller__)
+        root = self.environ['SCRIPT_ROOT']
+        link = self.app._routes_map.generate(controller, **kwargs)
+        if link is None:
+            return root
+        return posixpath.join(root, link[1:])
diff --git a/lib/colubrid/response.py b/lib/colubrid/response.py
@@ -0,0 +1,142 @@
+# -*- coding: utf-8 -*-
+"""
+    Colubrid Response
+"""
+from __future__ import generators
+from colubrid.utils import HttpHeaders
+from colubrid.const import HTTP_STATUS_CODES
+from Cookie import SimpleCookie
+from datetime import datetime
+from time import gmtime
+
+
+__all__ = ('HttpResponse',)
+
+
+class HttpResponse(object):
+    """
+    The Response object is used to collect the data to be written
+    back to the HTTP client.
+    """
+
+    def __init__(self, response=None, headers=None, status=200):
+        if response is None:
+            self.response = []
+        elif isinstance(response, basestring):
+            self.response = [response]
+        else:
+            self.response = response
+        if headers is None:
+            self.headers = HttpHeaders([])
+        elif isinstance(headers, list):
+            self.headers = HttpHeaders(headers)
+        elif isinstance(headers, HttpHeaders):
+            self.headers = headers
+        else:
+            raise TypeError('invalid header format')
+        self.status = status
+        self._cookies = None
+
+    def __setitem__(self, name, value):
+        self.headers[name] = value
+
+    def __getitem__(self, name):
+        self.headers.get(name)
+
+    def __delitem__(self, name):
+        del self.headers[name]
+
+    def __contains__(self, name):
+        return name in self.headers
+
+    def __len__(self):
+        if isinstance(self.response, list):
+            length = 0
+            for item in self.response:
+                length += len(item)
+            return length
+        try:
+            return len(self.response)
+        except:
+            return 0
+
+    def write(self, d):
+        if not isinstance(self.response, list):
+            raise TypeError('read only or dynamic response object')
+        elif isinstance(d, basestring):
+            self.response.append(d)
+        else:
+            raise TypeError('str or unicode required')
+
+    def set_cookie(self, key, value='', max_age=None, expires=None,
+                   path='/', domain=None, secure=None):
+        if self._cookies is None:
+            self._cookies = SimpleCookie()
+        self._cookies[key] = value
+        if not max_age is None:
+            self._cookies[key]['max-age'] = max_age
+        if not expires is None:
+            if isinstance(expires, basestring):
+                self._cookies[key]['expires'] = expires
+                expires = None
+            elif isinstance(expires, datetime):
+                expires = expires.utctimetuple()
+            elif not isinstance(expires, (int, long)):
+                expires = gmtime(expires)
+            else:
+                raise ValueError('datetime or integer required')
+            if not expires is None:
+                now = gmtime()
+                month = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul',
+                         'Aug', 'Sep', 'Oct', 'Nov', 'Dec'][now.tm_mon - 1]
+                day = ['Monday', 'Tuesday', 'Wednesday', 'Thursday',
+                       'Friday', 'Saturday', 'Sunday'][expires.tm_wday]
+                date = '%02d-%s-%s' % (
+                    now.tm_mday, month, str(now.tm_year)[-2:]
+                )
+                d = '%s, %s %02d:%02d:%02d GMT' % (day, date, now.tm_hour,
+                                                   now.tm_min, now.tm_sec)
+                self._cookies[key]['expires'] = d
+        if not path is None:
+            self._cookies[key]['path'] = path
+        if not domain is None:
+            self._cookies[key]['domain'] = domain
+        if not secure is None:
+            self._cookies[key]['secure'] = secure
+
+    def delete_cookie(self, key):
+        if self._cookies is None:
+            self._cookies = SimpleCookie()
+        if not key in self._cookies:
+            self._cookies[key] = ''
+        self._cookies[key]['max-age'] = 0
+
+    def __call__(self, request):
+        if not 'Content-Type' in self.headers:
+            self.headers['Content-Type'] = 'text/html; charset=%s' % \
+                                           str(request.charset)
+        headers = self.headers.get()
+        if not self._cookies is None:
+            for morsel in self._cookies.values():
+                headers.append(('Set-Cookie', morsel.output(header='')))
+        status = '%d %s' % (self.status,
+                            HTTP_STATUS_CODES.get(self.status, 'UNKNOWN'))
+        request.start_response(status, headers)
+        if self.response is None:
+            yield ''
+        elif isinstance(self.response, unicode):
+            yield self.response.encode(request.charset)
+        elif isinstance(self.response, str):
+            yield self.response
+        else:
+            try:
+                iterator = iter(self.response)
+            except TypeError:
+                raise TypeError('%r is not an valid response' % self.response)
+            for line in iterator:
+                if isinstance(line, unicode):
+                    yield line.encode(request.charset)
+                elif isinstance(line, str):
+                    yield line
+                else:
+                    raise TypeError('%r is not string or unicode' % line)
diff --git a/lib/colubrid/server.py b/lib/colubrid/server.py
@@ -0,0 +1,80 @@
+# -*- coding: utf-8 -*-
+"""
+    Colubrid Execute
+    ================
+
+    Since the 0.10 version of colubrid this is only a small helper module
+    which uses paster to serve the application.
+"""
+from __future__ import generators
+import sys
+import os
+from threading import Thread
+
+__all__ = ('execute', 'run_test', 'stop_test')
+
+
+class StaticExports(object):
+
+    def __init__(self, app, exports):
+        self.application = app
+        self.exports = exports
+
+    def serve_file(self, filename, start_response):
+        from mimetypes import guess_type
+        guessed_type = guess_type(filename)
+        if guessed_type[0] is None:
+            mime_type = 'text/plain'
+        else:
+            mime_type = guessed_type[0]
+        start_response('200 OK', [('Content-Type', mime_type)])
+        fp = file(filename, 'rb')
+        try:
+            result = fp.read()
+        finally:
+            fp.close()
+        return iter([result])
+
+    def __call__(self, environ, start_response):
+        path_info = environ.get('PATH_INFO', '')
+        for search_path, file_path in self.exports.iteritems():
+            if not search_path.endswith('/'):
+                search_path += '/'
+            if path_info.startswith(search_path):
+                real_path = os.path.join(file_path, path_info[len(search_path):])
+                if os.path.exists(real_path) and os.path.isfile(real_path):
+                    return self.serve_file(real_path, start_response)
+        return self.application(environ, start_response)
+
+
+def execute(app=None, debug=True, hostname='localhost', port=8080,
+            reload=False, evalex=False):
+    if app is None:
+        frm = sys._getframe().f_back
+        if not 'app' in frm.f_globals:
+            raise RuntimeError('no application found')
+        app = frm.f_globals['app']
+    if debug:
+        from colubrid.debug import DebuggedApplication
+        app = DebuggedApplication(app, evalex)
+
+    try:
+        from paste import httpserver
+        run = lambda: httpserver.serve(app, host=hostname, port=str(port))
+    except ImportError:
+        try:
+            from BaseWSGIServer import WSGIServer
+            run = WSGIServer(app, hostname, port).serve_forever
+        except ImportError:
+            try:
+                from wsgiref.simple_server import make_server
+                run = make_server(hostname, port, app).serve_forever
+            except ImportError:
+                run = None
+    if run is None:
+        raise RuntimeError('no standalone wsgi server found')
+    if reload:
+        from colubrid import reloader
+        reloader.main(run)
+    else:
+        run()
diff --git a/lib/colubrid/utils.py b/lib/colubrid/utils.py
@@ -0,0 +1,363 @@
+# -*- coding: utf-8 -*-
+"""
+    Colubrid Utilities
+    ------------------
+"""
+
+from __future__ import generators
+from urllib import quote
+from cStringIO import StringIO
+import posixpath
+
+
+def get_version():
+    """return the colubrid and python version."""
+    from colubrid import __version__
+    from sys import version
+    return '%s - Python %s' % (__version__, version.split('\n')[0].strip())
+
+
+class MultiDict(dict):
+    #adopted from django
+    
+    def __init__(self, key_to_list_mapping=()):
+        dict.__init__(self, key_to_list_mapping)
+
+    def __getitem__(self, key):
+        """
+        Return the last data value for this key, or [] if it's an empty list;
+        raises KeyError if not found.
+        """
+        list_ = dict.__getitem__(self, key)
+        try:
+            return list_[-1]
+        except IndexError:
+            return []
+
+    def _setitem_list(self, key, value):
+        dict.__setitem__(self, key, [value])
+    __setitem__ = _setitem_list
+
+    def get(self, key, default=None):
+        """Return the default value if the requested data doesn't exist"""
+        try:
+            val = self[key]
+        except KeyError:
+            return default
+        if val == []:
+            return default
+        return val
+
+    def getlist(self, key):
+        """Return an empty list if the requested data doesn't exist"""
+        try:
+            return dict.__getitem__(self, key)
+        except KeyError:
+            return []
+
+    def setlist(self, key, list_):
+        dict.__setitem__(self, key, list_)
+
+    def setdefault(self, key, default=None):
+        if key not in self:
+            self[key] = default
+        return self[key]
+
+    def setlistdefault(self, key, default_list=()):
+        if key not in self:
+            self.setlist(key, default_list)
+        return self.getlist(key)
+
+    def appendlist(self, key, value):
+        """Append an item to the internal list associated with key."""
+        self.setlistdefault(key, [])
+        dict.__setitem__(self, key, self.getlist(key) + [value])
+
+    def items(self):
+        """
+        Return a list of (key, value) pairs, where value is the last item in
+        the list associated with the key.
+        """
+        return [(key, self[key]) for key in self.keys()]
+
+    def lists(self):
+        """Return a list of (key, list) pairs."""
+        return dict.items(self)
+
+    def values(self):
+        """Returns a list of the last value on every key list."""
+        return [self[key] for key in self.keys()]
+
+    def copy(self):
+        """Returns a copy of this object."""
+        import copy
+        MultiDict.__setitem__ = dict.__setitem__
+        cp = copy.deepcopy(self)
+        MultiDict.__setitem__ = MultiDict._setitem_list
+        return cp
+
+    def update(self, other_dict):
+        """update() extends rather than replaces existing key lists."""
+        if isinstance(other_dict, MultiDict):
+            for key, value_list in other_dict.lists():
+                self.setlistdefault(key, []).extend(value_list)
+        else:
+            for key, value in other_dict.items():
+                self.setlistdefault(key, []).append(value)
+
+
+class MergedMultiDict(object):
+    """
+    A simple class for creating new "virtual" dictionaries that actualy look
+    up values in more than one MultiDict dictionary, passed in the constructor.
+    """
+    def __init__(self, *dicts):
+        self._dicts = dicts
+
+    def __getitem__(self, key):
+        for d in self._dicts:
+            try:
+                return d[key]
+            except KeyError:
+                pass
+        raise KeyError
+
+    def __repr__(self):
+        tmp = {}
+        for dict_ in self._dicts:
+            tmp.update(dict_)
+        return repr(tmp)
+
+    def __iter__(self):
+        return self.iterkeys()
+
+    def copy(self):
+        return dict(self.iteritems())
+
+    def iterkeys(self):
+        for d in self._dicts:
+            for key in d:
+                yield key
+
+    def itervalues(self):
+        for d in self._dicts:
+            for value in d.itervalues():
+                yield value
+
+    def iteritems(self):
+        for d in self._dicts:
+            for item in d.iteritems():
+                yield item
+
+    def get(self, key, default):
+        try:
+            return self[key]
+        except KeyError:
+            return default
+
+    def getlist(self, key):
+        for d in self._dicts:
+            try:
+                return d.getlist(key)
+            except KeyError:
+                pass
+        raise KeyError
+
+    def items(self):
+        return list(self.iteritems())
+
+    def __contains__(self, key):
+        for d in self._dicts:
+            if d.has_key(key):
+                return True
+        return False
+
+    def has_key(self, key):
+        return self.__contains__(key)
+
+
+class FieldStorage(object):
+
+    def __init__(self, name, filename, ftype, data):
+        self.name = name
+        self.type = ftype
+        self.filename = filename
+        self.data = data
+
+    def read(self, *args):
+        if not hasattr(self, '_cached_buffer'):
+            self._cached_buffer = StringIO(self.data)
+        return self._cached_buffer.read(*args)
+
+    def readline(self, *args):
+        if not hasattr(self, '_cached_buffer'):
+            self._cached_buffer = StringIO(self.data)
+        return self._cached_buffer.readline(*args)
+
+    def readlines(self):
+        result = []
+        while True:
+            row = self.readline()
+            if not row:
+                break
+            result.append(row)
+        return result
+
+    def __iter__(self):
+        while True:
+            row = self.readline()
+            if not row:
+                return
+            yield row
+
+    def __repr__(self):
+        return '%s (%s)' % (self.filename, self.type)
+
+    def __str__(self):
+        return self.__repr__()
+
+
+class HttpHeaders(object):
+        
+    def __init__(self, defaults=None):
+        if defaults is None:
+            self._defaults = []
+        elif isinstance(defaults, dict):
+            self._defaults = dict.items()
+        elif isinstance(defaults, list):
+            self._defaults = defaults[:]
+        else:
+            raise TypeError('invalid default value')
+        self.reset()
+        
+    def __setitem__(self, key, value):
+        self.set(key, value)
+        
+    def __delitem__(self, key):
+        self.remove(key)
+        
+    def __contains__(self, key):
+        if not isinstance(key, basestring):
+            raise TypeError('keys have to be string')
+        key = key.lower()
+        for k, v in self.data:
+            if k.lower() == key:
+                return True
+        return False
+        
+    def add(self, key, value):
+        """add a new header tuple to the list"""
+        self.data.append((key, value))
+        
+    def remove(self, key, count=-1):
+        """removes count header tuples from the list
+        where key matches
+        """
+        removed = 0
+        data = []
+        for _key, _value in self.data:
+            if _key.lower() != key.lower():
+                if count > -1:
+                    if removed >= count:
+                        break
+                    else:
+                        removed += 1
+                data.append((_key, _value))
+        self.data = data
+        
+    def reset(self):
+        """Reset to default headers."""
+        self.data = self._defaults
+        
+    def clear(self):
+        """clears all headers"""
+        self.data = []
+        
+    def set(self, key, value):
+        """remove all header tuples for key and add
+        a new one
+        """
+        self.remove(key)
+        self.add(key, value)
+        
+    def get(self, key=False, httpformat=False):
+        """returns matching headers as list
+        
+        if httpformat is set the result is a HTTP
+        header formatted list.
+        """
+        if not key:
+            result = self.data
+        elif not isinstance(key, basestring):
+            raise TypeError('keys have to be string')
+        else:
+            result = []
+            for k, v in self.data:
+                if k.lower() == key.lower():
+                    result.append((str(k), str(v)))
+        if httpformat:
+            return '\n'.join(['%s: %s' % item for item in result])
+        return result
+
+
+def get_full_url(environ, append=None):
+    if 'REQUEST_URI' in environ and append is None:
+        return environ['REQUEST_URI']
+    
+    url = environ['wsgi.url_scheme']+'://'
+    if environ.get('HTTP_HOST'):
+        url += environ['HTTP_HOST']
+    else:
+        url += environ['SERVER_NAME']
+        if environ['wsgi.url_scheme'] == 'https':
+            if environ['SERVER_PORT'] != '443':
+                url += ':' + environ['SERVER_PORT']
+        else:
+            if environ['SERVER_PORT'] != '80':
+                url += ':' + environ['SERVER_PORT']
+
+    if append is None:
+        url += quote(environ.get('SCRIPT_NAME', ''))
+        url += quote(environ.get('PATH_INFO', ''))
+        if environ.get('QUERY_STRING'):
+            url += '?' + environ['QUERY_STRING']
+    else:
+        url += append
+    return url
+
+
+def splitpath(p):
+    return [s for s in (posixpath.normpath(posixpath.join('/', p)) +
+            (p and p[-1] == '/' and '/' or '')).split('/') if s]
+
+
+def fix_slash(environ, wantslash):
+    """
+    Fixes the trailing slash in an url.
+    If the user requests an container object without an slash it
+    will appends one.
+    Requested an non container object with an traling slash will
+    result in an redirect to the same url without it.
+    the QUERY_STRING won't get lost but post data would. So don't
+    forget the slash problem in your form actions ;-)
+    """
+    from colubrid.exceptions import HttpMoved
+    #FIXME
+    #  argh. never did something that supid
+    #  find a better solution for that problem.
+    url = quote(environ.get('SCRIPT_NAME', ''))
+    url += quote(environ.get('PATH_INFO', ''))
+    query = environ.get('QUERY_STRING', '')
+    oldurl = query and ('%s?%s' % (url, query)) or url
+    
+    if oldurl and oldurl != '/':
+        if url.endswith('/'):
+            if not wantslash:
+                url = url[:-1]
+        else:
+            if wantslash:
+                url += '/'
+        
+        newurl = query and ('%s?%s' % (url, query)) or url
+        if oldurl != newurl:
+            raise HttpMoved(newurl)