trac-ticketlinks / trac / wiki /

# -*- coding: utf-8 -*-
# Copyright (C) 2005-2008 Edgewall Software
# Copyright (C) 2005-2006 Christian Boos <>
# All rights reserved.
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution. The terms
# are also available at
# This software consists of voluntary contributions made by many
# individuals. For the exact contribution history, see the revision
# history and logs, available at
# Author: Christian Boos <>

import re
    import threading
except ImportError:
    import dummy_threading as threading

from genshi.builder import tag

from trac.core import *
from import Formatter
from import WikiParser
from import IWikiChangeListener, IWikiMacroProvider

class InterWikiMap(Component):
    """Implements support for InterWiki maps."""

    implements(IWikiChangeListener, IWikiMacroProvider)

    _page_name = 'InterMapTxt'
    _interwiki_re = re.compile(r"(%s)[ \t]+([^ \t]+)(?:[ \t]+#(.*))?" %
                               WikiParser.LINK_SCHEME, re.UNICODE)
    _argspec_re = re.compile(r"\$\d")
    _interwiki_map = None

    def __init__(self):
        self._interwiki_lock = threading.RLock()

    def reset(self):
        self._interwiki_map = None
        # This dictionary maps upper-cased namespaces
        # to (namespace, prefix, title) values;

    # The component itself behaves as a map

    def __contains__(self, ns):
        return ns.upper() in self.interwiki_map

    def __getitem__(self, ns):
        return self.interwiki_map[ns.upper()]

    def __setitem__(self, ns, value):
        self.interwiki_map[ns.upper()] = value

    def keys(self):
        return self.interwiki_map.keys()

    # Expansion of positional arguments ($1, $2, ...) in URL and title
    def _expand(self, txt, args):
        """Replace "$1" by the first args, "$2" by the second, etc."""
        def setarg(match):
            num = int([1:])
            return 0 < num <= len(args) and args[num-1] or ''
        return re.sub(InterWikiMap._argspec_re, setarg, txt)

    def _expand_or_append(self, txt, args):
        """Like expand, but also append first arg if there's no "$"."""
        if not args:
            return txt
        expanded = self._expand(txt, args)
        return expanded == txt and txt + args[0] or expanded

    def url(self, ns, target):
        """Return `(url, title)` for the given InterWiki `ns`.
        Expand the colon-separated `target` arguments.
        ns, url, title = self[ns]
        maxargnum = max([0]+[int(a[1:]) for a in
                             re.findall(InterWikiMap._argspec_re, url)])
        if maxargnum > 0:
            args = target.split(':', (maxargnum - 1))
            args = [target]
        expanded_url = self._expand_or_append(url, args)
        expanded_title = self._expand(title, args)
        if expanded_title == title:
            expanded_title = target+' in '+title
        return expanded_url, expanded_title

    # IWikiChangeListener methods

    def wiki_page_added(self, page):

    def wiki_page_changed(self, page, version, t, comment, author, ipnr):
        if == InterWikiMap._page_name:

    def wiki_page_deleted(self, page):
        if == InterWikiMap._page_name:

    def wiki_page_version_deleted(self, page):
        if == InterWikiMap._page_name:

    def _get_interwiki_map(self):
        from import WikiPage
        if self._interwiki_map is None:
                if self._interwiki_map is None:
                    self._interwiki_map = {}
                    content = WikiPage(self.env, InterWikiMap._page_name).text
                    in_map = False
                    for line in content.split('\n'):
                        if in_map:
                            if line.startswith('----'):
                                in_map = False
                                m = re.match(InterWikiMap._interwiki_re, line)
                                if m:
                                    prefix, url, title = m.groups()
                                    url = url.strip()
                                    title = title and title.strip() or prefix
                                    self[prefix] = (prefix, url, title)
                        elif line.startswith('----'):
                            in_map = True
        return self._interwiki_map
    interwiki_map = property(_get_interwiki_map)

    # IWikiMacroProvider methods

    def get_macros(self):
        yield 'InterWiki'

    def get_macro_description(self, name): 
        return "Provide a description list for the known InterWiki prefixes."

    def expand_macro(self, formatter, name, content):
        from trac.util import sorted
        interwikis = []
        for k in sorted(self.keys()):
            prefix, url, title = self[k]
                'prefix': prefix, 'url': url, 'title': title,
                'rc_url': self._expand_or_append(url, ['RecentChanges']),
                'description': title == prefix and url or title})

        return tag.table("Prefix")),
                         [['prefix'], href=w['rc_url'])),
                          for w in interwikis ],
                         class_="wiki interwiki")