Source

Mango / utils.py

Full commit
#!/usr/bin/python
# -*- coding: utf-8 -*-

from __future__ import with_statement
from contextlib import closing
import htmlentitydefs
import logging
import os
import re
from urllib import urlencode
from urllib2 import Request, urlopen
from urlparse import urlparse

from django.conf import settings
from django.core.urlresolvers import reverse
from django.http import HttpResponse
from django.template import loader, RequestContext
from django.utils.safestring import mark_safe

import mango
from mango.decorators import baseurl
from mango.exceptions import EmptySettingError, InvalidSettingError
from mango.settings import (AKISMET_API_KEY, BASE_URL, CSS, DOCUMENTS_PATH,
        JS, MANGO_PATH, PROJECT_PATH, REPLACEMENTS)

RE = {
    'fragment': re.compile(r'(?s)(<code>.*?</code>|<pre>.*?</pre>|<skip>.*?</skip>)'),
    'replacements': (
        # ... -> ellipsis
        (re.compile(r'(?<![.])[.]{3}(?![.])'), u'\u2026'),
        # [space][hyphen][hyphen][space] -> [thin space][em dash][thin space]
        (re.compile(r' -- '), u'\u2009\u2014\u2009'),
    ),
    'snippet': re.compile(r'(?s)^<(code|pre|skip)>.*?</\1>$'),
}

_formatter = logging.Formatter(
        fmt='%(asctime)s %(name)s %(levelname)-8s : %(message)s',
        datefmt='%Y-%m-%d %H:%M:%S')

_console = logging.StreamHandler()
_console.setLevel(logging.DEBUG)
_console.setFormatter(_formatter)

_logfile = logging.FileHandler(filename=os.path.join(MANGO_PATH, 'mango.log'), encoding='utf-8')
_logfile.setLevel(logging.INFO)
_logfile.setFormatter(_formatter)

logger = logging.getLogger('mango')
logger.setLevel(logging.DEBUG)
logger.addHandler(_console)
logger.addHandler(_logfile)

def absolutize(path):
    """
    Returns the absolute path of `path`. If `path` is not (already) an absolute
    path, it is assumed to be relative to the project directory.
    
    >>> absolutize('mango/examples') == absolutize('mango/../mango/examples/')
    True
    """
    path = path.rstrip(u'/')
    if os.path.isabs(path):
        return path
    fragments = []
    head, tail = os.path.split(path)
    while tail:
        fragments.insert(0, tail)
        head, tail = os.path.split(head)
    return os.path.abspath(os.path.join(PROJECT_PATH, *fragments))

def akismet_api_key():
    def _(): pass
    _.cache = None

    def set(value):
        _.cache = (value,)
        return value

    def wrapper():
        if _.cache is not None:
            return _.cache[0]

        if AKISMET_API_KEY is None:
            return set(None)

        req = Request('http://rest.akismet.com/1.1/verify-key',
                data=urlencode({'key': AKISMET_API_KEY, 'blog': BASE_URL or baseurl()}),
                headers={'User-Agent': 'Mango/%s' % mango.VERSION})

        with closing(urlopen(req)) as page:
            if page.read().strip() == 'invalid':
                if settings.DEBUG:
                    raise InvalidSettingError('Akismet rejected the supplied API key',
                            'Double-check AKISMET_API_KEY in `mango/settings/custom.py`.')
                return set(None)
            else:
                return set(AKISMET_API_KEY)
    return wrapper
akismet_api_key = akismet_api_key()

def akismet_request(kind, dict_):
    return Request('http://%s.rest.akismet.com/1.1/%s' % (akismet_api_key(), kind),
            data=urlencode([(k, v.encode('utf-8')) for k, v in dict_.items()]),
            headers={'User-Agent': 'Mango/%s' % mango.VERSION})

def canonicalize(value, dict_):
    """
    >>> d = {1: 'foo', 2: ('bar', 'baz')}
    >>> canonicalize(1, d)
    1
    >>> canonicalize('foo', d)
    1
    >>> canonicalize('baz', d)
    2
    >>> canonicalize(3, d)
    3
    """
    if value in dict_:
        return value
    for k, v in dict_.items():
        for alias in [v] if isinstance(v, basestring) else v:
            if alias == value:
                return k
    return value

def html_response(template_name, request, dict_=None, status_code=None):
    if dict_ is None:
        dict_ = {}

    # provide access to the request (without relying upon a context processor)
    dict_['request'] = request

    # make Mango's settings accessible to templates
    for key, value in mango.settings.__dict__.items():
        if not key.startswith('_'):
            dict_[key] = value

    # Django settings
    dict_['settings'] = settings

    # set top-level category
    index = mango.main.Index.get()

    dict_['archives'] = index.archives()
    dict_['posts'] = index.descendants()
    dict_['tags'] = index.tags()

    dict_['stylesheets'] = stylesheets()
    dict_['scripts'] = scripts()

    response = HttpResponse(loader.render_to_string(template_name,
            context_instance=RequestContext(request, dict_)))
    if status_code:
        response.status_code = status_code
    return response

def lstrip(text, char='0'):
    return text[1:] if text.startswith(char) else text

def posts_directory():
    """ Creates "posts" directory if it does not already exist. """
    try:
        os.makedirs(DOCUMENTS_PATH)
    except OSError:
        if not os.path.exists(DOCUMENTS_PATH):
            raise OSError('Unable to create %s. Check permissions.' % DOCUMENTS_PATH)

def primary_author_email():
    """
    Returns the primary author's e-mail address as specified in Mango's
    settings file.
    
    >>> mango.settings.PRIMARY_AUTHOR_NAME = u'David Chambers'
    >>> mango.settings.PRIMARY_AUTHOR_EMAIL = u'david@mango.io'
    >>> primary_author_email()
    u'David Chambers <david@mango.io>'
    >>> mango.settings.PRIMARY_AUTHOR_NAME = None
    >>> primary_author_email()
    u'david@mango.io'
    """
    name = mango.settings.PRIMARY_AUTHOR_NAME
    email = mango.settings.PRIMARY_AUTHOR_EMAIL

    if email:
        return '%s <%s>' % (name, email) if name else email

    raise EmptySettingError('PRIMARY_AUTHOR_EMAIL setting is empty',
            'Add PRIMARY_AUTHOR_EMAIL to `mango/settings/custom.py`.')

def replace(html):
    if not REPLACEMENTS:
        return html

    def replace(text):
        for pattern, repl in RE['replacements']:
            text = re.sub(pattern, repl, text)
        return text

    return mark_safe(u''.join(
            chunk if re.match(RE['snippet'], chunk) else replace(chunk)
            for chunk in re.split(RE['fragment'], html)))

def scripts():
    def _(): pass
    _.cache = None
    def wrapper():
        if _.cache is None:
            scripts_root = prefix = JS[0]
            if prefix and not urlparse(prefix).scheme and not prefix.startswith('/'):
                scripts_root = '%s%s' % (reverse('mango.views.index'), prefix)
            _.cache = ['%s%s' % (scripts_root, filename) for filename in JS[1:]]
        return _.cache
    return wrapper
scripts = scripts()

def stylesheets():
    def _(): pass
    _.cache = None
    def wrapper():
        if _.cache is None:
            stylesheets_root = prefix = CSS[0]
            if prefix and not urlparse(prefix).scheme and not prefix.startswith('/'):
                stylesheets_root = '%s%s' % (reverse('mango.views.index'), prefix)
            _.cache = [{'media': media, 'href': '%s%s' % (stylesheets_root, filename)}
                    for media, filenames in CSS[1:]
                    for filename in ((filenames,) if isinstance(
                    filenames, basestring) else filenames)]
        return _.cache
    return wrapper
stylesheets = stylesheets()

def text_response(text, status_code=None):
    response = HttpResponse(text, content_type='text/plain; charset=utf-8')
    if status_code:
        response.status_code = status_code
    return response

# taken from http://effbot.org/zone/re-sub.htm#unescape-html (thanks, Fredrik!)
def unescape(text):
    """
    Removes HTML or XML character references and entities from a text string.
    
    >>> unescape('E = mc&#178;')
    u'E = mc\\xb2'
    >>> unescape('Jack &amp; Jill')
    u'Jack & Jill'
    """
    def fixup(m):
        text = m.group(0)
        if text[:2] == '&#':
            # character reference
            try:
                if text[:3] == '&#x':
                    return unichr(int(text[3:-1], 16))
                else:
                    return unichr(int(text[2:-1]))
            except ValueError:
                pass
        else:
            # named entity
            try:
                text = unichr(htmlentitydefs.name2codepoint[text[1:-1]])
            except KeyError:
                pass # leave as is
        return text
    return re.sub('&#?\w+;', fixup, text)