# -*- coding: utf-8 -*-
"""
    pocoo.pkg.core.acl
    ~~~~~~~~~~~~~~~~~~

    Pocoo ACL System.

    :copyright: 2006-2007 by Armin Ronacher.
    :license: GNU GPL, see LICENSE for more details.
"""

from pocoo.db import meta

from pocoo.pkg.core.forum import Site, Forum, Thread
from pocoo.pkg.core.user import User, Group

from pocoo.pkg.core.db import users, groups, group_members, privileges, \
     forums, posts, acl_mapping, acl_subjects, acl_objects


class AclManager(object):
    """
    Manager object to manage ALCs.
    """
    STRONG_NO = -1

    WEAK_NO = 0
    WEAK_YES = 1
    STRONG_YES = 2

    def __init__(self, ctx, subject):
        self.ctx = ctx

        self.subject = subject
        if isinstance(subject, User):
            self._type = 'user'

        elif isinstance(subject, Group):
            self._type = 'group'

        else:
            raise ValueError('neither user or group specified')

    def allow(self, privilege, obj, force=False):
        """Allows the subject privilege on obj."""

        return self._set(privilege, obj, 1 + bool(force))

    def default(self, privilege, obj):
        """Sets the state for privilege on obj back to weak yes."""

        return self._set(privilege, obj, 0)

    def deny(self, privilege, obj, force=False):
        """Denies the subject privilege on obj."""

        return self._set(privilege, obj, -1 - bool(force))

    def can_access(self, privilege, obj):
        """Checks if the current subject with the required privilege
        somehow. Either directly or when the subject is a user and
        one of its groups can access it."""

        #XXX: maybe this could be one big query instead of 4
        #XXX: this currently does not work correctly, therefore return True
        return True

        if not isinstance(obj, (Forum, Thread, Site.__class__)):
            raise TypeError('obj must be a forum, thread or site')
        privilege = privilege.upper()
        s = self._get_subject_join().alias('s').c

        def do_check(obj, tendency):
            db = self.ctx.engine

            o = self._get_object_join(obj).alias('o').c

            # self check
            r = db.execute(meta.select([acl_mapping.c.state],
                (acl_mapping.c.priv_id == privileges.c.priv_id) &

                (acl_mapping.c.subject_id == s.subject_id) &
                (acl_mapping.c.object_id == o.object_id) &

                (privileges.c.name == privilege)
            ))
            row = r.fetchone()
            if row is not None:
                if row['state'] in (self.STRONG_NO, self.STRONG_YES):
                    return row['state'] == self.STRONG_YES

                tendency = row['state']

            # if the controlled subject is a user check all groups
            if isinstance(self.subject, User):
                r = db.execute(meta.select([acl_mapping.c.state],
                    (acl_mapping.c.object_id == o.object_id) &

                    (acl_mapping.c.subject_id == groups.c.subject_id) &

                    (groups.c.group_id == group_members.c.group_id) &

                    (group_members.c.user_id == self.subject.user_id)
                ))
                while True:
                    row = r.fetchone()
                    if row is None:
                        break

                    state = row[0]
                    if state in (self.STRONG_YES, self.STRONG_NO):
                        return state == self.STRONG_YES

                    if tendency is None:
                        tendency = state
                    elif tendency == self.WEAK_NO and state == self.WEAK_YES:
                        tendency = self.WEAK_YES

            # check related objects
            if isinstance(obj, Thread):
                return do_check(obj.forum, tendency)
            elif isinstance(obj, Forum):
                return do_check(Site, tendency)
            else:
                return tendency

        return do_check(obj, None) in (self.WEAK_YES, self.STRONG_YES)

    def _set(self, privilege, obj, state):
        """Helper functions for settings privileges."""

        privilege = privilege.upper()
        if self.subject.subject_id is None:
            self._bootstrap()
        if obj.object_id is None:
            self._bootstrap_object(obj)
        # special state "0" which means delete

        if not state:
            p = meta.select([privileges.c.priv_id], privileges.c.name == privilege)
            self.ctx.engine.execute(acl_mapping.delete(
                (acl_mapping.c.priv_id == p.c.priv_id) &

                (acl_mapping.c.subject_id == self.subject.subject_id) &

                (acl_mapping.c.object_id == obj.object_id)
            ))
            return
        # touch privilege and check existing mapping

        priv_id = self._fetch_privilege(privilege)
        r = self.ctx.engine.execute(meta.select([acl_mapping.c.state],
            (acl_mapping.c.priv_id == priv_id) &

            (acl_mapping.c.subject_id == self.subject.subject_id) &

            (acl_mapping.c.object_id == obj.object_id)
        ))
        row = r.fetchone()
        if row is not None:
            # this rule exists already

            if row['state'] == state:
                return
            # goddamn, same rule - different state, delete old first
            self._set(privilege, obj, 0)
        # insert new rule

        self.ctx.engine.execute(acl_mapping.insert(),
            priv_id = priv_id,
            subject_id = self.subject.subject_id,
            object_id = obj.object_id,
            state = state

        )

    def _bootstrap(self):
        """This method is automatically called when subject_id is
        None and an subject_id is required."""
        r = self.ctx.engine.execute(acl_subjects.insert(),
            subject_type = self._type

        )
        self.subject.subject_id = r.last_inserted_ids()[0]
        self.subject.save()

    def _bootstrap_object(self, obj):
        """Like _bootstrap but works for objects."""

        objtype = self._get_object_type(obj)
        r = self.ctx.engine.execute(acl_objects.insert(),
            object_type = objtype

        )
        obj.object_id = r.last_inserted_ids()[0]
        obj.save()

    def _get_object_type(self, obj):
        if isinstance(obj, Forum):
            return 'forum'

        elif isinstance(obj, Thread):
            return 'thread'
        elif obj is Site:
            return 'site'

        raise TypeError('obj isn\'t a forum or thread')

    def _get_object_join(self, obj):
        """Returns a subjoin for the object id."""

        t = self._get_object_type(obj)
        if t == 'forum':
            return meta.select([forums.c.object_id],
                forums.c.forum_id == obj.forum_id

            )
        elif t == 'thread':
            return meta.select([posts.c.object_id],
                posts.c.post_id == obj.post_id

            )
        else:
            # XXX: it works ^^
            # i really want something like meta.select('0 as group_id')
            class Fake(object):
                def alias(self, n):
                    class _C(object):
                        class c(object):
                            object_id = 0

                    return _C
            return Fake()

    def _get_subject_join(self):
        """Returns a subjoin for the subject id."""

        if self._type == 'user':
            return meta.select([users.c.subject_id],
                users.c.user_id == self.subject.user_id

            )
        return meta.select([groups.c.subject_id],
            groups.c.group_id == self.subject.group_id

        )

    def _fetch_privilege(self, name):
        """Returns the priv_id for the given privilege. If it
        doesn\'t exist by now the system will create a new
        privilege."""
        r = self.ctx.engine.execute(meta.select([privileges.c.priv_id],
            privileges.c.name == name

        ))
        row = r.fetchone()
        if row is not None:
            return row[0]
        r = self.ctx.engine.execute(privileges.insert(),
            name = name

        )
        return r.last_inserted_ids()[0]

    def __repr__(self):
        if self._type == 'user':
            id_ = self.subject.user_id

        else:
            id_ = self.subject.group_id
        if self.subject.subject_id is None:
            return '<%s %s:%d inactive>' % (
                self.__class__.__name__,
                self._type,
                id_

            )
        return '<%s %s:%d active as %d>' % (
            self.__class__.__name__,
            self._type,
            id_,
            self.subject.subject_id

        )
# -*- coding: utf-8 -*-
"""
    pocoo.pkg.core.auth
    ~~~~~~~~~~~~~~~~~~~

    Default authentication module.

    :copyright: 2006-2007 by Armin Ronacher.
    :license: GNU GPL, see LICENSE for more details.
"""

from datetime import datetime
from pocoo.context import Component

from pocoo.utils.net import IP
from pocoo.application import RequestWrapper
from pocoo.settings import cfg

from pocoo.pkg.core.user import User, check_login_data


class AuthProvider(Component):

    @property

    def auth_name(self):
        """
        has to return the name of the auth module for the configuration
        file. This name defaults to the classname.
        """
        return self.__class__.__name__

    def get_user(self, req):
        """
        This method should either return a valid `User object`_ or ``None``.

        .. _User object: pocoo.pkg.core.user
        """

    def get_user_id(self, session_dict):
        """

        This method should either return the user_id of the user or ``None``.
        """

    def do_login(self, req, username, password):
        """

        This method should update the user session so that the auth provider
        can recognize the user in the ``get_user`` method.
        It has to return a valid ``HttpResponse``, for redirecting to external
        login scripts or ``False``, to display an error message (login failed).
        If it returns ``True`` pocoo will redirect to the last visited page.
        """

    def do_logout(self, req):
        """
        This method should return a valid ``Response`` for redirecting
        to external scripts or ``None``.
        """

class SessionAuth(AuthProvider):

    def get_user(self, req):
        try:
            user_id = req.session['user_id']
            return User(self.ctx, user_id)
        except (KeyError, User.NotFound):
            return None

    def do_login(self, req, username, password):
        user_id = check_login_data(req.ctx, username, password)
        if user_id is not None:
            req.session['user_id'] = user_id

            return True
        return False

    def do_logout(self, req):
        if 'user_id' in req.session:
            req.session.pop('user_id')

    def get_user_id(self, session_dict):
        return session_dict.get('user_id')



class AuthWrapper(RequestWrapper):

    def get_priority(self):
        # after SessionWrapper
        return 3

    def process_request(self, req):
        # XXX: what to do with uid?
        uid = req.session.get('user_id', -1)
        req.auth = AuthController(req)
        req.user = req.auth.get_user()

    def process_response(self, req, resp):
        return resp


def get_auth_provider_mapping(ctx):
    """Returns a list of auth providers."""
    providers = {}
    for comp in ctx.get_components(AuthProvider):
        providers[comp.auth_name] = comp

    return providers


def get_auth_provider(ctx):
    """Returns the enabled auth provider."""
    if 'auth/provider' not in ctx._cache:
        providers = get_auth_provider_mapping(ctx)
        provider = providers[ctx.cfg.get('general', 'auth_module')]
        ctx._cache['auth/provider'] = provider

    return ctx._cache['auth/provider']


class AuthController(object):
    auth_provider = cfg.str('general', 'auth_module')

    def __init__(self, req):
        self.ctx = req.ctx

        self.req = req
        self.provider = get_auth_provider(req.ctx)

    def get_user(self):
        """

        Returns the user for this request
        """
        user = self.provider.get_user(self.req)
        if user is not None:
            user.ip = IP(self.req.environ['REMOTE_ADDR'])
            return user

        # return anonymous user
        return User(self.ctx, -1)

    def do_login(self, username, password):
        """

        Returns a valid ``Response``, for redirecting to external
        login scripts or ``False``, to display an error message (login failed).
        If it returns ``True`` pocoo should redirect to the last visited page.
        """
        rv = self.provider.do_login(self.req, username, password)
        if rv is not False:
            self.req.user = self.get_user()
            return rv

        return False

    def do_logout(self):
        """
        Loggs the user out. Can eiter return None or a Response for
        external redirects.
        """
        # update last login time

        self.req.user.last_login = datetime.now()
        self.req.user.save()
        self.provider.do_logout(self.req)
        #XXX: maybe a bit slow

        self.req.user = self.get_user()
# -*- coding: utf-8 -*-
"""
    pocoo.pkg.core.bbcode
    ~~~~~~~~~~~~~~~~~~~~~

    Pocoo BBCode parser.

    :copyright: 2006-2007 by Georg Brandl, Armin Ronacher.
    :license: GNU GPL, see LICENSE for more details.

"""
import re

from pocoo import Component
from pocoo.pkg.core.textfmt import MarkupFormat

from pocoo.pkg.core.smilies import get_smiley_buttons, replace_smilies
from pocoo.utils.html import escape_html, translate_color

from pocoo.utils.activecache import Node, CallbackNode, NodeList

tag_re = re.compile(r'(\[(/?[a-zA-Z0-9]+)(?:=(&quot;.+?&quot;|.+?))?\])')



class EndOfText(Exception):
    """Raise when the end of the text is reached."""


class TokenList(list):
    """A subclass of a list for tokens which allows to flatten
    the tokens so that the original bbcode is the return value."""

    def flatten(self):
        return u''.join(token.raw for token in self)

    def __repr__(self):
        return '<%s %s>' % (
            self.__class__.__name__,
            list.__repr__(self)
        )



class Token(object):
    """Token Baseclass"""

    def __repr__(self):
        return '<%s %s>' % (
            self.__class__.__name__,
            self.raw

        )


class TextToken(Token):
    """A token for plain text."""

    def __init__(self, data):
        self.data = self.raw = data


class TagToken(Token):
    """A token for tags."""

    def __init__(self, raw, tagname, attr):
        self.raw = raw

        self.name = tagname
        self.attr = attr


class Parser(object):
    """
    BBCode Parser Class
    """

    def __init__(self, ctx, text, handlers, allowed_tags):
        self.ctx = ctx

        self._tokens = tag_re.split(text)
        self._tokens.reverse()
        self._is_text = True

        self._cache = []
        self._handlers = handlers
        self._allowed_tags = allowed_tags

    def tag_allowed(self, tagname):
        """
        Check if a tagname is allowed for this parser.
        """
        if self._allowed_tags is None:
            return True

        return tagname in self._allowed_tags

    def get_next_token(self):
        """

        Fetch the next raw token from the text
        Raise ``EndOfText`` if not further token exists.
        """
        if self._cache:
            return self._cache.pop()
        get_token = self._tokens.pop

        if not self._tokens:
            raise EndOfText()
        if self._is_text:
            self._is_text = False

            return TextToken(get_token())
        else:
            self._is_text = True

            raw = get_token()
            tagname = get_token().lower()
            attr = get_token()
            if attr and attr[:6] == attr[-6:] == '&quot;':
                attr = attr[6:-6]
            return TagToken(raw, tagname, attr)

    def push_token(self, token):
        """

        Pushes the last fetched token in a cache so that the next time
        you call ``get_next_token`` returns the pushed token.
        """
        self._cache.append(token)

    def parse(self, needle=None, preserve_needle=False):
        """

        Parses the text until ``needle`` or the end of text if not defined.
        If it finds the needle it will delete the needle token. If you want
        the needle token too set ``preserve_needle`` to ``True``.

        In comparison with the ``get_tokens`` method this method will call
        the node handlers for each node.
        """
        result = NodeList()
        try:
            while True:
                token = self.get_next_token()
                if isinstance(token, TagToken) and token.name == needle:
                    if preserve_needle:
                        self.push_token(token)
                    break

                result.append(self.get_node(token))
        except EndOfText:
            pass

        return result

    def get_tokens(self, needle=None, preserve_needle=False):
        """

        Like ``parse`` but returns an unparsed TokenList. Basically you
        would never need this method except for preserved areas like
        Code blocks etc.
        """
        result = TokenList()
        try:
            while True:
                token = self.get_next_token()
                if isinstance(token, TagToken) and token.name == needle:
                    if preserve_needle:
                        self.push_token(token)
                    break

                result.append(token)
        except EndOfText:
            pass
        return result

    def get_node(self, token):
        """
        Return the node for a token. If the token was a ``TextToken``
        the resulting node will call ``get_text_node`` which returns a
        \n to <br/> replaced version of the token value wrapped in a
        plain ``Node``. In all other cases it will try to lookup the node
        in the list of registered token handlers.

        If this fails it wraps the raw token value in a ``Node``.
        """

        if isinstance(token, TextToken):
            return self.get_text_node(token.data)
        if self.tag_allowed(token.name):
            for handler in self._handlers:
                rv = handler.get_node(token, self)
                if rv is not None:
                    if isinstance(rv, Node):
                        return rv

                    return Node(rv)
        return self.get_text_node(token.raw)

    def get_text_node(self, data):
        """

        Newline replaces the text and wraps it in an ``Node``.
        """
        text = replace_smilies(self.ctx, data)
        return Node(re.sub(r'\r?\n', '<br />\n', text))

    def wrap_render(self, tag, parse_until):
        """

        Renders untile ``parse_until`` and wraps it in the html tag ``tag``.
        """
        return NodeList(Node('<%s>' % tag), self.parse(parse_until),
                        Node('</%s>' % tag))

    def joined_render(self, *args):
        """

        Takes a number of arguments which are either strings, unicode objects
        or nodes. It creates a new newlist, iterates over all arguments and
        converts all to nodes if not happened by now.
        """
        result = NodeList()
        for arg in args:
            if isinstance(arg, Node):
                result.append(arg)
            else:
                result.append(Node(arg))
        return result

    def callback(self, callback, data):
        """
        Returns a new ``CallbackNode``. Don't create callback nodes on your
        own, this method might do some further magic in the future.
        """
        return CallbackNode(callback, *data)



class BBCodeTagProvider(Component):
    #: list of handled tags
    tags = []

    #: list of callbacks
    callbacks = []

    def get_node(self, token, parser):
        """

        Is called when a tag is found. It must return a valid ``Node``
        or a string which is automatically wrapped into a plain ``Node``.
        """

    def render_callback(self, req, callback, data):
        """

        Has to handle a callback for ``callback`` with ``data`` and return a
        string
        """
        return u''

    def get_buttons(self, req):
        """

        Return a valid button definition for "tagname" or
        None if no button is required.

        A valid button definition is a dict in the following
        form::

            {'name':        _('Bold'),
             'description': _('Insert bold text'),
             'icon':        self.ctx.make_url('!cobalt/...'),
             'insert':      '[b]{text}[/b]'}
        """

        return ()


class BBCode(MarkupFormat):
    """
    BBCode markup format.
    """
    name = 'bbcode'

    editor_javascript = '!cobalt/core/pocoo/app/BBCodeEditor.js'

    def __init__(self, ctx):
        super(BBCode, self).__init__(ctx)
        self.handlers = {}
        self.callbacks = {}
        for comp in ctx.get_components(BBCodeTagProvider):
            for tag in comp.tags:
                self.handlers.setdefault(tag, []).append(comp)
            for callback in comp.callbacks:
                self.callbacks[callback] = comp

    def get_signature_tags(self):
        """Returns the allowed signature tags or None if all"""
        if not hasattr(self, '_signature_tags'):
            r = self.ctx.cfg.get('board', 'bbcode_signature_tags', 'ALL')
            if r == 'ALL':
                self._signature_tags = None

            else:
                self._signature_tags = [s.strip().lower() for s in r.split(',')]
        return self._signature_tags

    def parse(self, text, signature):
        handlers = self.ctx.get_components(BBCodeTagProvider)
        allowed_tags = None

        if signature:
            allowed_tags = self.get_signature_tags()
        p = Parser(self.ctx, escape_html(text), handlers, allowed_tags)
        return p.parse()

    def render_callback(self, req, callback, data):
        """Redirect the callback to the BBCode Provider."""

        for comp in self.ctx.get_components(BBCodeTagProvider):
            rv = comp.render_callback(req, callback, data)
            if rv is not None:
                return rv

        raise Exception('unhandled callback %r' % callback)

    def quote_text(self, req, text, username=None):
        if username is None:
            return '[quote]%s[/quote]' % text

        return '[quote="%s"]%s[/quote]' % (username, text)

    def get_editor_options(self, req, signature):
        buttons = []
        if signature:
            signature_tags = self.get_signature_tags()
        for comp in self.ctx.get_components(BBCodeTagProvider):
            for button in comp.get_buttons(req):
                if signature and button['tagname'] not in signature_tags:
                    continue

                buttons.append(button)
        return {
            'buttons':  buttons,
            'smilies':  get_smiley_buttons(req.ctx)
        }



class BasicBBCodeTagProvider(BBCodeTagProvider):
    tags = ['b', 'i', 'u', 's', 'url', 'email', 'color', 'size',
            'code', 'quote', 'list']
    callbacks = ['quote', 'list']

    def get_node(self, token, parser):
        ctx = self.ctx

        if token.name == 'b':
            if token.attr:
                return

            return parser.wrap_render('strong', '/b')
        if token.name == 'i':
            if token.attr:
                return

            return parser.wrap_render('em', '/i')
        if token.name == 'u':
            if token.attr:
                return

            return parser.wrap_render('ins', '/u')
        if token.name == 's':
            if token.attr:
                return

            return parser.wrap_render('del', '/s')
        if token.name == 'url':
            if token.attr:
                content = parser.parse('/url')
                url = token.attr

            else:
                tokenlist = parser.get_tokens('/url')
                content = url = tokenlist.flatten()
            if url.startswith('javascript:'):
                url = url[11:]
            return parser.joined_render('<a href="', url, '">', content, '</a>')
        if token.name == 'email':
            if token.attr:
                content = parser.parse('/email')
                mail = token.attr

            else:
                tokenlist = parser.get_tokens('/email')
                mail = content = tokenlist.flatten()
            return parser.joined_render('<a href="mailto:"', mail, '">',
                                        content, '</a>')
        if token.name == 'color':
            content = parser.parse('/color')
            try:
                color = translate_color(token.attr)
            except ValueError:
                return token.raw

            return parser.joined_render('<span style="color: ', color, '">',
                                        content, '</span>')
        if token.name == 'size':
            content = parser.parse('/size')
            if not token.attr or not token.attr.isdigit() or len(token.attr) > 2:
                return token.raw

            return parser.joined_render('<span style="font-size: ', token.attr,
                                        'px">', content, '</span>')
        if token.name == 'img':
            if token.attr:
                return

            tokenlist = parser.get_tokens('/img')
            url = tokenlist.flatten()
            if url.startswith('javascript:'):
                url = url[11:]
            return u'<img src="%s" />' % url

        if token.name == 'code':
            if token.attr:
                return

            return u'<pre>%s</pre>' % parser.get_tokens('/code').flatten()
        if token.name == 'quote':
            return parser.callback('quote', (token.attr or u'',
                                             parser.parse('/quote')))
        if token.name == 'list':
            return parser.callback('list', (token.attr or u'*',
                                            parser.parse('/list')))

    def render_callback(self, req, callback, data):
        if callback == 'quote':
            _ = req.gettext

            written, body = data
            if written:
                if not written.endswith(':'):
                    written = (_('%s wrote') % written) + u':'

                written = u'<div class="written_by">%s</div>' % written
            return u'<blockquote>%s%s</blockquote>' % (
                written, body.render(req, self)
            )
        if callback == 'list':
            type, body = data

            lines = []
            for line in re.split(r'^\s*\[\*\](?m)', body.render(req, self)):
                line = line.strip()
                if line:
                    lines.append(u'<li>%s</li>' % line)
            return u'<ul>%s</ul>' % u'\n'.join(lines)

    def get_buttons(self, req):
        _ = req.gettext

        make_url = self.ctx.make_url
        #XXX: themeable
        icon_url = lambda x: make_url('!cobalt/core/default/img/bbcode/' + x)

        return [
            {'tagname':         'b',
             'name':            _('Bold'),
             'description':     _('Insert bold text'),
             'insert':          '[b]{text}[/b]',
             'icon':            icon_url('bold.png')},
            {'tagname':         'i',
             'name':            _('Italic'),
             'description':     _('Insert italic text'),
             'insert':          '[i]{text}[/i]',
             'icon':            icon_url('italic.png')},
            {'tagname':         'u',
             'name':            _('Underline'),
             'description':     _('Insert underlined text'),
             'insert':          '[u]{text}[/u]',
             'icon':            icon_url('underline.png')},
            {'tagname':         's',
             'name':            _('Strikethrough'),
             'description':     _('Insert striked text'),
             'insert':          '[i]{text}[/i]',
             'icon':            icon_url('strikethrough.png')},
            {'tagname':         'size',
             'name':            _('Font Size'),
             'description':     _('Change the font size'),
             'insert':          '[size={attr}]{text}[/size]',
             'values': [
                (8,             _('Tiny')),
                (11,            _('Small')),
                (13,            _('Normal')),
                (18,            _('Big')),
                (24,            _('Huge'))
             ]},
            {'tagname':         'color',
             'name':            _('Font Color'),
             'description':     _('Change Font Color'),
             'insert':          '[color={attr}]{text}[/size]',
             'values': [
                ('black',       _('Black')),
                ('blue',        _('Blue')),
                ('brown',       _('Brown')),
                ('cyan',        _('Cyan')),
                ('gray',        _('Gray')),
                ('green',       _('Green')),
                ('magenta',     _('Magenta')),
                ('purple',      _('Purple')),
                ('red',         _('Red')),
                ('white',       _('White')),
                ('yellow',      _('Yellow'))
             ]},
            {'tagname':         'url',
             'name':            _('Link'),
             'description':     _('Create a Link'),
             'icon':            icon_url('link.png'),
             'insert':          '[url]{text}[/url]'},
            {'tagname':         'img',
             'name':            _('Image'),
             'description':     _('Insert an image'),
             'icon':            icon_url('img.png'),
             'insert':          '[img]{text}[/img]'},
            {'tagname':         'code',
             'name':            _('Code'),
             'description':     _('Insert a codeblock'),
             'icon':            icon_url('code.png'),
             'insert':          '[code]{text}[/code]'},
            {'tagname':         'quote',
             'name':            _('Quote'),
             'description':     _('Insert a blockquote'),
             'icon':            icon_url('quote.png'),
             'insert':          '[quote]{text}[/quote]'}
        ]

# -*- coding: utf-8 -*-
"""
    pocoo.pkg.core.cache
    ~~~~~~~~~~~~~~~~~~~~

    Provides a very simple caching system for persistent processes.

    :copyright: 2006-2007 by Armin Ronacher.
    :license: GNU GPL, see LICENSE for more details.
"""
from pocoo.application import RequestWrapper
from pocoo.exceptions import PocooRuntimeError

from pocoo.utils.cache import Cache

# This is currently unused.

class CacheSystem(RequestWrapper):

    def __init__(self, ctx):
        self.cache = Cache(autoprune=ctx.cfg.get('cache', 'autoprune', False))
        self.uri2key = {}
        RequestWrapper.__init__(self, ctx)

    def get_priority(self):
        # caching has highest priority

        return 1

    def process_request(self, req):
        req.cache_control = None

        req.cache = self.cache

        if req.environ['REQUEST_METHOD'] != 'GET':
            return

        if req.environ['REQUEST_URI'] not in self.uri2key:
            return

        key = self.uri2key[req.environ['REQUEST_URI']]
        return self.cache.fetch(key, None)

    def process_response(self, req, resp):
        if not req.cache_control:
            return resp

        action, key = req.cache_control
        if action == 'set':
            self.cache.dump(key, resp)
            self.uri2key[req.environ['REQUEST_URI']] = key

        elif action == 'update':
            if isinstance(key, basestring):
                self.cache.remove(key)
            else:
                for k in key:
                    self.cache.remove(k)
        else:
            raise PocooRuntimeError('req.cache_control invalid')