view decoupage/web.py @ 34:527ccb76d043

merged some stuff
author k0s <k0scist@gmail.com>
date Mon, 08 Feb 2010 11:44:20 -0500
parents e384d4569ac3 89a72b2fd149
children 20e3d138dc98
line wrap: on
line source

"""
decoupage: a view with webob to index and serve static content
"""

import os

from contenttransformer.app import FileTypeTransformer
from formatters import formatters

from genshi.builder import Markup
from genshi.template import TemplateLoader
from genshi.template.base import TemplateError
from martini.config import ConfigMunger
from paste.fileapp import FileApp
from pkg_resources import resource_filename
from pkg_resources import iter_entry_points
from webob import Request, Response, exc

class Decoupage(object):

    ### class level variables
    defaults = { 'auto_reload': 'False',
                 'configuration': None,
                 'directory': None, # directory to serve
                 'cascade': 'True', # whether to cascade configuration
                 'template': 'index.html', # XXX see below
                 'template_directories': '' # list of directories to look for templates
                 }

    def __init__(self, **app_conf):

        # set defaults from app configuration
        kw = self.app_conf('decoupage', app_conf)
        for key in self.defaults:
            setattr(self, key, kw.get(key, self.defaults[key]))

        # configure defaults
        self.auto_reload = self.auto_reload.lower() == 'true'
        self.cascade = self.cascade.lower() == 'true'
        self.directory = self.directory.rstrip(os.path.sep)
        assert os.path.isdir(self.directory)
        self.template_directories = self.template_directories.split() # no spaces in directory names, for now

        for directory in self.template_directories:
            assert os.path.isdir(directory), "Decoupage template directory %s does not exist!" % directory

        # static file server
        self.fileserver = FileApp
        
        # pluggable index data formatters
        self.formatters = {}
        for formatter in iter_entry_points('decoupage.formatters'):
            try:
                _formatter = formatter.load()
                template_dir = resource_filename(formatter.module_name, 'templates')
                if template_dir not in self.template_directories and os.path.isdir(template_dir):
                    self.template_directories.append(template_dir)
            except:
                continue # XXX should probably raise
            self.formatters[formatter.name] = _formatter
        
        # template loader
        self.loader = TemplateLoader(self.template_directories, 
                                     auto_reload=self.auto_reload)

        

    ### methods dealing with HTTP
    def __call__(self, environ, start_response):
        request = Request(environ)
        filename = request.path_info.strip('/')
        path = os.path.join(self.directory, filename)
        if os.path.exists(path):
            if os.path.isdir(path):

                if not request.path_info.endswith('/'):
                    raise exc.HTTPMovedPermanently(add_slash=True)

                res = self.get(request)
                return res(environ, start_response)
            else:
                conf = self.conf(request.path_info.rsplit('/',1)[0])
                if '/transformer' in conf:
                    args = [i.split('=', 1) for i in conf['/transformer'].split(',') if '=' in i]
                    fileserver = FileTypeTransformer(*args)
                else:
                    fileserver = FileApp
                    
                fileserver = fileserver(path)
                return fileserver(environ, start_response)
        else:
            raise exc.HTTPNotFound()


    def get(self, request):
        """
        return response to a GET requst
        """
        # ensure a sane path        
        path = request.path_info.strip('/')
        directory = os.path.join(self.directory, path)
        path = '/%s' % path
        
        # get the configuraton
        conf = self.conf(path)

        ### build data dictionary
        files = self.filedata(path, directory, conf)
        data = {'path': path, 'files': files, 'request': request }

        # defaults; TODO: make this better
        data['title'] = conf.get('/title')
        data['directory'] = directory
        data['include'] = None
        data['css'] = ()
        data['scripts'] = ()
        data['icon'] = None

        # apply formatters
        # XXX this should be cached if not self.auto_reload
        if '/formatters' in conf:
            # ordered list of formatters to be applied first
            formatters = [ i for i in conf['/formatters'].split()
                           if i in self.formatters ]
        else:
            formatters = []
        for key in conf:
            if key.startswith('/'):
                key = key[1:]
                if key in self.formatters and key not in formatters:
                    formatters.append(key)
        for name in formatters:
            formatter = self.formatters[name](conf.get('/%s' % name, ''))
            formatter(request, data)

        # render the template
        template = conf.get('/template')
        local_index = False 
        if template is None:
            if 'index.html' in [ f['name'] for f in files ]:
                local_index = os.path.join(directory, 'index.html')
                template = local_index
            else:
                template = self.template
        else:
            if not os.path.isabs(template):
                _template = os.path.join(directory, template)
                if os.path.exists(_template):
                    template = _template
                else:
                    for directory in self.template_directories:
                        if template in os.listdir(directory):
                            break
                    else:
                        raise IOError("template %s not found" % template)
                
        try:
            template = self.loader.load(template)
            res = template.generate(**data).render('html', doctype='html')
        except TemplateError:
            if local_index:
                return self.fileserver(local_index)
            raise

        return Response(content_type='text/html', body=res)


    ### internal methods

    def filedata(self, path, directory, conf):
        files = []
        for i in os.listdir(directory):
            files.append({'path' : '%s/%s' % (path.rstrip('/'), i),
                          'name': i,
                          'description': conf.get(i.lower(), None)})
        return files

    def conf(self, path, cascade=None):
        """returns configuration dictionary appropriate to a path"""
        if cascade is None:
            cascase = self.cascade

        directory = os.path.join(self.directory, path.strip('/'))
        if path.strip('/'):
            path_tuple = tuple(path.strip('/').split('/'))
        else:
            path_tuple = ()

        # return cached configuration
        if hasattr(self, '_conf') and path_tuple in self._conf:
            return self._conf[path_tuple]

        conf = {}

        # local configuration
        ini_path = os.path.join(directory, 'index.ini')
        if os.path.exists(ini_path):
            _conf = ConfigMunger(ini_path).dict()
            if len(_conf) == 1:
                conf = _conf[_conf.keys()[0]].copy()

        # global configuration
        if not conf and self.configuration and os.path.exists(self.configuration):
            conf = ConfigMunger(self.configuration).dict().get('/%s' % path.rstrip('/'), {})

        # inherit and cascade configuration
        inherit_directory = None
        if '/inherit' in conf:
            inherit_directory = conf['/inherit']
        elif self.cascade and path_tuple:
            inherit_directory = '/%s' % '/'.join(path_tuple[:-1])
        if inherit_directory:
            parent_configuration = self.conf(inherit_directory)
            for key, value in parent_configuration.items():
                if key.startswith('/') and key not in conf:
                    conf[key] = value                

        # cache configuration
        if not self.auto_reload:
            if not hasattr(self, '_conf'):
                self._conf = {}
            self._conf[path_tuple] = conf

        return conf

    def fmtrs(self, path):
        formatters = []
        for key, value in self.conf(path).items():
            if key.startswith('/'):
                key = key[1:]
                if key in self.formatters:
                    formatter = self.formatters[key](value)        


    def app_conf(self, keystr, app_conf):
        keystr += '.'
        return dict([(key.split(keystr, 1)[-1], value)
                     for key, value in app_conf.items()
                     if key.startswith(keystr) ])