Source

slipper / slipper / app.py

Full commit
from mimetypes import guess_type
from os.path import exists, join, splitext
from sha import sha
import sys
from textwrap import dedent

from docutils.core import publish_string
from docutils.writers.html4css1 import Writer

from mercurial.localrepo import localrepository
from mercurial.revlog import LookupError
from mercurial.util import datestr

from slipper.config import repo_map_from_config
import slipper.directives
from slipper.path import relative


class Application(object):

    def __init__(self, global_conf, **local_conf):
        self.index_path = local_conf.pop('slipper.DEFAULT.index_path', None)
        self.repo_map = repo_map_from_config(local_conf)

    def __call__(self, environ, start_response):
        # Determine the section to use and the file to render.
        PATH_INFO = environ['PATH_INFO'][1:]
        if PATH_INFO == '':
            if not self.index_path:
                # XXX: HTTP experts: is this correct?
                status = '404 NOT FOUND'
                response_headers = [
                    ('Content-Type', 'text/plain'),
                    ]
                start_response(status, response_headers)
                return [status]
            else:
                status = '302 FOUND'
                response_headers = [
                    ('Content-Type', 'text/plain'),
                    ('Location', self.index_path),
                    ]
                start_response(status, response_headers)
                return [status]
        parts = PATH_INFO.split('/', 2)
        if len(parts) != 3:
            # Unsupported query string; act as if a resource was requested
            # that was not found.
            # XXX: HTTP experts: is this correct?
            status = '404 NOT FOUND'
            response_headers = [
                ('Content-Type', 'text/plain'),
                ]
            start_response(status, response_headers)
            return [status]
        section, revision, file_path = parts
        # Get the configuration for that section.
        try:
            config = self.repo_map[section]
        except KeyError:
            # XXX: HTTP experts: is this correct?
            status = '404 NOT FOUND'
            response_headers = [
                ('Content-Type', 'text/plain'),
                ]
            start_response(status, response_headers)
            return [status]
        # Use index.txt for directory indices.
        if file_path == '' or file_path.endswith('/'):
            file_path += 'index' + config.rst_extension
        # Determine MIME type.
        mime_type = guess_type(file_path)[0]
        if mime_type is None:
            # XXX: HTTP experts: is this correct?
            mime_type = 'application/octet-stream'
        # Get the text of requested file/revision from repository.
        # First open the repository and get the log of the requested file.
        repo = localrepository(None, config.local_dir)
        if revision == 'tip+':
            ctx = repo.workingctx()
            revision = 'tip'
        else:
            ctx = repo.changectx(revision)
        try:
            filectx = ctx[file_path]
        except LookupError:
            # XXX: HTTP experts: is this correct?
            status = '404 NOT FOUND'
            response_headers = [
                ('Content-Type', 'text/plain'),
                ]
            start_response(status, response_headers)
            return [status]
        try:
            data = filectx.data()
        except IOError:
            # XXX: HTTP experts: is this correct?
            status = '404 NOT FOUND'
            response_headers = [
                ('Content-Type', 'text/plain'),
                ]
            start_response(status, response_headers)
            return [status]
        date = datestr(filectx.date())
        # XXX: HTTP experts: How do we short-circuit if the client's side is
        # up-to-date?
        if splitext(file_path)[1] == config.rst_extension:
            if config.hgweb_url:
                # Add a link to the source text.
                data += dedent("""\

                    .. admonition:: Page source

                        `<%s/file/%s/%s>`__.
                    """ % (config.hgweb_url, revision, file_path))
            hash = sha(data).hexdigest()
            # Look in cache first.
            if config.cache_dir:
                cache_filename = join(config.cache_dir, '%s.html' % hash)
            if config.cache_dir and exists(cache_filename):
                f = open(cache_filename, 'rb')
                data = f.read()
                f.close()
            else:
                # Not in cache, convert to HTML.
                w = Writer()
                settings = dict()
                if config.stylesheet_path:
                    # Find relative URL of stylesheet as compared to
                    # file being rendered.
                    abs_file_path = '/' + file_path
                    rel_stylesheet_url = relative(
                        abs_file_path, config.stylesheet_path)
                    settings.update(dict(
                        embed_stylesheet = False,
                        stylesheet = rel_stylesheet_url,
                        stylesheet_path = None,
                        ))
                data = publish_string(
                    data, 
                    writer=w, 
                    settings_overrides=settings,
                    )
                if config.cache_dir:
                    f = open(cache_filename, 'wb')
                    f.write(data)
                    f.close()
            mime_type = 'text/html'
        status = '200 OK'
        response_headers = [
            ('Content-Type', mime_type),
            ('Date', date),
            ('Cache-Control', 'max-age=%d' % (48 * 60 * 60)),
            ]
        start_response(status, response_headers)
        return [data]