view martini/web.py @ 8:81aed4352851

make martini work with an ordered dictionary
author Jeff Hammel <jhammel@mozilla.com>
date Wed, 24 Nov 2010 11:05:40 -0800
parents 3c3522ce6e3a
children
line wrap: on
line source

"""
a view with webob + genshi for editing and viewing .ini files
"""
import os
import re

from genshi.template import TemplateLoader
from martini.config import ConfigMunger
from martini.utils import getbool
from martini.utils import getlist
from paste.httpexceptions import HTTPExceptionHandler
from paste.urlparser import StaticURLParser
from pkg_resources import resource_filename
from webob import Request, Response, exc

class MartiniWebView(object):

    ### class level variables
    defaults = { 'auto_reload': 'True',
                 'files': None,
                 'directories': None }

    def __init__(self, **kw):

        # set instance parameters from kw and defaults
        for key in self.defaults:
            setattr(self, key, kw.get(key, self.defaults[key]))
        self.auto_reload = getbool(self.auto_reload)

        # files
        self.files = getlist(self.files)
        if self.files:
            assert [ f for f in self.files if os.path.isabs(f) ]
            self.files = dict([(os.path.basename(f), f) 
                               for f in self.files])
        else:
            self.files = {}

        # directories
        self.directories = getlist(self.directories)
        if self.directories:
            assert [ d for d in self.directories if os.path.isabs(d) ]
            if len(self.directories) > 1 or self.files:
                # namespace directories
                self.directories = dict([(os.path.basename(d), d)
                                         for d in self.directories])
            else:
                # don't namespace a single directory
                self.directories = { '': self.directories[0] }
        else:
            self.directories = {}

        # have to have something to serve!
        assert self.files or self.directories

        # methods to respond to
        self.response_functions = { 'GET': self.get,
                                    'POST': self.post,
                                    }

        # template loader
        templates_dir = resource_filename(__name__, 'templates')
        self.loader = TemplateLoader(templates_dir,
                                     auto_reload=self.auto_reload)

        # fileserver
        self.fileserver = StaticURLParser(resource_filename(__name__, 'static'))

    def file(self, path):
        path = path.strip('/')
        if not path:
            return None
        f = self.files.get(path, None)
        if f is None:
            if path in self.directories:
                return self.directories[path]
            if '/' in path:
                path = path.split('/', 1)[-1]
                for d in self.directories.values():
                    filename = os.path.join(d, path)
                    if os.path.exists(filename):
                        return filename
        else:
            return f

    ### methods dealing with HTTP
    def __call__(self, environ, start_response):
        request = Request(environ)

        if request.path_info.strip('/') and os.path.exists(os.path.join(resource_filename(__name__, 'static'), request.path_info.strip('/'))):
            return self.fileserver(environ, start_response)

        if request.path_info.endswith('/'):
            if request.path_info.strip('/'):
                raise exc.HTTPFound(location=request.path_info.rstrip('/'))
            else:
                if request.path_info != '/':
                    raise exc.HTTPFound(location='/')
        res = self.make_response(request)
        return res(environ, start_response)
                                
    def make_response(self, request):
        return self.response_functions.get(request.method, self.error)(request)

    def get_response(self, text, content_type='text/html'):
        """make an HTTP response from text"""
        res = Response(content_type=content_type, body=text)
        return res

    def get(self, request):
        """
        return response to a GET requst
        """

        # index of all resources
        if not request.path_info.strip('/'):
            template = self.loader.load('index.html')
            items = self.files.keys() + self.directories.keys()
            res = template.generate(request=request, items=items).render('html', doctype='html')
            return self.get_response(res)

        # get the file
        f = self.file(request.path_info)
        if not f:
            raise exc.HTTPNotFound()
        
        # index page of a directory
        if os.path.isdir(f):
            template = self.loader.load('index.html')
            items = os.listdir(f)
            res = template.generate(request=request, items=items).render('html', doctype='html')
            return self.get_response(res)

        # get configuration
        conf = ConfigMunger(f)

        # render the template
        template = self.loader.load('table.html')
        res = template.generate(request=request, sections=conf.dict()).render('html', doctype='html')
        # generate the response
        return self.get_response(res)

    def post(self, request):
        """
        return response to a POST request
        """

        if not request.path_info.strip('/'):
            raise exc.HTTPMethodNotAllowed()

        # get the file
        f = self.file(request.path_info)
        if not f:
            raise exc.HTTPNotFound()
        if os.path.isdir(f):
            raise exc.HTTPMethodNotAllowed()

        regex = '\[([^\]]+)\](.*)'

        conf = ConfigMunger(f)

        delete = request.POST.getall('delete')
        if delete:
            del request.POST['delete']

        for key, value in request.POST.items():
            value = ' '.join(value.strip().split())
            match = re.match(regex, key)
            section, option = match.groups()
            if option:
                conf.set(section, option, value)
            else:
                if value:
                    conf.move_section(section, value)
                else:
                    conf.add_section(section)
        
        for d in delete:
            match = re.match(regex, d)
            section, option = match.groups()
            if conf.has_section(section):
                if option:
                    conf.remove_option(section, option)
                else:
                    conf.remove_section(section)

        output = file(f, 'w')
        conf.write(output)

        return exc.HTTPSeeOther(location=request.path_info)

    def error(self, request):
        """deal with non-supported methods"""
        return exc.HTTPMethodNotAllowed("Only %r operations are allowed" % self.response_functions.keys())


### paste factory
        
def factory(global_conf, **app_conf):
    """create a webob view and wrap it in middleware"""
    keystr = 'martini.'
    args = dict([(key.split(keystr, 1)[-1], value)
                 for key, value in app_conf.items()
                 if key.startswith(keystr) ])
    app = MartiniWebView(**args)
    return HTTPExceptionHandler(app)