changeset 510:201758d56fb1

branch merged with main repo
author Akash Sinha <akash2607@gmail.com>
date Mon, 08 Aug 2011 15:30:44 +0530
parents 92cc4c7ea1c0 (current diff) c61d7e7c6c6b (diff)
children 5c86980e7d06
files MoinMoin/apps/frontend/views.py MoinMoin/config/default.py MoinMoin/items/__init__.py MoinMoin/themes/__init__.py MoinMoin/user.py setup.py wikiconfig.py
diffstat 36 files changed, 481 insertions(+), 230 deletions(-) [+]
line wrap: on
line diff
--- a/MoinMoin/_tests/__init__.py	Mon Aug 08 15:16:46 2011 +0530
+++ b/MoinMoin/_tests/__init__.py	Mon Aug 08 15:30:44 2011 +0530
@@ -39,6 +39,7 @@
 def become_trusted(username=u"TrustedUser"):
     """ modify flaskg.user to make the user valid and trusted, so it is in acl group Trusted """
     become_valid(username)
+    flaskg.user.auth_trusted = True
     flaskg.user.auth_method = app.cfg.auth_methods_trusted[0]
 
 
--- a/MoinMoin/_tests/wikiconfig.py	Mon Aug 08 15:16:46 2011 +0530
+++ b/MoinMoin/_tests/wikiconfig.py	Mon Aug 08 15:30:44 2011 +0530
@@ -22,5 +22,5 @@
     _test_items_xml = join(_here, 'testitems.xml')
     content_acl = None
     item_root = 'FrontPage'
-    interwiki_map = dict(MoinMoin='http://moinmo.in/')
+    interwiki_map = dict(Self='http://localhost:8080/', MoinMoin='http://moinmo.in/')
 
--- a/MoinMoin/app.py	Mon Aug 08 15:16:46 2011 +0530
+++ b/MoinMoin/app.py	Mon Aug 08 15:30:44 2011 +0530
@@ -260,6 +260,10 @@
         userobj = user.User(auth_method='invalid')
     # if we have a valid user we store it in the session
     if userobj.valid:
+        # TODO: auth_trusted should be set by the auth method (auth class
+        # could have a param where the admin could tell whether he wants to
+        # trust it)
+        userobj.auth_trusted = userobj.auth_method in app.cfg.auth_methods_trusted
         session['user.id'] = userobj.id
         session['user.auth_method'] = userobj.auth_method
         session['user.auth_attribs'] = userobj.auth_attribs
--- a/MoinMoin/apps/feed/views.py	Mon Aug 08 15:16:46 2011 +0530
+++ b/MoinMoin/apps/feed/views.py	Mon Aug 08 15:30:44 2011 +0530
@@ -11,7 +11,7 @@
 
 from datetime import datetime
 
-from flask import request, url_for, Response
+from flask import request, Response
 from flask import current_app as app
 from flask import g as flaskg
 
@@ -27,6 +27,8 @@
 from MoinMoin.themes import get_editor_info
 from MoinMoin.items import Item
 from MoinMoin.util.crypto import cache_key
+from MoinMoin.util.interwiki import url_for_item
+
 
 @feed.route('/atom/<itemname:item_name>')
 @feed.route('/atom', defaults=dict(item_name=''))
@@ -66,7 +68,7 @@
                      summary=rev.get(COMMENT, ''), summary_type='text',
                      content=content, content_type=content_type,
                      author=get_editor_info(rev, external=True),
-                     url=url_for('frontend.show_item', item_name=name, rev=this_revno, _external=True),
+                     url=url_for_item(name, rev=this_revno, _external=True),
                      updated=datetime.utcfromtimestamp(rev.timestamp),
                     )
         content = feed.to_string()
--- a/MoinMoin/config/default.py	Mon Aug 08 15:16:46 2011 +0530
+++ b/MoinMoin/config/default.py	Mon Aug 08 15:30:44 2011 +0530
@@ -24,7 +24,7 @@
 from MoinMoin import datastruct
 from MoinMoin.auth import MoinAuth
 from MoinMoin.util import plugins
-from MoinMoin.security import FunctionACL
+from MoinMoin.security import AccessControlList
 
 
 class CacheClass(object):
@@ -72,7 +72,7 @@
         self.cache.item_group_regexact = re.compile(u'^%s$' % self.item_group_regex, re.UNICODE)
 
         # compiled functions ACL
-        self.cache.acl_functions = FunctionACL(self, [self.acl_functions])
+        self.cache.acl_functions = AccessControlList([self.acl_functions], valid=self.acl_rights_functions)
 
         plugins._loadPluginModule(self)
 
--- a/MoinMoin/converter/__init__.py	Mon Aug 08 15:16:46 2011 +0530
+++ b/MoinMoin/converter/__init__.py	Mon Aug 08 15:30:44 2011 +0530
@@ -88,31 +88,44 @@
 def convert_to_indexable(rev):
     """
     convert a revision to an indexable document
+
+    :param rev: item revision - please make sure that the content file is
+                ready to read all indexable content from it. if you have just
+                written that content or already read from it, you need to call
+                rev.seek(0) before calling convert_to_indexable(rev).
     """
     try:
-        # TODO use different converters / different converter mode?
-        # For now, just use some existing and working converter, later we
-        # should have a simple output converter just for indexing (that does not
-        # output any markup). Maybe we also want some special mode for the input
-        # converters so they emit different output than for normal rendering),
-        # esp. for the non-markup content types (images, etc.).
+        # TODO use different converter mode?
+        # Maybe we want some special mode for the input converters so they emit
+        # different output than for normal rendering), esp. for the non-markup
+        # content types (images, etc.).
         input_contenttype = rev[CONTENTTYPE]
         output_contenttype = 'text/plain'
+        type_input_contenttype = Type(input_contenttype)
+        type_output_contenttype = Type(output_contenttype)
         reg = default_registry
-        input_conv = reg.get(Type(input_contenttype), type_moin_document)
-        if not input_conv:
-            raise TypeError("We cannot handle the conversion from %s to the DOM tree" % input_contenttype)
-        output_conv = reg.get(type_moin_document, Type(output_contenttype))
-        if not output_conv:
-            raise TypeError("We cannot handle the conversion from the DOM tree to %s" % output_contenttype)
-        doc = input_conv(rev, input_contenttype)
-        # We do not convert smileys, includes, macros, links, because
-        # it does not improve search results or even makes results worse.
-        doc = output_conv(doc)
+        # first try a direct conversion (this could be useful for extraction
+        # of (meta)data from binary types, like from images or audio):
+        conv = reg.get(type_input_contenttype, type_output_contenttype)
+        if conv:
+            doc = conv(rev, input_contenttype)
+            return doc
+        # otherwise try via DOM as intermediate format (this is useful if
+        # input type is markup, to get rid of the markup):
+        input_conv = reg.get(type_input_contenttype, type_moin_document)
+        output_conv = reg.get(type_moin_document, type_output_contenttype)
+        if input_conv and output_conv:
+            doc = input_conv(rev, input_contenttype)
+            # We do not convert smileys, includes, macros, links, because
+            # it does not improve search results or even makes results worse.
+            doc = output_conv(doc)
+            return doc
+        # no way
+        raise TypeError("No converter for %s --> %s" % (input_contenttype, output_contenttype))
     except Exception as e: # catch all exceptions, we don't want to break an indexing run
         logging.exception("Exception happened in conversion:")
         doc = u'ERROR [%s]' % str(e)
-    return doc
+        return doc
 
 
 default_registry = RegistryConverter()
--- a/MoinMoin/converter/_tests/test_link.py	Mon Aug 08 15:16:46 2011 +0530
+++ b/MoinMoin/converter/_tests/test_link.py	Mon Aug 08 15:30:44 2011 +0530
@@ -17,20 +17,20 @@
 
 class TestConverterExternOutput(object):
     def setup_class(self):
-        url_root = Iri('./')
-        self.conv = ConverterExternOutput(url_root=url_root)
+        self.conv = ConverterExternOutput()
 
     def test_wiki(self):
         assert 'MoinMoin' in app.cfg.interwiki_map
         pairs = [
+            # note: result URLs assume test wiki running at /
             ('wiki:///Test',
-                './Test'),
+                '/Test'),
             ('wiki:///Test?mode=raw',
-                './Test?mode=raw'),
+                '/Test?mode=raw'),
             ('wiki:///Test#anchor',
-                './Test#anchor'),
+                '/Test#anchor'),
             ('wiki:///Test?mode=raw#anchor',
-                './Test?mode=raw#anchor'),
+                '/Test?mode=raw#anchor'),
             ('wiki://MoinMoin/Test',
                 'http://moinmo.in/Test'),
         ]
@@ -39,27 +39,28 @@
 
     def test_wikilocal(self):
         pairs = [
+            # note: result URLs assume test wiki running at /
             ('wiki.local:',
                 'wiki:///Root',
-                './Root'),
+                '/Root'),
             ('wiki.local:Test',
                 'wiki:///Root',
-                './Test'),
+                '/Test'),
             ('wiki.local:Test',
                 'wiki:///Root/Sub',
-                './Test'),
+                '/Test'),
             ('wiki.local:/Test',
                 'wiki:///Root',
-                './Root/Test'),
+                '/Root/Test'),
             ('wiki.local:/Test',
                 'wiki:///Root/Sub',
-                './Root/Sub/Test'),
+                '/Root/Sub/Test'),
             ('wiki.local:../Test',
                 'wiki:///Root',
-                './Test'),
+                '/Test'),
             ('wiki.local:../Test',
                 'wiki:///Root/Sub',
-                './Root/Test'),
+                '/Root/Test'),
         ]
         for i in pairs:
             yield (self._do_wikilocal, ) + i
@@ -81,8 +82,7 @@
 
 class TestConverterRefs(object):
     def setup_class(self):
-        root = url_root=Iri('./')
-        self.converter = ConverterItemRefs(url_root=root)
+        self.converter = ConverterItemRefs()
 
     def testItems(self):
         tree_xml = u"""
--- a/MoinMoin/converter/_util.py	Mon Aug 08 15:16:46 2011 +0530
+++ b/MoinMoin/converter/_util.py	Mon Aug 08 15:30:44 2011 +0530
@@ -33,7 +33,7 @@
             coding = ct.parameters.get('charset', coding)
         data = data.decode(coding)
     if not isinstance(data, unicode):
-        raise TypeError("data must be file-like or str (requires contenttype with charset) or unicode")
+        raise TypeError("data must be file-like or str (requires contenttype with charset) or unicode, but we got %r" % data)
     return data
 
 
--- a/MoinMoin/converter/link.py	Mon Aug 08 15:16:46 2011 +0530
+++ b/MoinMoin/converter/link.py	Mon Aug 08 15:30:44 2011 +0530
@@ -13,7 +13,7 @@
 
 from flask import g as flaskg
 
-from MoinMoin.util.interwiki import resolve_interwiki, join_wiki
+from MoinMoin.util.interwiki import is_known_wiki, url_for_item
 from MoinMoin.util.iri import Iri, IriPath
 from MoinMoin.util.mime import Type, type_moin_document
 from MoinMoin.util.tree import html, moin_page, xlink, xinclude
@@ -36,9 +36,6 @@
     def handle_wikilocal_transclusions(self, elem, link, page_name):
         pass
 
-    def __init__(self, url_root=None):
-        self.url_root = url_root
-
     def __call__(self, *args, **kw):
         """
         Calls the self.traverse_tree method
@@ -102,14 +99,16 @@
 
 class ConverterExternOutput(ConverterBase):
     @classmethod
-    def _factory(cls, input, output, links=None, url_root=None, **kw):
+    def _factory(cls, input, output, links=None, **kw):
         if links == 'extern':
-            return cls(url_root=url_root)
+            return cls()
 
     def _get_do_rev(self, query):
         """
         get 'do' and 'rev' values from query string and remove them from querystring
 
+        at the end, we translate the 'do' value to a werkzeug endpoint.
+
         Note: we can't use url_decode/url_encode from e.g. werkzeug because
               url_encode quotes the qs values (and Iri code will quote them again)
         """
@@ -138,62 +137,45 @@
             query = None
         if revno is not None:
             revno = int(revno)
-        return do, revno, query
+        do_to_endpoint = dict(
+            show='frontend.show_item',
+            get='frontend.get_item',
+            download='frontend.download_item',
+            modify='frontend.modify_item',
+            # TODO: if we just always used same function name as do=name, we did not need this dict
+            # ...
+        )
+        endpoint = do_to_endpoint[do or 'show']
+        return endpoint, revno, query
 
     def handle_wiki_links(self, elem, input):
-        do, revno, query = self._get_do_rev(input.query)
-        link = Iri(query=query, fragment=input.fragment)
-
+        wiki_name = 'Self'
         if input.authority and input.authority.host:
-            # interwiki link
-            wikitag, wikiurl, wikitail, err = resolve_interwiki(unicode(input.authority.host), unicode(input.path[1:]))
-            if not err:
+            wn = unicode(input.authority.host)
+            if is_known_wiki(wn):
+                # interwiki link
                 elem.set(html.class_, 'moin-interwiki')
-                if do is not None:
-                    # this will only work for wikis with compatible URL design
-                    # for other wikis, don't use do=... in your interwiki links
-                    wikitail = '/+' + do + wikitail
-                base = Iri(join_wiki(wikiurl, wikitail))
-            else:
-                # TODO (for now, we just link to Self:item_name in case of
-                # errors, see code below)
-                pass
-        else:
-            err = False
-
-        if not input.authority or err:
-            # local wiki link
-            path = input.path[1:]
-            if revno is not None:
-                path = IriPath('%d/' % revno) + path
-            if do is not None:
-                path = IriPath('+%s/' % do) + path
-            link.path = path
-            base = self.url_root
-
-        elem.set(self._tag_xlink_href, base + link)
+                wiki_name = wn
+        item_name = unicode(input.path[1:])
+        endpoint, revno, query = self._get_do_rev(input.query)
+        url = url_for_item(item_name, wiki_name=wiki_name, rev=revno, endpoint=endpoint)
+        link = Iri(url, query=query, fragment=input.fragment)
+        elem.set(self._tag_xlink_href, link)
 
     def handle_wikilocal_links(self, elem, input, page):
-        do, revno, query = self._get_do_rev(input.query)
-        link = Iri(query=query, fragment=input.fragment)
-
         if input.path:
+            # this can be a relative path, make it absolute:
             path = input.path
             path = self.absolute_path(path, page.path)
-
-            if not flaskg.storage.has_item(unicode(path)):
+            item_name = unicode(path)
+            if not flaskg.storage.has_item(item_name):
                 elem.set(html.class_, 'moin-nonexistent')
         else:
-            path = page.path[1:]
-
-        if revno is not None:
-            path = IriPath('%d/' % revno) + path
-        if do is not None:
-            path = IriPath('+%s/') + path
-        link.path = path
-        output = self.url_root + link
-
-        elem.set(self._tag_xlink_href, output)
+            item_name = unicode(page.path[1:])
+        endpoint, revno, query = self._get_do_rev(input.query)
+        url = url_for_item(item_name, rev=revno, endpoint=endpoint)
+        link = Iri(url, query=query, fragment=input.fragment)
+        elem.set(self._tag_xlink_href, link)
 
 
 class ConverterItemRefs(ConverterBase):
@@ -201,9 +183,9 @@
     determine all links and transclusions to other wiki items in this document
     """
     @classmethod
-    def _factory(cls, input, output, items=None, url_root=None, **kw):
+    def _factory(cls, input, output, items=None, **kw):
         if items == 'refs':
-            return cls(url_root=url_root)
+            return cls()
 
     def __init__(self, **kw):
         super(ConverterItemRefs, self).__init__(**kw)
--- a/MoinMoin/converter/moinwiki19_in.py	Mon Aug 08 15:16:46 2011 +0530
+++ b/MoinMoin/converter/moinwiki19_in.py	Mon Aug 08 15:30:44 2011 +0530
@@ -17,7 +17,7 @@
 logging = log.getLogger(__name__)
 
 from MoinMoin import config, wikiutil
-from MoinMoin.util.interwiki import resolve_interwiki
+from MoinMoin.util.interwiki import is_known_wiki
 from MoinMoin.util.iri import Iri
 from MoinMoin.util.tree import html, moin_page, xlink
 
@@ -103,8 +103,7 @@
             text = freelink_email
 
         else:
-            wikitag_bad = resolve_interwiki(freelink_interwiki_ref, freelink_interwiki_page)[3]
-            if wikitag_bad:
+            if not is_known_wiki(freelink_interwiki_ref):
                 stack.top_append(freelink)
                 return
 
--- a/MoinMoin/converter/moinwiki_in.py	Mon Aug 08 15:16:46 2011 +0530
+++ b/MoinMoin/converter/moinwiki_in.py	Mon Aug 08 15:30:44 2011 +0530
@@ -21,7 +21,7 @@
 from MoinMoin import config
 from MoinMoin.util.iri import Iri
 from MoinMoin.util.tree import html, moin_page, xlink, xinclude
-from MoinMoin.util.interwiki import resolve_interwiki
+from MoinMoin.util.interwiki import is_known_wiki
 from MoinMoin.i18n import _
 
 from ._args import Arguments
@@ -786,8 +786,7 @@
             link_interwiki_site=None, link_interwiki_item=None):
         """Handle all kinds of links."""
         if link_interwiki_site:
-            err = resolve_interwiki(link_interwiki_site, link_interwiki_item)[3]
-            if not err:
+            if is_known_wiki(link_interwiki_site):
                 link = Iri(scheme='wiki',
                         authority=link_interwiki_site,
                         path='/' + link_interwiki_item)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/converter/opendocument_in.py	Mon Aug 08 15:30:44 2011 +0530
@@ -0,0 +1,73 @@
+# Copyright: 2011 MoinMoin:ThomasWaldmann
+# License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
+
+"""
+MoinMoin - OpenDocument Format (ODF) input converter
+
+ODF documents can be created with OpenOffice.org, Libre Office and other software.
+"""
+
+
+from __future__ import absolute_import, division
+
+import zipfile
+
+from MoinMoin import log
+logging = log.getLogger(__name__)
+
+from .xml_in import strip_xml
+
+
+class OpenDocumentIndexingConverter(object):
+    @classmethod
+    def _factory(cls, input, output, **kw):
+        return cls()
+
+    def __call__(self, rev, contenttype=None, arguments=None):
+        zf = zipfile.ZipFile(rev, "r")  # rev is file-like
+        try:
+            data = zf.read("content.xml")
+            text = data.decode('utf-8')
+            text = strip_xml(text)
+            return text
+        finally:
+            zf.close()
+
+
+from . import default_registry
+from MoinMoin.util.mime import Type, type_text_plain
+
+opendocument_types = """\
+application/vnd.oasis.opendocument.chart
+application/vnd.oasis.opendocument.database
+application/vnd.oasis.opendocument.formula
+application/vnd.oasis.opendocument.graphics
+application/vnd.oasis.opendocument.graphics-template
+application/vnd.oasis.opendocument.image
+application/vnd.oasis.opendocument.presentation
+application/vnd.oasis.opendocument.presentation-template
+application/vnd.oasis.opendocument.spreadsheet
+application/vnd.oasis.opendocument.spreadsheet-template
+application/vnd.oasis.opendocument.text
+application/vnd.oasis.opendocument.text-master
+application/vnd.oasis.opendocument.text-template
+application/vnd.oasis.opendocument.text-web""".split()
+
+for t in opendocument_types:
+    default_registry.register(OpenDocumentIndexingConverter._factory, Type(t), type_text_plain)
+
+
+# use same converter for the old *.sx? (pre-opendocument) openoffice documents:
+OpenOfficeIndexingConverter = OpenDocumentIndexingConverter
+
+openoffice_types = """\
+application/vnd.sun.xml.calc
+application/vnd.sun.xml.draw
+application/vnd.sun.xml.impress
+application/vnd.sun.xml.math
+application/vnd.sun.xml.writer
+application/vnd.sun.xml.writer.global""".split()
+
+for t in openoffice_types:
+    default_registry.register(OpenOfficeIndexingConverter._factory, Type(t), type_text_plain)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/converter/pdf_in.py	Mon Aug 08 15:30:44 2011 +0530
@@ -0,0 +1,69 @@
+# Copyright: 2011 MoinMoin:ThomasWaldmann
+# License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
+
+"""
+MoinMoin - PDF input converter
+"""
+
+
+from __future__ import absolute_import, division
+
+from MoinMoin import log
+logging = log.getLogger(__name__)
+
+from pdfminer.pdfparser import PDFDocument, PDFParser
+from pdfminer.pdfinterp import PDFResourceManager, PDFPageInterpreter, process_pdf
+from pdfminer.pdfdevice import PDFDevice
+from pdfminer.converter import TextConverter
+from pdfminer.cmapdb import CMapDB
+from pdfminer.layout import LAParams
+
+
+LAPARAMS = LAParams(
+    # value is specified not as an actual length, but as a proportion of the length to the size of each character in question.
+    # two text chunks whose distance is closer than the char_margin is considered
+    # continuous and get grouped into one.
+    char_margin=0.3,
+    # it may be required to insert blank characters (spaces) as necessary if the distance
+    # between two words is greater than the word_margin, as a blank between words might
+    # not be represented as a space, but indicated by the positioning of each word.
+    word_margin=0.2,
+    # two lines whose distance is closer than the line_margin is grouped as a text box,
+    # which is a rectangular area that contains a "cluster" of text portions.
+    line_margin=0.3,
+)
+
+
+class UnicodeConverter(TextConverter):
+    # as result, we want a unicode object
+    # TextConverter only provides encoded output into a file-like object
+    def __init__(self, rsrcmgr, pageno=1, laparams=None, showpageno=False):
+        TextConverter.__init__(self, rsrcmgr, None, codec=None, pageno=pageno, laparams=laparams,
+                               showpageno=showpageno)
+        self.__text = []
+
+    def write_text(self, text):
+        self.__text.append(text)
+
+    def read_result(self):
+        return u''.join(self.__text)
+
+
+class PDFIndexingConverter(object):
+    @classmethod
+    def _factory(cls, input, output, **kw):
+        return cls()
+
+    def __call__(self, rev, contenttype=None, arguments=None):
+        rsrcmgr = PDFResourceManager()
+        device = UnicodeConverter(rsrcmgr, laparams=LAPARAMS)
+        try:
+            process_pdf(rsrcmgr, device, rev)
+            return device.read_result()
+        finally:
+            device.close()
+
+
+from . import default_registry
+from MoinMoin.util.mime import Type, type_text_plain
+default_registry.register(PDFIndexingConverter._factory, Type('application/pdf'), type_text_plain)
--- a/MoinMoin/converter/text_out.py	Mon Aug 08 15:16:46 2011 +0530
+++ b/MoinMoin/converter/text_out.py	Mon Aug 08 15:30:44 2011 +0530
@@ -28,6 +28,6 @@
 
 
 from . import default_registry
-from MoinMoin.util.mime import Type, type_moin_document
-default_registry.register(Converter.factory, type_moin_document, Type('text/plain'))
+from MoinMoin.util.mime import Type, type_moin_document, type_text_plain
+default_registry.register(Converter.factory, type_moin_document, type_text_plain)
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/converter/xml_in.py	Mon Aug 08 15:30:44 2011 +0530
@@ -0,0 +1,43 @@
+# Copyright: 2011 MoinMoin:ThomasWaldmann
+# License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
+
+"""
+MoinMoin - Generic XML input converter
+"""
+
+from __future__ import absolute_import, division
+
+import re
+
+from MoinMoin import log
+logging = log.getLogger(__name__)
+
+from ._util import decode_data
+
+RX_STRIPXML = re.compile(u"<[^>]*?>", re.U|re.DOTALL|re.MULTILINE)
+
+def strip_xml(text):
+    text = RX_STRIPXML.sub(u" ", text)
+    text = ' '.join(text.split())
+    return text
+
+
+class XMLIndexingConverter(object):
+    """
+    We try to generically extract contents from XML documents by just throwing
+    away all XML tags. This is for indexing, so this might be good enough.
+    """
+    @classmethod
+    def _factory(cls, input, output, **kw):
+        return cls()
+
+    def __call__(self, rev, contenttype=None, arguments=None):
+        text = decode_data(rev, contenttype)
+        text = strip_xml(text)
+        return text
+
+
+from . import default_registry
+from MoinMoin.util.mime import Type, type_text_plain
+default_registry.register(XMLIndexingConverter._factory, Type('text/xml'), type_text_plain)
+
--- a/MoinMoin/datastruct/backends/_tests/__init__.py	Mon Aug 08 15:16:46 2011 +0530
+++ b/MoinMoin/datastruct/backends/_tests/__init__.py	Mon Aug 08 15:30:44 2011 +0530
@@ -14,7 +14,7 @@
 from flask import current_app as app
 from flask import g as flaskg
 
-from MoinMoin.security import ContentACL
+from MoinMoin.security import AccessControlList
 from MoinMoin.datastruct import GroupDoesNotExistError
 
 
@@ -91,7 +91,7 @@
         Check user which has rights.
         """
         acl_rights = ["AdminGroup:admin,read,write"]
-        acl = ContentACL(app.cfg, acl_rights)
+        acl = AccessControlList(acl_rights, valid=app.cfg.acl_rights_contents)
 
         for user in self.expanded_groups['AdminGroup']:
             for permission in ["read", "write", "admin"]:
@@ -103,7 +103,7 @@
         Check user which does not have rights.
         """
         acl_rights = ["AdminGroup:read,write"]
-        acl = ContentACL(app.cfg, acl_rights)
+        acl = AccessControlList(acl_rights, valid=app.cfg.acl_rights_contents)
 
         assert u"SomeUser" not in flaskg.groups['AdminGroup']
         for permission in ["read", "write"]:
@@ -114,7 +114,7 @@
 
     def test_backend_acl_with_all(self):
         acl_rights = ["EditorGroup:read,write,admin All:read"]
-        acl = ContentACL(app.cfg, acl_rights)
+        acl = AccessControlList(acl_rights, valid=app.cfg.acl_rights_contents)
 
         for member in self.expanded_groups[u'EditorGroup']:
             for permission in ["read", "write", "admin"]:
@@ -128,7 +128,7 @@
         assert u'NotExistingGroup' not in flaskg.groups
 
         acl_rights = ["NotExistingGroup:read,write,admin All:read"]
-        acl = ContentACL(app.cfg, acl_rights)
+        acl = AccessControlList(acl_rights, valid=app.cfg.acl_rights_contents)
 
         assert not acl.may(u"Someone", "write")
 
--- a/MoinMoin/datastruct/backends/_tests/test_wiki_groups.py	Mon Aug 08 15:16:46 2011 +0530
+++ b/MoinMoin/datastruct/backends/_tests/test_wiki_groups.py	Mon Aug 08 15:30:44 2011 +0530
@@ -18,7 +18,7 @@
 from MoinMoin.datastruct.backends._tests import GroupsBackendTest
 from MoinMoin.datastruct import GroupDoesNotExistError
 from MoinMoin.config import USERGROUP
-from MoinMoin.security import ContentACL
+from MoinMoin.security import AccessControlList
 from MoinMoin.user import User
 from MoinMoin._tests import become_trusted, create_random_string_list, update_item
 
@@ -110,7 +110,7 @@
         update_item(u'NewGroup', 0, {USERGROUP: ["ExampleUser"]}, DATA)
 
         acl_rights = ["NewGroup:read,write"]
-        acl = ContentACL(app.cfg, acl_rights)
+        acl = AccessControlList(acl_rights, valid=app.cfg.acl_rights_contents)
 
         has_rights_before = acl.may(u"AnotherUser", "read")
 
--- a/MoinMoin/items/__init__.py	Mon Aug 08 15:16:46 2011 +0530
+++ b/MoinMoin/items/__init__.py	Mon Aug 08 15:30:44 2011 +0530
@@ -62,6 +62,7 @@
 from MoinMoin.themes import render_template
 from MoinMoin import wikiutil, config, user
 from MoinMoin.util.send_file import send_file
+from MoinMoin.util.interwiki import url_for_item
 from MoinMoin.storage.error import NoSuchItemError, NoSuchRevisionError, AccessDeniedError, \
                                    StorageError
 from MoinMoin.config import UUID, NAME, NAME_OLD, MTIME, REVERTED_TO, ACL, \
@@ -265,8 +266,7 @@
         from MoinMoin.converter import default_registry as reg
         include_conv = reg.get(type_moin_document, type_moin_document, includes='expandall')
         macro_conv = reg.get(type_moin_document, type_moin_document, macros='expandall')
-        link_conv = reg.get(type_moin_document, type_moin_document, links='extern',
-                url_root=Iri(request.url_root))
+        link_conv = reg.get(type_moin_document, type_moin_document, links='extern')
         flaskg.clock.start('conv_include')
         doc = include_conv(doc)
         flaskg.clock.stop('conv_include')
@@ -722,7 +722,7 @@
                 except AccessDeniedError:
                     abort(403)
                 else:
-                    return redirect(url_for('frontend.show_item', item_name=self.name))
+                    return redirect(url_for_item(self.name))
         return render_template(self.template,
                                item_name=self.name,
                                rows_meta=str(ROWS_META), cols=str(COLS),
@@ -1151,7 +1151,7 @@
         # TODO: use registry as soon as it is in there
         from MoinMoin.converter.pygments_in import Converter as PygmentsConverter
         pygments_conv = PygmentsConverter(contenttype=self.contenttype)
-        doc = pygments_conv(data_text.split(u'\n'))
+        doc = pygments_conv(data_text)
         # TODO: Real output format
         html_conv = reg.get(type_moin_document, Type('application/x-xhtml-moin-page'))
         doc = html_conv(doc)
@@ -1189,7 +1189,7 @@
                 except AccessDeniedError:
                     abort(403)
                 else:
-                    return redirect(url_for('frontend.show_item', item_name=self.name))
+                    return redirect(url_for_item(self.name))
         return render_template(self.template,
                                item_name=self.name,
                                rows_data=str(ROWS_DATA), rows_meta=str(ROWS_META), cols=str(COLS),
@@ -1222,8 +1222,7 @@
         from MoinMoin.converter import default_registry as reg
 
         input_conv = reg.get(Type(self.contenttype), type_moin_document)
-        item_conv = reg.get(type_moin_document, type_moin_document,
-                items='refs', url_root=Iri(request.url_root))
+        item_conv = reg.get(type_moin_document, type_moin_document, items='refs')
 
         i = Iri(scheme='wiki', authority='', path='/' + self.name)
 
--- a/MoinMoin/security/__init__.py	Mon Aug 08 15:16:46 2011 +0530
+++ b/MoinMoin/security/__init__.py	Mon Aug 08 15:30:44 2011 +0530
@@ -169,12 +169,12 @@
 
     special_users = ["All", "Known", "Trusted"] # order is important
 
-    def __init__(self, cfg, lines=[], default='', valid=None):
-        """ Initialize an ACL, starting from <nothing>. """
+    def __init__(self, lines=[], default='', valid=None):
+        """ Initialize an ACL, starting from <nothing>.
+        """
         assert valid is not None
         self.acl_rights_valid = valid
         self.default = default
-        self.auth_methods_trusted = cfg.auth_methods_trusted
         assert isinstance(lines, (list, tuple))
         if lines:
             self.acl = [] # [ ('User', {"read": 0, ...}), ... ]
@@ -270,8 +270,7 @@
             Does not work for subsription emails that should be sent to <user>,
             as he is not logged in in that case.
         """
-        if (flaskg.user.name == name and
-            flaskg.user.auth_method in self.auth_methods_trusted):
+        if flaskg.user.name == name and flaskg.user.auth_trusted:
             return rightsdict.get(dowhat)
         return None
 
@@ -282,30 +281,6 @@
         return self.acl_lines != other.acl_lines
 
 
-class ContentACL(AccessControlList):
-    """
-    Content AccessControlList
-
-    Uses cfg.acl_rights_contents if no list of valid rights is explicitly given.
-    """
-    def __init__(self, cfg, lines=[], default='', valid=None):
-        if valid is None:
-            valid = cfg.acl_rights_contents
-        super(ContentACL, self).__init__(cfg, lines, default, valid)
-
-
-class FunctionACL(AccessControlList):
-    """
-    Function AccessControlList
-
-    Uses cfg.acl_rights_functions if no list of valid rights is explicitly given.
-    """
-    def __init__(self, cfg, lines=[], default='', valid=None):
-        if valid is None:
-            valid = cfg.acl_rights_functions
-        super(FunctionACL, self).__init__(cfg, lines, default, valid)
-
-
 class ACLStringIterator(object):
     """ Iterator for acl string
 
--- a/MoinMoin/security/_tests/test_security.py	Mon Aug 08 15:16:46 2011 +0530
+++ b/MoinMoin/security/_tests/test_security.py	Mon Aug 08 15:30:44 2011 +0530
@@ -12,7 +12,7 @@
 
 from flask import current_app as app
 
-from MoinMoin.security import ContentACL, ACLStringIterator
+from MoinMoin.security import AccessControlList, ACLStringIterator
 
 from MoinMoin.user import User
 from MoinMoin.config import ACL
@@ -218,7 +218,7 @@
             "BadGuy:  "
             "All:read  "
             ]
-        acl = ContentACL(app.cfg, acl_rights)
+        acl = AccessControlList(acl_rights, valid=app.cfg.acl_rights_contents)
 
         # Should apply these rights:
         users = (
--- a/MoinMoin/storage/__init__.py	Mon Aug 08 15:16:46 2011 +0530
+++ b/MoinMoin/storage/__init__.py	Mon Aug 08 15:30:44 2011 +0530
@@ -850,6 +850,24 @@
 
         return [key for key in self._metadata if not key.startswith("__")]
 
+    def read(self, chunksize=-1):
+        """
+        @see: Backend._read_revision_data.__doc__
+        """
+        return self._backend._read_revision_data(self, chunksize)
+
+    def seek(self, position, mode=0):
+        """
+        @see: StringIO.StringIO().seek.__doc__
+        """
+        self._backend._seek_revision_data(self, position, mode)
+
+    def tell(self):
+        """
+        @see: StringIO.StringIO().tell.__doc__
+        """
+        return self._backend._tell_revision_data(self)
+
 
 class StoredRevision(Revision):
     """
@@ -878,24 +896,6 @@
         """
         raise AttributeError("Metadata of already existing revisions may not be altered.")
 
-    def read(self, chunksize=-1):
-        """
-        @see: Backend._read_revision_data.__doc__
-        """
-        return self._backend._read_revision_data(self, chunksize)
-
-    def seek(self, position, mode=0):
-        """
-        @see: StringIO.StringIO().seek.__doc__
-        """
-        self._backend._seek_revision_data(self, position, mode)
-
-    def tell(self):
-        """
-        @see: StringIO.StringIO().tell.__doc__
-        """
-        return self._backend._tell_revision_data(self)
-
     def destroy(self):
         """
         @see: Backend._destroy_revision.__doc__
--- a/MoinMoin/storage/_tests/test_backends.py	Mon Aug 08 15:16:46 2011 +0530
+++ b/MoinMoin/storage/_tests/test_backends.py	Mon Aug 08 15:30:44 2011 +0530
@@ -265,6 +265,42 @@
         rev = item.get_revision(0)
         assert rev.read() == "Alle meine Entchen"
 
+    def test_item_write_seek_read(self):
+        item = self.backend.create_item(u"write_seek_read")
+        rev = item.create_revision(0)
+        write_data = "some data"
+        rev.write(write_data)
+        rev.seek(0)
+        read_data = rev.read()
+        assert read_data == write_data
+        item.commit()
+        rev = item.get_revision(0)
+        assert rev.read() == write_data
+
+    def test_item_seek_tell_read(self):
+        item = self.backend.create_item(u"write_seek_read")
+        rev = item.create_revision(0)
+        write_data = "0123456789"
+        rev.write(write_data)
+        rev.seek(0)
+        assert rev.tell() == 0
+        read_data = rev.read()
+        assert read_data == write_data
+        rev.seek(4)
+        assert rev.tell() == 4
+        read_data = rev.read()
+        assert read_data == write_data[4:]
+        item.commit()
+        rev = item.get_revision(0)
+        rev.seek(0)
+        assert rev.tell() == 0
+        read_data = rev.read()
+        assert read_data == write_data
+        rev.seek(4)
+        assert rev.tell() == 4
+        read_data = rev.read()
+        assert read_data == write_data[4:]
+
     def test_item_reading_chunks(self):
         item = self.backend.create_item(u"slices")
         rev = item.create_revision(0)
--- a/MoinMoin/storage/backends/acl.py	Mon Aug 08 15:16:46 2011 +0530
+++ b/MoinMoin/storage/backends/acl.py	Mon Aug 08 15:30:44 2011 +0530
@@ -47,7 +47,7 @@
 from flask import current_app as app
 from flask import g as flaskg
 
-from MoinMoin.security import ContentACL
+from MoinMoin.security import AccessControlList
 
 from MoinMoin.storage import Item, NewRevision, StoredRevision
 from MoinMoin.storage.error import NoSuchItemError, NoSuchRevisionError, AccessDeniedError
@@ -85,10 +85,10 @@
         self.cfg = cfg
         self.backend = backend
         self.hierarchic = hierarchic
-        self.valid = valid
-        self.before = ContentACL(cfg, [before], default=default, valid=valid)
-        self.default = ContentACL(cfg, [default], default=default, valid=valid)
-        self.after = ContentACL(cfg, [after], default=default, valid=valid)
+        self.valid = valid if valid is not None else cfg.acl_rights_contents
+        self.before = AccessControlList([before], default=default, valid=self.valid)
+        self.default = AccessControlList([default], default=default, valid=self.valid)
+        self.after = AccessControlList([after], default=default, valid=self.valid)
 
     def __getattr__(self, attr):
         # Attributes that this backend does not define itself are just looked
@@ -176,7 +176,7 @@
             # do not use default acl here
             acls = []
         default = self.default.default
-        return ContentACL(self.cfg, tuple(acls), default=default, valid=self.valid)
+        return AccessControlList(tuple(acls), default=default, valid=self.valid)
 
     def _may(self, itemname, right, username=None):
         """ Check if username may have <right> access on item <itemname>.
--- a/MoinMoin/storage/backends/fileserver.py	Mon Aug 08 15:16:46 2011 +0530
+++ b/MoinMoin/storage/backends/fileserver.py	Mon Aug 08 15:30:44 2011 +0530
@@ -112,6 +112,11 @@
             rev._fs_data_file = open(rev._fs_data_fname, 'rb') # XXX keeps file open as long as rev exists
         return rev._fs_data_file.seek(position, mode)
 
+    def _tell_revision_data(self, rev):
+        if rev._fs_data_file is None:
+            rev._fs_data_file = open(rev._fs_data_fname, 'rb') # XXX keeps file open as long as rev exists
+        return rev._fs_data_file.tell()
+
 
 # Specialized Items/Revisions
 
--- a/MoinMoin/storage/backends/flatfile.py	Mon Aug 08 15:16:46 2011 +0530
+++ b/MoinMoin/storage/backends/flatfile.py	Mon Aug 08 15:30:44 2011 +0530
@@ -178,3 +178,7 @@
     def _seek_revision_data(self, rev, position, mode):
         rev._data.seek(position, mode)
 
+    def _tell_revision_data(self, rev):
+        return rev._data.tell()
+
+
--- a/MoinMoin/storage/backends/fs.py	Mon Aug 08 15:16:46 2011 +0530
+++ b/MoinMoin/storage/backends/fs.py	Mon Aug 08 15:30:44 2011 +0530
@@ -178,8 +178,9 @@
         rev._revno = revno
         fd, rev._fs_revpath = tempfile.mkstemp('-rev', 'tmp-', self._path)
         rev._fs_file = f = os.fdopen(fd, 'wb+') # XXX keeps file open as long a rev exists
-        f.write(struct.pack('!I', self._revmeta_reserved_space + 4))
-        f.seek(self._revmeta_reserved_space + 4)
+        rev._datastart = self._revmeta_reserved_space + 4
+        f.write(struct.pack('!I', rev._datastart))
+        f.seek(rev._datastart)
 
         return rev
 
--- a/MoinMoin/storage/backends/fs2.py	Mon Aug 08 15:16:46 2011 +0530
+++ b/MoinMoin/storage/backends/fs2.py	Mon Aug 08 15:30:44 2011 +0530
@@ -92,7 +92,7 @@
         def maketemp(kind):
             tmp_dir = self._backend._make_path(kind)
             fd, tmp_path = tempfile.mkstemp('.tmp', '', tmp_dir)
-            tmp_file = os.fdopen(fd, 'wb') # XXX keeps file open as long a rev exists
+            tmp_file = os.fdopen(fd, 'wb+') # XXX keeps file open as long a rev exists
             return tmp_file, tmp_path
 
         self._fs_file_meta, self._fs_path_meta = maketemp('meta')
--- a/MoinMoin/storage/backends/router.py	Mon Aug 08 15:16:46 2011 +0530
+++ b/MoinMoin/storage/backends/router.py	Mon Aug 08 15:30:44 2011 +0530
@@ -379,6 +379,15 @@
     def keys(self):
         return self._revision.keys()
 
+    def read(self, chunksize=-1):
+        return self._revision.read(chunksize)
+
+    def seek(self, position, mode=0):
+        return self._revision.seek(position, mode)
+
+    def tell(self):
+        return self._revision.tell()
+
     def write(self, data):
         self._revision.write(data)
 
--- a/MoinMoin/storage/backends/sqla.py	Mon Aug 08 15:16:46 2011 +0530
+++ b/MoinMoin/storage/backends/sqla.py	Mon Aug 08 15:30:44 2011 +0530
@@ -472,6 +472,8 @@
         @see: SQLAItem.setup.__doc__
         """
         self.chunkno = 0
+        # XXX if we keep the last chunk outside of _chunks, read() will not be
+        # able to read data from the last chunk
         self._last_chunk = Chunk(self.chunkno)
         self.cursor_pos = 0
 
--- a/MoinMoin/themes/__init__.py	Mon Aug 08 15:16:46 2011 +0530
+++ b/MoinMoin/themes/__init__.py	Mon Aug 08 15:30:44 2011 +0530
@@ -21,7 +21,7 @@
 from MoinMoin.i18n import _, L_, N_
 from MoinMoin import wikiutil, user
 from MoinMoin.config import USERID, ADDRESS, HOSTNAME
-from MoinMoin.util.interwiki import split_interwiki, resolve_interwiki, join_wiki, getInterwikiHome
+from MoinMoin.util.interwiki import split_interwiki, getInterwikiHome, is_local_wiki, is_known_wiki, url_for_item
 from MoinMoin.util.crypto import cache_key
 from MoinMoin.util.forms import make_generator
 
@@ -103,9 +103,9 @@
         trail = user.getTrail()
         for interwiki_item_name in trail:
             wiki_name, item_name = split_interwiki(interwiki_item_name)
-            wiki_name, wiki_base_url, item_name, err = resolve_interwiki(wiki_name, item_name)
-            href = join_wiki(wiki_base_url, item_name)
-            if wiki_name in [self.cfg.interwikiname, 'Self', ]:
+            err = not is_known_wiki(wiki_name)
+            href = url_for_item(item_name, wiki_name=wiki_name)
+            if is_local_wiki(wiki_name):
                 exists = self.storage.has_item(item_name)
                 wiki_name = ''  # means "this wiki" for the theme code
             else:
@@ -129,13 +129,12 @@
         wikiname, itemname = getInterwikiHome(name)
         title = "%s @ %s" % (aliasname, wikiname)
         # link to (interwiki) user homepage
-        if wikiname == "Self":
+        if is_local_wiki(wikiname):
             exists = self.storage.has_item(itemname)
         else:
             # We cannot check if wiki pages exists in remote wikis
             exists = True
-        wiki_name, wiki_base_url, item_name, err = resolve_interwiki(wikiname, itemname)
-        wiki_href = join_wiki(wiki_base_url, item_name)
+        wiki_href = url_for_item(itemname, wiki_name=wikiname)
         return wiki_href, aliasname, title, exists
 
     def split_navilink(self, text):
@@ -181,22 +180,13 @@
         if target.startswith("wiki:"):
             target = target[5:]
 
-        # try handling interwiki links
         wiki_name, item_name = split_interwiki(target)
-        wiki_name, wiki_base_url, item_name, err = resolve_interwiki(wiki_name, item_name)
-        href = join_wiki(wiki_base_url, item_name)
-        if wiki_name not in [self.cfg.interwikiname, 'Self', ]:
-            if not title:
-                title = item_name
-            return href, title, wiki_name
-
-        # Handle regular pagename like "FrontPage"
-        item_name = wikiutil.normalize_pagename(item_name, self.cfg)
-
+        if wiki_name == 'Self':
+            wiki_name = ''
+        href = url_for_item(item_name, wiki_name=wiki_name)
         if not title:
             title = item_name
-        href = url_for('frontend.show_item', item_name=item_name)
-        return href, title, wiki_local
+        return href, title, wiki_name
 
     def navibar(self, item_name):
         """
@@ -219,7 +209,7 @@
 
         # Add sister pages (see http://usemod.com/cgi-bin/mb.pl?SisterSitesImplementationGuide )
         for sistername, sisterurl in self.cfg.sistersites:
-            if sistername == self.cfg.interwikiname:  # it is THIS wiki
+            if is_local_wiki(sistername):
                 items.append(('sisterwiki current', sisterurl, sistername))
             else:
                 cid = cache_key(usage="SisterSites", sistername=sistername)
@@ -318,14 +308,11 @@
             css = 'editor mail'
         else:
             homewiki = app.cfg.user_homewiki
-            if homewiki in ('Self', app.cfg.interwikiname):
-                homewiki = u'Self'
+            if is_local_wiki(homewiki):
                 css = 'editor homepage local'
-                uri = url_for('frontend.show_item', item_name=name, _external=external)
             else:
                 css = 'editor homepage interwiki'
-                wt, wu, tail, err = resolve_interwiki(homewiki, name)
-                uri = join_wiki(wu, tail)
+            uri = url_for_item(name, wiki_name=homewiki, _external=external)
 
     result = dict(name=name, text=text, css=css, title=title)
     if uri:
@@ -397,6 +384,7 @@
                             'clock': flaskg.clock,
                             'cfg': app.cfg,
                             'item_name': 'handlers need to give it',
+                            'url_for_item': url_for_item,
                             'get_editor_info': lambda rev: get_editor_info(rev),
                             'gen': make_generator(),
                             })
--- a/MoinMoin/themes/_tests/test_navi_bar.py	Mon Aug 08 15:16:46 2011 +0530
+++ b/MoinMoin/themes/_tests/test_navi_bar.py	Mon Aug 08 15:30:44 2011 +0530
@@ -15,7 +15,7 @@
 
 class TestNaviBar(object):
     class Config(wikiconfig.Config):
-        interwiki_map = dict(MoinMoin='http://moinmo.in/', )
+        interwiki_map = dict(Self='http://localhost:8080/', MoinMoin='http://moinmo.in/', )
 
     def setup_method(self, method):
         self.theme = ThemeSupport(app.cfg)
--- a/MoinMoin/user.py	Mon Aug 08 15:16:46 2011 +0530
+++ b/MoinMoin/user.py	Mon Aug 08 15:30:44 2011 +0530
@@ -30,7 +30,7 @@
 
 from MoinMoin import config, wikiutil
 from MoinMoin.i18n import _, L_, N_
-from MoinMoin.util.interwiki import getInterwikiHome
+from MoinMoin.util.interwiki import getInterwikiHome, is_local_wiki
 from MoinMoin.util.crypto import crypt_password, upgrade_password, valid_password, \
                                  generate_token, valid_token
 
@@ -399,7 +399,8 @@
     def persistent_items(self):
         """ items we want to store into the user profile """
         nonpersistent_keys = ['id', 'valid', 'may', 'auth_username',
-                              'password', 'password2', 'auth_method', 'auth_attribs',
+                              'password', 'password2',
+                              'auth_method', 'auth_attribs', 'auth_trusted',
                              ]
         return [(key, value) for key, value in vars(self).items()
                     if key not in nonpersistent_keys and key[0] != '_' and value is not None]
@@ -730,7 +731,7 @@
             it doesn't matter whether it already exists or not.
         """
         wikiname, pagename = getInterwikiHome(self.name)
-        if wikiname == 'Self':
+        if is_local_wiki(wikiname):
             markup = '[[%s]]' % pagename
         else:
             markup = '[[%s:%s]]' % (wikiname, pagename)
--- a/MoinMoin/util/_tests/test_interwiki.py	Mon Aug 08 15:16:46 2011 +0530
+++ b/MoinMoin/util/_tests/test_interwiki.py	Mon Aug 08 15:30:44 2011 +0530
@@ -14,13 +14,13 @@
 import os.path
 import shutil
 
-from MoinMoin.util.interwiki import resolve_interwiki, split_interwiki, join_wiki, InterWikiMap
+from MoinMoin.util.interwiki import split_interwiki, join_wiki, InterWikiMap
 from MoinMoin._tests import wikiconfig
 
 
 class TestInterWiki(object):
     class Config(wikiconfig.Config):
-        interwiki_map = dict(MoinMoin='http://moinmo.in/', )
+        interwiki_map = dict(Self='http://localhost:8080/', MoinMoin='http://moinmo.in/', )
 
     def testSplitWiki(self):
         tests = [('SomePage', ('Self', 'SomePage')),
@@ -41,12 +41,6 @@
         for (baseurl, pagename), url in tests:
             assert join_wiki(baseurl, pagename) == url
 
-    def testResolveInterWiki(self):
-        result = resolve_interwiki('MoinMoin', 'SomePage')
-        assert result == ('MoinMoin', u'http://moinmo.in/', 'SomePage', False)
-        result = resolve_interwiki('Self', 'SomePage')
-        assert result == ('Self', u'/', 'SomePage', False)
-
 
 class TestInterWikiMapBackend(object):
     """
--- a/MoinMoin/util/interwiki.py	Mon Aug 08 15:16:46 2011 +0530
+++ b/MoinMoin/util/interwiki.py	Mon Aug 08 15:30:44 2011 +0530
@@ -11,14 +11,76 @@
 from werkzeug import url_quote
 
 from flask import current_app as app
-from flask import request
-from flask import g as flaskg
+from flask import url_for
 
 import os.path
 
 from MoinMoin import config
 
 
+def is_local_wiki(wiki_name):
+    """
+    check if <wiki_name> is THIS wiki
+    """
+    return wiki_name in ['', 'Self', app.cfg.interwikiname, ]
+
+
+def is_known_wiki(wiki_name):
+    """
+    check if <wiki_name> is a known wiki name
+
+    Note: interwiki_map should have entries for the special wikinames
+    denoting THIS wiki, so we do not need to check these names separately.
+    """
+    return wiki_name in app.cfg.interwiki_map
+
+
+def url_for_item(item_name, wiki_name='', rev=-1, endpoint='frontend.show_item', _external=False):
+    """
+    Compute URL for some local or remote/interwiki item.
+
+    For local items:
+    give <rev> to get the url of some specific revision.
+    give the <endpoint> to get the url of some specific view,
+    give _external=True to compute fully specified URLs.
+
+    For remote/interwiki items:
+    If you just give <item_name> and <wiki_name>, a generic interwiki URL
+    will be built.
+    If you also give <rev> and/or <endpoint>, it is assumed that remote wiki
+    URLs are built in the same way as local URLs.
+    Computed URLs are always fully specified.
+    """
+    if is_local_wiki(wiki_name):
+        if rev is None or rev == -1:
+            url = url_for(endpoint, item_name=item_name, _external=_external)
+        else:
+            url = url_for(endpoint, item_name=item_name, rev=rev, _external=_external)
+    else:
+        try:
+            wiki_base_url = app.cfg.interwiki_map[wiki_name]
+        except KeyError, err:
+            logging.warning("no interwiki_map entry for %r" % wiki_name)
+            url = '' # can we find something useful?
+        else:
+            if (rev is None or rev == -1) and endpoint == 'frontend.show_item':
+                # we just want to show latest revision (no special revision given) -
+                # this is the generic interwiki url support, should work for any remote wiki
+                url = join_wiki(wiki_base_url, item_name)
+            else:
+                # rev and/or endpoint was given, assume same URL building as for local wiki.
+                # we need this for moin wiki farms, e.g. to link from search results to
+                # some specific item/revision in another farm wiki.
+                local_url = url_for(endpoint, item_name=item_name, rev=rev, _external=False)
+                # we know that everything left of the + belongs to script url, but we
+                # just want e.g. +show/42/FooBar to append it to the other wiki's
+                # base URL.
+                i = local_url.index('/+')
+                path = local_url[i+1:]
+                url = wiki_base_url + path
+    return url
+
+
 def split_interwiki(wikiurl):
     """ Split a interwiki name, into wikiname and pagename, e.g:
 
@@ -38,24 +100,6 @@
     return wikiname, pagename
 
 
-def resolve_interwiki(wikiname, pagename):
-    """ Resolve an interwiki reference (wikiname:pagename).
-
-    :param wikiname: interwiki wiki name
-    :param pagename: interwiki page name
-    :rtype: tuple
-    :returns: (wikitag, wikiurl, wikitail, err)
-    """
-    this_wiki_url = request.script_root + '/'
-    if wikiname in ('Self', app.cfg.interwikiname):
-        return (wikiname, this_wiki_url, pagename, False)
-    else:
-        try:
-            return (wikiname, app.cfg.interwiki_map[wikiname], pagename, False)
-        except KeyError:
-            return (wikiname, this_wiki_url, "InterWiki", True)
-
-
 def join_wiki(wikiurl, wikitail):
     """
     Add a (url_quoted) page name to an interwiki url.
@@ -90,7 +134,7 @@
     :returns: (wikiname, itemname)
     """
     homewiki = app.cfg.user_homewiki
-    if homewiki == app.cfg.interwikiname:
+    if is_local_wiki(homewiki):
         homewiki = u'Self'
     return homewiki, username
 
--- a/MoinMoin/util/mime.py	Mon Aug 08 15:16:46 2011 +0530
+++ b/MoinMoin/util/mime.py	Mon Aug 08 15:30:44 2011 +0530
@@ -130,3 +130,7 @@
 # Own types, text type
 type_moin_creole = Type(type='text', subtype='x.moin.creole')
 type_moin_wiki = Type(type='text', subtype='x.moin.wiki')
+
+# Generic types, text type
+type_text_plain = Type(type='text', subtype='plain')
+
--- a/wikiconfig.py	Mon Aug 08 15:16:46 2011 +0530
+++ b/wikiconfig.py	Mon Aug 08 15:30:44 2011 +0530
@@ -47,6 +47,10 @@
 
     # Load the interwiki map from intermap.txt:
     interwiki_map = InterWikiMap.from_file(os.path.join(wikiconfig_dir, 'contrib', 'interwiki', 'intermap.txt')).iwmap
+    # we must add entries for 'Self' and our interwikiname:
+    #interwikiname = 'MyInterWikiName'
+    #interwiki_map[interwikiname] = 'http://localhost:8080/'
+    interwiki_map['Self'] = 'http://localhost:8080/'
 
     sitename = u'My MoinMoin'