changeset 2637:edf9a449fe9d

Merge main.
author Karol 'grzywacz' Nowak <grzywacz@sul.uni.lodz.pl>
date Fri, 03 Aug 2007 17:25:27 +0200
parents e76789b0e537 (current diff) b5efc2a7ba04 (diff)
children 6e84127a1860
files MoinMoin/events/emailnotify.py MoinMoin/events/jabbernotify.py
diffstat 35 files changed, 1059 insertions(+), 318 deletions(-) [+]
line wrap: on
line diff
--- a/MoinMoin/PageEditor.py	Thu Jul 26 04:39:03 2007 +0200
+++ b/MoinMoin/PageEditor.py	Fri Aug 03 17:25:27 2007 +0200
@@ -18,16 +18,13 @@
 
 import os, time, codecs, errno
 
-try:
-    set
-except:
-    from sets import Set as set
 
 from MoinMoin import caching, config, user, wikiutil, error
 from MoinMoin.Page import Page
 from MoinMoin.widget import html
 from MoinMoin.widget.dialog import Status
 from MoinMoin.logfile import editlog, eventlog
+from MoinMoin.support.python_compatibility import set
 from MoinMoin.util import filesys, timefuncs, web
 from MoinMoin.mail import sendmail
 from MoinMoin.events import PageDeletedEvent, PageRenamedEvent, PageCopiedEvent
--- a/MoinMoin/_tests/test_wikiutil.py	Thu Jul 26 04:39:03 2007 +0200
+++ b/MoinMoin/_tests/test_wikiutil.py	Fri Aug 03 17:25:27 2007 +0200
@@ -722,7 +722,7 @@
         tests = [
             # text                    expected output
             (u'\xf6\xf6ll\xdf\xdf',   'A.2BAPYA9g-ll.2BAN8A3w-'),
-            (u'level 2',              'level_2'),
+            (u'level 2',              'level2'),
             (u'',                     'A'),
             (u'123',                  'A123'),
         ]
--- a/MoinMoin/action/AttachFile.py	Thu Jul 26 04:39:03 2007 +0200
+++ b/MoinMoin/action/AttachFile.py	Fri Aug 03 17:25:27 2007 +0200
@@ -554,9 +554,16 @@
         else:
             msg = _('You are not allowed to save a drawing on this page.')
     elif do == 'upload':
-        if request.user.may.write(pagename):
+        overwrite = 0
+        if 'overwrite' in request.form:
+            try:
+                overwrite = int(request.form['overwrite'][0])
+            except:
+                pass
+        if (not overwrite and request.user.may.write(pagename)) or \
+           (overwrite and request.user.may.write(pagename) and request.user.may.delete(pagename)):
             if 'file' in request.form:
-                do_upload(pagename, request)
+                do_upload(pagename, request, overwrite)
             else:
                 # This might happen when trying to upload file names
                 # with non-ascii characters on Safari.
@@ -625,7 +632,7 @@
     request.theme.send_footer(pagename)
     request.theme.send_closing_html()
 
-def do_upload(pagename, request):
+def do_upload(pagename, request, overwrite):
     _ = request.getText
 
     # make filename
@@ -635,12 +642,6 @@
     rename = None
     if 'rename' in request.form:
         rename = request.form['rename'][0].strip()
-    overwrite = 0
-    if 'overwrite' in request.form:
-        try:
-            overwrite = int(request.form['overwrite'][0])
-        except:
-            pass
 
     # if we use twisted, "rename" field is NOT optional, because we
     # can't access the client filename
--- a/MoinMoin/action/SyncPages.py	Thu Jul 26 04:39:03 2007 +0200
+++ b/MoinMoin/action/SyncPages.py	Fri Aug 03 17:25:27 2007 +0200
@@ -12,12 +12,6 @@
 import traceback
 import StringIO # not relevant for speed, so we do not need cStringIO
 
-# Compatiblity to Python 2.3
-try:
-    set
-except NameError:
-    from sets import Set as set
-
 
 from MoinMoin import wikiutil
 from MoinMoin.packages import unpackLine, packLine
@@ -26,6 +20,7 @@
 from MoinMoin.wikidicts import Dict
 from MoinMoin.wikisync import TagStore, UnsupportedWikiException, SyncPage, NotAllowedException
 from MoinMoin.wikisync import MoinLocalWiki, MoinRemoteWiki, UP, DOWN, BOTH, MIMETYPE_MOIN
+from MoinMoin.support.python_compatibility import set
 from MoinMoin.util.bdiff import decompress, patch, compress, textdiff
 from MoinMoin.util import diff3, rpc_aggregator
 
--- a/MoinMoin/config/_tests/test_multiconfig.py	Thu Jul 26 04:39:03 2007 +0200
+++ b/MoinMoin/config/_tests/test_multiconfig.py	Fri Aug 03 17:25:27 2007 +0200
@@ -6,12 +6,8 @@
     @license: GNU GPL, see COPYING for details.
 """
 
-try:
-    set
-except:
-    from sets import Set as set
-
 import py
+from MoinMoin.support.python_compatibility import set
 
 
 class TestPasswordChecker:
--- a/MoinMoin/config/multiconfig.py	Thu Jul 26 04:39:03 2007 +0200
+++ b/MoinMoin/config/multiconfig.py	Fri Aug 03 17:25:27 2007 +0200
@@ -21,6 +21,7 @@
 from MoinMoin import session
 from MoinMoin.packages import packLine
 from MoinMoin.security import AccessControlList
+from MoinMoin.support.python_compatibility import set
 
 _url_re_cache = None
 _farmconfig_mtime = None
@@ -444,10 +445,6 @@
             @return: None if there is no problem with the password,
                      some string with an error msg, if the password is problematic.
         """
-        try:
-            set
-        except:
-            from sets import Set as set
 
         try:
             # in any case, do a very simple built-in check to avoid the worst passwords
--- a/MoinMoin/conftest.py	Thu Jul 26 04:39:03 2007 +0200
+++ b/MoinMoin/conftest.py	Fri Aug 03 17:25:27 2007 +0200
@@ -36,6 +36,7 @@
 
 sys.path.insert(0, str(moindir.join("tests")))
 
+from MoinMoin.support.python_compatibility import set
 
 coverage_modules = set()
 
--- a/MoinMoin/events/emailnotify.py	Thu Jul 26 04:39:03 2007 +0200
+++ b/MoinMoin/events/emailnotify.py	Fri Aug 03 17:25:27 2007 +0200
@@ -9,14 +9,10 @@
     @license: GNU GPL, see COPYING for details.
 """
 
-try:
-    set
-except:
-    from sets import Set as set
-
 from MoinMoin import user
 from MoinMoin.Page import Page
 from MoinMoin.mail import sendmail
+from MoinMoin.support.python_compatibility import set
 from MoinMoin.user import User, getUserList
 import MoinMoin.events as ev
 import MoinMoin.events.notification as notification
--- a/MoinMoin/events/jabbernotify.py	Thu Jul 26 04:39:03 2007 +0200
+++ b/MoinMoin/events/jabbernotify.py	Fri Aug 03 17:25:27 2007 +0200
@@ -10,14 +10,10 @@
 
 import xmlrpclib
 
-try:
-    set
-except:
-    from sets import Set as set
-
 from MoinMoin import error
 from MoinMoin.Page import Page
 from MoinMoin.user import User, getUserList
+from MoinMoin.support.python_compatibility import set
 import MoinMoin.events.notification as notification
 import MoinMoin.events as ev
 
--- a/MoinMoin/filter/_tests/test_filter.py	Thu Jul 26 04:39:03 2007 +0200
+++ b/MoinMoin/filter/_tests/test_filter.py	Fri Aug 03 17:25:27 2007 +0200
@@ -14,7 +14,7 @@
     def make_file(self, data):
         import tempfile
         fname = tempfile.mktemp()
-        f = file(fname, 'w')
+        f = file(fname, 'wb')
         f.write(data)
         f.close()
         return fname
--- a/MoinMoin/formatter/__init__.py	Thu Jul 26 04:39:03 2007 +0200
+++ b/MoinMoin/formatter/__init__.py	Fri Aug 03 17:25:27 2007 +0200
@@ -69,9 +69,13 @@
         return ""
 
     def startContent(self, content_id="content", **kw):
+        if self.page:
+            self.request.begin_include(self.page.page_name)
         return ""
 
     def endContent(self):
+        if self.page:
+            self.request.end_include()
         return ""
 
     # Links ##############################################################
@@ -309,6 +313,7 @@
         """ parser_name MUST be valid!
             writes out the result instead of returning it!
         """
+        # attention: this is copied into text_python!
         parser = wikiutil.searchAndImportPlugin(self.request.cfg, "parser", parser_name)
 
         args = self._get_bang_args(lines[0])
@@ -359,3 +364,40 @@
     def comment(self, text, **kw):
         return ""
 
+    # ID handling #################################################
+
+    def sanitize_to_id(self, text):
+        '''
+        Take 'text' and return something that is a valid ID
+        for this formatter.
+        The default returns the first non-space character of the string.
+
+        Because of the way this is used, it must be idempotent,
+        i.e. calling it on an already sanitized id must yield the
+        original id.
+        '''
+        return text.strip()[:1]
+
+    def make_id_unique(self, id):
+        '''
+        Take an ID and make it unique in the current namespace.
+        '''
+        ns = self.request.include_id
+        if not ns is None:
+            ns = self.sanitize_to_id(ns)
+        id = self.sanitize_to_id(id)
+        id = self.request.make_unique_id(id, ns)
+        return id
+
+    def qualify_id(self, id):
+        '''
+        Take an ID and return a string that is qualified by
+        the current namespace; this default implementation
+        is suitable if the dot ('.') is valid in IDs for your
+        formatter.
+        '''
+        ns = self.request.include_id
+        if not ns is None:
+            ns = self.sanitize_to_id(ns)
+            return '%s.%s' % (ns, id)
+        return id
--- a/MoinMoin/formatter/_tests/test_formatter.py	Thu Jul 26 04:39:03 2007 +0200
+++ b/MoinMoin/formatter/_tests/test_formatter.py	Fri Aug 03 17:25:27 2007 +0200
@@ -77,6 +77,53 @@
         return self.request.redirectedOutput(page.send_page, content_only=1)
 
 
+class TestIdIdempotency:
+    def test_sanitize_to_id_idempotent(self):
+        def _verify(formatter, id):
+            origid = formatter.sanitize_to_id(id)
+            id = origid
+            for i in xrange(3):
+                id = formatter.sanitize_to_id(id)
+                assert id == origid
+
+        formatters = wikiutil.getPlugins("formatter", self.request.cfg)
+        try:
+            from xml.dom import getDOMImplementation
+            dom = getDOMImplementation("4DOM")
+        except ImportError:
+            # if we don't have 4suite installed, the docbook formatter would just raise an exception
+            formatters.remove('text_docbook')
+
+        testids = [
+            r"tho/zeequeen&angu\za",
+            r"quuirahz\iphohsaij,i",
+            r"ashuifa+it[ohchieque",
+            r"ohyie-lakoo`duaghaib",
+            r"eixaepumuqu[ie\ba|eh",
+            r"theegieque;zahmeitie",
+            r"pahcooje&rahkeiz$oez",
+            r"ohjeeng*iequao%fai?p",
+            r"ahfoodahmepooquepee;",
+            r"ubed_aex;ohwebeixah%",
+            r"eitiekicaejuelae=g^u",
+            r"",
+            r'  ',
+            r'--123',
+            r'__$$',
+            r'@@',
+            u'\xf6\xf6llasdf\xe4',
+        ]
+
+        for f_name in formatters:
+            try:
+                formatter = wikiutil.importPlugin(self.request.cfg, "formatter",
+                                                  f_name, "Formatter")
+                f = formatter(self.request)
+                for id in testids:
+                    yield _verify, f, id
+            except wikiutil.PluginAttributeError:
+                pass
+
 coverage_modules = ['MoinMoin.formatter',
                     'MoinMoin.formatter.text_html',
                     'MoinMoin.formatter.text_gedit',
--- a/MoinMoin/formatter/text_html.py	Thu Jul 26 04:39:03 2007 +0200
+++ b/MoinMoin/formatter/text_html.py	Fri Aug 03 17:25:27 2007 +0200
@@ -7,15 +7,15 @@
 """
 import os.path, re
 
-try:
-    set
-except:
-    from sets import Set as set
-
 from MoinMoin.formatter import FormatterBase
 from MoinMoin import wikiutil, i18n
 from MoinMoin.Page import Page
 from MoinMoin.action import AttachFile
+from MoinMoin.support.python_compatibility import set
+
+# insert IDs into output wherever they occur
+# warning: breaks toggle line numbers javascript
+_id_debug = False
 
 line_anchors = True
 prettyprint = False
@@ -179,24 +179,14 @@
 
     def __init__(self, request, **kw):
         FormatterBase.__init__(self, request, **kw)
-
-        # inline tags stack. When an inline tag is called, it goes into
-        # the stack. When a block element starts, all inline tags in
-        # the stack are closed.
-        self._inlineStack = []
-
-        # stack of all tags
-        self._tag_stack = []
         self._indent_level = 0
 
         self._in_code = 0 # used by text_gedit
         self._in_code_area = 0
         self._in_code_line = 0
-        self._code_area_num = 0
         self._code_area_js = 0
         self._code_area_state = ['', 0, -1, -1, 0]
         self._show_section_numbers = None
-        self._content_ids = []
         self.pagelink_preclosed = False
         self._is_included = kw.get('is_included', False)
         self.request = request
@@ -324,13 +314,15 @@
             return ' '.join(all)
         return ''
 
-    def _open(self, tag, newline=False, attr=None, allowed_attrs=None, **kw):
+    def _open(self, tag, newline=False, attr=None, allowed_attrs=None,
+              is_unique=False, **kw):
         """ Open a tag with optional attributes (INTERNAL USE BY HTML FORMATTER ONLY!)
 
         @param tag: html tag, string
         @param newline: render tag so following data is on a separate line
         @param attr: dict with tag attributes
         @param allowed_attrs: list of allowed attributes for this element
+        @param is_unique: ID is already unique
         @param kw: arbitrary attributes and values
         @rtype: string ?
         @return: open tag with attributes as a string
@@ -338,6 +330,23 @@
         # If it is self-closing, then don't expect a closing tag later on.
         is_self_closing = (tag in _self_closing_tags) and ' /' or ''
 
+        # make ID unique
+        id = None
+        if not is_unique:
+            if attr and 'id' in attr:
+                id = self.make_id_unique(attr['id'])
+                id = self.qualify_id(id)
+                attr['id'] = id
+            if 'id' in kw:
+                id = self.make_id_unique(kw['id'])
+                id = self.qualify_id(id)
+                kw['id'] = id
+        else:
+            if attr and 'id' in attr:
+                id = attr['id']
+            if 'id' in kw:
+                id = kw['id']
+
         if tag in _blocks:
             # Block elements
             result = []
@@ -352,22 +361,14 @@
             result.append('<%s%s%s>' % (tag, attributes, is_self_closing))
             if newline:
                 result.append(self._newline())
+            if _id_debug and id:
+                result.append('(%s) ' % id)
             tagstr = ''.join(result)
         else:
             # Inline elements
-            # Add to inlineStack
-            if not is_self_closing:
-                # Only push on stack if we expect a close-tag later
-                self._inlineStack.append(tag)
-            # Format
             tagstr = '<%s%s%s>' % (tag,
                                       self._formatAttributes(attr, allowed_attrs, **kw),
                                       is_self_closing)
-        # XXX SENSE ???
-        #if not self.close:
-        #    self._tag_stack.append(tag)
-        #    if tag in _indenting_tags:
-        #        self._indent_level += 1
         return tagstr
 
     def _close(self, tag, newline=False):
@@ -383,32 +384,15 @@
             tagstr = ''
         elif tag in _blocks:
             # Block elements
-            # Close all tags in inline stack
-            # Work on a copy, because close(inline) manipulate the stack
             result = []
-            stack = self._inlineStack[:]
-            stack.reverse()
-            for inline in stack:
-                result.append(self._close(inline))
-            # Format with newline
             if newline:
                 result.append(self._newline())
             result.append('</%s>' % (tag))
             tagstr = ''.join(result)
         else:
             # Inline elements
-            # Pull from stack, ignore order, that is not our problem.
-            # The code that calls us should keep correct calling order.
-            if tag in self._inlineStack:
-                self._inlineStack.remove(tag)
             tagstr = '</%s>' % tag
 
-        # XXX see other place marked with "SENSE"
-        #if tag in _self_closing_tags:
-        #    self._tag_stack.pop()
-        #    if tag in _indenting_tags:
-        #        # decrease indent level
-        #        self._indent_level -= 1
         if newline:
             tagstr += self._newline()
         return tagstr
@@ -419,23 +403,19 @@
         """ Start page content div.
 
         A link anchor is provided at the beginning of the div, with
-        an id of 'top' or 'top_xxxx' if the content_id argument is
-        set to 'xxxx'.
+        an id of 'top' (modified by the request ID cache).
         """
 
-        # Setup id
-        if content_id != 'content':
-            aid = 'top_%s' % (content_id, )
-        else:
-            aid = 'top'
-        self._content_ids.append(content_id)
+        if hasattr(self, 'page'):
+            self.request.begin_include(self.page.page_name)
+
         result = []
         # Use the content language
         attr = self._langAttr(self.request.content_lang)
         attr['id'] = content_id
         result.append(self._open('div', newline=False, attr=attr,
                                  allowed_attrs=['align'], **kw))
-        result.append(self.anchordef(aid))
+        result.append(self.anchordef('top'))
         if newline:
             result.append('\n')
         return ''.join(result)
@@ -444,23 +424,14 @@
         """ Close page content div.
 
         A link anchor is provided at the end of the div, with
-        an id of 'bottom' or 'bottom_xxxx' if the content_id argument
-        to the previus startContent() call was set to 'xxxx'.
+        an id of 'bottom' (modified by the request ID cache).
         """
 
-        # Setup id
-        try:
-            cid = self._content_ids.pop()
-        except:
-            cid = 'content'
-        if cid != 'content':
-            aid = 'bottom_%s' % (cid, )
-        else:
-            aid = 'bottom'
-
         result = []
-        result.append(self.anchordef(aid))
+        result.append(self.anchordef('bottom'))
         result.append(self._close('div', newline=newline))
+        if hasattr(self, 'page'):
+            self.request.end_include()
         return ''.join(result)
 
     def lang(self, on, lang_name):
@@ -587,7 +558,9 @@
         # Don't add newlines, \n, as it will break pre and
         # line-numbered code sections (from line_achordef() method).
         #return '<a id="%s"></a>' % (id, ) # do not use - this breaks PRE sections for IE
-        return '<span class="anchor" id="%s"></span>' % wikiutil.escape(id, 1)
+        id = self.make_id_unique(id)
+        id = self.qualify_id(id)
+        return '<span class="anchor" id="%s"></span>' % id
 
     def line_anchordef(self, lineno):
         if line_anchors:
@@ -609,10 +582,10 @@
         The id argument, if provided, is instead the id of this link
         itself and not of the target element the link references.
         """
-
         attrs = self._langAttr()
         if name:
-            attrs['href'] = '#%s' % name
+            name = self.sanitize_to_id(name)
+            attrs['href'] = '#' + self.qualify_id(name)
         if 'href' in kw:
             del kw['href']
         if on:
@@ -835,7 +808,6 @@
         non-break spaces.
         """
         tag = 'tt'
-        # Maybe we don't need this, because we have tt will be in inlineStack.
         self._in_code = on
         if on:
             return self._open(tag, allowed_attrs=[], **kw)
@@ -947,12 +919,15 @@
         """
         _ = self.request.getText
         res = []
-        ci = self.request.make_unique_id('CA-%s_%03d' % (code_id, self._code_area_num))
         if on:
+            code_id = self.sanitize_to_id('CA-%s' % code_id)
+            ci = self.qualify_id(self.make_id_unique(code_id))
+
             # Open a code area
             self._in_code_area = 1
             self._in_code_line = 0
-            self._code_area_state = [ci, show, start, step, start]
+            # id in here no longer used
+            self._code_area_state = [None, show, start, step, start]
 
             # Open the code div - using left to right always!
             attr = {'class': 'codearea', 'lang': 'en', 'dir': 'ltr'}
@@ -970,13 +945,13 @@
 document.write('<a href="#" onclick="return togglenumber(\'%s\', %d, %d);" \
                 class="codenumbers">%s<\/a>');
 </script>
-''' % (self._code_area_state[0], self._code_area_state[2], self._code_area_state[3],
+''' % (ci, self._code_area_state[2], self._code_area_state[3],
        _("Toggle line numbers"))
                 res.append(toggleLineNumbersLink)
 
             # Open pre - using left to right always!
-            attr = {'id': self._code_area_state[0], 'lang': 'en', 'dir': 'ltr'}
-            res.append(self._open('pre', newline=True, attr=attr))
+            attr = {'id': ci, 'lang': 'en', 'dir': 'ltr'}
+            res.append(self._open('pre', newline=True, attr=attr, is_unique=True))
         else:
             # Close code area
             res = []
@@ -987,7 +962,6 @@
 
             # Update state
             self._in_code_area = 0
-            self._code_area_num += 1
 
         return ''.join(res)
 
@@ -1207,17 +1181,13 @@
             number = '.'.join([str(x) for x in self.request._fmt_hd_counters[self._show_section_numbers-1:]])
             if number: number += ". "
 
-        # make ID unique
-        if 'id' in kw:
-            kw['id'] = self.request.make_unique_id(kw['id'])
-
         # Add space before heading, easier to check source code
         result = '\n' + self._open('h%d' % heading_depth, **kw)
 
         if self.request.user.show_topbottom:
             result += "%s%s%s%s%s%s" % (
-                       self.url(1, "#bottom", do_escape=0), self.icon('bottom'), self.url(0),
-                       self.url(1, "#top", do_escape=0), self.icon('top'), self.url(0))
+                       self.anchorlink(1, "bottom"), self.icon('bottom'), self.anchorlink(0),
+                       self.anchorlink(1, "top"), self.icon('top'), self.anchorlink(0))
 
         return "%s%s" % (result, number)
 
@@ -1389,3 +1359,5 @@
             return self._open(tag, **kw)
         return self._close(tag)
 
+    def sanitize_to_id(self, text):
+        return wikiutil.anchor_name_from_text(text)
--- a/MoinMoin/formatter/text_python.py	Thu Jul 26 04:39:03 2007 +0200
+++ b/MoinMoin/formatter/text_python.py	Fri Aug 03 17:25:27 2007 +0200
@@ -11,6 +11,7 @@
 import time
 from MoinMoin import wikiutil
 
+
 class Formatter:
     """
         Inserts '<<<>>>' into the page and adds python code to
@@ -76,10 +77,23 @@
                                  # this object reusable
         return "".join(source)
 
+    def __cache_if_no_id(self, name, *args, **kw):
+        if not 'id' in kw:
+            return getattr(self.formatter, name)(*args, **kw)
+        else:
+            return self.__insert_code('request.write(%s.%s(*%r, **%r))' %
+                (self.__formatter, name, args, kw))
+
     def __getattr__(self, name):
-        """ For every thing we have no method/attribute use the formatter
         """
-        return getattr(self.formatter, name)
+        For every thing we have no method/attribute use the formatter
+        unless there's an ID in the keywords.
+        """
+        attr = getattr(self.formatter, name)
+        if callable(attr):
+            return lambda *args, **kw: self.__cache_if_no_id(name, *args, **kw)
+        else:
+            return attr
 
     def __insert_code(self, call):
         """ returns the python code
@@ -87,6 +101,10 @@
         self.code_fragments.append(call)
         return '<<<>>>'
 
+    def __insert_fmt_call(self, function, *args, **kw):
+        return self.__insert_code('request.write(%s.%s(*%r, **%r))' % (
+            self.__formatter, function, args, kw))
+
     def __is_static(self, dependencies):
         for dep in dependencies:
             if dep not in self.static:
@@ -125,24 +143,16 @@
                                       (self.__formatter, on, kw))
 
     def attachment_link(self, url, text, **kw):
-        return self.__insert_code(
-            'request.write(%s.attachment_link(%r, %r, **%r))' %
-            (self.__formatter, url, text, kw))
+        return self.__insert_fmt_call('attachment_link', url, text, **kw)
 
     def attachment_image(self, url, **kw):
-        return self.__insert_code(
-            'request.write(%s.attachment_image(%r, **%r))' %
-            (self.__formatter, url, kw))
+        return self.__insert_fmt_call('attachment_image', url, **kw)
 
     def attachment_drawing(self, url, text, **kw):
-        return self.__insert_code(
-            'request.write(%s.attachment_drawing(%r, %r, **%r))' %
-            (self.__formatter, url, text, kw))
+        return self.__insert_fmt_call('attachment_drawing', url, text, **kw)
 
     def attachment_inlined(self, url, text, **kw):
-        return self.__insert_code(
-            'request.write(%s.attachment_inlined(%r, %r, **%r))' %
-            (self.__formatter, url, text, kw))
+        return self.__insert_fmt_call('attachment_inlined', url, text, **kw)
 
     def heading(self, on, depth, **kw):
         if on:
@@ -159,27 +169,26 @@
         if self.__is_static(['user']):
             return self.formatter.icon(type)
         else:
-            return self.__insert_code('request.write(%s.icon(%r))' %
-                                      (self.__formatter, type))
+            return self.__insert_fmt_call('icon', type)
 
     smiley = icon
 
     def span(self, on, **kw):
         if on and 'comment' in kw.get('css_class', '').split():
-            return self.__insert_code('request.write(%s.span(%r, **%r))' %
-                                      (self.__formatter, on, kw))
+            return self.__insert_fmt_call('span', on, **kw)
         else:
             return self.formatter.span(on, **kw)
 
     def div(self, on, **kw):
         if on and 'comment' in kw.get('css_class', '').split():
-            return self.__insert_code('request.write(%s.div(%r, **%r))' %
-                                      (self.__formatter, on, kw))
+            return self.__insert_fmt_call('div', on, **kw)
         else:
             return self.formatter.div(on, **kw)
 
     def macro(self, macro_obj, name, args):
         if self.__is_static(macro_obj.get_dependencies(name)):
+            # XXX: why is this necessary??
+            macro_obj.formatter = self
             return macro_obj.execute(name, args)
         else:
             return self.__insert_code(
@@ -197,10 +206,57 @@
             Dependencies = self.defaultDependencies
 
         if self.__is_static(Dependencies):
-            return self.formatter.parser(parser_name, lines)
+            # XXX: copied from FormatterBase because we can't inherit from it
+            parser = wikiutil.searchAndImportPlugin(self.request.cfg, "parser", parser_name)
+
+            args = self._get_bang_args(lines[0])
+            if args is not None:
+                lines = lines[1:]
+            p = parser('\n'.join(lines), self.request, format_args=args)
+            p.format(self)
+            del p
+            return ''
         else:
             return self.__insert_code('%s%s.parser(%r, %r)' %
                                       (self.__adjust_formatter_state(),
                                        self.__formatter,
                                        parser_name, lines))
 
+    def startContent(self, content_id='content', newline=True, **kw):
+        # we need to tell the request about the start of the content
+        return self.__insert_fmt_call('startContent', content_id, newline, **kw)
+
+    def endContent(self, newline=True):
+        # we need to tell the request about the end of the content
+        return self.__insert_fmt_call('endContent', newline)
+
+    def anchorlink(self, on, name='', **kw):
+        # anchorlink depends on state now, namely the include ID in the request.
+        if on:
+            return self.__insert_fmt_call('anchorlink', on, name, **kw)
+        else:
+            return self.formatter.anchorlink(on, name=name, **kw)
+
+    def line_anchorlink(self, on, lineno=0):
+        # anchorlink depends on state now, namely the include ID in the request.
+        if on:
+            return self.__insert_fmt_call('line_anchorlink', on, lineno)
+        else:
+            return self.formatter.line_anchorlink(on, lineno)
+
+    def code_area(self, on, code_id, code_type='code', show=0, start=-1, step=-1):
+        if on:
+            # update state of the HTML formatter
+            self.formatter._in_code_area = 1
+            self.formatter._in_code_line = 0
+            self.formatter._code_area_state = [None, show, start, step, start]
+            return self.__insert_fmt_call('code_area', on, code_id, code_type, show,
+                                          start, step)
+        else:
+            return self.formatter.code_area(False, code_id, code_type, show, start, step)
+
+    def line_anchordef(self, lineno):
+        return self.__insert_fmt_call('line_anchordef', lineno)
+
+    def anchordef(self, id):
+        return self.__insert_fmt_call('anchordef', id)
--- a/MoinMoin/macro/Action.py	Thu Jul 26 04:39:03 2007 +0200
+++ b/MoinMoin/macro/Action.py	Fri Aug 03 17:25:27 2007 +0200
@@ -2,17 +2,7 @@
 """
     MoinMoin - Create an action link
 
-    Usage:
-
-        [[Action(action)]]
-
-        Create a link to page with ?action=action and the text action
-
-        [[Action(action, text)]]
-
-        Same with custom text.
-
-    @copyright: 2004 Johannes Berg <johannes@sipsolutions.de>
+    @copyright: 2004, 2007 Johannes Berg <johannes@sipsolutions.net>
                 2007 by MoinMoin:ReimarBauer
     @license: GNU GPL, see COPYING for details.
 """
@@ -22,69 +12,36 @@
 Dependencies = ["language"]
 
 
-class ActionLink:
-    """ ActionLink - link to page with valid action """
-
-    arguments = ['action', 'text']
-
-    def __init__(self, macro, args):
-        self.macro = macro
-        self.request = macro.request
-        self.args = self.getArgs(args)
-
-    def getValidActions(self):
-        """ lists all valid actions """
-        from MoinMoin import action
-        # builtin
-        actions_builtin = action.names
-        # global
-        actions_global = ([x for x in action.modules
-                           if not x in self.macro.request.cfg.actions_excluded])
-        # local
-        actions_local = ([x for x in wikiutil.wikiPlugins('action', self.macro.cfg)
-                          if not x in self.macro.request.cfg.actions_excluded])
-
-        return actions_builtin + actions_global + actions_local
-
-    def getArgs(self, argstr):
-        """ Temporary function until Oliver Graf args parser is finished
+def _get_valid_actions(macro):
+    """ lists all valid actions """
+    from MoinMoin import action
+    # builtin
+    actions_builtin = action.names
+    # global
+    actions_global = ([x for x in action.modules
+                       if not x in macro.request.cfg.actions_excluded])
+    # local
+    actions_local = ([x for x in wikiutil.wikiPlugins('action', macro.cfg)
+                      if not x in macro.request.cfg.actions_excluded])
 
-        @param string: string from the wiki markup [[NewPage(string)]]
-        @rtype: dict
-        @return: dictionary with macro options
-        """
-        if not argstr:
-            return {}
-        args = [s.strip() for s in argstr.split(',')]
-        args = dict(zip(self.arguments, args))
-        return args
-
-    def renderInText(self):
-        """ Render macro in text context
-
-        The parser should decide what to do if this macro is placed in a
-        paragraph context.
-        """
-        _ = self.request.getText
+    return actions_builtin + actions_global + actions_local
 
-        # Default to show page instead of an error message (too lazy to
-        # do an error message now).
-        action = self.args.get('action', 'show')
+def macro_Action(macro, action=u'show', text=None, _kwargs=None):
+    _ = macro.request.getText
+    if text is None:
+        text = action
+    if not _kwargs:
+        _kwargs = {}
 
-        # Use translated text or action name
-        text = self.args.get('text', action)
-        text = _(text, formatted=False)
-        text = wikiutil.escape(text, 1)
-        action, args = (action.split('&') + [None] * 2)[:2]
-        if action in self.getValidActions():
-            # Create link
-            page = self.macro.formatter.page
-            link = page.link_to(self.request, text, querystr='action=%s&%s' % (action, args))
-            return link
-        else:
-            return text
-
-def execute(macro, args):
-    """ Temporary glue code to use with moin current macro system """
-    return ActionLink(macro, args).renderInText()
-
+    text = _(text, formatted=False)
+    if action in _get_valid_actions(macro):
+        page = macro.formatter.page
+        _kwargs['action'] = action
+        url = page.url(macro.request, querystr=_kwargs, relative=False)
+        return ''.join([
+            macro.formatter.url(1, url),
+            macro.formatter.text(text),
+            macro.formatter.url(0),
+        ])
+    else:
+        return macro.formatter.text(text)
--- a/MoinMoin/macro/AdvancedSearch.py	Thu Jul 26 04:39:03 2007 +0200
+++ b/MoinMoin/macro/AdvancedSearch.py	Fri Aug 03 17:25:27 2007 +0200
@@ -9,9 +9,9 @@
 """
 
 from MoinMoin.i18n import languages
-from MoinMoin.support import sorted
 from MoinMoin.widget import html
 from MoinMoin.util.web import makeSelection
+from MoinMoin.support.python_compatibility import sorted
 
 import mimetypes
 
--- a/MoinMoin/macro/Include.py	Thu Jul 26 04:39:03 2007 +0200
+++ b/MoinMoin/macro/Include.py	Fri Aug 03 17:25:27 2007 +0200
@@ -52,7 +52,7 @@
         titles.append((title_text, level))
     return titles
 
-def execute(macro, text, args_re=re.compile(_args_re_pattern), title_re=re.compile(_title_re, re.M), called_by_toc=0):
+def execute(macro, text, args_re=re.compile(_args_re_pattern), title_re=re.compile(_title_re, re.M)):
     request = macro.request
     _ = request.getText
 
@@ -172,10 +172,6 @@
         ##result.append("*** f=%s t=%s ***" % (from_re, to_re))
         ##result.append("*** f=%d t=%d ***" % (from_pos, to_pos))
 
-        if called_by_toc:
-            result.append(inc_page.get_raw_body())
-            continue
-
         if not hasattr(request, "_Include_backto"):
             request._Include_backto = this_page.page_name
 
@@ -191,11 +187,12 @@
                               macro.formatter.text(heading) +
                               macro.formatter.heading(0, level))
             else:
-                hid = wikiutil.anchor_name_from_text(heading)
-                link = inc_page.link_to(request, heading, css_class="include-heading-link")
+                url = inc_page.url(request, relative=False)
                 result.extend([
-                    macro.formatter.heading(1, level, id=hid),
-                    macro.formatter.rawHTML(link),
+                    macro.formatter.heading(1, level, id=heading),
+                    macro.formatter.url(1, url, css="include-heading-link"),
+                    macro.formatter.text(heading),
+                    macro.formatter.url(0),
                     macro.formatter.heading(0, level),
                 ])
 
@@ -207,9 +204,9 @@
         strfile = StringIO.StringIO()
         request.redirect(strfile)
         try:
-            cid = request.make_unique_id("Include_%s" % wikiutil.quoteWikinameURL(inc_page.page_name))
-            inc_page.send_page(content_only=1, content_id=cid,
-                               omit_footnotes=True)
+            inc_page.send_page(content_only=True,
+                               omit_footnotes=True,
+                               count_hit=False)
             result.append(strfile.getvalue())
         finally:
             request.redirect()
--- a/MoinMoin/macro/RandomQuote.py	Thu Jul 26 04:39:03 2007 +0200
+++ b/MoinMoin/macro/RandomQuote.py	Fri Aug 03 17:25:27 2007 +0200
@@ -49,7 +49,7 @@
     quote = random.choice(quotes)
     page.set_raw_body(quote, 1)
     quote = macro.request.redirectedOutput(page.send_page,
-        content_only=1, content_id="RandomQuote_%s" % wikiutil.quoteWikinameFS(page.page_name) )
+        content_only=1, content_id="RandomQuote")
 
     return quote
 
--- a/MoinMoin/macro/TableOfContents.py	Thu Jul 26 04:39:03 2007 +0200
+++ b/MoinMoin/macro/TableOfContents.py	Fri Aug 03 17:25:27 2007 +0200
@@ -2,6 +2,25 @@
 """
     MoinMoin - TableOfContents Macro
 
+    The macro works as follows: First, it renders the page using
+    the TOCFormatter (below) to get access to the outline of the
+    page. During the page rendering, only macros whose
+    'generates_headings' property is set and True are rendered,
+    most macros don't generate any headings and thus need not be
+    executed speeding up the process considerably.
+
+    The generated outline is then written to the output.
+
+    However, this is not all. Consider included pages that include
+    a TOC themselves! First of all, TOCs don't generate headings
+    so we avoid recursion during the collection process. Secondly,
+    we always keep track of which content we are in and the
+    formatter's heading method is responsible for making all
+    IDs they generate unique. We use the same algorithm to make
+    the IDs unique during the TOCFormatter rendering step so that
+    in the end we can output the same IDs and the TOC is linked
+    correctly, even in the case of multiple nested inclusions.
+
     @copyright: 2007 MoinMoin:JohannesBerg
     @license: GNU GPL, see COPYING for details.
 """
@@ -12,7 +31,8 @@
 from MoinMoin import wikiutil
 
 
-Dependencies = ['page']
+# cannot be cached because of TOCs in included pages
+Dependencies = ['time']
 
 class TOCFormatter(FormatterBase):
     def __init__(self, request, **kw):
@@ -25,11 +45,21 @@
             self.collected_headings[-1][2] += text
         return text
 
+    def startContent(self, *args, **kw):
+        res = FormatterBase.startContent(self, *args, **kw)
+        self.collected_headings.append([1, self.request.include_id, None])
+        return res
+
+    def endContent(self):
+        res = FormatterBase.endContent(self)
+        self.collected_headings.append([0, self.request.include_id, None])
+        return res
+
     def heading(self, on, depth, **kw):
         id = kw.get('id', None)
+        self.in_heading = on
         if not id is None:
-            id = self.request.make_unique_id(kw['id'])
-        self.in_heading = on
+            id = self.request._tocfm_orig_formatter.make_id_unique(id)
         if on:
             self.collected_headings.append([depth, id, u''])
         return ''
@@ -53,8 +83,6 @@
     sysmsg = _anything_return_empty
     startDocument = _anything_return_empty
     endDocument = _anything_return_empty
-    startContent = _anything_return_empty
-    endContent = _anything_return_empty
     pagelink = _anything_return_empty
     interwikilink = _anything_return_empty
     url = _anything_return_empty
@@ -114,7 +142,11 @@
 
     pname = macro.formatter.page.page_name
 
+    macro.request.push_unique_ids()
+
     macro.request._tocfm_collected_headings = []
+    macro.request._tocfm_orig_formatter = macro.formatter
+
     tocfm = TOCFormatter(macro.request)
     p = Page(macro.request, pname, formatter=tocfm, rev=macro.request.rev)
     output = macro.request.redirectedOutput(p.send_page,
@@ -122,8 +154,6 @@
                                             count_hit=False,
                                             omit_footnotes=True)
 
-    macro.request.reset_unique_ids()
-
     _ = macro.request.getText
 
     result = [
@@ -136,21 +166,46 @@
     lastlvl = 0
 
     for lvl, id, txt in macro.request._tocfm_collected_headings:
-        if lvl > maxdepth or not id:
+        if txt is None:
+            incl_id = id
             continue
+        if lvl > maxdepth or id is None:
+            continue
+
+        # will be reset by pop_unique_ids below
+        macro.request.include_id = incl_id
+
+        need_li = lastlvl >= lvl
         while lastlvl > lvl:
-            result.append(macro.formatter.number_list(0))
+            result.extend([
+                macro.formatter.listitem(0),
+                macro.formatter.number_list(0),
+            ])
             lastlvl -= 1
         while lastlvl < lvl:
-            result.append(macro.formatter.number_list(1))
+            result.extend([
+                macro.formatter.number_list(1),
+                macro.formatter.listitem(1),
+            ])
             lastlvl += 1
+        if need_li:
+            result.extend([
+                macro.formatter.listitem(0),
+                macro.formatter.listitem(1),
+            ])
         result.extend([
-            macro.formatter.listitem(1),
+            '\n',
             macro.formatter.anchorlink(1, id),
             macro.formatter.text(txt),
             macro.formatter.anchorlink(0),
-            macro.formatter.listitem(0),
         ])
 
+    while lastlvl > 0:
+        result.append(macro.formatter.listitem(0))
+        result.append(macro.formatter.number_list(0))
+        lastlvl -= 1
+
+    macro.request.pop_unique_ids()
+
     result.append(macro.formatter.div(0))
     return ''.join(result)
--- a/MoinMoin/macro/_tests/test_Action.py	Thu Jul 26 04:39:03 2007 +0200
+++ b/MoinMoin/macro/_tests/test_Action.py	Fri Aug 03 17:25:27 2007 +0200
@@ -57,7 +57,7 @@
         text = '= title1 =\n||A||B||\n'
         self._createTestPage(text)
         m = self._make_macro()
-        result = Action.execute(m, 'raw')
+        result = Action.macro_Action(m, 'raw')
 
         assert result == expected
 
--- a/MoinMoin/parser/_tests/test_text_moin_wiki.py	Thu Jul 26 04:39:03 2007 +0200
+++ b/MoinMoin/parser/_tests/test_text_moin_wiki.py	Fri Aug 03 17:25:27 2007 +0200
@@ -40,8 +40,9 @@
         output = StringIO()
         saved_write = self.request.write
         self.request.write = output.write
+        self.request.page = page
         try:
-            Parser(body, self.request).format(page.formatter)
+            page.send_page(content_only=True, do_cache=False)
         finally:
             self.request.write = saved_write
         return output.getvalue()
@@ -472,15 +473,17 @@
 
     def testUrlAfterBlock(self):
         """ parser.wiki: tests url after block element """
-        cases = ('some text {{{some block text\n}}} and a URL http://moinmo.in/',
-                 'some text {{{some block text\n}}} and a WikiName')
+        case = 'some text {{{some block text\n}}} and a URL http://moinmo.in/'
 
-        for case in cases:
-            result = self.parse(case).strip()
-            match = result.endswith('</a>')
-            expected = True
-            self.assert_(match is True,
-                         'Expected "%(expected)s" but got "%(result)s"' % locals())
+        result = self.parse(case)
+        assert result.find('and a URL <a ') > -1
+
+    def testWikiNameAfterBlock(self):
+        """ parser.wiki: tests url after block element """
+        case = 'some text {{{some block text\n}}} and a WikiName'
+
+        result = self.parse(case)
+        assert result.find('and a <a ') > -1
 
     def testColorizedPythonParserAndNestingPreBrackets(self):
         """ tests nested {{{ }}} for the python colorized parser
@@ -492,10 +495,7 @@
 pattern = re.compile(r'{{{This is some nested text}}}')}}}"""
         output = self.parse(raw)
         output = ''.join(output)
-        result = "r'{{{This is some nested text}}}'" in output
-        expected = True
-
-        assert expected == result
+        assert "r'{{{This is some nested text}}}'" in output
 
     def testColorizedPythonParserAndNestingPreBracketsWithLinebreak(self):
         """ tests nested {{{ }}} for the python colorized parser
@@ -508,11 +508,7 @@
 }}}"""
         output = self.parse(raw)
         output = ''.join(output)
-        print output
-        result = "r'{{{This is some nested text}}}'" in output
-        expected = True
-
-        assert expected == result
+        assert "r'{{{This is some nested text}}}'" in output
 
     def testNestingPreBrackets(self):
         """ tests nested {{{ }}} for the wiki parser
@@ -523,10 +519,7 @@
 You can use {{{brackets}}}}}}"""
         output = self.parse(raw)
         output = ''.join(output)
-        result = 'You can use {{{brackets}}}' in output
-        expected = True
-
-        assert expected == result
+        assert 'You can use {{{brackets}}}' in output
 
     def testNestingPreBracketsWithLinebreak(self):
         """ tests nested {{{ }}} for the wiki parser
@@ -539,10 +532,7 @@
         output = self.parse(raw)
         output = ''.join(output)
         print output
-        result = 'You can use {{{brackets}}}' in output
-        expected = True
-
-        assert expected == result
+        assert 'You can use {{{brackets}}}' in output
 
     def testTextBeforeNestingPreBrackets(self):
         """ tests text before nested {{{ }}} for the wiki parser
@@ -553,10 +543,7 @@
 You can use {{{brackets}}}}}}"""
         output = self.parse(raw)
         output = ''.join(output)
-        result = 'Example <span class="anchor" id="line-0"></span><ul><li style="list-style-type:none"><span class="anchor" id="line-0"></span><pre>You can use {{{brackets}}}</pre>' in output
-        expected = True
-
-        assert expected == result
+        assert 'Example <span class="anchor" id="line-0-1"></span><ul><li style="list-style-type:none"><span class="anchor" id="line-0-2"></span><pre>You can use {{{brackets}}}</pre>' in output
 
     def testManyNestingPreBrackets(self):
         """ tests two nestings  ({{{ }}} and {{{ }}}) in one line for the wiki parser
@@ -580,7 +567,7 @@
         raw = 'def {{{ghi}}} jkl {{{mno}}} pqr'
         output = ''.join(self.parse(raw))
         # expected output copied from 1.5
-        expected = 'def <tt>ghi</tt> jkl <tt>mno</tt><span class="anchor" id="line-0"></span>pqr'
+        expected = 'def <tt>ghi</tt> jkl <tt>mno</tt><span class="anchor" id="line-0-1"></span>pqr'
         assert expected in output
 
 class TestLinkingMarkup(ParserTestCase):
@@ -622,4 +609,3 @@
 
 
 coverage_modules = ['MoinMoin.parser.text_moin_wiki']
-
--- a/MoinMoin/parser/text_moin_wiki.py	Thu Jul 26 04:39:03 2007 +0200
+++ b/MoinMoin/parser/text_moin_wiki.py	Fri Aug 03 17:25:27 2007 +0200
@@ -433,9 +433,11 @@
                 words = words * 2
 
             if words[0].startswith('#'): # anchor link
-                return (self.formatter.url(1, words[0]) +
-                        self.formatter.text(words[1]) +
-                        self.formatter.url(0))
+                res = []
+                res.append(self.formatter.anchorlink(1, words[0][1:]))
+                res.append(self.formatter.text(words[1]))
+                res.append(self.formatter.anchorlink(0))
+                return ''.join(res)
         else:
             scheme, rest = scheme_and_rest
             if scheme == "wiki":
@@ -762,11 +764,10 @@
         depth = min(5, level)
 
         title_text = h[level:-level].strip()
-        id = wikiutil.anchor_name_from_text(title_text)
 
         return ''.join([
             self._closeP(),
-            self.formatter.heading(1, depth, id=id),
+            self.formatter.heading(1, depth, id=title_text),
             self.formatter.text(title_text),
             self.formatter.heading(0, depth),
         ])
--- a/MoinMoin/request/__init__.py	Thu Jul 26 04:39:03 2007 +0200
+++ b/MoinMoin/request/__init__.py	Fri Aug 03 17:25:27 2007 +0200
@@ -11,13 +11,9 @@
 import logging
 import Cookie
 
-try:
-    set
-except:
-    from sets import Set as set
-
 from MoinMoin import config, wikiutil, user, caching, error
 from MoinMoin.config import multiconfig
+from MoinMoin.support.python_compatibility import set
 from MoinMoin.util import IsWin9x
 from MoinMoin import auth
 from urllib import quote, quote_plus
@@ -692,7 +688,7 @@
         self.current_lang = self.cfg.language_default
 
         # caches unique ids
-        self._page_ids = {}
+        self.init_unique_ids()
 
         if hasattr(self, "_fmt_hd_counters"):
             del self._fmt_hd_counters
@@ -1267,8 +1263,8 @@
         except MoinMoinFinish:
             pass
         except Exception, err:
+            self.fail(err)
             self.finish()
-            self.fail(err)
 
         if self.cfg.log_timing:
             self.timing_log(False, action)
@@ -1380,7 +1376,7 @@
         from MoinMoin import failure
         failure.handle(self, err)
 
-    def make_unique_id(self, base):
+    def make_unique_id(self, base, namespace=None):
         """
         Generates a unique ID using a given base name. Appends a running count to the base.
 
@@ -1388,20 +1384,74 @@
 
         @param base: the base of the id
         @type base: unicode
+        @param namespace: the namespace for the ID, used when including pages
 
-        @returns: an unique id
+        @returns: a unique (relatively to the namespace) ID
         @rtype: unicode
         """
         if not isinstance(base, unicode):
             base = unicode(str(base), 'ascii', 'ignore')
-        count = self._page_ids.get(base, -1) + 1
-        self._page_ids[base] = count
-        if count == 0:
+        if not namespace in self._page_ids:
+            self._page_ids[namespace] = {}
+        count = self._page_ids[namespace].get(base, -1) + 1
+        self._page_ids[namespace][base] = count
+        if not count:
             return base
         return u'%s-%d' % (base, count)
 
-    def reset_unique_ids(self):
+    def init_unique_ids(self):
+        '''Initialise everything needed for unique IDs'''
+        self._unique_id_stack = []
+        self._page_ids = {None: {}}
+        self.include_id = None
+        self._include_stack = []
+
+    def push_unique_ids(self):
+        '''
+        Used by the TOC macro, this ensures that the ID namespaces
+        are reset to the status when the current include started.
+        This guarantees that doing the ID enumeration twice results
+        in the same results, on any level.
+        '''
+        self._unique_id_stack.append((self._page_ids, self.include_id))
+        self.include_id, pids = self._include_stack[-1]
+        # make a copy of the containing ID namespaces, that is to say
+        # go back to the level we had at the previous include
         self._page_ids = {}
+        for namespace in pids:
+            self._page_ids[namespace] = pids[namespace].copy()
+
+    def pop_unique_ids(self):
+        '''
+        Used by the TOC macro to reset the ID namespaces after
+        having parsed the page for TOC generation and after
+        printing the TOC.
+        '''
+        self._page_ids, self.include_id = self._unique_id_stack.pop()
+
+    def begin_include(self, base):
+        '''
+        Called by the formatter when a document begins, which means
+        that include causing nested documents gives us an include
+        stack in self._include_id_stack.
+        '''
+        pids = {}
+        for namespace in self._page_ids:
+            pids[namespace] = self._page_ids[namespace].copy()
+        self._include_stack.append((self.include_id, pids))
+        self.include_id = self.make_unique_id(base)
+        # if it's the page name then set it to None so we don't
+        # prepend anything to IDs, but otherwise keep it.
+        if self.page and self.page.page_name == self.include_id:
+            self.include_id = None
+
+    def end_include(self):
+        '''
+        Called by the formatter when a document ends, restores
+        the current include ID to the previous one and discards
+        the page IDs state we kept around for push_unique_ids().
+        '''
+        self.include_id, pids = self._include_stack.pop()
 
     def httpDate(self, when=None, rfc='1123'):
         """ Returns http date string, according to rfc2068
--- a/MoinMoin/script/migration/1050800.py	Thu Jul 26 04:39:03 2007 +0200
+++ b/MoinMoin/script/migration/1050800.py	Fri Aug 03 17:25:27 2007 +0200
@@ -1,13 +1,17 @@
+#!/usr/bin/env python
 # -*- coding: iso-8859-1 -*-
 """
-    MoinMoin - dummy migration terminator script
+    MoinMoin - 1st pass of 1.6 migration
 
-    This must be the last migration script.
-
-    @copyright: 2006 by Thomas Waldmann
+    @copyright: 2007 by Thomas Waldmann
     @license: GNU GPL, see COPYING for details.
 """
 
+from _conv160 import DataConverter
+
 def execute(script, data_dir, rev):
-    return None
+    # the first pass just creates <data_dir>/rename1.txt
+    dc = DataConverter(script.request, data_dir, None)
+    dc.pass1()
+    return 1059999
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/script/migration/1059999.py	Fri Aug 03 17:25:27 2007 +0200
@@ -0,0 +1,34 @@
+#!/usr/bin/env python
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - 2nd pass of 1.6 migration
+
+    @copyright: 2007 by Thomas Waldmann
+    @license: GNU GPL, see COPYING for details.
+"""
+
+import os, shutil
+
+from _conv160 import DataConverter
+
+def execute(script, data_dir, rev):
+    rename1_map = os.path.join(data_dir, 'rename1.txt')
+    rename2_map = os.path.join(data_dir, 'rename2.txt')
+    if not os.path.exists(rename2_map):
+        print "You must first edit %s." % rename1_map
+        print "For editing it, please use an editor that honours TAB chars and is able to edit UTF-8 encoded files."
+        print "Carefully edit - the fields are separated by a single TAB char, do not change this!"
+        print "You may ONLY edit the rightmost field (this is the NEW name - in case you want to rename the page or file)."
+        print
+        print "After you have finished editing, rename the file to %s and re-issue the moin migrate command." % rename2_map
+        return None # terminate here
+    # the second pass does the conversion, reading <data_dir>/rename2.txt
+    src_data_dir = os.path.abspath(os.path.join(data_dir, '..', 'data.pre160')) # keep the orig data_dir here
+    dst_data_dir = data_dir
+    shutil.move(data_dir, src_data_dir)
+    os.mkdir(dst_data_dir)
+    shutil.move(os.path.join(src_data_dir, 'cache'), os.path.join(dst_data_dir, 'cache')) # mig script has locks there
+    dc = DataConverter(script.request, src_data_dir, dst_data_dir)
+    dc.pass2()
+    return 1060000
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/script/migration/1060000.py	Fri Aug 03 17:25:27 2007 +0200
@@ -0,0 +1,13 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - dummy migration terminator script
+
+    This must be the last migration script.
+
+    @copyright: 2006 by Thomas Waldmann
+    @license: GNU GPL, see COPYING for details.
+"""
+
+def execute(script, data_dir, rev):
+    return None
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/script/migration/_conv160.py	Fri Aug 03 17:25:27 2007 +0200
@@ -0,0 +1,494 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - migration from base rev 105xxyy
+
+    What it should do when it is ready:
+
+    a) reverse underscore == blank stuff in pagenames (introducing this was a fault)
+
+                   pagename            quoted pagename
+       -----------------------------------------------------
+       old         MainPage/Sub_Page   MainPage(2f)Sub_Page
+       new         MainPage/Sub Page   MainPage(2f)Sub(20)Page    or
+       new         MainPage/Sub_Page   MainPage(2f)Sub_Page       (user has to decide by editing rename1.txt)
+
+
+                   markup
+       ----------------------------------------------------
+       old         MoinMoin:MainPage/Sub_Page    ../Sub_Page2
+       new         MoinMoin:"MainPage/Sub Page"  "../Sub Page2"???? (TODO check if this works)
+
+
+    b) decode url encoded chars in attachment names (and quote the whole fname):
+
+                   markup
+       ----------------------------------------------------
+       old         attachment:file%20with%20blanks.txt
+       new         attachment:"file with blanks.txt"
+
+    c) users: move bookmarks from separate files into user profile
+    d) users: generate new name[] for lists and name{} for dicts
+
+    TODO:
+        * process page content / convert markup
+
+    DONE:
+        pass 1
+        * creating the rename.txt works
+        pass 2
+        * renaming of pages works
+         * renamed pagedirs
+         * renamed page names in global edit-log
+         * renamed page names in local edit-log
+         * renamed page names in event-log
+         * renamed pages in user subscribed pages
+         * renamed pages in user quicklinks
+        * renaming of attachments works
+         * renamed attachment files
+         * renamed attachment names in global edit-log
+         * renamed attachment names in local edit-log
+        * migrate separate user bookmark files into user profiles
+        * support new dict/list syntax in user profiles
+
+    @copyright: 2007 by Thomas Waldmann
+    @license: GNU GPL, see COPYING for details.
+"""
+
+import os.path, sys
+import codecs, urllib, glob
+
+from MoinMoin import config, wikiutil
+from MoinMoin.script.migration.migutil import opj, listdir, copy_file, move_file, copy_dir
+
+import mimetypes # this MUST be after wikiutil import!
+
+def markup_converter(text, renames):
+    """ Convert the <text> content of some Page, using <renames> dict to rename
+        links correctly. Additionally, convert some changed markup.
+    """
+    if "#format wiki" not in text and "#format" in text:
+        return text # this is not a wiki page, leave it as is
+    # TODO convert markup of page
+    return text
+
+
+class EventLog:
+    def __init__(self, request, fname):
+        self.request = request
+        self.fname = fname
+        self.data = None
+        self.renames = {}
+
+    def read(self):
+        """ read complete event-log from disk """
+        data = []
+        f = file(self.fname, 'r')
+        for line in f:
+            line = line.replace('\r', '').replace('\n', '')
+            if not line.strip(): # skip empty lines
+                continue
+            fields = line.split('\t')
+            timestamp, action, kvpairs = fields
+            timestamp = int(timestamp)
+            kvdict = wikiutil.parseQueryString(kvpairs)
+            data.append((timestamp, action, kvdict))
+        self.data = data
+
+    def write(self, fname):
+        """ write complete event-log to disk """
+        f = file(fname, 'w')
+        for timestamp, action, kvdict in self.data:
+            pagename = kvdict.get('pagename')
+            if pagename and ('PAGE', pagename) in self.renames:
+                kvdict['pagename'] = self.renames[('PAGE', pagename)]
+            kvpairs = wikiutil.makeQueryString(kvdict, want_unicode=False)
+            fields = str(timestamp), action, kvpairs
+            line = '\t'.join(fields) + '\n'
+            f.write(line)
+        f.close()
+
+    def copy(self, destfname, renames):
+        self.renames = renames
+        self.read()
+        self.write(destfname)
+
+
+class EditLog:
+    def __init__(self, request, fname):
+        self.request = request
+        self.fname = fname
+        self.data = None
+        self.renames = {}
+
+    def read(self):
+        """ read complete edit-log from disk """
+        data = {}
+        f = file(self.fname, 'r')
+        for line in f:
+            line = line.replace('\r', '').replace('\n', '')
+            if not line.strip(): # skip empty lines
+                continue
+            fields = line.split('\t') + [''] * 9
+            timestamp, rev, action, pagename, ip, hostname, userid, extra, comment = fields[:9]
+            timestamp = int(timestamp)
+            rev = int(rev)
+            pagename = wikiutil.unquoteWikiname(pagename)
+            data[(timestamp, rev, pagename)] = (timestamp, rev, action, pagename, ip, hostname, userid, extra, comment)
+        self.data = data
+
+    def write(self, fname):
+        """ write complete edit-log to disk """
+        editlog = self.data.items()
+        editlog.sort()
+        f = file(fname, "w")
+        for key, fields in editlog:
+            timestamp, rev, action, pagename, ip, hostname, userid, extra, comment = fields
+            if action.startswith('ATT'):
+                try:
+                    fname = urllib.unquote(extra).decode('utf-8')
+                except UnicodeDecodeError:
+                    fname = urllib.unquote(extra).decode('iso-8859-1')
+                if ('FILE', pagename, fname) in self.renames:
+                    fname = self.renames[('FILE', pagename, fname)]
+                extra = urllib.quote(fname.encode('utf-8'))
+            if ('PAGE', pagename) in self.renames:
+                pagename = self.renames[('PAGE', pagename)]
+            timestamp = str(timestamp)
+            rev = '%08d' % rev
+            pagename = wikiutil.quoteWikinameFS(pagename)
+            fields = timestamp, rev, action, pagename, ip, hostname, userid, extra, comment
+            log_str = '\t'.join(fields) + '\n'
+            f.write(log_str)
+        f.close()
+
+    def copy(self, destfname, renames):
+        self.renames = renames
+        self.read()
+        self.write(destfname)
+
+
+class PageRev:
+    """ a single revision of a page """
+    def __init__(self, request, rev_dir, rev):
+        self.request = request
+        self.rev_dir = rev_dir
+        self.rev = rev
+
+    def read(self):
+        fname = opj(self.rev_dir, '%08d' % self.rev)
+        f = file(fname, "rb")
+        data = f.read()
+        f.close()
+        data = data.decode(config.charset)
+        return data
+
+    def write(self, data, rev_dir, rev=None):
+        if rev is None:
+            rev = self.rev
+        data = markup_converter(data, self.renames)
+        fname = opj(rev_dir, '%08d' % rev)
+        data = data.encode(config.charset)
+        f = file(fname, "wb")
+        f.write(data)
+        f.close()
+
+    def copy(self, rev_dir, renames):
+        self.renames = renames
+        data = self.read()
+        self.write(data, rev_dir)
+
+
+class Attachment:
+    """ a single attachment """
+    def __init__(self, request, attach_dir, attfile):
+        self.request = request
+        self.path = opj(attach_dir, attfile)
+        self.name = attfile.decode('utf-8')
+
+    def copy(self, attach_dir):
+        """ copy attachment file from orig path to new destination """
+        attfile = self.name.encode('utf-8')
+        dest = opj(attach_dir, attfile)
+        copy_file(self.path, dest)
+
+
+class Page:
+    """ represents a page with all related data """
+    def __init__(self, request, pages_dir, qpagename):
+        self.request = request
+        self.name = wikiutil.unquoteWikiname(qpagename)
+        self.name_old = self.name # renaming: still original name when self.name has the new name
+        self.page_dir = opj(pages_dir, qpagename)
+        self.current = None # int current
+        self.editlog = None # dict (see read_editlog)
+        self.revlist = None # list of ints (page text revisions)
+        self.revisions = None # dict int: pagerev obj
+        self.attachments = None # dict of unicode fname: full path
+        self.renames = {} # info for renaming pages/attachments
+
+    def read(self):
+        """ read a page, including revisions, log, attachments from disk """
+        page_dir = self.page_dir
+        # read current file
+        current_fname = opj(page_dir, 'current')
+        if os.path.exists(current_fname):
+            current_file = file(current_fname, "r")
+            current_rev = current_file.read()
+            current_file.close()
+            self.current = int(current_rev)
+        # read edit-log
+        editlog_fname = opj(page_dir, 'edit-log')
+        if os.path.exists(editlog_fname):
+            self.editlog = EditLog(self.request, editlog_fname)
+        # read page revisions
+        rev_dir = opj(page_dir, 'revisions')
+        if os.path.exists(rev_dir):
+            revlist = listdir(rev_dir)
+            revlist = [int(rev) for rev in revlist]
+            revlist.sort()
+            self.revlist = revlist
+            self.revisions = {}
+            for rev in revlist:
+                self.revisions[rev] = PageRev(self.request, rev_dir, rev)
+        # read attachment filenames
+        attach_dir = opj(page_dir, 'attachments')
+        if os.path.exists(attach_dir):
+            self.attachments = {}
+            attlist = listdir(attach_dir)
+            for attfile in attlist:
+                a = Attachment(self.request, attach_dir, attfile)
+                self.attachments[a.name] = a
+
+    def write(self, pages_dir):
+        """ write a page, including revisions, log, attachments to disk """
+        if ('PAGE', self.name) in self.renames:
+            name_new = self.renames[('PAGE', self.name)]
+            if name_new != self.name:
+                print "Renaming page %r -> %r" % (self.name, name_new)
+                self.name_old = self.name
+                self.name = name_new
+        qpagename = wikiutil.quoteWikinameFS(self.name)
+        page_dir = opj(pages_dir, qpagename)
+        os.makedirs(page_dir)
+        # write current file
+        if self.current is not None:
+            current_fname = opj(page_dir, 'current')
+            current_file = file(current_fname, "w")
+            current_str = '%08d\n' % self.current
+            current_file.write(current_str)
+            current_file.close()
+        # copy edit-log
+        if self.editlog is not None:
+            editlog_fname = opj(page_dir, 'edit-log')
+            self.editlog.copy(editlog_fname, self.renames)
+        # copy page revisions
+        if self.revisions is not None:
+            rev_dir = opj(page_dir, 'revisions')
+            os.makedirs(rev_dir)
+            for rev in self.revlist:
+                self.revisions[rev].copy(rev_dir, self.renames)
+        # copy attachments
+        if self.attachments is not None:
+            attach_dir = opj(page_dir, 'attachments')
+            os.makedirs(attach_dir)
+            for fn, att in self.attachments.items():
+                # we have to check for renames here because we need the (old) pagename, too:
+                if ('FILE', self.name_old, fn) in self.renames:
+                    fn_new = self.renames[('FILE', self.name_old, fn)]
+                    if fn_new != fn:
+                        print "Renaming file %r %r -> %r" % (self.name_old, fn, fn_new)
+                        att.name = fn_new
+                att.copy(attach_dir)
+
+    def copy(self, pages_dir, renames):
+            self.renames = renames
+            self.read()
+            self.write(pages_dir)
+
+
+class User:
+    """ represents a user with all related data """
+    def __init__(self, request, users_dir, uid):
+        self.request = request
+        self.uid = uid
+        self.users_dir = users_dir
+        self.profile = None
+        self.bookmarks = None
+
+    def read(self):
+        """ read profile and bookmarks data from disk """
+        self.profile = {}
+        fname = opj(self.users_dir, self.uid)
+        # read user profile
+        f = codecs.open(fname, 'r', config.charset)
+        for line in f:
+            line = line.replace(u'\r', '').replace(u'\n', '')
+            if not line.strip() or line.startswith(u'#'): # skip empty or comment lines
+                continue
+            key, value = line.split(u'=', 1)
+            self.profile[key] = value
+        f.close()
+        # read bookmarks
+        self.bookmarks = {}
+        fname_pattern = opj(self.users_dir, "%s.*.bookmark" % self.uid)
+        for fname in glob.glob(fname_pattern):
+            f = file(fname, "r")
+            bookmark = f.read()
+            f.close()
+            wiki = fname.replace('.bookmark', '').replace(opj(self.users_dir, self.uid+'.'), '')
+            self.bookmarks[wiki] = int(bookmark)
+        # don't care about trail
+
+    def write(self, users_dir):
+        """ write profile and bookmarks data to disk """
+        fname = opj(users_dir, self.uid)
+        f = codecs.open(fname, 'w', config.charset)
+        for key, value in self.profile.items():
+            if key in (u'subscribed_pages', u'quicklinks'):
+                pages = value.split(u'\t')
+                for i in range(len(pages)):
+                    pagename = pages[i]
+                    try:
+                        interwiki, pagename = pagename.split(u':', 1)
+                    except:
+                        interwiki, pagename = u'Self', pagename
+                    if interwiki == u'Self' or interwiki == self.request.cfg.interwikiname:
+                        if ('PAGE', pagename) in self.renames:
+                            pagename = self.renames[('PAGE', pagename)]
+                            pages[i] = u'%s:%s' % (interwiki, pagename)
+                key += '[]' # we have lists here
+                value = u'\t'.join(pages)
+                f.write(u"%s=%s\n" % (key, value))
+            else:
+                f.write(u"%s=%s\n" % (key, value))
+        bookmark_entries = [u'%s:%s' % item for item in self.bookmarks.items()]
+        key = u"bookmarks{}"
+        value = u'\t'.join(bookmark_entries)
+        f.write(u"%s=%s\n" % (key, value))
+        f.close()
+        # don't care about trail
+
+    def copy(self, users_dir, renames):
+        self.renames = renames
+        self.read()
+        self.write(users_dir)
+
+
+class DataConverter(object):
+    def __init__(self, request, src_data_dir, dest_data_dir):
+        self.request = request
+        self.sdata = src_data_dir
+        self.ddata = dest_data_dir
+        self.pages = {}
+        self.users = {}
+        self.renames = {}
+        self.rename_fname1 = opj(self.sdata, 'rename1.txt')
+        self.rename_fname2 = opj(self.sdata, 'rename2.txt')
+
+    def pass1(self):
+        """ First create the rename list - the user has to review/edit it as
+            we can't decide about page/attachment names automatically.
+        """
+        self.read_src()
+        # pages
+        for pn, p in self.pages.items():
+            p.read()
+            if not p.revisions:
+                continue # we don't care for pages with no revisions (trash)
+            if "_" in pn:
+                # log all pagenames with underscores
+                self.renames[('PAGE', pn)] = None
+            if p.attachments is not None:
+                for fn in p.attachments:
+                    try:
+                        fn_str = fn.encode('ascii')
+                        log = False # pure ascii filenames are no problem
+                    except UnicodeEncodeError:
+                        log = True # this file maybe has a strange representation in wiki markup
+                    else:
+                        if ' ' in fn_str or '%' in fn_str: # files with blanks need quoting
+                            log = True
+                    if log:
+                        # log all strange attachment filenames
+                        fn_str = fn.encode('utf-8')
+                        self.renames[('FILE', pn, fn)] = None
+        self.save_renames()
+
+    def save_renames(self):
+        f = codecs.open(self.rename_fname1, 'w', 'utf-8')
+        for k in self.renames:
+            rtype, pn, fn = (k + (None, ))[:3]
+            if rtype == 'PAGE':
+                line = u"%s\t%s\t%s\r\n" % (rtype, pn, pn)
+            elif rtype == 'FILE':
+                line = u"%s\t%s\t%s\t%s\r\n" % (rtype, pn, fn, fn)
+            f.write(line)
+        f.close()
+
+    def load_renames(self):
+        f = codecs.open(self.rename_fname2, 'r', 'utf-8')
+        for line in f:
+            line = line.rstrip()
+            if not line:
+                continue
+            t = line.split(u'\t')
+            rtype, p1, p2, p3 = (t + [None]*3)[:4]
+            if rtype == u'PAGE':
+                self.renames[(str(rtype), p1)] = p2
+            elif rtype == u'FILE':
+                self.renames[(str(rtype), p1, p2)] = p3
+        f.close()
+
+    def pass2(self):
+        """ Second, read the (user edited) rename list and do the renamings everywhere. """
+        self.read_src()
+        self.load_renames()
+        self.write_dest()
+
+    def read_src(self):
+        # create Page objects in memory
+        pages_dir = opj(self.sdata, 'pages')
+        pagelist = listdir(pages_dir)
+        for qpagename in pagelist:
+            p = Page(self.request, pages_dir, qpagename)
+            self.pages[p.name] = p
+
+        # create User objects in memory
+        users_dir = opj(self.sdata, 'user')
+        userlist = listdir(users_dir)
+        userlist = [fn for fn in userlist if not fn.endswith(".trail") and not fn.endswith(".bookmark")]
+        for userid in userlist:
+            u = User(self.request, users_dir, userid)
+            self.users[u.uid] = u
+
+        # create log objects in memory
+        self.editlog = EditLog(self.request, opj(self.sdata, 'edit-log'))
+        self.eventlog = EventLog(self.request, opj(self.sdata, 'event-log'))
+
+    def write_dest(self):
+        self.init_dest()
+        # copy pages
+        pages_dir = opj(self.ddata, 'pages')
+        for page in self.pages.values():
+            page.copy(pages_dir, self.renames)
+
+        # copy users
+        users_dir = opj(self.ddata, 'user')
+        for user in self.users.values():
+            user.copy(users_dir, self.renames)
+
+        # copy logs
+        self.editlog.copy(opj(self.ddata, 'edit-log'), self.renames)
+        self.eventlog.copy(opj(self.ddata, 'event-log'), self.renames)
+
+    def init_dest(self):
+        try:
+            os.makedirs(self.ddata)
+        except:
+            pass
+        os.makedirs(opj(self.ddata, 'pages'))
+        os.makedirs(opj(self.ddata, 'user'))
+        copy_dir(opj(self.sdata, 'plugin'), opj(self.ddata, 'plugin'))
+        copy_file(opj(self.sdata, 'intermap.txt'), opj(self.ddata, 'intermap.txt'))
+
+
--- a/MoinMoin/support/__init__.py	Thu Jul 26 04:39:03 2007 +0200
+++ b/MoinMoin/support/__init__.py	Fri Aug 03 17:25:27 2007 +0200
@@ -10,16 +10,3 @@
     @copyright: 2001-2004 Juergen Hermann <jh@web.de>
     @license: GNU GPL, see COPYING for details.
 """
-
-try:
-    sorted = sorted
-except NameError:
-    def sorted(l, *args, **kw):
-        l = l[:]
-        # py2.3 is a bit different
-        if 'cmp' in kw:
-            args = (kw['cmp'], )
-
-        l.sort(*args)
-        return l
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/support/python_compatibility.py	Fri Aug 03 17:25:27 2007 +0200
@@ -0,0 +1,54 @@
+"""
+    MoinMoin - Support Package
+
+    Stuff for compatibility with older pytohn versions
+
+    @copyright: 2007 Heinrich Wendel <heinrich.wendel@gmail.com>
+    @license: GNU GPL, see COPYING for details.
+"""
+
+"""
+This is a feature from python 2.4, needed for compatibility with python 2.3,
+although it may not be 100% compatible.
+"""
+try:
+    sorted = sorted
+except NameError:
+    def sorted(l, *args, **kw):
+        if type(l) == dict:
+            l = l.keys()
+        l = l[:]
+        # py2.3 is a bit different
+        if 'cmp' in kw:
+            args = (kw['cmp'], )
+
+        l.sort(*args)
+        return l
+
+"""
+This is a feature from python 2.4, needed for compatibility with python 2.3,
+although it may not be 100% compatible.
+"""
+try:
+    set = set
+except NameError:
+    from sets import Set as set
+
+"""
+This is a feature from python 2.5, needed for compatibility with python 2.3 and 2.4.
+"""
+try:
+    from functools import partial
+except (NameError, ImportError):
+    class partial(object):
+        def __init__(*args, **kw):
+            self = args[0]
+            self.fn, self.args, self.kw = (args[1], args[2:], kw)
+
+        def __call__(self, *args, **kw):
+            if kw and self.kw:
+                d = self.kw.copy()
+                d.update(kw)
+            else:
+                d = kw or self.kw
+            return self.fn(*(self.args + args), **d)
--- a/MoinMoin/util/thread_monitor.py	Thu Jul 26 04:39:03 2007 +0200
+++ b/MoinMoin/util/thread_monitor.py	Fri Aug 03 17:25:27 2007 +0200
@@ -23,10 +23,8 @@
 from time import sleep
 from StringIO import StringIO
 
-try:
-    set
-except:
-    from sets import Set as set
+from MoinMoin.support.python_compatibility import set
+
 
 class AbstractMonitor(object):
     def activate_hook(self):
--- a/MoinMoin/wikiutil.py	Thu Jul 26 04:39:03 2007 +0200
+++ b/MoinMoin/wikiutil.py	Fri Aug 03 17:25:27 2007 +0200
@@ -2100,8 +2100,12 @@
     return lines
 
 def anchor_name_from_text(text):
+    '''
+    Generate an anchor name from the given text
+    This function generates valid HTML IDs.
+    '''
     quoted = urllib.quote_plus(text.encode('utf-7'))
-    res = quoted.replace('%', '.').replace('+', '_')
+    res = quoted.replace('%', '.').replace('+', '').replace('_', '')
     if not res[:1].isalpha():
         return 'A%s' % res
     return res
--- a/MoinMoin/xmlrpc/__init__.py	Thu Jul 26 04:39:03 2007 +0200
+++ b/MoinMoin/xmlrpc/__init__.py	Fri Aug 03 17:25:27 2007 +0200
@@ -640,7 +640,7 @@
             or the password were wrong. """
 
         if randint(0, 99) == 0:
-            _cleanup_stale_tokens(self.request)
+            self._cleanup_stale_tokens(self.request)
 
         u = self.request.handle_auth(None, username=username,
                                      password=password, login=True)
@@ -663,7 +663,7 @@
             return ""
 
         if randint(0, 99) == 0:
-            _cleanup_stale_tokens(self.request)
+            self._cleanup_stale_tokens(self.request)
 
         u = self.request.handle_jid_auth(jid)
 
--- a/MoinMoin/xmlrpc/_tests/test_xmlrpc.py	Thu Jul 26 04:39:03 2007 +0200
+++ b/MoinMoin/xmlrpc/_tests/test_xmlrpc.py	Fri Aug 03 17:25:27 2007 +0200
@@ -40,6 +40,10 @@
     assert type(token) == str or type(token) == unicode
     assert len(token) == 32
 
+def test_getAuthToken(request):
+    """ Tests if getAuthToken passes without crashing """
+    xmlrpc = XmlRpcBase(request)
+    assert xmlrpc.xmlrpc_getAuthToken("Foo", "bar") == ""
 
 coverage_modules = ['MoinMoin.xmlrpc']
 
--- a/docs/CHANGES	Thu Jul 26 04:39:03 2007 +0200
+++ b/docs/CHANGES	Fri Aug 03 17:25:27 2007 +0200
@@ -28,6 +28,12 @@
     and improving it and after having made a backup with some other, proven
     method. USE BOTH ON YOUR OWN RISK!
 
+Version 1.5.current:
+  Bugfixes:
+    * AttachFile overwrite mode (introduced in 1.5.7) did not check delete
+      rights, but only write rights. Now it checks that the user has write AND
+      delete rights before overwriting a file.
+
 Version 1.7.current:
     This is the active development branch. All changes get done here and
     critical stuff gets committed with -m "... (backport needed)" and then
--- a/setup.py	Thu Jul 26 04:39:03 2007 +0200
+++ b/setup.py	Fri Aug 03 17:25:27 2007 +0200
@@ -220,6 +220,7 @@
         'MoinMoin.auth',
         'MoinMoin.config',
         'MoinMoin.converter',
+        'MoinMoin.events',
         'MoinMoin.filter',
         'MoinMoin.formatter',
         'MoinMoin.i18n',